<?php
/**
 * Setup wizard — first-run onboarding.
 *
 * Routes the wizard page, manages step state, handles form POSTs and
 * AJAX, and gates Free vs Pro features at the template level. Owns
 * the redirect-from-Help "Run setup wizard" button as well.
 *
 * @package SkyEmagSync
 */

namespace SkyEmagSync\Admin;

use SkyEmagSync\Options;
use SkyEmagSync\Plugin;

defined( 'ABSPATH' ) || exit;

class SetupWizard {

	const PAGE_SLUG = 'skyservers-sync-setup';
	const NONCE     = 'skyemag_setup_wizard';

	// AJAX action names.
	const AJAX_TEST_CONNECTION = 'skyemag_setup_test_connection';
	const AJAX_BOOTSTRAP_STEP  = 'skyemag_setup_bootstrap_step';
	const AJAX_SKIP            = 'skyemag_setup_skip';
	const AJAX_DISMISS_NOTICE        = 'skyemag_setup_dismiss_notice';
	const USER_META_NOTICE_DISMISSED = 'skyemag_setup_notice_dismissed';

	// admin-post action names.
	const POST_NAVIGATE = 'skyemag_setup_navigate';
	const POST_FINISH   = 'skyemag_setup_finish';
	const POST_RESTART  = 'skyemag_setup_restart';

	public function register(): void {
		// AJAX
		add_action( 'wp_ajax_' . self::AJAX_TEST_CONNECTION, [ $this, 'ajax_test_connection' ] );
		add_action( 'wp_ajax_' . self::AJAX_BOOTSTRAP_STEP,  [ $this, 'ajax_bootstrap_step' ] );
		add_action( 'wp_ajax_' . self::AJAX_SKIP,            [ $this, 'ajax_skip' ] );
		add_action( 'wp_ajax_' . self::AJAX_DISMISS_NOTICE, [ $this, 'ajax_dismiss_notice' ] );

		// admin-post (form POST without page reload pivot)
		add_action( 'admin_post_' . self::POST_NAVIGATE, [ $this, 'handle_navigate' ] );
		add_action( 'admin_post_' . self::POST_FINISH,   [ $this, 'handle_finish' ] );
		add_action( 'admin_post_' . self::POST_RESTART,  [ $this, 'handle_restart' ] );
	}

	/**
	 * Total step count for the current build. The wizard adds an extra
	 * "Publish & Orders" step (Step 5) only on builds that physically ship
	 * the corresponding template file. The Free WordPress.org build strips
	 * `wizard-step-5.php` via the Freemius @fs_premium_only directive, so
	 * the step count there is derived from file presence — not a license
	 * check — and falls back to 5 steps with the "Done" page at the end.
	 */
	public static function total_steps(): int {
		return self::has_publish_step_template()
			? Options::SETUP_STEP_COUNT_PRO
			: Options::SETUP_STEP_COUNT_FREE;
	}

	/** Resolves the step index to its template file path. */
	public static function step_template( int $step ): string {
		$dir       = SKYEMAG_PLUGIN_DIR . 'includes/Admin/templates/';
		$has_step5 = self::has_publish_step_template();
		switch ( $step ) {
			case 1: return $dir . 'wizard-step-1.php';
			case 2: return $dir . 'wizard-step-2.php';
			case 3: return $dir . 'wizard-step-3.php';
			case 4: return $dir . 'wizard-step-4.php';
			case 5:
				return $has_step5
					? $dir . 'wizard-step-5.php'
					: $dir . 'wizard-step-done.php';
			case 6:
				return $dir . 'wizard-step-done.php';
			default:
				return $dir . 'wizard-step-1.php';
		}
	}

	/**
	 * Whether this build ships the optional Publish & Orders wizard step.
	 * Result cached for the request to avoid repeated filesystem stats.
	 */
	private static function has_publish_step_template(): bool {
		static $cached;
		if ( null === $cached ) {
			$cached = file_exists( SKYEMAG_PLUGIN_DIR . 'includes/Admin/templates/wizard-step-5.php' );
		}
		return $cached;
	}

	/** Whether the given step is the Done screen for the current tier. */
	public static function is_done_step( int $step ): bool {
		return $step >= self::total_steps();
	}

	/** Renders the full wizard page (called by the menu callback). */
	public static function render_page(): void {
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to access this page.', 'skyservers-product-sync-for-emag' ) );
		}
		$step      = Options::setup_step();
		$total     = self::total_steps();
		$completed = Options::setup_completed();

		// If the user already completed the wizard, allow re-entry but keep
		// the resume logic — they came here from the Help button.

		// Clamp step to a valid value for the current tier.
		if ( $step > $total ) {
			$step = $total;
		}

		include SKYEMAG_PLUGIN_DIR . 'includes/Admin/templates/wizard-shell.php';
	}

	// ── Placeholder method bodies (filled in later tasks) ────────────────

	public function ajax_test_connection(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'skyservers-product-sync-for-emag' ) ], 403 );
		}

		$username = isset( $_POST['username'] ) ? sanitize_text_field( wp_unslash( $_POST['username'] ) ) : '';
		$password = isset( $_POST['password'] ) ? wp_unslash( $_POST['password'] ) : '';
		$region   = isset( $_POST['region'] )   ? sanitize_key( wp_unslash( $_POST['region'] ) ) : 'ro';

		if ( '' === $username || '' === $password ) {
			wp_send_json_error( [ 'message' => __( 'Enter username and password before testing.', 'skyservers-product-sync-for-emag' ) ] );
		}

		// EmagClient expects raw plaintext password — it builds Basic auth directly
		// from $this->password using base64_encode( username . ':' . password ).
		// Options::api_credentials() always returns the decrypted (raw) value, so
		// any override we pass here must also be raw. Do NOT encrypt here.
		$client = new \SkyEmagSync\Api\EmagClient( $username, $password, $region );

		$start         = microtime( true );
		$order_result  = $client->count_orders();
		$ms            = (int) ( ( microtime( true ) - $start ) * 1000 );

		if ( is_wp_error( $order_result ) ) {
			$err_code = (string) $order_result->get_error_code();
			$err_msg  = (string) $order_result->get_error_message();
			wp_send_json_error( [
				'message' => self::test_connection_error_message( $err_code, $err_msg ),
			] );
		}

		// count_orders() returns standard EmagClient shape: ['success'=>bool, 'data'=>mixed, 'error'=>string].
		// On failure (success=false), data is null and error has the message.
		if ( ! $order_result['success'] ) {
			$err_msg = (string) ( $order_result['error'] ?: __( 'Connection failed.', 'skyservers-product-sync-for-emag' ) );
			wp_send_json_error( [ 'message' => self::test_connection_error_message( '', $err_msg ) ] );
		}

		$order_data  = is_array( $order_result['data'] ) ? $order_result['data'] : [];
		$order_count = (int) ( $order_data['noOfItems'] ?? $order_data['total'] ?? $order_data['count'] ?? 0 );

		// Also fetch the product/offer count so the user sees both totals at a glance.
		// If this call fails for any reason we still report the successful order count
		// rather than turning the whole probe into an error.
		$offer_result = $client->count_offers();
		$offer_count  = null;
		if ( ! is_wp_error( $offer_result ) && is_array( $offer_result ) && ! empty( $offer_result['success'] ) ) {
			$offer_data  = is_array( $offer_result['data'] ) ? $offer_result['data'] : [];
			$offer_count = (int) ( $offer_data['noOfItems'] ?? $offer_data['total'] ?? $offer_data['count'] ?? 0 );
		}

		if ( null === $offer_count ) {
			$message = sprintf(
				/* translators: %1$d: order count, %2$d: latency ms */
				esc_html__( 'Connection OK — %1$d orders accessible (latency %2$dms)', 'skyservers-product-sync-for-emag' ),
				$order_count,
				$ms
			);
		} else {
			$message = sprintf(
				/* translators: %1$d: order count, %2$d: product/offer count, %3$d: latency ms */
				esc_html__( 'Connection OK — %1$d orders accessible | %2$d products in catalog (latency %3$dms)', 'skyservers-product-sync-for-emag' ),
				$order_count,
				$offer_count,
				$ms
			);
		}

		wp_send_json_success( [ 'message' => $message ] );
	}

	public function ajax_bootstrap_step(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'skyservers-product-sync-for-emag' ) ], 403 );
		}

		// Bumps execution time so very large eMAG accounts don't trip the
		// 30s default during the wizard run. Best-effort only — set_time_limit
		// is a no-op (or warning-emitting) on hosts that run PHP in safe
		// mode or disable the function via disable_functions. Guarding with
		// function_exists keeps WP coding standards happy without wrapping
		// the call in @-suppression.
		if ( function_exists( 'set_time_limit' ) ) {
			set_time_limit( 120 );
		}

		$result = \SkyEmagSync\Sync\Bootstrap::run();

		if ( empty( $result['success'] ) ) {
			$first_error = isset( $result['errors'][0] ) ? (string) $result['errors'][0] : __( 'Bootstrap import failed.', 'skyservers-product-sync-for-emag' );
			wp_send_json_error( [ 'message' => $first_error ] );
		}

		wp_send_json_success( [
			'matched'           => (int) ( $result['matched'] ?? 0 ),
			'unmatched'         => (int) ( $result['unmatched'] ?? 0 ),
			'skipped'           => (int) ( $result['skipped'] ?? 0 ),
			'captured_margins'  => (int) ( $result['captured_margins'] ?? 0 ),
			'total'             => (int) ( ( $result['matched'] ?? 0 ) + ( $result['unmatched'] ?? 0 ) ),
		] );
	}

	public function ajax_skip(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'skyservers-product-sync-for-emag' ) ], 403 );
		}

		// Defensive: if the seller skipped before completing the credentials
		// step, force the master sync switch OFF so we don't accidentally
		// try to call the eMAG API with empty credentials. They can flip it
		// back on after entering credentials in Settings → SkyServers Sync.
		$creds = Options::api_credentials();
		if ( empty( $creds['username'] ) || empty( $creds['password'] ) ) {
			update_option( Options::MASTER_ENABLED, false );
		}

		update_option( Options::SETUP_COMPLETED, true );
		update_option( Options::SETUP_SKIPPED,   true );
		delete_option( Options::SETUP_STEP );

		wp_send_json_success( [
			'redirect' => admin_url( 'admin.php?page=' . Menu::SETTINGS_SLUG ),
		] );
	}

	public function handle_navigate(): void {
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to perform this action.', 'skyservers-product-sync-for-emag' ) );
		}
		check_admin_referer( self::NONCE );

		$direction    = isset( $_POST['direction'] ) ? sanitize_key( wp_unslash( $_POST['direction'] ) ) : 'next';
		$current_step = isset( $_POST['current_step'] ) ? (int) $_POST['current_step'] : Options::setup_step();
		$total        = self::total_steps();

		if ( 'back' === $direction ) {
			$next = max( 1, $current_step - 1 );
			update_option( Options::SETUP_STEP, $next );
			wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
			exit;
		}

		// Forward navigation: save the form values for $current_step, then advance.
		$this->save_step_values( $current_step );

		$next = min( $total, $current_step + 1 );
		update_option( Options::SETUP_STEP, $next );

		wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
		exit;
	}

	public function handle_finish(): void {
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to perform this action.', 'skyservers-product-sync-for-emag' ) );
		}
		check_admin_referer( self::NONCE );

		$enable_master = isset( $_POST['skyemag_enable_master'] ) ? '1' : '';
		update_option( Options::MASTER_ENABLED, (bool) $enable_master );

		// Mark complete and clear step pointer.
		update_option( Options::SETUP_COMPLETED, true );
		delete_option( Options::SETUP_STEP );

		// Always redirect to Products page so user can verify before turning sync on.
		wp_safe_redirect( admin_url( 'admin.php?page=' . Menu::PRODUCTS_SLUG ) );
		exit;
	}

	public function handle_restart(): void {
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to perform this action.', 'skyservers-product-sync-for-emag' ) );
		}
		check_admin_referer( self::NONCE );

		// Reset state so the wizard reopens at step 1 fresh. Saved options
		// (credentials, margin, etc.) are preserved — only the wizard
		// pointer + completed/skipped flags are cleared.
		update_option( Options::SETUP_STEP,      1 );
		update_option( Options::SETUP_COMPLETED, false );
		update_option( Options::SETUP_SKIPPED,   false );

		wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
		exit;
	}

	/**
	 * Persists the form values for the step the user just submitted.
	 * Each step writes only its own options. Validation that fails (e.g.
	 * regex on Step 4) redirects back to the same step with an admin notice.
	 */
	private function save_step_values( int $step ): void {
		switch ( $step ) {
			case 1:
				// Welcome has no form fields.
				return;
			case 2:
				$this->save_step_2_credentials();
				return;
			case 3:
				$this->save_step_3_pricing();
				return;
			case 4:
				$this->save_step_4_schedule();
				return;
			case 5:
				// Only invoke the publish-defaults save when this build
				// actually exposes Step 5 as a real form (i.e., the
				// `wizard-step-5.php` template ships in the install). On
				// builds where the template is stripped, Step 5 renders
				// the Done page and there is no Step 5 form payload to
				// process.
				if ( self::has_publish_step_template() ) {
					$this->save_step_5_publish();
				}
				return;
		}
	}

	private function save_step_2_credentials(): void {
		$username     = isset( $_POST['skyemag_username'] ) ? sanitize_text_field( wp_unslash( $_POST['skyemag_username'] ) ) : '';
		$password_raw = isset( $_POST['skyemag_password'] ) ? wp_unslash( $_POST['skyemag_password'] ) : '';
		$region       = isset( $_POST['skyemag_region'] )   ? sanitize_key( wp_unslash( $_POST['skyemag_region'] ) ) : 'ro';
		$api_code     = isset( $_POST['skyemag_api_code'] ) ? sanitize_text_field( wp_unslash( $_POST['skyemag_api_code'] ) ) : '';

		// Pull existing credentials. If the user left the password field blank
		// (edit mode — keep existing), preserve the stored encrypted value.
		$existing = (array) Options::get( Options::API_CREDENTIALS, [] );
		if ( '' === $password_raw && isset( $existing['password'] ) && '' !== (string) $existing['password'] ) {
			$password_stored = $existing['password']; // already encrypted (skyemag_enc:…)
		} else {
			$password_stored = Options::encrypt( $password_raw );
		}

		$creds = [
			'username' => $username,
			'password' => $password_stored,
			'region'   => $region,
			'api_code' => $api_code,
		];
		update_option( Options::API_CREDENTIALS, $creds );
	}

	private function save_step_3_pricing(): void {
		$margin     = isset( $_POST['skyemag_margin'] )     ? (float) wp_unslash( $_POST['skyemag_margin'] )     : 0.0;
		$min_margin = isset( $_POST['skyemag_min_margin'] ) ? wp_unslash( $_POST['skyemag_min_margin'] ) : '';
		$max_margin = isset( $_POST['skyemag_max_margin'] ) ? wp_unslash( $_POST['skyemag_max_margin'] ) : '';
		$stock_pct  = isset( $_POST['skyemag_stock_pct'] )  ? (int)   wp_unslash( $_POST['skyemag_stock_pct'] ) : 100;
		$rounding   = isset( $_POST['skyemag_rounding'] )   ? sanitize_key( wp_unslash( $_POST['skyemag_rounding'] ) ) : '2decimals';
		// Radio group: '0' = WC sale price (default), '1' = WC regular price.
		$use_reg    = isset( $_POST['skyemag_use_regular_price'] ) && '1' === (string) wp_unslash( $_POST['skyemag_use_regular_price'] ) ? '1' : '';
		$bs_mode    = isset( $_POST['skyemag_bootstrap_mode'] ) ? sanitize_key( wp_unslash( $_POST['skyemag_bootstrap_mode'] ) ) : Options::BOOTSTRAP_MODE_DEFAULT;

		update_option( Options::GLOBAL_MARGIN, $margin );
		update_option( Options::GLOBAL_MIN_MARGIN, '' === $min_margin ? '' : (float) $min_margin );
		update_option( Options::GLOBAL_MAX_MARGIN, '' === $max_margin ? '' : (float) $max_margin );
		update_option( Options::GLOBAL_STOCK_PCT,  max( 0, min( 100, $stock_pct ) ) );
		if ( in_array( $rounding, Options::ROUNDING_RULES, true ) ) {
			update_option( Options::ROUNDING_RULE, $rounding );
		}
		update_option( Options::USE_REGULAR_PRICE, (bool) $use_reg );

		if ( in_array( $bs_mode, Options::BOOTSTRAP_MODES, true ) ) {
			update_option( Options::BOOTSTRAP_MODE, $bs_mode );
		}

		// Min WC Stock.
		if ( isset( $_POST['skyemag_min_wc_stock'] ) ) {
			update_option( Options::MIN_WC_STOCK, max( 0, (int) wp_unslash( $_POST['skyemag_min_wc_stock'] ) ) );
		}
	}

	private function save_step_4_schedule(): void {
		$schedule          = isset( $_POST['skyemag_schedule'] ) ? sanitize_key( wp_unslash( $_POST['skyemag_schedule'] ) ) : Options::DEFAULT_SCHEDULE;
		$min_price         = isset( $_POST['skyemag_min_price'] ) ? (float) wp_unslash( $_POST['skyemag_min_price'] ) : 0;
		$max_weight        = isset( $_POST['skyemag_max_weight'] ) ? (float) wp_unslash( $_POST['skyemag_max_weight'] ) : 0;
		$skip_sku_pattern  = isset( $_POST['skyemag_skip_sku_pattern'] ) ? sanitize_text_field( wp_unslash( $_POST['skyemag_skip_sku_pattern'] ) ) : '';
		$skip_zero_stock   = isset( $_POST['skyemag_skip_zero_stock'] ) ? '1' : '';

		if ( ! in_array( $schedule, Options::SCHEDULES, true ) ) {
			$schedule = Options::DEFAULT_SCHEDULE;
		}
		update_option( Options::SYNC_SCHEDULE, $schedule );

		// Regex validation: invalid pattern blocks save and redirects back.
		if ( '' !== $skip_sku_pattern ) {
			// suppress warnings; @preg_match returns false for malformed patterns.
			$valid = @preg_match( $skip_sku_pattern, '' );
			if ( false === $valid ) {
				set_transient(
					'skyemag_setup_validation_error',
					sprintf(
						/* translators: %s: regex value the user submitted */
						__( 'Invalid regex pattern: %s. Common fix: ensure the pattern is wrapped in delimiters like /pattern/i.', 'skyservers-product-sync-for-emag' ),
						$skip_sku_pattern
					),
					60
				);
				// Don't advance — redirect back to the same step.
				wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
				exit;
			}
		}

		update_option( Options::SYNC_MIN_PRICE_EX_VAT, max( 0, $min_price ) );
		update_option( Options::SYNC_MAX_WEIGHT_KG,    max( 0, $max_weight ) );
		update_option( Options::SYNC_SKIP_SKU_PATTERN, $skip_sku_pattern );
		update_option( Options::SYNC_SKIP_ZERO_STOCK,  (bool) $skip_zero_stock );
	}

	private function save_step_5_publish(): void {
		// Publishing defaults
		$brand_sources    = isset( $_POST['skyemag_brand_sources'] )    ? sanitize_text_field( wp_unslash( $_POST['skyemag_brand_sources'] ) )    : 'pa_brand,_brand';
		$ean_sources      = isset( $_POST['skyemag_ean_sources'] )      ? sanitize_text_field( wp_unslash( $_POST['skyemag_ean_sources'] ) )      : '_global_unique_id';
		$warranty         = isset( $_POST['skyemag_publish_warranty'] ) ? (int) wp_unslash( $_POST['skyemag_publish_warranty'] ) : 24;
		$resupply         = isset( $_POST['skyemag_publish_resupply'] ) ? (int) wp_unslash( $_POST['skyemag_publish_resupply'] ) : 14;
		$min_image_pixels = isset( $_POST['skyemag_min_image_pixels'] ) ? (int) wp_unslash( $_POST['skyemag_min_image_pixels'] ) : 600;
		$auto_sanitize    = isset( $_POST['skyemag_auto_sanitize_chars'] ) ? '1' : '';
		$publish_live     = isset( $_POST['skyemag_publish_live'] )       ? '1' : '';

		// Order import (optional sub-section)
		$order_import_enabled = isset( $_POST['skyemag_order_import_enabled'] ) ? '1' : '';
		$auto_decrement_stock = isset( $_POST['skyemag_order_auto_decrement_stock'] ) ? '1' : '';

		update_option( Options::BRAND_SOURCES,         array_filter( array_map( 'trim', explode( ',', $brand_sources ) ) ) );
		update_option( Options::EAN_SOURCES,           array_filter( array_map( 'trim', explode( ',', $ean_sources ) ) ) );
		update_option( Options::PUBLISH_WARRANTY,      max( 0, $warranty ) );
		update_option( Options::PUBLISH_RESUPPLY_DAYS, max( 0, $resupply ) );
		update_option( Options::MIN_IMAGE_PIXELS,      max( 0, $min_image_pixels ) );
		update_option( Options::AUTO_SANITIZE_CHARS,   (bool) $auto_sanitize );
		update_option( Options::PUBLISH_LIVE,          (bool) $publish_live );

		update_option( Options::ORDER_IMPORT_ENABLED,         (bool) $order_import_enabled );
		update_option( Options::ORDER_AUTO_DECREMENT_STOCK,   (bool) $auto_decrement_stock );
		// Dry run defaults to ON (saved value is 'yes' string per Options memory).
		if ( '' === (string) Options::get( Options::ORDER_DRY_RUN, '' ) ) {
			update_option( Options::ORDER_DRY_RUN, 'yes' );
		}
	}

	/**
	 * Pro feature copy used in the Free-only showcase cards. Per-step
	 * arrays so each card stays contextual to what the user is configuring.
	 */
	public static function pro_features_for_step( int $step ): array {
		switch ( $step ) {
			case 1:
				return [
					__( 'Publish new products to eMAG', 'skyservers-product-sync-for-emag' ),
					__( 'Automatic order import + AWB shipping labels', 'skyservers-product-sync-for-emag' ),
					__( 'Buy Button rank tracking with drop alerts', 'skyservers-product-sync-for-emag' ),
					__( 'Email notifications for sync errors and competitor alerts', 'skyservers-product-sync-for-emag' ),
				];
			case 3:
				return [
					__( 'Publish new products from WooCommerce to eMAG', 'skyservers-product-sync-for-emag' ),
					__( 'Buy Button rank tracking with drop alerts', 'skyservers-product-sync-for-emag' ),
					__( 'AWB shipping label issuance and printing', 'skyservers-product-sync-for-emag' ),
				];
			case 4:
				return [
					__( 'Notifications page for sync errors', 'skyservers-product-sync-for-emag' ),
					__( 'Blocked product alerts and competitor undercut emails', 'skyservers-product-sync-for-emag' ),
					__( '5 email events with per-event throttling', 'skyservers-product-sync-for-emag' ),
				];
			default:
				return [];
		}
	}

	/**
	 * Renders the wizard navigation buttons (Skip / Back / Next). Caller
	 * is responsible for wrapping the inputs and these buttons in the same
	 * <form> with the navigate action + nonce + direction hidden fields.
	 */
	public static function render_step_buttons( int $step ): void {
		$total = self::total_steps();
		?>
		<div class="skyemag-wizard-footer">
			<div>
				<button type="button" class="skyemag-wizard-skip-link" data-skyemag-wizard-skip>
					<?php esc_html_e( 'Skip setup', 'skyservers-product-sync-for-emag' ); ?>
				</button>
			</div>

			<div class="skyemag-wizard-footer-actions">
				<?php if ( $step > 1 ) : ?>
					<button type="submit" class="button" name="direction" value="back">
						<?php esc_html_e( '← Back', 'skyservers-product-sync-for-emag' ); ?>
					</button>
				<?php endif; ?>
				<?php if ( $step < $total ) : ?>
					<button type="submit" class="button button-primary" name="direction" value="next">
						<?php esc_html_e( 'Next →', 'skyservers-product-sync-for-emag' ); ?>
					</button>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Builds the user-facing error message for a Test Connection failure.
	 * Appends a hint about the most common causes (wrong credentials or
	 * IP not whitelisted) when the response signals an auth issue.
	 */
	private static function test_connection_error_message( string $code, string $msg ): string {
		$prefix = '';
		if ( '' !== $code ) {
			$prefix = sprintf(
				/* translators: %1$s: error code, %2$s: error message */
				esc_html__( '%1$s: %2$s', 'skyservers-product-sync-for-emag' ),
				$code,
				$msg
			);
		} else {
			$prefix = $msg;
		}

		$haystack = strtolower( $code . ' ' . $msg );
		$is_auth_failure = false !== strpos( $haystack, '401' )
			|| false !== strpos( $haystack, '403' )
			|| false !== strpos( $haystack, 'unauthorized' )
			|| false !== strpos( $haystack, 'forbidden' );

		if ( $is_auth_failure ) {
			$prefix .= ' — ' . esc_html__( 'wrong username/password, or your server IP is not whitelisted in your eMAG account.', 'skyservers-product-sync-for-emag' );
		}

		return $prefix;
	}

	/**
	 * Helper: opens the wizard form for the given step. Pair with
	 * </form> in the template after inputs + render_step_buttons().
	 */
	public static function open_step_form( int $step ): void {
		?>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="skyemag-wizard-form">
			<?php wp_nonce_field( self::NONCE ); ?>
			<input type="hidden" name="action"       value="<?php echo esc_attr( self::POST_NAVIGATE ); ?>">
			<input type="hidden" name="current_step" value="<?php echo (int) $step; ?>">
		<?php
	}

	public function ajax_dismiss_notice(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		if ( ! current_user_can( Menu::CAPABILITY ) ) {
			wp_send_json_error( [], 403 );
		}
		update_user_meta( get_current_user_id(), self::USER_META_NOTICE_DISMISSED, '1' );
		wp_send_json_success();
	}

	/** Whether the current user has dismissed the skipped-wizard notice. */
	public static function notice_dismissed_by_current_user(): bool {
		return '1' === (string) get_user_meta( get_current_user_id(), self::USER_META_NOTICE_DISMISSED, true );
	}
}
