<?php

/**
 * Front section.
 *
 * @package    Extra_Product_Options_For_WooCommerce
 * @subpackage Extra_Product_Options_For_WooCommerce/includes
 */
// If this file is called directly, abort.
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * EPOFW_Admin class.
 */
if ( !class_exists( 'EPOFW_Front' ) ) {
    /**
     * EPOFW_Admin class.
     */
    class EPOFW_Front {
        /**
         * The object of class.
         *
         * @since    1.0.0
         * @var      string $instance Instance object.
         */
        protected static $instance = null;

        /**
         * The name of this plugin.
         *
         * @since    1.0.0
         * @var      string $plugin_name The ID of this plugin.
         */
        public function __construct() {
            $this->epofw_front_init();
        }

        /**
         * Define the plugins name and versions and also call admin section.
         *
         * @since    1.0.0
         * @return   object Instance of this class.
         */
        public static function instance() {
            if ( is_null( self::$instance ) ) {
                self::$instance = new self();
            }
            return self::$instance;
        }

        /**
         * Add filter in which add some functionality.
         *
         * @since    1.0.0
         */
        public function epofw_front_init() {
            add_action( 'woocommerce_before_add_to_cart_button', array($this, 'epofw_before_add_to_cart_button'), 10 );
            /**
             * Filter the priority for after add to cart button hook.
             *
             * @since 4.4.5
             *
             * @param int $priority Default priority (10).
             */
            $after_add_to_cart_priority = apply_filters( 'epofw_after_add_to_cart_button_priority', 10 );
            add_action( 'woocommerce_after_add_to_cart_button', array($this, 'epofw_after_add_to_cart_button'), $after_add_to_cart_priority );
            add_filter(
                'woocommerce_add_cart_item_data',
                array($this, 'epofw_add_cart_item_data'),
                10,
                3
            );
            add_filter(
                'woocommerce_get_item_data',
                array($this, 'epofw_get_item_data'),
                99,
                2
            );
            // Add tax data to variation JSON for frontend tax display.
            add_filter(
                'woocommerce_available_variation',
                array($this, 'epofw_add_variation_tax_data'),
                10,
                3
            );
            add_filter(
                'woocommerce_order_again_cart_item_data',
                array($this, 'epofw_order_again_cart_item_data_pro'),
                10,
                2
            );
            add_filter(
                'woocommerce_order_item_get_formatted_meta_data',
                array($this, 'epofw_item_get_formatted_meta_data_pro'),
                10,
                2
            );
            add_action( 'wp_enqueue_scripts', array($this, 'cop_front_enqueue_scripts_fn') );
            add_filter(
                'woocommerce_add_to_cart_validation',
                array($this, 'epofw_add_to_cart_validation'),
                10,
                2
            );
            add_filter(
                'epofw_productgroup_should_reduce_stock',
                array(__CLASS__, 'epofw_productgroup_should_reduce_stock'),
                10,
                3
            );
            add_filter(
                'woocommerce_order_item_display_meta_value',
                array($this, 'epofw_display_meta_value'),
                10,
                3
            );
            add_action(
                'woocommerce_checkout_create_order_line_item',
                array($this, 'checkout_create_order_line_item'),
                10,
                3
            );
            add_filter( 'woocommerce_add_cart_item', array($this, 'epofw_add_cart_item'), 10 );
            add_filter(
                'woocommerce_get_cart_item_from_session',
                array($this, 'epofw_get_cart_item_from_session'),
                99,
                3
            );
            add_action(
                'woocommerce_after_cart_item_quantity_update',
                array($this, 'epofw_update_price_on_quantity_update'),
                20,
                4
            );
            add_filter(
                'woocommerce_cart_item_price',
                array($this, 'epofw_woocommerce_cart_item_price'),
                10,
                3
            );
            // Mini-cart widget quantity × price display.
            add_filter(
                'woocommerce_widget_cart_item_quantity',
                array($this, 'epofw_widget_cart_item_quantity'),
                10,
                3
            );
            // Ensure cart totals are recalculated with addon prices.
            add_action(
                'woocommerce_before_calculate_totals',
                array($this, 'epofw_before_calculate_totals'),
                10,
                1
            );
            // Hook into cart fragments to recalculate totals for AJAX mini-cart.
            add_filter(
                'woocommerce_add_to_cart_fragments',
                array($this, 'epofw_add_to_cart_fragments'),
                5,
                1
            );
        }

        /**
         * Add nonce field to the form.
         *
         * @since 3.0.9
         * @return void
         */
        public function epofw_add_nonce_field() {
            global $post;
            if ( !$post ) {
                return;
            }
            // Track which product nonces have been rendered to avoid duplicates.
            static $rendered_nonces = array();
            $product_id = $post->ID;
            // Skip if nonce already rendered for this product.
            if ( isset( $rendered_nonces[$product_id] ) ) {
                return;
            }
            // Mark this product's nonce as rendered.
            $rendered_nonces[$product_id] = true;
            // Generate unique nonce for this product and user.
            $nonce_action = 'epofw_add_to_cart_' . $product_id . '_' . get_current_user_id();
            $nonce_name = 'epofw_add_to_cart_nonce_' . $product_id;
            wp_nonce_field( $nonce_action, $nonce_name );
        }

        /**
         * Verify the nonce for add to cart actions.
         *
         * @since    1.0.0
         *
         * @param int $product_id Product ID.
         *
         * @return   bool True if nonce is valid, false otherwise.
         */
        private function verify_add_to_cart_nonce( $product_id ) {
            if ( empty( $product_id ) ) {
                return false;
            }
            // Get the nonce name based on product ID.
            $nonce_name = 'epofw_add_to_cart_nonce_' . $product_id;
            // Verify nonce exists and is valid.
            if ( !isset( $_POST[$nonce_name] ) ) {
                return false;
            }
            $nonce = sanitize_text_field( wp_unslash( $_POST[$nonce_name] ) );
            // Generate the same nonce action as used when creating.
            $nonce_action = 'epofw_add_to_cart_' . $product_id . '_' . get_current_user_id();
            return wp_verify_nonce( $nonce, $nonce_action );
        }

        /**
         * Enqueue some js and css at front side.
         *
         * @since    1.0.0
         */
        public function cop_front_enqueue_scripts_fn() {
            // Check if page contains cart/checkout blocks (cart block can be on any page).
            $has_cart_block = false;
            $has_checkout_block = false;
            if ( function_exists( 'has_block' ) ) {
                global $post;
                if ( isset( $post ) && is_a( $post, 'WP_Post' ) ) {
                    $has_cart_block = has_block( 'woocommerce/cart', $post ) || has_block( 'woocommerce/filled-cart-block', $post );
                    $has_checkout_block = has_block( 'woocommerce/checkout', $post );
                }
            }
            if ( is_product() || is_shop() || is_product_category() || is_product_tag() || is_product_taxonomy() || is_cart() || is_checkout() || is_ajax() || $has_cart_block || $has_checkout_block ) {
                $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' );
                $pro_path = '';
                wp_enqueue_script( 'jquery' );
                wp_enqueue_script( 'jquery-ui-datepicker' );
                wp_enqueue_script(
                    'jquery-ui-timepicker-js',
                    EPOFW_PLUGIN_URL . 'assets/js/jquery-ui-timepicker' . $suffix . '.js',
                    array('jquery'),
                    EPOFW_PLUGIN_VERSION,
                    true
                );
                wp_enqueue_style(
                    'jquery-ui-timepicker-css',
                    EPOFW_PLUGIN_URL . 'assets/css/jquery-ui-timepicker' . $suffix . '.css',
                    array(),
                    EPOFW_PLUGIN_VERSION
                );
                wp_enqueue_style(
                    'select2-css',
                    EPOFW_PLUGIN_URL . 'assets/css/select2.min.css',
                    array(),
                    EPOFW_PLUGIN_VERSION
                );
                wp_enqueue_script(
                    'select2-js',
                    EPOFW_PLUGIN_URL . 'assets/js/select2.full.min.js',
                    array(),
                    EPOFW_PLUGIN_VERSION,
                    true
                );
                wp_enqueue_style(
                    'epofw-product-addons-front-css',
                    EPOFW_PLUGIN_URL . 'assets/' . $pro_path . 'css/epofw-product-addons-front' . $suffix . '.css',
                    '',
                    EPOFW_PLUGIN_VERSION,
                    false
                );
                $epofw_custom_css = epofw_general_settings( 'epofw_custom_css' );
                if ( !empty( $epofw_custom_css ) ) {
                    wp_add_inline_style( 'epofw-product-addons-front-css', $epofw_custom_css );
                }
                wp_register_style(
                    'jquery-ui-css',
                    EPOFW_PLUGIN_URL . 'assets/css/jquery-ui.min.css',
                    '',
                    EPOFW_PLUGIN_VERSION
                );
                wp_enqueue_style( 'jquery-ui-css' );
                // Enqueue jquery-ui-slider for slider field (available in free and premium).
                wp_enqueue_script( 'jquery-ui-slider' );
                wp_enqueue_style( 'wp-color-picker' );
                wp_enqueue_script(
                    'iris',
                    admin_url( 'js/iris.min.js' ),
                    array(
                        'jquery-ui-draggable',
                        'jquery-ui-slider',
                        'jquery-touch-punch',
                        'wp-i18n'
                    ),
                    EPOFW_PLUGIN_VERSION,
                    false
                );
                wp_enqueue_script(
                    'wp-color-picker',
                    admin_url( 'js/color-picker.min.js' ),
                    array('iris'),
                    EPOFW_PLUGIN_VERSION,
                    false
                );
                $colorpicker_l10n = array(
                    'clear'         => __( 'Clear', 'extra-product-options-for-woocommerce' ),
                    'defaultString' => __( 'Default', 'extra-product-options-for-woocommerce' ),
                    'pick'          => __( 'Select Color', 'extra-product-options-for-woocommerce' ),
                    'current'       => __( 'Current Color', 'extra-product-options-for-woocommerce' ),
                );
                wp_localize_script( 'wp-color-picker', 'wpColorPickerL10n', $colorpicker_l10n );
                // Determine front script dependencies based on premium status.
                $front_script_deps = array('jquery');
                wp_enqueue_script(
                    'epofw-front-js',
                    EPOFW_PLUGIN_URL . 'assets/' . $pro_path . 'js/epofw-front' . $suffix . '.js',
                    $front_script_deps,
                    EPOFW_PLUGIN_VERSION,
                    true
                );
                global $post;
                $product_data = wc_get_product( $post->ID );
                // Get tax information for product page display.
                $tax_rate = 0;
                $tax_label = '';
                $is_taxable = false;
                $prices_incl_tax = wc_prices_include_tax();
                $tax_display_shop = get_option( 'woocommerce_tax_display_shop', 'excl' );
                // Get all available tax rates for addon-specific tax calculation.
                // Always populate when WooCommerce tax is enabled (addons may have their own tax settings).
                $all_tax_rates = array();
                // Get product-specific tax information.
                if ( isset( $product_data ) && !empty( $product_data ) && $product_data->is_taxable() ) {
                    $is_taxable = true;
                    $tax_class = $product_data->get_tax_class();
                    // Get tax rates using base location if customer location isn't available.
                    $tax_rates = WC_Tax::get_rates( $tax_class );
                    // If no rates found (customer location not set), try with base location.
                    if ( empty( $tax_rates ) ) {
                        $base_location = wc_get_base_location();
                        $tax_rates = WC_Tax::find_rates( array(
                            'country'   => $base_location['country'],
                            'state'     => $base_location['state'],
                            'tax_class' => $tax_class,
                        ) );
                    }
                    if ( !empty( $tax_rates ) ) {
                        // Get the first tax rate (primary tax).
                        $first_rate = reset( $tax_rates );
                        $tax_rate = ( isset( $first_rate['rate'] ) ? floatval( $first_rate['rate'] ) : 0 );
                        $tax_label = ( isset( $first_rate['label'] ) ? $first_rate['label'] : __( 'Tax', 'extra-product-options-for-woocommerce' ) );
                    }
                }
                // Always populate all_tax_rates when WooCommerce tax is enabled.
                // This allows addons to have their own tax class even if the product is non-taxable.
                if ( wc_tax_enabled() ) {
                    $base_location = wc_get_base_location();
                    // Standard rate (empty string tax class).
                    $standard_rates = WC_Tax::get_rates( '' );
                    if ( empty( $standard_rates ) ) {
                        $standard_rates = WC_Tax::find_rates( array(
                            'country'   => $base_location['country'],
                            'state'     => $base_location['state'],
                            'tax_class' => '',
                        ) );
                    }
                    if ( !empty( $standard_rates ) ) {
                        $first_standard = reset( $standard_rates );
                        $all_tax_rates['standard'] = array(
                            'rate'  => ( isset( $first_standard['rate'] ) ? floatval( $first_standard['rate'] ) : 0 ),
                            'label' => ( isset( $first_standard['label'] ) ? $first_standard['label'] : __( 'Tax', 'extra-product-options-for-woocommerce' ) ),
                        );
                        // 'bops' (based on product setting) uses product's tax rate if taxable, else standard.
                        $all_tax_rates['bops'] = ( $is_taxable && $tax_rate > 0 ? array(
                            'rate'  => $tax_rate,
                            'label' => $tax_label,
                        ) : $all_tax_rates['standard'] );
                    }
                    // Reduced rate.
                    $reduced_rates = WC_Tax::get_rates( 'reduced-rate' );
                    if ( empty( $reduced_rates ) ) {
                        $reduced_rates = WC_Tax::find_rates( array(
                            'country'   => $base_location['country'],
                            'state'     => $base_location['state'],
                            'tax_class' => 'reduced-rate',
                        ) );
                    }
                    if ( !empty( $reduced_rates ) ) {
                        $first_reduced = reset( $reduced_rates );
                        $all_tax_rates['reduced-rate'] = array(
                            'rate'  => ( isset( $first_reduced['rate'] ) ? floatval( $first_reduced['rate'] ) : 0 ),
                            'label' => ( isset( $first_reduced['label'] ) ? $first_reduced['label'] : __( 'Reduced Rate', 'extra-product-options-for-woocommerce' ) ),
                        );
                    }
                    // Zero rate.
                    $zero_rates = WC_Tax::get_rates( 'zero-rate' );
                    if ( empty( $zero_rates ) ) {
                        $zero_rates = WC_Tax::find_rates( array(
                            'country'   => $base_location['country'],
                            'state'     => $base_location['state'],
                            'tax_class' => 'zero-rate',
                        ) );
                    }
                    if ( !empty( $zero_rates ) ) {
                        $first_zero = reset( $zero_rates );
                        $all_tax_rates['zero-rate'] = array(
                            'rate'  => ( isset( $first_zero['rate'] ) ? floatval( $first_zero['rate'] ) : 0 ),
                            'label' => ( isset( $first_zero['label'] ) ? $first_zero['label'] : __( 'Zero Rate', 'extra-product-options-for-woocommerce' ) ),
                        );
                    } else {
                        // Default zero rate if not defined.
                        $all_tax_rates['zero-rate'] = array(
                            'rate'  => 0,
                            'label' => __( 'Zero Rate', 'extra-product-options-for-woocommerce' ),
                        );
                    }
                }
                $front_localize_args = array(
                    'ajaxurl'                          => admin_url( 'admin-ajax.php' ),
                    'current_post_id'                  => ( isset( $product_data ) && !empty( $product_data ) ? $product_data->get_id() : '' ),
                    'product_price'                    => ( isset( $product_data ) && !empty( $product_data ) ? epofw_display_product_price( $product_data, 'shop' ) : '' ),
                    'currency'                         => get_woocommerce_currency_symbol(),
                    'position'                         => get_option( 'woocommerce_currency_pos' ),
                    'decimal_separator'                => wc_get_price_decimal_separator(),
                    'thousand_separator'               => wc_get_price_thousand_separator(),
                    'decimals'                         => wc_get_price_decimals(),
                    'tax_rate'                         => $tax_rate,
                    'tax_label'                        => $tax_label,
                    'is_taxable'                       => $is_taxable,
                    'prices_include_tax'               => $prices_incl_tax,
                    'tax_display_shop'                 => $tax_display_shop,
                    'all_tax_rates'                    => $all_tax_rates,
                    'including_tax_text'               => __( 'Including %s', 'extra-product-options-for-woocommerce' ),
                    'timepicker_select_validation'     => __( 'Please select a valid time.', 'extra-product-options-for-woocommerce' ),
                    'timepicker_change_validation'     => __( 'Please enter a valid time.', 'extra-product-options-for-woocommerce' ),
                    'datepicker_select_validation'     => __( 'Please select a valid date.', 'extra-product-options-for-woocommerce' ),
                    'datepicker_change_validation'     => __( 'Please enter a valid date.', 'extra-product-options-for-woocommerce' ),
                    'colorpicker_select_validation'    => __( 'Please select a valid color code.', 'extra-product-options-for-woocommerce' ),
                    'colorpicker_change_validation'    => __( 'Please enter a valid color code.', 'extra-product-options-for-woocommerce' ),
                    'switch_on_text'                   => __( 'On', 'extra-product-options-for-woocommerce' ),
                    'switch_off_text'                  => __( 'Off', 'extra-product-options-for-woocommerce' ),
                    'step'                             => __( 'step', 'extra-product-options-for-woocommerce' ),
                    'steps'                            => __( 'steps', 'extra-product-options-for-woocommerce' ),
                    'is_archive'                       => is_shop() || is_product_category() || is_product_tag() || is_product_taxonomy(),
                    'remove_uploaded_images_ajax'      => wp_create_nonce( 'epofw_remove_uploaded_images_ajax' ),
                    'uploaded_images_ajax'             => wp_create_nonce( 'epofw_uploaded_images_ajax' ),
                    'configure_text'                   => EPOFW_CONFIGURE_TEXT,
                    'options_configured_text_singular' => __( '%s option configured', 'extra-product-options-for-woocommerce' ),
                    'options_configured_text_plural'   => __( '%s options configured', 'extra-product-options-for-woocommerce' ),
                    'productgroup_msg_max_one'         => __( 'You can select only one product. Please uncheck the current selection first.', 'extra-product-options-for-woocommerce' ),
                    'productgroup_msg_max_many'        => __( 'You can select up to %s products. Please uncheck one or more to add this option.', 'extra-product-options-for-woocommerce' ),
                );
                /**
                 * Filter frontend localized script data.
                 *
                 * Allows modules to add custom data to the epofw_front_var JavaScript object.
                 *
                 * @since 4.4.5
                 *
                 * @param array      $front_localize_args Array of data to be localized.
                 * @param WC_Product $product_data        The product object (may be null).
                 */
                $front_localize_args = apply_filters( 'epofw_front_localize_data', $front_localize_args, $product_data );
                wp_localize_script( 'epofw-front-js', 'epofw_front_var', $front_localize_args );
                /**
                 * Fires after frontend scripts are enqueued.
                 *
                 * Use this hook to enqueue additional scripts or styles for frontend modules.
                 *
                 * @since 4.4.5
                 *
                 * @param WC_Product $product_data The product object (may be null).
                 */
                do_action( 'epofw_front_enqueue_scripts', $product_data );
            }
        }

        /**
         * Get match data from DB.
         *
         * @since    1.0.0
         *
         * @param array $match_field_id Matching field id.
         *
         * @return array $get_data_arr. Return array with general data.
         */
        public function epofw_get_match_data_from_db( $match_field_id ) {
            // Ensure $match_field_id is an array and bounded.
            if ( !is_array( $match_field_id ) || empty( $match_field_id ) ) {
                return array();
            }
            // Results are already bounded by post__in array, but set posts_per_page for clarity.
            $product_option_args = array(
                'post_type'      => EPOFW_DFT_POST_TYPE,
                'post_status'    => 'publish',
                'posts_per_page' => count( $match_field_id ),
                'post__in'       => $match_field_id,
                'orderby'        => 'ID',
                'order'          => 'DESC',
                'fields'         => 'ids',
            );
            $prd_wp_query = new WP_Query($product_option_args);
            $main_get_data_arr = array();
            $get_data_arr = array();
            if ( $prd_wp_query->have_posts() ) {
                foreach ( $prd_wp_query->posts as $f_id ) {
                    // Try to get from object cache first (optimized for new format).
                    $cache_key = 'epofw_data_' . $f_id;
                    $get_data = wp_cache_get( $cache_key, 'epofw' );
                    if ( false === $get_data ) {
                        $get_data = epofw_get_data_from_db( $f_id );
                        // Cache for 1 hour.
                        if ( !empty( $get_data ) ) {
                            wp_cache_set(
                                $cache_key,
                                $get_data,
                                'epofw',
                                HOUR_IN_SECONDS
                            );
                        }
                    }
                    if ( !empty( $get_data ) ) {
                        // Store the addon post ID for later use in section merging.
                        $get_data_arr['epofw_addon_post_id'] = $f_id;
                        foreach ( $get_data as $gkey => $gdata ) {
                            if ( 'general' !== $gkey ) {
                                $get_data_arr[$gkey] = $gdata;
                            } else {
                                $new_get_data = array();
                                if ( !empty( $gdata ) ) {
                                    foreach ( $gdata as $vkey => $vdata ) {
                                        $is_section = epofw_check_array_key_exists( 'section_id', $vdata );
                                        $is_field = epofw_check_array_key_exists( 'field_status', $vdata );
                                        if ( $is_section ) {
                                            $get_section_status = epofw_check_array_key_exists( 'section_status', $vdata );
                                            if ( empty( $get_section_status ) ) {
                                                unset($get_data['general'][$vkey]);
                                            } else {
                                                $new_get_data[$vkey] = $vdata;
                                            }
                                        } elseif ( $is_field ) {
                                            $get_field_status = epofw_check_array_key_exists( 'field_status', $vdata );
                                            if ( 'on' !== $get_field_status ) {
                                                unset($get_data['general'][$vkey]);
                                            } else {
                                                $new_get_data[$vkey] = $vdata;
                                                $get_logical_operation = epofw_check_array_key_exists( 'logical_operation', $vdata );
                                                if ( $get_logical_operation ) {
                                                    foreach ( (array) $get_logical_operation as $lodata ) {
                                                        $get_epofw_conditional_logic_fields = epofw_check_array_key_exists( 'epofw_conditional_logic_fields', $lodata );
                                                        if ( empty( $get_epofw_conditional_logic_fields ) ) {
                                                            unset($new_get_data[$vkey]['logical_operation']);
                                                        }
                                                    }
                                                }
                                            }
                                        } else {
                                            $new_get_data[$vkey] = $vdata;
                                        }
                                    }
                                }
                                $get_data_arr['general'] = $new_get_data;
                            }
                        }
                    }
                    $main_get_data_arr[] = $get_data_arr;
                }
            }
            /**
             * Apply filter for get match data from DB.
             *
             * @since 1.0.0
             */
            return apply_filters( 'epofw_get_match_data_from_db', $main_get_data_arr );
        }

        /**
         * Evaluate a single condition.
         *
         * @since 4.4.4
         *
         * @param array  $condition_data Condition data array.
         * @param string $current_prd_id Current product ID.
         *
         * @return bool True if condition is met, false otherwise.
         */
        private function epofw_evaluate_single_condition( $condition_data, $current_prd_id ) {
            $condition = ( isset( $condition_data['condition'] ) ? $condition_data['condition'] : '' );
            $operator = ( isset( $condition_data['operator'] ) ? $condition_data['operator'] : '' );
            $value = ( isset( $condition_data['value'] ) ? $condition_data['value'] : array() );
            if ( empty( $condition ) || empty( $operator ) || empty( $value ) ) {
                return false;
            }
            if ( is_array( $value ) && !empty( $value ) ) {
                $value = array_map( 'strval', $value );
            } else {
                return false;
            }
            $result = false;
            switch ( $condition ) {
                case 'product':
                    if ( 'is_equal_to' === $operator ) {
                        $result = in_array( $current_prd_id, $value, true );
                    } elseif ( 'not_equal_to' === $operator ) {
                        $result = !in_array( $current_prd_id, $value, true );
                    }
                    break;
                case 'category':
                    $cart_product_category = epofw_get_cached_product_terms( $current_prd_id, 'product_cat' );
                    if ( 'is_equal_to' === $operator ) {
                        if ( !empty( $cart_product_category ) ) {
                            foreach ( $cart_product_category as $cat_id ) {
                                if ( in_array( strval( $cat_id ), $value, true ) ) {
                                    $result = true;
                                    break;
                                }
                            }
                        }
                    } elseif ( 'not_equal_to' === $operator ) {
                        $has_match = false;
                        if ( !empty( $cart_product_category ) ) {
                            foreach ( $cart_product_category as $cat_id ) {
                                if ( in_array( strval( $cat_id ), $value, true ) ) {
                                    $has_match = true;
                                    break;
                                }
                            }
                        }
                        $result = !$has_match;
                    }
                    break;
                case 'tag':
                    $cart_product_tag = epofw_get_cached_product_terms( $current_prd_id, 'product_tag' );
                    if ( 'is_equal_to' === $operator ) {
                        if ( !empty( $cart_product_tag ) ) {
                            foreach ( $cart_product_tag as $tag_id ) {
                                if ( in_array( strval( $tag_id ), $value, true ) ) {
                                    $result = true;
                                    break;
                                }
                            }
                        }
                    } elseif ( 'not_equal_to' === $operator ) {
                        $has_match = false;
                        if ( !empty( $cart_product_tag ) ) {
                            foreach ( $cart_product_tag as $tag_id ) {
                                if ( in_array( strval( $tag_id ), $value, true ) ) {
                                    $has_match = true;
                                    break;
                                }
                            }
                        }
                        $result = !$has_match;
                    }
                    break;
                default:
                    break;
            }
            /**
             * Filter to customize condition evaluation result.
             *
             * @since 4.4.4
             *
             * @param bool  $result         Evaluation result.
             * @param array $condition_data Condition data.
             * @param int   $current_prd_id Current product ID.
             */
            $filtered_result = apply_filters(
                'epofw_evaluate_condition',
                $result,
                $condition_data,
                $current_prd_id
            );
            return $filtered_result;
        }

        /**
         * Combine boolean results with AND or OR logic.
         *
         * @since 4.4.4
         *
         * @param array  $results Array of boolean results.
         * @param string $logic   Logic type: 'and' or 'or'.
         *
         * @return bool Combined result.
         */
        private function epofw_combine_logic( $results, $logic = 'and' ) {
            if ( empty( $results ) ) {
                return true;
            }
            if ( 'or' === $logic ) {
                return in_array( true, $results, true );
            } else {
                return !in_array( false, $results, true );
            }
        }

        /**
         * Evaluate a rule group.
         *
         * @since 4.4.4
         *
         * @param array  $group          Group data with conditions and logic.
         * @param string $current_prd_id Current product ID.
         *
         * @return bool True if group conditions are met.
         */
        private function epofw_evaluate_rule_group( $group, $current_prd_id ) {
            if ( empty( $group['conditions'] ) || !is_array( $group['conditions'] ) ) {
                return true;
            }
            $condition_results = array();
            foreach ( $group['conditions'] as $condition_index => $condition_data ) {
                $condition_result = $this->epofw_evaluate_single_condition( $condition_data, $current_prd_id );
                $condition_results[] = $condition_result;
            }
            $group_logic = ( isset( $group['logic'] ) ? $group['logic'] : 'and' );
            $result = $this->epofw_combine_logic( $condition_results, $group_logic );
            /**
             * Filter to customize group evaluation result.
             *
             * @since 4.4.4
             *
             * @param bool  $result         Evaluation result.
             * @param array $group          Group data.
             * @param int   $current_prd_id Current product ID.
             */
            return apply_filters(
                'epofw_evaluate_rule_group',
                $result,
                $group,
                $current_prd_id
            );
        }

        /**
         * Evaluate nested additional rules.
         *
         * @since 4.4.4
         *
         * @param array  $rule_data      Rules data (old or new format).
         * @param string $current_prd_id Current product ID.
         *
         * @return bool True if all rules are satisfied.
         */
        private function epofw_evaluate_additional_rules( $rule_data, $current_prd_id ) {
            // Normalize rule data (migrate old format if needed).
            $rule_data = epofw_normalize_additional_rules_data( $rule_data );
            if ( empty( $rule_data['groups'] ) || !is_array( $rule_data['groups'] ) ) {
                return false;
                // No rules = hide addon (addon must have rules to display).
            }
            $group_results = array();
            foreach ( $rule_data['groups'] as $group_index => $group ) {
                $group_result = $this->epofw_evaluate_rule_group( $group, $current_prd_id );
                $group_results[] = $group_result;
            }
            $logic_between = ( isset( $rule_data['logic_between_groups'] ) ? $rule_data['logic_between_groups'] : 'and' );
            $final_result = $this->epofw_combine_logic( $group_results, $logic_between );
            /**
             * Filter to customize all rules evaluation result.
             *
             * @since 4.4.4
             *
             * @param bool  $final_result   Final evaluation result.
             * @param array $rule_data      Rules data.
             * @param int   $current_prd_id Current product ID.
             */
            return apply_filters(
                'epofw_evaluate_all_rules',
                $final_result,
                $rule_data,
                $current_prd_id
            );
        }

        /**
         * Find match id based on product id..
         *
         * @since 1.0.0
         *
         * @param int $current_prd_id Current product id.
         *
         * @return array $matched_id_arr. Return array with matched id.
         */
        public function epofw_find_match_id( $current_prd_id ) {
            global $wpdb;
            $current_prd_id = strval( $current_prd_id );
            // Static cache for per-product match results within the same request.
            static $epofw_match_cache = array();
            if ( isset( $epofw_match_cache[$current_prd_id] ) ) {
                return $epofw_match_cache[$current_prd_id];
            }
            /**
             * Filter the maximum number of addon post IDs to retrieve when finding matches.
             * This prevents unbounded queries on sites with many addons.
             *
             * @since 4.4.4
             *
             * @param int $limit Maximum number of addon post IDs. Default 1000.
             */
            $max_addon_ids = apply_filters( 'epofw_max_addon_ids_in_match_query', 1000 );
            // Try to get valid post IDs from object cache first.
            $cache_key = 'epofw_valid_addon_post_ids_' . $max_addon_ids;
            $valid_post_ids = wp_cache_get( $cache_key, 'epofw' );
            if ( false === $valid_post_ids ) {
                // Get only post IDs that exist in custom tables, with a limit to prevent unbounded queries.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query with caching implemented above.
                $valid_post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_id FROM {$wpdb->prefix}epofw_addons WHERE post_id IS NOT NULL LIMIT %d", $max_addon_ids ) );
                // Cache for 1 hour - invalidated when addons are saved/deleted.
                wp_cache_set(
                    $cache_key,
                    $valid_post_ids,
                    'epofw',
                    HOUR_IN_SECONDS
                );
            }
            if ( empty( $valid_post_ids ) ) {
                $epofw_match_cache[$current_prd_id] = array();
                return array();
            }
            // Since we're using post__in, posts_per_page doesn't matter much, but we set it to array size for clarity.
            $product_option_args = array(
                'post_type'      => EPOFW_DFT_POST_TYPE,
                'post_status'    => 'publish',
                'posts_per_page' => count( $valid_post_ids ),
                'orderby'        => 'ID',
                'order'          => 'DESC',
                'fields'         => 'ids',
                'post__in'       => $valid_post_ids,
            );
            $prd_wp_query = new WP_Query($product_option_args);
            $matched_id_arr = array();
            if ( $prd_wp_query->have_posts() ) {
                foreach ( $prd_wp_query->posts as $f_id ) {
                    $get_data = epofw_get_data_from_db( $f_id );
                    if ( !empty( $get_data ) ) {
                        $aadditional_rule_detail = epofw_check_array_key_exists( 'additional_rule_data', $get_data );
                        if ( !empty( $aadditional_rule_detail ) ) {
                            // Use new nested evaluation logic (handles both old and new formats).
                            $rules_match = $this->epofw_evaluate_additional_rules( $aadditional_rule_detail, $current_prd_id );
                            if ( $rules_match ) {
                                $matched_id_arr[] = $f_id;
                            }
                        }
                        // No rules = hide addon (addon must have rules to display).
                    }
                }
            }
            /**
             * Apply filter for find match id.
             *
             * @since 1.0.0
             */
            $result = apply_filters( 'epofw_find_match_id', array_unique( $matched_id_arr ) );
            $epofw_match_cache[$current_prd_id] = $result;
            return $result;
        }

        /**
         * Get fields data which matched.
         *
         * @since    1.0.0
         *
         * @param int $current_prd_id Current product id.
         *
         * @return array $get_data Return array with matched data.
         */
        public function epofw_get_fields_data( $current_prd_id ) {
            $get_match_field_id = $this->epofw_find_match_id( $current_prd_id );
            $get_data = '';
            if ( !empty( $get_match_field_id ) ) {
                $get_data = $this->epofw_get_match_data_from_db( $get_match_field_id );
            }
            /**
             * Apply filter for get field data.
             *
             * @since 1.0.0
             */
            return apply_filters( 'epofw_get_fields_data', $get_data );
        }

        /**
         * Add fields before add to cart button.
         *
         * @since 1.0.0
         */
        public function epofw_before_add_to_cart_button() {
            global $post;
            $main_fields_data_arr = $this->epofw_get_fields_data( $post->ID );
            if ( !empty( $main_fields_data_arr ) ) {
                // Add nonce field using the centralized function.
                $this->epofw_add_nonce_field();
                // Render each addon separately to preserve individual conditions.
                // Addons with different conditions (additional_rule_data) need separate containers.
                foreach ( $main_fields_data_arr as $fields_data_arr ) {
                    $addon_position = epofw_check_array_key_exists( 'epofw_addon_position', $fields_data_arr );
                    if ( 'before_add_to_cart' === $addon_position ) {
                        if ( !empty( $fields_data_arr ) ) {
                            $addon_general = epofw_check_array_key_exists( 'general', $fields_data_arr );
                            if ( !empty( $addon_general ) ) {
                                // Render this addon with its own conditions.
                                $this->epofw_front_html( $fields_data_arr, 'epofw_single' );
                            }
                        }
                    }
                }
            }
        }

        /**
         * FUnction will return front html.
         *
         * @param array  $fields_data_arr Array of field data.
         * @param string $epofw_action    Epofw action.
         */
        public function epofw_front_html( $fields_data_arr, $epofw_action ) {
            /**
             * Filter the fields data before rendering addon HTML.
             *
             * @since 4.4.5
             *
             * @param array  $fields_data_arr Array of addon field data.
             * @param string $epofw_action    Current action context (epofw_single, epofw_archive, etc.).
             */
            $fields_data_arr = apply_filters( 'epofw_before_render_addon_fields', $fields_data_arr, $epofw_action );
            // Clear any previously collected styles (for multiple addons on same page).
            EPOFW_Style_Collector::clear();
            /**
             * Fires before addon HTML is rendered on product page.
             *
             * @since 4.4.5
             *
             * @param array  $fields_data_arr Array of addon field data.
             * @param string $epofw_action    Current action context.
             */
            do_action( 'epofw_before_addon_html', $fields_data_arr, $epofw_action );
            // Use output buffering to collect template output, then output styles before template.
            ob_start();
            $file_name = 'epofw-addon-table-html.php';
            wc_get_template(
                $file_name,
                array(
                    'fields_data_arr' => $fields_data_arr,
                    'epofw_action'    => $epofw_action,
                ),
                epofw_get_template_path(),
                epofw_get_default_path()
            );
            $template_output = ob_get_clean();
            // Output collected styles first, then template.
            echo EPOFW_Style_Collector::output_styles();
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            echo $template_output;
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            /**
             * Fires after addon HTML is rendered on product page.
             *
             * @since 4.4.5
             *
             * @param array  $fields_data_arr Array of addon field data.
             * @param string $epofw_action    Current action context.
             */
            do_action( 'epofw_after_addon_html', $fields_data_arr, $epofw_action );
        }

        /**
         * Function will return front html.
         *
         * @param string $epofw_action Action of current page.
         */
        public function epofw_addon_html( $epofw_action ) {
            wc_get_template(
                'epofw-addon-details.php',
                array(
                    'epofw_action' => $epofw_action,
                ),
                epofw_get_template_path(),
                epofw_get_default_path()
            );
        }

        /**
         * Add field after add to cart button.
         *
         * @since 1.0.0
         */
        public function epofw_after_add_to_cart_button() {
            global $post;
            $main_fields_data_arr = $this->epofw_get_fields_data( $post->ID );
            if ( !empty( $main_fields_data_arr ) ) {
                // Add nonce field using the centralized function.
                $this->epofw_add_nonce_field();
                // Render each addon separately to preserve individual conditions.
                // Addons with different conditions (additional_rule_data) need separate containers.
                foreach ( $main_fields_data_arr as $fields_data_arr ) {
                    $addon_position = epofw_check_array_key_exists( 'epofw_addon_position', $fields_data_arr );
                    if ( 'after_add_to_cart' === $addon_position ) {
                        if ( !empty( $fields_data_arr ) ) {
                            $addon_general = epofw_check_array_key_exists( 'general', $fields_data_arr );
                            if ( !empty( $addon_general ) ) {
                                // Render this addon with its own conditions.
                                $this->epofw_front_html( $fields_data_arr, 'epofw_single' );
                            }
                        }
                    }
                }
                $this->epofw_addon_html( 'epofw_single' );
            }
        }

        /**
         * Exclude field types which not include in meta section.
         *
         * @since    1.0.0
         */
        public function exclude_field_type_on_cart_page() {
            $exclude_ftocp = array('heading', 'paragraph', 'separator');
            return $exclude_ftocp;
        }

        /**
         * Add to cart validation for field data.
         *
         * @since    1.0.0
         *
         * @param string $passed     Passed array.
         * @param int    $product_id Product id.
         *
         * @throws Exception Get Error.
         * @return string|boolean
         */
        public function epofw_add_to_cart_validation( $passed, $product_id ) {
            if ( !isset( $_POST ) && empty( $product_id ) ) {
                // phpcs:ignore WordPress.Security.NonceVerification.Missing
                return false;
            }
            /**
             * Filter to force validation even without nonce.
             * Used for theme compatibility (e.g., Elessi/NASA themes with custom AJAX).
             *
             * @since 4.4.5
             *
             * @param bool $force_validation Whether to force validation without nonce.
             * @param int  $product_id       Product ID.
             */
            $force_validation = apply_filters( 'epofw_force_validation_without_nonce', false, $product_id );
            if ( !$force_validation && !isset( $_POST['epofw_add_to_cart_nonce_' . $product_id] ) ) {
                return $passed;
            }
            // Check if this is an "Order Again" request.
            if ( isset( $_GET['order_again'] ) ) {
                // Verify WooCommerce's order again nonce.
                if ( !isset( $_GET['_wpnonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'woocommerce-order_again' ) ) {
                    return false;
                }
                return $passed;
            }
            // Verify nonce only if present (allows forced validation without nonce for theme compatibility).
            if ( isset( $_POST['epofw_add_to_cart_nonce_' . $product_id] ) && !$this->verify_add_to_cart_nonce( $product_id ) ) {
                wc_add_notice( esc_html__( 'Security check failed. Please refresh the page and try again.', 'extra-product-options-for-woocommerce' ), 'error' );
                return false;
            }
            // Preserve array structure for addon fields (e.g. ['value' => '...', 'quantity' => 1]); array_map would flatten to ''.
            $get_post_data = map_deep( wp_unslash( $_POST ), 'sanitize_text_field' );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            // Normalize POST keys: strip epofw_shop_{product_id}_ so field names match addon config.
            $get_post_data_normalized = array();
            $search_key = 'epofw_shop_' . (int) $product_id . '_';
            foreach ( $get_post_data as $post_key => $post_value_data ) {
                if ( strpos( $post_key, 'epofw_field_' ) !== false ) {
                    $normalized_key = ( strpos( $post_key, 'hidden_epofw_field_' ) !== false ? str_replace( 'hidden_', '', $post_key ) : $post_key );
                    $normalized_key = str_replace( $search_key, '', $normalized_key );
                    $get_post_data_normalized[$normalized_key] = $post_value_data;
                } else {
                    $get_post_data_normalized[$post_key] = $post_value_data;
                }
            }
            $main_getting_new_files_data = '';
            $fields_data_arr = $this->epofw_get_fields_data( $product_id );
            $get_field_name_arr = $this->epofw_get_field_name_from_data( $fields_data_arr, $get_post_data_normalized );
            $field_err_status = $this->epofw_check_validation( $get_post_data_normalized, $get_field_name_arr, $main_getting_new_files_data );
            if ( in_array( false, $field_err_status, true ) ) {
                $passed = false;
            }
            return $passed;
        }

        /**
         * Check validation.
         *
         * @since 1.0.0
         *
         * @param array $get_post_data               Get posted data.
         * @param array $get_field_name_arr          Get field name array.
         * @param array $main_getting_new_files_data Get main field array.
         *
         * @return array $field_err_status Status of uploaded file.
         */
        public function epofw_check_validation( $get_post_data, $get_field_name_arr, $main_getting_new_files_data ) {
            $field_err_status = array();
            if ( isset( $get_post_data ) ) {
                $epofw_post_data = array();
                if ( !empty( $get_post_data ) ) {
                    foreach ( $get_post_data as $post_key => $post_value_data ) {
                        if ( strpos( $post_key, 'epofw_field_' ) !== false ) {
                            if ( !empty( $post_value_data ) ) {
                                $epofw_post_data[$post_key] = $post_value_data;
                            }
                        }
                    }
                }
                if ( !empty( $epofw_post_data ) ) {
                    foreach ( $epofw_post_data as $post_data_key => $value ) {
                        if ( !empty( $value ) && !empty( $get_field_name_arr ) && array_key_exists( $post_data_key, $get_field_name_arr ) ) {
                            $check_field_label = epofw_check_array_key_exists( 'label', $get_field_name_arr[$post_data_key] );
                            $check_field_input = epofw_check_array_key_exists( 'field', $get_field_name_arr[$post_data_key] );
                            $check_field_lbl_title = '';
                            if ( $check_field_label ) {
                                $check_field_lbl_title = esc_html( epofw_check_array_key_exists( 'title', $check_field_label ) );
                            }
                            if ( $check_field_input ) {
                                $check_field_name = epofw_check_array_key_exists( 'name', $check_field_input );
                                $check_field_type = epofw_check_array_key_exists( 'type', $check_field_input );
                                if ( isset( $get_post_data[$check_field_name] ) || isset( $get_post_data[$check_field_name]['value'] ) ) {
                                    if ( isset( $get_post_data[$check_field_name]['value'] ) ) {
                                        // phpcs:ignore WordPress.Security.NonceVerification.Missing
                                        $post_check_field_name = trim( sanitize_text_field( wp_unslash( $get_post_data[$check_field_name]['value'] ) ) );
                                    } else {
                                        $post_check_field_name = trim( sanitize_text_field( wp_unslash( $get_post_data[$check_field_name] ) ) );
                                    }
                                    if ( 'text' === $check_field_type ) {
                                        $check_field_restriction = ( isset( $get_field_name_arr[$post_data_key]['epofw_field_settings']['field_restriction'] ) ? sanitize_text_field( $get_field_name_arr[$post_data_key]['epofw_field_settings']['field_restriction'] ) : '' );
                                        if ( $check_field_restriction ) {
                                            // Sanitize the field title once for all error messages.
                                            $safe_field_title = esc_html( $check_field_lbl_title );
                                            if ( 'only_number' === $check_field_restriction ) {
                                                if ( 1 !== preg_match( '/^[0-9]*$/', $post_check_field_name ) ) {
                                                    /* translators: %1$s: Field label that only accepts numbers */
                                                    $error_message = sprintf( 
                                                        /* translators: %1$s: Field label that only accepts numbers */
                                                        esc_html__( 'Only numbers are allowed for "%1$s".', 'extra-product-options-for-woocommerce' ),
                                                        esc_html( $safe_field_title )
                                                     );
                                                    $this->epofw_add_to_cart_error_msg( 'only_number', $error_message );
                                                    $field_err_status[] = false;
                                                }
                                            }
                                            if ( 'all_number' === $check_field_restriction ) {
                                                if ( 1 !== preg_match( '/^[0-9]+(\\.[0-9]+)]*$/', $post_check_field_name ) ) {
                                                    /* translators: 1: Field label */
                                                    $error_message = sprintf( __( 'Decimal numbers are allowed for " %1$s".', 'extra-product-options-for-woocommerce' ), $check_field_lbl_title );
                                                    $this->epofw_add_to_cart_error_msg( 'all_number', $error_message );
                                                    $field_err_status[] = false;
                                                }
                                            }
                                            if ( 'only_text' === $check_field_restriction ) {
                                                if ( 1 !== preg_match( '/^[a-zA-Z]*$/i', $post_check_field_name ) ) {
                                                    /* translators: 1: Field label */
                                                    $error_message = sprintf( __( 'Only texts are allowed for " %1$s".', 'extra-product-options-for-woocommerce' ), $check_field_lbl_title );
                                                    $this->epofw_add_to_cart_error_msg( 'only_text', $error_message );
                                                    $field_err_status[] = false;
                                                }
                                            }
                                            if ( 'text_number' === $check_field_restriction ) {
                                                if ( 1 !== preg_match( '/^[a-zA-Z0-9]*$/i', $post_check_field_name ) ) {
                                                    /* translators: 1: Field label */
                                                    $error_message = sprintf( __( 'Only text and numbers are allowed for " %1$s".', 'extra-product-options-for-woocommerce' ), $check_field_lbl_title );
                                                    $this->epofw_add_to_cart_error_msg( 'text_number', $error_message );
                                                    $field_err_status[] = false;
                                                }
                                            }
                                            if ( 'email' === $check_field_restriction ) {
                                                if ( !filter_var( $post_check_field_name, FILTER_VALIDATE_EMAIL ) ) {
                                                    /* translators: 1: Field label */
                                                    $error_message = sprintf( __( 'Invalid email format for " %1$s".', 'extra-product-options-for-woocommerce' ), $check_field_lbl_title );
                                                    $this->epofw_add_to_cart_error_msg( 'email', $error_message );
                                                    $field_err_status[] = false;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return $field_err_status;
        }

        /**
         * Add cart item data.
         *
         * @since 1.0.0
         *
         * @param array $cart_item  Array of cart item.
         * @param int   $product_id Product ID.
         *
         * @throws Exception When validation fails.
         * @return array|false $cart_item Array of cart item on success, false on failure.
         */
        public function epofw_add_cart_item_data( $cart_item, $product_id ) {
            // Verify product ID.
            $product_id = absint( $product_id );
            if ( !$product_id ) {
                return $cart_item;
            }
            // Verify we have POST data.
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally below for AJAX compatibility.
            if ( !isset( $_POST ) || empty( $_POST ) ) {
                return $cart_item;
            }
            // Check if nonce field exists in POST - if present, verify it.
            // If not present (e.g., AJAX add to cart), skip nonce verification but still process addon data.
            $nonce_name = 'epofw_add_to_cart_nonce_' . $product_id;
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Conditional nonce check: verify only if nonce field exists (AJAX compatibility).
            if ( isset( $_POST[$nonce_name] ) ) {
                // Nonce is present, verify it.
                if ( !$this->verify_add_to_cart_nonce( $product_id ) ) {
                    // Invalid nonce - return cart item without addon data for security.
                    return $cart_item;
                }
            }
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally above for AJAX compatibility.
            $get_post_data = map_deep( wp_unslash( $_POST ), 'sanitize_text_field' );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally above for AJAX compatibility.
            $get_product_id = filter_input( INPUT_POST, 'product_id', FILTER_VALIDATE_INT );
            $get_product_id = ( isset( $get_product_id ) ? sanitize_text_field( wp_unslash( $get_product_id ) ) : '' );
            $post_data = array();
            if ( isset( $get_post_data ) && !empty( $product_id ) ) {
                foreach ( $get_post_data as $post_key => $post_value_data ) {
                    if ( strpos( $post_key, 'epofw_field_' ) !== false ) {
                        if ( strpos( $post_key, 'hidden_epofw_field_' ) !== false ) {
                            $post_key = str_replace( 'hidden_', '', $post_key );
                        }
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally above for AJAX compatibility.
                        if ( !empty( $get_product_id ) ) {
                            $search_key = 'epofw_shop_' . esc_attr( $get_product_id ) . '_';
                            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally above for AJAX compatibility.
                            $post_key = str_replace( $search_key, '', $post_key );
                        }
                        // Handle array values properly.
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally above for AJAX compatibility.
                        if ( isset( $post_value_data['value'] ) ) {
                            $sanitized_data = array();
                            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled conditionally above for AJAX compatibility.
                            if ( is_array( $post_value_data['value'] ) ) {
                                $sanitized_data['value'] = array_map( 'sanitize_text_field', $post_value_data['value'] );
                            } else {
                                $sanitized_data['value'] = sanitize_text_field( $post_value_data['value'] );
                            }
                            if ( isset( $post_value_data['quantity'] ) ) {
                                $sanitized_data['quantity'] = absint( $post_value_data['quantity'] );
                            }
                            $post_data[$post_key] = $sanitized_data;
                        } elseif ( isset( $post_value_data['products'] ) && is_array( $post_value_data['products'] ) ) {
                            // Product Group: preserve products map (key => qty), sanitize as integers.
                            $sanitized_products = array();
                            foreach ( $post_value_data['products'] as $prod_key => $qty ) {
                                $k = absint( $prod_key );
                                $v = absint( $qty );
                                if ( $k > 0 && $v >= 0 ) {
                                    $sanitized_products[$k] = $v;
                                }
                            }
                            $post_data[$post_key] = array(
                                'products' => $sanitized_products,
                            );
                        } else {
                            $post_data[$post_key] = array_map( 'sanitize_text_field', $post_value_data );
                        }
                    } else {
                        $post_data[$post_key] = $post_value_data;
                    }
                }
            }
            // Here pass main product id.
            $fields_data_arr = $this->epofw_get_fields_data( $product_id );
            if ( isset( $post_data['variation_id'] ) ) {
                $product_id = $post_data['variation_id'];
            }
            $get_exftpoc = $this->exclude_field_type_on_cart_page();
            $passed_cart_data = array();
            if ( isset( $post_data ) ) {
                $get_field_name_arr = $this->epofw_get_field_name_from_data( $fields_data_arr, $post_data );
                if ( !empty( $get_field_name_arr ) ) {
                    $epofw_post_data = array();
                    $quantity = 1;
                    foreach ( $post_data as $post_key => $post_value_data ) {
                        if ( strpos( $post_key, 'epofw_field_' ) !== false ) {
                            if ( strpos( $post_key, 'hidden_epofw_field_' ) !== false ) {
                                $post_key = str_replace( 'hidden_', '', $post_key );
                            }
                            $epofw_post_data[$post_key] = $post_value_data;
                        }
                        $quantity = ( !empty( $post_data['quantity'] ) ? $post_data['quantity'] : 1 );
                    }
                    // Process each field value.
                    if ( !empty( $epofw_post_data ) ) {
                        foreach ( $epofw_post_data as $post_key => $post_value_data ) {
                            $key_exists = array_key_exists( $post_key, $get_field_name_arr );
                            if ( !empty( $post_value_data ) && $key_exists ) {
                                // Server-side conditional logic validation.
                                // Check if this field should be visible based on its conditions.
                                $field_form_data = epofw_check_array_key_exists( 'epofw_field_settings', $get_field_name_arr[$post_key] );
                                if ( !$field_form_data ) {
                                    // Try alternate key for field settings.
                                    $field_form_data = $get_field_name_arr[$post_key];
                                }
                                $check_field_label = epofw_check_array_key_exists( 'label', $get_field_name_arr[$post_key] );
                                $check_field_input = epofw_check_array_key_exists( 'field', $get_field_name_arr[$post_key] );
                                if ( $check_field_label ) {
                                    $check_field_lbl_title = epofw_check_array_key_exists( 'title', $check_field_label );
                                }
                                $check_field_name = '';
                                $check_field_inp_type = '';
                                if ( $check_field_input ) {
                                    $check_field_inp_type = epofw_check_array_key_exists( 'type', $check_field_input );
                                    $check_field_name = epofw_check_array_key_exists( 'name', $check_field_input );
                                    if ( strpos( $check_field_name, 'epofw_field_' ) === false ) {
                                        $check_field_name = 'epofw_field_' . $check_field_name;
                                    }
                                }
                                // For repeater instance fields, use the post_key (which includes _instance_X suffix).
                                $actual_field_name = $post_key;
                                if ( in_array( $check_field_inp_type, $get_exftpoc, true ) ) {
                                    unset($get_field_name_arr[$post_key]);
                                } elseif ( isset( $post_data[$actual_field_name] ) || isset( $post_data[$check_field_name] ) ) {
                                    if ( is_array( $post_value_data ) ) {
                                        $epofw_field_value = ( isset( $post_value_data['value'] ) ? $post_value_data['value'] : '' );
                                        $passed_cart_data['epofw_field_quantity'] = ( isset( $post_value_data['quantity'] ) ? $post_value_data['quantity'] : '1' );
                                    } else {
                                        // Use actual field name (instance-specific) or fall back to base field name.
                                        $epofw_field_value = ( isset( $post_data[$actual_field_name] ) ? $post_data[$actual_field_name] : $post_data[$check_field_name] );
                                    }
                                    if ( !in_array( $check_field_inp_type, $get_exftpoc, true ) ) {
                                        $passed_cart_data['epofw_label'] = $check_field_lbl_title;
                                    }
                                    if ( !in_array( $check_field_inp_type, $get_exftpoc, true ) ) {
                                        $passed_cart_data['product_id'] = $product_id;
                                        $passed_cart_data['epofw_type'] = $check_field_inp_type;
                                        // Use actual field name (includes instance suffix for repeater fields).
                                        $passed_cart_data['epofw_name'] = $actual_field_name;
                                        if ( 'productgroup' === $check_field_inp_type && isset( $post_value_data['products'] ) && is_array( $post_value_data['products'] ) ) {
                                            $pg_result = $this->epofw_process_productgroup_cart_data(
                                                $post_value_data['products'],
                                                $get_field_name_arr[$post_key],
                                                $product_id,
                                                $quantity,
                                                $check_field_lbl_title,
                                                $actual_field_name
                                            );
                                            if ( is_wp_error( $pg_result ) ) {
                                                throw new Exception(esc_html( $pg_result->get_error_message() ));
                                            }
                                            if ( !empty( $pg_result ) ) {
                                                $passed_cart_data = array_merge( $passed_cart_data, $pg_result );
                                                $passed_cart_data['epofw_form_data'] = $get_field_name_arr[$post_key];
                                            } else {
                                                $passed_cart_data = array();
                                            }
                                        } else {
                                            $epofw_slider_range_min_value = '';
                                            if ( is_array( $epofw_field_value ) ) {
                                                // Sanitize array values and remove any empty or malicious content.
                                                $post_check_field_name = array_filter( array_map( function ( $value ) {
                                                    // Remove any HTML, extra whitespace, and sanitize.
                                                    $clean_value = trim( sanitize_text_field( wp_unslash( $value ) ) );
                                                    return ( '' !== $clean_value ? $clean_value : null );
                                                }, $epofw_field_value ) );
                                                // Join with comma and space, escape for display.
                                                $passed_cart_data['epofw_value'] = esc_html( implode( ', ', array_filter( $post_check_field_name ) ) );
                                            } else {
                                                $post_check_field_name = trim( sanitize_text_field( wp_unslash( esc_html( wp_strip_all_tags( $epofw_field_value ) ) ) ) );
                                                $passed_cart_data['epofw_value'] = $post_check_field_name;
                                            }
                                            // Dual-handle slider (min/max) pricing needs both posted handles.
                                            if ( 'slider' === $check_field_inp_type && is_array( $post_value_data ) ) {
                                                $epofw_posted_range_min = ( isset( $post_value_data['range_min'] ) ? $post_value_data['range_min'] : '' );
                                                if ( '' !== $epofw_posted_range_min ) {
                                                    $epofw_posted_range_min = trim( sanitize_text_field( wp_unslash( (string) $epofw_posted_range_min ) ) );
                                                    if ( '' !== $epofw_posted_range_min && is_numeric( $epofw_posted_range_min ) ) {
                                                        $epofw_slider_range_min_value = (float) $epofw_posted_range_min;
                                                    }
                                                }
                                            }
                                            // For range slider, update display value to "min - max" format.
                                            if ( 'slider' === $check_field_inp_type && '' !== $epofw_slider_range_min_value ) {
                                                $passed_cart_data['epofw_value'] = esc_html( $epofw_slider_range_min_value . ' - ' . $post_check_field_name );
                                            }
                                            $fields_data_for_price = $get_field_name_arr[$post_key];
                                            if ( 'slider' === $check_field_inp_type && '' !== $epofw_slider_range_min_value ) {
                                                $fields_data_for_price['epofw_posted_range_min_value'] = $epofw_slider_range_min_value;
                                            }
                                            $epofw_validate_option = $this->epofw_validate_options_exists( $post_check_field_name, $get_field_name_arr[$post_key] );
                                            if ( !$epofw_validate_option ) {
                                                /**
                                                 * Filter the error message for invalid option.
                                                 *
                                                 * @since 1.0.0
                                                 *
                                                 * @param string $message Error message.
                                                 */
                                                $message = apply_filters( 'epfow_addon_invalid_option', __( 'Option is invalid', 'extra-product-options-for-woocommerce' ), $product_id );
                                                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Hook name kept for backward compatibility (typo in original name).
                                                throw new Exception(esc_html( $message ));
                                            }
                                            $find_price_based_on_name = $this->epofw_find_price_based_on_name(
                                                $product_id,
                                                $post_check_field_name,
                                                $quantity,
                                                $fields_data_for_price
                                            );
                                            if ( $find_price_based_on_name ) {
                                                $check_addon_price = epofw_check_array_key_exists( 'addon_price', $find_price_based_on_name );
                                                $check_addon_price_type = epofw_check_array_key_exists( 'addon_price_type', $find_price_based_on_name );
                                                $passed_cart_data['epofw_price'] = $check_addon_price;
                                                $check_addon_original_price = epofw_check_array_key_exists( 'addon_original_price', $find_price_based_on_name );
                                                $passed_cart_data['epofw_original_price'] = $check_addon_original_price;
                                                $passed_cart_data['epofw_price_type'] = $check_addon_price_type;
                                            } else {
                                                $passed_cart_data['epofw_price'] = 0;
                                                $passed_cart_data['epofw_original_price'] = 0;
                                                $passed_cart_data['epofw_original_price'] = 0;
                                                $passed_cart_data['epofw_price_type'] = 'fixed';
                                            }
                                            $passed_cart_data['epofw_form_data'] = $get_field_name_arr[$post_key];
                                        }
                                    }
                                    if ( !empty( $passed_cart_data ) ) {
                                        // Use $actual_field_name (includes _instance_X suffix for repeater fields)
                                        // to ensure each instance field has its own unique key in cart data.
                                        $cart_item['epofw_data'][$actual_field_name] = $passed_cart_data;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            /**
             * Filter cart item data after addon data has been added.
             *
             * @since 4.4.5
             *
             * @param array $cart_item  Cart item data with epofw_data.
             * @param int   $product_id Product ID.
             */
            $cart_item = apply_filters( 'epofw_cart_item_data_after_addon', $cart_item, $product_id );
            /**
             * Fires after addon data has been added to cart item.
             *
             * @since 4.4.5
             *
             * @param array $cart_item  Cart item data with epofw_data.
             * @param int   $product_id Product ID.
             */
            do_action( 'epofw_after_add_cart_item_data', $cart_item, $product_id );
            return $cart_item;
        }

        /**
         * Process Product Group field data for cart: validate and compute price/lines.
         *
         * @since 4.4.6
         * @param array  $products_qty   Map of product_id/variation_id => qty (already sanitized).
         * @param array  $fields_data    Field config with options.
         * @param int    $main_product_id Main product ID (for context).
         * @param int    $quantity       Main product quantity.
         * @param string $label_title    Field label for display.
         * @param string $actual_field_name Field name key.
         * @return array|WP_Error Cart data slice (epofw_value, epofw_price, epofw_product_group_lines, etc.) or WP_Error on validation failure.
         */
        public function epofw_process_productgroup_cart_data(
            array $products_qty,
            array $fields_data,
            $main_product_id,
            $quantity,
            $label_title,
            $actual_field_name
        ) {
            $settings = ( isset( $fields_data['epofw_field_settings'] ) ? $fields_data['epofw_field_settings'] : array() );
            $options = ( isset( $settings['options'] ) ? $settings['options'] : array() );
            $min_sel = ( isset( $settings['min_selections'] ) ? absint( $settings['min_selections'] ) : 0 );
            $max_sel = ( isset( $settings['max_selections'] ) && '' !== (string) $settings['max_selections'] ? absint( $settings['max_selections'] ) : 0 );
            $required = !empty( $settings['required'] );
            $main_price = 0.0;
            $main_prod = wc_get_product( $main_product_id );
            if ( $main_prod ) {
                $main_price = (float) epofw_display_product_price( $main_prod, 'shop' );
            }
            // Build allowed keys (product_id or variation_id) from field options.
            $allowed_keys = array();
            $option_by_key = array();
            foreach ( $options as $opt_value ) {
                if ( empty( $opt_value ) ) {
                    continue;
                }
                $opt_data = epofw_explore_label_from_opt_group( $opt_value );
                $pid = ( isset( $opt_data['product_id'] ) ? absint( $opt_data['product_id'] ) : 0 );
                $vid = ( isset( $opt_data['variation_id'] ) ? absint( $opt_data['variation_id'] ) : 0 );
                $key = ( $vid ? $vid : $pid );
                if ( $key > 0 ) {
                    $allowed_keys[$key] = true;
                    $option_by_key[$key] = $opt_value;
                }
            }
            // Filter to only allowed products and qty > 0.
            $selected = array();
            foreach ( $products_qty as $key => $qty ) {
                $key = absint( $key );
                $qty = absint( $qty );
                if ( $key > 0 && $qty > 0 && isset( $allowed_keys[$key] ) ) {
                    $selected[$key] = $qty;
                }
            }
            $total_qty = array_sum( $selected );
            $count_products = count( $selected );
            $allow_multiple = !empty( $settings['allow_multiple_productgroup'] );
            $disable_stock = !empty( $settings['disable_addon_product_stock'] );
            // Product Group min/max settings refer to number of selected products, not sum of per-product quantities.
            $selections_basis = (int) $count_products;
            $effective_max_sel = ( $allow_multiple ? $max_sel : 1 );
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                error_log( wp_json_encode( array(
                    'context'         => 'epofw_productgroup',
                    'field'           => $actual_field_name,
                    'main_product_id' => (int) $main_product_id,
                    'min'             => (int) $min_sel,
                    'max'             => (int) $max_sel,
                    'required'        => (bool) $required,
                    'selected_keys'   => array_keys( $selected ),
                    'selected_qty'    => $selected,
                    'total_qty'       => (int) $total_qty,
                    'count_products'  => (int) $count_products,
                ) ) );
            }
            if ( $required && $selections_basis <= 0 ) {
                return new WP_Error('epofw_productgroup_required', __( 'Please select at least one product from the Product Group.', 'extra-product-options-for-woocommerce' ));
            }
            if ( $min_sel > 0 && $selections_basis < $min_sel ) {
                return new WP_Error('epofw_productgroup_min', sprintf( 
                    /* translators: %s: minimum number */
                    __( 'Product Group: select at least %s product(s).', 'extra-product-options-for-woocommerce' ),
                    $min_sel
                 ));
            }
            if ( $effective_max_sel > 0 && $selections_basis > $effective_max_sel ) {
                $max_msg = ( 1 === $effective_max_sel ? __( 'You can select only one product. Please uncheck the current selection first.', 'extra-product-options-for-woocommerce' ) : sprintf( 
                    /* translators: %s: maximum number */
                    __( 'You can select up to %s products. Please uncheck one or more to add this option.', 'extra-product-options-for-woocommerce' ),
                    $effective_max_sel
                 ) );
                return new WP_Error('epofw_productgroup_max', $max_msg);
            }
            /**
             * Filter whether Product Group should validate stock for selected products.
             *
             * @since 4.4.6
             *
             * @param bool   $should_validate Whether to run stock validation.
             * @param array  $selected        Selected product keys => qty.
             * @param array  $settings        Field settings.
             * @param int    $main_product_id Main product ID.
             * @param string $actual_field_name Field name key.
             */
            $should_validate_stock = apply_filters(
                'epofw_productgroup_should_validate_stock',
                !$disable_stock,
                $selected,
                $settings,
                $main_product_id,
                $actual_field_name
            );
            if ( $should_validate_stock ) {
                /**
                 * Filter Product Group stock validation result (before generic addon filter).
                 *
                 * @since 4.4.6
                 *
                 * @param bool|WP_Error $stock_validation_result True to pass, or WP_Error.
                 * @param array         $selected                Selected product keys => qty.
                 * @param array         $option_by_key           Option lookup.
                 * @param array         $fields_data             Field configuration.
                 * @param int           $main_product_id         Main product ID.
                 * @param int           $quantity                Cart quantity.
                 * @param string        $label_title             Field label.
                 * @param string        $actual_field_name       Field name key.
                 */
                $stock_validation_result = apply_filters(
                    'epofw_productgroup_stock_validation_result',
                    true,
                    $selected,
                    $option_by_key,
                    $fields_data,
                    $main_product_id,
                    $quantity,
                    $label_title,
                    $actual_field_name
                );
                /**
                 * Filter addon stock validation result (all product-group-aware addons).
                 *
                 * @since 4.4.6
                 *
                 * @param bool|WP_Error $stock_validation_result Prior result.
                 * @param string        $addon_type              Field type (e.g. productgroup).
                 * @param array         $selected                Selected product keys => qty.
                 * @param array         $option_by_key           Option lookup.
                 * @param array         $fields_data             Field configuration.
                 * @param int           $main_product_id         Main product ID.
                 * @param int           $quantity                Cart quantity.
                 * @param string        $label_title             Field label.
                 * @param string        $actual_field_name       Field name key.
                 */
                $stock_validation_result = apply_filters(
                    'epofw_addon_stock_validation_result',
                    $stock_validation_result,
                    'productgroup',
                    $selected,
                    $option_by_key,
                    $fields_data,
                    $main_product_id,
                    $quantity,
                    $label_title,
                    $actual_field_name
                );
                if ( is_wp_error( $stock_validation_result ) ) {
                    return $stock_validation_result;
                }
            }
            // If nothing selected and not required, do not add to cart.
            if ( $selections_basis <= 0 ) {
                return array();
            }
            $lines = array();
            $total_price = 0.0;
            $display_parts = array();
            foreach ( $selected as $key => $qty ) {
                $opt_value = ( isset( $option_by_key[$key] ) ? $option_by_key[$key] : null );
                if ( !$opt_value ) {
                    continue;
                }
                $opt_data = epofw_explore_label_from_opt_group( $opt_value );
                $pid = ( isset( $opt_data['product_id'] ) ? absint( $opt_data['product_id'] ) : 0 );
                $vid = ( isset( $opt_data['variation_id'] ) ? absint( $opt_data['variation_id'] ) : 0 );
                $label = ( isset( $opt_data['opt_label'] ) ? $opt_data['opt_label'] : '' );
                $price_type = ( isset( $opt_data['opt_price_type'] ) ? $opt_data['opt_price_type'] : 'fixed' );
                $price = ( isset( $opt_data['opt_price'] ) ? $opt_data['opt_price'] : 0 );
                $wc_product = ( $vid ? wc_get_product( $vid ) : wc_get_product( $pid ) );
                if ( !$wc_product ) {
                    continue;
                }
                $product_price_val = ( $wc_product->get_price() !== '' ? (float) $wc_product->get_price() : $main_price );
                if ( 'fixed' === $price_type ) {
                    $unit_price = (float) $price;
                } elseif ( 'product_price' === $price_type ) {
                    $unit_price = $product_price_val;
                } else {
                    $unit_price = $product_price_val * ((float) $price / 100);
                }
                $unit_price = (float) epofw_price_filter( $unit_price );
                $line_total = $unit_price * $qty;
                $total_price += $line_total;
                $name = ( $label ? $label : $wc_product->get_name() );
                $lines[] = array(
                    'product_id'   => $pid,
                    'variation_id' => $vid,
                    'key'          => $key,
                    'label'        => $name,
                    'unit_price'   => $unit_price,
                    'qty'          => $qty,
                );
                $display_parts[] = sprintf(
                    '%1$s (%2$s) %3$s',
                    $name,
                    wp_strip_all_tags( wc_price( $unit_price ) ),
                    $qty
                );
            }
            return array(
                'epofw_value'                       => implode( ', ', $display_parts ),
                'epofw_price'                       => $total_price,
                'epofw_original_price'              => $total_price,
                'epofw_price_type'                  => 'fixed',
                'epofw_product_group_lines'         => $lines,
                'epofw_product_group_disable_stock' => $disable_stock,
                'epofw_field_quantity'              => $quantity,
            );
        }

        /**
         * Whether to reduce stock for a product that is part of a Product Group addon.
         * Product group addons are stored in order item meta; WooCommerce does not reduce their stock by default.
         * Use this filter when implementing stock reduction for addon products.
         *
         * @since 4.4.0
         * @param bool  $should_reduce Whether to reduce stock (default true).
         * @param int   $product_id    Product or variation ID.
         * @param array $addon_meta    Order item addon meta (e.g. contains epofw_product_group_disable_stock, epofw_product_group_lines).
         * @return bool
         */
        public static function epofw_productgroup_should_reduce_stock( $should_reduce, $product_id, $addon_meta ) {
            if ( !is_array( $addon_meta ) ) {
                return $should_reduce;
            }
            if ( !empty( $addon_meta['epofw_product_group_disable_stock'] ) ) {
                return false;
            }
            return $should_reduce;
        }

        /**
         * Get per character price.
         *
         * @param string $field_type  Field type.
         * @param array  $egppc_args  Array of data.
         * @param array  $fields_data Array of fields data.
         *
         * @return int|float $check_addon_price
         */
        public function epofw_get_price_per_char( $field_type, $egppc_args, $fields_data ) {
            $post_check_field_name = ( isset( $egppc_args['post_check_field_name'] ) ? $egppc_args['post_check_field_name'] : '' );
            $opt_price_type = ( isset( $egppc_args['opt_price_type'] ) ? $egppc_args['opt_price_type'] : '' );
            $check_addon_price = ( isset( $egppc_args['opt_price'] ) ? $egppc_args['opt_price'] : '' );
            $product_price = ( isset( $egppc_args['product_price'] ) ? $egppc_args['product_price'] : '' );
            $quantity = ( isset( $egppc_args['qty'] ) ? $egppc_args['qty'] : '' );
            $gpcp = 0;
            if ( !empty( $post_check_field_name ) ) {
                $gpcp = $check_addon_price;
            } else {
                $gpcp = 0;
            }
            $egppc_args['opt_price'] = $gpcp;
            $gpcp = epofw_format_price_with_decimals( $gpcp, $egppc_args, $fields_data );
            return $gpcp;
        }

        /**
         * Function to validate option exists.
         *
         * @since    3.0.5
         *
         * @param mixed $post_check_field_name Post check field name.
         * @param array $fields_data           Get fields data.
         *
         * @return bool $is_valid Is valid.
         */
        public function epofw_validate_options_exists( $post_check_field_name, $fields_data ) {
            $check_field_options = ( isset( $fields_data['epofw_field_settings']['options'] ) ? $fields_data['epofw_field_settings']['options'] : array() );
            $check_field_inp_type = ( isset( $fields_data['field']['type'] ) ? $fields_data['field']['type'] : '' );
            $is_valid = true;
            if ( 'select' === $check_field_inp_type || 'radiogroup' === $check_field_inp_type || 'checkbox' === $check_field_inp_type || 'checkboxgroup' === $check_field_inp_type || 'colorswitcher' === $check_field_inp_type || 'textswitcher' === $check_field_inp_type || 'imageswitcher' === $check_field_inp_type || 'multiselect' === $check_field_inp_type ) {
                if ( $check_field_options ) {
                    if ( !empty( $post_check_field_name ) && is_array( $post_check_field_name ) ) {
                        foreach ( $post_check_field_name as $post_check_field_val ) {
                            if ( !$this->epofw_option_label_exists( $post_check_field_val, $check_field_options ) ) {
                                $is_valid = false;
                            }
                        }
                    } elseif ( !$this->epofw_option_label_exists( $post_check_field_name, $check_field_options ) ) {
                        $is_valid = false;
                    }
                }
            }
            return $is_valid;
        }

        /**
         * Check if an option label exists in the options array.
         * Handles both old format (label as key) and new format (label as property).
         *
         * @since 4.4.0
         *
         * @param string $label   The option label to find.
         * @param array  $options The options array.
         *
         * @return bool True if label exists, false otherwise.
         */
        private function epofw_option_label_exists( $label, $options ) {
            return false !== $this->epofw_find_option_by_label( $label, $options );
        }

        /**
         * Find option data by label from the options array.
         * Handles both old format (label as key) and new format (label as property).
         *
         * @since 4.4.0
         *
         * @param string $label   The option label to find.
         * @param array  $options The options array.
         *
         * @return array|false The option data if found, false otherwise.
         */
        private function epofw_find_option_by_label( $label, $options ) {
            if ( empty( $options ) ) {
                return false;
            }
            $label_trimmed = ( is_string( $label ) ? trim( $label ) : (string) $label );
            if ( '' === $label_trimmed ) {
                return false;
            }
            foreach ( $options as $key => $option ) {
                // NEW FORMAT: Array with 'label' key.
                if ( is_array( $option ) && isset( $option['label'] ) ) {
                    $opt_label = ( is_string( $option['label'] ) ? trim( $option['label'] ) : $option['label'] );
                    if ( $opt_label === $label_trimmed ) {
                        return $option;
                    }
                    // Also check switcher value for colorswitcher/imageswitcher/textswitcher.
                    if ( isset( $option['swticher'] ) ) {
                        $sw = ( is_string( $option['swticher'] ) ? trim( $option['swticher'] ) : $option['swticher'] );
                        if ( $sw === $label_trimmed ) {
                            return $option;
                        }
                    }
                } else {
                    // OLD FORMAT: Label is the key, value is the option data.
                    $key_trimmed = ( is_string( $key ) ? trim( $key ) : $key );
                    if ( $key_trimmed === $label_trimmed ) {
                        return $option;
                    }
                }
            }
            return false;
        }

        /**
         * Find price based on field name.
         *
         * @since    1.0.0
         *
         * @param int   $product_id            Product Id.
         * @param mixed $post_check_field_name Post check field name.
         * @param int   $quantity              Product quantity.
         * @param array $fields_data           Get fields data.
         *
         * @return array $chk_price_and_type Addon price and type.
         */
        public function epofw_find_price_based_on_name(
            $product_id,
            $post_check_field_name,
            $quantity,
            $fields_data
        ) {
            $product_data = wc_get_product( $product_id );
            $product_price = epofw_display_product_price( $product_data, 'shop' );
            $check_epofw_field_settings = ( isset( $fields_data['epofw_field_settings'] ) ? $fields_data['epofw_field_settings'] : array() );
            $check_field_options = ( isset( $fields_data['epofw_field_settings']['options'] ) ? $fields_data['epofw_field_settings']['options'] : array() );
            $check_field_inp_type = ( isset( $fields_data['field']['type'] ) ? $fields_data['field']['type'] : '' );
            $check_apodp_lop = ( 'datepicker' === $check_field_inp_type && isset( $fields_data['apodp_logical_operation'] ) ? $fields_data['apodp_logical_operation'] : array() );
            $egppc_args = array(
                'product_data'          => $product_data,
                'post_check_field_name' => $post_check_field_name,
                'product_price'         => $product_price,
                'qty'                   => $quantity,
            );
            $chk_price_and_type = array();
            if ( $check_field_options ) {
                $total_addon_price = 0;
                $total_addon_original_price = 0;
                if ( !empty( $post_check_field_name ) && is_array( $post_check_field_name ) ) {
                    foreach ( $post_check_field_name as $post_check_field_val ) {
                        // Use helper method to find option by label (supports both old and new format).
                        $check_field_options_val_arr = $this->epofw_find_option_by_label( $post_check_field_val, $check_field_options );
                        if ( $check_field_options_val_arr ) {
                            $opt_grp_data = epofw_explore_label_from_opt_group( $check_field_options_val_arr );
                            $opt_price_type = $opt_grp_data['opt_price_type'];
                            $get_opt_price = $opt_grp_data['opt_price'];
                            $egppc_args['opt_price'] = $get_opt_price;
                            $egppc_args['opt_price_type'] = $opt_price_type;
                            $opt_price = $this->epofw_get_price_per_char( $check_field_inp_type, $egppc_args, $fields_data );
                            $total_addon_price += (float) $opt_price;
                            $chk_price_and_type['addon_price'] = $total_addon_price;
                            // Change $get_opt_price to $opt_price because its creating issue once we add price with custom formula - {ppv}*4.
                            $total_addon_original_price += (float) $opt_price;
                            $chk_price_and_type['addon_original_price'] = $total_addon_original_price;
                            $chk_price_and_type['addon_price_type'] = $opt_price_type;
                        }
                    }
                } else {
                    // Use helper method to find option by label (supports both old and new format).
                    $check_field_options_val = $this->epofw_find_option_by_label( $post_check_field_name, $check_field_options );
                    if ( $check_field_options_val ) {
                        $opt_grp_data = epofw_explore_label_from_opt_group( $check_field_options_val );
                        $opt_price_type = $opt_grp_data['opt_price_type'];
                        $get_opt_price = $opt_grp_data['opt_price'];
                        $egppc_args['opt_price'] = $get_opt_price;
                        $egppc_args['opt_price_type'] = $opt_price_type;
                        $opt_price = $this->epofw_get_price_per_char( $check_field_inp_type, $egppc_args, $fields_data );
                        $total_addon_price += (float) $opt_price;
                        $chk_price_and_type['addon_price'] = $total_addon_price;
                        // Change $get_opt_price to $opt_price because its creating issue once we add price with custom formula - {ppv}*4.
                        $total_addon_original_price += (float) $opt_price;
                        $chk_price_and_type['addon_original_price'] = $total_addon_original_price;
                        $chk_price_and_type['addon_price_type'] = $opt_price_type;
                    }
                }
            } else {
                $check_field_enable_price = epofw_check_array_key_exists( 'enable_price_extra', $check_epofw_field_settings );
                $total_addon_inp_price = 0;
                $total_addon_original_price = 0;
                if ( $check_field_enable_price ) {
                    $date_addon_extra_price = 0;
                    if ( !empty( $check_apodp_lop ) && is_array( $check_apodp_lop ) ) {
                        $post_check_field_name_date = strtotime( $post_check_field_name );
                        foreach ( $check_apodp_lop as $check_apodp_lop_data ) {
                            $epofw_cnd_start_date = strtotime( $check_apodp_lop_data['epofw_cnd_start_date'] );
                            $epofw_cnd_end_date = strtotime( $check_apodp_lop_data['epofw_cnd_end_date'] );
                            if ( $post_check_field_name_date >= $epofw_cnd_start_date && $post_check_field_name_date <= $epofw_cnd_end_date ) {
                                $date_addon_extra_price = $check_apodp_lop_data['epofw_cnd_price'];
                            }
                        }
                    }
                    $check_field_addon_price_type = epofw_check_array_key_exists( 'addon_price_type', $check_epofw_field_settings );
                    $check_field_addon_price = epofw_check_array_key_exists( 'addon_price', $check_epofw_field_settings );
                    $opt_price_type = $check_field_addon_price_type;
                    $get_opt_price = $check_field_addon_price;
                    $egppc_args['opt_price'] = $get_opt_price;
                    $egppc_args['opt_price_type'] = $opt_price_type;
                    $opt_price = $this->epofw_get_price_per_char( $check_field_inp_type, $egppc_args, $fields_data );
                    $total_addon_inp_price += (float) $opt_price;
                    // Change $get_opt_price to $opt_price because its creating issue once we add price with custom formula - {ppv}*4.
                    $total_addon_original_price += (float) $opt_price;
                    $chk_price_and_type['addon_original_price'] = $total_addon_original_price;
                    $total_addon_inp_price += (float) $date_addon_extra_price;
                    $chk_price_and_type['addon_price'] = $total_addon_inp_price;
                    $chk_price_and_type['addon_price_type'] = $opt_price_type;
                }
            }
            return $chk_price_and_type;
        }

        /**
         * Get cart item data.
         *
         * @since    1.0.0
         *
         * @param array $cart_data Array of cart data.
         * @param array $cart_item Array of cart item.
         *
         * @return  array $meta_items Array of cart item.
         */
        public function epofw_get_item_data( $cart_data, $cart_item = null ) {
            $meta_items = array();
            if ( !empty( $cart_data ) ) {
                $meta_items = $cart_data;
            }
            if ( isset( $cart_item ) ) {
                $check_epofw_data = epofw_check_array_key_exists( 'epofw_data', $cart_item );
                if ( !empty( $check_epofw_data ) && is_array( $check_epofw_data ) ) {
                    $custom_meta_data = array();
                    foreach ( $check_epofw_data as $key => $epofw_data_value ) {
                        $get_epofw_value = epofw_check_array_key_exists( 'epofw_value', $epofw_data_value );
                        $check_epofw_haoic = '';
                        $check_epofw_haopic = '';
                        $check_epofw_vat = '';
                        if ( !$check_epofw_haoic || !$check_epofw_haopic ) {
                            // Remove option field name and value from cart if that option have enable wc tax option. ( Enable WooCommerce Options - Enable Addon Tax ).
                            if ( !empty( $get_epofw_value ) && isset( $epofw_data_value['epofw_form_data'] ) && !$check_epofw_vat ) {
                                $epofw_label = epofw_check_array_key_exists( 'epofw_label', $epofw_data_value );
                                $epofw_type = epofw_check_array_key_exists( 'epofw_type', $epofw_data_value );
                                // Check if we're in a Store API context (cart block) for proper formatting.
                                $is_store_api = false;
                                // Always set 'key' and 'name' as strings to prevent undefined array key warnings.
                                $custom_meta_data[$key]['key'] = ( !empty( $epofw_label ) ? $epofw_label : '' );
                                $name_value = $this->epofw_display_name_in_cart(
                                    $epofw_data_value,
                                    $check_epofw_haoic,
                                    $check_epofw_haopic,
                                    $is_store_api
                                );
                                // Ensure 'name' is never null (fallback to 'key' if empty).
                                $custom_meta_data[$key]['name'] = ( !empty( $name_value ) ? $name_value : $custom_meta_data[$key]['key'] );
                                // For free version, use HTML rendering.
                                // Always set both 'value' and 'display' for WooCommerce template compatibility.
                                $display_value = $this->epofw_display_value_in_cart( $epofw_data_value );
                                $custom_meta_data[$key]['value'] = $display_value;
                                $custom_meta_data[$key]['display'] = $display_value;
                            }
                        }
                    }
                    if ( !empty( $custom_meta_data ) ) {
                        foreach ( $custom_meta_data as $data ) {
                            $meta_items[] = $data;
                        }
                    }
                }
            }
            /**
             * Filter cart item display data before returning.
             *
             * Allows modules to modify how addon data is displayed in the cart.
             *
             * @since 4.4.5
             *
             * @param array $meta_items Array of meta items to display.
             * @param array $cart_item  Original cart item data.
             */
            $meta_items = apply_filters( 'epofw_cart_item_display_data', $meta_items, $cart_item );
            return $meta_items;
        }

        /**
         * Display field name in cart section.
         *
         * @since 1.0.0
         *
         * @param array $epofw_data_value   Array of field data.
         * @param bool  $check_epofw_haoic  Hide addon order in cart page.
         * @param bool  $check_epofw_haopic Hide addon order price in cart page.
         * @param bool  $is_store_api       Whether this is a Store API request (cart block). Default false.
         *
         * @return string $epofw_name Return string.
         */
        public function epofw_display_name_in_cart(
            $epofw_data_value,
            $check_epofw_haoic,
            $check_epofw_haopic,
            $is_store_api = false
        ) {
            $epofw_price = epofw_check_array_key_exists( 'epofw_price', $epofw_data_value );
            $epofw_label = epofw_check_array_key_exists( 'epofw_label', $epofw_data_value );
            // Sanitize and escape the label.
            $epofw_label = esc_html( wp_strip_all_tags( $epofw_label ) );
            // Initialize the name variable.
            $epofw_name = '';
            // Get field quantity for premium users.
            $epofw_field_quantity = 0;
            if ( $epofw_price && 0 !== $epofw_price ) {
                if ( !$check_epofw_haoic ) {
                    $epofw_name = $epofw_label;
                }
                if ( !$check_epofw_haopic ) {
                    // Use plain text for Store API (cart block), HTML for shortcode.
                    if ( $is_store_api ) {
                        $formatted_price = wp_strip_all_tags( wc_price( epofw_price_filter( $epofw_price ) ) );
                    } else {
                        $formatted_price = wc_price( epofw_price_filter( $epofw_price ) );
                    }
                    if ( $epofw_field_quantity > 1 ) {
                        $label_name_with_price = '(' . $formatted_price . ' x ' . $epofw_field_quantity . ')';
                    } else {
                        $label_name_with_price = '(' . $formatted_price . ')';
                    }
                    // Add space before price if label exists.
                    if ( !empty( $epofw_name ) ) {
                        $epofw_name .= ' ' . $label_name_with_price;
                    } else {
                        $epofw_name = $label_name_with_price;
                    }
                } elseif ( $epofw_field_quantity > 1 ) {
                    // Price is hidden but show quantity.
                    if ( !empty( $epofw_name ) ) {
                        $epofw_name .= ' (x ' . $epofw_field_quantity . ')';
                    } else {
                        $epofw_name = '(x ' . $epofw_field_quantity . ')';
                    }
                }
            } else {
                $epofw_name = $epofw_label;
                // Show quantity even when there's no price.
                if ( $epofw_field_quantity > 1 ) {
                    $epofw_name .= ' (x ' . $epofw_field_quantity . ')';
                }
            }
            /**
             * Apply filter for display name in cart.
             *
             * @since 1.0.0
             */
            return apply_filters(
                'epofw_display_name_in_cart',
                $epofw_name,
                $epofw_data_value,
                $check_epofw_haoic,
                $check_epofw_haopic
            );
        }

        /**
         * Display field value in cart section.
         *
         * @since 1.0.0
         *
         * @param array $epofw_data_value Array of field data.
         *
         * @return mixed $epofw_value Return html or string.
         */
        public function epofw_display_value_in_cart( $epofw_data_value ) {
            $epofw_type = epofw_check_array_key_exists( 'epofw_type', $epofw_data_value );
            $get_epofw_value = epofw_check_array_key_exists( 'epofw_value', $epofw_data_value );
            $epofw_value = esc_html( wp_strip_all_tags( $get_epofw_value ) );
            // Mask password field values for security.
            if ( 'password' === $epofw_type ) {
                $epofw_value = str_repeat( '●', strlen( $get_epofw_value ) );
            } elseif ( 'colorpicker' === $epofw_type ) {
                $epofw_value = $this->epofw_render_color( $get_epofw_value );
            }
            /**
             * Apply filter for display value in cart.
             *
             * @since 1.0.0
             */
            return apply_filters(
                'epofw_display_value_in_cart',
                $epofw_value,
                $epofw_type,
                $epofw_data_value
            );
        }

        /**
         * Display field value in cart block (Store API).
         * This includes price and quantity since cart blocks don't use the 'name' field.
         *
         * @since 4.4.0
         *
         * @param array $epofw_data_value   Array of field data.
         * @param bool  $check_epofw_haopic Hide addon order price in cart page.
         *
         * @return string $display_value Return plain text value with price and qty.
         */
        public function epofw_display_value_in_cart_block( $epofw_data_value, $check_epofw_haopic = false ) {
            $epofw_type = epofw_check_array_key_exists( 'epofw_type', $epofw_data_value );
            $get_epofw_value = epofw_check_array_key_exists( 'epofw_value', $epofw_data_value );
            $epofw_price = epofw_check_array_key_exists( 'epofw_price', $epofw_data_value );
            // Sanitize and escape the value.
            $display_value = esc_html( wp_strip_all_tags( $get_epofw_value ) );
            // Mask password field values for security.
            if ( 'password' === $epofw_type ) {
                $display_value = str_repeat( '●', strlen( $get_epofw_value ) );
            } elseif ( 'colorpicker' === $epofw_type || 'colorswitcher' === $epofw_type ) {
                // For color fields, just show the color code as plain text.
                $display_value = esc_html( $get_epofw_value );
            } elseif ( 'file' === $epofw_type ) {
                // For file uploads, show filename(s) as plain text.
                if ( !empty( $get_epofw_value ) ) {
                    /**
                     * Filter the raw file field value before parsing for cart block display.
                     *
                     * @since 4.4.6
                     *
                     * @param string $get_epofw_value File field value (JSON or comma-separated URLs).
                     */
                    $get_epofw_value = apply_filters( 'epofw_file_value_before_parse', $get_epofw_value );
                    $decoded_json = json_decode( $get_epofw_value, true );
                    if ( is_array( $decoded_json ) ) {
                        $file_urls = $decoded_json;
                    } elseif ( strpos( $get_epofw_value, ',' ) !== false ) {
                        $file_urls = explode( ',', $get_epofw_value );
                    } else {
                        $file_urls = array($get_epofw_value);
                    }
                    $filenames = array();
                    foreach ( $file_urls as $file_url ) {
                        $file_url = trim( $file_url );
                        if ( !empty( $file_url ) ) {
                            $filenames[] = basename( $file_url );
                        }
                    }
                    $display_value = implode( ', ', $filenames );
                }
            } elseif ( 'imageswitcher' === $epofw_type ) {
                // For image switcher, show the label from epofw_label if available.
                $epofw_label = epofw_check_array_key_exists( 'epofw_label', $epofw_data_value );
                if ( !empty( $epofw_label ) ) {
                    $display_value = esc_html( wp_strip_all_tags( $epofw_label ) );
                } elseif ( !empty( $get_epofw_value ) ) {
                    // Fallback to filename if no label.
                    $display_value = basename( $get_epofw_value );
                }
            } elseif ( 'productgroup' === $epofw_type ) {
            }
            // Get field quantity for premium users.
            $epofw_field_quantity = 0;
            // Build the price and quantity suffix.
            $price_qty_suffix = '';
            if ( $epofw_price && 0 !== $epofw_price && !$check_epofw_haopic ) {
                // Format price as plain text (no HTML).
                $formatted_price = wp_strip_all_tags( wc_price( epofw_price_filter( $epofw_price ) ) );
                if ( $epofw_field_quantity > 1 ) {
                    $price_qty_suffix = ' (' . $formatted_price . ' x ' . $epofw_field_quantity . ')';
                } else {
                    $price_qty_suffix = ' (' . $formatted_price . ')';
                }
            } elseif ( $epofw_field_quantity > 1 ) {
                // Price is hidden but show quantity.
                $price_qty_suffix = ' (x ' . $epofw_field_quantity . ')';
            }
            $display_value .= $price_qty_suffix;
            /**
             * Apply filter for display value in cart block.
             *
             * @since 4.4.0
             */
            return apply_filters(
                'epofw_display_value_in_cart_block',
                $display_value,
                $epofw_type,
                $epofw_data_value,
                $check_epofw_haopic
            );
        }

        /**
         * Function will render color html.
         *
         * @since 2.4.4
         *
         * @param string $get_epofw_value Value of car data.
         *
         * @return string $epofw_value Return html for color data.
         */
        public function epofw_render_color( $get_epofw_value ) {
            $epofw_value = '';
            if ( !empty( $get_epofw_value ) ) {
                // Sanitize and escape the color value.
                $safe_color = sanitize_hex_color( $get_epofw_value );
                if ( !$safe_color ) {
                    $safe_color = sanitize_text_field( $get_epofw_value );
                }
                $epofw_value .= sprintf( '<span style="color:%1$s;font-size: 20px;padding: 0;line-height: 0">■</span>&nbsp;', esc_attr( $safe_color ) );
                $epofw_value .= '</span>';
                // Escape the value before adding to filter.
                /**
                 * Apply filter for render color code.
                 *
                 * @since 1.0.0
                 */
                $epofw_value .= apply_filters( 'epofw_render_color_code', esc_html( $get_epofw_value ) );
            }
            /**
             * Apply filter for render color html.
             *
             * @since 1.0.0
             */
            return apply_filters( 'epofw_render_color_html', $epofw_value, esc_html( $get_epofw_value ) );
        }

        /**
         * Add field in order meta.
         *
         * @since    1.0.0
         *
         * @param mixed  $item          Item of cart.
         * @param string $cart_item_key key of cart item.
         * @param array  $values        Array of cart values.
         *
         * @throws Exception Get error.
         */
        public function checkout_create_order_line_item( $item, $cart_item_key, $values ) {
            $check_epofw_data = epofw_check_array_key_exists( 'epofw_data', $values );
            if ( !empty( $check_epofw_data ) && is_array( $check_epofw_data ) ) {
                foreach ( $check_epofw_data as $epofw_data_value ) {
                    $epofw_data_name = epofw_check_array_key_exists( 'epofw_name', $epofw_data_value );
                    $epofw_value = epofw_check_array_key_exists( 'epofw_value', $epofw_data_value );
                    if ( !empty( $epofw_value ) ) {
                        $new_check_epofw_data = array();
                        $new_check_epofw_data[$epofw_data_name] = $epofw_data_value;
                        $item->add_meta_data( $epofw_data_name, wp_json_encode( $new_check_epofw_data ) );
                    }
                }
            }
        }

        /**
         * Display meta value in cart section.
         *
         * @since 1.0.0
         *
         * @param string $display_value Display value in cart.
         * @param object $meta          Object of cart data.
         * @param array  $item          Array of cart item data.
         *
         * @throws Exception Get error.
         * @return string Return moxed data.
         */
        public function epofw_display_meta_value( $display_value, $meta = null, $item = null ) {
            $out_display_value = '';
            if ( '' !== $item && '' !== $meta ) {
                $product_id = $item['product_id'];
                $get_field_id_arr = $this->epofw_find_match_id( $product_id );
                $fields_data_arr = $this->epofw_get_match_data_from_db( $get_field_id_arr );
                $get_field_name_arr = $this->epofw_get_field_name_from_data( $fields_data_arr, array() );
                if ( is_array( json_decode( $meta->value, true ) ) ) {
                    $meta_value_data = json_decode( $meta->value, true );
                    if ( !empty( $meta_value_data ) ) {
                        foreach ( $meta_value_data as $meta_value_details ) {
                            $out_display_value = ( isset( $meta_value_details['epofw_value'] ) ? $meta_value_details['epofw_value'] : '' );
                            // Mask password field values for security.
                            if ( isset( $meta_value_details['epofw_type'] ) && 'password' === $meta_value_details['epofw_type'] ) {
                                $out_display_value = str_repeat( '●', strlen( $out_display_value ) );
                            } elseif ( isset( $meta_value_details['epofw_type'] ) && 'colorpicker' === $meta_value_details['epofw_type'] ) {
                                if ( isset( $meta_value_details['epofw_value'] ) && !empty( $meta_value_details['epofw_value'] ) ) {
                                    $out_display_value .= $this->epofw_render_color( $meta_value_details['epofw_value'] );
                                }
                            }
                        }
                    }
                } else {
                    $get_type = ( isset( $get_field_name_arr[$meta->key]['field'] ) && isset( $get_field_name_arr[$meta->key]['field']['type'] ) ? $get_field_name_arr[$meta->key]['field']['type'] : '' );
                    $out_display_value = $display_value;
                    // Mask password field values for security.
                    if ( isset( $get_type ) && 'password' === $get_type ) {
                        $out_display_value = str_repeat( '●', strlen( $meta->value ) );
                    } elseif ( isset( $get_type ) && 'colorpicker' === $get_type ) {
                        if ( isset( $meta->value ) && !empty( $meta->value ) ) {
                            $out_display_value .= $this->epofw_render_color( $meta->value );
                        }
                    }
                }
                /**
                 * Apply filter for order item display meta value.
                 *
                 * @since 1.0.0
                 */
                return apply_filters(
                    'epofw_order_item_display_meta_value',
                    $out_display_value,
                    $display_value,
                    $item
                );
            }
        }

        /**
         * Add field in order meta.
         *
         * @since    1.0.0
         *
         * @param array  $cart_item_data Array of cart items.
         * @param object $item           Object of item.
         *
         * @throws Exception Get error.
         * @return array $cart_item_data
         */
        public function epofw_order_again_cart_item_data_pro( $cart_item_data, $item ) {
            if ( $item ) {
                $product_id = $item->get_product_id();
                $get_field_id_arr = $this->epofw_find_match_id( $product_id );
                $fields_data_arr = $this->epofw_get_match_data_from_db( $get_field_id_arr );
                $get_field_name_arr = $this->epofw_get_field_name_from_data( $fields_data_arr, array() );
                $meta_data = $item->get_meta_data();
                $new_item_data_arr = array();
                if ( !empty( $meta_data ) ) {
                    foreach ( $meta_data as $meta_data_value ) {
                        if ( is_array( json_decode( $meta_data_value->value, true ) ) ) {
                            $meta_value_data = json_decode( $meta_data_value->value, true );
                            if ( array_key_exists( $meta_data_value->key, $meta_value_data ) ) {
                                $addon_value = $meta_value_data[$meta_data_value->key]['epofw_value'];
                                $data_arr = array();
                                $data_arr['epofw_name'] = $meta_data_value->key;
                                $data_arr['epofw_label'] = $meta_value_data[$meta_data_value->key]['epofw_form_data']['label']['title'];
                                $data_arr['epofw_value'] = $addon_value;
                                // Here was $meta_data_value->value;.
                                $data_arr['epofw_price'] = $meta_value_data[$meta_data_value->key]['epofw_price'];
                                $data_arr['epofw_original_price'] = epofw_price_filter( $meta_value_data[$meta_data_value->key]['epofw_original_price'] );
                                $data_arr['epofw_price_type'] = 'fixed';
                                $data_arr['epofw_type'] = $meta_value_data[$meta_data_value->key]['epofw_form_data']['field']['type'];
                                if ( isset( $meta_value_data[$meta_data_value->key]['epofw_form_data']['common'] ) ) {
                                    $data_arr['epofw_form_data']['common'] = $meta_value_data[$meta_data_value->key]['epofw_form_data']['common'];
                                }
                                if ( isset( $meta_value_data[$meta_data_value->key]['epofw_form_data']['epofw_field_settings'] ) ) {
                                    $data_arr['epofw_form_data']['epofw_field_settings'] = $meta_value_data[$meta_data_value->key]['epofw_form_data']['epofw_field_settings'];
                                }
                                if ( isset( $meta_value_data[$meta_data_value->key]['epofw_product_group_lines'] ) ) {
                                    $data_arr['epofw_product_group_lines'] = $meta_value_data[$meta_data_value->key]['epofw_product_group_lines'];
                                }
                                $new_item_data_arr[$meta_data_value->key] = $data_arr;
                            }
                        } elseif ( array_key_exists( $meta_data_value->key, $get_field_name_arr ) ) {
                            $get_display_key = $this->epofw_get_addon_title_pro(
                                $product_id,
                                $get_field_name_arr,
                                $meta_data_value->key,
                                $meta_data_value->value,
                                $item['quantity']
                            );
                            $data_arr = array();
                            $data_arr['epofw_name'] = $meta_data_value->key;
                            $data_arr['epofw_label'] = $get_field_name_arr[$meta_data_value->key]['label']['title'];
                            $data_arr['epofw_value'] = $meta_data_value->value;
                            $data_arr['epofw_price'] = epofw_price_filter( $get_display_key['epofw_price'] );
                            $data_arr['epofw_original_price'] = epofw_price_filter( $get_display_key['epofw_price'] );
                            $data_arr['epofw_price_type'] = 'fixed';
                            $data_arr['epofw_type'] = $get_field_name_arr[$meta_data_value->key]['field']['type'];
                            $data_arr['epofw_form_data']['common'] = $get_field_name_arr[$meta_data_value->key]['common'];
                            $data_arr['epofw_form_data']['epofw_field_settings'] = $get_field_name_arr[$meta_data_value->key]['epofw_field_settings'];
                            $new_item_data_arr[$meta_data_value->key] = $data_arr;
                        }
                    }
                }
                $cart_item_data['epofw_data'] = $new_item_data_arr;
            }
            return $cart_item_data;
        }

        /**
         * Get field name from data.
         *
         * @since 1.0.0
         * @since 4.4.0 Updated to support new custom tables structure with sections/rows/columns.
         *
         * @param array $fields_data_arr Array of fields data which fetched from DB.
         * @param array $post_data       Array of post data.
         *
         * @throws Exception Get error.
         * @return array $new_field_array
         */
        public function epofw_get_field_name_from_data( $fields_data_arr, $post_data = array() ) {
            $new_field_array = array();
            if ( empty( $fields_data_arr ) ) {
                return $new_field_array;
            }
            // Extract all fields from sections → row_groups → columns_groups → rows → fields structure.
            foreach ( $fields_data_arr as $addon_data ) {
                $addon_post_id = ( isset( $addon_data['epofw_addon_post_id'] ) ? absint( $addon_data['epofw_addon_post_id'] ) : 0 );
                $sections = epofw_check_array_key_exists( 'general', $addon_data );
                if ( empty( $sections ) ) {
                    continue;
                }
                foreach ( $sections as $section_data ) {
                    $row_groups = epofw_check_array_key_exists( 'row_groups', $section_data );
                    if ( empty( $row_groups ) ) {
                        continue;
                    }
                    foreach ( $row_groups as $group_data ) {
                        $columns_groups = epofw_check_array_key_exists( 'columns_groups', $group_data );
                        if ( empty( $columns_groups ) ) {
                            continue;
                        }
                        foreach ( $columns_groups as $columns_data ) {
                            $rows = epofw_check_array_key_exists( 'rows', $columns_data );
                            if ( empty( $rows ) ) {
                                continue;
                            }
                            foreach ( $rows as $row_data ) {
                                $fields = epofw_check_array_key_exists( 'fields', $row_data );
                                if ( empty( $fields ) ) {
                                    continue;
                                }
                                foreach ( $fields as $field_id => $field_data ) {
                                    $check_field = epofw_check_array_key_exists( 'field', $field_data );
                                    if ( !$check_field ) {
                                        continue;
                                    }
                                    $check_field_name = epofw_check_array_key_exists( 'name', $check_field );
                                    $check_field_type = epofw_check_array_key_exists( 'type', $check_field );
                                    if ( empty( $check_field_name ) ) {
                                        $check_field_name = $field_id;
                                    }
                                    if ( strpos( $check_field_name, 'epofw_field_' ) === false ) {
                                        $check_field_name = 'epofw_field_' . $check_field_name;
                                    }
                                    $field_data['epofw_field_id'] = $field_id;
                                    if ( $addon_post_id ) {
                                        $field_data['epofw_addon_post_id'] = $addon_post_id;
                                    }
                                    if ( !empty( $post_data ) ) {
                                        $new_field_array[$check_field_name] = $field_data;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // Filter by post_data if provided (only return fields with non-empty values).
            $new1 = array();
            if ( $new_field_array ) {
                if ( !empty( $post_data ) ) {
                    // First pass: identify fields that are part of a repeater.
                    // Track which instance numbers exist for each base field.
                    $field_instance_counts = array();
                    // base_field => array of instance numbers with values.
                    $fields_with_instance_0 = array();
                    // base fields that have _instance_0 with value.
                    // Collect all instance fields and their numbers.
                    foreach ( $post_data as $post_data_key => $post_data_value ) {
                        if ( preg_match( '/_instance_(\\d+)$/', $post_data_key, $matches ) ) {
                            $instance_num = (int) $matches[1];
                            $has_value = isset( $post_data_value['value'] ) && !empty( $post_data_value['value'] );
                            if ( $has_value ) {
                                $base_field_key = preg_replace( '/_instance_\\d+$/', '', $post_data_key );
                                if ( !isset( $field_instance_counts[$base_field_key] ) ) {
                                    $field_instance_counts[$base_field_key] = array();
                                }
                                $field_instance_counts[$base_field_key][] = $instance_num;
                                if ( 0 === $instance_num ) {
                                    $fields_with_instance_0[$base_field_key] = true;
                                }
                            }
                        }
                    }
                    // Determine which base fields should show instance labels.
                    // Two scenarios for repeatable sections:
                    //
                    // Scenario 1: Section has fields BOTH outside and inside repeater (same field names)
                    // - _instance_0 exists
                    // - Base field = OUTSIDE repeater (no instance label)
                    // - _instance_0 = Instance 1 INSIDE repeater.
                    // - _instance_1 = Instance 2 INSIDE repeater.
                    //
                    // Scenario 2: Section has fields ONLY inside repeater (no outside fields).
                    // - NO _instance_0, only _instance_N where N > 0.
                    // - Base field = Instance 1 INSIDE repeater.
                    // - _instance_N = Instance N+1 INSIDE repeater.
                    $repeater_base_fields = array();
                    $base_field_is_instance_1 = array();
                    // Base fields that should show "Instance 1".
                    foreach ( $field_instance_counts as $base_field_key => $instances ) {
                        // Check if base field has value too.
                        $base_has_value = isset( $post_data[$base_field_key]['value'] ) && !empty( $post_data[$base_field_key]['value'] );
                        if ( isset( $fields_with_instance_0[$base_field_key] ) ) {
                            // Scenario 1: _instance_0 exists.
                            // Base field is OUTSIDE repeater (no instance label).
                            // _instance_0 = Instance 1, _instance_1 = Instance 2, etc.
                            $repeater_base_fields[$base_field_key] = true;
                        } elseif ( count( $instances ) >= 1 && $base_has_value ) {
                            // Scenario 2: No _instance_0, but has _instance_N (N > 0) and base field has value.
                            // Base field IS Instance 1, _instance_N = Instance N+1.
                            $repeater_base_fields[$base_field_key] = true;
                            $base_field_is_instance_1[$base_field_key] = true;
                        }
                        // If no base value and only instance fields, process instances without labels.
                    }
                    /**
                     * Filter the instance label text displayed in cart/order.
                     *
                     * @since 4.4.5
                     *
                     * @param string $instance_label The instance label text. Default 'Instance'.
                     */
                    $instance_label = apply_filters( 'epofw_repeater_instance_label', __( 'Instance', 'extra-product-options-for-woocommerce' ) );
                    foreach ( $post_data as $post_data_key => $post_data_value ) {
                        // Check for direct match first (base field without _instance_ suffix).
                        if ( isset( $new_field_array[$post_data_key] ) ) {
                            $field_data = $new_field_array[$post_data_key];
                            $field_type = ( isset( $field_data['field']['type'] ) ? $field_data['field']['type'] : '' );
                            $has_value = isset( $post_data_value['value'] ) && !empty( $post_data_value['value'] );
                            $has_products = 'productgroup' === $field_type && isset( $post_data_value['products'] ) && is_array( $post_data_value['products'] );
                            if ( $has_products ) {
                                $products_qty = array_filter( array_map( 'absint', $post_data_value['products'] ) );
                                $has_products = !empty( array_filter( $products_qty ) );
                            }
                            if ( $has_value || $has_products ) {
                                // Check if base field should show "(Instance 1)" label.
                                // This happens in Scenario 2: no _instance_0, but has _instance_N (N > 0).
                                if ( isset( $base_field_is_instance_1[$post_data_key] ) ) {
                                    if ( isset( $field_data['label']['title'] ) ) {
                                        $field_data['label']['title'] .= ' (' . $instance_label . ' 1)';
                                    }
                                }
                                // Otherwise, base field has no instance label (Scenario 1: outside repeater).
                                $new1[$post_data_key] = $field_data;
                            }
                        } elseif ( preg_match( '/_instance_(\\d+)$/', $post_data_key, $matches ) ) {
                            // Handle repeater instance fields - map back to base field.
                            $base_field_key = preg_replace( '/_instance_\\d+$/', '', $post_data_key );
                            $instance_num = (int) $matches[1];
                            if ( isset( $new_field_array[$base_field_key] ) ) {
                                $has_value = isset( $post_data_value['value'] ) && !empty( $post_data_value['value'] );
                                if ( $has_value ) {
                                    // Clone the field data and add instance info.
                                    $instance_field_data = $new_field_array[$base_field_key];
                                    // Calculate display instance number based on scenario.
                                    if ( isset( $base_field_is_instance_1[$base_field_key] ) ) {
                                        // Scenario 2: Base field is Instance 1, so _instance_N = Instance N+1.
                                        $display_instance = $instance_num + 1;
                                    } else {
                                        // Scenario 1: _instance_0 = Instance 1, _instance_1 = Instance 2, etc.
                                        $display_instance = $instance_num + 1;
                                    }
                                    // Only add instance label if this field has multiple instances.
                                    if ( isset( $repeater_base_fields[$base_field_key] ) ) {
                                        if ( isset( $instance_field_data['label']['title'] ) ) {
                                            $instance_field_data['label']['title'] .= ' (' . $instance_label . ' ' . $display_instance . ')';
                                        }
                                    }
                                    $new1[$post_data_key] = $instance_field_data;
                                }
                            }
                        }
                    }
                }
            }
            return $new1;
        }

        /**
         * Formatted meta data.
         *
         * @since 1.0.0
         *
         * @param array $formatted_meta Array of meta data.
         * @param array $order_item     Array of Order data.
         *
         * @throws Exception Get error.
         * @return array $formatted_meta
         */
        public function epofw_item_get_formatted_meta_data_pro( $formatted_meta, $order_item ) {
            if ( $order_item ) {
                $product_id = $order_item['product_id'];
                $quantity = $order_item['quantity'];
                $get_field_id_arr = $this->epofw_find_match_id( $product_id );
                $fields_data_arr = $this->epofw_get_match_data_from_db( $get_field_id_arr );
                $get_field_name_arr = $this->epofw_get_field_name_from_data( $fields_data_arr, array() );
                foreach ( $formatted_meta as $key => $meta ) {
                    if ( is_array( json_decode( $meta->value, true ) ) ) {
                        $meta_value_data = json_decode( $meta->value, true );
                        if ( isset( $meta_value_data[$meta->key] ) ) {
                            $addon_value = $meta_value_data[$meta->key]['epofw_value'];
                            $get_display_key = $this->epofw_get_addon_title_pro(
                                $product_id,
                                $meta_value_data,
                                $meta->key,
                                $addon_value,
                                $quantity
                            );
                            if ( !empty( $get_display_key['epofw_label'] ) ) {
                                // Display all addon option value on order page at admin side if that option have enable or not wc tax option.
                                if ( is_admin() ) {
                                    $formatted_meta[$key] = (object) array(
                                        'key'           => $meta->key,
                                        'value'         => $meta->value,
                                        'display_key'   => $get_display_key['epofw_label'],
                                        'display_value' => $meta->display_value,
                                    );
                                }
                                // Unset addon option value on order page at front side if that option have enable wc tax option.
                                if ( !is_admin() ) {
                                    $formatted_meta[$key] = (object) array(
                                        'key'           => $meta->key,
                                        'value'         => $meta->value,
                                        'display_key'   => $get_display_key['epofw_label'],
                                        'display_value' => $meta->display_value,
                                    );
                                }
                            } else {
                                unset($formatted_meta[$key]);
                            }
                        } else {
                            unset($formatted_meta[$key]);
                        }
                    } elseif ( array_key_exists( $meta->key, $get_field_name_arr ) ) {
                        $get_display_key = $this->epofw_get_addon_title_pro(
                            $product_id,
                            $get_field_name_arr,
                            $meta->key,
                            $meta->value,
                            $quantity
                        );
                        if ( !empty( $get_display_key['epofw_label'] ) ) {
                            $formatted_meta[$key] = (object) array(
                                'key'           => $meta->key,
                                'value'         => $meta->value,
                                'display_key'   => $get_display_key['epofw_label'],
                                'display_value' => $meta->display_value,
                            );
                        } else {
                            unset($formatted_meta[$key]);
                        }
                    }
                }
            }
            return $formatted_meta;
        }

        /**
         * Get Addon label name and price for order detail on the front side.
         * Retrieves the addon label name and price based on the provided parameters.
         *
         * @since 1.0.0
         *
         * @param int    $product_id         The ID of the product.
         * @param array  $get_field_name_arr Array of meta data.
         * @param string $meta_key           Meta key.
         * @param string $meta_value         Meta value representing the price.
         * @param int    $quantity           The quantity of the product.
         *
         * @return array An array containing 'epofw_label' (Addon label name) and 'opt_price' (Addon price).
         */
        public function epofw_get_addon_title_pro(
            $product_id,
            $get_field_name_arr,
            $meta_key,
            $meta_value,
            $quantity
        ) {
            if ( !empty( $meta_key ) ) {
                $epofw_field_quantity = epofw_check_array_key_exists( 'epofw_field_quantity', $get_field_name_arr[$meta_key] );
                if ( !empty( $epofw_field_quantity ) ) {
                    $check_epofw_form_data = epofw_check_array_key_exists( 'epofw_form_data', $get_field_name_arr[$meta_key] );
                    $check_field_input = epofw_check_array_key_exists( 'field', $check_epofw_form_data );
                    $opt_price = '';
                    $opt_price_type = 'fixed';
                    if ( $check_field_input ) {
                        $check_addon_price = epofw_check_array_key_exists( 'epofw_price', $get_field_name_arr[$meta_key] );
                        $opt_price = 0;
                        if ( $check_addon_price ) {
                            $opt_price = $check_addon_price;
                        }
                    }
                    $check_epofw_haoiod = '';
                    $check_epofw_haopiod = '';
                    $epofw_label = '';
                    if ( !$check_epofw_haoiod || !$check_epofw_haopiod ) {
                        $check_label = epofw_check_array_key_exists( 'label', $check_epofw_form_data );
                        if ( $check_label ) {
                            $check_title = epofw_check_array_key_exists( 'title', $check_label );
                            if ( $check_title ) {
                                if ( !$check_epofw_haoiod ) {
                                    $epofw_label = $check_title;
                                }
                                if ( !$check_epofw_haopiod ) {
                                    $label_name_with_price = ' (' . wc_price( epofw_price_filter( $opt_price ) ) . ')';
                                    $epofw_label .= $label_name_with_price;
                                } else {
                                }
                            }
                        }
                    }
                } else {
                    $check_field_input = epofw_check_array_key_exists( 'field', $get_field_name_arr[$meta_key] );
                    $opt_price = '';
                    $epofw_label = '';
                    $opt_price_type = 'fixed';
                    if ( $check_field_input ) {
                        if ( strpos( $meta_value, ',' ) !== false ) {
                            // Parse comma-separated values and trim whitespace from each.
                            $meta_value_data = array_map( 'trim', explode( ',', $meta_value ) );
                        } else {
                            $meta_value_data = $meta_value;
                        }
                        $find_price_based_on_name = $this->epofw_find_price_based_on_name(
                            $product_id,
                            $meta_value_data,
                            $quantity,
                            $get_field_name_arr[$meta_key]
                        );
                        if ( $find_price_based_on_name ) {
                            $check_addon_price = epofw_check_array_key_exists( 'addon_price', $find_price_based_on_name );
                            $opt_price = $check_addon_price;
                        } else {
                            $opt_price = 0;
                        }
                    }
                    $check_epofw_haoiod = '';
                    $check_epofw_haopiod = '';
                    $epofw_label = '';
                    if ( !$check_epofw_haoiod || !$check_epofw_haopiod ) {
                        $check_label = epofw_check_array_key_exists( 'label', $get_field_name_arr[$meta_key] );
                        if ( $check_label ) {
                            $check_title = epofw_check_array_key_exists( 'title', $check_label );
                            if ( $check_title ) {
                                if ( !$check_epofw_haoiod ) {
                                    $epofw_label = $check_title;
                                }
                                if ( !$check_epofw_haopiod ) {
                                    $epofw_label .= ' (' . wc_price( epofw_price_filter( $opt_price ) ) . ')';
                                }
                            }
                        }
                    }
                }
                return array(
                    'epofw_label'      => $epofw_label,
                    'epofw_price'      => $opt_price,
                    'epofw_price_type' => $opt_price_type,
                );
            }
        }

        /**
         * Error message.
         *
         * @since 1.0.0
         *
         * @param string $filter_name   Filter name.
         * @param string $error_message Error Message.
         */
        public function epofw_add_to_cart_error_msg( $filter_name, $error_message ) {
            wc_add_notice( 
                /**
                 * Apply filter for error message.
                 *
                 * @since 1.0.0
                 */
                apply_filters( 'epofw_' . $filter_name . '_field_error_msg', $error_message ),
                'error'
             );
        }

        /**
         * Calculate addon price for a cart item.
         * Helper method to avoid code duplication across cart price functions.
         *
         * This total (base + addons) should match the product page "Addon Totals" subtotal
         * when the same options are submitted: the frontend sums #addon_total .addon_price
         * (data-addon-price); cart uses epofw_original_price per addon. Tax display
         * (incl/excl) on product page vs cart can differ; internally we use excl-tax for calculation.
         *
         * @since    4.4.4
         *
         * @param array $cart_item Cart item data.
         *
         * @return float|false Total price including addons, or false if no addons.
         */
        private function epofw_calculate_cart_item_total_price( $cart_item ) {
            if ( !isset( $cart_item['epofw_data'] ) || empty( $cart_item['epofw_data'] ) ) {
                return false;
            }
            // Get base price.
            $base_price = ( isset( $cart_item['epofw_product_price_without_tax'] ) ? (float) $cart_item['epofw_product_price_without_tax'] : (float) $cart_item['data']->get_price() );
            // Calculate addon price (per-unit).
            $addon_price = 0;
            foreach ( $cart_item['epofw_data'] as $addon_key => $addon ) {
                $check_epofw_vat = ( function_exists( 'epofw_get_addon_vat' ) ? epofw_get_addon_vat( $addon['epofw_form_data'] ) : (( function_exists( 'epofw_get_addon_vat__premium_only' ) ? epofw_get_addon_vat__premium_only( $addon['epofw_form_data'] ) : false )) );
                // Only add price if addon has original_price and vat is not enabled.
                if ( isset( $addon['epofw_original_price'] ) && empty( $check_epofw_vat ) ) {
                    $addon_original_price = (float) $addon['epofw_original_price'];
                    // Convert addon price to excl-tax if prices were entered with tax.
                    // This ensures addon prices are treated consistently with base product price (both excl-tax).
                    if ( wc_prices_include_tax() && $cart_item['data']->is_taxable() ) {
                        $tax_rates = WC_Tax::get_rates( $cart_item['data']->get_tax_class() );
                        $tax_rate_sum = array_sum( wp_list_pluck( $tax_rates, 'rate' ) );
                        $addon_original_price = $addon_original_price / (1 + $tax_rate_sum / 100);
                    }
                    // Check if field has addon quantity enabled and is not using product quantity.
                    $field_settings = ( isset( $addon['epofw_form_data']['epofw_field_settings'] ) ? $addon['epofw_form_data']['epofw_field_settings'] : array() );
                    $enable_af_per_qty = ( isset( $field_settings['enable_af_per_qty'] ) ? $field_settings['enable_af_per_qty'] : '' );
                    $af_product_qty = ( isset( $field_settings['epofw_af_product_quantity'] ) ? $field_settings['epofw_af_product_quantity'] : '' );
                    $field_quantity = ( isset( $addon['epofw_field_quantity'] ) ? intval( $addon['epofw_field_quantity'] ) : 1 );
                    if ( 'on' === $enable_af_per_qty && 'on' !== $af_product_qty && $field_quantity > 0 ) {
                        // Field quantity enabled with independent qty: multiply by field qty (per-unit price).
                        $addon_price += $addon_original_price * $field_quantity;
                    } else {
                        // No field quantity or synced with product qty: just add the original price (per unit).
                        // WooCommerce will multiply by cart quantity automatically.
                        $addon_price += $addon_original_price;
                    }
                }
            }
            // Return per-unit price (base + addons). WooCommerce handles cart qty multiplication.
            return $base_price + $addon_price;
        }

        /**
         * Filter the cart item price to include addon prices.
         *
         * @since 1.0.0
         *
         * @param string $product_price The formatted product price.
         * @param array  $cart_item_data The cart item data.
         * @return string The formatted price including addons.
         */
        public function epofw_woocommerce_cart_item_price( $product_price, $cart_item_data ) {
            // Calculate total price including addon prices from epofw_data.
            // Note: epofw_calculate_cart_item_total_price returns price EXCLUDING tax.
            $total_price = $this->epofw_calculate_cart_item_total_price( $cart_item_data );
            if ( false !== $total_price ) {
                // The $total_price is calculated using excl-tax prices, so we need to convert
                // it for display based on WooCommerce's tax display settings.
                $tax_display = get_option( 'woocommerce_tax_display_cart' );
                if ( 'incl' === $tax_display ) {
                    // Display prices including tax: add tax to the excl-tax total.
                    $display_price = wc_get_price_including_tax( $cart_item_data['data'], array(
                        'price' => $total_price,
                    ) );
                } else {
                    // Display prices excluding tax: show excl-tax total as-is.
                    $display_price = $total_price;
                }
                $product_price = wc_price( $display_price );
            } elseif ( isset( $cart_item_data['epofw_product_price_without_tax'] ) ) {
                // Fallback: Use excl-tax product price with proper display conversion.
                $tax_display = get_option( 'woocommerce_tax_display_cart' );
                if ( 'incl' === $tax_display ) {
                    $display_price = wc_get_price_including_tax( $cart_item_data['data'], array(
                        'price' => $cart_item_data['epofw_product_price_without_tax'],
                    ) );
                } else {
                    $display_price = $cart_item_data['epofw_product_price_without_tax'];
                }
                $product_price = wc_price( $display_price );
            }
            return $product_price;
        }

        /**
         * Filter the mini-cart widget item quantity display to include addon prices.
         * This handles the "quantity × price" format in the mini-cart.
         *
         * @since    4.4.4
         *
         * @param string $html          The HTML output for quantity × price.
         * @param array  $cart_item     Cart item data.
         * @param string $cart_item_key Cart item key.
         *
         * @return string
         */
        public function epofw_widget_cart_item_quantity( $html, $cart_item, $cart_item_key ) {
            $total_price = $this->epofw_calculate_cart_item_total_price( $cart_item );
            if ( false !== $total_price ) {
                // The $total_price is calculated using excl-tax prices, so we need to convert
                // it for display based on WooCommerce's tax display settings.
                $tax_display = get_option( 'woocommerce_tax_display_cart' );
                if ( 'incl' === $tax_display ) {
                    // Display prices including tax: add tax to the excl-tax total.
                    $display_price = wc_get_price_including_tax( $cart_item['data'], array(
                        'price' => $total_price,
                    ) );
                } else {
                    // Display prices excluding tax: show excl-tax total as-is.
                    $display_price = $total_price;
                }
                $product_price = wc_price( $display_price );
                // Format: quantity × price.
                $html = '<span class="quantity">' . sprintf( '%s &times; %s', $cart_item['quantity'], $product_price ) . '</span>';
            }
            return $html;
        }

        /**
         * Hook into cart fragments to ensure prices are recalculated for AJAX mini-cart.
         *
         * @since    4.4.4
         *
         * @param array $fragments Cart fragments.
         *
         * @return array
         */
        public function epofw_add_to_cart_fragments( $fragments ) {
            // Force cart totals recalculation to ensure addon prices are applied.
            // Price recalculation is handled by epofw_before_calculate_totals hook.
            if ( WC()->cart ) {
                // Force cart to recalculate totals (triggers epofw_before_calculate_totals).
                WC()->cart->calculate_totals();
                // Regenerate mini-cart widget content fragment with correct subtotal.
                // This ensures the theme's fragment is replaced with one that has correct prices.
                if ( isset( $fragments['div.widget_shopping_cart_content'] ) ) {
                    ob_start();
                    woocommerce_mini_cart();
                    $mini_cart = ob_get_clean();
                    $fragments['div.widget_shopping_cart_content'] = '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>';
                }
            }
            return $fragments;
        }

        /**
         * Recalculate cart item prices before totals are calculated.
         * This ensures addon prices are included in cart totals for AJAX add to cart.
         *
         * @since    4.4.4
         *
         * @param WC_Cart $cart Cart object.
         */
        public function epofw_before_calculate_totals( $cart ) {
            // Use static variable to prevent recursion (not action count).
            // Action count check was causing issues when cart is reloaded from session
            // during fragment generation - the price modifications would be skipped.
            static $running = false;
            if ( is_admin() && !defined( 'DOING_AJAX' ) ) {
                return;
            }
            // Prevent recursion within the same call stack.
            if ( $running ) {
                return;
            }
            $running = true;
            foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
                // epofw_calculate_cart_item_total_price returns price EXCLUDING tax.
                $total_price_excl_tax = $this->epofw_calculate_cart_item_total_price( $cart_item );
                if ( false !== $total_price_excl_tax ) {
                    // WooCommerce's set_price() expects a price in the "stored" format.
                    // If prices are entered WITH tax, we need to convert our excl-tax total to incl-tax.
                    // If prices are entered WITHOUT tax, we can use the excl-tax total as-is.
                    if ( wc_prices_include_tax() && $cart_item['data']->is_taxable() ) {
                        // Manually add tax to the excl-tax price.
                        // Note: wc_get_price_including_tax() doesn't add tax when wc_prices_include_tax() is true
                        // because it assumes the passed price already includes tax. So we calculate manually.
                        $tax_rates = WC_Tax::get_rates( $cart_item['data']->get_tax_class() );
                        $taxes = WC_Tax::calc_tax( $total_price_excl_tax, $tax_rates, false );
                        $price_to_set = $total_price_excl_tax + array_sum( $taxes );
                    } else {
                        // Prices don't include tax, so use excl-tax directly.
                        $price_to_set = $total_price_excl_tax;
                    }
                    // Set the product price.
                    $cart_item['data']->set_price( $price_to_set );
                }
            }
            $running = false;
        }

        /**
         * Function will fires after add cart item data filter.
         *
         * @since    1.0.0
         *
         * @param array $cart_item_data Cart item meta data.
         *
         * @return array
         */
        public function epofw_add_cart_item( $cart_item_data ) {
            $prices = epofw_product_price( $cart_item_data );
            return $this->epofw_set_product_price(
                $cart_item_data,
                $cart_item_data['quantity'],
                $prices,
                'product'
            );
        }

        /**
         * Function will set product price.
         *
         * @since    1.0.0
         *
         * @param array $cart_item_data Cart item data.
         * @param int   $quantity       Product quantity.
         * @param array $prices         Prices array.
         *
         * @return array
         */
        public function epofw_set_product_price( $cart_item_data, $quantity, $prices ) {
            if ( !empty( $cart_item_data['epofw_data'] ) ) {
                /**
                 * Apply filter for price without tax.
                 *
                 * @since 1.0.0
                 */
                $price_without_tax = apply_filters( 'epofw_original_price_without_tax', $prices['price_without_tax'], $cart_item_data );
                /**
                 * Apply filter for regular price without tax.
                 *
                 * @since 1.0.0
                 */
                $regular_price_without_tax = apply_filters( 'epofw_original_regular_price_without_tax', $prices['regular_price_without_tax'], $cart_item_data );
                /**
                 * Apply filter for sale price without tax.
                 *
                 * @since 1.0.0
                 */
                $sale_price_without_tax = apply_filters( 'epofw_original_sale_price_without_tax', $prices['sale_price_without_tax'], $cart_item_data );
                /**
                 * Apply filter for price before calculation.
                 *
                 * @since 1.0.0
                 */
                $price = apply_filters( 'epofw_price_before_calculation', $prices['price'], $cart_item_data );
                /**
                 * Apply filter for regular price before calculation.
                 *
                 * @since 1.0.0
                 */
                $regular_price = apply_filters( 'epofw_regular_price_before_calculation', $prices['regular_price'], $cart_item_data );
                /**
                 * Apply filter for sale price before calculation.
                 *
                 * @since 1.0.0
                 */
                $sale_price = apply_filters( 'epofw_sale_price_before_calculation', $prices['sale_price'], $cart_item_data );
                $cart_item_data['epofw_product_price_without_tax'] = (float) $price_without_tax;
                $cart_item_data['epofw_regular_price_without_tax'] = (float) $regular_price_without_tax;
                $cart_item_data['epofw_sale_price_without_tax'] = (float) $sale_price_without_tax;
                $cart_item_data['addons_price_before_calc'] = (float) $price;
                $cart_item_data['addons_regular_price_before_calc'] = (float) $regular_price;
                $cart_item_data['addons_sale_price_before_calc'] = (float) $sale_price;
                /**
                 * Apply filter for set product price.
                 *
                 * @since 1.0.0
                 */
                $price = apply_filters( 'epofw_set_product_price_after', $price, $quantity );
                /**
                 * Apply filter for set product regular price.
                 *
                 * @since 1.0.0
                 */
                $regular_price = apply_filters( 'epofw_set_product_regular_price_after', $regular_price, $quantity );
                /**
                 * Apply filter for set product sale price.
                 *
                 * @since 1.0.0
                 */
                $sale_price = apply_filters( 'epofw_set_product_sale_price_after', $sale_price, $quantity );
                if ( !empty( $cart_item_data ) && isset( $cart_item_data['epofw_data'] ) ) {
                    $addon_price = 0;
                    $addon_qty = false;
                    foreach ( $cart_item_data['epofw_data'] as $addon_key => $addon ) {
                        $check_epofw_vat = '';
                        // We will not add option field's price into product for cart if that option have enable wc tax option. ( Enable WooCommerce Options - Enable Addon Tax ).
                        if ( isset( $addon['epofw_original_price'] ) && empty( $check_epofw_vat ) ) {
                            $addon_original_price = (float) $addon['epofw_original_price'];
                            // We set addon original price here to work WC Tax settings - $addon['epofw_original_price'].
                            $addon_price += $addon_original_price * (int) $quantity;
                        }
                    }
                    $updated_addon_price = (float) ($addon_price / $quantity);
                    /**
                     * Apply filter for set addon price.
                     *
                     * @since 1.0.0
                     */
                    $updated_addon_price = apply_filters(
                        'epofw_set_addon_price_after',
                        $updated_addon_price,
                        $addon_price,
                        $addon_qty,
                        $quantity
                    );
                    $price += $updated_addon_price;
                    $regular_price += $updated_addon_price;
                    $sale_price += $updated_addon_price;
                }
                $cart_item_data['data']->set_price( $price );
                $has_regular_price = is_numeric( $cart_item_data['data']->get_regular_price( 'edit' ) );
                if ( $has_regular_price ) {
                    $cart_item_data['data']->set_regular_price( $regular_price );
                }
                $has_sale_price = is_numeric( $cart_item_data['data']->get_sale_price( 'edit' ) );
                if ( $has_sale_price ) {
                    $cart_item_data['data']->set_sale_price( $sale_price );
                }
            }
            return $cart_item_data;
        }

        /**
         * Get cart item from session.
         *
         * @since    1.0.0
         *
         * @param array $cart_item_data Cart item data.
         * @param array $values         Addon fields data.
         *
         * @return array
         */
        public function epofw_get_cart_item_from_session( $cart_item_data, $values ) {
            if ( !empty( $values['epofw_data'] ) ) {
                $prices = epofw_product_price( $cart_item_data );
                $cart_item_data['epofw_data'] = $values['epofw_data'];
                $cart_item_data = $this->epofw_set_product_price( $cart_item_data, $cart_item_data['quantity'], $prices );
            }
            return $cart_item_data;
        }

        /**
         * Function will update price when quantity update.
         *
         * @since    1.0.0
         *
         * @param string $cart_item_key Cart item key.
         * @param int    $quantity      Updated product quantity.
         * @param int    $old_quantity  Old product quantity.
         * @param object $cart          Cart data.
         *
         * @return array|void
         */
        public function epofw_update_price_on_quantity_update(
            $cart_item_key,
            $quantity,
            $old_quantity,
            $cart
        ) {
            $cart_item_data = $cart->get_cart_item( $cart_item_key );
            if ( !empty( $cart_item_data['epofw_data'] ) ) {
                $prices = array(
                    'price'                     => $cart_item_data['addons_price_before_calc'],
                    'regular_price'             => $cart_item_data['addons_regular_price_before_calc'],
                    'sale_price'                => $cart_item_data['addons_sale_price_before_calc'],
                    'price_without_tax'         => $cart_item_data['epofw_product_price_without_tax'],
                    'regular_price_without_tax' => $cart_item_data['epofw_regular_price_without_tax'],
                    'sale_price_without_tax'    => $cart_item_data['epofw_sale_price_without_tax'],
                );
                return $this->epofw_set_product_price(
                    $cart_item_data,
                    $quantity,
                    $prices,
                    'cart_update'
                );
            }
        }

        /**
         * Add tax data to variation JSON for frontend tax display.
         *
         * This allows the frontend to update tax calculations when variations change,
         * especially for variable products with different tax classes per variation.
         *
         * @since 4.4.6
         *
         * @param array                $variation_data Array of variation data.
         * @param WC_Product_Variable  $product        Parent variable product.
         * @param WC_Product_Variation $variation      Variation product object.
         *
         * @return array Modified variation data with tax information.
         */
        public function epofw_add_variation_tax_data( $variation_data, $product, $variation ) {
            // Default tax data.
            $variation_data['epofw_tax_rate'] = 0;
            $variation_data['epofw_tax_label'] = '';
            $variation_data['epofw_is_taxable'] = false;
            // Only add tax data if variation is taxable.
            if ( $variation->is_taxable() ) {
                $variation_data['epofw_is_taxable'] = true;
                $tax_class = $variation->get_tax_class();
                // Get tax rates using customer location.
                $tax_rates = WC_Tax::get_rates( $tax_class );
                // Fallback to base location if customer location rates are empty.
                if ( empty( $tax_rates ) ) {
                    $base_location = wc_get_base_location();
                    $tax_rates = WC_Tax::find_rates( array(
                        'country'   => $base_location['country'],
                        'state'     => $base_location['state'],
                        'tax_class' => $tax_class,
                    ) );
                }
                if ( !empty( $tax_rates ) ) {
                    // Get the first tax rate (primary tax).
                    $first_rate = reset( $tax_rates );
                    $variation_data['epofw_tax_rate'] = ( isset( $first_rate['rate'] ) ? floatval( $first_rate['rate'] ) : 0 );
                    $variation_data['epofw_tax_label'] = ( isset( $first_rate['label'] ) ? $first_rate['label'] : __( 'Tax', 'extra-product-options-for-woocommerce' ) );
                }
            }
            return $variation_data;
        }

    }

}