<?php
/**
 * WC Affiliate Migration Handler
 *
 * This file handles the migration from legacy WC Affiliate versions to the new modern version.
 * It detects existing installations, shows migration notices, and manages the transition process.
 *
 * Key Features:
 * - Detects legacy plugin installations by checking for old option keys
 * - Shows admin notices to guide users through the migration process
 * - Handles secure UI switching via GET parameters with nonce verification
 * - Loads legacy plugin for backward compatibility during migration
 * - Prevents loading new UI components until migration is confirmed
 * - Suppresses migration notices when WC Affiliate Pro < 3.0.0 is active
 *
 * @since       3.0.0
 * @subpackage  Migration
 * @package     WC_Affiliate
 * @author      Codexpert
 * @license     GPL-2.0-or-later
 */

/**
 * Check if legacy WC Affiliate options exist in the database.
 *
 * Determines whether the user has a legacy version of WC Affiliate installed
 * by checking for the presence of old option keys in the database.
 *
 * @since 3.0.0
 *
 * @return bool True if any legacy options are found, false otherwise.
 */
function wc_affiliate_has_legacy_options(): bool {
	$legacy_options = array(
		'wc_affiliate_basic',
		'wc_affiliate_advanced',
		'wc_affiliate_mlc',
		'wc_affiliate_email',
		'wc_affiliate_tools',
	);

	foreach ( $legacy_options as $option ) {
		$value = get_option( $option, null );
		if ( ! empty( $value ) ) {
			return true;
		}
	}

	return false;
}

/**
 * Resolve and persist the plugin user status (existing or fresh).
 *
 * Writes the user status option only once — on first load when the option
 * does not yet exist — to avoid redundant database writes on every request.
 * Subsequent calls read the cached option value.
 *
 * @since 3.0.0
 *
 * @return string Either 'existing' or 'fresh'.
 */
function wc_affiliate_resolve_user_status(): string {
	$status = get_option( 'wc_affiliate_plugin_user_status', '' );

	if ( '' === $status ) {
		$status = wc_affiliate_has_legacy_options() ? 'existing' : 'fresh';
		update_option( 'wc_affiliate_plugin_user_status', $status, false );
	}

	return $status;
}

/**
 * Determine whether the legacy migration gate is active.
 *
 * Centralises the repeated three-option check that was previously duplicated
 * across both wc-affiliate-migrate.php and wc-affiliate.php. The filter
 * wc_affiliate_legacy_migration_notice_show is applied exactly once per
 * request from this single source of truth.
 *
 * @since 3.0.0
 *
 * @return bool True if legacy mode should be enforced (new UI blocked).
 */
function wc_affiliate_is_legacy_mode_active(): bool {
	$user_status      = wc_affiliate_resolve_user_status();
	$migration_status = get_option( 'wc_affiliate_plugin_migration_status', 'pending' );

	/**
	 * Filter whether the legacy migration notice and gate should be active.
	 *
	 * Returning false from this filter will bypass the legacy gate entirely,
	 * loading the new UI regardless of migration status.
	 *
	 * @since 3.0.0
	 *
	 * @param bool $enforce Whether to enforce the legacy gate. Default true.
	 */
	$should_enforce = (bool) apply_filters( 'wc_affiliate_legacy_migration_notice_show', true );

	return 'existing' === $user_status && 'confirmed' !== $migration_status && $should_enforce;
}

/**
 * Check if the migration notice has been dismissed by the user.
 *
 * @since 3.0.0
 *
 * @return bool True if the notice has been dismissed, false otherwise.
 */
function wc_affiliate_is_notice_cleared(): bool {
	return (bool) get_option( 'wc_affiliate_migration_notice_dismissed', false );
}

/**
 * Check if migration notice should be displayed on the current screen.
 *
 * Determines whether the migration notice should be shown by verifying:
 * - The current screen is one of the registered WC Affiliate admin pages.
 * - The notice has not been dismissed by the user.
 * - The legacy migration gate is active (wc_affiliate_is_legacy_mode_active).
 *
 * Note: get_current_screen() is only available after admin-header.php loads.
 * This function is intentionally called from admin_notices and
 * admin_enqueue_scripts hooks where screen context is guaranteed to exist.
 * The early return on missing get_current_screen() is a deliberate guard,
 * not an error condition.
 *
 * @since 3.0.0
 *
 * @return bool True if the notice should be displayed, false otherwise.
 */
function wc_affiliate_should_show_migration_notice(): bool {
	if ( ! function_exists( 'get_current_screen' ) ) {
		return false;
	}

	$current_screen = get_current_screen();
	if ( ! $current_screen instanceof WP_Screen ) {
		return false;
	}

	/**
	 * Filter the list of admin screen IDs on which the migration notice appears.
	 *
	 * @since 3.0.0
	 *
	 * @param string[] $target_screens Array of WordPress screen IDs.
	 */
	$target_screens = apply_filters(
		'wc_affiliate_target_screens',
		array(
			'toplevel_page_wc-affiliate',
			'wc-affiliate_page_affiliates',
			'wc-affiliate_page_visits',
			'wc-affiliate_page_referrals',
			'wc-affiliate_page_payables',
			'wc-affiliate_page_transactions',
			'edit-banner',
			'wc-affiliate_page_wc-affiliate-settings',
		)
	);

	return in_array( $current_screen->id, $target_screens, true ) && ! wc_affiliate_is_notice_cleared();
}

/**
 * Show admin notice for legacy version migration and new UI switch.
 *
 * Renders a contextual admin notice based on the current migration status:
 * - Confirmed users see a rollback option.
 * - Unconfirmed existing users see a prompt to switch to the new UI.
 *
 * The notice respects the wc_affiliate_legacy_migration_notice_show filter
 * via wc_affiliate_should_show_migration_notice(), ensuring consistent
 * gating from a single source of truth.
 *
 * @since 3.0.0
 *
 * @return void
 */
function wc_affiliate_legacy_migration_notice(): void {
	if ( ! wc_affiliate_is_compatible_with_pro() ) {
		return;
	}

	if ( ! current_user_can( 'manage_options' ) || ! wc_affiliate_should_show_migration_notice() ) {
		return;
	}

	if ( 'existing' !== wc_affiliate_resolve_user_status() ) {
		return;
	}

	$migration_status = get_option( 'wc_affiliate_plugin_migration_status', 'pending' );

	if ( 'confirmed' === $migration_status ) {
		?>
		<div class="notice notice-success is-dismissible wc-affiliate-migrate">
			<p>
				<strong><?php esc_html_e( 'WC Affiliate: Using New UI', 'wc-affiliate' ); ?></strong>
			</p>
			<p>
				<?php esc_html_e( 'You\'re currently using the new WC Affiliate interface. If you need to switch back to the legacy version, you can rollback the migration.', 'wc-affiliate' ); ?>
			</p>
			<p>
				<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=wc-affiliate&wc_affiliate_migrate=rollback' ), 'wc_affiliate_migrate_nonce' ) ); ?>" class="button button-secondary">
					<?php esc_html_e( 'Rollback to Legacy UI', 'wc-affiliate' ); ?>
				</a>
			</p>
		</div>
		<?php
	} else {
		?>
		<div class="notice notice-info is-dismissible wc-affiliate-migrate">
			<p>
				<strong><?php esc_html_e( 'WC Affiliate: New UI Available!', 'wc-affiliate' ); ?></strong>
			</p>
			<p>
				<?php esc_html_e( 'We\'ve upgraded WC Affiliate with a modern, intuitive interface. Switch to the new UI for an enhanced experience.', 'wc-affiliate' ); ?>
			</p>
			<p>
				<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=wc-affiliate&wc_affiliate_migrate=new-ui' ), 'wc_affiliate_migrate_nonce' ) ); ?>" class="button button-primary">
					<?php esc_html_e( 'Switch to New UI', 'wc-affiliate' ); ?>
				</a>
				<a href="https://wcaffiliate.com/docs/admin/migration-guide" target="_blank" class="button">
					<?php esc_html_e( 'View Migration Guide', 'wc-affiliate' ); ?>
				</a>
			</p>
		</div>
		<?php
	}
}

/**
 * Handle migration confirmation and rollback via GET parameter.
 *
 * Processes the wc_affiliate_migrate GET parameter to either confirm the
 * migration to the new UI or rollback to the legacy UI. Nonce verification
 * is enforced before any option is updated.
 *
 * @since 3.0.0
 *
 * @return void
 */
function wc_affiliate_legacy_migration_handle_notice_params(): void {
	if (
			! current_user_can( 'manage_options' )
			|| wc_affiliate_is_notice_cleared()
			|| ! wc_affiliate_is_compatible_with_pro()
			|| ! isset( $_GET['wc_affiliate_migrate'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	) {
		return;
	}

	check_admin_referer( 'wc_affiliate_migrate_nonce' );

	$action = sanitize_key( $_GET['wc_affiliate_migrate'] );

	if ( 'new-ui' === $action ) {
		update_option( 'wc_affiliate_plugin_migration_status', 'confirmed', false );
	} elseif ( 'rollback' === $action ) {
		update_option( 'wc_affiliate_plugin_migration_status', 'pending', false );
	}

	wp_safe_redirect( admin_url( 'admin.php?page=wc-affiliate' ) );
	exit;
}

/**
 * Register migration-related REST API routes for the WC Affiliate plugin.
 *
 * Registers the notices/dismiss endpoint used by the admin JS to persist
 * notice dismissals via the REST API. The permission callback enforces
 * manage_options capability at the REST layer — not only at registration time.
 *
 * @since 3.0.0
 *
 * @return void
 */
function wc_affiliate_register_migration_routes(): void {
	if ( ! current_user_can( 'manage_options' ) || wc_affiliate_is_notice_cleared() || ! wc_affiliate_is_compatible_with_pro() ) {
		return;
	}

	register_rest_route(
		'wc-affiliate/v1',
		'/notices/dismiss',
		array(
			'methods'             => WP_REST_Server::CREATABLE,
			'callback'            => 'wc_affiliate_migration_rest_dismiss_notice',
			'permission_callback' => static function () {
				return current_user_can( 'manage_options' );
			},
			'args'                => apply_filters(
				'wc_affiliate_rest_api_notices_dismiss_args',
				array(
					'notice_type' => array(
						'description'       => esc_html__( 'Type of notice to dismiss.', 'wc-affiliate' ),
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_key',
					),
				)
			),
		)
	);
}

/**
 * REST API callback: Dismiss a migration-related admin notice.
 *
 * Persists the dismissal state for the given notice type by updating a
 * dedicated option. The notice_type parameter is sanitized via the route
 * args sanitize_callback before this callback is invoked.
 *
 * Note: The delete_transient() call present in the original implementation
 * was removed. No transient keyed by notice_type is set anywhere in this
 * plugin. If a transient-based expiry mechanism is introduced in future,
 * restore and document it here explicitly.
 *
 * @since 3.0.0
 *
 * @param WP_REST_Request $request The REST request object containing notice_type.
 *
 * @return WP_REST_Response The response confirming dismissal.
 */
function wc_affiliate_migration_rest_dismiss_notice( WP_REST_Request $request ): WP_REST_Response {
	// notice_type is pre-sanitized via route args sanitize_callback (sanitize_key).
	$notice_type = $request->get_param( 'notice_type' );

	// Persist dismissal. Key format: {notice_type}_notice_dismissed.
	// For the default notice_type 'wc_affiliate_migration', this resolves to
	// 'wc_affiliate_migration_notice_dismissed' — consistent with wc_affiliate_is_notice_cleared().
	update_option( $notice_type . '_notice_dismissed', true, false );

	$response_data = array(
		'message' => __( 'Notice Removed!', 'wc-affiliate' ),
		'url'     => admin_url( 'admin.php?page=wc-affiliate/#/' ),
	);

	/**
	 * Filter the REST API success response data for notice dismissal.
	 *
	 * @since 3.0.0
	 *
	 * @param array  $response_data The response payload.
	 * @param array  $data          The payload data.
	 * @param string $message_type  The message type.
	 * @param int    $response_code The response code.
	 */
	$response_data = apply_filters( 'wc_affiliate_rest_api_response_success', $response_data, $response_data, '', 200 );

	return new WP_REST_Response( $response_data, 200 );
}

/**
 * Enqueue scripts required for the migration notice dismissal interaction.
 *
 * Injects a lightweight inline script that intercepts the native WordPress
 * notice dismiss button click and sends a REST API request to persist the
 * dismissal state server-side.
 *
 * @since 3.0.0
 *
 * @return void
 */
function wc_affiliate_legacy_migration_scripts(): void {
	if ( ! wc_affiliate_is_compatible_with_pro() ) {
		return;
	}

	if ( ! current_user_can( 'manage_options' ) || ! wc_affiliate_should_show_migration_notice() ) {
		return;
	}

	wp_enqueue_script( 'jquery' );
	wp_enqueue_script( 'wp-api-fetch' );

	add_action(
		'admin_footer',
		static function () {
			?>
				<script type="text/javascript" id="wc-affiliate-migration-js">
					jQuery( ( $ ) => {
						$( document ).on( 'click', '.notice.is-dismissible.wc-affiliate-migrate button.notice-dismiss', ( e ) => {
							e.preventDefault();

							if ( ! wp.apiFetch ) {
								return;
							}

							void wp.apiFetch( {
								path: '/wc-affiliate/v1/notices/dismiss',
								method: 'POST',
								data: {
									notice_type: 'wc_affiliate_migration',
								},
							} );
						} );
					} );
				</script>
				<?php
		}
	);
}

/**
 * Determine whether migration hooks should be registered.
 *
 * Migration notices and hooks are suppressed entirely when WC Affiliate Pro
 * is active but below version 3.0.0, since the legacy Pro plugin manages
 * compatibility independently in that state.
 *
 * is_plugin_active() requires wp-admin/includes/plugin.php which may not be
 * loaded during early plugin initialisation. The require_once guard below
 * ensures safe usage at any load point without side effects.
 *
 * @since 3.0.0
 *
 * @return bool True if migration hooks should be registered.
 */
function wc_affiliate_is_compatible_with_pro(): bool {
	if ( ! function_exists( 'is_plugin_active' ) ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
	}

	// Pro plugin not active — always register migration hooks.
	if ( ! is_plugin_active( 'wc-affiliate-pro/wc-affiliate-pro.php' ) ) {
		return true;
	}

	$plugin_file = dirname( __DIR__ ) . '/wc-affiliate-pro/wc-affiliate-pro.php';
	$plugin_data = get_plugin_data( $plugin_file, false, false );

	// Register hooks only for Pro 3.0.0 and above.
	return version_compare( $plugin_data['Version'], '3.0.0', '>=' );
}

// Resolve and persist user status exactly once per request lifecycle.
wc_affiliate_resolve_user_status();
