<?php
/**
 * Shipping Calculation Class
 * Handles all shipping-related calculations for GGM Commerce
 */

// If this file is called directly, abort.
if (!defined('ABSPATH')) {
    exit;
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter

class GGM_Commerce_Shipping {

    private static $instance = null;
    private $settings = null;
    private $zones_cache = null;

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

    /**
     * Constructor
     */
    public function __construct() {
        $this->load_settings();
        $this->register_ajax_handlers();
    }

    /**
     * Load shipping settings from database
     */
    private function load_settings() {
        $this->settings = get_option('ggm_commerce_shipping_options', []);
    }

    /**
     * Reload settings (useful after settings change)
     */
    public function reload_settings() {
        $this->load_settings();
        $this->zones_cache = null;
    }

    /**
     * Register AJAX handlers
     */
    private function register_ajax_handlers() {
        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']);
    }

    // =========================================================================
    // PREMIUM & SETTINGS CHECKS
    // =========================================================================

    /**
     * Check if shipping premium feature is activated
     */
    public function is_premium_activated() {
        if (function_exists('ggm_commerce_license')) {
            $license = ggm_commerce_license();
            return $license->is_widget_activated('shipping');
        }
        return false;
    }

    /**
     * Check if shipping is enabled
     * Requires premium activated AND enabled in settings
     */
    public function is_enabled() {
        if (!$this->is_premium_activated()) {
            return false;
        }
        return !empty($this->settings['enabled']);
    }

    /**
     * Get display mode
     * @return string 'auto' (single calculated rate) or 'choice' (customer chooses)
     */
    public function get_display_mode() {
        return isset($this->settings['display_mode']) ? $this->settings['display_mode'] : 'auto';
    }

    /**
     * Check if customer can choose shipping method
     */
    public function allows_customer_choice() {
        return $this->get_display_mode() === 'choice';
    }

    /**
     * Get default shipping label
     */
    public function get_label() {
        return isset($this->settings['label']) ? $this->settings['label'] : 'Shipping';
    }

    /**
     * Get fallback cost when no zone matches
     */
    public function get_fallback_cost() {
        return isset($this->settings['fallback_cost']) ? floatval($this->settings['fallback_cost']) : 5.99;
    }

    /**
     * Get free shipping message
     */
    public function get_free_shipping_message() {
        return isset($this->settings['free_shipping_message']) ? $this->settings['free_shipping_message'] : '';
    }

    // =========================================================================
    // ZONE CRUD METHODS
    // =========================================================================

    /**
     * Get all shipping zones
     *
     * @param bool $active_only Only return active zones
     * @return array
     */
    public function get_all_zones($active_only = true) {
        global $wpdb;

        if ($this->zones_cache !== null && $active_only) {
            return $this->zones_cache;
        }

        $where = $active_only ? "WHERE status = 'active'" : "";

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is safely built above
        $zones = $wpdb->get_results(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_zones $where ORDER BY priority DESC, display_order ASC"
        );

        // Decode JSON fields (with safety check in case already decoded)
        foreach ($zones as $zone) {
            $zone->countries = is_array($zone->countries) ? $zone->countries : (json_decode($zone->countries, true) ?: []);
            $zone->regions = is_array($zone->regions) ? $zone->regions : (json_decode($zone->regions, true) ?: []);
            $zone->postcodes = is_array($zone->postcodes) ? $zone->postcodes : (json_decode($zone->postcodes, true) ?: []);
        }

        if ($active_only) {
            $this->zones_cache = $zones;
        }

        return $zones;
    }

    /**
     * Get a single zone by ID
     *
     * @param int $zone_id
     * @return object|null
     */
    public function get_zone($zone_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $zone = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_zones WHERE id = %d",
            $zone_id
        ));

        if ($zone) {
            $zone->countries = is_array($zone->countries) ? $zone->countries : (json_decode($zone->countries, true) ?: []);
            $zone->regions = is_array($zone->regions) ? $zone->regions : (json_decode($zone->regions, true) ?: []);
            $zone->postcodes = is_array($zone->postcodes) ? $zone->postcodes : (json_decode($zone->postcodes, true) ?: []);
        }

        return $zone;
    }

    /**
     * Get a zone by slug
     *
     * @param string $slug
     * @return object|null
     */
    public function get_zone_by_slug($slug) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $zone = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_zones WHERE slug = %s",
            $slug
        ));

        if ($zone) {
            $zone->countries = is_array($zone->countries) ? $zone->countries : (json_decode($zone->countries, true) ?: []);
            $zone->regions = is_array($zone->regions) ? $zone->regions : (json_decode($zone->regions, true) ?: []);
            $zone->postcodes = is_array($zone->postcodes) ? $zone->postcodes : (json_decode($zone->postcodes, true) ?: []);
        }

        return $zone;
    }

    /**
     * Get the default zone (fallback for unmatched addresses)
     *
     * @return object|null
     */
    public function get_default_zone() {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $zone = $wpdb->get_row(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_zones WHERE is_default = 1 AND status = 'active' LIMIT 1"
        );

        if ($zone) {
            $zone->countries = is_array($zone->countries) ? $zone->countries : (json_decode($zone->countries, true) ?: []);
            $zone->regions = is_array($zone->regions) ? $zone->regions : (json_decode($zone->regions, true) ?: []);
            $zone->postcodes = is_array($zone->postcodes) ? $zone->postcodes : (json_decode($zone->postcodes, true) ?: []);
        }

        return $zone;
    }

    /**
     * Create a new shipping zone
     *
     * @param array $data
     * @return int|false Zone ID on success, false on failure
     */
    public function create_zone($data) {
        global $wpdb;

        // Generate unique slug
        $slug = sanitize_title($data['name']);
        $original_slug = $slug;
        $counter = 1;
        while ($this->get_zone_by_slug($slug)) {
            $slug = $original_slug . '-' . $counter;
            $counter++;
        }

        $insert_data = [
            'name' => sanitize_text_field($data['name']),
            'slug' => $slug,
            'description' => isset($data['description']) ? sanitize_textarea_field($data['description']) : '',
            'countries' => wp_json_encode(isset($data['countries']) ? $data['countries'] : []),
            'regions' => wp_json_encode(isset($data['regions']) ? $data['regions'] : []),
            'postcodes' => wp_json_encode(isset($data['postcodes']) ? $data['postcodes'] : []),
            'is_default' => isset($data['is_default']) ? (int) $data['is_default'] : 0,
            'priority' => isset($data['priority']) ? (int) $data['priority'] : 0,
            'status' => isset($data['status']) ? sanitize_text_field($data['status']) : 'active',
            'display_order' => isset($data['display_order']) ? (int) $data['display_order'] : 0,
        ];

        // If setting as default, unset other defaults
        if ($insert_data['is_default']) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update(
                "{$wpdb->prefix}ggmc_shipping_zones",
                ['is_default' => 0],
                ['is_default' => 1]
            );
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->insert("{$wpdb->prefix}ggmc_shipping_zones", $insert_data);

        $this->zones_cache = null;

        return $result ? $wpdb->insert_id : false;
    }

    /**
     * Update a shipping zone
     *
     * @param int $zone_id
     * @param array $data
     * @return bool
     */
    public function update_zone($zone_id, $data) {
        global $wpdb;

        $update_data = [];

        if (isset($data['name'])) {
            $update_data['name'] = sanitize_text_field($data['name']);
        }
        if (isset($data['description'])) {
            $update_data['description'] = sanitize_textarea_field($data['description']);
        }
        if (isset($data['countries'])) {
            $update_data['countries'] = wp_json_encode($data['countries']);
        }
        if (isset($data['regions'])) {
            $update_data['regions'] = wp_json_encode($data['regions']);
        }
        if (isset($data['postcodes'])) {
            $update_data['postcodes'] = wp_json_encode($data['postcodes']);
        }
        if (isset($data['is_default'])) {
            $update_data['is_default'] = (int) $data['is_default'];
            // If setting as default, unset other defaults
            if ($update_data['is_default']) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->update(
                    "{$wpdb->prefix}ggmc_shipping_zones",
                    ['is_default' => 0],
                    ['is_default' => 1]
                );
            }
        }
        if (isset($data['priority'])) {
            $update_data['priority'] = (int) $data['priority'];
        }
        if (isset($data['status'])) {
            $update_data['status'] = sanitize_text_field($data['status']);
        }
        if (isset($data['display_order'])) {
            $update_data['display_order'] = (int) $data['display_order'];
        }

        if (empty($update_data)) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update(
            "{$wpdb->prefix}ggmc_shipping_zones",
            $update_data,
            ['id' => $zone_id]
        );

        $this->zones_cache = null;

        return $result !== false;
    }

    /**
     * Delete a shipping zone
     *
     * @param int $zone_id
     * @return bool
     */
    public function delete_zone($zone_id) {
        global $wpdb;

        // Delete associated methods first
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete("{$wpdb->prefix}ggmc_shipping_methods", ['zone_id' => $zone_id]);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->delete("{$wpdb->prefix}ggmc_shipping_zones", ['id' => $zone_id]);

        $this->zones_cache = null;

        return $result !== false;
    }

    // =========================================================================
    // ZONE MATCHING
    // =========================================================================

    /**
     * Get the matching zone for an address
     *
     * @param string $country Country code (e.g., 'GB')
     * @param string $state State/region code
     * @param string $postcode Postcode
     * @return object|null Matching zone or default zone
     */
    public function get_zone_for_address($country, $state = '', $postcode = '') {
        $zones = $this->get_all_zones(true);

        // Sort by priority (highest first)
        usort($zones, function($a, $b) {
            return $b->priority - $a->priority;
        });

        foreach ($zones as $zone) {
            if ($this->matches_zone($zone, $country, $state, $postcode)) {
                return $zone;
            }
        }

        // Fallback to default zone
        return $this->get_default_zone();
    }

    /**
     * Check if an address matches a zone
     *
     * @param object $zone
     * @param string $country
     * @param string $state
     * @param string $postcode
     * @return bool
     */
    private function matches_zone($zone, $country, $state, $postcode) {
        // If zone is default (catch-all), it matches everything
        if ($zone->is_default) {
            return true;
        }

        // Check country match
        if (!empty($zone->countries)) {
            if (!in_array($country, $zone->countries, true)) {
                return false;
            }
        }

        // Check region/state match (if specified)
        if (!empty($zone->regions)) {
            $region_key = $country . ':' . $state;
            if (!in_array($region_key, $zone->regions, true) && !in_array($state, $zone->regions, true)) {
                return false;
            }
        }

        // Check postcode match (if specified)
        if (!empty($zone->postcodes)) {
            $postcode_matches = false;
            $normalized_postcode = strtoupper(str_replace(' ', '', $postcode));

            foreach ($zone->postcodes as $pattern) {
                if ($this->match_postcode_pattern($normalized_postcode, $pattern)) {
                    $postcode_matches = true;
                    break;
                }
            }

            if (!$postcode_matches) {
                return false;
            }
        }

        return true;
    }

    /**
     * Match a postcode against a pattern
     * Supports wildcards: SW1* matches SW1A, SW1B, etc.
     *
     * @param string $postcode Normalized postcode
     * @param string $pattern Pattern to match
     * @return bool
     */
    private function match_postcode_pattern($postcode, $pattern) {
        $normalized_pattern = strtoupper(str_replace(' ', '', $pattern));

        // Exact match
        if ($postcode === $normalized_pattern) {
            return true;
        }

        // Wildcard match
        if (strpos($normalized_pattern, '*') !== false) {
            $regex = '/^' . str_replace('*', '.*', preg_quote($normalized_pattern, '/')) . '$/';
            return (bool) preg_match($regex, $postcode);
        }

        // Range match (e.g., SW1-SW9)
        if (strpos($normalized_pattern, '-') !== false) {
            list($start, $end) = explode('-', $normalized_pattern, 2);
            return $postcode >= $start && $postcode <= $end;
        }

        return false;
    }

    // =========================================================================
    // SHIPPING METHOD CRUD
    // =========================================================================

    /**
     * Get all methods for a zone
     *
     * @param int $zone_id
     * @param bool $active_only
     * @return array
     */
    public function get_zone_methods($zone_id, $active_only = true) {
        global $wpdb;

        $where = "WHERE zone_id = %d";
        if ($active_only) {
            $where .= " AND status = 'active'";
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is safely built above
        $methods = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_methods $where ORDER BY display_order ASC",
            $zone_id
        ));

        foreach ($methods as $method) {
            // Handle null or empty settings - ensure we pass a string to json_decode
            if (is_array($method->settings)) {
                // Already an array, keep it
            } elseif (!empty($method->settings) && is_string($method->settings)) {
                $method->settings = json_decode($method->settings, true) ?: [];
            } else {
                $method->settings = [];
            }
        }

        return $methods;
    }

    /**
     * Get a single shipping method
     *
     * @param int $method_id
     * @return object|null
     */
    public function get_method($method_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $method = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_methods WHERE id = %d",
            $method_id
        ));

        if ($method) {
            $method->settings = is_array($method->settings) ? $method->settings : (json_decode($method->settings, true) ?: []);
        }

        return $method;
    }

    /**
     * Create a shipping method
     *
     * @param int $zone_id
     * @param array $data
     * @return int|false Method ID on success
     */
    public function create_method($zone_id, $data) {
        global $wpdb;

        // Generate unique slug
        $slug = sanitize_title($data['name']);
        $original_slug = $slug;
        $counter = 1;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        while ($wpdb->get_var($wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}ggmc_shipping_methods WHERE slug = %s",
            $slug
        ))) {
            $slug = $original_slug . '-' . $counter;
            $counter++;
        }

        $insert_data = [
            'zone_id' => (int) $zone_id,
            'name' => sanitize_text_field($data['name']),
            'slug' => $slug,
            'method_type' => sanitize_text_field($data['method_type']),
            'description' => isset($data['description']) ? sanitize_textarea_field($data['description']) : '',
            'cost' => isset($data['cost']) ? floatval($data['cost']) : 0.00,
            'free_threshold' => isset($data['free_threshold']) ? floatval($data['free_threshold']) : null,
            'settings' => wp_json_encode(isset($data['settings']) ? $data['settings'] : []),
            'is_taxable' => isset($data['is_taxable']) ? (int) $data['is_taxable'] : 1,
            'estimated_days_min' => isset($data['estimated_days_min']) ? (int) $data['estimated_days_min'] : null,
            'estimated_days_max' => isset($data['estimated_days_max']) ? (int) $data['estimated_days_max'] : null,
            'status' => isset($data['status']) ? sanitize_text_field($data['status']) : 'active',
            'display_order' => isset($data['display_order']) ? (int) $data['display_order'] : 0,
        ];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->insert("{$wpdb->prefix}ggmc_shipping_methods", $insert_data);

        return $result ? $wpdb->insert_id : false;
    }

    /**
     * Update a shipping method
     *
     * @param int $method_id
     * @param array $data
     * @return bool
     */
    public function update_method($method_id, $data) {
        global $wpdb;

        $update_data = [];

        if (isset($data['name'])) {
            $update_data['name'] = sanitize_text_field($data['name']);
        }
        if (isset($data['method_type'])) {
            $update_data['method_type'] = sanitize_text_field($data['method_type']);
        }
        if (isset($data['description'])) {
            $update_data['description'] = sanitize_textarea_field($data['description']);
        }
        if (isset($data['cost'])) {
            $update_data['cost'] = floatval($data['cost']);
        }
        if (array_key_exists('free_threshold', $data)) {
            $update_data['free_threshold'] = $data['free_threshold'] !== null ? floatval($data['free_threshold']) : null;
        }
        if (isset($data['settings'])) {
            $update_data['settings'] = wp_json_encode($data['settings']);
        }
        if (isset($data['is_taxable'])) {
            $update_data['is_taxable'] = (int) $data['is_taxable'];
        }
        if (isset($data['estimated_days_min'])) {
            $update_data['estimated_days_min'] = (int) $data['estimated_days_min'];
        }
        if (isset($data['estimated_days_max'])) {
            $update_data['estimated_days_max'] = (int) $data['estimated_days_max'];
        }
        if (isset($data['status'])) {
            $update_data['status'] = sanitize_text_field($data['status']);
        }
        if (isset($data['display_order'])) {
            $update_data['display_order'] = (int) $data['display_order'];
        }

        if (empty($update_data)) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update(
            "{$wpdb->prefix}ggmc_shipping_methods",
            $update_data,
            ['id' => $method_id]
        );

        return $result !== false;
    }

    /**
     * Delete a shipping method
     *
     * @param int $method_id
     * @return bool
     */
    public function delete_method($method_id) {
        global $wpdb;

        // Delete associated rates first
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete("{$wpdb->prefix}ggmc_shipping_rates", ['method_id' => $method_id]);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->delete("{$wpdb->prefix}ggmc_shipping_methods", ['id' => $method_id]);

        return $result !== false;
    }

    // =========================================================================
    // SHIPPING RATE TIERS (for weight-based)
    // =========================================================================

    /**
     * Get rate tiers for a method
     *
     * @param int $method_id
     * @return array
     */
    public function get_method_rates($method_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_shipping_rates WHERE method_id = %d ORDER BY min_value ASC",
            $method_id
        ));
    }

    /**
     * Create a rate tier
     *
     * @param int $method_id
     * @param array $data
     * @return int|false
     */
    public function create_rate($method_id, $data) {
        global $wpdb;

        $insert_data = [
            'method_id' => (int) $method_id,
            'rate_type' => sanitize_text_field($data['rate_type']),
            'min_value' => isset($data['min_value']) ? floatval($data['min_value']) : 0,
            'max_value' => isset($data['max_value']) ? floatval($data['max_value']) : null,
            'cost' => floatval($data['cost']),
            'per_unit_cost' => isset($data['per_unit_cost']) ? floatval($data['per_unit_cost']) : 0,
            'status' => isset($data['status']) ? sanitize_text_field($data['status']) : 'active',
        ];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->insert("{$wpdb->prefix}ggmc_shipping_rates", $insert_data);

        return $result ? $wpdb->insert_id : false;
    }

    /**
     * Update a rate tier
     *
     * @param int $rate_id
     * @param array $data
     * @return bool
     */
    public function update_rate($rate_id, $data) {
        global $wpdb;

        $update_data = [];

        if (isset($data['rate_type'])) {
            $update_data['rate_type'] = sanitize_text_field($data['rate_type']);
        }
        if (isset($data['min_value'])) {
            $update_data['min_value'] = floatval($data['min_value']);
        }
        if (array_key_exists('max_value', $data)) {
            $update_data['max_value'] = $data['max_value'] !== null ? floatval($data['max_value']) : null;
        }
        if (isset($data['cost'])) {
            $update_data['cost'] = floatval($data['cost']);
        }
        if (isset($data['per_unit_cost'])) {
            $update_data['per_unit_cost'] = floatval($data['per_unit_cost']);
        }
        if (isset($data['status'])) {
            $update_data['status'] = sanitize_text_field($data['status']);
        }

        if (empty($update_data)) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update(
            "{$wpdb->prefix}ggmc_shipping_rates",
            $update_data,
            ['id' => $rate_id]
        );

        return $result !== false;
    }

    /**
     * Delete a rate tier
     *
     * @param int $rate_id
     * @return bool
     */
    public function delete_rate($rate_id) {
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $wpdb->delete("{$wpdb->prefix}ggmc_shipping_rates", ['id' => $rate_id]) !== false;
    }

    // =========================================================================
    // CALCULATION METHODS
    // =========================================================================

    /**
     * Calculate shipping for cart
     *
     * @param array $cart_items Array of cart items with product data
     * @param array $destination ['country' => 'GB', 'state' => '', 'postcode' => 'SW1A 1AA']
     * @return array
     */
    public function calculate_shipping($cart_items, $destination) {
        $country = isset($destination['country']) ? $destination['country'] : '';
        $state = isset($destination['state']) ? $destination['state'] : '';
        $postcode = isset($destination['postcode']) ? $destination['postcode'] : '';

        // Filter to only shippable items using Virtual Products helper
        if (class_exists('GGM_Commerce_Virtual_Products')) {
            $virtual_helper = GGM_Commerce_Virtual_Products::instance();
            $shippable_items = $virtual_helper->get_shippable_items($cart_items);

            // If all items are non-shippable (digital/virtual), no shipping needed
            if (empty($shippable_items)) {
                return [
                    'available_methods' => [],
                    'selected_method' => null,
                    'shipping_cost' => 0,
                    'is_free' => true,
                    'all_digital' => true,
                    'zone' => null,
                    'error' => null,
                ];
            }

            // Use only shippable items for calculation
            $cart_items = $shippable_items;
        }

        // Get matching zone
        $zone = $this->get_zone_for_address($country, $state, $postcode);

        if (!$zone) {
            // No zone found, use fallback
            return [
                'available_methods' => [],
                'selected_method' => null,
                'shipping_cost' => $this->get_fallback_cost(),
                'is_free' => false,
                'zone' => null,
                'error' => 'No shipping zone found for this address',
            ];
        }

        // Get available methods for this zone
        $available_methods = $this->get_available_methods($cart_items, $zone);

        if (empty($available_methods)) {
            return [
                'available_methods' => [],
                'selected_method' => null,
                'shipping_cost' => $this->get_fallback_cost(),
                'is_free' => false,
                'zone' => $zone,
                'error' => 'No shipping methods available for this zone',
            ];
        }

        // Get selected method from session or use cheapest/first
        $selected_method = $this->get_selected_method_from_available($available_methods);

        return [
            'available_methods' => $available_methods,
            'selected_method' => $selected_method,
            'shipping_cost' => $selected_method ? $selected_method->calculated_cost : $this->get_fallback_cost(),
            'is_free' => $selected_method && $selected_method->calculated_cost == 0,
            'zone' => $zone,
            'error' => null,
        ];
    }

    /**
     * Get available shipping methods with calculated costs
     *
     * @param array $cart_items
     * @param object $zone
     * @return array
     */
    public function get_available_methods($cart_items, $zone) {
        $methods = $this->get_zone_methods($zone->id, true);
        $available = [];
        $subtotal = $this->get_cart_subtotal($cart_items);

        foreach ($methods as $method) {
            $cost = $this->calculate_method_cost($method, $cart_items, $subtotal);

            // For free shipping, check if threshold is met
            if ($method->method_type === 'free_shipping') {
                if ($method->free_threshold !== null && $subtotal < $method->free_threshold) {
                    // Threshold not met, skip this method
                    continue;
                }
            }

            $method->calculated_cost = $cost;
            $method->delivery_estimate = $this->format_delivery_estimate($method);
            $available[] = $method;
        }

        return $available;
    }

    /**
     * Calculate cost for a specific method
     *
     * @param object $method
     * @param array $cart_items
     * @param float $subtotal
     * @return float
     */
    public function calculate_method_cost($method, $cart_items, $subtotal) {
        switch ($method->method_type) {
            case 'flat_rate':
                return $this->calculate_flat_rate($method, $cart_items);

            case 'free_shipping':
                return $this->calculate_free_shipping($method, $subtotal);

            case 'weight_based':
                return $this->calculate_weight_based($method, $cart_items);

            case 'carrier_api':
                // Future: Integrate with carrier APIs
                return $method->cost;

            default:
                return $method->cost;
        }
    }

    /**
     * Calculate flat rate shipping
     *
     * @param object $method
     * @param array $cart_items
     * @return float
     */
    private function calculate_flat_rate($method, $cart_items) {
        return floatval($method->cost);
    }

    /**
     * Calculate free shipping
     *
     * @param object $method
     * @param float $subtotal
     * @return float
     */
    private function calculate_free_shipping($method, $subtotal) {
        // If threshold is set and not met, this method shouldn't be available
        // But if it is available, shipping is free
        if ($method->free_threshold !== null && $subtotal >= $method->free_threshold) {
            return 0.00;
        }
        return 0.00;
    }

    /**
     * Calculate weight-based shipping
     *
     * @param object $method
     * @param array $cart_items
     * @return float
     */
    private function calculate_weight_based($method, $cart_items) {
        $total_weight = $this->get_cart_total_weight($cart_items);
        $rates = $this->get_method_rates($method->id);

        if (empty($rates)) {
            // No rate tiers defined, use base cost
            return floatval($method->cost);
        }

        // Find matching rate tier
        foreach ($rates as $rate) {
            if ($rate->status !== 'active') {
                continue;
            }

            $min = floatval($rate->min_value);
            $max = $rate->max_value !== null ? floatval($rate->max_value) : PHP_FLOAT_MAX;

            if ($total_weight >= $min && $total_weight <= $max) {
                $cost = floatval($rate->cost);

                // Add per-unit cost if specified
                if ($rate->per_unit_cost > 0 && $total_weight > $min) {
                    $extra_weight = $total_weight - $min;
                    $cost += $extra_weight * floatval($rate->per_unit_cost);
                }

                return $cost;
            }
        }

        // No matching tier, use base cost
        return floatval($method->cost);
    }

    // =========================================================================
    // CART HELPERS
    // =========================================================================

    /**
     * Get total cart weight
     *
     * @param array $cart_items
     * @return float
     */
    public function get_cart_total_weight($cart_items) {
        $total = 0;

        foreach ($cart_items as $item) {
            $weight = isset($item->weight) ? floatval($item->weight) : 0;
            $quantity = isset($item->quantity) ? (int) $item->quantity : 1;
            $total += $weight * $quantity;
        }

        return $total;
    }

    /**
     * Get cart item count
     *
     * @param array $cart_items
     * @return int
     */
    public function get_cart_item_count($cart_items) {
        $count = 0;

        foreach ($cart_items as $item) {
            $count += isset($item->quantity) ? (int) $item->quantity : 1;
        }

        return $count;
    }

    /**
     * Get cart subtotal
     *
     * @param array $cart_items
     * @return float
     */
    public function get_cart_subtotal($cart_items) {
        $subtotal = 0;

        foreach ($cart_items as $item) {
            $price = isset($item->price) ? floatval($item->price) : 0;
            $quantity = isset($item->quantity) ? (int) $item->quantity : 1;
            $subtotal += $price * $quantity;
        }

        return $subtotal;
    }

    // =========================================================================
    // SESSION MANAGEMENT
    // =========================================================================

    /**
     * Get selected shipping method ID from session
     *
     * @return int|null
     */
    public function get_selected_shipping_method() {
        if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
            session_start();
        }
        return isset($_SESSION['ggmc_selected_shipping_method']) ? (int) $_SESSION['ggmc_selected_shipping_method'] : null;
    }

    /**
     * Set selected shipping method in session
     *
     * @param int $method_id
     */
    public function set_selected_shipping_method($method_id) {
        if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
            session_start();
        }
        $_SESSION['ggmc_selected_shipping_method'] = (int) $method_id;
    }

    /**
     * Clear selected shipping method from session
     */
    public function clear_selected_shipping_method() {
        if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
            session_start();
        }
        unset($_SESSION['ggmc_selected_shipping_method']);
    }

    /**
     * Get selected method from available methods
     *
     * @param array $available_methods
     * @return object|null
     */
    private function get_selected_method_from_available($available_methods) {
        if (empty($available_methods)) {
            return null;
        }

        $selected_id = $this->get_selected_shipping_method();

        // Check if selected method is in available methods
        if ($selected_id) {
            foreach ($available_methods as $method) {
                if ($method->id == $selected_id) {
                    return $method;
                }
            }
        }

        // If display mode is 'auto', return cheapest method
        if (!$this->allows_customer_choice()) {
            return $this->get_cheapest_method($available_methods);
        }

        // Return first available method
        return $available_methods[0];
    }

    /**
     * Get cheapest available method
     *
     * @param array $methods
     * @return object|null
     */
    public function get_cheapest_method($methods) {
        if (empty($methods)) {
            return null;
        }

        $cheapest = $methods[0];
        foreach ($methods as $method) {
            if ($method->calculated_cost < $cheapest->calculated_cost) {
                $cheapest = $method;
            }
        }

        return $cheapest;
    }

    // =========================================================================
    // FORMATTING
    // =========================================================================

    /**
     * Format price with currency
     *
     * @param float $amount
     * @return string
     */
    public function format_price($amount) {
        $currency_options = get_option('ggm_commerce_currency_options', array());
        $currency_symbol = isset($currency_options['symbol']) ? $currency_options['symbol'] : '£';
        return $currency_symbol . number_format($amount, 2);
    }

    /**
     * Format delivery estimate
     *
     * @param object $method
     * @return string
     */
    public function format_delivery_estimate($method) {
        if (!$method->estimated_days_min && !$method->estimated_days_max) {
            return '';
        }

        if ($method->estimated_days_min && $method->estimated_days_max) {
            if ($method->estimated_days_min === $method->estimated_days_max) {
                return sprintf('%d business days', $method->estimated_days_min);
            }
            return sprintf('%d-%d business days', $method->estimated_days_min, $method->estimated_days_max);
        }

        if ($method->estimated_days_min) {
            return sprintf('%d+ business days', $method->estimated_days_min);
        }

        return sprintf('Up to %d business days', $method->estimated_days_max);
    }

    // =========================================================================
    // AJAX HANDLERS
    // =========================================================================

    /**
     * AJAX handler for calculating shipping
     */
    public function ajax_calculate_shipping() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

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

        // Get cart items
        if (function_exists('ggm_commerce_cart')) {
            $cart = ggm_commerce_cart();
            $cart_items = $cart->get_cart_items_with_products();
        } else {
            $cart_items = [];
        }

        $result = $this->calculate_shipping($cart_items, $destination);

        // Add formatted prices
        $result['shipping_cost_formatted'] = $this->format_price($result['shipping_cost']);

        foreach ($result['available_methods'] as $method) {
            $method->cost_formatted = $this->format_price($method->calculated_cost);
        }

        wp_send_json_success($result);
    }

    /**
     * AJAX handler for selecting shipping method
     */
    public function ajax_select_shipping_method() {
        check_ajax_referer('ggm_commerce_nonce', 'nonce');

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

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

        $method = $this->get_method($method_id);

        if (!$method) {
            wp_send_json_error(['message' => 'Shipping method not found']);
        }

        $this->set_selected_shipping_method($method_id);

        wp_send_json_success([
            'method_id' => $method_id,
            'method_name' => $method->name,
            'message' => 'Shipping method selected',
        ]);
    }

    /**
     * Get settings array
     *
     * @return array
     */
    public function get_settings() {
        return $this->settings;
    }
}

/**
 * Helper function to get Shipping instance
 *
 * @return GGM_Commerce_Shipping
 */
function ggm_commerce_shipping() {
    return GGM_Commerce_Shipping::get_instance();
}
