<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Commerce;

use Help4BuilderSuite\Contracts\ModuleContract;
use Help4BuilderSuite\Support\AdminRequest;
use Help4BuilderSuite\Support\ContactRoutes;
use Help4BuilderSuite\Support\IpTools;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

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

// Custom commerce tables are intentional; state-changing handlers perform nonce/capability checks before writes.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

final class Module implements ModuleContract
{
    private const INVENTORY_HOLD_CRON = 'h4bs_commerce_release_expired_inventory';

    private ProductRepository $products;
    private ProductImporter $importer;
    private FeedPuller $feedPuller;
    private ProductReviewsAddon $reviewsAddon;
    private PromotionRepository $promotions;
    private PromotionEngine $promotionEngine;
    private TaxCalculator $taxCalculator;
    private CustomerFieldRepository $customerFields;

    public function __construct()
    {
        $this->products = new ProductRepository();
        $this->importer = new ProductImporter($this->products);
        $this->feedPuller = new FeedPuller($this->importer);
        $this->reviewsAddon = new ProductReviewsAddon($this->products);
        $this->promotions = new PromotionRepository();
        $this->promotionEngine = new PromotionEngine($this->promotions);
        $this->taxCalculator = new TaxCalculator();
        $this->customerFields = new CustomerFieldRepository();
    }

    public function key(): string
    {
        return 'commerce';
    }

    public function boot(): void
    {
        add_action('admin_menu', [$this, 'registerAdminPage']);
        add_action('admin_init', [$this, 'handleAdminActions']);
        add_action('rest_api_init', [$this, 'registerRestRoutes']);
        add_shortcode('h4_products', [$this, 'renderProductsShortcode']);
        add_shortcode('h4_checkout', [$this, 'renderCheckoutShortcode']);
        add_shortcode('h4_customer_account', [$this, 'renderCustomerAccountShortcode']);
        add_action('init', [$this, 'handleCustomerAccountFieldSave']);
        add_action(self::INVENTORY_HOLD_CRON, [$this, 'releaseExpiredInventoryHolds']);
        add_filter('wp_privacy_personal_data_exporters', [$this, 'registerCustomerFieldPrivacyExporter']);
        add_filter('wp_privacy_personal_data_erasers', [$this, 'registerCustomerFieldPrivacyEraser']);
        if (! wp_next_scheduled(self::INVENTORY_HOLD_CRON)) {
            wp_schedule_event(time() + 15 * MINUTE_IN_SECONDS, 'hourly', self::INVENTORY_HOLD_CRON);
        }
        $this->reviewsAddon->boot();

        if (defined('WP_CLI') && WP_CLI && class_exists('\WP_CLI')) {
            \WP_CLI::add_command('h4bs load', LoadTestCommand::class);
        }
    }

    public function registerAdminPage(): void
    {
        add_menu_page(
            __('Help4 Commerce', 'help4-builder-suite'),
            __('Commerce', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce',
            [$this, 'renderAdminPage'],
            'dashicons-cart',
            57
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Commerce Dashboard', 'help4-builder-suite'),
            __('Dashboard', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce',
            [$this, 'renderAdminPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Products', 'help4-builder-suite'),
            __('Products', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-products',
            [$this, 'renderProductsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Orders', 'help4-builder-suite'),
            __('Orders', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-orders',
            [$this, 'renderOrdersPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Promotions', 'help4-builder-suite'),
            __('Promotions', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-promotions',
            [$this, 'renderPromotionsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Tax Settings', 'help4-builder-suite'),
            __('Taxes', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-taxes',
            [$this, 'renderTaxSettingsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Customers', 'help4-builder-suite'),
            __('Customers', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-customers',
            [$this, 'renderCustomersPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Customer Fields', 'help4-builder-suite'),
            __('Customer Fields', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-customer-fields',
            [$this, 'renderCustomerFieldsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Subscriptions', 'help4-builder-suite'),
            __('Subscriptions', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-subscriptions',
            [$this, 'renderSubscriptionsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Gateway Manager', 'help4-builder-suite'),
            __('Gateways', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-gateways',
            [$this, 'renderGatewaySettingsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Shipping Integrations', 'help4-builder-suite'),
            __('Shipping', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-shipping',
            [$this, 'renderShippingSettingsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Imports and Feeds', 'help4-builder-suite'),
            __('Imports', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-imports',
            [$this, 'renderImportsPage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Migration Bridge', 'help4-builder-suite'),
            __('Migration Bridge', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-migration',
            [$this, 'renderMigrationBridgePage']
        );

        add_submenu_page(
            'h4bs-commerce',
            __('Load Tests', 'help4-builder-suite'),
            __('Load Tests', 'help4-builder-suite'),
            'manage_options',
            'h4bs-commerce-load-tests',
            [$this, 'renderLoadTestsPage']
        );
    }

    public function renderAdminPage(): void
    {
        global $wpdb;
        $settings = $this->settings();
        $catalogMode = $this->commerceCatalogMode($settings);
        $sourceStrategy = $this->commerceSourceStrategy($settings);
        $localProductEditing = $this->localProductEditingEnabled($settings);
        $gateways = (new GatewayRegistry($settings))->all();
        $productsCount = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $wpdb->prefix . 'h4_products'));
        $ordersCount = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $wpdb->prefix . 'h4_orders'));
        $customersCount = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $wpdb->prefix . 'h4_customers'));
        $reviewsCount = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $wpdb->prefix . 'h4_product_reviews'));
        $subsCount = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $wpdb->prefix . 'h4_subscriptions'));
        $promotionsCount = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i WHERE status = %s', $wpdb->prefix . 'h4_promotions', 'active'));
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Help4 Commerce', 'help4-builder-suite'); ?></h1>
            <span class="h4bs-admin-kicker"><?php esc_html_e('Native catalog, checkout, customers, and payment routing', 'help4-builder-suite'); ?></span>
            <p class="h4bs-admin-lead">
                <?php esc_html_e('Help4 Commerce is the built-in store layer for large product catalogs, service packages, subscriptions, digital delivery, bookings, shipping, and checkout routing. Use the menus below like a launch checklist: catalog first, payment routes second, then orders and customers.', 'help4-builder-suite'); ?>
            </p>
            <p>
                <a class="button button-primary" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-products')); ?>"><?php esc_html_e('Manage Products', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-orders')); ?>"><?php esc_html_e('View Orders', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-promotions')); ?>"><?php esc_html_e('Manage Promotions', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-taxes')); ?>"><?php esc_html_e('Configure Taxes', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-customers')); ?>"><?php esc_html_e('View Customers', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-reviews')); ?>"><?php esc_html_e('Moderate Reviews', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-gateways')); ?>"><?php esc_html_e('Configure Gateways', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-migration')); ?>"><?php esc_html_e('Migration Bridge', 'help4-builder-suite'); ?></a>
            </p>
            <p>
                <strong><?php esc_html_e('Catalog Mode:', 'help4-builder-suite'); ?></strong>
                <?php echo esc_html($catalogMode === 'migration_bridge' ? __('Migration Bridge', 'help4-builder-suite') : __('Native Catalog', 'help4-builder-suite')); ?>
                |
                <strong><?php esc_html_e('Source Strategy:', 'help4-builder-suite'); ?></strong>
                <?php echo esc_html(ucfirst($sourceStrategy)); ?>
                |
                <strong><?php esc_html_e('Local Product Editing:', 'help4-builder-suite'); ?></strong>
                <?php echo esc_html($localProductEditing ? __('Enabled', 'help4-builder-suite') : __('Disabled', 'help4-builder-suite')); ?>
            </p>

            <div class="h4bs-admin-card-grid">
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Catalogs up to enterprise size', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Prefix search, imports, variants, media ingestion, and load-test commands are built for large catalogs instead of tiny demo stores.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Subscriptions without bolted-on store plugins', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Recurring terms live on the product, then checkout records subscriptions with gateway references for support and reporting.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Payment router first', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Gateway rules decide which processor should handle a checkout by product type, amount, currency, location, or subscription status.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Controlled catalog bridge', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Migration Bridge lets this site showcase products while imports and checkout connections are validated before local editing is unlocked.', 'help4-builder-suite'); ?></p>
                </div>
            </div>

            <h2><?php esc_html_e('Quick Stats', 'help4-builder-suite'); ?></h2>
            <table class="widefat striped" style="max-width:900px;">
                <tbody>
                    <tr><th><?php esc_html_e('Products', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $productsCount); ?></td></tr>
                    <tr><th><?php esc_html_e('Orders', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $ordersCount); ?></td></tr>
                    <tr><th><?php esc_html_e('Customers', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $customersCount); ?></td></tr>
                    <tr><th><?php esc_html_e('Product Reviews', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $reviewsCount); ?></td></tr>
                    <tr><th><?php esc_html_e('Subscriptions', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $subsCount); ?></td></tr>
                    <tr><th><?php esc_html_e('Active Promotions', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $promotionsCount); ?></td></tr>
                </tbody>
            </table>

            <h2><?php esc_html_e('Gateway Status', 'help4-builder-suite'); ?></h2>
            <table class="widefat striped">
                <thead>
                    <tr>
                        <th><?php esc_html_e('Gateway', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Configured', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Notes', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gateways as $gateway) : ?>
                        <tr>
                            <td><?php echo esc_html((string) ($gateway['label'] ?? '')); ?></td>
                            <td><?php echo ! empty($gateway['configured']) ? 'Yes' : 'No'; ?></td>
                            <td>
                                <?php echo esc_html((string) ($gateway['description'] ?? '')); ?>
                                <?php if (! empty($gateway['processor_endpoint'])) : ?>
                                    <br><small><code><?php echo esc_html((string) ($gateway['processor_endpoint'] ?? '')); ?></code></small>
                                <?php endif; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <p>
                <a href="<?php echo esc_url(ContactRoutes::supportTicketUrl($settings)); ?>" target="_blank" rel="noopener">
                    <?php esc_html_e('Need another payment provider? Contact Help4 to add it.', 'help4-builder-suite'); ?>
                </a>
            </p>
        </div>
        <?php
    }

    public function handleAdminActions(): void
    {
        if (! is_admin() || ! current_user_can('manage_options')) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Individual commerce action handlers verify their own nonces before writes.
        $post = wp_unslash($_POST);
        $action = isset($post['h4bs_commerce_action']) ? sanitize_key((string) $post['h4bs_commerce_action']) : '';
        if ($action === 'save_product') {
            $this->handleSaveProduct();
        }
        if ($action === 'save_gateway_settings') {
            $this->handleSaveGatewaySettings();
        }
        if ($action === 'save_shipping_settings') {
            $this->handleSaveShippingSettings();
        }
        if ($action === 'save_migration_bridge') {
            $this->handleSaveMigrationBridge();
        }
        if ($action === 'run_import') {
            $this->handleRunImport();
        }
        if ($action === 'import_orders') {
            $this->handleImportOrders();
        }
        if ($action === 'export_orders') {
            $this->handleExportOrders();
        }
        if ($action === 'import_customers') {
            $this->handleImportCustomers();
        }
        if ($action === 'export_customers') {
            $this->handleExportCustomers();
        }
        if ($action === 'save_customer_field') {
            $this->handleSaveCustomerField();
        }
        if ($action === 'save_customer_field_values') {
            $this->handleSaveCustomerFieldValues();
        }
        if ($action === 'install_customer_field_preset') {
            $this->handleInstallCustomerFieldPreset();
        }
        if ($action === 'import_customer_fields') {
            $this->handleImportCustomerFields();
        }
        if ($action === 'export_customer_fields') {
            $this->handleExportCustomerFields();
        }
        if ($action === 'update_order_status') {
            $this->handleUpdateOrderStatus();
        }
        if ($action === 'save_promotion') {
            $this->handleSavePromotion();
        }
        if ($action === 'save_tax_settings') {
            $this->handleSaveTaxSettings();
        }

        $deleteAction = AdminRequest::queryKey('h4bs_action');
        if ($deleteAction === 'delete_product') {
            $this->handleDeleteProduct();
        }
        if ($deleteAction === 'delete_promotion') {
            $this->handleDeletePromotion();
        }
        if ($deleteAction === 'archive_customer_field') {
            $this->handleArchiveCustomerField();
        }
        if ($deleteAction === 'duplicate_customer_field') {
            $this->handleDuplicateCustomerField();
        }
    }

    private function handleSaveProduct(): void
    {
        check_admin_referer('h4bs_save_product', 'h4bs_save_product_nonce');
        if (! $this->localProductEditingEnabled($this->settings())) {
            $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'product_editing_disabled']);
        }

        $post = wp_unslash($_POST);
        $id = isset($post['product_id']) ? absint($post['product_id']) : 0;
        $existing = $id > 0 ? $this->products->get($id) : null;
        $existingPayload = is_array($existing)
            ? $this->decodeJsonArray((string) ($existing['payload'] ?? ''))
            : [];

        $productType = $this->sanitizeProductType(
            (string) (
                $post['product_type']
                ?? ($existingPayload['type'] ?? 'simple')
            )
        );
        $fulfillmentType = $this->sanitizeFulfillmentType(
            (string) (
                $post['fulfillment_type']
                ?? ($existingPayload['fulfillment_type'] ?? $this->defaultFulfillmentForProductType($productType))
            )
        );

        $isDigital = ! empty($post['is_digital']);
        $isBooking = ! empty($post['is_booking']);
        $requiresShipping = ! empty($post['requires_shipping']);

        if (in_array($productType, ['digital', 'downloadable', 'virtual'], true) || $fulfillmentType === 'digital') {
            $isDigital = true;
        }
        if ($productType === 'booking' || $fulfillmentType === 'booking') {
            $isBooking = true;
        }
        if ($isDigital || $isBooking) {
            $requiresShipping = false;
        }

        $downloads = $this->normalizeDownloads((string) ($post['download_urls'] ?? ''));
        $bookingDuration = max(5, min(1440, (int) ($post['booking_duration_minutes'] ?? 60)));
        $bookingBuffer = max(0, min(480, (int) ($post['booking_buffer_minutes'] ?? 0)));
        $bookingRequiresConfirmation = ! empty($post['booking_requires_confirmation']);
        if (! $isBooking) {
            $bookingDuration = 0;
            $bookingBuffer = 0;
            $bookingRequiresConfirmation = false;
        }
        $existingSubscription = is_array($existingPayload['subscription'] ?? null) ? $existingPayload['subscription'] : [];
        $subscriptionInterval = $this->sanitizeSubscriptionInterval(
            (string) (
                $post['subscription_interval']
                ?? ($existingSubscription['interval_code'] ?? ($existingSubscription['interval'] ?? 'monthly'))
            )
        );
        $subscriptionIntervalCount = max(
            1,
            min(
                36,
                (int) (
                    $post['subscription_interval_count']
                    ?? ($existingSubscription['interval_count'] ?? 1)
                )
            )
        );
        $subscriptionTrialDays = max(
            0,
            min(
                365,
                (int) (
                    $post['subscription_trial_days']
                    ?? ($existingSubscription['trial_days'] ?? 0)
                )
            )
        );
        $subscriptionSetupFee = max(
            0.0,
            (float) (
                $post['subscription_setup_fee']
                ?? ($existingSubscription['setup_fee'] ?? 0)
            )
        );
        $subscriptionAutoRenew = array_key_exists('subscription_auto_renew', $post)
            ? ! empty($post['subscription_auto_renew'])
            : (bool) ($existingSubscription['auto_renew'] ?? true);

        $customerRoleAfterPurchase = $this->sanitizeCustomerRole(
            (string) (
                $post['customer_role_after_purchase']
                ?? ($existingPayload['customer_account']['role_after_purchase'] ?? '')
            ),
            false
        );
        $customerStatusAfterPurchase = $this->sanitizeCustomerStatus(
            (string) (
                $post['customer_status_after_purchase']
                ?? ($existingPayload['customer_account']['status_after_purchase'] ?? '')
            ),
            false
        );
        $shippingWeight = max(0, (float) ($post['shipping_weight'] ?? ($existingPayload['shipping_profile']['weight'] ?? 0)));
        $shippingLength = max(0, (float) ($post['shipping_length'] ?? ($existingPayload['shipping_profile']['length'] ?? 0)));
        $shippingWidth = max(0, (float) ($post['shipping_width'] ?? ($existingPayload['shipping_profile']['width'] ?? 0)));
        $shippingHeight = max(0, (float) ($post['shipping_height'] ?? ($existingPayload['shipping_profile']['height'] ?? 0)));
        $shippingWeightUnit = sanitize_key((string) ($post['shipping_weight_unit'] ?? ($existingPayload['shipping_profile']['weight_unit'] ?? 'lb')));
        if (! in_array($shippingWeightUnit, ['lb', 'kg', 'oz', 'g'], true)) {
            $shippingWeightUnit = 'lb';
        }
        $shippingDimensionUnit = sanitize_key((string) ($post['shipping_dimension_unit'] ?? ($existingPayload['shipping_profile']['dimension_unit'] ?? 'in')));
        if (! in_array($shippingDimensionUnit, ['in', 'cm', 'm'], true)) {
            $shippingDimensionUnit = 'in';
        }

        $existingInventory = $this->normalizeInventoryConfig($existingPayload);
        $inventoryTrackStock = array_key_exists('inventory_track_stock', $post)
            ? rest_sanitize_boolean($post['inventory_track_stock'])
            : (bool) $existingInventory['track_stock'];
        $inventoryPolicy = $this->sanitizeInventoryPolicy(
            (string) ($post['inventory_policy'] ?? $existingInventory['policy'])
        );
        $inventoryLowStockThreshold = max(
            0,
            min(1000000000, (int) ($post['inventory_low_stock_threshold'] ?? $existingInventory['low_stock_threshold']))
        );
        $inventorySoldIndividually = array_key_exists('inventory_sold_individually', $post)
            ? rest_sanitize_boolean($post['inventory_sold_individually'])
            : (bool) $existingInventory['sold_individually'];
        $inventoryQuantityEnabled = array_key_exists('inventory_quantity_enabled', $post)
            ? rest_sanitize_boolean($post['inventory_quantity_enabled'])
            : (bool) $existingInventory['quantity_enabled'];
        $inventoryMinQty = max(0.001, min(1000, (float) ($post['inventory_min_qty'] ?? $existingInventory['min_qty'])));
        $inventoryMaxQty = max(0.0, min(1000, (float) ($post['inventory_max_qty'] ?? $existingInventory['max_qty'])));
        if ($inventoryMaxQty > 0 && $inventoryMaxQty < $inventoryMinQty) {
            $inventoryMaxQty = $inventoryMinQty;
        }
        $inventoryQtyStep = max(0.001, min(1000, (float) ($post['inventory_qty_step'] ?? $existingInventory['qty_step'])));
        if (! $inventoryQuantityEnabled) {
            $inventoryMinQty = 1.0;
            $inventoryMaxQty = 1.0;
            $inventoryQtyStep = 1.0;
        }
        $preorderAvailableOn = $this->sanitizeCommerceDate(
            (string) ($post['inventory_preorder_available_on'] ?? $existingInventory['preorder_available_on'])
        );
        $preorderMessage = sanitize_text_field(
            (string) ($post['inventory_preorder_message'] ?? $existingInventory['preorder_message'])
        );

        $existingPricing = $this->normalizePricingConfig($existingPayload);
        $compareAtPrice = max(0.0, (float) ($post['compare_at_price'] ?? $existingPricing['compare_at_price']));
        $unitCost = max(0.0, (float) ($post['unit_cost'] ?? $existingPricing['cost']));

        $existingMerchandising = $this->normalizeMerchandisingConfig($existingPayload);
        $customBadge = sanitize_text_field((string) ($post['custom_badge'] ?? $existingMerchandising['badge']));
        $purchaseNote = sanitize_textarea_field((string) ($post['purchase_note'] ?? $existingMerchandising['purchase_note']));
        $relatedProductIds = $this->sanitizeProductIdList(
            $post['related_product_ids'] ?? $existingMerchandising['related_product_ids']
        );
        $upsellProductIds = $this->sanitizeProductIdList(
            $post['upsell_product_ids'] ?? $existingMerchandising['upsell_product_ids']
        );
        $crossSellProductIds = $this->sanitizeProductIdList(
            $post['cross_sell_product_ids'] ?? $existingMerchandising['cross_sell_product_ids']
        );
        $existingTax = is_array($existingPayload['tax'] ?? null) ? $existingPayload['tax'] : [];
        $taxStatus = sanitize_key((string) ($post['tax_status'] ?? ($existingTax['status'] ?? 'taxable')));
        if (! in_array($taxStatus, ['taxable', 'none'], true)) {
            $taxStatus = 'taxable';
        }
        $taxClass = sanitize_key((string) ($post['tax_class'] ?? ($existingTax['class'] ?? 'standard'))) ?: 'standard';
        $seoDescription = sanitize_textarea_field((string) ($post['seo_description'] ?? ($existingPayload['description'] ?? '')));
        $seoBrand = sanitize_text_field((string) ($post['seo_brand'] ?? ($existingPayload['brand'] ?? '')));
        $seoGtin = (string) preg_replace('/[^0-9]/', '', sanitize_text_field((string) ($post['seo_gtin'] ?? ($existingPayload['gtin'] ?? ''))));
        if (! in_array(strlen($seoGtin), [0, 8, 12, 13, 14], true)) {
            $seoGtin = '';
        }
        $seoMpn = sanitize_text_field((string) ($post['seo_mpn'] ?? ($existingPayload['mpn'] ?? '')));
        $seoCondition = sanitize_key((string) ($post['seo_condition'] ?? ($existingPayload['condition'] ?? 'new')));
        if (! in_array($seoCondition, ['new', 'used', 'refurbished'], true)) {
            $seoCondition = 'new';
        }
        $seoLandingUrl = esc_url_raw((string) ($post['seo_landing_url'] ?? ($existingPayload['external_url'] ?? '')), ['http', 'https']);
        $seoImageUrl = esc_url_raw((string) ($post['seo_image_url'] ?? ($existingPayload['featured_image_url'] ?? '')), ['http', 'https']);
        $seoProductCategory = sanitize_text_field((string) ($post['seo_product_category'] ?? ($existingPayload['google_product_category'] ?? '')));

        $productPayload = array_merge(
            $existingPayload,
            [
                'type'              => $productType,
                'fulfillment_type'  => $fulfillmentType,
                'is_digital'        => $isDigital,
                'downloadable'      => $isDigital,
                'virtual'           => $isDigital || $isBooking,
                'is_booking'        => $isBooking,
                'requires_shipping' => $requiresShipping,
                'downloads'         => $downloads,
                'download_urls'     => array_map(
                    static fn (array $download): string => (string) ($download['url'] ?? ''),
                    $downloads
                ),
                'booking'           => [
                    'enabled'               => $isBooking,
                    'duration_minutes'      => $bookingDuration,
                    'buffer_minutes'        => $bookingBuffer,
                    'requires_confirmation' => $bookingRequiresConfirmation,
                ],
                'subscription'       => [
                    'enabled'        => $productType === 'subscription',
                    'interval_code'  => $subscriptionInterval,
                    'interval_count' => $subscriptionIntervalCount,
                    'trial_days'     => $subscriptionTrialDays,
                    'setup_fee'      => $subscriptionSetupFee,
                    'auto_renew'     => $subscriptionAutoRenew,
                ],
                'customer_account'  => [
                    'role_after_purchase' => $customerRoleAfterPurchase,
                    'status_after_purchase' => $customerStatusAfterPurchase,
                ],
                'shipping_profile' => [
                    'weight' => $shippingWeight,
                    'weight_unit' => $shippingWeightUnit,
                    'length' => $shippingLength,
                    'width' => $shippingWidth,
                    'height' => $shippingHeight,
                    'dimension_unit' => $shippingDimensionUnit,
                ],
                'inventory' => [
                    'track_stock' => $inventoryTrackStock,
                    'policy' => $inventoryPolicy,
                    'low_stock_threshold' => $inventoryLowStockThreshold,
                    'sold_individually' => $inventorySoldIndividually,
                    'quantity_enabled' => $inventoryQuantityEnabled,
                    'min_qty' => $inventoryMinQty,
                    'max_qty' => $inventoryMaxQty,
                    'qty_step' => $inventoryQtyStep,
                    'preorder_available_on' => $preorderAvailableOn,
                    'preorder_message' => $preorderMessage,
                ],
                'pricing' => [
                    'compare_at_price' => $compareAtPrice,
                    'cost' => $unitCost,
                ],
                'tax' => [
                    'status' => $taxStatus,
                    'class' => $taxClass,
                ],
                'description' => $seoDescription,
                'brand' => $seoBrand,
                'gtin' => $seoGtin,
                'mpn' => $seoMpn,
                'condition' => $seoCondition,
                'external_url' => $seoLandingUrl,
                'featured_image_url' => $seoImageUrl,
                'google_product_category' => $seoProductCategory,
                'merchandising' => [
                    'badge' => $customBadge,
                    'purchase_note' => $purchaseNote,
                    'related_product_ids' => $relatedProductIds,
                    'upsell_product_ids' => $upsellProductIds,
                    'cross_sell_product_ids' => $crossSellProductIds,
                ],
            ]
        );

        $variantRows = $this->parseAdminVariantFormRows($post);
        $rawVariants = trim((string) ($post['variants_json'] ?? ''));
        if ($rawVariants !== '') {
            $parsedVariants = $this->parseAdminVariantRows($rawVariants);
            if ($parsedVariants instanceof WP_Error) {
                $this->redirectCommerce(
                    'h4bs-commerce-products',
                    [
                        'h4bs_notice' => 'invalid_variants',
                        'action' => $id > 0 ? 'edit' : 'new',
                        'edit' => $id,
                    ]
                );
            }
            $variantRows = $parsedVariants;
        }

        $payload = [
            'name'      => sanitize_text_field((string) ($post['name'] ?? '')),
            'sku'       => sanitize_text_field((string) ($post['sku'] ?? '')),
            'slug'      => sanitize_title((string) ($post['slug'] ?? '')),
            'status'    => sanitize_key((string) ($post['status'] ?? 'active')),
            'price'     => (float) ($post['price'] ?? 0),
            'currency'  => strtoupper(sanitize_text_field((string) ($post['currency'] ?? 'USD'))),
            'stock_qty' => (int) ($post['stock_qty'] ?? 0),
            'payload'   => $productPayload,
        ];

        if ($payload['name'] === '') {
            $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'product_name_required']);
        }

        if ($id > 0) {
            $this->products->update($id, $payload);
            if (is_array($variantRows)) {
                $this->replaceProductVariants($id, $variantRows);
            }
            do_action('h4bs_commerce_product_saved', $id, $payload, $productPayload);
            $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'product_updated']);
        }

        $createdProductId = $this->products->create($payload);
        if (is_array($variantRows) && $createdProductId > 0) {
            $this->replaceProductVariants($createdProductId, $variantRows);
        }
        if ($createdProductId > 0) {
            do_action('h4bs_commerce_product_saved', $createdProductId, $payload, $productPayload);
        }
        $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'product_created']);
    }

    /**
     * @return array<int, array<string, mixed>>|WP_Error
     */
    private function parseAdminVariantRows(string $raw): array|WP_Error
    {
        $decoded = json_decode($raw, true);
        if (! is_array($decoded) || ! array_is_list($decoded)) {
            return new WP_Error('h4bs_invalid_variants', 'Variant matrix must be a JSON array.');
        }

        $rows = [];
        foreach ($decoded as $index => $row) {
            if (! is_array($row)) {
                return new WP_Error('h4bs_invalid_variant_row', 'Every variant row must be an object.');
            }

            $attributes = $row['attributes'] ?? [];
            if (is_string($attributes)) {
                $parsedAttributes = json_decode($attributes, true);
                $attributes = is_array($parsedAttributes) ? $parsedAttributes : [];
            }
            if (! is_array($attributes)) {
                $attributes = [];
            }

            $cleanAttributes = [];
            foreach ($attributes as $key => $value) {
                $attributeKey = sanitize_key((string) $key);
                if ($attributeKey === '') {
                    continue;
                }
                $cleanAttributes[$attributeKey] = sanitize_text_field((string) $value);
            }

            $rows[] = [
                'sku' => sanitize_text_field((string) ($row['sku'] ?? '')),
                'attributes' => $cleanAttributes,
                'price' => (float) ($row['price'] ?? 0),
                'stock_qty' => (int) ($row['stock_qty'] ?? 0),
                'image_id' => absint($row['image_id'] ?? 0),
                'position' => array_key_exists('position', $row) ? (int) $row['position'] : $index,
            ];
        }

        return $rows;
    }

    /**
     * @param array<string, mixed> $post
     * @return array<int, array<string, mixed>>|null
     */
    private function parseAdminVariantFormRows(array $post): ?array
    {
        $skus = is_array($post['variant_sku'] ?? null) ? (array) $post['variant_sku'] : null;
        if ($skus === null) {
            return null;
        }

        $attributes = is_array($post['variant_attributes'] ?? null) ? (array) $post['variant_attributes'] : [];
        $prices = is_array($post['variant_price'] ?? null) ? (array) $post['variant_price'] : [];
        $stock = is_array($post['variant_stock_qty'] ?? null) ? (array) $post['variant_stock_qty'] : [];
        $imageIds = is_array($post['variant_image_id'] ?? null) ? (array) $post['variant_image_id'] : [];
        $positions = is_array($post['variant_position'] ?? null) ? (array) $post['variant_position'] : [];
        $rowCount = max(
            count($skus),
            count($attributes),
            count($prices),
            count($stock),
            count($imageIds),
            count($positions)
        );
        $rows = [];

        for ($index = 0; $index < $rowCount; $index++) {
            $sku = sanitize_text_field((string) ($skus[$index] ?? ''));
            $attributeText = sanitize_text_field((string) ($attributes[$index] ?? ''));
            $price = (float) ($prices[$index] ?? 0);
            $stockQty = (int) ($stock[$index] ?? 0);
            $imageId = absint($imageIds[$index] ?? 0);
            $position = array_key_exists($index, $positions) ? (int) $positions[$index] : $index;

            if ($sku === '' && $attributeText === '' && $price <= 0 && $stockQty <= 0 && $imageId <= 0) {
                continue;
            }

            $rows[] = [
                'sku' => $sku,
                'attributes' => $this->parseAdminVariantAttributeText($attributeText),
                'price' => $price,
                'stock_qty' => $stockQty,
                'image_id' => $imageId,
                'position' => $position,
            ];
        }

        return $rows;
    }

    /**
     * @return array<string, string>
     */
    private function parseAdminVariantAttributeText(string $attributeText): array
    {
        $attributes = [];
        foreach (explode(',', $attributeText) as $pair) {
            $pair = trim($pair);
            if ($pair === '' || ! str_contains($pair, '=')) {
                continue;
            }
            [$key, $value] = array_map('trim', explode('=', $pair, 2));
            $key = sanitize_key($key);
            if ($key === '') {
                continue;
            }
            $attributes[$key] = sanitize_text_field($value);
        }

        return $attributes;
    }

    /**
     * @param array<int, array<string, mixed>> $variantRows
     */
    private function replaceProductVariants(int $productId, array $variantRows): void
    {
        $this->products->deleteVariantsByProduct($productId);
        foreach ($variantRows as $variant) {
            $this->products->createVariant($productId, $variant);
        }
    }

    private function adminVariantMatrixJson(int $productId): string
    {
        if ($productId <= 0) {
            return '';
        }

        $variants = [];
        foreach ($this->products->listVariants($productId) as $variant) {
            if (! is_array($variant)) {
                continue;
            }
            $variants[] = [
                'sku' => sanitize_text_field((string) ($variant['sku'] ?? '')),
                'attributes' => $this->decodeJsonArray((string) ($variant['attributes_json'] ?? '{}')),
                'price' => (float) ($variant['price'] ?? 0),
                'stock_qty' => (int) ($variant['stock_qty'] ?? 0),
                'image_id' => absint($variant['image_id'] ?? 0),
                'position' => (int) ($variant['position'] ?? 0),
            ];
        }

        if (! $variants) {
            return '';
        }

        return (string) wp_json_encode($variants, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function adminVariantFormRows(int $productId): array
    {
        $rows = [];
        if ($productId > 0) {
            foreach ($this->products->listVariants($productId) as $variant) {
                if (! is_array($variant)) {
                    continue;
                }
                $attributes = $this->decodeJsonArray((string) ($variant['attributes_json'] ?? '{}'));
                $attributePairs = [];
                foreach ($attributes as $key => $value) {
                    $attributePairs[] = sanitize_key((string) $key) . '=' . sanitize_text_field((string) $value);
                }
                $rows[] = [
                    'sku' => sanitize_text_field((string) ($variant['sku'] ?? '')),
                    'attributes' => implode(', ', $attributePairs),
                    'price' => (float) ($variant['price'] ?? 0),
                    'stock_qty' => (int) ($variant['stock_qty'] ?? 0),
                    'image_id' => absint($variant['image_id'] ?? 0),
                    'position' => (int) ($variant['position'] ?? 0),
                ];
            }
        }

        while (count($rows) < 3) {
            $rows[] = [
                'sku' => '',
                'attributes' => '',
                'price' => '',
                'stock_qty' => '',
                'image_id' => '',
                'position' => count($rows),
            ];
        }

        return $rows;
    }

    private function handleDeleteProduct(): void
    {
        if (! $this->localProductEditingEnabled($this->settings())) {
            $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'product_editing_disabled']);
        }
        $id = AdminRequest::queryAbsInt('product_id');
        if ($id <= 0) {
            $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'invalid_product']);
        }

        check_admin_referer('h4bs_delete_product_' . $id);
        $product = $this->products->get($id);
        $this->products->delete($id);
        if (is_array($product)) {
            do_action('h4bs_commerce_product_deleted', $id, $product);
        }
        $this->redirectCommerce('h4bs-commerce-products', ['h4bs_notice' => 'product_deleted']);
    }

    private function handleSavePromotion(): void
    {
        check_admin_referer('h4bs_save_promotion', 'h4bs_save_promotion_nonce');
        $post = wp_unslash($_POST);
        $promotionId = absint($post['promotion_id'] ?? 0);
        $promotion = [
            'code' => (string) ($post['code'] ?? ''),
            'name' => (string) ($post['name'] ?? ''),
            'status' => (string) ($post['status'] ?? 'active'),
            'discount_type' => (string) ($post['discount_type'] ?? 'percent'),
            'amount' => (float) ($post['amount'] ?? 0),
            'currency' => (string) ($post['currency'] ?? ''),
            'minimum_subtotal' => (float) ($post['minimum_subtotal'] ?? 0),
            'maximum_discount' => (float) ($post['maximum_discount'] ?? 0),
            'usage_limit' => (int) ($post['usage_limit'] ?? 0),
            'per_customer_limit' => (int) ($post['per_customer_limit'] ?? 0),
            'starts_at' => (string) ($post['starts_at'] ?? ''),
            'ends_at' => (string) ($post['ends_at'] ?? ''),
            'payload' => [
                'product_ids' => $this->sanitizeProductIdList($post['product_ids'] ?? []),
                'excluded_product_ids' => $this->sanitizeProductIdList($post['excluded_product_ids'] ?? []),
            ],
        ];
        $saved = $this->promotions->save($promotion, $promotionId);
        if ($saved instanceof WP_Error) {
            $this->redirectCommerce(
                'h4bs-commerce-promotions',
                [
                    'h4bs_notice' => $saved->get_error_code(),
                    'action' => $promotionId > 0 ? 'edit' : 'new',
                    'edit' => $promotionId,
                ]
            );
        }
        $this->redirectCommerce(
            'h4bs-commerce-promotions',
            ['h4bs_notice' => $promotionId > 0 ? 'promotion_updated' : 'promotion_created']
        );
    }

    private function handleDeletePromotion(): void
    {
        $promotionId = AdminRequest::queryAbsInt('promotion_id');
        if ($promotionId <= 0) {
            $this->redirectCommerce('h4bs-commerce-promotions', ['h4bs_notice' => 'invalid_promotion']);
        }
        check_admin_referer('h4bs_delete_promotion_' . $promotionId);
        $this->promotions->delete($promotionId);
        $this->redirectCommerce('h4bs-commerce-promotions', ['h4bs_notice' => 'promotion_deleted']);
    }

    private function handleSaveTaxSettings(): void
    {
        check_admin_referer('h4bs_save_tax_settings', 'h4bs_save_tax_settings_nonce');
        $post = wp_unslash($_POST);
        $ids = is_array($post['tax_rate_id'] ?? null) ? $post['tax_rate_id'] : [];
        $labels = is_array($post['tax_rate_label'] ?? null) ? $post['tax_rate_label'] : [];
        $countries = is_array($post['tax_rate_country'] ?? null) ? $post['tax_rate_country'] : [];
        $states = is_array($post['tax_rate_state'] ?? null) ? $post['tax_rate_state'] : [];
        $postcodes = is_array($post['tax_rate_postcode'] ?? null) ? $post['tax_rate_postcode'] : [];
        $classes = is_array($post['tax_rate_class'] ?? null) ? $post['tax_rate_class'] : [];
        $rates = is_array($post['tax_rate_percent'] ?? null) ? $post['tax_rate_percent'] : [];
        $priorities = is_array($post['tax_rate_priority'] ?? null) ? $post['tax_rate_priority'] : [];
        $enabled = is_array($post['tax_rate_enabled'] ?? null) ? $post['tax_rate_enabled'] : [];
        $compound = is_array($post['tax_rate_compound'] ?? null) ? $post['tax_rate_compound'] : [];
        $shipping = is_array($post['tax_rate_shipping'] ?? null) ? $post['tax_rate_shipping'] : [];
        $rowKeys = array_values(array_unique(array_merge(array_keys($labels), array_keys($rates))));
        $taxRates = [];
        foreach ($rowKeys as $key) {
            $label = sanitize_text_field((string) ($labels[$key] ?? ''));
            $rate = max(0.0, min(100.0, (float) ($rates[$key] ?? 0)));
            if ($label === '' && $rate <= 0) {
                continue;
            }
            $taxRates[] = [
                'id' => sanitize_key((string) ($ids[$key] ?? 'rate_' . (count($taxRates) + 1))),
                'label' => $label !== '' ? $label : __('Tax', 'help4-builder-suite'),
                'country' => strtoupper(substr(sanitize_text_field((string) ($countries[$key] ?? '')), 0, 2)),
                'state' => strtoupper(sanitize_text_field((string) ($states[$key] ?? ''))),
                'postcode_prefix' => strtoupper(sanitize_text_field((string) ($postcodes[$key] ?? ''))),
                'tax_class' => sanitize_key((string) ($classes[$key] ?? 'standard')) ?: 'standard',
                'rate' => $rate,
                'priority' => max(1, min(9999, (int) ($priorities[$key] ?? 100))),
                'enabled' => ! empty($enabled[$key]),
                'compound' => ! empty($compound[$key]),
                'shipping' => ! empty($shipping[$key]),
            ];
        }
        $settings = $this->settings();
        $settings['commerce_tax_rates'] = $taxRates;
        update_option('h4bs_settings', $settings, false);
        $this->redirectCommerce('h4bs-commerce-taxes', ['h4bs_notice' => 'taxes_saved']);
    }

    private function handleUpdateOrderStatus(): void
    {
        $post = wp_unslash($_POST);
        $orderId = absint($post['order_id'] ?? 0);
        if ($orderId <= 0) {
            $this->redirectCommerce('h4bs-commerce-orders', ['h4bs_notice' => 'invalid_order']);
        }
        check_admin_referer('h4bs_update_order_status_' . $orderId, 'h4bs_update_order_status_nonce');

        $nextStatus = sanitize_key((string) ($post['order_status'] ?? ''));
        $transition = $this->transitionOrderStatus($orderId, $nextStatus);
        if ($transition instanceof WP_Error) {
            $notice = match ($transition->get_error_code()) {
                'h4bs_inventory_unavailable' => 'order_inventory_unavailable',
                'h4bs_promotion_unavailable' => 'order_promotion_unavailable',
                default => $transition->get_error_code(),
            };
            $this->redirectCommerce('h4bs-commerce-orders', ['h4bs_notice' => $notice, 'view' => $orderId]);
        }
        $this->redirectCommerce('h4bs-commerce-orders', ['h4bs_notice' => 'order_status_updated', 'view' => $orderId]);
    }

    /**
     * Change an order state while keeping inventory reservations idempotent.
     * Payment adapters may call this after validating their own webhook signature.
     *
     * @return array<string, mixed>|WP_Error
     */
    public function transitionOrderStatus(int $orderId, string $nextStatus): array|WP_Error
    {
        $nextStatus = sanitize_key($nextStatus);
        $allowedStatuses = ['pending', 'processing', 'paid', 'payment_failed', 'cancelled', 'refunded'];
        if ($orderId <= 0 || ! in_array($nextStatus, $allowedStatuses, true)) {
            return new WP_Error('invalid_order_status', __('Choose a valid order status.', 'help4-builder-suite'));
        }

        global $wpdb;
        $ordersTable = $wpdb->prefix . 'h4_orders';
        $order = $wpdb->get_row(
            $wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $ordersTable, $orderId),
            ARRAY_A
        );
        if (! is_array($order)) {
            return new WP_Error('invalid_order', __('Order not found.', 'help4-builder-suite'));
        }

        $payload = $this->decodeJsonArray((string) ($order['payload'] ?? ''));
        $inventory = is_array($payload['_h4bs_inventory'] ?? null) ? $payload['_h4bs_inventory'] : [];
        $claims = is_array($inventory['claims'] ?? null) ? array_values(array_filter($inventory['claims'], 'is_array')) : [];
        $inventoryState = sanitize_key((string) ($inventory['state'] ?? ($claims ? 'reserved' : 'not_tracked')));
        $promotion = is_array($payload['_h4bs_promotion'] ?? null) ? $payload['_h4bs_promotion'] : [];
        $promotionState = sanitize_key((string) ($promotion['state'] ?? 'not_applied'));
        $releaseStatuses = ['payment_failed', 'cancelled', 'refunded'];
        if ($inventoryState === 'releasing') {
            return new WP_Error(
                'h4bs_inventory_busy',
                __('Inventory is being reconciled for this order. Try the status update again.', 'help4-builder-suite')
            );
        }

        $inventoryWasReclaimed = false;
        if ($claims && in_array($nextStatus, $releaseStatuses, true) && $inventoryState === 'reserved') {
            $inventoryState = $this->releaseCheckoutInventory($claims) ? 'released' : 'release_failed';
            $payload['_h4bs_inventory']['released_at_gmt'] = gmdate('c');
            $payload['_h4bs_inventory']['release_reason'] = $nextStatus;
        } elseif ($claims && ! in_array($nextStatus, $releaseStatuses, true) && $inventoryState === 'released') {
            $reservation = $this->reserveCheckoutInventory($claims);
            if ($reservation instanceof WP_Error) {
                return $reservation;
            }
            $claims = $reservation;
            $inventoryState = 'reserved';
            $inventoryWasReclaimed = true;
            $payload['_h4bs_inventory']['claims'] = $claims;
            $payload['_h4bs_inventory']['reserved_at_gmt'] = gmdate('c');
            unset($payload['_h4bs_inventory']['released_at_gmt'], $payload['_h4bs_inventory']['release_reason']);
        }

        if (in_array($nextStatus, $releaseStatuses, true) && $promotionState === 'reserved') {
            $promotionState = $this->promotions->releaseForOrder($orderId) ? 'released' : 'release_failed';
            $payload['_h4bs_promotion']['released_at_gmt'] = gmdate('c');
            $payload['_h4bs_promotion']['release_reason'] = $nextStatus;
        } elseif (! in_array($nextStatus, $releaseStatuses, true) && $promotionState === 'released') {
            if (! $this->promotions->reclaimForOrder($orderId)) {
                if ($inventoryWasReclaimed) {
                    $this->releaseCheckoutInventory($claims);
                    $inventoryState = 'released';
                    $payload['_h4bs_inventory']['state'] = 'released';
                }
                return new WP_Error(
                    'h4bs_promotion_unavailable',
                    __('The order promotion can no longer be reserved.', 'help4-builder-suite')
                );
            }
            $promotionState = 'reserved';
            $payload['_h4bs_promotion']['reserved_at_gmt'] = gmdate('c');
            unset($payload['_h4bs_promotion']['released_at_gmt'], $payload['_h4bs_promotion']['release_reason']);
        }

        $payload['_h4bs_inventory']['state'] = $inventoryState;
        if ($promotion) {
            $payload['_h4bs_promotion']['state'] = $promotionState;
        }
        $updated = $wpdb->update(
            $ordersTable,
            [
                'status' => $nextStatus,
                'payload' => wp_json_encode($payload),
            ],
            ['id' => $orderId],
            ['%s', '%s'],
            ['%d']
        );
        if ($updated === false) {
            return new WP_Error('order_update_failed', __('The order could not be updated.', 'help4-builder-suite'));
        }

        $result = [
            'order_id' => $orderId,
            'previous_status' => sanitize_key((string) ($order['status'] ?? '')),
            'status' => $nextStatus,
            'inventory_state' => $inventoryState,
            'inventory_claims' => $claims,
            'promotion_state' => $promotionState,
        ];
        do_action('h4bs_commerce_order_status_changed', $orderId, $result['previous_status'], $nextStatus, $inventoryState, $claims);

        return $result;
    }

    /**
     * Restore stock held by abandoned pending checkouts.
     *
     * @return array<string, int>
     */
    public function releaseExpiredInventoryHolds(int $limit = 100, int $onlyOrderId = 0): array
    {
        $holdMinutes = max(0, min(10080, (int) ($this->settings()['commerce_inventory_hold_minutes'] ?? 60)));
        $limit = max(1, min(500, $limit));
        $result = [
            'checked' => 0,
            'released' => 0,
            'failed' => 0,
            'skipped' => 0,
        ];
        if ($holdMinutes <= 0) {
            return $result;
        }

        global $wpdb;
        $ordersTable = $wpdb->prefix . 'h4_orders';
        $cutoff = gmdate('Y-m-d H:i:s', time() - $holdMinutes * MINUTE_IN_SECONDS);
        $onlyOrderId = absint($onlyOrderId);
        if ($onlyOrderId > 0) {
            $orders = $wpdb->get_results(
                $wpdb->prepare(
                    'SELECT * FROM %i WHERE status = %s AND created_at <= %s AND id = %d ORDER BY created_at ASC, id ASC LIMIT %d',
                    $ordersTable,
                    'pending',
                    $cutoff,
                    $onlyOrderId,
                    $limit
                ),
                ARRAY_A
            );
        } else {
            $orders = $wpdb->get_results(
                $wpdb->prepare(
                    'SELECT * FROM %i WHERE status = %s AND created_at <= %s ORDER BY created_at ASC, id ASC LIMIT %d',
                    $ordersTable,
                    'pending',
                    $cutoff,
                    $limit
                ),
                ARRAY_A
            );
        }
        if (! is_array($orders)) {
            return $result;
        }

        foreach ($orders as $order) {
            if (! is_array($order)) {
                continue;
            }
            $result['checked']++;
            $orderId = absint($order['id'] ?? 0);
            $payload = $this->decodeJsonArray((string) ($order['payload'] ?? ''));
            $inventory = is_array($payload['_h4bs_inventory'] ?? null) ? $payload['_h4bs_inventory'] : [];
            $claims = is_array($inventory['claims'] ?? null) ? array_values(array_filter($inventory['claims'], 'is_array')) : [];
            $promotion = is_array($payload['_h4bs_promotion'] ?? null) ? $payload['_h4bs_promotion'] : [];
            $promotionReserved = sanitize_key((string) ($promotion['state'] ?? '')) === 'reserved';
            $inventoryReserved = $claims && sanitize_key((string) ($inventory['state'] ?? '')) === 'reserved';
            if ($orderId <= 0 || (! $inventoryReserved && ! $promotionReserved)) {
                $result['skipped']++;
                continue;
            }

            $payload['_h4bs_inventory']['state'] = 'releasing';
            $claimed = $wpdb->update(
                $ordersTable,
                [
                    'status' => 'cancelled',
                    'payload' => wp_json_encode($payload),
                ],
                [
                    'id' => $orderId,
                    'status' => 'pending',
                ],
                ['%s', '%s'],
                ['%d', '%s']
            );
            if ($claimed !== 1) {
                $result['skipped']++;
                continue;
            }

            $inventoryReleased = ! $inventoryReserved || $this->releaseCheckoutInventory($claims);
            $promotionReleased = ! $promotionReserved || $this->promotions->releaseForOrder($orderId);
            if ($inventoryReserved) {
                $payload['_h4bs_inventory']['state'] = $inventoryReleased ? 'released' : 'release_failed';
                $payload['_h4bs_inventory']['released_at_gmt'] = gmdate('c');
                $payload['_h4bs_inventory']['release_reason'] = 'hold_expired';
            }
            if ($promotionReserved) {
                $payload['_h4bs_promotion']['state'] = $promotionReleased ? 'released' : 'release_failed';
                $payload['_h4bs_promotion']['released_at_gmt'] = gmdate('c');
                $payload['_h4bs_promotion']['release_reason'] = 'hold_expired';
            }
            $wpdb->update(
                $ordersTable,
                ['payload' => wp_json_encode($payload)],
                ['id' => $orderId],
                ['%s'],
                ['%d']
            );

            $released = $inventoryReleased && $promotionReleased;
            if ($released) {
                $result['released']++;
            } else {
                $result['failed']++;
            }
            do_action('h4bs_commerce_inventory_hold_expired', $orderId, $released, $claims, $holdMinutes);
        }

        return $result;
    }

    private function handleSaveGatewaySettings(): void
    {
        check_admin_referer('h4bs_save_gateway_settings', 'h4bs_save_gateway_settings_nonce');

        $post = wp_unslash($_POST);
        $settings = $this->settings();
        $lockedEndpoints = \Help4BuilderSuite\Support\Settings::lockedEndpointDefaults();
        $settings['stripe_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['stripe_router_endpoint'] ?? ($settings['stripe_router_endpoint'] ?? '')));
        $settings['paypal_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['paypal_router_endpoint'] ?? ($settings['paypal_router_endpoint'] ?? '')));
        $settings['square_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['square_router_endpoint'] ?? ($settings['square_router_endpoint'] ?? '')));
        $settings['braintree_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['braintree_router_endpoint'] ?? ($settings['braintree_router_endpoint'] ?? '')));
        $settings['hosted_payments_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['hosted_payments_router_endpoint'] ?? ($settings['hosted_payments_router_endpoint'] ?? ($settings['godaddy_router_endpoint'] ?? ''))));
        $settings['godaddy_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['godaddy_router_endpoint'] ?? ($settings['godaddy_router_endpoint'] ?? '')));
        $settings['authorize_net_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['authorize_net_router_endpoint'] ?? ($settings['authorize_net_router_endpoint'] ?? '')));
        $settings['wise_router_endpoint'] = esc_url_raw((string) ($lockedEndpoints['wise_router_endpoint'] ?? ($settings['wise_router_endpoint'] ?? '')));
        $settings['stripe_publishable_key'] = sanitize_text_field((string) ($post['stripe_publishable_key'] ?? ''));
        $settings['stripe_secret_key'] = sanitize_text_field((string) ($post['stripe_secret_key'] ?? ''));
        $settings['stripe_webhook_secret'] = sanitize_text_field((string) ($post['stripe_webhook_secret'] ?? ''));
        $settings['stripe_api_track'] = sanitize_key((string) ($post['stripe_api_track'] ?? 'basil'));
        $settings['stripe_api_version_override'] = sanitize_text_field((string) ($post['stripe_api_version_override'] ?? ''));
        $settings['stripe_embedded_checkout_enabled'] = ! empty($post['stripe_embedded_checkout_enabled']) ? '1' : '0';
        $settings['payment_router_shared_secret'] = sanitize_text_field((string) ($post['payment_router_shared_secret'] ?? ''));

        $mode = sanitize_key((string) ($post['commerce_customer_data_mode'] ?? 'both'));
        if (! in_array($mode, ['stripe_only', 'both', 'wordpress_only'], true)) {
            $mode = 'both';
        }
        $settings['commerce_customer_data_mode'] = $mode;
        $settings['commerce_default_customer_role'] = $this->sanitizeCustomerRole((string) ($post['commerce_default_customer_role'] ?? 'customer'));
        $settings['commerce_default_customer_status'] = $this->sanitizeCustomerStatus((string) ($post['commerce_default_customer_status'] ?? 'active'));
        $settings['commerce_inventory_hold_minutes'] = (string) max(
            0,
            min(10080, (int) ($post['commerce_inventory_hold_minutes'] ?? 60))
        );

        $settings['paypal_client_id'] = sanitize_text_field((string) ($post['paypal_client_id'] ?? ''));
        $settings['square_application_id'] = sanitize_text_field((string) ($post['square_application_id'] ?? ''));
        $settings['braintree_merchant_id'] = sanitize_text_field((string) ($post['braintree_merchant_id'] ?? ''));
        $settings['hosted_payments_profile'] = sanitize_text_field((string) ($post['hosted_payments_profile'] ?? ($post['godaddy_payments_profile'] ?? '')));
        $settings['godaddy_payments_profile'] = sanitize_text_field((string) ($post['godaddy_payments_profile'] ?? ''));
        $settings['authorize_net_login_id'] = sanitize_text_field((string) ($post['authorize_net_login_id'] ?? ''));
        $settings['wise_profile_id'] = sanitize_text_field((string) ($post['wise_profile_id'] ?? ''));

        $gatewayCatalog = array_map(
            static fn(array $item): string => sanitize_key((string) ($item['slug'] ?? '')),
            (new GatewayRegistry($settings))->all()
        );
        $gatewayCatalog = array_values(array_filter($gatewayCatalog, static fn(string $slug): bool => $slug !== ''));

        $routeConfig = $this->sanitizeGatewayRouteConfig($post, $settings, $gatewayCatalog);
        $settings['commerce_gateway_routes'] = $routeConfig['routes'];
        $settings['commerce_gateway_rules'] = $routeConfig['rules'];

        update_option('h4bs_settings', $settings);
        $this->redirectCommerce('h4bs-commerce-gateways', ['h4bs_notice' => 'gateways_saved']);
    }

    private function handleSaveShippingSettings(): void
    {
        check_admin_referer('h4bs_save_shipping_settings', 'h4bs_save_shipping_settings_nonce');

        $post = wp_unslash($_POST);
        $settings = $this->settings();
        $rateMode = sanitize_key((string) ($post['shipping_rate_mode'] ?? 'preset'));
        if (! in_array($rateMode, ['api', 'preset'], true)) {
            $rateMode = 'preset';
        }
        $settings['shipping_rate_mode'] = $rateMode;
        $settings['shipping_origin_country'] = strtoupper(substr(sanitize_text_field((string) ($post['shipping_origin_country'] ?? 'US')), 0, 2));
        $settings['shipping_origin_postal'] = sanitize_text_field((string) ($post['shipping_origin_postal'] ?? ''));
        $settings['shipping_default_service'] = sanitize_text_field((string) ($post['shipping_default_service'] ?? 'standard'));

        foreach (['usps', 'ups', 'fedex', 'dhl'] as $carrier) {
            $settings['shipping_' . $carrier . '_enabled'] = ! empty($post['shipping_' . $carrier . '_enabled']) ? '1' : '0';
            $settings['shipping_' . $carrier . '_api_key'] = sanitize_text_field((string) ($post['shipping_' . $carrier . '_api_key'] ?? ''));
            $settings['shipping_' . $carrier . '_api_secret'] = sanitize_text_field((string) ($post['shipping_' . $carrier . '_api_secret'] ?? ''));
            $settings['shipping_' . $carrier . '_account'] = sanitize_text_field((string) ($post['shipping_' . $carrier . '_account'] ?? ''));
            $settings['shipping_' . $carrier . '_preset_domestic'] = (string) max(0, (float) ($post['shipping_' . $carrier . '_preset_domestic'] ?? 0));
            $settings['shipping_' . $carrier . '_preset_international'] = (string) max(0, (float) ($post['shipping_' . $carrier . '_preset_international'] ?? 0));
            $settings['shipping_' . $carrier . '_per_weight_domestic'] = (string) max(0, (float) ($post['shipping_' . $carrier . '_per_weight_domestic'] ?? 0));
            $settings['shipping_' . $carrier . '_per_weight_international'] = (string) max(0, (float) ($post['shipping_' . $carrier . '_per_weight_international'] ?? 0));
        }

        update_option('h4bs_settings', $settings);
        $this->redirectCommerce('h4bs-commerce-shipping', ['h4bs_notice' => 'shipping_saved']);
    }

    private function handleSaveMigrationBridge(): void
    {
        check_admin_referer('h4bs_save_migration_bridge', 'h4bs_save_migration_bridge_nonce');

        $post = wp_unslash($_POST);
        $settings = $this->settings();
        $catalogMode = sanitize_key((string) ($post['commerce_catalog_mode'] ?? 'migration_bridge'));
        if (! in_array($catalogMode, ['migration_bridge', 'native_catalog'], true)) {
            $catalogMode = 'migration_bridge';
        }
        $sourceStrategy = $this->normalizeCommerceSourceStrategy((string) ($post['commerce_source_strategy'] ?? 'none'));
        $syncMode = sanitize_key((string) ($post['commerce_billing_bridge_sync_mode'] ?? ($post[$this->legacyCommerceBillingBridgeKey('sync_mode')] ?? 'disabled')));
        if (! in_array($syncMode, ['disabled', 'manual_only', 'enabled'], true)) {
            $syncMode = 'disabled';
        }
        $billingBridgeApiUrl = esc_url_raw(
            (string) (
                $post['commerce_billing_bridge_api_url']
                ?? ($post[$this->legacyCommerceBillingBridgeKey('api_url')]
                ?? $this->settingsString(
                    $settings,
                    'commerce_billing_bridge_api_url',
                    $this->legacyCommerceBillingBridgeKey('api_url'),
                    'billing_bridge_api_url',
                    $this->legacyBillingBridgeApiKey('url')
                ))
            )
        );
        $billingBridgeIdentifier = sanitize_text_field(
            (string) (
                $post['commerce_billing_bridge_identifier']
                ?? ($post[$this->legacyCommerceBillingBridgeKey('identifier')]
                ?? $this->settingsString(
                    $settings,
                    'commerce_billing_bridge_identifier',
                    $this->legacyCommerceBillingBridgeKey('identifier'),
                    'billing_bridge_api_identifier',
                    $this->legacyBillingBridgeApiKey('identifier')
                ))
            )
        );
        $billingBridgeSecret = sanitize_text_field(
            (string) (
                $post['commerce_billing_bridge_secret']
                ?? ($post[$this->legacyCommerceBillingBridgeKey('secret')]
                ?? $this->settingsString(
                    $settings,
                    'commerce_billing_bridge_secret',
                    $this->legacyCommerceBillingBridgeKey('secret'),
                    'billing_bridge_api_secret',
                    $this->legacyBillingBridgeApiKey('secret')
                ))
            )
        );

        $settings['commerce_catalog_mode'] = $catalogMode;
        $settings['commerce_source_strategy'] = $sourceStrategy;
        $settings['commerce_disable_local_products'] = ! empty($post['commerce_disable_local_products']) ? '1' : '0';
        if ($catalogMode === 'migration_bridge') {
            $settings['commerce_disable_local_products'] = '1';
        }
        $settings['commerce_external_checkout_url'] = esc_url_raw((string) ($post['commerce_external_checkout_url'] ?? ''));
        $settings['commerce_shopify_store_domain'] = sanitize_text_field((string) ($post['commerce_shopify_store_domain'] ?? ''));
        $settings['commerce_shopify_access_token'] = sanitize_text_field((string) ($post['commerce_shopify_access_token'] ?? ''));
        $settings['commerce_billing_bridge_api_url'] = $billingBridgeApiUrl;
        $settings['commerce_billing_bridge_identifier'] = $billingBridgeIdentifier;
        $settings['commerce_billing_bridge_secret'] = $billingBridgeSecret;
        $settings['commerce_billing_bridge_sync_mode'] = $syncMode;
        $settings['commerce_migration_field_map'] = [
            'product' => sanitize_textarea_field((string) ($post['commerce_map_product'] ?? 'name,sku,price,currency,description,type,categories,external_id,external_url')),
            'order' => sanitize_textarea_field((string) ($post['commerce_map_order'] ?? 'order_number,status,total,currency,customer_email,line_items')),
            'customer' => sanitize_textarea_field((string) ($post['commerce_map_customer'] ?? 'email,first_name,last_name,role,status,total_spent,last_order_id')),
        ];

        update_option('h4bs_settings', $settings);
        $this->redirectCommerce('h4bs-commerce-migration', ['h4bs_notice' => 'migration_saved']);
    }

    private function handleRunImport(): void
    {
        check_admin_referer('h4bs_run_import', 'h4bs_run_import_nonce');

        $post = wp_unslash($_POST);
        $downloadImages = ! empty($post['download_images']);
        $replaceVariants = ! array_key_exists('replace_variants', $post) || ! empty($post['replace_variants']);
        $rawJson = trim((string) ($post['import_json'] ?? ''));
        $sourceUrl = esc_url_raw((string) ($post['source_url'] ?? ''));
        $connector = sanitize_key((string) ($post['feed_connector'] ?? 'generic_json'));
        $itemsPath = sanitize_text_field((string) ($post['items_path'] ?? ''));
        $bearerToken = sanitize_text_field((string) ($post['feed_bearer_token'] ?? ''));
        $maxPages = max(1, min(100, (int) ($post['feed_max_pages'] ?? 1)));
        $perPage = max(0, min(500, (int) ($post['feed_per_page'] ?? 0)));
        $items = [];
        $result = [];

        if ($rawJson !== '') {
            $decoded = json_decode($rawJson, true);
            if (is_array($decoded)) {
                if (isset($decoded['items']) && is_array($decoded['items'])) {
                    $items = $decoded['items'];
                } else {
                    $items = array_is_list($decoded) ? $decoded : [];
                }
            }
            if (! $items) {
                $this->redirectCommerce('h4bs-commerce-imports', ['h4bs_notice' => 'import_invalid_payload']);
            }

            $result = $this->importer->import(
                $items,
                [
                    'download_images' => $downloadImages,
                    'replace_variants'=> $replaceVariants,
                ]
            );
        } elseif ($sourceUrl !== '') {
            $pulled = $this->feedPuller->pull(
                [
                    'url'             => $sourceUrl,
                    'connector'       => $connector,
                    'items_path'      => $itemsPath,
                    'bearer_token'    => $bearerToken,
                    'download_images' => $downloadImages,
                    'replace_variants'=> $replaceVariants,
                    'max_pages'       => $maxPages,
                    'per_page'        => $perPage,
                ]
            );

            if ($pulled instanceof WP_Error) {
                $result = [
                    'total'    => 0,
                    'created'  => 0,
                    'updated'  => 0,
                    'variants' => 0,
                    'errors'   => [$pulled->get_error_message()],
                ];
                set_transient('h4bs_last_import_result', $result, 300);
                $this->redirectCommerce('h4bs-commerce-imports', ['h4bs_notice' => 'import_failed']);
            }

            $result = $pulled;
        } else {
            $this->redirectCommerce('h4bs-commerce-imports', ['h4bs_notice' => 'import_invalid_payload']);
        }

        set_transient('h4bs_last_import_result', $result, 300);
        $this->redirectCommerce('h4bs-commerce-imports', ['h4bs_notice' => 'import_completed']);
    }

    private function handleExportOrders(): void
    {
        check_admin_referer('h4bs_export_orders', 'h4bs_export_orders_nonce');

        global $wpdb;
        $ordersTable = $wpdb->prefix . 'h4_orders';
        $itemsTable = $wpdb->prefix . 'h4_order_items';
        $orders = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i ORDER BY created_at DESC', $ordersTable), ARRAY_A);
        if (! is_array($orders)) {
            $orders = [];
        }

        nocache_headers();
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="h4-orders-' . gmdate('Ymd-His') . '.csv"');

        $output = fopen('php://output', 'wb');
        if (! is_resource($output)) {
            exit;
        }

        fputcsv(
            $output,
            [
                'order_id',
                'order_number',
                'customer_email',
                'customer_user_id',
                'total',
                'currency',
                'status',
                'gateway',
                'created_at',
                'updated_at',
                'items_json',
                'payload_json',
            ]
        );

        foreach ($orders as $order) {
            if (! is_array($order)) {
                continue;
            }
            $orderId = absint($order['id'] ?? 0);
            $items = $wpdb->get_results(
                $wpdb->prepare(
                    'SELECT product_id, variant_id, qty, unit_price, payload FROM %i WHERE order_id = %d ORDER BY id ASC',
                    $itemsTable,
                    $orderId
                ),
                ARRAY_A
            );
            if (! is_array($items)) {
                $items = [];
            }

            fputcsv(
                $output,
                [
                    (string) $orderId,
                    (string) ($order['order_number'] ?? ''),
                    (string) ($order['customer_email'] ?? ''),
                    (string) absint($order['customer_user_id'] ?? 0),
                    (string) ($order['total'] ?? 0),
                    (string) ($order['currency'] ?? 'USD'),
                    (string) ($order['status'] ?? 'pending'),
                    (string) ($order['gateway'] ?? ''),
                    (string) ($order['created_at'] ?? ''),
                    (string) ($order['updated_at'] ?? ''),
                    wp_json_encode($items),
                    (string) ($order['payload'] ?? ''),
                ]
            );
        }

        exit;
    }

    private function handleImportOrders(): void
    {
        check_admin_referer('h4bs_import_orders', 'h4bs_import_orders_nonce');

        $post = wp_unslash($_POST);
        $raw = trim((string) ($post['orders_import_json'] ?? ''));
        if ($raw === '') {
            $this->redirectCommerce('h4bs-commerce-orders', ['h4bs_notice' => 'orders_import_empty']);
        }

        $decoded = json_decode($raw, true);
        $rows = [];
        if (is_array($decoded)) {
            if (isset($decoded['items']) && is_array($decoded['items'])) {
                $rows = $decoded['items'];
            } elseif (array_is_list($decoded)) {
                $rows = $decoded;
            }
        }
        if (! $rows) {
            $this->redirectCommerce('h4bs-commerce-orders', ['h4bs_notice' => 'orders_import_invalid']);
        }

        global $wpdb;
        $ordersTable = $wpdb->prefix . 'h4_orders';
        $itemsTable = $wpdb->prefix . 'h4_order_items';
        $settings = $this->settings();
        $customerDataMode = sanitize_key((string) ($settings['commerce_customer_data_mode'] ?? 'both'));
        if (! in_array($customerDataMode, ['stripe_only', 'both', 'wordpress_only'], true)) {
            $customerDataMode = 'both';
        }
        $defaultRole = $this->sanitizeCustomerRole((string) ($settings['commerce_default_customer_role'] ?? 'customer'));
        $defaultStatus = $this->sanitizeCustomerStatus((string) ($settings['commerce_default_customer_status'] ?? 'active'));

        $imported = 0;
        $skipped = 0;
        $errors = 0;

        foreach ($rows as $index => $row) {
            if (! is_array($row)) {
                $errors++;
                continue;
            }

            $orderNumber = sanitize_text_field((string) ($row['order_number'] ?? ''));
            if ($orderNumber === '') {
                $orderNumber = 'MIG-' . gmdate('Ymd') . '-' . ($index + 1) . '-' . wp_rand(1000, 9999);
            }

            $existingOrderId = (int) $wpdb->get_var(
                $wpdb->prepare(
                    'SELECT id FROM %i WHERE order_number = %s LIMIT 1',
                    $ordersTable,
                    $orderNumber
                )
            );
            if ($existingOrderId > 0) {
                $skipped++;
                continue;
            }

            $customerEmail = sanitize_email((string) ($row['customer_email'] ?? ($row['email'] ?? '')));
            $firstName = sanitize_text_field((string) ($row['customer_first_name'] ?? ($row['first_name'] ?? '')));
            $lastName = sanitize_text_field((string) ($row['customer_last_name'] ?? ($row['last_name'] ?? '')));
            $role = $this->sanitizeCustomerRole((string) ($row['customer_role'] ?? ($row['role'] ?? '')), false);
            $status = $this->sanitizeCustomerStatus((string) ($row['customer_status'] ?? ''), false);
            $finalRole = $role !== '' ? $role : $defaultRole;
            $finalStatus = $status !== '' ? $status : $defaultStatus;
            $customerUserId = absint($row['customer_user_id'] ?? 0);

            if ($customerEmail !== '' && $customerDataMode !== 'stripe_only') {
                $account = $this->ensureCustomerUser(
                    $customerEmail,
                    $firstName,
                    $lastName,
                    $finalRole,
                    $finalStatus
                );
                if ($customerUserId <= 0) {
                    $customerUserId = absint($account['user_id'] ?? 0);
                }
            }

            $payload = $row['payload'] ?? [];
            if (is_array($payload)) {
                $payload = wp_json_encode($payload);
            } elseif (! is_string($payload)) {
                $payload = '';
            }
            $orderStatus = sanitize_key((string) ($row['status'] ?? 'pending'));
            if ($orderStatus === '') {
                $orderStatus = 'pending';
            }
            $orderGateway = sanitize_key((string) ($row['gateway'] ?? 'import'));
            if ($orderGateway === '') {
                $orderGateway = 'import';
            }

            $inserted = $wpdb->insert(
                $ordersTable,
                [
                    'order_number' => $orderNumber,
                    'customer_email' => $customerDataMode === 'stripe_only' ? '' : $customerEmail,
                    'customer_user_id' => $customerUserId,
                    'total' => (float) ($row['total'] ?? ($row['order_total'] ?? 0)),
                    'currency' => strtoupper(sanitize_text_field((string) ($row['currency'] ?? 'USD'))),
                    'status' => $orderStatus,
                    'gateway' => $orderGateway,
                    'payload' => (string) $payload,
                ],
                ['%s', '%s', '%d', '%f', '%s', '%s', '%s', '%s']
            );

            if ($inserted === false) {
                $errors++;
                continue;
            }

            $orderId = (int) $wpdb->insert_id;
            $lineItems = $row['items'] ?? ($row['line_items'] ?? ($row['items_json'] ?? []));
            if (is_string($lineItems)) {
                $decodedItems = json_decode($lineItems, true);
                $lineItems = is_array($decodedItems) ? $decodedItems : [];
            }
            if (! is_array($lineItems)) {
                $lineItems = [];
            }

            foreach ($lineItems as $lineItem) {
                if (! is_array($lineItem)) {
                    continue;
                }
                $itemPayload = $lineItem['payload'] ?? [];
                if (is_array($itemPayload)) {
                    $itemPayload = wp_json_encode($itemPayload);
                } elseif (! is_string($itemPayload)) {
                    $itemPayload = '';
                }
                $wpdb->insert(
                    $itemsTable,
                    [
                        'order_id' => $orderId,
                        'product_id' => absint($lineItem['product_id'] ?? 0),
                        'variant_id' => absint($lineItem['variant_id'] ?? 0),
                        'qty' => (float) ($lineItem['qty'] ?? 1),
                        'unit_price' => (float) ($lineItem['unit_price'] ?? 0),
                        'payload' => (string) $itemPayload,
                    ],
                    ['%d', '%d', '%d', '%f', '%f', '%s']
                );
            }

            if ($customerEmail !== '' && $customerDataMode !== 'stripe_only') {
                $this->upsertCustomerRecord(
                    $customerEmail,
                    $customerUserId,
                    $firstName,
                    $lastName,
                    $finalRole,
                    $finalStatus,
                    $orderId
                );
            }

            $imported++;
        }

        set_transient(
            'h4bs_orders_import_result',
            [
                'imported' => $imported,
                'skipped' => $skipped,
                'errors' => $errors,
            ],
            600
        );
        $this->redirectCommerce('h4bs-commerce-orders', ['h4bs_notice' => 'orders_import_completed']);
    }

    private function handleExportCustomers(): void
    {
        check_admin_referer('h4bs_export_customers', 'h4bs_export_customers_nonce');

        global $wpdb;
        $customersTable = $wpdb->prefix . 'h4_customers';
        $customers = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i ORDER BY updated_at DESC', $customersTable), ARRAY_A);
        if (! is_array($customers)) {
            $customers = [];
        }
        $exportableDefinitions = array_values(
            array_filter(
                $this->customerFields->listDefinitions('', [], true),
                static fn(array $definition): bool => ! empty($definition['exportable']) && (string) ($definition['sensitivity'] ?? '') !== 'sensitive'
            )
        );
        $exportableKeys = array_values(
            array_filter(
                array_map(static fn(array $definition): string => (string) ($definition['field_key'] ?? ''), $exportableDefinitions)
            )
        );

        nocache_headers();
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="h4-customers-' . gmdate('Ymd-His') . '.csv"');

        $output = fopen('php://output', 'wb');
        if (! is_resource($output)) {
            exit;
        }

        fputcsv(
            $output,
            [
                'id',
                'wp_user_id',
                'email',
                'first_name',
                'last_name',
                'role',
                'status',
                'order_count',
                'total_spent',
                'last_order_id',
                'created_at',
                'updated_at',
                'payload_json',
                'customer_fields_json',
            ]
        );

        foreach ($customers as $customer) {
            if (! is_array($customer)) {
                continue;
            }
            $customerFieldValues = $this->customerFields->getValues(absint($customer['id'] ?? 0));
            $customerFieldExport = array_intersect_key($customerFieldValues, array_fill_keys($exportableKeys, true));
            fputcsv(
                $output,
                [
                    (string) ($customer['id'] ?? ''),
                    (string) ($customer['wp_user_id'] ?? 0),
                    (string) ($customer['email'] ?? ''),
                    (string) ($customer['first_name'] ?? ''),
                    (string) ($customer['last_name'] ?? ''),
                    (string) ($customer['role'] ?? 'customer'),
                    (string) ($customer['status'] ?? 'active'),
                    (string) ($customer['order_count'] ?? 0),
                    (string) ($customer['total_spent'] ?? 0),
                    (string) ($customer['last_order_id'] ?? 0),
                    (string) ($customer['created_at'] ?? ''),
                    (string) ($customer['updated_at'] ?? ''),
                    (string) ($customer['payload'] ?? ''),
                    (string) wp_json_encode($customerFieldExport, JSON_UNESCAPED_SLASHES),
                ]
            );
        }

        exit;
    }

    private function handleImportCustomers(): void
    {
        check_admin_referer('h4bs_import_customers', 'h4bs_import_customers_nonce');

        $post = wp_unslash($_POST);
        $raw = trim((string) ($post['customers_import_json'] ?? ''));
        if ($raw === '') {
            $this->redirectCommerce('h4bs-commerce-customers', ['h4bs_notice' => 'customers_import_empty']);
        }

        $decoded = json_decode($raw, true);
        $rows = [];
        if (is_array($decoded)) {
            if (isset($decoded['items']) && is_array($decoded['items'])) {
                $rows = $decoded['items'];
            } elseif (array_is_list($decoded)) {
                $rows = $decoded;
            }
        }
        if (! $rows) {
            $this->redirectCommerce('h4bs-commerce-customers', ['h4bs_notice' => 'customers_import_invalid']);
        }

        $settings = $this->settings();
        $defaultRole = $this->sanitizeCustomerRole((string) ($settings['commerce_default_customer_role'] ?? 'customer'));
        $defaultStatus = $this->sanitizeCustomerStatus((string) ($settings['commerce_default_customer_status'] ?? 'active'));
        $imported = 0;
        $errors = 0;

        foreach ($rows as $row) {
            if (! is_array($row)) {
                $errors++;
                continue;
            }
            $email = sanitize_email((string) ($row['email'] ?? ($row['customer_email'] ?? '')));
            if ($email === '') {
                $errors++;
                continue;
            }
            $firstName = sanitize_text_field((string) ($row['first_name'] ?? ($row['customer_first_name'] ?? '')));
            $lastName = sanitize_text_field((string) ($row['last_name'] ?? ($row['customer_last_name'] ?? '')));
            $role = $this->sanitizeCustomerRole((string) ($row['role'] ?? ($row['customer_role'] ?? '')), false);
            $status = $this->sanitizeCustomerStatus((string) ($row['status'] ?? ($row['customer_status'] ?? '')), false);
            $finalRole = $role !== '' ? $role : $defaultRole;
            $finalStatus = $status !== '' ? $status : $defaultStatus;
            $account = $this->ensureCustomerUser($email, $firstName, $lastName, $finalRole, $finalStatus);
            $orderCount = array_key_exists('order_count', $row) ? max(0, (int) $row['order_count']) : null;
            $totalSpent = array_key_exists('total_spent', $row) ? max(0.0, (float) $row['total_spent']) : null;
            $lastOrderId = absint($row['last_order_id'] ?? 0);

            $customerId = $this->upsertCustomerRecord(
                $email,
                absint($account['user_id'] ?? 0),
                $firstName,
                $lastName,
                $finalRole,
                $finalStatus,
                $lastOrderId,
                $totalSpent,
                $orderCount
            );
            $rawFieldValues = $row['customer_fields'] ?? ($row['customer_fields_json'] ?? []);
            if (is_string($rawFieldValues)) {
                $decodedFieldValues = json_decode($rawFieldValues, true);
                $rawFieldValues = is_array($decodedFieldValues) ? $decodedFieldValues : [];
            }
            if ($customerId > 0 && is_array($rawFieldValues) && $rawFieldValues) {
                $definitions = $this->customerFields->listDefinitions('', [], true);
                $validatedFields = $this->customerFields->validateValues($rawFieldValues, $definitions, true);
                if ($validatedFields instanceof WP_Error || $this->customerFields->saveValues($customerId, $validatedFields, $definitions) instanceof WP_Error) {
                    $errors++;
                    continue;
                }
            }
            $imported++;
        }

        set_transient(
            'h4bs_customers_import_result',
            [
                'imported' => $imported,
                'errors' => $errors,
            ],
            600
        );
        $this->redirectCommerce('h4bs-commerce-customers', ['h4bs_notice' => 'customers_import_completed']);
    }

    private function handleSaveCustomerField(): void
    {
        check_admin_referer('h4bs_save_customer_field', 'h4bs_save_customer_field_nonce');
        $post = wp_unslash($_POST);
        $post['placements'] = is_array($post['placements'] ?? null) ? $post['placements'] : [];
        $result = $this->customerFields->saveDefinition($post);
        if ($result instanceof WP_Error) {
            set_transient('h4bs_customer_field_notice', ['type' => 'error', 'message' => $result->get_error_message()], 300);
        } else {
            set_transient('h4bs_customer_field_notice', ['type' => 'success', 'message' => __('Customer field saved.', 'help4-builder-suite')], 300);
        }
        $this->redirectCommerce('h4bs-commerce-customer-fields');
    }

    private function handleArchiveCustomerField(): void
    {
        $fieldId = AdminRequest::queryAbsInt('field_id');
        check_admin_referer('h4bs_archive_customer_field_' . $fieldId);
        $archived = $this->customerFields->archiveDefinition($fieldId);
        set_transient(
            'h4bs_customer_field_notice',
            [
                'type' => $archived ? 'success' : 'error',
                'message' => $archived
                    ? __('Customer field archived. Existing values were preserved.', 'help4-builder-suite')
                    : __('Customer field could not be archived.', 'help4-builder-suite'),
            ],
            300
        );
        $this->redirectCommerce('h4bs-commerce-customer-fields');
    }

    private function handleDuplicateCustomerField(): void
    {
        $fieldId = AdminRequest::queryAbsInt('field_id');
        check_admin_referer('h4bs_duplicate_customer_field_' . $fieldId);
        $result = $this->customerFields->duplicateDefinition($fieldId);
        set_transient(
            'h4bs_customer_field_notice',
            [
                'type' => $result instanceof WP_Error ? 'error' : 'success',
                'message' => $result instanceof WP_Error
                    ? $result->get_error_message()
                    : __('Customer field duplicated as an inactive draft.', 'help4-builder-suite'),
            ],
            300
        );
        $this->redirectCommerce('h4bs-commerce-customer-fields');
    }

    private function handleInstallCustomerFieldPreset(): void
    {
        check_admin_referer('h4bs_install_customer_field_preset', 'h4bs_customer_field_preset_nonce');
        $post = wp_unslash($_POST);
        $result = $this->customerFields->installPreset(sanitize_key((string) ($post['preset'] ?? '')));
        $message = $result instanceof WP_Error
            ? $result->get_error_message()
            : sprintf(
                /* translators: 1: created field count, 2: skipped field count. */
                __('Field pack installed: %1$d created, %2$d existing keys skipped.', 'help4-builder-suite'),
                (int) ($result['created'] ?? 0),
                (int) ($result['skipped'] ?? 0)
            );
        set_transient(
            'h4bs_customer_field_notice',
            ['type' => $result instanceof WP_Error ? 'error' : 'success', 'message' => $message],
            300
        );
        $this->redirectCommerce('h4bs-commerce-customer-fields');
    }

    private function handleImportCustomerFields(): void
    {
        check_admin_referer('h4bs_import_customer_fields', 'h4bs_customer_field_import_nonce');
        $post = wp_unslash($_POST);
        $result = $this->customerFields->importDefinitions(
            (string) ($post['customer_fields_json'] ?? ''),
            sanitize_key((string) ($post['conflict_mode'] ?? 'skip'))
        );
        $message = $result instanceof WP_Error
            ? $result->get_error_message()
            : sprintf(
                /* translators: 1: created, 2: updated, 3: skipped, 4: failed. */
                __('Customer fields imported: %1$d created, %2$d updated, %3$d skipped, %4$d failed.', 'help4-builder-suite'),
                (int) ($result['created'] ?? 0),
                (int) ($result['updated'] ?? 0),
                (int) ($result['skipped'] ?? 0),
                count((array) ($result['errors'] ?? []))
            );
        set_transient(
            'h4bs_customer_field_notice',
            ['type' => $result instanceof WP_Error || ! empty($result['errors']) ? 'error' : 'success', 'message' => $message],
            300
        );
        $this->redirectCommerce('h4bs-commerce-customer-fields');
    }

    private function handleExportCustomerFields(): void
    {
        check_admin_referer('h4bs_export_customer_fields', 'h4bs_customer_field_export_nonce');
        $json = wp_json_encode($this->customerFields->exportDefinitions(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        if (! is_string($json)) {
            wp_die(esc_html__('The customer-field export could not be encoded.', 'help4-builder-suite'));
        }
        nocache_headers();
        header('Content-Type: application/json; charset=utf-8');
        header('Content-Disposition: attachment; filename="help4-customer-fields-' . gmdate('Ymd-His') . '.json"');
        echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Downloaded JSON is encoded by wp_json_encode above.
        exit;
    }

    private function handleSaveCustomerFieldValues(): void
    {
        $post = wp_unslash($_POST);
        $customerId = absint($post['customer_id'] ?? 0);
        check_admin_referer('h4bs_save_customer_field_values_' . $customerId, 'h4bs_save_customer_field_values_nonce');
        $definitions = $this->customerFields->listDefinitions('admin');
        $rawValues = is_array($post['customer_fields'] ?? null) ? $post['customer_fields'] : [];
        $clearKeys = is_array($post['clear_customer_fields'] ?? null)
            ? array_values(array_unique(array_filter(array_map('sanitize_key', $post['clear_customer_fields']))))
            : [];
        foreach ($definitions as $definition) {
            $key = (string) ($definition['field_key'] ?? '');
            if (in_array($key, $clearKeys, true)) {
                unset($rawValues[$key]);
                if (! empty($definition['required'])) {
                    set_transient('h4bs_customer_field_value_notice', ['type' => 'error', 'message' => sprintf(
                        /* translators: %s: field label. */
                        __('%s is required and cannot be removed without a replacement.', 'help4-builder-suite'),
                        (string) ($definition['label'] ?? $key)
                    )], 300);
                    $this->redirectCommerce('h4bs-commerce-customers', ['customer_id' => $customerId]);
                }
                continue;
            }
            if ((string) ($definition['sensitivity'] ?? '') === 'sensitive' && $key !== '') {
                if (! array_key_exists($key, $rawValues) || $this->customerFieldInputIsEmpty($rawValues[$key])) {
                    unset($rawValues[$key]);
                    continue;
                }
            }
            if ($key === '') {
                continue;
            }
            if (in_array((string) ($definition['field_type'] ?? ''), ['checkbox', 'toggle'], true) && $key !== '' && ! array_key_exists($key, $rawValues)) {
                $rawValues[$key] = '0';
            }
        }
        $validated = $this->customerFields->validateValues($rawValues, $definitions, true);
        $result = $validated instanceof WP_Error
            ? $validated
            : $this->customerFields->saveValues($customerId, $validated, $definitions, $clearKeys);
        set_transient(
            'h4bs_customer_field_value_notice',
            [
                'type' => $result instanceof WP_Error ? 'error' : 'success',
                'message' => $result instanceof WP_Error
                    ? $result->get_error_message()
                    : __('Customer details saved.', 'help4-builder-suite'),
            ],
            300
        );
        $this->redirectCommerce('h4bs-commerce-customers', ['customer_id' => $customerId]);
    }

    public function handleCustomerAccountFieldSave(): void
    {
        $requestMethod = isset($_SERVER['REQUEST_METHOD'])
            ? strtoupper(sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_METHOD'])))
            : '';
        if (is_admin() || ! is_user_logged_in() || $requestMethod !== 'POST') {
            return;
        }

        $post = wp_unslash($_POST);
        if (sanitize_key((string) ($post['h4bs_customer_account_action'] ?? '')) !== 'save_customer_fields') {
            return;
        }
        if (! isset($post['h4bs_customer_account_nonce'])
            || ! wp_verify_nonce(sanitize_text_field((string) $post['h4bs_customer_account_nonce']), 'h4bs_save_customer_account_fields')) {
            wp_die(esc_html__('Your customer details could not be verified. Refresh the page and try again.', 'help4-builder-suite'), '', ['response' => 403]);
        }

        $user = wp_get_current_user();
        $userId = (int) $user->ID;
        $email = sanitize_email((string) $user->user_email);
        $noticeKey = 'h4bs_customer_account_notice_' . $userId;
        $allDefinitions = $this->customerFields->listDefinitions('account', ['user_roles' => (array) $user->roles]);
        $editableDefinitions = array_values(
            array_filter(
                $allDefinitions,
                static fn(array $definition): bool => ! empty($definition['customer_editable'])
            )
        );
        $rawValues = is_array($post['customer_fields'] ?? null) ? $post['customer_fields'] : [];
        $editableKeys = array_fill_keys(
            array_filter(array_map(static fn(array $definition): string => sanitize_key((string) ($definition['field_key'] ?? '')), $editableDefinitions)),
            true
        );
        $rawValues = array_intersect_key($rawValues, $editableKeys);
        $clearKeys = is_array($post['clear_customer_fields'] ?? null)
            ? array_values(array_unique(array_filter(array_map('sanitize_key', $post['clear_customer_fields']))))
            : [];
        $clearKeys = array_values(array_filter($clearKeys, static fn(string $key): bool => isset($editableKeys[$key])));
        foreach ($editableDefinitions as $definition) {
            $key = sanitize_key((string) ($definition['field_key'] ?? ''));
            if ($key === '') {
                continue;
            }
            if (in_array($key, $clearKeys, true)) {
                unset($rawValues[$key]);
                if (! empty($definition['required'])) {
                    set_transient($noticeKey, ['type' => 'error', 'message' => sprintf(
                        /* translators: %s: field label. */
                        __('%s is required and cannot be removed without a replacement.', 'help4-builder-suite'),
                        (string) ($definition['label'] ?? $key)
                    )], 300);
                    $target = wp_get_referer();
                    wp_safe_redirect(is_string($target) && $target !== '' ? $target : home_url('/'));
                    exit;
                }
                continue;
            }
            if ((string) ($definition['sensitivity'] ?? '') === 'sensitive') {
                if (! array_key_exists($key, $rawValues) || $this->customerFieldInputIsEmpty($rawValues[$key])) {
                    unset($rawValues[$key]);
                    continue;
                }
            }
            if (in_array((string) ($definition['field_type'] ?? ''), ['checkbox', 'toggle'], true) && ! array_key_exists($key, $rawValues)) {
                $rawValues[$key] = '0';
            }
        }

        $validated = $this->customerFields->validateValues($rawValues, $allDefinitions, true);
        $result = $validated;
        if (! $validated instanceof WP_Error) {
            $customerId = $this->upsertCustomerRecord(
                $email,
                $userId,
                sanitize_text_field((string) get_user_meta($userId, 'first_name', true)),
                sanitize_text_field((string) get_user_meta($userId, 'last_name', true)),
                'customer',
                'active'
            );
            $result = $customerId > 0
                ? $this->customerFields->saveValues($customerId, $validated, $allDefinitions, $clearKeys)
                : new WP_Error('h4bs_customer_account_record', __('Your customer profile could not be prepared.', 'help4-builder-suite'));
        }

        set_transient(
            $noticeKey,
            [
                'type' => $result instanceof WP_Error ? 'error' : 'success',
                'message' => $result instanceof WP_Error
                    ? $result->get_error_message()
                    : __('Your customer details were saved.', 'help4-builder-suite'),
            ],
            300
        );
        $target = wp_get_referer();
        wp_safe_redirect(is_string($target) && $target !== '' ? $target : home_url('/'));
        exit;
    }

    private function customerFieldInputIsEmpty(mixed $value): bool
    {
        if (is_array($value)) {
            return array_filter($value, static fn(mixed $item): bool => trim((string) $item) !== '') === [];
        }
        return trim((string) $value) === '';
    }

    /**
     * @param array<string, mixed> $exporters
     * @return array<string, mixed>
     */
    public function registerCustomerFieldPrivacyExporter(array $exporters): array
    {
        $exporters['help4-commerce-customer-fields'] = [
            'exporter_friendly_name' => __('Help4 Commerce customer fields', 'help4-builder-suite'),
            'callback' => [$this, 'exportCustomerFieldPrivacyData'],
        ];
        return $exporters;
    }

    /**
     * @return array{data:array<int, array<string, mixed>>,done:bool}
     */
    public function exportCustomerFieldPrivacyData(string $emailAddress, int $page = 1): array
    {
        if ($page > 1) {
            return ['data' => [], 'done' => true];
        }
        $customerId = $this->customerFields->findCustomerIdByEmail($emailAddress);
        if ($customerId <= 0) {
            return ['data' => [], 'done' => true];
        }
        $values = $this->customerFields->getValues($customerId, true);
        $definitions = $this->customerFields->listDefinitions('', [], true);
        $data = [];
        foreach ($definitions as $definition) {
            $key = (string) ($definition['field_key'] ?? '');
            if ($key === '' || ! array_key_exists($key, $values)) {
                continue;
            }
            $value = $values[$key];
            $data[] = [
                'name' => (string) ($definition['label'] ?? $key),
                'value' => is_array($value) ? implode(', ', array_map('strval', $value)) : (string) $value,
            ];
        }
        return [
            'data' => $data ? [[
                'group_id' => 'help4-commerce-customer-fields',
                'group_label' => __('Help4 Commerce customer fields', 'help4-builder-suite'),
                'item_id' => 'customer-' . $customerId,
                'data' => $data,
            ]] : [],
            'done' => true,
        ];
    }

    /**
     * @param array<string, mixed> $erasers
     * @return array<string, mixed>
     */
    public function registerCustomerFieldPrivacyEraser(array $erasers): array
    {
        $erasers['help4-commerce-customer-fields'] = [
            'eraser_friendly_name' => __('Help4 Commerce customer fields', 'help4-builder-suite'),
            'callback' => [$this, 'eraseCustomerFieldPrivacyData'],
        ];
        return $erasers;
    }

    /**
     * @return array{items_removed:bool,items_retained:bool,messages:array<int,string>,done:bool}
     */
    public function eraseCustomerFieldPrivacyData(string $emailAddress, int $page = 1): array
    {
        $customerId = $page === 1 ? $this->customerFields->findCustomerIdByEmail($emailAddress) : 0;
        $removed = $customerId > 0 ? $this->customerFields->deleteValues($customerId) : 0;
        return [
            'items_removed' => $removed > 0,
            'items_retained' => false,
            'messages' => [],
            'done' => true,
        ];
    }

    private function redirectCommerce(string $page, array $args = []): void
    {
        $target = add_query_arg(array_merge(['page' => $page], $args), admin_url('admin.php'));
        wp_safe_redirect($target);
        exit;
    }

    private function deleteProductUrl(int $productId): string
    {
        return wp_nonce_url(
            add_query_arg(
                [
                    'page'        => 'h4bs-commerce-products',
                    'h4bs_action' => 'delete_product',
                    'product_id'  => $productId,
                ],
                admin_url('admin.php')
            ),
            'h4bs_delete_product_' . $productId
        );
    }

    private function archiveCustomerFieldUrl(int $fieldId): string
    {
        return wp_nonce_url(
            add_query_arg(
                [
                    'page' => 'h4bs-commerce-customer-fields',
                    'h4bs_action' => 'archive_customer_field',
                    'field_id' => $fieldId,
                ],
                admin_url('admin.php')
            ),
            'h4bs_archive_customer_field_' . $fieldId
        );
    }

    private function duplicateCustomerFieldUrl(int $fieldId): string
    {
        return wp_nonce_url(
            add_query_arg(
                [
                    'page' => 'h4bs-commerce-customer-fields',
                    'h4bs_action' => 'duplicate_customer_field',
                    'field_id' => $fieldId,
                ],
                admin_url('admin.php')
            ),
            'h4bs_duplicate_customer_field_' . $fieldId
        );
    }

    private function customerFieldPresetDescription(string $preset): string
    {
        return match ($preset) {
            'business_profile' => __('Company identity, organization size, industry, and encrypted tax identifier.', 'help4-builder-suite'),
            'service_intake' => __('Project goals, budget, preferred launch date, and contact method.', 'help4-builder-suite'),
            'delivery_preferences' => __('Delivery windows, contactless preference, and special instructions.', 'help4-builder-suite'),
            'membership_onboarding' => __('Experience, weekly goals, and interest areas for member personalization.', 'help4-builder-suite'),
            default => __('A reusable group of customer fields.', 'help4-builder-suite'),
        };
    }

    /**
     * @return array<string, mixed>
     */
    private function settings(): array
    {
        $settings = \Help4BuilderSuite\Support\Settings::all();
        return is_array($settings) ? $settings : [];
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function commerceCatalogMode(array $settings): string
    {
        $mode = sanitize_key((string) ($settings['commerce_catalog_mode'] ?? 'migration_bridge'));
        if (! in_array($mode, ['migration_bridge', 'native_catalog'], true)) {
            $mode = 'migration_bridge';
        }
        return $mode;
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function commerceSourceStrategy(array $settings): string
    {
        return $this->normalizeCommerceSourceStrategy((string) ($settings['commerce_source_strategy'] ?? 'none'));
    }

    private function normalizeCommerceSourceStrategy(string $strategy): string
    {
        $strategy = sanitize_key($strategy);
        if ($strategy === $this->legacyBillingBridgeStrategy()) {
            return 'billing_bridge';
        }
        if (! in_array($strategy, ['none', 'shopify', 'billing_bridge', 'hybrid'], true)) {
            return 'none';
        }
        return $strategy;
    }

    private function legacyBillingBridgeStrategy(): string
    {
        return 'wh' . 'mcs';
    }

    private function legacyCommerceBillingBridgeKey(string $suffix): string
    {
        return 'commerce_' . $this->legacyBillingBridgeStrategy() . '_' . $suffix;
    }

    private function legacyBillingBridgeApiKey(string $suffix): string
    {
        return $this->legacyBillingBridgeStrategy() . '_api_' . $suffix;
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function settingsString(array $settings, string ...$keys): string
    {
        $fallback = '';
        foreach ($keys as $key) {
            if (! array_key_exists($key, $settings) || is_array($settings[$key]) || is_object($settings[$key])) {
                continue;
            }
            $value = (string) $settings[$key];
            if ($value !== '') {
                return $value;
            }
            $fallback = $value;
        }
        return $fallback;
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function localProductEditingEnabled(array $settings): bool
    {
        $mode = $this->commerceCatalogMode($settings);
        if ($mode === 'migration_bridge') {
            return false;
        }
        return ! rest_sanitize_boolean($settings['commerce_disable_local_products'] ?? false);
    }

    private function localCatalogEditingBlockedResponse(): ?WP_REST_Response
    {
        if ($this->localProductEditingEnabled($this->settings())) {
            return null;
        }
        return new WP_REST_Response(
            [
                'message' => 'Local catalog editing is disabled while Migration Bridge mode is enabled.',
                'mode' => 'migration_bridge',
            ],
            423
        );
    }

    public function renderProductsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $search = AdminRequest::queryText('s');
        $status = AdminRequest::queryKey('status');
        $searchMode = AdminRequest::queryKey('search_mode', 'prefix');
        if (! in_array($searchMode, ['prefix', 'contains', 'exact'], true)) {
            $searchMode = 'prefix';
        }
        $sort = $this->sanitizeCatalogSort(AdminRequest::queryKey('sort', 'updated_desc'));
        $minPrice = max(0, AdminRequest::queryFloat('min_price'));
        $maxPrice = max(0, AdminRequest::queryFloat('max_price'));
        $inStockOnly = AdminRequest::queryHasValue('in_stock_only');
        $settings = $this->settings();
        $catalogMode = $this->commerceCatalogMode($settings);
        $sourceStrategy = $this->commerceSourceStrategy($settings);
        $localProductEditingEnabled = $this->localProductEditingEnabled($settings);
        $viewAction = AdminRequest::queryKey('action');
        $isEditorView = in_array($viewAction, ['new', 'edit'], true);
        if (! $localProductEditingEnabled) {
            $isEditorView = false;
        }
        $editId = AdminRequest::queryAbsInt('edit');
        $editing = $editId > 0 ? $this->products->get($editId) : null;
        $editingPayload = is_array($editing) ? $this->decodeJsonArray((string) ($editing['payload'] ?? '')) : [];
        $editingType = $this->sanitizeProductType((string) ($editingPayload['type'] ?? 'simple'));
        $editingFulfillment = $this->sanitizeFulfillmentType(
            (string) (
                $editingPayload['fulfillment_type']
                ?? $this->defaultFulfillmentForProductType($editingType)
            )
        );
        $editingIsDigital = $this->detectDigitalProduct($editingPayload, []);
        $editingIsBooking = $this->detectBookingProduct($editingPayload, []);
        $editingRequiresShipping = $this->detectShippingRequirement(
            $editingPayload,
            [],
            $editingFulfillment,
            $editingIsDigital,
            $editingIsBooking
        );
        $editingDownloads = $this->normalizeDownloads($editingPayload['downloads'] ?? ($editingPayload['download_urls'] ?? []));
        $editingDownloadText = $this->downloadsToTextarea($editingDownloads);
        $editingBooking = is_array($editingPayload['booking'] ?? null) ? $editingPayload['booking'] : [];
        $editingBookingDuration = max(5, min(1440, (int) ($editingBooking['duration_minutes'] ?? 60)));
        $editingBookingBuffer = max(0, min(480, (int) ($editingBooking['buffer_minutes'] ?? 0)));
        $editingBookingRequiresConfirmation = ! empty($editingBooking['requires_confirmation']);
        $editingSubscription = is_array($editingPayload['subscription'] ?? null) ? $editingPayload['subscription'] : [];
        $editingSubscriptionInterval = $this->sanitizeSubscriptionInterval((string) ($editingSubscription['interval_code'] ?? ($editingSubscription['interval'] ?? 'monthly')));
        $editingSubscriptionIntervalCount = max(1, min(36, (int) ($editingSubscription['interval_count'] ?? 1)));
        $editingSubscriptionTrialDays = max(0, min(365, (int) ($editingSubscription['trial_days'] ?? 0)));
        $editingSubscriptionSetupFee = max(0.0, (float) ($editingSubscription['setup_fee'] ?? 0));
        $editingSubscriptionAutoRenew = (bool) ($editingSubscription['auto_renew'] ?? true);
        $editingShippingProfile = is_array($editingPayload['shipping_profile'] ?? null) ? $editingPayload['shipping_profile'] : [];
        $editingWeight = max(0, (float) ($editingShippingProfile['weight'] ?? 0));
        $editingLength = max(0, (float) ($editingShippingProfile['length'] ?? 0));
        $editingWidth = max(0, (float) ($editingShippingProfile['width'] ?? 0));
        $editingHeight = max(0, (float) ($editingShippingProfile['height'] ?? 0));
        $editingWeightUnit = sanitize_key((string) ($editingShippingProfile['weight_unit'] ?? 'lb'));
        if (! in_array($editingWeightUnit, ['lb', 'kg', 'oz', 'g'], true)) {
            $editingWeightUnit = 'lb';
        }
        $editingDimensionUnit = sanitize_key((string) ($editingShippingProfile['dimension_unit'] ?? 'in'));
        if (! in_array($editingDimensionUnit, ['in', 'cm', 'm'], true)) {
            $editingDimensionUnit = 'in';
        }
        $editingCustomerAccount = is_array($editingPayload['customer_account'] ?? null) ? $editingPayload['customer_account'] : [];
        $editingRoleAfterPurchase = $this->sanitizeCustomerRole((string) ($editingCustomerAccount['role_after_purchase'] ?? ''), false);
        $editingStatusAfterPurchase = $this->sanitizeCustomerStatus((string) ($editingCustomerAccount['status_after_purchase'] ?? ''), false);
        $editingInventory = $this->normalizeInventoryConfig($editingPayload);
        $editingInventoryTrackStock = is_array($editing) ? (bool) $editingInventory['track_stock'] : true;
        $editingInventoryPolicy = (string) $editingInventory['policy'];
        $editingInventoryLowStockThreshold = (int) $editingInventory['low_stock_threshold'];
        $editingInventorySoldIndividually = (bool) $editingInventory['sold_individually'];
        $editingInventoryQuantityEnabled = (bool) $editingInventory['quantity_enabled'];
        $editingInventoryMinQty = (float) $editingInventory['min_qty'];
        $editingInventoryMaxQty = (float) $editingInventory['max_qty'];
        $editingInventoryQtyStep = (float) $editingInventory['qty_step'];
        $editingPreorderAvailableOn = (string) $editingInventory['preorder_available_on'];
        $editingPreorderMessage = (string) $editingInventory['preorder_message'];
        $editingPricing = $this->normalizePricingConfig($editingPayload);
        $editingTax = is_array($editingPayload['tax'] ?? null) ? $editingPayload['tax'] : [];
        $editingTaxStatus = sanitize_key((string) ($editingTax['status'] ?? 'taxable'));
        if (! in_array($editingTaxStatus, ['taxable', 'none'], true)) {
            $editingTaxStatus = 'taxable';
        }
        $editingTaxClass = sanitize_key((string) ($editingTax['class'] ?? 'standard')) ?: 'standard';
        $editingMerchandising = $this->normalizeMerchandisingConfig($editingPayload);
        $editingRelatedProductIds = implode(', ', $editingMerchandising['related_product_ids']);
        $editingUpsellProductIds = implode(', ', $editingMerchandising['upsell_product_ids']);
        $editingCrossSellProductIds = implode(', ', $editingMerchandising['cross_sell_product_ids']);
        $editingSeoDescription = sanitize_textarea_field((string) ($editingPayload['description'] ?? ''));
        $editingSeoBrand = sanitize_text_field((string) ($editingPayload['brand'] ?? ''));
        $editingSeoGtin = sanitize_text_field((string) ($editingPayload['gtin'] ?? ''));
        $editingSeoMpn = sanitize_text_field((string) ($editingPayload['mpn'] ?? ''));
        $editingSeoCondition = sanitize_key((string) ($editingPayload['condition'] ?? 'new'));
        if (! in_array($editingSeoCondition, ['new', 'used', 'refurbished'], true)) {
            $editingSeoCondition = 'new';
        }
        $editingSeoLandingUrl = esc_url_raw((string) ($editingPayload['external_url'] ?? ($editingPayload['product_url'] ?? '')), ['http', 'https']);
        $editingSeoImageUrl = esc_url_raw((string) ($editingPayload['featured_image_url'] ?? ($editingPayload['image_url'] ?? '')), ['http', 'https']);
        $editingSeoProductCategory = sanitize_text_field((string) ($editingPayload['google_product_category'] ?? ''));
        $editingProductId = is_array($editing) ? (int) ($editing['id'] ?? 0) : 0;
        $editingVariantMatrixJson = $this->adminVariantMatrixJson($editingProductId);
        $editingVariantRows = $this->adminVariantFormRows($editingProductId);
        $availableRoles = $this->availableCustomerRoles();
        $products = $this->products->list(
            [
                'search' => $search,
                'status' => $status,
                'search_mode' => $searchMode,
                'sort' => $sort,
                'min_price' => $minPrice,
                'max_price' => $maxPrice,
                'in_stock_only' => $inStockOnly,
                'limit'  => 200,
                'offset' => 0,
            ]
        );
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1 class="wp-heading-inline"><?php esc_html_e('Products', 'help4-builder-suite'); ?></h1>
            <?php if ($isEditorView && $localProductEditingEnabled) : ?>
                <a class="page-title-action" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-products')); ?>">
                    <?php esc_html_e('Back to Products', 'help4-builder-suite'); ?>
                </a>
            <?php elseif ($localProductEditingEnabled) : ?>
                <a class="page-title-action" href="<?php echo esc_url(add_query_arg(['page' => 'h4bs-commerce-products', 'action' => 'new'], admin_url('admin.php'))); ?>">
                    <?php esc_html_e('Add Product', 'help4-builder-suite'); ?>
                </a>
            <?php else : ?>
                <a class="page-title-action" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-migration')); ?>">
                    <?php esc_html_e('Open Migration Bridge', 'help4-builder-suite'); ?>
                </a>
            <?php endif; ?>
            <hr class="wp-header-end">
            <?php $notice = AdminRequest::queryKey('h4bs_notice'); ?>
            <?php if ($notice === 'product_editing_disabled') : ?>
                <div class="notice notice-warning is-dismissible"><p><?php esc_html_e('Local product editing is currently disabled for this site. Use Commerce > Migration Bridge + Imports while migration is in progress.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>
            <?php if ($notice === 'product_created') : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Product created.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ($notice === 'product_updated') : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Product updated.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ($notice === 'product_deleted') : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Product deleted.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ($notice === 'product_name_required') : ?>
                <div class="notice notice-error is-dismissible"><p><?php esc_html_e('Product name is required.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ($notice === 'invalid_variants') : ?>
                <div class="notice notice-error is-dismissible"><p><?php esc_html_e('Variant matrix must be valid JSON: an array of variant objects with sku, attributes, price, stock_qty, optional image_id, and position.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>
            <p class="description">
                <?php
                printf(
                    /* translators: 1: catalog mode, 2: source strategy */
                    esc_html__('Catalog mode: %1$s | Source strategy: %2$s', 'help4-builder-suite'),
                    esc_html($catalogMode === 'migration_bridge' ? __('Migration Bridge', 'help4-builder-suite') : __('Native Catalog', 'help4-builder-suite')),
                    esc_html(ucfirst($sourceStrategy))
                );
                ?>
            </p>
            <?php if (! $localProductEditingEnabled) : ?>
                <div class="notice notice-info"><p><?php esc_html_e('This environment is in migration-safe mode. Existing products can be reviewed, but new product editing is locked until you switch to Native Catalog in Migration Bridge.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>

            <div class="h4bs-admin-card-grid" aria-label="<?php esc_attr_e('Product setup guide', 'help4-builder-suite'); ?>">
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Choose what the customer is buying', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Simple, service, digital, booking, variable, and subscription products all route through the native Help4 checkout model.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Use variants for real catalogs', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('The variant matrix stores SKU, attributes, price, stock, image, and position rows without needing a separate store plugin.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Send the right payment path', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Product type, fulfillment, subscription status, amount, country, and currency can all feed payment-router rules.', 'help4-builder-suite'); ?></p>
                </div>
            </div>

            <?php if ($isEditorView && $localProductEditingEnabled) : ?>
            <form method="post" class="postbox" style="padding:1rem;max-width:980px;">
                <?php wp_nonce_field('h4bs_save_product', 'h4bs_save_product_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="save_product">
                <input type="hidden" name="product_id" value="<?php echo esc_attr((string) ($editing['id'] ?? 0)); ?>">
                <h2><?php echo ($viewAction === 'edit' && $editing) ? esc_html__('Edit Product', 'help4-builder-suite') : esc_html__('Add Product', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr><th><label for="h4bs_product_name"><?php esc_html_e('Name', 'help4-builder-suite'); ?></label></th><td><input required class="regular-text" id="h4bs_product_name" type="text" name="name" value="<?php echo esc_attr((string) ($editing['name'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="h4bs_product_sku"><?php esc_html_e('SKU', 'help4-builder-suite'); ?></label></th><td><input class="regular-text" id="h4bs_product_sku" type="text" name="sku" value="<?php echo esc_attr((string) ($editing['sku'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="h4bs_product_slug"><?php esc_html_e('Slug', 'help4-builder-suite'); ?></label></th><td><input class="regular-text" id="h4bs_product_slug" type="text" name="slug" value="<?php echo esc_attr((string) ($editing['slug'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="h4bs_product_status"><?php esc_html_e('Status', 'help4-builder-suite'); ?></label></th><td><select id="h4bs_product_status" name="status"><option value="active" <?php selected((string) ($editing['status'] ?? ''), 'active'); ?>>active</option><option value="draft" <?php selected((string) ($editing['status'] ?? ''), 'draft'); ?>>draft</option><option value="archived" <?php selected((string) ($editing['status'] ?? ''), 'archived'); ?>>archived</option></select></td></tr>
                        <tr><th><label for="h4bs_product_price"><?php esc_html_e('Price', 'help4-builder-suite'); ?></label></th><td><input class="small-text" id="h4bs_product_price" type="number" step="0.01" min="0" name="price" value="<?php echo esc_attr((string) ($editing['price'] ?? '0')); ?>"> <input class="small-text" type="text" name="currency" value="<?php echo esc_attr((string) ($editing['currency'] ?? 'USD')); ?>"></td></tr>
                        <tr><th><label for="h4bs_product_stock"><?php esc_html_e('Stock Qty', 'help4-builder-suite'); ?></label></th><td><input class="small-text" id="h4bs_product_stock" type="number" step="1" min="0" name="stock_qty" value="<?php echo esc_attr((string) ($editing['stock_qty'] ?? '0')); ?>"></td></tr>
                        <tr>
                            <th><?php esc_html_e('Search and Merchant Discovery', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_seo_description"><strong><?php esc_html_e('Product description', 'help4-builder-suite'); ?></strong></label><br>
                                <textarea id="h4bs_seo_description" class="large-text" rows="5" name="seo_description"><?php echo esc_textarea($editingSeoDescription); ?></textarea>
                                <p class="description"><?php esc_html_e('Write factual product-specific copy. This feeds catalog cards, readiness checks, and the optional merchant feed.', 'help4-builder-suite'); ?></p>
                                <label for="h4bs_seo_landing_url"><?php esc_html_e('Product landing URL', 'help4-builder-suite'); ?></label><br>
                                <input id="h4bs_seo_landing_url" class="large-text code" type="url" name="seo_landing_url" value="<?php echo esc_attr($editingSeoLandingUrl); ?>" placeholder="https://example.com/products/product-name/"><br><br>
                                <label for="h4bs_seo_image_url"><?php esc_html_e('Primary product image URL', 'help4-builder-suite'); ?></label><br>
                                <input id="h4bs_seo_image_url" class="large-text code" type="url" name="seo_image_url" value="<?php echo esc_attr($editingSeoImageUrl); ?>" placeholder="https://example.com/wp-content/uploads/product.jpg"><br><br>
                                <label for="h4bs_seo_brand"><?php esc_html_e('Brand', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_seo_brand" class="regular-text" type="text" name="seo_brand" value="<?php echo esc_attr($editingSeoBrand); ?>">
                                &nbsp;
                                <label for="h4bs_seo_condition"><?php esc_html_e('Condition', 'help4-builder-suite'); ?></label>
                                <select id="h4bs_seo_condition" name="seo_condition"><option value="new" <?php selected($editingSeoCondition, 'new'); ?>><?php esc_html_e('New', 'help4-builder-suite'); ?></option><option value="used" <?php selected($editingSeoCondition, 'used'); ?>><?php esc_html_e('Used', 'help4-builder-suite'); ?></option><option value="refurbished" <?php selected($editingSeoCondition, 'refurbished'); ?>><?php esc_html_e('Refurbished', 'help4-builder-suite'); ?></option></select><br><br>
                                <label for="h4bs_seo_gtin"><?php esc_html_e('GTIN', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_seo_gtin" class="regular-text code" inputmode="numeric" type="text" name="seo_gtin" value="<?php echo esc_attr($editingSeoGtin); ?>" pattern="[0-9]{8}|[0-9]{12}|[0-9]{13}|[0-9]{14}">
                                &nbsp;
                                <label for="h4bs_seo_mpn"><?php esc_html_e('MPN', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_seo_mpn" class="regular-text code" type="text" name="seo_mpn" value="<?php echo esc_attr($editingSeoMpn); ?>">
                                <p class="description"><?php esc_html_e('Use the manufacturer-assigned GTIN when one exists. Do not invent identifiers. SKU above remains the store-owned identifier.', 'help4-builder-suite'); ?></p>
                                <label for="h4bs_seo_product_category"><?php esc_html_e('Merchant product category', 'help4-builder-suite'); ?></label><br>
                                <input id="h4bs_seo_product_category" class="large-text" type="text" name="seo_product_category" value="<?php echo esc_attr($editingSeoProductCategory); ?>" placeholder="Apparel & Accessories > Clothing">
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Pricing Strategy', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_compare_at_price"><?php esc_html_e('Compare-at Price', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_compare_at_price" class="small-text" type="number" step="0.01" min="0" name="compare_at_price" value="<?php echo esc_attr((string) $editingPricing['compare_at_price']); ?>">
                                &nbsp;
                                <label for="h4bs_unit_cost"><?php esc_html_e('Unit Cost', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_unit_cost" class="small-text" type="number" step="0.01" min="0" name="unit_cost" value="<?php echo esc_attr((string) $editingPricing['cost']); ?>">
                                <p class="description"><?php esc_html_e('Compare-at pricing can show customer savings. Unit cost stays private and supports margin reporting.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Tax Treatment', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_tax_status"><?php esc_html_e('Status', 'help4-builder-suite'); ?></label>
                                <select id="h4bs_tax_status" name="tax_status">
                                    <option value="taxable" <?php selected($editingTaxStatus, 'taxable'); ?>><?php esc_html_e('Taxable', 'help4-builder-suite'); ?></option>
                                    <option value="none" <?php selected($editingTaxStatus, 'none'); ?>><?php esc_html_e('Not taxable', 'help4-builder-suite'); ?></option>
                                </select>
                                &nbsp;
                                <label for="h4bs_tax_class"><?php esc_html_e('Tax Class', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_tax_class" class="regular-text" type="text" name="tax_class" value="<?php echo esc_attr($editingTaxClass); ?>" placeholder="standard">
                                <p class="description"><?php esc_html_e('Match this class to Commerce > Taxes. Use names such as standard, reduced, digital, or exempt.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Inventory Rules', 'help4-builder-suite'); ?></th>
                            <td>
                                <input type="hidden" name="inventory_track_stock" value="0">
                                <label><input type="checkbox" name="inventory_track_stock" value="1" <?php checked($editingInventoryTrackStock); ?>> <?php esc_html_e('Track and reserve stock during checkout', 'help4-builder-suite'); ?></label>
                                <br><br>
                                <label for="h4bs_inventory_policy"><?php esc_html_e('When stock runs out', 'help4-builder-suite'); ?></label>
                                <select id="h4bs_inventory_policy" name="inventory_policy">
                                    <option value="deny" <?php selected($editingInventoryPolicy, 'deny'); ?>><?php esc_html_e('Stop sales', 'help4-builder-suite'); ?></option>
                                    <option value="backorder_notify" <?php selected($editingInventoryPolicy, 'backorder_notify'); ?>><?php esc_html_e('Allow backorders and tell the customer', 'help4-builder-suite'); ?></option>
                                    <option value="backorder" <?php selected($editingInventoryPolicy, 'backorder'); ?>><?php esc_html_e('Allow backorders', 'help4-builder-suite'); ?></option>
                                    <option value="preorder" <?php selected($editingInventoryPolicy, 'preorder'); ?>><?php esc_html_e('Allow preorders', 'help4-builder-suite'); ?></option>
                                </select>
                                &nbsp;
                                <label for="h4bs_low_stock_threshold"><?php esc_html_e('Low-stock warning at', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_low_stock_threshold" class="small-text" type="number" step="1" min="0" name="inventory_low_stock_threshold" value="<?php echo esc_attr((string) $editingInventoryLowStockThreshold); ?>">
                                <br><br>
                                <input type="hidden" name="inventory_quantity_enabled" value="0">
                                <label><input type="checkbox" name="inventory_quantity_enabled" value="1" data-h4bs-product-quantity-enabled <?php checked($editingInventoryQuantityEnabled); ?>> <?php esc_html_e('Allow customers to choose a quantity', 'help4-builder-suite'); ?></label>
                                <p class="description"><?php esc_html_e('Turn this off for configured bundles, packages, bookings, and other products that must remain one unit per cart line. Customers can still add another separately configured line.', 'help4-builder-suite'); ?></p>
                                <span data-h4bs-product-quantity-rules>
                                    <label for="h4bs_inventory_min_qty"><?php esc_html_e('Minimum', 'help4-builder-suite'); ?></label>
                                    <input id="h4bs_inventory_min_qty" class="small-text" type="number" step="0.001" min="0.001" max="1000" name="inventory_min_qty" value="<?php echo esc_attr((string) $editingInventoryMinQty); ?>">
                                    &nbsp;
                                    <label for="h4bs_inventory_max_qty"><?php esc_html_e('Maximum', 'help4-builder-suite'); ?></label>
                                    <input id="h4bs_inventory_max_qty" class="small-text" type="number" step="0.001" min="0" max="1000" name="inventory_max_qty" value="<?php echo esc_attr((string) $editingInventoryMaxQty); ?>">
                                    &nbsp;
                                    <label for="h4bs_inventory_qty_step"><?php esc_html_e('Quantity step', 'help4-builder-suite'); ?></label>
                                    <input id="h4bs_inventory_qty_step" class="small-text" type="number" step="0.001" min="0.001" max="1000" name="inventory_qty_step" value="<?php echo esc_attr((string) $editingInventoryQtyStep); ?>">
                                </span>
                                <br><br>
                                <input type="hidden" name="inventory_sold_individually" value="0">
                                <label><input type="checkbox" name="inventory_sold_individually" value="1" <?php checked($editingInventorySoldIndividually); ?>> <?php esc_html_e('Limit each order to one', 'help4-builder-suite'); ?></label>
                                <p class="description"><?php esc_html_e('Order limit is separate from quantity display: enable it only when the entire order may contain one. Maximum 0 means no product-level limit.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Preorder Details', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_preorder_available_on"><?php esc_html_e('Expected availability', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_preorder_available_on" type="date" name="inventory_preorder_available_on" value="<?php echo esc_attr($editingPreorderAvailableOn); ?>">
                                <br><br>
                                <label for="h4bs_preorder_message"><?php esc_html_e('Customer message', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_preorder_message" class="regular-text" type="text" name="inventory_preorder_message" value="<?php echo esc_attr($editingPreorderMessage); ?>" placeholder="Ships when available">
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Merchandising', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_custom_badge"><?php esc_html_e('Custom Badge', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_custom_badge" class="regular-text" type="text" name="custom_badge" value="<?php echo esc_attr((string) $editingMerchandising['badge']); ?>" placeholder="Staff pick">
                                <br><br>
                                <label for="h4bs_related_products"><?php esc_html_e('Related Product IDs', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_related_products" class="regular-text" type="text" name="related_product_ids" value="<?php echo esc_attr($editingRelatedProductIds); ?>" placeholder="12, 18, 24">
                                <br><br>
                                <label for="h4bs_upsell_products"><?php esc_html_e('Upsell Product IDs', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_upsell_products" class="regular-text" type="text" name="upsell_product_ids" value="<?php echo esc_attr($editingUpsellProductIds); ?>" placeholder="31, 42">
                                <br><br>
                                <label for="h4bs_cross_sell_products"><?php esc_html_e('Cross-sell Product IDs', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_cross_sell_products" class="regular-text" type="text" name="cross_sell_product_ids" value="<?php echo esc_attr($editingCrossSellProductIds); ?>" placeholder="51, 52">
                                <br><br>
                                <label for="h4bs_purchase_note"><?php esc_html_e('Post-purchase Note', 'help4-builder-suite'); ?></label>
                                <textarea id="h4bs_purchase_note" class="large-text" rows="3" name="purchase_note"><?php echo esc_textarea((string) $editingMerchandising['purchase_note']); ?></textarea>
                                <p class="description"><?php esc_html_e('Product relationships are available to catalog, cart, and checkout extensions. The purchase note is stored with the line item for confirmation and fulfillment use.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_product_type"><?php esc_html_e('Product Type', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="h4bs_product_type" name="product_type">
                                    <option value="simple" <?php selected($editingType, 'simple'); ?>><?php esc_html_e('Simple', 'help4-builder-suite'); ?></option>
                                    <option value="variable" <?php selected($editingType, 'variable'); ?>><?php esc_html_e('Variable', 'help4-builder-suite'); ?></option>
                                    <option value="digital" <?php selected($editingType, 'digital'); ?>><?php esc_html_e('Digital Download', 'help4-builder-suite'); ?></option>
                                    <option value="booking" <?php selected($editingType, 'booking'); ?>><?php esc_html_e('Booking / Appointment', 'help4-builder-suite'); ?></option>
                                    <option value="service" <?php selected($editingType, 'service'); ?>><?php esc_html_e('Service', 'help4-builder-suite'); ?></option>
                                    <option value="subscription" <?php selected($editingType, 'subscription'); ?>><?php esc_html_e('Subscription', 'help4-builder-suite'); ?></option>
                                </select>
                                <p class="description"><?php esc_html_e('This controls digital/booking/shipping behavior for checkout routing.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_variant_matrix"><?php esc_html_e('Variant Matrix', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <div class="h4bs-admin-table-scroll">
                                    <table class="widefat striped h4bs-variant-table">
                                        <thead>
                                            <tr>
                                                <th><?php esc_html_e('SKU', 'help4-builder-suite'); ?></th>
                                                <th><?php esc_html_e('Attributes', 'help4-builder-suite'); ?></th>
                                                <th><?php esc_html_e('Price', 'help4-builder-suite'); ?></th>
                                                <th><?php esc_html_e('Stock', 'help4-builder-suite'); ?></th>
                                                <th><?php esc_html_e('Image ID', 'help4-builder-suite'); ?></th>
                                                <th><?php esc_html_e('Sort', 'help4-builder-suite'); ?></th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            <?php foreach ($editingVariantRows as $variantRow) : ?>
                                                <tr>
                                                    <td><input class="regular-text" type="text" name="variant_sku[]" value="<?php echo esc_attr((string) ($variantRow['sku'] ?? '')); ?>" placeholder="SKU-SMALL"></td>
                                                    <td><input class="regular-text" type="text" name="variant_attributes[]" value="<?php echo esc_attr((string) ($variantRow['attributes'] ?? '')); ?>" placeholder="size=Small, color=Blue"></td>
                                                    <td><input class="small-text" type="number" step="0.01" min="0" name="variant_price[]" value="<?php echo esc_attr((string) ($variantRow['price'] ?? '')); ?>"></td>
                                                    <td><input class="small-text" type="number" step="1" name="variant_stock_qty[]" value="<?php echo esc_attr((string) ($variantRow['stock_qty'] ?? '')); ?>"></td>
                                                    <td><input class="small-text" type="number" step="1" min="0" name="variant_image_id[]" value="<?php echo esc_attr((string) ($variantRow['image_id'] ?? '')); ?>"></td>
                                                    <td><input class="small-text" type="number" step="1" name="variant_position[]" value="<?php echo esc_attr((string) ($variantRow['position'] ?? 0)); ?>"></td>
                                                </tr>
                                            <?php endforeach; ?>
                                        </tbody>
                                    </table>
                                </div>
                                <p class="description"><?php esc_html_e('Use rows for normal editing. Attributes use key=value pairs separated by commas, like size=Small, color=Blue. Saving replaces this product\'s current variants with the non-empty rows.', 'help4-builder-suite'); ?></p>
                                <details>
                                    <summary><?php esc_html_e('Advanced: paste full variant JSON instead', 'help4-builder-suite'); ?></summary>
                                    <textarea id="h4bs_variant_matrix" name="variants_json" class="large-text code h4bs-admin-code-textarea" rows="9" placeholder='[{"sku":"SKU-SMALL","attributes":{"size":"Small"},"price":19.99,"stock_qty":25,"image_id":0,"position":0},{"sku":"SKU-LARGE","attributes":{"size":"Large"},"price":24.99,"stock_qty":12,"image_id":0,"position":1}]'><?php echo esc_textarea($editingVariantMatrixJson); ?></textarea>
                                    <p class="description"><?php esc_html_e('When this JSON field is filled, it overrides the row table for bulk variant replacement.', 'help4-builder-suite'); ?></p>
                                </details>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Subscription Terms', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_subscription_interval_count"><?php esc_html_e('Every', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_subscription_interval_count" class="small-text" type="number" step="1" min="1" max="36" name="subscription_interval_count" value="<?php echo esc_attr((string) $editingSubscriptionIntervalCount); ?>">
                                <select name="subscription_interval">
                                    <option value="daily" <?php selected($editingSubscriptionInterval, 'daily'); ?>><?php esc_html_e('Day(s)', 'help4-builder-suite'); ?></option>
                                    <option value="weekly" <?php selected($editingSubscriptionInterval, 'weekly'); ?>><?php esc_html_e('Week(s)', 'help4-builder-suite'); ?></option>
                                    <option value="monthly" <?php selected($editingSubscriptionInterval, 'monthly'); ?>><?php esc_html_e('Month(s)', 'help4-builder-suite'); ?></option>
                                    <option value="quarterly" <?php selected($editingSubscriptionInterval, 'quarterly'); ?>><?php esc_html_e('Quarter(s)', 'help4-builder-suite'); ?></option>
                                    <option value="yearly" <?php selected($editingSubscriptionInterval, 'yearly'); ?>><?php esc_html_e('Year(s)', 'help4-builder-suite'); ?></option>
                                </select>
                                &nbsp;
                                <label for="h4bs_subscription_trial_days"><?php esc_html_e('Trial Days', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_subscription_trial_days" class="small-text" type="number" step="1" min="0" max="365" name="subscription_trial_days" value="<?php echo esc_attr((string) $editingSubscriptionTrialDays); ?>">
                                &nbsp;
                                <label for="h4bs_subscription_setup_fee"><?php esc_html_e('Setup Fee', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_subscription_setup_fee" class="small-text" type="number" step="0.01" min="0" name="subscription_setup_fee" value="<?php echo esc_attr((string) $editingSubscriptionSetupFee); ?>">
                                <br><br>
                                <label><input type="checkbox" name="subscription_auto_renew" value="1" <?php checked($editingSubscriptionAutoRenew); ?>> <?php esc_html_e('Auto-renew until canceled', 'help4-builder-suite'); ?></label>
                                <p class="description"><?php esc_html_e('Used when Product Type is Subscription. These terms are sent to payment-router adapters and stored on checkout-created subscription records.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_product_fulfillment"><?php esc_html_e('Fulfillment Type', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="h4bs_product_fulfillment" name="fulfillment_type">
                                    <option value="shipping" <?php selected($editingFulfillment, 'shipping'); ?>><?php esc_html_e('Shipped', 'help4-builder-suite'); ?></option>
                                    <option value="digital" <?php selected($editingFulfillment, 'digital'); ?>><?php esc_html_e('Digital', 'help4-builder-suite'); ?></option>
                                    <option value="booking" <?php selected($editingFulfillment, 'booking'); ?>><?php esc_html_e('Booking', 'help4-builder-suite'); ?></option>
                                    <option value="service" <?php selected($editingFulfillment, 'service'); ?>><?php esc_html_e('Service', 'help4-builder-suite'); ?></option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Checkout Flags', 'help4-builder-suite'); ?></th>
                            <td>
                                <label><input type="checkbox" name="is_digital" value="1" <?php checked($editingIsDigital); ?>> <?php esc_html_e('Digital product / no shipment', 'help4-builder-suite'); ?></label><br>
                                <label><input type="checkbox" name="is_booking" value="1" <?php checked($editingIsBooking); ?>> <?php esc_html_e('Booking product', 'help4-builder-suite'); ?></label><br>
                                <label><input type="checkbox" name="requires_shipping" value="1" <?php checked($editingRequiresShipping); ?>> <?php esc_html_e('Requires shipping address', 'help4-builder-suite'); ?></label>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Shipping Package Profile', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_product_weight"><?php esc_html_e('Weight', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_product_weight" class="small-text" type="number" step="0.01" min="0" name="shipping_weight" value="<?php echo esc_attr((string) $editingWeight); ?>">
                                <select name="shipping_weight_unit">
                                    <option value="lb" <?php selected($editingWeightUnit, 'lb'); ?>>lb</option>
                                    <option value="kg" <?php selected($editingWeightUnit, 'kg'); ?>>kg</option>
                                    <option value="oz" <?php selected($editingWeightUnit, 'oz'); ?>>oz</option>
                                    <option value="g" <?php selected($editingWeightUnit, 'g'); ?>>g</option>
                                </select>
                                <br><br>
                                <label for="h4bs_product_length"><?php esc_html_e('Dimensions (L x W x H)', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_product_length" class="small-text" type="number" step="0.01" min="0" name="shipping_length" value="<?php echo esc_attr((string) $editingLength); ?>">
                                <input class="small-text" type="number" step="0.01" min="0" name="shipping_width" value="<?php echo esc_attr((string) $editingWidth); ?>">
                                <input class="small-text" type="number" step="0.01" min="0" name="shipping_height" value="<?php echo esc_attr((string) $editingHeight); ?>">
                                <select name="shipping_dimension_unit">
                                    <option value="in" <?php selected($editingDimensionUnit, 'in'); ?>>in</option>
                                    <option value="cm" <?php selected($editingDimensionUnit, 'cm'); ?>>cm</option>
                                    <option value="m" <?php selected($editingDimensionUnit, 'm'); ?>>m</option>
                                </select>
                                <p class="description"><?php esc_html_e('Used by shipping carrier integrations and preset-rate calculations.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_product_downloads"><?php esc_html_e('Digital Download URLs', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <textarea id="h4bs_product_downloads" name="download_urls" class="large-text code" rows="4" placeholder="https://example.com/files/guide.pdf&#10;https://example.com/files/video.mp4"><?php echo esc_textarea($editingDownloadText); ?></textarea>
                                <p class="description"><?php esc_html_e('One URL per line. Sent to router endpoints in each line item downloads payload.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Booking Details', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_booking_duration"><?php esc_html_e('Duration (minutes)', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_booking_duration" class="small-text" type="number" step="5" min="5" max="1440" name="booking_duration_minutes" value="<?php echo esc_attr((string) $editingBookingDuration); ?>">
                                &nbsp;
                                <label for="h4bs_booking_buffer"><?php esc_html_e('Buffer (minutes)', 'help4-builder-suite'); ?></label>
                                <input id="h4bs_booking_buffer" class="small-text" type="number" step="5" min="0" max="480" name="booking_buffer_minutes" value="<?php echo esc_attr((string) $editingBookingBuffer); ?>">
                                <br><br>
                                <label><input type="checkbox" name="booking_requires_confirmation" value="1" <?php checked($editingBookingRequiresConfirmation); ?>> <?php esc_html_e('Requires manual confirmation', 'help4-builder-suite'); ?></label>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Post-purchase Customer Account', 'help4-builder-suite'); ?></th>
                            <td>
                                <label for="h4bs_customer_role_after_purchase"><?php esc_html_e('Role After Purchase', 'help4-builder-suite'); ?></label>
                                <select id="h4bs_customer_role_after_purchase" name="customer_role_after_purchase">
                                    <option value=""><?php esc_html_e('Use commerce default role', 'help4-builder-suite'); ?></option>
                                    <?php foreach ($availableRoles as $roleKey => $roleLabel) : ?>
                                        <option value="<?php echo esc_attr($roleKey); ?>" <?php selected($editingRoleAfterPurchase, $roleKey); ?>>
                                            <?php echo esc_html($roleLabel); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <br><br>
                                <label for="h4bs_customer_status_after_purchase"><?php esc_html_e('Customer Status After Purchase', 'help4-builder-suite'); ?></label>
                                <select id="h4bs_customer_status_after_purchase" name="customer_status_after_purchase">
                                    <option value=""><?php esc_html_e('Use commerce default status', 'help4-builder-suite'); ?></option>
                                    <option value="active" <?php selected($editingStatusAfterPurchase, 'active'); ?>><?php esc_html_e('Active', 'help4-builder-suite'); ?></option>
                                    <option value="vip" <?php selected($editingStatusAfterPurchase, 'vip'); ?>><?php esc_html_e('VIP', 'help4-builder-suite'); ?></option>
                                    <option value="suspended" <?php selected($editingStatusAfterPurchase, 'suspended'); ?>><?php esc_html_e('Suspended', 'help4-builder-suite'); ?></option>
                                    <option value="blocked" <?php selected($editingStatusAfterPurchase, 'blocked'); ?>><?php esc_html_e('Blocked', 'help4-builder-suite'); ?></option>
                                </select>
                                <p class="description"><?php esc_html_e('Lets a product change the customer role/status after successful checkout. Leave as default to inherit global commerce settings.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                    </tbody>
                </table>
                <?php submit_button($editing ? __('Update Product', 'help4-builder-suite') : __('Create Product', 'help4-builder-suite')); ?>
            </form>
            <?php else : ?>
            <form method="get">
                <input type="hidden" name="page" value="h4bs-commerce-products">
                <input type="search" name="s" value="<?php echo esc_attr($search); ?>" placeholder="<?php esc_attr_e('Search name / sku / slug', 'help4-builder-suite'); ?>">
                <select name="search_mode">
                    <option value="prefix" <?php selected($searchMode, 'prefix'); ?>><?php esc_html_e('Prefix Search (Fast)', 'help4-builder-suite'); ?></option>
                    <option value="contains" <?php selected($searchMode, 'contains'); ?>><?php esc_html_e('Contains Search (Broad)', 'help4-builder-suite'); ?></option>
                    <option value="exact" <?php selected($searchMode, 'exact'); ?>><?php esc_html_e('Exact Match', 'help4-builder-suite'); ?></option>
                </select>
                <select name="status">
                    <option value=""><?php esc_html_e('All statuses', 'help4-builder-suite'); ?></option>
                    <option value="active" <?php selected($status, 'active'); ?>>active</option>
                    <option value="draft" <?php selected($status, 'draft'); ?>>draft</option>
                    <option value="archived" <?php selected($status, 'archived'); ?>>archived</option>
                </select>
                <select name="sort">
                    <option value="updated_desc" <?php selected($sort, 'updated_desc'); ?>><?php esc_html_e('Newest Updated', 'help4-builder-suite'); ?></option>
                    <option value="updated_asc" <?php selected($sort, 'updated_asc'); ?>><?php esc_html_e('Oldest Updated', 'help4-builder-suite'); ?></option>
                    <option value="price_asc" <?php selected($sort, 'price_asc'); ?>><?php esc_html_e('Price Low to High', 'help4-builder-suite'); ?></option>
                    <option value="price_desc" <?php selected($sort, 'price_desc'); ?>><?php esc_html_e('Price High to Low', 'help4-builder-suite'); ?></option>
                    <option value="name_asc" <?php selected($sort, 'name_asc'); ?>><?php esc_html_e('Name A-Z', 'help4-builder-suite'); ?></option>
                    <option value="name_desc" <?php selected($sort, 'name_desc'); ?>><?php esc_html_e('Name Z-A', 'help4-builder-suite'); ?></option>
                    <option value="sku_asc" <?php selected($sort, 'sku_asc'); ?>><?php esc_html_e('SKU A-Z', 'help4-builder-suite'); ?></option>
                    <option value="sku_desc" <?php selected($sort, 'sku_desc'); ?>><?php esc_html_e('SKU Z-A', 'help4-builder-suite'); ?></option>
                </select>
                <input class="small-text" type="number" step="0.01" min="0" name="min_price" value="<?php echo esc_attr($minPrice > 0 ? (string) $minPrice : ''); ?>" placeholder="<?php esc_attr_e('Min $', 'help4-builder-suite'); ?>">
                <input class="small-text" type="number" step="0.01" min="0" name="max_price" value="<?php echo esc_attr($maxPrice > 0 ? (string) $maxPrice : ''); ?>" placeholder="<?php esc_attr_e('Max $', 'help4-builder-suite'); ?>">
                <label><input type="checkbox" name="in_stock_only" value="1" <?php checked($inStockOnly); ?>> <?php esc_html_e('In stock only', 'help4-builder-suite'); ?></label>
                <?php submit_button(__('Filter', 'help4-builder-suite'), 'secondary', '', false); ?>
            </form>

            <table class="widefat striped">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th><?php esc_html_e('Name', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('SKU', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Price', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Inventory', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Type', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Fulfillment', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Customer Account', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (! $products) : ?>
                        <tr><td colspan="10"><?php esc_html_e('No products found.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ($products as $product) : ?>
                        <?php $productId = (int) ($product['id'] ?? 0); ?>
                        <?php $rowPayload = $this->decodeJsonArray((string) ($product['payload'] ?? '')); ?>
                        <?php $rowType = $this->sanitizeProductType((string) ($rowPayload['type'] ?? 'simple')); ?>
                        <?php $rowFulfillment = $this->sanitizeFulfillmentType((string) ($rowPayload['fulfillment_type'] ?? $this->defaultFulfillmentForProductType($rowType))); ?>
                        <?php $rowCustomerAccount = is_array($rowPayload['customer_account'] ?? null) ? $rowPayload['customer_account'] : []; ?>
                        <?php $rowRoleAfter = $this->sanitizeCustomerRole((string) ($rowCustomerAccount['role_after_purchase'] ?? ''), false); ?>
                        <?php $rowStatusAfter = $this->sanitizeCustomerStatus((string) ($rowCustomerAccount['status_after_purchase'] ?? ''), false); ?>
                        <?php $rowInventory = $this->normalizeInventoryConfig($rowPayload); ?>
                        <?php $rowInventorySnapshot = $this->publicInventorySnapshot($rowInventory, (int) ($product['stock_qty'] ?? 0)); ?>
                        <?php $rowPricing = $this->normalizePricingConfig($rowPayload); ?>
                        <tr>
                            <td><?php echo esc_html((string) $productId); ?></td>
                            <td><?php echo esc_html((string) ($product['name'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($product['sku'] ?? '')); ?></td>
                            <td>
                                <?php echo esc_html((string) ($product['currency'] ?? 'USD')); ?> <?php echo esc_html(number_format((float) ($product['price'] ?? 0), 2)); ?>
                                <?php if ((float) $rowPricing['compare_at_price'] > (float) ($product['price'] ?? 0)) : ?>
                                    <br><small><?php esc_html_e('Compare at', 'help4-builder-suite'); ?> <?php echo esc_html(number_format((float) $rowPricing['compare_at_price'], 2)); ?></small>
                                <?php endif; ?>
                            </td>
                            <td>
                                <strong><?php echo esc_html((string) $rowInventorySnapshot['label']); ?></strong><br>
                                <?php if (! empty($rowInventory['track_stock'])) : ?>
                                    <small>
                                        <?php
                                        printf(
                                            /* translators: 1: available stock quantity, 2: inventory policy. */
                                            esc_html__('%1$d units, %2$s', 'help4-builder-suite'),
                                            (int) ($product['stock_qty'] ?? 0),
                                            esc_html(str_replace('_', ' ', (string) $rowInventory['policy']))
                                        );
                                        ?>
                                    </small>
                                <?php else : ?>
                                    <small><?php esc_html_e('Stock tracking off', 'help4-builder-suite'); ?></small>
                                <?php endif; ?>
                            </td>
                            <td><?php echo esc_html(ucfirst($rowType)); ?></td>
                            <td><?php echo esc_html(ucfirst($rowFulfillment)); ?></td>
                            <td>
                                <?php if ($rowRoleAfter === '' && $rowStatusAfter === '') : ?>
                                    <em><?php esc_html_e('Default', 'help4-builder-suite'); ?></em>
                                <?php else : ?>
                                    <?php echo esc_html($rowRoleAfter !== '' ? $rowRoleAfter : __('default role', 'help4-builder-suite')); ?>
                                    /
                                    <?php echo esc_html($rowStatusAfter !== '' ? $rowStatusAfter : __('default status', 'help4-builder-suite')); ?>
                                <?php endif; ?>
                            </td>
                            <td><?php echo esc_html((string) ($product['status'] ?? '')); ?></td>
                            <td>
                                <a href="<?php echo esc_url(add_query_arg(['page' => 'h4bs-commerce-products', 'action' => 'edit', 'edit' => $productId], admin_url('admin.php'))); ?>"><?php esc_html_e('Edit', 'help4-builder-suite'); ?></a>
                                |
                                <a href="<?php echo esc_url($this->deleteProductUrl($productId)); ?>" onclick="return confirm('<?php echo esc_js(__('Delete this product?', 'help4-builder-suite')); ?>');"><?php esc_html_e('Delete', 'help4-builder-suite'); ?></a>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php endif; ?>
        </div>
        <?php
    }

    public function renderPromotionsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $action = AdminRequest::queryKey('action');
        $promotionId = AdminRequest::queryAbsInt('edit');
        $editing = $action === 'edit' && $promotionId > 0 ? $this->promotions->get($promotionId) : null;
        $showForm = $action === 'new' || is_array($editing);
        $payload = is_array($editing) ? $this->decodeJsonArray((string) ($editing['payload'] ?? '')) : [];
        $productIds = implode(',', array_map('strval', $this->sanitizeProductIdList($payload['product_ids'] ?? [])));
        $excludedProductIds = implode(',', array_map('strval', $this->sanitizeProductIdList($payload['excluded_product_ids'] ?? [])));
        $promotions = $this->promotions->list(500);
        $notice = AdminRequest::queryKey('h4bs_notice');
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Promotions', 'help4-builder-suite'); ?></h1>
            <p class="h4bs-admin-lead"><?php esc_html_e('Create scheduled percentage, cart, product, or free-shipping offers with global and per-customer limits. Checkout reserves each redemption under a database lock so limited offers cannot be oversold.', 'help4-builder-suite'); ?></p>
            <?php if (in_array($notice, ['promotion_created', 'promotion_updated', 'promotion_deleted'], true)) : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Promotion changes saved.', 'help4-builder-suite'); ?></p></div>
            <?php elseif (str_starts_with($notice, 'h4bs_promotion_') || $notice === 'invalid_promotion') : ?>
                <div class="notice notice-error is-dismissible"><p><?php esc_html_e('The promotion could not be saved. Codes must be unique and all limits must be valid.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>
            <?php if (! $showForm) : ?>
                <p><a class="button button-primary" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-promotions&action=new')); ?>"><?php esc_html_e('Add Promotion', 'help4-builder-suite'); ?></a></p>
                <table class="widefat striped">
                    <thead><tr>
                        <th><?php esc_html_e('Code', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Offer', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Minimum', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Usage', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Schedule', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th>
                    </tr></thead>
                    <tbody>
                    <?php if (! $promotions) : ?>
                        <tr><td colspan="7"><?php esc_html_e('No promotions yet.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ($promotions as $promotion) : ?>
                        <?php $rowId = absint($promotion['id'] ?? 0); ?>
                        <tr>
                            <td><strong><code><?php echo esc_html((string) ($promotion['code'] ?? '')); ?></code></strong><br><small><?php echo esc_html((string) ($promotion['name'] ?? '')); ?></small></td>
                            <td><?php echo esc_html(ucwords(str_replace('_', ' ', (string) ($promotion['discount_type'] ?? '')))); ?>: <?php echo esc_html(number_format_i18n((float) ($promotion['amount'] ?? 0), 2)); ?></td>
                            <td><?php echo esc_html(number_format_i18n((float) ($promotion['minimum_subtotal'] ?? 0), 2)); ?></td>
                            <td><?php echo esc_html((string) ((int) ($promotion['usage_count'] ?? 0))); ?> / <?php echo (int) ($promotion['usage_limit'] ?? 0) > 0 ? esc_html((string) ((int) $promotion['usage_limit'])) : esc_html__('Unlimited', 'help4-builder-suite'); ?></td>
                            <td><small><?php echo esc_html((string) ($promotion['starts_at'] ?? __('Now', 'help4-builder-suite'))); ?><br><?php echo esc_html((string) ($promotion['ends_at'] ?? __('No end', 'help4-builder-suite'))); ?></small></td>
                            <td><?php echo esc_html((string) ($promotion['status'] ?? '')); ?></td>
                            <td>
                                <a href="<?php echo esc_url(add_query_arg(['page' => 'h4bs-commerce-promotions', 'action' => 'edit', 'edit' => $rowId], admin_url('admin.php'))); ?>"><?php esc_html_e('Edit', 'help4-builder-suite'); ?></a>
                                |
                                <a href="<?php echo esc_url(wp_nonce_url(add_query_arg(['page' => 'h4bs-commerce-promotions', 'h4bs_action' => 'delete_promotion', 'promotion_id' => $rowId], admin_url('admin.php')), 'h4bs_delete_promotion_' . $rowId)); ?>" onclick="return confirm('<?php echo esc_js(__('Delete or disable this promotion?', 'help4-builder-suite')); ?>');"><?php esc_html_e('Delete', 'help4-builder-suite'); ?></a>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            <?php else : ?>
                <p><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-promotions')); ?>"><?php esc_html_e('Back to Promotions', 'help4-builder-suite'); ?></a></p>
                <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:1rem;max-width:1080px;">
                    <?php wp_nonce_field('h4bs_save_promotion', 'h4bs_save_promotion_nonce'); ?>
                    <input type="hidden" name="h4bs_commerce_action" value="save_promotion">
                    <input type="hidden" name="promotion_id" value="<?php echo esc_attr((string) $promotionId); ?>">
                    <table class="form-table" role="presentation"><tbody>
                        <tr><th><label for="h4bs_promotion_code"><?php esc_html_e('Code', 'help4-builder-suite'); ?></label></th><td><input id="h4bs_promotion_code" class="regular-text code" required maxlength="80" name="code" value="<?php echo esc_attr((string) ($editing['code'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="h4bs_promotion_name"><?php esc_html_e('Customer Label', 'help4-builder-suite'); ?></label></th><td><input id="h4bs_promotion_name" class="regular-text" name="name" value="<?php echo esc_attr((string) ($editing['name'] ?? '')); ?>"></td></tr>
                        <tr><th><?php esc_html_e('Status and Type', 'help4-builder-suite'); ?></th><td>
                            <select name="status"><option value="active" <?php selected((string) ($editing['status'] ?? 'active'), 'active'); ?>><?php esc_html_e('Active', 'help4-builder-suite'); ?></option><option value="disabled" <?php selected((string) ($editing['status'] ?? ''), 'disabled'); ?>><?php esc_html_e('Disabled', 'help4-builder-suite'); ?></option></select>
                            <select name="discount_type">
                                <option value="percent" <?php selected((string) ($editing['discount_type'] ?? 'percent'), 'percent'); ?>><?php esc_html_e('Percent', 'help4-builder-suite'); ?></option>
                                <option value="fixed_cart" <?php selected((string) ($editing['discount_type'] ?? ''), 'fixed_cart'); ?>><?php esc_html_e('Fixed Cart', 'help4-builder-suite'); ?></option>
                                <option value="fixed_product" <?php selected((string) ($editing['discount_type'] ?? ''), 'fixed_product'); ?>><?php esc_html_e('Fixed Per Eligible Item', 'help4-builder-suite'); ?></option>
                                <option value="free_shipping" <?php selected((string) ($editing['discount_type'] ?? ''), 'free_shipping'); ?>><?php esc_html_e('Free Shipping', 'help4-builder-suite'); ?></option>
                            </select>
                        </td></tr>
                        <tr><th><?php esc_html_e('Value', 'help4-builder-suite'); ?></th><td>
                            <label><?php esc_html_e('Amount', 'help4-builder-suite'); ?> <input class="small-text" type="number" step="0.01" min="0" name="amount" value="<?php echo esc_attr((string) ($editing['amount'] ?? 0)); ?>"></label>
                            <label><?php esc_html_e('Currency', 'help4-builder-suite'); ?> <input class="small-text" maxlength="8" name="currency" value="<?php echo esc_attr((string) ($editing['currency'] ?? '')); ?>" placeholder="Any"></label>
                            <label><?php esc_html_e('Maximum Discount', 'help4-builder-suite'); ?> <input class="small-text" type="number" step="0.01" min="0" name="maximum_discount" value="<?php echo esc_attr((string) ($editing['maximum_discount'] ?? 0)); ?>"></label>
                        </td></tr>
                        <tr><th><?php esc_html_e('Eligibility', 'help4-builder-suite'); ?></th><td>
                            <label><?php esc_html_e('Minimum Subtotal', 'help4-builder-suite'); ?> <input class="small-text" type="number" step="0.01" min="0" name="minimum_subtotal" value="<?php echo esc_attr((string) ($editing['minimum_subtotal'] ?? 0)); ?>"></label><br><br>
                            <label><?php esc_html_e('Only Product IDs', 'help4-builder-suite'); ?> <input class="regular-text" name="product_ids" value="<?php echo esc_attr($productIds); ?>" placeholder="12, 18"></label><br><br>
                            <label><?php esc_html_e('Excluded Product IDs', 'help4-builder-suite'); ?> <input class="regular-text" name="excluded_product_ids" value="<?php echo esc_attr($excludedProductIds); ?>" placeholder="24, 25"></label>
                        </td></tr>
                        <tr><th><?php esc_html_e('Usage Limits', 'help4-builder-suite'); ?></th><td>
                            <label><?php esc_html_e('Global', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" name="usage_limit" value="<?php echo esc_attr((string) ($editing['usage_limit'] ?? 0)); ?>"></label>
                            <label><?php esc_html_e('Per Customer', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" name="per_customer_limit" value="<?php echo esc_attr((string) ($editing['per_customer_limit'] ?? 0)); ?>"></label>
                            <p class="description"><?php esc_html_e('Zero means unlimited. Customer limits use a one-way email hash in the redemption ledger.', 'help4-builder-suite'); ?></p>
                        </td></tr>
                        <tr><th><?php esc_html_e('Schedule', 'help4-builder-suite'); ?></th><td>
                            <label><?php esc_html_e('Starts', 'help4-builder-suite'); ?> <input type="datetime-local" name="starts_at" value="<?php echo esc_attr(str_replace(' ', 'T', substr((string) ($editing['starts_at'] ?? ''), 0, 16))); ?>"></label>
                            <label><?php esc_html_e('Ends', 'help4-builder-suite'); ?> <input type="datetime-local" name="ends_at" value="<?php echo esc_attr(str_replace(' ', 'T', substr((string) ($editing['ends_at'] ?? ''), 0, 16))); ?>"></label>
                        </td></tr>
                    </tbody></table>
                    <?php submit_button(is_array($editing) ? __('Update Promotion', 'help4-builder-suite') : __('Create Promotion', 'help4-builder-suite')); ?>
                </form>
            <?php endif; ?>
        </div>
        <?php
    }

    public function renderTaxSettingsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }
        $settings = $this->settings();
        $rates = is_array($settings['commerce_tax_rates'] ?? null) ? $settings['commerce_tax_rates'] : [];
        for ($blankRate = 0; $blankRate < 5; $blankRate++) {
            $rates[] = [
                'id' => 'rate_' . (count($rates) + 1),
                'label' => '',
                'country' => '',
                'state' => '',
                'postcode_prefix' => '',
                'tax_class' => 'standard',
                'rate' => 0,
                'priority' => 100 + count($rates),
                'enabled' => false,
                'compound' => false,
                'shipping' => false,
            ];
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Tax Settings', 'help4-builder-suite'); ?></h1>
            <p class="h4bs-admin-lead"><?php esc_html_e('Configure destination-based rates by country, state, postal prefix, and product tax class. Priorities determine order; compound rates calculate on top of earlier tax lines.', 'help4-builder-suite'); ?></p>
            <?php if (AdminRequest::queryKey('h4bs_notice') === 'taxes_saved') : ?><div class="notice notice-success is-dismissible"><p><?php esc_html_e('Tax rates saved.', 'help4-builder-suite'); ?></p></div><?php endif; ?>
            <form method="post">
                <?php wp_nonce_field('h4bs_save_tax_settings', 'h4bs_save_tax_settings_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="save_tax_settings">
                <table class="widefat striped">
                    <thead><tr>
                        <th><?php esc_html_e('On', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Label', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Country', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('State', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Postal Prefix', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Class', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Rate %', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Priority', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Compound', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Shipping', 'help4-builder-suite'); ?></th>
                    </tr></thead>
                    <tbody>
                    <?php foreach ($rates as $index => $rate) : ?>
                        <?php if (! is_array($rate)) { continue; } ?>
                        <tr>
                            <td><input type="checkbox" name="tax_rate_enabled[<?php echo esc_attr((string) $index); ?>]" value="1" <?php checked(! empty($rate['enabled'])); ?>><input type="hidden" name="tax_rate_id[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['id'] ?? 'rate_' . ($index + 1))); ?>"></td>
                            <td><input type="text" name="tax_rate_label[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['label'] ?? '')); ?>"></td>
                            <td><input class="small-text" maxlength="2" name="tax_rate_country[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['country'] ?? '')); ?>" placeholder="US"></td>
                            <td><input class="small-text" name="tax_rate_state[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['state'] ?? '')); ?>" placeholder="MI"></td>
                            <td><input class="small-text" name="tax_rate_postcode[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['postcode_prefix'] ?? '')); ?>"></td>
                            <td><input class="small-text" name="tax_rate_class[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['tax_class'] ?? 'standard')); ?>"></td>
                            <td><input class="small-text" type="number" min="0" max="100" step="0.0001" name="tax_rate_percent[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['rate'] ?? 0)); ?>"></td>
                            <td><input class="small-text" type="number" min="1" name="tax_rate_priority[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rate['priority'] ?? 100)); ?>"></td>
                            <td><input type="checkbox" name="tax_rate_compound[<?php echo esc_attr((string) $index); ?>]" value="1" <?php checked(! empty($rate['compound'])); ?>></td>
                            <td><input type="checkbox" name="tax_rate_shipping[<?php echo esc_attr((string) $index); ?>]" value="1" <?php checked(! empty($rate['shipping'])); ?>></td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
                <p class="description"><?php esc_html_e('Blank country, state, or postal fields are wildcards. Add more rows by saving the current set; five blank rows are always retained.', 'help4-builder-suite'); ?></p>
                <?php submit_button(__('Save Tax Rates', 'help4-builder-suite')); ?>
            </form>
        </div>
        <?php
    }

    public function renderOrdersPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        global $wpdb;
        $ordersTable = $wpdb->prefix . 'h4_orders';
        $itemsTable = $wpdb->prefix . 'h4_order_items';
        $status = AdminRequest::queryKey('status');
        $viewOrder = AdminRequest::queryAbsInt('view');

        $orders = $wpdb->get_results(
            $wpdb->prepare(
                'SELECT * FROM %i WHERE (%s = "" OR status = %s) ORDER BY created_at DESC LIMIT 300',
                $ordersTable,
                $status,
                $status
            ),
            ARRAY_A
        );
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Orders', 'help4-builder-suite'); ?></h1>
            <?php $orderNotice = AdminRequest::queryKey('h4bs_notice'); ?>
            <?php if ($orderNotice === 'order_status_updated') : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Order status and inventory state updated.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ($orderNotice === 'order_inventory_unavailable') : ?>
                <div class="notice notice-error is-dismissible"><p><?php esc_html_e('The order status was not changed because its inventory could not be reserved again.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ($orderNotice === 'order_promotion_unavailable') : ?>
                <div class="notice notice-error is-dismissible"><p><?php esc_html_e('The order status was not changed because its promotion could not be reserved again.', 'help4-builder-suite'); ?></p></div>
            <?php elseif (in_array($orderNotice, ['invalid_order', 'invalid_order_status', 'order_update_failed'], true)) : ?>
                <div class="notice notice-error is-dismissible"><p><?php esc_html_e('The order could not be updated. Review the order and try again.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>
            <?php $ordersImportResult = get_transient('h4bs_orders_import_result'); ?>
            <?php if (is_array($ordersImportResult)) : ?>
                <?php delete_transient('h4bs_orders_import_result'); ?>
                <div class="notice notice-success is-dismissible">
                    <p>
                        <?php
                        echo esc_html(
                            sprintf(
                                /* translators: 1: imported count, 2: skipped count, 3: error count */
                                __('Order import finished. Imported: %1$d, Skipped: %2$d, Errors: %3$d', 'help4-builder-suite'),
                                (int) ($ordersImportResult['imported'] ?? 0),
                                (int) ($ordersImportResult['skipped'] ?? 0),
                                (int) ($ordersImportResult['errors'] ?? 0)
                            )
                        );
                        ?>
                    </p>
                </div>
            <?php endif; ?>
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:1200px;margin-bottom:12px;">
                <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:12px;border-radius:8px;">
                    <?php wp_nonce_field('h4bs_export_orders', 'h4bs_export_orders_nonce'); ?>
                    <input type="hidden" name="h4bs_commerce_action" value="export_orders">
                    <h2 style="margin-top:0;"><?php esc_html_e('Export Orders', 'help4-builder-suite'); ?></h2>
                    <p class="description"><?php esc_html_e('Downloads CSV for migration/backup of invoice history.', 'help4-builder-suite'); ?></p>
                    <?php submit_button(__('Export Orders CSV', 'help4-builder-suite'), 'secondary', 'submit', false); ?>
                </form>
                <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:12px;border-radius:8px;">
                    <?php wp_nonce_field('h4bs_import_orders', 'h4bs_import_orders_nonce'); ?>
                    <input type="hidden" name="h4bs_commerce_action" value="import_orders">
                    <h2 style="margin-top:0;"><?php esc_html_e('Import Orders', 'help4-builder-suite'); ?></h2>
                    <p class="description"><?php esc_html_e('Paste JSON array with order records and optional `items` arrays.', 'help4-builder-suite'); ?></p>
                    <textarea class="large-text code" name="orders_import_json" rows="5" placeholder='[{"order_number":"MIG-1001","customer_email":"client@example.com","total":99.99,"currency":"USD","status":"paid","gateway":"paypal_router","items":[{"product_id":1,"qty":1,"unit_price":99.99}]}]'></textarea>
                    <?php submit_button(__('Import Orders', 'help4-builder-suite'), 'primary', 'submit', false); ?>
                </form>
            </div>
            <form method="get">
                <input type="hidden" name="page" value="h4bs-commerce-orders">
                <select name="status">
                    <option value=""><?php esc_html_e('All statuses', 'help4-builder-suite'); ?></option>
                    <option value="pending" <?php selected($status, 'pending'); ?>>pending</option>
                    <option value="processing" <?php selected($status, 'processing'); ?>>processing</option>
                    <option value="paid" <?php selected($status, 'paid'); ?>>paid</option>
                    <option value="failed" <?php selected($status, 'failed'); ?>>failed</option>
                    <option value="payment_failed" <?php selected($status, 'payment_failed'); ?>>payment_failed</option>
                    <option value="cancelled" <?php selected($status, 'cancelled'); ?>>cancelled</option>
                    <option value="refunded" <?php selected($status, 'refunded'); ?>>refunded</option>
                </select>
                <?php submit_button(__('Filter', 'help4-builder-suite'), 'secondary', '', false); ?>
            </form>

            <table class="widefat striped">
                <thead><tr><th>ID</th><th><?php esc_html_e('Order #', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Email', 'help4-builder-suite'); ?></th><th><?php esc_html_e('WP User', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Total', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Gateway', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Promotion', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Inventory', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Created', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th></tr></thead>
                <tbody>
                    <?php if (! is_array($orders) || ! $orders) : ?>
                        <tr><td colspan="11"><?php esc_html_e('No orders found.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ((array) $orders as $order) : ?>
                        <?php $orderId = (int) ($order['id'] ?? 0); ?>
                        <?php $orderUserId = absint($order['customer_user_id'] ?? 0); ?>
                        <?php $orderPayload = $this->decodeJsonArray((string) ($order['payload'] ?? '')); ?>
                        <?php $orderInventory = is_array($orderPayload['_h4bs_inventory'] ?? null) ? $orderPayload['_h4bs_inventory'] : []; ?>
                        <?php $orderPromotion = is_array($orderPayload['_h4bs_promotion'] ?? null) ? $orderPayload['_h4bs_promotion'] : []; ?>
                        <tr>
                            <td><?php echo esc_html((string) $orderId); ?></td>
                            <td><?php echo esc_html((string) ($order['order_number'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($order['customer_email'] ?? '')); ?></td>
                            <td>
                                <?php if ($orderUserId > 0) : ?>
                                    <a href="<?php echo esc_url(admin_url('user-edit.php?user_id=' . $orderUserId)); ?>">
                                        <?php echo esc_html('#' . (string) $orderUserId); ?>
                                    </a>
                                <?php else : ?>
                                    <em><?php esc_html_e('none', 'help4-builder-suite'); ?></em>
                                <?php endif; ?>
                            </td>
                            <td><?php echo esc_html((string) ($order['currency'] ?? 'USD')); ?> <?php echo esc_html(number_format((float) ($order['total'] ?? 0), 2)); ?></td>
                            <td><?php echo esc_html((string) ($order['status'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($order['gateway'] ?? '')); ?></td>
                            <td>
                                <?php if ((string) ($orderPromotion['code'] ?? '') !== '') : ?>
                                    <code><?php echo esc_html((string) $orderPromotion['code']); ?></code><br>
                                    <small><?php echo esc_html(ucwords(str_replace('_', ' ', (string) ($orderPromotion['state'] ?? 'unknown')))); ?></small>
                                <?php else : ?>
                                    <em><?php esc_html_e('none', 'help4-builder-suite'); ?></em>
                                <?php endif; ?>
                            </td>
                            <td><?php echo esc_html(ucwords(str_replace('_', ' ', (string) ($orderInventory['state'] ?? 'legacy')))); ?></td>
                            <td><?php echo esc_html((string) ($order['created_at'] ?? '')); ?></td>
                            <td><a href="<?php echo esc_url(add_query_arg(['page' => 'h4bs-commerce-orders', 'view' => $orderId], admin_url('admin.php'))); ?>"><?php esc_html_e('View Items', 'help4-builder-suite'); ?></a></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <?php if ($viewOrder > 0) : ?>
                <?php
                $viewOrderRow = $wpdb->get_row(
                    $wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $ordersTable, $viewOrder),
                    ARRAY_A
                );
                $items = $wpdb->get_results(
                    $wpdb->prepare(
                        'SELECT * FROM %i WHERE order_id = %d ORDER BY id ASC',
                        $itemsTable,
                        $viewOrder
                    ),
                    ARRAY_A
                );
                ?>
                <?php if (is_array($viewOrderRow)) : ?>
                    <?php $viewOrderPayload = $this->decodeJsonArray((string) ($viewOrderRow['payload'] ?? '')); ?>
                    <?php $viewOrderInventory = is_array($viewOrderPayload['_h4bs_inventory'] ?? null) ? $viewOrderPayload['_h4bs_inventory'] : []; ?>
                    <?php $viewOrderPricing = is_array($viewOrderPayload['_h4bs_pricing'] ?? null) ? $viewOrderPayload['_h4bs_pricing'] : []; ?>
                    <?php $viewOrderTax = is_array($viewOrderPricing['tax'] ?? null) ? $viewOrderPricing['tax'] : []; ?>
                    <?php $viewOrderPromotion = is_array($viewOrderPayload['_h4bs_promotion'] ?? null) ? $viewOrderPayload['_h4bs_promotion'] : []; ?>
                    <?php $viewOrderCustomerFields = is_array($viewOrderPayload['_h4bs_customer_fields'] ?? null) ? $viewOrderPayload['_h4bs_customer_fields'] : []; ?>
                    <h2><?php esc_html_e('Order Lifecycle', 'help4-builder-suite'); ?></h2>
                    <form method="post" class="postbox" style="padding:1rem;max-width:900px;">
                        <?php wp_nonce_field('h4bs_update_order_status_' . $viewOrder, 'h4bs_update_order_status_nonce'); ?>
                        <input type="hidden" name="h4bs_commerce_action" value="update_order_status">
                        <input type="hidden" name="order_id" value="<?php echo esc_attr((string) $viewOrder); ?>">
                        <label for="h4bs_order_status"><strong><?php esc_html_e('Status', 'help4-builder-suite'); ?></strong></label>
                        <select id="h4bs_order_status" name="order_status">
                            <?php foreach (['pending', 'processing', 'paid', 'payment_failed', 'cancelled', 'refunded'] as $orderStatusOption) : ?>
                                <option value="<?php echo esc_attr($orderStatusOption); ?>" <?php selected((string) ($viewOrderRow['status'] ?? ''), $orderStatusOption); ?>><?php echo esc_html(ucwords(str_replace('_', ' ', $orderStatusOption))); ?></option>
                            <?php endforeach; ?>
                        </select>
                        &nbsp;
                        <strong><?php esc_html_e('Inventory:', 'help4-builder-suite'); ?></strong>
                        <?php echo esc_html(ucwords(str_replace('_', ' ', (string) ($viewOrderInventory['state'] ?? 'legacy')))); ?>
                        &nbsp;
                        <strong><?php esc_html_e('Promotion:', 'help4-builder-suite'); ?></strong>
                        <?php echo esc_html((string) ($viewOrderPromotion['code'] ?? __('None', 'help4-builder-suite'))); ?>
                        <?php if ((string) ($viewOrderPromotion['code'] ?? '') !== '') : ?>
                            (<?php echo esc_html(ucwords(str_replace('_', ' ', (string) ($viewOrderPromotion['state'] ?? 'unknown')))); ?>)
                        <?php endif; ?>
                        <?php submit_button(__('Update Order', 'help4-builder-suite'), 'secondary', 'submit', false); ?>
                        <p class="description"><?php esc_html_e('Cancelling, refunding, or marking payment failed restores reserved stock and promotion usage once. Returning a released order to pending, processing, or paid must reserve both again before the status can change.', 'help4-builder-suite'); ?></p>
                    </form>
                    <?php if ($viewOrderPricing) : ?>
                        <h2><?php esc_html_e('Pricing Breakdown', 'help4-builder-suite'); ?></h2>
                        <table class="widefat striped" style="max-width:600px;margin-bottom:1rem;">
                            <tbody>
                                <tr><th><?php esc_html_e('Subtotal', 'help4-builder-suite'); ?></th><td><?php echo esc_html(number_format_i18n((float) ($viewOrderPricing['subtotal'] ?? 0), 2)); ?></td></tr>
                                <tr><th><?php esc_html_e('Product discount', 'help4-builder-suite'); ?></th><td><?php echo esc_html(number_format_i18n((float) ($viewOrderPricing['product_discount'] ?? 0), 2)); ?></td></tr>
                                <tr><th><?php esc_html_e('Shipping', 'help4-builder-suite'); ?></th><td><?php echo esc_html(number_format_i18n((float) ($viewOrderPricing['net_shipping'] ?? 0), 2)); ?></td></tr>
                                <tr><th><?php esc_html_e('Shipping discount', 'help4-builder-suite'); ?></th><td><?php echo esc_html(number_format_i18n((float) ($viewOrderPricing['shipping_discount'] ?? 0), 2)); ?></td></tr>
                                <tr><th><?php esc_html_e('Tax', 'help4-builder-suite'); ?></th><td><?php echo esc_html(number_format_i18n((float) ($viewOrderTax['total'] ?? 0), 2)); ?></td></tr>
                                <tr><th><?php esc_html_e('Total', 'help4-builder-suite'); ?></th><td><strong><?php echo esc_html((string) ($viewOrderPricing['currency'] ?? ($viewOrderRow['currency'] ?? 'USD'))); ?> <?php echo esc_html(number_format_i18n((float) ($viewOrderPricing['total'] ?? $viewOrderRow['total'] ?? 0), 2)); ?></strong></td></tr>
                            </tbody>
                        </table>
                    <?php endif; ?>
                    <?php if ($viewOrderCustomerFields) : ?>
                        <h2><?php esc_html_e('Customer Details Snapshot', 'help4-builder-suite'); ?></h2>
                        <table class="widefat striped" style="max-width:760px;margin-bottom:1rem;">
                            <tbody>
                                <?php foreach ($viewOrderCustomerFields as $fieldSnapshot) : ?>
                                    <?php if (! is_array($fieldSnapshot)) { continue; } ?>
                                    <?php $fieldSnapshotValue = $fieldSnapshot['value'] ?? ''; ?>
                                    <tr><th><?php echo esc_html((string) ($fieldSnapshot['label'] ?? $fieldSnapshot['key'] ?? '')); ?></th><td><?php echo esc_html(is_array($fieldSnapshotValue) ? implode(', ', array_map('strval', $fieldSnapshotValue)) : (string) $fieldSnapshotValue); ?></td></tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    <?php endif; ?>
                <?php endif; ?>
                <h2><?php /* translators: %d: order ID. */ printf(esc_html__('Order Items: #%d', 'help4-builder-suite'), absint($viewOrder)); ?></h2>
                <table class="widefat striped">
                    <thead><tr><th>ID</th><th><?php esc_html_e('Product ID', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Qty', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Unit Price', 'help4-builder-suite'); ?></th></tr></thead>
                    <tbody>
                        <?php if (! is_array($items) || ! $items) : ?>
                            <tr><td colspan="4"><?php esc_html_e('No items found.', 'help4-builder-suite'); ?></td></tr>
                        <?php endif; ?>
                        <?php foreach ((array) $items as $item) : ?>
                            <tr>
                                <td><?php echo esc_html((string) ($item['id'] ?? '')); ?></td>
                                <td><?php echo esc_html((string) ($item['product_id'] ?? '')); ?></td>
                                <td><?php echo esc_html((string) ($item['qty'] ?? '')); ?></td>
                                <td><?php echo esc_html(number_format((float) ($item['unit_price'] ?? 0), 2)); ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            <?php endif; ?>
        </div>
        <?php
    }

    public function renderCustomerFieldsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $editingId = AdminRequest::queryAbsInt('field_id');
        $editing = $editingId > 0 ? $this->customerFields->getDefinition($editingId) : null;
        $settings = is_array($editing['settings'] ?? null) ? $editing['settings'] : [];
        $validation = is_array($settings['validation'] ?? null) ? $settings['validation'] : [];
        $calculation = is_array($settings['calculation'] ?? null) ? $settings['calculation'] : [];
        $conditions = is_array($settings['conditions'] ?? null) ? array_values(array_filter($settings['conditions'], 'is_array')) : [];
        if (! $conditions && is_array($settings['condition'] ?? null) && ! empty($settings['condition']['field'])) {
            $conditions = [$settings['condition']];
        }
        $conditionRows = array_pad(array_slice($conditions, 0, 5), 5, ['field' => '', 'operator' => 'equals', 'value' => '']);
        $visibility = is_array($settings['visibility'] ?? null) ? $settings['visibility'] : [];
        $definitions = $this->customerFields->listDefinitions('', [], true);
        $notice = get_transient('h4bs_customer_field_notice');
        if (is_array($notice)) {
            delete_transient('h4bs_customer_field_notice');
        }
        $options = is_array($settings['options'] ?? null) ? $settings['options'] : [];
        $optionsText = implode(
            "\n",
            array_map(
                static fn(string $key, string $label): string => $key . '|' . $label,
                array_map('strval', array_keys($options)),
                array_map('strval', array_values($options))
            )
        );
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Customer Fields', 'help4-builder-suite'); ?></h1>
            <p class="h4bs-admin-lead"><?php esc_html_e('Create typed customer details once, then use the same definition in checkout, customer accounts, support administration, order records, exports, and privacy tools.', 'help4-builder-suite'); ?></p>
            <?php if (is_array($notice)) : ?>
                <div class="notice notice-<?php echo esc_attr((string) ($notice['type'] ?? 'info')); ?> is-dismissible"><p><?php echo esc_html((string) ($notice['message'] ?? '')); ?></p></div>
            <?php endif; ?>

            <h2><?php esc_html_e('Quick-start field packs', 'help4-builder-suite'); ?></h2>
            <p><?php esc_html_e('Install a complete editable starting set. Existing field keys are never overwritten.', 'help4-builder-suite'); ?></p>
            <div class="h4bs-admin-card-grid h4bs-customer-field-packs">
                <?php foreach (CustomerFieldRepository::presetLabels() as $presetKey => $presetLabel) : ?>
                    <form method="post" class="h4bs-admin-card">
                        <?php wp_nonce_field('h4bs_install_customer_field_preset', 'h4bs_customer_field_preset_nonce'); ?>
                        <input type="hidden" name="h4bs_commerce_action" value="install_customer_field_preset">
                        <input type="hidden" name="preset" value="<?php echo esc_attr($presetKey); ?>">
                        <h3><?php echo esc_html($presetLabel); ?></h3>
                        <p><?php echo esc_html($this->customerFieldPresetDescription($presetKey)); ?></p>
                        <p class="h4bs-admin-card-action"><button class="button button-secondary" type="submit"><?php esc_html_e('Install Pack', 'help4-builder-suite'); ?></button></p>
                    </form>
                <?php endforeach; ?>
            </div>

            <form method="post">
                <?php wp_nonce_field('h4bs_save_customer_field', 'h4bs_save_customer_field_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="save_customer_field">
                <input type="hidden" name="id" value="<?php echo esc_attr((string) ($editing['id'] ?? 0)); ?>">
                <h2><?php echo esc_html($editing ? __('Edit customer field', 'help4-builder-suite') : __('Add customer field', 'help4-builder-suite')); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr>
                            <th><label for="h4bs_customer_field_key"><?php esc_html_e('Key', 'help4-builder-suite'); ?></label></th>
                            <td><input id="h4bs_customer_field_key" class="regular-text code" name="field_key" required pattern="[a-z0-9_]+" value="<?php echo esc_attr((string) ($editing['field_key'] ?? '')); ?>" placeholder="company_size"><p class="description"><?php esc_html_e('Stable lowercase key used by checkout, imports, exports, and integrations.', 'help4-builder-suite'); ?></p></td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_customer_field_label"><?php esc_html_e('Label', 'help4-builder-suite'); ?></label></th>
                            <td><input id="h4bs_customer_field_label" class="regular-text" name="label" required maxlength="190" value="<?php echo esc_attr((string) ($editing['label'] ?? '')); ?>"></td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_customer_field_type"><?php esc_html_e('Type', 'help4-builder-suite'); ?></label></th>
                            <td><select id="h4bs_customer_field_type" name="field_type">
                                <?php foreach (CustomerFieldRepository::typeLabels() as $typeKey => $typeLabel) : ?>
                                    <option value="<?php echo esc_attr($typeKey); ?>" <?php selected((string) ($editing['field_type'] ?? 'text'), $typeKey); ?>><?php echo esc_html($typeLabel); ?></option>
                                <?php endforeach; ?>
                            </select></td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_customer_field_group"><?php esc_html_e('Group', 'help4-builder-suite'); ?></label></th>
                            <td><input id="h4bs_customer_field_group" class="regular-text" name="field_group" value="<?php echo esc_attr((string) ($editing['field_group'] ?? __('Customer details', 'help4-builder-suite'))); ?>"></td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Placements', 'help4-builder-suite'); ?></th>
                            <td>
                                <?php $editingPlacements = is_array($editing['placements'] ?? null) ? $editing['placements'] : ['admin']; ?>
                                <label><input type="checkbox" name="placements[]" value="checkout" <?php checked(in_array('checkout', $editingPlacements, true)); ?>> <?php esc_html_e('Checkout', 'help4-builder-suite'); ?></label>&nbsp;&nbsp;
                                <label><input type="checkbox" name="placements[]" value="account" <?php checked(in_array('account', $editingPlacements, true)); ?>> <?php esc_html_e('Customer account', 'help4-builder-suite'); ?></label>&nbsp;&nbsp;
                                <label><input type="checkbox" name="placements[]" value="admin" <?php checked(in_array('admin', $editingPlacements, true)); ?>> <?php esc_html_e('Customer administration', 'help4-builder-suite'); ?></label>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_customer_field_options"><?php esc_html_e('Choices', 'help4-builder-suite'); ?></label></th>
                            <td><textarea id="h4bs_customer_field_options" class="large-text code" rows="5" name="options" placeholder="small|1 to 10 people&#10;medium|11 to 50 people"><?php echo esc_textarea($optionsText); ?></textarea><p class="description"><?php esc_html_e('One value and label per line, separated with |. Used by select, radio, and multiple-choice fields.', 'help4-builder-suite'); ?></p></td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Help and default', 'help4-builder-suite'); ?></th>
                            <td>
                                <input class="regular-text" name="placeholder" value="<?php echo esc_attr((string) ($settings['placeholder'] ?? '')); ?>" placeholder="<?php esc_attr_e('Placeholder', 'help4-builder-suite'); ?>">
                                <input class="regular-text" name="default_value" value="<?php echo esc_attr(is_scalar($settings['default_value'] ?? '') ? (string) $settings['default_value'] : ''); ?>" placeholder="<?php esc_attr_e('Default value', 'help4-builder-suite'); ?>">
                                <textarea class="large-text" rows="3" name="instructions" placeholder="<?php esc_attr_e('Instructions shown below the field', 'help4-builder-suite'); ?>"><?php echo esc_textarea((string) ($settings['instructions'] ?? '')); ?></textarea>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Calculation', 'help4-builder-suite'); ?></th>
                            <td>
                                <input class="large-text code" name="calculation_formula" maxlength="500" value="<?php echo esc_attr((string) ($calculation['formula'] ?? '')); ?>" placeholder="round({quantity} * {unit_rate}, 2)">
                                <p>
                                    <label><?php esc_html_e('Decimal places', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" max="6" name="calculation_precision" value="<?php echo esc_attr((string) ($calculation['precision'] ?? 2)); ?>"></label>&nbsp;&nbsp;
                                    <label><?php esc_html_e('Display prefix', 'help4-builder-suite'); ?> <input class="small-text" maxlength="32" name="calculation_prefix" value="<?php echo esc_attr((string) ($calculation['prefix'] ?? '')); ?>" placeholder="$"></label>&nbsp;&nbsp;
                                    <label><?php esc_html_e('Display suffix', 'help4-builder-suite'); ?> <input class="small-text" maxlength="32" name="calculation_suffix" value="<?php echo esc_attr((string) ($calculation['suffix'] ?? '')); ?>" placeholder="hours"></label>
                                </p>
                                <p class="description"><?php esc_html_e('Used only by Calculated value fields. Reference numeric field keys with braces. Allowed operators: +, -, *, / and parentheses. Allowed functions: abs, ceil, floor, round, min, and max. The server ignores submitted calculated values and recomputes the result.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Browser and layout', 'help4-builder-suite'); ?></th>
                            <td>
                                <label><?php esc_html_e('Autocomplete', 'help4-builder-suite'); ?> <input class="regular-text code" name="autocomplete" value="<?php echo esc_attr((string) ($settings['autocomplete'] ?? '')); ?>" placeholder="organization, tel, bday"></label>
                                <label><?php esc_html_e('Control width', 'help4-builder-suite'); ?> <select name="input_width">
                                    <?php foreach (['auto' => __('Automatic', 'help4-builder-suite'), 'full' => __('Full width', 'help4-builder-suite'), 'half' => __('Half width', 'help4-builder-suite'), 'third' => __('One third', 'help4-builder-suite'), 'two_thirds' => __('Two thirds', 'help4-builder-suite')] as $widthKey => $widthLabel) : ?>
                                        <option value="<?php echo esc_attr($widthKey); ?>" <?php selected((string) ($settings['input_width'] ?? 'auto'), $widthKey); ?>><?php echo esc_html($widthLabel); ?></option>
                                    <?php endforeach; ?>
                                </select></label>
                                <p class="description"><?php esc_html_e('Autocomplete uses standard browser tokens. Widths collapse safely to one column on small screens.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Validation', 'help4-builder-suite'); ?></th>
                            <td>
                                <label><?php esc_html_e('Minimum length', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" max="10000" name="min_length" value="<?php echo esc_attr((string) ($validation['min_length'] ?? 0)); ?>"></label>&nbsp;&nbsp;
                                <label><?php esc_html_e('Maximum length', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" max="10000" name="max_length" value="<?php echo esc_attr((string) ($validation['max_length'] ?? 0)); ?>"></label>&nbsp;&nbsp;
                                <label><?php esc_html_e('Minimum number', 'help4-builder-suite'); ?> <input class="small-text" type="number" step="any" name="min" value="<?php echo esc_attr(isset($validation['min']) ? (string) $validation['min'] : ''); ?>"></label>&nbsp;&nbsp;
                                <label><?php esc_html_e('Maximum number', 'help4-builder-suite'); ?> <input class="small-text" type="number" step="any" name="max" value="<?php echo esc_attr(isset($validation['max']) ? (string) $validation['max'] : ''); ?>"></label><br><br>
                                <label><?php esc_html_e('Number step', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" step="any" name="step" value="<?php echo esc_attr(isset($validation['step']) ? (string) $validation['step'] : ''); ?>"></label>&nbsp;&nbsp;
                                <label><?php esc_html_e('Minimum selections', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" max="50" name="min_selections" value="<?php echo esc_attr((string) ($validation['min_selections'] ?? 0)); ?>"></label>&nbsp;&nbsp;
                                <label><?php esc_html_e('Maximum selections', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" max="50" name="max_selections" value="<?php echo esc_attr((string) ($validation['max_selections'] ?? 0)); ?>"></label><br><br>
                                <label><?php esc_html_e('Earliest date/time', 'help4-builder-suite'); ?> <input type="text" name="date_min" value="<?php echo esc_attr((string) ($validation['date_min'] ?? '')); ?>" placeholder="2026-08-01 or 2026-08-01T09:00"></label>&nbsp;&nbsp;
                                <label><?php esc_html_e('Latest date/time', 'help4-builder-suite'); ?> <input type="text" name="date_max" value="<?php echo esc_attr((string) ($validation['date_max'] ?? '')); ?>" placeholder="2027-12-31 or 2027-12-31T17:00"></label>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Conditional rules', 'help4-builder-suite'); ?></th>
                            <td>
                                <label><?php esc_html_e('Show when', 'help4-builder-suite'); ?> <select name="condition_match"><option value="all" <?php selected((string) ($settings['condition_match'] ?? 'all'), 'all'); ?>><?php esc_html_e('All rules match', 'help4-builder-suite'); ?></option><option value="any" <?php selected((string) ($settings['condition_match'] ?? 'all'), 'any'); ?>><?php esc_html_e('Any rule matches', 'help4-builder-suite'); ?></option></select></label>
                                <div class="h4bs-customer-condition-rows">
                                    <?php foreach ($conditionRows as $condition) : ?>
                                        <div class="h4bs-customer-condition-row">
                                            <input class="regular-text code" name="condition_field[]" value="<?php echo esc_attr((string) ($condition['field'] ?? '')); ?>" placeholder="other_field_key">
                                            <select name="condition_operator[]">
                                                <?php foreach (CustomerFieldRepository::conditionOperatorLabels() as $operator => $operatorLabel) : ?>
                                                    <option value="<?php echo esc_attr($operator); ?>" <?php selected((string) ($condition['operator'] ?? 'equals'), $operator); ?>><?php echo esc_html($operatorLabel); ?></option>
                                                <?php endforeach; ?>
                                            </select>
                                            <input class="regular-text" name="condition_value[]" value="<?php echo esc_attr(is_scalar($condition['value'] ?? '') ? (string) $condition['value'] : ''); ?>" placeholder="<?php esc_attr_e('Expected value', 'help4-builder-suite'); ?>">
                                        </div>
                                    <?php endforeach; ?>
                                </div>
                                <p class="description"><?php esc_html_e('Use comma-separated values with “matches one of.” To compare against another live field, enter its stable key in braces, such as {credit_limit}. Blank rule rows are ignored.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Audience targeting', 'help4-builder-suite'); ?></th>
                            <td>
                                <input class="regular-text code" name="product_ids" value="<?php echo esc_attr(implode(',', array_map('strval', (array) ($visibility['product_ids'] ?? [])))); ?>" placeholder="12,45,87">
                                <input class="regular-text code" name="product_types" value="<?php echo esc_attr(implode(',', array_map('strval', (array) ($visibility['product_types'] ?? [])))); ?>" placeholder="service,subscription,booking">
                                <input class="regular-text code" name="customer_roles" value="<?php echo esc_attr(implode(',', array_map('strval', (array) ($visibility['customer_roles'] ?? [])))); ?>" placeholder="subscriber,member,vip"><br><br>
                                <input class="regular-text code" name="countries" value="<?php echo esc_attr(implode(',', array_map('strval', (array) ($visibility['countries'] ?? [])))); ?>" placeholder="US,CA,GB">
                                <label><?php esc_html_e('Minimum subtotal', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" step="0.01" name="min_subtotal" value="<?php echo esc_attr(isset($visibility['min_subtotal']) ? (string) $visibility['min_subtotal'] : ''); ?>"></label>
                                <label><?php esc_html_e('Maximum subtotal', 'help4-builder-suite'); ?> <input class="small-text" type="number" min="0" step="0.01" name="max_subtotal" value="<?php echo esc_attr(isset($visibility['max_subtotal']) ? (string) $visibility['max_subtotal'] : ''); ?>"></label>
                                <p class="description"><?php esc_html_e('Leave every target blank for all customers and products. Server validation enforces these rules during checkout.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_customer_field_sensitivity"><?php esc_html_e('Privacy', 'help4-builder-suite'); ?></label></th>
                            <td><select id="h4bs_customer_field_sensitivity" name="sensitivity">
                                <option value="standard" <?php selected((string) ($editing['sensitivity'] ?? 'personal'), 'standard'); ?>><?php esc_html_e('Standard operational data', 'help4-builder-suite'); ?></option>
                                <option value="personal" <?php selected((string) ($editing['sensitivity'] ?? 'personal'), 'personal'); ?>><?php esc_html_e('Personal data', 'help4-builder-suite'); ?></option>
                                <option value="sensitive" <?php selected((string) ($editing['sensitivity'] ?? 'personal'), 'sensitive'); ?>><?php esc_html_e('Sensitive, encrypted and excluded from search/order snapshots', 'help4-builder-suite'); ?></option>
                            </select></td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Behavior', 'help4-builder-suite'); ?></th>
                            <td>
                                <label><input type="checkbox" name="required" value="1" <?php checked(! empty($editing['required'])); ?>> <?php esc_html_e('Required when visible', 'help4-builder-suite'); ?></label><br>
                                <label><input type="checkbox" name="customer_editable" value="1" <?php checked(! $editing || ! empty($editing['customer_editable'])); ?>> <?php esc_html_e('Customer can update it', 'help4-builder-suite'); ?></label><br>
                                <label><input type="checkbox" name="show_in_orders" value="1" <?php checked(! $editing || ! empty($editing['show_in_orders'])); ?>> <?php esc_html_e('Include a non-sensitive snapshot on orders', 'help4-builder-suite'); ?></label><br>
                                <label><input type="checkbox" name="exportable" value="1" <?php checked(! $editing || ! empty($editing['exportable'])); ?>> <?php esc_html_e('Include in customer migration exports', 'help4-builder-suite'); ?></label>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Ordering and status', 'help4-builder-suite'); ?></th>
                            <td><input class="small-text" type="number" name="sort_order" value="<?php echo esc_attr((string) ($editing['sort_order'] ?? 10)); ?>"> <select name="status"><option value="active" <?php selected((string) ($editing['status'] ?? 'active'), 'active'); ?>><?php esc_html_e('Active', 'help4-builder-suite'); ?></option><option value="inactive" <?php selected((string) ($editing['status'] ?? ''), 'inactive'); ?>><?php esc_html_e('Inactive', 'help4-builder-suite'); ?></option><option value="archived" <?php selected((string) ($editing['status'] ?? ''), 'archived'); ?>><?php esc_html_e('Archived', 'help4-builder-suite'); ?></option></select></td>
                        </tr>
                    </tbody>
                </table>
                <?php submit_button($editing ? __('Update Customer Field', 'help4-builder-suite') : __('Add Customer Field', 'help4-builder-suite')); ?>
                <?php if ($editing) : ?><a class="button button-secondary" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-customer-fields')); ?>"><?php esc_html_e('Cancel edit', 'help4-builder-suite'); ?></a><?php endif; ?>
            </form>

            <h2><?php esc_html_e('Field catalog', 'help4-builder-suite'); ?></h2>
            <table class="widefat striped">
                <thead><tr><th><?php esc_html_e('Order', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Field', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Type', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Placements', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Privacy', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th></tr></thead>
                <tbody>
                    <?php if (! $definitions) : ?><tr><td colspan="7"><?php esc_html_e('No customer fields have been created yet.', 'help4-builder-suite'); ?></td></tr><?php endif; ?>
                    <?php foreach ($definitions as $definition) : ?>
                        <tr>
                            <td><?php echo esc_html((string) ($definition['sort_order'] ?? 10)); ?></td>
                            <td><strong><?php echo esc_html((string) ($definition['label'] ?? '')); ?></strong><br><code><?php echo esc_html((string) ($definition['field_key'] ?? '')); ?></code><br><small><?php echo esc_html((string) ($definition['field_group'] ?? '')); ?></small></td>
                            <td><?php echo esc_html((string) (CustomerFieldRepository::typeLabels()[(string) ($definition['field_type'] ?? '')] ?? $definition['field_type'] ?? '')); ?></td>
                            <td><?php echo esc_html(implode(', ', array_map('strval', (array) ($definition['placements'] ?? [])))); ?></td>
                            <td><?php echo esc_html((string) ($definition['sensitivity'] ?? 'personal')); ?></td>
                            <td><?php echo esc_html((string) ($definition['status'] ?? 'active')); ?></td>
                            <td><a href="<?php echo esc_url(add_query_arg(['page' => 'h4bs-commerce-customer-fields', 'field_id' => absint($definition['id'] ?? 0)], admin_url('admin.php'))); ?>"><?php esc_html_e('Edit', 'help4-builder-suite'); ?></a> | <a href="<?php echo esc_url($this->duplicateCustomerFieldUrl(absint($definition['id'] ?? 0))); ?>"><?php esc_html_e('Duplicate', 'help4-builder-suite'); ?></a> | <a href="<?php echo esc_url($this->archiveCustomerFieldUrl(absint($definition['id'] ?? 0))); ?>"><?php esc_html_e('Archive', 'help4-builder-suite'); ?></a></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <details>
                <summary><?php esc_html_e('Move field catalogs between sites', 'help4-builder-suite'); ?></summary>
                <div class="h4bs-admin-card-grid h4bs-customer-field-transfer">
                    <form method="post" class="h4bs-admin-card">
                        <?php wp_nonce_field('h4bs_export_customer_fields', 'h4bs_customer_field_export_nonce'); ?>
                        <input type="hidden" name="h4bs_commerce_action" value="export_customer_fields">
                        <h3><?php esc_html_e('Export JSON', 'help4-builder-suite'); ?></h3>
                        <p><?php esc_html_e('Download the complete field catalog without customer values.', 'help4-builder-suite'); ?></p>
                        <p class="h4bs-admin-card-action"><button class="button button-secondary" type="submit"><?php esc_html_e('Download Catalog', 'help4-builder-suite'); ?></button></p>
                    </form>
                    <form method="post" class="h4bs-admin-card">
                        <?php wp_nonce_field('h4bs_import_customer_fields', 'h4bs_customer_field_import_nonce'); ?>
                        <input type="hidden" name="h4bs_commerce_action" value="import_customer_fields">
                        <h3><?php esc_html_e('Import JSON', 'help4-builder-suite'); ?></h3>
                        <textarea class="h4bs-admin-code-textarea" name="customer_fields_json" required placeholder='{"schema":"h4bs/customer-fields","version":1,"fields":[]}'></textarea>
                        <p><label><?php esc_html_e('Existing keys', 'help4-builder-suite'); ?> <select name="conflict_mode"><option value="skip"><?php esc_html_e('Keep existing fields', 'help4-builder-suite'); ?></option><option value="update"><?php esc_html_e('Update from import', 'help4-builder-suite'); ?></option></select></label></p>
                        <p class="h4bs-admin-card-action"><button class="button button-primary" type="submit"><?php esc_html_e('Import Catalog', 'help4-builder-suite'); ?></button></p>
                    </form>
                </div>
            </details>
        </div>
        <?php
    }

    public function renderCustomersPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        global $wpdb;
        $customersTable = $wpdb->prefix . 'h4_customers';
        $fieldValuesTable = $wpdb->prefix . 'h4_customer_field_values';
        $search = AdminRequest::queryText('s');
        $role = $this->sanitizeCustomerRole(AdminRequest::queryText('role'), false);
        $status = $this->sanitizeCustomerStatus(AdminRequest::queryText('customer_status'), false);
        $availableRoles = $this->availableCustomerRoles();
        $viewCustomerId = AdminRequest::queryAbsInt('customer_id');
        $viewCustomer = $viewCustomerId > 0
            ? $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $customersTable, $viewCustomerId), ARRAY_A)
            : null;
        $customerFieldDefinitions = is_array($viewCustomer) ? $this->customerFields->listDefinitions('admin') : [];
        $customerFieldValues = is_array($viewCustomer) ? $this->customerFields->getValues($viewCustomerId) : [];
        $customerFieldStoredKeys = is_array($viewCustomer) ? $this->customerFields->getStoredKeys($viewCustomerId) : [];

        $like = '%' . $wpdb->esc_like($search) . '%';
        $customers = $wpdb->get_results(
            $wpdb->prepare(
                'SELECT c.* FROM %i c WHERE (%s = "" OR c.email LIKE %s OR c.first_name LIKE %s OR c.last_name LIKE %s OR EXISTS (SELECT 1 FROM %i fv WHERE fv.customer_id = c.id AND fv.search_value LIKE %s)) AND (%s = "" OR c.role = %s) AND (%s = "" OR c.status = %s) ORDER BY c.updated_at DESC LIMIT 500',
                $customersTable,
                $search,
                $like,
                $like,
                $like,
                $fieldValuesTable,
                $like,
                $role,
                $role,
                $status,
                $status
            ),
            ARRAY_A
        );
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Customers', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Commerce customers are stored as WordPress users and mirrored in Help4 customer records for migration/reporting.', 'help4-builder-suite'); ?></p>
            <?php $customersImportResult = get_transient('h4bs_customers_import_result'); ?>
            <?php if (is_array($customersImportResult)) : ?>
                <?php delete_transient('h4bs_customers_import_result'); ?>
                <div class="notice notice-success is-dismissible">
                    <p>
                        <?php
                        echo esc_html(
                            sprintf(
                                /* translators: 1: imported count, 2: error count */
                                __('Customer import finished. Imported: %1$d, Errors: %2$d', 'help4-builder-suite'),
                                (int) ($customersImportResult['imported'] ?? 0),
                                (int) ($customersImportResult['errors'] ?? 0)
                            )
                        );
                        ?>
                    </p>
                </div>
            <?php endif; ?>
            <?php $fieldValueNotice = get_transient('h4bs_customer_field_value_notice'); ?>
            <?php if (is_array($fieldValueNotice)) : ?>
                <?php delete_transient('h4bs_customer_field_value_notice'); ?>
                <div class="notice notice-<?php echo esc_attr((string) ($fieldValueNotice['type'] ?? 'info')); ?> is-dismissible"><p><?php echo esc_html((string) ($fieldValueNotice['message'] ?? '')); ?></p></div>
            <?php endif; ?>

            <?php if (is_array($viewCustomer)) : ?>
                <h2><?php echo esc_html(sprintf(
                    /* translators: %s: customer email address. */
                    __('Customer details: %s', 'help4-builder-suite'),
                    (string) ($viewCustomer['email'] ?? '')
                )); ?></h2>
                <form method="post" class="h4bs-customer-fields-editor">
                    <?php wp_nonce_field('h4bs_save_customer_field_values_' . $viewCustomerId, 'h4bs_save_customer_field_values_nonce'); ?>
                    <input type="hidden" name="h4bs_commerce_action" value="save_customer_field_values">
                    <input type="hidden" name="customer_id" value="<?php echo esc_attr((string) $viewCustomerId); ?>">
                    <?php if (! $customerFieldDefinitions) : ?>
                        <p><?php esc_html_e('No active customer fields are assigned to administration.', 'help4-builder-suite'); ?> <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-customer-fields')); ?>"><?php esc_html_e('Create customer fields', 'help4-builder-suite'); ?></a></p>
                    <?php endif; ?>
                    <?php foreach ($customerFieldDefinitions as $definition) : ?>
                        <?php $fieldKey = (string) ($definition['field_key'] ?? ''); ?>
                        <?php $this->renderCustomerFieldControl($definition, $customerFieldValues[$fieldKey] ?? '', false, in_array($fieldKey, $customerFieldStoredKeys, true)); ?>
                    <?php endforeach; ?>
                    <?php if ($customerFieldDefinitions) : ?><?php submit_button(__('Save Customer Details', 'help4-builder-suite')); ?><?php endif; ?>
                    <a class="button button-secondary" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-customers')); ?>"><?php esc_html_e('Close customer', 'help4-builder-suite'); ?></a>
                </form>
            <?php endif; ?>

            <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:1200px;margin-bottom:12px;">
                <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:12px;border-radius:8px;">
                    <?php wp_nonce_field('h4bs_export_customers', 'h4bs_export_customers_nonce'); ?>
                    <input type="hidden" name="h4bs_commerce_action" value="export_customers">
                    <h2 style="margin-top:0;"><?php esc_html_e('Export Customers', 'help4-builder-suite'); ?></h2>
                    <p class="description"><?php esc_html_e('Downloads CSV including role, status, order totals, and exportable non-sensitive customer fields for migration.', 'help4-builder-suite'); ?></p>
                    <?php submit_button(__('Export Customers CSV', 'help4-builder-suite'), 'secondary', 'submit', false); ?>
                </form>
                <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:12px;border-radius:8px;">
                    <?php wp_nonce_field('h4bs_import_customers', 'h4bs_import_customers_nonce'); ?>
                    <input type="hidden" name="h4bs_commerce_action" value="import_customers">
                    <h2 style="margin-top:0;"><?php esc_html_e('Import Customers', 'help4-builder-suite'); ?></h2>
                    <p class="description"><?php esc_html_e('Paste JSON with email, optional identity fields, and a customer_fields object keyed by the field catalog.', 'help4-builder-suite'); ?></p>
                    <textarea class="large-text code" name="customers_import_json" rows="5" placeholder='[{"email":"client@example.com","first_name":"Taylor","customer_fields":{"company_size":"small"}}]'></textarea>
                    <?php submit_button(__('Import Customers', 'help4-builder-suite'), 'primary', 'submit', false); ?>
                </form>
            </div>

            <form method="get">
                <input type="hidden" name="page" value="h4bs-commerce-customers">
                <input type="search" name="s" value="<?php echo esc_attr($search); ?>" placeholder="<?php esc_attr_e('Search email or name', 'help4-builder-suite'); ?>">
                <select name="role">
                    <option value=""><?php esc_html_e('All roles', 'help4-builder-suite'); ?></option>
                    <?php foreach ($availableRoles as $roleKey => $roleLabel) : ?>
                        <option value="<?php echo esc_attr($roleKey); ?>" <?php selected($role, $roleKey); ?>>
                            <?php echo esc_html($roleLabel); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
                <select name="customer_status">
                    <option value=""><?php esc_html_e('All statuses', 'help4-builder-suite'); ?></option>
                    <option value="active" <?php selected($status, 'active'); ?>><?php esc_html_e('Active', 'help4-builder-suite'); ?></option>
                    <option value="vip" <?php selected($status, 'vip'); ?>><?php esc_html_e('VIP', 'help4-builder-suite'); ?></option>
                    <option value="suspended" <?php selected($status, 'suspended'); ?>><?php esc_html_e('Suspended', 'help4-builder-suite'); ?></option>
                    <option value="blocked" <?php selected($status, 'blocked'); ?>><?php esc_html_e('Blocked', 'help4-builder-suite'); ?></option>
                </select>
                <?php submit_button(__('Filter', 'help4-builder-suite'), 'secondary', '', false); ?>
            </form>

            <table class="widefat striped">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th><?php esc_html_e('Email', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Name', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('WP User', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Role', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Orders', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Total Spent', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Updated', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (! is_array($customers) || ! $customers) : ?>
                        <tr><td colspan="10"><?php esc_html_e('No customers found.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ((array) $customers as $customer) : ?>
                        <?php $userId = absint($customer['wp_user_id'] ?? 0); ?>
                        <tr>
                            <td><?php echo esc_html((string) ($customer['id'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($customer['email'] ?? '')); ?></td>
                            <td><?php echo esc_html(trim((string) (($customer['first_name'] ?? '') . ' ' . ($customer['last_name'] ?? '')))); ?></td>
                            <td>
                                <?php if ($userId > 0) : ?>
                                    <a href="<?php echo esc_url(admin_url('user-edit.php?user_id=' . $userId)); ?>">
                                        <?php echo esc_html('#' . (string) $userId); ?>
                                    </a>
                                <?php else : ?>
                                    <em><?php esc_html_e('none', 'help4-builder-suite'); ?></em>
                                <?php endif; ?>
                            </td>
                            <td><?php echo esc_html((string) ($customer['role'] ?? 'customer')); ?></td>
                            <td><?php echo esc_html((string) ($customer['status'] ?? 'active')); ?></td>
                            <td><?php echo esc_html((string) ($customer['order_count'] ?? 0)); ?></td>
                            <td><?php echo esc_html(number_format((float) ($customer['total_spent'] ?? 0), 2)); ?></td>
                            <td><?php echo esc_html((string) ($customer['updated_at'] ?? '')); ?></td>
                            <td><a href="<?php echo esc_url(add_query_arg(['page' => 'h4bs-commerce-customers', 'customer_id' => absint($customer['id'] ?? 0)], admin_url('admin.php'))); ?>"><?php esc_html_e('View and edit', 'help4-builder-suite'); ?></a></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <?php
    }

    public function renderSubscriptionsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        global $wpdb;
        $table = $wpdb->prefix . 'h4_subscriptions';
        $items = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i ORDER BY updated_at DESC LIMIT 300', $table), ARRAY_A);
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Subscriptions', 'help4-builder-suite'); ?></h1>
            <p class="h4bs-admin-lead"><?php esc_html_e('Subscriptions are created by checkout when a subscription product is purchased. Each record keeps the customer, recurring amount, interval, status, gateway, and router reference together so support can see what is actually billing.', 'help4-builder-suite'); ?></p>
            <div class="h4bs-admin-card-grid">
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Product terms drive billing', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Set interval, trial days, setup fee, and auto-renew on the product before sending customers to checkout.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Gateway status stays visible', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Router results can mark a subscription active, trialing, paused, cancelled, expired, or payment failed.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('Support can reconcile quickly', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Use customer email, product ID, gateway, and updated date to match client questions back to the checkout event.', 'help4-builder-suite'); ?></p>
                </div>
            </div>
            <table class="widefat striped">
                <thead><tr><th>ID</th><th><?php esc_html_e('Email', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Product', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Amount', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Interval', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Gateway', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Updated', 'help4-builder-suite'); ?></th></tr></thead>
                <tbody>
                    <?php if (! is_array($items) || ! $items) : ?>
                        <tr><td colspan="8"><?php esc_html_e('No subscriptions found.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ((array) $items as $item) : ?>
                        <tr>
                            <td><?php echo esc_html((string) ($item['id'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($item['customer_email'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($item['product_id'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($item['currency'] ?? 'USD')); ?> <?php echo esc_html(number_format((float) ($item['amount'] ?? 0), 2)); ?></td>
                            <td><?php echo esc_html((string) ($item['interval_code'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($item['status'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($item['gateway'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($item['updated_at'] ?? '')); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <?php
    }

    public function renderGatewaySettingsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $settings = $this->settings();
        $gateways = (new GatewayRegistry($settings))->all();
        $gatewayCatalog = array_values(
            array_filter(
                array_map(
                    static fn(array $item): string => sanitize_key((string) ($item['slug'] ?? '')),
                    $gateways
                ),
                static fn(string $slug): bool => $slug !== ''
            )
        );
        $routeConfig = $this->gatewayRouteConfig($settings, $gatewayCatalog);
        $routeRows = $routeConfig['routes'];
        $ruleRows = $routeConfig['rules'];
        $gatewayOptionsHtml = '';
        foreach ($gateways as $gateway) {
            if (! is_array($gateway)) {
                continue;
            }
            $slug = sanitize_key((string) ($gateway['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            $label = sanitize_text_field((string) ($gateway['label'] ?? $slug));
            $gatewayOptionsHtml .= '<option value="' . esc_attr($slug) . '">' . esc_html($label) . '</option>';
        }
        $conditionTypeOptionsHtml = '';
        foreach (
            [
                'always' => __('Always', 'help4-builder-suite'),
                'product_id' => __('Product ID', 'help4-builder-suite'),
                'product_type' => __('Product Type', 'help4-builder-suite'),
                'fulfillment_type' => __('Fulfillment Type', 'help4-builder-suite'),
                'contains_digital' => __('Contains Digital', 'help4-builder-suite'),
                'contains_shippable' => __('Contains Shippable', 'help4-builder-suite'),
                'contains_booking' => __('Contains Booking', 'help4-builder-suite'),
                'contains_service' => __('Contains Service', 'help4-builder-suite'),
                'contains_subscription' => __('Contains Subscription', 'help4-builder-suite'),
                'currency' => __('Currency', 'help4-builder-suite'),
                'country' => __('Country', 'help4-builder-suite'),
                'state' => __('State', 'help4-builder-suite'),
                'zip' => __('ZIP', 'help4-builder-suite'),
                'order_total' => __('Order Total', 'help4-builder-suite'),
            ] as $value => $label
        ) {
            $conditionTypeOptionsHtml .= '<option value="' . esc_attr($value) . '">' . esc_html($label) . '</option>';
        }
        $availableRoles = $this->availableCustomerRoles();
        $defaultCustomerRole = $this->sanitizeCustomerRole((string) ($settings['commerce_default_customer_role'] ?? 'customer'));
        $defaultCustomerStatus = $this->sanitizeCustomerStatus((string) ($settings['commerce_default_customer_status'] ?? 'active'));
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Gateway Manager', 'help4-builder-suite'); ?></h1>
            <p class="h4bs-admin-lead"><?php esc_html_e('Configure processors with Help4-native code only. No third-party gateway plugins required. Routes are reusable payment connections; rules decide when a checkout should use a route.', 'help4-builder-suite'); ?></p>
            <div class="h4bs-admin-steps">
                <div class="h4bs-admin-step">
                    <div><strong><?php esc_html_e('Connect providers', 'help4-builder-suite'); ?></strong><span><?php esc_html_e('Add the account identifiers and shared secret used by the Help4 payment router endpoints.', 'help4-builder-suite'); ?></span></div>
                </div>
                <div class="h4bs-admin-step">
                    <div><strong><?php esc_html_e('Build route profiles', 'help4-builder-suite'); ?></strong><span><?php esc_html_e('Create a default route plus backup pools for Stripe, PayPal, Square, Braintree, Hosted Payments, Authorize.Net, or Wise.', 'help4-builder-suite'); ?></span></div>
                </div>
                <div class="h4bs-admin-step">
                    <div><strong><?php esc_html_e('Assign checkout rules', 'help4-builder-suite'); ?></strong><span><?php esc_html_e('Use conditions like subscription, product type, order total, currency, country, and ZIP to pick the best route.', 'help4-builder-suite'); ?></span></div>
                </div>
            </div>

            <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:1rem;border-radius:8px;max-width:980px;">
                <?php wp_nonce_field('h4bs_save_gateway_settings', 'h4bs_save_gateway_settings_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="save_gateway_settings">
                <h2><?php esc_html_e('Stripe (Basil Track)', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr><th><?php esc_html_e('Stripe Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['stripe_router_endpoint'] ?? '')); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="stripe_publishable_key"><?php esc_html_e('Stripe Publishable Key', 'help4-builder-suite'); ?></label></th><td><input id="stripe_publishable_key" class="regular-text code" type="text" name="stripe_publishable_key" value="<?php echo esc_attr((string) ($settings['stripe_publishable_key'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="stripe_secret_key"><?php esc_html_e('Stripe Secret Key', 'help4-builder-suite'); ?></label></th><td><input id="stripe_secret_key" class="regular-text code" type="text" name="stripe_secret_key" value="<?php echo esc_attr((string) ($settings['stripe_secret_key'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="stripe_webhook_secret"><?php esc_html_e('Stripe Webhook Secret', 'help4-builder-suite'); ?></label></th><td><input id="stripe_webhook_secret" class="regular-text code" type="text" name="stripe_webhook_secret" value="<?php echo esc_attr((string) ($settings['stripe_webhook_secret'] ?? '')); ?>"></td></tr>
                        <tr><th><label for="stripe_api_track"><?php esc_html_e('Stripe API Track', 'help4-builder-suite'); ?></label></th><td><select id="stripe_api_track" name="stripe_api_track"><option value="basil" <?php selected((string) ($settings['stripe_api_track'] ?? ''), 'basil'); ?>>basil</option><option value="latest" <?php selected((string) ($settings['stripe_api_track'] ?? ''), 'latest'); ?>>latest</option></select></td></tr>
                        <tr><th><label for="stripe_api_version_override"><?php esc_html_e('Stripe API Version Override', 'help4-builder-suite'); ?></label></th><td><input id="stripe_api_version_override" class="regular-text code" type="text" name="stripe_api_version_override" value="<?php echo esc_attr((string) ($settings['stripe_api_version_override'] ?? '')); ?>"><p class="description"><?php esc_html_e('Optional full API version string.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="stripe_embedded_checkout_enabled"><?php esc_html_e('Enable Stripe Embedded Checkout Builder', 'help4-builder-suite'); ?></label></th><td><input id="stripe_embedded_checkout_enabled" type="checkbox" name="stripe_embedded_checkout_enabled" value="1" <?php checked(! empty($settings['stripe_embedded_checkout_enabled'])); ?>></td></tr>
                        <tr><th><label for="commerce_customer_data_mode"><?php esc_html_e('Customer Data Storage Mode', 'help4-builder-suite'); ?></label></th><td><select id="commerce_customer_data_mode" name="commerce_customer_data_mode"><option value="stripe_only" <?php selected((string) ($settings['commerce_customer_data_mode'] ?? ''), 'stripe_only'); ?>><?php esc_html_e('Stripe only', 'help4-builder-suite'); ?></option><option value="both" <?php selected((string) ($settings['commerce_customer_data_mode'] ?? ''), 'both'); ?>><?php esc_html_e('Both Stripe and WordPress', 'help4-builder-suite'); ?></option><option value="wordpress_only" <?php selected((string) ($settings['commerce_customer_data_mode'] ?? ''), 'wordpress_only'); ?>><?php esc_html_e('WordPress only', 'help4-builder-suite'); ?></option></select></td></tr>
                        <tr>
                            <th><label for="commerce_default_customer_role"><?php esc_html_e('Default Customer Role', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="commerce_default_customer_role" name="commerce_default_customer_role">
                                    <?php foreach ($availableRoles as $roleKey => $roleLabel) : ?>
                                        <option value="<?php echo esc_attr($roleKey); ?>" <?php selected($defaultCustomerRole, $roleKey); ?>>
                                            <?php echo esc_html($roleLabel); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <p class="description"><?php esc_html_e('Used when checkout creates or updates a WordPress user account.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="commerce_default_customer_status"><?php esc_html_e('Default Customer Status', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="commerce_default_customer_status" name="commerce_default_customer_status">
                                    <option value="active" <?php selected($defaultCustomerStatus, 'active'); ?>><?php esc_html_e('Active', 'help4-builder-suite'); ?></option>
                                    <option value="vip" <?php selected($defaultCustomerStatus, 'vip'); ?>><?php esc_html_e('VIP', 'help4-builder-suite'); ?></option>
                                    <option value="suspended" <?php selected($defaultCustomerStatus, 'suspended'); ?>><?php esc_html_e('Suspended', 'help4-builder-suite'); ?></option>
                                    <option value="blocked" <?php selected($defaultCustomerStatus, 'blocked'); ?>><?php esc_html_e('Blocked', 'help4-builder-suite'); ?></option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="commerce_inventory_hold_minutes"><?php esc_html_e('Pending Inventory Hold', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <input id="commerce_inventory_hold_minutes" class="small-text" type="number" min="0" max="10080" step="1" name="commerce_inventory_hold_minutes" value="<?php echo esc_attr((string) ($settings['commerce_inventory_hold_minutes'] ?? '60')); ?>">
                                <?php esc_html_e('minutes', 'help4-builder-suite'); ?>
                                <p class="description"><?php esc_html_e('Tracked stock reserved by a pending checkout is restored after this window. Use 0 to keep pending reservations until an order is updated manually or by its payment adapter.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr><th><label for="payment_router_shared_secret"><?php esc_html_e('Payment Router Shared Secret', 'help4-builder-suite'); ?></label></th><td><input id="payment_router_shared_secret" class="regular-text code" type="text" name="payment_router_shared_secret" value="<?php echo esc_attr((string) ($settings['payment_router_shared_secret'] ?? '')); ?>"><p class="description"><?php esc_html_e('Optional HMAC signing secret sent to configured payment-router gateway endpoints.', 'help4-builder-suite'); ?></p></td></tr>
                    </tbody>
                </table>
                <h2><?php esc_html_e('Other Gateways', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr><th><?php esc_html_e('PayPal Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['paypal_router_endpoint'] ?? '')); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="paypal_client_id"><?php esc_html_e('PayPal Client ID', 'help4-builder-suite'); ?></label></th><td><input id="paypal_client_id" class="regular-text code" type="text" name="paypal_client_id" value="<?php echo esc_attr((string) ($settings['paypal_client_id'] ?? '')); ?>"></td></tr>
                        <tr><th><?php esc_html_e('Square Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['square_router_endpoint'] ?? '')); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="square_application_id"><?php esc_html_e('Square Application ID', 'help4-builder-suite'); ?></label></th><td><input id="square_application_id" class="regular-text code" type="text" name="square_application_id" value="<?php echo esc_attr((string) ($settings['square_application_id'] ?? '')); ?>"></td></tr>
                        <tr><th><?php esc_html_e('Braintree Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['braintree_router_endpoint'] ?? '')); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="braintree_merchant_id"><?php esc_html_e('Braintree Merchant ID', 'help4-builder-suite'); ?></label></th><td><input id="braintree_merchant_id" class="regular-text code" type="text" name="braintree_merchant_id" value="<?php echo esc_attr((string) ($settings['braintree_merchant_id'] ?? '')); ?>"></td></tr>
                        <tr><th><?php esc_html_e('Hosted Payments Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['hosted_payments_router_endpoint'] ?? ($settings['godaddy_router_endpoint'] ?? ''))); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="hosted_payments_profile"><?php esc_html_e('Hosted Payments Profile', 'help4-builder-suite'); ?></label></th><td><input id="hosted_payments_profile" class="regular-text code" type="text" name="hosted_payments_profile" value="<?php echo esc_attr((string) ($settings['hosted_payments_profile'] ?? ($settings['godaddy_payments_profile'] ?? ''))); ?>"></td></tr>
                        <tr><th><?php esc_html_e('Authorize.Net Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['authorize_net_router_endpoint'] ?? '')); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="authorize_net_login_id"><?php esc_html_e('Authorize.Net Login ID', 'help4-builder-suite'); ?></label></th><td><input id="authorize_net_login_id" class="regular-text code" type="text" name="authorize_net_login_id" value="<?php echo esc_attr((string) ($settings['authorize_net_login_id'] ?? '')); ?>"></td></tr>
                        <tr><th><?php esc_html_e('Wise Router Endpoint', 'help4-builder-suite'); ?></th><td><code><?php echo esc_html((string) ($settings['wise_router_endpoint'] ?? '')); ?></code><p class="description"><?php esc_html_e('Optional endpoint configured by the site administrator.', 'help4-builder-suite'); ?></p></td></tr>
                        <tr><th><label for="wise_profile_id"><?php esc_html_e('Wise Profile ID', 'help4-builder-suite'); ?></label></th><td><input id="wise_profile_id" class="regular-text code" type="text" name="wise_profile_id" value="<?php echo esc_attr((string) ($settings['wise_profile_id'] ?? '')); ?>"></td></tr>
                    </tbody>
                </table>

                <h2><?php esc_html_e('Payment Routes (Connections)', 'help4-builder-suite'); ?></h2>
                <p class="description"><?php esc_html_e('Define route profiles first (like Stripe Router Connections), then map rule conditions to each route.', 'help4-builder-suite'); ?></p>
                <table class="widefat striped" id="h4bs-route-table">
                    <thead>
                        <tr>
                            <th><?php esc_html_e('Route Name', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Route Slug', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Primary Gateway', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Gateway Pool (CSV)', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Priority', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Enabled', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Default', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Action', 'help4-builder-suite'); ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($routeRows as $index => $route) : ?>
                            <tr class="h4bs-route-row">
                                <td><input class="regular-text" type="text" name="route_name[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($route['name'] ?? '')); ?>"></td>
                                <td><input class="regular-text code" type="text" name="route_slug[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($route['slug'] ?? '')); ?>"></td>
                                <td>
                                    <select name="route_primary_gateway[<?php echo esc_attr((string) $index); ?>]">
                                        <?php foreach ($gateways as $gateway) : ?>
                                            <?php $gatewaySlug = sanitize_key((string) ($gateway['slug'] ?? '')); ?>
                                            <?php if ($gatewaySlug === '') { continue; } ?>
                                            <option value="<?php echo esc_attr($gatewaySlug); ?>" <?php selected((string) ($route['primary_gateway'] ?? ''), $gatewaySlug); ?>>
                                                <?php echo esc_html((string) ($gateway['label'] ?? $gatewaySlug)); ?>
                                            </option>
                                        <?php endforeach; ?>
                                    </select>
                                </td>
                                <td><input class="regular-text code" type="text" name="route_gateway_pool[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr(implode(',', array_map('strval', (array) ($route['gateway_pool'] ?? [])))); ?>" placeholder="stripe_router,paypal"></td>
                                <td><input class="small-text" type="number" min="1" max="9999" step="1" name="route_priority[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($route['priority'] ?? 100)); ?>"></td>
                                <td><label><input type="checkbox" name="route_enabled[<?php echo esc_attr((string) $index); ?>]" value="1" <?php checked(! empty($route['enabled'])); ?>></label></td>
                                <td><label><input type="checkbox" name="route_is_default[<?php echo esc_attr((string) $index); ?>]" value="1" <?php checked(! empty($route['is_default'])); ?>></label></td>
                                <td><button type="button" class="button h4bs-remove-route-row"><?php esc_html_e('Remove', 'help4-builder-suite'); ?></button></td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
                <p><button type="button" class="button" id="h4bs-add-route-row"><?php esc_html_e('Add Route', 'help4-builder-suite'); ?></button></p>

                <h2><?php esc_html_e('Route Usage Rules (Assignments)', 'help4-builder-suite'); ?></h2>
                <p class="description"><?php esc_html_e('Rules pick which route/gateway to use by checkout conditions. Top priority (lowest number) runs first.', 'help4-builder-suite'); ?></p>
                <table class="widefat striped" id="h4bs-rule-table">
                    <thead>
                        <tr>
                            <th><?php esc_html_e('Rule Name', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Route Slug', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Priority', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Condition Type', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Operator', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Value', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Gateway Mode', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Single Gateway', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Gateway Pool (CSV)', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Enabled', 'help4-builder-suite'); ?></th>
                            <th><?php esc_html_e('Action', 'help4-builder-suite'); ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($ruleRows as $index => $rule) : ?>
                            <tr class="h4bs-rule-row">
                                <td><input class="regular-text" type="text" name="rule_name[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rule['name'] ?? '')); ?>"></td>
                                <td><input class="regular-text code" type="text" name="rule_route_slug[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rule['route_slug'] ?? '')); ?>" placeholder="stripe_router_route"></td>
                                <td><input class="small-text" type="number" min="1" max="9999" step="1" name="rule_priority[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rule['priority'] ?? 100)); ?>"></td>
                                <td>
                                    <select name="rule_condition_type[<?php echo esc_attr((string) $index); ?>]">
                                        <?php
                                        $conditionType = sanitize_key((string) ($rule['condition_type'] ?? 'always'));
                                        $conditionTypeOptions = [
                                            'always' => __('Always', 'help4-builder-suite'),
                                            'product_id' => __('Product ID', 'help4-builder-suite'),
                                            'product_type' => __('Product Type', 'help4-builder-suite'),
                                            'fulfillment_type' => __('Fulfillment Type', 'help4-builder-suite'),
                                            'contains_digital' => __('Contains Digital', 'help4-builder-suite'),
                                            'contains_shippable' => __('Contains Shippable', 'help4-builder-suite'),
                                            'contains_booking' => __('Contains Booking', 'help4-builder-suite'),
                                            'contains_service' => __('Contains Service', 'help4-builder-suite'),
                                            'contains_subscription' => __('Contains Subscription', 'help4-builder-suite'),
                                            'currency' => __('Currency', 'help4-builder-suite'),
                                            'country' => __('Country', 'help4-builder-suite'),
                                            'state' => __('State', 'help4-builder-suite'),
                                            'zip' => __('ZIP', 'help4-builder-suite'),
                                            'order_total' => __('Order Total', 'help4-builder-suite'),
                                        ];
                                        foreach ($conditionTypeOptions as $value => $label) :
                                            ?>
                                            <option value="<?php echo esc_attr($value); ?>" <?php selected($conditionType, $value); ?>><?php echo esc_html($label); ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                </td>
                                <td>
                                    <?php $conditionOperator = sanitize_key((string) ($rule['condition_operator'] ?? 'eq')); ?>
                                    <select name="rule_condition_operator[<?php echo esc_attr((string) $index); ?>]">
                                        <option value="eq" <?php selected($conditionOperator, 'eq'); ?>>eq</option>
                                        <option value="neq" <?php selected($conditionOperator, 'neq'); ?>>neq</option>
                                        <option value="in" <?php selected($conditionOperator, 'in'); ?>>in</option>
                                        <option value="not_in" <?php selected($conditionOperator, 'not_in'); ?>>not_in</option>
                                        <option value="gte" <?php selected($conditionOperator, 'gte'); ?>>gte</option>
                                        <option value="lte" <?php selected($conditionOperator, 'lte'); ?>>lte</option>
                                        <option value="contains" <?php selected($conditionOperator, 'contains'); ?>>contains</option>
                                    </select>
                                </td>
                                <td><input class="regular-text code" type="text" name="rule_condition_value[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr((string) ($rule['condition_value'] ?? '')); ?>" placeholder="USD or 100 or service"></td>
                                <td>
                                    <?php $gatewayMode = sanitize_key((string) ($rule['gateway_mode'] ?? 'route_default')); ?>
                                    <select name="rule_gateway_mode[<?php echo esc_attr((string) $index); ?>]">
                                        <option value="route_default" <?php selected($gatewayMode, 'route_default'); ?>><?php esc_html_e('Use Route Defaults', 'help4-builder-suite'); ?></option>
                                        <option value="single" <?php selected($gatewayMode, 'single'); ?>><?php esc_html_e('Force Single Gateway', 'help4-builder-suite'); ?></option>
                                        <option value="multiple" <?php selected($gatewayMode, 'multiple'); ?>><?php esc_html_e('Allow Gateway Pool', 'help4-builder-suite'); ?></option>
                                    </select>
                                </td>
                                <td>
                                    <select name="rule_gateway_single[<?php echo esc_attr((string) $index); ?>]">
                                        <option value=""><?php esc_html_e('-- none --', 'help4-builder-suite'); ?></option>
                                        <?php foreach ($gateways as $gateway) : ?>
                                            <?php $gatewaySlug = sanitize_key((string) ($gateway['slug'] ?? '')); ?>
                                            <?php if ($gatewaySlug === '') { continue; } ?>
                                            <option value="<?php echo esc_attr($gatewaySlug); ?>" <?php selected((string) ($rule['gateway_single'] ?? ''), $gatewaySlug); ?>>
                                                <?php echo esc_html((string) ($gateway['label'] ?? $gatewaySlug)); ?>
                                            </option>
                                        <?php endforeach; ?>
                                    </select>
                                </td>
                                <td><input class="regular-text code" type="text" name="rule_gateway_pool[<?php echo esc_attr((string) $index); ?>]" value="<?php echo esc_attr(implode(',', array_map('strval', (array) ($rule['gateway_pool'] ?? [])))); ?>" placeholder="paypal,square"></td>
                                <td><label><input type="checkbox" name="rule_enabled[<?php echo esc_attr((string) $index); ?>]" value="1" <?php checked(! empty($rule['enabled'])); ?>></label></td>
                                <td><button type="button" class="button h4bs-remove-rule-row"><?php esc_html_e('Remove', 'help4-builder-suite'); ?></button></td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
                <p><button type="button" class="button" id="h4bs-add-rule-row"><?php esc_html_e('Add Rule', 'help4-builder-suite'); ?></button></p>
                <?php submit_button(__('Save Gateway Settings', 'help4-builder-suite')); ?>
            </form>

            <h2><?php esc_html_e('Current Provider Status', 'help4-builder-suite'); ?></h2>
            <table class="widefat striped">
                <thead><tr><th><?php esc_html_e('Gateway', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Configured', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Details', 'help4-builder-suite'); ?></th></tr></thead>
                <tbody>
                    <?php foreach ($gateways as $gateway) : ?>
                        <tr>
                            <td><?php echo esc_html((string) ($gateway['label'] ?? '')); ?></td>
                            <td><?php echo ! empty($gateway['configured']) ? esc_html__('Yes', 'help4-builder-suite') : esc_html__('No', 'help4-builder-suite'); ?></td>
                            <td>
                                <?php echo esc_html((string) ($gateway['description'] ?? '')); ?>
                                <?php if (! empty($gateway['processor_endpoint'])) : ?>
                                    <br><small><code><?php echo esc_html((string) ($gateway['processor_endpoint'] ?? '')); ?></code></small>
                                <?php endif; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <script>
            (function () {
                const routeBody = document.querySelector('#h4bs-route-table tbody');
                const ruleBody = document.querySelector('#h4bs-rule-table tbody');
                const addRouteButton = document.getElementById('h4bs-add-route-row');
                const addRuleButton = document.getElementById('h4bs-add-rule-row');
                if (!routeBody || !ruleBody || !addRouteButton || !addRuleButton) {
                    return;
                }

                let routeIndex = routeBody.querySelectorAll('.h4bs-route-row').length;
                let ruleIndex = ruleBody.querySelectorAll('.h4bs-rule-row').length;
                const gatewayOptions = <?php echo wp_json_encode($gatewayOptionsHtml); ?>;
                const conditionOptions = <?php echo wp_json_encode($conditionTypeOptionsHtml); ?>;
                const operatorOptions = '<option value="eq">eq</option><option value="neq">neq</option><option value="in">in</option><option value="not_in">not_in</option><option value="gte">gte</option><option value="lte">lte</option><option value="contains">contains</option>';
                const modeOptions = '<option value="route_default"><?php echo esc_js(__('Use Route Defaults', 'help4-builder-suite')); ?></option><option value="single"><?php echo esc_js(__('Force Single Gateway', 'help4-builder-suite')); ?></option><option value="multiple"><?php echo esc_js(__('Allow Gateway Pool', 'help4-builder-suite')); ?></option>';

                addRouteButton.addEventListener('click', function () {
                    const i = routeIndex++;
                    const row = document.createElement('tr');
                    row.className = 'h4bs-route-row';
                    row.innerHTML = '' +
                        '<td><input class="regular-text" type="text" name="route_name[' + i + ']" value=""></td>' +
                        '<td><input class="regular-text code" type="text" name="route_slug[' + i + ']" value=""></td>' +
                        '<td><select name="route_primary_gateway[' + i + ']">' + gatewayOptions + '</select></td>' +
                        '<td><input class="regular-text code" type="text" name="route_gateway_pool[' + i + ']" value="" placeholder="stripe_router,paypal"></td>' +
                        '<td><input class="small-text" type="number" min="1" max="9999" step="1" name="route_priority[' + i + ']" value="100"></td>' +
                        '<td><label><input type="checkbox" name="route_enabled[' + i + ']" value="1" checked></label></td>' +
                        '<td><label><input type="checkbox" name="route_is_default[' + i + ']" value="1"></label></td>' +
                        '<td><button type="button" class="button h4bs-remove-route-row"><?php echo esc_js(__('Remove', 'help4-builder-suite')); ?></button></td>';
                    routeBody.appendChild(row);
                });

                addRuleButton.addEventListener('click', function () {
                    const i = ruleIndex++;
                    const row = document.createElement('tr');
                    row.className = 'h4bs-rule-row';
                    row.innerHTML = '' +
                        '<td><input class="regular-text" type="text" name="rule_name[' + i + ']" value=""></td>' +
                        '<td><input class="regular-text code" type="text" name="rule_route_slug[' + i + ']" value="" placeholder="stripe_router_route"></td>' +
                        '<td><input class="small-text" type="number" min="1" max="9999" step="1" name="rule_priority[' + i + ']" value="100"></td>' +
                        '<td><select name="rule_condition_type[' + i + ']">' + conditionOptions + '</select></td>' +
                        '<td><select name="rule_condition_operator[' + i + ']">' + operatorOptions + '</select></td>' +
                        '<td><input class="regular-text code" type="text" name="rule_condition_value[' + i + ']" value="" placeholder="USD or 100 or service"></td>' +
                        '<td><select name="rule_gateway_mode[' + i + ']">' + modeOptions + '</select></td>' +
                        '<td><select name="rule_gateway_single[' + i + ']"><option value=""><?php echo esc_js(__('-- none --', 'help4-builder-suite')); ?></option>' + gatewayOptions + '</select></td>' +
                        '<td><input class="regular-text code" type="text" name="rule_gateway_pool[' + i + ']" value="" placeholder="paypal,square"></td>' +
                        '<td><label><input type="checkbox" name="rule_enabled[' + i + ']" value="1" checked></label></td>' +
                        '<td><button type="button" class="button h4bs-remove-rule-row"><?php echo esc_js(__('Remove', 'help4-builder-suite')); ?></button></td>';
                    ruleBody.appendChild(row);
                });

                document.addEventListener('click', function (event) {
                    const routeRemove = event.target.closest('.h4bs-remove-route-row');
                    if (routeRemove) {
                        const row = routeRemove.closest('.h4bs-route-row');
                        if (row && routeBody.querySelectorAll('.h4bs-route-row').length > 1) {
                            row.remove();
                        }
                        return;
                    }

                    const ruleRemove = event.target.closest('.h4bs-remove-rule-row');
                    if (ruleRemove) {
                        const row = ruleRemove.closest('.h4bs-rule-row');
                        if (row) {
                            row.remove();
                        }
                    }
                });
            }());
            </script>
        </div>
        <?php
    }

    public function renderShippingSettingsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $settings = $this->settings();
        $rateMode = sanitize_key((string) ($settings['shipping_rate_mode'] ?? 'preset'));
        if (! in_array($rateMode, ['api', 'preset'], true)) {
            $rateMode = 'preset';
        }
        $originCountry = strtoupper(substr(sanitize_text_field((string) ($settings['shipping_origin_country'] ?? 'US')), 0, 2));
        $originPostal = sanitize_text_field((string) ($settings['shipping_origin_postal'] ?? ''));
        $defaultService = sanitize_text_field((string) ($settings['shipping_default_service'] ?? 'standard'));
        $carriers = [
            'usps' => 'USPS',
            'ups' => 'UPS',
            'fedex' => 'FedEx',
            'dhl' => 'DHL',
        ];
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Shipping Integrations', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Configure live carrier APIs or fallback preset rates per carrier. Checkout can calculate shipping from product weight and dimensions.', 'help4-builder-suite'); ?></p>
            <?php if (AdminRequest::queryKey('h4bs_notice') === 'shipping_saved') : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Shipping settings saved.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>

            <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:1rem;border-radius:8px;max-width:1080px;">
                <?php wp_nonce_field('h4bs_save_shipping_settings', 'h4bs_save_shipping_settings_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="save_shipping_settings">
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr>
                            <th><label for="shipping_rate_mode"><?php esc_html_e('Rate Mode', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="shipping_rate_mode" name="shipping_rate_mode">
                                    <option value="preset" <?php selected($rateMode, 'preset'); ?>><?php esc_html_e('Preset (fast fallback)', 'help4-builder-suite'); ?></option>
                                    <option value="api" <?php selected($rateMode, 'api'); ?>><?php esc_html_e('Live Carrier API (with fallback)', 'help4-builder-suite'); ?></option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="shipping_origin_country"><?php esc_html_e('Origin Country', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <input id="shipping_origin_country" class="small-text" maxlength="2" type="text" name="shipping_origin_country" value="<?php echo esc_attr($originCountry); ?>">
                                <label for="shipping_origin_postal"><?php esc_html_e('Origin Postal Code', 'help4-builder-suite'); ?></label>
                                <input id="shipping_origin_postal" class="regular-text" type="text" name="shipping_origin_postal" value="<?php echo esc_attr($originPostal); ?>">
                            </td>
                        </tr>
                        <tr>
                            <th><label for="shipping_default_service"><?php esc_html_e('Default Service Label', 'help4-builder-suite'); ?></label></th>
                            <td><input id="shipping_default_service" class="regular-text" type="text" name="shipping_default_service" value="<?php echo esc_attr($defaultService); ?>"></td>
                        </tr>
                    </tbody>
                </table>

                <?php foreach ($carriers as $carrierKey => $carrierLabel) : ?>
                    <?php
                    $enabled = ! empty($settings['shipping_' . $carrierKey . '_enabled']);
                    $apiKey = (string) ($settings['shipping_' . $carrierKey . '_api_key'] ?? '');
                    $apiSecret = (string) ($settings['shipping_' . $carrierKey . '_api_secret'] ?? '');
                    $account = (string) ($settings['shipping_' . $carrierKey . '_account'] ?? '');
                    $presetDomestic = (float) ($settings['shipping_' . $carrierKey . '_preset_domestic'] ?? 0);
                    $presetInternational = (float) ($settings['shipping_' . $carrierKey . '_preset_international'] ?? 0);
                    $perWeightDomestic = (float) ($settings['shipping_' . $carrierKey . '_per_weight_domestic'] ?? 0);
                    $perWeightInternational = (float) ($settings['shipping_' . $carrierKey . '_per_weight_international'] ?? 0);
                    ?>
                    <h2><?php echo esc_html($carrierLabel); ?></h2>
                    <table class="form-table" role="presentation">
                        <tbody>
                            <tr>
                                <th><label for="shipping_<?php echo esc_attr($carrierKey); ?>_enabled"><?php esc_html_e('Enable Carrier', 'help4-builder-suite'); ?></label></th>
                                <td><input id="shipping_<?php echo esc_attr($carrierKey); ?>_enabled" type="checkbox" name="shipping_<?php echo esc_attr($carrierKey); ?>_enabled" value="1" <?php checked($enabled); ?>></td>
                            </tr>
                            <tr>
                                <th><label for="shipping_<?php echo esc_attr($carrierKey); ?>_api_key"><?php esc_html_e('API Key', 'help4-builder-suite'); ?></label></th>
                                <td><input id="shipping_<?php echo esc_attr($carrierKey); ?>_api_key" class="regular-text code" type="text" name="shipping_<?php echo esc_attr($carrierKey); ?>_api_key" value="<?php echo esc_attr($apiKey); ?>"></td>
                            </tr>
                            <tr>
                                <th><label for="shipping_<?php echo esc_attr($carrierKey); ?>_api_secret"><?php esc_html_e('API Secret', 'help4-builder-suite'); ?></label></th>
                                <td><input id="shipping_<?php echo esc_attr($carrierKey); ?>_api_secret" class="regular-text code" type="text" name="shipping_<?php echo esc_attr($carrierKey); ?>_api_secret" value="<?php echo esc_attr($apiSecret); ?>"></td>
                            </tr>
                            <tr>
                                <th><label for="shipping_<?php echo esc_attr($carrierKey); ?>_account"><?php esc_html_e('Account / Meter', 'help4-builder-suite'); ?></label></th>
                                <td><input id="shipping_<?php echo esc_attr($carrierKey); ?>_account" class="regular-text" type="text" name="shipping_<?php echo esc_attr($carrierKey); ?>_account" value="<?php echo esc_attr($account); ?>"></td>
                            </tr>
                            <tr>
                                <th><?php esc_html_e('Preset Rates', 'help4-builder-suite'); ?></th>
                                <td>
                                    <label><?php esc_html_e('Domestic', 'help4-builder-suite'); ?></label>
                                    <input class="small-text" type="number" step="0.01" min="0" name="shipping_<?php echo esc_attr($carrierKey); ?>_preset_domestic" value="<?php echo esc_attr((string) $presetDomestic); ?>">
                                    &nbsp;
                                    <label><?php esc_html_e('International', 'help4-builder-suite'); ?></label>
                                    <input class="small-text" type="number" step="0.01" min="0" name="shipping_<?php echo esc_attr($carrierKey); ?>_preset_international" value="<?php echo esc_attr((string) $presetInternational); ?>">
                                </td>
                            </tr>
                            <tr>
                                <th><?php esc_html_e('Per Weight Add-on', 'help4-builder-suite'); ?></th>
                                <td>
                                    <label><?php esc_html_e('Domestic / lb', 'help4-builder-suite'); ?></label>
                                    <input class="small-text" type="number" step="0.01" min="0" name="shipping_<?php echo esc_attr($carrierKey); ?>_per_weight_domestic" value="<?php echo esc_attr((string) $perWeightDomestic); ?>">
                                    &nbsp;
                                    <label><?php esc_html_e('International / lb', 'help4-builder-suite'); ?></label>
                                    <input class="small-text" type="number" step="0.01" min="0" name="shipping_<?php echo esc_attr($carrierKey); ?>_per_weight_international" value="<?php echo esc_attr((string) $perWeightInternational); ?>">
                                </td>
                            </tr>
                        </tbody>
                    </table>
                <?php endforeach; ?>

                <?php submit_button(__('Save Shipping Settings', 'help4-builder-suite')); ?>
            </form>
        </div>
        <?php
    }

    public function renderImportsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $result = get_transient('h4bs_last_import_result');
        if (! is_array($result)) {
            $result = [];
        }
        $settings = $this->settings();
        $catalogMode = $this->commerceCatalogMode($settings);
        $sourceStrategy = $this->commerceSourceStrategy($settings);
        $connectors = FeedPuller::connectorCatalog();
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Product Imports and Feed Puller', 'help4-builder-suite'); ?></h1>
            <p class="h4bs-admin-lead"><?php esc_html_e('Use this page to bring in products at scale: simple products, variant matrices, subscriptions, service packages, image galleries, and configured checkout/catalog feeds. Imports can be repeated safely while Migration Bridge keeps local editing locked.', 'help4-builder-suite'); ?></p>
            <p class="description">
                <?php
                printf(
                    /* translators: 1: catalog mode label, 2: source strategy */
                    esc_html__('Current mode: %1$s | Source strategy: %2$s', 'help4-builder-suite'),
                    esc_html($catalogMode === 'migration_bridge' ? __('Migration Bridge', 'help4-builder-suite') : __('Native Catalog', 'help4-builder-suite')),
                    esc_html(ucfirst($sourceStrategy))
                );
                ?>
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-commerce-migration')); ?>"><?php esc_html_e('Open Migration Bridge', 'help4-builder-suite'); ?></a>
            </p>

            <div class="h4bs-admin-card-grid">
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('1. Pull or paste', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Paste JSON for a controlled test, or point at a feed URL once the data shape is known.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('2. Normalize product data', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('The importer maps product name, SKU, price, currency, type, gallery URLs, downloads, subscriptions, and variants.', 'help4-builder-suite'); ?></p>
                </div>
                <div class="h4bs-admin-card">
                    <h3><?php esc_html_e('3. Review counts before launch', 'help4-builder-suite'); ?></h3>
                    <p><?php esc_html_e('Created, updated, variant, page, and error counts make it obvious whether the feed is ready for a real catalog cutover.', 'help4-builder-suite'); ?></p>
                </div>
            </div>

            <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:1rem;border-radius:8px;max-width:980px;">
                <?php wp_nonce_field('h4bs_run_import', 'h4bs_run_import_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="run_import">
                <h2><?php esc_html_e('Import JSON Payload', 'help4-builder-suite'); ?></h2>
                <p class="description"><?php esc_html_e('Paste an array of products or an object with an `items` array.', 'help4-builder-suite'); ?></p>
                <textarea name="import_json" rows="14" style="width:100%;font-family:Consolas,Menlo,monospace;"></textarea>
                <p><strong><?php esc_html_e('OR Feed URL', 'help4-builder-suite'); ?></strong></p>
                <input class="regular-text code" type="url" name="source_url" placeholder="https://example.com/feed.json">
                <table class="form-table" role="presentation" style="margin-top:1rem;">
                    <tbody>
                        <tr>
                            <th><label for="h4bs_feed_connector"><?php esc_html_e('Feed Connector', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="h4bs_feed_connector" name="feed_connector">
                                    <?php foreach ($connectors as $connector) : ?>
                                        <option value="<?php echo esc_attr((string) ($connector['slug'] ?? 'generic_json')); ?>">
                                            <?php echo esc_html((string) ($connector['label'] ?? 'Generic JSON')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <p class="description"><?php esc_html_e('Choose Shopify, a billing/catalog connector, Store REST API, affiliate-style JSON, or generic JSON.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_items_path"><?php esc_html_e('Items Path (Optional)', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <input id="h4bs_items_path" class="regular-text code" type="text" name="items_path" placeholder="data.products">
                                <p class="description"><?php esc_html_e('Dot path for nested item arrays when using generic JSON feeds.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_feed_bearer_token"><?php esc_html_e('Bearer Token (Optional)', 'help4-builder-suite'); ?></label></th>
                            <td><input id="h4bs_feed_bearer_token" class="regular-text code" type="text" name="feed_bearer_token" value=""></td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_feed_max_pages"><?php esc_html_e('Max Pages', 'help4-builder-suite'); ?></label></th>
                            <td><input id="h4bs_feed_max_pages" class="small-text" type="number" min="1" max="100" name="feed_max_pages" value="1"></td>
                        </tr>
                        <tr>
                            <th><label for="h4bs_feed_per_page"><?php esc_html_e('Per Page (Optional)', 'help4-builder-suite'); ?></label></th>
                            <td><input id="h4bs_feed_per_page" class="small-text" type="number" min="0" max="500" name="feed_per_page" value="0"></td>
                        </tr>
                    </tbody>
                </table>
                <p>
                    <label><input type="checkbox" name="download_images" value="1" checked> <?php esc_html_e('Download image and gallery URLs into WordPress media library', 'help4-builder-suite'); ?></label>
                </p>
                <p>
                    <label><input type="checkbox" name="replace_variants" value="1" checked> <?php esc_html_e('Replace old variants not present in the latest import payload', 'help4-builder-suite'); ?></label>
                </p>
                <?php submit_button(__('Run Import', 'help4-builder-suite')); ?>
            </form>

            <h2><?php esc_html_e('Feed/Importer Notes', 'help4-builder-suite'); ?></h2>
            <ul style="list-style:disc;padding-left:1.2rem;">
                <li><?php esc_html_e('Variable products: send `variants` as an array with `attributes`, `sku`, `price`, `stock_qty`, and optional `image_url`.', 'help4-builder-suite'); ?></li>
                <li><?php esc_html_e('Gallery import: send `gallery_urls` or `image_urls` arrays to ingest full galleries.', 'help4-builder-suite'); ?></li>
                <li><?php esc_html_e('Connectors available now: generic JSON, Shopify, billing/catalog connector, Store REST API, and affiliate-style JSON.', 'help4-builder-suite'); ?></li>
            </ul>
            <table class="widefat striped" style="max-width:980px;">
                <thead>
                    <tr>
                        <th><?php esc_html_e('Connector', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Description', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($connectors as $connector) : ?>
                        <tr>
                            <td><?php echo esc_html((string) ($connector['label'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($connector['status'] ?? 'ready')); ?></td>
                            <td><?php echo esc_html((string) ($connector['description'] ?? '')); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <?php if ($result) : ?>
                <h2><?php esc_html_e('Last Import Result', 'help4-builder-suite'); ?></h2>
                <table class="widefat striped" style="max-width:900px;">
                    <tbody>
                        <tr><th><?php esc_html_e('Total', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['total'] ?? 0)); ?></td></tr>
                        <tr><th><?php esc_html_e('Created', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['created'] ?? 0)); ?></td></tr>
                        <tr><th><?php esc_html_e('Updated', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['updated'] ?? 0)); ?></td></tr>
                        <tr><th><?php esc_html_e('Variants', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['variants'] ?? 0)); ?></td></tr>
                        <tr><th><?php esc_html_e('Connector', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['connector'] ?? 'manual_json')); ?></td></tr>
                        <tr><th><?php esc_html_e('Pages Fetched', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['pages_fetched'] ?? 1)); ?></td></tr>
                        <tr><th><?php esc_html_e('Items Received', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['items_received'] ?? ($result['total'] ?? 0))); ?></td></tr>
                        <tr><th><?php esc_html_e('Items Normalized', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) ($result['items_normalized'] ?? ($result['total'] ?? 0))); ?></td></tr>
                    </tbody>
                </table>
                <?php if (! empty($result['errors']) && is_array($result['errors'])) : ?>
                    <h3><?php esc_html_e('Errors', 'help4-builder-suite'); ?></h3>
                    <ul style="list-style:disc;padding-left:1.2rem;">
                        <?php foreach ($result['errors'] as $error) : ?>
                            <li><?php echo esc_html((string) $error); ?></li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
            <?php endif; ?>
        </div>
        <?php
    }

    public function renderMigrationBridgePage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        $settings = $this->settings();
        $catalogMode = $this->commerceCatalogMode($settings);
        $sourceStrategy = $this->commerceSourceStrategy($settings);
        $disableLocalProducts = ! $this->localProductEditingEnabled($settings);
        $fieldMap = is_array($settings['commerce_migration_field_map'] ?? null)
            ? (array) $settings['commerce_migration_field_map']
            : [];
        $shopifyDomain = sanitize_text_field((string) ($settings['commerce_shopify_store_domain'] ?? ''));
        $shopifyToken = sanitize_text_field((string) ($settings['commerce_shopify_access_token'] ?? ''));
        $billingBridgeApiUrl = esc_url(
            $this->settingsString(
                $settings,
                'commerce_billing_bridge_api_url',
                $this->legacyCommerceBillingBridgeKey('api_url'),
                'billing_bridge_api_url',
                $this->legacyBillingBridgeApiKey('url')
            )
        );
        $billingBridgeIdentifier = sanitize_text_field(
            $this->settingsString(
                $settings,
                'commerce_billing_bridge_identifier',
                $this->legacyCommerceBillingBridgeKey('identifier'),
                'billing_bridge_api_identifier',
                $this->legacyBillingBridgeApiKey('identifier')
            )
        );
        $billingBridgeSecret = sanitize_text_field(
            $this->settingsString(
                $settings,
                'commerce_billing_bridge_secret',
                $this->legacyCommerceBillingBridgeKey('secret'),
                'billing_bridge_api_secret',
                $this->legacyBillingBridgeApiKey('secret')
            )
        );
        $billingBridgeSyncMode = sanitize_key(
            $this->settingsString(
                $settings,
                'commerce_billing_bridge_sync_mode',
                $this->legacyCommerceBillingBridgeKey('sync_mode')
            )
        );
        if (! in_array($billingBridgeSyncMode, ['disabled', 'manual_only', 'enabled'], true)) {
            $billingBridgeSyncMode = 'disabled';
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Commerce Migration Bridge', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Keep catalog changes controlled while preparing Shopify and billing data paths for later cutover.', 'help4-builder-suite'); ?></p>
            <?php if (AdminRequest::queryKey('h4bs_notice') === 'migration_saved') : ?>
                <div class="notice notice-success is-dismissible"><p><?php esc_html_e('Migration bridge settings saved.', 'help4-builder-suite'); ?></p></div>
            <?php endif; ?>

            <form method="post" style="background:#fff;border:1px solid #dbe7e6;padding:1rem;border-radius:8px;max-width:980px;">
                <?php wp_nonce_field('h4bs_save_migration_bridge', 'h4bs_save_migration_bridge_nonce'); ?>
                <input type="hidden" name="h4bs_commerce_action" value="save_migration_bridge">

                <h2><?php esc_html_e('Operating Mode', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr>
                            <th><label for="commerce_catalog_mode"><?php esc_html_e('Catalog Mode', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="commerce_catalog_mode" name="commerce_catalog_mode">
                                    <option value="migration_bridge" <?php selected($catalogMode, 'migration_bridge'); ?>><?php esc_html_e('Migration Bridge (controlled imports)', 'help4-builder-suite'); ?></option>
                                    <option value="native_catalog" <?php selected($catalogMode, 'native_catalog'); ?>><?php esc_html_e('Native Catalog (full local products)', 'help4-builder-suite'); ?></option>
                                </select>
                                <p class="description"><?php esc_html_e('Migration Bridge keeps local product editing locked while you import/map external catalog data.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="commerce_source_strategy"><?php esc_html_e('Source Strategy', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="commerce_source_strategy" name="commerce_source_strategy">
                                    <option value="none" <?php selected($sourceStrategy, 'none'); ?>><?php esc_html_e('None / Manual', 'help4-builder-suite'); ?></option>
                                    <option value="shopify" <?php selected($sourceStrategy, 'shopify'); ?>><?php esc_html_e('Shopify Bridge', 'help4-builder-suite'); ?></option>
                                    <option value="billing_bridge" <?php selected($sourceStrategy, 'billing_bridge'); ?>><?php esc_html_e('Billing Catalog Bridge', 'help4-builder-suite'); ?></option>
                                    <option value="hybrid" <?php selected($sourceStrategy, 'hybrid'); ?>><?php esc_html_e('Hybrid (Shopify + Billing Catalog)', 'help4-builder-suite'); ?></option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <th><?php esc_html_e('Local Product Editing', 'help4-builder-suite'); ?></th>
                            <td>
                                <label><input type="checkbox" name="commerce_disable_local_products" value="1" <?php checked($disableLocalProducts); ?>> <?php esc_html_e('Disable local product create/edit/delete screens until imports and checkout routing are approved.', 'help4-builder-suite'); ?></label>
                            </td>
                        </tr>
                        <tr>
                            <th><label for="commerce_external_checkout_url"><?php esc_html_e('External Checkout URL', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <input id="commerce_external_checkout_url" class="regular-text code" type="url" name="commerce_external_checkout_url" value="<?php echo esc_attr((string) ($settings['commerce_external_checkout_url'] ?? '')); ?>" placeholder="https://example.com/cart">
                                <p class="description"><?php esc_html_e('Use this when this site should showcase catalog/pages but checkout is routed to another system.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                    </tbody>
                </table>

                <h2><?php esc_html_e('Shopify Bridge Prep', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr>
                            <th><label for="commerce_shopify_store_domain"><?php esc_html_e('Store Domain', 'help4-builder-suite'); ?></label></th>
                            <td><input id="commerce_shopify_store_domain" class="regular-text code" type="text" name="commerce_shopify_store_domain" value="<?php echo esc_attr($shopifyDomain); ?>" placeholder="store.myshopify.com"></td>
                        </tr>
                        <tr>
                            <th><label for="commerce_shopify_access_token"><?php esc_html_e('Admin API Token', 'help4-builder-suite'); ?></label></th>
                            <td><input id="commerce_shopify_access_token" class="regular-text code" type="text" name="commerce_shopify_access_token" value="<?php echo esc_attr($shopifyToken); ?>" placeholder="shpat_xxx"></td>
                        </tr>
                    </tbody>
                </table>

                <h2><?php esc_html_e('Billing Catalog Bridge Prep', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr>
                            <th><label for="commerce_billing_bridge_api_url"><?php esc_html_e('Billing Catalog API URL', 'help4-builder-suite'); ?></label></th>
                            <td><input id="commerce_billing_bridge_api_url" class="regular-text code" type="url" name="commerce_billing_bridge_api_url" value="<?php echo esc_attr($billingBridgeApiUrl); ?>" placeholder="https://example.com/products-api"></td>
                        </tr>
                        <tr>
                            <th><label for="commerce_billing_bridge_identifier"><?php esc_html_e('API Identifier', 'help4-builder-suite'); ?></label></th>
                            <td><input id="commerce_billing_bridge_identifier" class="regular-text code" type="text" name="commerce_billing_bridge_identifier" value="<?php echo esc_attr($billingBridgeIdentifier); ?>"></td>
                        </tr>
                        <tr>
                            <th><label for="commerce_billing_bridge_secret"><?php esc_html_e('API Secret', 'help4-builder-suite'); ?></label></th>
                            <td><input id="commerce_billing_bridge_secret" class="regular-text code" type="text" name="commerce_billing_bridge_secret" value="<?php echo esc_attr($billingBridgeSecret); ?>"></td>
                        </tr>
                        <tr>
                            <th><label for="commerce_billing_bridge_sync_mode"><?php esc_html_e('Billing Catalog Sync Mode', 'help4-builder-suite'); ?></label></th>
                            <td>
                                <select id="commerce_billing_bridge_sync_mode" name="commerce_billing_bridge_sync_mode">
                                    <option value="disabled" <?php selected($billingBridgeSyncMode, 'disabled'); ?>><?php esc_html_e('Disabled', 'help4-builder-suite'); ?></option>
                                    <option value="manual_only" <?php selected($billingBridgeSyncMode, 'manual_only'); ?>><?php esc_html_e('Manual Import Only', 'help4-builder-suite'); ?></option>
                                    <option value="enabled" <?php selected($billingBridgeSyncMode, 'enabled'); ?>><?php esc_html_e('Enabled (use with caution)', 'help4-builder-suite'); ?></option>
                                </select>
                                <p class="description"><?php esc_html_e('Leave disabled until Imports and connector pulls have been validated for the current catalog.', 'help4-builder-suite'); ?></p>
                            </td>
                        </tr>
                    </tbody>
                </table>

                <h2><?php esc_html_e('Field Mapping Templates', 'help4-builder-suite'); ?></h2>
                <table class="form-table" role="presentation">
                    <tbody>
                        <tr>
                            <th><label for="commerce_map_product"><?php esc_html_e('Product Map', 'help4-builder-suite'); ?></label></th>
                            <td><textarea id="commerce_map_product" class="large-text code" name="commerce_map_product" rows="2"><?php echo esc_textarea((string) ($fieldMap['product'] ?? 'name,sku,price,currency,description,type,categories,external_id,external_url')); ?></textarea></td>
                        </tr>
                        <tr>
                            <th><label for="commerce_map_order"><?php esc_html_e('Order Map', 'help4-builder-suite'); ?></label></th>
                            <td><textarea id="commerce_map_order" class="large-text code" name="commerce_map_order" rows="2"><?php echo esc_textarea((string) ($fieldMap['order'] ?? 'order_number,status,total,currency,customer_email,line_items')); ?></textarea></td>
                        </tr>
                        <tr>
                            <th><label for="commerce_map_customer"><?php esc_html_e('Customer Map', 'help4-builder-suite'); ?></label></th>
                            <td><textarea id="commerce_map_customer" class="large-text code" name="commerce_map_customer" rows="2"><?php echo esc_textarea((string) ($fieldMap['customer'] ?? 'email,first_name,last_name,role,status,total_spent,last_order_id')); ?></textarea></td>
                        </tr>
                    </tbody>
                </table>

                <?php submit_button(__('Save Migration Bridge Settings', 'help4-builder-suite')); ?>
            </form>

            <h2><?php esc_html_e('Recommended Workflow', 'help4-builder-suite'); ?></h2>
            <ol style="padding-left:1.2rem;">
                <li><?php esc_html_e('Keep catalog mode on Migration Bridge while pages, products, and checkout routing are being validated.', 'help4-builder-suite'); ?></li>
                <li><?php esc_html_e('Use Commerce > Imports with Shopify or billing/catalog connector payloads for controlled data validation.', 'help4-builder-suite'); ?></li>
                <li><?php esc_html_e('Only switch to Native Catalog when local product editing is actually needed.', 'help4-builder-suite'); ?></li>
            </ol>
        </div>
        <?php
    }

    public function renderLoadTestsPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Commerce Load Tests', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Run large catalog tests through WP-CLI over SSH for 20k/150k/1.5M scale validation.', 'help4-builder-suite'); ?></p>
            <pre style="background:#fff;border:1px solid #dbe7e6;padding:1rem;overflow:auto;">wp h4bs load seed --count=20000 --batch=1000
wp h4bs load benchmark --search=demo --search_mode=prefix --runs=30
wp h4bs load seed --count=150000 --batch=2000
wp h4bs load benchmark --search=demo --search_mode=prefix --runs=50
wp h4bs load seed --count=1500000 --batch=5000
wp h4bs load benchmark --search=demo --search_mode=prefix --runs=60 --offset=1000000
wp h4bs load benchmark --search=demo --search_mode=contains --runs=20
wp h4bs load cleanup --prefix=H4LT</pre>
            <p><?php esc_html_e('These commands generate reproducible import and search performance numbers for your environment. Prefix mode is tuned for very large catalogs.', 'help4-builder-suite'); ?></p>
        </div>
        <?php
    }

    public function registerRestRoutes(): void
    {
        register_rest_route(
            'h4bs/v1',
            '/products',
            [
                [
                    'methods'             => 'GET',
                    'callback'            => [$this, 'listProducts'],
                    'permission_callback' => [$this, 'canReadCatalogApi'],
                ],
                [
                    'methods'             => 'POST',
                    'callback'            => [$this, 'createProduct'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/products/(?P<id>\d+)',
            [
                [
                    'methods'             => 'GET',
                    'callback'            => [$this, 'getProduct'],
                    'permission_callback' => [$this, 'canReadCatalogApi'],
                ],
                [
                    'methods'             => ['PUT', 'PATCH'],
                    'callback'            => [$this, 'updateProduct'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
                [
                    'methods'             => 'DELETE',
                    'callback'            => [$this, 'deleteProduct'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/products/(?P<id>\d+)/variants',
            [
                [
                    'methods'             => 'GET',
                    'callback'            => [$this, 'listVariants'],
                    'permission_callback' => [$this, 'canReadCatalogApi'],
                ],
                [
                    'methods'             => 'POST',
                    'callback'            => [$this, 'createVariant'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/variants/(?P<variant_id>\d+)',
            [
                [
                    'methods'             => ['PUT', 'PATCH'],
                    'callback'            => [$this, 'updateVariant'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
                [
                    'methods'             => 'DELETE',
                    'callback'            => [$this, 'deleteVariant'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/products/import',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'importProducts'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/feeds/pull',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'pullFeed'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/feeds/connectors',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'listFeedConnectors'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/checkout',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'checkout'],
                'permission_callback' => '__return_true',
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/gateways',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'listGateways'],
                'permission_callback' => '__return_true',
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/subscriptions',
            [
                [
                    'methods'             => 'GET',
                    'callback'            => [$this, 'listSubscriptions'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
                [
                    'methods'             => 'POST',
                    'callback'            => [$this, 'createSubscription'],
                    'permission_callback' => static fn (): bool => current_user_can('manage_options'),
                ],
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/customers',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'listCustomers'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );
    }

    public function canReadCatalogApi(): bool
    {
        if (current_user_can('manage_options')) {
            return true;
        }

        /**
         * Allow intentional public product reads when a site explicitly enables it.
         * Default stays locked down to reduce accidental data exposure.
         */
        return (bool) apply_filters('h4bs_commerce_public_catalog_api', false);
    }

    public function listProducts(WP_REST_Request $request): WP_REST_Response
    {
        $searchMode = sanitize_key((string) $request->get_param('search_mode'));
        if (! in_array($searchMode, ['prefix', 'contains', 'exact'], true)) {
            $searchMode = 'prefix';
        }
        $sort = $this->sanitizeCatalogSort((string) $request->get_param('sort'));
        $offset = max(0, (int) $request->get_param('offset'));
        $rows = $this->products->list(
            [
                'status' => (string) $request->get_param('status'),
                'search' => (string) $request->get_param('search'),
                'search_mode' => $searchMode,
                'sort' => $sort,
                'limit'  => (int) $request->get_param('limit'),
                'offset' => $offset,
                'min_price' => (float) $request->get_param('min_price'),
                'max_price' => (float) $request->get_param('max_price'),
                'in_stock_only' => rest_sanitize_boolean($request->get_param('in_stock_only')),
                'cursor_updated_at' => (string) $request->get_param('cursor_updated_at'),
                'cursor_id' => (int) $request->get_param('cursor_id'),
            ]
        );

        $items = [];
        foreach ($rows as $row) {
            if (! is_array($row)) {
                continue;
            }
            $items[] = $this->sanitizePublicProduct($row);
        }

        $nextCursor = null;
        $last = end($rows);
        if (is_array($last) && ! empty($last['updated_at']) && ! empty($last['id'])) {
            $nextCursor = [
                'updated_at' => (string) $last['updated_at'],
                'id' => (int) $last['id'],
            ];
        }

        $offsetWarning = null;
        if ($offset >= 100000) {
            $offsetWarning = 'Deep offset paging can slow down at high catalog counts. Use cursor paging for consistent speed.';
        }

        return new WP_REST_Response(
            [
                'items' => $items,
                'search_mode' => $searchMode,
                'sort' => $sort,
                'next_cursor' => $nextCursor,
                'count' => count($items),
                'offset_warning' => $offsetWarning,
            ]
        );
    }

    public function getProduct(WP_REST_Request $request): WP_REST_Response
    {
        $id = (int) $request->get_param('id');
        $product = $this->products->get($id);
        if (! $product) {
            return new WP_REST_Response(['message' => 'Product not found'], 404);
        }

        $responseProduct = $this->sanitizePublicProduct($product);
        if (rest_sanitize_boolean($request->get_param('include_variants'))) {
            $variants = [];
            foreach ($this->products->listVariants($id) as $variant) {
                if (! is_array($variant)) {
                    continue;
                }
                $variants[] = $this->sanitizePublicVariant($variant);
            }
            $responseProduct['variants'] = $variants;
        }

        return new WP_REST_Response($responseProduct);
    }

    public function createProduct(WP_REST_Request $request): WP_REST_Response
    {
        $blocked = $this->localCatalogEditingBlockedResponse();
        if ($blocked instanceof WP_REST_Response) {
            return $blocked;
        }

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $id = $this->products->create($payload);
        $product = $this->products->get($id);

        return new WP_REST_Response($product, 201);
    }

    public function updateProduct(WP_REST_Request $request): WP_REST_Response
    {
        $blocked = $this->localCatalogEditingBlockedResponse();
        if ($blocked instanceof WP_REST_Response) {
            return $blocked;
        }

        $id = (int) $request->get_param('id');
        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $updated = $this->products->update($id, $payload);
        if (! $updated) {
            return new WP_REST_Response(['message' => 'Unable to update product'], 422);
        }

        $product = $this->products->get($id);
        return new WP_REST_Response($product);
    }

    public function deleteProduct(WP_REST_Request $request): WP_REST_Response
    {
        $blocked = $this->localCatalogEditingBlockedResponse();
        if ($blocked instanceof WP_REST_Response) {
            return $blocked;
        }

        $id = (int) $request->get_param('id');
        $deleted = $this->products->delete($id);
        return new WP_REST_Response(['deleted' => $deleted]);
    }

    public function listVariants(WP_REST_Request $request): WP_REST_Response
    {
        $productId = (int) $request->get_param('id');
        $items = [];
        foreach ($this->products->listVariants($productId) as $variant) {
            if (! is_array($variant)) {
                continue;
            }
            $items[] = $this->sanitizePublicVariant($variant);
        }

        return new WP_REST_Response(['items' => $items]);
    }

    public function createVariant(WP_REST_Request $request): WP_REST_Response
    {
        $productId = (int) $request->get_param('id');
        $product = $this->products->get($productId);
        if (! $product) {
            return new WP_REST_Response(['message' => 'Product not found'], 404);
        }

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $variantId = $this->products->createVariant($productId, $payload);
        return new WP_REST_Response(
            [
                'id'        => $variantId,
                'productId' => $productId,
            ],
            201
        );
    }

    public function updateVariant(WP_REST_Request $request): WP_REST_Response
    {
        $variantId = (int) $request->get_param('variant_id');
        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $updated = $this->products->updateVariant($variantId, $payload);
        return new WP_REST_Response(['updated' => $updated]);
    }

    public function deleteVariant(WP_REST_Request $request): WP_REST_Response
    {
        $variantId = (int) $request->get_param('variant_id');
        $deleted = $this->products->deleteVariant($variantId);
        return new WP_REST_Response(['deleted' => $deleted]);
    }

    public function importProducts(WP_REST_Request $request): WP_REST_Response
    {
        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            return new WP_REST_Response(['message' => 'Invalid payload'], 422);
        }

        $items = [];
        if (isset($payload['items']) && is_array($payload['items'])) {
            $items = $payload['items'];
        } elseif (array_is_list($payload)) {
            $items = $payload;
        }

        if (! $items) {
            return new WP_REST_Response(['message' => 'No import items provided'], 422);
        }

        $result = $this->importer->import(
            $items,
            [
                'download_images' => ! empty($payload['download_images']),
                'replace_variants'=> ! array_key_exists('replace_variants', $payload)
                    || rest_sanitize_boolean($payload['replace_variants']),
            ]
        );
        return new WP_REST_Response($result);
    }

    public function pullFeed(WP_REST_Request $request): WP_REST_Response
    {
        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            return new WP_REST_Response(['message' => 'Invalid payload'], 422);
        }
        $result = $this->feedPuller->pull($payload);
        if ($result instanceof WP_Error) {
            return new WP_REST_Response(['message' => $result->get_error_message()], 422);
        }
        return new WP_REST_Response($result);
    }

    public function listFeedConnectors(): WP_REST_Response
    {
        return new WP_REST_Response(['items' => FeedPuller::connectorCatalog()]);
    }

    public function checkout(WP_REST_Request $request): WP_REST_Response
    {
        global $wpdb;

        $rateLimitResponse = $this->enforcePublicCheckoutRateLimit();
        if ($rateLimitResponse instanceof WP_REST_Response) {
            return $rateLimitResponse;
        }

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            return new WP_REST_Response(['message' => 'Invalid request payload'], 422);
        }

        $checkoutKey = substr(
            (string) preg_replace('/[^A-Za-z0-9._:-]/', '', sanitize_text_field((string) ($payload['checkout_key'] ?? ''))),
            0,
            64
        );
        if ($checkoutKey === '') {
            $checkoutKey = wp_generate_uuid4();
        }
        $ordersTable = $wpdb->prefix . 'h4_orders';
        $itemsTable = $wpdb->prefix . 'h4_order_items';
        $existingOrder = $wpdb->get_row(
            $wpdb->prepare('SELECT id, order_number, status FROM %i WHERE checkout_key = %s LIMIT 1', $ordersTable, $checkoutKey),
            ARRAY_A
        );
        if (is_array($existingOrder)) {
            return new WP_REST_Response(
                [
                    'message' => 'This checkout was already submitted. No duplicate order was created.',
                    'code' => 'h4bs_checkout_replay',
                    'order_id' => absint($existingOrder['id'] ?? 0),
                    'order_number' => sanitize_text_field((string) ($existingOrder['order_number'] ?? '')),
                    'status' => sanitize_key((string) ($existingOrder['status'] ?? 'pending')),
                ],
                409
            );
        }

        $rawCheckoutPayload = $payload;
        $payload = $this->sanitizeCheckoutRequestPayload($payload);
        $payload['checkout_key'] = $checkoutKey;
        $filteredPayload = apply_filters('h4bs_checkout_request_payload', $payload, $rawCheckoutPayload, $request);
        if (is_array($filteredPayload)) {
            $payload = $filteredPayload;
        }

        $items = $payload['items'] ?? [];
        if (! is_array($items) || ! $items) {
            return new WP_REST_Response(['message' => 'Checkout requires items'], 422);
        }

        $requestedGateway = sanitize_key((string) ($payload['gateway'] ?? ''));
        $requestedRoute = sanitize_key((string) ($payload['route'] ?? ''));
        $promotionCode = $this->promotions->normalizeCode(
            (string) ($payload['promotion_code'] ?? ($payload['coupon_code'] ?? ''))
        );
        $customerEmail = sanitize_email((string) ($payload['customer_email'] ?? ''));
        $customerFirstName = sanitize_text_field(
            (string) (
                $payload['customer_first_name']
                ?? ($payload['customer']['first_name'] ?? ($payload['billing']['first_name'] ?? ''))
            )
        );
        $customerLastName = sanitize_text_field(
            (string) (
                $payload['customer_last_name']
                ?? ($payload['customer']['last_name'] ?? ($payload['billing']['last_name'] ?? ''))
            )
        );
        $currency = strtoupper(sanitize_text_field((string) ($payload['currency'] ?? 'USD')));
        if ($customerEmail === '') {
            return new WP_REST_Response(['message' => 'Customer email is required'], 422);
        }

        $total = 0.0;
        $orderItems = [];
        $processorLineItems = [];
        $containsDigital = false;
        $downloadableCount = 0;
        $containsShippable = false;
        $shippableCount = 0;
        $containsBooking = false;
        $bookingCount = 0;
        $containsService = false;
        $serviceCount = 0;
        $containsSubscription = false;
        $subscriptionCount = 0;
        $subscriptionCandidates = [];
        $customerRoleOverride = '';
        $customerStatusOverride = '';
        $inventoryClaims = [];
        $quantityTotals = [];
        foreach ($items as $item) {
            if (! is_array($item)) {
                continue;
            }
            $productId = absint($item['product_id'] ?? 0);
            $qty = (float) ($item['qty'] ?? 1);
            if ($qty <= 0 || $qty > 1000) {
                return new WP_REST_Response(['message' => 'Product quantity must be between 0 and 1000.'], 422);
            }

            $product = $this->products->get($productId);
            if (! $product) {
                return new WP_REST_Response(['message' => 'A checkout product could not be found.'], 422);
            }
            if (sanitize_key((string) ($product['status'] ?? '')) !== 'active') {
                return new WP_REST_Response(['message' => 'A checkout product is not currently available.'], 422);
            }

            $productPayload = $this->decodeJsonArray((string) ($product['payload'] ?? ''));
            $resolvedProductType = $this->resolveProductType($productPayload, $item);
            $inventory = $this->normalizeInventoryConfig($productPayload);
            $quantityError = $this->validateCheckoutQuantity($qty, $inventory);
            if ($quantityError instanceof WP_Error) {
                return new WP_REST_Response(
                    [
                        'message' => $quantityError->get_error_message(),
                        'code' => $quantityError->get_error_code(),
                        'product_id' => $productId,
                    ],
                    422
                );
            }
            $variantId = absint($item['variant_id'] ?? 0);
            $variantData = null;
            $availableVariants = ($variantId > 0 || ! empty($item['variant_sku']) || $resolvedProductType === 'variable')
                ? $this->products->listVariants($productId)
                : [];
            if ($variantId > 0) {
                foreach ($availableVariants as $variant) {
                    if (! is_array($variant) || absint($variant['id'] ?? 0) !== $variantId) {
                        continue;
                    }
                    $variantData = $variant;
                    break;
                }
            } elseif (! empty($item['variant_sku'])) {
                $variantSku = sanitize_text_field((string) $item['variant_sku']);
                foreach ($availableVariants as $variant) {
                    if (! is_array($variant)) {
                        continue;
                    }
                    if (sanitize_text_field((string) ($variant['sku'] ?? '')) === $variantSku) {
                        $variantId = absint($variant['id'] ?? 0);
                        $variantData = $variant;
                        break;
                    }
                }
            }
            if (($variantId > 0 || ! empty($item['variant_sku'])) && ! is_array($variantData)) {
                return new WP_REST_Response(
                    [
                        'message' => 'A selected product option is no longer available.',
                        'product_id' => $productId,
                    ],
                    422
                );
            }
            if ($resolvedProductType === 'variable' && $availableVariants && ! is_array($variantData)) {
                return new WP_REST_Response(
                    [
                        'message' => 'Choose an available product option before checkout.',
                        'code' => 'h4bs_variant_required',
                        'product_id' => $productId,
                    ],
                    422
                );
            }

            $quantityKey = $variantId > 0 ? 'variant:' . $variantId : 'product:' . $productId;
            if (! isset($quantityTotals[$quantityKey])) {
                $quantityTotals[$quantityKey] = [
                    'quantity' => 0.0,
                    'sold_individually' => (bool) $inventory['sold_individually'],
                    'quantity_enabled' => (bool) $inventory['quantity_enabled'],
                    'max_qty' => (float) $inventory['max_qty'],
                ];
            }
            $quantityTotals[$quantityKey]['quantity'] += $qty;
            $aggregateQty = (float) $quantityTotals[$quantityKey]['quantity'];
            if (! empty($quantityTotals[$quantityKey]['sold_individually']) && $aggregateQty > 1) {
                return new WP_REST_Response(
                    [
                        'message' => 'This product is limited to one per order.',
                        'code' => 'h4bs_sold_individually',
                        'product_id' => $productId,
                    ],
                    422
                );
            }
            $aggregateMax = (float) ($quantityTotals[$quantityKey]['max_qty'] ?? 0);
            if (! empty($quantityTotals[$quantityKey]['quantity_enabled']) && $aggregateMax > 0 && $aggregateQty > $aggregateMax) {
                return new WP_REST_Response(
                    [
                        'message' => sprintf(
                            /* translators: %s: maximum permitted product quantity. */
                            __('The maximum quantity for this product is %s.', 'help4-builder-suite'),
                            number_format_i18n($aggregateMax, 3)
                        ),
                        'code' => 'h4bs_quantity_maximum',
                        'product_id' => $productId,
                    ],
                    422
                );
            }

            if (! empty($inventory['track_stock'])) {
                $claimKey = $quantityKey;
                if (! isset($inventoryClaims[$claimKey])) {
                    $inventoryClaims[$claimKey] = [
                        'product_id' => $productId,
                        'variant_id' => $variantId,
                        'quantity' => 0,
                        'policy' => (string) $inventory['policy'],
                        'low_stock_threshold' => (int) $inventory['low_stock_threshold'],
                        'name' => sanitize_text_field((string) ($product['name'] ?? '')),
                        'sku' => is_array($variantData)
                            ? sanitize_text_field((string) ($variantData['sku'] ?? ''))
                            : sanitize_text_field((string) ($product['sku'] ?? '')),
                    ];
                }
                $inventoryClaims[$claimKey]['quantity'] += (int) round($qty);
            }

            $unitPrice = (float) ($product['price'] ?? 0);
            if (is_array($variantData) && isset($variantData['price'])) {
                $unitPrice = (float) $variantData['price'];
            }
            $lineTotal = $unitPrice * $qty;

            $isDigital = $this->detectDigitalProduct($productPayload, $item);
            $downloads = $this->normalizeDownloads(
                $item['downloads']
                ?? ($productPayload['downloads']
                ?? ($productPayload['download_urls'] ?? []))
            );
            if ($isDigital) {
                $containsDigital = true;
                if ($downloads) {
                    $downloadableCount++;
                }
            }
            $isBooking = $this->detectBookingProduct($productPayload, $item);
            $productType = $resolvedProductType;
            $fulfillmentType = $this->resolveFulfillmentType($productPayload, $item, $isDigital, $isBooking);
            $subscription = $this->resolveSubscriptionMeta(
                $productPayload,
                $item,
                $productType,
                $unitPrice,
                $lineTotal,
                $currency
            );
            if (! empty($subscription['is_subscription'])) {
                $lineTotal += max(0.0, (float) ($subscription['setup_fee'] ?? 0));
            }
            $taxConfig = is_array($productPayload['tax'] ?? null) ? $productPayload['tax'] : [];
            $taxStatus = sanitize_key((string) ($taxConfig['status'] ?? 'taxable'));
            if (! in_array($taxStatus, ['taxable', 'none'], true)) {
                $taxStatus = 'taxable';
            }
            $taxClass = sanitize_key((string) ($taxConfig['class'] ?? 'standard')) ?: 'standard';
            $total += $lineTotal;
            $requiresShipping = $this->detectShippingRequirement(
                $productPayload,
                $item,
                $fulfillmentType,
                $isDigital,
                $isBooking
            );
            $shippingProfile = $this->resolveShippingProfile($productPayload, $item);
            $merchandising = $this->normalizeMerchandisingConfig($productPayload);
            $booking = $this->extractBookingMeta($productPayload, $item, $isBooking);
            $isService = $fulfillmentType === 'service' || $productType === 'service';
            if ($requiresShipping) {
                $containsShippable = true;
                $shippableCount++;
            }
            if ($isBooking) {
                $containsBooking = true;
                $bookingCount++;
            }
            if ($isService) {
                $containsService = true;
                $serviceCount++;
            }
            if (! empty($subscription['is_subscription'])) {
                $containsSubscription = true;
                $subscriptionCount++;
                $subscriptionCandidates[] = [
                    'product_id' => $productId,
                    'variant_id' => $variantId,
                    'sku' => sanitize_text_field((string) ($product['sku'] ?? '')),
                    'variant_sku' => is_array($variantData) ? sanitize_text_field((string) ($variantData['sku'] ?? '')) : '',
                    'name' => sanitize_text_field((string) ($product['name'] ?? '')),
                    'qty' => $qty,
                    'subscription' => $subscription,
                ];
            }

            $linePayload = [
                'line_key' => sanitize_text_field((string) ($item['line_key'] ?? '')),
                'request' => $item,
                'product' => [
                    'id'   => $productId,
                    'sku'  => sanitize_text_field((string) ($product['sku'] ?? '')),
                    'name' => sanitize_text_field((string) ($product['name'] ?? '')),
                ],
                'variant' => [
                    'id'   => $variantId,
                    'sku'  => is_array($variantData) ? sanitize_text_field((string) ($variantData['sku'] ?? '')) : '',
                    'data' => is_array($variantData) ? $variantData : null,
                ],
                'digital' => [
                    'is_digital' => $isDigital,
                    'downloads'  => $downloads,
                ],
                'fulfillment' => [
                    'product_type'      => $productType,
                    'fulfillment_type'  => $fulfillmentType,
                    'requires_shipping' => $requiresShipping,
                    'is_booking'        => $isBooking,
                    'is_service'        => $isService,
                ],
                'shipping_profile' => $shippingProfile,
                'booking' => $booking,
                'subscription' => $subscription,
                'inventory' => $this->publicInventorySnapshot(
                    $inventory,
                    is_array($variantData)
                        ? (int) ($variantData['stock_qty'] ?? 0)
                        : (int) ($product['stock_qty'] ?? 0)
                ),
                'merchandising' => [
                    'purchase_note' => (string) $merchandising['purchase_note'],
                    'related_product_ids' => $merchandising['related_product_ids'],
                    'upsell_product_ids' => $merchandising['upsell_product_ids'],
                    'cross_sell_product_ids' => $merchandising['cross_sell_product_ids'],
                ],
                'tax' => [
                    'status' => $taxStatus,
                    'class' => $taxClass,
                ],
            ];

            $customerAccountRules = is_array($productPayload['customer_account'] ?? null)
                ? $productPayload['customer_account']
                : [];
            $lineRoleOverride = $this->sanitizeCustomerRole((string) ($customerAccountRules['role_after_purchase'] ?? ''), false);
            $lineStatusOverride = $this->sanitizeCustomerStatus((string) ($customerAccountRules['status_after_purchase'] ?? ''), false);
            if ($lineRoleOverride !== '') {
                $customerRoleOverride = $lineRoleOverride;
            }
            if ($lineStatusOverride !== '') {
                $customerStatusOverride = $lineStatusOverride;
            }
            $linePayload['customer_account'] = [
                'role_after_purchase' => $lineRoleOverride,
                'status_after_purchase' => $lineStatusOverride,
            ];

            $orderItems[] = [
                'line_key' => sanitize_text_field((string) ($item['line_key'] ?? '')),
                'product_id' => $productId,
                'variant_id' => $variantId,
                'qty'        => $qty,
                'unit_price' => $unitPrice,
                'payload'    => $linePayload,
            ];

            $variantAttributes = is_array($variantData)
                ? $this->decodeJsonArray((string) ($variantData['attributes_json'] ?? '{}'))
                : [];

            $processorLineItems[] = [
                'line_key'     => sanitize_text_field((string) ($item['line_key'] ?? '')),
                'product_id'   => $productId,
                'variant_id'   => $variantId,
                'sku'          => sanitize_text_field((string) ($product['sku'] ?? '')),
                'variant_sku'  => is_array($variantData) ? sanitize_text_field((string) ($variantData['sku'] ?? '')) : '',
                'name'         => sanitize_text_field((string) ($product['name'] ?? '')),
                'qty'          => $qty,
                'unit_price'   => $unitPrice,
                'line_total'   => $lineTotal,
                'currency'     => $currency,
                'tax_status'   => $taxStatus,
                'tax_class'    => $taxClass,
                'is_digital'   => $isDigital,
                'downloads'    => $downloads,
                'product_type' => $productType,
                'fulfillment_type' => $fulfillmentType,
                'requires_shipping' => $requiresShipping,
                'shipping_profile' => $shippingProfile,
                'is_booking'   => $isBooking,
                'booking'      => $booking,
                'is_subscription' => ! empty($subscription['is_subscription']),
                'subscription' => $subscription,
                'attributes'   => $variantAttributes,
                'configuration' => is_array($item['configuration'] ?? null) ? $item['configuration'] : [],
                'customer_account' => [
                    'role_after_purchase' => $lineRoleOverride,
                    'status_after_purchase' => $lineStatusOverride,
                ],
                'inventory' => $linePayload['inventory'],
                'quantity_policy' => [
                    'quantity_enabled' => (bool) $inventory['quantity_enabled'],
                    'quantity_mode' => (string) $inventory['quantity_mode'],
                    'fixed_quantity' => $inventory['fixed_quantity'],
                    'merge_cart_lines' => (bool) $inventory['merge_cart_lines'],
                ],
                'purchase_note' => (string) $merchandising['purchase_note'],
            ];
        }

        if (! $orderItems) {
            return new WP_REST_Response(['message' => 'No valid products in checkout'], 422);
        }

        if ($containsShippable) {
            $shippingAddress = is_array($payload['shipping'] ?? null) ? $payload['shipping'] : [];
            $requiredShippingFields = apply_filters(
                'h4bs_checkout_required_shipping_fields',
                ['address_1', 'city', 'postcode', 'country'],
                $shippingAddress,
                $processorLineItems,
                $payload
            );
            if (! is_array($requiredShippingFields)) {
                $requiredShippingFields = ['address_1', 'city', 'postcode', 'country'];
            }
            $missingShippingFields = [];
            foreach ($requiredShippingFields as $requiredShippingField) {
                $field = sanitize_key((string) $requiredShippingField);
                if ($field !== '' && trim((string) ($shippingAddress[$field] ?? '')) === '') {
                    $missingShippingFields[] = $field;
                }
            }
            if ($missingShippingFields) {
                return new WP_REST_Response(
                    [
                        'message' => 'Complete the required delivery address before checkout.',
                        'code' => 'h4bs_shipping_address_required',
                        'fields' => array_values(array_unique($missingShippingFields)),
                    ],
                    422
                );
            }
        }

        $settings = $this->settings();
        $subtotal = $total;
        $shippingSummary = $this->buildShippingSummary(
            $payload,
            $settings,
            $processorLineItems,
            $currency,
            $containsShippable
        );
        $shippingTotal = (float) ($shippingSummary['selected_amount'] ?? 0);
        $promotion = $this->promotionEngine->evaluate(
            $promotionCode,
            $subtotal,
            $shippingTotal,
            $currency,
            $processorLineItems
        );
        if ($promotion instanceof WP_Error) {
            return new WP_REST_Response(
                [
                    'message' => $promotion->get_error_message(),
                    'code' => $promotion->get_error_code(),
                    'promotion_code' => $promotionCode,
                ],
                422
            );
        }
        $discountTotal = max(0.0, (float) ($promotion['discount'] ?? 0));
        $shippingDiscount = max(0.0, (float) ($promotion['shipping_discount'] ?? 0));
        $lineDiscounts = is_array($promotion['line_discounts'] ?? null) ? $promotion['line_discounts'] : [];
        foreach ($processorLineItems as $lineIndex => &$processorLineItem) {
            $lineDiscount = max(0.0, (float) ($lineDiscounts[$lineIndex] ?? 0));
            $lineTotal = max(0.0, (float) ($processorLineItem['line_total'] ?? 0));
            $processorLineItem['discount_total'] = min($lineTotal, $lineDiscount);
            $processorLineItem['net_line_total'] = max(0.0, $lineTotal - $lineDiscount);
            if (isset($orderItems[$lineIndex]) && is_array($orderItems[$lineIndex])) {
                $orderItems[$lineIndex]['payload']['pricing'] = [
                    'line_total' => round($lineTotal, 2),
                    'discount_total' => round($lineDiscount, 2),
                    'net_line_total' => round(max(0.0, $lineTotal - $lineDiscount), 2),
                ];
            }
        }
        unset($processorLineItem);
        $netShippingTotal = max(0.0, $shippingTotal - $shippingDiscount);
        $shippingSummary['discount'] = $shippingDiscount;
        $shippingSummary['net_amount'] = $netShippingTotal;
        $destination = is_array($shippingSummary['destination'] ?? null) ? $shippingSummary['destination'] : [];
        $taxSummary = $this->taxCalculator->calculate(
            $processorLineItems,
            $subtotal,
            $discountTotal,
            $netShippingTotal,
            $destination,
            $settings
        );
        $taxTotal = max(0.0, (float) ($taxSummary['total'] ?? 0));
        $total = max(0.0, $subtotal - $discountTotal + $netShippingTotal + $taxTotal);
        $registry = new GatewayRegistry($settings);
        $gatewayCatalog = $registry->all();
        $routingContext = [
            'currency' => $currency,
            'order_total' => $total,
            'order_subtotal' => $subtotal,
            'customer_roles' => is_user_logged_in() ? (array) wp_get_current_user()->roles : ['guest'],
            'discount_total' => $discountTotal + $shippingDiscount,
            'shipping_total' => $netShippingTotal,
            'tax_total' => $taxTotal,
            'contains_digital' => $containsDigital,
            'contains_shippable' => $containsShippable,
            'contains_booking' => $containsBooking,
            'contains_service' => $containsService,
            'contains_subscription' => $containsSubscription,
            'subscription_count' => $subscriptionCount,
            'product_ids' => array_values(
                array_unique(
                    array_map(
                        static fn(array $line): int => absint($line['product_id'] ?? 0),
                        $processorLineItems
                    )
                )
            ),
            'product_types' => array_values(
                array_unique(
                    array_filter(
                        array_map(
                            static fn(array $line): string => sanitize_key((string) ($line['product_type'] ?? '')),
                            $processorLineItems
                        ),
                        static fn(string $type): bool => $type !== ''
                    )
                )
            ),
            'fulfillment_types' => array_values(
                array_unique(
                    array_filter(
                        array_map(
                            static fn(array $line): string => sanitize_key((string) ($line['fulfillment_type'] ?? '')),
                            $processorLineItems
                        ),
                        static fn(string $type): bool => $type !== ''
                    )
                )
            ),
            'country' => strtoupper(sanitize_text_field((string) ($destination['country'] ?? ''))),
            'state' => strtoupper(sanitize_text_field((string) ($destination['state'] ?? ''))),
            'zip' => sanitize_text_field((string) ($destination['postal_code'] ?? '')),
        ];
        $checkoutCustomerFieldDefinitions = $this->customerFields->listDefinitions('checkout', $routingContext);
        $customerFieldValues = $this->customerFields->validateValues(
            $payload['customer_fields'] ?? [],
            $checkoutCustomerFieldDefinitions
        );
        if ($customerFieldValues instanceof WP_Error) {
            $customerFieldErrorData = $customerFieldValues->get_error_data();
            return new WP_REST_Response(
                [
                    'message' => $customerFieldValues->get_error_message(),
                    'code' => $customerFieldValues->get_error_code(),
                    'field' => is_array($customerFieldErrorData) ? (string) ($customerFieldErrorData['field'] ?? '') : '',
                ],
                422
            );
        }
        $customerFieldSnapshot = $this->customerFields->orderSnapshot(
            $customerFieldValues,
            $checkoutCustomerFieldDefinitions
        );
        $routingDecision = $this->resolveGatewayRouteDecision(
            $settings,
            $gatewayCatalog,
            $routingContext,
            $requestedGateway,
            $requestedRoute
        );
        $gateway = sanitize_key((string) ($routingDecision['gateway'] ?? 'stripe_router'));
        $gatewayData = $registry->get($gateway);
        if (! $gatewayData) {
            return new WP_REST_Response(
                [
                    'message' => 'Unsupported gateway',
                    'routing' => $routingDecision,
                ],
                422
            );
        }
        $gatewayReady = (bool) apply_filters(
            'h4bs_checkout_gateway_ready',
            ! empty($gatewayData['configured']),
            $gatewayData,
            $routingDecision,
            $payload
        );
        if (! $gatewayReady) {
            return new WP_REST_Response(
                [
                    'message' => 'The selected payment method is not ready to accept checkout.',
                    'code' => 'h4bs_gateway_not_ready',
                    'gateway' => $gateway,
                ],
                422
            );
        }

        $customerDataMode = sanitize_key((string) ($settings['commerce_customer_data_mode'] ?? 'both'));
        if (! in_array($customerDataMode, ['stripe_only', 'both', 'wordpress_only'], true)) {
            $customerDataMode = 'both';
        }
        $defaultCustomerRole = $this->sanitizeCustomerRole((string) ($settings['commerce_default_customer_role'] ?? 'customer'));
        $defaultCustomerStatus = $this->sanitizeCustomerStatus((string) ($settings['commerce_default_customer_status'] ?? 'active'));
        $finalCustomerRole = $customerRoleOverride !== '' ? $customerRoleOverride : $defaultCustomerRole;
        $finalCustomerStatus = $customerStatusOverride !== '' ? $customerStatusOverride : $defaultCustomerStatus;
        $storedCustomerEmail = $customerDataMode === 'stripe_only' ? '' : $customerEmail;
        $storedPayload = $this->sanitizePayloadForStorage($payload, $customerDataMode);
        if ($customerDataMode !== 'stripe_only' && $customerFieldSnapshot) {
            $storedPayload['_h4bs_customer_fields'] = $customerFieldSnapshot;
        }
        $customerAccount = $customerDataMode === 'stripe_only'
            ? [
                'user_id' => 0,
                'created' => false,
                'updated' => false,
                'role' => $finalCustomerRole,
                'status' => $finalCustomerStatus,
            ]
            : $this->ensureCustomerUser(
                $customerEmail,
                $customerFirstName,
                $customerLastName,
                $finalCustomerRole,
                $finalCustomerStatus
            );
        $customerUserId = absint($customerAccount['user_id'] ?? 0);

        $storedPayload['_h4bs_pricing'] = [
            'subtotal' => round($subtotal, 2),
            'product_discount' => round($discountTotal, 2),
            'shipping' => round($shippingTotal, 2),
            'shipping_discount' => round($shippingDiscount, 2),
            'net_shipping' => round($netShippingTotal, 2),
            'tax' => $taxSummary,
            'total' => round($total, 2),
            'currency' => $currency,
        ];
        $storedPayload['_h4bs_promotion'] = [
            'state' => ! empty($promotion['applied']) ? 'pending' : 'not_applied',
            'promotion_id' => absint($promotion['promotion_id'] ?? 0),
            'code' => (string) ($promotion['code'] ?? ''),
            'label' => (string) ($promotion['label'] ?? ''),
            'type' => (string) ($promotion['type'] ?? ''),
            'discount' => round($discountTotal, 2),
            'shipping_discount' => round($shippingDiscount, 2),
        ];

        $inventoryReservation = $this->reserveCheckoutInventory(array_values($inventoryClaims));
        if ($inventoryReservation instanceof WP_Error) {
            return new WP_REST_Response(
                [
                    'message' => $inventoryReservation->get_error_message(),
                    'code' => $inventoryReservation->get_error_code(),
                    'inventory' => $inventoryReservation->get_error_data(),
                ],
                409
            );
        }
        $storedPayload['_h4bs_inventory'] = [
            'state' => $inventoryReservation ? 'reserved' : 'not_tracked',
            'claims' => $inventoryReservation,
            'reserved_at_gmt' => gmdate('c'),
        ];

        $orderNumber = 'H4-' . gmdate('Ymd-His') . '-' . wp_rand(1000, 9999);
        $orderInserted = $wpdb->insert(
            $ordersTable,
            [
                'order_number' => $orderNumber,
                'checkout_key' => $checkoutKey,
                'customer_email' => $storedCustomerEmail,
                'customer_user_id' => $customerUserId,
                'total'       => $total,
                'currency'    => $currency,
                'status'      => 'pending',
                'gateway'     => $gateway,
                'payload'     => wp_json_encode($storedPayload),
            ],
            ['%s', '%s', '%s', '%d', '%f', '%s', '%s', '%s', '%s']
        );
        $orderId = (int) $wpdb->insert_id;
        if ($orderInserted === false || $orderId <= 0) {
            $this->releaseCheckoutInventory($inventoryReservation);
            $duplicateOrder = $wpdb->get_row(
                $wpdb->prepare('SELECT id, order_number, status FROM %i WHERE checkout_key = %s LIMIT 1', $ordersTable, $checkoutKey),
                ARRAY_A
            );
            if (is_array($duplicateOrder)) {
                return new WP_REST_Response(
                    [
                        'message' => 'This checkout was already submitted. No duplicate order was created.',
                        'code' => 'h4bs_checkout_replay',
                        'order_id' => absint($duplicateOrder['id'] ?? 0),
                        'order_number' => sanitize_text_field((string) ($duplicateOrder['order_number'] ?? '')),
                        'status' => sanitize_key((string) ($duplicateOrder['status'] ?? 'pending')),
                    ],
                    409
                );
            }
            return new WP_REST_Response(['message' => 'The order could not be created. No inventory was charged.'], 500);
        }

        $promotionReservation = [];
        if (! empty($promotion['applied'])) {
            $claimedPromotion = $this->promotions->claim(
                absint($promotion['promotion_id'] ?? 0),
                $orderId,
                $customerEmail,
                $discountTotal + $shippingDiscount
            );
            if ($claimedPromotion instanceof WP_Error) {
                $wpdb->delete($ordersTable, ['id' => $orderId], ['%d']);
                $this->releaseCheckoutInventory($inventoryReservation);
                return new WP_REST_Response(
                    [
                        'message' => $claimedPromotion->get_error_message(),
                        'code' => $claimedPromotion->get_error_code(),
                        'promotion_code' => $promotionCode,
                    ],
                    409
                );
            }
            $promotionReservation = $claimedPromotion;
            $storedPayload['_h4bs_promotion']['state'] = 'reserved';
            $storedPayload['_h4bs_promotion']['redemption_id'] = absint($claimedPromotion['redemption_id'] ?? 0);
            if ($wpdb->update(
                $ordersTable,
                ['payload' => wp_json_encode($storedPayload)],
                ['id' => $orderId],
                ['%s'],
                ['%d']
            ) === false) {
                $this->promotions->releaseForOrder($orderId);
                $wpdb->delete($ordersTable, ['id' => $orderId], ['%d']);
                $this->releaseCheckoutInventory($inventoryReservation);
                return new WP_REST_Response(['message' => 'The promotion could not be attached to the order.'], 500);
            }
        }

        foreach ($orderItems as $item) {
            $itemInserted = $wpdb->insert(
                $itemsTable,
                [
                    'order_id'   => $orderId,
                    'product_id' => (int) $item['product_id'],
                    'variant_id' => (int) ($item['variant_id'] ?? 0),
                    'qty'        => (float) $item['qty'],
                    'unit_price' => (float) $item['unit_price'],
                    'payload'    => wp_json_encode($item['payload']),
                ],
                ['%d', '%d', '%d', '%f', '%f', '%s']
            );
            if ($itemInserted === false) {
                $wpdb->delete($itemsTable, ['order_id' => $orderId], ['%d']);
                $wpdb->delete($ordersTable, ['id' => $orderId], ['%d']);
                $this->promotions->releaseForOrder($orderId);
                $this->releaseCheckoutInventory($inventoryReservation);
                return new WP_REST_Response(['message' => 'The order items could not be created. No inventory was charged.'], 500);
            }
        }

        if ($customerDataMode !== 'stripe_only') {
            $customerId = $this->upsertCustomerRecord(
                $customerEmail,
                $customerUserId,
                $customerFirstName,
                $customerLastName,
                $finalCustomerRole,
                $finalCustomerStatus,
                $orderId
            );
            if ($customerFieldValues) {
                $fieldSave = $customerId > 0
                    ? $this->customerFields->saveValues($customerId, $customerFieldValues, $checkoutCustomerFieldDefinitions)
                    : new WP_Error('h4bs_customer_field_customer', __('The customer profile could not be created.', 'help4-builder-suite'));
                if ($fieldSave instanceof WP_Error) {
                    $wpdb->delete($itemsTable, ['order_id' => $orderId], ['%d']);
                    $wpdb->delete($ordersTable, ['id' => $orderId], ['%d']);
                    $this->promotions->releaseForOrder($orderId);
                    $this->releaseCheckoutInventory($inventoryReservation);
                    if ($customerId > 0) {
                        $this->upsertCustomerRecord(
                            $customerEmail,
                            $customerUserId,
                            $customerFirstName,
                            $customerLastName,
                            $finalCustomerRole,
                            $finalCustomerStatus
                        );
                    }
                    return new WP_REST_Response(
                        [
                            'message' => $fieldSave->get_error_message(),
                            'code' => $fieldSave->get_error_code(),
                        ],
                        500
                    );
                }
            }
        }

        $processorPayload = [
            'order' => [
                'id'        => $orderId,
                'number'    => $orderNumber,
                'status'    => 'pending',
                'gateway'   => $gateway,
                'requested_gateway' => $requestedGateway,
                'requested_route' => $requestedRoute,
                'currency'  => $currency,
                'subtotal'  => $subtotal,
                'discount_total' => $discountTotal + $shippingDiscount,
                'product_discount' => $discountTotal,
                'shipping_total' => $netShippingTotal,
                'shipping_original' => $shippingTotal,
                'shipping_discount' => $shippingDiscount,
                'tax_total' => $taxTotal,
                'total'     => $total,
            ],
            'customer' => [
                'email' => $customerEmail,
                'first_name' => $customerFirstName,
                'last_name' => $customerLastName,
                'user_id' => $customerUserId,
                'role' => $finalCustomerRole,
                'status' => $finalCustomerStatus,
                'fields' => $customerFieldSnapshot,
            ],
            'line_items'         => $processorLineItems,
            'contains_digital'   => $containsDigital,
            'downloadable_count' => $downloadableCount,
            'contains_shippable' => $containsShippable,
            'shippable_count'    => $shippableCount,
            'contains_booking'   => $containsBooking,
            'booking_count'      => $bookingCount,
            'contains_service'   => $containsService,
            'service_count'      => $serviceCount,
            'contains_subscription' => $containsSubscription,
            'subscription_count' => $subscriptionCount,
            'subscription_candidates' => $subscriptionCandidates,
            'requires_shipping_address' => $containsShippable,
            'shipping' => $shippingSummary,
            'promotion' => $promotion,
            'tax' => $taxSummary,
            'fulfillment_summary' => [
                'digital' => [
                    'contains' => $containsDigital,
                    'downloadable_count' => $downloadableCount,
                ],
                'shipping' => [
                    'contains' => $containsShippable,
                    'line_item_count' => $shippableCount,
                ],
                'booking' => [
                    'contains' => $containsBooking,
                    'line_item_count' => $bookingCount,
                ],
                'service' => [
                    'contains' => $containsService,
                    'line_item_count' => $serviceCount,
                ],
            ],
            'customer_data_mode' => $customerDataMode,
            'storage' => [
                'customer_email_stored_in_wordpress' => $customerDataMode !== 'stripe_only',
                'customer_payload_stored_in_wordpress' => $customerDataMode !== 'stripe_only',
            ],
            'customer_account' => $customerAccount,
            'routing' => $routingDecision,
            'inventory' => [
                'state' => $inventoryReservation ? 'reserved' : 'not_tracked',
                'claims' => $inventoryReservation,
            ],
            'raw_request'        => $payload,
        ];
        try {
            $processorPayload = apply_filters('h4bs_checkout_processor_payload', $processorPayload, $orderId, $gateway, $payload);
            if (! is_array($processorPayload)) {
                $processorPayload = [];
            }
            $processorRouter = new ProcessorRouter($settings);
            $routerInstructions = $processorRouter->route($orderId, $gateway, $gatewayData, $processorPayload);
        } catch (\Throwable $throwable) {
            $inventoryReleased = $this->releaseCheckoutInventory($inventoryReservation);
            $promotionReleased = $this->promotions->releaseForOrder($orderId);
            $storedPayload['_h4bs_inventory']['state'] = $inventoryReleased ? 'released' : 'release_failed';
            $storedPayload['_h4bs_inventory']['released_at_gmt'] = gmdate('c');
            if (! empty($promotionReservation)) {
                $storedPayload['_h4bs_promotion']['state'] = $promotionReleased ? 'released' : 'release_failed';
                $storedPayload['_h4bs_promotion']['released_at_gmt'] = gmdate('c');
            }
            $wpdb->update(
                $ordersTable,
                [
                    'status' => 'payment_failed',
                    'payload' => wp_json_encode($storedPayload),
                ],
                ['id' => $orderId],
                ['%s', '%s'],
                ['%d']
            );
            do_action('h4bs_commerce_processor_exception', $orderId, $gateway, $throwable);
            return new WP_REST_Response(
                [
                    'message' => 'Payment processing could not start. Reserved inventory was released.',
                    'order_id' => $orderId,
                    'order_number' => $orderNumber,
                ],
                502
            );
        }
        $orderStatus = $this->checkoutOrderStatusFromRouter($routerInstructions);
        $gatewayReference = $this->gatewayReferenceFromRouter($routerInstructions);

        $inventoryState = $inventoryReservation ? 'reserved' : 'not_tracked';
        if ($orderStatus === 'payment_failed' && $inventoryReservation) {
            $inventoryState = $this->releaseCheckoutInventory($inventoryReservation) ? 'released' : 'release_failed';
            $storedPayload['_h4bs_inventory']['released_at_gmt'] = gmdate('c');
        }
        if ($orderStatus === 'payment_failed' && $promotionReservation) {
            $storedPayload['_h4bs_promotion']['state'] = $this->promotions->releaseForOrder($orderId) ? 'released' : 'release_failed';
            $storedPayload['_h4bs_promotion']['released_at_gmt'] = gmdate('c');
        }
        $storedPayload['_h4bs_inventory']['state'] = $inventoryState;

        $orderUpdate = [
            'status' => $orderStatus,
            'payload' => wp_json_encode($storedPayload),
        ];
        $orderUpdateFormat = ['%s', '%s'];
        if ($gatewayReference !== '') {
            $orderUpdate['gateway_reference'] = $gatewayReference;
            $orderUpdateFormat[] = '%s';
        }
        $wpdb->update(
            $ordersTable,
            $orderUpdate,
            ['id' => $orderId],
            $orderUpdateFormat,
            ['%d']
        );

        $subscriptionRows = $this->createCheckoutSubscriptions(
            $subscriptionCandidates,
            $orderId,
            $orderNumber,
            $customerEmail,
            $gateway,
            $currency,
            $routerInstructions
        );

        $response = [
            'order_id'       => $orderId,
            'order_number'   => $orderNumber,
            'checkout_key'   => $checkoutKey,
            'status'         => $orderStatus,
            'subtotal'       => $subtotal,
            'discount_total' => $discountTotal + $shippingDiscount,
            'product_discount' => $discountTotal,
            'shipping_total' => $netShippingTotal,
            'shipping_original' => $shippingTotal,
            'shipping_discount' => $shippingDiscount,
            'tax_total'      => $taxTotal,
            'total'          => $total,
            'currency'       => $currency,
            'gateway'        => $gateway,
            'gateway_reference' => $gatewayReference,
            'requested_gateway' => $requestedGateway,
            'requested_route' => $requestedRoute,
            'line_items'     => $processorLineItems,
            'customer'       => [
                'email' => $customerEmail,
                'first_name' => $customerFirstName,
                'last_name' => $customerLastName,
                'user_id' => $customerUserId,
                'role' => $finalCustomerRole,
                'status' => $finalCustomerStatus,
            ],
            'contains_digital' => $containsDigital,
            'downloadable_items' => $downloadableCount,
            'contains_shippable' => $containsShippable,
            'shippable_items' => $shippableCount,
            'contains_booking' => $containsBooking,
            'booking_items' => $bookingCount,
            'contains_service' => $containsService,
            'service_items' => $serviceCount,
            'contains_subscription' => $containsSubscription,
            'subscription_items' => $subscriptionCount,
            'subscriptions' => $subscriptionRows,
            'requires_shipping_address' => $containsShippable,
            'shipping' => $shippingSummary,
            'promotion' => [
                'applied' => ! empty($promotion['applied']),
                'code' => (string) ($promotion['code'] ?? ''),
                'label' => (string) ($promotion['label'] ?? ''),
                'type' => (string) ($promotion['type'] ?? ''),
                'discount' => $discountTotal,
                'shipping_discount' => $shippingDiscount,
                'state' => (string) ($storedPayload['_h4bs_promotion']['state'] ?? 'not_applied'),
            ],
            'tax' => $taxSummary,
            'gateway_config' => [
                'configured' => ! empty($gatewayData['configured']),
                'slug'       => $gatewayData['slug'],
                'label'      => $gatewayData['label'],
                'customer_data_mode' => $customerDataMode,
            ],
            'processor_router' => $routerInstructions,
            'routing' => $routingDecision,
            'inventory' => [
                'state' => $inventoryState,
                'claims' => $inventoryReservation,
            ],
            'next_step'      => (string) ($routerInstructions['next_step']
                ?? (! empty($gatewayData['configured'])
                    ? 'Use your custom gateway processor hook h4bs_process_payment to complete transaction.'
                    : 'Configure this gateway in Help4 Builder settings or contact support for provider setup.')),
        ];

        /**
         * Fire payment processor hook for custom router integrations.
         *
         * @param int $orderId
         * @param string $gateway
         * @param array<string, mixed> $payload
         * @param array<string, mixed> $response
         * @param array<string, mixed> $processorPayload
         */
        do_action('h4bs_process_payment', $orderId, $gateway, $payload, $response, $processorPayload);
        do_action('h4bs_process_payment_router', $orderId, $gateway, $payload, $response, $processorPayload, $routerInstructions);
        if ($inventoryState === 'reserved') {
            do_action('h4bs_commerce_inventory_reserved', $inventoryReservation, $orderId, $response);
            foreach ($inventoryReservation as $inventoryLine) {
                if (! is_array($inventoryLine)) {
                    continue;
                }
                if ((int) ($inventoryLine['remaining'] ?? PHP_INT_MAX) <= (int) ($inventoryLine['low_stock_threshold'] ?? -1)) {
                    do_action('h4bs_commerce_low_stock', $inventoryLine, $orderId, $response);
                }
            }
        }

        /**
         * Allow integrators to attach processor-specific checkout instructions.
         *
         * @param array<string, mixed> $response
         * @param array<string, mixed> $payload
         * @param array<string, mixed> $processorPayload
         */
        $response = apply_filters('h4bs_checkout_response', $response, $payload, $processorPayload);

        return new WP_REST_Response($response, 201);
    }

    public function listGateways(): WP_REST_Response
    {
        $settings = $this->settings();
        $registry = new GatewayRegistry($settings);
        $gateways = $registry->all();

        if (current_user_can('manage_options')) {
            return new WP_REST_Response(['items' => $gateways]);
        }

        return new WP_REST_Response(['items' => $this->publicGatewayItems($gateways, $settings)]);
    }

    /**
     * @param array<int, array<string, mixed>> $gateways
     * @param array<string, mixed> $settings
     * @return array<int, array<string, string>>
     */
    private function publicGatewayItems(array $gateways, array $settings): array
    {
        $items = [];
        foreach ($gateways as $gateway) {
            $slug = sanitize_key((string) ($gateway['slug'] ?? ''));
            $label = sanitize_text_field((string) ($gateway['label'] ?? ''));

            if ($slug === '' || $label === '') {
                continue;
            }

            $items[] = [
                'slug'        => $slug,
                'label'       => $label,
                'description' => sanitize_text_field((string) ($gateway['description'] ?? '')),
                'contact_url' => esc_url_raw((string) ($gateway['contact_url'] ?? '')),
            ];
        }

        /**
         * Customize the anonymous gateway catalog without exposing processor endpoints,
         * account setting names, or configured-account flags by default.
         *
         * @param array<int, array<string, string>> $items
         * @param array<int, array<string, mixed>> $gateways
         * @param array<string, mixed> $settings
         */
        $items = apply_filters('h4bs_public_gateway_items', $items, $gateways, $settings);

        return is_array($items) ? array_values(array_filter($items, 'is_array')) : [];
    }

    public function listSubscriptions(): WP_REST_Response
    {
        global $wpdb;
        $table = $wpdb->prefix . 'h4_subscriptions';
        $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i ORDER BY updated_at DESC LIMIT 200', $table), ARRAY_A);
        return new WP_REST_Response(['items' => is_array($rows) ? $rows : []]);
    }

    public function listCustomers(): WP_REST_Response
    {
        global $wpdb;
        $table = $wpdb->prefix . 'h4_customers';
        $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i ORDER BY updated_at DESC LIMIT 500', $table), ARRAY_A);
        if (is_array($rows)) {
            foreach ($rows as &$row) {
                if (! is_array($row)) {
                    continue;
                }
                $row['customer_fields'] = $this->customerFields->getValues(absint($row['id'] ?? 0));
            }
            unset($row);
        }
        return new WP_REST_Response(['items' => is_array($rows) ? $rows : []]);
    }

    public function createSubscription(WP_REST_Request $request): WP_REST_Response
    {
        global $wpdb;

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            return new WP_REST_Response(['message' => 'Invalid payload'], 422);
        }

        $table = $wpdb->prefix . 'h4_subscriptions';
        $wpdb->insert(
            $table,
            [
                'customer_email' => sanitize_email((string) ($payload['customer_email'] ?? '')),
                'product_id'     => absint($payload['product_id'] ?? 0),
                'variant_id'     => absint($payload['variant_id'] ?? 0),
                'interval_code'  => sanitize_key((string) ($payload['interval_code'] ?? 'monthly')),
                'amount'         => (float) ($payload['amount'] ?? 0),
                'currency'       => strtoupper(sanitize_text_field((string) ($payload['currency'] ?? 'USD'))),
                'status'         => 'active',
                'gateway'        => sanitize_key((string) ($payload['gateway'] ?? 'stripe_router')),
                'payload'        => wp_json_encode($payload),
            ],
            ['%s', '%d', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
        );

        return new WP_REST_Response(
            [
                'id'      => (int) $wpdb->insert_id,
                'status'  => 'active',
                'message' => 'Subscription created for Help4 Commerce. Use checkout-created subscriptions for payment-router-linked recurring billing.',
            ],
            201
        );
    }

    /**
     * @param array<int, array<string, mixed>> $candidates
     * @param array<string, mixed> $routerInstructions
     * @return array<int, array<string, mixed>>
     */
    private function createCheckoutSubscriptions(
        array $candidates,
        int $orderId,
        string $orderNumber,
        string $customerEmail,
        string $gateway,
        string $currency,
        array $routerInstructions
    ): array {
        if (! $candidates) {
            return [];
        }

        global $wpdb;
        $table = $wpdb->prefix . 'h4_subscriptions';
        $status = $this->subscriptionStatusFromRouter($routerInstructions);
        $filteredStatus = apply_filters(
            'h4bs_checkout_subscription_status',
            $status,
            $candidates,
            $orderId,
            $routerInstructions
        );
        if (is_string($filteredStatus)) {
            $status = $this->sanitizeSubscriptionStatus($filteredStatus);
        }
        $routerSummary = $this->subscriptionRouterSummary($routerInstructions);
        $created = [];

        foreach ($candidates as $candidate) {
            if (! is_array($candidate)) {
                continue;
            }

            $subscription = is_array($candidate['subscription'] ?? null) ? $candidate['subscription'] : [];
            if (empty($subscription['is_subscription'])) {
                continue;
            }

            $productId = absint($candidate['product_id'] ?? 0);
            if ($productId <= 0) {
                continue;
            }

            $interval = $this->sanitizeSubscriptionInterval((string) ($subscription['interval_code'] ?? 'monthly'));
            $amount = max(0.0, (float) ($subscription['amount'] ?? 0));
            $rowPayload = [
                'source' => 'checkout',
                'order_id' => $orderId,
                'order_number' => sanitize_text_field($orderNumber),
                'line_item' => [
                    'product_id' => $productId,
                    'variant_id' => absint($candidate['variant_id'] ?? 0),
                    'sku' => sanitize_text_field((string) ($candidate['sku'] ?? '')),
                    'variant_sku' => sanitize_text_field((string) ($candidate['variant_sku'] ?? '')),
                    'name' => sanitize_text_field((string) ($candidate['name'] ?? '')),
                    'qty' => max(0.001, (float) ($candidate['qty'] ?? 1)),
                ],
                'subscription' => [
                    'interval_code' => $interval,
                    'interval_count' => max(1, min(36, (int) ($subscription['interval_count'] ?? 1))),
                    'trial_days' => max(0, min(365, (int) ($subscription['trial_days'] ?? 0))),
                    'setup_fee' => max(0.0, (float) ($subscription['setup_fee'] ?? 0)),
                    'auto_renew' => ! array_key_exists('auto_renew', $subscription) || rest_sanitize_boolean($subscription['auto_renew']),
                ],
                'router' => $routerSummary,
                'created_at' => gmdate('c'),
            ];

            $wpdb->insert(
                $table,
                [
                    'customer_email' => sanitize_email($customerEmail),
                    'product_id'     => $productId,
                    'variant_id'     => absint($candidate['variant_id'] ?? 0),
                    'interval_code'  => $interval,
                    'amount'         => $amount,
                    'currency'       => strtoupper(sanitize_text_field($currency)),
                    'status'         => $status,
                    'gateway'        => sanitize_key($gateway),
                    'payload'        => wp_json_encode($rowPayload),
                ],
                ['%s', '%d', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
            );

            $created[] = [
                'id' => (int) $wpdb->insert_id,
                'status' => $status,
                'product_id' => $productId,
                'variant_id' => absint($candidate['variant_id'] ?? 0),
                'interval_code' => $interval,
                'interval_count' => (int) $rowPayload['subscription']['interval_count'],
                'amount' => $amount,
                'currency' => strtoupper(sanitize_text_field($currency)),
                'gateway' => sanitize_key($gateway),
            ];
        }

        /**
         * Fires after checkout-created subscriptions are stored.
         *
         * @param array<int, array<string, mixed>> $created
         * @param int $orderId
         * @param array<string, mixed> $routerInstructions
         */
        do_action('h4bs_checkout_subscriptions_created', $created, $orderId, $routerInstructions);

        return $created;
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderProductsShortcode(array $atts): string
    {
        $settings = $this->settings();

        $atts = shortcode_atts(
            [
                'limit'  => 20,
                'search' => '',
                'status' => 'active',
                'search_mode' => 'prefix',
                'sort' => 'updated_desc',
                'min_price' => '',
                'max_price' => '',
                'in_stock_only' => '0',
                'show_filters' => '1',
                'cursor' => '1',
                'query_prefix' => 'h4p',
                'bg_color' => '',
                'bg_gradient' => '',
                'bg_image' => '',
                'bg_size' => 'cover',
                'bg_position' => 'center center',
                'bg_repeat' => 'no-repeat',
                'bg_attachment' => 'scroll',
                'panel_bg' => '',
                'panel_border' => '',
                'panel_padding' => '',
                'panel_radius' => '',
                'card_style' => (string) ($settings['commerce_product_card_style'] ?? 'overlay'),
                'hover' => (string) ($settings['commerce_product_hover'] ?? 'lift'),
                'columns' => (string) ($settings['commerce_product_columns_desktop'] ?? '4'),
                'columns_tablet' => (string) ($settings['commerce_product_columns_tablet'] ?? '2'),
                'columns_mobile' => (string) ($settings['commerce_product_columns_mobile'] ?? '1'),
                'filter_style' => (string) ($settings['commerce_filter_style'] ?? 'toolbar'),
                'show_images' => (string) ($settings['commerce_show_product_image'] ?? '1'),
                'show_quick_view' => (string) ($settings['commerce_show_quick_view'] ?? '1'),
                'show_compare' => (string) ($settings['commerce_show_compare_action'] ?? '1'),
                'show_stock_badges' => (string) ($settings['commerce_show_stock_badge'] ?? '1'),
                'show_sku' => (string) ($settings['commerce_show_sku'] ?? '1'),
                'show_type_badges' => (string) ($settings['commerce_show_product_type_badges'] ?? '1'),
                'show_mobile_toolbar' => (string) ($settings['commerce_sticky_mobile_toolbar'] ?? '1'),
                'show_price_watch' => (string) ($settings['commerce_show_price_watch_prompt'] ?? '1'),
                'show_waitlist' => (string) ($settings['commerce_show_waitlist_prompt'] ?? '1'),
                'request_anchor' => 'h4bs-contact',
            ],
            $atts,
            'h4_products'
        );

        $queryPrefix = sanitize_key((string) $atts['query_prefix']);
        if ($queryPrefix === '') {
            $queryPrefix = 'h4p';
        }

        $search = sanitize_text_field((string) $atts['search']);
        $status = sanitize_key((string) $atts['status']);
        $searchMode = sanitize_key((string) $atts['search_mode']);
        if (! in_array($searchMode, ['prefix', 'contains', 'exact'], true)) {
            $searchMode = 'prefix';
        }
        $sort = $this->sanitizeCatalogSort((string) $atts['sort']);
        $limit = max(1, min(100, (int) $atts['limit']));
        $minPrice = max(0, (float) $atts['min_price']);
        $maxPrice = max(0, (float) $atts['max_price']);
        $inStockOnly = $this->truthyFlag($atts['in_stock_only'] ?? '0');
        $showFilters = $this->truthyFlag($atts['show_filters'] ?? '1');
        $cursorEnabled = $this->truthyFlag($atts['cursor'] ?? '1');
        $bgColor = $this->sanitizeCssColorLike((string) $atts['bg_color']);
        $bgGradient = $this->sanitizeCssGradient((string) $atts['bg_gradient']);
        $bgImage = esc_url_raw((string) $atts['bg_image']);
        $bgSize = $this->sanitizeCssBackgroundSize((string) $atts['bg_size']);
        $bgPosition = $this->sanitizeCssBackgroundPosition((string) $atts['bg_position']);
        $bgRepeat = $this->sanitizeCssBackgroundRepeat((string) $atts['bg_repeat']);
        $bgAttachment = $this->sanitizeCssBackgroundAttachment((string) $atts['bg_attachment']);
        $panelBg = $this->sanitizeCssColorLike((string) $atts['panel_bg']);
        $panelBorder = $this->sanitizeCssColorLike((string) $atts['panel_border']);
        $panelPadding = $this->sanitizeCssSpacing((string) $atts['panel_padding']);
        $panelRadius = $this->sanitizeCssSize((string) $atts['panel_radius']);
        $cardStyle = $this->sanitizeCommerceCardStyle((string) $atts['card_style']);
        $cardHover = $this->sanitizeCommerceHover((string) $atts['hover']);
        $filterStyle = $this->sanitizeCommerceFilterStyle((string) $atts['filter_style']);
        $columnsDesktop = $this->sanitizeCommerceColumns($atts['columns'] ?? '4', 1, 6);
        $columnsTablet = $this->sanitizeCommerceColumns($atts['columns_tablet'] ?? '2', 1, 4);
        $columnsMobile = $this->sanitizeCommerceColumns($atts['columns_mobile'] ?? '1', 1, 2);
        $showImages = $this->truthyFlag($atts['show_images'] ?? '1');
        $showQuickView = $this->truthyFlag($atts['show_quick_view'] ?? '1');
        $showCompare = $this->truthyFlag($atts['show_compare'] ?? '1');
        $showStockBadges = $this->truthyFlag($atts['show_stock_badges'] ?? '1');
        $showSku = $this->truthyFlag($atts['show_sku'] ?? '1');
        $showTypeBadges = $this->truthyFlag($atts['show_type_badges'] ?? '1');
        $showMobileToolbar = $this->truthyFlag($atts['show_mobile_toolbar'] ?? '1');
        $showPriceWatch = $this->truthyFlag($atts['show_price_watch'] ?? '1');
        $showWaitlist = $this->truthyFlag($atts['show_waitlist'] ?? '1');
        $requestAnchor = $this->sanitizeAnchorTarget((string) $atts['request_anchor']);
        $cursorUpdatedAt = '';
        $cursorId = 0;

        $backgroundLayers = [];
        if ($bgGradient !== '') {
            $backgroundLayers[] = $bgGradient;
        }
        if ($bgImage !== '') {
            $backgroundLayers[] = "url('{$bgImage}')";
        }

        $styleVars = [];
        if ($bgColor !== '') {
            $styleVars[] = '--h4bs-products-bg:' . $bgColor;
        }
        if ($backgroundLayers) {
            $styleVars[] = '--h4bs-products-bg-image:' . implode(', ', $backgroundLayers);
            $styleVars[] = '--h4bs-products-bg-size:' . $bgSize;
            $styleVars[] = '--h4bs-products-bg-position:' . $bgPosition;
            $styleVars[] = '--h4bs-products-bg-repeat:' . $bgRepeat;
            $styleVars[] = '--h4bs-products-bg-attachment:' . $bgAttachment;
        }
        if ($panelBg !== '') {
            $styleVars[] = '--h4bs-products-panel-bg:' . $panelBg;
        }
        if ($panelBorder !== '') {
            $styleVars[] = '--h4bs-products-panel-border:' . $panelBorder;
        }
        if ($panelPadding !== '') {
            $styleVars[] = '--h4bs-products-panel-padding:' . $panelPadding;
        }
        if ($panelRadius !== '') {
            $styleVars[] = '--h4bs-products-panel-radius:' . $panelRadius;
        }
        $styleVars[] = '--h4bs-product-columns:' . (string) $columnsDesktop;
        $styleVars[] = '--h4bs-product-columns-tablet:' . (string) $columnsTablet;
        $styleVars[] = '--h4bs-product-columns-mobile:' . (string) $columnsMobile;
        $catalogInlineStyle = implode(';', $styleVars);

        if ($showFilters) {
            $query = AdminRequest::queryArray();
            $searchKey = $queryPrefix . '_q';
            if (isset($query[$searchKey])) {
                $search = sanitize_text_field((string) $query[$searchKey]);
            }

            $statusKey = $queryPrefix . '_status';
            if (isset($query[$statusKey])) {
                $status = sanitize_key((string) $query[$statusKey]);
            }

            $modeKey = $queryPrefix . '_mode';
            if (isset($query[$modeKey])) {
                $searchMode = sanitize_key((string) $query[$modeKey]);
                if (! in_array($searchMode, ['prefix', 'contains', 'exact'], true)) {
                    $searchMode = 'prefix';
                }
            }

            $sortKey = $queryPrefix . '_sort';
            if (isset($query[$sortKey])) {
                $sort = $this->sanitizeCatalogSort((string) $query[$sortKey]);
            }

            $minPriceKey = $queryPrefix . '_min';
            if (isset($query[$minPriceKey])) {
                $minPrice = max(0, (float) $query[$minPriceKey]);
            }

            $maxPriceKey = $queryPrefix . '_max';
            if (isset($query[$maxPriceKey])) {
                $maxPrice = max(0, (float) $query[$maxPriceKey]);
            }

            $stockKey = $queryPrefix . '_stock';
            $inStockOnly = isset($query[$stockKey]) && (string) $query[$stockKey] !== '';

            $limitKey = $queryPrefix . '_limit';
            if (isset($query[$limitKey])) {
                $limit = max(1, min(100, (int) $query[$limitKey]));
            }

            $cursorUpdatedAtKey = $queryPrefix . '_cursor_u';
            if (isset($query[$cursorUpdatedAtKey])) {
                $cursorUpdatedAt = sanitize_text_field((string) $query[$cursorUpdatedAtKey]);
            }
            $cursorIdKey = $queryPrefix . '_cursor_i';
            if (isset($query[$cursorIdKey])) {
                $cursorId = absint($query[$cursorIdKey]);
            }
        }

        $listArgs = [
            'limit'  => $limit,
            'search' => $search,
            'status' => $status,
            'search_mode' => $searchMode,
            'sort' => $sort,
            'min_price' => $minPrice,
            'max_price' => $maxPrice,
            'in_stock_only' => $inStockOnly,
        ];
        if ($cursorEnabled) {
            $listArgs['cursor_updated_at'] = $cursorUpdatedAt;
            $listArgs['cursor_id'] = $cursorId;
        }

        $items = $this->products->list(
            $listArgs
        );

        $nextCursor = null;
        $last = end($items);
        if (is_array($last) && ! empty($last['updated_at']) && ! empty($last['id'])) {
            $nextCursor = [
                'updated_at' => (string) $last['updated_at'],
                'id' => (int) $last['id'],
            ];
        }
        $catalogId = 'h4bs-products-' . sanitize_html_class(wp_unique_id());
        $filterId = $catalogId . '-filters';
        $catalogClasses = [
            'h4bs-product-catalog',
            'h4bs-product-catalog--style-' . $cardStyle,
            'h4bs-product-catalog--hover-' . $cardHover,
            'h4bs-product-catalog--filters-' . $filterStyle,
        ];
        if ($styleVars) {
            $catalogClasses[] = 'h4bs-product-catalog--custom';
        }

        ob_start();
        ?>
        <div id="<?php echo esc_attr($catalogId); ?>" class="<?php echo esc_attr(implode(' ', $catalogClasses)); ?>"<?php echo $catalogInlineStyle !== '' ? ' style="' . esc_attr($catalogInlineStyle) . '"' : ''; ?>>
            <?php if ($showFilters) : ?>
                <?php
                $sortOptions = $this->catalogSortOptions();
                $searchKey = $queryPrefix . '_q';
                $statusKey = $queryPrefix . '_status';
                $modeKey = $queryPrefix . '_mode';
                $sortKey = $queryPrefix . '_sort';
                $minKey = $queryPrefix . '_min';
                $maxKey = $queryPrefix . '_max';
                $stockKey = $queryPrefix . '_stock';
                $limitKey = $queryPrefix . '_limit';
                $cursorUpdatedAtKey = $queryPrefix . '_cursor_u';
                $cursorIdKey = $queryPrefix . '_cursor_i';
                ?>
                <form id="<?php echo esc_attr($filterId); ?>" class="h4bs-product-toolbar" method="get">
                    <input type="search" name="<?php echo esc_attr($searchKey); ?>" value="<?php echo esc_attr($search); ?>" placeholder="<?php esc_attr_e('Search products', 'help4-builder-suite'); ?>">
                    <select name="<?php echo esc_attr($statusKey); ?>">
                        <option value=""><?php esc_html_e('All statuses', 'help4-builder-suite'); ?></option>
                        <option value="active" <?php selected($status, 'active'); ?>><?php esc_html_e('Active', 'help4-builder-suite'); ?></option>
                        <option value="draft" <?php selected($status, 'draft'); ?>><?php esc_html_e('Draft', 'help4-builder-suite'); ?></option>
                        <option value="archived" <?php selected($status, 'archived'); ?>><?php esc_html_e('Archived', 'help4-builder-suite'); ?></option>
                    </select>
                    <select name="<?php echo esc_attr($modeKey); ?>">
                        <option value="prefix" <?php selected($searchMode, 'prefix'); ?>><?php esc_html_e('Prefix', 'help4-builder-suite'); ?></option>
                        <option value="contains" <?php selected($searchMode, 'contains'); ?>><?php esc_html_e('Contains', 'help4-builder-suite'); ?></option>
                        <option value="exact" <?php selected($searchMode, 'exact'); ?>><?php esc_html_e('Exact', 'help4-builder-suite'); ?></option>
                    </select>
                    <select name="<?php echo esc_attr($sortKey); ?>">
                        <?php foreach ($sortOptions as $sortSlug => $label) : ?>
                            <option value="<?php echo esc_attr($sortSlug); ?>" <?php selected($sort, $sortSlug); ?>><?php echo esc_html($label); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <input type="number" step="0.01" min="0" name="<?php echo esc_attr($minKey); ?>" value="<?php echo esc_attr($minPrice > 0 ? (string) $minPrice : ''); ?>" placeholder="<?php esc_attr_e('Min', 'help4-builder-suite'); ?>">
                    <input type="number" step="0.01" min="0" name="<?php echo esc_attr($maxKey); ?>" value="<?php echo esc_attr($maxPrice > 0 ? (string) $maxPrice : ''); ?>" placeholder="<?php esc_attr_e('Max', 'help4-builder-suite'); ?>">
                    <input type="number" min="1" max="100" name="<?php echo esc_attr($limitKey); ?>" value="<?php echo esc_attr((string) $limit); ?>" placeholder="<?php esc_attr_e('Limit', 'help4-builder-suite'); ?>">
                    <label><input type="checkbox" name="<?php echo esc_attr($stockKey); ?>" value="1" <?php checked($inStockOnly); ?>> <?php esc_html_e('In stock only', 'help4-builder-suite'); ?></label>
                    <button type="submit"><?php esc_html_e('Apply Filters', 'help4-builder-suite'); ?></button>
                </form>
                <p class="h4bs-product-toolbar-note"><?php esc_html_e('Tip: cursor paging keeps large catalogs fast. Avoid deep offset paging for very large stores.', 'help4-builder-suite'); ?></p>
                <?php if ($cursorUpdatedAt !== '' && $cursorId > 0) : ?>
                    <p><a href="<?php echo esc_url(remove_query_arg([$cursorUpdatedAtKey, $cursorIdKey])); ?>"><?php esc_html_e('Back to first page', 'help4-builder-suite'); ?></a></p>
                <?php endif; ?>
            <?php endif; ?>

            <?php if (! $items) : ?>
                <p><?php esc_html_e('No products matched this filter set.', 'help4-builder-suite'); ?></p>
            <?php else : ?>
            <div class="h4bs-product-grid">
                <?php foreach ($items as $item) : ?>
                    <?php
                    $productId = absint($item['id'] ?? 0);
                    $payload = $this->decodeJsonArray((string) ($item['payload'] ?? ''));
                    $productName = sanitize_text_field((string) ($item['name'] ?? __('Untitled Product', 'help4-builder-suite')));
                    $sku = sanitize_text_field((string) ($item['sku'] ?? ''));
                    $stockQty = (int) ($item['stock_qty'] ?? 0);
                    $priceLabel = $this->formatProductPrice($item);
                    $priceHtml = $this->productPriceHtml($item, $payload);
                    $productType = $this->resolveProductType($payload, []);
                    $inventoryConfig = $this->normalizeInventoryConfig($payload);
                    $merchandising = $this->normalizeMerchandisingConfig($payload);
                    $summary = $this->productSummary($payload);
                    $categories = $this->productPayloadTextList($payload['categories'] ?? []);
                    $variants = ($showQuickView || $showTypeBadges || $showStockBadges || $productType === 'variable') && $productId > 0
                        ? $this->products->listVariants($productId)
                        : [];
                    $displayStockQty = $stockQty;
                    if (! empty($inventoryConfig['track_stock']) && $variants) {
                        $displayStockQty = array_sum(
                            array_map(
                                static fn(array $variant): int => (int) ($variant['stock_qty'] ?? 0),
                                array_filter($variants, 'is_array')
                            )
                        );
                    }
                    $inventorySnapshot = $this->publicInventorySnapshot($inventoryConfig, $displayStockQty);
                    $primaryUrl = $this->productPrimaryUrl($payload);
                    $imageHtml = $showImages ? $this->productCardImageHtml($payload, $productName) : '';
                    $compareKey = $this->productCompareKey($productId, $sku, $productName);
                    $cardClasses = ['h4bs-product-card', 'h4bs-product-card--' . $cardStyle];
                    if ($imageHtml === '') {
                        $cardClasses[] = 'h4bs-product-card--no-image';
                    }
                    if (($inventorySnapshot['status'] ?? '') === 'out_of_stock') {
                        $cardClasses[] = 'h4bs-product-card--out-of-stock';
                    }
                    ?>
                    <article class="<?php echo esc_attr(implode(' ', $cardClasses)); ?>" data-h4bs-product-card="<?php echo esc_attr($compareKey); ?>" data-h4bs-product-id="<?php echo esc_attr((string) $productId); ?>" data-h4bs-quantity-enabled="<?php echo esc_attr(! empty($inventorySnapshot['quantity_enabled']) ? '1' : '0'); ?>" data-h4bs-quantity-mode="<?php echo esc_attr((string) $inventorySnapshot['quantity_mode']); ?>" data-h4bs-merge-cart-lines="<?php echo esc_attr(! empty($inventorySnapshot['merge_cart_lines']) ? '1' : '0'); ?>">
                        <?php if ($imageHtml !== '') : ?>
                            <div class="h4bs-product-card-media">
                                <?php echo $imageHtml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns escaped image markup. ?>
                                <?php if ($showStockBadges || $showTypeBadges) : ?>
                                    <div class="h4bs-product-badges h4bs-product-badges--media">
                                        <?php echo $this->productBadgesHtml($productType, $displayStockQty, count($variants), $showStockBadges, $showTypeBadges, $inventorySnapshot, (string) $merchandising['badge']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns escaped badge markup. ?>
                                    </div>
                                <?php endif; ?>
                            </div>
                        <?php endif; ?>
                        <div class="h4bs-product-card-body">
                            <?php if ($imageHtml === '' && ($showStockBadges || $showTypeBadges)) : ?>
                                <div class="h4bs-product-badges">
                                    <?php echo $this->productBadgesHtml($productType, $displayStockQty, count($variants), $showStockBadges, $showTypeBadges, $inventorySnapshot, (string) $merchandising['badge']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns escaped badge markup. ?>
                                </div>
                            <?php endif; ?>
                            <div class="h4bs-product-card-heading">
                                <h3>
                                    <?php if ($primaryUrl !== '') : ?>
                                        <a href="<?php echo esc_url($primaryUrl); ?>"><?php echo esc_html($productName); ?></a>
                                    <?php else : ?>
                                        <?php echo esc_html($productName); ?>
                                    <?php endif; ?>
                                </h3>
                                <?php if ($categories) : ?>
                                    <p class="h4bs-product-categories"><?php echo esc_html(implode(' / ', array_slice($categories, 0, 3))); ?></p>
                                <?php endif; ?>
                            </div>
                            <?php if ($summary !== '') : ?>
                                <p class="h4bs-product-summary"><?php echo esc_html($summary); ?></p>
                            <?php endif; ?>
                            <div class="h4bs-product-meta">
                                <p class="h4bs-product-price"><?php echo $priceHtml; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns escaped price markup. ?></p>
                                <?php if ($showSku && $sku !== '') : ?>
                                    <?php
                                    $skuLabel = sprintf(
                                        /* translators: %s: product SKU. */
                                        __('SKU: %s', 'help4-builder-suite'),
                                        $sku
                                    );
                                    ?>
                                    <p class="h4bs-product-sku"><?php echo esc_html($skuLabel); ?></p>
                                <?php endif; ?>
                            </div>
                            <div class="h4bs-product-actions">
                                <?php if ($primaryUrl !== '') : ?>
                                    <a class="h4bs-product-action h4bs-product-action--primary" href="<?php echo esc_url($primaryUrl); ?>"><?php esc_html_e('View Product', 'help4-builder-suite'); ?></a>
                                <?php endif; ?>
                                <?php if ($showCompare) : ?>
                                    <button
                                        type="button"
                                        class="h4bs-product-action h4bs-product-compare-button"
                                        data-h4bs-compare-product="<?php echo esc_attr($compareKey); ?>"
                                        data-h4bs-compare-name="<?php echo esc_attr($productName); ?>"
                                        data-h4bs-compare-price="<?php echo esc_attr($priceLabel); ?>"
                                        data-h4bs-compare-type="<?php echo esc_attr($this->productTypeLabel($productType)); ?>"
                                    >
                                        <?php esc_html_e('Compare', 'help4-builder-suite'); ?>
                                    </button>
                                <?php endif; ?>
                            </div>
                            <?php if ($showQuickView) : ?>
                                <?php echo $this->productQuickViewHtml($payload, $variants, $summary, $productType, $displayStockQty, $requestAnchor, $showPriceWatch, $showWaitlist, $inventorySnapshot); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper returns escaped quick-view markup. ?>
                            <?php endif; ?>
                        </div>
                    </article>
                <?php endforeach; ?>
            </div>
            <?php if ($showCompare) : ?>
                <aside class="h4bs-product-compare-tray" data-h4bs-compare-tray hidden tabindex="-1" aria-label="<?php esc_attr_e('Product comparison tray', 'help4-builder-suite'); ?>">
                    <div class="h4bs-product-compare-tray-head">
                        <strong><?php esc_html_e('Compare products', 'help4-builder-suite'); ?></strong>
                        <button type="button" data-h4bs-compare-clear><?php esc_html_e('Clear', 'help4-builder-suite'); ?></button>
                    </div>
                    <div class="h4bs-screen-reader-text" data-h4bs-compare-status role="status" aria-live="polite" aria-atomic="true"></div>
                    <div class="h4bs-product-compare-list" data-h4bs-compare-list></div>
                </aside>
            <?php endif; ?>
            <?php if ($showMobileToolbar) : ?>
                <nav class="h4bs-commerce-mobile-toolbar" aria-label="<?php esc_attr_e('Commerce shortcuts', 'help4-builder-suite'); ?>">
                    <a href="#<?php echo esc_attr($catalogId); ?>"><?php esc_html_e('Products', 'help4-builder-suite'); ?></a>
                    <?php if ($showFilters) : ?>
                        <a href="#<?php echo esc_attr($filterId); ?>"><?php esc_html_e('Filters', 'help4-builder-suite'); ?></a>
                    <?php endif; ?>
                    <?php if ($showCompare) : ?>
                        <button type="button" data-h4bs-compare-open>
                            <?php esc_html_e('Compare', 'help4-builder-suite'); ?>
                            <span data-h4bs-compare-count>0</span>
                        </button>
                    <?php endif; ?>
                    <a href="#h4bs-checkout"><?php esc_html_e('Checkout', 'help4-builder-suite'); ?></a>
                </nav>
            <?php endif; ?>
            <?php endif; ?>

            <?php if ($showFilters && $nextCursor && $cursorEnabled && in_array($sort, ['updated_desc', 'updated_asc'], true)) : ?>
                <?php
                $nextArgs = [
                    $searchKey => $search,
                    $statusKey => $status,
                    $modeKey => $searchMode,
                    $sortKey => $sort,
                    $minKey => $minPrice > 0 ? (string) $minPrice : null,
                    $maxKey => $maxPrice > 0 ? (string) $maxPrice : null,
                    $limitKey => (string) $limit,
                    $stockKey => $inStockOnly ? '1' : null,
                    $cursorUpdatedAtKey => (string) $nextCursor['updated_at'],
                    $cursorIdKey => (string) $nextCursor['id'],
                ];
                $nextArgs = array_filter($nextArgs, static fn($value): bool => $value !== null && $value !== '');
                $nextUrl = add_query_arg($nextArgs);
                ?>
                <p class="h4bs-product-pager">
                    <a class="h4bs-product-next" href="<?php echo esc_url($nextUrl); ?>"><?php esc_html_e('Load Next Products', 'help4-builder-suite'); ?></a>
                </p>
            <?php endif; ?>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    private function sanitizeCommerceCardStyle(string $style): string
    {
        $style = sanitize_key($style);
        return in_array($style, ['clean', 'overlay', 'compact', 'list', 'showcase'], true) ? $style : 'overlay';
    }

    private function sanitizeCommerceHover(string $hover): string
    {
        $hover = sanitize_key($hover);
        return in_array($hover, ['none', 'lift', 'image_zoom', 'quick_actions'], true) ? $hover : 'lift';
    }

    private function sanitizeCommerceFilterStyle(string $style): string
    {
        $style = sanitize_key($style);
        return in_array($style, ['toolbar', 'compact', 'drawer'], true) ? $style : 'toolbar';
    }

    /**
     * @param mixed $value
     */
    private function sanitizeCommerceColumns($value, int $min, int $max): int
    {
        return max($min, min($max, (int) $value));
    }

    private function sanitizeInventoryPolicy(string $policy): string
    {
        $policy = sanitize_key($policy);
        return in_array($policy, ['deny', 'backorder_notify', 'backorder', 'preorder'], true)
            ? $policy
            : 'deny';
    }

    private function sanitizeCommerceDate(string $date): string
    {
        $date = sanitize_text_field($date);
        if ($date === '' || preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) !== 1) {
            return '';
        }

        [$year, $month, $day] = array_map('intval', explode('-', $date));
        return checkdate($month, $day, $year) ? $date : '';
    }

    /**
     * @param mixed $value
     * @return array<int, int>
     */
    private function sanitizeProductIdList($value): array
    {
        if (is_string($value)) {
            $value = preg_split('/[\s,]+/', $value) ?: [];
        }
        if (! is_array($value)) {
            return [];
        }

        return array_values(
            array_unique(
                array_filter(
                    array_map('absint', $value),
                    static fn(int $id): bool => $id > 0
                )
            )
        );
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function normalizeInventoryConfig(array $payload): array
    {
        $inventory = is_array($payload['inventory'] ?? null) ? $payload['inventory'] : [];
        $minQty = max(0.001, min(1000, (float) ($inventory['min_qty'] ?? 1)));
        $maxQty = max(0.0, min(1000, (float) ($inventory['max_qty'] ?? 0)));
        if ($maxQty > 0 && $maxQty < $minQty) {
            $maxQty = $minQty;
        }
        $quantityPolicy = [
            'quantity_enabled' => ! array_key_exists('quantity_enabled', $inventory)
                || rest_sanitize_boolean($inventory['quantity_enabled']),
            'sold_individually' => rest_sanitize_boolean($inventory['sold_individually'] ?? false),
            'min_qty' => $minQty,
            'max_qty' => $maxQty,
            'qty_step' => max(0.001, min(1000, (float) ($inventory['qty_step'] ?? 1))),
        ];
        $filteredQuantityPolicy = apply_filters(
            'h4bs_commerce_product_quantity_policy',
            $quantityPolicy,
            $payload
        );
        if (is_array($filteredQuantityPolicy)) {
            $quantityPolicy = array_merge($quantityPolicy, $filteredQuantityPolicy);
        }
        $quantityEnabled = rest_sanitize_boolean($quantityPolicy['quantity_enabled'] ?? true);
        $minQty = max(0.001, min(1000, (float) ($quantityPolicy['min_qty'] ?? 1)));
        $maxQty = max(0.0, min(1000, (float) ($quantityPolicy['max_qty'] ?? 0)));
        if ($maxQty > 0 && $maxQty < $minQty) {
            $maxQty = $minQty;
        }
        $qtyStep = max(0.001, min(1000, (float) ($quantityPolicy['qty_step'] ?? 1)));
        if (! $quantityEnabled) {
            $minQty = 1.0;
            $maxQty = 1.0;
            $qtyStep = 1.0;
        }

        return [
            // Legacy products remain untracked until a merchant explicitly enables inventory control.
            'track_stock' => rest_sanitize_boolean($inventory['track_stock'] ?? false),
            'policy' => $this->sanitizeInventoryPolicy((string) ($inventory['policy'] ?? 'deny')),
            'low_stock_threshold' => max(0, min(1000000000, (int) ($inventory['low_stock_threshold'] ?? 5))),
            'sold_individually' => rest_sanitize_boolean($quantityPolicy['sold_individually'] ?? false),
            'quantity_enabled' => $quantityEnabled,
            'quantity_mode' => $quantityEnabled ? 'selectable' : 'fixed_per_line',
            'fixed_quantity' => $quantityEnabled ? null : 1,
            'merge_cart_lines' => $quantityEnabled,
            'min_qty' => $minQty,
            'max_qty' => $maxQty,
            'qty_step' => $qtyStep,
            'preorder_available_on' => $this->sanitizeCommerceDate((string) ($inventory['preorder_available_on'] ?? '')),
            'preorder_message' => sanitize_text_field((string) ($inventory['preorder_message'] ?? '')),
        ];
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, float>
     */
    private function normalizePricingConfig(array $payload): array
    {
        $pricing = is_array($payload['pricing'] ?? null) ? $payload['pricing'] : [];
        return [
            'compare_at_price' => max(0.0, (float) ($pricing['compare_at_price'] ?? 0)),
            'cost' => max(0.0, (float) ($pricing['cost'] ?? 0)),
        ];
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function normalizeMerchandisingConfig(array $payload): array
    {
        $merchandising = is_array($payload['merchandising'] ?? null) ? $payload['merchandising'] : [];
        return [
            'badge' => sanitize_text_field((string) ($merchandising['badge'] ?? '')),
            'purchase_note' => sanitize_textarea_field((string) ($merchandising['purchase_note'] ?? '')),
            'related_product_ids' => $this->sanitizeProductIdList($merchandising['related_product_ids'] ?? []),
            'upsell_product_ids' => $this->sanitizeProductIdList($merchandising['upsell_product_ids'] ?? []),
            'cross_sell_product_ids' => $this->sanitizeProductIdList($merchandising['cross_sell_product_ids'] ?? []),
        ];
    }

    /**
     * @param array<string, mixed> $inventory
     */
    private function validateCheckoutQuantity(float $quantity, array $inventory): ?WP_Error
    {
        if (! is_finite($quantity) || $quantity <= 0 || $quantity > 1000) {
            return new WP_Error('h4bs_invalid_quantity', __('Choose a valid product quantity.', 'help4-builder-suite'));
        }
        if (empty($inventory['quantity_enabled'])) {
            if (abs($quantity - 1.0) > 0.000001) {
                return new WP_Error(
                    'h4bs_quantity_disabled',
                    __('This configured product is one item per cart line. Add another item separately instead of changing its quantity.', 'help4-builder-suite')
                );
            }
            return null;
        }
        if (! empty($inventory['sold_individually']) && $quantity > 1) {
            return new WP_Error('h4bs_sold_individually', __('This product is limited to one per order.', 'help4-builder-suite'));
        }

        $minimum = max(0.001, (float) ($inventory['min_qty'] ?? 1));
        if ($quantity + 0.000001 < $minimum) {
            return new WP_Error(
                'h4bs_quantity_minimum',
                sprintf(
                    /* translators: %s: minimum permitted product quantity. */
                    __('The minimum quantity for this product is %s.', 'help4-builder-suite'),
                    number_format_i18n($minimum, 3)
                )
            );
        }

        $maximum = max(0.0, (float) ($inventory['max_qty'] ?? 0));
        if ($maximum > 0 && $quantity - 0.000001 > $maximum) {
            return new WP_Error(
                'h4bs_quantity_maximum',
                sprintf(
                    /* translators: %s: maximum permitted product quantity. */
                    __('The maximum quantity for this product is %s.', 'help4-builder-suite'),
                    number_format_i18n($maximum, 3)
                )
            );
        }

        if (! empty($inventory['track_stock']) && abs($quantity - round($quantity)) > 0.000001) {
            return new WP_Error(
                'h4bs_inventory_whole_units',
                __('Tracked inventory must be purchased in whole units.', 'help4-builder-suite')
            );
        }

        $step = max(0.001, (float) ($inventory['qty_step'] ?? 1));
        $steps = ($quantity - $minimum) / $step;
        if (abs($steps - round($steps)) > 0.000001) {
            return new WP_Error(
                'h4bs_quantity_step',
                sprintf(
                    /* translators: 1: quantity step, 2: starting quantity. */
                    __('Choose quantities in steps of %1$s starting at %2$s.', 'help4-builder-suite'),
                    number_format_i18n($step, 3),
                    number_format_i18n($minimum, 3)
                )
            );
        }

        return null;
    }

    /**
     * @param array<string, mixed> $inventory
     * @return array<string, mixed>
     */
    private function publicInventorySnapshot(array $inventory, int $stockQuantity): array
    {
        $tracked = ! empty($inventory['track_stock']);
        $policy = $this->sanitizeInventoryPolicy((string) ($inventory['policy'] ?? 'deny'));
        $threshold = max(0, (int) ($inventory['low_stock_threshold'] ?? 5));
        $status = 'available';
        $label = __('Available', 'help4-builder-suite');
        $purchasable = true;

        if ($tracked && $stockQuantity > $threshold) {
            $status = 'in_stock';
            $label = __('In stock', 'help4-builder-suite');
        } elseif ($tracked && $stockQuantity > 0) {
            $status = 'low_stock';
            $label = __('Low stock', 'help4-builder-suite');
        } elseif ($tracked && $policy === 'preorder') {
            $status = 'preorder';
            $label = __('Preorder', 'help4-builder-suite');
        } elseif ($tracked && in_array($policy, ['backorder', 'backorder_notify'], true)) {
            $status = 'backorder';
            $label = __('Available on backorder', 'help4-builder-suite');
        } elseif ($tracked) {
            $status = 'out_of_stock';
            $label = __('Sold out', 'help4-builder-suite');
            $purchasable = false;
        }

        return [
            'track_stock' => $tracked,
            'policy' => $policy,
            'status' => $status,
            'label' => $label,
            'purchasable' => $purchasable,
            'stock_qty' => $tracked ? $stockQuantity : null,
            'low_stock_threshold' => $threshold,
            'sold_individually' => ! empty($inventory['sold_individually']),
            'quantity_enabled' => ! empty($inventory['quantity_enabled']),
            'quantity_mode' => ! empty($inventory['quantity_enabled']) ? 'selectable' : 'fixed_per_line',
            'fixed_quantity' => ! empty($inventory['quantity_enabled']) ? null : 1,
            'merge_cart_lines' => ! empty($inventory['quantity_enabled']),
            'min_qty' => max(0.001, (float) ($inventory['min_qty'] ?? 1)),
            'max_qty' => max(0.0, (float) ($inventory['max_qty'] ?? 0)),
            'qty_step' => max(0.001, (float) ($inventory['qty_step'] ?? 1)),
            'preorder_available_on' => $this->sanitizeCommerceDate((string) ($inventory['preorder_available_on'] ?? '')),
            'preorder_message' => sanitize_text_field((string) ($inventory['preorder_message'] ?? '')),
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $claims
     * @return array<int, array<string, mixed>>|WP_Error
     */
    private function reserveCheckoutInventory(array $claims): array|WP_Error
    {
        $reserved = [];
        foreach ($claims as $claim) {
            if (! is_array($claim)) {
                continue;
            }
            $productId = absint($claim['product_id'] ?? 0);
            $variantId = absint($claim['variant_id'] ?? 0);
            $quantity = max(0, (int) ($claim['quantity'] ?? 0));
            $policy = $this->sanitizeInventoryPolicy((string) ($claim['policy'] ?? 'deny'));
            if ($productId <= 0 || $quantity <= 0) {
                $this->releaseCheckoutInventory($reserved);
                return new WP_Error('h4bs_invalid_inventory_claim', __('Inventory could not be reserved for this order.', 'help4-builder-suite'));
            }

            $before = $this->products->stockQuantity($productId, $variantId);
            $allowBackorder = in_array($policy, ['backorder', 'backorder_notify', 'preorder'], true);
            if ($before === null || ! $this->products->claimStock($productId, $variantId, $quantity, $allowBackorder)) {
                $this->releaseCheckoutInventory($reserved);
                return new WP_Error(
                    'h4bs_inventory_unavailable',
                    __('A product just sold out or no longer has the requested quantity. Review the cart and try again.', 'help4-builder-suite'),
                    [
                        'product_id' => $productId,
                        'variant_id' => $variantId,
                        'requested' => $quantity,
                        'available' => $before,
                    ]
                );
            }

            $after = $this->products->stockQuantity($productId, $variantId);
            $reserved[] = [
                'product_id' => $productId,
                'variant_id' => $variantId,
                'quantity' => $quantity,
                'policy' => $policy,
                'low_stock_threshold' => max(0, (int) ($claim['low_stock_threshold'] ?? 5)),
                'name' => sanitize_text_field((string) ($claim['name'] ?? '')),
                'sku' => sanitize_text_field((string) ($claim['sku'] ?? '')),
                'before' => $before,
                'remaining' => $after,
            ];
        }

        return $reserved;
    }

    /**
     * @param array<int, array<string, mixed>> $reservations
     */
    private function releaseCheckoutInventory(array $reservations): bool
    {
        $released = true;
        foreach (array_reverse($reservations) as $reservation) {
            if (! is_array($reservation)) {
                continue;
            }
            $productId = absint($reservation['product_id'] ?? 0);
            $variantId = absint($reservation['variant_id'] ?? 0);
            $quantity = max(0, (int) ($reservation['quantity'] ?? 0));
            if (! $this->products->releaseStock($productId, $variantId, $quantity)) {
                $released = false;
            }
        }

        return $released;
    }

    private function sanitizeAnchorTarget(string $anchor): string
    {
        $anchor = trim($anchor);
        $anchor = ltrim($anchor, '#');
        $anchor = sanitize_html_class($anchor);
        return $anchor !== '' ? '#' . $anchor : '#h4bs-contact';
    }

    /**
     * @param array<string, mixed> $item
     */
    private function formatProductPrice(array $item): string
    {
        $currency = strtoupper(sanitize_text_field((string) ($item['currency'] ?? 'USD')));
        if ($currency === '') {
            $currency = 'USD';
        }

        return trim($currency . ' ' . number_format_i18n((float) ($item['price'] ?? 0), 2));
    }

    /**
     * @param array<string, mixed> $item
     * @param array<string, mixed> $payload
     */
    private function productPriceHtml(array $item, array $payload): string
    {
        $current = $this->formatProductPrice($item);
        $pricing = $this->normalizePricingConfig($payload);
        $compareAt = (float) $pricing['compare_at_price'];
        $price = max(0.0, (float) ($item['price'] ?? 0));
        if ($compareAt <= $price || $compareAt <= 0) {
            return esc_html($current);
        }

        $currency = strtoupper(sanitize_text_field((string) ($item['currency'] ?? 'USD')));
        if ($currency === '') {
            $currency = 'USD';
        }
        $compareLabel = trim($currency . ' ' . number_format_i18n($compareAt, 2));
        $savings = $compareAt > 0 ? (int) round((($compareAt - $price) / $compareAt) * 100) : 0;

        return sprintf(
            '<del>%1$s</del> <ins>%2$s</ins>%3$s',
            esc_html($compareLabel),
            esc_html($current),
            $savings > 0
                ? ' <span class="h4bs-product-savings">' . esc_html(sprintf(
                    /* translators: %d: percent saved. */
                    __('Save %d%%', 'help4-builder-suite'),
                    $savings
                )) . '</span>'
                : ''
        );
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function productSummary(array $payload): string
    {
        $candidate = (string) (
            $payload['short_description']
            ?? ($payload['summary']
            ?? ($payload['excerpt']
            ?? ($payload['description'] ?? '')))
        );
        $candidate = trim(wp_strip_all_tags($candidate));
        if ($candidate === '') {
            return '';
        }

        return wp_trim_words($candidate, 24, '...');
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function productCardImageHtml(array $payload, string $productName): string
    {
        $imageId = absint(
            $payload['featured_image_id']
            ?? ($payload['image_id']
            ?? ($payload['thumbnail_id'] ?? 0))
        );
        if ($imageId > 0) {
            $html = wp_get_attachment_image(
                $imageId,
                'medium_large',
                false,
                [
                    'class' => 'h4bs-product-image',
                    'loading' => 'lazy',
                    'decoding' => 'async',
                    'alt' => $productName,
                ]
            );
            if (is_string($html) && $html !== '') {
                return $html;
            }
        }

        $imageUrl = esc_url_raw(
            (string) (
                $payload['featured_image_url']
                ?? ($payload['image_url']
                ?? ($payload['thumbnail_url'] ?? ''))
            )
        );
        if ($imageUrl === '') {
            $galleryIds = is_array($payload['gallery_image_ids'] ?? null) ? $payload['gallery_image_ids'] : [];
            $firstGalleryId = absint($galleryIds[0] ?? 0);
            if ($firstGalleryId > 0) {
                $html = wp_get_attachment_image(
                    $firstGalleryId,
                    'medium_large',
                    false,
                    [
                        'class' => 'h4bs-product-image',
                        'loading' => 'lazy',
                        'decoding' => 'async',
                        'alt' => $productName,
                    ]
                );
                if (is_string($html) && $html !== '') {
                    return $html;
                }
            }

            $galleryUrls = is_array($payload['gallery_urls'] ?? null) ? $payload['gallery_urls'] : [];
            $imageUrl = esc_url_raw((string) ($galleryUrls[0] ?? ''));
        }

        if ($imageUrl === '') {
            return '';
        }

        return sprintf(
            '<img class="h4bs-product-image" src="%s" alt="%s" loading="lazy" decoding="async">',
            esc_url($imageUrl),
            esc_attr($productName)
        );
    }

    /**
     * @param mixed $raw
     * @return array<int, string>
     */
    private function productPayloadTextList($raw): array
    {
        if (is_string($raw)) {
            $raw = preg_split('/[\r\n,]+/', $raw) ?: [];
        }
        if (! is_array($raw)) {
            return [];
        }
        if (! array_is_list($raw)) {
            $hasLabelShape = isset($raw['name']) || isset($raw['title']) || isset($raw['label']) || isset($raw['slug']);
            if (! $hasLabelShape) {
                $flattened = [];
                foreach ($raw as $key => $value) {
                    if (! is_scalar($value)) {
                        continue;
                    }
                    $label = sanitize_text_field((string) $key);
                    $text = sanitize_text_field((string) $value);
                    if ($label !== '' && $text !== '') {
                        $flattened[] = $label . ': ' . $text;
                    }
                }
                return array_values(array_unique($flattened));
            }
            $raw = [$raw];
        }

        $items = [];
        foreach ($raw as $entry) {
            if (is_array($entry)) {
                $label = (string) (
                    $entry['name']
                    ?? ($entry['title']
                    ?? ($entry['label']
                    ?? ($entry['slug'] ?? '')))
                );
            } else {
                $label = (string) $entry;
            }
            $label = sanitize_text_field($label);
            if ($label !== '') {
                $items[] = $label;
            }
        }

        return array_values(array_unique($items));
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function productPrimaryUrl(array $payload): string
    {
        return esc_url_raw(
            (string) (
                $payload['external_url']
                ?? ($payload['product_url']
                ?? ($payload['permalink']
                ?? ($payload['url'] ?? '')))
            )
        );
    }

    private function productCompareKey(int $productId, string $sku, string $productName): string
    {
        if ($productId > 0) {
            return 'p-' . (string) $productId;
        }
        $candidate = $sku !== '' ? $sku : $productName;
        $candidate = sanitize_title($candidate);
        return $candidate !== '' ? 'p-' . $candidate : 'p-' . wp_generate_password(8, false, false);
    }

    private function productTypeLabel(string $type): string
    {
        return match ($this->sanitizeProductType($type)) {
            'subscription' => __('Subscription', 'help4-builder-suite'),
            'bundle' => __('Bundle', 'help4-builder-suite'),
            'digital' => __('Digital', 'help4-builder-suite'),
            'service' => __('Service', 'help4-builder-suite'),
            'booking' => __('Booking', 'help4-builder-suite'),
            'donation' => __('Donation', 'help4-builder-suite'),
            default => __('Product', 'help4-builder-suite'),
        };
    }

    private function productBadgesHtml(
        string $productType,
        int $stockQty,
        int $variantCount,
        bool $showStock,
        bool $showType,
        array $inventorySnapshot = [],
        string $customBadge = ''
    ): string {
        $badges = [];
        if ($showStock) {
            $stockStatus = sanitize_key((string) ($inventorySnapshot['status'] ?? ($stockQty > 0 ? 'in_stock' : 'out_of_stock')));
            $stockLabel = sanitize_text_field((string) ($inventorySnapshot['label'] ?? ($stockQty > 0 ? __('In stock', 'help4-builder-suite') : __('Sold out', 'help4-builder-suite'))));
            $badges[] = sprintf(
                '<span class="h4bs-product-badge h4bs-product-badge--stock %s">%s</span>',
                esc_attr('is-' . str_replace('_', '-', $stockStatus)),
                esc_html($stockLabel)
            );
        }
        $customBadge = sanitize_text_field($customBadge);
        if ($customBadge !== '') {
            $badges[] = sprintf(
                '<span class="h4bs-product-badge h4bs-product-badge--custom">%s</span>',
                esc_html($customBadge)
            );
        }
        if ($showType) {
            $badges[] = sprintf(
                '<span class="h4bs-product-badge h4bs-product-badge--type">%s</span>',
                esc_html($this->productTypeLabel($productType))
            );
            if ($variantCount > 0) {
                $variantCountLabel = sprintf(
                    /* translators: %d: number of product options or variants. */
                    _n('%d option', '%d options', $variantCount, 'help4-builder-suite'),
                    $variantCount
                );
                $badges[] = sprintf(
                    '<span class="h4bs-product-badge h4bs-product-badge--variants">%s</span>',
                    esc_html($variantCountLabel)
                );
            }
        }

        return implode('', $badges);
    }

    /**
     * @param array<string, mixed> $payload
     * @param array<int, array<string, mixed>> $variants
     */
    private function productQuickViewHtml(
        array $payload,
        array $variants,
        string $summary,
        string $productType,
        int $stockQty,
        string $requestAnchor,
        bool $showPriceWatch,
        bool $showWaitlist,
        array $inventorySnapshot = []
    ): string {
        $details = [];
        $details[] = '<p>' . esc_html($summary !== '' ? $summary : __('This product is ready for a focused product section, quote flow, or checkout path.', 'help4-builder-suite')) . '</p>';
        $details[] = '<dl class="h4bs-product-quick-facts">';
        $details[] = '<dt>' . esc_html__('Type', 'help4-builder-suite') . '</dt><dd>' . esc_html($this->productTypeLabel($productType)) . '</dd>';
        $availabilityLabel = sanitize_text_field((string) ($inventorySnapshot['label'] ?? ($stockQty > 0 ? __('In stock', 'help4-builder-suite') : __('Sold out', 'help4-builder-suite'))));
        $details[] = '<dt>' . esc_html__('Availability', 'help4-builder-suite') . '</dt><dd>' . esc_html($availabilityLabel) . '</dd>';
        $preorderDate = $this->sanitizeCommerceDate((string) ($inventorySnapshot['preorder_available_on'] ?? ''));
        $preorderMessage = sanitize_text_field((string) ($inventorySnapshot['preorder_message'] ?? ''));
        if ($preorderDate !== '') {
            $details[] = '<dt>' . esc_html__('Expected', 'help4-builder-suite') . '</dt><dd>' . esc_html($preorderDate) . '</dd>';
        }
        if ($preorderMessage !== '') {
            $details[] = '<dt>' . esc_html__('Fulfillment note', 'help4-builder-suite') . '</dt><dd>' . esc_html($preorderMessage) . '</dd>';
        }
        $details[] = '</dl>';

        if ($variants) {
            $variantLabels = [];
            foreach (array_slice($variants, 0, 6) as $variant) {
                $variantSku = sanitize_text_field((string) ($variant['sku'] ?? ''));
                $attributes = $this->decodeJsonArray((string) ($variant['attributes_json'] ?? '{}'));
                $parts = $this->productPayloadTextList($attributes);
                $label = $variantSku !== '' ? $variantSku : implode(' / ', $parts);
                if ($label === '') {
                    $label = sprintf(
                        /* translators: %d: fallback product option number. */
                        __('Option %d', 'help4-builder-suite'),
                        count($variantLabels) + 1
                    );
                }
                $variantLabels[] = $label;
            }
            if ($variantLabels) {
                $details[] = '<p class="h4bs-product-variant-list"><strong>' . esc_html__('Popular options:', 'help4-builder-suite') . '</strong> ' . esc_html(implode(', ', $variantLabels)) . '</p>';
            }
        }

        $promptLinks = [];
        if ($showWaitlist && isset($inventorySnapshot['purchasable']) && ! $inventorySnapshot['purchasable']) {
            $promptLinks[] = '<a href="' . esc_url($requestAnchor) . '">' . esc_html__('Request a restock notice', 'help4-builder-suite') . '</a>';
        }
        if ($showPriceWatch) {
            $promptLinks[] = '<a href="' . esc_url($requestAnchor) . '">' . esc_html__('Ask about pricing', 'help4-builder-suite') . '</a>';
        }
        if ($promptLinks) {
            $details[] = '<p class="h4bs-product-request-links">' . implode(' ', $promptLinks) . '</p>';
        }

        return '<details class="h4bs-product-quick-view"><summary>' . esc_html__('Quick view', 'help4-builder-suite') . '</summary><div>' . implode('', $details) . '</div></details>';
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderCheckoutShortcode(array $atts): string
    {
        $atts = shortcode_atts(
            [
                'product_id' => '0',
                'variant_id' => '0',
                'quantity' => '1',
                'title' => __('Secure Checkout', 'help4-builder-suite'),
                'button_label' => __('Continue to Payment', 'help4-builder-suite'),
            ],
            $atts,
            'h4_checkout'
        );
        $productId = absint($atts['product_id']);
        $variantId = absint($atts['variant_id']);
        $quantity = max(0.001, min(1000, (float) $atts['quantity']));
        $product = $productId > 0 ? $this->products->get($productId) : null;
        if (! is_array($product) || sanitize_key((string) ($product['status'] ?? '')) !== 'active') {
            return '<div class="h4bs-checkout-shell"><p role="alert">' . esc_html__('This checkout is not available.', 'help4-builder-suite') . '</p></div>';
        }

        $variant = null;
        if ($variantId > 0) {
            foreach ($this->products->listVariants($productId) as $candidate) {
                if (is_array($candidate) && absint($candidate['id'] ?? 0) === $variantId) {
                    $variant = $candidate;
                    break;
                }
            }
        }
        $unitPrice = is_array($variant) ? (float) ($variant['price'] ?? 0) : (float) ($product['price'] ?? 0);
        $currency = strtoupper(sanitize_text_field((string) ($product['currency'] ?? 'USD')));
        $productPayload = $this->decodeJsonArray((string) ($product['payload'] ?? ''));
        $isDigital = $this->detectDigitalProduct($productPayload, []);
        $isBooking = $this->detectBookingProduct($productPayload, []);
        $fulfillmentType = $this->resolveFulfillmentType($productPayload, [], $isDigital, $isBooking);
        $requiresShipping = $this->detectShippingRequirement($productPayload, [], $fulfillmentType, $isDigital, $isBooking);
        $inventory = $this->normalizeInventoryConfig($productPayload);
        $quantityEnabled = ! empty($inventory['quantity_enabled']);
        if (! $quantityEnabled) {
            $quantity = 1.0;
        }
        $formId = wp_unique_id('h4bs-checkout-');
        $settings = $this->settings();
        $registry = new GatewayRegistry($settings);
        $gateways = array_values(
            array_filter(
                $registry->all(),
                static fn(array $gateway): bool => ! empty($gateway['configured'])
            )
        );
        $filteredGateways = apply_filters('h4bs_checkout_public_gateways', $gateways, $product, $settings);
        if (is_array($filteredGateways)) {
            $gateways = array_values(array_filter($filteredGateways, 'is_array'));
        }
        $checkoutReady = $gateways !== [];
        $countries = CountryCatalog::all();
        $checkoutCustomerFields = $this->customerFields->listDefinitions(
            'checkout',
            [
                'product_ids' => [$productId],
                'product_types' => [sanitize_key((string) ($product['type'] ?? 'simple'))],
                'customer_roles' => is_user_logged_in() ? (array) wp_get_current_user()->roles : ['guest'],
                'order_subtotal' => $unitPrice * $quantity,
            ]
        );
        $customerFieldGroups = [];
        foreach ($checkoutCustomerFields as $definition) {
            $group = sanitize_text_field((string) ($definition['field_group'] ?? __('Additional details', 'help4-builder-suite')));
            $customerFieldGroups[$group][] = $definition;
        }

        ob_start();
        ?>
        <div class="h4bs-checkout-shell" data-h4bs-checkout-shell>
            <h3><?php echo esc_html((string) $atts['title']); ?></h3>
            <div class="h4bs-checkout-product">
                <strong><?php echo esc_html((string) ($product['name'] ?? '')); ?></strong>
                <span><?php echo esc_html($currency . ' ' . number_format_i18n($unitPrice, 2)); ?></span>
            </div>
            <form id="<?php echo esc_attr($formId); ?>" data-h4bs-checkout data-product-id="<?php echo esc_attr((string) $productId); ?>" data-variant-id="<?php echo esc_attr((string) $variantId); ?>" data-currency="<?php echo esc_attr($currency); ?>" data-unit-price="<?php echo esc_attr((string) $unitPrice); ?>" data-requires-shipping="<?php echo esc_attr($requiresShipping ? '1' : '0'); ?>" data-quantity-enabled="<?php echo esc_attr($quantityEnabled ? '1' : '0'); ?>" data-quantity-mode="<?php echo esc_attr((string) $inventory['quantity_mode']); ?>" data-fixed-quantity="<?php echo esc_attr((string) ($inventory['fixed_quantity'] ?? '')); ?>">
                <fieldset class="h4bs-checkout-fields">
                    <legend><?php esc_html_e('Contact and delivery details', 'help4-builder-suite'); ?></legend>
                    <p><label><?php esc_html_e('First Name', 'help4-builder-suite'); ?><input required autocomplete="given-name" maxlength="120" name="first_name" type="text"></label></p>
                    <p><label><?php esc_html_e('Last Name', 'help4-builder-suite'); ?><input required autocomplete="family-name" maxlength="120" name="last_name" type="text"></label></p>
                    <p class="h4bs-checkout-field-wide"><label><?php esc_html_e('Email', 'help4-builder-suite'); ?><input required autocomplete="email" maxlength="190" name="email" type="email"></label></p>
                    <p class="h4bs-checkout-field-wide"><label><?php esc_html_e('Address', 'help4-builder-suite'); ?><input autocomplete="address-line1" maxlength="190" name="address_1" type="text"<?php if ($requiresShipping) : ?> required<?php endif; ?>></label></p>
                    <p class="h4bs-checkout-field-wide"><label><?php esc_html_e('Apartment, suite, or unit', 'help4-builder-suite'); ?><input autocomplete="address-line2" maxlength="190" name="address_2" type="text"></label></p>
                    <p><label><?php esc_html_e('City', 'help4-builder-suite'); ?><input autocomplete="address-level2" maxlength="120" name="city" type="text"<?php if ($requiresShipping) : ?> required<?php endif; ?>></label></p>
                    <p><label><?php esc_html_e('State / Region', 'help4-builder-suite'); ?><input autocomplete="address-level1" maxlength="120" name="state" type="text"></label></p>
                    <p><label><?php esc_html_e('Postal Code', 'help4-builder-suite'); ?><input autocomplete="postal-code" maxlength="32" name="postcode" type="text"<?php if ($requiresShipping) : ?> required<?php endif; ?>></label></p>
                    <p><label><?php esc_html_e('Country / Region', 'help4-builder-suite'); ?><select required autocomplete="country" name="country">
                        <?php foreach ($countries as $countryCode => $countryLabel) : ?>
                            <option value="<?php echo esc_attr($countryCode); ?>" <?php selected($countryCode, 'US'); ?>><?php echo esc_html($countryLabel); ?></option>
                        <?php endforeach; ?>
                    </select></label></p>
                    <p><label><?php esc_html_e('Phone', 'help4-builder-suite'); ?><input autocomplete="tel" maxlength="40" name="phone" type="tel"></label></p>
                    <?php if ($quantityEnabled) : ?>
                        <p><label><?php esc_html_e('Quantity', 'help4-builder-suite'); ?><input required min="<?php echo esc_attr((string) $inventory['min_qty']); ?>"<?php if ((float) $inventory['max_qty'] > 0) : ?> max="<?php echo esc_attr((string) $inventory['max_qty']); ?>"<?php endif; ?> step="<?php echo esc_attr((string) $inventory['qty_step']); ?>" name="quantity" type="number" value="<?php echo esc_attr((string) $quantity); ?>"></label></p>
                    <?php endif; ?>
                    <p class="h4bs-checkout-field-wide"><label><?php esc_html_e('Promotion Code', 'help4-builder-suite'); ?><input autocapitalize="characters" autocomplete="off" maxlength="80" name="promotion_code" type="text"></label></p>
                    <p class="h4bs-checkout-field-wide"><label><?php esc_html_e('Payment Method', 'help4-builder-suite'); ?><select name="gateway" <?php disabled(! $checkoutReady); ?>>
                        <?php if (! $checkoutReady) : ?>
                            <option value=""><?php esc_html_e('No payment method is available', 'help4-builder-suite'); ?></option>
                        <?php endif; ?>
                        <?php foreach ($gateways as $gateway) : ?>
                            <option value="<?php echo esc_attr(sanitize_key((string) ($gateway['slug'] ?? ''))); ?>"><?php echo esc_html(sanitize_text_field((string) ($gateway['label'] ?? ''))); ?></option>
                        <?php endforeach; ?>
                    </select></label></p>
                </fieldset>
                <?php foreach ($customerFieldGroups as $groupLabel => $groupDefinitions) : ?>
                    <fieldset class="h4bs-checkout-fields h4bs-customer-fields-group">
                        <legend><?php echo esc_html($groupLabel); ?></legend>
                        <?php foreach ($groupDefinitions as $definition) : ?>
                            <?php $this->renderCustomerFieldControl($definition, $definition['settings']['default_value'] ?? '', true); ?>
                        <?php endforeach; ?>
                    </fieldset>
                <?php endforeach; ?>
                <?php if (! $checkoutReady) : ?>
                    <p class="h4bs-checkout-unavailable" role="status"><?php esc_html_e('Checkout is temporarily unavailable while payment setup is completed.', 'help4-builder-suite'); ?></p>
                <?php endif; ?>
                <button class="h4bs-checkout-submit" type="submit" <?php disabled(! $checkoutReady); ?>><?php echo esc_html((string) $atts['button_label']); ?></button>
                <div class="h4bs-checkout-status" data-h4bs-checkout-status aria-live="polite"></div>
                <dl class="h4bs-checkout-summary" data-h4bs-checkout-summary hidden></dl>
            </form>
        </div>
        <?php

        return (string) ob_get_clean();
    }

    public function renderCustomerAccountShortcode(): string
    {
        if (! is_user_logged_in()) {
            $loginUrl = wp_login_url((string) get_permalink());
            return '<div class="h4bs-customer-account"><p>'
                . esc_html__('Sign in to view and update your customer details.', 'help4-builder-suite')
                . '</p><p><a class="h4bs-button" href="' . esc_url($loginUrl) . '">'
                . esc_html__('Sign In', 'help4-builder-suite')
                . '</a></p></div>';
        }

        $user = wp_get_current_user();
        $customerId = $this->customerFields->findCustomerIdByEmail((string) $user->user_email);
        $definitions = array_values(
            array_filter(
                $this->customerFields->listDefinitions('account', ['user_roles' => (array) $user->roles]),
                static fn(array $definition): bool => ! empty($definition['customer_editable'])
                    || (string) ($definition['field_type'] ?? '') === 'calculated'
            )
        );
        $values = $customerId > 0 ? $this->customerFields->getValues($customerId) : [];
        $storedKeys = $customerId > 0 ? $this->customerFields->getStoredKeys($customerId) : [];
        $groups = [];
        foreach ($definitions as $definition) {
            $group = sanitize_text_field((string) ($definition['field_group'] ?? __('Customer details', 'help4-builder-suite')));
            $groups[$group][] = $definition;
        }
        $noticeKey = 'h4bs_customer_account_notice_' . (int) $user->ID;
        $notice = get_transient($noticeKey);
        if (is_array($notice)) {
            delete_transient($noticeKey);
        }

        ob_start();
        ?>
        <section class="h4bs-customer-account" aria-labelledby="h4bs-customer-account-title">
            <div class="h4bs-customer-account__header">
                <h2 id="h4bs-customer-account-title"><?php esc_html_e('Customer Details', 'help4-builder-suite'); ?></h2>
                <p><?php esc_html_e('Keep the information used for checkout, service delivery, and account support current.', 'help4-builder-suite'); ?></p>
            </div>
            <?php if (is_array($notice)) : ?>
                <div class="h4bs-customer-account__notice is-<?php echo esc_attr((string) ($notice['type'] ?? 'info')); ?>" role="status"><?php echo esc_html((string) ($notice['message'] ?? '')); ?></div>
            <?php endif; ?>
            <?php if (! $definitions) : ?>
                <p><?php esc_html_e('There are no customer details to update right now.', 'help4-builder-suite'); ?></p>
            <?php else : ?>
                <form method="post" data-h4bs-customer-fields-form>
                    <?php wp_nonce_field('h4bs_save_customer_account_fields', 'h4bs_customer_account_nonce'); ?>
                    <input type="hidden" name="h4bs_customer_account_action" value="save_customer_fields">
                    <?php foreach ($groups as $groupLabel => $groupDefinitions) : ?>
                        <fieldset class="h4bs-customer-account__group h4bs-checkout-fields">
                            <legend><?php echo esc_html($groupLabel); ?></legend>
                            <?php foreach ($groupDefinitions as $definition) : ?>
                                <?php $fieldKey = (string) ($definition['field_key'] ?? ''); ?>
                                <?php $this->renderCustomerFieldControl($definition, $values[$fieldKey] ?? '', false, in_array($fieldKey, $storedKeys, true)); ?>
                            <?php endforeach; ?>
                        </fieldset>
                    <?php endforeach; ?>
                    <button class="h4bs-button h4bs-customer-account__submit" type="submit"><?php esc_html_e('Save Customer Details', 'help4-builder-suite'); ?></button>
                </form>
            <?php endif; ?>
        </section>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * @param array<string, mixed> $definition
     */
    private function renderCustomerFieldControl(array $definition, mixed $value = '', bool $checkout = false, bool $hasStoredValue = false): void
    {
        $key = sanitize_key((string) ($definition['field_key'] ?? ''));
        if ($key === '') {
            return;
        }
        $label = (string) ($definition['label'] ?? $key);
        $type = sanitize_key((string) ($definition['field_type'] ?? 'text'));
        $settings = is_array($definition['settings'] ?? null) ? $definition['settings'] : [];
        $options = is_array($settings['options'] ?? null) ? $settings['options'] : [];
        $validation = is_array($settings['validation'] ?? null) ? $settings['validation'] : [];
        $calculation = is_array($settings['calculation'] ?? null) ? $settings['calculation'] : [];
        $condition = is_array($settings['condition'] ?? null) ? $settings['condition'] : [];
        $conditions = is_array($settings['conditions'] ?? null) ? array_values(array_filter($settings['conditions'], 'is_array')) : [];
        if (! $conditions && ! empty($condition['field'])) {
            $conditions = [$condition];
        }
        $conditionField = sanitize_key((string) ($condition['field'] ?? ''));
        $conditionOperator = sanitize_key((string) ($condition['operator'] ?? 'equals'));
        $conditionValue = is_scalar($condition['value'] ?? '') ? (string) $condition['value'] : '';
        $required = ! empty($definition['required']);
        $inputWidth = sanitize_key((string) ($settings['input_width'] ?? 'auto'));
        $wide = $inputWidth === 'full' || ($inputWidth === 'auto' && in_array($type, ['textarea', 'radio', 'multiselect', 'calculated'], true));
        $widthClass = in_array($inputWidth, ['full', 'half', 'third', 'two_thirds'], true) ? ' h4bs-customer-field--' . str_replace('_', '-', $inputWidth) : '';
        $inputId = wp_unique_id('h4bs-customer-field-' . $key . '-');
        $name = 'customer_fields[' . $key . ']';
        $descriptionId = $inputId . '-description';
        $instructions = (string) ($settings['instructions'] ?? '');
        $placeholder = (string) ($settings['placeholder'] ?? '');
        $sensitive = (string) ($definition['sensitivity'] ?? '') === 'sensitive';
        $calculated = $type === 'calculated';
        $displayValue = $sensitive ? '' : $value;
        $displayPlaceholder = $sensitive && ! $checkout
            ? __('Leave blank to keep the saved value', 'help4-builder-suite')
            : $placeholder;
        $controlRequired = ! $calculated && $required && (! $sensitive || $checkout);
        $selectedValues = is_array($displayValue) ? array_map('strval', $displayValue) : [(string) $displayValue];
        $autocomplete = substr(sanitize_text_field((string) ($settings['autocomplete'] ?? '')), 0, 80);
        $conditionsJson = wp_json_encode($conditions, JSON_UNESCAPED_SLASHES);
        $conditionMatch = sanitize_key((string) ($settings['condition_match'] ?? 'all'));
        $visibility = is_array($settings['visibility'] ?? null) ? $settings['visibility'] : [];
        $targetCountries = array_values(array_filter(array_map('strval', (array) ($visibility['countries'] ?? []))));
        $calculationFormula = $calculated && ! $sensitive ? (string) ($calculation['formula'] ?? '') : '';
        $calculationPrecision = max(0, min(6, absint($calculation['precision'] ?? 2)));
        $calculationPrefix = substr(sanitize_text_field((string) ($calculation['prefix'] ?? '')), 0, 32);
        $calculationSuffix = substr(sanitize_text_field((string) ($calculation['suffix'] ?? '')), 0, 32);
        $calculationValue = is_numeric($displayValue)
            ? $calculationPrefix . number_format((float) $displayValue, $calculationPrecision, '.', '') . $calculationSuffix
            : '';
        ?>
        <div class="<?php echo esc_attr(($wide ? 'h4bs-customer-field h4bs-checkout-field-wide' : 'h4bs-customer-field') . $widthClass); ?>" data-h4bs-customer-field-wrap data-condition-field="<?php echo esc_attr($conditionField); ?>" data-condition-operator="<?php echo esc_attr($conditionOperator); ?>" data-condition-value="<?php echo esc_attr($conditionValue); ?>" data-conditions="<?php echo esc_attr(is_string($conditionsJson) ? $conditionsJson : '[]'); ?>" data-condition-match="<?php echo esc_attr($conditionMatch); ?>" data-target-countries="<?php echo esc_attr(implode(',', $targetCountries)); ?>" data-min-subtotal="<?php echo esc_attr(isset($visibility['min_subtotal']) ? (string) $visibility['min_subtotal'] : ''); ?>" data-max-subtotal="<?php echo esc_attr(isset($visibility['max_subtotal']) ? (string) $visibility['max_subtotal'] : ''); ?>" data-required="<?php echo esc_attr($controlRequired ? '1' : '0'); ?>">
            <?php if ($calculated) : ?>
                <span class="h4bs-customer-field-calculated-label" id="<?php echo esc_attr($inputId . '-label'); ?>"><?php echo esc_html($label); ?></span>
                <output id="<?php echo esc_attr($inputId); ?>" class="h4bs-customer-field-calculated" data-h4bs-calculated-field="<?php echo esc_attr($key); ?>" data-calculation-formula="<?php echo esc_attr($calculationFormula); ?>" data-calculation-precision="<?php echo esc_attr((string) $calculationPrecision); ?>" data-calculation-prefix="<?php echo esc_attr($calculationPrefix); ?>" data-calculation-suffix="<?php echo esc_attr($calculationSuffix); ?>" data-calculation-sensitive="<?php echo esc_attr($sensitive ? '1' : '0'); ?>" aria-labelledby="<?php echo esc_attr($inputId . '-label'); ?>" aria-live="polite"><?php echo esc_html($sensitive ? __('Calculated securely after submission', 'help4-builder-suite') : ($calculationValue !== '' ? $calculationValue : __('Waiting for numeric values', 'help4-builder-suite'))); ?></output>
            <?php elseif (in_array($type, ['checkbox', 'toggle'], true)) : ?>
                <label class="h4bs-customer-field-choice<?php echo esc_attr($type === 'toggle' ? ' h4bs-customer-field-toggle' : ''); ?>" for="<?php echo esc_attr($inputId); ?>"><input id="<?php echo esc_attr($inputId); ?>" data-h4bs-customer-field="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($name); ?>" type="checkbox" value="1" <?php checked(! empty($displayValue)); ?><?php if ($controlRequired) : ?> required<?php endif; ?>> <span><?php echo esc_html($label); ?><?php if ($required) : ?> *<?php endif; ?></span></label>
            <?php elseif ($type === 'radio') : ?>
                <fieldset class="h4bs-customer-field-choices"<?php if ($instructions !== '') : ?> aria-describedby="<?php echo esc_attr($descriptionId); ?>"<?php endif; ?>>
                    <legend><?php echo esc_html($label); ?><?php if ($required) : ?> *<?php endif; ?></legend>
                    <?php foreach ($options as $optionValue => $optionLabel) : ?>
                        <label class="h4bs-customer-field-choice"><input data-h4bs-customer-field="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($name); ?>" type="radio" value="<?php echo esc_attr((string) $optionValue); ?>" <?php checked((string) $displayValue, (string) $optionValue); ?><?php if ($controlRequired) : ?> required<?php endif; ?>> <span><?php echo esc_html((string) $optionLabel); ?></span></label>
                    <?php endforeach; ?>
                </fieldset>
            <?php elseif ($type === 'textarea') : ?>
                <label for="<?php echo esc_attr($inputId); ?>"><?php echo esc_html($label); ?><?php if ($required) : ?> *<?php endif; ?></label>
                <textarea id="<?php echo esc_attr($inputId); ?>" data-h4bs-customer-field="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($name); ?>" rows="4" placeholder="<?php echo esc_attr($displayPlaceholder); ?>"<?php if ($controlRequired) : ?> required<?php endif; ?><?php if ($instructions !== '') : ?> aria-describedby="<?php echo esc_attr($descriptionId); ?>"<?php endif; ?>><?php echo esc_textarea((string) $displayValue); ?></textarea>
            <?php elseif (in_array($type, ['select', 'multiselect', 'country'], true)) : ?>
                <label for="<?php echo esc_attr($inputId); ?>"><?php echo esc_html($label); ?><?php if ($required) : ?> *<?php endif; ?></label>
                <?php $selectOptions = $type === 'country' ? CountryCatalog::all() : $options; ?>
                <select id="<?php echo esc_attr($inputId); ?>" data-h4bs-customer-field="<?php echo esc_attr($key); ?>" name="<?php echo esc_attr($type === 'multiselect' ? $name . '[]' : $name); ?>"<?php if ($type === 'multiselect') : ?> multiple size="5" data-min-selections="<?php echo esc_attr((string) ($validation['min_selections'] ?? 0)); ?>" data-max-selections="<?php echo esc_attr((string) ($validation['max_selections'] ?? 0)); ?>"<?php endif; ?><?php if ($controlRequired) : ?> required<?php endif; ?><?php if ($instructions !== '') : ?> aria-describedby="<?php echo esc_attr($descriptionId); ?>"<?php endif; ?><?php if ($autocomplete !== '') : ?> autocomplete="<?php echo esc_attr($autocomplete); ?>"<?php endif; ?>>
                    <?php if ($type !== 'multiselect') : ?><option value=""><?php esc_html_e('Choose an option', 'help4-builder-suite'); ?></option><?php endif; ?>
                    <?php foreach ($selectOptions as $optionValue => $optionLabel) : ?>
                        <option value="<?php echo esc_attr((string) $optionValue); ?>" <?php selected(in_array((string) $optionValue, $selectedValues, true)); ?>><?php echo esc_html((string) $optionLabel); ?></option>
                    <?php endforeach; ?>
                </select>
            <?php else : ?>
                <?php $htmlType = $sensitive ? 'password' : (in_array($type, ['email', 'url', 'number', 'date', 'time', 'color', 'range'], true) ? $type : ($type === 'datetime' ? 'datetime-local' : ($type === 'phone' ? 'tel' : 'text'))); ?>
                <label for="<?php echo esc_attr($inputId); ?>"><?php echo esc_html($label); ?><?php if ($required) : ?> *<?php endif; ?></label>
                <input id="<?php echo esc_attr($inputId); ?>" data-h4bs-customer-field="<?php echo esc_attr($key); ?>"<?php if ($type === 'range') : ?> data-h4bs-range-input<?php endif; ?> name="<?php echo esc_attr($name); ?>" type="<?php echo esc_attr($htmlType); ?>" value="<?php echo esc_attr((string) $displayValue); ?>" placeholder="<?php echo esc_attr($displayPlaceholder); ?>"<?php if ($controlRequired) : ?> required<?php endif; ?><?php if (isset($validation['min']) && $validation['min'] !== null) : ?> min="<?php echo esc_attr((string) $validation['min']); ?>"<?php endif; ?><?php if (isset($validation['max']) && $validation['max'] !== null) : ?> max="<?php echo esc_attr((string) $validation['max']); ?>"<?php endif; ?><?php if (isset($validation['step']) && $validation['step'] !== null) : ?> step="<?php echo esc_attr((string) $validation['step']); ?>"<?php endif; ?><?php if (! empty($validation['min_length'])) : ?> minlength="<?php echo esc_attr((string) $validation['min_length']); ?>"<?php endif; ?><?php if (! empty($validation['max_length'])) : ?> maxlength="<?php echo esc_attr((string) $validation['max_length']); ?>"<?php endif; ?><?php if (! empty($validation['date_min']) && in_array($type, ['date', 'datetime'], true)) : ?> min="<?php echo esc_attr((string) $validation['date_min']); ?>"<?php endif; ?><?php if (! empty($validation['date_max']) && in_array($type, ['date', 'datetime'], true)) : ?> max="<?php echo esc_attr((string) $validation['date_max']); ?>"<?php endif; ?><?php if ($instructions !== '') : ?> aria-describedby="<?php echo esc_attr($descriptionId); ?>"<?php endif; ?> autocomplete="<?php echo esc_attr($sensitive ? 'new-password' : ($autocomplete !== '' ? $autocomplete : 'on')); ?>">
                <?php if ($type === 'range') : ?><output class="h4bs-customer-field-range-value" for="<?php echo esc_attr($inputId); ?>" data-h4bs-range-output><?php echo esc_html((string) $displayValue); ?></output><?php endif; ?>
            <?php endif; ?>
            <?php if ($instructions !== '') : ?><small id="<?php echo esc_attr($descriptionId); ?>" class="description"><?php echo esc_html($instructions); ?></small><?php endif; ?>
            <?php if (! $checkout && (string) ($definition['sensitivity'] ?? '') === 'sensitive') : ?><small class="description"><?php esc_html_e('Encrypted at rest and omitted from search, routine exports, and order snapshots.', 'help4-builder-suite'); ?></small><?php endif; ?>
            <?php if (! $checkout && $sensitive && $hasStoredValue && ! $required) : ?><label class="h4bs-customer-field-clear"><input type="checkbox" name="clear_customer_fields[]" value="<?php echo esc_attr($key); ?>"> <?php esc_html_e('Remove the saved encrypted value', 'help4-builder-suite'); ?></label><?php endif; ?>
        </div>
        <?php
    }

    /**
     * @param array<string, mixed> $settings
     * @param array<int, string> $gatewayCatalog
     * @return array{routes: array<int, array<string, mixed>>, rules: array<int, array<string, mixed>>}
     */
    private function gatewayRouteConfig(array $settings, array $gatewayCatalog): array
    {
        $input = [
            'route_slug' => [],
            'route_name' => [],
            'route_primary_gateway' => [],
            'route_gateway_pool' => [],
            'route_priority' => [],
            'route_enabled' => [],
            'route_is_default' => [],
            'rule_name' => [],
            'rule_route_slug' => [],
            'rule_priority' => [],
            'rule_condition_type' => [],
            'rule_condition_operator' => [],
            'rule_condition_value' => [],
            'rule_gateway_mode' => [],
            'rule_gateway_single' => [],
            'rule_gateway_pool' => [],
            'rule_enabled' => [],
        ];

        $savedRoutes = is_array($settings['commerce_gateway_routes'] ?? null)
            ? $settings['commerce_gateway_routes']
            : [];
        foreach ($savedRoutes as $index => $route) {
            if (! is_array($route)) {
                continue;
            }
            $key = (string) $index;
            $input['route_slug'][$key] = (string) ($route['slug'] ?? '');
            $input['route_name'][$key] = (string) ($route['name'] ?? '');
            $input['route_primary_gateway'][$key] = (string) ($route['primary_gateway'] ?? '');
            $input['route_gateway_pool'][$key] = implode(',', array_map('strval', (array) ($route['gateway_pool'] ?? [])));
            $input['route_priority'][$key] = (string) ($route['priority'] ?? '100');
            if (! empty($route['enabled'])) {
                $input['route_enabled'][$key] = '1';
            }
            if (! empty($route['is_default'])) {
                $input['route_is_default'][$key] = '1';
            }
        }

        $savedRules = is_array($settings['commerce_gateway_rules'] ?? null)
            ? $settings['commerce_gateway_rules']
            : [];
        foreach ($savedRules as $index => $rule) {
            if (! is_array($rule)) {
                continue;
            }
            $key = (string) $index;
            $input['rule_name'][$key] = (string) ($rule['name'] ?? '');
            $input['rule_route_slug'][$key] = (string) ($rule['route_slug'] ?? '');
            $input['rule_priority'][$key] = (string) ($rule['priority'] ?? '100');
            $input['rule_condition_type'][$key] = (string) ($rule['condition_type'] ?? 'always');
            $input['rule_condition_operator'][$key] = (string) ($rule['condition_operator'] ?? 'eq');
            $input['rule_condition_value'][$key] = (string) ($rule['condition_value'] ?? '');
            $input['rule_gateway_mode'][$key] = (string) ($rule['gateway_mode'] ?? 'route_default');
            $input['rule_gateway_single'][$key] = (string) ($rule['gateway_single'] ?? '');
            $input['rule_gateway_pool'][$key] = implode(',', array_map('strval', (array) ($rule['gateway_pool'] ?? [])));
            if (! empty($rule['enabled'])) {
                $input['rule_enabled'][$key] = '1';
            }
        }

        return $this->sanitizeGatewayRouteConfig($input, $settings, $gatewayCatalog);
    }

    /**
     * @param array<string, mixed> $input
     * @param array<string, mixed> $settings
     * @param array<int, string> $gatewayCatalog
     * @return array{routes: array<int, array<string, mixed>>, rules: array<int, array<string, mixed>>}
     */
    private function sanitizeGatewayRouteConfig(array $input, array $settings, array $gatewayCatalog): array
    {
        $gatewayCatalog = array_values(
            array_unique(
                array_filter(
                    array_map(static fn(string $slug): string => sanitize_key($slug), $gatewayCatalog),
                    static fn(string $slug): bool => $slug !== ''
                )
            )
        );
        if (! $gatewayCatalog) {
            $gatewayCatalog = ['stripe_router', 'paypal', 'square', 'braintree', 'hosted_payments', 'authorize_net', 'wise'];
        }

        $routes = [];
        $routeSlugs = is_array($input['route_slug'] ?? null) ? $input['route_slug'] : [];
        $routeNames = is_array($input['route_name'] ?? null) ? $input['route_name'] : [];
        $routePrimaryGateways = is_array($input['route_primary_gateway'] ?? null) ? $input['route_primary_gateway'] : [];
        $routeGatewayPools = is_array($input['route_gateway_pool'] ?? null) ? $input['route_gateway_pool'] : [];
        $routePriorities = is_array($input['route_priority'] ?? null) ? $input['route_priority'] : [];
        $routeEnabled = is_array($input['route_enabled'] ?? null) ? $input['route_enabled'] : [];
        $routeDefaults = is_array($input['route_is_default'] ?? null) ? $input['route_is_default'] : [];

        foreach ($routeSlugs as $key => $rawSlug) {
            $slug = sanitize_key((string) $rawSlug);
            if ($slug === '') {
                continue;
            }

            $name = sanitize_text_field((string) ($routeNames[$key] ?? $slug));
            if ($name === '') {
                $name = strtoupper(str_replace('_', ' ', $slug));
            }

            $primaryGateway = $this->normalizeGatewaySlug((string) ($routePrimaryGateways[$key] ?? ''));
            if (! in_array($primaryGateway, $gatewayCatalog, true)) {
                $primaryGateway = '';
            }

            $pool = array_map([$this, 'normalizeGatewaySlug'], $this->parseCsvTokens((string) ($routeGatewayPools[$key] ?? '')));
            $pool = array_values(array_filter($pool, static fn(string $item): bool => in_array($item, $gatewayCatalog, true)));
            if ($primaryGateway !== '' && ! in_array($primaryGateway, $pool, true)) {
                array_unshift($pool, $primaryGateway);
            }
            if ($primaryGateway === '' && $pool) {
                $primaryGateway = (string) $pool[0];
            }
            if ($primaryGateway === '') {
                $primaryGateway = (string) $gatewayCatalog[0];
            }
            if (! in_array($primaryGateway, $pool, true)) {
                array_unshift($pool, $primaryGateway);
            }

            $routes[] = [
                'slug' => $slug,
                'name' => $name,
                'primary_gateway' => $primaryGateway,
                'gateway_pool' => array_values(array_unique($pool)),
                'priority' => max(1, min(9999, (int) ($routePriorities[$key] ?? 100))),
                'enabled' => ! empty($routeEnabled[$key]),
                'is_default' => ! empty($routeDefaults[$key]),
            ];
        }

        if (! $routes) {
            $routes = $this->defaultGatewayRoutes($gatewayCatalog);
        }

        $routeMap = [];
        foreach ($routes as $idx => $route) {
            $baseSlug = sanitize_key((string) ($route['slug'] ?? 'route_' . ($idx + 1)));
            if ($baseSlug === '') {
                $baseSlug = 'route_' . ($idx + 1);
            }
            $finalSlug = $baseSlug;
            $suffix = 2;
            while (isset($routeMap[$finalSlug])) {
                $finalSlug = $baseSlug . '_' . $suffix;
                $suffix++;
            }
            $routes[$idx]['slug'] = $finalSlug;
            $routeMap[$finalSlug] = $idx;
        }

        usort(
            $routes,
            static fn(array $a, array $b): int => ((int) ($a['priority'] ?? 0)) <=> ((int) ($b['priority'] ?? 0))
        );

        $defaultIndex = null;
        foreach ($routes as $idx => $route) {
            if (! empty($route['is_default']) && ! empty($route['enabled'])) {
                $defaultIndex = $idx;
                break;
            }
        }
        if ($defaultIndex === null) {
            foreach ($routes as $idx => $route) {
                if (! empty($route['enabled'])) {
                    $defaultIndex = $idx;
                    break;
                }
            }
        }
        if ($defaultIndex === null && $routes) {
            $defaultIndex = 0;
        }
        foreach ($routes as $idx => $route) {
            $routes[$idx]['is_default'] = ($defaultIndex !== null && $idx === $defaultIndex);
        }

        $routeSlugsCatalog = array_map(static fn(array $route): string => (string) ($route['slug'] ?? ''), $routes);
        $defaultRouteSlug = (string) ($routes[$defaultIndex ?? 0]['slug'] ?? ($routes[0]['slug'] ?? ''));

        $rules = [];
        $ruleNames = is_array($input['rule_name'] ?? null) ? $input['rule_name'] : [];
        $ruleRouteSlugs = is_array($input['rule_route_slug'] ?? null) ? $input['rule_route_slug'] : [];
        $rulePriorities = is_array($input['rule_priority'] ?? null) ? $input['rule_priority'] : [];
        $ruleConditionTypes = is_array($input['rule_condition_type'] ?? null) ? $input['rule_condition_type'] : [];
        $ruleConditionOperators = is_array($input['rule_condition_operator'] ?? null) ? $input['rule_condition_operator'] : [];
        $ruleConditionValues = is_array($input['rule_condition_value'] ?? null) ? $input['rule_condition_value'] : [];
        $ruleGatewayModes = is_array($input['rule_gateway_mode'] ?? null) ? $input['rule_gateway_mode'] : [];
        $ruleGatewaySingles = is_array($input['rule_gateway_single'] ?? null) ? $input['rule_gateway_single'] : [];
        $ruleGatewayPools = is_array($input['rule_gateway_pool'] ?? null) ? $input['rule_gateway_pool'] : [];
        $ruleEnabled = is_array($input['rule_enabled'] ?? null) ? $input['rule_enabled'] : [];

        $allowedConditionTypes = [
            'always',
            'product_id',
            'product_type',
            'fulfillment_type',
            'contains_digital',
            'contains_shippable',
            'contains_booking',
            'contains_service',
            'contains_subscription',
            'currency',
            'country',
            'state',
            'zip',
            'order_total',
        ];
        $allowedOperators = ['eq', 'neq', 'in', 'not_in', 'gte', 'lte', 'contains'];
        $allowedGatewayModes = ['route_default', 'single', 'multiple'];

        foreach ($ruleNames as $key => $rawName) {
            $name = sanitize_text_field((string) $rawName);
            $routeSlug = sanitize_key((string) ($ruleRouteSlugs[$key] ?? ''));
            if (! in_array($routeSlug, $routeSlugsCatalog, true)) {
                $routeSlug = $defaultRouteSlug;
            }

            $conditionType = sanitize_key((string) ($ruleConditionTypes[$key] ?? 'always'));
            if (! in_array($conditionType, $allowedConditionTypes, true)) {
                $conditionType = 'always';
            }

            $conditionOperator = sanitize_key((string) ($ruleConditionOperators[$key] ?? 'eq'));
            if (! in_array($conditionOperator, $allowedOperators, true)) {
                $conditionOperator = 'eq';
            }

            $conditionValue = sanitize_text_field((string) ($ruleConditionValues[$key] ?? ''));
            if ($conditionType !== 'always' && $conditionValue === '') {
                continue;
            }

            $gatewayMode = sanitize_key((string) ($ruleGatewayModes[$key] ?? 'route_default'));
            if (! in_array($gatewayMode, $allowedGatewayModes, true)) {
                $gatewayMode = 'route_default';
            }

            $gatewaySingle = $this->normalizeGatewaySlug((string) ($ruleGatewaySingles[$key] ?? ''));
            if (! in_array($gatewaySingle, $gatewayCatalog, true)) {
                $gatewaySingle = '';
            }

            $gatewayPool = array_map([$this, 'normalizeGatewaySlug'], $this->parseCsvTokens((string) ($ruleGatewayPools[$key] ?? '')));
            $gatewayPool = array_values(
                array_unique(
                    array_filter(
                        $gatewayPool,
                        static fn(string $gateway): bool => in_array($gateway, $gatewayCatalog, true)
                    )
                )
            );

            if ($gatewayMode === 'single' && $gatewaySingle === '') {
                $gatewayMode = 'route_default';
            }
            if ($gatewayMode === 'multiple' && ! $gatewayPool) {
                $gatewayMode = 'route_default';
            }

            $rules[] = [
                'name' => $name !== '' ? $name : ('Rule ' . (count($rules) + 1)),
                'route_slug' => $routeSlug,
                'priority' => max(1, min(9999, (int) ($rulePriorities[$key] ?? 100))),
                'condition_type' => $conditionType,
                'condition_operator' => $conditionOperator,
                'condition_value' => $conditionValue,
                'gateway_mode' => $gatewayMode,
                'gateway_single' => $gatewaySingle,
                'gateway_pool' => $gatewayPool,
                'enabled' => ! empty($ruleEnabled[$key]),
            ];
        }

        usort(
            $rules,
            static fn(array $a, array $b): int => ((int) ($a['priority'] ?? 0)) <=> ((int) ($b['priority'] ?? 0))
        );

        return [
            'routes' => $routes,
            'rules' => $rules,
        ];
    }

    /**
     * @param array<int, string> $gatewayCatalog
     * @return array<int, array<string, mixed>>
     */
    private function defaultGatewayRoutes(array $gatewayCatalog): array
    {
        $routes = [];
        $priority = 10;
        foreach ($gatewayCatalog as $index => $gateway) {
            $label = ucwords(str_replace('_', ' ', $gateway));
            $routes[] = [
                'slug' => sanitize_key($gateway . '_route'),
                'name' => $label . ' Route',
                'primary_gateway' => sanitize_key($gateway),
                'gateway_pool' => [sanitize_key($gateway)],
                'priority' => $priority,
                'enabled' => true,
                'is_default' => $index === 0,
            ];
            $priority += 10;
        }

        return $routes;
    }

    /**
     * @param array<string, mixed> $settings
     * @param array<int, array<string, mixed>> $gatewayCatalog
     * @param array<string, mixed> $context
     * @return array<string, mixed>
     */
    private function resolveGatewayRouteDecision(
        array $settings,
        array $gatewayCatalog,
        array $context,
        string $requestedGateway,
        string $requestedRoute
    ): array {
        $requestedGateway = $this->normalizeGatewaySlug($requestedGateway);
        $requestedRoute = $this->normalizeGatewayRouteSlug($requestedRoute);
        $gatewayMap = [];
        foreach ($gatewayCatalog as $gateway) {
            if (! is_array($gateway)) {
                continue;
            }
            $slug = sanitize_key((string) ($gateway['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            $gatewayMap[$slug] = $gateway;
        }

        $gatewaySlugs = array_keys($gatewayMap);
        $routeConfig = $this->gatewayRouteConfig($settings, $gatewaySlugs);
        $routes = $routeConfig['routes'];
        $rules = $routeConfig['rules'];

        $routeMap = [];
        foreach ($routes as $route) {
            if (! is_array($route)) {
                continue;
            }
            $slug = sanitize_key((string) ($route['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            $routeMap[$slug] = $route;
        }

        $selectedRoute = null;
        $selectedRule = null;
        $reason = 'fallback_default_route';

        if ($requestedRoute !== '' && isset($routeMap[$requestedRoute]) && ! empty($routeMap[$requestedRoute]['enabled'])) {
            $selectedRoute = $routeMap[$requestedRoute];
            $reason = 'requested_route';
        }

        if (! $selectedRoute) {
            foreach ($rules as $rule) {
                if (! is_array($rule) || empty($rule['enabled'])) {
                    continue;
                }
                if (! $this->routingRuleMatches($rule, $context)) {
                    continue;
                }
                $candidateRouteSlug = sanitize_key((string) ($rule['route_slug'] ?? ''));
                $candidateRoute = $routeMap[$candidateRouteSlug] ?? null;
                if (! is_array($candidateRoute) || empty($candidateRoute['enabled'])) {
                    continue;
                }
                $selectedRoute = $candidateRoute;
                $selectedRule = $rule;
                $reason = 'matched_rule';
                break;
            }
        }

        if (! $selectedRoute) {
            foreach ($routes as $route) {
                if (! empty($route['enabled']) && ! empty($route['is_default'])) {
                    $selectedRoute = $route;
                    break;
                }
            }
        }
        if (! $selectedRoute) {
            foreach ($routes as $route) {
                if (! empty($route['enabled'])) {
                    $selectedRoute = $route;
                    break;
                }
            }
        }
        if (! $selectedRoute && $routes) {
            $selectedRoute = $routes[0];
        }

        $gatewayCandidates = [];
        if (is_array($selectedRule)) {
            $mode = sanitize_key((string) ($selectedRule['gateway_mode'] ?? 'route_default'));
            if ($mode === 'single') {
                $candidate = sanitize_key((string) ($selectedRule['gateway_single'] ?? ''));
                if ($candidate !== '') {
                    $gatewayCandidates[] = $candidate;
                }
            } elseif ($mode === 'multiple') {
                foreach ((array) ($selectedRule['gateway_pool'] ?? []) as $candidate) {
                    $candidate = sanitize_key((string) $candidate);
                    if ($candidate !== '') {
                        $gatewayCandidates[] = $candidate;
                    }
                }
            }
        }

        if (! $gatewayCandidates && $requestedGateway !== '') {
            $gatewayCandidates[] = $requestedGateway;
            $reason = $reason === 'matched_rule' ? $reason : 'requested_gateway';
        }

        if (! $gatewayCandidates && is_array($selectedRoute)) {
            $primary = sanitize_key((string) ($selectedRoute['primary_gateway'] ?? ''));
            if ($primary !== '') {
                $gatewayCandidates[] = $primary;
            }
            foreach ((array) ($selectedRoute['gateway_pool'] ?? []) as $candidate) {
                $candidate = sanitize_key((string) $candidate);
                if ($candidate !== '') {
                    $gatewayCandidates[] = $candidate;
                }
            }
        }

        if (! $gatewayCandidates) {
            $gatewayCandidates = $gatewaySlugs;
        }

        $gatewayCandidates = array_values(
            array_unique(
                array_filter(
                    array_map(static fn(string $item): string => sanitize_key($item), $gatewayCandidates),
                    static fn(string $item): bool => isset($gatewayMap[$item])
                )
            )
        );
        if (! $gatewayCandidates) {
            $gatewayCandidates = $gatewaySlugs;
        }

        $resolvedGateway = '';
        foreach ($gatewayCandidates as $candidate) {
            if (! empty($gatewayMap[$candidate]['configured'])) {
                $resolvedGateway = $candidate;
                break;
            }
        }
        if ($resolvedGateway === '') {
            $resolvedGateway = (string) ($gatewayCandidates[0] ?? ($gatewaySlugs[0] ?? 'stripe_router'));
        }

        return [
            'requested_gateway' => $requestedGateway,
            'requested_route' => $requestedRoute,
            'gateway' => $resolvedGateway,
            'gateway_candidates' => $gatewayCandidates,
            'route' => is_array($selectedRoute) ? $selectedRoute : null,
            'rule' => is_array($selectedRule) ? $selectedRule : null,
            'reason' => $reason,
        ];
    }

    /**
     * @param array<string, mixed> $rule
     * @param array<string, mixed> $context
     */
    private function routingRuleMatches(array $rule, array $context): bool
    {
        $type = sanitize_key((string) ($rule['condition_type'] ?? 'always'));
        $operator = sanitize_key((string) ($rule['condition_operator'] ?? 'eq'));
        $rawValue = sanitize_text_field((string) ($rule['condition_value'] ?? ''));

        if ($type === 'always') {
            return true;
        }

        if ($type === 'order_total') {
            $actual = (float) ($context['order_total'] ?? 0);
            $expected = (float) $rawValue;
            return match ($operator) {
                'neq' => $actual !== $expected,
                'gte' => $actual >= $expected,
                'lte' => $actual <= $expected,
                default => $actual === $expected,
            };
        }

        if (in_array($type, ['contains_digital', 'contains_shippable', 'contains_booking', 'contains_service', 'contains_subscription'], true)) {
            $actual = ! empty($context[$type]);
            $expected = rest_sanitize_boolean($rawValue);
            if ($operator === 'neq' || $operator === 'not_in') {
                return $actual !== $expected;
            }
            return $actual === $expected;
        }

        if ($type === 'product_id') {
            $actualValues = array_map('strval', (array) ($context['product_ids'] ?? []));
            return $this->routingListMatches($actualValues, $operator, $rawValue);
        }
        if ($type === 'product_type') {
            $actualValues = array_map('strval', (array) ($context['product_types'] ?? []));
            return $this->routingListMatches($actualValues, $operator, $rawValue);
        }
        if ($type === 'fulfillment_type') {
            $actualValues = array_map('strval', (array) ($context['fulfillment_types'] ?? []));
            return $this->routingListMatches($actualValues, $operator, $rawValue);
        }

        $actualScalar = sanitize_text_field((string) ($context[$type] ?? ''));
        return $this->routingScalarMatches($actualScalar, $operator, $rawValue);
    }

    /**
     * @param array<int, string> $actualValues
     */
    private function routingListMatches(array $actualValues, string $operator, string $rawValue): bool
    {
        $actual = array_values(
            array_filter(
                array_map(
                    static fn(string $item): string => strtolower(trim($item)),
                    $actualValues
                ),
                static fn(string $item): bool => $item !== ''
            )
        );
        if (! $actual) {
            return false;
        }

        $expected = array_values(
            array_filter(
                array_map(
                    static fn(string $item): string => strtolower($item),
                    $this->parseCsvTokens($rawValue)
                ),
                static fn(string $item): bool => $item !== ''
            )
        );
        if (! $expected) {
            return false;
        }

        $hasIntersection = (bool) array_intersect($actual, $expected);
        if (in_array($operator, ['neq', 'not_in'], true)) {
            return ! $hasIntersection;
        }

        return $hasIntersection;
    }

    private function routingScalarMatches(string $actualValue, string $operator, string $rawValue): bool
    {
        $actual = strtolower(trim($actualValue));
        $singleExpected = strtolower(trim($rawValue));
        $expected = array_values(
            array_filter(
                array_map(
                    static fn(string $item): string => strtolower($item),
                    $this->parseCsvTokens($rawValue)
                ),
                static fn(string $item): bool => $item !== ''
            )
        );

        return match ($operator) {
            'neq' => $actual !== $singleExpected,
            'in' => in_array($actual, $expected, true),
            'not_in' => ! in_array($actual, $expected, true),
            'contains' => $singleExpected !== '' && str_contains($actual, $singleExpected),
            default => $actual === $singleExpected || in_array($actual, $expected, true),
        };
    }

    /**
     * @return array<int, string>
     */
    private function parseCsvTokens(string $value): array
    {
        if (trim($value) === '') {
            return [];
        }

        $parts = array_map(
            static fn(string $item): string => sanitize_key(trim($item)),
            explode(',', $value)
        );

        return array_values(array_filter($parts, static fn(string $item): bool => $item !== ''));
    }

    private function normalizeGatewaySlug(string $value): string
    {
        $slug = sanitize_key($value);
        if ($slug === 'godaddy_payments') {
            return 'hosted_payments';
        }

        return $slug;
    }

    private function normalizeGatewayRouteSlug(string $value): string
    {
        $slug = sanitize_key($value);
        if ($slug === 'godaddy_payments_route') {
            return 'hosted_payments_route';
        }

        return $slug;
    }

    private function sanitizeCssColorLike(string $value): string
    {
        $value = trim($value);
        if ($value === '' || preg_match('/[;{}<>]/', $value) === 1) {
            return '';
        }

        $hex = sanitize_hex_color($value);
        if (is_string($hex) && $hex !== '') {
            return $hex;
        }

        if (preg_match('/^(rgba?|hsla?)\(\s*[-\d.%\s,]+\)$/i', $value) === 1) {
            return $value;
        }

        if (preg_match('/^var\(--[a-z0-9_-]+\)$/i', $value) === 1) {
            return $value;
        }

        $allowedKeywords = ['transparent', 'currentcolor', 'inherit', 'initial', 'unset', 'white', 'black'];
        if (in_array(strtolower($value), $allowedKeywords, true)) {
            return strtolower($value);
        }

        return '';
    }

    private function sanitizeCssGradient(string $value): string
    {
        $value = trim($value);
        if ($value === '' || strlen($value) > 320 || preg_match('/[;{}<>]/', $value) === 1) {
            return '';
        }

        $normalized = strtolower($value);
        $isGradient = str_starts_with($normalized, 'linear-gradient(')
            || str_starts_with($normalized, 'radial-gradient(')
            || str_starts_with($normalized, 'conic-gradient(')
            || str_starts_with($normalized, 'repeating-linear-gradient(')
            || str_starts_with($normalized, 'repeating-radial-gradient(')
            || str_starts_with($normalized, 'repeating-conic-gradient(');

        if (! $isGradient || ! str_ends_with($normalized, ')')) {
            return '';
        }

        return $value;
    }

    private function sanitizeCssSpacing(string $value): string
    {
        $value = trim($value);
        if ($value === '' || strlen($value) > 96 || preg_match('/[;{}<>]/', $value) === 1) {
            return '';
        }

        if (
            preg_match(
                '/^(-?\d+(?:\.\d+)?(?:px|rem|em|%|vw|vh)?|auto)(\s+(-?\d+(?:\.\d+)?(?:px|rem|em|%|vw|vh)?|auto)){0,3}$/i',
                $value
            ) === 1
        ) {
            return $value;
        }

        return '';
    }

    private function sanitizeCssSize(string $value): string
    {
        $value = trim($value);
        if ($value === '' || strlen($value) > 32 || preg_match('/[;{}<>]/', $value) === 1) {
            return '';
        }

        if (preg_match('/^-?\d+(?:\.\d+)?(?:px|rem|em|%|vw|vh)?$/i', $value) === 1) {
            return $value;
        }

        return '';
    }

    private function sanitizeCssBackgroundSize(string $value): string
    {
        $value = strtolower(trim($value));
        if ($value === '') {
            return 'cover';
        }
        if (in_array($value, ['cover', 'contain', 'auto'], true)) {
            return $value;
        }
        if (preg_match('/^\d+(?:\.\d+)?(?:px|%|vw|vh|rem|em)(\s+\d+(?:\.\d+)?(?:px|%|vw|vh|rem|em))?$/i', $value) === 1) {
            return $value;
        }

        return 'cover';
    }

    private function sanitizeCssBackgroundPosition(string $value): string
    {
        $value = strtolower(trim($value));
        if ($value === '') {
            return 'center center';
        }
        if (strlen($value) > 48 || preg_match('/[;{}<>]/', $value) === 1) {
            return 'center center';
        }
        if (preg_match('/^[a-z0-9.%\s-]+$/', $value) === 1) {
            return $value;
        }

        return 'center center';
    }

    private function sanitizeCssBackgroundRepeat(string $value): string
    {
        $value = strtolower(trim($value));
        $allowed = ['no-repeat', 'repeat', 'repeat-x', 'repeat-y', 'space', 'round'];
        if (in_array($value, $allowed, true)) {
            return $value;
        }

        return 'no-repeat';
    }

    private function sanitizeCssBackgroundAttachment(string $value): string
    {
        $value = strtolower(trim($value));
        if (in_array($value, ['scroll', 'fixed', 'local'], true)) {
            return $value;
        }

        return 'scroll';
    }

    private function sanitizeCatalogSort(string $sort): string
    {
        $sort = sanitize_key($sort);
        $allowed = [
            'updated_desc',
            'updated_asc',
            'price_desc',
            'price_asc',
            'name_asc',
            'name_desc',
            'sku_asc',
            'sku_desc',
            'id_desc',
            'id_asc',
        ];

        if (! in_array($sort, $allowed, true)) {
            return 'updated_desc';
        }

        return $sort;
    }

    /**
     * @return array<string, string>
     */
    private function catalogSortOptions(): array
    {
        return [
            'updated_desc' => __('Newest Updated', 'help4-builder-suite'),
            'updated_asc' => __('Oldest Updated', 'help4-builder-suite'),
            'price_asc' => __('Price Low to High', 'help4-builder-suite'),
            'price_desc' => __('Price High to Low', 'help4-builder-suite'),
            'name_asc' => __('Name A-Z', 'help4-builder-suite'),
            'name_desc' => __('Name Z-A', 'help4-builder-suite'),
            'sku_asc' => __('SKU A-Z', 'help4-builder-suite'),
            'sku_desc' => __('SKU Z-A', 'help4-builder-suite'),
            'id_desc' => __('Newest ID', 'help4-builder-suite'),
            'id_asc' => __('Oldest ID', 'help4-builder-suite'),
        ];
    }

    /**
     * @param mixed $value
     */
    private function truthyFlag($value): bool
    {
        if (is_bool($value)) {
            return $value;
        }
        $value = strtolower(trim((string) $value));
        return ! in_array($value, ['', '0', 'false', 'no', 'off', 'null'], true);
    }

    /**
     * @return array<string, string>
     */
    private function availableCustomerRoles(): array
    {
        global $wp_roles;

        $available = [];
        if (isset($wp_roles) && is_object($wp_roles) && method_exists($wp_roles, 'get_names')) {
            $names = $wp_roles->get_names();
            if (is_array($names)) {
                foreach ($names as $role => $label) {
                    $roleKey = sanitize_key((string) $role);
                    if ($roleKey === '') {
                        continue;
                    }
                    $available[$roleKey] = sanitize_text_field((string) $label);
                }
            }
        }

        if (! isset($available['customer'])) {
            $available['customer'] = __('Customer', 'help4-builder-suite');
        }
        if (! isset($available['subscriber'])) {
            $available['subscriber'] = __('Subscriber', 'help4-builder-suite');
        }

        ksort($available);
        return $available;
    }

    private function sanitizeCustomerRole(string $role, bool $fallback = true): string
    {
        $role = sanitize_key($role);
        if ($role === '' && ! $fallback) {
            return '';
        }

        $availableRoles = $this->availableCustomerRoles();
        if (isset($availableRoles[$role])) {
            return $role;
        }

        if (isset($availableRoles['customer'])) {
            return 'customer';
        }

        $firstRole = array_key_first($availableRoles);
        if (is_string($firstRole) && $firstRole !== '') {
            return $firstRole;
        }

        return 'subscriber';
    }

    private function sanitizeCustomerStatus(string $status, bool $fallback = true): string
    {
        $status = sanitize_key($status);
        if ($status === '' && ! $fallback) {
            return '';
        }

        $allowed = ['active', 'vip', 'suspended', 'blocked'];
        if (in_array($status, $allowed, true)) {
            return $status;
        }

        return 'active';
    }

    /**
     * @return array<string, mixed>
     */
    private function ensureCustomerUser(
        string $email,
        string $firstName,
        string $lastName,
        string $role,
        string $status
    ): array {
        $email = sanitize_email($email);
        $role = $this->sanitizeCustomerRole($role);
        $status = $this->sanitizeCustomerStatus($status);
        if ($email === '') {
            return [
                'user_id' => 0,
                'created' => false,
                'updated' => false,
                'role' => $role,
                'status' => $status,
            ];
        }

        $existing = get_user_by('email', $email);
        return [
            'user_id' => $existing ? (int) $existing->ID : 0,
            'created' => false,
            'updated' => false,
            'role' => $role,
            'status' => $status,
        ];
    }

    private function generateCustomerLoginFromEmail(string $email): string
    {
        $base = sanitize_user((string) strstr($email, '@', true), true);
        if ($base === '') {
            $base = 'h4customer';
        }

        $candidate = $base;
        $suffix = 1;
        while (username_exists($candidate)) {
            $candidate = $base . $suffix;
            $suffix++;
            if ($suffix > 9999) {
                $candidate = $base . wp_rand(1000, 999999);
                break;
            }
        }

        return $candidate;
    }

    private function upsertCustomerRecord(
        string $email,
        int $userId,
        string $firstName,
        string $lastName,
        string $role,
        string $status,
        int $lastOrderId = 0,
        ?float $forcedTotalSpent = null,
        ?int $forcedOrderCount = null
    ): int {
        $email = sanitize_email($email);
        if ($email === '') {
            return 0;
        }

        global $wpdb;
        $customersTable = $wpdb->prefix . 'h4_customers';

        $role = $this->sanitizeCustomerRole($role);
        $status = $this->sanitizeCustomerStatus($status);
        $firstName = sanitize_text_field($firstName);
        $lastName = sanitize_text_field($lastName);
        $stats = $this->calculateCustomerStats($email, $userId);
        if ($forcedOrderCount !== null) {
            $stats['order_count'] = max(0, $forcedOrderCount);
        }
        if ($forcedTotalSpent !== null) {
            $stats['total_spent'] = max(0.0, $forcedTotalSpent);
        }
        if ($lastOrderId > 0) {
            $stats['last_order_id'] = $lastOrderId;
        }

        $data = [
            'wp_user_id' => $userId > 0 ? $userId : null,
            'email' => $email,
            'first_name' => $firstName,
            'last_name' => $lastName,
            'role' => $role,
            'status' => $status,
            'order_count' => max(0, (int) ($stats['order_count'] ?? 0)),
            'total_spent' => max(0.0, (float) ($stats['total_spent'] ?? 0)),
            'last_order_id' => max(0, (int) ($stats['last_order_id'] ?? 0)),
            'payload' => wp_json_encode(
                [
                    'source' => 'help4-commerce',
                    'updated_at' => gmdate('c'),
                ]
            ),
        ];

        $existing = $wpdb->get_row(
            $wpdb->prepare('SELECT id FROM %i WHERE email = %s LIMIT 1', $customersTable, $email),
            ARRAY_A
        );

        if (is_array($existing) && ! empty($existing['id'])) {
            $wpdb->update(
                $customersTable,
                $data,
                ['id' => absint($existing['id'])],
                ['%d', '%s', '%s', '%s', '%s', '%s', '%d', '%f', '%d', '%s'],
                ['%d']
            );
            return absint($existing['id']);
        }

        $inserted = $wpdb->insert(
            $customersTable,
            $data,
            ['%d', '%s', '%s', '%s', '%s', '%s', '%d', '%f', '%d', '%s']
        );
        return $inserted === false ? 0 : (int) $wpdb->insert_id;
    }

    /**
     * @return array{order_count:int,total_spent:float,last_order_id:int}
     */
    private function calculateCustomerStats(string $email, int $userId = 0): array
    {
        global $wpdb;
        $ordersTable = $wpdb->prefix . 'h4_orders';

        $email = sanitize_email($email);
        if ($email === '' && $userId <= 0) {
            return [
                'order_count' => 0,
                'total_spent' => 0.0,
                'last_order_id' => 0,
            ];
        }

        if ($userId > 0 && $email !== '') {
            $stats = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT COUNT(*) AS order_count, COALESCE(SUM(total), 0) AS total_spent, COALESCE(MAX(id), 0) AS last_order_id
                     FROM %i
                     WHERE customer_email = %s OR customer_user_id = %d",
                    $ordersTable,
                    $email,
                    $userId
                ),
                ARRAY_A
            );
        } elseif ($userId > 0) {
            $stats = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT COUNT(*) AS order_count, COALESCE(SUM(total), 0) AS total_spent, COALESCE(MAX(id), 0) AS last_order_id
                     FROM %i
                     WHERE customer_user_id = %d",
                    $ordersTable,
                    $userId
                ),
                ARRAY_A
            );
        } else {
            $stats = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT COUNT(*) AS order_count, COALESCE(SUM(total), 0) AS total_spent, COALESCE(MAX(id), 0) AS last_order_id
                     FROM %i
                     WHERE customer_email = %s",
                    $ordersTable,
                    $email
                ),
                ARRAY_A
            );
        }

        if (! is_array($stats)) {
            return [
                'order_count' => 0,
                'total_spent' => 0.0,
                'last_order_id' => 0,
            ];
        }

        return [
            'order_count' => max(0, (int) ($stats['order_count'] ?? 0)),
            'total_spent' => max(0.0, (float) ($stats['total_spent'] ?? 0)),
            'last_order_id' => max(0, (int) ($stats['last_order_id'] ?? 0)),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeJsonArray(string $json): array
    {
        $json = trim($json);
        if ($json === '') {
            return [];
        }

        $decoded = json_decode($json, true);
        return is_array($decoded) ? $decoded : [];
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     */
    private function detectDigitalProduct(array $productPayload, array $lineItem): bool
    {
        $digitalFlags = [
            $lineItem['is_digital'] ?? null,
            $lineItem['downloadable'] ?? null,
            $lineItem['virtual'] ?? null,
            $productPayload['is_digital'] ?? null,
            $productPayload['downloadable'] ?? null,
            $productPayload['virtual'] ?? null,
        ];

        foreach ($digitalFlags as $flag) {
            if (is_bool($flag) && $flag) {
                return true;
            }

            if (is_scalar($flag) && rest_sanitize_boolean($flag)) {
                return true;
            }
        }

        $type = sanitize_key((string) ($lineItem['type'] ?? ($productPayload['type'] ?? '')));
        if (in_array($type, ['digital', 'download', 'downloadable', 'virtual'], true)) {
            return true;
        }

        $rawSource = $productPayload['raw_source'] ?? null;
        if (is_array($rawSource)) {
            $sourceType = sanitize_key((string) ($rawSource['type'] ?? ''));
            if (in_array($sourceType, ['digital', 'download', 'downloadable', 'virtual'], true)) {
                return true;
            }
        }

        return $this->normalizeDownloads(
            $lineItem['downloads']
            ?? ($productPayload['downloads']
            ?? ($productPayload['download_urls']
            ?? (is_array($rawSource) ? ($rawSource['downloads'] ?? ($rawSource['download_urls'] ?? [])) : [])))
        ) !== [];
    }

    /**
     * @param mixed $raw
     * @return array<int, array<string, mixed>>
     */
    private function normalizeDownloads($raw): array
    {
        if (is_string($raw)) {
            $raw = trim($raw);
            if ($raw === '') {
                return [];
            }

            if ($raw[0] === '[' || $raw[0] === '{') {
                $decoded = json_decode($raw, true);
                if (is_array($decoded)) {
                    $raw = $decoded;
                }
            }

            if (! is_array($raw)) {
                $raw = preg_split('/[\r\n,]+/', $raw) ?: [];
            }
        }

        if (! is_array($raw)) {
            return [];
        }

        if (! array_is_list($raw)) {
            $raw = [$raw];
        }

        $normalized = [];
        foreach ($raw as $entry) {
            $url = '';
            $label = '';
            $expiresAt = '';
            $downloadLimit = 0;

            if (is_string($entry)) {
                $url = esc_url_raw($entry);
                if ($url !== '') {
                    $label = sanitize_text_field((string) wp_basename((string) wp_parse_url($url, PHP_URL_PATH)));
                }
            } elseif (is_array($entry)) {
                $url = esc_url_raw(
                    (string) (
                        $entry['url']
                        ?? ($entry['download_url']
                        ?? ($entry['src']
                        ?? ($entry['href']
                        ?? ($entry['file'] ?? '')))
                    ))
                );
                $label = sanitize_text_field((string) ($entry['label'] ?? ($entry['name'] ?? ($entry['title'] ?? ''))));
                $expiresAt = sanitize_text_field((string) ($entry['expires_at'] ?? ($entry['expires'] ?? '')));
                $downloadLimit = max(0, (int) ($entry['download_limit'] ?? ($entry['limit'] ?? 0)));
            }

            if ($url === '') {
                continue;
            }
            if ($label === '') {
                $label = sanitize_text_field((string) wp_basename((string) wp_parse_url($url, PHP_URL_PATH)));
            }

            $normalized[] = [
                'url'            => $url,
                'label'          => $label,
                'expires_at'     => $expiresAt,
                'download_limit' => $downloadLimit,
            ];
        }

        if (! $normalized) {
            return [];
        }

        $seen = [];
        $deduped = [];
        foreach ($normalized as $row) {
            $key = (string) ($row['url'] ?? '');
            if ($key === '' || isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $deduped[] = $row;
        }

        return $deduped;
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     * @return array<string, mixed>
     */
    private function resolveSubscriptionMeta(
        array $productPayload,
        array $lineItem,
        string $productType,
        float $unitPrice,
        float $lineTotal,
        string $currency
    ): array {
        $subscription = is_array($productPayload['subscription'] ?? null) ? $productPayload['subscription'] : [];
        $flags = [
            $productType === 'subscription',
            $lineItem['is_subscription'] ?? null,
            $lineItem['subscription'] ?? null,
            $lineItem['recurring'] ?? null,
            $subscription['enabled'] ?? null,
            $productPayload['is_subscription'] ?? null,
            $productPayload['recurring'] ?? null,
        ];
        $isSubscription = false;
        foreach ($flags as $flag) {
            if (is_bool($flag) && $flag) {
                $isSubscription = true;
                break;
            }
            if (is_array($flag)) {
                $isSubscription = true;
                break;
            }
            if (is_scalar($flag) && rest_sanitize_boolean($flag)) {
                $isSubscription = true;
                break;
            }
        }

        if (! $isSubscription) {
            return ['is_subscription' => false];
        }

        $lineSubscription = is_array($lineItem['subscription'] ?? null) ? $lineItem['subscription'] : [];
        $interval = $this->sanitizeSubscriptionInterval(
            (string) (
                $lineItem['subscription_interval']
                ?? ($lineItem['interval_code']
                ?? ($lineSubscription['interval_code']
                ?? ($lineSubscription['interval']
                ?? ($subscription['interval_code']
                ?? ($subscription['interval']
                ?? ($productPayload['billing_interval'] ?? 'monthly'))))))
            )
        );
        $intervalCount = max(
            1,
            min(
                36,
                (int) (
                    $lineItem['subscription_interval_count']
                    ?? ($lineSubscription['interval_count']
                    ?? ($subscription['interval_count'] ?? 1))
                )
            )
        );
        $trialDays = max(
            0,
            min(
                365,
                (int) (
                    $lineItem['subscription_trial_days']
                    ?? ($lineSubscription['trial_days']
                    ?? ($subscription['trial_days'] ?? 0))
                )
            )
        );
        $setupFee = max(
            0.0,
            (float) (
                $lineItem['subscription_setup_fee']
                ?? ($lineSubscription['setup_fee']
                ?? ($subscription['setup_fee'] ?? 0))
            )
        );
        $qty = max(0.001, (float) ($lineItem['qty'] ?? 1));
        $recurringAmount = null;
        foreach ([
            $lineItem['recurring_amount'] ?? null,
            $lineSubscription['amount'] ?? null,
            $lineSubscription['recurring_amount'] ?? null,
            $subscription['amount'] ?? null,
            $subscription['recurring_amount'] ?? null,
        ] as $amountCandidate) {
            if (is_numeric($amountCandidate) && (float) $amountCandidate >= 0) {
                $recurringAmount = (float) $amountCandidate;
                break;
            }
        }
        $amount = $recurringAmount === null ? $lineTotal : $recurringAmount * $qty;

        return [
            'is_subscription' => true,
            'interval_code' => $interval,
            'interval_count' => $intervalCount,
            'trial_days' => $trialDays,
            'setup_fee' => $setupFee,
            'auto_renew' => ! array_key_exists('auto_renew', $subscription) || rest_sanitize_boolean($subscription['auto_renew']),
            'unit_amount' => max(0.0, $recurringAmount ?? $unitPrice),
            'amount' => max(0.0, $amount),
            'currency' => strtoupper(sanitize_text_field($currency)),
        ];
    }

    private function sanitizeSubscriptionInterval(string $interval): string
    {
        $interval = sanitize_key($interval);
        $aliases = [
            'day' => 'daily',
            'week' => 'weekly',
            'month' => 'monthly',
            'quarter' => 'quarterly',
            'year' => 'yearly',
            'annual' => 'yearly',
            'annually' => 'yearly',
        ];
        $interval = (string) ($aliases[$interval] ?? $interval);

        return in_array($interval, ['daily', 'weekly', 'monthly', 'quarterly', 'yearly'], true)
            ? $interval
            : 'monthly';
    }

    private function sanitizeSubscriptionStatus(string $status): string
    {
        $status = sanitize_key($status);
        $aliases = [
            'succeeded' => 'active',
            'paid' => 'active',
            'complete' => 'active',
            'completed' => 'active',
            'failed' => 'payment_failed',
        ];
        $status = (string) ($aliases[$status] ?? $status);

        return in_array($status, ['pending', 'trialing', 'active', 'paused', 'cancelled', 'expired', 'payment_failed'], true)
            ? $status
            : 'pending';
    }

    /**
     * @param array<string, mixed> $routerInstructions
     */
    private function checkoutOrderStatusFromRouter(array $routerInstructions): string
    {
        $dispatch = is_array($routerInstructions['dispatch'] ?? null) ? $routerInstructions['dispatch'] : [];
        $remoteStatus = sanitize_key((string) ($routerInstructions['remote_status'] ?? ''));
        $orderStatus = sanitize_key((string) ($routerInstructions['order_status'] ?? ''));
        $candidate = $orderStatus !== '' ? $orderStatus : $remoteStatus;

        if (! empty($routerInstructions['paid']) || in_array($candidate, ['paid', 'completed', 'complete', 'succeeded', 'captured', 'active'], true)) {
            return 'paid';
        }
        if (in_array($candidate, ['processing', 'authorized'], true)) {
            return 'processing';
        }
        if (! empty($dispatch['attempted']) && empty($dispatch['success'])) {
            return 'payment_failed';
        }

        return 'pending';
    }

    /**
     * @param array<string, mixed> $routerInstructions
     */
    private function gatewayReferenceFromRouter(array $routerInstructions): string
    {
        foreach (['transaction_id', 'payment_intent', 'payment_id', 'checkout_session', 'subscription_id'] as $field) {
            if (! empty($routerInstructions[$field])) {
                return sanitize_text_field((string) $routerInstructions[$field]);
            }
        }

        $dispatch = is_array($routerInstructions['dispatch'] ?? null) ? $routerInstructions['dispatch'] : [];
        $parsed = is_array($dispatch['parsed'] ?? null) ? $dispatch['parsed'] : [];
        foreach (['transaction_id', 'payment_intent', 'payment_id', 'checkout_session', 'subscription_id'] as $field) {
            if (! empty($parsed[$field])) {
                return sanitize_text_field((string) $parsed[$field]);
            }
        }

        return '';
    }

    /**
     * @param array<string, mixed> $routerInstructions
     */
    private function subscriptionStatusFromRouter(array $routerInstructions): string
    {
        $orderStatus = $this->checkoutOrderStatusFromRouter($routerInstructions);
        if (in_array($orderStatus, ['paid', 'processing'], true)) {
            return 'active';
        }
        if ($orderStatus === 'payment_failed') {
            return 'payment_failed';
        }

        $remoteStatus = sanitize_key((string) ($routerInstructions['remote_status'] ?? ''));
        if ($remoteStatus !== '') {
            return $this->sanitizeSubscriptionStatus($remoteStatus);
        }

        return 'pending';
    }

    /**
     * @param array<string, mixed> $routerInstructions
     * @return array<string, mixed>
     */
    private function subscriptionRouterSummary(array $routerInstructions): array
    {
        $dispatch = is_array($routerInstructions['dispatch'] ?? null) ? $routerInstructions['dispatch'] : [];

        return [
            'route' => sanitize_key((string) ($routerInstructions['route'] ?? ($routerInstructions['gateway_slug'] ?? ''))),
            'gateway_slug' => sanitize_key((string) ($routerInstructions['gateway_slug'] ?? '')),
            'processor_mode' => sanitize_key((string) ($routerInstructions['processor_mode'] ?? '')),
            'dispatch_attempted' => ! empty($dispatch['attempted']),
            'dispatch_success' => ! empty($dispatch['success']),
            'http_code' => max(0, (int) ($dispatch['http_code'] ?? 0)),
            'remote_status' => sanitize_key((string) ($routerInstructions['remote_status'] ?? '')),
            'order_status' => sanitize_key((string) ($routerInstructions['order_status'] ?? '')),
            'gateway_reference' => $this->gatewayReferenceFromRouter($routerInstructions),
            'checkout_url' => esc_url_raw((string) ($routerInstructions['checkout_url'] ?? '')),
            'redirect_url' => esc_url_raw((string) ($routerInstructions['redirect_url'] ?? '')),
        ];
    }

    private function sanitizeProductType(string $type): string
    {
        $type = sanitize_key($type);
        $allowed = ['simple', 'variable', 'digital', 'downloadable', 'virtual', 'booking', 'service', 'subscription'];
        if (! in_array($type, $allowed, true)) {
            return 'simple';
        }

        if (in_array($type, ['downloadable', 'virtual'], true)) {
            return 'digital';
        }

        return $type;
    }

    private function sanitizeFulfillmentType(string $type): string
    {
        $type = sanitize_key($type);
        $allowed = ['shipping', 'digital', 'booking', 'service'];
        if (! in_array($type, $allowed, true)) {
            return 'shipping';
        }

        return $type;
    }

    private function defaultFulfillmentForProductType(string $productType): string
    {
        $productType = $this->sanitizeProductType($productType);
        if ($productType === 'digital') {
            return 'digital';
        }
        if ($productType === 'booking') {
            return 'booking';
        }
        if ($productType === 'service' || $productType === 'subscription') {
            return 'service';
        }

        return 'shipping';
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     */
    private function resolveProductType(array $productPayload, array $lineItem): string
    {
        $candidate = (string) (
            $lineItem['product_type']
            ?? ($lineItem['type']
            ?? ($productPayload['type'] ?? 'simple'))
        );
        return $this->sanitizeProductType($candidate);
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     */
    private function resolveFulfillmentType(
        array $productPayload,
        array $lineItem,
        bool $isDigital,
        bool $isBooking
    ): string {
        if ($isDigital) {
            return 'digital';
        }
        if ($isBooking) {
            return 'booking';
        }

        $candidate = (string) (
            $lineItem['fulfillment_type']
            ?? ($productPayload['fulfillment_type'] ?? '')
        );
        $candidate = sanitize_key($candidate);
        if ($candidate !== '' && in_array($candidate, ['shipping', 'digital', 'booking', 'service'], true)) {
            return $this->sanitizeFulfillmentType($candidate);
        }

        return $this->defaultFulfillmentForProductType($this->resolveProductType($productPayload, $lineItem));
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     */
    private function detectBookingProduct(array $productPayload, array $lineItem): bool
    {
        $flags = [
            $lineItem['is_booking'] ?? null,
            $lineItem['booking_enabled'] ?? null,
            $productPayload['is_booking'] ?? null,
            $productPayload['booking_enabled'] ?? null,
            is_array($productPayload['booking'] ?? null) ? ($productPayload['booking']['enabled'] ?? null) : null,
        ];

        foreach ($flags as $flag) {
            if (is_bool($flag) && $flag) {
                return true;
            }
            if (is_scalar($flag) && rest_sanitize_boolean($flag)) {
                return true;
            }
        }

        $productType = $this->resolveProductType($productPayload, $lineItem);
        if ($productType === 'booking') {
            return true;
        }

        $fulfillmentType = sanitize_key((string) ($lineItem['fulfillment_type'] ?? ($productPayload['fulfillment_type'] ?? '')));
        return $fulfillmentType === 'booking';
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     */
    private function detectShippingRequirement(
        array $productPayload,
        array $lineItem,
        string $fulfillmentType,
        bool $isDigital,
        bool $isBooking
    ): bool {
        if ($isDigital || $isBooking || $fulfillmentType === 'digital' || $fulfillmentType === 'booking' || $fulfillmentType === 'service') {
            return false;
        }

        $flags = [
            $lineItem['requires_shipping'] ?? null,
            $lineItem['needs_shipping'] ?? null,
            $productPayload['requires_shipping'] ?? null,
            $productPayload['needs_shipping'] ?? null,
        ];
        foreach ($flags as $flag) {
            if (is_bool($flag)) {
                return $flag;
            }
            if (is_scalar($flag) && trim((string) $flag) !== '') {
                return rest_sanitize_boolean($flag);
            }
        }

        return true;
    }

    /**
     * @param array<string, mixed> $requestPayload
     * @param array<string, mixed> $settings
     * @param array<int, array<string, mixed>> $lineItems
     * @return array<string, mixed>
     */
    private function buildShippingSummary(
        array $requestPayload,
        array $settings,
        array $lineItems,
        string $currency,
        bool $containsShippable
    ): array {
        $destination = $this->normalizeShippingDestination($requestPayload, $settings);
        $originCountry = strtoupper(substr(sanitize_text_field((string) ($settings['shipping_origin_country'] ?? 'US')), 0, 2));
        $isDomestic = $originCountry !== '' && strtoupper((string) ($destination['country'] ?? '')) === $originCountry;
        $rateMode = sanitize_key((string) ($settings['shipping_rate_mode'] ?? 'preset'));
        if (! in_array($rateMode, ['api', 'preset'], true)) {
            $rateMode = 'preset';
        }

        $totalWeightLb = 0.0;
        $maxDimensionIn = 0.0;
        $shippableQty = 0.0;
        foreach ($lineItems as $lineItem) {
            if (! is_array($lineItem) || empty($lineItem['requires_shipping'])) {
                continue;
            }
            $qty = max(0.001, (float) ($lineItem['qty'] ?? 1));
            $profile = is_array($lineItem['shipping_profile'] ?? null) ? $lineItem['shipping_profile'] : [];
            $weightValue = max(0, (float) ($profile['weight'] ?? 0));
            $weightUnit = sanitize_key((string) ($profile['weight_unit'] ?? 'lb'));
            $dimensionUnit = sanitize_key((string) ($profile['dimension_unit'] ?? 'in'));
            $length = max(0, (float) ($profile['length'] ?? 0));
            $width = max(0, (float) ($profile['width'] ?? 0));
            $height = max(0, (float) ($profile['height'] ?? 0));

            $weightLb = $this->convertWeightToPounds($weightValue, $weightUnit);
            $totalWeightLb += ($weightLb * $qty);
            $maxDimensionIn = max(
                $maxDimensionIn,
                $this->convertDimensionToInches($length, $dimensionUnit),
                $this->convertDimensionToInches($width, $dimensionUnit),
                $this->convertDimensionToInches($height, $dimensionUnit)
            );
            $shippableQty += $qty;
        }

        if (! $containsShippable) {
            return [
                'required' => false,
                'mode' => $rateMode,
                'origin' => [
                    'country' => $originCountry,
                    'postal_code' => sanitize_text_field((string) ($settings['shipping_origin_postal'] ?? '')),
                ],
                'destination' => $destination,
                'is_domestic' => $isDomestic,
                'total_weight_lb' => 0,
                'max_dimension_in' => 0,
                'options' => [],
                'selected' => null,
                'selected_amount' => 0.0,
            ];
        }

        $carrierLabels = [
            'usps' => 'USPS',
            'ups' => 'UPS',
            'fedex' => 'FedEx',
            'dhl' => 'DHL',
        ];
        $options = [];
        foreach ($carrierLabels as $carrierKey => $carrierLabel) {
            if (empty($settings['shipping_' . $carrierKey . '_enabled'])) {
                continue;
            }

            $apiRate = null;
            if ($rateMode === 'api') {
                $apiPayload = [
                    'carrier' => $carrierKey,
                    'origin_country' => $originCountry,
                    'origin_postal' => sanitize_text_field((string) ($settings['shipping_origin_postal'] ?? '')),
                    'destination' => $destination,
                    'is_domestic' => $isDomestic,
                    'currency' => $currency,
                    'total_weight_lb' => $totalWeightLb,
                    'max_dimension_in' => $maxDimensionIn,
                    'item_qty' => $shippableQty,
                    'api_key' => (string) ($settings['shipping_' . $carrierKey . '_api_key'] ?? ''),
                    'api_secret' => (string) ($settings['shipping_' . $carrierKey . '_api_secret'] ?? ''),
                    'account' => (string) ($settings['shipping_' . $carrierKey . '_account'] ?? ''),
                ];
                $rawApiRate = apply_filters('h4bs_shipping_api_quote', null, $apiPayload, $requestPayload);
                if (is_numeric($rawApiRate)) {
                    $apiRate = max(0, (float) $rawApiRate);
                } elseif (is_array($rawApiRate) && isset($rawApiRate['amount']) && is_numeric($rawApiRate['amount'])) {
                    $apiRate = max(0, (float) $rawApiRate['amount']);
                }
            }

            if ($apiRate !== null) {
                $options[] = [
                    'carrier' => $carrierKey,
                    'carrier_label' => $carrierLabel,
                    'service' => sanitize_text_field((string) ($settings['shipping_default_service'] ?? 'standard')),
                    'amount' => round($apiRate, 2),
                    'currency' => $currency,
                    'source' => 'api',
                ];
                continue;
            }

            $baseRate = $isDomestic
                ? (float) ($settings['shipping_' . $carrierKey . '_preset_domestic'] ?? 0)
                : (float) ($settings['shipping_' . $carrierKey . '_preset_international'] ?? 0);
            $perWeight = $isDomestic
                ? (float) ($settings['shipping_' . $carrierKey . '_per_weight_domestic'] ?? 0)
                : (float) ($settings['shipping_' . $carrierKey . '_per_weight_international'] ?? 0);
            $amount = max(0, $baseRate + ($perWeight * $totalWeightLb));
            if ($amount <= 0) {
                continue;
            }
            if ($maxDimensionIn > 48) {
                $amount += 12.50;
            } elseif ($maxDimensionIn > 30) {
                $amount += 6.50;
            }

            $options[] = [
                'carrier' => $carrierKey,
                'carrier_label' => $carrierLabel,
                'service' => sanitize_text_field((string) ($settings['shipping_default_service'] ?? 'standard')),
                'amount' => round($amount, 2),
                'currency' => $currency,
                'source' => 'preset',
            ];
        }

        usort(
            $options,
            static fn (array $a, array $b): int => ((float) ($a['amount'] ?? 0)) <=> ((float) ($b['amount'] ?? 0))
        );

        $selected = null;
        $requestedCarrier = sanitize_key((string) ($requestPayload['shipping']['carrier'] ?? ($requestPayload['shipping_carrier'] ?? '')));
        if ($requestedCarrier !== '') {
            foreach ($options as $option) {
                if (sanitize_key((string) ($option['carrier'] ?? '')) === $requestedCarrier) {
                    $selected = $option;
                    break;
                }
            }
        }
        if (! is_array($selected) && ! empty($options[0]) && is_array($options[0])) {
            $selected = $options[0];
        }
        $selectedAmount = is_array($selected) ? (float) ($selected['amount'] ?? 0) : 0.0;

        return [
            'required' => true,
            'mode' => $rateMode,
            'origin' => [
                'country' => $originCountry,
                'postal_code' => sanitize_text_field((string) ($settings['shipping_origin_postal'] ?? '')),
            ],
            'destination' => $destination,
            'is_domestic' => $isDomestic,
            'total_weight_lb' => round($totalWeightLb, 4),
            'max_dimension_in' => round($maxDimensionIn, 2),
            'options' => $options,
            'selected' => $selected,
            'selected_amount' => round($selectedAmount, 2),
        ];
    }

    /**
     * @param array<string, mixed> $requestPayload
     * @param array<string, mixed> $settings
     * @return array<string, string>
     */
    private function normalizeShippingDestination(array $requestPayload, array $settings): array
    {
        $shipping = is_array($requestPayload['shipping'] ?? null) ? $requestPayload['shipping'] : [];
        $billing = is_array($requestPayload['billing'] ?? null) ? $requestPayload['billing'] : [];
        $country = strtoupper(substr(sanitize_text_field((string) ($shipping['country'] ?? ($billing['country'] ?? ($settings['shipping_origin_country'] ?? 'US')))), 0, 2));
        $postalCode = sanitize_text_field((string) ($shipping['postal_code'] ?? ($shipping['postcode'] ?? ($billing['postal_code'] ?? ($billing['postcode'] ?? '')))));
        $state = sanitize_text_field((string) ($shipping['state'] ?? ($billing['state'] ?? '')));
        $city = sanitize_text_field((string) ($shipping['city'] ?? ($billing['city'] ?? '')));

        return [
            'country' => $country,
            'postal_code' => $postalCode,
            'state' => $state,
            'city' => $city,
        ];
    }

    private function convertWeightToPounds(float $weight, string $unit): float
    {
        $weight = max(0, $weight);
        $unit = sanitize_key($unit);
        if ($unit === 'kg') {
            return $weight * 2.2046226218;
        }
        if ($unit === 'oz') {
            return $weight / 16;
        }
        if ($unit === 'g') {
            return $weight * 0.0022046226;
        }
        return $weight;
    }

    private function convertDimensionToInches(float $dimension, string $unit): float
    {
        $dimension = max(0, $dimension);
        $unit = sanitize_key($unit);
        if ($unit === 'cm') {
            return $dimension * 0.3937007874;
        }
        if ($unit === 'm') {
            return $dimension * 39.37007874;
        }
        return $dimension;
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     * @return array<string, mixed>
     */
    private function resolveShippingProfile(array $productPayload, array $lineItem): array
    {
        $profile = is_array($productPayload['shipping_profile'] ?? null) ? $productPayload['shipping_profile'] : [];
        if (is_array($lineItem['shipping_profile'] ?? null)) {
            $profile = array_merge($profile, $lineItem['shipping_profile']);
        }

        $weight = max(0, (float) ($profile['weight'] ?? ($lineItem['shipping_weight'] ?? 0)));
        $length = max(0, (float) ($profile['length'] ?? ($lineItem['shipping_length'] ?? 0)));
        $width = max(0, (float) ($profile['width'] ?? ($lineItem['shipping_width'] ?? 0)));
        $height = max(0, (float) ($profile['height'] ?? ($lineItem['shipping_height'] ?? 0)));
        $weightUnit = sanitize_key((string) ($profile['weight_unit'] ?? ($lineItem['shipping_weight_unit'] ?? 'lb')));
        if (! in_array($weightUnit, ['lb', 'kg', 'oz', 'g'], true)) {
            $weightUnit = 'lb';
        }
        $dimensionUnit = sanitize_key((string) ($profile['dimension_unit'] ?? ($lineItem['shipping_dimension_unit'] ?? 'in')));
        if (! in_array($dimensionUnit, ['in', 'cm', 'm'], true)) {
            $dimensionUnit = 'in';
        }

        return [
            'weight' => $weight,
            'weight_unit' => $weightUnit,
            'length' => $length,
            'width' => $width,
            'height' => $height,
            'dimension_unit' => $dimensionUnit,
        ];
    }

    /**
     * @param array<string, mixed> $productPayload
     * @param array<string, mixed> $lineItem
     * @return array<string, mixed>
     */
    private function extractBookingMeta(array $productPayload, array $lineItem, bool $isBooking): array
    {
        $booking = [];
        if (is_array($productPayload['booking'] ?? null)) {
            $booking = $productPayload['booking'];
        }
        if (is_array($lineItem['booking'] ?? null)) {
            $booking = array_merge($booking, $lineItem['booking']);
        }

        $duration = max(0, min(1440, (int) ($booking['duration_minutes'] ?? ($lineItem['booking_duration_minutes'] ?? 0))));
        $buffer = max(0, min(480, (int) ($booking['buffer_minutes'] ?? ($lineItem['booking_buffer_minutes'] ?? 0))));

        return [
            'enabled' => $isBooking,
            'duration_minutes' => $duration,
            'buffer_minutes' => $buffer,
            'requires_confirmation' => rest_sanitize_boolean(
                $booking['requires_confirmation']
                ?? ($lineItem['booking_requires_confirmation'] ?? false)
            ),
            'start_at' => sanitize_text_field((string) ($booking['start_at'] ?? ($lineItem['booking_start_at'] ?? ''))),
            'end_at' => sanitize_text_field((string) ($booking['end_at'] ?? ($lineItem['booking_end_at'] ?? ''))),
            'timezone' => sanitize_text_field((string) ($booking['timezone'] ?? ($lineItem['booking_timezone'] ?? ''))),
            'staff_id' => absint($booking['staff_id'] ?? ($lineItem['booking_staff_id'] ?? 0)),
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $downloads
     */
    private function downloadsToTextarea(array $downloads): string
    {
        if (! $downloads) {
            return '';
        }

        $urls = [];
        foreach ($downloads as $download) {
            if (! is_array($download)) {
                continue;
            }
            $url = esc_url_raw((string) ($download['url'] ?? ''));
            if ($url !== '') {
                $urls[] = $url;
            }
        }

        return implode("\n", $urls);
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function sanitizePayloadForStorage(array $payload, string $customerDataMode): array
    {
        unset($payload['customer_fields']);
        if ($customerDataMode !== 'stripe_only') {
            return $payload;
        }

        unset($payload['customer_email'], $payload['customer'], $payload['billing'], $payload['shipping']);

        return $payload;
    }

    /**
     * Keep public checkout input limited to customer-selectable values. Product pricing,
     * fulfillment, downloads, subscription terms, tax, and inventory always come from storage.
     *
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function sanitizeCheckoutRequestPayload(array $payload): array
    {
        $customer = is_array($payload['customer'] ?? null) ? $payload['customer'] : [];
        $items = [];
        foreach (array_slice(is_array($payload['items'] ?? null) ? $payload['items'] : [], 0, 100) as $itemIndex => $item) {
            if (! is_array($item)) {
                continue;
            }
            $productId = absint($item['product_id'] ?? 0);
            if ($productId <= 0) {
                continue;
            }
            $booking = is_array($item['booking'] ?? null) ? $item['booking'] : [];
            $lineKey = substr(
                (string) preg_replace(
                    '/[^A-Za-z0-9._:-]/',
                    '',
                    sanitize_text_field((string) ($item['line_key'] ?? ''))
                ),
                0,
                100
            );
            if ($lineKey === '') {
                $lineKey = 'line-' . ((int) $itemIndex + 1);
            }
            $items[] = [
                'line_key' => $lineKey,
                'product_id' => $productId,
                'variant_id' => absint($item['variant_id'] ?? 0),
                'variant_sku' => substr(sanitize_text_field((string) ($item['variant_sku'] ?? '')), 0, 100),
                'qty' => max(0.0, min(1000.0, (float) ($item['qty'] ?? 1))),
                'configuration' => $this->sanitizeCheckoutExtensions(
                    $item['configuration'] ?? ($item['options'] ?? [])
                ),
                'booking' => [
                    'start_at' => substr(sanitize_text_field((string) ($booking['start_at'] ?? ($item['booking_start_at'] ?? ''))), 0, 100),
                    'end_at' => substr(sanitize_text_field((string) ($booking['end_at'] ?? ($item['booking_end_at'] ?? ''))), 0, 100),
                    'timezone' => substr(sanitize_text_field((string) ($booking['timezone'] ?? ($item['booking_timezone'] ?? ''))), 0, 100),
                    'staff_id' => absint($booking['staff_id'] ?? ($item['booking_staff_id'] ?? 0)),
                ],
            ];
        }

        return [
            'checkout_key' => substr(
                (string) preg_replace('/[^A-Za-z0-9._:-]/', '', sanitize_text_field((string) ($payload['checkout_key'] ?? ''))),
                0,
                64
            ),
            'customer_email' => sanitize_email((string) ($payload['customer_email'] ?? ($customer['email'] ?? ''))),
            'customer_first_name' => substr(
                sanitize_text_field((string) ($payload['customer_first_name'] ?? ($customer['first_name'] ?? ''))),
                0,
                120
            ),
            'customer_last_name' => substr(
                sanitize_text_field((string) ($payload['customer_last_name'] ?? ($customer['last_name'] ?? ''))),
                0,
                120
            ),
            'currency' => strtoupper(substr(sanitize_text_field((string) ($payload['currency'] ?? 'USD')), 0, 8)),
            'gateway' => sanitize_key((string) ($payload['gateway'] ?? '')),
            'route' => sanitize_key((string) ($payload['route'] ?? '')),
            'promotion_code' => $this->promotions->normalizeCode(
                (string) ($payload['promotion_code'] ?? ($payload['coupon_code'] ?? ''))
            ),
            'shipping_carrier' => sanitize_key((string) ($payload['shipping_carrier'] ?? '')),
            'shipping' => $this->sanitizeCheckoutAddress($payload['shipping'] ?? []),
            'billing' => $this->sanitizeCheckoutAddress($payload['billing'] ?? []),
            'order_note' => substr(sanitize_textarea_field((string) ($payload['order_note'] ?? '')), 0, 2000),
            'customer_fields' => $this->sanitizeCheckoutCustomerFieldInput($payload['customer_fields'] ?? []),
            'extensions' => $this->sanitizeCheckoutExtensions($payload['extensions'] ?? []),
            'items' => $items,
        ];
    }

    /**
     * Keep dynamic customer input bounded until the active field catalog performs typed validation.
     *
     * @return array<string, mixed>
     */
    private function sanitizeCheckoutCustomerFieldInput(mixed $rawValues): array
    {
        if (! is_array($rawValues)) {
            return [];
        }
        $values = [];
        foreach (array_slice($rawValues, 0, 100, true) as $key => $value) {
            $cleanKey = sanitize_key((string) $key);
            if ($cleanKey === '') {
                continue;
            }
            if (is_array($value)) {
                $values[$cleanKey] = array_values(
                    array_map(
                        static fn(mixed $item): string => substr(sanitize_text_field(is_scalar($item) ? (string) $item : ''), 0, 1000),
                        array_slice($value, 0, 100)
                    )
                );
                continue;
            }
            $values[$cleanKey] = substr(sanitize_textarea_field(is_scalar($value) ? (string) $value : ''), 0, 10000);
        }
        return $values;
    }

    /**
     * @return array<string, string>
     */
    private function sanitizeCheckoutAddress(mixed $rawAddress): array
    {
        $address = is_array($rawAddress) ? $rawAddress : [];
        return [
            'first_name' => substr(sanitize_text_field((string) ($address['first_name'] ?? '')), 0, 120),
            'last_name' => substr(sanitize_text_field((string) ($address['last_name'] ?? '')), 0, 120),
            'address_1' => substr(sanitize_text_field((string) ($address['address_1'] ?? '')), 0, 190),
            'address_2' => substr(sanitize_text_field((string) ($address['address_2'] ?? '')), 0, 190),
            'city' => substr(sanitize_text_field((string) ($address['city'] ?? '')), 0, 120),
            'state' => substr(sanitize_text_field((string) ($address['state'] ?? '')), 0, 120),
            'postcode' => substr(sanitize_text_field((string) ($address['postcode'] ?? ($address['postal_code'] ?? ($address['zip'] ?? '')))), 0, 32),
            'country' => strtoupper(substr(sanitize_text_field((string) ($address['country'] ?? '')), 0, 2)),
            'phone' => substr(sanitize_text_field((string) ($address['phone'] ?? '')), 0, 40),
            'carrier' => sanitize_key((string) ($address['carrier'] ?? '')),
        ];
    }

    /**
     * Preserve bounded namespaced extension values without accepting executable content.
     *
     * @return array<string, mixed>
     */
    private function sanitizeCheckoutExtensions(mixed $rawExtensions, int $depth = 0): array
    {
        if (! is_array($rawExtensions) || $depth > 3) {
            return [];
        }

        $extensions = [];
        foreach (array_slice($rawExtensions, 0, 50, true) as $key => $value) {
            $safeKey = sanitize_key((string) $key);
            if ($safeKey === '' || strlen($safeKey) > 64) {
                continue;
            }
            if (is_array($value)) {
                $extensions[$safeKey] = $this->sanitizeCheckoutExtensions($value, $depth + 1);
            } elseif (is_bool($value)) {
                $extensions[$safeKey] = $value;
            } elseif (is_numeric($value)) {
                $extensions[$safeKey] = (float) $value;
            } elseif (is_scalar($value)) {
                $extensions[$safeKey] = substr(sanitize_text_field((string) $value), 0, 2000);
            }
        }
        return $extensions;
    }

    /**
     * @param array<string, mixed> $product
     * @return array<string, mixed>
     */
    private function sanitizePublicProduct(array $product): array
    {
        $payload = $this->decodeJsonArray((string) ($product['payload'] ?? ''));
        $type = $this->resolveProductType($payload, []);
        $isDigital = $this->detectDigitalProduct($payload, []);
        $isBooking = $this->detectBookingProduct($payload, []);
        $fulfillmentType = $this->resolveFulfillmentType($payload, [], $isDigital, $isBooking);
        $requiresShipping = $this->detectShippingRequirement($payload, [], $fulfillmentType, $isDigital, $isBooking);
        $downloads = $this->normalizeDownloads($payload['downloads'] ?? ($payload['download_urls'] ?? []));
        $shippingProfile = $this->resolveShippingProfile($payload, []);
        $subscription = $this->resolveSubscriptionMeta(
            $payload,
            ['qty' => 1],
            $type,
            (float) ($product['price'] ?? 0),
            (float) ($product['price'] ?? 0),
            strtoupper(sanitize_text_field((string) ($product['currency'] ?? 'USD')))
        );
        $customerAccount = is_array($payload['customer_account'] ?? null) ? $payload['customer_account'] : [];
        $customerRoleAfterPurchase = $this->sanitizeCustomerRole((string) ($customerAccount['role_after_purchase'] ?? ''), false);
        $customerStatusAfterPurchase = $this->sanitizeCustomerStatus((string) ($customerAccount['status_after_purchase'] ?? ''), false);
        $inventory = $this->normalizeInventoryConfig($payload);
        $inventorySnapshot = $this->publicInventorySnapshot($inventory, (int) ($product['stock_qty'] ?? 0));
        $pricing = $this->normalizePricingConfig($payload);
        $merchandising = $this->normalizeMerchandisingConfig($payload);
        $tax = is_array($payload['tax'] ?? null) ? $payload['tax'] : [];
        $taxStatus = sanitize_key((string) ($tax['status'] ?? 'taxable'));
        if (! in_array($taxStatus, ['taxable', 'none'], true)) {
            $taxStatus = 'taxable';
        }
        $taxClass = sanitize_key((string) ($tax['class'] ?? 'standard')) ?: 'standard';
        $public = [
            'id'        => absint($product['id'] ?? 0),
            'sku'       => sanitize_text_field((string) ($product['sku'] ?? '')),
            'name'      => sanitize_text_field((string) ($product['name'] ?? '')),
            'slug'      => sanitize_title((string) ($product['slug'] ?? '')),
            'status'    => sanitize_key((string) ($product['status'] ?? 'active')),
            'price'     => (float) ($product['price'] ?? 0),
            'currency'  => strtoupper(sanitize_text_field((string) ($product['currency'] ?? 'USD'))),
            'stock_qty' => (int) ($product['stock_qty'] ?? 0),
            'product_type' => $type,
            'fulfillment_type' => $fulfillmentType,
            'is_digital' => $isDigital,
            'is_booking' => $isBooking,
            'requires_shipping' => $requiresShipping,
            'has_downloads' => ! empty($downloads),
            'download_count' => count($downloads),
            'shipping_profile' => $shippingProfile,
            'is_subscription' => ! empty($subscription['is_subscription']),
            'inventory' => $inventorySnapshot,
            'compare_at_price' => (float) $pricing['compare_at_price'],
            'tax' => [
                'status' => $taxStatus,
                'class' => $taxClass,
            ],
            'merchandising' => [
                'badge' => (string) $merchandising['badge'],
                'related_product_ids' => $merchandising['related_product_ids'],
                'upsell_product_ids' => $merchandising['upsell_product_ids'],
                'cross_sell_product_ids' => $merchandising['cross_sell_product_ids'],
            ],
        ];

        if (! empty($subscription['is_subscription'])) {
            $public['subscription'] = [
                'interval_code' => (string) ($subscription['interval_code'] ?? 'monthly'),
                'interval_count' => max(1, (int) ($subscription['interval_count'] ?? 1)),
                'trial_days' => max(0, (int) ($subscription['trial_days'] ?? 0)),
                'setup_fee' => max(0.0, (float) ($subscription['setup_fee'] ?? 0)),
                'auto_renew' => ! array_key_exists('auto_renew', $subscription) || rest_sanitize_boolean($subscription['auto_renew']),
            ];
        }

        if (current_user_can('manage_options')) {
            $public['downloads'] = $downloads;
            $public['customer_account'] = [
                'role_after_purchase' => $customerRoleAfterPurchase,
                'status_after_purchase' => $customerStatusAfterPurchase,
            ];
            $public['unit_cost'] = (float) $pricing['cost'];
            $public['purchase_note'] = (string) $merchandising['purchase_note'];
        }

        return $public;
    }

    /**
     * @param array<string, mixed> $variant
     * @return array<string, mixed>
     */
    private function sanitizePublicVariant(array $variant): array
    {
        return [
            'id'         => absint($variant['id'] ?? 0),
            'product_id' => absint($variant['product_id'] ?? 0),
            'sku'        => sanitize_text_field((string) ($variant['sku'] ?? '')),
            'price'      => (float) ($variant['price'] ?? 0),
            'stock_qty'  => (int) ($variant['stock_qty'] ?? 0),
            'image_id'   => absint($variant['image_id'] ?? 0),
            'position'   => (int) ($variant['position'] ?? 0),
            'attributes' => $this->decodeJsonArray((string) ($variant['attributes_json'] ?? '{}')),
        ];
    }

    private function enforcePublicCheckoutRateLimit(): ?WP_REST_Response
    {
        $ip = IpTools::currentIp();
        if ($ip === '') {
            return null;
        }

        $key = 'h4bs_checkout_rl_' . md5($ip);
        $rateLimit = apply_filters(
            'h4bs_checkout_rate_limit',
            ['window_seconds' => 300, 'max_attempts' => 30],
            $ip
        );
        if (! is_array($rateLimit)) {
            $rateLimit = [];
        }
        $windowSeconds = max(60, min(DAY_IN_SECONDS, (int) ($rateLimit['window_seconds'] ?? 300)));
        $maxPerWindow = max(0, min(10000, (int) ($rateLimit['max_attempts'] ?? 30)));
        if ($maxPerWindow === 0) {
            return null;
        }
        $hits = (int) get_transient($key);
        if ($hits >= $maxPerWindow) {
            return new WP_REST_Response(
                ['message' => 'Too many checkout attempts. Please wait a few minutes and try again.'],
                429
            );
        }

        set_transient($key, (string) ($hits + 1), $windowSeconds);
        return null;
    }
}
