<?php
namespace Pay4AllForMypos\Backend;

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

/**
 * 4-step installation wizard for Pay4All for myPOS.
 *
 *   1. Pack Production      - optional, "Skip - Test mode only" available
 *   2. Payment methods      - card / ideal / twint / iris / satispay / all
 *   3. WC statuses          - virtual / non-virtual / deferred / failed / cancelled (+ P4Shop when available)
 *   4. État                 - status recap + mode cursor + trash button for the Production Pack
 *
 * The Test Pack is loaded silently from PAY4ALL_FOR_MYPOS_DEFAULT_TEST_PACK
 * on every wizard render, so the merchant never has to paste anything for
 * Test mode.
 *
 * Until the wizard is marked complete (option
 * `pay4all_for_mypos_wizard_complete` === 'yes') the mode cursor on the
 * settings page is disabled, so a merchant can't accidentally flip to
 * production before the credentials pipeline has been verified.
 *
 * Note: unlike the Stripe variant, there is NO webhook creation step -
 * myPOS uses a fixed callback URL (`home_url('/?wc-api=wc_gateway_pay4all_for_mypos')`)
 * that the merchant hard-codes in the myPOS portal when they generate
 * the Pack.
 */
final class WizardPage {

    const OPT_COMPLETE = 'pay4all_for_mypos_wizard_complete';
    const SLUG         = 'pay4all-for-mypos-wizard';

    public static function register() : void {
        add_action( 'admin_post_pay4all_for_mypos_wizard_save_pack',          [ self::class, 'handle_save_pack' ] );
        add_action( 'admin_post_pay4all_for_mypos_wizard_save_methods',       [ self::class, 'handle_save_methods' ] );
        add_action( 'admin_post_pay4all_for_mypos_wizard_save_statuses',      [ self::class, 'handle_save_statuses' ] );
        add_action( 'admin_post_pay4all_for_mypos_wizard_finish',             [ self::class, 'handle_finish' ] );
        add_action( 'admin_post_pay4all_for_mypos_wizard_reset',              [ self::class, 'handle_reset' ] );
        add_action( 'admin_post_pay4all_for_mypos_wizard_delete_credential',  [ self::class, 'handle_delete_credential' ] );
    }

    public static function is_complete() : bool {
        return get_option( self::OPT_COMPLETE ) === 'yes';
    }

    public static function url( int $step = 0 ) : string {
        $args = [ 'page' => self::SLUG ];
        if ( $step > 0 ) { $args['step'] = $step; }
        return add_query_arg( $args, admin_url( 'admin.php' ) );
    }

    /**
     * Fixed myPOS IPC callback URL - matches
     * MyposGateway::SLUG's `wc-api` action registered in Batch 3.
     */
    public static function webhook_url() : string {
        return home_url( '/?wc-api=wc_gateway_pay4all_for_mypos' );
    }

    /**
     * Computes the first step a merchant should land on based on what's
     * already configured. Fresh install -> 1. All done but not marked
     * complete -> 4.
     */
    private static function compute_initial_step() : int {
        $has_live      = ConfigPage::get_pack( 'live' ) !== '';
        $prod_deferred = get_option( 'pay4all_for_mypos_prod_deferred' ) === 'yes';
        $has_meth      = get_option( 'pay4all_for_mypos_methods_enabled' ) !== false;
        $has_status    = get_option( 'pay4all_for_mypos_status_non_virtual' ) !== false
                       && get_option( 'pay4all_for_mypos_status_virtual' ) !== false;

        if ( ! $has_live && ! $prod_deferred ) return 1;
        if ( ! $has_meth ) return 2;
        if ( ! $has_status ) return 3;
        return 4;
    }

    /**
     * If the merchant hasn't provided a Test Pack yet, silently save the
     * shared myPOS demo credentials shipped with the plugin. Called on
     * every wizard render so the auto-load happens the very first time the
     * page opens - the merchant never needs to paste a Pack for Test mode.
     *
     * Ignore decode failures: if the constant is malformed (shouldn't
     * happen in a released build), Test mode simply has no credentials
     * until the merchant supplies their own via the settings page.
     */
    private static function maybe_bootstrap_default_test_pack() : void {
        if ( ! defined( 'PAY4ALL_FOR_MYPOS_DEFAULT_TEST_PACK' ) ) { return; }
        $default = PAY4ALL_FOR_MYPOS_DEFAULT_TEST_PACK;
        if ( ! is_string( $default ) || $default === '' )         { return; }
        if ( ConfigPage::get_pack( 'test' ) !== '' )              { return; }
        try {
            ConfigPage::set_pack( 'test', $default );
        } catch ( \Throwable $e ) {
            // Silent - Test mode will simply have no credentials.
        }
    }

    /* --------------------------------------------------------------------- *
     *  Rendering
     * --------------------------------------------------------------------- */

    public static function render() : void {
        $cap = ( defined( 'PAY4ALL_FOR_MYPOS_WC_ACTIVE' ) && PAY4ALL_FOR_MYPOS_WC_ACTIVE ) ? 'manage_woocommerce' : 'manage_options';
        if ( ! current_user_can( $cap ) ) {
            return;
        }

        self::enqueue_wizard_assets();
        self::maybe_bootstrap_default_test_pack();

        $flash = get_transient( 'pay4all_for_mypos_wizard_flash' );
        if ( is_array( $flash ) ) {
            delete_transient( 'pay4all_for_mypos_wizard_flash' );
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only step navigation.
        $step = isset( $_GET['step'] ) ? max( 1, min( 4, (int) $_GET['step'] ) ) : self::compute_initial_step();
        ?>
        <div class="wrap pay4all-for-mypos-wizard">
            <h1 class="pay4all-for-mypos-wizard-title"><?php esc_html_e( 'Pay4All for myPOS installation wizard', 'pay4all-for-mypos' ); ?></h1>

            <?php if ( ! empty( $flash ) ) : ?>
                <div class="notice notice-<?php echo esc_attr( $flash['type'] ); ?> is-dismissible">
                    <p><?php echo wp_kses_post( $flash['message'] ); ?></p>
                </div>
            <?php endif; ?>

            <?php self::render_step_pills( $step ); ?>

            <?php
            switch ( $step ) {
                case 2: self::render_step_methods();      break;
                case 3: self::render_step_statuses();     break;
                case 4: self::render_step_status_recap(); break;
                default: self::render_step_pack_prod();
            }
            ?>
        </div>
        <?php
    }

    private static function render_step_pills( int $step ) : void {
        $labels = [
            1 => __( 'Production Pack', 'pay4all-for-mypos' ),
            2 => __( 'Payment methods', 'pay4all-for-mypos' ),
            3 => __( 'Order', 'pay4all-for-mypos' ),
            4 => __( 'Status', 'pay4all-for-mypos' ),
        ];
        ?>
        <ol class="pay4all-for-mypos-wizard-steps">
            <?php foreach ( $labels as $num => $label ) :
                $state    = ( $num === $step ) ? 'is-active' : ( ( $num < $step ) ? 'is-done' : 'is-todo' );
                $is_final = ( $num === 4 );
                ?>
                <li class="<?php echo esc_attr( $state . ( $is_final ? ' is-final' : '' ) ); ?>">
                    <a href="<?php echo esc_url( self::url( $num ) ); ?>">
                        <span class="pay4all-for-mypos-step-num">
                            <?php if ( $is_final ) : ?>
                                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>
                            <?php else : ?>
                                <?php echo (int) $num; ?>
                            <?php endif; ?>
                        </span>
                        <span class="pay4all-for-mypos-step-label"><?php echo esc_html( $label ); ?></span>
                    </a>
                </li>
            <?php endforeach; ?>
        </ol>
        <?php
    }

    /* --------------------------------------------------------------------- *
     *  Step 1 : Production Pack
     * --------------------------------------------------------------------- */

    private static function render_step_pack_prod() : void {
        $pack_present  = ConfigPage::get_pack( 'live' ) !== '';
        $decoded       = $pack_present ? ConfigPage::decode_pack( ConfigPage::get_pack( 'live' ) ) : [];
        $guide_url     = KeysGuidePage::url( 'prod' );
        $mypos_portal  = 'https://www.mypos.com/en/login';
        ?>
        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pay4all-for-mypos-wizard-form">
            <input type="hidden" name="action" value="pay4all_for_mypos_wizard_save_pack">
            <input type="hidden" name="mode" value="live">
            <?php wp_nonce_field( 'pay4all_for_mypos_wizard_save_pack' ); ?>

            <section class="pay4all-for-mypos-wizard-card">
                <h2><?php esc_html_e( 'Your Production Pack', 'pay4all-for-mypos' ); ?></h2>
                <p class="pay4all-for-mypos-card-intro">
                    <?php esc_html_e( 'The Production Pack is used to accept real payments. Available in the myPOS merchant portal after your account has been approved and your IBAN verified.', 'pay4all-for-mypos' ); ?>
                </p>

                <div style="background:#f0fdf4;border-left:4px solid #22c55e;padding:12px 16px;border-radius:4px;margin:0 0 20px;">
                    <p style="margin:0;">
                        <?php
                        printf(
                            wp_kses(
                                /* translators: 1: link to the visual tutorial, 2: link to the myPOS portal. */
                                __( 'Need help finding your Pack? Check the step-by-step %1$s or open %2$s directly.', 'pay4all-for-mypos' ),
                                [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [] ] ]
                            ),
                            '<a href="' . esc_url( $guide_url ) . '"><strong>' . esc_html__( 'visual tutorial', 'pay4all-for-mypos' ) . '</strong></a>',
                            '<a href="' . esc_url( $mypos_portal ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $mypos_portal ) . '</a>'
                        );
                        ?>
                    </p>
                </div>

                <?php if ( $pack_present && ! empty( $decoded ) ) : ?>
                <div style="background:#f0fdf4;border-left:4px solid #22c55e;padding:10px 14px;border-radius:4px;margin:0 0 16px;">
                    <p style="margin:0;font-size:13px;">
                        <strong><?php esc_html_e( 'Pack saved.', 'pay4all-for-mypos' ); ?></strong>
                        <?php
                        printf(
                            /* translators: 1: masked SID, 2: masked wallet number (CN), 3: key index. */
                            esc_html__( 'Store ID %1$s / Wallet %2$s / Key index %3$s.', 'pay4all-for-mypos' ),
                            esc_html( ConfigPage::mask( $decoded['sid'] ) ),
                            esc_html( ConfigPage::mask( $decoded['cn'] ) ),
                            esc_html( $decoded['idx'] )
                        );
                        ?>
                    </p>
                    <p class="description" style="margin:4px 0 0;font-size:12px;">
                        <?php esc_html_e( 'Paste a new Pack below to replace it, or leave the field empty to keep the saved Pack.', 'pay4all-for-mypos' ); ?>
                    </p>
                </div>
                <?php endif; ?>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-pack">
                        <?php esc_html_e( 'Configuration Pack (base64)', 'pay4all-for-mypos' ); ?>
                    </label>
                    <textarea id="pay4all-for-mypos-pack"
                              name="pack"
                              rows="6"
                              placeholder="eyJzaWQiOiIx..."
                              style="width:100%;font-family:Menlo,Consolas,monospace;font-size:12px;"></textarea>
                </div>
            </section>

            <div class="pay4all-for-mypos-actions">
                <span></span>
                <div style="display:flex;gap:.5rem;">
                    <button type="submit" name="skip_prod" value="1" class="button button-secondary">
                        <?php esc_html_e( 'Skip - Test mode only', 'pay4all-for-mypos' ); ?>
                    </button>
                    <button type="submit" class="button button-primary">
                        <?php esc_html_e( 'Save and continue', 'pay4all-for-mypos' ); ?>
                    </button>
                </div>
            </div>
        </form>
        <?php
    }

    /* --------------------------------------------------------------------- *
     *  Step 2 : Payment methods
     * --------------------------------------------------------------------- */

    private static function render_step_methods() : void {
        $methods   = ConfigPage::method_labels();
        $enabled   = ConfigPage::enabled_methods();
        $guide_url = KeysGuidePage::url( 'methods' );
        ?>
        <section class="pay4all-for-mypos-wizard-card">
            <h2><?php esc_html_e( 'Payment methods to enable at checkout', 'pay4all-for-mypos' ); ?></h2>
            <p class="pay4all-for-mypos-card-intro">
                <?php esc_html_e( 'Tick the payment methods you want to offer to your customers on the myPOS payment page.', 'pay4all-for-mypos' ); ?>
            </p>

            <div style="background:#f0fdf4;border-left:4px solid #22c55e;padding:12px 16px;border-radius:4px;margin:0 0 20px;">
                <p style="margin:0;">
                    <?php
                    printf(
                        wp_kses(
                            /* translators: %s: link to the in-plugin tutorial page. */
                            __( 'Need help configuring your payment methods on myPOS? Read the %s.', 'pay4all-for-mypos' ),
                            [ 'a' => [ 'href' => [], 'target' => [], 'rel' => [] ] ]
                        ),
                        '<a href="' . esc_url( $guide_url ) . '"><strong>' . esc_html__( 'visual tutorial', 'pay4all-for-mypos' ) . '</strong></a>'
                    );
                    ?>
                </p>
            </div>

            <div style="background:#fefce8;border-left:4px solid #f59e0b;padding:10px 14px;border-radius:4px;margin:0 0 20px;">
                <p style="margin:0;font-size:13px;">
                    <strong><?php esc_html_e( 'Note:', 'pay4all-for-mypos' ); ?></strong>
                    <?php esc_html_e( 'On the myPOS side, "All methods" overrides any individual method selection - the myPOS payment page will display every method activated on your store, regardless of the other checkboxes below.', 'pay4all-for-mypos' ); ?>
                </p>
            </div>

            <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
                <input type="hidden" name="action" value="pay4all_for_mypos_wizard_save_methods">
                <?php wp_nonce_field( 'pay4all_for_mypos_wizard_save_methods' ); ?>

                <?php foreach ( $methods as $key => $label ) :
                    $is_checked = in_array( $key, $enabled, true );
                    ?>
                    <div class="pay4all-for-mypos-field-row">
                        <label class="pay4all-for-mypos-checkbox">
                            <input type="checkbox" name="payment_methods[]" value="<?php echo esc_attr( $key ); ?>" <?php checked( $is_checked ); ?>>
                            <strong><?php echo esc_html( $label ); ?></strong>
                        </label>
                    </div>
                <?php endforeach; ?>

                <div class="pay4all-for-mypos-actions">
                    <a href="<?php echo esc_url( self::url( 1 ) ); ?>" class="button button-secondary">&larr; <?php esc_html_e( 'Previous step', 'pay4all-for-mypos' ); ?></a>
                    <button type="submit" class="button button-primary">
                        <?php esc_html_e( 'Save and continue', 'pay4all-for-mypos' ); ?>
                    </button>
                </div>
            </form>
        </section>
        <?php
    }

    /* --------------------------------------------------------------------- *
     *  Step 3 : Order statuses (WC + optional Pay4All Shop)
     * --------------------------------------------------------------------- */

    private static function render_step_statuses() : void {
        $has_p4form = ConfigPage::has_pay4all_form();
        $has_wc     = ConfigPage::has_woocommerce();

        // Pay4All Shop reads (scoped keys - never touch WC's shared keys).
        if ( $has_p4form ) {
            $p4form_virtual  = get_option( 'pay4all_for_mypos_p4form_status_virtual',  'p4all_completed' );
            $p4form_physical = get_option( 'pay4all_for_mypos_p4form_status_physical', 'p4all_processing' );
            $p4form_choices  = ConfigPage::p4form_status_choices();
            if ( ! isset( $p4form_choices[ $p4form_virtual ] ) )  { $p4form_virtual  = 'p4all_completed'; }
            if ( ! isset( $p4form_choices[ $p4form_physical ] ) ) { $p4form_physical = 'p4all_processing'; }
        }

        // WooCommerce reads (5 selectors).
        if ( $has_wc ) {
            $wc_paid_labels = [
                'on-hold'    => __( 'On hold', 'pay4all-for-mypos' ),
                'processing' => __( 'In progress', 'pay4all-for-mypos' ),
                'completed'  => __( 'Completed', 'pay4all-for-mypos' ),
            ];
            $wc_failed_labels = [
                'pending' => __( 'Pending payment', 'pay4all-for-mypos' ),
                'failed'  => __( 'Failed', 'pay4all-for-mypos' ),
            ];
            $wc_cancelled_labels = [
                'pending'   => __( 'Pending payment', 'pay4all-for-mypos' ),
                'cancelled' => __( 'Cancelled', 'pay4all-for-mypos' ),
            ];
            $wc = [
                'status_non_virtual' => ConfigPage::get_status_non_virtual(),
                'status_virtual'     => ConfigPage::get_status_virtual(),
                'status_deferred'    => ConfigPage::get_status_deferred(),
                'status_failed'      => ConfigPage::get_status_failed(),
                'status_cancelled'   => ConfigPage::get_status_cancelled(),
            ];
        }
        ?>
        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pay4all-for-mypos-wizard-form">
            <input type="hidden" name="action" value="pay4all_for_mypos_wizard_save_statuses">
            <input type="hidden" name="redirect_to" value="<?php echo esc_url( self::url( 4 ) ); ?>">
            <?php wp_nonce_field( 'pay4all_for_mypos_wizard_save_statuses' ); ?>

            <?php if ( $has_p4form ) : ?>
            <section class="pay4all-for-mypos-wizard-card">
                <h2><?php esc_html_e( 'Pay4All Shop order status', 'pay4all-for-mypos' ); ?></h2>
                <p class="pay4all-for-mypos-card-intro">
                    <?php esc_html_e( 'Choose the status the Pay4All Shop order should switch to as soon as the myPOS payment is confirmed.', 'pay4all-for-mypos' ); ?>
                </p>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-p4form-status-physical"><?php esc_html_e( 'Order containing at least one physical product', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-p4form-status-physical" name="pay4all_for_mypos_p4form_status_physical">
                        <?php foreach ( $p4form_choices as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $p4form_physical, $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <p class="description"><?php esc_html_e( 'As soon as a single physical product is present, the order must be prepared and shipped before being marked as completed.', 'pay4all-for-mypos' ); ?></p>
                </div>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-p4form-status-virtual"><?php esc_html_e( 'Order containing only virtual products or tickets', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-p4form-status-virtual" name="pay4all_for_mypos_p4form_status_virtual">
                        <?php foreach ( $p4form_choices as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $p4form_virtual, $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <p class="description"><?php esc_html_e( 'Applied only when every product in the order is virtual or a ticket. No logistical preparation is required, the order can be completed automatically.', 'pay4all-for-mypos' ); ?></p>
                </div>
            </section>
            <?php endif; ?>

            <?php if ( $has_wc ) : ?>
            <section class="pay4all-for-mypos-wizard-card">
                <h2><?php esc_html_e( 'WooCommerce order status', 'pay4all-for-mypos' ); ?></h2>
                <p class="pay4all-for-mypos-card-intro">
                    <?php esc_html_e( 'Sets which status the WooCommerce order moves to as soon as the myPOS payment is confirmed, based on product type and transaction outcome.', 'pay4all-for-mypos' ); ?>
                </p>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-status-non-virtual-wiz"><?php esc_html_e( 'Order containing at least one physical product', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-status-non-virtual-wiz" name="status_non_virtual">
                        <?php foreach ( $wc_paid_labels as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $wc['status_non_virtual'], $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <p class="description"><?php esc_html_e( 'Recommended: "In progress" (the product needs to be shipped).', 'pay4all-for-mypos' ); ?></p>
                </div>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-status-virtual-wiz"><?php esc_html_e( 'Order containing only virtual / downloadable products', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-status-virtual-wiz" name="status_virtual">
                        <?php foreach ( $wc_paid_labels as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $wc['status_virtual'], $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <p class="description"><?php esc_html_e( 'Recommended: "Completed" (nothing to ship, the customer accesses the product right away).', 'pay4all-for-mypos' ); ?></p>
                </div>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-status-deferred-wiz"><?php esc_html_e( 'Deferred payment - awaiting confirmation', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-status-deferred-wiz" name="status_deferred">
                        <?php foreach ( $wc_paid_labels as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $wc['status_deferred'], $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-status-failed-wiz"><?php esc_html_e( 'Payment failed', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-status-failed-wiz" name="status_failed">
                        <?php foreach ( $wc_failed_labels as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $wc['status_failed'], $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <div class="pay4all-for-mypos-field-row">
                    <label for="pay4all-for-mypos-status-cancelled-wiz"><?php esc_html_e( 'Payment cancelled', 'pay4all-for-mypos' ); ?></label>
                    <select id="pay4all-for-mypos-status-cancelled-wiz" name="status_cancelled">
                        <?php foreach ( $wc_cancelled_labels as $val => $lbl ) : ?>
                            <option value="<?php echo esc_attr( $val ); ?>" <?php selected( $wc['status_cancelled'], $val ); ?>><?php echo esc_html( $lbl ); ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>
            </section>
            <?php endif; ?>

            <?php if ( ! $has_p4form && ! $has_wc ) : ?>
            <section class="pay4all-for-mypos-wizard-card">
                <p><?php esc_html_e( 'This tab will appear as soon as a compatible plugin is installed (Pay4All Shop or WooCommerce).', 'pay4all-for-mypos' ); ?></p>
            </section>
            <?php endif; ?>

            <div class="pay4all-for-mypos-actions">
                <a href="<?php echo esc_url( self::url( 2 ) ); ?>" class="button button-secondary">&larr; <?php esc_html_e( 'Previous step', 'pay4all-for-mypos' ); ?></a>
                <button type="submit" class="button button-primary">
                    <?php esc_html_e( 'Save and continue', 'pay4all-for-mypos' ); ?>
                </button>
            </div>
        </form>
        <?php
    }

    /* --------------------------------------------------------------------- *
     *  Step 4 : Recap (État) + finish
     * --------------------------------------------------------------------- */

    private static function render_step_status_recap() : void {
        $has_live  = ConfigPage::get_pack( 'live' ) !== '';
        $methods   = ConfigPage::enabled_methods();
        $has_meth  = ! empty( $methods );
        $has_status = get_option( 'pay4all_for_mypos_status_non_virtual' ) !== false
                      && get_option( 'pay4all_for_mypos_status_virtual' ) !== false;
        $complete  = self::is_complete();
        $prod_deferred = get_option( 'pay4all_for_mypos_prod_deferred' ) === 'yes';
        $all_done  = ( $has_live || $prod_deferred ) && $has_meth && $has_status;

        // $credential : one of 'pack_live' / 'pending_payments_flush' (the
        // latter is a Batch 3 stub - the wizard doesn't render it yet, but
        // the delete handler accepts it so a downstream feature can reuse
        // the same UI convention).
        $row = static function ( bool $ok, string $label, string $credential = '', string $confirm = '' ) : void {
            $icon  = $ok ? '&#x2713;' : '&#x2717;';
            $color = $ok ? '#15803d' : '#a00';
            ?>
            <li style="padding:8px 0;border-bottom:1px solid #f0f0f1;display:flex;align-items:center;gap:.5rem;">
                <span style="display:inline-block;width:22px;color:<?php echo esc_attr( $color ); ?>;font-weight:700;font-size:16px;flex:0 0 auto;"><?php echo wp_kses( $icon, [] ); ?></span>
                <span style="flex:1;"><?php echo esc_html( $label ); ?></span>
                <?php if ( $ok && $credential !== '' ) : ?>
                    <form method="post"
                          action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"
                          style="margin:0;flex:0 0 auto;"
                          onsubmit="return confirm(<?php echo esc_attr( wp_json_encode( $confirm ) ); ?>);">
                        <?php wp_nonce_field( 'pay4all_for_mypos_wizard_delete_credential' ); ?>
                        <input type="hidden" name="action" value="pay4all_for_mypos_wizard_delete_credential">
                        <input type="hidden" name="credential" value="<?php echo esc_attr( $credential ); ?>">
                        <button type="submit"
                                style="color:#b32d2e;border:none;background:transparent;padding:2px 6px;line-height:1;cursor:pointer;"
                                title="<?php esc_attr_e( 'Delete', 'pay4all-for-mypos' ); ?>">
                            <span class="dashicons dashicons-trash" style="font-size:18px;width:18px;height:18px;line-height:1;vertical-align:middle;"></span>
                        </button>
                    </form>
                <?php endif; ?>
            </li>
            <?php
        };
        ?>

        <!-- ============ Operating mode (test / production cursor) ============ -->
        <?php ConfigPage::render_mode_cursor(); ?>

        <section class="pay4all-for-mypos-wizard-card">
            <h2><?php esc_html_e( 'Configuration status', 'pay4all-for-mypos' ); ?></h2>
            <p class="pay4all-for-mypos-card-intro">
                <?php esc_html_e( 'Steps summary. Complete the configuration to enable the "Operating mode" switch above.', 'pay4all-for-mypos' ); ?>
            </p>

            <ul style="list-style:none;padding:0;margin:0 0 20px;">
                <?php
                $row(
                    $has_live,
                    __( 'Production Pack saved', 'pay4all-for-mypos' ),
                    'pack_live',
                    __( 'Delete the saved Production Pack? The stored Pack will be removed. The plugin will fall back to Test mode.', 'pay4all-for-mypos' )
                );
                $row( $has_meth, sprintf(
                    /* translators: %d: number of enabled payment methods. */
                    _n( '%d payment method selected', '%d payment methods selected', count( $methods ), 'pay4all-for-mypos' ),
                    count( $methods )
                ) );
                $row( $has_status, __( 'Order statuses configured (physical / virtual)', 'pay4all-for-mypos' ) );
                ?>
            </ul>

            <?php if ( $complete ) : ?>
                <div class="notice notice-success" style="margin:0 0 16px;padding:12px 16px;">
                    <p style="margin:0;">
                        <strong><?php esc_html_e( 'Configuration complete.', 'pay4all-for-mypos' ); ?></strong>
                        <?php esc_html_e( 'You can now flip the "Operating mode" switch above.', 'pay4all-for-mypos' ); ?>
                    </p>
                </div>
            <?php elseif ( $all_done ) : ?>
                <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline-block;">
                    <input type="hidden" name="action" value="pay4all_for_mypos_wizard_finish">
                    <?php wp_nonce_field( 'pay4all_for_mypos_wizard_finish' ); ?>
                    <div class="pay4all-for-mypos-actions">
                        <span></span>
                        <button type="submit" class="button button-primary">
                            <?php esc_html_e( 'Finish configuration', 'pay4all-for-mypos' ); ?>
                        </button>
                    </div>
                </form>
            <?php else : ?>
                <p class="description" style="color:#a00;">
                    <?php esc_html_e( 'One or more steps are incomplete. Go back through the steps marked X to complete them.', 'pay4all-for-mypos' ); ?>
                </p>
            <?php endif; ?>
        </section>
        <?php
    }

    /* --------------------------------------------------------------------- *
     *  POST handlers
     * --------------------------------------------------------------------- */

    public static function handle_save_pack() : void {
        self::assert_admin_and_nonce( 'pay4all_for_mypos_wizard_save_pack' );

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
        $mode = isset( $_POST['mode'] ) ? sanitize_key( (string) wp_unslash( $_POST['mode'] ) ) : 'live';
        if ( ! in_array( $mode, [ 'live', 'test' ], true ) ) {
            $mode = 'live';
        }
        // Textarea content : strip only outer whitespace. sanitize_textarea_field
        // would collapse newlines - fine for base64 but breaks defensive
        // decoding of a merchant who pasted the raw JSON by mistake.
        // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- nonce verified above ; the value is validated by ConfigPage::decode_pack() before use.
        $pack = isset( $_POST['pack'] ) ? trim( wp_unslash( (string) $_POST['pack'] ) ) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
        $skip_prod = $mode === 'live' && ! empty( $_POST['skip_prod'] );

        // Prod-skip shortcut : the merchant explicitly clicked "Skip - Test
        // mode only". Record the deferred state so compute_initial_step()
        // stops sending them to step 1 on every wizard reload.
        if ( $skip_prod ) {
            update_option( 'pay4all_for_mypos_prod_deferred', 'yes', false );
            self::flash_and_redirect( 'success',
                __( 'Production Pack skipped. The plugin will operate in Test mode only until you add it later.', 'pay4all-for-mypos' ),
                2
            );
        }

        // Empty Pack in Live mode = "keep the current one, if any, and just
        // move on". This lets the merchant hit "Save and continue" on step 1
        // when they already saved a Pack earlier and don't want to re-paste
        // it.
        if ( $pack === '' ) {
            if ( $mode === 'live' ) {
                if ( ConfigPage::get_pack( 'live' ) === '' ) {
                    // No Pack, no skip flag - treat as an implicit skip.
                    update_option( 'pay4all_for_mypos_prod_deferred', 'yes', false );
                    self::flash_and_redirect( 'success',
                        __( 'No production Pack provided - continuing in Test-only mode.', 'pay4all-for-mypos' ),
                        2
                    );
                }
                self::flash_and_redirect( 'success', __( 'Production Pack preserved.', 'pay4all-for-mypos' ), 2 );
            }
            // Test mode empty save - defensive fallback, no wizard step
            // exposes this form anymore. Just bounce back to step 1.
            self::flash_and_redirect( 'success', __( 'Test Pack preserved.', 'pay4all-for-mypos' ), 1 );
        }

        try {
            ConfigPage::set_pack( $mode, $pack );
        } catch ( \InvalidArgumentException $e ) {
            self::flash_and_redirect( 'error',
                __( 'The Configuration Pack is invalid. Expected a base64-encoded JSON with sid, cn, pk, pc and idx.', 'pay4all-for-mypos' ),
                1
            );
        } catch ( \Throwable $e ) {
            self::flash_and_redirect( 'error',
                sprintf(
                    /* translators: %s: error message. */
                    __( 'The Pack could not be saved: %s', 'pay4all-for-mypos' ),
                    $e->getMessage()
                ),
                1
            );
        }

        // Merchant actually saved live Pack -> clear any stale "deferred"
        // flag so the État recap offers the Prod/Test toggle again.
        if ( $mode === 'live' ) {
            delete_option( 'pay4all_for_mypos_prod_deferred' );
        }

        $mode_label = $mode === 'live' ? __( 'production', 'pay4all-for-mypos' ) : __( 'test', 'pay4all-for-mypos' );
        self::flash_and_redirect(
            'success',
            sprintf(
                /* translators: %s: mode name (test / production). */
                __( '%s Pack saved.', 'pay4all-for-mypos' ),
                ucfirst( $mode_label )
            ),
            2
        );
    }

    public static function handle_save_methods() : void {
        self::assert_admin_and_nonce( 'pay4all_for_mypos_wizard_save_methods' );

        // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- nonce verified above ; each element sanitized by sanitize_key() below.
        $raw = isset( $_POST['payment_methods'] ) ? (array) wp_unslash( $_POST['payment_methods'] ) : [];
        $clean = array_values( array_unique( array_map( 'sanitize_key', $raw ) ) );

        $allowed = array_keys( ConfigPage::method_labels() );
        $clean   = array_values( array_intersect( $clean, $allowed ) );

        update_option( 'pay4all_for_mypos_methods_enabled', $clean, false );

        self::flash_and_redirect( 'success', sprintf(
            /* translators: %d: number of methods saved. */
            _n( '%d payment method selected.', '%d payment methods selected.', count( $clean ), 'pay4all-for-mypos' ),
            count( $clean )
        ), 3 );
    }

    /**
     * Wizard-specific status handler - thin wrapper that forwards to
     * ConfigPage::handle_save_statuses() then jumps to step 4. Kept as
     * its own handler so the redirect_to logic stays local (no need to
     * cross the ConfigPage boundary just for the target URL).
     */
    public static function handle_save_statuses() : void {
        self::assert_admin_and_nonce( 'pay4all_for_mypos_wizard_save_statuses' );

        $allowed_paid      = [ 'on-hold', 'processing', 'completed' ];
        $allowed_failed    = [ 'pending', 'failed' ];
        $allowed_cancelled = [ 'pending', 'cancelled' ];

        // phpcs:disable WordPress.Security.NonceVerification.Missing -- nonce verified above via self::assert_admin_and_nonce().
        $non_virtual = isset( $_POST['status_non_virtual'] ) ? sanitize_key( (string) wp_unslash( $_POST['status_non_virtual'] ) ) : PAY4ALL_FOR_MYPOS_DEFAULT_STATUS_ORDER;
        $virtual     = isset( $_POST['status_virtual'] )     ? sanitize_key( (string) wp_unslash( $_POST['status_virtual'] ) )     : PAY4ALL_FOR_MYPOS_DEFAULT_STATUS_VIRTUAL;
        if ( ! in_array( $non_virtual, $allowed_paid, true ) ) { $non_virtual = PAY4ALL_FOR_MYPOS_DEFAULT_STATUS_ORDER; }
        if ( ! in_array( $virtual, $allowed_paid, true ) )     { $virtual     = PAY4ALL_FOR_MYPOS_DEFAULT_STATUS_VIRTUAL; }
        update_option( 'pay4all_for_mypos_status_non_virtual', $non_virtual, false );
        update_option( 'pay4all_for_mypos_status_virtual',     $virtual,     false );

        if ( isset( $_POST['status_deferred'] ) ) {
            $val = sanitize_key( (string) wp_unslash( $_POST['status_deferred'] ) );
            update_option( 'pay4all_for_mypos_status_deferred', in_array( $val, $allowed_paid, true ) ? $val : PAY4ALL_FOR_MYPOS_DEFAULT_STATUS_DEFERRED, false );
        }
        if ( isset( $_POST['status_failed'] ) ) {
            $val = sanitize_key( (string) wp_unslash( $_POST['status_failed'] ) );
            update_option( 'pay4all_for_mypos_status_failed', in_array( $val, $allowed_failed, true ) ? $val : 'pending', false );
        }
        if ( isset( $_POST['status_cancelled'] ) ) {
            $val = sanitize_key( (string) wp_unslash( $_POST['status_cancelled'] ) );
            update_option( 'pay4all_for_mypos_status_cancelled', in_array( $val, $allowed_cancelled, true ) ? $val : 'cancelled', false );
        }

        // Pay4All Shop-scoped keys.
        $allowed_p4form = array_keys( ConfigPage::p4form_status_choices() );
        if ( isset( $_POST['pay4all_for_mypos_p4form_status_virtual'] ) ) {
            $val = sanitize_key( (string) wp_unslash( $_POST['pay4all_for_mypos_p4form_status_virtual'] ) );
            if ( in_array( $val, $allowed_p4form, true ) ) {
                update_option( 'pay4all_for_mypos_p4form_status_virtual', $val, false );
            }
        }
        if ( isset( $_POST['pay4all_for_mypos_p4form_status_physical'] ) ) {
            $val = sanitize_key( (string) wp_unslash( $_POST['pay4all_for_mypos_p4form_status_physical'] ) );
            if ( in_array( $val, $allowed_p4form, true ) ) {
                update_option( 'pay4all_for_mypos_p4form_status_physical', $val, false );
            }
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        self::flash_and_redirect( 'success', __( 'Order statuses saved.', 'pay4all-for-mypos' ), 4 );
    }

    public static function handle_finish() : void {
        self::assert_admin_and_nonce( 'pay4all_for_mypos_wizard_finish' );
        update_option( self::OPT_COMPLETE, 'yes', false );
        self::flash_and_redirect( 'success', __( 'Wizard finished - the "Operating mode" switch is now active.', 'pay4all-for-mypos' ), 4 );
    }

    public static function handle_reset() : void {
        self::assert_admin_and_nonce( 'pay4all_for_mypos_wizard_reset' );
        delete_option( self::OPT_COMPLETE );
        self::flash_and_redirect( 'success', __( 'Wizard reset.', 'pay4all-for-mypos' ), 1 );
    }

    /**
     * Deletes one of the credentials rendered on step 4 (État):
     *
     *   - `pack_live`               : the encrypted Production Pack. Flips
     *                                 the mode back to Test.
     *   - `pending_payments_flush`  : Batch 3 reserved slot. Currently a
     *                                 no-op ; kept in the whitelist so the
     *                                 wizard UI can render the button early
     *                                 without hitting an "unknown credential"
     *                                 error mid-development.
     *
     * The Test Pack is auto-loaded on every render, so deleting it would
     * just get re-bootstrapped - no delete button is exposed for it.
     */
    public static function handle_delete_credential() : void {
        self::assert_admin_and_nonce( 'pay4all_for_mypos_wizard_delete_credential' );

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
        $credential = isset( $_POST['credential'] ) ? sanitize_key( (string) wp_unslash( $_POST['credential'] ) ) : '';
        $allowed    = [ 'pack_live', 'pending_payments_flush' ];
        if ( ! in_array( $credential, $allowed, true ) ) {
            self::flash_and_redirect( 'error', __( 'Unknown credential to delete.', 'pay4all-for-mypos' ), 4 );
        }

        if ( $credential === 'pack_live' ) {
            delete_option( 'pay4all_for_mypos_pack_live' );
            // Flip back to Test mode + clear the deferred flag so the État
            // recap shows a consistent state on the next render.
            update_option( 'pay4all_for_mypos_mode', 'test', false );
            delete_option( 'pay4all_for_mypos_prod_deferred' );
            self::flash_and_redirect( 'success', __( 'Production Pack deleted. The plugin is back in Test mode.', 'pay4all-for-mypos' ), 4 );
        }

        // `pending_payments_flush` - Batch 3 will implement the actual flush.
        self::flash_and_redirect( 'success', __( 'Pending payments flush endpoint stubbed (will run the DB truncation in Batch 3).', 'pay4all-for-mypos' ), 4 );
    }

    /* --------------------------------------------------------------------- *
     *  Helpers
     * --------------------------------------------------------------------- */

    private static function enqueue_wizard_assets() : void {
        if ( ! defined( 'PAY4ALL_FOR_MYPOS_PLUGIN_URL' ) || ! defined( 'PAY4ALL_FOR_MYPOS_PLUGIN_VERSION' ) ) { return; }
        $css_rel = 'assets/admin/css/wizard.css';
        $css_abs = PAY4ALL_FOR_MYPOS_PLUGIN_PATH . '/' . $css_rel;
        if ( ! file_exists( $css_abs ) ) { return; }
        wp_enqueue_style(
            'pay4all-for-mypos-wizard',
            PAY4ALL_FOR_MYPOS_PLUGIN_URL . $css_rel,
            [],
            PAY4ALL_FOR_MYPOS_PLUGIN_VERSION
        );
    }

    private static function assert_admin_and_nonce( string $action ) : void {
        $cap = ( defined( 'PAY4ALL_FOR_MYPOS_WC_ACTIVE' ) && PAY4ALL_FOR_MYPOS_WC_ACTIVE ) ? 'manage_woocommerce' : 'manage_options';
        if ( ! current_user_can( $cap ) ) {
            wp_die( esc_html__( 'Permission denied.', 'pay4all-for-mypos' ), 403 );
        }
        check_admin_referer( $action );
    }

    private static function flash_and_redirect( string $type, string $message, int $step = 1 ) : void {
        set_transient( 'pay4all_for_mypos_wizard_flash', [ 'type' => $type, 'message' => $message ], 30 );
        wp_safe_redirect( self::url( $step ) );
        exit;
    }
}
