<?php

namespace WooNooW\Api;

use WP_Error;
use WP_REST_Request;
use WC_Order;
use WC_Product;
use WC_Shipping_Zones;
use WC_Shipping_Rate;

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

class CheckoutController
{

    /**
     * Register REST routes for checkout quote & submit
     */
    public static function register()
    {
        $namespace = 'woonoow/v1';
        register_rest_route($namespace, '/checkout/quote', [
            'methods'  => 'POST',
            'callback' => [new self(), 'quote'],
            'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'], // consider nonce check later
        ]);
        register_rest_route($namespace, '/checkout/submit', [
            'methods'  => 'POST',
            'callback' => [new self(), 'submit'],
            'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'], // consider capability/nonce
        ]);
        register_rest_route($namespace, '/checkout/fields', [
            'methods'  => 'POST',
            'callback' => [new self(), 'get_fields'],
            'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'],
        ]);
        // Public countries endpoint for customer checkout form
        register_rest_route($namespace, '/countries', [
            'methods'  => 'GET',
            'callback' => [new self(), 'get_countries'],
            'permission_callback' => '__return_true', // Public - needed for checkout
        ]);
        // Public order view endpoint for thank you page
        register_rest_route($namespace, '/checkout/order/(?P<id>\d+)', [
            'methods'  => 'GET',
            'callback' => [new self(), 'get_order'],
            'permission_callback' => '__return_true', // Public, validated via order_key
            'args' => [
                'key' => [
                    'type' => 'string',
                    'required' => false,
                ],
            ],
        ]);
        // Get available shipping rates for given address
        register_rest_route($namespace, '/checkout/shipping-rates', [
            'methods'  => 'POST',
            'callback' => [new self(), 'get_shipping_rates'],
            'permission_callback' => [\WooNooW\Api\Permissions::class, 'anon_or_wp_nonce'],
        ]);
        // Process payment for an existing order (e.g. renewal)
        register_rest_route($namespace, '/checkout/pay-order/(?P<id>\d+)', [
            'methods'  => 'POST',
            'callback' => [new self(), 'pay_order'],
            'permission_callback' => '__return_true', // Validated via order key/owner in method
        ]);
    }

    /**
     * Build a quote for the given payload:
     * {
     *   items: [{ product_id, variation_id?, qty, meta?[] }],
     *   billing: {...},
     *   shipping: {..., ship_to_different?},
     *   coupons: ["CODE"],
     *   shipping_method: "flat_rate:1" | "free_shipping:3" | ...
     * }
     */
    public function quote(WP_REST_Request $r): array
    {
        $__t0 = microtime(true);
        $payload = $this->sanitize_payload($r);

        // Allow a fully accurate quote using Woo's Cart mechanics (slower, but parity)
        $useAccurate = (bool) apply_filters('woonoow/quote/accurate', false);
        if ($useAccurate) {
            $resp = $this->accurate_quote_via_wc_cart($payload);
            if (!headers_sent()) {
                header('Server-Timing: app;dur=' . round((microtime(true) - $__t0) * 1000, 1));
            }
            return $resp;
        }

        // Build a transient cart-like calculation using Woo helpers.
        // We will sum line totals and attempt shipping/tax estimates.
        $lines = [];
        $subtotal = 0.0;
        $discount = 0.0; // coupons not applied to calculations in this v0 (placeholder)
        $shipping_total = 0.0;
        $tax_total = 0.0;

        foreach ($payload['items'] as $line) {
            $product = $this->load_product($line);
            if ($product instanceof WP_Error) {
                return ['error' => $product->get_error_message()];
            }
            $qty = max(1, (int)($line['qty'] ?? 1));
            $price = (float) wc_get_price_to_display($product);
            $line_total = $price * $qty;

            $lines[] = [
                'product_id' => $product->get_id(),
                'name'       => $product->get_name(),
                'qty'        => $qty,
                'price'      => $price,
                'line_total' => $line_total,
            ];
            $subtotal += $line_total;
        }

        // --- simple coupon handling (fast path) ---
        if (!empty($payload['coupons'])) {
            foreach ($payload['coupons'] as $code) {
                $coupon = new \WC_Coupon($code);
                if ($coupon->get_id()) {
                    $type = $coupon->get_discount_type(); // 'percent', 'fixed_cart', etc.
                    $amount = (float) $coupon->get_amount();
                    if ($type === 'percent') {
                        $discount += ($amount / 100.0) * $subtotal;
                    } elseif ($type === 'fixed_cart') {
                        $discount += min($amount, $subtotal);
                    }
                    // NOTE: fixed_product & restrictions are ignored in FastQuote
                }
            }
        }
        $discount = min($discount, $subtotal);

        // Simple shipping estimate using zones (optional, best-effort).
        if (!empty($payload['shipping']['postcode']) && !empty($payload['shipping']['country'])) {
            $shipping_total = $this->estimate_shipping($payload['shipping'], $payload['shipping_method'] ?? null);
        }

        $grand = max(0, $subtotal - $discount + $shipping_total + $tax_total);

        if (!headers_sent()) {
            header('Server-Timing: app;dur=' . round((microtime(true) - $__t0) * 1000, 1));
        }

        return [
            'ok'      => true,
            'items'   => $lines,
            'totals'  => [
                'subtotal'        => wc_format_decimal($subtotal, wc_get_price_decimals()),
                'discount_total'  => wc_format_decimal($discount, wc_get_price_decimals()),
                'shipping_total'  => wc_format_decimal($shipping_total, wc_get_price_decimals()),
                'tax_total'       => wc_format_decimal($tax_total, wc_get_price_decimals()),
                'grand_total'     => wc_format_decimal($grand, wc_get_price_decimals()),
                'currency'        => get_woocommerce_currency(),
                'currency_symbol' => get_woocommerce_currency_symbol(),
                'currency_pos'    => get_option('woocommerce_currency_pos', 'left'),
                'decimals'        => wc_get_price_decimals(),
                'decimal_sep'     => wc_get_price_decimal_separator(),
                'thousand_sep'    => wc_get_price_thousand_separator(),
            ],
        ];
    }

    /**
     * Public order view endpoint for thank you page
     * Validates access via order_key (for guests) or logged-in customer ID
     * GET /checkout/order/{id}?key=wc_order_xxx
     */
    public function get_order(WP_REST_Request $r): array
    {
        $order_id = absint($r['id']);
        $order_key = sanitize_text_field($r->get_param('key') ?? '');

        if (!$order_id) {
            return ['error' => __('Invalid order ID', 'woonoow')];
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return ['error' => __('Order not found', 'woonoow')];
        }

        // Validate access: order_key must match OR user must be logged in and own the order (or be admin)
        $valid_key = $order_key && hash_equals($order->get_order_key(), $order_key);
        $valid_owner = is_user_logged_in() && (
            get_current_user_id() === $order->get_customer_id() ||
            current_user_can('manage_woocommerce')
        );

        if (!$valid_key && !$valid_owner) {
            return ['error' => __('Unauthorized access to order', 'woonoow')];
        }

        // Build order items
        $items = [];
        foreach ($order->get_items() as $item) {
            $product = $item->get_product();
            $items[] = [
                'id' => $item->get_id(),
                'product_id' => $product ? $product->get_id() : 0,
                'name' => $item->get_name(),
                'qty' => (int) $item->get_quantity(),
                'price' => (float) $item->get_total() / max(1, $item->get_quantity()),
                'total' => (float) $item->get_total(),
                'image' => $product ? wp_get_attachment_image_url($product->get_image_id(), 'thumbnail') : null,
            ];
        }

        // Build shipping lines
        $shipping_lines = [];
        foreach ($order->get_shipping_methods() as $shipping_item) {
            $shipping_lines[] = [
                'id' => $shipping_item->get_id(),
                'method_title' => $shipping_item->get_method_title(),
                'method_id' => $shipping_item->get_method_id(),
                'total' => wc_price($shipping_item->get_total()),
            ];
        }

        // Get tracking info from order meta (various plugins use different keys)
        $tracking_number = $order->get_meta('_tracking_number')
            ?: $order->get_meta('_wc_shipment_tracking_items')
            ?: $order->get_meta('_rajaongkir_awb_number')
            ?: '';
        $tracking_url = $order->get_meta('_tracking_url')
            ?: $order->get_meta('_rajaongkir_tracking_url')
            ?: '';

        // Check for shipment tracking plugin format (array of tracking items)
        if (is_array($tracking_number) && !empty($tracking_number)) {
            $first_tracking = reset($tracking_number);
            $tracking_number = $first_tracking['tracking_number'] ?? '';
            $tracking_url = $first_tracking['tracking_url'] ?? $tracking_url;
        }

        return [
            'ok' => true,
            'id' => $order->get_id(),
            'number' => $order->get_order_number(),
            'status' => $order->get_status(),
            'created_via' => $order->get_created_via(),
            'subtotal' => (float) $order->get_subtotal(),
            'discount_total' => (float) $order->get_discount_total(),
            'shipping_total' => (float) $order->get_shipping_total(),
            'tax_total' => (float) $order->get_total_tax(),
            'total' => (float) $order->get_total(),
            'currency' => $order->get_currency(),
            'currency_symbol' => get_woocommerce_currency_symbol($order->get_currency()),
            'payment_method' => $order->get_payment_method_title(),
            'needs_shipping' => count($shipping_lines) > 0 || $order->needs_shipping_address(),
            'shipping_lines' => $shipping_lines,
            'tracking_number' => $tracking_number,
            'tracking_url' => $tracking_url,
            'billing' => [
                'first_name' => $order->get_billing_first_name(),
                'last_name' => $order->get_billing_last_name(),
                'email' => $order->get_billing_email(),
                'phone' => $order->get_billing_phone(),
            ],
            'items' => $items,
            'subscription' => $this->get_subscription_for_response($order),
            'available_gateways' => $this->get_available_gateways_for_order($order),
        ];
    }

    private function get_subscription_for_response($order)
    {
        if (!class_exists('\WooNooW\Modules\Subscription\SubscriptionManager')) {
            return null;
        }
        $sub = \WooNooW\Modules\Subscription\SubscriptionManager::get_by_order_id($order->get_id());
        if (!$sub) return null;

        return [
            'id' => (int) $sub->id,
            'status' => $sub->status,
            'billing_period' => $sub->billing_period,
            'billing_interval' => (int) $sub->billing_interval,
            'start_date' => $sub->start_date,
            'next_payment_date' => $sub->next_payment_date,
            'end_date' => $sub->end_date,
            'recurring_amount' => (float) $sub->recurring_amount,
        ];
    }

    /**
     * Submit an order:
     * {
     *   items: [{ product_id, variation_id?, qty, meta?[] }],
     *   billing: {...},
     *   shipping: {..., ship_to_different?},
     *   coupons: ["CODE"],
     *   shipping_method: "flat_rate:1",
     *   payment_method: "cod" | "bacs" | ...
     * }
     */
    public function submit(WP_REST_Request $r): array
    {
        $__t0 = microtime(true);
        $payload = $this->sanitize_payload($r);

        if (empty($payload['items'])) {
            return ['error' => __('No items provided', 'woonoow')];
        }

        // Security: Rate limiting check
        if (\WooNooW\Compat\SecuritySettingsProvider::is_rate_limited()) {
            return ['error' => __('Too many orders. Please try again later.', 'woonoow')];
        }

        // Security: CAPTCHA validation
        $captcha_token = $payload['captcha_token'] ?? '';
        $captcha_result = \WooNooW\Compat\SecuritySettingsProvider::validate_captcha($captcha_token);
        if (is_wp_error($captcha_result)) {
            return ['error' => $captcha_result->get_error_message()];
        }

        // Create order
        $order = wc_create_order();
        if (is_wp_error($order)) {
            return ['error' => $order->get_error_message()];
        }

        if (!empty($payload['referral_code'])) {
            $order->update_meta_data('_woonoow_referral_code', $payload['referral_code']);
            $_COOKIE['woonoow_ref'] = $payload['referral_code'];
        }

        // Track if user was logged in during this request (for frontend page reload)
        $user_logged_in = false;

        // Set customer ID if user is logged in
        if (is_user_logged_in()) {
            $user_id = get_current_user_id();
            $order->set_customer_id($user_id);

            // Update user's billing information from checkout data
            if (!empty($payload['billing'])) {
                $billing = $payload['billing'];

                // Update first name and last name
                if (!empty($billing['first_name'])) {
                    update_user_meta($user_id, 'first_name', sanitize_text_field($billing['first_name']));
                    update_user_meta($user_id, 'billing_first_name', sanitize_text_field($billing['first_name']));
                }
                if (!empty($billing['last_name'])) {
                    update_user_meta($user_id, 'last_name', sanitize_text_field($billing['last_name']));
                    update_user_meta($user_id, 'billing_last_name', sanitize_text_field($billing['last_name']));
                }

                // Update billing phone
                if (!empty($billing['phone'])) {
                    update_user_meta($user_id, 'billing_phone', sanitize_text_field($billing['phone']));
                }

                // Update billing email
                if (!empty($billing['email'])) {
                    update_user_meta($user_id, 'billing_email', sanitize_email($billing['email']));
                }
            }
        } else {
            // Guest checkout - check if auto-register is enabled
            $customer_settings = \WooNooW\Compat\CustomerSettingsProvider::get_settings();
            $auto_register = $customer_settings['auto_register_members'] ?? false;

            if ($auto_register && !empty($payload['billing']['email'])) {
                $email = sanitize_email($payload['billing']['email']);

                // Check if user already exists
                $existing_user = get_user_by('email', $email);

                if ($existing_user) {
                    // User exists - link order to them (but do NOT auto-login for security)
                    $order->set_customer_id($existing_user->ID);
                    // Note: user_logged_in stays false - existing users must authenticate separately
                } else {
                    // Create new user account
                    $password = wp_generate_password(12, true, true);

                    $userdata = [
                        'user_login'   => $email,
                        'user_email'   => $email,
                        'user_pass'    => $password,
                        'first_name'   => sanitize_text_field($payload['billing']['first_name'] ?? ''),
                        'last_name'    => sanitize_text_field($payload['billing']['last_name'] ?? ''),
                        'display_name' => trim((sanitize_text_field($payload['billing']['first_name'] ?? '') . ' ' . sanitize_text_field($payload['billing']['last_name'] ?? ''))) ?: $email,
                        'role'         => 'customer', // WooCommerce customer role
                    ];

                    $new_user_id = wp_insert_user($userdata);

                    if (!is_wp_error($new_user_id)) {
                        // Link order to new user
                        $order->set_customer_id($new_user_id);

                        // Store temp password in user meta for email template
                        // The real password is already set via wp_insert_user
                        update_user_meta($new_user_id, '_woonoow_temp_password', $password);

                        // AUTO-LOGIN: Set authentication cookie so user is logged in after page reload
                        wp_set_auth_cookie($new_user_id, true);
                        wp_set_current_user($new_user_id);
                        $user_logged_in = true;

                        // Set WooCommerce customer billing data
                        $customer = new \WC_Customer($new_user_id);

                        if (!empty($payload['billing']['first_name'])) $customer->set_billing_first_name(sanitize_text_field($payload['billing']['first_name']));
                        if (!empty($payload['billing']['last_name'])) $customer->set_billing_last_name(sanitize_text_field($payload['billing']['last_name']));
                        if (!empty($payload['billing']['email'])) $customer->set_billing_email(sanitize_email($payload['billing']['email']));
                        if (!empty($payload['billing']['phone'])) $customer->set_billing_phone(sanitize_text_field($payload['billing']['phone']));
                        if (!empty($payload['billing']['address_1'])) $customer->set_billing_address_1(sanitize_text_field($payload['billing']['address_1']));
                        if (!empty($payload['billing']['city'])) $customer->set_billing_city(sanitize_text_field($payload['billing']['city']));
                        if (!empty($payload['billing']['state'])) $customer->set_billing_state(sanitize_text_field($payload['billing']['state']));
                        if (!empty($payload['billing']['postcode'])) $customer->set_billing_postcode(sanitize_text_field($payload['billing']['postcode']));
                        if (!empty($payload['billing']['country'])) $customer->set_billing_country(sanitize_text_field($payload['billing']['country']));

                        $customer->save();

                        // Send new account email (WooCommerce will handle this automatically via hook)
                        do_action('woocommerce_created_customer', $new_user_id, $userdata, $password);
                    }
                }
            }
        }

        // Add items
        foreach ($payload['items'] as $line) {
            $product = $this->load_product($line);
            if (is_wp_error($product)) {
                return ['error' => $product->get_error_message()];
            }
            $qty = max(1, (int)($line['qty'] ?? 1));

            $args = [];
            if (!empty($line['meta']) && is_array($line['meta'])) {
                $args['item_meta_array'] = $line['meta'];
            }

            $order->add_product($product, $qty, $args);
        }

        // Addresses
        if (!empty($payload['billing'])) {
            $order->set_address($this->only_address_fields($payload['billing']), 'billing');
        }
        if (!empty($payload['shipping'])) {
            $ship = $payload['shipping'];
            // If ship_to_different is false, copy billing
            if (empty($ship['ship_to_different'])) {
                $ship = $this->only_address_fields($payload['billing'] ?: []);
            }
            $order->set_address($this->only_address_fields($ship), 'shipping');
        }

        // Coupons (best‑effort using Woo Coupon objects)
        if (!empty($payload['coupons']) && is_array($payload['coupons'])) {
            foreach ($payload['coupons'] as $code) {
                try {
                    $coupon = new \WC_Coupon(wc_clean(wp_unslash($code)));
                    if ($coupon->get_id()) {
                        $order->apply_coupon($coupon);
                    }
                } catch (\Throwable $e) {
                    // ignore invalid in v0
                }
            }
        }

        // Shipping (best‑effort estimate)
        if (!empty($payload['shipping_method'])) {
            $rate = $this->find_shipping_rate_for_order($order, $payload['shipping_method']);
            if ($rate instanceof WC_Shipping_Rate) {
                $item = new \WC_Order_Item_Shipping();
                $item->set_props([
                    'method_title' => $rate->get_label(),
                    'method_id'    => $rate->get_method_id(),
                    'instance_id'  => $rate->get_instance_id(),
                    'total'        => $rate->get_cost(),
                    'taxes'        => $rate->get_taxes(),
                ]);
                $order->add_item($item);
            } elseif (!empty($payload['shipping_cost']) && $payload['shipping_cost'] > 0) {
                // Fallback: use shipping_cost directly from frontend
                // This handles API-based shipping like Rajaongkir where WC zones don't apply
                $item = new \WC_Order_Item_Shipping();

                // Parse method ID from shipping_method (format: "method_id:instance_id" or "method_id:instance_id:variant")
                $parts = explode(':', $payload['shipping_method']);
                $method_id = $parts[0] ?? 'shipping';
                $instance_id = isset($parts[1]) ? (int)$parts[1] : 0;

                $item->set_props([
                    'method_title' => sanitize_text_field($payload['shipping_title'] ?? 'Shipping'),
                    'method_id'    => sanitize_text_field($method_id),
                    'instance_id'  => $instance_id,
                    'total'        => floatval($payload['shipping_cost']),
                ]);
                $order->add_item($item);
            }
        }

        // Payment method
        if (!empty($payload['payment_method'])) {
            $order->set_payment_method($payload['payment_method']);
        }

        // Totals
        $order->calculate_totals();

        // Mirror Woo hooks so extensions still work (but not thankyou which outputs HTML)
        do_action('woocommerce_checkout_create_order', $order->get_id(), $order);

        $order->save();

        // wc_create_order() fires woocommerce_new_order before SPA checkout line
        // items exist. Fire the processed hook after saving so integrations that
        // calculate from order items, like affiliate tracking, see a complete order.
        do_action('woocommerce_checkout_order_processed', $order->get_id(), $payload, $order);

        if (!headers_sent()) {
            header('Server-Timing: app;dur=' . round((microtime(true) - $__t0) * 1000, 1));
        }

        // Clear WooCommerce cart after successful order placement
        // This ensures the cart page won't re-populate from server session
        if (function_exists('WC') && WC()->cart) {
            WC()->cart->empty_cart();
        }

        // Record this order attempt for rate limiting
        \WooNooW\Compat\SecuritySettingsProvider::record_order_attempt();

        return [
            'ok' => true,
            'order_id' => $order->get_id(),
            'order_key' => $order->get_order_key(),
            'status' => $order->get_status(),
            'pay_url' => $order->get_checkout_payment_url(),
            'thankyou_url' => $order->get_checkout_order_received_url(),
            'user_logged_in' => $user_logged_in, // True if user was logged in during this request (requires page reload)
        ];
    }

    /**
     * Process payment for an existing order
     * POST /checkout/pay-order/{id}
     */
    public function pay_order(WP_REST_Request $r): array
    {
        $order_id = absint($r['id']);
        $order = wc_get_order($order_id);

        if (!$order) {
            return ['error' => __('Order not found', 'woonoow')];
        }

        // Validate access
        $key = $r->get_param('key'); // optional if logged in
        $valid_key = $key && hash_equals($order->get_order_key(), $key);
        $valid_owner = is_user_logged_in() && (
            get_current_user_id() === $order->get_customer_id() ||
            current_user_can('manage_woocommerce')
        );

        if (!$valid_key && !$valid_owner) {
            return ['error' => __('Unauthorized access', 'woonoow')];
        }

        if ($order->is_paid()) {
            return ['error' => __('Order already paid', 'woonoow')];
        }

        $payment_method = wc_clean($r->get_param('payment_method'));
        if (empty($payment_method)) {
            return ['error' => __('Payment method required', 'woonoow')];
        }

        // Update payment method
        $available = WC()->payment_gateways()->get_available_payment_gateways();
        if (!isset($available[$payment_method])) {
            return ['error' => __('Invalid payment method', 'woonoow')];
        }

        $gateway = $available[$payment_method];
        $order->set_payment_method($gateway);
        $order->save();

        // Process payment
        $result = $gateway->process_payment($order_id);

        if (isset($result['result']) && $result['result'] === 'success') {
            return [
                'ok' => true,
                'redirect' => $result['redirect'] ?? $order->get_checkout_order_received_url(),
            ];
        }

        return [
            'error' => __('Payment failed', 'woonoow') . (isset($result['result']) ? ': ' . $result['result'] : ''),
            'messages' => wc_get_notices('error'),
        ];
    }

    /**
     * Get checkout fields with all filters applied
     * Accepts: { items: [...], is_digital_only?: bool }
     * Returns fields with required, hidden, etc. based on addons + cart context
     */
    public function get_fields(WP_REST_Request $r): array
    {
        $json = $r->get_json_params();
        $items = isset($json['items']) && is_array($json['items']) ? $json['items'] : [];
        $is_digital_only = isset($json['is_digital_only']) ? (bool) $json['is_digital_only'] : false;

        // Initialize WooCommerce checkout if not already
        if (!WC()->checkout()) {
            WC()->initialize_session();
            WC()->initialize_cart();
        }

        // Get checkout fields with all filters applied
        $fields = WC()->checkout()->get_checkout_fields();

        $formatted = [];

        foreach ($fields as $fieldset_key => $fieldset) {
            foreach ($fieldset as $key => $field) {
                // Check if field should be hidden
                $hidden = false;

                // Hide shipping fields if digital only (your existing logic)
                if ($is_digital_only && $fieldset_key === 'shipping') {
                    $hidden = true;
                }

                // Check if addon/filter explicitly hides this field
                if (isset($field['class']) && is_array($field['class'])) {
                    if (in_array('hidden', $field['class']) || in_array('hide', $field['class'])) {
                        $hidden = true;
                    }
                }

                // Respect 'enabled' flag if set by addons
                if (isset($field['enabled']) && !$field['enabled']) {
                    $hidden = true;
                }

                $formatted[] = [
                    'key'         => $key,
                    'fieldset'    => $fieldset_key, // billing, shipping, account, order
                    'type'        => $field['type'] ?? 'text',
                    'label'       => $field['label'] ?? '',
                    'placeholder' => $field['placeholder'] ?? '',
                    'required'    => $field['required'] ?? false,
                    'hidden'      => $hidden,
                    'class'       => $field['class'] ?? [],
                    'priority'    => $field['priority'] ?? 10,
                    'options'     => $field['options'] ?? null, // For select fields
                    'custom'      => !in_array($key, $this->get_standard_field_keys()), // Flag custom fields
                    'autocomplete' => $field['autocomplete'] ?? '',
                    'validate'    => $field['validate'] ?? [],
                    // New fields for dynamic rendering
                    'input_class'       => $field['input_class'] ?? [],
                    'custom_attributes' => $field['custom_attributes'] ?? [],
                    'default'           => $field['default'] ?? '',
                    // For searchable_select type
                    'search_endpoint'   => $field['search_endpoint'] ?? null,
                    'search_param'      => $field['search_param'] ?? 'search',
                    'min_chars'         => $field['min_chars'] ?? 2,
                ];
            }
        }

        // Sort by priority
        usort($formatted, function ($a, $b) {
            return $a['priority'] <=> $b['priority'];
        });

        return [
            'ok' => true,
            'fields' => $formatted,
            'is_digital_only' => $is_digital_only,
        ];
    }

    /**
     * Get list of standard WooCommerce field keys
     * Plugins can extend this list via the 'woonoow_standard_checkout_field_keys' filter
     */
    private function get_standard_field_keys(): array
    {
        $keys = [
            'billing_first_name',
            'billing_last_name',
            'billing_company',
            'billing_country',
            'billing_address_1',
            'billing_address_2',
            'billing_city',
            'billing_state',
            'billing_postcode',
            'billing_phone',
            'billing_email',
            'shipping_first_name',
            'shipping_last_name',
            'shipping_company',
            'shipping_country',
            'shipping_address_1',
            'shipping_address_2',
            'shipping_city',
            'shipping_state',
            'shipping_postcode',
            'order_comments',
        ];

        /**
         * Filter the list of standard checkout field keys.
         * Plugins can add their own field keys to be recognized as "standard" (not custom).
         *
         * @param array $keys List of standard field keys
         */
        return apply_filters('woonoow_standard_checkout_field_keys', $keys);
    }

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

    private function accurate_quote_via_wc_cart(array $payload): array
    {
        if (!WC()->customer) {
            WC()->customer = new \WC_Customer(get_current_user_id(), true);
        }
        if (!WC()->cart) {
            WC()->cart = new \WC_Cart();
        }

        // Address context for taxes/shipping rules - set temporarily without saving to user profile
        $ship = !empty($payload['shipping']) ? $payload['shipping'] : $payload['billing'];
        if (!empty($payload['billing'])) {
            foreach (['country', 'state', 'postcode', 'city', 'address_1', 'address_2'] as $k) {
                $setter = 'set_billing_' . $k;
                if (method_exists(WC()->customer, $setter) && isset($payload['billing'][$k])) {
                    WC()->customer->{$setter}(wc_clean($payload['billing'][$k]));
                }
            }
        }
        if (!empty($ship)) {
            foreach (['country', 'state', 'postcode', 'city', 'address_1', 'address_2'] as $k) {
                $setter = 'set_shipping_' . $k;
                if (method_exists(WC()->customer, $setter) && isset($ship[$k])) {
                    WC()->customer->{$setter}(wc_clean($ship[$k]));
                }
            }
        }
        // DO NOT save customer data - only use for quote calculation
        // WC()->customer->save(); // REMOVED - should not update user profile during checkout

        WC()->cart->empty_cart(true);
        foreach ($payload['items'] as $line) {
            $product = $this->load_product($line);
            if (is_wp_error($product)) {
                return ['error' => $product->get_error_message()];
            }
            WC()->cart->add_to_cart($product->get_id(), max(1, (int) ($line['qty'] ?? 1)));
        }

        if (!empty($payload['coupons'])) {
            foreach ($payload['coupons'] as $code) {
                try {
                    WC()->cart->apply_coupon($code);
                } catch (\Throwable $e) {
                    // ignore invalid in v0
                }
            }
        }

        WC()->cart->calculate_totals();

        $subtotal = (float) WC()->cart->get_subtotal();
        $discount = (float) WC()->cart->get_discount_total();
        $shipping_total = (float) WC()->cart->get_shipping_total();
        $tax_total = (float) WC()->cart->get_total_tax();
        $grand = (float) WC()->cart->get_total('edit');

        $lines = [];
        foreach (WC()->cart->get_cart() as $ci) {
            $p = $ci['data'];
            $qty = (int) $ci['quantity'];
            $price = (float) wc_get_price_to_display($p);
            $lines[] = [
                'product_id' => $p->get_id(),
                'name'       => $p->get_name(),
                'qty'        => $qty,
                'price'      => $price,
                'line_total' => $price * $qty,
            ];
        }

        return [
            'ok' => true,
            'items' => $lines,
            'totals'  => [
                'subtotal'        => wc_format_decimal($subtotal, wc_get_price_decimals()),
                'discount_total'  => wc_format_decimal($discount, wc_get_price_decimals()),
                'shipping_total'  => wc_format_decimal($shipping_total, wc_get_price_decimals()),
                'tax_total'       => wc_format_decimal($tax_total, wc_get_price_decimals()),
                'grand_total'     => wc_format_decimal($grand, wc_get_price_decimals()),
                'currency'        => get_woocommerce_currency(),
                'currency_symbol' => get_woocommerce_currency_symbol(),
                'currency_pos'    => get_option('woocommerce_currency_pos', 'left'),
                'decimals'        => wc_get_price_decimals(),
                'decimal_sep'     => wc_get_price_decimal_separator(),
                'thousand_sep'    => wc_get_price_thousand_separator(),
            ],
        ];
    }

    private function sanitize_payload(WP_REST_Request $r): array
    {
        $json = $r->get_json_params();

        $items = isset($json['items']) && is_array($json['items']) ? $json['items'] : [];
        $billing = isset($json['billing']) && is_array($json['billing']) ? $json['billing'] : [];
        $shipping = isset($json['shipping']) && is_array($json['shipping']) ? $json['shipping'] : [];
        $coupons = isset($json['coupons']) && is_array($json['coupons']) ? array_map('wc_clean', $json['coupons']) : [];
        $custom_fields = isset($json['custom_fields']) && is_array($json['custom_fields']) ? $json['custom_fields'] : [];

        return [
            'items' => array_map(function ($i) {
                return [
                    'product_id'   => isset($i['product_id']) ? (int) $i['product_id'] : 0,
                    'variation_id' => isset($i['variation_id']) ? (int) $i['variation_id'] : 0,
                    'qty'          => isset($i['qty']) ? (int) $i['qty'] : 1,
                    'meta'         => isset($i['meta']) && is_array($i['meta']) ? $i['meta'] : [],
                ];
            }, $items),
            'billing' => $billing,
            'shipping' => $shipping,
            'coupons' => $coupons,
            'shipping_method' => isset($json['shipping_method']) ? wc_clean($json['shipping_method']) : null,
            'payment_method'  => isset($json['payment_method']) ? wc_clean($json['payment_method']) : null,
            // NEW: Added missing fields that were causing shipping to not be applied
            'shipping_cost'   => isset($json['shipping_cost']) ? (float) $json['shipping_cost'] : null,
            'shipping_title'  => isset($json['shipping_title']) ? sanitize_text_field($json['shipping_title']) : null,
            'custom_fields'   => $custom_fields,
            'customer_note'   => isset($json['customer_note']) ? sanitize_textarea_field($json['customer_note']) : '',
            'referral_code'   => isset($json['referral_code']) ? sanitize_text_field($json['referral_code']) : '',
        ];
    }

    private function load_product(array $line)
    {
        $pid = (int)($line['variation_id'] ?? 0) ?: (int)($line['product_id'] ?? 0);
        if (!$pid) {
            return new WP_Error('bad_item', __('Invalid product id', 'woonoow'));
        }
        $product = wc_get_product($pid);
        if (!$product || !$product->is_purchasable()) {
            return new WP_Error('bad_item', __('Product not purchasable', 'woonoow'));
        }
        return $product;
    }

    private function only_address_fields(array $src): array
    {
        $keys = ['first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'email', 'phone'];
        $out = [];
        foreach ($keys as $k) {
            if (isset($src[$k])) $out[$k] = wc_clean(wp_unslash($src[$k]));
        }
        return $out;
    }

    private function estimate_shipping(array $address, ?string $chosen_method): float
    {
        $country = wc_clean($address['country'] ?? '');
        $postcode = wc_clean($address['postcode'] ?? '');
        $state = wc_clean($address['state'] ?? '');
        $city = wc_clean($address['city'] ?? '');

        $cache_key = 'wnw_ship_' . md5(json_encode([$country, $state, $postcode, $city, (string) $chosen_method]));
        $cached = wp_cache_get($cache_key, 'woonoow');
        if ($cached !== false) {
            return (float) $cached;
        }

        if (!$country) return 0.0;

        $packages = [[
            'destination' => compact('country', 'state', 'postcode', 'city'),
            'contents_cost' => 0, // not exact in v0
            'contents' => [],
            'applied_coupons' => [],
            'user' => ['ID' => get_current_user_id()],
        ]];

        $zone = \WC_Shipping_Zones::get_zone_matching_package($packages[0]);
        $methods = $zone ? $zone->get_shipping_methods(true) : [];
        $cost = 0.0;

        foreach ($methods as $method) {
            if (!empty($chosen_method)) {
                $id = $method->id . ':' . $method->get_instance_id();
                if ($id === $chosen_method && method_exists($method, 'get_rates_for_package')) {
                    $rates = $method->get_rates_for_package($packages[0]);
                    if (!empty($rates)) {
                        $rate = reset($rates);
                        $cost = (float) $rate->get_cost();
                        break;
                    }
                }
            }
        }
        wp_cache_set($cache_key, $cost, 'woonoow', 60); // cache for 60 seconds
        return $cost;
    }

    private function find_shipping_rate_for_order(WC_Order $order, string $chosen)
    {
        $shipping = $order->get_address('shipping');
        $packages = [[
            'destination' => [
                'country'  => $shipping['country'] ?? '',
                'state'    => $shipping['state'] ?? '',
                'postcode' => $shipping['postcode'] ?? '',
                'city'     => $shipping['city'] ?? '',
            ],
            'contents' => [],
            'applied_coupons' => [],
            'user' => ['ID' => $order->get_user_id()],
        ]];

        $zone = \WC_Shipping_Zones::get_zone_matching_package($packages[0]);
        if (!$zone) return null;
        foreach ($zone->get_shipping_methods(true) as $method) {
            $id = $method->id . ':' . $method->get_instance_id();
            if ($id === $chosen && method_exists($method, 'get_rates_for_package')) {
                $rates = $method->get_rates_for_package($packages[0]);
                if (!empty($rates)) {
                    return reset($rates);
                }
            }
        }
        return null;
    }

    /**
     * Get countries and states for checkout form
     * Public endpoint - no authentication required
     */
    public function get_countries(): array
    {
        $wc_countries = WC()->countries;

        // Get allowed selling countries
        $allowed = $wc_countries->get_allowed_countries();

        // Format for frontend
        $countries = [];
        foreach ($allowed as $code => $name) {
            $countries[] = [
                'code' => $code,
                'name' => $name,
            ];
        }

        // Get states for all allowed countries
        $states = [];
        foreach (array_keys($allowed) as $country_code) {
            $country_states = $wc_countries->get_states($country_code);
            if (!empty($country_states) && is_array($country_states)) {
                $states[$country_code] = $country_states;
            }
        }

        // Get default country
        $default_country = $wc_countries->get_base_country();

        return [
            'countries' => $countries,
            'states' => $states,
            'default_country' => $default_country,
        ];
    }

    /**
     * Get available shipping rates for given address
     * POST /checkout/shipping-rates
     * Body: { shipping: { country, state, city, postcode, destination_id? }, items: [...] }
     */
    public function get_shipping_rates(WP_REST_Request $r): array
    {
        $payload = $r->get_json_params();
        $shipping = $payload['shipping'] ?? [];
        $items = $payload['items'] ?? [];

        $country = wc_clean($shipping['country'] ?? '');
        $state = wc_clean($shipping['state'] ?? '');
        $city = wc_clean($shipping['city'] ?? '');
        $postcode = wc_clean($shipping['postcode'] ?? '');

        if (empty($country)) {
            return [
                'ok' => true,
                'rates' => [],
                'message' => 'Country is required',
            ];
        }

        // Trigger hook for plugins to set session data (e.g., Rajaongkir destination_id)
        do_action('woonoow/shipping/before_calculate', $shipping, $items);

        // Set customer location for shipping calculation
        if (WC()->customer) {
            WC()->customer->set_shipping_country($country);
            WC()->customer->set_shipping_state($state);
            WC()->customer->set_shipping_city($city);
            WC()->customer->set_shipping_postcode($postcode);
        }

        // Build package for shipping calculation
        $contents = [];
        $contents_cost = 0;
        foreach ($items as $item) {
            $product = wc_get_product($item['product_id'] ?? 0);
            if (!$product) continue;
            $qty = max(1, (int)($item['quantity'] ?? $item['qty'] ?? 1));
            $price = (float) wc_get_price_to_display($product);
            $contents[] = [
                'data' => $product,
                'quantity' => $qty,
                'line_total' => $price * $qty,
            ];
            $contents_cost += $price * $qty;
        }

        $package = [
            'destination' => [
                'country' => $country,
                'state' => $state,
                'city' => $city,
                'postcode' => $postcode,
            ],
            'contents' => $contents,
            'contents_cost' => $contents_cost,
            'applied_coupons' => [],
            'user' => ['ID' => get_current_user_id()],
        ];

        // Get matching shipping zone
        $zone = WC_Shipping_Zones::get_zone_matching_package($package);
        if (!$zone) {
            return [
                'ok' => true,
                'rates' => [],
                'message' => 'No shipping zone matches your location',
            ];
        }

        // Get enabled shipping methods from zone
        $methods = $zone->get_shipping_methods(true);
        $rates = [];

        foreach ($methods as $method) {
            // Check if method has rates (some methods like live rate need to calculate)
            if (method_exists($method, 'get_rates_for_package')) {
                $method_rates = $method->get_rates_for_package($package);
                foreach ($method_rates as $rate) {
                    $rates[] = [
                        'id' => $rate->get_id(),
                        'label' => $rate->get_label(),
                        'cost' => (float) $rate->get_cost(),
                        'method_id' => $rate->get_method_id(),
                        'instance_id' => $rate->get_instance_id(),
                    ];
                }
            } else {
                // Fallback for simple methods
                $method_id = $method->id . ':' . $method->get_instance_id();
                $cost = 0;

                // Try to get cost from method
                if (isset($method->cost)) {
                    $cost = (float) $method->cost;
                } elseif (method_exists($method, 'get_option')) {
                    $cost = (float) $method->get_option('cost', 0);
                }

                $rates[] = [
                    'id' => $method_id,
                    'label' => $method->get_title(),
                    'cost' => $cost,
                    'method_id' => $method->id,
                    'instance_id' => $method->get_instance_id(),
                ];
            }
        }

        return [
            'ok' => true,
            'rates' => $rates,
            'zone_name' => $zone->get_zone_name(),
        ];
    }

    private function get_available_gateways_for_order(WC_Order $order): array
    {
        // Mock cart for gateways that check cart total
        if (!WC()->cart) {
            WC()->initialize_cart();
        }
        // We can't easily bake the order into the cart, but many gateways just check 'needs_payment'
        // or country.

        $gateways = WC()->payment_gateways()->get_available_payment_gateways();
        $results = [];

        foreach ($gateways as $gateway) {
            $results[] = [
                'id' => $gateway->id,
                'title' => $gateway->get_title() ?: $gateway->method_title ?: ucfirst($gateway->id), // Fallbacks
                'description' => $gateway->get_description(),
                'icon' => $gateway->get_icon(),
            ];
        }
        return $results;
    }
}
