<?php
/**
 * GGM Commerce Helper Functions
 *
 * @package GGM Commerce
 * @subpackage Helpers
 * @version 1.0.0
 */

// 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_Helpers
 *
 * Contains utility functions for the GGM Commerce plugin
 */
class GGM_Commerce_Helpers {

    /** @var array Per-request cache for product detail URLs */
    private static $product_url_cache = [];

    /** @var array Per-request cache for product slug lookups */
    private static $product_slug_cache = [];

    /**
     * Initialize helper functionality
     */
    public static function init() {
        // Add rewrite rules for friendly product URLs
        add_action('init', [__CLASS__, 'add_product_rewrite_rules'], 10);
        add_action('init', [__CLASS__, 'add_taxonomy_rewrite_rules'], 10);
        add_filter('query_vars', [__CLASS__, 'add_product_query_vars']);
        add_filter('query_vars', [__CLASS__, 'add_taxonomy_query_vars']);
        add_action('template_redirect', [__CLASS__, 'handle_product_slug_redirect']);

        // Alternative approach: parse request to handle slug URLs
        add_action('parse_request', [__CLASS__, 'parse_product_slug_request']);
        add_action('parse_request', [__CLASS__, 'parse_taxonomy_slug_request']);

        // Check if we need to flush rewrite rules (after plugin activation)
        add_action('init', [__CLASS__, 'maybe_flush_rewrite_rules'], 20);
    }

    /**
     * Flush rewrite rules if needed (after plugin activation)
     */
    public static function maybe_flush_rewrite_rules() {
        if (get_transient('ggm_commerce_flush_rewrite_rules')) {
            delete_transient('ggm_commerce_flush_rewrite_rules');
            flush_rewrite_rules();
        }
    }

    /**
     * Add rewrite rules for product slugs
     * Allows URLs like /product-details/iphone-15-pro-max/
     */
    public static function add_product_rewrite_rules() {
        // Get the Product Details page slug
        $product_details_page_id = get_option('ggm_commerce_product_details_page_id');
        if (!$product_details_page_id) {
            return;
        }

        $page = get_post($product_details_page_id);
        if (!$page) {
            return;
        }

        $page_slug = $page->post_name;

        // Add rewrite rule for /product-details/{product-slug}/
        // Use page_id instead of pagename for more reliable routing
        add_rewrite_rule(
            '^' . preg_quote($page_slug, '/') . '/([^/]+)/?$',
            'index.php?page_id=' . $product_details_page_id . '&ggmc_product_slug=$matches[1]',
            'top'
        );
    }

    /**
     * Register custom query variable for product slug
     */
    public static function add_product_query_vars($vars) {
        $vars[] = 'ggmc_product_slug';
        return $vars;
    }

    /**
     * Parse request to handle product slug URLs
     * This is a more reliable approach than rewrite rules
     *
     * @param WP $wp The WordPress environment instance
     */
    public static function parse_product_slug_request($wp) {
        // Get the Product Details page
        $product_details_page_id = get_option('ggm_commerce_product_details_page_id');
        if (!$product_details_page_id) {
            return;
        }

        $page = get_post($product_details_page_id);
        if (!$page) {
            return;
        }

        $page_slug = $page->post_name;

        // Get the request path - try multiple sources for compatibility
        $request = '';
        if (!empty($wp->request)) {
            $request = trim($wp->request, '/');
        } elseif (isset($_SERVER['REQUEST_URI'])) {
            // Fallback to REQUEST_URI for environments where $wp->request is empty
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- URL path is validated and compared against known values
            $request_uri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI']));
            // Remove query string
            if (($pos = strpos($request_uri, '?')) !== false) {
                $request_uri = substr($request_uri, 0, $pos);
            }
            // Remove site path prefix if any
            $home_path = wp_parse_url(home_url(), PHP_URL_PATH);
            if ($home_path && strpos($request_uri, $home_path) === 0) {
                $request_uri = substr($request_uri, strlen($home_path));
            }
            $request = trim($request_uri, '/');
        }

        if (empty($request)) {
            return;
        }

        // Build the pattern to match: {page-slug}/{product-slug}
        $pattern = '#^' . preg_quote($page_slug, '#') . '/([^/]+)/?$#i';

        // Check if the request matches our pattern
        if (preg_match($pattern, $request, $matches)) {
            $product_slug = sanitize_title($matches[1]);

            // Verify the product exists (check cache first)
            $product = null;
            if (isset(self::$product_slug_cache[$product_slug])) {
                $product = self::$product_slug_cache[$product_slug];
            } else {
                global $wpdb;
                $product = $wpdb->get_row($wpdb->prepare(
                    "SELECT id FROM {$wpdb->prefix}ggmc_products WHERE slug = %s",
                    $product_slug
                ));
                self::$product_slug_cache[$product_slug] = $product;
            }

            if ($product) {
                // Store the product ID globally for widgets
                $GLOBALS['ggmc_current_product_id'] = (int) $product->id;

                // Clear ALL query vars and set only what we need
                // This prevents WordPress from treating this as an attachment or other query
                $wp->query_vars = [
                    'page_id' => $product_details_page_id,
                    'ggmc_product_slug' => $product_slug,
                ];

                // Also update the matched query to prevent further processing issues
                $wp->matched_rule = null;
                $wp->matched_query = 'page_id=' . $product_details_page_id;
            }
        }
    }

    /**
     * Handle product slug lookup - converts slug to product ID
     */
    public static function handle_product_slug_redirect() {
        $product_slug = get_query_var('ggmc_product_slug');

        if (empty($product_slug)) {
            return;
        }

        // Look up product by slug (check cache first)
        $clean_slug = sanitize_title($product_slug);
        $product = null;
        if (isset(self::$product_slug_cache[$clean_slug])) {
            $product = self::$product_slug_cache[$clean_slug];
        } else {
            global $wpdb;
            $product = $wpdb->get_row($wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}ggmc_products WHERE slug = %s",
                $clean_slug
            ));
            self::$product_slug_cache[$clean_slug] = $product;
        }

        if ($product) {
            // Set a global variable that widgets can access
            $GLOBALS['ggmc_current_product_id'] = $product->id;
        }
    }

    /**
     * Get the current product ID from URL (supports both ?id= and /slug/ formats)
     *
     * @return int|null Product ID or null if not found
     */
    public static function get_current_product_id() {
        // First check for slug-based URL
        if (isset($GLOBALS['ggmc_current_product_id'])) {
            return (int) $GLOBALS['ggmc_current_product_id'];
        }

        // Fall back to query string
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- These are URL parameter checks for routing, not form processing
        if (isset($_GET['id'])) {
            return absint($_GET['id']);
        }

        // Check query var (may be set during template processing)
        $product_slug = get_query_var('ggmc_product_slug');
        if (!empty($product_slug)) {
            global $wpdb;
            $product_id = $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}ggmc_products WHERE slug = %s",
                sanitize_title($product_slug)
            ));
            if ($product_id) {
                $GLOBALS['ggmc_current_product_id'] = (int) $product_id;
                return (int) $product_id;
            }
        }

        return null;
    }

    /**
     * Get product URL with friendly slug or ID based on settings
     *
     * @param int $product_id Product ID
     * @return string Product URL
     */
    public static function get_product_details_url($product_id) {
        // Per-request cache: avoid repeated DB lookups for the same product
        if (isset(self::$product_url_cache[$product_id])) {
            return self::$product_url_cache[$product_id];
        }

        global $wpdb;

        // Get the Product Details page
        $product_details_page_id = get_option('ggm_commerce_product_details_page_id');

        // Check URL format preference (defaults to 'slug' for friendly URLs)
        $url_format = get_option('ggm_commerce_url_format', 'slug');

        // If user prefers ID-based URLs, use query string format
        if ($url_format === 'id') {
            if ($product_details_page_id) {
                $url = add_query_arg('id', $product_id, get_permalink($product_details_page_id));
            } else {
                $url = home_url('/product-details/?id=' . $product_id);
            }
            self::$product_url_cache[$product_id] = $url;
            return $url;
        }

        // Get the product slug for friendly URL
        $product = $wpdb->get_row($wpdb->prepare(
            "SELECT slug FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
            $product_id
        ));

        if ($product && !empty($product->slug) && $product_details_page_id) {
            // Return friendly URL
            $page_url = get_permalink($product_details_page_id);
            $url = trailingslashit($page_url) . $product->slug . '/';
            self::$product_url_cache[$product_id] = $url;
            return $url;
        }

        // Fall back to query string format if slug not available
        if ($product_details_page_id) {
            $url = add_query_arg('id', $product_id, get_permalink($product_details_page_id));
        } else {
            // Ultimate fallback
            $url = home_url('/product-details/?id=' . $product_id);
        }
        self::$product_url_cache[$product_id] = $url;
        return $url;
    }

    /**
     * Get product URL
     *
     * @param int $product_id Product ID
     * @return string Product permalink
     */
    public static function get_product_url($product_id) {
        // Return the permalink for the product custom post type
        return get_permalink($product_id);
    }

    /**
     * Format price with currency symbol and optional tax suffix
     * Respects customer's price display preference for suffix text
     * Uses per-product tax rates when product_id is available
     *
     * @param mixed $price Price value (can be product object, array, or scalar)
     * @param bool  $include_suffix Whether to include tax suffix (null = use setting)
     * @param int|null $user_id WordPress user ID, or null for current user
     * @return string Formatted price with currency symbol and optional tax suffix
     */
    public static function format_price($price, $include_suffix = null, $user_id = null) {
        $tax = ggm_commerce_tax();
        $sale_price = null;
        $regular_price = null;
        $product_id = null;

        // Handle different price input types
        if (is_object($price)) {
            // If it's a product object, check for sale price and get product_id
            $regular_price = isset($price->price) ? $price->price : (isset($price->amount) ? $price->amount : 0);
            $sale_price = isset($price->sale_price) ? $price->sale_price : null;
            $product_id = isset($price->id) ? (int) $price->id : null;

            // Only use sale price if it's actually lower than regular price
            if ($sale_price !== null && ($sale_price >= $regular_price || empty($sale_price))) {
                $sale_price = null;
            }
        } elseif (is_array($price)) {
            // If it's an array, try to get the price values
            $regular_price = isset($price['price']) ? $price['price'] : (isset($price['amount']) ? $price['amount'] : 0);
            $sale_price = isset($price['sale_price']) ? $price['sale_price'] : null;
            $product_id = isset($price['id']) ? (int) $price['id'] : null;

            if ($sale_price !== null && ($sale_price >= $regular_price || empty($sale_price))) {
                $sale_price = null;
            }
        } else {
            // If it's already a scalar value
            $regular_price = $price;
        }

        // Ensure we have numeric values
        $regular_price = is_numeric($regular_price) ? (float)$regular_price : 0.0;

        // Get display price using per-product tax rate if product_id available
        if ($product_id) {
            $regular_display = $tax->get_product_display_price($regular_price, $product_id, $user_id);
            $regular_price = $regular_display['price'];
            if ($sale_price !== null) {
                $sale_display = $tax->get_product_display_price((float)$sale_price, $product_id, $user_id);
                $sale_price = $sale_display['price'];
            }
        }

        // Get tax suffix if enabled (respects customer preference)
        $suffix_html = '';
        $show_suffix = ($include_suffix !== null) ? $include_suffix : $tax->should_show_suffix($user_id);
        if ($show_suffix) {
            $suffix_html = ' <span class="ggmc-tax-suffix">' . esc_html($tax->get_suffix_text(null, $user_id)) . '</span>';
        }

        // Format the price(s)
        if ($sale_price !== null) {
            $sale_price = (float)$sale_price;
            // Has sale price - show sale price with suffix, then crossed-out regular price
            return '<span class="sale-price">' . esc_html($tax->format_price($sale_price)) . '</span>' . $suffix_html . ' ' .
                   '<span class="original-price">' . esc_html($tax->format_price($regular_price)) . '</span>';
        } else {
            // Just regular price
            return '<span class="current-price">' . esc_html($tax->format_price($regular_price)) . '</span>' . $suffix_html;
        }
    }

    /**
     * Get product stock status text
     *
     * @param int $stock_quantity Stock quantity
     * @param int $low_stock_threshold Low stock threshold (default 10)
     * @return string Stock status text
     */
    public static function get_product_stock_status($stock_quantity, $low_stock_threshold = 10) {
        if ($stock_quantity > $low_stock_threshold) {
            return 'In Stock';
        } elseif ($stock_quantity > 0) {
            return 'Low Stock';
        } else {
            return 'Out of Stock';
        }
    }

    /**
     * Get product stock CSS class
     *
     * @param int $stock_quantity Stock quantity
     * @param int $low_stock_threshold Low stock threshold (default 10)
     * @return string CSS class for stock status
     */
    public static function get_product_stock_class($stock_quantity, $low_stock_threshold = 10) {
        if ($stock_quantity > $low_stock_threshold) {
            return 'in-stock';
        } elseif ($stock_quantity > 0) {
            return 'low-stock';
        } else {
            return 'out-of-stock';
        }
    }

    /**
     * Build restock display text based on the chosen format.
     *
     * @param string|null $restock_date The restock date in Y-m-d format, or null.
     * @param string      $format       One of: 'exact_date', 'weeks', 'descriptive'.
     * @return string Display text, or empty string if no valid future date.
     */
    public static function get_restock_display_text( $restock_date, $format = 'descriptive' ) {
        if ( empty( $restock_date ) ) {
            return '';
        }

        $now  = new \DateTime( 'now', wp_timezone() );
        $date = \DateTime::createFromFormat( 'Y-m-d', $restock_date, wp_timezone() );
        if ( ! $date || $date <= $now ) {
            return '';
        }

        $days_away = (int) $now->diff( $date )->days;

        switch ( $format ) {
            case 'exact_date':
                return sprintf(
                    /* translators: %s: formatted date e.g. "04/Aug" */
                    __( 'Expected %s', 'goose-commerce' ),
                    $date->format( 'd/M' )
                );

            case 'weeks':
                if ( $days_away <= 7 ) {
                    return __( 'Expected within 1 week', 'goose-commerce' );
                }
                $weeks_low  = (int) floor( $days_away / 7 );
                $weeks_high = $weeks_low + 1;
                return sprintf(
                    /* translators: %1$d: lower week count, %2$d: upper week count */
                    __( 'Expected in %1$d to %2$d weeks', 'goose-commerce' ),
                    $weeks_low,
                    $weeks_high
                );

            case 'descriptive':
            default:
                if ( $days_away <= 7 ) {
                    return __( 'Restock Imminent', 'goose-commerce' );
                } elseif ( $days_away <= 21 ) {
                    return __( 'Restock Expected Soon', 'goose-commerce' );
                }
                $weeks_low  = (int) floor( $days_away / 7 );
                $weeks_high = $weeks_low + 1;
                return sprintf(
                    /* translators: %1$d: lower week count, %2$d: upper week count */
                    __( 'Expected in %1$d to %2$d weeks', 'goose-commerce' ),
                    $weeks_low,
                    $weeks_high
                );
        }
    }

    /**
     * Get default styles for a specific widget
     *
     * Checks if defaults have been globally cleared and returns appropriate values
     *
     * @param string $widget_name The widget name (e.g., 'ggmc-add-to-cart')
     * @return array Widget default styles array, or empty array if defaults are cleared
     */
    public static function get_plugin_default_styles($widget_name = '') {
        // Check if defaults have been globally cleared
        $defaults_cleared = get_option('ggm_commerce_defaults_cleared', false);

        if ($defaults_cleared) {
            return []; // Return empty array - no defaults applied
        }

        // Load the centralized defaults configuration
        require_once plugin_dir_path(__FILE__) . 'default-widget-styles.php';
        $all_defaults = ggm_commerce_get_default_styles_config();

        // If no widget name specified, return empty array
        if (empty($widget_name)) {
            return [];
        }

        // Return defaults for the specific widget, or empty array if not found
        return isset($all_defaults[$widget_name]) ? $all_defaults[$widget_name] : [];
    }

    /**
     * Check if default styles are currently enabled globally
     *
     * @return bool True if defaults are enabled, false if cleared
     */
    public static function are_default_styles_enabled() {
        $option_value = get_option('ggm_commerce_defaults_cleared', false);
        return !$option_value;
    }

    /**
     * Enable default styles globally
     *
     * @return bool Success status
     */
    public static function enable_default_styles() {
        return delete_option('ggm_commerce_defaults_cleared');
    }

    /**
     * Disable (clear) default styles globally
     *
     * @return bool Success status
     */
    public static function disable_default_styles() {
        return update_option('ggm_commerce_defaults_cleared', true);
    }

    /**
     * Get list of all widget names that have default styles
     *
     * @return array Array of widget names
     */
    public static function get_widgets_with_defaults() {
        require_once plugin_dir_path(__FILE__) . 'default-widget-styles.php';
        $all_defaults = ggm_commerce_get_default_styles_config();
        return array_keys($all_defaults);
    }

    // =========================================================================
    // TAXONOMY (CATEGORY/TAG) URL HANDLING
    // =========================================================================

    /**
     * Add rewrite rules for category and tag archive pages
     */
    public static function add_taxonomy_rewrite_rules() {
        // Get the Category Archive page
        $category_page_id = get_option('ggm_commerce_category_archive_page_id');
        if ($category_page_id) {
            $page = get_post($category_page_id);
            if ($page) {
                $page_slug = $page->post_name;
                add_rewrite_rule(
                    '^' . preg_quote($page_slug, '/') . '/([^/]+)/?$',
                    'index.php?page_id=' . $category_page_id . '&ggmc_category_slug=$matches[1]',
                    'top'
                );
            }
        }

        // Get the Tag Archive page
        $tag_page_id = get_option('ggm_commerce_tag_archive_page_id');
        if ($tag_page_id) {
            $page = get_post($tag_page_id);
            if ($page) {
                $page_slug = $page->post_name;
                add_rewrite_rule(
                    '^' . preg_quote($page_slug, '/') . '/([^/]+)/?$',
                    'index.php?page_id=' . $tag_page_id . '&ggmc_tag_slug=$matches[1]',
                    'top'
                );
            }
        }
    }

    /**
     * Register custom query variables for category and tag slugs
     */
    public static function add_taxonomy_query_vars($vars) {
        $vars[] = 'ggmc_category_slug';
        $vars[] = 'ggmc_tag_slug';
        return $vars;
    }

    /**
     * Parse request to handle category and tag slug URLs
     *
     * @param WP $wp The WordPress environment instance
     */
    public static function parse_taxonomy_slug_request($wp) {
        // Get the request path
        $request = '';
        if (!empty($wp->request)) {
            $request = trim($wp->request, '/');
        } elseif (isset($_SERVER['REQUEST_URI'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- URL path is validated and compared against known values
            $request_uri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI']));
            if (($pos = strpos($request_uri, '?')) !== false) {
                $request_uri = substr($request_uri, 0, $pos);
            }
            $home_path = wp_parse_url(home_url(), PHP_URL_PATH);
            if ($home_path && strpos($request_uri, $home_path) === 0) {
                $request_uri = substr($request_uri, strlen($home_path));
            }
            $request = trim($request_uri, '/');
        }

        if (empty($request)) {
            return;
        }

        // Check for category archive page
        $category_page_id = get_option('ggm_commerce_category_archive_page_id');
        if ($category_page_id) {
            $page = get_post($category_page_id);
            if ($page) {
                $page_slug = $page->post_name;
                $pattern = '#^' . preg_quote($page_slug, '#') . '/([^/]+)/?$#i';

                if (preg_match($pattern, $request, $matches)) {
                    $category_slug = sanitize_title($matches[1]);

                    global $wpdb;
                    $category = $wpdb->get_row($wpdb->prepare(
                        "SELECT id FROM {$wpdb->prefix}ggmc_categories WHERE slug = %s AND status = 'active'",
                        $category_slug
                    ));

                    if ($category) {
                        $GLOBALS['ggmc_current_category_id'] = (int) $category->id;
                        $wp->query_vars = [
                            'page_id' => $category_page_id,
                            'ggmc_category_slug' => $category_slug,
                        ];
                        $wp->matched_rule = null;
                        $wp->matched_query = 'page_id=' . $category_page_id;
                        return;
                    }
                }
            }
        }

        // Check for tag archive page
        $tag_page_id = get_option('ggm_commerce_tag_archive_page_id');
        if ($tag_page_id) {
            $page = get_post($tag_page_id);
            if ($page) {
                $page_slug = $page->post_name;
                $pattern = '#^' . preg_quote($page_slug, '#') . '/([^/]+)/?$#i';

                if (preg_match($pattern, $request, $matches)) {
                    $tag_slug = sanitize_title($matches[1]);

                    global $wpdb;
                    $tag = $wpdb->get_row($wpdb->prepare(
                        "SELECT id FROM {$wpdb->prefix}ggmc_tags WHERE slug = %s AND status = 'active'",
                        $tag_slug
                    ));

                    if ($tag) {
                        $GLOBALS['ggmc_current_tag_id'] = (int) $tag->id;
                        $wp->query_vars = [
                            'page_id' => $tag_page_id,
                            'ggmc_tag_slug' => $tag_slug,
                        ];
                        $wp->matched_rule = null;
                        $wp->matched_query = 'page_id=' . $tag_page_id;
                        return;
                    }
                }
            }
        }
    }

    /**
     * Get the current category ID from URL
     *
     * @return int|null Category ID or null if not found
     */
    public static function get_current_category_id() {
        if (isset($GLOBALS['ggmc_current_category_id'])) {
            return (int) $GLOBALS['ggmc_current_category_id'];
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- These are URL parameter checks for routing, not form processing
        if (isset($_GET['category_id'])) {
            return absint($_GET['category_id']);
        }

        $category_slug = get_query_var('ggmc_category_slug');
        if (!empty($category_slug)) {
            global $wpdb;
            $category_id = $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}ggmc_categories WHERE slug = %s AND status = 'active'",
                sanitize_title($category_slug)
            ));
            if ($category_id) {
                $GLOBALS['ggmc_current_category_id'] = (int) $category_id;
                return (int) $category_id;
            }
        }

        return null;
    }

    /**
     * Get the current tag ID from URL
     *
     * @return int|null Tag ID or null if not found
     */
    public static function get_current_tag_id() {
        if (isset($GLOBALS['ggmc_current_tag_id'])) {
            return (int) $GLOBALS['ggmc_current_tag_id'];
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- These are URL parameter checks for routing, not form processing
        if (isset($_GET['tag_id'])) {
            return absint($_GET['tag_id']);
        }

        $tag_slug = get_query_var('ggmc_tag_slug');
        if (!empty($tag_slug)) {
            global $wpdb;
            $tag_id = $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}ggmc_tags WHERE slug = %s AND status = 'active'",
                sanitize_title($tag_slug)
            ));
            if ($tag_id) {
                $GLOBALS['ggmc_current_tag_id'] = (int) $tag_id;
                return (int) $tag_id;
            }
        }

        return null;
    }

    /**
     * Get category archive URL with friendly slug
     *
     * @param int|object $category Category ID or category object
     * @return string Category archive URL
     */
    public static function get_category_url($category) {
        global $wpdb;

        // If passed an ID, get the category object
        if (is_numeric($category)) {
            $category = $wpdb->get_row($wpdb->prepare(
                "SELECT id, slug FROM {$wpdb->prefix}ggmc_categories WHERE id = %d",
                $category
            ));
        }

        if (!$category || empty($category->slug)) {
            return home_url('/product-category/');
        }

        // Get the Category Archive page
        $category_page_id = get_option('ggm_commerce_category_archive_page_id');

        if ($category_page_id) {
            $page_url = get_permalink($category_page_id);
            return trailingslashit($page_url) . $category->slug . '/';
        }

        // Fallback to query string format
        return home_url('/product-category/?category_id=' . $category->id);
    }

    /**
     * Get tag archive URL with friendly slug
     *
     * @param int|object $tag Tag ID or tag object
     * @return string Tag archive URL
     */
    public static function get_tag_url($tag) {
        global $wpdb;

        // If passed an ID, get the tag object
        if (is_numeric($tag)) {
            $tag = $wpdb->get_row($wpdb->prepare(
                "SELECT id, slug FROM {$wpdb->prefix}ggmc_tags WHERE id = %d",
                $tag
            ));
        }

        if (!$tag || empty($tag->slug)) {
            return home_url('/product-tag/');
        }

        // Get the Tag Archive page
        $tag_page_id = get_option('ggm_commerce_tag_archive_page_id');

        if ($tag_page_id) {
            $page_url = get_permalink($tag_page_id);
            return trailingslashit($page_url) . $tag->slug . '/';
        }

        // Fallback to query string format
        return home_url('/product-tag/?tag_id=' . $tag->id);
    }

    // =========================================================================
    // PRODUCT TYPE CONFIGURATION
    // =========================================================================

    /**
     * Get all available product types with their configuration.
     *
     * @return array Product types with labels and section visibility.
     */
    public static function get_product_types() {
        return array(
            'simple'   => array(
                'label'       => __( 'Simple Product', 'ggm-commerce' ),
                'description' => __( 'A standard physical product.', 'ggm-commerce' ),
                'premium'     => false,
                'sections'    => array( 'identity', 'description', 'pricing', 'inventory', 'specifications', 'images', 'categories', 'subscription-settings' ),
            ),
            'variable' => array(
                'label'       => __( 'Variable Product', 'ggm-commerce' ),
                'description' => __( 'A product with variations like size, color, etc.', 'ggm-commerce' ),
                'premium'     => true,
                'premium_slug' => 'product-variations',
                'sections'    => array( 'identity', 'description', 'pricing', 'inventory', 'specifications', 'images', 'categories', 'variations', 'subscription-settings' ),
            ),
            'digital'  => array(
                'label'       => __( 'Digital Product', 'ggm-commerce' ),
                'description' => __( 'A downloadable product (no shipping).', 'ggm-commerce' ),
                'premium'     => true,
                'premium_slug' => 'downloads',
                'sections'    => array( 'identity', 'description', 'pricing', 'images', 'categories', 'downloads', 'subscription-settings' ),
            ),
            'virtual'  => array(
                'label'       => __( 'Virtual Product', 'ggm-commerce' ),
                'description' => __( 'A service or non-shipped product.', 'ggm-commerce' ),
                'premium'     => false,
                'sections'    => array( 'identity', 'description', 'pricing', 'images', 'categories', 'subscription-settings' ),
            ),
            'kit'      => array(
                'label'       => __( 'Kit (Breakable)', 'ggm-commerce' ),
                'description' => __( 'A bundle that can be broken down into individual items.', 'ggm-commerce' ),
                'premium'     => true,
                'premium_slug' => 'product-kits',
                'sections'    => array( 'identity', 'kit-components', 'description', 'pricing', 'inventory', 'specifications', 'images', 'categories', 'subscription-settings' ),
            ),
            'build'    => array(
                'label'       => __( 'Build (Permanent)', 'ggm-commerce' ),
                'description' => __( 'A permanent assembly that cannot be broken down.', 'ggm-commerce' ),
                'premium'     => true,
                'premium_slug' => 'product-kits',
                'sections'    => array( 'identity', 'kit-components', 'description', 'pricing', 'inventory', 'specifications', 'images', 'categories', 'subscription-settings' ),
            ),
            'gift_card' => array(
                'label'       => __( 'Gift Card / Voucher', 'ggm-commerce' ),
                'description' => __( 'A purchasable voucher that generates a discount code on order completion.', 'ggm-commerce' ),
                'premium'     => true,
                'premium_slug' => 'discount-vouchers',
                'sections'    => array( 'identity', 'description', 'gift-card-settings', 'images', 'categories' ),
            ),
        );
    }

    /**
     * Get product type options for dropdown (label only).
     *
     * @param bool $include_premium Whether to include premium types regardless of activation.
     * @return array Product type options for select dropdown.
     */
    public static function get_product_type_options( $include_premium = true ) {
        $types   = self::get_product_types();
        $options = array();
        $license = ggm_commerce_license();

        foreach ( $types as $key => $type ) {
            // Check if premium type is available.
            $is_available = true;
            $suffix       = '';

            if ( $type['premium'] && ! empty( $type['premium_slug'] ) ) {
                $is_activated = $license->is_widget_activated( $type['premium_slug'] );

                if ( ! $is_activated ) {
                    if ( ! $include_premium ) {
                        continue;
                    }
                    $suffix       = ' 🔒';
                    $is_available = false;
                }
            }

            $options[ $key ] = array(
                'label'     => $type['label'] . $suffix,
                'available' => $is_available,
            );
        }

        return $options;
    }

    /**
     * Get sections that should be visible for a product type.
     *
     * @param string $product_type The product type key.
     * @return array Array of section keys that should be visible.
     */
    public static function get_visible_sections_for_type( $product_type ) {
        $types = self::get_product_types();

        if ( isset( $types[ $product_type ] ) ) {
            return $types[ $product_type ]['sections'];
        }

        // Default to simple product sections.
        return $types['simple']['sections'];
    }

    /**
     * Check if a product type is premium and activated.
     *
     * @param string $product_type The product type key.
     * @return bool True if the type is available (not premium or premium and activated).
     */
    public static function is_product_type_available( $product_type ) {
        $types = self::get_product_types();

        if ( ! isset( $types[ $product_type ] ) ) {
            return false;
        }

        $type = $types[ $product_type ];

        // Non-premium types are always available.
        if ( ! $type['premium'] ) {
            return true;
        }

        // Check if the premium feature is activated.
        $license = ggm_commerce_license();
        return $license->is_widget_activated( $type['premium_slug'] );
    }

    /**
     * Get all section definitions with their labels.
     *
     * @return array Section definitions.
     */
    public static function get_all_sections() {
        return array(
            'identity'           => __( 'Product Identity', 'ggm-commerce' ),
            'kit-components'     => __( 'Kit Components', 'ggm-commerce' ),
            'description'        => __( 'Product Description', 'ggm-commerce' ),
            'pricing'            => __( 'Pricing', 'ggm-commerce' ),
            'inventory'          => __( 'Inventory', 'ggm-commerce' ),
            'specifications'     => __( 'Specifications', 'ggm-commerce' ),
            'images'             => __( 'Product Images', 'ggm-commerce' ),
            'categories'         => __( 'Categories & Tags', 'ggm-commerce' ),
            'variations'         => __( 'Product Variations', 'ggm-commerce' ),
            'downloads'          => __( 'Downloads & Files', 'ggm-commerce' ),
            'gift-card-settings' => __( 'Gift Card Settings', 'ggm-commerce' ),
        );
    }

    /**
     * Generate order confirmation URL
     * This is a standalone helper that doesn't depend on Elementor widgets
     *
     * @param object $order Order object with id, order_number, customer_email, created_at
     * @return string Order confirmation URL
     */
    public static function get_order_confirmation_url($order) {
        $thank_you_page_id = get_option('ggm_commerce_thank_you_page_id');

        if (!$thank_you_page_id) {
            // Fallback to checkout page if thank you page not set
            $thank_you_page_id = get_option('ggm_commerce_checkout_page_id');
        }

        $base_url = $thank_you_page_id ? get_permalink($thank_you_page_id) : home_url('/');

        // Generate secure key
        $data = $order->id . $order->order_number . $order->customer_email . $order->created_at;
        $key = wp_hash($data, 'ggmc_order_key');

        return add_query_arg([
            'order_id' => $order->id,
            'key' => $key,
        ], $base_url);
    }
}

/**
 * Global helper function to format a price with currency symbol
 *
 * @param float $amount The amount to format
 * @return string Formatted price with currency symbol
 */
function ggm_commerce_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((float)$amount, 2);
}

/**
 * Format a subscription price with billing period
 *
 * @param float  $price     Recurring price.
 * @param string $period    Billing period (day, week, month, year).
 * @param int    $interval  Billing interval.
 * @param array  $trial     Optional trial data with 'length' and 'period' keys.
 * @return string Formatted subscription price string.
 */
function ggm_commerce_format_subscription_price( $price, $period = 'month', $interval = 1, $trial = array() ) {
    $formatted_price = ggm_commerce_format_price( $price );

    $periods = array(
        'day'   => array( 'day', 'days' ),
        'week'  => array( 'week', 'weeks' ),
        'month' => array( 'month', 'months' ),
        'year'  => array( 'year', 'years' ),
    );

    $period_labels = $periods[ $period ] ?? array( $period, $period . 's' );
    $period_label  = (int) $interval === 1 ? $period_labels[0] : $period_labels[1];

    if ( (int) $interval === 1 ) {
        $result = $formatted_price . '/' . $period_label;
    } else {
        $result = $formatted_price . ' every ' . $interval . ' ' . $period_label;
    }

    if ( ! empty( $trial['length'] ) && (int) $trial['length'] > 0 ) {
        $trial_period_labels = $periods[ $trial['period'] ?? 'day' ] ?? array( 'day', 'days' );
        $trial_label         = (int) $trial['length'] === 1 ? $trial_period_labels[0] : $trial_period_labels[1];
        $result              = 'Free for ' . (int) $trial['length'] . ' ' . $trial_label . ', then ' . $result;
    }

    return $result;
}

/**
 * Builder-agnostic editor mode detection.
 *
 * Returns true when the current request is from either the Elementor
 * editor or the Gutenberg block editor (REST/ServerSideRender).
 *
 * @return bool
 */
function ggmc_is_editor_mode() {
    // Gutenberg block editor — ServerSideRender uses a REST request.
    if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
        return true;
    }

    // Elementor editor.
    if ( class_exists( '\Elementor\Plugin' ) ) {
        try {
            $instance = \Elementor\Plugin::$instance;
            if ( $instance && isset( $instance->editor ) && $instance->editor
                 && method_exists( $instance->editor, 'is_edit_mode' )
                 && $instance->editor->is_edit_mode() ) {
                return true;
            }
        } catch ( \Exception $e ) {
            // Silently continue.
        }
    }

    return false;
}

/**
 * Render an icon — works with or without Elementor.
 * Falls back to outputting an <i> tag with the icon class when Elementor is not active.
 *
 * @param array $icon Icon data array (Elementor format: ['value' => 'eicon-heart', 'library' => 'eicons']).
 * @param array $attrs HTML attributes for the icon element.
 */
function ggmc_render_icon( $icon, $attrs = array() ) {
    if ( empty( $icon ) || empty( $icon['value'] ) ) {
        return;
    }

    if ( class_exists( '\Elementor\Icons_Manager' ) ) {
        \Elementor\Icons_Manager::render_icon( $icon, $attrs );
        return;
    }

    // Fallback: render as <i> tag with the icon class
    $class = esc_attr( $icon['value'] );
    $attr_str = '';
    foreach ( $attrs as $key => $val ) {
        $attr_str .= ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
    }
    echo '<i class="' . $class . '"' . $attr_str . '></i>';
}

/**
 * Get placeholder image URL — works with or without Elementor.
 *
 * @return string Placeholder image URL.
 */
function ggmc_get_placeholder_image_src() {
    if ( class_exists( '\Elementor\Utils' ) ) {
        return \Elementor\Utils::get_placeholder_image_src();
    }
    return includes_url( 'images/blank.gif' );
}