<?php
/**
 * Duplicate Menu Module
 *
 * Adds a Duplicate button to the nav-menus admin page to clone menus with all
 * items, hierarchy, and meta preserved.
 *
 * @package DuplicateMenu
 * @since 1.0.0
 */

namespace DuplicateMenu;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\Contracts\DataLocationsInterface;

class Module extends AbstractModule implements DataLocationsInterface {

	private const MODULE_ID = 'duplicate-menu';
	private const MODULE_VERSION = '1.0.1';
	private const ACTION = 'wpps_duplicate_menu';

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Duplicate Menu', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Duplicate a menu and all its items in one click from the Menus screen.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin' ];
	}

	protected function registerHooks(): void {
		add_action( 'admin_footer', [ $this, 'addDuplicateButton' ], 10 );
		add_action( 'admin_action_' . self::ACTION, [ $this, 'handleDuplicate' ] );
		add_action( 'admin_notices', [ $this, 'showDuplicateNotice' ] );
	}

	public function addDuplicateButton(): void {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen || $screen->id !== 'nav-menus' ) {
			return;
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			return;
		}

		$menu_id = $this->getCurrentMenuId();
		if ( $menu_id <= 0 ) {
			return;
		}

		$duplicate_url = wp_nonce_url(
			admin_url( 'admin.php?action=' . self::ACTION . '&menu=' . $menu_id ),
			'wpps_duplicate_menu_' . $menu_id
		);
		?>
		<style id="wpps-duplicate-menu-btn-styles">
		.wpps-duplicate-menu-btn.button {
			display: inline-flex !important;
			align-items: center !important;
			justify-content: center;
			gap: 6px;
			vertical-align: middle !important;
		}
		.wpps-duplicate-menu-btn.button .dashicons {
			display: inline-flex;
			align-items: center;
			justify-content: center;
			line-height: 1 !important;
			width: 20px;
			height: 20px;
		}
		</style>
		<script type="text/javascript">
		(function($) {
			$(function() {
				var $action = $('#update-nav-menu .publishing-action');
				if (!$action.length) {
					return;
				}

				var $wrapper = $('<span class="wpps-duplicate-menu-wrap" style="display:inline-flex;align-items:center;margin-left:10px;"></span>');
				var $btn = $('<a class="button wpps-duplicate-menu-btn" href="<?php echo esc_url( $duplicate_url ); ?>" title="<?php esc_attr_e( 'Duplicate this menu', 'wp-powersuite' ); ?>"><span class="dashicons dashicons-admin-page"></span><?php esc_html_e( 'Duplicate Menu', 'wp-powersuite' ); ?></a>');
				var $spinner = $('<span class="spinner" style="float:none;margin:0 0 0 6px;visibility:hidden;"></span>');

				$btn.on('click', function() {
					$spinner.css('visibility', 'visible');
					$btn.css('pointer-events', 'none').css('opacity', '0.7');
				});

				$wrapper.append($btn).append($spinner);
				$action.append($wrapper);
			});
		})(jQuery);
		</script>
		<?php
	}

	public function handleDuplicate(): void {
		$menu_id = isset( $_GET['menu'] ) ? absint( $_GET['menu'] ) : 0;
		if ( $menu_id <= 0 ) {
			wp_die( esc_html__( 'No menu to duplicate.', 'wp-powersuite' ), '', [ 'response' => 400 ] );
		}

		check_admin_referer( 'wpps_duplicate_menu_' . $menu_id );

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to duplicate menus.', 'wp-powersuite' ), '', [ 'response' => 403 ] );
		}

		$source = wp_get_nav_menu_object( $menu_id );
		if ( ! $source || is_wp_error( $source ) ) {
			wp_die( esc_html__( 'Menu not found.', 'wp-powersuite' ), '', [ 'response' => 404 ] );
		}

		$result = $this->duplicateMenu( $menu_id, $source->name );

		if ( is_wp_error( $result ) ) {
			wp_die( esc_html( $result->get_error_message() ), '', [ 'response' => 500 ] );
		}

		$query_args = [
			'action'     => 'edit',
			'menu'       => $result['menu_id'],
			'duplicated' => '1',
			'menu_name'  => rawurlencode( $result['menu_name'] ),
		];

		if ( ! empty( $result['failed_count'] ) ) {
			$query_args['duplicate_partial'] = '1';
			$query_args['duplicate_failed']  = (string) $result['failed_count'];
			$query_args['duplicate_total']   = (string) $result['total_count'];
		}

		wp_safe_redirect( add_query_arg( $query_args, admin_url( 'nav-menus.php' ) ) );
		exit;
	}

	public function showDuplicateNotice(): void {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen || $screen->id !== 'nav-menus' ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only notice from redirect
		if ( empty( $_GET['duplicated'] ) || $_GET['duplicated'] !== '1' ) {
			return;
		}

		$menu_name = '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only
		if ( ! empty( $_GET['menu_name'] ) ) {
			$menu_name = sanitize_text_field( wp_unslash( urldecode( (string) $_GET['menu_name'] ) ) );
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only
		$failed_count = isset( $_GET['duplicate_failed'] ) ? absint( $_GET['duplicate_failed'] ) : 0;
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only
		$total_count = isset( $_GET['duplicate_total'] ) ? absint( $_GET['duplicate_total'] ) : 0;
		$is_partial  = ! empty( $_GET['duplicate_partial'] ) && $failed_count > 0 && $total_count > 0;

		if ( $is_partial ) {
			?>
			<div class="notice notice-warning is-dismissible">
				<p>
					<?php
					if ( $menu_name !== '' ) {
						/* translators: 1: duplicated menu name, 2: number of items that failed to copy, 3: total source items */
						printf(
							esc_html__( 'Menu "%1$s" was created, but %2$d of %3$d items could not be copied. Review the menu and add any missing links manually.', 'wp-powersuite' ),
							esc_html( $menu_name ),
							$failed_count,
							$total_count
						);
					} else {
						/* translators: 1: number of items that failed to copy, 2: total source items */
						printf(
							esc_html__( 'The menu was created, but %1$d of %2$d items could not be copied. Review the menu and add any missing links manually.', 'wp-powersuite' ),
							$failed_count,
							$total_count
						);
					}
					?>
				</p>
			</div>
			<?php
			return;
		}

		?>
		<div class="notice notice-success is-dismissible">
			<p>
				<?php
				if ( $menu_name !== '' ) {
					/* translators: %s: duplicated menu name */
					printf( esc_html__( 'Menu duplicated successfully. You are now editing "%s".', 'wp-powersuite' ), esc_html( $menu_name ) );
				} else {
					esc_html_e( 'Menu duplicated successfully.', 'wp-powersuite' );
				}
				?>
			</p>
		</div>
		<?php
	}

	/**
	 * Duplicate a navigation menu and its items.
	 *
	 * @param int    $source_id Source menu term ID.
	 * @param string $base_name Base name for the new menu.
	 * @return array{menu_id: int, menu_name: string, failed_count: int, total_count: int}|\WP_Error
	 */
	private function duplicateMenu( int $source_id, string $base_name ) {
		$base_name = preg_replace( '/\s*\(Copy(?:\s+\d+)?\)\s*$/', '', trim( $base_name ) );
		if ( $base_name === '' ) {
			$base_name = __( 'Menu', 'wp-powersuite' );
		}

		$new_name = $this->getUniqueMenuName( $base_name );

		$new_menu_id = wp_create_nav_menu( $new_name );
		if ( is_wp_error( $new_menu_id ) ) {
			return $new_menu_id;
		}

		$source_items = wp_get_nav_menu_items( $source_id );
		if ( ! is_array( $source_items ) || empty( $source_items ) ) {
			return [
				'menu_id'       => (int) $new_menu_id,
				'menu_name'     => $new_name,
				'failed_count'  => 0,
				'total_count'   => 0,
			];
		}

		$id_map       = [];
		$order        = 0;
		$failed_count = 0;
		$total_count  = count( $source_items );

		foreach ( $source_items as $item ) {
			$new_item_id = $this->duplicateMenuItem( $item, (int) $new_menu_id, $order );
			if ( $new_item_id > 0 ) {
				$id_map[ (int) $item->db_id ] = $new_item_id;
			} else {
				++$failed_count;
			}
			++$order;
		}

		// Second pass: update parent relationships (in case children were processed before parents).
		foreach ( $source_items as $item ) {
			$old_id = (int) $item->db_id;
			$new_id = $id_map[ $old_id ] ?? 0;
			if ( $new_id <= 0 ) {
				continue;
			}
			$parent_old = (int) $item->menu_item_parent;
			$parent_new = $parent_old > 0 ? ( $id_map[ $parent_old ] ?? 0 ) : 0;
			update_post_meta( $new_id, '_menu_item_menu_item_parent', (string) $parent_new );
		}

		return [
			'menu_id'      => (int) $new_menu_id,
			'menu_name'    => $new_name,
			'failed_count' => $failed_count,
			'total_count'  => $total_count,
		];
	}

	/**
	 * Create a unique menu name, appending (Copy) or (Copy N) as needed.
	 */
	private function getUniqueMenuName( string $base_name ): string {
		/* translators: %s: menu name */
		$name = sprintf( __( '%s (Copy)', 'wp-powersuite' ), $base_name );
		$n    = 1;

		while ( wp_get_nav_menu_object( $name ) && $n <= 10 ) {
			/* translators: 1: menu name, 2: copy number */
			$name = sprintf( __( '%1$s (Copy %2$d)', 'wp-powersuite' ), $base_name, $n );
			++$n;
		}

		if ( wp_get_nav_menu_object( $name ) ) {
			$name = $base_name . ' ' . gmdate( 'Y-m-d-H-i-s' );
		}

		return $name;
	}

	/**
	 * Duplicate a single menu item.
	 *
	 * @param object $item    Source menu item from wp_get_nav_menu_items.
	 * @param int    $menu_id New menu term ID.
	 * @param int    $order   Menu order.
	 * @return int New post ID, or 0 on failure.
	 */
	private function duplicateMenuItem( object $item, int $menu_id, int $order ): int {
		global $wpdb;

		$args = [
			'post_title'     => $item->title ?? '',
			'post_content'   => $item->description ?? '',
			'post_excerpt'   => $item->attr_title ?? '',
			'post_status'    => $item->post_status ?? 'publish',
			'post_type'      => 'nav_menu_item',
			'menu_order'     => $order,
			'comment_status' => 'closed',
			'ping_status'    => 'closed',
		];

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for meta copy
		$meta_rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d",
				$item->ID
			)
		);

		$meta_input = [];
		$skip_keys  = [ '_menu_item_menu_item_parent' ];

		foreach ( (array) $meta_rows as $row ) {
			if ( in_array( $row->meta_key, $skip_keys, true ) ) {
				continue;
			}
			$meta_input[ $row->meta_key ] = maybe_unserialize( $row->meta_value );
		}

		$args['meta_input'] = $meta_input;

		$new_id = wp_insert_post( $args, true );
		if ( is_wp_error( $new_id ) || $new_id <= 0 ) {
			return 0;
		}

		wp_set_object_terms( $new_id, [ $menu_id ], 'nav_menu' );

		return (int) $new_id;
	}

	private function getCurrentMenuId(): int {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only for UI
		if ( ! empty( $_GET['menu'] ) ) {
			return absint( $_GET['menu'] );
		}
		$recent = (int) get_user_option( 'nav_menu_recently_edited' );
		return $recent > 0 ? $recent : 0;
	}

	public function getDefaultSettings(): array {
		return [];
	}

	public function getDataLocations(): array {
		return [
			'option_store_keys' => [],
		];
	}
}
