<?php
/**
 * Onboarding Wizard
 *
 * Paginated 7-step onboarding wizard for SEOvault AI.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Admin;

use SEOvaultSEO\Core\Options;
use SEOvaultSEO\Migration\MigrationManager;

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

/**
 * Paginated onboarding wizard (admin UI, state persistence, and form handlers).
 */
class Onboarding {

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

	private const STATE_OPTION     = 'seovaultseo_onboarding_state';
	private const COMPLETED_OPTION = 'seovaultseo_onboarding_completed';
	private const SKIPPED_OPTION   = 'seovaultseo_onboarding_skipped';
	private const NONCE_ACTION     = 'seovaultseo_onboarding_nonce';
	private const STEP_ACTION      = 'seovaultseo_onboarding_step';
	private const SKIP_ACTION      = 'seovaultseo_onboarding_skip';
	public const RESTART_ACTION    = 'seovaultseo_onboarding_restart';
	public const RESTART_NONCE     = 'seovaultseo_onboarding_restart_nonce';

	/**
	 * Ordered wizard step slugs.
	 *
	 * @var string[]
	 */
	private static $steps = array(
		'welcome',
		'seo_mode',
		'site_identity',
		'seo_settings',
		'import',
		'connect',
		'finish',
	);

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

	/**
	 * Private constructor.
	 */
	private function __construct() {}

	/**
	 * Register hooks for the onboarding wizard.
	 *
	 * @return void
	 */
	public static function init() {
		add_action( 'admin_menu', array( __CLASS__, 'register_menu' ) );
		add_action( 'admin_post_' . self::STEP_ACTION, array( __CLASS__, 'handle_step' ) );
		add_action( 'admin_post_' . self::SKIP_ACTION, array( __CLASS__, 'handle_skip' ) );
		add_action( 'admin_post_' . self::RESTART_ACTION, array( __CLASS__, 'handle_restart' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
	}

	/**
	 * Register the onboarding submenu page.
	 *
	 * @return void
	 */
	public static function register_menu() {
		$capability = apply_filters( 'seovaultseo_main_menu_capability', 'manage_options' );
		if ( ! current_user_can( $capability ) ) {
			return;
		}

		add_submenu_page(
			'seovault-ai',
			__( 'Onboarding', 'seovault-ai' ),
			__( 'Onboarding', 'seovault-ai' ),
			$capability,
			'seovault-ai-onboarding',
			array( __CLASS__, 'render_onboarding' ),
			99
		);
	}

	/**
	 * Enqueue onboarding CSS/JS on the wizard screen.
	 *
	 * @param string $hook Current admin page hook suffix.
	 * @return void
	 */
	public static function enqueue_assets( $hook ) {
		if ( false === strpos( $hook, 'seovault-ai-onboarding' ) ) {
			return;
		}

		\SEOvaultSEO\Extension\Assets::register();

		wp_enqueue_style(
			'seovaultseo-onboarding',
			SEOVAULT_SEO_URL . 'assets/css/onboarding.css',
			array( \SEOvaultSEO\Extension\ExtensionApi::HANDLE_ADMIN_FOUNDATION ),
			SEOVAULT_SEO_VERSION . '-7'
		);

		wp_enqueue_media();

		wp_enqueue_script(
			'seovaultseo-onboarding',
			SEOVAULT_SEO_URL . 'assets/js/onboarding.js',
			array( 'jquery' ),
			SEOVAULT_SEO_VERSION . '-7',
			true
		);

		wp_set_script_translations( 'seovaultseo-onboarding', 'seovault-ai', SEOVAULT_SEO_DIR . 'languages/' );

		wp_localize_script(
			'seovaultseo-onboarding',
			'seovaultseo_onboarding',
			array(
				'ajax_url'       => admin_url( 'admin-ajax.php' ),
				'nonce'          => wp_create_nonce( 'seovaultseo_onboarding_ajax' ),
				'site_key'       => Options::get_site_key(),
				'rest_url'       => rest_url( 'seovaultai/v1/verify' ),
				'copied_text'    => __( 'Copied!', 'seovault-ai' ),
				'field_required' => __( 'This field is required.', 'seovault-ai' ),
				'admin_url'      => admin_url( 'admin.php?page=seovault-ai-onboarding' ),
			)
		);
	}

	/**
	 * Render the onboarding wizard page.
	 *
	 * @return void
	 */
	public static function render_onboarding() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'seovault-ai' ) );
		}

		$state = self::get_state();
		$step  = isset( $state['step'] ) ? $state['step'] : 'welcome';

		if ( \SEOvaultSEO\Schema\SchemaOnboardingGuidance::should_route_to_migration() && 'welcome' === $step && empty( $state['force_wizard'] ) ) {
			self::render_migration_gate();
			return;
		}
		self::render_wizard( $state );
	}

	/**
	 * Get persisted wizard state (creates defaults when missing).
	 *
	 * @return array
	 */
	public static function get_state() {
		$state = get_option( self::STATE_OPTION, array() );
		if ( ! is_array( $state ) || empty( $state ) ) {
			$state = self::get_default_state();
			update_option( self::STATE_OPTION, $state, false );
		}
		return self::normalize_state( $state );
	}

	/**
	 * Persist wizard state.
	 *
	 * @param array $state Wizard state.
	 * @return bool
	 */
	public static function update_state( $state ) {
		return update_option( self::STATE_OPTION, self::normalize_state( $state ), false );
	}

	/**
	 * Delete wizard state and completion flags.
	 *
	 * @return void
	 */
	public static function reset_state() {
		delete_option( self::STATE_OPTION );
		delete_option( self::COMPLETED_OPTION );
		delete_option( self::SKIPPED_OPTION );
	}

	/**
	 * Whether onboarding was completed.
	 *
	 * @return bool
	 */
	public static function is_completed() {
		return (bool) get_option( self::COMPLETED_OPTION, false );
	}

	/**
	 * Whether onboarding was skipped.
	 *
	 * @return bool
	 */
	public static function is_skipped() {
		return (bool) get_option( self::SKIPPED_OPTION, false );
	}

	/**
	 * Whether WordPress is currently rendering the onboarding page.
	 *
	 * @return bool
	 */
	public static function is_onboarding_screen() {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( $screen instanceof \WP_Screen && false !== strpos( (string) $screen->id, 'seovault-ai-onboarding' ) ) {
			return true;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page routing check.
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
		return 'seovault-ai-onboarding' === $page;
	}

	/**
	 * Mark onboarding complete (optionally as skipped).
	 *
	 * @param bool $skipped Whether the user skipped the wizard.
	 * @return void
	 */
	public static function mark_completed( $skipped = false ) {
		update_option( self::COMPLETED_OPTION, true, false );
		if ( $skipped ) {
			update_option( self::SKIPPED_OPTION, true, false );
		}
		// Skipping/completing the wizard ends the pending schema identity nudge.
		\SEOvaultSEO\Schema\SchemaOnboardingGuidance::mark_identity_guidance_complete();
	}

	/**
	 * Reopen onboarding at Step 1 while preserving saved wizard selections.
	 *
	 * @return void
	 */
	public static function restart_state() {
		$state                 = self::get_state();
		$state['step']         = 'welcome';
		$state['status']       = 'in_progress';
		$state['force_wizard'] = true;

		self::update_state( $state );
		delete_option( self::COMPLETED_OPTION );
		delete_option( self::SKIPPED_OPTION );
	}

	/**
	 * Upgrade path: do not show fresh-install onboarding; send users to migration review.
	 *
	 * @return void
	 */
	private static function render_migration_gate() {
		$review_url = \SEOvaultSEO\Admin\SchemaMigrationAdmin::review_url();
		$dash_url   = admin_url( 'admin.php?page=seovault-ai' );
		?>
		<div class="wrap seovaultseo-wizard seovaultseo-wizard-migration-gate">
			<h1><?php esc_html_e( 'Schema setup was updated', 'seovault-ai' ); ?></h1>
			<p><?php echo esc_html( \SEOvaultSEO\Schema\SchemaOnboardingGuidance::automatic_basics_copy() ); ?></p>
			<p>
				<?php
				esc_html_e(
					'This site already has a schema configuration from a previous version. Review the migration report instead of starting the fresh-install setup.',
					'seovault-ai'
				);
				?>
			</p>
			<p><?php echo esc_html( \SEOvaultSEO\Schema\SchemaOnboardingGuidance::additional_schema_copy() ); ?></p>
			<p>
				<a class="button button-primary" href="<?php echo esc_url( $review_url ); ?>">
					<?php esc_html_e( 'Open migration review', 'seovault-ai' ); ?>
				</a>
				<a class="button button-secondary" href="<?php echo esc_url( $dash_url ); ?>">
					<?php esc_html_e( 'Go to Dashboard', 'seovault-ai' ); ?>
				</a>
			</p>
		</div>
		<?php
	}

	// phpcs:disable Squiz.Commenting.FunctionComment.Missing -- Private wizard render/state helpers; public handlers documented below.

	private static function get_default_state() {
		return array(
			'step'         => 'welcome',
			'mode'         => 'auto',
			'identity'     => array(
				'site_type'        => '',
				'entity_type'      => 'organization',
				'name'             => '',
				'logo_id'          => 0,
				'logo_url'         => '',
				'social_image_id'  => 0,
				'social_image_url' => '',
				'skipped'          => false,
			),
			'settings'     => array(
				'home_title'                      => '',
				'home_description'                => '',
				'output_mode'                     => 'auto',
				'social_title_template'           => '',
				'social_description_template'     => '',
				'robots_empty_taxonomies_noindex' => false,
				'robots_nofollow_external'        => false,
				'robots_new_tab_external'         => false,
			),
			'import'       => array(
				'skipped' => false,
				'source'  => '',
				'done'    => false,
			),
			'connection'   => array(
				'skipped' => false,
			),
			'status'       => 'in_progress',
			'force_wizard' => false,
		);
	}

	private static function normalize_state( $state ) {
		$default = self::get_default_state();
		$state   = is_array( $state ) ? $state : array();
		$state   = wp_parse_args( $state, $default );
		foreach ( array( 'identity', 'settings', 'import', 'connection' ) as $key ) {
			if ( ! is_array( $state[ $key ] ) ) {
				$state[ $key ] = array();
			}
			$state[ $key ] = wp_parse_args( $state[ $key ], $default[ $key ] );
		}
		if ( ! in_array( $state['step'], self::$steps, true ) ) {
			$state['step'] = 'welcome';
		}
		return $state;
	}

	private static function get_step_index( $step ) {
		$index = array_search( $step, self::$steps, true );
		return false === $index ? 0 : $index;
	}

	private static function get_step_number( $step ) {
		return self::get_step_index( $step ) + 1;
	}

	private static function get_step_label( $step ) {
		$labels = array(
			'welcome'       => __( 'Welcome', 'seovault-ai' ),
			'seo_mode'      => __( 'SEO Mode', 'seovault-ai' ),
			'site_identity' => __( 'Site Identity', 'seovault-ai' ),
			'seo_settings'  => __( 'SEO Settings', 'seovault-ai' ),
			'import'        => __( 'Import', 'seovault-ai' ),
			'connect'       => __( 'Connect', 'seovault-ai' ),
			'finish'        => __( 'Finish', 'seovault-ai' ),
		);
		return isset( $labels[ $step ] ) ? $labels[ $step ] : '';
	}

	private static function render_wizard( $state ) {
		$step = $state['step'];
		?>
		<div class="wrap seovaultseo-wizard">
			<div class="seovaultseo-wizard-container">
				<?php self::render_stepper( $step ); ?>
				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php?action=' . self::STEP_ACTION ) ); ?>" class="seovaultseo-wizard-form" novalidate>
					<?php wp_nonce_field( self::NONCE_ACTION ); ?>
					<input type="hidden" name="seovaultseo_step" value="<?php echo esc_attr( $step ); ?>" />
					<div class="seovaultseo-wizard-body">
						<?php self::render_step( $step, $state ); ?>
					</div>
					<?php self::render_footer( $step, $state ); ?>
				</form>
			</div>
		</div>
		<?php
	}

	private static function render_stepper( $current_step ) {
		$current_index = self::get_step_index( $current_step );
		?>
		<div class="seovaultseo-wizard-header">
			<div class="seovaultseo-wizard-stepper" role="tablist" aria-label="<?php esc_attr_e( 'Onboarding progress', 'seovault-ai' ); ?>">
				<?php foreach ( self::$steps as $index => $step ) : ?>
					<?php
					$status = $index < $current_index ? 'completed' : ( $index === $current_index ? 'active' : 'pending' );
					$label  = self::get_step_label( $step );
					$number = $index + 1;
					?>
					<div class="seovaultseo-wizard-step seovaultseo-wizard-step-<?php echo esc_attr( $status ); ?>" role="tab" aria-current="<?php echo ( 'active' === $status ) ? 'step' : 'false'; ?>" aria-label="<?php echo esc_attr( sprintf( /* translators: 1: step number, 2: step label, 3: step status */ __( 'Step %1$d: %2$s (%3$s)', 'seovault-ai' ), $number, $label, $status ) ); ?>">
						<span class="seovaultseo-wizard-step-number">
							<?php if ( 'completed' === $status ) : ?>
								<span class="dashicons dashicons-yes-alt"></span>
							<?php else : ?>
								<?php echo esc_html( $number ); ?>
							<?php endif; ?>
						</span>
						<span class="seovaultseo-wizard-step-label"><?php echo esc_html( $label ); ?></span>
					</div>
				<?php endforeach; ?>
			</div>
		</div>
		<?php
	}

	private static function render_step( $step, $state ) {
		$method = 'render_step_' . $step;
		if ( is_callable( array( __CLASS__, $method ) ) ) {
			call_user_func( array( __CLASS__, $method ), $state );
		} else {
			self::render_step_placeholder( $step );
		}
	}

	private static function render_step_placeholder( $step ) {
		?>
		<div class="seovaultseo-wizard-step-content">
			<h2><?php echo esc_html( self::get_step_label( $step ) ); ?></h2>
			<p><?php esc_html_e( 'This step will be built in the next phase.', 'seovault-ai' ); ?></p>
		</div>
		<?php
	}

	private static function render_step_welcome( $state ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
		$logo_url   = SEOVAULT_SEO_URL . 'assets/images/seovault-logo.svg';
		$basics     = \SEOvaultSEO\Schema\SchemaOnboardingGuidance::automatic_basics_copy();
		$additional = \SEOvaultSEO\Schema\SchemaOnboardingGuidance::additional_schema_copy();
		$competitor = \SEOvaultSEO\Schema\SchemaOnboardingGuidance::competitor_notice_copy();
		?>
		<div class="seovaultseo-wizard-step-content seovaultseo-wizard-welcome">
			<div class="seovaultseo-wizard-logo">
				<img src="<?php echo esc_url( $logo_url ); ?>" alt="<?php esc_attr_e( 'SEOvault AI logo', 'seovault-ai' ); ?>" />
			</div>
			<h2><?php esc_html_e( 'Welcome to SEOvault AI', 'seovault-ai' ); ?></h2>
			<p><?php esc_html_e( 'Professional-grade SEO features in your WordPress dashboard. Connect the optional AI web app when you are ready for cloud-based workflows.', 'seovault-ai' ); ?></p>
			<div class="seovaultseo-wizard-hint seovaultseo-wizard-schema-hint">
				<p><strong><?php esc_html_e( 'Structured data (schema)', 'seovault-ai' ); ?></strong></p>
				<p><?php echo esc_html( $basics ); ?></p>
				<p><?php echo esc_html( $additional ); ?></p>
				<?php if ( '' !== $competitor ) : ?>
					<p><?php echo esc_html( $competitor ); ?></p>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	private static function render_step_seo_mode( $state ) {
		$mode = isset( $state['mode'] ) ? sanitize_text_field( $state['mode'] ) : 'auto';
		if ( ! in_array( $mode, array( 'auto', 'manual' ), true ) ) {
			$mode = 'auto';
		}
		?>
		<div class="seovaultseo-wizard-step-content">
			<h2><?php esc_html_e( 'How would you like SEOvault AI to work?', 'seovault-ai' ); ?></h2>
			<p><?php esc_html_e( 'Choose how much control you want. You can change this later in the plugin settings.', 'seovault-ai' ); ?></p>

			<div class="seovaultseo-wizard-option-cards" role="radiogroup" aria-label="<?php esc_attr_e( 'SEO mode', 'seovault-ai' ); ?>">
				<label class="seovaultseo-wizard-option-card<?php echo 'auto' === $mode ? ' seovaultseo-wizard-option-card-selected' : ''; ?>">
					<input type="radio" name="seovaultseo_mode" value="auto" <?php checked( 'auto', $mode ); ?> />
					<h3><?php esc_html_e( 'Auto', 'seovault-ai' ); ?></h3>
					<p><?php esc_html_e( 'Let SEOvault AI configure your homepage meta, robots rules, sitemap, and automatic schema using your site identity. Extra schema types are added later on posts — not selected all at once.', 'seovault-ai' ); ?></p>
				</label>
				<label class="seovaultseo-wizard-option-card<?php echo 'manual' === $mode ? ' seovaultseo-wizard-option-card-selected' : ''; ?>">
					<input type="radio" name="seovaultseo_mode" value="manual" <?php checked( 'manual', $mode ); ?> />
					<h3><?php esc_html_e( 'Manual', 'seovault-ai' ); ?></h3>
					<p><?php esc_html_e( 'I want full control. Skip the auto defaults and configure everything myself in the plugin settings.', 'seovault-ai' ); ?></p>
				</label>
			</div>
		</div>
		<?php
	}

	private static function render_step_site_identity( $state ) {
		$identity  = $state['identity'];
		$mode      = isset( $state['mode'] ) ? $state['mode'] : 'auto';
		$is_manual = 'manual' === $mode;

		$site_type        = isset( $identity['site_type'] ) ? $identity['site_type'] : '';
		$entity_type      = isset( $identity['entity_type'] ) ? $identity['entity_type'] : 'organization';
		$name             = isset( $identity['name'] ) && $identity['name'] ? $identity['name'] : get_bloginfo( 'name' );
		$logo_id          = isset( $identity['logo_id'] ) ? (int) $identity['logo_id'] : 0;
		$logo_url         = isset( $identity['logo_url'] ) ? $identity['logo_url'] : '';
		$social_image_id  = isset( $identity['social_image_id'] ) ? (int) $identity['social_image_id'] : 0;
		$social_image_url = isset( $identity['social_image_url'] ) ? $identity['social_image_url'] : '';

		$site_types = array(
			'blog'      => __( 'Blog / News', 'seovault-ai' ),
			'business'  => __( 'Business / Corporate', 'seovault-ai' ),
			'ecommerce' => __( 'Online shop / E-commerce', 'seovault-ai' ),
			'portfolio' => __( 'Portfolio / Personal', 'seovault-ai' ),
			'other'     => __( 'Other', 'seovault-ai' ),
		);
		?>
		<div class="seovaultseo-wizard-step-content">
			<h2><?php $is_manual ? esc_html_e( 'Site identity (optional)', 'seovault-ai' ) : esc_html_e( 'Tell us about your site', 'seovault-ai' ); ?></h2>
			<p>
				<?php
				if ( $is_manual ) {
					esc_html_e( 'You can set this up now or later in the plugin settings. Site identity powers automatic Organization or Person schema.', 'seovault-ai' );
				} else {
					esc_html_e( 'SEOvault needs your publisher identity for automatic schema. Choose Organization or Person — you do not select every schema type.', 'seovault-ai' );
				}
				?>
			</p>

			<?php if ( ! $is_manual ) : ?>
				<div class="seovaultseo-wizard-hint">
					<?php esc_html_e( 'Adding a logo and site name helps social previews and automatic schema look correct. Extra types like FAQ or Event are added later on posts, blocks, or templates.', 'seovault-ai' ); ?>
				</div>
			<?php endif; ?>

			<div class="seovaultseo-wizard-field-group">
				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_site_type"><?php esc_html_e( 'Site type', 'seovault-ai' ); ?></label>
					<select id="seovaultseo_site_type" name="seovaultseo_site_type">
						<option value=""><?php esc_html_e( 'Select a type…', 'seovault-ai' ); ?></option>
						<?php foreach ( $site_types as $value => $label ) : ?>
							<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $site_type, $value ); ?>><?php echo esc_html( $label ); ?></option>
						<?php endforeach; ?>
					</select>
				</div>

				<div class="seovaultseo-wizard-field">
					<label><?php esc_html_e( 'Publisher identity for automatic schema', 'seovault-ai' ); ?></label>
					<div class="seovaultseo-wizard-radio-group" role="radiogroup" aria-label="<?php esc_attr_e( 'Publisher identity for automatic schema', 'seovault-ai' ); ?>">
						<label class="seovaultseo-wizard-radio-label">
							<input type="radio" name="seovaultseo_entity_type" value="person" <?php checked( 'person', $entity_type ); ?> />
							<?php esc_html_e( 'Person', 'seovault-ai' ); ?>
						</label>
						<label class="seovaultseo-wizard-radio-label">
							<input type="radio" name="seovaultseo_entity_type" value="organization" <?php checked( 'organization', $entity_type ); ?> />
							<?php esc_html_e( 'Organization', 'seovault-ai' ); ?>
						</label>
					</div>
					<p class="description"><?php esc_html_e( 'This is not a list of schema types to enable site-wide.', 'seovault-ai' ); ?></p>
				</div>

				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_identity_name"><?php esc_html_e( 'Name', 'seovault-ai' ); ?></label>
					<input type="text" id="seovaultseo_identity_name" name="seovaultseo_identity_name" value="<?php echo esc_attr( $name ); ?>" placeholder="<?php esc_attr_e( 'Your site or organization name', 'seovault-ai' ); ?>" />
				</div>

				<div class="seovaultseo-wizard-field">
					<label><?php esc_html_e( 'Logo', 'seovault-ai' ); ?></label>
					<div class="seovaultseo-wizard-media-upload" data-target="logo">
						<input type="hidden" name="seovaultseo_logo_id" value="<?php echo esc_attr( $logo_id ); ?>" class="seovaultseo-wizard-media-id" />
						<input type="hidden" name="seovaultseo_logo_url" value="<?php echo esc_attr( $logo_url ); ?>" class="seovaultseo-wizard-media-url" />
						<div class="seovaultseo-wizard-media-preview">
							<?php if ( $logo_url ) : ?>
								<img src="<?php echo esc_url( $logo_url ); ?>" alt="<?php esc_attr_e( 'Logo preview', 'seovault-ai' ); ?>" />
							<?php endif; ?>
						</div>
						<button type="button" class="button button-secondary seovaultseo-wizard-media-button"><?php esc_html_e( 'Choose logo', 'seovault-ai' ); ?></button>
						<button type="button" class="button button-link seovaultseo-wizard-media-remove" <?php echo $logo_id ? '' : 'style="display:none;"'; ?>><?php esc_html_e( 'Remove', 'seovault-ai' ); ?></button>
					</div>
				</div>

				<div class="seovaultseo-wizard-field">
					<label><?php esc_html_e( 'Default social image', 'seovault-ai' ); ?></label>
					<div class="seovaultseo-wizard-media-upload" data-target="social_image">
						<input type="hidden" name="seovaultseo_social_image_id" value="<?php echo esc_attr( $social_image_id ); ?>" class="seovaultseo-wizard-media-id" />
						<input type="hidden" name="seovaultseo_social_image_url" value="<?php echo esc_attr( $social_image_url ); ?>" class="seovaultseo-wizard-media-url" />
						<div class="seovaultseo-wizard-media-preview">
							<?php if ( $social_image_url ) : ?>
								<img src="<?php echo esc_url( $social_image_url ); ?>" alt="<?php esc_attr_e( 'Social image preview', 'seovault-ai' ); ?>" />
							<?php endif; ?>
						</div>
						<button type="button" class="button button-secondary seovaultseo-wizard-media-button"><?php esc_html_e( 'Choose image', 'seovault-ai' ); ?></button>
						<button type="button" class="button button-link seovaultseo-wizard-media-remove" <?php echo $social_image_id ? '' : 'style="display:none;"'; ?>><?php esc_html_e( 'Remove', 'seovault-ai' ); ?></button>
					</div>
				</div>
			</div>
		</div>
		<?php
	}

	private static function render_step_seo_settings( $state ) {
		$settings = $state['settings'];
		$mode     = isset( $state['mode'] ) ? $state['mode'] : 'auto';
		$is_auto  = 'auto' === $mode;
		$identity = $state['identity'];

		// Pre-fill values: Auto mode uses identity + bloginfo, Manual uses existing Options.
		if ( $is_auto ) {
			$site_name     = ! empty( $identity['name'] ) ? $identity['name'] : get_bloginfo( 'name' );
			$tagline       = get_bloginfo( 'description' );
			$home_title    = ! empty( $settings['home_title'] ) ? $settings['home_title'] : $site_name;
			$home_desc     = ! empty( $settings['home_description'] ) ? $settings['home_description'] : $tagline;
			$output_mode   = ! empty( $settings['output_mode'] ) ? $settings['output_mode'] : 'auto';
			$soc_title     = ! empty( $settings['social_title_template'] ) ? $settings['social_title_template'] : '%title% | %sitename%';
			$soc_desc      = ! empty( $settings['social_description_template'] ) ? $settings['social_description_template'] : '%excerpt%';
			$noindex_empty = ! empty( $settings['robots_empty_taxonomies_noindex'] ) ? $settings['robots_empty_taxonomies_noindex'] : true;
			$nofollow_ext  = ! empty( $settings['robots_nofollow_external'] ) ? $settings['robots_nofollow_external'] : false;
			$new_tab_ext   = ! empty( $settings['robots_new_tab_external'] ) ? $settings['robots_new_tab_external'] : false;
		} else {
			$home_title    = ! empty( $settings['home_title'] ) ? $settings['home_title'] : Options::get_home_title();
			$home_desc     = ! empty( $settings['home_description'] ) ? $settings['home_description'] : Options::get_home_description();
			$output_mode   = ! empty( $settings['output_mode'] ) ? $settings['output_mode'] : Options::get_output_mode();
			$soc_title     = ! empty( $settings['social_title_template'] ) ? $settings['social_title_template'] : Options::get_title_template();
			$soc_desc      = ! empty( $settings['social_description_template'] ) ? $settings['social_description_template'] : Options::get_description_template();
			$noindex_empty = ! empty( $settings['robots_empty_taxonomies_noindex'] ) ? $settings['robots_empty_taxonomies_noindex'] : Options::is_empty_archives_noindex();
			$nofollow_ext  = ! empty( $settings['robots_nofollow_external'] ) ? $settings['robots_nofollow_external'] : (bool) Options::get( 'nofollow_external', false );
			$new_tab_ext   = ! empty( $settings['robots_new_tab_external'] ) ? $settings['robots_new_tab_external'] : (bool) Options::get( 'new_tab_external', false );
		}

		$output_modes = array(
			'auto'     => __( 'Full SEO output (recommended)', 'seovault-ai' ),
			'manual'   => __( 'Title only', 'seovault-ai' ),
			'disabled' => __( 'No output (advanced)', 'seovault-ai' ),
		);
		?>
		<div class="seovaultseo-wizard-step-content">
			<h2><?php esc_html_e( 'SEO Settings', 'seovault-ai' ); ?></h2>
			<p>
				<?php
				if ( $is_auto ) {
					esc_html_e( 'Auto-configured — review and confirm. These settings power your homepage meta, social previews, and robots rules.', 'seovault-ai' );
				} else {
					esc_html_e( 'Configure your homepage meta, social templates, and robots defaults. You can change everything later in the plugin settings.', 'seovault-ai' );
				}
				?>
			</p>

			<?php if ( $is_auto ) : ?>
				<div class="seovaultseo-wizard-hint">
					<?php esc_html_e( 'Auto-configured — review and confirm.', 'seovault-ai' ); ?>
				</div>
			<?php endif; ?>

			<div class="seovaultseo-wizard-field-group">
				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_home_title"><?php esc_html_e( 'Homepage title', 'seovault-ai' ); ?> <span aria-hidden="true">*</span></label>
					<input type="text" id="seovaultseo_home_title" name="seovaultseo_home_title" value="<?php echo esc_attr( $home_title ); ?>" placeholder="<?php esc_attr_e( 'Your homepage SEO title', 'seovault-ai' ); ?>" required data-error-message="<?php esc_attr_e( 'Please enter a homepage title.', 'seovault-ai' ); ?>" />
					<div class="seovaultseo-wizard-error" aria-live="polite"></div>
				</div>

				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_home_description"><?php esc_html_e( 'Homepage meta description', 'seovault-ai' ); ?></label>
					<textarea id="seovaultseo_home_description" name="seovaultseo_home_description" maxlength="160" placeholder="<?php esc_attr_e( 'A short description of your site (max 160 characters)', 'seovault-ai' ); ?>" rows="3"><?php echo esc_textarea( $home_desc ); ?></textarea>
					<div class="seovaultseo-wizard-char-counter" data-target="seovaultseo_home_description" data-max="160">
						<span class="seovaultseo-wizard-char-count">0</span> / 160
					</div>
				</div>

				<div class="seovaultseo-wizard-field">
					<label><?php esc_html_e( 'Output mode', 'seovault-ai' ); ?></label>
					<div class="seovaultseo-wizard-radio-group" role="radiogroup" aria-label="<?php esc_attr_e( 'Output mode', 'seovault-ai' ); ?>">
						<?php foreach ( $output_modes as $value => $label ) : ?>
							<label class="seovaultseo-wizard-radio-label">
								<input type="radio" name="seovaultseo_output_mode" value="<?php echo esc_attr( $value ); ?>" <?php checked( $value, $output_mode ); ?> />
								<?php echo esc_html( $label ); ?>
							</label>
						<?php endforeach; ?>
					</div>
				</div>

				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_social_title_template"><?php esc_html_e( 'Default social title template', 'seovault-ai' ); ?></label>
					<input type="text" id="seovaultseo_social_title_template" name="seovaultseo_social_title_template" value="<?php echo esc_attr( $soc_title ); ?>" placeholder="<?php /* translators: Template variable tokens, not sprintf placeholders. */ esc_attr_e( '%title% | %sitename%', 'seovault-ai' ); ?>" maxlength="60" />
					<div class="seovaultseo-wizard-char-counter" data-target="seovaultseo_social_title_template" data-max="60">
						<span class="seovaultseo-wizard-char-count">0</span> / 60
					</div>
					<p class="description">
					<?php
					/* translators: %title%, %sitename%, %sep% are literal template variable tokens shown to users. */
					esc_html_e( 'Variables: %title%, %sitename%, %sep%', 'seovault-ai' ); // phpcs:ignore WordPress.WP.I18n.UnorderedPlaceholdersText
					?>
					</p>
				</div>

				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_social_description_template"><?php esc_html_e( 'Default social description template', 'seovault-ai' ); ?></label>
					<input type="text" id="seovaultseo_social_description_template" name="seovaultseo_social_description_template" value="<?php echo esc_attr( $soc_desc ); ?>" placeholder="<?php /* translators: Template variable token, not a sprintf placeholder. */ esc_attr_e( '%excerpt%', 'seovault-ai' ); ?>" maxlength="160" />
					<div class="seovaultseo-wizard-char-counter" data-target="seovaultseo_social_description_template" data-max="160">
						<span class="seovaultseo-wizard-char-count">0</span> / 160
					</div>
					<p class="description">
					<?php
					/* translators: %excerpt%, %description% are literal template variable tokens shown to users. */
					esc_html_e( 'Variables: %excerpt%, %description%', 'seovault-ai' ); // phpcs:ignore WordPress.WP.I18n.UnorderedPlaceholdersText
					?>
					</p>
				</div>

				<div class="seovaultseo-wizard-field">
					<label><?php esc_html_e( 'Robots defaults', 'seovault-ai' ); ?></label>
					<div class="seovaultseo-wizard-checkbox-group">
						<label class="seovaultseo-wizard-checkbox-label">
							<input type="checkbox" name="seovaultseo_robots_empty_taxonomies_noindex" value="1" <?php checked( $noindex_empty ); ?> />
							<?php esc_html_e( 'No-index empty taxonomies', 'seovault-ai' ); ?>
						</label>
						<label class="seovaultseo-wizard-checkbox-label">
							<input type="checkbox" name="seovaultseo_robots_nofollow_external" value="1" <?php checked( $nofollow_ext ); ?> />
							<?php esc_html_e( 'Nofollow external links', 'seovault-ai' ); ?>
						</label>
						<label class="seovaultseo-wizard-checkbox-label">
							<input type="checkbox" name="seovaultseo_robots_new_tab_external" value="1" <?php checked( $new_tab_ext ); ?> />
							<?php esc_html_e( 'Open external links in a new tab', 'seovault-ai' ); ?>
						</label>
					</div>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Detect active competitor SEO plugins available for migration.
	 *
	 * @return array Map of slug => plugin name.
	 */
	private static function detect_migration_sources() {
		$status = MigrationManager::get_status();

		if ( empty( $status['available'] ) || empty( $status['providers'] ) ) {
			return array();
		}

		$sources = array();
		foreach ( $status['providers'] as $slug => $info ) {
			$sources[ $slug ] = $info['name'];
		}

		return $sources;
	}

	private static function render_step_import( $state ) {
		$sources = self::detect_migration_sources();

		if ( empty( $sources ) ) {
			?>
			<div class="seovaultseo-wizard-step-content">
				<h2><?php esc_html_e( 'Import SEO Data', 'seovault-ai' ); ?></h2>
				<p><?php esc_html_e( 'No supported SEO plugins detected. You can skip this step.', 'seovault-ai' ); ?></p>
			</div>
			<?php
			return;
		}

		$import          = $state['import'];
		$selected_source = isset( $import['source'] ) ? $import['source'] : '';
		$first_slug      = key( $sources );
		$plugin_names    = implode( ', ', $sources );
		?>
		<div class="seovaultseo-wizard-step-content">
			<h2><?php esc_html_e( 'Import your existing SEO data', 'seovault-ai' ); ?></h2>
			<p>
				<?php
				echo esc_html(
					sprintf(
					/* translators: %s: detected plugin name(s) */
						__( 'We found %s. You can import titles, descriptions, and meta settings now, or do it later from the Tools tab.', 'seovault-ai' ),
						$plugin_names
					)
				);
				?>
			</p>

			<?php if ( count( $sources ) > 1 ) : ?>
				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_import_source"><?php esc_html_e( 'Import from', 'seovault-ai' ); ?></label>
					<select id="seovaultseo_import_source" name="seovaultseo_import_source">
						<?php foreach ( $sources as $slug => $name ) : ?>
							<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( $selected_source, $slug ); ?>><?php echo esc_html( $name ); ?></option>
						<?php endforeach; ?>
					</select>
				</div>
			<?php else : ?>
				<input type="hidden" name="seovaultseo_import_source" value="<?php echo esc_attr( $first_slug ); ?>" />
			<?php endif; ?>

			<div class="seovaultseo-wizard-hint">
				<?php esc_html_e( 'This step imports global SEO settings like the homepage title and description. Keep the source plugin active if you also plan a full post-and-term import from General > Tools, and deactivate it only after all needed data is imported.', 'seovault-ai' ); ?>
			</div>
		</div>
		<?php
	}

	private static function render_step_connect( $state ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
		$site_key    = Options::get_site_key();
		$rest_url    = rest_url( 'seovaultai/v1/verify' );
		$web_app_url = apply_filters( 'seovaultseo_web_app_url', 'https://app.seovaultai.com/dashboard' );
		?>
		<div class="seovaultseo-wizard-step-content">
			<h2><?php esc_html_e( 'Connect to the SEOvault AI web app', 'seovault-ai' ); ?></h2>
			<p><?php esc_html_e( 'The plugin is already working. The optional connection adds AI-powered content, bulk actions, and advanced reports performed through the web service.', 'seovault-ai' ); ?></p>

			<div class="seovaultseo-wizard-field-group">
				<div class="seovaultseo-wizard-field">
					<label for="seovaultseo_site_key_display"><?php esc_html_e( 'Your site key', 'seovault-ai' ); ?></label>
					<div class="seovaultseo-wizard-key-row">
						<input type="text" id="seovaultseo_site_key_display" class="seovaultseo-wizard-key-input" value="<?php echo esc_attr( $site_key ); ?>" readonly />
						<button type="button" class="button button-secondary seovaultseo-copy-key" data-key="<?php echo esc_attr( $site_key ); ?>">
							<span class="dashicons dashicons-clipboard"></span>
							<?php esc_html_e( 'Copy', 'seovault-ai' ); ?>
						</button>
					</div>
					<p class="description"><?php esc_html_e( 'Use this key when adding your site in the SEOvault AI web app.', 'seovault-ai' ); ?></p>
				</div>

				<div class="seovaultseo-wizard-field">
					<label><?php esc_html_e( 'REST endpoint', 'seovault-ai' ); ?></label>
					<code class="seovaultseo-wizard-rest-url"><?php echo esc_html( $rest_url ); ?></code>
				</div>

				<div class="seovaultseo-wizard-feature-teaser">
					<h3><?php esc_html_e( 'What you get by connecting', 'seovault-ai' ); ?></h3>
					<ul>
						<li><span class="dashicons dashicons-welcome-write-blog"></span> <?php esc_html_e( 'AI content suggestions', 'seovault-ai' ); ?></li>
						<li><span class="dashicons dashicons-edit-page"></span> <?php esc_html_e( 'Bulk title/description updates', 'seovault-ai' ); ?></li>
						<li><span class="dashicons dashicons-chart-area"></span> <?php esc_html_e( 'Advanced analytics and reports', 'seovault-ai' ); ?></li>
					</ul>
				</div>
			</div>

			<div class="seovaultseo-wizard-connect-actions">
				<a href="<?php echo esc_url( $web_app_url ); ?>" target="_blank" rel="noopener noreferrer" class="button button-primary seovaultseo-wizard-connect-now">
					<?php esc_html_e( 'Connect now', 'seovault-ai' ); ?>
				</a>
				<button type="submit" name="seovaultseo_step_action" value="skip_connection" class="button button-link seovaultseo-wizard-skip-step-link">
					<?php esc_html_e( "I'll connect later", 'seovault-ai' ); ?>
				</button>
			</div>
		</div>
		<?php
	}

	private static function render_step_finish( $state ) {
		$mode               = $state['mode'];
		$identity_skipped   = ! empty( $state['identity']['skipped'] );
		$import_done        = ! empty( $state['import']['done'] );
		$import_skipped     = ! empty( $state['import']['skipped'] );
		$connection_skipped = ! empty( $state['connection']['skipped'] );
		$is_connected       = Options::is_connection_verified() && ! empty( Options::get_connected_at() );

		$mode_label     = 'auto' === $mode ? __( 'Auto', 'seovault-ai' ) : __( 'Manual', 'seovault-ai' );
		$identity_label = $identity_skipped ? __( 'Skipped', 'seovault-ai' ) : __( 'Completed', 'seovault-ai' );
		$settings_label = __( 'Saved', 'seovault-ai' );

		if ( $import_done ) {
			$import_label = __( 'Completed', 'seovault-ai' );
		} elseif ( $import_skipped ) {
			$import_label = __( 'Skipped', 'seovault-ai' );
		} else {
			$import_label = __( 'Not needed', 'seovault-ai' );
		}

		if ( $is_connected ) {
			$connection_label = __( 'Connected', 'seovault-ai' );
		} elseif ( $connection_skipped ) {
			$connection_label = __( 'Skipped', 'seovault-ai' );
		} else {
			$connection_label = __( 'Not connected', 'seovault-ai' );
		}

		$summary_items = array(
			array(
				'label' => __( 'SEO mode', 'seovault-ai' ),
				'value' => $mode_label,
			),
			array(
				'label' => __( 'Site identity', 'seovault-ai' ),
				'value' => $identity_label,
			),
			array(
				'label' => __( 'SEO settings', 'seovault-ai' ),
				'value' => $settings_label,
			),
			array(
				'label' => __( 'Import', 'seovault-ai' ),
				'value' => $import_label,
			),
			array(
				'label' => __( 'Connection', 'seovault-ai' ),
				'value' => $connection_label,
			),
		);
		?>
		<div class="seovaultseo-wizard-step-content seovaultseo-wizard-finish">
			<h2><?php esc_html_e( "You're all set", 'seovault-ai' ); ?></h2>
			<p><?php esc_html_e( 'SEOvault AI is configured and ready. Your site is optimized with the settings you chose.', 'seovault-ai' ); ?></p>
			<div class="seovaultseo-wizard-hint seovaultseo-wizard-schema-hint">
				<p><?php echo esc_html( \SEOvaultSEO\Schema\SchemaOnboardingGuidance::automatic_basics_copy() ); ?></p>
				<p><?php echo esc_html( \SEOvaultSEO\Schema\SchemaOnboardingGuidance::additional_schema_copy() ); ?></p>
			</div>

			<div class="seovaultseo-wizard-summary">
				<?php foreach ( $summary_items as $item ) : ?>
					<div class="seovaultseo-wizard-summary-row">
						<span class="seovaultseo-wizard-summary-label"><?php echo esc_html( $item['label'] ); ?></span>
						<span class="seovaultseo-wizard-summary-value"><?php echo esc_html( $item['value'] ); ?></span>
					</div>
				<?php endforeach; ?>
			</div>

			<?php if ( ! $is_connected ) : ?>
				<div class="seovaultseo-wizard-finish-connect-link">
					<button type="submit" name="seovaultseo_step_action" value="back" class="button button-link seovaultseo-wizard-skip-step-link">
						<?php esc_html_e( 'Connect to web app', 'seovault-ai' ); ?>
					</button>
				</div>
			<?php endif; ?>
		</div>
		<?php
	}

	private static function render_footer( $step, $state ) {
		$step_index  = self::get_step_index( $step );
		$is_first    = 0 === $step_index;
		$is_last     = count( self::$steps ) - 1 === $step_index;
		$has_sources = 'import' === $step && ! empty( self::detect_migration_sources() );
		$is_connect  = 'connect' === $step;
		$is_finish   = 'finish' === $step;
		?>
		<div class="seovaultseo-wizard-footer">
			<?php if ( ! $is_finish ) : ?>
				<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=seovault-ai-onboarding&skip_onboarding=true' ), 'seovaultseo_skip_onboarding' ) ); ?>" class="button button-link seovaultseo-wizard-skip-link">
					<?php esc_html_e( 'Skip onboarding', 'seovault-ai' ); ?>
				</a>
			<?php else : ?>
				<span></span>
			<?php endif; ?>
			<div class="seovaultseo-wizard-footer-actions">
				<?php if ( 'site_identity' === $step && 'manual' === $state['mode'] ) : ?>
					<button type="submit" name="seovaultseo_step_action" value="skip_identity" class="button button-link seovaultseo-wizard-skip-step-link">
						<?php esc_html_e( 'Set up later', 'seovault-ai' ); ?>
					</button>
				<?php endif; ?>
				<?php if ( $has_sources ) : ?>
					<button type="submit" name="seovaultseo_step_action" value="skip_import" class="button button-link seovaultseo-wizard-skip-step-link">
						<?php esc_html_e( 'Skip import', 'seovault-ai' ); ?>
					</button>
				<?php endif; ?>
				<?php if ( ! $is_first && ! $is_connect ) : ?>
					<button type="submit" name="seovaultseo_step_action" value="back" class="button button-secondary">
						<?php esc_html_e( 'Back', 'seovault-ai' ); ?>
					</button>
				<?php endif; ?>
				<?php if ( $is_connect ) : ?>
					<button type="submit" name="seovaultseo_step_action" value="next" class="button button-primary">
						<?php esc_html_e( 'Next', 'seovault-ai' ); ?>
					</button>
				<?php elseif ( $is_finish ) : ?>
					<button type="submit" name="seovaultseo_step_action" value="finish" class="button button-primary">
						<?php esc_html_e( 'Go to Dashboard', 'seovault-ai' ); ?>
					</button>
				<?php elseif ( ! $is_last ) : ?>
					<button type="submit" name="seovaultseo_step_action" value="next" class="button button-primary">
						<?php if ( 'welcome' === $step ) : ?>
							<?php esc_html_e( 'Start setup', 'seovault-ai' ); ?>
						<?php elseif ( $has_sources ) : ?>
							<?php esc_html_e( 'Import now', 'seovault-ai' ); ?>
						<?php else : ?>
							<?php esc_html_e( 'Next', 'seovault-ai' ); ?>
						<?php endif; ?>
					</button>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	// phpcs:enable Squiz.Commenting.FunctionComment.Missing

	/**
	 * Handle wizard step form submission (admin-post).
	 *
	 * @return void
	 */
	public static function handle_step() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have sufficient permissions.', 'seovault-ai' ) );
		}

		if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), self::NONCE_ACTION ) ) {
			wp_die( esc_html__( 'Security check failed.', 'seovault-ai' ) );
		}

		$state  = self::get_state();
		$step   = isset( $_POST['seovaultseo_step'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_step'] ) ) : $state['step'];
		$action = isset( $_POST['seovaultseo_step_action'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_step_action'] ) ) : 'next';

		if ( ! in_array( $step, self::$steps, true ) ) {
			$step = $state['step'];
		}

		if ( 'back' === $action ) {
			$state['step'] = self::get_previous_step( $step, $state );
		} elseif ( 'skip_identity' === $action ) {
			$state['identity']['skipped'] = true;
			$state['step']                = 'seo_settings';
		} elseif ( 'skip_import' === $action ) {
			$state['import']['skipped'] = true;
			$state['step']              = 'connect';
		} elseif ( 'skip_connection' === $action ) {
			$state['connection']['skipped'] = true;
			$state['step']                  = 'finish';
		} elseif ( 'finish' === $action ) {
			$state['status'] = 'completed';
			$state['step']   = 'finish';
			self::update_state( $state );
			self::mark_completed( false );

			wp_safe_redirect( admin_url( 'admin.php?page=seovault-ai' ) );
			exit;
		} else {
			$state         = self::save_step_data( $step, $state );
			$state['step'] = self::get_next_step( $step, $state );
		}

		self::update_state( $state );

		wp_safe_redirect( admin_url( 'admin.php?page=seovault-ai-onboarding' ) );
		exit;
	}

	/**
	 * Determine the previous step for the Back action, respecting skipped steps.
	 *
	 * @param string $step  Current step slug.
	 * @param array  $state Current wizard state.
	 * @return string
	 */
	private static function get_previous_step( $step, $state ) {
		if ( 'seo_settings' === $step && ! empty( $state['identity']['skipped'] ) ) {
			return 'seo_mode';
		}
		if ( 'connect' === $step ) {
			if ( ! empty( $state['import']['done'] ) || ! empty( $state['import']['skipped'] ) ) {
				return 'import';
			}
			$sources = self::detect_migration_sources();
			return empty( $sources ) ? 'seo_settings' : 'import';
		}
		$index = self::get_step_index( $step );
		return self::$steps[ max( 0, $index - 1 ) ];
	}

	/**
	 * Determine the next step for the Next action, respecting mode and skips.
	 *
	 * @param string $step  Current step slug.
	 * @param array  $state Current wizard state.
	 * @return string
	 */
	private static function get_next_step( $step, $state ) {
		if ( 'welcome' === $step ) {
			return 'seo_mode';
		}
		if ( 'seo_mode' === $step ) {
			return 'manual' === $state['mode'] ? 'seo_settings' : 'site_identity';
		}
		if ( 'seo_settings' === $step ) {
			$sources = self::detect_migration_sources();
			return empty( $sources ) ? 'connect' : 'import';
		}
		$index = self::get_step_index( $step );
		return $index < count( self::$steps ) - 1 ? self::$steps[ $index + 1 ] : self::$steps[ count( self::$steps ) - 1 ];
	}

	/**
	 * Save the data submitted for the current step.
	 *
	 * @param string $step  Current step slug.
	 * @param array  $state Current wizard state.
	 * @return array
	 */
	private static function save_step_data( $step, $state ) {
		// phpcs:disable WordPress.Security.NonceVerification.Missing -- Only called from handle_step() after wp_verify_nonce().
		switch ( $step ) {
			case 'seo_mode':
				$mode = isset( $_POST['seovaultseo_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_mode'] ) ) : 'auto';
				if ( ! in_array( $mode, array( 'auto', 'manual' ), true ) ) {
					$mode = 'auto';
				}
				$state['mode'] = $mode;
				if ( 'manual' === $mode ) {
					$state['identity']['skipped'] = true;
				}
				break;
			case 'site_identity':
				$site_type   = isset( $_POST['seovaultseo_site_type'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_site_type'] ) ) : '';
				$entity_type = isset( $_POST['seovaultseo_entity_type'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_entity_type'] ) ) : 'organization';
				$name        = isset( $_POST['seovaultseo_identity_name'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_identity_name'] ) ) : '';
				$logo_id     = isset( $_POST['seovaultseo_logo_id'] ) ? absint( $_POST['seovaultseo_logo_id'] ) : 0;
				$logo_url    = isset( $_POST['seovaultseo_logo_url'] ) ? esc_url_raw( wp_unslash( $_POST['seovaultseo_logo_url'] ) ) : '';
				$social_id   = isset( $_POST['seovaultseo_social_image_id'] ) ? absint( $_POST['seovaultseo_social_image_id'] ) : 0;
				$social_url  = isset( $_POST['seovaultseo_social_image_url'] ) ? esc_url_raw( wp_unslash( $_POST['seovaultseo_social_image_url'] ) ) : '';

				if ( ! in_array( $entity_type, array( 'person', 'organization' ), true ) ) {
					$entity_type = 'organization';
				}
				$valid_site_types = array( 'blog', 'business', 'ecommerce', 'portfolio', 'other' );
				if ( ! in_array( $site_type, $valid_site_types, true ) ) {
					$site_type = '';
				}

				$state['identity']['site_type']        = $site_type;
				$state['identity']['entity_type']      = $entity_type;
				$state['identity']['name']             = $name;
				$state['identity']['logo_id']          = $logo_id;
				$state['identity']['logo_url']         = $logo_url;
				$state['identity']['social_image_id']  = $social_id;
				$state['identity']['social_image_url'] = $social_url;
				$state['identity']['skipped']          = false;

				\SEOvaultSEO\Schema\SchemaOnboardingGuidance::apply_publisher_identity( $entity_type );
				break;

			case 'seo_settings':
				$home_title  = isset( $_POST['seovaultseo_home_title'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_home_title'] ) ) : '';
				$home_desc   = isset( $_POST['seovaultseo_home_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['seovaultseo_home_description'] ) ) : '';
				$output_mode = isset( $_POST['seovaultseo_output_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_output_mode'] ) ) : 'auto';
				$soc_title   = isset( $_POST['seovaultseo_social_title_template'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_social_title_template'] ) ) : '';
				$soc_desc    = isset( $_POST['seovaultseo_social_description_template'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_social_description_template'] ) ) : '';

				if ( ! in_array( $output_mode, array( 'auto', 'manual', 'disabled' ), true ) ) {
					$output_mode = 'auto';
				}

				$noindex_empty = isset( $_POST['seovaultseo_robots_empty_taxonomies_noindex'] );
				$nofollow_ext  = isset( $_POST['seovaultseo_robots_nofollow_external'] );
				$new_tab_ext   = isset( $_POST['seovaultseo_robots_new_tab_external'] );

				$state['settings']['home_title']                      = $home_title;
				$state['settings']['home_description']                = $home_desc;
				$state['settings']['output_mode']                     = $output_mode;
				$state['settings']['social_title_template']           = $soc_title;
				$state['settings']['social_description_template']     = $soc_desc;
				$state['settings']['robots_empty_taxonomies_noindex'] = (bool) $noindex_empty;
				$state['settings']['robots_nofollow_external']        = (bool) $nofollow_ext;
				$state['settings']['robots_new_tab_external']         = (bool) $new_tab_ext;

				// Apply settings to the plugin's Options API.
				$prev_output_mode = Options::get_output_mode();
				Options::set_home_title( $home_title );
				Options::set_home_description( $home_desc );
				Options::set_output_mode( $output_mode );
				Options::set_title_template( $soc_title );
				Options::set_description_template( $soc_desc );
				Options::set_empty_archives_noindex( $noindex_empty ? 'yes' : 'no' );
				Options::set( 'nofollow_external', $nofollow_ext ? '1' : '0' );
				Options::set( 'new_tab_external', $new_tab_ext ? '1' : '0' );

				// Flush rewrite rules if output mode changed (affects sitemap routing).
				if ( $prev_output_mode !== $output_mode ) {
					flush_rewrite_rules();
				}
				break;

			case 'import':
				$source  = isset( $_POST['seovaultseo_import_source'] ) ? sanitize_text_field( wp_unslash( $_POST['seovaultseo_import_source'] ) ) : '';
				$sources = self::detect_migration_sources();

				if ( ! empty( $source ) && isset( $sources[ $source ] ) ) {
					$result                     = MigrationManager::import_global( $source );
					$state['import']['done']    = ! empty( $result['success'] );
					$state['import']['source']  = $source;
					$state['import']['skipped'] = false;
				}
				break;

			case 'connect':
				$state['connection']['skipped'] = false;
				break;
		}
		// phpcs:enable WordPress.Security.NonceVerification.Missing
		return $state;
	}

	/**
	 * Handle skip-onboarding form submission (admin-post).
	 *
	 * @return void
	 */
	public static function handle_skip() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have sufficient permissions.', 'seovault-ai' ) );
		}

		if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), self::NONCE_ACTION ) ) {
			wp_die( esc_html__( 'Security check failed.', 'seovault-ai' ) );
		}

		$state           = self::get_state();
		$state['status'] = 'skipped';
		self::update_state( $state );
		self::mark_completed( true );

		wp_safe_redirect( admin_url( 'admin.php?page=seovault-ai' ) );
		exit;
	}

	/**
	 * Handle the restart-onboarding action from General > Tools.
	 *
	 * @return void
	 */
	public static function handle_restart() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have sufficient permissions.', 'seovault-ai' ) );
		}

		if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), self::RESTART_NONCE ) ) {
			wp_die( esc_html__( 'Security check failed.', 'seovault-ai' ) );
		}
		self::restart_state();

		wp_safe_redirect( admin_url( 'admin.php?page=seovault-ai-onboarding' ) );
		exit;
	}
}
