<?php

/**
 * Common functions for plugins.
 *
 * @since      1.0.0
 * @package    Extra_Product_Options_For_WooCommerce
 * @subpackage Extra_Product_Options_For_WooCommerce/settings
 */
// If this file is called directly, abort.
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Additional conditional rule name.
 *
 * @since 1.0.0
 * @return array $conditional_name_arr Return conditional rule name.
 */
function epofw_field_additional_conditional_name() {
    $conditional_name_arr = array(
        'product'  => esc_html__( 'Product', 'extra-product-options-for-woocommerce' ),
        'category' => esc_html__( 'Category', 'extra-product-options-for-woocommerce' ),
        'tag'      => esc_html__( 'Tag', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filters for field additional conditional name.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_field_additional_conditional_name', $conditional_name_arr );
}

/**
 * Get additional conditional operator name for Extra Product Options fields.
 * This function retrieves the additional conditional operator name based on the provided condition.
 *
 * @since 1.0.0
 *
 * @param string $check_condition The provided condition for the additional conditional operator.
 *
 * @return array Additional conditional operator name.
 */
function epofw_field_additional_conditional_operator(  $check_condition = ''  ) {
    $conditional_op_arr = array(
        'is_equal_to'  => esc_html__( 'Is Equal To', 'extra-product-options-for-woocommerce' ),
        'not_equal_to' => esc_html__( 'Not Equal To', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filters for field additional conditional operator.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_field_additional_conditional_operator', $conditional_op_arr );
}

/**
 * Title position data..
 *
 * @since 4.4.1
 *
 * @param bool $include_use_global When true, include the "Use Global" option.
 *
 * @return array $conditional_op_arr Return conditional operator name.
 */
function epofw_field_title_position_data(  $include_use_global = false  ) {
    $title_position_arr = array(
        'free_1' => esc_html__( 'Left of the field', 'extra-product-options-for-woocommerce' ),
        'free_2' => esc_html__( 'Above field', 'extra-product-options-for-woocommerce' ),
        'free_3' => esc_html__( 'Right of the field', 'extra-product-options-for-woocommerce' ),
        'free_4' => esc_html__( 'Disable', 'extra-product-options-for-woocommerce' ),
    );
    // Add "Use Global" option for field-level settings.
    if ( $include_use_global ) {
        $title_position_arr[''] = esc_html__( '-- Use Global --', 'extra-product-options-for-woocommerce' );
    }
    /**
     * Filter and retrieve title position data for Extra Product Options fields.
     * This line of code applies a filter to get the title position data for Extra Product Options fields
     * and returns the filtered data array.
     *
     * @since 4.4.1
     *
     * @param array $title_position_arr The default array of title position data for Extra Product Options fields.
     *
     * @return array Filtered array of title position data for Extra Product Options fields.
     */
    return apply_filters( 'epofw_field_title_position_data', $title_position_arr );
}

/**
 * Price type.
 *
 * @since 1.0.0
 *
 * @param string $type Field type.
 *
 * @return array $conditional_op_arr Return conditional operator name.
 */
function epofw_price_type_data(  $type  ) {
    $title_position_arr = array();
    if ( !empty( $type ) ) {
        $title_position_arr = array(
            'fixed' => esc_html__( 'Fixed', 'extra-product-options-for-woocommerce' ),
        );
    }
    /**
     * Filters for price type data.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_price_type_data', $title_position_arr );
}

/**
 * List of tag name.
 *
 * @since 1.0.0
 *
 * @param bool $include_use_global When true, include the "Use Global" option.
 *
 * @return array $field_type_arr Return tag name.
 */
function epofw_field_heading_type_options_data(  $include_use_global = false  ) {
    $field_type_arr = array();
    // Add "Use Global" option for field-level settings.
    if ( $include_use_global ) {
        $field_type_arr[''] = esc_html__( '-- Use Global --', 'extra-product-options-for-woocommerce' );
    }
    $field_type_arr['label'] = esc_html__( 'Label', 'extra-product-options-for-woocommerce' );
    $field_type_arr['h1'] = esc_html__( 'H1', 'extra-product-options-for-woocommerce' );
    $field_type_arr['h2'] = esc_html__( 'H2', 'extra-product-options-for-woocommerce' );
    $field_type_arr['h3'] = esc_html__( 'H3', 'extra-product-options-for-woocommerce' );
    $field_type_arr['h4'] = esc_html__( 'H4', 'extra-product-options-for-woocommerce' );
    $field_type_arr['h5'] = esc_html__( 'H5', 'extra-product-options-for-woocommerce' );
    $field_type_arr['h6'] = esc_html__( 'H6', 'extra-product-options-for-woocommerce' );
    $field_type_arr['strong'] = esc_html__( 'Strong', 'extra-product-options-for-woocommerce' );
    $field_type_arr['span'] = esc_html__( 'Span', 'extra-product-options-for-woocommerce' );
    $field_type_arr['div'] = esc_html__( 'Div', 'extra-product-options-for-woocommerce' );
    /**
     * Filters for field heading type options data.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_field_heading_type_options_data', $field_type_arr );
}

/**
 * List of field restriction type.
 *
 * @since 1.0.0
 * @return array $field_type_arr Return restriction list.
 */
function epofw_field_restriction_options_data() {
    $field_type_arr = array(
        'all'         => esc_html__( 'Allow all', 'extra-product-options-for-woocommerce' ),
        'only_text'   => esc_html__( 'Only Text', 'extra-product-options-for-woocommerce' ),
        'only_number' => esc_html__( 'Only Number', 'extra-product-options-for-woocommerce' ),
        'all_number'  => esc_html__( 'Number with decimal', 'extra-product-options-for-woocommerce' ),
        'text_number' => esc_html__( 'Text and Number', 'extra-product-options-for-woocommerce' ),
        'email'       => esc_html__( 'Email', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filters for field restriction options data.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_field_restriction_options_data', $field_type_arr );
}

/**
 * List of content related tag name.
 *
 * @since 1.0.0
 * @return array $field_type_arr Return content related tag name.
 */
function epofw_field_content_type_options_data() {
    $field_type_arr = array(
        'p'          => esc_html__( 'P', 'extra-product-options-for-woocommerce' ),
        'address'    => esc_html__( 'Address', 'extra-product-options-for-woocommerce' ),
        'blockquote' => esc_html__( 'Blockquote', 'extra-product-options-for-woocommerce' ),
        'canvas'     => esc_html__( 'Canvas', 'extra-product-options-for-woocommerce' ),
        'output'     => esc_html__( 'Output', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filters for field content type options data.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_field_content_type_options_data', $field_type_arr );
}

/**
 * Filter for field type options data: Like: Text, Number, Textarea etc.
 *
 * @since 1.0.0
 * @return array $field_type_arr Return all fields type.
 */
function epofw_field_type_options_data() {
    $field_type_arr = array(
        'text'          => esc_html__( 'Text', 'extra-product-options-for-woocommerce' ),
        'hidden'        => esc_html__( 'Hidden', 'extra-product-options-for-woocommerce' ),
        'number'        => esc_html__( 'Number', 'extra-product-options-for-woocommerce' ),
        'password'      => esc_html__( 'Password', 'extra-product-options-for-woocommerce' ),
        'textarea'      => esc_html__( 'Textarea', 'extra-product-options-for-woocommerce' ),
        'select'        => esc_html__( 'Select', 'extra-product-options-for-woocommerce' ),
        'multiselect'   => esc_html__( 'Multiselect', 'extra-product-options-for-woocommerce' ),
        'checkbox'      => esc_html__( 'Checkbox', 'extra-product-options-for-woocommerce' ),
        'checkboxgroup' => esc_html__( 'Checkbox Group', 'extra-product-options-for-woocommerce' ),
        'radiogroup'    => esc_html__( 'Radio', 'extra-product-options-for-woocommerce' ),
        'datepicker'    => esc_html__( 'Date Picker', 'extra-product-options-for-woocommerce' ),
        'timepicker'    => esc_html__( 'Time Picker', 'extra-product-options-for-woocommerce' ),
        'colorpicker'   => esc_html__( 'Color Picker', 'extra-product-options-for-woocommerce' ),
        'switch'        => esc_html__( 'Switch', 'extra-product-options-for-woocommerce' ),
        'slider'        => esc_html__( 'Slider', 'extra-product-options-for-woocommerce' ),
        'heading'       => esc_html__( 'Heading', 'extra-product-options-for-woocommerce' ),
        'paragraph'     => esc_html__( 'Paragraph', 'extra-product-options-for-woocommerce' ),
        'separator'     => esc_html__( 'Separator', 'extra-product-options-for-woocommerce' ),
    );
    // Always add premium field types - they will show with PRO badge in free version.
    $field_type_arr['file'] = esc_html__( 'File Upload', 'extra-product-options-for-woocommerce' );
    $field_type_arr['colorswitcher'] = esc_html__( 'Color Switcher', 'extra-product-options-for-woocommerce' );
    $field_type_arr['textswitcher'] = esc_html__( 'Text Switcher', 'extra-product-options-for-woocommerce' );
    $field_type_arr['imageswitcher'] = esc_html__( 'Image Switcher', 'extra-product-options-for-woocommerce' );
    $field_type_arr['repeater'] = esc_html__( 'Repeater', 'extra-product-options-for-woocommerce' );
    $field_type_arr['productgroup'] = esc_html__( 'Product Group', 'extra-product-options-for-woocommerce' );
    /**
     * Filters for field type options data.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_field_type_options_data', $field_type_arr );
}

/**
 * Render a field on the front side based on type and attribute data.
 *
 * @since 1.0.0
 * @since 4.4.5 Renamed from cp_render_fields_front to epofw_render_fields_front.
 *
 * @param array  $attr_data      All fields attribute data.
 * @param string $field_type     The type of the field.
 * @param array  $args           {
 *                               The array of data with key epofwtwp_args.
 *
 * @type object  $product_data   Data of the current product.
 * @type float   $product_price  Product price.
 * @type string  $opt_price_type Price type - Fixed, Percentage of product price, etc.
 * @type string  $epofw_action   Page action - shop|product page.
 * @type array   $fields_data    Array of addon data.
 *                               }
 * @return mixed Rendered HTML for the field.
 */
function epofw_render_fields_front(  $attr_data, $field_type, $args  ) {
    $fields_data = ( isset( $args['epofwtwp_args']['fields_data'] ) ? $args['epofwtwp_args']['fields_data'] : array() );
    $epofw_action = ( isset( $args['epofwtwp_args']['epofw_action'] ) ? $args['epofwtwp_args']['epofw_action'] : array() );
    $product_data = ( isset( $args['epofwtwp_args']['product_data'] ) ? $args['epofwtwp_args']['product_data'] : array() );
    $qty = 1;
    $mask_field_class = ( isset( $fields_data['epofw_field_settings']['msk_frmt'] ) && !empty( $fields_data['epofw_field_settings']['msk_frmt'] ) ? 'mask_field' : '' );
    $common_class = 'epofw_fields_tag_class';
    if ( isset( $attr_data['class'] ) ) {
        $attr_data['class'] = esc_attr( $attr_data['class'] . ' ' . $common_class . ' ' . $mask_field_class );
    }
    $html = '';
    $args['epofwtwp_args']['attr_data'] = $attr_data;
    $epofwtwp_args = array(
        'product_data' => $product_data,
        'qty'          => $qty,
    );
    if ( !empty( $product_data ) ) {
        if ( 'epofw_shop' === $epofw_action ) {
            $product_price = epofw_display_product_price( $product_data, 'shop' );
        } else {
            $product_price = epofw_display_product_price( $product_data, '' );
        }
        $epofwtwp_args['product_price'] = $product_price;
    }
    $fields_array = array(
        'text',
        'password',
        'hidden',
        'number',
        'datepicker',
        'colorpicker',
        'timepicker',
        'checkboxgroup',
        'radiogroup',
        'checkbox',
        'textarea',
        'select',
        'multiselect',
        'html',
        'switch',
        'slider'
    );
    /**
     * Allow addons to register field types that have a front template.
     *
     * @since 4.4.6
     * @param array $fields_array List of field type keys.
     */
    $fields_array = apply_filters( 'epofw_front_render_field_types', $fields_array );
    if ( in_array( $field_type, $fields_array, true ) ) {
        $file_name = $field_type . '.php';
        wc_get_template(
            'epofw-' . $file_name,
            $args,
            epofw_get_template_path(),
            epofw_get_default_path()
        );
    }
    /**
     * Filters to prints fields html.
     *
     * @since 3.0.0
     */
    return apply_filters(
        'epofw_print_fields_html',
        wp_kses( $html, wp_kses_allowed_html( 'epofw_custom' ) ),
        $attr_data,
        $field_type
    );
    // nosemgrep: audit.php.wp.security.xss.block-attr -- False positive: $attr_data is escaped in templates with esc_attr() and sanitize_key().
}

/**
 * Render a field on the front side based on type and attribute data.
 *
 * @since 1.0.0
 * @deprecated 4.4.5 Use epofw_render_fields_front() instead.
 * @see epofw_render_fields_front()
 *
 * @param array  $attr_data  All fields attribute data.
 * @param string $field_type The type of the field.
 * @param array  $args       The array of data with key epofwtwp_args.
 * @return mixed Rendered HTML for the field.
 */
function cp_render_fields_front(  $attr_data, $field_type, $args  ) {
    _deprecated_function( __FUNCTION__, '4.4.5', 'epofw_render_fields_front()' );
    // nosemgrep: audit.php.wp.security.xss.block-attr -- Deprecated wrapper, escaping handled in epofw_render_fields_front().
    return epofw_render_fields_front( $attr_data, $field_type, $args );
}

/**
 * Get data for fields from custom tables.
 *
 * @since 1.0.0
 * @since 4.4.0 Updated to use custom tables exclusively with sections, rows, and columns.
 *
 * @param int $get_post_id Post id.
 *
 * @return array Addon data from custom tables.
 */
function epofw_get_data_from_db(  $get_post_id  ) {
    if ( empty( $get_post_id ) ) {
        return array();
    }
    // Load from custom tables ONLY (v4.4.0+ with sections/rows/columns architecture).
    $addon_data = EPOFW_Custom_Tables::load_addon( $get_post_id );
    if ( false === $addon_data || empty( $addon_data ) ) {
        return array();
    }
    return $addon_data;
}

/**
 * Save addon data to custom tables.
 *
 * @since 4.4.6
 *
 * @param int   $post_id    Addon post ID.
 * @param array $addon_data Addon data to save.
 *
 * @return bool True on success, false on failure.
 */
function epofw_save_data_to_db(  $post_id, $addon_data  ) {
    $post_id = absint( $post_id );
    if ( !$post_id || empty( $addon_data ) || !is_array( $addon_data ) ) {
        return false;
    }
    try {
        EPOFW_Custom_Tables::save_addon( $post_id, $addon_data );
        return true;
    } catch ( Exception $e ) {
        return false;
    }
}

/**
 * DEPRECATED: Load addon data from post_meta (legacy storage).
 * This function is deprecated as of v4.4.0. Use epofw_get_data_from_db() instead.
 *
 * @since 1.0.0
 * @deprecated 4.4.0 Use epofw_get_data_from_db() which loads from custom tables.
 *
 * @param int $get_post_id Post ID.
 *
 * @return array Addon data from post_meta.
 */
function epofw_get_data_from_post_meta(  $get_post_id  ) {
    _deprecated_function( __FUNCTION__, '4.4.0', 'epofw_get_data_from_db()' );
    if ( empty( $get_post_id ) ) {
        return array();
    }
    $old_data = get_post_meta( $get_post_id, 'epofw_prd_opt_data', true );
    return ( !empty( $old_data ) ? $old_data : array() );
}

/**
 * Check array key exists or not.
 *
 * @since 1.0.0
 *
 * @param string $key  Key of the field.
 * @param array  $data Array of the data.
 *
 * @return string|array $var_name Return var name.
 */
function epofw_check_array_key_exists(  $key, $data  ) {
    $var_name = '';
    if ( !empty( $data ) && is_array( $data ) ) {
        if ( array_key_exists( $key, $data ) ) {
            $var_name = $data[$key];
        }
    }
    if ( is_array( $var_name ) ) {
        return $var_name;
    } else {
        return ( !empty( $var_name ) ? trim( $var_name ) : '' );
    }
}

/**
 * Calculate addon price based on condition and return formatted price.
 *
 * @since 1.0.0
 *
 * @param array $epofwtwp_args Addon args.
 * @param array $fields_data   Fields data.
 *
 * @return float $product_price Product price.
 */
function epofw_calculate_price_based_on_condition(  $epofwtwp_args, $fields_data  ) {
    $price = ( isset( $epofwtwp_args['opt_price'] ) ? $epofwtwp_args['opt_price'] : '' );
    $price_type = ( isset( $epofwtwp_args['opt_price_type'] ) ? $epofwtwp_args['opt_price_type'] : '' );
    $product_price = ( isset( $epofwtwp_args['product_price'] ) ? $epofwtwp_args['product_price'] : '' );
    $addon_price = epofw_calculated_price_based_on_condition( $price, $price_type, $product_price );
    $epofwtwp_args['opt_price'] = $addon_price;
    $addon_price = epofw_format_price_with_decimals( $addon_price, $epofwtwp_args, $fields_data );
    /**
     * Filters to calculate price based on condition.
     *
     * @since 3.0.0
     */
    return apply_filters(
        'epofw_calculate_price_based_on_condition',
        $addon_price,
        $epofwtwp_args,
        $fields_data
    );
}

/**
 * Calculate and return the price based on a given condition.
 * This function calculates and returns the price based on the provided condition,
 * price type, and the original product price.
 *
 * @since 2.5
 *
 * @param float  $price         The original price before calculation.
 * @param string $price_type    The type of calculation to be applied, e.g., 'percentage' or 'fixed'.
 * @param float  $product_price The original product price before any modifications.
 *
 * @return float|int Calculated price based on the condition and price type.
 */
function epofw_calculated_price_based_on_condition(  $price, $price_type, $product_price  ) {
    $addon_price = 0;
    if ( 'fixed' === $price_type ) {
        $addon_price = $price;
    }
    /**
     * Filters to calculated price based on condition.
     *
     * @since 3.0.0
     */
    return apply_filters(
        'epofw_calculated_price_based_on_condition',
        $addon_price,
        $price,
        $price_type,
        $product_price
    );
}

/**
 * Explore opt group fields data.
 *
 * @since 1.1
 *
 * @param array|string $v_value Option data (array or legacy string).
 *
 * @return array Option data with keys: opt_label, opt_price_type, opt_price, opt_swticher, opt_stock.
 */
function epofw_explore_label_from_opt_group(  $v_value  ) {
    $opt_id = '';
    $opt_label = '';
    $opt_price_type = 'fixed';
    $opt_price = 0;
    $opt_colorswticher = '';
    $opt_stock = '';
    $opt_product_id = '';
    $opt_variation_id = '';
    if ( is_string( $v_value ) && strpos( $v_value, '||' ) !== false ) {
        $parts = explode( '||', $v_value );
        $opt_label = ( isset( $parts[0] ) ? $parts[0] : '' );
        $opt_price_type = ( isset( $parts[1] ) ? $parts[1] : 'fixed' );
        $opt_price = ( isset( $parts[2] ) ? $parts[2] : 0 );
        if ( isset( $parts[3] ) && !empty( $parts[3] ) ) {
            if ( strpos( $parts[3], 'opt_' ) === 0 ) {
                $opt_id = $parts[3];
                $opt_stock = ( isset( $parts[4] ) ? $parts[4] : '' );
            } else {
                $opt_colorswticher = $parts[3];
                if ( isset( $parts[4] ) && strpos( $parts[4], 'opt_' ) === 0 ) {
                    $opt_id = $parts[4];
                    $opt_stock = ( isset( $parts[5] ) ? $parts[5] : '' );
                }
            }
        }
    } elseif ( is_array( $v_value ) ) {
        $opt_label = ( isset( $v_value['label'] ) ? $v_value['label'] : '' );
        $opt_price_type = ( isset( $v_value['price_type'] ) ? $v_value['price_type'] : 'fixed' );
        $opt_price = ( isset( $v_value['price'] ) ? $v_value['price'] : 0 );
        $opt_stock = ( isset( $v_value['stock'] ) ? $v_value['stock'] : '' );
        // Get option ID if present (empty for existing options without ID - uses label fallback).
        if ( isset( $v_value['id'] ) && !empty( $v_value['id'] ) ) {
            $opt_id = $v_value['id'];
        }
        // Note: IDs are only assigned when options are saved in admin, not auto-generated here.
        // Product Group addon: product_id and variation_id.
        if ( isset( $v_value['product_id'] ) && '' !== $v_value['product_id'] ) {
            $opt_product_id = absint( $v_value['product_id'] );
        }
        if ( isset( $v_value['variation_id'] ) && '' !== $v_value['variation_id'] ) {
            $opt_variation_id = absint( $v_value['variation_id'] );
        }
    }
    $explore_opt_group_data = array(
        'opt_id'         => $opt_id,
        'opt_label'      => $opt_label,
        'opt_price_type' => $opt_price_type,
        'opt_price'      => $opt_price,
    );
    // Product Group addon: expose product_id and variation_id for frontend/cart.
    if ( '' !== $opt_product_id ) {
        $explore_opt_group_data['product_id'] = $opt_product_id;
    }
    if ( '' !== $opt_variation_id ) {
        $explore_opt_group_data['variation_id'] = $opt_variation_id;
    }
    return $explore_opt_group_data;
}

/**
 * Get the list of fields for a specific product in Extra Product Options for WooCommerce.
 * This function retrieves the list of fields based on the provided product and field IDs.
 *
 * @since 1.0.0
 *
 * @param int $current_prd_id   The ID of the current product.
 * @param int $current_field_id The ID of the current field.
 *
 * @return array List of fields for the specified product.
 */
function epofw_get_fields_list(  $current_prd_id, $current_field_id  ) {
    $fields_array = array();
    // Load directly from custom tables.
    $get_data = epofw_get_data_from_db( $current_prd_id );
    if ( !empty( $get_data ) ) {
        // Custom tables structure (v4.4.0+): general -> [section_id] -> row_groups -> [row_group_id] -> columns_groups -> [columns_id] -> rows -> [row_id] -> fields.
        $sections_data = epofw_check_array_key_exists( 'general', $get_data );
        if ( !empty( $sections_data ) && is_array( $sections_data ) ) {
            foreach ( $sections_data as $section_key => $section_data ) {
                $row_groups = epofw_check_array_key_exists( 'row_groups', $section_data );
                if ( !empty( $row_groups ) && is_array( $row_groups ) ) {
                    foreach ( $row_groups as $row_group_key => $row_group_data ) {
                        // v4.4.0+: Navigate through columns_groups level.
                        $columns_groups = epofw_check_array_key_exists( 'columns_groups', $row_group_data );
                        if ( !empty( $columns_groups ) && is_array( $columns_groups ) ) {
                            foreach ( $columns_groups as $columns_group_key => $columns_group_data ) {
                                $rows = epofw_check_array_key_exists( 'rows', $columns_group_data );
                                if ( !empty( $rows ) && is_array( $rows ) ) {
                                    foreach ( $rows as $row_key => $row_data ) {
                                        $fields = epofw_check_array_key_exists( 'fields', $row_data );
                                        if ( !empty( $fields ) && is_array( $fields ) ) {
                                            foreach ( $fields as $field_key => $field_data ) {
                                                // Skip current field.
                                                if ( $current_field_id === $field_key ) {
                                                    continue;
                                                }
                                                // Check field status.
                                                $field_status = epofw_check_array_key_exists( 'field_status', $field_data );
                                                if ( 'on' !== $field_status ) {
                                                    continue;
                                                }
                                                // Get field type first to check if it should be excluded.
                                                $field_info = epofw_check_array_key_exists( 'field', $field_data );
                                                $field_type = 'text';
                                                if ( !empty( $field_info ) && is_array( $field_info ) ) {
                                                    $field_type = epofw_check_array_key_exists( 'type', $field_info );
                                                    if ( empty( $field_type ) ) {
                                                        $field_type = 'text';
                                                    }
                                                }
                                                // Skip field types that cannot be used as conditional logic sources.
                                                // These fields either have no user-interactive value or cannot be compared.
                                                $excluded_field_types = array(
                                                    'heading',
                                                    'paragraph',
                                                    'hidden',
                                                    'file'
                                                );
                                                if ( in_array( $field_type, $excluded_field_types, true ) ) {
                                                    continue;
                                                }
                                                // Get field label.
                                                $label_data = epofw_check_array_key_exists( 'label', $field_data );
                                                $field_label = '';
                                                if ( !empty( $label_data ) && is_array( $label_data ) ) {
                                                    $field_label = epofw_check_array_key_exists( 'title', $label_data );
                                                }
                                                $generate_key = 'epofw_' . $field_type . '_' . $field_key;
                                                $fields_array[$field_key] = $generate_key . '||' . $field_type . '||' . $field_label;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return $fields_array;
}

/**
 * Display or retrieve the formatted product price based on the provided product data and action.
 * This function is responsible for displaying or retrieving the formatted product price
 * based on the provided product data and action.
 *
 * @since 1.0.0
 *
 * @param object $product_data An array containing product-related information.
 * @param string $action       The action to be performed, e.g., 'display' or 'retrieve'.
 *
 * @return mixed Formatted product price if action is 'display', or retrieved price if action is 'retrieve'.
 */
function epofw_display_product_price(  $product_data, $action  ) {
    $product_price = '';
    if ( !empty( $product_data ) ) {
        $raw_price = $product_data->get_price();
        /**
         * Apply filters for display product price before tax.
         *
         * @since 1.0.0
         */
        $product_price = apply_filters( 'epofw_display_product_price_before_tax', $raw_price, $product_data );
        // Cache tax display option to avoid multiple get_option calls.
        $epofw_tax_display = get_option( 'woocommerce_tax_display_shop' );
        if ( 'shop' === $action ) {
            if ( 'incl' === $epofw_tax_display ) {
                $product_price = wc_get_price_including_tax( $product_data );
                // Price with VAT.
            } elseif ( 'excl' === $epofw_tax_display ) {
                $product_price = wc_get_price_excluding_tax( $product_data );
                // Price with VAT.
            }
        } elseif ( 'cart' === $action ) {
            // Use cart's tax display mode, or fallback to cart tax display option (not shop).
            $cart_tax_display = ( WC()->cart ? WC()->cart->get_tax_price_display_mode() : get_option( 'woocommerce_tax_display_cart' ) );
            if ( 'incl' === $cart_tax_display ) {
                $product_price = wc_get_price_including_tax( $product_data );
                // Price with VAT.
            } elseif ( 'excl' === $cart_tax_display ) {
                $product_price = wc_get_price_excluding_tax( $product_data );
                // Price without VAT.
            }
        }
        // Get Product bundle price.
        if ( is_object( $product_data ) && $product_data->is_type( 'bundle' ) ) {
            $product_price = $product_data->get_bundle_price();
        }
    }
    /**
     * Filters for display product price.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_display_product_price', $product_price, $product_data );
}

/**
 * Convert a string from Cyrillic to Latin characters.
 * This function performs a conversion of a string from Cyrillic to Latin characters.
 *
 * @since 1.2
 *
 * @param string $name_string The string to be converted.
 *
 * @return string Converted string with Latin characters.
 */
function epofw_get_cyric_string_to_latin(  $name_string  ) {
    if ( preg_match( '/[А-Яа-яЁё]/u', $name_string ) ) {
        $cyr = array(
            'а',
            'б',
            'в',
            'г',
            'д',
            'е',
            'ё',
            'ж',
            'з',
            'и',
            'й',
            'к',
            'л',
            'м',
            'н',
            'о',
            'п',
            'р',
            'с',
            'т',
            'у',
            'ф',
            'х',
            'ц',
            'ч',
            'ш',
            'щ',
            'ъ',
            'ы',
            'ь',
            'э',
            'ю',
            'я',
            'А',
            'Б',
            'В',
            'Г',
            'Д',
            'Е',
            'Ё',
            'Ж',
            'З',
            'И',
            'Й',
            'К',
            'Л',
            'М',
            'Н',
            'О',
            'П',
            'Р',
            'С',
            'Т',
            'У',
            'Ф',
            'Х',
            'Ц',
            'Ч',
            'Ш',
            'Щ',
            'Ъ',
            'Ы',
            'Ь',
            'Э',
            'Ю',
            'Я'
        );
        $lat = array(
            'a',
            'b',
            'v',
            'g',
            'd',
            'e',
            'io',
            'zh',
            'z',
            'i',
            'y',
            'k',
            'l',
            'm',
            'n',
            'o',
            'p',
            'r',
            's',
            't',
            'u',
            'f',
            'h',
            'ts',
            'ch',
            'sh',
            'sht',
            'a',
            'i',
            'y',
            'e',
            'yu',
            'ya',
            'A',
            'B',
            'V',
            'G',
            'D',
            'E',
            'Io',
            'Zh',
            'Z',
            'I',
            'Y',
            'K',
            'L',
            'M',
            'N',
            'O',
            'P',
            'R',
            'S',
            'T',
            'U',
            'F',
            'H',
            'Ts',
            'Ch',
            'Sh',
            'Sht',
            'A',
            'I',
            'Y',
            'e',
            'Yu',
            'Ya'
        );
        $textcyr = str_replace( $cyr, $lat, $name_string );
    } else {
        $textcyr = $name_string;
    }
    return $textcyr;
}

/**
 * Clear the general settings cache.
 *
 * Call this function when general settings are updated to invalidate the cache.
 *
 * @since 4.4.5
 *
 * @return void
 */
function epofw_clear_general_settings_cache() {
    global $epofw_globals;
    // Clear object cache.
    wp_cache_delete( 'epofw_general_settings_data', 'epofw' );
    // Set flag to force refresh of static cache on next call.
    $epofw_globals['settings_cache_cleared'] = true;
}

/**
 * Get the general setting value for a specific key.
 * This function retrieves the value associated with a specific key from the general settings.
 * Uses static variable and object cache for performance optimization.
 *
 * @since 1.0.0
 * @since 4.4.5 Added caching for performance optimization.
 *
 * @param string $key The key for which to retrieve the general setting value.
 *
 * @return mixed|null General setting value for the specified key, or null if not found.
 */
function epofw_general_settings(  $key  ) {
    global $epofw_globals;
    // Static cache for same-request performance.
    static $epofw_cached_settings = null;
    // Check if cache was cleared and reset static cache.
    if ( !empty( $epofw_globals['settings_cache_cleared'] ) ) {
        $epofw_cached_settings = null;
        $epofw_globals['settings_cache_cleared'] = false;
    }
    // Check static cache first (fastest).
    if ( null === $epofw_cached_settings ) {
        // Try object cache next.
        $epofw_cached_settings = wp_cache_get( 'epofw_general_settings_data', 'epofw' );
        // If not in object cache, fetch from database.
        if ( false === $epofw_cached_settings ) {
            $epofw_get_data = get_option( 'epofw_general_data' );
            if ( is_serialized( $epofw_get_data ) ) {
                $epofw_get_data = maybe_unserialize( $epofw_get_data );
                $epofw_cached_settings = epofw_check_array_key_exists( 'epofw_general_data', $epofw_get_data );
                if ( empty( $epofw_cached_settings ) ) {
                    $epofw_cached_settings = $epofw_get_data;
                }
            } else {
                $epofw_cached_settings = $epofw_get_data;
            }
            // Ensure we have an array.
            if ( !is_array( $epofw_cached_settings ) ) {
                $epofw_cached_settings = array();
            }
            // Store in object cache for 1 hour.
            wp_cache_set(
                'epofw_general_settings_data',
                $epofw_cached_settings,
                'epofw',
                HOUR_IN_SECONDS
            );
        }
    }
    return epofw_check_array_key_exists( $key, $epofw_cached_settings );
}

/**
 * Get cached product terms by taxonomy.
 *
 * Uses static caching to avoid repeated database queries for the same product
 * within a single request. This is particularly useful for conditional logic
 * evaluation where the same product's terms may be checked multiple times.
 *
 * @since 4.4.5
 *
 * @param int    $product_id Product ID.
 * @param string $taxonomy   Taxonomy name ('product_cat' or 'product_tag').
 *
 * @return array Array of term IDs, or empty array if none found.
 */
function epofw_get_cached_product_terms(  $product_id, $taxonomy  ) {
    // Static cache for same-request performance.
    static $epofw_product_terms_cache = array();
    $product_id = absint( $product_id );
    $cache_key = $product_id . '_' . $taxonomy;
    // Check static cache first.
    if ( isset( $epofw_product_terms_cache[$cache_key] ) ) {
        return $epofw_product_terms_cache[$cache_key];
    }
    // Fetch from database.
    $epofw_terms = wp_get_post_terms( $product_id, $taxonomy, array(
        'fields' => 'ids',
    ) );
    // Handle WP_Error or non-array results.
    if ( is_wp_error( $epofw_terms ) || !is_array( $epofw_terms ) ) {
        $epofw_terms = array();
    }
    // Store in static cache.
    $epofw_product_terms_cache[$cache_key] = $epofw_terms;
    return $epofw_terms;
}

/**
 * Get the details title for a specific addon key.
 * This function retrieves the details title for a given addon key, using general settings.
 * If no specific details title is found, it returns a default fallback title.
 *
 * @since 1.0.0
 *
 * @param string $key The addon key for which to retrieve the details title.
 *
 * @return string Details title for the specified addon key, or a default fallback title.
 */
function epofw_get_addon_details_title(  $key  ) {
    $epofw_addon_details_title = epofw_general_settings( $key );
    return ( empty( $epofw_addon_details_title ) ? esc_html__( 'Addon Details', 'extra-product-options-for-woocommerce' ) : $epofw_addon_details_title );
}

/**
 * Get the subtitle title for a specific addon key.
 * This function retrieves the subtitle title for a given addon key.
 *
 * @since 1.0.0
 *
 * @param string $key The addon key for which to retrieve the subtitle title.
 *
 * @return mixed|void Subtitle title for the specified addon key.
 */
function epofw_get_addon_subtitle_title(  $key  ) {
    $epofw_addon_details_subtotal_title = epofw_general_settings( $key );
    return ( empty( $epofw_addon_details_subtotal_title ) ? esc_html__( 'Subtotal', 'extra-product-options-for-woocommerce' ) : $epofw_addon_details_subtotal_title );
}

/**
 * Get the CSS class for a specific title position value.
 *
 * Returns the appropriate CSS class based on the field's Title Position setting.
 * Used for advanced label positioning modes like placeholder, compact_above, etc.
 *
 * @since 4.4.5
 *
 * @param string $title_position The title position value (placeholder, compact_above, caption_below, inline, tooltip).
 *
 * @return string CSS class for the label mode, or empty string for standard positions.
 */
function epofw_get_title_position_class(  $title_position  ) {
    $position_classes = array(
        'placeholder'   => 'epofw-label-placeholder',
        'compact_above' => 'epofw-label-compact-above',
        'caption_below' => 'epofw-label-caption-below',
        'inline'        => 'epofw-label-inline',
        'tooltip'       => 'epofw-label-tooltip',
    );
    return ( isset( $position_classes[$title_position] ) ? $position_classes[$title_position] : '' );
}

/**
 * Check if the title position is an advanced mode that requires special CSS handling.
 *
 * @since 4.4.5
 *
 * @param string $title_position The title position value.
 *
 * @return bool True if it's an advanced position mode.
 */
function epofw_is_advanced_title_position(  $title_position  ) {
    // Currently only 'auto' is an advanced position. More options can be added later.
    return 'auto' === $title_position;
}

/**
 * Get the smart label mode CSS class based on field type.
 *
 * Used when title position is set to 'auto'.
 * Returns appropriate CSS class based on field type for smart label handling.
 *
 * @since 4.4.5
 *
 * @param string $field_type The field type (text, number, checkbox, radio, etc.).
 *
 * @return string CSS class for the label mode.
 */
function epofw_get_smart_label_class(  $field_type  ) {
    /**
     * For now, use compact-above for ALL field types.
     * This ensures labels display properly in multi-column layouts.
     *
     * Future enhancement: Add 'hidden' option for placeholder mode on text fields.
     * Future enhancement: Add 'caption-below' for image/color/text switchers.
     * Future enhancement: Add 'inline' for switch/slider fields.
     */
    return 'epofw-label-compact-above';
}

/**
 * Get a global setting value.
 *
 * Retrieves a specific setting from the global settings option.
 * Global settings serve as defaults for all addon fields.
 *
 * @since 4.4.5
 *
 * @param string $key     The setting key to retrieve.
 * @param mixed  $default Default value if setting is not found.
 *
 * @return mixed The setting value or default.
 */
function epofw_get_global_setting(  $key, $default = ''  ) {
    // Try to get from cache first.
    $global_settings = wp_cache_get( 'epofw_global_settings_data', 'epofw' );
    if ( false === $global_settings ) {
        $global_settings = get_option( 'epofw_global_settings', array() );
        wp_cache_set(
            'epofw_global_settings_data',
            $global_settings,
            'epofw',
            HOUR_IN_SECONDS
        );
    }
    if ( !is_array( $global_settings ) ) {
        $global_settings = array();
    }
    return ( isset( $global_settings[$key] ) && '' !== $global_settings[$key] ? $global_settings[$key] : $default );
}

/**
 * Get the default title position from global settings.
 *
 * @since 4.4.5
 *
 * @return string The default title position, or empty string if not set.
 */
function epofw_get_global_default_title_position() {
    return epofw_get_global_setting( 'default_title_position', '' );
}

/**
 * Check if a settings array contains any non-empty values.
 *
 * @since 4.4.6
 *
 * @param array $settings Settings array.
 *
 * @return bool True if any value is non-empty.
 */
function epofw_has_non_empty_settings_value(  $settings  ) {
    if ( empty( $settings ) || !is_array( $settings ) ) {
        return false;
    }
    foreach ( $settings as $value ) {
        if ( is_array( $value ) ) {
            if ( epofw_has_non_empty_settings_value( $value ) ) {
                return true;
            }
        } elseif ( '' !== $value && null !== $value ) {
            return true;
        }
    }
    return false;
}

/**
 * Merge global defaults with field settings (field overrides when non-empty).
 *
 * @since 4.4.6
 *
 * @param array $epofw_global_settings Global defaults.
 * @param array $epofw_field_settings  Field-specific settings.
 *
 * @return array Merged settings.
 */
function epofw_merge_settings_with_global(  $epofw_global_settings, $epofw_field_settings  ) {
    $epofw_merged = ( is_array( $epofw_global_settings ) ? $epofw_global_settings : array() );
    $epofw_field_settings = ( is_array( $epofw_field_settings ) ? $epofw_field_settings : array() );
    foreach ( $epofw_field_settings as $epofw_key => $epofw_value ) {
        if ( is_array( $epofw_value ) ) {
            $epofw_merged[$epofw_key] = epofw_merge_settings_with_global( ( isset( $epofw_merged[$epofw_key] ) && is_array( $epofw_merged[$epofw_key] ) ? $epofw_merged[$epofw_key] : array() ), $epofw_value );
        } elseif ( '' !== $epofw_value && null !== $epofw_value ) {
            $epofw_merged[$epofw_key] = $epofw_value;
        }
    }
    return $epofw_merged;
}

/**
 * Get global label settings (array).
 *
 * @since 4.4.6
 *
 * @return array Global label defaults.
 */
function epofw_get_global_label_settings() {
    $epofw_label_settings = epofw_get_global_setting( 'label', array() );
    return ( is_array( $epofw_label_settings ) ? $epofw_label_settings : array() );
}

/**
 * Get global field container settings (array).
 *
 * @since 4.4.6
 *
 * @return array Global field container defaults.
 */
function epofw_get_global_field_container_settings() {
    $epofw_field_container_settings = epofw_get_global_setting( 'field_container_settings', array() );
    return ( is_array( $epofw_field_container_settings ) ? $epofw_field_container_settings : array() );
}

/**
 * Filter field container settings to free-safe keys.
 *
 * @since 4.4.6
 *
 * @param array $epofw_settings Field container settings.
 *
 * @return array Filtered settings for free users.
 */
function epofw_filter_field_container_settings_for_free(  $epofw_settings  ) {
    if ( empty( $epofw_settings ) || !is_array( $epofw_settings ) ) {
        return array();
    }
    // Remove premium-only keys.
    $epofw_premium_keys = array(
        'border_color',
        'border_width',
        'border_style',
        'border_radius',
        'width',
        'max_width',
        'min_width',
        'gap',
        'box_shadow'
    );
    foreach ( $epofw_premium_keys as $epofw_key ) {
        if ( isset( $epofw_settings[$epofw_key] ) ) {
            unset($epofw_settings[$epofw_key]);
        }
    }
    // Advanced padding/margin arrays are premium-only.
    if ( isset( $epofw_settings['padding'] ) && is_array( $epofw_settings['padding'] ) ) {
        unset($epofw_settings['padding']);
    }
    if ( isset( $epofw_settings['margin'] ) && is_array( $epofw_settings['margin'] ) ) {
        unset($epofw_settings['margin']);
    }
    return $epofw_settings;
}

/**
 * Get field container settings with global defaults applied.
 *
 * @since 4.4.6
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return array Field container settings.
 */
function epofw_get_field_container_settings_with_global(  $epofw_fields_data  ) {
    $epofw_field_container_settings = ( isset( $epofw_fields_data['field_container_settings'] ) && is_array( $epofw_fields_data['field_container_settings'] ) ? $epofw_fields_data['field_container_settings'] : array() );
    $epofw_global_container_settings = epofw_get_global_field_container_settings();
    $epofw_field_container_settings = epofw_filter_field_container_settings_for_free( $epofw_field_container_settings );
    $epofw_global_container_settings = epofw_filter_field_container_settings_for_free( $epofw_global_container_settings );
    if ( empty( $epofw_global_container_settings ) ) {
        return $epofw_field_container_settings;
    }
    if ( !epofw_has_non_empty_settings_value( $epofw_field_container_settings ) ) {
        return $epofw_global_container_settings;
    }
    return epofw_merge_settings_with_global( $epofw_global_container_settings, $epofw_field_container_settings );
}

/**
 * Function will return title with price.
 *
 * @param array $epofwtwp_args Args of addons.
 * @param array $fields_data   Array of fields data.
 *
 * @return string
 */
function epofw_title_with_price(  $epofwtwp_args, $fields_data  ) {
    $title = ( isset( $epofwtwp_args['opt_label'] ) ? $epofwtwp_args['opt_label'] : '' );
    $opt_price = ( isset( $epofwtwp_args['opt_price'] ) ? $epofwtwp_args['opt_price'] : '' );
    $check_hop = ( isset( $epofwtwp_args['check_hop'] ) ? $epofwtwp_args['check_hop'] : '' );
    /**
     * Filters for bracket before title with price.
     *
     * @since 3.0.0
     */
    $price_sign = apply_filters( 'epofw_open_bracket_before_title_with_price', ' (+' );
    if ( strpos( $opt_price, '(' ) !== false ) {
        /**
         * Filters for bracket before title with price.
         *
         * @since 3.0.0
         */
        $price_sign = apply_filters( 'epofw_open_bracket_before_title_with_price', ' (' );
    }
    /**
     * Filters for option price without wc format and with title.
     *
     * @since 3.0.0
     */
    $opt_price = apply_filters(
        'epofw_opt_price_without_wc_price_format_title_with_price',
        epofw_calculate_price_based_on_condition( $epofwtwp_args, $fields_data ),
        $epofwtwp_args,
        $fields_data
    );
    /**
     * Filters for option price with wc format and with title.
     *
     * @since 3.0.0
     */
    $opt_price = apply_filters( 'epofw_opt_price_with_wc_price_format_title_with_price', wc_price( epofw_price_filter( $opt_price ), $epofwtwp_args ), $fields_data );
    $suffix_html = '';
    /**
     * Filters for close bracket before title with price.
     *
     * @since 3.0.0
     */
    $label_title = $price_sign . $opt_price . $suffix_html . apply_filters( 'epofw_close_bracket_before_title_with_price', ')' );
    if ( $title ) {
        if ( 'on' !== $check_hop ) {
            /**
             * Filters for close bracket before title with price.
             *
             * @since 3.0.0
             */
            $label_title = $title . $price_sign . $opt_price . $suffix_html . apply_filters( 'epofw_close_bracket_before_title_with_price', ')' );
        } else {
            $label_title = $title;
        }
    }
    /**
     * Filters for title with price.
     *
     * @since 3.0.0
     */
    return apply_filters(
        'epofw_title_with_price',
        $label_title,
        $epofwtwp_args,
        $fields_data
    );
}

/**
 * Add a filter for the price.
 * This function applies a filter to modify the provided Extra Product Options for WooCommerce price.
 *
 * @since 1.0.0
 *
 * @param mixed $epofw_price The original Extra Product Options for WooCommerce price.
 *
 * @return mixed|void Modified or unmodified Extra Product Options for WooCommerce price.
 */
function epofw_price_filter(  $epofw_price  ) {
    /**
     * Filters for addon price.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_price_filter', $epofw_price );
}

/**
 * Format the addon price with decimals.
 * This function formats the addon price with the specified decimal precision.
 *
 * @since 2.4.2
 *
 * @param float $addon_price The addon price to be formatted.
 * @param array $egppc_args  Array of fields argument.
 * @param array $fields_data Array of fields data.
 *
 * @return mixed|string Formatted addon price with decimals.
 */
function epofw_format_price_with_decimals(  $addon_price, $egppc_args, $fields_data  ) {
    $opt_price = ( empty( $addon_price ) ? ( !empty( $egppc_args['opt_price'] ) ? $egppc_args['opt_price'] : 0 ) : $addon_price );
    /**
     * Filters for before format price with decimals.
     *
     * @since 3.0.0
     */
    $opt_price = apply_filters(
        'epofw_before_format_price_with_decimals',
        $opt_price,
        $egppc_args,
        $fields_data
    );
    if ( !is_string( $opt_price ) ) {
        $opt_price = number_format(
            $opt_price,
            wc_get_price_decimals(),
            '.',
            ''
        );
    }
    /**
     * Filters to format price with decimals.
     *
     * @since 3.0.0
     */
    return apply_filters(
        'epofw_format_price_with_decimals',
        $opt_price,
        $addon_price,
        $egppc_args,
        $fields_data
    );
}

/**
 * Function will returns the path for overriding the templates.
 *
 * @since 2.5
 * @return string
 */
function epofw_get_template_path() {
    /**
     * Filters to override path.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_template_override_path', EPOFW_TEMPLATE_CONSTANT . '/' );
}

/**
 * Function will returns the default path for the templates.
 *
 * @since 2.5
 * @return string
 */
function epofw_get_default_path() {
    /**
     * Filters to get default template path.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_default_template_path', EPOFW_TEMPLATE_PATH );
}

/**
 * Evaluate a single server-side condition for JavaScript pre-evaluation.
 *
 * This function evaluates conditions that JavaScript cannot handle (like user_role)
 * so we can pass pre-evaluation flags to help JS determine group satisfaction.
 *
 * @since 4.4.0
 *
 * @param string $condition The condition type (user_role, product, category, tag).
 * @param string $operator  The operator (is_equal_to, not_equal_to, etc.).
 * @param array  $value     The condition values to check against.
 *
 * @return bool True if condition is satisfied, false otherwise.
 */
function epofw_evaluate_single_condition_for_js(  $condition, $operator, $value  ) {
    $result = false;
    // Get current product ID.
    global $product;
    $current_product_id = 0;
    if ( $product && is_a( $product, 'WC_Product' ) ) {
        $current_product_id = $product->get_id();
    }
    if ( 'user_role' === $condition ) {
        global $current_user;
        if ( is_user_logged_in() ) {
            $current_user_role = ( isset( $current_user->roles[0] ) ? $current_user->roles[0] : '' );
        } else {
            $current_user_role = 'guest';
        }
        if ( 'is_equal_to' === $operator ) {
            $result = in_array( $current_user_role, (array) $value, true );
        } elseif ( 'not_equal_to' === $operator ) {
            $result = !in_array( $current_user_role, (array) $value, true );
        }
    } elseif ( 'product' === $condition ) {
        if ( 'is_equal_to' === $operator ) {
            $result = in_array( $current_product_id, array_map( 'intval', (array) $value ), true );
        } elseif ( 'not_equal_to' === $operator ) {
            $result = !in_array( $current_product_id, array_map( 'intval', (array) $value ), true );
        }
    } elseif ( 'category' === $condition ) {
        $product_categories = epofw_get_cached_product_terms( $current_product_id, 'product_cat' );
        if ( !empty( $product_categories ) ) {
            $has_match = false;
            foreach ( $product_categories as $cat_id ) {
                if ( in_array( strval( $cat_id ), (array) $value, true ) ) {
                    $has_match = true;
                    break;
                }
            }
            if ( 'is_equal_to' === $operator ) {
                $result = $has_match;
            } elseif ( 'not_equal_to' === $operator ) {
                $result = !$has_match;
            }
        } elseif ( 'not_equal_to' === $operator ) {
            $result = true;
        }
    } elseif ( 'tag' === $condition ) {
        $product_tags = epofw_get_cached_product_terms( $current_product_id, 'product_tag' );
        if ( !empty( $product_tags ) ) {
            $has_match = false;
            foreach ( $product_tags as $tag_id ) {
                if ( in_array( strval( $tag_id ), (array) $value, true ) ) {
                    $has_match = true;
                    break;
                }
            }
            if ( 'is_equal_to' === $operator ) {
                $result = $has_match;
            } elseif ( 'not_equal_to' === $operator ) {
                $result = !$has_match;
            }
        } elseif ( 'not_equal_to' === $operator ) {
            $result = true;
        }
    }
    return $result;
}

/**
 * Attr data for front side.
 *
 * @since 2.5
 *
 * @param array $additional_rule_datas Additional rul dats.
 *
 * @return array
 */
function epofw_data_attr_json(  $additional_rule_datas  ) {
    $data_sub_attr = array();
    $condition_type = 'prd';
    $should_hide_initially = false;
    $data_attr = wp_json_encode( $data_sub_attr );
    return array(
        'data_attr'             => $data_attr,
        'condition_type'        => $condition_type,
        'should_hide_initially' => $should_hide_initially,
    );
}

/**
 * Function will return field title.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_input_property(  $fields_data  ) {
    $field_input_property = ( !empty( $fields_data['field'] ) ? $fields_data['field'] : array() );
    // Add data-default attribute for repeater field support.
    // This allows new repeater instances to restore default values.
    if ( !empty( $field_input_property['value'] ) ) {
        $field_input_property['data-default'] = $field_input_property['value'];
    }
    /**
     * Filters to get field input property.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_input_property', $field_input_property, $fields_data );
}

/**
 * Function will return field title.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_type(  $fields_data  ) {
    $field_type = ( !empty( $fields_data['field']['type'] ) ? $fields_data['field']['type'] : '' );
    /**
     * Filters to get field type.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_type', $field_type, $fields_data );
}

/**
 * Function will return heading type.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_heading_type(  $fields_data  ) {
    $heading_type = ( !empty( $fields_data['field']['heading_type'] ) ? $fields_data['field']['heading_type'] : '' );
    /**
     * Filters to get field heading type.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_heading_type', $heading_type, $fields_data );
}

/**
 * Function will return content.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_content(  $fields_data  ) {
    $content = ( !empty( $fields_data['field']['content'] ) ? $fields_data['field']['content'] : '' );
    /**
     * Filters to get field content.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_content', $content, $fields_data );
}

/**
 * Function will return content type.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_content_type(  $fields_data  ) {
    $content_type = ( !empty( $fields_data['field']['content_type'] ) ? $fields_data['field']['content_type'] : '' );
    /**
     * Filters to get field content type.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_content_type', $content_type, $fields_data );
}

/**
 * Function will return field is required or not.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_required(  $fields_data  ) {
    $required = ( !empty( $fields_data['field']['required'] ) ? $fields_data['field']['required'] : '' );
    /**
     * Filters to get field required.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_required', $required, $fields_data );
}

/**
 * Function will get field label title.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_title(  $epofw_fields_data  ) {
    $epofw_field_label_title = ( !empty( $epofw_fields_data['label']['title'] ) ? $epofw_fields_data['label']['title'] : '' );
    /**
     * Filters to get field label title.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_title', $epofw_field_label_title, $epofw_fields_data );
}

/**
 * Function will get field label class.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_class(  $epofw_fields_data  ) {
    $epofw_field_label_class = ( !empty( $epofw_fields_data['label']['class'] ) ? $epofw_fields_data['label']['class'] : '' );
    if ( empty( $epofw_field_label_class ) ) {
        $epofw_global_label_settings = epofw_get_global_label_settings();
        $epofw_field_label_class = ( !empty( $epofw_global_label_settings['class'] ) ? $epofw_global_label_settings['class'] : '' );
    }
    /**
     * Filters to get field label class.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_class', $epofw_field_label_class, $epofw_fields_data );
}

/**
 * Function will get field label title type.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_title_type(  $epofw_fields_data  ) {
    $epofw_field_label_title_type = ( isset( $epofw_fields_data['label']['title_type'] ) ? $epofw_fields_data['label']['title_type'] : '' );
    $epofw_field_label_title_type = ( is_string( $epofw_field_label_title_type ) ? strtolower( trim( $epofw_field_label_title_type ) ) : '' );
    // If field value is empty ("Use Global"), use global setting.
    if ( empty( $epofw_field_label_title_type ) ) {
        $epofw_global_label_settings = epofw_get_global_label_settings();
        $epofw_global_title_type = ( !empty( $epofw_global_label_settings['title_type'] ) ? $epofw_global_label_settings['title_type'] : '' );
        $epofw_field_label_title_type = ( is_string( $epofw_global_title_type ) ? strtolower( trim( $epofw_global_title_type ) ) : '' );
    }
    // Final fallback to 'span' if still empty.
    if ( empty( $epofw_field_label_title_type ) ) {
        $epofw_field_label_title_type = 'span';
    }
    /**
     * Filters to get field label title type.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_title_type', $epofw_field_label_title_type, $epofw_fields_data );
}

/**
 * Function will get field label subtitle class.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_subtitle_class(  $epofw_fields_data  ) {
    $epofw_field_label_subtitle_class = ( !empty( $epofw_fields_data['label']['subtitle_class'] ) ? $epofw_fields_data['label']['subtitle_class'] : '' );
    if ( empty( $epofw_field_label_subtitle_class ) ) {
        $epofw_global_label_settings = epofw_get_global_label_settings();
        $epofw_field_label_subtitle_class = ( !empty( $epofw_global_label_settings['subtitle_class'] ) ? $epofw_global_label_settings['subtitle_class'] : '' );
    }
    /**
     * Filters to get field label subtitle class.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_subtitle_class', $epofw_field_label_subtitle_class, $epofw_fields_data );
}

/**
 * Function will get field label subtitle.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_subtitle(  $epofw_fields_data  ) {
    $epofw_field_label_subtitle = ( !empty( $epofw_fields_data['label']['subtitle'] ) ? $epofw_fields_data['label']['subtitle'] : '' );
    /**
     * Filters to get field label subtitle.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_subtitle', $epofw_field_label_subtitle, $epofw_fields_data );
}

/**
 * Function will get field label subtitle type.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_subtitle_type(  $epofw_fields_data  ) {
    $epofw_field_label_subtitle_type = ( isset( $epofw_fields_data['label']['subtitle_type'] ) ? $epofw_fields_data['label']['subtitle_type'] : '' );
    $epofw_field_label_subtitle_type = ( is_string( $epofw_field_label_subtitle_type ) ? strtolower( trim( $epofw_field_label_subtitle_type ) ) : '' );
    // If field value is empty ("Use Global"), use global setting.
    if ( empty( $epofw_field_label_subtitle_type ) ) {
        $epofw_global_label_settings = epofw_get_global_label_settings();
        $epofw_global_subtitle_type = ( !empty( $epofw_global_label_settings['subtitle_type'] ) ? $epofw_global_label_settings['subtitle_type'] : '' );
        $epofw_field_label_subtitle_type = ( is_string( $epofw_global_subtitle_type ) ? strtolower( trim( $epofw_global_subtitle_type ) ) : '' );
    }
    // Final fallback to 'span' if still empty.
    if ( empty( $epofw_field_label_subtitle_type ) ) {
        $epofw_field_label_subtitle_type = 'span';
    }
    /**
     * Filters to field label subtitle type.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_subtitle_type', $epofw_field_label_subtitle_type, $epofw_fields_data );
}

/**
 * Function will get field label title position.
 *
 * @since 2.5
 *
 * @param array $epofw_fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_label_title_position(  $epofw_fields_data  ) {
    return 'left';
    // Check global setting first - if set to 'auto', it overrides all field settings.
    $epofw_global_settings = get_option( 'epofw_global_settings', array() );
    $epofw_global_title_position = ( !empty( $epofw_global_settings['default_title_position'] ) ? $epofw_global_settings['default_title_position'] : '' );
    // If global is set to 'auto', override all fields with auto.
    if ( 'auto' === $epofw_global_title_position ) {
        $epofw_field_label_title_position = 'auto';
    } else {
        // Otherwise, use field-level setting or fall back to global/default.
        $epofw_field_label_title_position = ( !empty( $epofw_fields_data['label']['title_position'] ) ? $epofw_fields_data['label']['title_position'] : '' );
        if ( empty( $epofw_field_label_title_position ) ) {
            $epofw_field_label_title_position = ( !empty( $epofw_global_title_position ) ? $epofw_global_title_position : 'left' );
        }
    }
    /**
     * Filters to get field label title position.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_label_title_position', $epofw_field_label_title_position, $epofw_fields_data );
}

/**
 * Function will get field settings.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_settings(  $fields_data  ) {
    $field_settings = ( !empty( $fields_data['epofw_field_settings'] ) ? $fields_data['epofw_field_settings'] : '' );
    /**
     * Filters to get field settings.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_settings', $field_settings, $fields_data );
}

/**
 * Function will check field restriction is enable or not for text.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_field_restriction(  $fields_data  ) {
    $field_restriction = ( !empty( $fields_data['epofw_field_settings']['field_restriction'] ) ? $fields_data['epofw_field_settings']['field_restriction'] : '' );
    /**
     * Filters to get field restriction.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_field_restriction', $field_restriction, $fields_data );
}

/**
 * Function will check addon price is enable or not.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_check_enable_price_extra(  $fields_data  ) {
    $enable_price_extra = ( !empty( $fields_data['epofw_field_settings']['enable_price_extra'] ) ? $fields_data['epofw_field_settings']['enable_price_extra'] : '' );
    /**
     * Filters to check enable extra price.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_check_enable_price_extra', $enable_price_extra, $fields_data );
}

/**
 * Function will get addon price type.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_addon_price_type(  $fields_data  ) {
    $addon_price_type = ( !empty( $fields_data['epofw_field_settings']['addon_price_type'] ) ? $fields_data['epofw_field_settings']['addon_price_type'] : '' );
    /**
     * Filters for get addon price type.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_addon_price_type', $addon_price_type, $fields_data );
}

/**
 * Function will get addon price.
 *
 * @since 2.5
 *
 * @param array $fields_data Field data.
 *
 * @return mixed|void
 */
function epofw_get_addon_price(  $fields_data  ) {
    $addon_price = ( !empty( $fields_data['epofw_field_settings']['addon_price'] ) ? $fields_data['epofw_field_settings']['addon_price'] : 0 );
    /**
     * Filters for get addon price.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_get_addon_price', $addon_price, $fields_data );
}

/**
 * Evaluate conditional logic server-side to validate field visibility.
 *
 * This function checks if a field should be visible based on its conditional
 * logic rules and the submitted POST data. Used for server-side validation
 * to prevent bypassing client-side display:none.
 *
 * @since 4.4.5
 *
 * @param array $field_data The field configuration data.
 * @param array $post_data  The submitted POST data.
 * @param array $all_fields All fields configuration for reference.
 *
 * @return bool True if field should be processed, false if it should be ignored.
 */
function epofw_evaluate_conditional_logic_server_side(  $field_data, $post_data, $all_fields = array()  ) {
    // Check if conditional logic is enabled for this field.
    $check_enable_cl = epofw_check_logical_operation__premium_only( $field_data );
    if ( empty( $check_enable_cl ) || 'on' !== $check_enable_cl ) {
        // No conditional logic - field should be processed.
        return true;
    }
    // Get the conditional logic rules.
    $logical_operations = epofw_get_logical_operation__premium_only( $field_data );
    if ( empty( $logical_operations ) ) {
        // No conditions defined - field should be processed.
        return true;
    }
    // Evaluate each condition (AND logic - all must pass).
    foreach ( $logical_operations as $condition ) {
        $parent_field_name = ( isset( $condition['epofw_conditional_logic_options'] ) ? $condition['epofw_conditional_logic_options'] : '' );
        $operator = ( isset( $condition['epofw_conditional_logic_operator'] ) ? $condition['epofw_conditional_logic_operator'] : 'equal_to' );
        $expected_value = ( isset( $condition['epofw_conditional_logic_fields'] ) ? $condition['epofw_conditional_logic_fields'] : '' );
        if ( empty( $parent_field_name ) ) {
            continue;
        }
        // Get the parent field value from POST data.
        $parent_value = epofw_get_parent_field_value_from_post( $parent_field_name, $post_data );
        // Evaluate the condition.
        $condition_met = epofw_evaluate_single_condition( $parent_value, $operator, $expected_value );
        if ( !$condition_met ) {
            // Condition not met - field should be hidden/ignored.
            return false;
        }
    }
    // All conditions met - field should be processed.
    return true;
}

/**
 * Get parent field value from POST data.
 *
 * @since 4.4.5
 *
 * @param string $field_name The field name to look for.
 * @param array  $post_data  The submitted POST data.
 *
 * @return string The field value or empty string if not found.
 */
function epofw_get_parent_field_value_from_post(  $field_name, $post_data  ) {
    // Try direct match first.
    if ( isset( $post_data[$field_name] ) ) {
        $value = $post_data[$field_name];
        if ( is_array( $value ) && isset( $value['value'] ) ) {
            return ( is_array( $value['value'] ) ? implode( ',', $value['value'] ) : $value['value'] );
        }
        return ( is_array( $value ) ? implode( ',', $value ) : $value );
    }
    // Try with epofw_field_ prefix.
    $prefixed_name = 'epofw_field_' . $field_name;
    if ( isset( $post_data[$prefixed_name] ) ) {
        $value = $post_data[$prefixed_name];
        if ( is_array( $value ) && isset( $value['value'] ) ) {
            return ( is_array( $value['value'] ) ? implode( ',', $value['value'] ) : $value['value'] );
        }
        return ( is_array( $value ) ? implode( ',', $value ) : $value );
    }
    // Try without epofw_field_ prefix if it has one.
    if ( strpos( $field_name, 'epofw_field_' ) === 0 ) {
        $unprefixed_name = str_replace( 'epofw_field_', '', $field_name );
        if ( isset( $post_data[$unprefixed_name] ) ) {
            $value = $post_data[$unprefixed_name];
            if ( is_array( $value ) && isset( $value['value'] ) ) {
                return ( is_array( $value['value'] ) ? implode( ',', $value['value'] ) : $value['value'] );
            }
            return ( is_array( $value ) ? implode( ',', $value ) : $value );
        }
    }
    return '';
}

/**
 * Evaluate a single conditional logic condition.
 *
 * @since 4.4.5
 *
 * @param string $field_value    The actual field value from POST.
 * @param string $operator       The comparison operator.
 * @param string $expected_value The expected value for the condition.
 *
 * @return bool True if condition is met, false otherwise.
 */
function epofw_evaluate_single_condition(  $field_value, $operator, $expected_value  ) {
    // Normalize values for comparison.
    $field_value = trim( (string) $field_value );
    $expected_value = trim( (string) $expected_value );
    switch ( $operator ) {
        case 'equal_to':
        case 'is':
            return $field_value === $expected_value;
        case 'not_equal_to':
        case 'is_not':
            return $field_value !== $expected_value;
        case 'is_empty':
            return empty( $field_value );
        case 'is_not_empty':
            return !empty( $field_value );
        case 'contains':
            return strpos( $field_value, $expected_value ) !== false;
        case 'not_contains':
            return strpos( $field_value, $expected_value ) === false;
        case 'less_then':
        case 'less_than':
            return is_numeric( $field_value ) && is_numeric( $expected_value ) && floatval( $field_value ) < floatval( $expected_value );
        case 'less_equal_to':
        case 'less_than_or_equal':
            return is_numeric( $field_value ) && is_numeric( $expected_value ) && floatval( $field_value ) <= floatval( $expected_value );
        case 'greater_then':
        case 'greater_than':
            return is_numeric( $field_value ) && is_numeric( $expected_value ) && floatval( $field_value ) > floatval( $expected_value );
        case 'greater_equal_to':
        case 'greater_than_or_equal':
            return is_numeric( $field_value ) && is_numeric( $expected_value ) && floatval( $field_value ) >= floatval( $expected_value );
        default:
            // Unknown operator - default to true (don't block).
            return true;
    }
}

/**
 * Function will return all field attr as json.
 *
 * @since 2.5
 *
 * @param array $args           {
 *                              The array of data.
 *
 * @type object    $product_data   Data of the current product.
 * @type int|float $product_price  Product price.
 * @type string    $opt_price_type Price type - Fixed, Percentage of product price or etc.
 * @type string    $epofw_action   Page action - shop|product page.
 * @type array     $fields_data    Array of addon data.
 *                                 }
 * @return mixed|void
 */
function epofw_all_fields_attr(  $args  ) {
    $field_attr_atrr = array();
    $fields_data = ( isset( $args['epofwtwp_args']['fields_data'] ) ? $args['epofwtwp_args']['fields_data'] : array() );
    $check_field_required = epofw_get_field_required( $fields_data );
    $required_data['rd_status'] = $check_field_required;
    $field_attr_atrr['required'] = $required_data;
    $check_epofw_field_settings = epofw_get_field_settings( $fields_data );
    $check_field_lbl_title = epofw_get_field_label_title( $fields_data );
    $check_field_inp_type = epofw_get_field_type( $fields_data );
    if ( !empty( $check_epofw_field_settings ) ) {
        foreach ( $check_epofw_field_settings as $key => $value ) {
            if ( !empty( $value ) ) {
                $field_attr_atrr[$key] = $value;
            }
            if ( 'options' === $key ) {
                $new_options = array();
                foreach ( $value as $opt_key => $opt_value ) {
                    // Handle both formats: array object (new) and pipe-separated string (legacy).
                    if ( is_array( $opt_value ) ) {
                        // NEW FORMAT: Array with label, price_type, price keys.
                        $opt_value_ex = array(( isset( $opt_value['label'] ) ? $opt_value['label'] : '' ), ( isset( $opt_value['price_type'] ) ? $opt_value['price_type'] : 'fixed' ), ( isset( $opt_value['price'] ) ? $opt_value['price'] : '0' ));
                        // Handle switcher value (color, text, image URL).
                        if ( isset( $opt_value['swticher'] ) ) {
                            $opt_value_ex[3] = $opt_value['swticher'];
                        }
                    } else {
                        // LEGACY FORMAT: Pipe-separated string "Label||type||price".
                        $opt_value_ex = explode( '||', $opt_value );
                    }
                    if ( isset( $opt_value_ex[1] ) && isset( $opt_value_ex[2] ) ) {
                        $args['epofwtwp_args']['opt_price'] = $opt_value_ex[2];
                        $args['epofwtwp_args']['opt_price_type'] = $opt_value_ex[1];
                        // Get price.
                        $opt_value_ex[2] = epofw_price_filter( epofw_calculate_price_based_on_condition( $args['epofwtwp_args'], $fields_data ) );
                    }
                    // Store by label (index 0).
                    $new_options[$opt_value_ex[0]] = $opt_value_ex;
                    // Also store by switcher value if present, for colorswitcher/textswitcher/imageswitcher lookup.
                    if ( isset( $opt_value_ex[3] ) && !empty( $opt_value_ex[3] ) ) {
                        $new_options[$opt_value_ex[3]] = $opt_value_ex;
                    }
                }
                $field_attr_atrr[$key] = $new_options;
            }
        }
    }
    $field_attr_atrr['data-label-name'] = $check_field_lbl_title;
    $check_field_restriction = epofw_get_field_restriction( $fields_data );
    if ( $check_field_restriction ) {
        unset($check_epofw_field_settings['field_restriction']);
    }
    $field_attr_atrr['data_inp_type'] = $check_field_inp_type;
    $check_field_enable_price = epofw_check_enable_price_extra( $fields_data );
    $check_field_addon_price_type = epofw_get_addon_price_type( $fields_data );
    $check_field_addon_price = epofw_get_addon_price( $fields_data );
    $field_attr_atrr['enable_price_extra'] = $check_field_enable_price;
    if ( 'on' === $check_field_enable_price ) {
        $field_attr_atrr['addon_price_type'] = $check_field_addon_price_type;
        $args['epofwtwp_args']['opt_price'] = $check_field_addon_price;
        $args['epofwtwp_args']['opt_price_type'] = $check_field_addon_price_type;
        // Get price.
        $field_attr_atrr['addon_price'] = epofw_price_filter( epofw_calculate_price_based_on_condition( $args['epofwtwp_args'], $fields_data ) );
    } else {
        $field_attr_atrr['addon_price_type'] = 'fixed';
        $field_attr_atrr['addon_price'] = 0.0;
    }
    // Ensure default date_format for datepicker fields.
    if ( 'datepicker' === $check_field_inp_type && empty( $field_attr_atrr['date_format'] ) ) {
        $field_attr_atrr['date_format'] = 'dd-mm-yy';
    }
    // Ensure default time_format for timepicker fields.
    if ( 'timepicker' === $check_field_inp_type && empty( $field_attr_atrr['epofw_tf'] ) ) {
        $field_attr_atrr['epofw_tf'] = 'hh:mm tt';
    }
    /**
     * Filters for All fields attribute.
     *
     * @since 3.0.0
     */
    return apply_filters( 'epofw_all_fields_attr', wp_json_encode( array($field_attr_atrr) ), $args );
}

/**
 * Function will return tag based on structure.
 *
 * @since 2.5
 * @since 4.4.0 Table structure has been removed. Always returns 'div' now.
 *
 * @param string $default_display_tag Tag name (unused, kept for backward compatibility).
 *
 * @return string Always returns 'div'.
 */
function epofw_get_tag_based_on_structure(  $default_display_tag  ) {
    return 'div';
}

/**
 * Function will return product prices with tax and without tax array based on cart item.
 *
 * @since 2.5
 *
 * @param array $cart_item_data Array of cart item data.
 *
 * @return array
 */
function epofw_product_price(  $cart_item_data  ) {
    $price = wc_get_price_to_display( $cart_item_data['data'], array(
        'price' => $cart_item_data['data']->get_price( 'edit' ),
    ) );
    $regular_price = wc_get_price_to_display( $cart_item_data['data'], array(
        'price' => $cart_item_data['data']->get_regular_price( 'edit' ),
    ) );
    $sale_price = wc_get_price_to_display( $cart_item_data['data'], array(
        'price' => $cart_item_data['data']->get_sale_price( 'edit' ),
    ) );
    return array(
        'price'                     => (float) $price,
        'regular_price'             => (float) $regular_price,
        'sale_price'                => (float) $sale_price,
        'price_without_tax'         => (float) wc_get_price_excluding_tax( $cart_item_data['data'] ),
        'regular_price_without_tax' => (float) wc_get_price_excluding_tax( $cart_item_data['data'], array(
            'price' => $cart_item_data['data']->get_regular_price( 'edit' ),
        ) ),
        'sale_price_without_tax'    => (float) wc_get_price_excluding_tax( $cart_item_data['data'], array(
            'price' => $cart_item_data['data']->get_sale_price( 'edit' ),
        ) ),
    );
}

/**
 * Recursively sanitize an array of data.
 *
 * @since 4.4.5
 *
 * @param array|string $data Array of data or input.
 *
 * @return array|string Sanitized data.
 */
function epofw_sanitize_array(  &$data  ) {
    foreach ( $data as &$value ) {
        if ( !is_array( $value ) ) {
            // Sanitize if value is not an array.
            $value = sanitize_text_field( wp_unslash( $value ) );
        } else {
            // Recursively sanitize nested arrays.
            epofw_sanitize_array( $value );
        }
    }
    return $data;
}

/**
 * Recursively sanitize an array of data.
 *
 * @since 1.0.0
 * @deprecated 4.4.5 Use epofw_sanitize_array() instead.
 *
 * @param array|string $data Array of data or input.
 *
 * @return array|string Sanitized data.
 */
function sanitize_array(  &$data  ) {
    _deprecated_function( __FUNCTION__, '4.4.5', 'epofw_sanitize_array' );
    return epofw_sanitize_array( $data );
}

/**
 * Function will return conditional operator.
 *
 * @since 4.3.3
 *
 * @param string $field_type The field type.
 *
 * @return array Conditional operator.
 */
function epofw_field_conditional_operator(  $field_type = ''  ) {
    $conditional_op_arr = array(
        'equal_to'     => esc_html__( 'Equal To', 'extra-product-options-for-woocommerce' ),
        'not_equal_to' => esc_html__( 'Not Equal To', 'extra-product-options-for-woocommerce' ),
    );
    if ( 'number' === $field_type || 'slider' === $field_type ) {
        $conditional_op_arr['less_then'] = esc_html__( 'Less Then', 'extra-product-options-for-woocommerce' );
        $conditional_op_arr['less_equal_to'] = esc_html__( 'Less Then Equal To', 'extra-product-options-for-woocommerce' );
        $conditional_op_arr['greater_then'] = esc_html__( 'Greater Then', 'extra-product-options-for-woocommerce' );
        $conditional_op_arr['greater_equal_to'] = esc_html__( 'Greater Then Equal to', 'extra-product-options-for-woocommerce' );
    }
    return $conditional_op_arr;
}

/**
 * Get field type with icon and description.
 *
 * @since 4.4.0
 *
 * @return array Field type with icon and description.
 */
function epofw_get_field_type_with_icon_and_description() {
    $field_type_arr = epofw_field_type_options_data();
    foreach ( $field_type_arr as $field_type => $type_name ) {
        // Get icon and description based on field type.
        $icon_class = '';
        $description = '';
        if ( 'text' === $field_type ) {
            $icon_class = 'dashicons-editor-textcolor';
            $description = esc_html__( 'Single line text input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'hidden' === $field_type ) {
            $icon_class = 'dashicons-hidden';
            $description = esc_html__( 'Hidden field input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'number' === $field_type ) {
            $icon_class = 'dashicons-calculator';
            $description = esc_html__( 'Numeric input with validation', 'extra-product-options-for-woocommerce' );
        } elseif ( 'password' === $field_type ) {
            $icon_class = 'dashicons-lock';
            $description = esc_html__( 'Secure password input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'textarea' === $field_type ) {
            $icon_class = 'dashicons-editor-paragraph';
            $description = esc_html__( 'Multi-line text input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'select' === $field_type ) {
            $icon_class = 'dashicons-list-view';
            $description = esc_html__( 'Dropdown selection', 'extra-product-options-for-woocommerce' );
        } elseif ( 'multiselect' === $field_type ) {
            $icon_class = 'dashicons-list-view';
            $description = esc_html__( 'Multiple choice selection', 'extra-product-options-for-woocommerce' );
        } elseif ( 'checkbox' === $field_type ) {
            $icon_class = 'dashicons-yes';
            $description = esc_html__( 'Single checkbox option', 'extra-product-options-for-woocommerce' );
        } elseif ( 'checkboxgroup' === $field_type ) {
            $icon_class = 'dashicons-yes-alt';
            $description = esc_html__( 'Multiple choice checkboxes', 'extra-product-options-for-woocommerce' );
        } elseif ( 'radiogroup' === $field_type ) {
            $icon_class = 'dashicons-marker';
            $description = esc_html__( 'Single choice from options', 'extra-product-options-for-woocommerce' );
        } elseif ( 'datepicker' === $field_type ) {
            $icon_class = 'dashicons-calendar-alt';
            $description = esc_html__( 'Date picker input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'timepicker' === $field_type ) {
            $icon_class = 'dashicons-clock';
            $description = esc_html__( 'Time picker input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'colorpicker' === $field_type ) {
            $icon_class = 'dashicons-art';
            $description = esc_html__( 'Color selection tool', 'extra-product-options-for-woocommerce' );
        } elseif ( 'switch' === $field_type ) {
            $icon_class = 'dashicons-toggle-on';
            $description = esc_html__( 'Toggle switch input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'heading' === $field_type ) {
            $icon_class = 'dashicons-heading';
            $description = esc_html__( 'Section heading display', 'extra-product-options-for-woocommerce' );
        } elseif ( 'paragraph' === $field_type ) {
            $icon_class = 'dashicons-text';
            $description = esc_html__( 'Text paragraph display', 'extra-product-options-for-woocommerce' );
        } elseif ( 'separator' === $field_type ) {
            $icon_class = 'dashicons-minus';
            $description = esc_html__( 'Horizontal divider line', 'extra-product-options-for-woocommerce' );
        } elseif ( 'slider' === $field_type ) {
            $icon_class = 'dashicons-slides';
            $description = esc_html__( 'Range slider input', 'extra-product-options-for-woocommerce' );
        } elseif ( 'file' === $field_type ) {
            $icon_class = 'dashicons-upload';
            $description = esc_html__( 'Single file upload', 'extra-product-options-for-woocommerce' );
        } elseif ( 'colorswitcher' === $field_type ) {
            $icon_class = 'dashicons-palette';
            $description = esc_html__( 'Color switching options', 'extra-product-options-for-woocommerce' );
        } elseif ( 'textswitcher' === $field_type ) {
            $icon_class = 'dashicons-editor-textcolor';
            $description = esc_html__( 'Text switching options', 'extra-product-options-for-woocommerce' );
        } elseif ( 'imageswitcher' === $field_type ) {
            $icon_class = 'dashicons-format-image';
            $description = esc_html__( 'Image switching options', 'extra-product-options-for-woocommerce' );
        } elseif ( 'productgroup' === $field_type ) {
            $icon_class = 'dashicons-cart';
            $description = esc_html__( 'Let customers pick linked WooCommerce products with quantities.', 'extra-product-options-for-woocommerce' );
        }
        // Check if field type is premium (Pro badge + masking use this; see epofw_get_premium_field_types()).
        $premium_field_types = epofw_get_premium_field_types();
        $is_premium = in_array( $field_type, $premium_field_types, true );
        $field_type_arr[$field_type] = array(
            'icon'        => $icon_class,
            'description' => $description,
            'name'        => $type_name,
            'is_premium'  => $is_premium,
        );
    }
    /**
     * Filter field types with icons and descriptions.
     *
     * Allows modules to add custom field types with their icons and descriptions.
     *
     * @since 4.4.5
     *
     * @param array $field_type_arr Array of field types with icon, description, name, and is_premium.
     */
    return apply_filters( 'epofw_field_types_with_icons', $field_type_arr );
}

/**
 * Render section wrapper with styling (div structure)
 *
 * @param string $section_title The section title.
 * @param array  $section_settings The section settings.
 * @param string $section_id The section ID.
 */
function epofw_render_section_wrapper(  $section_title, $section_settings, $section_id  ) {
    // Check if section label (title & description) should be displayed.
    // Only show label if explicitly set to 'on' (checkbox checked).
    $show_label_setting = ( isset( $section_settings['show_label'] ) ? $section_settings['show_label'] : '' );
    $show_label = ( 'on' === $show_label_setting ? 'on' : 'off' );
    // Check if user is premium (for premium-only features).
    $is_premium_user = false;
    // Use centralized style helper function for section styles (returns class name).
    // Pass premium status to filter premium-only styles.
    $section_style_class = epofw_generate_section_styles( $section_settings, $is_premium_user );
    $section_class = 'epofw-section-wrapper';
    if ( !empty( $section_style_class ) ) {
        $section_class .= ' ' . esc_attr( $section_style_class );
    }
    /**
     * Filter section wrapper CSS classes.
     *
     * @since 4.4.5
     *
     * @param string $section_class    Section CSS classes.
     * @param string $section_id       Section ID.
     * @param array  $section_settings Section settings array.
     */
    $section_class = apply_filters(
        'epofw_section_wrapper_class',
        $section_class,
        $section_id,
        $section_settings
    );
    /**
     * Filter section wrapper HTML attributes.
     *
     * Allows modules to add custom data-* attributes for functionality like repeater.
     *
     * @since 4.4.5
     *
     * @param array  $attributes       Array of attribute => value pairs.
     * @param string $section_id       Section ID.
     * @param array  $section_settings Section settings array.
     */
    $section_attributes = apply_filters(
        'epofw_section_wrapper_attributes',
        array(),
        $section_id,
        $section_settings
    );
    // Build attributes string.
    $attributes_string = '';
    if ( !empty( $section_attributes ) && is_array( $section_attributes ) ) {
        foreach ( $section_attributes as $attr_name => $attr_value ) {
            $attributes_string .= ' ' . esc_attr( $attr_name ) . '="' . esc_attr( $attr_value ) . '"';
        }
    }
    // Render section wrapper.
    $section_selector = '#epofw-section-' . esc_attr( $section_id );
    // Output custom CSS for section if available (PREMIUM ONLY).
    if ( $is_premium_user ) {
        $section_custom_css = ( !empty( $section_settings['custom_css'] ) ? $section_settings['custom_css'] : '' );
        if ( !empty( $section_custom_css ) ) {
            echo epofw_output_custom_css( $section_custom_css, $section_selector );
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        }
    }
    /**
     * Fires before section wrapper is rendered.
     *
     * @since 4.4.5
     *
     * @param string $section_id       Section ID.
     * @param array  $section_settings Section settings array.
     * @param string $section_title    Section title.
     */
    do_action(
        'epofw_before_section_wrapper',
        $section_id,
        $section_settings,
        $section_title
    );
    ?>
	<div class="<?php 
    echo esc_attr( $section_class );
    ?>" id="epofw-section-<?php 
    echo esc_attr( $section_id );
    ?>"<?php 
    echo $attributes_string;
    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    ?>>
		<?php 
    /**
     * Action after section wrapper opens, before label check.
     *
     * Useful for accordion/tabs headers that need to render regardless of show_label setting.
     *
     * @since 4.4.6
     *
     * @param string $section_id       Section ID.
     * @param string $section_title    Section title.
     * @param array  $section_settings Section settings array.
     * @param string $show_label       Whether label is shown ('on' or 'off').
     */
    do_action(
        'epofw_section_wrapper_start',
        $section_id,
        $section_title,
        $section_settings,
        $show_label
    );
    ?>
		<?php 
    if ( 'on' === $show_label && !empty( $section_title ) ) {
        ?>
			<?php 
        // Use centralized style helper function for title styles (FREE - title color & alignment).
        $title_style_attr = epofw_generate_section_title_styles( $section_settings );
        // Ensure it's a string (not an array) for wp_kses().
        if ( !is_string( $title_style_attr ) ) {
            $title_style_attr = '';
        }
        // Title type (FREE).
        $title_type = ( !empty( $section_settings['title_type'] ) ? $section_settings['title_type'] : 'h3' );
        // Description (PREMIUM ONLY).
        $description = '';
        $description_style_attr = '';
        if ( $is_premium_user ) {
            $description = ( !empty( $section_settings['description'] ) ? $section_settings['description'] : '' );
            // Use centralized style helper function for description styles.
            $description_style_attr = epofw_generate_section_description_styles( $section_settings );
            // Ensure it's a string (not an array) for wp_kses().
            if ( !is_string( $description_style_attr ) ) {
                $description_style_attr = '';
            }
        }
        ?>
			<?php 
        // Build the title HTML.
        ob_start();
        ?>
			<<?php 
        echo esc_attr( $title_type );
        ?> class="epofw-section-title"<?php 
        echo $title_style_attr;
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Sanitized by epofw_generate_section_title_styles().
        ?>>
				<?php 
        echo esc_html( $section_title );
        ?>
			</<?php 
        echo esc_attr( $title_type );
        ?>>
			<?php 
        $title_html = ob_get_clean();
        /**
         * Filter section title HTML.
         *
         * Allows modules to wrap/modify the section title HTML (e.g., for accordion header).
         *
         * @since 4.4.6
         *
         * @param string $title_html        The HTML markup for the section title.
         * @param string $section_id        Section ID.
         * @param string $section_title     Section title text.
         * @param array  $section_settings  Section settings array.
         */
        $title_html = apply_filters(
            'epofw_section_title_html',
            $title_html,
            $section_id,
            $section_title,
            $section_settings
        );
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Title HTML is built with proper escaping above.
        echo $title_html;
        /**
         * Action after section title.
         *
         * Allows modules to add content after the section title (e.g., accordion toggle icon).
         *
         * @since 4.4.6
         *
         * @param string $section_id       Section ID.
         * @param array  $section_settings Section settings array.
         */
        do_action( 'epofw_section_title_after', $section_id, $section_settings );
        ?>
			<?php 
        if ( $is_premium_user && !empty( $description ) ) {
            ?>
				<?php 
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Sanitized by epofw_generate_section_description_styles().
            ?>
				<div class="epofw-section-description"<?php 
            echo $description_style_attr;
            ?>>
					<?php 
            echo esc_html( $description );
            ?>
				</div>
			<?php 
        }
        ?>
		<?php 
    }
    ?>
		<div class="epofw-section-fields">
	<?php 
}

/**
 * Close section wrapper
 *
 * @param string $section_id       The section ID.
 * @param array  $section_settings The section settings.
 */
function epofw_close_section_wrapper(  $section_id = '', $section_settings = array()  ) {
    ?>
		</div>
	<?php 
    /**
     * Fires after section fields, before closing wrapper.
     *
     * Useful for adding repeater add/remove buttons.
     *
     * @since 4.4.5
     *
     * @param string $section_id       Section ID.
     * @param array  $section_settings Section settings array.
     */
    do_action( 'epofw_section_wrapper_footer', $section_id, $section_settings );
    ?>
	</div>
	<?php 
    /**
     * Fires after section wrapper is closed.
     *
     * @since 4.4.5
     *
     * @param string $section_id       Section ID.
     * @param array  $section_settings Section settings array.
     */
    do_action( 'epofw_after_section_wrapper', $section_id, $section_settings );
}

/**
 * Group fields by row_association for admin display.
 *
 * @since 4.4.0
 *
 * @param array $fields Array of fields to group.
 * @return array Grouped fields by row_groups and columns_groups.
 */
function epofw_group_fields_by_rows_admin(  $fields  ) {
    $grouped_fields = array(
        'row_based' => array(),
    );
    if ( empty( $fields ) || !is_array( $fields ) ) {
        return $grouped_fields;
    }
    foreach ( $fields as $field_id => $field_data ) {
        if ( !isset( $field_data['row_association'] ) || !is_array( $field_data['row_association'] ) ) {
            continue;
        }
        $row_assoc = $field_data['row_association'];
        $group_id = $row_assoc['group_id'];
        $row_id = $row_assoc['row_id'];
        $columns_id = ( isset( $row_assoc['columns_id'] ) ? $row_assoc['columns_id'] : null );
        // Initialize group if not exists.
        if ( !isset( $grouped_fields['row_based'][$group_id] ) ) {
            $grouped_fields['row_based'][$group_id] = array(
                'layout_type'    => 'single',
                'rows_count'     => 1,
                'columns_groups' => array(),
            );
        }
        // Use columns_groups structure.
        if ( $columns_id ) {
            // Initialize columns_group if not exists.
            if ( !isset( $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id] ) ) {
                $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id] = array(
                    'columns_id'    => $columns_id,
                    'columns_order' => ( isset( $row_assoc['columns_order'] ) ? (int) $row_assoc['columns_order'] : 0 ),
                    'layout_type'   => 'single',
                    'columns_count' => 0,
                    'rows'          => array(),
                );
            }
            // Initialize row (column) within columns_group if not exists.
            if ( !isset( $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['rows'][$row_id] ) ) {
                $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['rows'][$row_id] = array(
                    'row_number' => count( $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['rows'] ) + 1,
                    'fields'     => array(),
                );
                // Update columns_count and layout_type.
                $col_count = count( $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['rows'] );
                $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['columns_count'] = $col_count;
                // Set layout_type based on column count.
                if ( 2 === (int) $col_count ) {
                    $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['layout_type'] = 'two';
                } elseif ( 3 === (int) $col_count ) {
                    $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['layout_type'] = 'three';
                } elseif ( (int) $col_count >= 4 ) {
                    $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['layout_type'] = 'four';
                }
            }
            // Add field to the row within columns_group.
            $grouped_fields['row_based'][$group_id]['columns_groups'][$columns_id]['rows'][$row_id]['fields'][$field_id] = $field_data;
        }
    }
    return $grouped_fields;
}

/**
 * Render premium feature preview for free users.
 *
 * Displays a header with PRO badge and upgrade button at top,
 * with clear screenshot image below so admin can see the features.
 *
 * @since 4.4.0
 *
 * @param string $image_name   The image filename (without path) in premium-previews folder.
 * @param string $title        The feature title to display.
 * @param string $description  Optional description text.
 * @param string $subfolder    Optional subfolder within premium-previews (e.g., 'field-types', 'settings').
 * @return void
 */
function epofw_render_premium_preview(
    $image_name,
    $title,
    $description = '',
    $subfolder = ''
) {
    $image_path = EPOFW_PLUGIN_URL . 'assets/images/premium-previews/';
    if ( !empty( $subfolder ) ) {
        $image_path .= trailingslashit( $subfolder );
    }
    $image_url = $image_path . $image_name;
    $upgrade_url = '';
    ?>
	<div class="epofw-premium-preview-wrapper">
		<div class="epofw-premium-preview">
			<div class="epofw-premium-preview-header">
				<div class="epofw-premium-preview-header-left">
					<span class="epofw-pro-badge">
						<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"/></svg>
						<?php 
    esc_html_e( 'PRO', 'extra-product-options-for-woocommerce' );
    ?>
					</span>
					<h4 class="epofw-premium-preview-title"><?php 
    echo esc_html( $title );
    ?></h4>
				</div>
				<a href="<?php 
    echo esc_url( $upgrade_url );
    ?>" class="epofw-upgrade-button" target="_blank">
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 5v6h6l-8 8-8-8h6V5h4z" transform="rotate(180 12 12)"/></svg>
					<?php 
    esc_html_e( 'Upgrade to Pro', 'extra-product-options-for-woocommerce' );
    ?>
				</a>
			</div>
			<?php 
    if ( !empty( $description ) ) {
        ?>
				<div class="epofw-premium-preview-description">
					<?php 
        echo esc_html( $description );
        ?>
				</div>
			<?php 
    }
    ?>
			<div class="epofw-premium-preview-image">
				<img src="<?php 
    echo esc_url( $image_url );
    ?>" alt="<?php 
    echo esc_attr( $title );
    ?>">
			</div>
		</div>
	</div>
	<?php 
}

/**
 * Get list of premium-only field types.
 *
 * @since 4.4.0
 * @return array List of premium field type keys.
 */
function epofw_get_premium_field_types() {
    return array(
        'file',
        'colorswitcher',
        'textswitcher',
        'imageswitcher',
        'repeater',
        'productgroup'
    );
}

/**
 * Check if a field type is premium-only.
 *
 * @since 4.4.0
 * @param string $field_type The field type to check.
 * @return bool True if the field type is premium-only.
 */
function epofw_is_premium_field_type(  $field_type  ) {
    return in_array( $field_type, epofw_get_premium_field_types(), true );
}

/**
 * Recursively force slider_mode to single for non-Pro saves (dual-handle is Pro-only).
 *
 * @since 4.4.7
 *
 * @param array $data Reference to a branch of addon/field data.
 */
function epofw_normalize_slider_mode_for_free_walk(  array &$data  ) {
    if ( isset( $data['field']['type'] ) && 'slider' === $data['field']['type'] ) {
        $data['field']['slider_mode'] = 'single';
    }
    foreach ( $data as $key => &$value ) {
        if ( is_array( $value ) ) {
            epofw_normalize_slider_mode_for_free_walk( $value );
        }
    }
    unset($value);
}

/**
 * Ensure saved addon data never stores dual-handle slider mode without Pro (tamper-safe).
 *
 * @since 4.4.7
 *
 * @param array $parsed_data Parsed addon payload.
 * @return array
 */
function epofw_normalize_slider_mode_for_free_before_save(  $parsed_data  ) {
    if ( !is_array( $parsed_data ) ) {
        return $parsed_data;
    }
    if ( !empty( $parsed_data['general'] ) && is_array( $parsed_data['general'] ) ) {
        epofw_normalize_slider_mode_for_free_walk( $parsed_data['general'] );
    }
    return $parsed_data;
}

/**
 * Check if current user can access premium features.
 *
 * @since 4.4.0
 * @return bool True if premium features are available, false otherwise.
 */
function epofw_is_premium() {
    return false;
}

/**
 * Get allowed HTML tags for PRO badge output.
 *
 * Returns array of allowed HTML tags for use with wp_kses() when outputting PRO badge.
 *
 * @since 4.4.0
 * @return array Allowed HTML tags for PRO badge.
 */
function epofw_get_badge_allowed_html() {
    return array(
        'span' => array(
            'class' => true,
        ),
    );
}

/**
 * Get premium field data for displaying disabled fields with PRO badge.
 *
 * Returns an array with CSS class, PRO badge HTML, disabled attribute, and premium status.
 * Use this to show premium fields in free version with disabled state.
 *
 * @since 4.4.0
 * @return array Array containing 'is_premium', 'class', 'badge', 'disabled', 'badge_allowed_html' keys.
 */
function epofw_get_premium_field_data() {
    $epofw_is_premium = false;
    return array(
        'is_premium'         => $epofw_is_premium,
        'class'              => ( !$epofw_is_premium ? ' epofw-premium-field-disabled' : '' ),
        'badge'              => ( !$epofw_is_premium ? '<span class="epofw-pro-badge-inline">' . esc_html__( 'PRO', 'extra-product-options-for-woocommerce' ) . '</span>' : '' ),
        'disabled'           => ( !$epofw_is_premium ? ' disabled' : '' ),
        'badge_allowed_html' => epofw_get_badge_allowed_html(),
    );
}

/**
 * Get all price type options including PRO options with premium flag.
 *
 * Returns all price type options with 'is_pro' flag for each option.
 * Free options work for everyone, PRO options are display-only for free users.
 *
 * @since 4.4.0
 * @param string $type Field type.
 * @return array Array of price type options with 'label' and 'is_pro' keys.
 */
function epofw_price_type_data_all(  $type  ) {
    $options = array();
    if ( !empty( $type ) ) {
        // Free option - available to all.
        $options['fixed'] = array(
            'label'  => esc_html__( 'Fixed', 'extra-product-options-for-woocommerce' ),
            'is_pro' => false,
        );
        // PRO option - Percentage of Product Price.
        // Default placeholder key (actual key set in premium-only block that gets stripped from free version).
        $pbopp_key = 'xpt_a1';
        $options[$pbopp_key] = array(
            'label'  => esc_html__( 'Percentage of Product Price', 'extra-product-options-for-woocommerce' ),
            'is_pro' => true,
        );
        // PRO option - Custom (User-entered price for donations, tips).
        // Only for number field type.
        if ( 'number' === $type ) {
            $custom_key = 'xpt_g7';
            $options[$custom_key] = array(
                'label'  => esc_html__( 'Custom', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
            // PRO option - Dynamic (Price per 'n' units).
            $dynamic_key = 'xpt_h8';
            $options[$dynamic_key] = array(
                'label'  => esc_html__( 'Dynamic', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
            // PRO option - Dynamic Exclude Base (Price per unit excluding base).
            $dynamic_excl_key = 'xpt_i9';
            $options[$dynamic_excl_key] = array(
                'label'  => esc_html__( 'Dynamic - Exclude Base Price', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
        }
        // Text field specific PRO options.
        if ( 'text' === $type || 'textarea' === $type ) {
            $ppc_key = 'xpt_b2';
            $options[$ppc_key] = array(
                'label'  => esc_html__( 'Price Per Char', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
            $ppcis_key = 'xpt_c3';
            $options[$ppcis_key] = array(
                'label'  => esc_html__( 'Price Per Char (Ignore Space)', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
            $ppw_key = 'xpt_d4';
            $options[$ppw_key] = array(
                'label'  => esc_html__( 'Price Per Word', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
        }
        // Custom Price Formula for text, textarea, timepicker, number.
        if ( 'text' === $type || 'textarea' === $type || 'timepicker' === $type || 'number' === $type ) {
            $cpf_key = 'xpt_e5';
            $options[$cpf_key] = array(
                'label'  => esc_html__( 'Custom Price Formula', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
        }
        // Slider specific PRO options.
        if ( 'slider' === $type ) {
            $pps_key = 'xpt_f6';
            $options[$pps_key] = array(
                'label'  => esc_html__( 'Price Per Step', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
            $cpf_key = 'xpt_e5';
            $options[$cpf_key] = array(
                'label'  => esc_html__( 'Custom Price Formula', 'extra-product-options-for-woocommerce' ),
                'is_pro' => true,
            );
        }
    }
    /**
     * Filters for all price type data including PRO options.
     *
     * @since 4.4.0
     */
    return apply_filters( 'epofw_price_type_data_all', $options, $type );
}

/**
 * Migrate old flat additional rules format to new nested structure.
 *
 * @since 4.4.4
 * @param array $old_rules Old flat format rules data.
 * @return array New nested structure with groups.
 */
function epofw_migrate_additional_rules_to_nested(  $old_rules  ) {
    if ( empty( $old_rules ) || !is_array( $old_rules ) ) {
        return array(
            'structure_version'    => '2.0',
            'groups'               => array(),
            'logic_between_groups' => 'and',
        );
    }
    // Check if already in new format (groups array present, structure_version may be missing from older saves).
    if ( isset( $old_rules['groups'] ) && is_array( $old_rules['groups'] ) ) {
        // Ensure structure_version is set for consistency.
        if ( !isset( $old_rules['structure_version'] ) ) {
            $old_rules['structure_version'] = '2.0';
        }
        return $old_rules;
    }
    // Convert old flat format to new nested structure.
    $conditions = array();
    foreach ( $old_rules as $rule_id => $rule_data ) {
        if ( isset( $rule_data['condition'] ) && isset( $rule_data['operator'] ) && isset( $rule_data['value'] ) ) {
            $conditions[] = array(
                'condition' => $rule_data['condition'],
                'operator'  => $rule_data['operator'],
                'value'     => $rule_data['value'],
            );
        }
    }
    if ( empty( $conditions ) ) {
        return array(
            'structure_version'    => '2.0',
            'groups'               => array(),
            'logic_between_groups' => 'and',
        );
    }
    // Wrap all old rules in a single group with AND logic (old format was AND only).
    return array(
        'structure_version'    => '2.0',
        'groups'               => array(array(
            'id'         => 'group_' . wp_rand( 100000, 999999 ),
            'logic'      => 'and',
            'conditions' => $conditions,
        )),
        'logic_between_groups' => 'and',
    );
}

/**
 * Get normalized additional rules data (migrates old format if needed).
 *
 * @since 4.4.4
 * @param array $rule_data Rules data (old or new format).
 * @return array Normalized rules data in new format.
 */
function epofw_normalize_additional_rules_data(  $rule_data  ) {
    if ( empty( $rule_data ) || !is_array( $rule_data ) ) {
        return array(
            'structure_version'    => '2.0',
            'groups'               => array(),
            'logic_between_groups' => 'and',
        );
    }
    // Check if already in new format.
    // Detect new format by presence of groups array (structure_version may be missing from older saves).
    if ( isset( $rule_data['groups'] ) && is_array( $rule_data['groups'] ) ) {
        // Ensure structure_version is set for consistency.
        if ( !isset( $rule_data['structure_version'] ) ) {
            $rule_data['structure_version'] = '2.0';
        }
        /**
         * Filter to modify rule data after loading.
         *
         * @since 4.4.4
         * @param array $rule_data Normalized rule data.
         */
        return apply_filters( 'epofw_load_additional_rules', $rule_data );
    }
    // Migrate old format to new format.
    $normalized = epofw_migrate_additional_rules_to_nested( $rule_data );
    /**
     * Filter to modify rule data after loading.
     *
     * @since 4.4.4
     * @param array $normalized Normalized rule data.
     */
    return apply_filters( 'epofw_load_additional_rules', $normalized );
}

/**
 * Border style options for field container (global settings).
 *
 * @since 4.4.6
 *
 * @return array Associative array of value => label.
 */
function epofw_field_container_border_style_data() {
    $options = array(
        'free1' => esc_html__( 'Solid', 'extra-product-options-for-woocommerce' ),
        'free2' => esc_html__( 'Dashed', 'extra-product-options-for-woocommerce' ),
        'free3' => esc_html__( 'Dotted', 'extra-product-options-for-woocommerce' ),
        'free4' => esc_html__( 'Double', 'extra-product-options-for-woocommerce' ),
        'free5' => esc_html__( 'None', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filter border style options for the field container settings UI.
     *
     * @since 4.4.6
     */
    return apply_filters( 'epofw_field_container_border_style_data', $options );
}

/**
 * Spacing units options for field container (padding/margin).
 *
 * @since 4.4.6
 *
 * @return array Associative array of value => label.
 */
function epofw_field_container_spacing_units_data() {
    $options = array(
        'free1' => esc_html__( 'Pixels', 'extra-product-options-for-woocommerce' ),
        'free2' => esc_html__( 'Em', 'extra-product-options-for-woocommerce' ),
        'free3' => esc_html__( 'Rem', 'extra-product-options-for-woocommerce' ),
        'free4' => esc_html__( 'Percent', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filter spacing unit options for field container padding/margin.
     *
     * @since 4.4.6
     */
    return apply_filters( 'epofw_field_container_spacing_units_data', $options );
}

/**
 * Font weight options for label styling (global settings).
 *
 * @since 4.4.6
 *
 * @return array Associative array of value => label.
 */
function epofw_label_styling_font_weight_data() {
    $options = array(
        'free1'  => esc_html__( 'Default', 'extra-product-options-for-woocommerce' ),
        'free2'  => '100',
        'free3'  => '200',
        'free4'  => '300',
        'free5'  => '400 (Normal)',
        'free6'  => '500',
        'free7'  => '600',
        'free8'  => '700 (Bold)',
        'free9'  => '800',
        'free10' => '900',
    );
    /**
     * Filter font weight options for label styling (global settings).
     *
     * @since 4.4.6
     */
    return apply_filters( 'epofw_label_styling_font_weight_data', $options );
}

/**
 * Text align options for label styling (global settings).
 *
 * @since 4.4.6
 *
 * @return array Associative array of value => label.
 */
function epofw_label_styling_text_align_data() {
    $options = array(
        'free1' => esc_html__( 'Default', 'extra-product-options-for-woocommerce' ),
        'free2' => 'left',
        'free3' => 'center',
        'free4' => 'right',
        'free5' => 'justify',
    );
    /**
     * Filter text alignment options for label styling (global settings).
     *
     * @since 4.4.6
     */
    return apply_filters( 'epofw_label_styling_text_align_data', $options );
}

/**
 * Text transform options for label styling (global settings).
 *
 * @since 4.4.6
 *
 * @return array Associative array of value => label.
 */
function epofw_label_styling_text_transform_data() {
    $options = array(
        'free1' => esc_html__( 'Default', 'extra-product-options-for-woocommerce' ),
        'free2' => esc_html__( 'Uppercase', 'extra-product-options-for-woocommerce' ),
        'free3' => esc_html__( 'Lowercase', 'extra-product-options-for-woocommerce' ),
        'free4' => esc_html__( 'Capitalize', 'extra-product-options-for-woocommerce' ),
        'free5' => esc_html__( 'None', 'extra-product-options-for-woocommerce' ),
    );
    /**
     * Filter text transform options for label styling (global settings).
     *
     * @since 4.4.6
     */
    return apply_filters( 'epofw_label_styling_text_transform_data', $options );
}
