<?php

namespace GriffinForms\Frontend\Html\Forms\Fields;

use GriffinForms\Includes\Helpers\Currency;
use GriffinForms\Includes\Integrations\Categories\Payment as PaymentRegistry;
use GriffinForms\Settings;

class Payment extends \GriffinForms\Frontend\Html\Forms\Fields\Format
{
    /**
     * Cached currency code.
     *
     * @var string|null
     */
    protected $currency_code;

    protected function isPaidEditLock(): bool
    {
        if (!$this->edit_mode) {
            return false;
        }

        $submission = isset($this->submission_context['submission']) && is_array($this->submission_context['submission'])
            ? $this->submission_context['submission']
            : array();
        $payment_status = isset($submission['payment_status']) ? sanitize_key((string) $submission['payment_status']) : '';

        return $payment_status === 'paid';
    }

    protected function setErrors()
    {
        $this->errors = array('required');
    }

    protected function formControl($iteration = 0)
        {
            $is_paid_lock = $this->isPaidEditLock();
            $payload_value = '';
            if (is_array($this->value) && !empty($this->value)) {
                $payload_value = wp_json_encode($this->value);
            } elseif (is_string($this->value) && $this->value !== '') {
                $payload_value = $this->value;
            }
            $hidden_input  = '<input type="hidden" class="' . esc_attr($this->html_ids['formcontrol']) . ' griffinforms-payment-hidden"';
            $hidden_input .= ' data-gf-formpage="' . absint($this->data['formpage']) . '"';
            $hidden_input .= ' data-gf-payment-value="' . esc_attr($payload_value !== '' ? $payload_value : '') . '"';
            if ($payload_value !== '') {
                $hidden_input .= ' value="' . esc_attr($payload_value) . '"';
            }
            $hidden_input .= '>';

        $products = $this->getProducts();
        $is_theme_set = !empty($this->data['is_theme_set']);
        $show_images = $this->shouldShowImages();
        $has_any_images = false;
        if ($show_images) {
            foreach ($products as $product) {
                if (!empty($product['image'])) {
                    $has_any_images = true;
                    break;
                }
            }
        }

        if (empty($products)) {
            $html  = '<div class="griffinforms-payment-empty text-muted small">';
            $html .= esc_html__('This payment option is not available yet.', 'griffinforms-form-builder');
            $html .= '</div>';
            return $html . $hidden_input;
        }

        $limits = $this->getSelectionLimits(count($products));
        $field_heading = (string) $this->item->getProp('heading');
        $layout = $this->getLayout();
        $group_classes = array('griffinforms-payment-field-group', 'griffinforms-payment-layout-' . esc_attr($layout));
        if (!$this->shouldShowImages()) {
            $group_classes[] = 'griffinforms-payment-hide-images';
        }
        if (!$this->shouldShowDescriptions()) {
            $group_classes[] = 'griffinforms-payment-hide-descriptions';
        }

        $currency_helper = Currency::getInstance();
        $currency_code   = $this->getCurrencyCode();
        $tax_settings    = $this->getTaxSettings();

        $group_attr  = ' data-gf-payment-field-group="' . absint($this->id) . '"';
        $group_attr .= ' data-min-select="' . esc_attr($limits['min']) . '"';
        $group_attr .= ' data-max-select="' . esc_attr($limits['max']) . '"';
        $group_attr .= ' data-min-error="' . esc_attr($limits['min_error']) . '"';
        $group_attr .= ' data-max-error="' . esc_attr($limits['max_error']) . '"';
        $group_attr .= ' data-gf-layout="' . esc_attr($layout) . '"';

        $wrapper_classes = 'griffinforms-payment-field-wrapper';
        if (!$is_theme_set) {
            $wrapper_classes .= ' card border-0 shadow-sm p-4 bg-light my-2';
        }

        $grid  = '<div class="' . esc_attr(implode(' ', $group_classes)) . '"' . $group_attr . '>';
        foreach ($products as $index => $product) {
            $product_payload = $this->prepareProductPayload($product, $currency_code);
            $grid .= $this->renderProductCard(
                $product_payload,
                $currency_helper,
                $currency_code,
                $index,
                $is_theme_set,
                $show_images,
                $has_any_images
            );
        }
        $grid .= '</div>';

        $summary_classes = 'griffinforms-payment-cart-summary';
        if (!$is_theme_set) {
            $summary_classes .= ' alert alert-secondary bg-white border rounded-3';
        }

        $summary  = '<div class="' . esc_attr($summary_classes) . '" data-gf-payment-summary';
        $summary .= ' data-summary-label="' . esc_attr($field_heading) . '"';
        $summary .= ' data-tax-enabled="' . (int) $tax_settings['enabled'] . '"';
        $summary .= ' data-tax-type="' . esc_attr($tax_settings['type']) . '"';
        $summary .= ' data-tax-value="' . esc_attr($tax_settings['value']) . '"';
        $summary .= ' data-tax-label="' . esc_attr($tax_settings['label']) . '"';
        $summary .= ' data-tax-registration-label="' . esc_attr($tax_settings['registration_label']) . '"';
        $summary .= ' data-tax-registration-number="' . esc_attr($tax_settings['registration_number']) . '"';
        $summary .= '>';
        $summary_empty_classes = 'griffinforms-payment-cart-empty small';
        if (!$is_theme_set) {
            $summary_empty_classes .= ' text-muted';
        }
        $summary .= '<div class="' . esc_attr($summary_empty_classes) . '">';
        $summary .= esc_html__('Cart is empty. Select a product to begin.', 'griffinforms-form-builder');
        $summary .= '</div>';
        $summary .= '<div class="griffinforms-payment-cart-body" style="display:none;">';
        $summary .= '<div class="griffinforms-payment-cart-items" data-gf-payment-summary-items></div>';
        $summary .= '<div class="griffinforms-payment-cart-footer">';
        $summary .= '<div class="griffinforms-payment-summary-row" data-summary-row="subtotal">';
        $summary .= '<span class="griffinforms-payment-summary-label">' . esc_html__('Subtotal', 'griffinforms-form-builder') . '</span>';
        $summary .= '<span class="griffinforms-payment-summary-amount" data-gf-payment-summary-subtotal>' . esc_html($currency_helper->formatAmount(0, $currency_code)) . '</span>';
        $summary .= '</div>';
        $summary .= '<div class="griffinforms-payment-summary-row" data-summary-row="coupon" style="display:none;">';
        $summary .= '<span class="griffinforms-payment-summary-label">' . esc_html__('Coupon', 'griffinforms-form-builder') . '</span>';
        $summary .= '<span class="griffinforms-payment-summary-amount" data-gf-payment-summary-coupon></span>';
        $summary .= '</div>';
        if ($tax_settings['enabled']) {
            $summary .= '<div class="griffinforms-payment-summary-row" data-summary-row="tax">';
            $summary .= '<span class="griffinforms-payment-summary-label" data-gf-payment-summary-tax-label>' . esc_html($tax_settings['label']) . '</span>';
            $summary .= '<span class="griffinforms-payment-summary-amount" data-gf-payment-summary-tax>' . esc_html($currency_helper->formatAmount(0, $currency_code)) . '</span>';
            $summary .= '</div>';
        } else {
            $summary .= '<div class="griffinforms-payment-summary-row" data-summary-row="tax" style="display:none;">';
            $summary .= '<span class="griffinforms-payment-summary-label" data-gf-payment-summary-tax-label></span>';
            $summary .= '<span class="griffinforms-payment-summary-amount" data-gf-payment-summary-tax></span>';
            $summary .= '</div>';
        }
        $summary .= '<div class="griffinforms-payment-summary-row griffinforms-payment-summary-total">';
        $summary .= '<span class="griffinforms-payment-summary-label">' . esc_html__('Total', 'griffinforms-form-builder') . '</span>';
        $summary .= '<span class="griffinforms-payment-summary-amount" data-gf-payment-summary-total>' . esc_html($currency_helper->formatAmount(0, $currency_code)) . '</span>';
        $summary .= '</div>';
        $tax_registration_style = ($tax_settings['enabled'] && ($tax_settings['registration_label'] || $tax_settings['registration_number'])) ? '' : ' style="display:none;"';
        $summary .= '<div class="griffinforms-payment-tax-registration small text-muted" data-gf-payment-tax-registration' . $tax_registration_style . '>';
        if ($tax_settings['enabled'] && ($tax_settings['registration_label'] || $tax_settings['registration_number'])) {
            $text = ($tax_settings['registration_label'] && $tax_settings['registration_number'])
                ? $tax_settings['registration_label'] . ': ' . $tax_settings['registration_number']
                : ($tax_settings['registration_label'] ?: $tax_settings['registration_number']);
            $summary .= esc_html($text);
        }
        $summary .= '</div>';
        $summary .= '<input type="hidden" class="griffinforms-payment-tax-config" value=\'' . esc_attr(wp_json_encode($tax_settings)) . '\'>';
        $summary .= '</div></div></div>';

        $group_error_classes = 'griffinforms-payment-group-error small';
        if (!$is_theme_set) {
            $group_error_classes .= ' text-danger';
        }

        $gateway_selector = $this->getGatewaySelectorData();
        $selected_gateway_attr = $gateway_selector['selected'] ?: $gateway_selector['default'];

        $html  = '<div class="' . esc_attr($wrapper_classes) . '" data-gf-payment-field-id="' . absint($this->id) . '"';
        $html .= $is_paid_lock ? ' data-gf-payment-locked="1"' : '';
        $html .= '>';
        if ($is_paid_lock) {
            $html .= '<div class="griffinforms-payment-lock-banner">';
            $html .= '<span class="griffinforms-payment-lock-badge">' . esc_html__('Paid', 'griffinforms-form-builder') . '</span>';
            $html .= '<span class="griffinforms-payment-lock-text">' . esc_html__('This payment has already been completed and cannot be changed in edit mode.', 'griffinforms-form-builder') . '</span>';
            $html .= '</div>';
        }
        $html .= $grid;
        $html .= $summary;
        $html .= '<div class="griffinforms-payment-gateway-config"';
        $html .= ' data-gf-payment-gateways=\'' . esc_attr(wp_json_encode($gateway_selector['options'])) . '\'';
        $html .= ' data-gf-gateway-default="' . esc_attr($gateway_selector['default']) . '"';
        $html .= ' data-gf-selected-gateway="' . esc_attr($selected_gateway_attr) . '"';
        $html .= ' data-gf-gateway-required="' . ($gateway_selector['required'] ? '1' : '0') . '"';
        $html .= '></div>';
        $html .= '<div class="' . esc_attr($group_error_classes) . '" data-gf-element="error" style="display:none;"></div>';
        $html .= $hidden_input;
        $html .= '</div>';

        return $html;
    }

    protected function renderProductCard(
        array $product,
        Currency $currency_helper,
        string $currency_code,
        int $index,
        bool $is_theme_set,
        bool $show_images,
        bool $has_any_images
    ): string
    {
        $is_minimal_layout = false;
        $checkbox_id = 'griffinforms-payment-toggle-' . $this->id . '-' . $index;
        $qty_id      = 'griffinforms-payment-qty-' . $this->id . '-' . $index;
        $is_paid_lock = $this->isPaidEditLock();
        $disabled    = ($this->isDisabled() || $is_paid_lock) ? ' disabled="disabled"' : '';
        $readonly    = ($this->isReadonly() || $is_paid_lock) ? ' disabled="disabled"' : '';
        $can_select  = empty($disabled) && empty($readonly);
        $checkbox_attrs = $can_select ? '' : ' disabled="disabled"';
        $price_display = $currency_helper->formatAmount($product['price'], $currency_code);

        $field_classes = array('griffinforms-payment-field');
        if (!empty($product['featured'])) {
            $field_classes[] = 'griffinforms-payment-field--featured';
        }

        $card_classes = array('griffinforms-payment-card');
        if (!empty($product['featured'])) {
            $card_classes[] = 'griffinforms-payment-card--featured';
        }

        $html  = '<div class="' . esc_attr(implode(' ', $field_classes)) . '" data-gf-payment-field="' . absint($this->id) . '"';
        $html .= ' data-product=\'' . esc_attr(wp_json_encode($product)) . '\'';
        $html .= ' data-product-id="' . esc_attr($product['id']) . '">';
        $html .= '<div class="' . esc_attr(implode(' ', $card_classes)) . '">';

        if ($show_images) {
            if (!empty($product['image'])) {
                $html .= '<div class="griffinforms-payment-card-media">';
                $html .= '<img src="' . esc_url($product['image']) . '" alt="' . esc_attr($product['name']) . '">';
                $html .= '</div>';
            } elseif ($has_any_images) {
                $html .= '<div class="griffinforms-payment-card-media griffinforms-payment-card-media--placeholder"></div>';
            }
        }

        $html .= '<div class="griffinforms-payment-card-body">';
        if (!empty($product['featured'])) {
            $html .= '<div class="griffinforms-payment-featured-tag">';
            $html .= esc_html__('Featured', 'griffinforms-form-builder');
            $html .= '</div>';
        }
        $html .= '<div class="griffinforms-payment-card-header">';
        $html .= '<span class="griffinforms-payment-name">' . esc_html($product['name']) . '</span>';
        if ($this->shouldShowPrices()) {
            $html .= '<span class="griffinforms-payment-price" data-gf-payment-price>' . esc_html($price_display) . '</span>';
        }
        $html .= '</div>';

        if ($this->shouldShowDescriptions() && !empty($product['description'])) {
            $html .= '<p class="griffinforms-payment-description">' . esc_html($product['description']) . '</p>';
        }

        if (!empty($product['link'])) {
            $html .= '<div class="griffinforms-payment-link">';
            $html .= '<a href="' . esc_url($product['link']) . '" class="griffinforms-payment-link-anchor" target="_blank" rel="noopener">';
            $html .= esc_html__('View product details', 'griffinforms-form-builder');
            $html .= '</a>';
            $html .= '</div>';
        }

        $html .= '<div class="griffinforms-payment-actions">';
        $html .= '<div class="griffinforms-payment-select w-100 mb-2">';
            $html .= '<label class="form-check-label d-flex align-items-center gap-2 justify-content-start">';
        $html .= '<input type="checkbox" id="' . esc_attr($checkbox_id) . '" class="griffinforms-payment-toggle"' . $checkbox_attrs . '>';
        $html .= '<span class="griffinforms-payment-include-label">' . esc_html__('Include this item', 'griffinforms-form-builder') . '</span>';
        $html .= '</label>';
        $html .= '</div>';

        if ($this->allowQuantityAdjustments() && !empty($product['allow_quantity'])) {
            $html .= '<div class="griffinforms-payment-qty-control w-100" data-gf-payment-qty-wrapper>';
            $html .= '<label class="form-label d-block" for="' . esc_attr($qty_id) . '">' . esc_html__('Quantity', 'griffinforms-form-builder') . '</label>';
            $html .= '<div class="griffinforms-payment-qty-inputs">';
            $html .= '<button type="button" class="griffinforms-payment-qty-btn" data-gf-payment-step="-1" aria-label="' . esc_attr__('Decrease quantity', 'griffinforms-form-builder') . '"' . $checkbox_attrs . '>-</button>';
            $html .= '<div class="griffinforms-payment-qty-display" id="' . esc_attr($qty_id) . '" data-min="' . absint($product['min_qty']) . '" data-max="' . absint($product['max_qty']) . '" data-default="' . absint($product['default_qty']) . '">' . absint($product['default_qty']) . '</div>';
            $html .= '<button type="button" class="griffinforms-payment-qty-btn" data-gf-payment-step="1" aria-label="' . esc_attr__('Increase quantity', 'griffinforms-form-builder') . '"' . $checkbox_attrs . '>+</button>';
            $html .= '</div>';
            $html .= '</div>';
        } else {
            $html .= '<div class="griffinforms-payment-qty-fixed">';
            /* translators: %d refers to the fixed quantity */
            $html .= esc_html(sprintf(__('Quantity fixed at %d', 'griffinforms-form-builder'), absint($product['default_qty'])));
            $html .= '</div>';
        }

        $html .= '</div>';
        $inline_error_classes = 'griffinforms-payment-inline-error small';
        if (!$is_theme_set) {
            $inline_error_classes .= ' text-danger bg-danger bg-opacity-10';
        }
        $html .= '<div class="' . esc_attr($inline_error_classes) . '" data-gf-element="error" style="display:none;"></div>';
        $html .= '</div>'; // body
        $html .= '</div>'; // card
        $html .= '</div>'; // field wrapper

        return $html;
    }

    protected function subItemJs()
    {
        $field_id = absint($this->id);
        ?>
        (function($){
            const groups = $(".griffinforms-payment-field-group[data-gf-payment-field-group=\"<?php echo esc_js($field_id); ?>\"]");
            const gatewayBus = window.griffinformsPaymentGatewayBus = window.griffinformsPaymentGatewayBus || $({});

            if (!groups.length) {
                return;
            }

            groups.each(function(){
                const group = $(this);
                const fieldGroupId = group.data("gfPaymentFieldGroup");
                const wrapper = group.closest(".griffinforms-payment-field-wrapper");
                const containers = group.find(".griffinforms-payment-field[data-gf-payment-field=\"<?php echo esc_js($field_id); ?>\"]");
                const hiddenInput = wrapper.find(".<?php echo esc_js($this->html_ids['formcontrol']); ?>").first();
                const minSelect = parseInt(group.data("min-select"), 10) || 0;
                const maxSelect = parseInt(group.data("max-select"), 10) || 0;
                const minErrorText = group.data("min-error") || "";
                const maxErrorText = group.data("max-error") || "";
                const groupError = wrapper.find(".griffinforms-payment-group-error");
                const selectionState = {};
                const summaryContainer = wrapper.find("[data-gf-payment-summary]");
                const summaryBody = summaryContainer.find(".griffinforms-payment-cart-body");
                const summaryItems = summaryContainer.find("[data-gf-payment-summary-items]");
                const summarySubtotal = summaryContainer.find("[data-gf-payment-summary-subtotal]");
                const summaryTotal = summaryContainer.find("[data-gf-payment-summary-total]");
                const summaryCouponRow = summaryContainer.find('[data-summary-row="coupon"]');
                const summaryTaxRow = summaryContainer.find('[data-summary-row="tax"]');
                const summaryTaxLabel = summaryContainer.find("[data-gf-payment-summary-tax-label]");
                const summaryCouponAmount = summaryContainer.find("[data-gf-payment-summary-coupon]");
                const summaryTaxAmount = summaryContainer.find("[data-gf-payment-summary-tax]");
                const summaryEmpty = summaryContainer.find(".griffinforms-payment-cart-empty");
                const taxRegistrationNode = summaryContainer.find("[data-gf-payment-tax-registration]");
                const taxConfigInput = summaryContainer.find(".griffinforms-payment-tax-config");
                let taxConfig = {};
                if (taxConfigInput.length) {
                    try {
                        taxConfig = JSON.parse(taxConfigInput.val() || "{}");
                    } catch (error) {
                        taxConfig = {};
                    }
                }
                const taxEnabled = !!taxConfig.enabled;
                const taxType = (taxConfig.type || "percentage").toLowerCase();
                const taxValue = parseFloat(taxConfig.value) || 0;
                const configuredTaxLabel = (taxConfig.label || "").trim();
                const taxRegistrationLabelAttr = (taxConfig.registration_label || "").trim();
                const taxRegistrationNumberAttr = (taxConfig.registration_number || "").trim();
                const defaultTaxLabel = "<?php echo esc_js(__('Tax', 'griffinforms-form-builder')); ?>";
                const gatewayConfigNode = wrapper.find(".griffinforms-payment-gateway-config");
                let gatewayOptions = [];
                let selectedGateway = "";
                let storedPayload = { items: [], gateway: "" };
                if (gatewayConfigNode.length) {
                    const gatewaysAttr = gatewayConfigNode.attr("data-gf-payment-gateways") || "[]";
                    try {
                        gatewayOptions = JSON.parse(gatewaysAttr) || [];
                    } catch (error) {
                        gatewayOptions = [];
                    }
                    selectedGateway = gatewayConfigNode.attr("data-gf-selected-gateway") || gatewayConfigNode.attr("data-gf-gateway-default") || "";
                    if (!selectedGateway && gatewayOptions.length) {
                        selectedGateway = gatewayOptions[0].id || "";
                    }
                    gatewayConfigNode.attr("data-gf-selected-gateway", selectedGateway);
                }
                if (hiddenInput.length) {
                    const rawStored = hiddenInput.val() || hiddenInput.attr("data-gf-payment-value") || hiddenInput.attr("value") || "";
                    if (rawStored) {
                        try {
                            const parsedStored = JSON.parse(rawStored);
                            if (Array.isArray(parsedStored)) {
                                storedPayload.items = parsedStored;
                            } else if (parsedStored && typeof parsedStored === "object") {
                                storedPayload.items = Array.isArray(parsedStored.items) ? parsedStored.items : [];
                                storedPayload.gateway = typeof parsedStored.gateway === "string" ? parsedStored.gateway : "";
                            }
                        } catch (error) {
                            storedPayload = { items: [], gateway: "" };
                        }
                    }
                }
                const storedItemsById = {};
                (storedPayload.items || []).forEach(function(item){
                    if (item && item.product_id) {
                        storedItemsById[String(item.product_id)] = item;
                    }
                });
                const quantityMessages = {
                    min: "<?php echo esc_js(__('Minimum quantity for this product is %d.', 'griffinforms-form-builder')); ?>",
                    max: "<?php echo esc_js(__('Maximum quantity for this product is %d.', 'griffinforms-form-builder')); ?>"
                };
                const groupErrorTimerKey = "griffinformsPaymentGroupErrorTimer";
                const formatCurrency = function(amount, currencyCode) {
                    const code = currencyCode || "USD";
                    try {
                        return new Intl.NumberFormat(undefined, {
                            style: "currency",
                            currency: code
                        }).format(amount);
                    } catch (error) {
                        return code + " " + (amount || 0).toFixed(2);
                    }
                };

                const clearGroupErrorTimeout = function() {
                    const timer = group.data(groupErrorTimerKey);
                    if (timer) {
                        window.clearTimeout(timer);
                        group.removeData(groupErrorTimerKey);
                    }
                };

                const hideGroupError = function() {
                    clearGroupErrorTimeout();
                    groupError.stop(true, true).fadeOut(150, function(){
                        $(this).text("");
                    });
                };

                const showGroupError = function(message = "", transient = false) {
                    clearGroupErrorTimeout();
                    if (!message) {
                        hideGroupError();
                        return;
                    }
                    groupError.text(message).fadeIn(150);
                    if (transient) {
                        const timeoutId = window.setTimeout(function(){
                            hideGroupError();
                        }, 2500);
                        group.data(groupErrorTimerKey, timeoutId);
                    }
                };

                const updateTaxRegistration = function() {
                    if (!taxRegistrationNode.length) {
                        return;
                    }
                    if (taxRegistrationLabelAttr || taxRegistrationNumberAttr) {
                        const text = taxRegistrationLabelAttr && taxRegistrationNumberAttr
                            ? taxRegistrationLabelAttr + ": " + taxRegistrationNumberAttr
                            : (taxRegistrationLabelAttr || taxRegistrationNumberAttr);
                        taxRegistrationNode.text(text).show();
                    } else {
                        taxRegistrationNode.hide();
                    }
                };

                const renderSummary = function() {
                    if (!summaryContainer.length) {
                        return;
                    }
                    const items = Object.values(selectionState);
                    if (!items.length) {
                        summaryContainer.removeClass("is-active");
                        summaryBody.hide();
                        summaryEmpty.show();
                        summaryItems.empty();
                        summarySubtotal.text(formatCurrency(0));
                        summaryTotal.text(formatCurrency(0));
                        summaryCouponRow.hide();
                        summaryTaxRow.hide();
                        summaryCouponAmount.text("");
                        summaryTaxAmount.text("");
                        updateTaxRegistration();
                        return;
                    }

                    summaryContainer.addClass("is-active");
                    summaryEmpty.hide();
                    summaryBody.show();
                    summaryItems.empty();

                    let subtotal = 0;
                    items.forEach(function(entry){
                        subtotal += entry.total;
                        const line = $("<div/>", { "class": "griffinforms-payment-cart-item" });
                        const label = entry.product_name || "<?php echo esc_js(__('Item', 'griffinforms-form-builder')); ?>";
                        const qtyText = entry.quantity > 1 ? " × " + entry.quantity : "";
                        line.append($("<span/>").text(label + qtyText));
                        line.append($("<span/>").text(formatCurrency(entry.total, entry.currency)));
                        summaryItems.append(line);
                    });

                    const currencyCode = items[0].currency || "USD";
                    summarySubtotal.text(formatCurrency(subtotal, currencyCode));
                    summaryCouponRow.hide();
                    summaryCouponAmount.text("");

                    let taxAmount = 0;
                    if (taxEnabled) {
                        if (taxValue > 0) {
                            if (taxType === "percentage") {
                                taxAmount = subtotal * (taxValue / 100);
                            } else {
                                taxAmount = taxValue;
                            }
                        }
                        if (summaryTaxLabel.length) {
                            const resolvedLabel = configuredTaxLabel || defaultTaxLabel;
                            summaryTaxLabel.text(resolvedLabel);
                        }
                        summaryTaxRow.show();
                        summaryTaxAmount.text(formatCurrency(taxAmount, currencyCode));
                    } else {
                        summaryTaxRow.hide();
                        summaryTaxAmount.text("");
                    }

                    summaryTotal.text(formatCurrency(subtotal + taxAmount, currencyCode));
                    updateTaxRegistration();
                };

                const getSelectedCount = function() {
                    return Object.keys(selectionState).length;
                };

                const syncHiddenInput = function() {
                    const items = Object.values(selectionState);
                    if (!items.length) {
                        hiddenInput.val("");
                        renderSummary();
                        group.attr("data-gf-has-items", "0");
                        $(document).trigger("griffinforms:fieldChanged", { fieldId: fieldGroupId });
                        return;
                    }
                    const payload = {
                        items: items,
                        gateway: selectedGateway || ""
                    };
                    hiddenInput.val(JSON.stringify(payload));
                    renderSummary();
                    group.attr("data-gf-has-items", "1");
                    $(document).trigger("griffinforms:fieldChanged", { fieldId: fieldGroupId });
                };

                const sanitizeGatewayValue = function(value) {
                    if (typeof value !== "string") {
                        return "";
                    }
                    return value.trim();
                };

                const setSelectedGateway = function(value, skipSync, fromBus) {
                    selectedGateway = sanitizeGatewayValue(value);
                    if (gatewayConfigNode.length) {
                        gatewayConfigNode.attr("data-gf-selected-gateway", selectedGateway);
                    }
                    if (!skipSync) {
                        syncHiddenInput();
                    }
                    if (!fromBus && gatewayBus && fieldGroupId) {
                        gatewayBus.trigger("griffinforms:setGateway", [selectedGateway, fieldGroupId]);
                    }
                };

                if (gatewayBus && fieldGroupId) {
                    gatewayBus.on("griffinforms:setGateway", function(event, value, sourceFieldId){
                        if (!value || sourceFieldId === fieldGroupId) {
                            return;
                        }
                        setSelectedGateway(value, false, true);
                    });
                }

                if (storedPayload.gateway) {
                    setSelectedGateway(storedPayload.gateway, true, true);
                }

                const enforceMinRequirement = function() {
                    if (!minSelect) {
                        hideGroupError();
                        return;
                    }
                    if (getSelectedCount() >= minSelect) {
                        hideGroupError();
                    } else if (minErrorText) {
                        showGroupError(minErrorText);
                    }
                };

                if (!containers.length || !hiddenInput.length) {
                    return;
                }

                containers.each(function(){
                    const fieldWrapper = $(this);
                    const toggle = fieldWrapper.find(".griffinforms-payment-toggle");
                    const qtyDisplay = fieldWrapper.find(".griffinforms-payment-qty-display");
                    const qtyWrapper = fieldWrapper.find("[data-gf-payment-qty-wrapper]");
                    const inlineError = fieldWrapper.find(".griffinforms-payment-inline-error");
                    const qtyButtons = fieldWrapper.find("[data-gf-payment-step]");
                    const currencyFallback = "USD";
                    let productData = fieldWrapper.data("product") || {};

                    if (typeof productData === "string") {
                        try {
                            productData = JSON.parse(productData);
                        } catch (error) {
                            productData = {};
                        }
                    }

                    const productId = productData.id || fieldWrapper.data("product-id") || ("prod_" + Math.random().toString(36).slice(2));
                    const currency = productData.currency || currencyFallback;

                    function formatAmount(amount) {
                        try {
                            return new Intl.NumberFormat(undefined, {
                                style: "currency",
                                currency: currency
                            }).format(amount);
                        } catch (error) {
                            return currency + " " + amount.toFixed(2);
                        }
                    }

                    function getQuantityValue() {
                        const minQty = parseInt(productData.min_qty, 10) || 1;
                        const maxQty = parseInt(productData.max_qty, 10) || minQty;
                        let qty = parseInt(qtyDisplay.text(), 10);

                        if (isNaN(qty)) {
                            qty = minQty;
                        }

                        if (qty < minQty) {
                            qty = minQty;
                        }

                        if (qty > maxQty) {
                            qty = maxQty;
                        }

                        return qty;
                    }

                    function deselectProduct() {
                        delete selectionState[productId];
                        fieldWrapper.removeClass("is-selected");
                        window.clearTimeout(fieldWrapper.data("gfInlineErrorTimeout"));
                        inlineError.stop(true, true).hide().text("");
                        syncHiddenInput();
                        enforceMinRequirement();
                    }

                    function updateState(showError = "") {
                        const isSelected = toggle.is(":checked");
                        let quantity = getQuantityValue();
                        const allowQuantity = parseInt(productData.allow_quantity, 10) === 1;

                        window.clearTimeout(fieldWrapper.data("gfInlineErrorTimeout"));
                        inlineError.stop(true, true).hide().text("");

                        if (allowQuantity && qtyDisplay.length) {
                            quantity = getQuantityValue();
                            qtyDisplay.toggleClass("griffinforms-payment-qty-disabled-display", !isSelected || toggle.is(":disabled"));
                            qtyWrapper.toggleClass("griffinforms-payment-qty-disabled", !isSelected);
                        } else {
                            quantity = getQuantityValue();
                        }

                        const unitPrice = parseFloat(productData.price) || 0;
                        const total = isSelected ? unitPrice * quantity : 0;

                        if (!isSelected) {
                            deselectProduct();
                            return;
                        }

                        fieldWrapper.addClass("is-selected");
                        selectionState[productId] = {
                            selected: true,
                            product_id: productId,
                            product_name: productData.name || "",
                            quantity: quantity,
                            unit_price: unitPrice,
                            currency: currency,
                            total: total
                        };

                        if (showError) {
                            inlineError.text(showError).show();
                            const timeoutId = window.setTimeout(function(){
                                inlineError.fadeOut(200, function(){
                                    inlineError.text("").hide();
                                });
                            }, 2000);
                            fieldWrapper.data("gfInlineErrorTimeout", timeoutId);
                        }

                        syncHiddenInput();
                        enforceMinRequirement();
                    }

                    toggle.on("change", function(){
                        window.clearTimeout(fieldWrapper.data("gfInlineErrorTimeout"));
                        inlineError.stop(true, true).hide().text("");
                        if (!$(this).is(":checked")) {
                            deselectProduct();
                            return;
                        }

                        if (maxSelect > 0 && getSelectedCount() >= maxSelect) {
                            $(this).prop("checked", false);
                            showGroupError(maxErrorText || "<?php echo esc_js(__('You have reached the maximum selectable products for this field.', 'griffinforms-form-builder')); ?>", true);

                            window.clearTimeout(fieldWrapper.data("gfInlineErrorTimeout"));
                            inlineError
                                .text(maxErrorText || "<?php echo esc_js(__('You have reached the maximum selectable products for this field.', 'griffinforms-form-builder')); ?>")
                                .show();
                            const timeoutId = window.setTimeout(function(){
                                inlineError.fadeOut(200, function(){
                                    inlineError.text("").hide();
                                });
                            }, 2000);
                            fieldWrapper.data("gfInlineErrorTimeout", timeoutId);
                            return;
                        }

                        updateState();
                    });

                    const storedItem = storedItemsById[productId] || null;
                    if (storedItem) {
                        if (typeof storedItem.quantity !== "undefined") {
                            qtyDisplay.text(parseInt(storedItem.quantity, 10) || qtyDisplay.data("default") || 1);
                        }
                        toggle.prop("checked", true);
                        updateState();
                    } else {
                        updateState();
                    }

                    if (qtyButtons.length) {
                        qtyButtons.on("click", function(event){
                            event.preventDefault();
                            if (toggle.is(":disabled")) {
                                return;
                            }
                            let currentQty = getQuantityValue();
                            const step = parseInt($(this).data("gf-payment-step"), 10) || 0;
                            const attemptedQty = currentQty + step;
                            const minQty = parseInt(productData.min_qty, 10) || 1;
                            const maxQty = parseInt(productData.max_qty, 10) || minQty;
                            let limitError = "";

                            if (attemptedQty < minQty) {
                                limitError = quantityMessages.min.replace("%d", minQty);
                            } else if (attemptedQty > maxQty) {
                                limitError = quantityMessages.max.replace("%d", maxQty);
                            }

                            currentQty = Math.max(minQty, Math.min(maxQty, attemptedQty));
                            qtyDisplay.text(currentQty);
                            if (!toggle.is(":checked")) {
                                toggle.prop("checked", true).trigger("change");
                            }
                            updateState(limitError);
                        });
                    }

                });

                enforceMinRequirement();
                syncHiddenInput();
                group.data("griffinformsPaymentApi", {
                    requiresGateway: function() {
                        return gatewayOptions.length > 0;
                    },
                    getSelectedGateway: function() {
                        return selectedGateway;
                    },
                    setSelectedGateway: function(value, skipSync) {
                        setSelectedGateway(value, skipSync);
                    },
                    getGatewayOptions: function() {
                        return gatewayOptions;
                    }
                });
            });
        })(jQuery);
        <?php
    }

    protected function getProducts(): array
    {
        if (!is_array($this->field_options) || empty($this->field_options)) {
            return [];
        }

        $products = [];
        foreach ($this->field_options as $product) {
            if (!is_array($product)) {
                continue;
            }
            $product['id'] = $product['id'] ?? ('prod_' . wp_generate_password(6, false, false));
            $product['price'] = isset($product['price']) ? (float) $product['price'] : 0;
            $product['min_qty'] = isset($product['min_qty']) ? max(1, (int) $product['min_qty']) : 1;
            $product['max_qty'] = isset($product['max_qty']) ? max($product['min_qty'], (int) $product['max_qty']) : $product['min_qty'];
            $product['default_qty'] = isset($product['default_qty']) ? max(1, (int) $product['default_qty']) : 1;
            $products[] = $product;
        }

        return $products;
    }

    /**
     * Prepare product payload for JS.
     */
    protected function prepareProductPayload(array $product, string $currency): array
    {
        return array(
            'id'             => sanitize_text_field($product['id'] ?? ''),
            'name'           => sanitize_text_field($product['name'] ?? ''),
            'featured'       => !empty($product['featured']),
            'price'          => (float) ($product['price'] ?? 0),
            'description'    => wp_strip_all_tags($product['description'] ?? ''),
            'image'          => esc_url_raw($product['image'] ?? ''),
            'link'           => esc_url_raw($product['link'] ?? ''),
            'allow_quantity' => (int) ($product['allow_quantity'] ?? 0),
            'min_qty'        => max(1, (int) ($product['min_qty'] ?? 1)),
            'max_qty'        => max((int) ($product['min_qty'] ?? 1), (int) ($product['max_qty'] ?? 1)),
            'default_qty'    => max(1, (int) ($product['default_qty'] ?? 1)),
            'currency'       => $currency,
        );
    }

    protected function getCurrencyCode(): string
    {
        if ($this->currency_code) {
            return $this->currency_code;
        }

        $settings = Settings::getInstance();
        $stored_currency = $settings->getOption('payment_currency', 'USD');
        $this->currency_code = Currency::getInstance()->sanitizeCurrency($stored_currency);

        return $this->currency_code;
    }

    protected function getTaxSettings(): array
    {
        $field_mode = isset($this->field_settings['tax_mode']) ? sanitize_text_field($this->field_settings['tax_mode']) : 'inherit';
        if ($field_mode === 'custom') {
            $type = isset($this->field_settings['tax_rate_type']) ? sanitize_key($this->field_settings['tax_rate_type']) : 'percentage';
            if (!in_array($type, array('percentage', 'fixed'), true)) {
                $type = 'percentage';
            }
            $value = isset($this->field_settings['tax_rate_value']) ? (float) $this->field_settings['tax_rate_value'] : 0;
            if ($value < 0) {
                $value = 0;
            }
            $label = isset($this->field_settings['tax_display_label'])
                ? sanitize_text_field($this->field_settings['tax_display_label'])
                : '';
            if ($label === '') {
                $label = __('Tax', 'griffinforms-form-builder');
            }
            $registration_label = isset($this->field_settings['tax_registration_label'])
                ? sanitize_text_field($this->field_settings['tax_registration_label'])
                : '';
            $registration_number = isset($this->field_settings['tax_registration_number'])
                ? sanitize_text_field($this->field_settings['tax_registration_number'])
                : '';

            return array(
                'enabled' => $value > 0,
                'type' => $type,
                'value' => $value,
                'label' => $label,
                'registration_label' => $registration_label,
                'registration_number' => $registration_number,
            );
        }

        $settings = Settings::getInstance();
        $enabled = (bool) $settings->getOption('tax_enabled', 0);
        $type = sanitize_key($settings->getOption('tax_rate_type', 'percentage'));
        if (!in_array($type, array('percentage', 'fixed'), true)) {
            $type = 'percentage';
        }
        $value = (float) $settings->getOption('tax_rate_value', 0);
        if ($value < 0) {
            $value = 0;
        }
        $label = sanitize_text_field($settings->getOption('tax_display_label', __('Tax', 'griffinforms-form-builder')));
        if ($label === '') {
            $label = __('Tax', 'griffinforms-form-builder');
        }
        $registration_label = sanitize_text_field($settings->getOption('tax_registration_label', ''));
        $registration_number = sanitize_text_field($settings->getOption('tax_registration_number', ''));

        return array(
            'enabled' => $enabled,
            'type' => $type,
            'value' => $value,
            'label' => $label,
            'registration_label' => $registration_label,
            'registration_number' => $registration_number,
        );
    }

    protected function getSelectionLimits(int $product_count = 0): array
    {
        if ($product_count <= 0) {
            $product_count = is_array($this->field_options) ? count($this->field_options) : 0;
        }

        $min = isset($this->field_settings['min_select']) ? max(0, (int) $this->field_settings['min_select']) : 0;
        $max = isset($this->field_settings['max_select']) ? max(0, (int) $this->field_settings['max_select']) : 0;

        if ($product_count <= 0) {
            $min = 0;
            $max = 0;
        } else {
            if ($min > $product_count) {
                $min = $product_count;
            }
            if ($max > 0) {
                $max = min($max, $product_count);
            }
        }

        return array(
            'min' => $min,
            'max' => $max,
            'min_error' => $min > 0 ? sprintf(__('Please select at least %d product(s).', 'griffinforms-form-builder'), $min) : '',
            'max_error' => $max > 0 ? sprintf(__('You can select up to %d product(s).', 'griffinforms-form-builder'), $max) : '',
        );
    }

    protected function getLayout(): string
    {
        $layout = isset($this->field_settings['layout']) ? sanitize_key($this->field_settings['layout']) : 'cards';
        $allowed = array('cards', 'list', 'minimal');
        return in_array($layout, $allowed, true) ? $layout : 'cards';
    }

    protected function shouldShowImages(): bool
    {
        return !isset($this->field_settings['show_images']) || (int) $this->field_settings['show_images'] === 1;
    }

    protected function shouldShowDescriptions(): bool
    {
        return !isset($this->field_settings['show_descriptions']) || (int) $this->field_settings['show_descriptions'] === 1;
    }

    protected function shouldShowPrices(): bool
    {
        return !isset($this->field_settings['show_prices']) || (int) $this->field_settings['show_prices'] === 1;
    }

    protected function allowQuantityAdjustments(): bool
    {
        return !isset($this->field_settings['allow_quantity_controls']) || (int) $this->field_settings['allow_quantity_controls'] === 1;
    }

    protected function getGatewaySelectorData(): array
    {
        $registry = PaymentRegistry::getInstance();
        $settings = Settings::getInstance();
        $options = [];
        $default = '';

        if ($registry) {
            $providers = $registry->getProviders();
            $allowed_ids = $registry->getAllowedProviderIds();

            foreach ($allowed_ids as $provider_id) {
                if (!isset($providers[$provider_id])) {
                    continue;
                }
                $provider = $providers[$provider_id];
                $custom_label = $settings->getOption($provider_id . '_frontend_label', '');
                $custom_desc = $settings->getOption($provider_id . '_frontend_description', '');
                $hide_logo = (bool) $settings->getOption($provider_id . '_frontend_hide_logo', false);
                $resolved_label = $custom_label !== '' ? $custom_label : ($provider['name'] ?? $provider_id);
                $resolved_desc = $custom_desc !== '' ? $custom_desc : '';
                $options[] = array(
                    'id'          => sanitize_key($provider_id),
                    'label'       => sanitize_text_field($resolved_label),
                    'description' => sanitize_textarea_field($resolved_desc),
                    'configured'  => !empty($provider['configured']),
                    'icon'        => esc_url($provider['icon'] ?? ''),
                    'hide_logo'   => $hide_logo,
                );
            }
        }

        if (!empty($options)) {
            $default = sanitize_key($options[0]['id']);
        }

        if (empty($options) && isset($this->log)) {
            $this->log->add(
                'warning',
                'form',
                isset($this->data['form_id']) ? (int) $this->data['form_id'] : 0,
                get_current_user_id(),
                sprintf(
                    'Payment field #%d rendered with no configured gateways; users cannot complete payment.',
                    (int) $this->id
                ),
                'Payment Integration (gateway)'
            );
        }

        return array(
            'options'  => $options,
            'default'  => $default,
            'selected' => $default,
            'required' => true,
        );
    }
}
