<?php

namespace Storeplugin\PDFInvoice\App\Settings;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Framework\Contracts\ServiceInterface;
use Storeplugin\PDFInvoice\Framework\Fields\FieldRenderer;
use Storeplugin\PDFInvoice\App\Settings\SettingsSchema;
use Storeplugin\PDFInvoice\App\Utils;
use Storeplugin\PDFInvoice\Config;
use Storeplugin\PDFInvoice\Asset;

class SetupWizard implements ServiceInterface
{
    private const PAGE_SLUG = 'nxtpdf-setup';
    private const REDIRECT_TARGET_PAGE = 'nxtpdf-invoice-editor-panel';
    private const AJAX_ACTION = 'nxtpdf_setup_wizard_save';
    private const NONCE_ACTION = 'nxtpdf_setup_wizard_nonce';

    /**
     * WordPress hook suffix for the wizard screen.
     *
     * @var string
     */
    private string $hookSuffix = '';

    /**
     * Cached wizard field map (field ID → option group).
     *
     * @var array<string, string>|null
     */
    private ?array $wizardFieldMap = null;

    public function register(): void
    {
        add_action('admin_menu', [$this, 'registerPage']);
        add_action('admin_init', [$this, 'maybeRedirectToWizard']);
        add_action('wp_ajax_' . self::AJAX_ACTION, [$this, 'ajaxSave']);
    }

    /**
     * Register hidden setup wizard admin page.
     */
    public function registerPage(): void
    {
        $hookSuffix = add_submenu_page(
            '',
            __('PDF Invoice Setup Wizard', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            __('PDF Invoice Setup Wizard', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'manage_options',
            self::PAGE_SLUG,
            [$this, 'renderPage']
        );

        if (!is_string($hookSuffix) || $hookSuffix === '') {
            return;
        }

        $this->hookSuffix = $hookSuffix;
        add_action('load-' . $this->hookSuffix, function (): void {
            global $title;

            if (empty($title)) {
                $title = __('PDF Invoice Setup Wizard', 'storeinvoice-print-pdf-invoices-and-packing-slips');
            }
        });
        add_action('load-' . $this->hookSuffix, [$this, 'enqueueAssets']);
        add_filter('admin_body_class', [$this, 'addAdminBodyClass']);
    }

    /**
     * Redirect to wizard once after activation.
     */
    public function maybeRedirectToWizard(): void
    {
        if (!is_admin() || !current_user_can('manage_options')) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is only for redirecting after activation, no sensitive action being performed
        if (is_network_admin() || isset($_GET['activate-multi'])) {
            return;
        }

        if (wp_doing_ajax() || wp_doing_cron() || (defined('REST_REQUEST') && REST_REQUEST) || (defined('WP_CLI') && WP_CLI)) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is only for redirecting after activation, no sensitive action being performed
        $currentPage = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
        if ($currentPage === self::PAGE_SLUG) {
            return;
        }

        if ((string) get_option(SettingsSchema::WIZARD_COMPLETED_OPTION, '') === '1') {
            return;
        }

        if (!get_transient(SettingsSchema::WIZARD_REDIRECT_TRANSIENT)) {
            return;
        }

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

    /**
     * Enqueue wizard assets.
     */
    public function enqueueAssets(): void
    {
        Asset::registerFrameworkAssets();
        Asset::registerAppAdminAssets();

        $setupScript = Config::asset('setup-wizard');
        $styleHandle = Config::asset('style');

        wp_enqueue_style(
            $styleHandle,
            trailingslashit(NXTPDF_URL) . 'assets/css/style.css',
            [],
            Config::version()
        );
        wp_enqueue_style('dashicons');
        wp_enqueue_style(Config::asset('framework'));
        wp_enqueue_style(Config::asset('admin-style'));
        wp_enqueue_style(Config::asset('select2-style'));

        wp_enqueue_script(Config::asset('options'));
        wp_enqueue_script(Config::asset('select2-script'));
        wp_enqueue_script($setupScript);

        wp_localize_script($setupScript, 'nxtpdfSetupWizard', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'action' => self::AJAX_ACTION,
            'nonce' => wp_create_nonce(self::NONCE_ACTION),
            'redirectUrl' => admin_url('admin.php?page=' . self::REDIRECT_TARGET_PAGE),
            'countries' => $this->getCountries(),
            'states' => $this->getStates(),
            'strings' => [
                'stepLabel' => __('Step', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'ofLabel' => __('of', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'saving' => __('Saving...', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'finish' => __('Finish Setup', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'saveError' => __('Unable to save setup wizard settings.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'unexpectedError' => __('Something went wrong. Please try again.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'statePlaceholder' => __('Select state / province', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'stateInputPlaceholder' => __('State or province', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            ],
        ]);
    }

    /**
     * Add a scoped admin body class for the setup wizard screen.
     */
    public function addAdminBodyClass(string $classes): string
    {
        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        $screenId = is_object($screen) && isset($screen->id) ? (string) $screen->id : '';

        if ($this->hookSuffix !== '' && $screenId === $this->hookSuffix) {
            return trim($classes . ' nxtpdf-setup-wizard-admin');
        }

        return $classes;
    }

    /**
     * Render setup wizard page.
     */
    public function renderPage(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Unauthorized', 'storeinvoice-print-pdf-invoices-and-packing-slips'));
        }

        $steps = $this->getSteps();
        $totalSteps = count($steps);
        $initialProgressWidth = (int) round((1 / max(1, $totalSteps)) * 100);

        $settingsOptions = Utils::getOptions(Utils::GROUP_SETTINGS);
        $editorOptions = Utils::getOptions(Utils::GROUP_INVOICE_EDITOR);

        ?>
        <div id="nxtpdf-setup-wizard" class="wrap nxtpdf-setup-wizard-wrap">
            <div class="nxtpdf-setup-shell">
                <div class="nxtpdf-setup-container">
                    <div class="nxtpdf-setup-card">
                        <header class="nxtpdf-setup-header">
                            <p class="nxtpdf-setup-kicker"><?php echo esc_html__('Welcome', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?></p>
                            <h1 class="nxtpdf-setup-title"><?php echo esc_html__('Set Up PDF Invoice', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?></h1>
                            <p class="nxtpdf-setup-subtitle"><?php echo esc_html__('Complete these steps to configure your invoice defaults.', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?></p>
                        </header>

                        <div class="nxtpdf-setup-progress">
                            <div class="nxtpdf-setup-progress-row">
                                <p data-wizard-progress-text class="nxtpdf-setup-progress-text">
                                    <?php
                                    /* translators: 1: Current wizard step number, 2: Total number of wizard steps. */
                                    echo esc_html(sprintf(__('Step %1$d of %2$d', 'storeinvoice-print-pdf-invoices-and-packing-slips'), 1, max(1, $totalSteps)));
                                    ?>
                                </p>
                                <div class="nxtpdf-setup-indicators">
                                    <?php for ($index = 0; $index < $totalSteps; $index++): ?>
                                        <?php
                                        $indicatorClass = 'nxtpdf-setup-indicator '
                                            . ($index === 0 ? 'is-current' : 'is-pending');
                                        ?>
                                        <span
                                            data-wizard-indicator
                                            class="<?php echo esc_attr($indicatorClass); ?>"
                                        >
                                            <?php echo esc_html((string) ($index + 1)); ?>
                                        </span>
                                    <?php endfor; ?>
                                </div>
                            </div>
                            <div class="nxtpdf-setup-progress-track">
                                <div data-wizard-progress-bar class="nxtpdf-setup-progress-bar" style="<?php echo esc_attr(sprintf('width: %d%%;', $initialProgressWidth)); ?>"></div>
                            </div>
                        </div>

                        <form id="nxtpdf-setup-form" class="nxtpdf-setup-form">
                            <?php foreach ($steps as $stepIndex => $step): ?>
                                <section data-wizard-step="<?php echo esc_attr((string) $stepIndex); ?>" class="<?php echo esc_attr($stepIndex === 0 ? 'nxtpdf-setup-step' : 'nxtpdf-setup-step nxtpdf-hidden'); ?>">
                                    <div class="nxtpdf-setup-step-head">
                                        <h2 class="nxtpdf-setup-step-title"><?php echo esc_html($step['title']); ?></h2>
                                        <?php if (!empty($step['description'])): ?>
                                            <p class="nxtpdf-setup-step-description"><?php echo esc_html($step['description']); ?></p>
                                        <?php endif; ?>
                                    </div>

                                    <?php if (!empty($step['fields'])): ?>
                                        <div class="nxtpdf-setup-step-fields">
                                            <?php $this->renderStepFields($step['fields'], $settingsOptions, $editorOptions); ?>
                                        </div>
                                    <?php else: ?>
                                        <div class="nxtpdf-setup-success">
                                            <div class="nxtpdf-setup-success-row">
                                                <span class="dashicons dashicons-yes-alt nxtpdf-setup-success-icon"></span>
                                                <div>
                                                    <p class="nxtpdf-setup-success-title"><?php echo esc_html__('Everything is ready.', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?></p>
                                                    <p class="nxtpdf-setup-success-description"><?php echo esc_html__('Click Finish Setup to save your choices and continue to settings.', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?></p>
                                                </div>
                                            </div>
                                        </div>
                                    <?php endif; ?>
                                </section>
                            <?php endforeach; ?>

                            <div class="nxtpdf-setup-actions">
                                <p data-wizard-error class="nxtpdf-setup-error nxtpdf-hidden"></p>
                                <div class="nxtpdf-setup-actions-group">
                                    <button type="button" data-wizard-back class="nxtpdf-setup-btn nxtpdf-setup-btn-back">
                                        <?php echo esc_html__('Back', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?>
                                    </button>
                                    <button type="button" data-wizard-skip class="nxtpdf-setup-btn nxtpdf-setup-btn-skip">
                                        <?php echo esc_html__('Skip', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?>
                                    </button>
                                    <button type="button" data-wizard-next class="nxtpdf-setup-btn nxtpdf-setup-btn-next">
                                        <?php echo esc_html__('Next', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?>
                                    </button>
                                    <button type="button" data-wizard-finish class="nxtpdf-setup-btn nxtpdf-setup-btn-finish nxtpdf-hidden">
                                        <?php echo esc_html__('Finish Setup', 'storeinvoice-print-pdf-invoices-and-packing-slips'); ?>
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Render fields for a wizard step.
     *
     * @param array<string, mixed> $fields
     * @param array<string, mixed> $settingsOptions
     * @param array<string, mixed> $editorOptions
     */
    private function renderStepFields(array $fields, array $settingsOptions, array $editorOptions): void
    {
        foreach ($fields as $field) {
            if (!is_array($field) || empty($field['id']) || empty($field['group'])) {
                continue;
            }

            $fieldConfig = $field;
            $optionGroup = sanitize_key((string) $fieldConfig['group']);
            unset($fieldConfig['group']);

            $value = $this->resolveFieldValue($field, $settingsOptions, $editorOptions);
            FieldRenderer::render($fieldConfig, $value, $optionGroup);
        }
    }

    /**
     * Resolve current value for wizard field.
     *
     * @param array<string, mixed> $field
     * @param array<string, mixed> $settingsOptions
     * @param array<string, mixed> $editorOptions
     * @return mixed
     */
    private function resolveFieldValue(array $field, array $settingsOptions, array $editorOptions)
    {
        $fieldId = sanitize_key((string) ($field['id'] ?? ''));
        $group = sanitize_key((string) ($field['group'] ?? ''));

        if ($fieldId === '' || $group === '') {
            return '';
        }

        $source = $group === Utils::GROUP_SETTINGS ? $settingsOptions : $editorOptions;
        if (array_key_exists($fieldId, $source)) {
            $value = $source[$fieldId];

            // Ignore values that are just the schema placeholder defaults
            // (saved to DB on activation) so wizard fields show empty with placeholders.
            $schemaDefaults = SettingsSchema::settingsDefaults();
            if (isset($schemaDefaults[$fieldId]) && $value === $schemaDefaults[$fieldId]) {
                return '';
            }

            return $value;
        }

        // Use wizard field-level default if defined.
        if (array_key_exists('default', $field)) {
            return $field['default'];
        }

        if ($fieldId === 'attach_to_emails') {
            return [];
        }

        return '';
    }

    /**
     * Setup wizard AJAX save.
     */
    public function ajaxSave(): void
    {
        check_ajax_referer(self::NONCE_ACTION, 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error([
                'message' => __('Unauthorized', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            ]);
        }

        if (!isset($_POST['settings'])) {
            wp_send_json_error([
                'message' => __('No settings payload received.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            ]);
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Raw settings are unslashed in parseSettingsPayload() and sanitized per-field in sanitizeWizardSettings().
        $parsedSettings = $this->parseSettingsPayload($_POST['settings']);
        $flattenedSettings = $this->flattenSettings($parsedSettings);
        $sanitizedSettings = $this->sanitizeWizardSettings($flattenedSettings);

        $groupedSettings = $this->groupSettingsByOptionGroup($sanitizedSettings);

        if (!empty($sanitizedSettings['logo_url'])) {
            $groupedSettings[Utils::GROUP_INVOICE_EDITOR]['show_logo'] = true;
        }

        foreach ($groupedSettings as $group => $values) {
            if (!is_array($values) || empty($values)) {
                continue;
            }

            $existing = Utils::getOptions($group);
            $merged = array_merge($existing, $values);
            if ($merged !== $existing) {
                Utils::updateOptions($group, $merged);
            }
        }

        update_option(SettingsSchema::WIZARD_COMPLETED_OPTION, '1');
        delete_transient(SettingsSchema::WIZARD_REDIRECT_TRANSIENT);

        wp_send_json_success([
            'redirect_url' => admin_url('admin.php?page=' . self::REDIRECT_TARGET_PAGE),
            'message' => __('Setup completed successfully.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
        ]);
    }

    /**
     * Parse payload from AJAX request.
     *
     * @param mixed $rawSettings
     * @return array<string, mixed>
     */
    private function parseSettingsPayload($rawSettings): array
    {
        $rawSettings = wp_unslash($rawSettings);

        if (is_string($rawSettings)) {
            $parsed = [];
            parse_str($rawSettings, $parsed);
            return is_array($parsed) ? $parsed : [];
        }

        return is_array($rawSettings) ? $rawSettings : [];
    }

    /**
     * Flatten nested settings payload and normalize toggles.
     *
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    private function flattenSettings(array $settings): array
    {
        $flattened = [];
        $renderedToggles = [];

        $knownGroups = [Utils::GROUP_SETTINGS, Utils::GROUP_INVOICE_EDITOR];

        foreach ($knownGroups as $group) {
            if (!isset($settings[$group]) || !is_array($settings[$group])) {
                continue;
            }

            foreach ($settings[$group] as $key => $value) {
                $this->collectFlattenedValue($flattened, $renderedToggles, $key, $value);
            }

            unset($settings[$group]);
        }

        foreach ($settings as $key => $value) {
            $this->collectFlattenedValue($flattened, $renderedToggles, $key, $value);
        }

        foreach (array_keys($renderedToggles) as $toggleKey) {
            if (!array_key_exists($toggleKey, $flattened)) {
                $flattened[$toggleKey] = false;
            }
        }

        return $flattened;
    }

    /**
     * Collect a single parsed key/value pair into flat settings structure.
     *
     * @param array<string, mixed> $flattened
     * @param array<string, bool>  $renderedToggles
     * @param mixed                $key
     * @param mixed                $value
     */
    private function collectFlattenedValue(array &$flattened, array &$renderedToggles, $key, $value): void
    {
        $key = sanitize_key((string) $key);
        if ($key === '') {
            return;
        }

        if (strpos($key, '_nxtf_rendered_') === 0) {
            $toggleKey = sanitize_key(substr($key, 15));
            if ($toggleKey !== '') {
                $renderedToggles[$toggleKey] = true;
            }
            return;
        }

        $flattened[$key] = $value;
    }

    /**
     * Sanitize wizard settings according to schema and explicit wizard rules.
     *
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    private function sanitizeWizardSettings(array $settings): array
    {
        $wizardMap = $this->getWizardFieldMap();
        $sanitized = [];

        foreach ($settings as $key => $value) {
            $key = sanitize_key((string) $key);
            if ($key === '') {
                continue;
            }

            if (!isset($wizardMap[$key])) {
                continue;
            }

            $sanitized[$key] = $this->sanitizeSettingValue($key, $value);
        }

        return $sanitized;
    }

    /**
     * Sanitize a value for a specific setting key.
     *
     * @param string $key
     * @param mixed  $value
     * @return mixed
     */
    private function sanitizeSettingValue(string $key, $value)
    {
        if ($key === 'attach_to_emails') {
            $values = is_array($value) ? $value : ((is_scalar($value) && (string) $value !== '') ? [$value] : []);
            $allowed = array_keys($this->getAttachToEmailOptions());
            $sanitized = [];

            foreach ($values as $item) {
                if (!is_scalar($item)) {
                    continue;
                }

                $candidate = sanitize_text_field((string) $item);
                if (in_array($candidate, $allowed, true)) {
                    $sanitized[$candidate] = true;
                }
            }

            return array_keys($sanitized);
        }

        return SettingsSchema::sanitizeValue($key, $value);
    }

    /**
     * Group sanitized settings by options group.
     *
     * @param array<string, mixed> $settings
     * @return array<string, array<string, mixed>>
     */
    private function groupSettingsByOptionGroup(array $settings): array
    {
        $wizardMap = $this->getWizardFieldMap();
        $grouped = [];

        foreach ($settings as $key => $value) {
            $group = $wizardMap[$key] ?? '';
            $group = sanitize_key((string) $group);

            if ($group === '') {
                continue;
            }

            if (!isset($grouped[$group])) {
                $grouped[$group] = [];
            }

            $grouped[$group][$key] = $value;
        }

        return $grouped;
    }

    /**
     * Wizard step definitions.
     *
     * @return array<int, array<string, mixed>>
     */
    private function getSteps(): array
    {
        $countries = $this->getCountries();
        if (empty($countries)) {
            $countries = ['' => __('Select country', 'storeinvoice-print-pdf-invoices-and-packing-slips')];
        }

        return [
            [
                'title' => __('Enter your shop name', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'description' => __('Add your shop details used in invoice headers and address blocks.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'fields' => [
                    [
                        'id' => 'company_name',
                        'label' => __('Company Name', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'text',
                        'placeholder' => __('Enter company name', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'company_address_1',
                        'label' => __('Address Line 1', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'text',
                        'placeholder' => __('Enter company address line 1', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'company_address_2',
                        'label' => __('Address Line 2', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'text',
                        'placeholder' => __('Apartment, suite, unit, etc.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'company_country',
                        'label' => __('Country', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'select2',
                        'support' => ['single', 'search'],
                        'placeholder' => __('Select country', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'options' => $countries,
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'company_state',
                        'label' => __('State / Province', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'text',
                        'placeholder' => __('State or province', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'company_city',
                        'label' => __('City', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'text',
                        'placeholder' => __('City', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'company_postcode',
                        'label' => __('Postal Code', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'text',
                        'placeholder' => __('Postal code', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                ],
            ],
            [
                'title' => __('Your logo', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'description' => __('Upload a company logo used on PDF invoices.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'fields' => [
                    [
                        'id' => 'logo_url',
                        'label' => __('Logo', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'file_upload',
                        'placeholder' => __('No image selected', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'button_label' => __('Select image', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'remove_label' => __('Remove image', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'modal_title' => __('Choose a logo', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'modal_button' => __('Use this image', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'library_type' => 'image',
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                ],
            ],
            [
                'title' => __('Attach invoice to...', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'description' => __('Choose which WooCommerce emails should include the PDF invoice.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'fields' => [
                    [
                        'id' => 'attach_to_emails',
                        'label' => __('Email types', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'select2',
                        'support' => ['multi', 'search'],
                        'placeholder' => __('Select email types', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'options' => $this->getAttachToEmailOptions(),
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                ],
            ],
            [
                'title' => __('Display options', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'description' => __('Choose what customer and document details appear on invoices.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'fields' => [
                    [
                        'id' => 'invoice_date_source',
                        'label' => __('Invoice Date', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'select',
                        'default' => 'order',
                        'options' => [
                            'invoice' => __('Invoice Date', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                            'order' => __('Order Date', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        ],
                        'group' => Utils::GROUP_SETTINGS,
                    ],
                    [
                        'id' => 'invoice_number_mode',
                        'label' => __('Invoice Number', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'select',
                        'default' => 'order_number',
                        'options' => [
                            'sequential' => __('Sequential', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                            'order_number' => __('Order Number', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        ],
                        'group' => Utils::GROUP_INVOICE_EDITOR,
                    ],
                ],
            ],
            [
                'title' => __('Paper format', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'description' => __('Choose the document size used by generated PDF invoices.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'fields' => [
                    [
                        'id' => 'paper_size',
                        'label' => __('Paper Size', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                        'type' => 'select',
                        'default' => 'A4',
                        'options' => [
                            'A4' => 'A4',
                            'Letter' => 'Letter',
                            'Legal' => 'Legal',
                        ],
                        'group' => Utils::GROUP_INVOICE_EDITOR,
                    ],
                ],
            ],
            [
                'title' => __('You are good to go!', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'description' => __('Review complete. Save these settings and continue to the main editor.', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
                'fields' => [],
            ],
        ];
    }

    /**
     * Wizard key -> option group map, derived from step definitions.
     *
     * @return array<string, string>
     */
    private function getWizardFieldMap(): array
    {
        if ($this->wizardFieldMap !== null) {
            return $this->wizardFieldMap;
        }

        $map = [];
        foreach ($this->getSteps() as $step) {
            foreach ($step['fields'] ?? [] as $field) {
                if (!empty($field['id']) && !empty($field['group'])) {
                    $map[$field['id']] = $field['group'];
                }
            }
        }

        $this->wizardFieldMap = $map;
        return $map;
    }

    /**
     * Attach-to-email options.
     *
     * @return array<string, string>
     */
    private function getAttachToEmailOptions(): array
    {
        return [
            'new_order' => __('New Order (Admin)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'cancelled_order' => __('Cancelled Order (Admin)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'failed_order' => __('Failed Order (Admin)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'customer_on_hold_order' => __('Order On-Hold (Customer)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'customer_processing_order' => __('Processing Order (Customer)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'customer_completed_order' => __('Completed Order (Customer)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'customer_refunded_order' => __('Refunded Order (Customer)', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
            'customer_invoice' => __('Customer Invoice', 'storeinvoice-print-pdf-invoices-and-packing-slips'),
        ];
    }

    /**
     * WooCommerce country list.
     *
     * @return array<string, string>
     */
    private function getCountries(): array
    {
        if (!class_exists('WooCommerce') || !function_exists('WC')) {
            return [];
        }

        $woocommerce = WC();
        if (!$woocommerce || !isset($woocommerce->countries)) {
            return [];
        }

        $countries = $woocommerce->countries->get_countries();
        if (!is_array($countries)) {
            return [];
        }

        $normalized = [];
        foreach ($countries as $countryCode => $countryName) {
            $countryCode = sanitize_text_field((string) $countryCode);
            $countryName = sanitize_text_field((string) $countryName);
            if ($countryCode === '' || $countryName === '') {
                continue;
            }
            $normalized[$countryCode] = $countryName;
        }

        return $normalized;
    }

    /**
     * WooCommerce states by country.
     *
     * @return array<string, array<string, string>>
     */
    private function getStates(): array
    {
        if (!class_exists('WooCommerce') || !function_exists('WC')) {
            return [];
        }

        $woocommerce = WC();
        if (!$woocommerce || !isset($woocommerce->countries)) {
            return [];
        }

        $states = $woocommerce->countries->get_states();
        if (!is_array($states)) {
            return [];
        }

        $normalized = [];
        foreach ($states as $countryCode => $countryStates) {
            if (!is_array($countryStates)) {
                continue;
            }

            $cleanCountryCode = sanitize_text_field((string) $countryCode);
            if ($cleanCountryCode === '') {
                continue;
            }

            $normalized[$cleanCountryCode] = [];
            foreach ($countryStates as $stateCode => $stateName) {
                $cleanStateCode = sanitize_text_field((string) $stateCode);
                $cleanStateName = sanitize_text_field((string) $stateName);
                if ($cleanStateCode === '' || $cleanStateName === '') {
                    continue;
                }
                $normalized[$cleanCountryCode][$cleanStateCode] = $cleanStateName;
            }
        }

        return $normalized;
    }
}
