<?php

// no outside access
if (!defined('WPINC')) {
    die;
}

/**
 * Internal cart for Product Search → Cockpit checkout.
 *
 * Storage: per-user user_meta on the current blog.
 *
 * Cart shape:
 * {
 *   items: [
 *     {
 *       key: string,              // unique within cart
 *       distid: string,
 *       distsku: string,
 *       upc: string,
 *       name: string,
 *       mfg_name: string,
 *       mpn: string,
 *       qty_on_hand: int,
 *       unit_price: float,
 *       shipping_cost: float,
 *       total_cost: float,
 *       drop_ship_flg: int|bool,
 *       ffl_req: bool|null,       // unknown from search results; optional
 *       product_url: string|null,
 *       permalink: string|null,
 *       images: array,
 *       qty: int                  // desired qty
 *     }
 *   ],
 *   updated_at: string ISO
 * }
 */
class GFFLCockpit_Cart {

    private const USER_META_KEY = '_g_ffl_cockpit_internal_cart_v1';

    /**
     * Tracks any qty downgrades the most recent get_cart() call had to apply
     * (because a stored line's qty exceeded its qty_on_hand). Read once by
     * handle_get to surface a one-time banner on the cart page.
     *
     * @var array<int, array{key:string, distid:string, distsku:string, name:string, requested_qty:int, adjusted_qty:int, qty_on_hand:int}>
     */
    private static $last_adjustments = [];

    public static function register_routes(): void {
        register_rest_route('fflcockpit/v1', '/cart', [
            'methods' => 'GET',
            'callback' => [__CLASS__, 'handle_get'],
            'permission_callback' => [__CLASS__, 'permission_admin'],
        ]);

        register_rest_route('fflcockpit/v1', '/cart/add', [
            'methods' => 'POST',
            'callback' => [__CLASS__, 'handle_add'],
            'permission_callback' => [__CLASS__, 'permission_admin'],
        ]);

        register_rest_route('fflcockpit/v1', '/cart/update', [
            'methods' => 'POST',
            'callback' => [__CLASS__, 'handle_update'],
            'permission_callback' => [__CLASS__, 'permission_admin'],
        ]);

        register_rest_route('fflcockpit/v1', '/cart/remove', [
            'methods' => 'POST',
            'callback' => [__CLASS__, 'handle_remove'],
            'permission_callback' => [__CLASS__, 'permission_admin'],
        ]);

        register_rest_route('fflcockpit/v1', '/cart/clear', [
            'methods' => 'POST',
            'callback' => [__CLASS__, 'handle_clear'],
            'permission_callback' => [__CLASS__, 'permission_admin'],
        ]);

        register_rest_route('fflcockpit/v1', '/cart/checkout', [
            'methods' => 'POST',
            'callback' => [__CLASS__, 'handle_checkout'],
            'permission_callback' => [__CLASS__, 'permission_admin'],
        ]);
    }

    public static function permission_admin(): bool {
        // Cockpit page requires manage_woocommerce for non-super-admin users in some use cases.
        return current_user_can('manage_woocommerce') || current_user_can('manage_options');
    }

    /** @return array{items: array, updated_at: string|null} */
    public static function get_cart(int $user_id = 0): array {
        $user_id = $user_id ?: get_current_user_id();
        $cart = get_user_meta($user_id, self::USER_META_KEY, true);
        if (!is_array($cart)) {
            $cart = [
                'items' => [],
                'updated_at' => null,
            ];
        }
        if (!isset($cart['items']) || !is_array($cart['items'])) {
            $cart['items'] = [];
        }

        // Backfill/normalize item shapes to current schema (e.g., product_class fields)
        // so computations like shipping estimates are correct even for carts created
        // before newer fields were introduced. Also tracks any qty cap downgrades so
        // the cart UI can surface a one-time banner.
        $normalized = [];
        $adjustments = [];
        foreach ($cart['items'] as $it) {
            if (!is_array($it)) {
                continue;
            }
            $raw_qty = max(1, isset($it['qty']) ? (int) $it['qty'] : 1);
            $sanitized = self::sanitize_item($it);
            if ($raw_qty > (int) $sanitized['qty']) {
                $adjustments[] = [
                    'key'            => (string) $sanitized['key'],
                    'distid'         => (string) $sanitized['distid'],
                    'distsku'        => (string) $sanitized['distsku'],
                    'upc'            => (string) $sanitized['upc'],
                    'name'           => (string) $sanitized['name'],
                    'requested_qty'  => (int) $raw_qty,
                    'adjusted_qty'   => (int) $sanitized['qty'],
                    'qty_on_hand'    => (int) $sanitized['qty_on_hand'],
                ];
            }
            $normalized[] = $sanitized;
        }
        $cart['items'] = $normalized;

        // Drain any add-time clamp adjustments that handle_add buffered between
        // cart loads. We surface them in the next response and clear the buffer
        // so the banner doesn't repeat.
        $buffered = isset($cart['pending_adjustments']) && is_array($cart['pending_adjustments'])
            ? $cart['pending_adjustments']
            : [];
        $had_buffered = !empty($buffered);
        if ($had_buffered) {
            $cart['pending_adjustments'] = [];
            // Merge after load-time adjustments so newer (add-time) entries appear last.
            $adjustments = array_merge($adjustments, $buffered);
        }

        // Stash adjustments for the immediate caller. handle_get reads this before
        // any further state-mutating cart call clobbers it.
        self::$last_adjustments = $adjustments;

        // Persist the corrected cart so the same banner doesn't fire on every refresh.
        // Also persists the drained-empty pending_adjustments buffer.
        // Skipped when no user is logged in (defensive — get_user_meta would return '').
        if (($had_buffered || !empty($adjustments)) && $user_id > 0) {
            $cart['updated_at'] = gmdate('c');
            update_user_meta($user_id, self::USER_META_KEY, $cart);
        }

        return $cart;
    }

    public static function save_cart(array $cart, int $user_id = 0): array {
        $user_id = $user_id ?: get_current_user_id();
        $cart['updated_at'] = gmdate('c');
        update_user_meta($user_id, self::USER_META_KEY, $cart);
        return $cart;
    }

    private static function sanitize_item(array $item): array {
        $out = [];

        $out['distid'] = isset($item['distid']) ? sanitize_text_field(wp_unslash($item['distid'])) : '';
        $out['distsku'] = isset($item['distsku']) ? sanitize_text_field(wp_unslash($item['distsku'])) : '';
        $out['upc'] = isset($item['upc']) ? sanitize_text_field(wp_unslash($item['upc'])) : '';
        $out['name'] = isset($item['name']) ? sanitize_text_field(wp_unslash($item['name'])) : '';
        $out['mfg_name'] = isset($item['mfg_name']) ? sanitize_text_field(wp_unslash($item['mfg_name'])) : '';
        $out['mpn'] = isset($item['mpn']) ? sanitize_text_field(wp_unslash($item['mpn'])) : '';

        // Used for shipping estimation and other downstream logic.
        $out['product_class'] = isset($item['product_class']) ? sanitize_text_field(wp_unslash($item['product_class'])) : '';
        $out['product_class_description'] = isset($item['product_class_description']) ? sanitize_text_field(wp_unslash($item['product_class_description'])) : '';

        // Fallback: if product_class isn't provided by the search payload (can happen when
        // product restrictions are on), attempt a conservative classification for shipping.
        // This is only meant to differentiate FA vs NON_FA.
        if ($out['product_class'] === '') {
            $name = strtolower((string) $out['name']);
            $desc = strtolower((string) $out['product_class_description']);
            $maybe_firearm = false;

            if ($desc !== '') {
                $maybe_firearm = (strpos($desc, 'firearm') !== false) || (strpos($desc, 'handgun') !== false) || (strpos($desc, 'rifle') !== false) || (strpos($desc, 'shotgun') !== false);
            }
            if (!$maybe_firearm && $name !== '') {
                $maybe_firearm = (strpos($name, 'handgun') !== false) || (strpos($name, 'pistol') !== false) || (strpos($name, 'revolver') !== false) || (strpos($name, 'rifle') !== false) || (strpos($name, 'shotgun') !== false) || (strpos($name, 'firearm') !== false);
            }

            if ($maybe_firearm) {
                $out['product_class'] = 'FA';
            }
        }

        $out['qty_on_hand'] = isset($item['qty_on_hand']) ? (int) $item['qty_on_hand'] : 0;
        $out['unit_price'] = isset($item['unit_price']) ? (float) $item['unit_price'] : 0.0;
        $out['shipping_cost'] = isset($item['shipping_cost']) ? (float) $item['shipping_cost'] : 0.0;
        $out['total_cost'] = isset($item['total_cost']) ? (float) $item['total_cost'] : 0.0;

        $out['drop_ship_flg'] = isset($item['drop_ship_flg']) ? (int) $item['drop_ship_flg'] : 0;

        // optional
        $out['ffl_req'] = isset($item['ffl_req']) ? (bool) $item['ffl_req'] : null;
        $out['product_url'] = isset($item['product_url']) ? esc_url_raw(wp_unslash($item['product_url'])) : '';
        $out['permalink'] = isset($item['permalink']) ? esc_url_raw(wp_unslash($item['permalink'])) : '';
        $out['images'] = isset($item['images']) && is_array($item['images']) ? $item['images'] : [];

        $out['qty'] = max(1, isset($item['qty']) ? (int) $item['qty'] : 1);

        // Per-line qty cap: a cart line must not request more units than the distributor
        // had on hand at add time (or as last refreshed). Without this, repeated adds of
        // the same item could overshoot. Applied here so every cart read (handle_get,
        // handle_add, handle_update, handle_checkout) gets the cap for free.
        // qty_on_hand <= 0 is treated as "unknown / out of stock" and not used to cap —
        // the UI surfaces those separately.
        if ($out['qty_on_hand'] > 0 && $out['qty'] > $out['qty_on_hand']) {
            $out['qty'] = $out['qty_on_hand'];
        }

        // key: stable across adds for same dist+sku+upc
        $out['key'] = isset($item['key']) ? sanitize_key($item['key']) : '';
        if ($out['key'] === '') {
            $out['key'] = sanitize_key(strtolower($out['distid'] . '_' . $out['distsku'] . '_' . $out['upc']));
        }

        return $out;
    }

    private static function cart_summary(array $cart, bool $debug = false): array {
        $items = $cart['items'] ?? [];
        $count = 0;
        $items_subtotal = 0.0;

    // Shipping estimate: only charge shipping once per distinct (distributor + firearm/non-firearm class).
    // We take the maximum shipping_cost among that group's lines as the best rough estimate.
    // Note: Some distributors have separate shipping charges for firearms vs non-firearms.
        $shipping_by_group = [];
        $shipping_group_meta = [];
        $debug_lines = [];
        $cart_has_firearm = false;
        foreach ($items as $it) {
            $qty = isset($it['qty']) ? (int) $it['qty'] : 1;
            $count += $qty;

            $unit_total = (float) ($it['total_cost'] ?? 0.0);
            $unit_ship = (float) ($it['shipping_cost'] ?? 0.0);
            $items_subtotal += $unit_total * $qty;

            $distid = isset($it['distid']) ? (string) $it['distid'] : '';
            $product_class = isset($it['product_class']) ? strtoupper((string) $it['product_class']) : '';
            
            // Firearm detection: primary is product_class === 'FA'. Fallback to description hints.
            $is_firearm = ($product_class === 'FA');
            if ($is_firearm) {
                $cart_has_firearm = true;
            }
        
            $ship_class = $is_firearm ? 'FA' : 'NON_FA';

            if ($debug) {
                $debug_lines[] = [
                    'distid' => $distid,
                    'upc' => (string) ($it['upc'] ?? ''),
                    'distsku' => (string) ($it['distsku'] ?? ''),
                    'name' => (string) ($it['name'] ?? ''),
                    'product_class' => $product_class,
                    'product_class_description' => (string) ($it['product_class_description'] ?? ''),
                    'is_firearm' => (bool) $is_firearm,
                    'ship_class' => $ship_class,
                    'shipping_cost' => (float) $unit_ship,
                ];
            }

            if ($distid !== '') {
                $group_key = $distid . '|' . $ship_class;
                if (!isset($shipping_by_group[$group_key]) || $unit_ship > $shipping_by_group[$group_key]) {
                    $shipping_by_group[$group_key] = $unit_ship;
                }

                // capture lightweight debug info for each group
                if (!isset($shipping_group_meta[$group_key])) {
                    $shipping_group_meta[$group_key] = [
                        'distid' => $distid,
                        'ship_class' => $ship_class,
                        'shipping_cost' => (float) $unit_ship,
                        'example_upc' => (string) ($it['upc'] ?? ''),
                        'example_name' => (string) ($it['name'] ?? ''),
                    ];
                } else {
                    // keep shipping_cost in meta aligned to chosen max
                    if ((float) $unit_ship >= (float) ($shipping_group_meta[$group_key]['shipping_cost'] ?? 0.0)) {
                        $shipping_group_meta[$group_key]['shipping_cost'] = (float) $unit_ship;
                        $shipping_group_meta[$group_key]['example_upc'] = (string) ($it['upc'] ?? '');
                        $shipping_group_meta[$group_key]['example_name'] = (string) ($it['name'] ?? '');
                    }
                }
            }
        }

        $shipping_estimate = 0.0;
        foreach ($shipping_by_group as $ship) {
            $shipping_estimate += (float) $ship;
        }

        $estimated_total = $items_subtotal + $shipping_estimate;
        $out = [
            'item_count' => $count,
            'line_count' => is_array($items) ? count($items) : 0,
            'has_firearm' => (bool) $cart_has_firearm,
            'items_subtotal' => $items_subtotal,
            'shipping_estimate' => $shipping_estimate,
            'estimated_total' => $estimated_total,
            'shipping_estimate_disclaimer' => 'Shipping is estimated and is counted once per distributor and class (firearm vs non-firearm). Final shipping may differ.',
            'shipping_estimate_groups' => array_values($shipping_group_meta),
        ];

        if ($debug) {
            $out['debug_lines'] = $debug_lines;
        }

        return $out;
    }

    public static function handle_get(WP_REST_Request $request): WP_REST_Response {
        $cart = self::get_cart();
        // Snapshot adjustments before any subsequent get_cart() call (none here, but
        // future code might add one) overwrites the static.
        $adjustments = self::$last_adjustments;

        // Optional debug info for shipping troubleshooting.
        // Enable via URL: /wp-json/fflcockpit/v1/cart?debug=1
        // Only available to users who can pass permission_admin().
        $debug = (bool) $request->get_param('debug');

        return new WP_REST_Response([
            'cart' => $cart,
            'summary' => self::cart_summary($cart, $debug),
            'adjustments' => $adjustments,
        ], 200);
    }

    public static function handle_add(WP_REST_Request $request): WP_REST_Response {
        $body = $request->get_json_params();
        $item = isset($body['item']) && is_array($body['item']) ? $body['item'] : null;
        if (!$item) {
            return new WP_REST_Response(['error' => 'Missing item'], 400);
        }

        $item = self::sanitize_item($item);
        if ($item['distid'] === '' || $item['distsku'] === '' || $item['upc'] === '') {
            return new WP_REST_Response(['error' => 'Item missing required fields (distid, distsku, upc)'], 400);
        }

        $cart = self::get_cart();
        $items = $cart['items'];

        $found = false;
        $add_time_adjustment = null;
        foreach ($items as &$existing) {
            if (!is_array($existing)) continue;
            if (($existing['key'] ?? '') === $item['key']) {
                $previous_qty = (int) ($existing['qty'] ?? 1);
                $existing['qty'] = max(1, $previous_qty + (int) $item['qty']);
                // Refresh payload fields in case they changed, but preserve existing
                // product_class signals if the incoming payload doesn't contain them.
                $incoming_class = (string) ($item['product_class'] ?? '');
                $incoming_desc = (string) ($item['product_class_description'] ?? '');
                if ($incoming_class === '' && !empty($existing['product_class'])) {
                    $item['product_class'] = (string) $existing['product_class'];
                }
                if ($incoming_desc === '' && !empty($existing['product_class_description'])) {
                    $item['product_class_description'] = (string) $existing['product_class_description'];
                }

                $requested_total = (int) $existing['qty'];
                $existing = array_merge($existing, $item, ['qty' => $existing['qty']]);

                // Cap the merged qty so a sequence of small adds can't drift past on-hand.
                $merged_on_hand = (int) ($existing['qty_on_hand'] ?? 0);
                if ($merged_on_hand > 0 && (int) $existing['qty'] > $merged_on_hand) {
                    $existing['qty'] = $merged_on_hand;
                    // Capture so the next /cart load can show the banner.
                    $add_time_adjustment = [
                        'key'           => (string) $existing['key'],
                        'distid'        => (string) $existing['distid'],
                        'distsku'       => (string) $existing['distsku'],
                        'upc'           => (string) $existing['upc'],
                        'name'          => (string) $existing['name'],
                        'requested_qty' => $requested_total,
                        'adjusted_qty'  => (int) $existing['qty'],
                        'qty_on_hand'   => $merged_on_hand,
                    ];
                }

                $found = true;
                break;
            }
        }
        unset($existing);

        if (!$found) {
            $items[] = $item;
            // Even on a fresh line, sanitize_item may have capped the request.
            $requested_qty = max(1, (int) ($body['item']['qty'] ?? 1));
            if ((int) $item['qty'] < $requested_qty && (int) $item['qty_on_hand'] > 0) {
                $add_time_adjustment = [
                    'key'           => (string) $item['key'],
                    'distid'        => (string) $item['distid'],
                    'distsku'       => (string) $item['distsku'],
                    'upc'           => (string) $item['upc'],
                    'name'          => (string) $item['name'],
                    'requested_qty' => $requested_qty,
                    'adjusted_qty'  => (int) $item['qty'],
                    'qty_on_hand'   => (int) $item['qty_on_hand'],
                ];
            }
        }

        $cart['items'] = array_values($items);

        // Buffer add-time clamps so the cart page can render the banner the next
        // time it loads — without this, write-time clamps are silent.
        if ($add_time_adjustment !== null) {
            if (!isset($cart['pending_adjustments']) || !is_array($cart['pending_adjustments'])) {
                $cart['pending_adjustments'] = [];
            }
            $cart['pending_adjustments'][] = $add_time_adjustment;
        }

        $cart = self::save_cart($cart);

        return new WP_REST_Response([
            'ok' => true,
            'cart' => $cart,
            'summary' => self::cart_summary($cart),
        ], 200);
    }

    public static function handle_update(WP_REST_Request $request): WP_REST_Response {
        $body = $request->get_json_params();
        $key = isset($body['key']) ? sanitize_key($body['key']) : '';
        $qty = isset($body['qty']) ? (int) $body['qty'] : 0;
        if ($key === '') {
            return new WP_REST_Response(['error' => 'Missing key'], 400);
        }
        if ($qty < 1) {
            return new WP_REST_Response(['error' => 'Quantity must be >= 1'], 400);
        }

        $cart = self::get_cart();
        $items = $cart['items'];
        foreach ($items as &$it) {
            if (!is_array($it)) continue;
            if (($it['key'] ?? '') === $key) {
                $it['qty'] = $qty;
                break;
            }
        }
        unset($it);

        $cart['items'] = array_values($items);
        $cart = self::save_cart($cart);

        return new WP_REST_Response([
            'ok' => true,
            'cart' => $cart,
            'summary' => self::cart_summary($cart),
        ], 200);
    }

    public static function handle_remove(WP_REST_Request $request): WP_REST_Response {
        $body = $request->get_json_params();
        $key = isset($body['key']) ? sanitize_key($body['key']) : '';
        if ($key === '') {
            return new WP_REST_Response(['error' => 'Missing key'], 400);
        }

        $cart = self::get_cart();
        $cart['items'] = array_values(array_filter($cart['items'], function ($it) use ($key) {
            return is_array($it) && (($it['key'] ?? '') !== $key);
        }));

        $cart = self::save_cart($cart);

        return new WP_REST_Response([
            'ok' => true,
            'cart' => $cart,
            'summary' => self::cart_summary($cart),
        ], 200);
    }

    public static function handle_clear(WP_REST_Request $request): WP_REST_Response {
        $cart = [
            'items' => [],
            'updated_at' => null,
        ];
        $cart = self::save_cart($cart);
        return new WP_REST_Response([
            'ok' => true,
            'cart' => $cart,
            'summary' => self::cart_summary($cart),
        ], 200);
    }

    /**
     * Checkout:
     * - Validates cart has items.
     * - POSTs to Garidium API `place_distributor_order` with an order_source of `cockpit_cart`.
     * - Includes customer/store info as `customer` field.
     */
    public static function handle_checkout(WP_REST_Request $request): WP_REST_Response {
        $body = $request->get_json_params();

        $stored_key = trim((string) get_option('g_ffl_cockpit_key'));
        $provided_key = isset($body['api_key']) ? trim((string) sanitize_text_field(wp_unslash($body['api_key']))) : '';

        // Prefer a provided api_key if present, but prevent mismatches.
        // This keeps the checkout flow resilient in multisite / key-migration scenarios.
        if ($provided_key !== '' && $stored_key !== '' && $provided_key !== $stored_key) {
            return new WP_REST_Response(['error' => 'Invalid FFL Cockpit API key'], 400);
        }

        $api_key = $stored_key !== '' ? $stored_key : $provided_key;
        if ($api_key === '') {
            return new WP_REST_Response(['error' => 'Missing FFL Cockpit API key'], 400);
        }
        $ship_to_store = !empty($body['ship_to_store']);
        $destination = isset($body['destination']) ? $body['destination'] : '';

        $ffl = isset($body['ffl']) && is_array($body['ffl']) ? $body['ffl'] : [];
        $ffl_license_raw = '';
        if (isset($ffl['licenseNumber'])) {
            $ffl_license_raw = $ffl['licenseNumber'];
        } elseif (isset($ffl['license_number'])) {
            $ffl_license_raw = $ffl['license_number'];
        }
        $ffl_sanitized = [
            // The external place-order API expects camelCase.
            'licenseNumber' => $ffl_license_raw !== '' ? sanitize_text_field(wp_unslash($ffl_license_raw)) : '',
            'name' => isset($ffl['name']) ? sanitize_text_field(wp_unslash($ffl['name'])) : '',
            'address' => isset($ffl['address']) ? sanitize_text_field(wp_unslash($ffl['address'])) : '',
            'city' => isset($ffl['city']) ? sanitize_text_field(wp_unslash($ffl['city'])) : '',
            'state' => isset($ffl['state']) ? sanitize_text_field(wp_unslash($ffl['state'])) : '',
            'phone' => isset($ffl['phone']) ? sanitize_text_field(wp_unslash($ffl['phone'])) : '',
            'email' => isset($ffl['email']) ? sanitize_email(wp_unslash($ffl['email'])) : '',
        ];

        $customer = isset($body['customer']) && is_array($body['customer']) ? $body['customer'] : [];
        $customer = [
            'name' => isset($customer['name']) ? sanitize_text_field(wp_unslash($customer['name'])) : '',
            'email' => isset($customer['email']) ? sanitize_email(wp_unslash($customer['email'])) : '',
            'phone' => isset($customer['phone']) ? sanitize_text_field(wp_unslash($customer['phone'])) : '',
            'address1' => isset($customer['address1']) ? sanitize_text_field(wp_unslash($customer['address1'])) : '',
            'address2' => isset($customer['address2']) ? sanitize_text_field(wp_unslash($customer['address2'])) : '',
            'city' => isset($customer['city']) ? sanitize_text_field(wp_unslash($customer['city'])) : '',
            'state' => isset($customer['state']) ? sanitize_text_field(wp_unslash($customer['state'])) : '',
            'zip' => isset($customer['zip']) ? sanitize_text_field(wp_unslash($customer['zip'])) : '',
        ];

        // Minimal validation
        $required = $ship_to_store ? ['name'] : ['name'];
        if (!$ship_to_store) {
            // If destination is 'ffl', address is not required, but phone/email are.
            // If destination is 'customer' or 'mixed', address is required.
            // If destination is not provided (legacy), assume address is required.
            
            if ($destination === 'customer' || $destination === 'mixed' || empty($destination)) {
                $required = array_merge($required, ['address1', 'city', 'state', 'zip']);
            }
            
            if ($destination === 'ffl' || $destination === 'mixed') {
                $required = array_merge($required, ['phone', 'email']);
            }
        }
        
        foreach ($required as $f) {
            if (empty($customer[$f])) {
                return new WP_REST_Response(['error' => 'Missing required customer field: ' . $f], 400);
            }
        }

        $cart = self::get_cart();
        if (empty($cart['items'])) {
            return new WP_REST_Response(['error' => 'Cart is empty'], 400);
        }

        $summary = self::cart_summary($cart, false);
        $has_firearm = !empty($summary['has_firearm']);

        // If this is a firearm drop-ship order, require an FFL.
        if (!$ship_to_store && $has_firearm) {
            if (empty($ffl_sanitized['licenseNumber'])) {
                return new WP_REST_Response(['error' => 'Missing required FFL license number for firearm drop ship order'], 400);
            }
        }

        // Build items payload to match existing WooCommerce fulfillment order submission
        $items = [];
        foreach ($cart['items'] as $it) {
            if (!is_array($it)) continue;
            $items[] = [
                'distid' => (string) ($it['distid'] ?? ''),
                'distsku' => (string) ($it['distsku'] ?? ''),
                'upc' => (string) ($it['upc'] ?? ''),
                'ffl_req' => (bool) ($it['ffl_req'] ?? false),
                'qty' => max(1, (int) ($it['qty'] ?? 1)),
            ];
        }
        if (empty($items)) {
            return new WP_REST_Response(['error' => 'Cart contains no valid items'], 400);
        }

        $data = [
            'api_key' => $api_key,
            'order_source' => 'cockpit_cart',
            'order_id' => time(),
            'origin' => home_url(),
            'ship_to_store' => $ship_to_store,
            'destination' => $destination,
            'items' => $items,
            'user' => [
                'id' => get_current_user_id(),
                'user_login' => wp_get_current_user()->user_login,
                'user_email' => wp_get_current_user()->user_email,
                'display_name' => wp_get_current_user()->display_name,
            ],
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
        ];

        $data['customer'] = $customer;
        if (!$ship_to_store) {
            if ($has_firearm || $destination === 'ffl' || $destination === 'mixed') {
                $data['ffl'] = $ffl_sanitized;
            }
        }

        $payload = [
            'action' => 'place_distributor_order',
            'data' => $data,
        ];

        // If preview requested, return the payload without sending.
        if (!empty($body['preview'])) {
            return new WP_REST_Response([
                'ok' => true,
                'preview' => $payload,
            ], 200);
        }

        $resp = wp_remote_post('https://ffl-api.garidium.com', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'x-api-key' => $api_key,
            ],
            'timeout' => 30,
            'body' => wp_json_encode($payload),
        ]);

        if (is_wp_error($resp)) {
            return new WP_REST_Response(['error' => $resp->get_error_message()], 500);
        }

        $code = (int) wp_remote_retrieve_response_code($resp);
        $raw = (string) wp_remote_retrieve_body($resp);
        $decoded = json_decode($raw, true);

        if (!is_array($decoded)) {
            return new WP_REST_Response([
                'error' => 'Unexpected response from server',
                'status_code' => $code,
                'raw' => $raw,
            ], 502);
        }

        // If success, clear cart.
        if (!empty($decoded['status']) || !empty($decoded['success'])) {
            self::handle_clear($request);
        }

        return new WP_REST_Response([
            'ok' => true,
            'result' => $decoded,
        ], 200);
    }
}
