<?php
// Enhanced Cart System - Phase 1 Foundation
if (!defined('ABSPATH')) {
    exit;
}

class GGM_Commerce_Cart {
    private static $instance = null;
    private $cart_key = 'ggm_commerce_cart';
    private $cart_items = [];

    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function __construct() {
        $this->load_cart_from_session();

        // New action names
        add_action('wp_ajax_ggm_commerce_add_to_cart', [$this, 'ajax_add_to_cart']);
        add_action('wp_ajax_nopriv_ggm_commerce_add_to_cart', [$this, 'ajax_add_to_cart']);
        add_action('wp_ajax_ggm_commerce_remove_from_cart', [$this, 'ajax_remove_from_cart']);
        add_action('wp_ajax_nopriv_ggm_commerce_remove_from_cart', [$this, 'ajax_remove_from_cart']);
        add_action('wp_ajax_ggm_commerce_update_cart_quantity', [$this, 'ajax_update_quantity']);
        add_action('wp_ajax_nopriv_ggm_commerce_update_cart_quantity', [$this, 'ajax_update_quantity']);
        add_action('wp_ajax_ggm_commerce_update_cart', [$this, 'ajax_update_cart']);
        add_action('wp_ajax_nopriv_ggm_commerce_update_cart', [$this, 'ajax_update_cart']);
        add_action('wp_ajax_ggm_commerce_get_cart_data', [$this, 'ajax_get_cart_data']);
        add_action('wp_ajax_nopriv_ggm_commerce_get_cart_data', [$this, 'ajax_get_cart_data']);
        add_action('wp_ajax_ggm_commerce_set_b2b_exemption', [$this, 'ajax_set_b2b_exemption']);
        add_action('wp_ajax_nopriv_ggm_commerce_set_b2b_exemption', [$this, 'ajax_set_b2b_exemption']);
        add_action('wp_ajax_ggm_commerce_calculate_shipping', [$this, 'ajax_calculate_shipping']);
        add_action('wp_ajax_nopriv_ggm_commerce_calculate_shipping', [$this, 'ajax_calculate_shipping']);
        add_action('wp_ajax_ggm_commerce_select_shipping_method', [$this, 'ajax_select_shipping_method']);
        add_action('wp_ajax_nopriv_ggm_commerce_select_shipping_method', [$this, 'ajax_select_shipping_method']);

        // Legacy action names for backward compatibility
        add_action('wp_ajax_ggmc_add_to_cart', [$this, 'ajax_add_to_cart']);
        add_action('wp_ajax_nopriv_ggmc_add_to_cart', [$this, 'ajax_add_to_cart']);
        add_action('wp_ajax_ggmc_remove_from_cart', [$this, 'ajax_remove_from_cart']);
        add_action('wp_ajax_nopriv_ggmc_remove_from_cart', [$this, 'ajax_remove_from_cart']);
        add_action('wp_ajax_ggmc_update_cart', [$this, 'ajax_update_cart']);
        add_action('wp_ajax_nopriv_ggmc_update_cart', [$this, 'ajax_update_cart']);
    }

    private function load_cart_from_session() {
        if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
            session_start();
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Cart data is our own structured array, sanitized on add
        $this->cart_items = isset($_SESSION[$this->cart_key]) ? $_SESSION[$this->cart_key] : [];
    }

    private function save_cart_to_session() {
        if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
            session_start();
        }
        $_SESSION[$this->cart_key] = $this->cart_items;
    }

    /**
     * Add a product to the cart
     *
     * @param int   $product_id    Product ID.
     * @param int   $quantity      Quantity to add.
     * @param array $variation_data Variation data (can include 'variation_id', 'attributes', 'variation_mode', 'selected_options').
     * @return bool|array True on success, array with error on failure.
     */
    public function add_to_cart($product_id, $quantity = 1, $variation_data = []) {
        global $wpdb;

        // Validate product exists and is active
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $product = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_products WHERE id = %d AND status = 'active'",
            $product_id
        ));

        if (!$product) {
            return false;
        }

        // Extract variation_id if provided
        $variation_id = isset($variation_data['variation_id']) ? intval($variation_data['variation_id']) : 0;

        // Check if product has variations
        if (!empty($product->has_variations) && $product->has_variations && class_exists('GGM_Commerce_Variations')) {
            $variations_manager = GGM_Commerce_Variations::instance();
            $variation_mode = $variations_manager->get_product_variation_mode($product_id);

            if ('independent' === $variation_mode) {
                // Independent mode: no variation_id needed, just selected options
                // Validate that options were selected (attributes array)
                if (empty($variation_data['attributes'])) {
                    return ['error' => 'please_select_variation'];
                }

                // Mark this as independent mode for later processing
                $variation_data['variation_mode'] = 'independent';

                // Calculate price from selected options
                $selected_options = $variation_data['attributes'];
                $calculated_price = $variations_manager->get_independent_price($product_id, $selected_options);
                $variation_data['calculated_price'] = $calculated_price;

                // Independent mode always uses parent stock (only if stock is managed)
                if (!empty($product->manage_stock) && $product->stock !== null && $product->stock < $quantity) {
                    return ['error' => 'out_of_stock'];
                }
            } else {
                // Combined mode: variation_id is required
                if (!$variation_id) {
                    return ['error' => 'please_select_variation'];
                }

                $variation = $variations_manager->get_variation($variation_id);

                if (!$variation || intval($variation['product_id']) !== intval($product_id)) {
                    return ['error' => 'invalid_variation'];
                }

                // Check variation stock availability
                if (!$variations_manager->check_availability($variation_id, $quantity)) {
                    return ['error' => 'variation_out_of_stock'];
                }
            }
        } elseif (!empty($product->has_variations) && $product->has_variations && !$variation_id) {
            // Product has variations but variations manager not available
            return ['error' => 'please_select_variation'];
        } else {
            // Simple product - check parent stock (only if stock is managed)
            if (!empty($product->manage_stock) && $product->stock !== null && $product->stock < $quantity) {
                return false;
            }
        }

        $cart_item_key = $this->generate_cart_item_key($product_id, $variation_data);

        if (isset($this->cart_items[$cart_item_key])) {
            $new_quantity = $this->cart_items[$cart_item_key]['quantity'] + $quantity;

            // Re-check stock for new total quantity
            if ($variation_id && class_exists('GGM_Commerce_Variations')) {
                $variations_manager = GGM_Commerce_Variations::instance();
                if (!$variations_manager->check_availability($variation_id, $new_quantity)) {
                    return ['error' => 'variation_insufficient_stock'];
                }
            } elseif (isset($variation_data['variation_mode']) && 'independent' === $variation_data['variation_mode']) {
                // Independent mode - check parent stock (only if stock is managed)
                if (!empty($product->manage_stock) && $product->stock !== null && $product->stock < $new_quantity) {
                    return ['error' => 'insufficient_stock'];
                }
            }

            $this->cart_items[$cart_item_key]['quantity'] = $new_quantity;
        } else {
            $cart_item = [
                'product_id' => $product_id,
                'quantity' => $quantity,
                'variation_id' => $variation_id,
                'variation_data' => $variation_data,
                'added_at' => current_time('timestamp')
            ];

            // Attach subscription metadata if product is a subscription.
            if ( class_exists( 'GGM_Commerce_Subscriptions' ) && GGM_Commerce_Subscriptions::is_activated() ) {
                $sub_manager = GGM_Commerce_Subscriptions::instance();
                if ( $sub_manager->is_product_subscription( $product_id ) ) {
                    $sub_data = $sub_manager->get_product_subscription_data( $product_id );
                    $cart_item['is_subscription']  = true;
                    $cart_item['subscription_data'] = $sub_data;
                }
            }

            $this->cart_items[$cart_item_key] = $cart_item;
        }

        $this->save_cart_to_session();
        return true;
    }

    public function remove_from_cart($cart_item_key) {
        if (isset($this->cart_items[$cart_item_key])) {
            unset($this->cart_items[$cart_item_key]);
            $this->save_cart_to_session();
            return true;
        }
        return false;
    }

    public function update_quantity($cart_item_key, $quantity) {
        if (isset($this->cart_items[$cart_item_key]) && $quantity > 0) {
            $this->cart_items[$cart_item_key]['quantity'] = $quantity;
            $this->save_cart_to_session();
            return true;
        }
        return false;
    }

    public function get_cart_items() {
        return $this->cart_items;
    }

    public function get_cart_items_with_products() {
        global $wpdb;
        $cart_data = [];

        foreach ($this->cart_items as $key => $item) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $product = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
                $item['product_id']
            ));

            if ($product) {
                $price = floatval($product->price ?? 0);
                $variation_id = isset($item['variation_id']) ? intval($item['variation_id']) : 0;
                $variation_info = null;
                $variation_attributes_display = '';

                // Convert image_id to featured_image URL
                $featured_image = '';
                if (!empty($product->image_id)) {
                    $featured_image = wp_get_attachment_image_url($product->image_id, 'thumbnail');
                }

                // Check for Independent mode (no variation_id, but has calculated_price)
                $variation_data = $item['variation_data'] ?? [];
                $is_independent = isset($variation_data['variation_mode']) && 'independent' === $variation_data['variation_mode'];

                if ($is_independent && class_exists('GGM_Commerce_Variations')) {
                    // Independent mode: use calculated price from cart data
                    if (isset($variation_data['calculated_price'])) {
                        $price = floatval($variation_data['calculated_price']);
                    }

                    // Build display string from selected options
                    $variations_manager = GGM_Commerce_Variations::instance();
                    if (!empty($variation_data['attributes'])) {
                        $variation_attributes_display = $variations_manager->format_selected_options(
                            $item['product_id'],
                            $variation_data['attributes']
                        );
                    }

                    // Store variation info for independent mode
                    $variation_info = [
                        'id' => null,
                        'mode' => 'independent',
                        'selected_options' => $variation_data['attributes'] ?? [],
                        'attributes_display' => $variation_attributes_display,
                    ];
                } elseif ($variation_id && class_exists('GGM_Commerce_Variations')) {
                    // Combined mode: get variation-specific data
                    $variations_manager = GGM_Commerce_Variations::instance();
                    $variation = $variations_manager->get_variation($variation_id);

                    if ($variation) {
                        // Get variation price (may be different from parent)
                        $variation_price = $variations_manager->get_variation_price($variation_id);
                        if ($variation_price !== null) {
                            $price = $variation_price;
                        }

                        // Get variation image if available
                        if (!empty($variation['image_id'])) {
                            $variation_image = wp_get_attachment_image_url($variation['image_id'], 'thumbnail');
                            if ($variation_image) {
                                $featured_image = $variation_image;
                            }
                        }

                        // Build variation attributes display string
                        $attr_parts = [];
                        if (!empty($variation['attributes'])) {
                            foreach ($variation['attributes'] as $attr) {
                                $attr_parts[] = $attr['attribute_name'] . ': ' . $attr['attribute_value'];
                            }
                            $variation_attributes_display = implode(', ', $attr_parts);
                        }

                        // Store variation info
                        $variation_info = [
                            'id' => $variation_id,
                            'sku' => $variation['sku'] ?? '',
                            'attributes' => $variation['attributes'] ?? [],
                            'attributes_display' => $variation_attributes_display,
                        ];
                    }
                }

                // Create a combined object with both cart and product data
                $cart_item = (object) array_merge((array) $product, [
                    'cart_key' => $key,
                    'quantity' => $item['quantity'],
                    'variation_id' => $variation_id,
                    'variation_data' => $item['variation_data'] ?? [],
                    'variation_info' => $variation_info,
                    'variation_display' => $variation_attributes_display,
                    'added_at' => $item['added_at'] ?? 0,
                    'unit_price' => $price,
                    'line_total' => $price * $item['quantity'],
                    'featured_image' => $featured_image
                ]);

                $cart_data[] = $cart_item;
            }
        }

        return $cart_data;
    }

    public function get_cart_count() {
        return array_sum(array_column($this->cart_items, 'quantity'));
    }

    public function get_cart_total() {
        global $wpdb;
        $total = 0;

        foreach ($this->cart_items as $item) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $product = $wpdb->get_row($wpdb->prepare(
                "SELECT price FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
                $item['product_id']
            ));

            if ($product && $product->price) {
                $price = floatval($product->price);

                // Check for Independent mode first
                $variation_data = $item['variation_data'] ?? [];
                $is_independent = isset($variation_data['variation_mode']) && 'independent' === $variation_data['variation_mode'];

                if ($is_independent && isset($variation_data['calculated_price'])) {
                    // Independent mode: use pre-calculated price
                    $price = floatval($variation_data['calculated_price']);
                } else {
                    // Check for Combined mode variation-specific pricing
                    $variation_id = isset($item['variation_id']) ? intval($item['variation_id']) : 0;
                    if ($variation_id && class_exists('GGM_Commerce_Variations')) {
                        $variations_manager = GGM_Commerce_Variations::instance();
                        $variation_price = $variations_manager->get_variation_price($variation_id);
                        if ($variation_price !== null) {
                            $price = $variation_price;
                        }
                    }
                }

                $total += $price * $item['quantity'];
            }
        }

        return $total;
    }

    /**
     * Get cart totals with tax calculations
     * Returns subtotal, tax, and grand total
     * Uses per-product tax rates and takes B2B exemptions into account
     */
    public function get_cart_totals_with_tax() {
        global $wpdb;
        $tax = ggm_commerce_tax();
        $subtotal = $this->get_cart_total(); // Gross total (inc VAT)

        if (!$tax->is_enabled()) {
            return [
                'subtotal' => $subtotal,
                'subtotal_net' => $subtotal,
                'tax_amount' => 0,
                'tax_rate' => 0,
                'tax_label' => '',
                'total' => $subtotal,
                'prices_include_tax' => false,
                'is_exempt' => false,
            ];
        }

        // Check if customer is tax exempt
        $is_exempt = false;
        if (is_user_logged_in()) {
            $is_exempt = $tax->is_customer_tax_exempt();
        } elseif ($tax->get_session_tax_exempt()) {
            $is_exempt = true;
        }

        // Calculate tax per-product using their individual tax rates
        $gross_total = 0;
        $net_total = 0;
        $tax_total = 0;

        foreach ($this->cart_items as $item) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $product = $wpdb->get_row($wpdb->prepare(
                "SELECT id, price FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
                $item['product_id']
            ));

            if ($product && $product->price) {
                $line_total = floatval($product->price) * $item['quantity'];
                $product_tax = $tax->calculate_product_tax($line_total, $product->id);

                $gross_total += $product_tax['gross'];
                $net_total += $product_tax['net'];

                // Only add tax if customer is not exempt
                if (!$is_exempt) {
                    $tax_total += $product_tax['tax'];
                }
            }
        }

        // If exempt, tax is 0 and total equals net
        if ($is_exempt) {
            $tax_total = 0;
            $final_total = $net_total;
        } else {
            $final_total = $gross_total;
        }

        // Calculate subscription recurring totals.
        $has_subscription   = false;
        $recurring_subtotal = 0;
        $recurring_period   = '';
        $recurring_interval = 0;
        $has_trial          = false;
        $trial_end_text     = '';

        foreach ( $this->cart_items as $cart_item ) {
            if ( ! empty( $cart_item['is_subscription'] ) && ! empty( $cart_item['subscription_data'] ) ) {
                $has_subscription = true;
                $sd = $cart_item['subscription_data'];

                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $sub_product = $wpdb->get_row( $wpdb->prepare(
                    "SELECT price FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
                    $cart_item['product_id']
                ) );
                if ( $sub_product ) {
                    $recurring_subtotal += floatval( $sub_product->price ) * $cart_item['quantity'];
                }

                $recurring_period   = $sd['period'] ?? 'month';
                $recurring_interval = $sd['interval'] ?? 1;

                if ( ! empty( $sd['trial_length'] ) && (int) $sd['trial_length'] > 0 ) {
                    $has_trial     = true;
                    $trial_end_text = (int) $sd['trial_length'] . ' ' . ( $sd['trial_period'] ?? 'day' ) . ' free trial';
                }
            }
        }

        return [
            'subtotal' => round($gross_total, 2),
            'subtotal_net' => round($net_total, 2),
            'tax_amount' => round($tax_total, 2),
            'tax_rate' => 0, // Mixed rates, not applicable
            'tax_label' => $tax->get_label(),
            'total' => round($final_total, 2),
            'prices_include_tax' => true, // Prices are always stored inc VAT
            'is_exempt' => $is_exempt,
            'has_subscription'   => $has_subscription,
            'recurring_subtotal' => round( $recurring_subtotal, 2 ),
            'recurring_period'   => $recurring_period,
            'recurring_interval' => $recurring_interval,
            'has_trial'          => $has_trial,
            'trial_end_text'     => $trial_end_text,
        ];
    }

    /**
     * Get cart tax amount
     */
    public function get_cart_tax() {
        $totals = $this->get_cart_totals_with_tax();
        return $totals['tax_amount'];
    }

    /**
     * Get cart grand total (including tax)
     */
    public function get_cart_grand_total() {
        $totals = $this->get_cart_totals_with_tax();
        return $totals['total'];
    }

    public function clear_cart() {
        $this->cart_items = [];
        $this->save_cart_to_session();
    }

    private function generate_cart_item_key($product_id, $variation_data = []) {
        return md5($product_id . serialize($variation_data));
    }

    // AJAX Handlers
    public function ajax_add_to_cart() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- intval handles missing/invalid values
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
        $quantity = isset($_POST['quantity']) ? intval($_POST['quantity']) : 1;
        $variation_id = isset($_POST['variation_id']) ? intval($_POST['variation_id']) : 0;

        // Build variation data array
        $variation_data = [];
        if ($variation_id) {
            $variation_data['variation_id'] = $variation_id;
        }

        // Include variation attributes if provided
        if (isset($_POST['variation_attributes']) && is_array($_POST['variation_attributes'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized individually below
            $attributes = wp_unslash($_POST['variation_attributes']);
            $variation_data['attributes'] = array_map('sanitize_text_field', $attributes);
        }

        $result = $this->add_to_cart($product_id, $quantity, $variation_data);

        if ($result === true) {
            $totals = $this->get_cart_totals_with_tax();
            wp_send_json_success([
                'message' => __('Product added to cart!', 'ggm-commerce'),
                'cart_count' => $this->get_cart_count(),
                'cart_total' => $this->get_cart_total(),
                'cart_tax' => $totals['tax_amount'],
                'cart_grand_total' => $totals['total'],
                'tax_label' => $totals['tax_label'],
                'cart_items' => $this->get_cart_items_with_products()
            ]);
        } elseif (is_array($result) && isset($result['error'])) {
            // Handle specific error types
            $error_messages = [
                'please_select_variation' => __('Please select all product options before adding to cart.', 'ggm-commerce'),
                'invalid_variation' => __('Invalid product variation selected.', 'ggm-commerce'),
                'variation_out_of_stock' => __('Sorry, this variation is out of stock.', 'ggm-commerce'),
                'variation_insufficient_stock' => __('Sorry, not enough stock available for this variation.', 'ggm-commerce'),
            ];

            $message = isset($error_messages[$result['error']])
                ? $error_messages[$result['error']]
                : __('Failed to add product to cart.', 'ggm-commerce');

            wp_send_json_error([
                'message' => $message,
                'error_code' => $result['error']
            ]);
        } else {
            wp_send_json_error([
                'message' => __('Failed to add product to cart. Product may not exist or be out of stock.', 'ggm-commerce')
            ]);
        }
    }

    public function ajax_remove_from_cart() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

        $cart_item_key = isset($_POST['cart_item_id']) ? sanitize_text_field(wp_unslash($_POST['cart_item_id'])) : '';
        
        if ($this->remove_from_cart($cart_item_key)) {
            $cart_count = $this->get_cart_count();
            $totals = $this->get_cart_totals_with_tax();
            wp_send_json_success([
                'message' => 'Product removed from cart',
                'cart_count' => $cart_count,
                'cart_total' => $this->get_cart_total(),
                'cart_tax' => $totals['tax_amount'],
                'cart_grand_total' => $totals['total'],
                'tax_label' => $totals['tax_label'],
                'cart_empty' => $cart_count === 0,
                'cart_items' => $this->get_cart_items_with_products()
            ]);
        } else {
            wp_send_json_error([
                'message' => 'Failed to remove product from cart'
            ]);
        }
    }

    public function ajax_update_quantity() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

        $cart_item_key = isset($_POST['cart_item_id']) ? sanitize_text_field(wp_unslash($_POST['cart_item_id'])) : '';
        $quantity = isset($_POST['quantity']) ? intval($_POST['quantity']) : 0;
        
        if ($this->update_quantity($cart_item_key, $quantity)) {
            $totals = $this->get_cart_totals_with_tax();
            wp_send_json_success([
                'message' => 'Cart updated',
                'cart_count' => $this->get_cart_count(),
                'cart_total' => $this->get_cart_total(),
                'cart_tax' => $totals['tax_amount'],
                'cart_grand_total' => $totals['total'],
                'tax_label' => $totals['tax_label']
            ]);
        } else {
            wp_send_json_error([
                'message' => 'Failed to update cart'
            ]);
        }
    }

    public function ajax_get_cart_data() {
        global $wpdb;
        
        $cart_data = [];
        foreach ($this->cart_items as $key => $item) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $product = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
                $item['product_id']
            ));

            if ($product) {
                $cart_data[] = [
                    'key' => $key,
                    'product' => $product,
                    'quantity' => $item['quantity'],
                    'line_total' => floatval($product->price) * $item['quantity']
                ];
            }
        }
        
        $totals = $this->get_cart_totals_with_tax();
        wp_send_json_success([
            'items' => $cart_data,
            'count' => $this->get_cart_count(),
            'total' => $this->get_cart_total(),
            'tax' => $totals['tax_amount'],
            'grand_total' => $totals['total'],
            'tax_label' => $totals['tax_label'],
            'tax_rate' => $totals['tax_rate'],
            'prices_include_tax' => $totals['prices_include_tax']
        ]);
    }
    
    public function ajax_update_cart() {
        // Security check
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'ggm_commerce_nonce')) {
            wp_send_json_error(['message' => 'Security check failed']);
            return;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Array values sanitized via intval in the loop
        $updates = isset($_POST['updates']) ? (array) $_POST['updates'] : [];
        
        if (empty($updates) || !is_array($updates)) {
            wp_send_json_error(['message' => 'No updates provided']);
            return;
        }
        
        $updated_items = 0;
        global $wpdb;

        foreach ($updates as $cart_item_id => $quantity) {
            $quantity = intval($quantity);

            if ($quantity <= 0) {
                // Remove item if quantity is 0 or less
                $this->remove_from_cart($cart_item_id);
                $updated_items++;
            } else if (isset($this->cart_items[$cart_item_id])) {
                // Update quantity
                $this->cart_items[$cart_item_id]['quantity'] = $quantity;
                $updated_items++;
            }
        }

        if ($updated_items > 0) {
            $this->save_cart_to_session();

            // Prepare updated line totals for frontend
            $updated_line_totals = [];
            foreach ($updates as $cart_item_id => $quantity) {
                if (isset($this->cart_items[$cart_item_id])) {
                    // Fetch product price from database
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $product = $wpdb->get_row($wpdb->prepare(
                        "SELECT price FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
                        $this->cart_items[$cart_item_id]['product_id']
                    ));

                    if ($product) {
                        $price = floatval($product->price);
                        $line_total = $price * $this->cart_items[$cart_item_id]['quantity'];

                        $updated_line_totals[$cart_item_id] = [
                            'quantity' => $this->cart_items[$cart_item_id]['quantity'],
                            'line_total' => $line_total,
                            'price' => $price
                        ];
                    }
                }
            }
            
            $totals = $this->get_cart_totals_with_tax();
            wp_send_json_success([
                'message' => $updated_items . ' item(s) updated successfully',
                'cart_count' => $this->get_cart_count(),
                'cart_total' => $this->get_cart_total(),
                'cart_tax' => $totals['tax_amount'],
                'cart_grand_total' => $totals['total'],
                'tax_label' => $totals['tax_label'],
                'updated_items' => $updated_line_totals,
                'cart_items' => $this->get_cart_items_with_products()
            ]);
        } else {
            wp_send_json_error(['message' => 'No items were updated']);
        }
    }

    /**
     * AJAX handler to set B2B tax exemption status in session
     */
    public function ajax_set_b2b_exemption() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

        $tax = ggm_commerce_tax();

        // Check if B2B is enabled
        if (!$tax->is_b2b_enabled()) {
            wp_send_json_error(['message' => 'B2B tax exemption is not enabled']);
            return;
        }

        $is_exempt = isset($_POST['is_exempt']) ? (bool) intval($_POST['is_exempt']) : false;
        $tax_id = isset($_POST['tax_id']) ? sanitize_text_field(wp_unslash($_POST['tax_id'])) : '';

        // Set session-based exemption
        $tax->set_session_tax_exempt($is_exempt, $tax_id);

        // Get updated cart totals with shipping
        $totals = $this->get_cart_totals_with_shipping();

        wp_send_json_success([
            'message' => $is_exempt ? 'Tax exemption applied' : 'Tax exemption removed',
            'is_exempt' => $totals['is_exempt'],
            'tax_amount' => $totals['tax_amount'],
            'tax_label' => $totals['tax_label'],
            'subtotal' => $totals['subtotal'],
            'grand_total' => $totals['total'],
            'shipping' => $totals['shipping_amount'],
            'shipping_label' => $totals['shipping_label'],
        ]);
    }

    /**
     * Get cart totals including shipping
     *
     * @param array $destination Optional destination for shipping calculation
     * @return array Complete totals array with subtotal, tax, shipping, and grand total
     */
    public function get_cart_totals_with_shipping($destination = []) {
        $tax_totals = $this->get_cart_totals_with_tax();

        // Initialize shipping values
        $shipping_amount = 0;
        $shipping_label = __('Shipping', 'ggm-commerce');
        $shipping_method_id = null;
        $shipping_method_name = '';
        $shipping_methods = [];
        $allows_choice = false;
        $premium_shipping_active = false;

        // Check if premium shipping is enabled
        if (function_exists('ggm_commerce_shipping')) {
            $shipping = ggm_commerce_shipping();

            if ($shipping->is_enabled()) {
                $premium_shipping_active = true;
                $shipping_label = $shipping->get_label();
                $allows_choice = $shipping->allows_customer_choice();

                // Build cart items array for shipping calculation
                $cart_items = [];
                $cart_products = $this->get_cart_items_with_products();
                foreach ($cart_products as $item) {
                    $cart_items[] = [
                        'product_id' => $item->id,
                        'quantity' => $item->quantity,
                        'weight' => floatval($item->weight ?? 0),
                        'price' => floatval($item->unit_price ?? $item->price ?? 0),
                        'line_total' => $item->line_total,
                        // Include virtual/downloadable flags for shipping calculation
                        'is_virtual' => !empty($item->is_virtual),
                        'is_downloadable' => !empty($item->is_downloadable),
                        'requires_shipping' => isset($item->requires_shipping) ? (bool) $item->requires_shipping : true,
                    ];
                }

                // Calculate shipping
                $shipping_result = $shipping->calculate_shipping($cart_items, $destination);

                // Check if all items are digital (no shipping needed)
                if (!empty($shipping_result['all_digital'])) {
                    $shipping_amount = 0;
                    $shipping_method_name = __('No shipping required', 'ggm-commerce');
                    $allows_choice = false;
                    $shipping_methods = [];
                } elseif ($shipping_result && isset($shipping_result['shipping_cost'])) {
                    $shipping_amount = $shipping_result['shipping_cost'] ?? 0;
                    $selected_method = $shipping_result['selected_method'] ?? null;
                    $shipping_method_id = $selected_method ? $selected_method->id : null;
                    $shipping_method_name = $selected_method ? $selected_method->name : '';

                    // Get all available methods if customer choice is enabled
                    if ($allows_choice && !empty($shipping_result['available_methods'])) {
                        foreach ($shipping_result['available_methods'] as $method) {
                            $method_cost = isset($method->calculated_cost) ? $method->calculated_cost : 0;
                            $shipping_methods[] = [
                                'id' => $method->id,
                                'name' => $method->name,
                                'cost' => $method_cost,
                                'formatted_cost' => $method_cost > 0 ? ggm_commerce_format_price($method_cost) : __('Free', 'ggm-commerce'),
                                'selected' => $method->id == $shipping_method_id,
                            ];
                        }
                    }
                }
            }
        }

        // Fallback: Use basic shipping settings when premium is not active
        if (!$premium_shipping_active) {
            $basic_options = get_option('ggm_commerce_basic_shipping', []);
            $flat_rate = isset($basic_options['flat_rate']) ? floatval($basic_options['flat_rate']) : 5.99;
            $free_threshold = isset($basic_options['free_threshold']) ? floatval($basic_options['free_threshold']) : 50;
            $label = isset($basic_options['label']) && !empty($basic_options['label']) ? $basic_options['label'] : __('Shipping', 'ggm-commerce');

            $shipping_label = $label;
            $cart_total = $this->get_cart_total();

            // Free shipping if cart total meets threshold (0 = disabled)
            if ($free_threshold > 0 && $cart_total >= $free_threshold) {
                $shipping_amount = 0;
                $shipping_method_name = __('Free Shipping', 'ggm-commerce');
            } else {
                $shipping_amount = $flat_rate;
                $shipping_method_name = $label;
            }
        }

        // Calculate discount if applied
        $discount_amount = 0;
        $discount_code = '';
        $discount_type = '';
        $discount_message = '';
        $free_shipping_from_discount = false;

        if ( class_exists( 'GGM_Commerce_Discounts' ) && GGM_Commerce_Discounts::is_activated() ) {
            $discounts_handler = GGM_Commerce_Discounts::get_instance();
            $applied_discount = $discounts_handler->get_applied_discount();

            if ( $applied_discount ) {
                $validation = $discounts_handler->validate_discount_code(
                    $applied_discount->code,
                    $tax_totals['subtotal']
                );

                if ( $validation['valid'] ) {
                    $discount_amount = $validation['discount_amount'];
                    $discount_code = $applied_discount->code;
                    $discount_type = $applied_discount->discount_type;
                    $discount_message = $validation['message'];

                    // Check if discount provides free shipping
                    if ( ! empty( $applied_discount->free_shipping ) ) {
                        $free_shipping_from_discount = true;
                        $shipping_amount = 0;
                        $shipping_method_name = __( 'Free Shipping', 'ggm-commerce' );
                    }
                }
            }
        }

        // Calculate grand total
        $grand_total = max( 0, $tax_totals['total'] + $shipping_amount - $discount_amount );

        return [
            // Tax totals
            'subtotal' => $tax_totals['subtotal'],
            'subtotal_net' => $tax_totals['subtotal_net'],
            'tax_amount' => $tax_totals['tax_amount'],
            'tax_rate' => $tax_totals['tax_rate'],
            'tax_label' => $tax_totals['tax_label'],
            'prices_include_tax' => $tax_totals['prices_include_tax'],
            'is_exempt' => $tax_totals['is_exempt'],
            // Shipping
            'shipping_amount' => round($shipping_amount, 2),
            'shipping_label' => $shipping_label,
            'shipping_method_id' => $shipping_method_id,
            'shipping_method_name' => $shipping_method_name,
            'shipping_methods' => $shipping_methods,
            'allows_shipping_choice' => $allows_choice,
            // Discount
            'discount_amount' => round( $discount_amount, 2 ),
            'discount_code' => $discount_code,
            'discount_type' => $discount_type,
            'discount_message' => $discount_message,
            'free_shipping_from_discount' => $free_shipping_from_discount,
            // Grand total
            'total' => round($grand_total, 2),
        ];
    }

    /**
     * AJAX handler to calculate shipping for a destination
     */
    public function ajax_calculate_shipping() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

        $destination = [
            'country' => isset($_POST['country']) ? sanitize_text_field(wp_unslash($_POST['country'])) : '',
            'state' => isset($_POST['state']) ? sanitize_text_field(wp_unslash($_POST['state'])) : '',
            'postcode' => isset($_POST['postcode']) ? sanitize_text_field(wp_unslash($_POST['postcode'])) : '',
        ];

        // Get totals with shipping for this destination
        $totals = $this->get_cart_totals_with_shipping($destination);

        wp_send_json_success([
            'shipping_amount' => $totals['shipping_amount'],
            'shipping_label' => $totals['shipping_label'],
            'shipping_method_id' => $totals['shipping_method_id'],
            'shipping_method_name' => $totals['shipping_method_name'],
            'shipping_methods' => $totals['shipping_methods'],
            'allows_shipping_choice' => $totals['allows_shipping_choice'],
            'subtotal' => $totals['subtotal'],
            'tax_amount' => $totals['tax_amount'],
            'tax_label' => $totals['tax_label'],
            'total' => $totals['total'],
        ]);
    }

    /**
     * AJAX handler to select a shipping method
     */
    public function ajax_select_shipping_method() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

        $method_id = isset($_POST['method_id']) ? intval($_POST['method_id']) : 0;

        if (!function_exists('ggm_commerce_shipping')) {
            wp_send_json_error(['message' => 'Shipping not available']);
            return;
        }

        $shipping = ggm_commerce_shipping();

        if (!$shipping->is_enabled()) {
            wp_send_json_error(['message' => 'Shipping not enabled']);
            return;
        }

        // Set the selected shipping method in session
        $shipping->set_selected_shipping_method($method_id);

        // Get the method details
        $method = $shipping->get_method($method_id);

        if (!$method) {
            wp_send_json_error(['message' => 'Invalid shipping method']);
            return;
        }

        // Calculate the cost for this method
        $cart_items = [];
        $cart_products = $this->get_cart_items_with_products();
        foreach ($cart_products as $item) {
            $cart_items[] = [
                'product_id' => $item->id,
                'quantity' => $item->quantity,
                'weight' => floatval($item->weight ?? 0),
                'price' => floatval($item->price ?? 0),
                'line_total' => $item->line_total,
            ];
        }

        // Calculate cost based on method type
        $shipping_cost = 0;
        switch ($method->method_type) {
            case 'free_shipping':
                $shipping_cost = 0;
                break;
            case 'flat_rate':
                $shipping_cost = floatval($method->cost);
                // Check free threshold
                $cart_total = $this->get_cart_total();
                if ($method->free_threshold > 0 && $cart_total >= $method->free_threshold) {
                    $shipping_cost = 0;
                }
                break;
            case 'weight_based':
                // Get weight-based calculation
                $total_weight = array_sum(array_map(function($item) {
                    return $item['weight'] * $item['quantity'];
                }, $cart_items));
                $rates = $shipping->get_method_rates($method_id);
                $shipping_cost = floatval($method->cost);
                foreach ($rates as $rate) {
                    if ($total_weight >= $rate->min_value && ($rate->max_value === null || $total_weight <= $rate->max_value)) {
                        $shipping_cost = floatval($rate->cost);
                        if ($rate->per_unit_cost > 0) {
                            $shipping_cost += $total_weight * floatval($rate->per_unit_cost);
                        }
                        break;
                    }
                }
                break;
            default:
                $shipping_cost = floatval($method->cost);
        }

        // Get updated totals
        $tax_totals = $this->get_cart_totals_with_tax();
        $grand_total = $tax_totals['total'] + $shipping_cost;

        wp_send_json_success([
            'message' => 'Shipping method updated',
            'shipping_method_id' => $method_id,
            'shipping_method_name' => $method->name,
            'shipping_amount' => round($shipping_cost, 2),
            'shipping_formatted' => $shipping_cost > 0 ? ggm_commerce_format_price($shipping_cost) : __('Free', 'ggm-commerce'),
            'total' => round($grand_total, 2),
            'total_formatted' => ggm_commerce_format_price($grand_total),
        ]);
    }
}

// Initialize cart function
function ggm_commerce_cart() {
    return GGM_Commerce_Cart::get_instance();
}

// Initialize on WordPress init, but only on frontend and admin pages (not during activation)
add_action('wp_loaded', function() {
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a simple existence check for initialization timing, not form processing
    if (!is_admin() || (is_admin() && !isset($_GET['action']))) {
        ggm_commerce_cart();
    }
});