<?php
/**
 * KooKoa Folders — Migration / importer AJAX handlers.
 *
 * Run a migration importer (FileBird, Premio Folders, Real Media Library),
 * dismiss/reset importer cards, hide the migrate section, dismiss the sidebar
 * hint, and deactivate a source plugin after import. Registered and dispatched
 * by KookoaWPF_Ajax; this collaborator holds the handler bodies and the source
 * plugin whitelist they share.
 *
 * @package KookoaWPF
 * @license GPL-2.0-or-later
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * AJAX handlers for the "Migrate from other plugins" feature.
 */
class KookoaWPF_Ajax_Migration extends KookoaWPF_Ajax_Handler {

	/**
	 * Singleton instance.
	 *
	 * @var self|null
	 */
	private static $instance = null;

	/**
	 * Return the singleton instance.
	 *
	 * @return self
	 */
	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor. The action map lives on the KookoaWPF_Ajax dispatcher, so
	 * this collaborator registers nothing of its own.
	 */
	private function __construct() {
		// Registration is owned by KookoaWPF_Ajax (the dispatch surface).
	}

	/**
	 * Execute a migration importer.
	 *
	 * Reads importer_id + mode (merge/replace) from POST, runs the
	 * matching importer, and returns the result summary plus an optional
	 * deactivate hint when a known source plugin is still active.
	 */
	public function run_importer(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_admin_request();

		$importer_id = isset( $_POST['importer_id'] ) ? sanitize_key( wp_unslash( $_POST['importer_id'] ) ) : '';
		$mode        = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : 'merge';
		if ( 'replace' !== $mode ) {
			$mode = 'merge';
		}

		$importer = KookoaWPF_Importer_Registry::instance()->get( $importer_id );
		if ( ! $importer ) {
			wp_send_json_error( array( 'message' => __( 'Unknown importer.', 'kookoa-folders' ) ) );
		}

		if ( $importer->requires_pro() && ! function_exists( 'kookoawpf_pro_bootstrap' ) ) {
			wp_send_json_error( array( 'message' => __( 'This importer requires the PRO version.', 'kookoa-folders' ) ) );
		}

		// Per-importer lock against concurrent runs. add_option() is
		// atomic in WordPress (INSERT IGNORE under the hood), so two
		// admins clicking "Import" on the same source within the same
		// request window will see exactly one winner — the other gets
		// a clear "already running" message instead of interleaving
		// folder creation / deletion. The lock is per importer_id, so
		// unrelated importers can still run in parallel.
		$lock_key    = 'kookoawpf_importer_lock_' . str_replace( '-', '_', $importer_id );
		$stale_after = 15 * MINUTE_IN_SECONDS;
		$existing    = (int) get_option( $lock_key, 0 );
		if ( $existing > 0 && ( time() - $existing ) > $stale_after ) {
			// Previous run almost certainly died on fatal/timeout —
			// clear the stale lock so the user can retry.
			delete_option( $lock_key );
		}
		if ( ! add_option( $lock_key, time(), '', false ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'This import is already running. Please wait a moment and refresh the page.', 'kookoa-folders' ),
				)
			);
		}

		// Server-side idempotency: refuse to re-run an importer that has
		// already completed once. The UI hides imported cards, but a
		// direct AJAX replay (curl, devtools) would otherwise duplicate
		// folders. "Reset import detection" re-enables the importer.
		if ( $importer->is_imported() ) {
			delete_option( $lock_key );
			wp_send_json_error(
				array(
					'message' => __( 'This source has already been imported. Use "Reset import detection" if you need to run it again.', 'kookoa-folders' ),
				)
			);
		}

		// Block the import early when the target post type has not been
		// enabled in Settings → General. Without this, every wp_insert_term
		// call inside the loop would silently fail with "invalid taxonomy",
		// surfacing to the user only as a vague "see browser console" hint.
		$target_pt  = $importer->get_target_post_type();
		$target_tax = KookoaWPF_Folders::get_taxonomy( $target_pt );
		if ( ! taxonomy_exists( $target_tax ) ) {
			delete_option( $lock_key );
			$pt_obj   = get_post_type_object( $target_pt );
			$pt_label = ( $pt_obj && isset( $pt_obj->labels->name ) ) ? $pt_obj->labels->name : ucfirst( $target_pt );
			wp_send_json_error(
				array(
					'message' => sprintf(
						/* translators: %s: post type plural label, e.g. "Posts" or "Pages" */
						__( 'To import %1$s folders, first enable %1$s in KooKoa Folders → Settings → General → "Enable folders for", then come back here.', 'kookoa-folders' ),
						$pt_label
					),
				)
			);
		}

		$result = $importer->run( $mode );
		if ( is_wp_error( $result ) ) {
			delete_option( $lock_key );
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		// First successful import = the sidebar hint has done its job
		// (the user knows the feature exists and has used it). Dismiss
		// the hint permanently for this user so it does not reappear
		// even after a "Reset import detection" — Reset is meant to
		// re-expose the cards in Settings, not to nag with the
		// awareness banner that has already converted.
		$current_user_id = get_current_user_id();
		if ( $current_user_id ) {
			update_user_meta( $current_user_id, 'kookoawpf_sidebar_migrate_hint_dismissed', '1' );
		}

		// Surface a deactivate hint for known source plugins still active.
		$deactivate = null;
		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		foreach ( self::source_plugin_whitelist() as $slug => $info ) {
			if ( 0 === strpos( $importer_id, $info['id_prefix'] ) && is_plugin_active( $slug ) ) {
				$deactivate = array(
					'slug'  => $slug,
					'label' => $info['label'],
				);
				break;
			}
		}

		delete_option( $lock_key );
		wp_send_json_success(
			array(
				'summary'    => $result,
				'deactivate' => $deactivate,
			)
		);
	}

	/**
	 * Dismiss an importer card ("Don't show again").
	 */
	public function dismiss_importer(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_admin_request();

		$importer_id = isset( $_POST['importer_id'] ) ? sanitize_key( wp_unslash( $_POST['importer_id'] ) ) : '';
		$importer    = KookoaWPF_Importer_Registry::instance()->get( $importer_id );
		if ( ! $importer ) {
			wp_send_json_error( array( 'message' => __( 'Unknown importer.', 'kookoa-folders' ) ) );
		}
		$importer->dismiss();
		wp_send_json_success();
	}

	/**
	 * Clear imported/dismissed flags on every registered importer so
	 * the "Migrate from other plugins" section repopulates fully.
	 */
	public function reset_importers(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_admin_request();

		KookoaWPF_Importer_Registry::instance()->reset_all();
		wp_send_json_success();
	}

	/**
	 * Permanently hide the Migrate from Other Plugins section.
	 *
	 * Idempotent and irreversible from the UI — to undo, the user must
	 * use "Reset import detection" (which clears every importer flag
	 * including this one) or edit the option directly in the database.
	 */
	public function hide_migrate_section(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_admin_request();

		KookoaWPF_Importer_Registry::instance()->hide_section_forever();
		wp_send_json_success();
	}

	/**
	 * Permanently dismiss the migration hint shown in the Media sidebar.
	 *
	 * Per-user flag. Independent of the Settings → Import & Export
	 * section: dismissing the sidebar hint does NOT hide the importer
	 * cards in Settings, so the user can still reach the importer when
	 * they decide to actually import.
	 */
	public function dismiss_sidebar_migrate_hint(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_request();

		$user_id = get_current_user_id();
		if ( ! $user_id ) {
			wp_send_json_error( array( 'message' => __( 'No user.', 'kookoa-folders' ) ) );
		}
		update_user_meta( $user_id, 'kookoawpf_sidebar_migrate_hint_dismissed', '1' );
		wp_send_json_success();
	}

	/**
	 * Deactivate a source plugin after a successful migration. Locked to
	 * the whitelist defined by source_plugin_whitelist() so this endpoint
	 * is not a generic "deactivate anything" tool.
	 */
	public function deactivate_source_plugin(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_admin_request();

		if ( ! current_user_can( 'deactivate_plugins' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'kookoa-folders' ) ) );
		}

		$slug      = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
		$whitelist = self::source_plugin_whitelist();
		if ( ! isset( $whitelist[ $slug ] ) ) {
			wp_send_json_error( array( 'message' => __( 'Plugin not in migration whitelist.', 'kookoa-folders' ) ) );
		}

		if ( ! function_exists( 'deactivate_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		deactivate_plugins( $slug );
		wp_send_json_success();
	}

	/**
	 * Whitelist of source plugin slugs the migration UI is allowed to
	 * deactivate after a successful import. Keep tight even though
	 * manage_options would already permit deactivation in general — the
	 * point is to make the AJAX endpoint single-purpose and unsurprising.
	 *
	 * @return array<string, array{label: string, id_prefix: string}>
	 *   Map keyed by plugin slug, value is label + importer id_prefix
	 *   used to match which importer (e.g. "filebird-media") belongs to
	 *   which plugin slug.
	 */
	private static function source_plugin_whitelist(): array {
		return array(
			'filebird/filebird.php'             => array(
				'label'     => 'FileBird',
				'id_prefix' => 'filebird-',
			),
			'folders/folders.php'               => array(
				'label'     => 'Folders (Premio)',
				'id_prefix' => 'premio-',
			),
			'real-media-library-lite/index.php' => array(
				'label'     => 'Real Media Library',
				'id_prefix' => 'rml-',
			),
		);
	}
}
