<?php
/**
 * Mersal Suite — shared setup wizard.
 *
 * Every Mersal plugin ships the same guided first-run experience: a welcome
 * screen that explains what the plugin does, the Mersal account link, then the
 * handful of settings the plugin genuinely cannot work without, and a finish
 * screen. Plugins only describe their own steps; the chrome, the progress rail,
 * the account step, the not-subscribed gate, saving and resuming all live here
 * so the three plugins stay identical.
 *
 * Like the rest of the Suite SDK this file is shipped inside every plugin and
 * only the newest copy is ever loaded.
 *
 * @package Mersal_Suite
 */

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

if ( class_exists( 'Mersal_Suite_Wizard' ) ) {
	return;
}

/**
 * The shared wizard.
 */
class Mersal_Suite_Wizard {

	/** Option holding every plugin's wizard progress. */
	const STATE_KEY = 'mersal_suite_wizard_state';

	/** Where a customer buys the add-on that unlocks a plugin. */
	const SUBSCRIBE_URL = 'https://mersal.it/user/addons/';

	/** @var array<string,array> Registered wizards keyed by plugin id. */
	private static $wizards = array();

	/** @var bool Whether the shared hooks are wired. */
	private static $booted = false;

	/**
	 * Register a plugin's wizard.
	 *
	 * @param array $args {
	 *     @type string   $id      Plugin id ('otp' | 'nt' | 'cm').
	 *     @type array    $title   ['ar'=>..,'en'=>..].
	 *     @type string   $icon    Material Symbols name.
	 *     @type string   $accent  Hex accent.
	 *     @type string   $app     mersal.it add-on slug, for the subscribe link.
	 *     @type string   $field   POST array name the settings live under.
	 *     @type callable $save    Receives the sanitised POST array.
	 *     @type array    $intro   Welcome copy: lead + points[].
	 *     @type array    $account Callables describing the Mersal connection.
	 *     @type array    $steps   Settings steps.
	 *     @type string   $finish  Route to open when the wizard completes.
	 * }
	 */
	public static function register( array $args ) {
		if ( empty( $args['id'] ) ) {
			return;
		}
		self::$wizards[ $args['id'] ] = wp_parse_args(
			$args,
			array(
				'title'   => array( 'ar' => '', 'en' => '' ),
				'icon'    => 'extension',
				'accent'  => '#0f7a4a',
				'app'     => '',
				'field'   => '',
				'save'    => null,
				'intro'   => array(),
				'account' => array(),
				'steps'   => array(),
				'finish'  => $args['id'] . '.dash',
			)
		);
		self::boot();
	}

	/**
	 * Wire the shared hooks once.
	 */
	public static function boot() {
		if ( self::$booted || ! is_admin() ) {
			return;
		}
		self::$booted = true;
		add_action( 'admin_post_mersal_suite_wizard', array( __CLASS__, 'handle_post' ) );
		add_action( 'wp_ajax_mersal_suite_wizard_step', array( __CLASS__, 'ajax_step' ) );
		add_action( 'admin_init', array( __CLASS__, 'maybe_first_run_redirect' ), 30 );
	}

	/**
	 * Route id for a plugin's wizard.
	 *
	 * @param string $id Plugin id.
	 * @return string
	 */
	public static function route( $id ) {
		return $id . '.wizard';
	}

	/**
	 * Route => callback entry a plugin merges into its Suite routes.
	 *
	 * @param string $id Plugin id.
	 * @return array
	 */
	public static function route_map( $id ) {
		return array(
			self::route( $id ) => static function ( $ctx ) use ( $id ) {
				self::render( $ctx, $id );
			},
		);
	}

	/* ------------------------------------------------------------------ */
	/* State                                                              */
	/* ------------------------------------------------------------------ */

	/**
	 * Whole wizard state map.
	 *
	 * @return array
	 */
	private static function state() {
		$s = get_option( self::STATE_KEY, array() );
		return is_array( $s ) ? $s : array();
	}

	/**
	 * One plugin's progress.
	 *
	 * @param string $id Plugin id.
	 * @return array{done:bool,seen:array,step:string,started:int}
	 */
	public static function progress( $id ) {
		$all = self::state();
		$p   = isset( $all[ $id ] ) && is_array( $all[ $id ] ) ? $all[ $id ] : array();
		return wp_parse_args(
			$p,
			array(
				'done'    => false,
				'seen'    => array(),
				'step'    => 'welcome',
				'started' => 0,
			)
		);
	}

	/**
	 * Persist one plugin's progress.
	 *
	 * @param string $id  Plugin id.
	 * @param array  $new Keys to merge in.
	 */
	private static function save_progress( $id, array $new ) {
		$all        = self::state();
		$all[ $id ] = array_merge( self::progress( $id ), $new );
		update_option( self::STATE_KEY, $all, false );
	}

	/**
	 * Has this plugin's wizard been completed (or dismissed)?
	 *
	 * @param string $id Plugin id.
	 * @return bool
	 */
	public static function is_done( $id ) {
		$p = self::progress( $id );
		return ! empty( $p['done'] );
	}

	/**
	 * Mark a wizard finished — also used by "skip setup".
	 *
	 * @param string $id Plugin id.
	 */
	public static function mark_done( $id ) {
		self::save_progress( $id, array( 'done' => true ) );
	}

	/* ------------------------------------------------------------------ */
	/* Steps                                                              */
	/* ------------------------------------------------------------------ */

	/**
	 * The full ordered step list: welcome, connect, plugin steps, finish.
	 *
	 * @param string $id Plugin id.
	 * @return array<int,array>
	 */
	private static function steps( $id ) {
		$w  = isset( self::$wizards[ $id ] ) ? self::$wizards[ $id ] : array();
		$st = array(
			array(
				'id'    => 'welcome',
				'kind'  => 'welcome',
				'title' => array( 'ar' => 'أهلاً بك', 'en' => 'Welcome' ),
				'sub'   => array( 'ar' => 'تعرّف على الإضافة قبل ما تبدأ', 'en' => 'A quick look before you begin' ),
				'icon'  => 'waving_hand',
			),
			array(
				'id'    => 'connect',
				'kind'  => 'connect',
				'title' => array( 'ar' => 'اربط حسابك', 'en' => 'Link your account' ),
				'sub'   => array( 'ar' => 'سجّل الدخول بحسابك في مرسال', 'en' => 'Sign in with your Mersal account' ),
				'icon'  => 'link',
			),
		);
		foreach ( (array) ( $w['steps'] ?? array() ) as $s ) {
			$s['kind'] = 'settings';
			$st[]      = $s;
		}
		$st[] = array(
			'id'    => 'finish',
			'kind'  => 'finish',
			'title' => array( 'ar' => 'تم', 'en' => 'Done' ),
			'sub'   => array( 'ar' => 'كل حاجة جاهزة', 'en' => 'Everything is ready' ),
			'icon'  => 'check_circle',
		);
		return $st;
	}

	/**
	 * Index of a step id (0 when unknown).
	 *
	 * @param string $id   Plugin id.
	 * @param string $step Step id.
	 * @return int
	 */
	private static function index_of( $id, $step ) {
		foreach ( self::steps( $id ) as $i => $s ) {
			if ( $s['id'] === $step ) {
				return $i;
			}
		}
		return 0;
	}

	/* ------------------------------------------------------------------ */
	/* Account helpers                                                    */
	/* ------------------------------------------------------------------ */

	/**
	 * Call one of the plugin-supplied account callbacks.
	 *
	 * @param string $id  Plugin id.
	 * @param string $key Callback key.
	 * @param mixed  $def Default when absent.
	 * @return mixed
	 */
	private static function account( $id, $key, $def = '' ) {
		$cb = self::$wizards[ $id ]['account'][ $key ] ?? null;
		if ( is_callable( $cb ) ) {
			return call_user_func( $cb );
		}
		return $def;
	}

	/**
	 * Where this plugin's add-on is sold. `plugin` deep-links the add-on card so
	 * the customer lands on the right product rather than the whole shelf.
	 *
	 * @param string $id Plugin id.
	 * @return string
	 */
	public static function subscribe_url( $id ) {
		$app = self::$wizards[ $id ]['app'] ?? '';
		$url = self::SUBSCRIBE_URL;
		return $app ? add_query_arg( 'plugin', rawurlencode( $app ), $url ) : $url;
	}

	/* ------------------------------------------------------------------ */
	/* Saving                                                             */
	/* ------------------------------------------------------------------ */

	/**
	 * Handle a wizard form submit: save the step, then move.
	 */
	public static function handle_post() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You are not allowed to do this.', 'default' ) );
		}
		check_admin_referer( 'mersal_suite_wizard', 'mersal_wizard_nonce' );

		$res = self::advance();
		if ( empty( $res['ok'] ) ) {
			wp_safe_redirect( admin_url() );
			exit;
		}
		if ( ! empty( $res['leave'] ) ) {
			wp_safe_redirect( $res['leave'] );
			exit;
		}
		wp_safe_redirect( self::step_url( $res['id'], $res['step'] ) );
		exit;
	}

	/**
	 * Same move, answered as JSON with the next step already rendered.
	 *
	 * Every Next used to reload the whole admin page — several hundred
	 * kilobytes of chrome redrawn to change one card, with the modal and the
	 * scroll position lost each time.
	 */
	public static function ajax_step() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
		}
		check_ajax_referer( 'mersal_suite_wizard', 'mersal_wizard_nonce' );

		$res = self::advance();
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( array( 'message' => 'unknown wizard' ), 400 );
		}
		if ( ! empty( $res['leave'] ) ) {
			wp_send_json_success( array( 'redirect' => $res['leave'] ) );
		}

		$id    = $res['id'];
		$ctx   = class_exists( 'Mersal_Suite' ) ? Mersal_Suite::instance() : null;
		$steps = self::steps( $id );
		$at    = self::index_of( $id, $res['step'] );

		if ( ! $ctx ) {
			// No Suite to render against — fall back to a full page load.
			wp_send_json_success( array( 'redirect' => self::step_url( $id, $res['step'] ) ) );
		}

		ob_start();
		self::render_body( $ctx, $id, $steps, $at, 'ar' === $ctx->lang() );
		$html = ob_get_clean();

		wp_send_json_success(
			array(
				'html' => $html,
				'step' => $res['step'],
				'url'  => self::step_url( $id, $res['step'] ),
			)
		);
	}

	/**
	 * Apply a Back / Next / Skip / Finish move.
	 *
	 * @return array{ok:bool,id?:string,step?:string,leave?:string}
	 */
	private static function advance() {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- callers verify.
		$id = isset( $_POST['wizard'] ) ? sanitize_key( wp_unslash( $_POST['wizard'] ) ) : '';
		if ( ! isset( self::$wizards[ $id ] ) ) {
			return array( 'ok' => false );
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- callers verify.
		$step = isset( $_POST['step'] ) ? sanitize_key( wp_unslash( $_POST['step'] ) ) : 'welcome';
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- callers verify.
		$move = isset( $_POST['move'] ) ? sanitize_key( wp_unslash( $_POST['move'] ) ) : 'next';

		$steps = self::steps( $id );
		$at    = self::index_of( $id, $step );
		$leave = self::plugin_url( $id, self::$wizards[ $id ]['finish'] );

		// "Skip setup" leaves everything untouched and never asks again.
		if ( 'skip' === $move ) {
			self::mark_done( $id );
			return array( 'ok' => true, 'id' => $id, 'leave' => $leave );
		}

		// Only a forward move commits the fields — going Back must not persist
		// half-filled inputs the admin was in the middle of reconsidering.
		if ( 'back' !== $move ) {
			self::persist_step( $id, $steps[ $at ] ?? array() );
			$seen   = self::progress( $id )['seen'];
			$seen[] = $step;
			self::save_progress( $id, array( 'seen' => array_values( array_unique( $seen ) ) ) );
		}

		if ( 'finish' === $move ) {
			self::mark_done( $id );
			return array( 'ok' => true, 'id' => $id, 'leave' => $leave );
		}

		$to = 'back' === $move ? max( 0, $at - 1 ) : min( count( $steps ) - 1, $at + 1 );
		self::save_progress( $id, array( 'step' => $steps[ $to ]['id'] ) );

		return array( 'ok' => true, 'id' => $id, 'step' => $steps[ $to ]['id'] );
	}

	/**
	 * Hand the posted fields to the plugin's own settings sanitiser.
	 *
	 * @param string $id   Plugin id.
	 * @param array  $step Step descriptor.
	 */
	private static function persist_step( $id, array $step ) {
		$w = self::$wizards[ $id ];

		// A step may own its saving entirely (e.g. one that creates a page).
		if ( isset( $step['save'] ) && is_callable( $step['save'] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by caller.
			call_user_func( $step['save'], wp_unslash( $_POST ) );
			return;
		}

		$field = (string) $w['field'];
		if ( '' === $field || ! is_callable( $w['save'] ) ) {
			return;
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by caller.
		$raw = isset( $_POST[ $field ] ) && is_array( $_POST[ $field ] ) ? wp_unslash( $_POST[ $field ] ) : array();
		if ( empty( $raw ) ) {
			return;
		}
		call_user_func( $w['save'], $raw );
	}

	/* ------------------------------------------------------------------ */
	/* URLs                                                               */
	/* ------------------------------------------------------------------ */

	/**
	 * URL of a Suite route.
	 *
	 * @param string $id    Plugin id.
	 * @param string $route Route.
	 * @return string
	 */
	private static function plugin_url( $id, $route ) {
		if ( class_exists( 'Mersal_Suite' ) ) {
			return Mersal_Suite::instance()->url( $route );
		}
		return admin_url( 'admin.php?page=mersal-suite&route=' . rawurlencode( $route ) );
	}

	/**
	 * URL of one wizard step.
	 *
	 * @param string $id   Plugin id.
	 * @param string $step Step id.
	 * @return string
	 */
	public static function step_url( $id, $step = '' ) {
		$url = self::plugin_url( $id, self::route( $id ) );
		return $step ? add_query_arg( 'wstep', rawurlencode( $step ), $url ) : $url;
	}

	/* ------------------------------------------------------------------ */
	/* First-run redirect                                                 */
	/* ------------------------------------------------------------------ */

	/**
	 * Send an admin to the wizard once, right after activation.
	 *
	 * A plugin sets the `mersal_suite_wizard_launch` option to its id on
	 * activation; the redirect is consumed on the first admin page load so a
	 * bulk activation of all three plugins can't fight over the screen.
	 */
	public static function maybe_first_run_redirect() {
		if ( wp_doing_ajax() || wp_doing_cron() || ! current_user_can( 'manage_options' ) ) {
			return;
		}
		// Never hijack the activation request itself — WordPress needs to finish
		// its own redirect, and a bulk activate must stay on the plugins screen.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( isset( $_GET['activate-multi'] ) || ! empty( $GLOBALS['pagenow'] ) && 'plugins.php' === $GLOBALS['pagenow'] ) {
			return;
		}
		$id = get_option( 'mersal_suite_wizard_launch', '' );
		if ( ! $id || ! isset( self::$wizards[ $id ] ) ) {
			return;
		}
		delete_option( 'mersal_suite_wizard_launch' );
		if ( self::is_done( $id ) ) {
			return;
		}
		wp_safe_redirect( self::step_url( $id, 'welcome' ) );
		exit;
	}

	/**
	 * Arm the first-run redirect. Call from register_activation_hook().
	 *
	 * @param string $id Plugin id.
	 */
	public static function arm_first_run( $id ) {
		if ( ! self::is_done( $id ) ) {
			update_option( 'mersal_suite_wizard_launch', $id, false );
		}
	}

	/* ------------------------------------------------------------------ */
	/* Render                                                             */
	/* ------------------------------------------------------------------ */

	/**
	 * Render the wizard for one plugin.
	 *
	 * @param Mersal_Suite $ctx Suite context.
	 * @param string       $id  Plugin id.
	 */
	public static function render( $ctx, $id ) {
		if ( ! isset( self::$wizards[ $id ] ) ) {
			return;
		}
		$w     = self::$wizards[ $id ];
		$ar    = 'ar' === $ctx->lang();
		$steps = self::steps( $id );

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$want = isset( $_GET['wstep'] ) ? sanitize_key( wp_unslash( $_GET['wstep'] ) ) : '';
		if ( '' === $want ) {
			$want = self::progress( $id )['step'];
		}
		$at   = self::index_of( $id, $want );
		$step = $steps[ $at ];

		if ( ! self::progress( $id )['started'] ) {
			self::save_progress( $id, array( 'started' => time() ) );
		}

		self::print_css();

		// The wizard is a dialog, not a page. Setup is a task you start and
		// finish, and putting it behind the sidebar invited people to wander off
		// half-configured; an overlay keeps the one job in front of them.
		$exit = $ctx->url( $w['finish'] );
		echo '<div class="msw-modal" data-msw-modal data-msw-id="' . esc_attr( $id ) . '" data-msw-exit="' . esc_url( $exit ) . '">';
		echo '<div class="msw-panel" role="dialog" aria-modal="true" aria-label="' . esc_attr( $ctx->L( $w['title'] ) ) . '">';
		echo '<button type="button" class="msw-x" data-msw-close aria-label="' . esc_attr( $ar ? 'إغلاق' : 'Close' ) . '"><span class="msym">close</span></button>';
		echo '<div class="msw-scroll" data-msw-body>';
		self::render_body( $ctx, $id, $steps, $at, $ar );
		echo '</div></div></div>';

		self::print_js( $ar );
	}

	/**
	 * The part of the wizard that changes between steps.
	 *
	 * Rendered on first paint and returned on its own by the AJAX step handler,
	 * so advancing swaps this and nothing else — the page never reloads and the
	 * scroll position, the modal and the rest of the admin all stay put.
	 *
	 * @param Mersal_Suite $ctx   Suite.
	 * @param string       $id    Plugin id.
	 * @param array        $steps Steps.
	 * @param int          $at    Current index.
	 * @param bool         $ar    Arabic?
	 */
	private static function render_body( $ctx, $id, $steps, $at, $ar ) {
		$step = $steps[ $at ];

		self::rail( $ctx, $id, $steps, $at, $ar );

		echo '<form method="post" class="msw" data-msw-form action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
		wp_nonce_field( 'mersal_suite_wizard', 'mersal_wizard_nonce' );
		echo '<input type="hidden" name="action" value="mersal_suite_wizard">';
		echo '<input type="hidden" name="wizard" value="' . esc_attr( $id ) . '">';
		echo '<input type="hidden" name="step" value="' . esc_attr( $step['id'] ) . '">';

		$blocked = false;
		switch ( $step['kind'] ) {
			case 'welcome':
				self::step_welcome( $ctx, $id, $ar );
				break;
			case 'connect':
				$blocked = self::step_connect( $ctx, $id, $ar );
				break;
			case 'finish':
				self::step_finish( $ctx, $id, $ar );
				break;
			default:
				self::step_settings( $ctx, $id, $step, $ar );
		}

		self::footer( $ctx, $id, $steps, $at, $ar, ! empty( $blocked ) );
		echo '</form>';
	}

	/**
	 * Numbered progress rail.
	 *
	 * @param Mersal_Suite $ctx   Suite.
	 * @param string       $id    Plugin id.
	 * @param array        $steps Steps.
	 * @param int          $at    Current index.
	 * @param bool         $ar    Arabic?
	 */
	private static function rail( $ctx, $id, $steps, $at, $ar ) {
		$w     = self::$wizards[ $id ];
		$total = count( $steps );
		$pct   = $total > 1 ? (int) round( ( $at / ( $total - 1 ) ) * 100 ) : 0;
		$seen  = self::progress( $id )['seen'];
		?>
		<div class="msw-head">
			<div class="msw-head-top">
				<div class="msw-brand" style="--msw-accent:<?php echo esc_attr( $w['accent'] ); ?>;">
					<span class="msym fill"><?php echo esc_html( $w['icon'] ); ?></span>
				</div>
				<div class="msw-head-txt">
					<h1><?php echo esc_html( ( $ar ? 'إعداد ' : 'Set up ' ) . $ctx->L( $w['title'] ) ); ?></h1>
					<p><?php echo esc_html( $ar ? 'خطوات قصيرة تخلّي الإضافة شغالة صح من أول مرة.' : 'A few short steps to get the plugin working properly from the start.' ); ?></p>
				</div>
				<div class="msw-head-count"><?php echo esc_html( sprintf( $ar ? 'خطوة %1$d من %2$d' : 'Step %1$d of %2$d', $at + 1, $total ) ); ?></div>
			</div>
			<div class="msw-bar"><span style="width:<?php echo (int) $pct; ?>%;"></span></div>
			<ol class="msw-rail">
				<?php foreach ( $steps as $i => $s ) : ?>
					<?php
					$done  = in_array( $s['id'], $seen, true ) && $i < $at;
					$state = $i === $at ? 'is-now' : ( $done ? 'is-done' : '' );
					// Only a step already visited is clickable — jumping ahead
					// past the account link would land on an unusable screen.
					$open  = $done || $i < $at;
					?>
					<li class="msw-step <?php echo esc_attr( $state ); ?>">
						<?php if ( $open ) : ?><a href="<?php echo esc_url( self::step_url( $id, $s['id'] ) ); ?>"><?php else : ?><span><?php endif; ?>
							<i class="msw-dot"><?php echo $done ? '<span class="msym fill">check</span>' : esc_html( (string) ( $i + 1 ) ); ?></i>
							<b><?php echo esc_html( $ctx->L( $s['title'] ) ); ?></b>
						<?php if ( $open ) : ?></a><?php else : ?></span><?php endif; ?>
					</li>
				<?php endforeach; ?>
			</ol>
		</div>
		<?php
	}

	/**
	 * Welcome / explanation step.
	 *
	 * @param Mersal_Suite $ctx Suite.
	 * @param string       $id  Plugin id.
	 * @param bool         $ar  Arabic?
	 */
	private static function step_welcome( $ctx, $id, $ar ) {
		$w     = self::$wizards[ $id ];
		$intro = (array) $w['intro'];
		$lead  = isset( $intro['lead'] ) ? $ctx->L( $intro['lead'] ) : '';
		$mins  = (int) ( $intro['minutes'] ?? 3 );
		?>
		<div class="msw-card msw-hero" style="--msw-accent:<?php echo esc_attr( $w['accent'] ); ?>;">
			<div class="msw-hero-in">
				<span class="msw-pill"><span class="msym fill">auto_awesome</span><?php echo esc_html( $ar ? 'إعداد سريع' : 'Quick setup' ); ?></span>
				<h2><?php echo esc_html( $ar ? ( 'أهلاً بك في ' . $ctx->L( $w['title'] ) ) : ( 'Welcome to ' . $ctx->L( $w['title'] ) ) ); ?></h2>
				<p><?php echo esc_html( $lead ); ?></p>
				<div class="msw-meta">
					<span><span class="msym">schedule</span><?php echo esc_html( sprintf( $ar ? 'حوالي %d دقائق' : 'about %d minutes', $mins ) ); ?></span>
					<span><span class="msym">save</span><?php echo esc_html( $ar ? 'كل خطوة بتتحفظ لوحدها' : 'each step saves on its own' ); ?></span>
					<span><span class="msym">restart_alt</span><?php echo esc_html( $ar ? 'تقدر ترجع تكمل في أي وقت' : 'resume any time' ); ?></span>
				</div>
			</div>
		</div>

		<?php if ( ! empty( $intro['points'] ) ) : ?>
			<div class="msw-points">
				<?php foreach ( (array) $intro['points'] as $p ) : ?>
					<div class="msw-point">
						<div class="msw-point-ico" style="--msw-accent:<?php echo esc_attr( $w['accent'] ); ?>;"><span class="msym fill"><?php echo esc_html( $p['icon'] ?? 'bolt' ); ?></span></div>
						<div>
							<b><?php echo esc_html( $ctx->L( $p['title'] ) ); ?></b>
							<span><?php echo esc_html( $ctx->L( $p['text'] ) ); ?></span>
						</div>
					</div>
				<?php endforeach; ?>
			</div>
		<?php endif; ?>

		<div class="msw-note">
			<span class="msym">info</span>
			<div><?php echo esc_html( $ar ? 'الخطوة الجاية هي ربط الإضافة بحسابك في مرسال — من غير الربط الإضافة مش هتقدر تبعت أي رسالة.' : 'Next you will link the plugin to your Mersal account — without the link it cannot send anything.' ); ?></div>
		</div>
		<?php
	}

	/**
	 * Mersal account step. Returns true when the wizard must not advance yet.
	 *
	 * @param Mersal_Suite $ctx Suite.
	 * @param string       $id  Plugin id.
	 * @param bool         $ar  Arabic?
	 * @return bool Blocked?
	 */
	private static function step_connect( $ctx, $id, $ar ) {
		$w         = self::$wizards[ $id ];
		$connected = (bool) self::account( $id, 'is_connected', false );
		$error     = (string) self::account( $id, 'error', '' );
		$no_plan   = (bool) self::account( $id, 'needs_subscription', false );

		if ( $connected ) {
			$name  = (string) self::account( $id, 'name', '' );
			$email = (string) self::account( $id, 'email', '' );
			$plan  = (string) self::account( $id, 'plan', '' );
			?>
			<div class="msw-card msw-ok">
				<div class="msw-ok-ico"><span class="msym fill">check_circle</span></div>
				<div class="msw-ok-txt">
					<b><?php echo esc_html( $ar ? 'الموقع متصل بحسابك في مرسال' : 'This site is linked to your Mersal account' ); ?></b>
					<span><?php echo esc_html( $name ? $name : $email ); ?><?php echo $email && $name ? esc_html( ' · ' . $email ) : ''; ?></span>
					<?php if ( $plan ) : ?><span class="msw-plan"><span class="msym fill">workspace_premium</span><?php echo esc_html( $plan ); ?></span><?php endif; ?>
				</div>
				<?php $sync = (string) self::account( $id, 'sync_url', '' ); ?>
				<?php if ( $sync ) : ?>
					<a class="msw-btn ghost" href="<?php echo esc_url( $sync ); ?>"><span class="msym">sync</span><?php echo esc_html( $ar ? 'تحديث البيانات' : 'Refresh' ); ?></a>
				<?php endif; ?>
			</div>
			<div class="msw-note">
				<span class="msym">verified_user</span>
				<div><?php echo esc_html( $ar ? 'الرصيد والبوابات بيتقروا من حسابك مباشرة — مش محتاج تدخل أي مفاتيح API يدوي.' : 'Credit and gateways are read straight from your account — no API keys to paste anywhere.' ); ?></div>
			</div>
			<?php
			return false;
		}

		// Refused for want of a subscription: say so plainly and sell the fix,
		// rather than dropping the customer back on a generic "connect" button
		// that will refuse them again.
		if ( $no_plan ) {
			self::not_subscribed_card( $ctx, $id, $ar, $error );
			return true;
		}

		$connect = (string) self::account( $id, 'connect_url', '' );
		?>
		<div class="msw-card msw-connect" style="--msw-accent:<?php echo esc_attr( $w['accent'] ); ?>;">
			<div class="msw-connect-ico"><span class="msym fill">link</span></div>
			<h2><?php echo esc_html( $ar ? 'اربط الموقع بحسابك في مرسال' : 'Link this site to your Mersal account' ); ?></h2>
			<p><?php echo esc_html( $ar ? 'اضغط الزر وسجّل الدخول بحسابك في مرسال. لو مش مسجّل دخول، هتسجّل الأول وبعدها هنرجّعك للإضافة تلقائيًا ويكمل الربط لوحده.' : 'Click the button and sign in to Mersal. If you are signed out you will sign in first — we bring you straight back here and finish the link automatically.' ); ?></p>

			<?php if ( $error ) : ?>
				<div class="msw-alert"><span class="msym fill">error</span><div><?php echo esc_html( $error ); ?></div></div>
			<?php endif; ?>

			<?php if ( $connect ) : ?>
				<a class="msw-btn primary lg" href="<?php echo esc_url( $connect ); ?>">
					<span class="msym fill">login</span><?php echo esc_html( $ar ? 'تسجيل الدخول والربط' : 'Sign in & link' ); ?>
				</a>
			<?php endif; ?>

			<ul class="msw-checklist">
				<li><span class="msym fill">check</span><?php echo esc_html( $ar ? 'بيستخدم بوابات الواتساب والـSMS المربوطة بحسابك' : 'Uses the WhatsApp and SMS gateways already on your account' ); ?></li>
				<li><span class="msym fill">check</span><?php echo esc_html( $ar ? 'الرصيد والفواتير كلها من مكان واحد' : 'One place for credit and billing' ); ?></li>
				<li><span class="msym fill">check</span><?php echo esc_html( $ar ? 'تقدر تفكّ الربط في أي وقت' : 'Unlink at any time' ); ?></li>
			</ul>
		</div>
		<div class="msw-note">
			<span class="msym">help</span>
			<div>
				<?php echo esc_html( $ar ? 'لسه معندكش حساب في مرسال؟' : 'No Mersal account yet?' ); ?>
				<a href="https://mersal.it/register" target="_blank" rel="noopener"><?php echo esc_html( $ar ? 'أنشئ حساب مجاني' : 'Create a free account' ); ?></a>
			</div>
		</div>
		<?php
		return true;
	}

	/**
	 * The "your plan does not include this plugin" state.
	 *
	 * @param Mersal_Suite $ctx   Suite.
	 * @param string       $id    Plugin id.
	 * @param bool         $ar    Arabic?
	 * @param string       $error Message from mersal.it, when any.
	 */
	public static function not_subscribed_card( $ctx, $id, $ar, $error = '' ) {
		$w    = self::$wizards[ $id ] ?? array( 'title' => array( 'ar' => '', 'en' => '' ) );
		$name = $ctx->L( $w['title'] );
		?>
		<div class="msw-card msw-locked">
			<div class="msw-locked-ico"><span class="msym fill">lock</span></div>
			<h2><?php echo esc_html( $ar ? 'اشتراكك الحالي مايشملش الإضافة دي' : 'Your subscription does not include this plugin' ); ?></h2>
			<p>
				<?php
				echo esc_html(
					$ar
						? sprintf( 'حسابك في مرسال مش مشترك في %s، عشان كده الربط اترفض. اشترك في الإضافة من متجر الإضافات وبعدين ارجع اضغط «تسجيل الدخول والربط».', $name )
						: sprintf( 'Your Mersal account is not subscribed to %s, so the link was refused. Subscribe from the add-ons store, then come back and link the site.', $name )
				);
				?>
			</p>
			<?php if ( $error ) : ?>
				<div class="msw-alert"><span class="msym fill">error</span><div><?php echo esc_html( $error ); ?></div></div>
			<?php endif; ?>
			<div class="msw-locked-actions">
				<a class="msw-btn primary lg" href="<?php echo esc_url( self::subscribe_url( $id ) ); ?>" target="_blank" rel="noopener">
					<span class="msym fill">shopping_cart</span><?php echo esc_html( $ar ? 'اشترك الآن' : 'Subscribe now' ); ?>
				</a>
				<?php $connect = (string) self::account( $id, 'connect_url', '' ); ?>
				<?php if ( $connect ) : ?>
					<a class="msw-btn ghost" href="<?php echo esc_url( $connect ); ?>"><span class="msym">refresh</span><?php echo esc_html( $ar ? 'اشتركت بالفعل — جرّب الربط تاني' : 'Already subscribed — try linking again' ); ?></a>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * A plugin-supplied settings step.
	 *
	 * @param Mersal_Suite $ctx  Suite.
	 * @param string       $id   Plugin id.
	 * @param array        $step Step descriptor.
	 * @param bool         $ar   Arabic?
	 */
	private static function step_settings( $ctx, $id, array $step, $ar ) {
		$w = self::$wizards[ $id ];
		echo '<div class="msw-card">';
		echo '<div class="msw-card-head" style="--msw-accent:' . esc_attr( $w['accent'] ) . ';">';
		echo '<div class="msw-card-ico"><span class="msym fill">' . esc_html( $step['icon'] ?? 'tune' ) . '</span></div>';
		echo '<div><b>' . esc_html( $ctx->L( $step['title'] ) ) . '</b>';
		if ( ! empty( $step['sub'] ) ) {
			echo '<span>' . esc_html( $ctx->L( $step['sub'] ) ) . '</span>';
		}
		echo '</div></div>';
		echo '<div class="msw-card-body">';
		if ( isset( $step['render'] ) && is_callable( $step['render'] ) ) {
			call_user_func( $step['render'], $ctx, new self() );
		}
		echo '</div></div>';
	}

	/**
	 * Finish step.
	 *
	 * @param Mersal_Suite $ctx Suite.
	 * @param string       $id  Plugin id.
	 * @param bool         $ar  Arabic?
	 */
	private static function step_finish( $ctx, $id, $ar ) {
		$w     = self::$wizards[ $id ];
		$links = (array) ( $w['finish_links'] ?? array() );
		?>
		<div class="msw-card msw-hero msw-done" style="--msw-accent:<?php echo esc_attr( $w['accent'] ); ?>;">
			<div class="msw-hero-in">
				<span class="msw-pill"><span class="msym fill">celebration</span><?php echo esc_html( $ar ? 'تمام' : 'All set' ); ?></span>
				<h2><?php echo esc_html( $ar ? 'الإضافة جاهزة للشغل' : 'Your plugin is ready' ); ?></h2>
				<p><?php echo esc_html( $ar ? 'كل الإعدادات الأساسية اتحفظت. تقدر تغيّر أي حاجة في أي وقت من شاشات الإضافة، وتقدر تفتح المعالج ده تاني من نفس القائمة.' : 'Every essential setting is saved. Change anything later from the plugin screens — and this wizard stays available in the same menu.' ); ?></p>
			</div>
		</div>
		<?php if ( $links ) : ?>
			<div class="msw-points">
				<?php foreach ( $links as $l ) : ?>
					<a class="msw-point msw-point-link" href="<?php echo esc_url( $ctx->url( $l['route'] ) ); ?>">
						<div class="msw-point-ico" style="--msw-accent:<?php echo esc_attr( $w['accent'] ); ?>;"><span class="msym fill"><?php echo esc_html( $l['icon'] ?? 'arrow_forward' ); ?></span></div>
						<div>
							<b><?php echo esc_html( $ctx->L( $l['title'] ) ); ?></b>
							<span><?php echo esc_html( $ctx->L( $l['text'] ) ); ?></span>
						</div>
					</a>
				<?php endforeach; ?>
			</div>
		<?php endif;
	}

	/**
	 * Back / skip / next footer.
	 *
	 * @param Mersal_Suite $ctx     Suite.
	 * @param string       $id      Plugin id.
	 * @param array        $steps   Steps.
	 * @param int          $at      Index.
	 * @param bool         $ar      Arabic?
	 * @param bool         $blocked Whether Next must be disabled.
	 */
	private static function footer( $ctx, $id, $steps, $at, $ar, $blocked ) {
		$last  = ( count( $steps ) - 1 ) === $at;
		$first = 0 === $at;
		?>
		<div class="msw-foot">
			<div class="msw-foot-l">
				<?php if ( ! $first ) : ?>
					<button type="submit" name="move" value="back" class="msw-btn ghost"><span class="msym"><?php echo $ar ? 'chevron_right' : 'chevron_left'; ?></span><?php echo esc_html( $ar ? 'رجوع' : 'Back' ); ?></button>
				<?php endif; ?>
			</div>
			<div class="msw-foot-r">
				<?php if ( ! $last ) : ?>
					<button type="submit" name="move" value="skip" class="msw-btn quiet"><?php echo esc_html( $ar ? 'تخطّي الإعداد' : 'Skip setup' ); ?></button>
				<?php endif; ?>
				<?php if ( $blocked ) : ?>
					<span class="msw-btn primary is-off" aria-disabled="true"><?php echo esc_html( $ar ? 'اربط الحساب للمتابعة' : 'Link your account to continue' ); ?><span class="msym">lock</span></span>
				<?php elseif ( $last ) : ?>
					<button type="submit" name="move" value="finish" class="msw-btn primary lg"><span class="msym fill">check</span><?php echo esc_html( $ar ? 'إنهاء' : 'Finish' ); ?></button>
				<?php else : ?>
					<button type="submit" name="move" value="next" class="msw-btn primary lg"><?php echo esc_html( $ar ? 'التالي' : 'Next' ); ?><span class="msym"><?php echo $ar ? 'chevron_left' : 'chevron_right'; ?></span></button>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/* ------------------------------------------------------------------ */
	/* Field helpers (used by the plugins' step renderers)                 */
	/* ------------------------------------------------------------------ */

	/**
	 * A labelled row wrapper.
	 *
	 * @param string $label Label.
	 * @param string $hint  Hint.
	 * @param string $inner Inner HTML.
	 */
	public function field( $label, $hint, $inner ) {
		echo '<div class="msw-field"><label>' . esc_html( $label ) . '</label>';
		echo $inner; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built by the helpers below.
		if ( $hint ) {
			echo '<small>' . esc_html( $hint ) . '</small>';
		}
		echo '</div>';
	}

	/**
	 * Text / number input.
	 *
	 * @param string $name  Full input name.
	 * @param string $value Value.
	 * @param string $label Label.
	 * @param string $hint  Hint.
	 * @param string $type  Input type.
	 */
	public function text( $name, $value, $label, $hint = '', $type = 'text' ) {
		$this->field(
			$label,
			$hint,
			'<input class="msw-input" type="' . esc_attr( $type ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '">'
		);
	}

	/**
	 * Select box.
	 *
	 * @param string $name    Full input name.
	 * @param string $value   Current value.
	 * @param string $label   Label.
	 * @param array  $options value => label.
	 * @param string $hint    Hint.
	 */
	public function select( $name, $value, $label, array $options, $hint = '' ) {
		$html = '<select class="msw-input" name="' . esc_attr( $name ) . '">';
		foreach ( $options as $v => $l ) {
			$html .= '<option value="' . esc_attr( $v ) . '" ' . selected( (string) $value, (string) $v, false ) . '>' . esc_html( $l ) . '</option>';
		}
		$html .= '</select>';
		$this->field( $label, $hint, $html );
	}

	/**
	 * On/off row. A hidden 0 rides ahead of the checkbox so an unticked box
	 * still posts a value — otherwise turning something OFF would save nothing.
	 *
	 * @param string $name  Full input name.
	 * @param bool   $on    Current state.
	 * @param string $title Title.
	 * @param string $desc  Description.
	 */
	public function toggle( $name, $on, $title, $desc = '' ) {
		echo '<label class="msw-toggle">';
		echo '<input type="hidden" name="' . esc_attr( $name ) . '" value="0">';
		echo '<input type="checkbox" name="' . esc_attr( $name ) . '" value="1" ' . checked( (bool) $on, true, false ) . '>';
		echo '<span class="msw-toggle-ui"></span>';
		echo '<span class="msw-toggle-txt"><b>' . esc_html( $title ) . '</b>';
		if ( $desc ) {
			echo '<span>' . esc_html( $desc ) . '</span>';
		}
		echo '</span></label>';
	}

	/**
	 * Big radio cards.
	 *
	 * @param string $name    Full input name.
	 * @param string $value   Current value.
	 * @param array  $options value => [icon, title, desc].
	 */
	public function cards( $name, $value, array $options ) {
		echo '<div class="msw-cards">';
		foreach ( $options as $v => $o ) {
			$on = ( (string) $value === (string) $v );
			echo '<label class="msw-choice' . ( $on ? ' is-on' : '' ) . '">';
			echo '<input type="radio" name="' . esc_attr( $name ) . '" value="' . esc_attr( $v ) . '" ' . checked( $on, true, false ) . '>';
			echo '<span class="msym fill">' . esc_html( $o[0] ) . '</span>';
			echo '<b>' . esc_html( $o[1] ) . '</b>';
			if ( isset( $o[2] ) ) {
				echo '<span class="msw-choice-d">' . esc_html( $o[2] ) . '</span>';
			}
			echo '</label>';
		}
		echo '</div>';
	}

	/**
	 * Textarea.
	 *
	 * @param string $name  Full input name.
	 * @param string $value Value.
	 * @param string $label Label.
	 * @param string $hint  Hint.
	 * @param int    $rows  Rows.
	 */
	public function textarea( $name, $value, $label, $hint = '', $rows = 4 ) {
		$this->field(
			$label,
			$hint,
			'<textarea class="msw-input" rows="' . (int) $rows . '" name="' . esc_attr( $name ) . '">' . esc_textarea( $value ) . '</textarea>'
		);
	}

	/**
	 * A neutral explanatory note inside a step.
	 *
	 * @param string $text Text.
	 * @param string $icon Icon.
	 */
	public function note( $text, $icon = 'info' ) {
		echo '<div class="msw-note"><span class="msym">' . esc_html( $icon ) . '</span><div>' . esc_html( $text ) . '</div></div>';
	}

	/**
	 * Two-column grid open/close.
	 *
	 * @param bool $open Open or close.
	 */
	public function grid( $open = true ) {
		echo $open ? '<div class="msw-grid">' : '</div>';
	}

	/* ------------------------------------------------------------------ */
	/* Styles                                                             */
	/* ------------------------------------------------------------------ */

	/**
	 * Print the wizard stylesheet once per page.
	 */
	private static function print_css() {
		static $done = false;
		if ( $done ) {
			return;
		}
		$done = true;
		?>
		<style id="mersal-suite-wizard-css">
		/* ---- The dialog ------------------------------------------------ */
		.msw-modal { position: fixed; inset: 0; z-index: 160000; display: flex; align-items: center;
			justify-content: center; padding: 20px; background: rgba(8, 20, 15, .55);
			-webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); animation: msFade .2s ease; }
		.msw-panel { position: relative; width: 100%; max-width: 880px; max-height: calc(100vh - 40px);
			background: var(--bg, #f4f1ea); border-radius: 16px; box-shadow: 0 24px 70px rgba(0,0,0,.4);
			display: flex; flex-direction: column; overflow: hidden; animation: msPop .22s ease; }
		.msw-scroll { overflow-y: auto; padding: 20px 22px 22px; min-height: 0;
			scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
		.msw-x { position: absolute; top: 10px; inset-inline-end: 12px; z-index: 2; width: 30px; height: 30px;
			border: 0; border-radius: 9px; background: var(--surface-3, #f1efe8); color: var(--fg-2, #51605a);
			cursor: pointer; display: grid; place-items: center; font-family: inherit; }
		.msw-x:hover { background: var(--danger-soft, #fbe2df); color: var(--danger, #d8524a); }
		.msw-x .msym { font-size: 18px; }
		/* The step being replaced dims rather than vanishing, so a slow save
		   reads as "working" instead of "broken". */
		.msw-scroll.is-busy { opacity: .55; pointer-events: none; transition: opacity .12s ease; }
		@media (max-width: 782px) {
			.msw-modal { padding: 0; }
			.msw-panel { max-width: none; max-height: 100vh; height: 100vh; border-radius: 0; }
		}

		.msw, .msw-head { max-width: 860px; margin-inline: auto; }
		.msw-head { margin-bottom: 20px; animation: msFade .3s ease; }
		.msw-head-top { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
		.msw-brand { width: 36px; height: 36px; border-radius: 11px; flex: 0 0 36px; display: grid; place-items: center;
			background: color-mix(in srgb, var(--msw-accent) 16%, transparent); color: var(--msw-accent); }
		.msw-brand .msym { font-size: 26px; }
		.msw-head-txt { flex: 1; min-width: 0; }
		.msw-head-txt h1 { margin: 0; font-size: 17px; font-weight: 800; color: var(--fg-1); letter-spacing: -.01em; }
		.msw-head-txt p { margin: 3px 0 0; font-size: 13px; color: var(--fg-2); }
		.msw-head-count { font-size: 12px; font-weight: 700; color: var(--fg-3); white-space: nowrap; }
		.msw-bar { height: 5px; border-radius: 99px; background: var(--surface-3); overflow: hidden; }
		.msw-bar > span { display: block; height: 100%; border-radius: 99px; background: var(--accent); transition: width .3s ease; }
		.msw-rail { display: flex; flex-wrap: wrap; gap: 6px 18px; list-style: none; margin: 14px 0 0; padding: 0; }
		.msw-step a, .msw-step span { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; color: var(--fg-3); }
		.msw-step a { cursor: pointer; }
		.msw-step a:hover b { color: var(--fg-1); }
		.msw-dot { width: 21px; height: 21px; border-radius: 50%; flex: 0 0 21px; display: grid; place-items: center;
			background: var(--surface-3); color: var(--fg-3); font-size: 11px; font-weight: 800; font-style: normal; }
		.msw-dot .msym { font-size: 14px; }
		.msw-step.is-now .msw-dot { background: var(--accent); color: #fff; }
		.msw-step.is-now b { color: var(--fg-1); }
		.msw-step.is-done .msw-dot { background: var(--accent-soft); color: var(--accent); }

		.msw-card { background: var(--surface); border: 1px solid var(--border); border-radius: 18px;
			box-shadow: var(--shadow); margin-bottom: 16px; overflow: hidden; animation: msFade .3s ease; }
		.msw-card-head { display: flex; align-items: center; gap: 10px; padding: 11px 13px; border-bottom: 1px solid var(--border); }
		.msw-card-ico { width: 30px; height: 30px; border-radius: 9px; flex: 0 0 30px; display: grid; place-items: center;
			background: color-mix(in srgb, var(--msw-accent) 15%, transparent); color: var(--msw-accent); }
		.msw-card-ico .msym { font-size: 22px; }
		.msw-card-head b { display: block; font-size: 15px; font-weight: 800; color: var(--fg-1); }
		.msw-card-head span { display: block; font-size: 12.5px; color: var(--fg-2); margin-top: 2px; }
		.msw-card-body { padding: 13px; }

		.msw-hero { border: none; background: linear-gradient(125deg, #0f7a4a 0%, #0c6740 55%, #0a5436 100%); color: #fff; }
		.msw-hero-in { padding: 20px 22px; }
		.msw-hero h2 { margin: 0 0 6px; font-size: 19px; font-weight: 800; color: #fff; }
		.msw-hero p { margin: 0; font-size: 14px; line-height: 1.7; color: rgba(255,255,255,.85); max-width: 620px; }
		.msw-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700;
			background: rgba(255,255,255,.16); padding: 5px 11px; border-radius: 999px; margin-bottom: 14px; }
		.msw-pill .msym { font-size: 15px; }
		.msw-meta { display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 18px; }
		.msw-meta span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: rgba(255,255,255,.8); }
		.msw-meta .msym { font-size: 16px; }

		.msw-points { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; margin-bottom: 16px; }
		@media (max-width: 782px) { .msw-points { grid-template-columns: 1fr; } }
		.msw-point { display: flex; gap: 10px; align-items: flex-start; background: var(--surface);
			border: 1px solid var(--border); border-radius: 11px; padding: 11px; box-shadow: var(--shadow); }
		.msw-point-link { transition: transform .15s ease, box-shadow .15s ease; }
		.msw-point-link:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
		.msw-point-ico { width: 36px; height: 36px; border-radius: 11px; flex: 0 0 36px; display: grid; place-items: center;
			background: color-mix(in srgb, var(--msw-accent) 15%, transparent); color: var(--msw-accent); }
		.msw-point-ico .msym { font-size: 20px; }
		.msw-point b { display: block; font-size: 13.5px; color: var(--fg-1); }
		.msw-point span { display: block; font-size: 12.5px; color: var(--fg-2); line-height: 1.6; margin-top: 2px; }

		.msw-note { display: flex; gap: 10px; align-items: flex-start; background: var(--surface-2);
			border: 1px solid var(--border); border-radius: 14px; padding: 13px 15px; margin-bottom: 16px;
			font-size: 12.5px; color: var(--fg-2); line-height: 1.7; }
		.msw-note .msym { font-size: 19px; color: var(--fg-3); flex: 0 0 auto; }
		.msw-note a { color: var(--accent); font-weight: 700; }

		.msw-alert { display: flex; gap: 10px; align-items: flex-start; background: var(--danger-soft);
			border: 1px solid var(--danger); border-radius: 12px; padding: 12px 14px; margin: 4px 0 16px;
			font-size: 12.5px; color: var(--danger); line-height: 1.7; text-align: start; }
		.msw-alert .msym { font-size: 19px; flex: 0 0 auto; }

		.msw-ok { display: flex; align-items: center; gap: 15px; padding: 20px; }
		.msw-ok-ico { width: 46px; height: 46px; border-radius: 14px; flex: 0 0 46px; display: grid; place-items: center;
			background: var(--accent-soft); color: var(--accent); }
		.msw-ok-ico .msym { font-size: 26px; }
		.msw-ok-txt { flex: 1; min-width: 0; }
		.msw-ok-txt b { display: block; font-size: 15px; color: var(--fg-1); }
		.msw-ok-txt span { display: block; font-size: 12.5px; color: var(--fg-2); margin-top: 2px; word-break: break-word; }
		.msw-plan { display: inline-flex !important; align-items: center; gap: 5px; margin-top: 7px !important;
			background: var(--accent-soft); color: var(--accent); font-weight: 700; padding: 3px 9px; border-radius: 999px; }
		.msw-plan .msym { font-size: 14px; }

		.msw-connect, .msw-locked { padding: 22px 20px; text-align: center; }
		.msw-connect-ico, .msw-locked-ico { width: 60px; height: 60px; border-radius: 18px; margin: 0 auto 16px;
			display: grid; place-items: center; background: color-mix(in srgb, var(--msw-accent, #0f7a4a) 15%, transparent); color: var(--msw-accent, #0f7a4a); }
		.msw-locked-ico { background: var(--warn-soft); color: var(--warn); }
		.msw-connect-ico .msym, .msw-locked-ico .msym { font-size: 32px; }
		.msw-connect h2, .msw-locked h2 { margin: 0 0 8px; font-size: 19px; font-weight: 800; color: var(--fg-1); }
		.msw-connect p, .msw-locked p { margin: 0 auto 20px; font-size: 13.5px; color: var(--fg-2); line-height: 1.8; max-width: 520px; }
		.msw-checklist { list-style: none; margin: 22px auto 0; padding: 0; max-width: 460px; text-align: start; }
		.msw-checklist li { display: flex; align-items: flex-start; gap: 9px; font-size: 12.5px; color: var(--fg-2); padding: 6px 0; }
		.msw-checklist .msym { font-size: 17px; color: var(--accent); flex: 0 0 auto; }
		.msw-locked-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

		.msw-sechead { font-size: 11.5px; font-weight: 800; color: var(--fg-3); letter-spacing: .02em;
			margin: 16px 0 6px; padding-top: 12px; border-top: 1px solid var(--border); }
		.msw-sechead:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
		.msw-field { margin-bottom: 11px; }
		.msw-field > label { display: block; font-size: 12.5px; font-weight: 700; color: var(--fg-1); margin-bottom: 6px; }
		.msw-field small { display: block; font-size: 11.5px; color: var(--fg-3); margin-top: 5px; line-height: 1.6; }
		.msw-input { width: 100%; padding: 7px 10px; border-radius: 11px; border: 1px solid var(--border-strong);
			background: var(--surface); color: var(--fg-1); font-size: 13.5px; }
		.msw-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
		textarea.msw-input { line-height: 1.7; resize: vertical; }
		.msw-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 0 16px; }
		@media (max-width: 782px) { .msw-grid { grid-template-columns: 1fr; } }

		.msw-toggle { display: flex; align-items: flex-start; gap: 10px; padding: 9px 0; cursor: pointer; }
		.msw-toggle + .msw-toggle { border-top: 1px solid var(--border); }
		.msw-toggle input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; }
		.msw-toggle-ui { width: 40px; height: 23px; border-radius: 99px; background: var(--border-strong);
			flex: 0 0 40px; position: relative; transition: background .15s ease; margin-top: 1px; }
		.msw-toggle-ui::after { content: ""; position: absolute; top: 3px; inset-inline-start: 3px; width: 17px; height: 17px;
			border-radius: 50%; background: #fff; transition: transform .15s ease; }
		.msw-toggle input:checked + .msw-toggle-ui { background: var(--accent); }
		.msw-toggle input:checked + .msw-toggle-ui::after { transform: translateX(17px); }
		.msw-app[dir="rtl"] .msw-toggle input:checked + .msw-toggle-ui::after,
		[dir="rtl"] .msw-toggle input:checked + .msw-toggle-ui::after { transform: translateX(-17px); }
		.msw-toggle input:focus-visible + .msw-toggle-ui { outline: 2px solid var(--accent); outline-offset: 2px; }
		.msw-toggle-txt b { display: block; font-size: 13.5px; color: var(--fg-1); }
		.msw-toggle-txt span { display: block; font-size: 12px; color: var(--fg-2); margin-top: 2px; line-height: 1.6; }

		.msw-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 16px; }
		.msw-choice { border: 1.5px solid var(--border); border-radius: 11px; padding: 10px 9px; text-align: center;
			cursor: pointer; background: var(--surface); transition: border-color .14s, background .14s; }
		.msw-choice input { position: absolute; opacity: 0; width: 0; height: 0; }
		.msw-choice .msym { font-size: 24px; color: var(--fg-3); display: block; margin-bottom: 7px; }
		.msw-choice b { display: block; font-size: 13px; color: var(--fg-1); }
		.msw-choice-d { display: block; font-size: 11.5px; color: var(--fg-3); margin-top: 3px; line-height: 1.5; }
		.msw-choice.is-on, .msw-choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
		.msw-choice.is-on .msym, .msw-choice:has(input:checked) .msym { color: var(--accent); }

		.msw-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
			margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); flex-wrap: wrap; }
		.msw-foot-r { display: flex; align-items: center; gap: 10px; margin-inline-start: auto; }
		.msw-btn { display: inline-flex; align-items: center; gap: 6px; border-radius: 10px; padding: 8px 14px;
			font-size: 13.5px; font-weight: 700; cursor: pointer; border: 1px solid transparent; font-family: inherit; }
		.msw-btn .msym { font-size: 18px; }
		.msw-btn.lg { padding: 12px 24px; font-size: 14px; }
		.msw-btn.primary { background: var(--accent); color: #fff; }
		.msw-btn.primary:hover { background: var(--accent-2); }
		.msw-btn.primary.is-off { background: var(--surface-3); color: var(--fg-3); cursor: not-allowed; }
		.msw-btn.ghost { background: var(--surface); color: var(--fg-1); border-color: var(--border-strong); }
		.msw-btn.quiet { background: transparent; color: var(--fg-3); }
		.msw-btn.quiet:hover { color: var(--fg-1); }
		</style>
		<?php
	}

	/**
	 * The dialog's behaviour, printed once.
	 *
	 * Progressive enhancement on purpose: the form still posts to admin-post.php
	 * and still works with this script absent or broken. All it does is
	 * intercept the submit, send the same fields to the AJAX twin, and swap in
	 * the step it returns.
	 *
	 * @param bool $ar Arabic?
	 */
	private static function print_js( $ar ) {
		static $done = false;
		if ( $done ) {
			return;
		}
		$done = true;
		$strings = array(
			'ajax'  => admin_url( 'admin-ajax.php' ),
			'error' => $ar ? 'حصلت مشكلة. جرّب تاني.' : 'Something went wrong. Please try again.',
		);
		?>
		<script id="mersal-suite-wizard-js">
		(function () {
			var CFG   = <?php echo wp_json_encode( $strings ); ?>;
			var modal = document.querySelector('[data-msw-modal]');
			if (!modal) return;
			var body  = modal.querySelector('[data-msw-body]');

			function leave() { window.location.href = modal.getAttribute('data-msw-exit'); }

			// Which button was pressed decides the move, so it has to be read
			// before the form is serialised — a plain FormData does not include it.
			var pressed = null;
			modal.addEventListener('click', function (e) {
				var b = e.target.closest('button[name="move"]');
                if (b) pressed = b.value;
				if (e.target.closest('[data-msw-close]')) { e.preventDefault(); leave(); }
			});

			modal.addEventListener('keydown', function (e) {
				if (e.key === 'Escape') leave();
			});

			modal.addEventListener('submit', function (e) {
				var form = e.target.closest('[data-msw-form]');
				if (!form) return;
				e.preventDefault();

				var data = new FormData(form);
				var move = (e.submitter && e.submitter.value) || pressed || 'next';
				data.set('move', move);
				data.set('action', 'mersal_suite_wizard_step');
				pressed = null;

				body.classList.add('is-busy');

				fetch(CFG.ajax, { method: 'POST', credentials: 'same-origin', body: data })
					.then(function (r) { return r.json(); })
					.then(function (j) {
						if (!j || !j.success) { throw new Error('bad response'); }
						if (j.data && j.data.redirect) { window.location.href = j.data.redirect; return; }
						body.innerHTML = j.data.html;
						body.classList.remove('is-busy');
						body.scrollTop = 0;
						// Keep the address bar honest so a refresh lands on the
						// step the admin is actually looking at.
						if (j.data.url && window.history && window.history.replaceState) {
							window.history.replaceState({}, '', j.data.url);
						}
						// Freshly-injected .mi spans still hold their icon NAME as
						// text; each plugin exposes the same swap under its own
						// global, so re-run whichever one is present.
						['MOTPicons', 'MNOTIFicons', 'MCAMPicons'].forEach(function (g) {
							if (window[g] && typeof window[g].swap === 'function') { window[g].swap(body); }
						});
						document.dispatchEvent(new CustomEvent('mersal-wizard-step', { detail: j.data }));
					})
					.catch(function () {
						// Fall back to the ordinary post rather than stranding them.
						body.classList.remove('is-busy');
						var hidden = document.createElement('input');
						hidden.type = 'hidden'; hidden.name = 'move'; hidden.value = move;
						form.appendChild(hidden);
						form.submit();
					});
			});
		})();
		</script>
		<?php
	}
}
