<?php

namespace GriffinForms\Frontend\Html\Themes;

use GriffinForms\Items\FormTheme;

/**
 * ThemeCssGenerator
 *
 * Generates scoped CSS for form themes with support for:
 * - Pseudo-states (:hover, :focus, :active)
 * - Complex style properties (backgrounds, gradients, shadows, custom CSS)
 *
 * The generated CSS uses data attribute selectors for scoping to prevent
 * conflicts between multiple forms on the same page.
 */
class ThemeCssGen
{
    /**
     * FormTheme object instance
     *
     * @var \GriffinForms\Items\FormTheme
     */
    protected $theme;

    /**
     * Form ID for CSS scoping
     *
     * @var int
     */
    protected $form_id;

    /**
     * Normalized payment control style tokens for inline gateways.
     *
     * @var array
     */
    protected $payment_control_tokens = array();

    /**
     * Constructor
     *
     * @param \GriffinForms\Items\FormTheme $theme   FormTheme object
     * @param int                           $form_id Form ID for CSS scoping
     */
    public function __construct($theme, $form_id)
    {
        $this->theme = $theme;
        $this->form_id = absint($form_id);
    }

    /**
     * Generate complete CSS with all elements.
     *
     * @return string Complete CSS code ready for <style> tag
     */
    public function generateAllCss()
    {
        $css_parts = array();

        // Form container styles
        $form_css = $this->generateFormCss();
        if (!empty($form_css)) {
            $css_parts[] = $form_css;
        }

        // Page container styles
        $page_css = $this->generatePageCss();
        if (!empty($page_css)) {
            $css_parts[] = $page_css;
        }

        // Form title and description
        $title_desc_css = $this->generateTitleDescriptionCss();
        if (!empty($title_desc_css)) {
            $css_parts[] = $title_desc_css;
        }

        // Page heading and description
        $page_heading_css = $this->generatePageHeadingDescriptionCss();
        if (!empty($page_heading_css)) {
            $css_parts[] = $page_heading_css;
        }

        // Progress bar colors
        $progress_bar_css = $this->generateProgressBarCss();
        if (!empty($progress_bar_css)) {
            $css_parts[] = $progress_bar_css;
        }

        // Row heading and description
        $row_heading_css = $this->generateRowHeadingDescriptionCss();
        if (!empty($row_heading_css)) {
            $css_parts[] = $row_heading_css;
        }

        // Row container styles (background, border, etc.)
        $row_style_css = $this->generateRowContainerCss();
        if (!empty($row_style_css)) {
            $css_parts[] = $row_style_css;
        }

        // Field label, description, and error styles
        $field_text_css = $this->generateFieldTextCss();
        if (!empty($field_text_css)) {
            $css_parts[] = $field_text_css;
        }

        // Form field styles (text inputs, textareas, selects, etc.)
        $field_css = $this->generateFieldCss();
        if (!empty($field_css)) {
            $css_parts[] = $field_css;
        }

        // Validation state tokens for themed invalid controls
        $validation_css = $this->generateValidationStateCss();
        if (!empty($validation_css)) {
            $css_parts[] = $validation_css;
        }

        // Payment card styles
        $payment_css = $this->generatePaymentFieldCss();
        if (!empty($payment_css)) {
            $css_parts[] = $payment_css;
        }

        // Button styles (submit, previous, next)
        $button_css = $this->generateButtonCss();
        if (!empty($button_css)) {
            $css_parts[] = $button_css;
        }

        // Success and error message styles
        $message_css = $this->generateMessageCss();
        if (!empty($message_css)) {
            $css_parts[] = $message_css;
        }

        // No-JS notice styles
        $nojs_css = $this->generateNoJsNoticeCss();
        if (!empty($nojs_css)) {
            $css_parts[] = $nojs_css;
        }

        // File list styles (for file upload field file listings)
        $file_list_css = $this->generateFileListCss();
        if (!empty($file_list_css)) {
            $css_parts[] = $file_list_css;
        }

        // Form lock overlay styles (submission/validation blocking)
        $lock_css = $this->generateFormLockCss();
        if (!empty($lock_css)) {
            $css_parts[] = $lock_css;
        }

        // Checkbox, radio, and terms condition label styles
        $option_label_css = $this->generateOptionLabelCss();
        if (!empty($option_label_css)) {
            $css_parts[] = $option_label_css;
        }

        $final_css = implode("\n\n", array_filter($css_parts));

        return $final_css;
    }

    /**
     * Expose normalized payment control tokens for frontend scripting.
     */
    public function getPaymentControlTokens(): array
    {
        return $this->payment_control_tokens;
    }

    /**
     * Generate CSS for the form container.
     */
    protected function generateFormCss()
    {
        $selector = $this->buildSelector('form');
        $style_css = $this->theme->getFormStyleCss('default');

        if (empty($style_css)) {
            return $selector . ' { padding: 0 !important; }';
        }

        return $this->buildCssRule($selector, $style_css);
    }

    protected function buildSelectArrowCss($color)
    {
        if (empty($color)) {
            $color = '#5a5a5a';
        }

        $encoded_color = str_replace('#', '%23', trim($color));
        $arrow_svg = sprintf(
            "data:image/svg+xml,%%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%s' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%%3E%%3Cpolyline points='6 9 12 15 18 9' /%%3E%%3C/svg%%3E",
            $encoded_color
        );

        return sprintf(
            'background-image: url("%s"); background-repeat: no-repeat; background-position: right 18px center; background-size: 14px; padding-right: 44px; appearance: none; -webkit-appearance: none; -moz-appearance: none;',
            $arrow_svg
        );
    }

    /**
     * Generate CSS for page containers.
     */
    protected function generatePageCss()
    {
        $selector = $this->buildSelector('page');
        $style_css = $this->theme->getPageStyleCss('default');

        if (empty($style_css)) {
            return $selector . ' { margin: 0 !important; }';
        }

        return $this->buildCssRule($selector, $style_css);
    }

    /**
     * Generate CSS custom properties for themed invalid-state styling.
     *
     * @return string
     */
    protected function generateValidationStateCss()
    {
        $selector = $this->buildSelector('form');
        $page_style = $this->theme->getPageStyleResolved('default');
        $page_bg = $this->sanitizeColorValue($page_style['background-color'] ?? '#ffffff', '#ffffff');
        $error_color = $this->sanitizeColorValue($this->theme->getErrorColor(), '#dc3545');
        $error_bg = $this->mixColor($page_bg, $error_color, 0.88);
        $error_ring = $this->buildRgbaColor($error_color, 0.28);

        $css = sprintf(
            '--gf-validation-error-color:%1$s; --gf-validation-error-bg:%2$s; --gf-validation-error-ring:%3$s;',
            $error_color,
            $error_bg,
            $error_ring
        );

        return $this->buildCssRule($selector, $css);
    }

    /**
     * Generate CSS for form title and description with all states
     *
     * @return string
     */
    protected function generateTitleDescriptionCss()
    {
        $css_parts = array();

        // Form title
        $title_css = $this->theme->getFormTitleCss('default');
        if (!empty($title_css)) {
            $selector = $this->buildSelector('element', 'form-title');
            $rule = $this->buildCssRule($selector, $title_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Form description
        $desc_css = $this->theme->getFormDescriptionCss('default');
        if (!empty($desc_css)) {
            $selector = $this->buildSelector('element', 'form-description');
            $rule = $this->buildCssRule($selector, $desc_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        return implode("\n", $css_parts);
    }

    /**
     * Generate CSS for page heading and description
     *
     * @return string
     */
    protected function generatePageHeadingDescriptionCss()
    {
        $css_parts = array();

        // Page heading
        $heading_css = $this->theme->getPageHeadingCss('default');
        if (!empty($heading_css)) {
            $selector = $this->buildSelector('element', 'page-heading');
            $rule = $this->buildCssRule($selector, $heading_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Page description
        $desc_css = $this->theme->getPageDescriptionCss('default');
        if (!empty($desc_css)) {
            $selector = $this->buildSelector('element', 'page-description');
            $rule = $this->buildCssRule($selector, $desc_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        return implode("\n", $css_parts);
    }

    /**
     * Generate CSS for row headings and descriptions
     *
     * @return string
     */
    protected function generateRowHeadingDescriptionCss()
    {
        $css_parts = array();

        // Row heading
        $heading_css = $this->theme->getRowHeadingCss('default');
        if (!empty($heading_css)) {
            $selector = $this->buildSelector('element', 'row-heading');
            $rule = $this->buildCssRule($selector, $heading_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Row description
        $desc_css = $this->theme->getRowDescriptionCss('default');
        if (!empty($desc_css)) {
            $selector = $this->buildSelector('element', 'row-description');
            $rule = $this->buildCssRule($selector, $desc_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        return implode("\n", $css_parts);
    }

    /**
     * Generate CSS for row containers (spacing, background, etc.).
     *
     * @return string
     */
    protected function generateRowContainerCss()
    {
        $css_parts = array();

        $style_css = $this->theme->getRowStyleCss('default');
        if (!empty($style_css)) {
            $selector = $this->buildSelector('row');
            $rule = $this->buildCssRule($selector, $style_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        return implode("\n", $css_parts);
    }

    /**
     * Generate CSS custom properties for the progress bar.
     *
     * Uses existing theme colors instead of introducing new theme properties.
     */
    protected function generateProgressBarCss()
    {
        $selector = $this->buildSelector('form');

        $page_text = $this->sanitizeColorValue($this->theme->getPageTextColor(), '#1d2327');
        $page_bg = $this->sanitizeColorValue($this->theme->getPageBackgroundColor(), '#ffffff');
        $label_color = $this->sanitizeColorValue($this->theme->getLabelColor(), $page_text);
        $description_color = $this->sanitizeColorValue($this->theme->getFieldDescriptionColor(), '#6b7280');
        $accent_color = $this->sanitizeColorValue($this->theme->getSubmitButtonBackgroundColor(), $label_color);

        $css = sprintf(
            '--gf-progress-bar-accent: %1$s; --gf-progress-bar-muted: %2$s; --gf-progress-bar-text: %3$s; --gf-progress-bar-label: %4$s; --gf-progress-bar-page-bg: %5$s;',
            $accent_color,
            $description_color,
            $page_text,
            $label_color,
            $page_bg
        );

        return $this->buildCssRule($selector, $css);
    }

    /**
     * Generate CSS for field labels, descriptions, and errors
     *
     * @return string
     */
    protected function generateFieldTextCss()
    {
        $css_parts = array();

        // Field labels
        $label_css = $this->theme->getFieldLabelCss('default');
        if (!empty($label_css)) {
            $selector = $this->buildSelector('element', 'label');
            $rule = $this->buildCssRule($selector, $label_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Checkbox and radio labels (use same typography as field labels)
        if (!empty($label_css)) {
            $selector = $this->buildSelector('element', 'checkbox-label');
            $rule = $this->buildCssRule($selector, $label_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Rating step labels inherit label color
        if (!empty($label_css)) {
            $label_color = $this->extractCssProperty($label_css, 'color');
            if (!empty($label_color)) {
                $form_scope = '[data-gf-form-id="' . absint($this->form_id) . '"]';
                $rule = $this->buildCssRule($form_scope . ' .gf-rating-label', 'color: ' . $label_color . ';');
                if ($rule !== '') {
                    $css_parts[] = $rule;
                }
            }
        }

        // Floating labels for address fields (Bootstrap .form-floating)
        if (!empty($label_css)) {
            $form_scope = '[data-gf-form-id="' . absint($this->form_id) . '"]';

            // Default state - label appears as placeholder inside input
            $floating_css = $this->buildFloatingLabelCss($label_css);
            $rule = $this->buildCssRule($form_scope . ' [data-gf-element="floating-label"]', $floating_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }

            // Focused/filled state - label moves up and scales down
            // Extract color from label CSS and use it in focused state
            $color = $this->extractCssProperty($label_css, 'color');
            // Get field background color from field_style (text_input default state)
            $field_style = $this->theme->getProp('field_style');
            $field_bg_color = $this->extractFieldBackgroundColor($field_style);

            // In focused state, override the default centering transform and move label up
            // The label moves from center of input to above the border
            // top: 0.3rem positions it above the border with better spacing
            $floating_focused_css = 'top: 0.3rem; transform: scale(.75) translateX(.15rem);';
            if (!empty($color)) {
                // Use the same color as default state in focused state too
                $floating_focused_css .= ' color: ' . $color . ';';
            }
            // Keep background transparent in focused state to avoid visible background block
            // when page background differs from field background
            $floating_focused_css .= ' background-color: transparent;';

            // This matches Bootstrap's floating label behavior when field is focused or has a value
            $focused_selector = $form_scope . ' .form-floating > input:focus ~ [data-gf-element="floating-label"], '
                . $form_scope . ' .form-floating > input:not(:placeholder-shown) ~ [data-gf-element="floating-label"], '
                . $form_scope . ' .form-floating > textarea:focus ~ [data-gf-element="floating-label"], '
                . $form_scope . ' .form-floating > textarea:not(:placeholder-shown) ~ [data-gf-element="floating-label"], '
                . $form_scope . ' .form-floating > select ~ [data-gf-element="floating-label"]';
            $rule = $this->buildCssRule($focused_selector, $floating_focused_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }

            // Hide Bootstrap's ::after pseudo-element that creates the background for floating labels
            // We're using our own background-color property instead
            $rule = $this->buildCssRule($form_scope . ' .form-floating > [data-gf-element="floating-label"]::after', 'display: none !important;');
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Field descriptions
        $desc_css = $this->theme->getFieldDescriptionCss('default');
        if (!empty($desc_css)) {
            $selector = $this->buildSelector('element', 'description');
            $rule = $this->buildCssRule($selector, $desc_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Field error messages
        $error_css = $this->theme->getFieldErrorsCss('default');
        if (!empty($error_css)) {
            $selector = $this->buildSelector('element', 'error', false);
            $rule = $this->buildCssRule($selector, $error_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        return implode("\n", $css_parts);
    }

    /**
     * Generate CSS for form fields with all supported types and states.
     *
     * Field types: text_input, email_input, number_input, password_input,
     * url_input, phone_input, date_input, time_input, textarea, select_input,
     * checkbox_radio (radio and checkbox), file_upload
     *
     * @return string
     */
    protected function generateFieldCss()
    {
        $field_types = array(
            'text_input',
            'email_input',
            'number_input',
            'password_input',
            'url_input',
            'phone_input',
            'date_input',
            'time_input',
            'textarea',
            'select_input',
            'checkbox_radio',
            'terms_conditions',
            'file_upload',
            'address_input',
            'signature',
            'inputrichtext',
            'inputlikert',
            'imageselection',
        );

        $css_parts = array();

        foreach ($field_types as $field_type) {
            $field_css = $this->generateSingleFieldCss($field_type);
            if (!empty($field_css)) {
                $css_parts[] = $field_css;
            }
        }

        return implode("\n\n", $css_parts);
    }

    /**
     * Generates theme-driven styling for payment cards.
     */
    protected function generatePaymentFieldCss()
    {
        $scope = $this->buildSelector('form');
        $page_style = $this->theme->getPageStyleResolved('default');
        $text_input_style = $this->theme->getFieldStyleResolved('text_input', 'default');
        $text_input_focus = $this->theme->getFieldStyleResolved('text_input', 'focus');

        $page_bg = $this->sanitizeColorValue($page_style['background-color'] ?? '#ffffff', '#ffffff');
        $card_bg = $this->mixColor($page_bg, '#ffffff', 0.9);
        $border_color = $this->sanitizeColorValue($this->extractBorderColor($page_style), 'rgba(0,0,0,0.08)');
        $border_style = $page_style['border']['style'] ?? 'solid';
        $border_width = $this->extractBorderWidth($page_style);
        $text_color = $this->sanitizeColorValue($this->theme->getPageTextColor(), '#111827');
        $label_color = $this->sanitizeColorValue($this->theme->getLabelColor(), $text_color);
        $field_description_css = $this->theme->getFieldDescriptionCss('default');
        $field_description_color = $this->sanitizeColorValue($this->theme->getFieldDescriptionColor(), '#6b7280');
        $input_text_color = $this->sanitizeColorValue($text_input_style['color'] ?? $text_color, $text_color);
        $input_border_color = $this->sanitizeColorValue($this->extractBorderColor($text_input_style), 'rgba(0,0,0,0.1)');
        $input_border_radius = $text_input_style['border-radius'] ?? '12px';
        $input_bg_color = $this->sanitizeColorValue($text_input_style['background-color'] ?? '#ffffff', '#ffffff');
        $focus_border_color = $this->sanitizeColorValue($this->extractBorderColor($text_input_focus), $input_border_color);
        $focus_shadow = !empty($text_input_focus['box-shadow']) ? $text_input_focus['box-shadow'] : '0 0 0 3px rgba(79, 70, 229, 0.25)';
        $checkbox_accent = $this->theme->getCheckboxRadioAccentColor('default');
        $summary_background = $this->mixColor($page_bg, '#000000', 0.97);
        $submit_bg = $this->sanitizeColorValue($this->theme->getSubmitButtonBackgroundColor(), $label_color);
        $error_color = $this->sanitizeColorValue($this->theme->getErrorColor(), '#dc3545');
        $error_bg = $this->mixColor($page_bg, $error_color, 0.85);

        $css_parts = array();

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-field',
            sprintf(
                'background-color:%s; border-color:%s; border-style:%s; border-width:%s;',
                $card_bg,
                $border_color,
                $border_style,
                $border_width
            )
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-card',
            'color:' . $text_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-name',
            'color:' . $label_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-select span',
            'color:' . $label_color . ';'
        );

        if (!empty($field_description_css)) {
            $css_parts[] = $this->buildCssRule(
                $scope . ' .griffinforms-payment-description',
                $field_description_css . ' margin-bottom:0;'
            );
        }

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-price',
            'color:' . $label_color . '; font-weight:600;'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-link-anchor',
            'color:' . $input_text_color . ';'
        );

        if (!empty($checkbox_accent)) {
        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-toggle, '
            . $scope . ' .griffinforms-payment-gateway-radio',
            'accent-color:' . $checkbox_accent . ';'
        );
        }

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-summary',
            'background-color:' . $summary_background . '; border-radius:' . $input_border_radius . '; color:' . $text_color . ';'
        );
        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-summary-label',
            $field_description_css !== '' ? $field_description_css : ('color:' . $text_color . ';')
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-summary-amount',
            'color:' . $input_text_color . '; font-weight:600;'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-cart-summary',
            'border-color:' . $border_color . '; color:' . $text_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-cart-body',
            'background-color:' . $card_bg . '; border-radius:' . $input_border_radius . '; padding:0.75rem;'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-cart-item',
            'color:' . $text_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-card-media--placeholder',
            'background-color:' . $summary_background . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-summary-row',
            'color:' . $text_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-summary-total',
            'color:' . $label_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-cart-empty',
            'color:' . $field_description_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-review-container',
            sprintf(
                'background-color:%s; border-color:%s; border-radius:%s; color:%s;',
                $page_bg,
                $border_color,
                $input_border_radius,
                $text_color
            )
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-review-summary',
            'background-color:' . $summary_background . '; border-radius:' . $input_border_radius . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-review-field',
            sprintf(
                'background-color:%s; border-color:%s; border-radius:%s;',
                $card_bg,
                $border_color,
                $input_border_radius
            )
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-review-heading, '
            . $scope . ' .griffinforms-payment-review-title',
            'color:' . $label_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-review-description',
            $field_description_css !== '' ? $field_description_css : ('color:' . $field_description_color . ';')
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-gateway-grid',
            'gap:1rem;'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-gateway-option',
            sprintf(
                'background-color:%s; border-color:%s; border-radius:%s; color:%s;',
                $card_bg,
                $border_color,
                $input_border_radius,
                $text_color
            )
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-gateway-label',
            'color:' . $label_color . '; font-weight:600;'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-gateway-description',
            $field_description_css !== '' ? $field_description_css : ('color:' . $field_description_color . ';')
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-gateway-media',
            'border-radius:' . $input_border_radius . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-qty-inputs',
            sprintf(
                'border-color:%s; border-radius:%s; background-color:%s;',
                $input_border_color,
                $input_border_radius,
                $input_bg_color
            )
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-qty-control label',
            $field_description_css !== '' ? $field_description_css : ('color:' . $text_color . ';')
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-qty-display',
            'color:' . $input_text_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-qty-btn',
            'color:' . $label_color . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-qty-btn:hover, '
            . $scope . ' .griffinforms-payment-qty-btn:focus, '
            . $scope . ' .griffinforms-payment-qty-btn:active',
            'color:' . $submit_bg . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-inline-error',
            'color:' . $error_color . '; background-color:' . $error_bg . ';'
        );

        $css_parts[] = $this->buildCssRule(
            $scope . ' .griffinforms-payment-field.is-selected',
            'border-color:' . $focus_border_color . '; box-shadow:' . (is_string($focus_shadow) ? $focus_shadow : '0 0 0 3px rgba(79, 70, 229, 0.25)') . ';'
        );

        $this->payment_control_tokens = array(
            'color'             => $input_text_color,
            'font_family'       => $text_input_style['font-family'] ?? "system-ui, -apple-system, 'Segoe UI', sans-serif",
            'font_size'         => $text_input_style['font-size'] ?? '16px',
            'line_height'       => $text_input_style['line-height'] ?? '1.5',
            'background_color'  => $input_bg_color,
            'border_color'      => $input_border_color,
            'border_radius'     => $input_border_radius ?? '0.5rem',
            'padding'           => $text_input_style['padding'] ?? '0.75rem 1rem',
            'placeholder_color' => $field_description_color ?: '#9ca3af',
        );

        return implode("\n", array_filter($css_parts));
    }

    /**
     * Generate CSS for a single field type with all supported states.
     *
     * @param string $field_type Field type key (e.g., 'text_input', 'textarea')
     * @return string
     */
    protected function generateSingleFieldCss($field_type)
    {
        $css_parts = array();
        $states = array('default', 'hover', 'focus');

        // Map field types to theme base types that have styles
        $theme_field_type = $this->mapFieldTypeToThemeType($field_type);

        foreach ($states as $state) {
            $style_css = $this->theme->getFieldStyleCss($theme_field_type, $state);
            if (empty($style_css)) {
                continue;
            }

            $selector = $this->buildSelector('field', $field_type);

            if ($state !== 'default') {
                // Apply pseudo-class to each selector in the comma-separated list
                $selector_parts = array_map('trim', explode(',', $selector));
                $selector_parts = array_map(function($s) use ($state) {
                    return $s . ':' . $state;
                }, $selector_parts);
                $selector = implode(', ', $selector_parts);
            }

            if ($field_type === 'select_input' && $state === 'default') {
                $style_css .= ' ' . $this->buildSelectArrowCss($this->theme->getSelectCaretColor());
            }

            $rule = $this->buildCssRule($selector, $style_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        // Add placeholder styling for text input and textarea (only default state)
        // This applies the --placeholder-color custom property to the ::placeholder pseudo-element
        if (in_array($theme_field_type, array('text_input', 'textarea'), true)) {
            $placeholder_css = $this->generatePlaceholderCss($field_type);
            if (!empty($placeholder_css)) {
                $css_parts[] = $placeholder_css;
            }
        }

        // Add file selector button styling for file upload inputs
        // This applies theme styles to the ::file-selector-button pseudo-element
        if ($field_type === 'file_upload') {
            $file_button_css = $this->generateFileSelectorButtonCss($field_type);
            if (!empty($file_button_css)) {
                $css_parts[] = $file_button_css;
            }
        }

        if ($field_type === 'signature') {
            $selector = $this->buildSelector('field', 'signature');
            $css_parts[] = $this->buildCssRule($selector, 'padding: 0 !important;');
        }

        if ($field_type === 'inputrichtext') {
            $selector = $this->buildSelector('field', 'inputrichtext');
            $css_parts[] = $this->buildCssRule($selector, 'padding: 0 !important; overflow: hidden !important;');

            $resolved     = $this->theme->getFieldStyleResolved('text_input', 'default');
            $toolbar_selector = $this->buildSelector('form') . ' [data-gf-field-type="inputrichtext"] .gf-richtext-toolbar';

            // Toolbar background matches the field background colour.
            $bg_val = !empty($resolved['background-color']) ? $resolved['background-color'] : '';
            if ($bg_val !== '') {
                $css_parts[] = $this->buildCssRule($toolbar_selector, 'background-color: ' . $bg_val . ' !important;');
            }

            // Toolbar border-bottom matches the field outer border colour.
            $border_val = $this->sanitizeColorValue($this->extractBorderColor($resolved), '');
            if ($border_val !== '') {
                $css_parts[] = $this->buildCssRule($toolbar_selector, 'border-bottom-color: ' . $border_val . ' !important;');
            }

            // Active toolbar button uses the submit button background (theme primary colour).
            $active_bg = $this->theme->getSubmitButtonBackgroundColor();
            if (!empty($active_bg)) {
                $active_selector = $this->buildSelector('form') . ' [data-gf-field-type="inputrichtext"] .gf-richtext-toolbar button.is-active';
                $css_parts[] = $this->buildCssRule($active_selector, 'background-color: ' . $active_bg . ' !important; color: #fff !important;');
            }

            $editor_scope = $this->buildSelector('form') . ' [data-gf-field-type="inputrichtext"] .gf-richtext-editor';

            // Character counter: description colour normally, error colour at limit.
            $desc_color  = $this->theme->getFieldDescriptionColor();
            $error_color = $this->theme->getErrorColor();
            $counter_selector = $this->buildSelector('form') . ' [data-gf-field-type="inputrichtext"] .gf-richtext-counter';
            if (!empty($desc_color)) {
                $css_parts[] = $this->buildCssRule($counter_selector, 'color: ' . $desc_color . ' !important;');
            }
            if (!empty($error_color)) {
                $css_parts[] = $this->buildCssRule($counter_selector . '.at-limit', 'color: ' . $error_color . ' !important;');
            }

            // Hyperlinks use the theme primary colour (submit button background).
            if (!empty($active_bg)) {
                $css_parts[] = $this->buildCssRule($editor_scope . ' a', 'color: ' . $active_bg . ' !important;');
            }

            // Blockquote: left border and text colour from theme.
            if ($border_val !== '') {
                $css_parts[] = $this->buildCssRule($editor_scope . ' blockquote', 'border-left-color: ' . $border_val . ' !important;');
            }
            $label_color = $this->theme->getLabelColor();
            if (!empty($label_color)) {
                $css_parts[] = $this->buildCssRule($editor_scope . ' blockquote', 'color: ' . $label_color . ' !important;');
            }

            // Code: background is the field background mixed slightly toward black for contrast.
            if ($bg_val !== '') {
                $code_bg = $this->mixColor($bg_val, '#000000', 0.93);
                $css_parts[] = $this->buildCssRule($editor_scope . ' code', 'background: ' . $code_bg . ' !important;');
            }

            // HR: label colour at reduced opacity for a visible but not harsh rule.
            if (!empty($label_color)) {
                $hr_color = $this->mixColor($label_color, $bg_val !== '' ? $bg_val : '#ffffff', 0.6);
                $css_parts[] = $this->buildCssRule($editor_scope . ' hr', 'border-top-color: ' . $hr_color . ' !important;');
            }
        }

        if ($field_type === 'inputlikert') {
            // Suppress theme padding on the field container.
            $selector = $this->buildSelector('field', 'inputlikert');
            $css_parts[] = $this->buildCssRule($selector, 'padding: 0 !important;');

            $resolved     = $this->theme->getFieldStyleResolved('text_input', 'default');
            $border_val   = $this->sanitizeColorValue($this->extractBorderColor($resolved), '');

            // Apply border, border-radius, box-shadow from text_input theme to the wrap container.
            $wrap_scope   = $this->buildSelector('form') . ' [data-gf-field-type="inputlikert"] .gf-likert-wrap';
            $wrap_props   = ['overflow-x: auto !important', 'overflow-y: hidden !important'];
            $border_radius = !empty($resolved['border-radius']) ? $resolved['border-radius'] : '';
            if ($border_radius !== '') {
                $wrap_props[] = 'border-radius: ' . $border_radius . ' !important';
            }
            $css_parts[] = $this->buildCssRule($wrap_scope, implode('; ', $wrap_props));

            // Table header and cell borders use the theme border colour.
            $table_scope = $this->buildSelector('form') . ' [data-gf-field-type="inputlikert"] .gf-likert-table';
            if ($border_val !== '') {
                $css_parts[] = $this->buildCssRule($table_scope . ' thead th', 'border-bottom-color: ' . $border_val . ' !important;');
                $css_parts[] = $this->buildCssRule($table_scope . ' thead th:not(:first-child)', 'border-left-color: ' . $border_val . ' !important;');
                $css_parts[] = $this->buildCssRule($table_scope . ' tbody td', 'border-bottom-color: ' . $border_val . ' !important;');
                $css_parts[] = $this->buildCssRule($table_scope . ' tbody td:not(:first-child)', 'border-left-color: ' . $border_val . ' !important;');
                $css_parts[] = $this->buildCssRule($table_scope . ' tbody tr:last-child td', 'border-bottom: none !important;');
            }

            // Radio accent colour from theme primary (submit button bg).
            $accent_val = $this->sanitizeColorValue($this->theme->getSubmitButtonBackgroundColor(), '');
            if ($accent_val !== '') {
                $css_parts[] = $this->buildCssRule($table_scope . ' .gf-likert-radio', 'accent-color: ' . $accent_val . ' !important;');
            }
        }

        if ($field_type === 'imageselection') {
            $selector = $this->buildSelector('field', 'imageselection');
            $css_parts[] = $this->buildCssRule($selector, 'padding: 0 !important; overflow: hidden !important;');

            $resolved      = $this->theme->getFieldStyleResolved('text_input', 'default');
            $hover_css     = $this->theme->getFieldStyleCss('text_input', 'hover');
            $accent_val    = $this->sanitizeColorValue($this->theme->getSubmitButtonBackgroundColor(), '');
            $border_radius = !empty($resolved['border-radius']) ? $resolved['border-radius'] : '';
            $border_val    = $this->sanitizeColorValue($this->extractBorderColor($resolved), '');
            $border_width  = $this->extractBorderWidth($resolved);
            $border_style  = !empty($resolved['border']['style']) ? $resolved['border']['style'] : '';
            $hover_shadow  = $this->extractCssProperty($hover_css, 'box-shadow');
            $card_scope    = $this->buildSelector('form') . ' [data-gf-field-type="imageselection"] .gf-imgsel-card';
            $wrap_scope    = $this->buildSelector('form') . ' [data-gf-field-type="imageselection"] .gf-imgsel-wrap';

            if ($border_radius !== '') {
                $css_parts[] = $this->buildCssRule($card_scope, 'border-radius: ' . $border_radius . ' !important;');
            }

            $card_border_css = array();
            if ($border_width !== '') {
                $card_border_css[] = 'border-width: ' . $border_width . ' !important';
            }
            if ($border_style !== '') {
                $card_border_css[] = 'border-style: ' . $border_style . ' !important';
            }
            if ($border_val !== '') {
                $card_border_css[] = 'border-color: ' . $border_val . ' !important';
            }
            if (!empty($card_border_css)) {
                $css_parts[] = $this->buildCssRule($card_scope, implode('; ', $card_border_css));
            }

            if ($hover_shadow !== '') {
                $css_parts[] = $this->buildCssRule($card_scope . ':hover', 'box-shadow: ' . $hover_shadow . ';');
            }

            if ($border_val !== '') {
                $css_parts[] = $this->buildCssRule($card_scope . ' .gf-imgsel-card-body', 'border-top-color: ' . $border_val . ' !important;');
            }

            if ($accent_val !== '') {
                $css_parts[] = $this->buildCssRule($wrap_scope, '--gf-accent-color: ' . $accent_val . ';');
            }

            if ($accent_val !== '') {
                $css_parts[] = $this->buildCssRule(
                    $card_scope . '.gf-imgsel-selected',
                    'position: relative !important; z-index: 1 !important; border-color: ' . $accent_val . ' !important; box-shadow: none !important;'
                );
            }
        }

        return implode("\n", array_filter($css_parts));
    }

    /**
     * Generate CSS for placeholder text styling
     *
     * The ::placeholder pseudo-element receives the --placeholder-color custom property
     * that was set on the input/textarea element. This makes the placeholder text
     * inherit the theme's configured placeholder color.
     *
     * @param string $field_type Frontend field type (e.g., 'text_input', 'textarea')
     * @return string CSS rules for placeholder pseudo-element, or empty string if no placeholder color defined
     */
    protected function generatePlaceholderCss($field_type)
    {
        // Get the default state styles to extract placeholder color
        $theme_field_type = $this->mapFieldTypeToThemeType($field_type);
        $default_style_css = $this->theme->getFieldStyleCss($theme_field_type, 'default');

        // Check if the style CSS contains the --placeholder-color custom property
        if (empty($default_style_css) || strpos($default_style_css, '--placeholder-color:') === false) {
            return '';
        }

        // Build selector using the standard buildSelector method, but with ::placeholder pseudo-element
        $selector = $this->buildSelector('field', $field_type);

        // Apply ::placeholder pseudo-element to each selector part (input and textarea)
        $selector_parts = array_map('trim', explode(',', $selector));
        $selector_parts = array_map(function($s) {
            return $s . '::placeholder';
        }, $selector_parts);
        $selector = implode(', ', $selector_parts);

        // Extract just the placeholder color from the default state CSS
        $placeholder_color = $this->extractCssProperty($default_style_css, '--placeholder-color');
        if (!empty($placeholder_color)) {
            // Use the custom property variable for placeholder color and ensure full opacity
            return $this->buildCssRule($selector, 'color: var(--placeholder-color); opacity: 1;');
        }

        return '';
    }

    /**
     * Generate CSS for file input ::file-selector-button pseudo-element
     *
     * The ::file-selector-button pseudo-element styles the "Choose File" button.
     * It inherits border, padding, and other styles from the text_input theme.
     *
     * @param string $field_type Frontend field type (should be 'file_upload')
     * @return string CSS rules for ::file-selector-button pseudo-element
     */
    protected function generateFileSelectorButtonCss($field_type)
    {
        // Build selector for file input with ::file-selector-button pseudo-element
        $selector = $this->buildSelector('field', $field_type);

        // Apply ::file-selector-button pseudo-element to the selector
        $selector_parts = array_map('trim', explode(',', $selector));
        $selector_parts = array_map(function($s) {
            return $s . '::file-selector-button';
        }, $selector_parts);
        $selector = implode(', ', $selector_parts);

        // Get button typography styles if available
        $button_typography = $this->theme->getButtonTypographyCss('default');

        // Combine with base button styles
        $css_rules = array(
            'cursor: pointer',
            'transition: border-color 0.15s ease-in-out',
        );

        // Add button typography if available
        if (!empty($button_typography)) {
            $css_rules[] = $button_typography;
        }

        return $this->buildCssRule($selector, implode('; ', $css_rules) . ';');
    }

    /**
     * Generate CSS for file list styling
     *
     * Styles the Bootstrap list-group that displays uploaded files, matching the theme's
     * file input field styling. List items get border color from the input, and text color
     * from the input's text color. The meta text (file type and size) gets the same color
     * as the file name.
     *
     * The file list is dynamically injected into the form and scoped within
     * .griffinforms-formpage-container to avoid affecting other elements.
     *
     * @return string CSS rules for file list items
     */
    protected function generateFileListCss()
    {
        // Get file upload field styling to extract colors and border properties
        $text_input_css = $this->theme->getFieldStyleCss('text_input', 'default');

        if (empty($text_input_css)) {
            return '';
        }

        // Extract border and styling properties from the text input field
        $border_color = $this->extractCssProperty($text_input_css, 'border-color');
        $border_width = $this->extractCssProperty($text_input_css, 'border-width');
        $border_style = $this->extractCssProperty($text_input_css, 'border-style');
        $border_radius = $this->extractCssProperty($text_input_css, 'border-radius');
        $background_color = $this->extractCssProperty($text_input_css, 'background-color');
        $box_shadow = $this->extractCssProperty($text_input_css, 'box-shadow');

        // Get label color from the theme instead of input field text color
        $label_color = $this->theme->getLabelColor();

        if (empty($border_color) && empty($label_color)) {
            return '';
        }

        // Helper to clean !important from extracted values (they already contain it)
        $clean_important = function($value) {
            return trim(str_replace('!important', '', $value));
        };

        $css_rules = array();

        // Reset list-group margins/padding to avoid interference from theme styles
        $reset_rule = $this->buildCssRule(
            '.griffinforms-formpage-container ul.list-group',
            'margin: 0 0 8px 0 !important; padding: 0 !important;'
        );
        if ($reset_rule !== '') {
            $css_rules[] = $reset_rule;
        }

        // Style file list items with border styling from input field
        $item_selector = '.griffinforms-formpage-container [id^="gf-fileslist-"] .list-group-item';

        $item_styles = array();

        // Apply border properties if available (need all three for valid border)
        if (!empty($border_color) && !empty($border_width) && !empty($border_style)) {
            $item_styles[] = 'border-width: ' . $clean_important($border_width) . ' !important';
            $item_styles[] = 'border-style: ' . $clean_important($border_style) . ' !important';
            $item_styles[] = 'border-color: ' . $clean_important($border_color) . ' !important';
        } elseif (!empty($border_color)) {
            // Fallback: if we only have color, still apply it (Bootstrap has default width/style)
            $item_styles[] = 'border-color: ' . $clean_important($border_color) . ' !important';
        }

        // Prevent double borders between list items
        $item_styles[] = 'margin-bottom: -1px !important';

        // Apply background color if available
        if (!empty($background_color)) {
            $item_styles[] = 'background-color: ' . $clean_important($background_color) . ' !important';
        }

        // Apply label color if available
        if (!empty($label_color)) {
            $item_styles[] = 'color: ' . $label_color . ' !important';
        }

        if (!empty($item_styles)) {
            $rule = $this->buildCssRule($item_selector, implode('; ', $item_styles) . ';');
            if ($rule !== '') {
                $css_rules[] = $rule;
            }

            // Also style the file name text specifically
            if (!empty($label_color)) {
                $rule = $this->buildCssRule(
                    '.griffinforms-formpage-container [id^="gf-fileslist-"] .list-group-item .fw-medium',
                    'color: ' . $label_color . ' !important;'
                );
                if ($rule !== '') {
                    $css_rules[] = $rule;
                }
            }

            // Style the meta text (file type and size) same as file name
            if (!empty($label_color)) {
                $rule = $this->buildCssRule(
                    '.griffinforms-formpage-container [id^="gf-fileslist-"] .list-group-item .text-muted',
                    'color: ' . $label_color . ' !important;'
                );
                if ($rule !== '') {
                    $css_rules[] = $rule;
                }
            }
        }

        // Apply border radius and shadow to the list-group container (not individual items)
        if (!empty($border_radius) || !empty($box_shadow)) {
            $list_group_selector = '.griffinforms-formpage-container [id^="gf-fileslist-"]';
            $list_group_styles = array();

            if (!empty($border_radius)) {
                $list_group_styles[] = 'border-radius: ' . $clean_important($border_radius) . ' !important';
            }

            if (!empty($box_shadow)) {
                $list_group_styles[] = 'box-shadow: ' . $clean_important($box_shadow) . ' !important';
            }

            if (!empty($list_group_styles)) {
                $rule = $this->buildCssRule($list_group_selector, implode('; ', $list_group_styles) . ';');
                if ($rule !== '') {
                    $css_rules[] = $rule;
                }
            }
        }

        return implode("\n", array_filter($css_rules));
    }

    /**
     * Generate CSS for checkbox, radio, and terms condition labels using label color
     *
     * Styles the form-check-label elements in checkbox groups, radio groups, and
     * terms conditions with the theme's label color for proper contrast and
     * visual consistency.
     *
     * Uses data-gf-element="checkbox-label" for checkbox/radio labels and
     * .griffinforms-terms-field .form-check-label for terms condition labels.
     *
     * @return string CSS rules for option labels
     */
    protected function generateOptionLabelCss()
    {
        // Get label color from the theme
        $label_color = $this->theme->getLabelColor();

        if (empty($label_color)) {
            return '';
        }

        $css_rules = array();

        // CSS selector for checkbox and radio labels with data-gf-element attribute
        $checkbox_label_selector = '[data-gf-element="checkbox-label"]';

        // CSS selector for terms condition labels (both with data-gf-element and class selector)
        $terms_label_direct = '.griffinforms-terms-field .form-check-label';

        // Apply label color to checkbox/radio labels
        $css_rules[] = $checkbox_label_selector . ' { color: ' . $label_color . ' !important; }';

        // Apply label color to terms condition labels
        $css_rules[] = $terms_label_direct . ' { color: ' . $label_color . ' !important; }';

        // Apply label color to terms subtext helper copy
        $css_rules[] = '.griffinforms-terms-subtext { color: ' . $label_color . ' !important; }';

        // Align checkbox input and label using flex layout when theme is active
        $css_rules[] = '.griffinforms-terms-check { display: flex; align-items: center; column-gap: 0.5rem; }';

        // Apply icon tinting based on label color brightness
        // Calculate brightness to determine if color is light or dark
        $icon_filter = $this->getIconFilterForColor($label_color);
        if (!empty($icon_filter)) {
            $css_rules[] = '.gf-file-icon { filter: ' . $icon_filter . ' !important; }';
        }

        // Apply error color to file remove button
        $error_color = $this->getErrorColor();
        if (!empty($error_color)) {
            $css_rules[] = '.gf-file-remove { color: ' . $error_color . ' !important; }';
        }

        return implode("\n", array_filter($css_rules));
    }

    /**
     * Calculate a CSS filter to tint icons to match label color brightness
     *
     * Uses brightness and optional invert filters to adjust icon appearance
     * based on whether the label color is light or dark.
     *
     * @param string $color The label color (hex or rgb format)
     * @return string CSS filter value or empty string if unable to determine
     */
    protected function getIconFilterForColor($color)
    {
        // Parse color to RGB values
        $rgb = $this->parseColorToRgb($color);
        if (empty($rgb)) {
            return '';
        }

        // Calculate relative brightness using standard luminance formula
        // Formula: (R*299 + G*587 + B*114) / 1000
        $brightness = ($rgb['r'] * 299 + $rgb['g'] * 587 + $rgb['b'] * 114) / 1000;

        // If color is dark (brightness < 128), lighten the icon
        // If color is light (brightness >= 128), darken the icon
        if ($brightness < 128) {
            // Dark color: brighten the icon
            return 'brightness(1.5)';
        } else {
            // Light color: darken the icon
            return 'brightness(0.6)';
        }
    }

    /**
     * Parse a color string (hex or rgb) into RGB array
     *
     * @param string $color Color in #RRGGBB or rgb(r,g,b) format
     * @return array|empty Array with keys 'r', 'g', 'b' or empty array if unable to parse
     */
    protected function parseColorToRgb($color)
    {
        $color = trim((string) $color);

        // Handle hex color (#RRGGBB or #RGB)
        if (strpos($color, '#') === 0) {
            $hex = substr($color, 1);

            if (strlen($hex) === 6) {
                return array(
                    'r' => hexdec(substr($hex, 0, 2)),
                    'g' => hexdec(substr($hex, 2, 2)),
                    'b' => hexdec(substr($hex, 4, 2)),
                );
            } elseif (strlen($hex) === 3) {
                return array(
                    'r' => hexdec(str_repeat(substr($hex, 0, 1), 2)),
                    'g' => hexdec(str_repeat(substr($hex, 1, 1), 2)),
                    'b' => hexdec(str_repeat(substr($hex, 2, 1), 2)),
                );
            }
        }

        // Handle rgb/rgba color
        if (strpos($color, 'rgb') === 0) {
            if (preg_match('/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/', $color, $matches)) {
                return array(
                    'r' => intval($matches[1]),
                    'g' => intval($matches[2]),
                    'b' => intval($matches[3]),
                );
            }
        }

        return array();
    }

    /**
     * Get the error text color from the theme
     *
     * Retrieves the error text color directly from the theme configuration.
     * Used for styling error indicators like the file remove button.
     *
     * @return string The error color (e.g., "#dc3545") or empty string if not found
     */
    protected function getErrorColor()
    {
        return $this->theme->getErrorColor();
    }

    /**
     * Map frontend field types to theme base field types
     *
     * FormTheme only stores styles for 4 base types:
     * - text_input (used for: text, email, number, password, url, phone, date, time, address, file_upload)
     * - textarea (used for: textarea)
     * - select_input (used for: select_input)
     * - checkbox_radio (used for: checkbox_radio, terms_conditions)
     *
     * @param string $field_type Frontend field type (e.g., 'email_input')
     * @return string Theme base field type (e.g., 'text_input')
     */
    protected function mapFieldTypeToThemeType($field_type)
    {
        // Map all input types to text_input base style (including file_upload)
        $text_input_types = array(
            'text_input',
            'email_input',
            'number_input',
            'password_input',
            'url_input',
            'phone_input',
            'date_input',
            'time_input',
            'address_input',
            'file_upload',
            'signature',
            'inputrichtext',
            'inputlikert',
        );

        if ($field_type === 'imageselection') {
            return 'choice';
        }

        if (in_array($field_type, $text_input_types, true)) {
            return 'text_input';
        }

        // Map checkbox/radio/terms to checkbox_radio base style
        $checkbox_types = array(
            'checkbox_radio',
            'terms_conditions',
        );

        if (in_array($field_type, $checkbox_types, true)) {
            return 'checkbox_radio';
        }

        // Return as-is for known types (textarea, select_input)
        return $field_type;
    }

    /**
     * Generate CSS for buttons (submit, previous, next) with all states.
     */
    protected function generateButtonCss()
    {
        $button_types = array(
            'submit' => array('method' => 'getSubmitButtonCss', 'data' => 'submit'),
            'previous' => array('method' => 'getPreviousButtonCss', 'data' => 'previous'),
            'next' => array('method' => 'getNextButtonCss', 'data' => 'next'),
            'review-back' => array('method' => 'getPreviousButtonCss', 'data' => 'review-back'),
            'review-continue' => array('method' => 'getSubmitButtonCss', 'data' => 'review-continue'),
        );

        $css_parts = array();
        $states = array('default', 'hover', 'focus', 'active');

        foreach ($button_types as $button_info) {
            $method = $button_info['method'];
            $data_value = $button_info['data'];

            foreach ($states as $state) {
                $style_css = $this->theme->{$method}($state);
                if (empty($style_css)) {
                    continue;
                }

                $selector = $this->buildSelector('button', $data_value);
                if ($state !== 'default') {
                    $selector .= ':' . $state;
                }

                $rule = $this->buildCssRule($selector, $style_css);
                if ($rule !== '') {
                    $css_parts[] = $rule;
                }
            }
        }

        return implode("\n", array_filter($css_parts));
    }

    /**
     * Generate CSS for success and error messages
     *
     * @return string
     */
    protected function generateMessageCss()
    {
        $css_parts = array();

        $message_states = array('default', 'hover', 'focus');
        foreach ($message_states as $state) {
            $success_css = $this->theme->getSuccessMessageCss($state);
            if (empty($success_css)) {
                continue;
            }

            $selector = $this->buildSelector('element', 'success-message', false);
            if ($state !== 'default') {
                $selector .= ':' . $state;
            }

            $rule = $this->buildCssRule($selector, $success_css);
            if ($rule !== '') {
                $css_parts[] = $rule;
            }
        }

        return implode("\n", $css_parts);
    }

    /**
     * Generate CSS for no-JS notice.
     */
    protected function generateNoJsNoticeCss()
    {
        $css_parts = array();

        $form_css = $this->theme->getFormStyleCss('default');
        $page_css = $this->theme->getPageStyleCss('default');
        $label_css = $this->theme->getFieldLabelCss('default');

        $form_bg = $this->extractCssProperty($form_css, 'background-color');
        $form_text = $this->extractCssProperty($form_css, 'color');
        $form_border = $this->extractCssProperty($form_css, 'border-color');
        $page_bg = $this->extractCssProperty($page_css, 'background-color');
        $label_color = $this->extractCssProperty($label_css, 'color');

        $notice_bg = $this->sanitizeColorValue($page_bg, $this->sanitizeColorValue($form_bg, '#fef9c3'));
        $notice_text = $this->sanitizeColorValue($label_color, $this->sanitizeColorValue($form_text, '#1d2327'));
        $notice_border = $this->sanitizeColorValue($form_border, '#f1c40f');

        $notice_selector = $this->buildSelector('element', 'nojs-notice', false);
        $title_selector = $this->buildSelector('element', 'nojs-title', false);
        $body_selector = $this->buildSelector('element', 'nojs-body', false);

        $css_parts[] = $notice_selector . ' { background: ' . $notice_bg . '; border: 1px solid ' . $notice_border . '; color: ' . $notice_text . '; }';
        $css_parts[] = $title_selector . ' { color: ' . $notice_text . '; font-weight: 600; }';
        $css_parts[] = $body_selector . ' { color: ' . $notice_text . '; }';

        return implode("\n", array_filter($css_parts));
    }

    /**
     * Generate CSS for form lock overlay and spinner container.
     */
    protected function generateFormLockCss()
    {
        $css_parts = array();

        $form_css = $this->theme->getFormStyleCss('default');
        $page_css = $this->theme->getPageStyleCss('default');
        $label_css = $this->theme->getFieldLabelCss('default');
        $button_css = $this->theme->getSubmitButtonCss('default');

        $form_bg = $this->extractCssProperty($form_css, 'background-color');
        $form_text = $this->extractCssProperty($form_css, 'color');
        $form_border = $this->extractCssProperty($form_css, 'border-color');
        $page_bg = $this->extractCssProperty($page_css, 'background-color');
        $label_color = $this->extractCssProperty($label_css, 'color');
        $button_bg = $this->extractCssProperty($button_css, 'background-color');
        $button_color = $this->extractCssProperty($button_css, 'color');

        $overlay_base = $this->sanitizeColorValue($form_bg, '#ffffff');
        $overlay_color = $this->buildRgbaColor($overlay_base, 0.6);

        $card_bg = $this->sanitizeColorValue($page_bg, $this->sanitizeColorValue($form_bg, '#ffffff'));
        $card_border = $this->sanitizeColorValue($form_border, '#dcdcde');
        $card_text = $this->sanitizeColorValue($label_color, $this->sanitizeColorValue($form_text, '#1d2327'));

        $spinner_color = $this->sanitizeColorValue($button_bg, $this->sanitizeColorValue($button_color, '#2271b1'));

        $overlay_selector = $this->buildSelector('element', 'form-lock', false);
        $card_selector = $this->buildSelector('element', 'form-lock-card', false);
        $spinner_selector = $this->buildSelector('element', 'form-lock-spinner', false);
        $field_spinner_selector = $this->buildSelector('element', 'field-spinner', false);

        $css_parts[] = $overlay_selector . ' { background: ' . $overlay_color . '; }';
        $css_parts[] = $card_selector . ' { background: ' . $card_bg . '; border-color: ' . $card_border . '; color: ' . $card_text . '; font-family: inherit; }';
        $css_parts[] = $spinner_selector . ' { color: ' . $spinner_color . '; }';
        $css_parts[] = $field_spinner_selector . ' { color: ' . $spinner_color . '; }';

        return implode("\n", array_filter($css_parts));
    }

    /**
     * Build a CSS selector scoped to this form using data attributes
     *
     * Examples:
     *   buildSelector('form') → [data-gf-form-id="123"]
     *   buildSelector('field', 'text_input') → [data-gf-form-id="123"] [data-gf-field-type="text_input"]
     *   buildSelector('element', 'label') → [data-gf-form-id="123"] [data-gf-element="label"]
     *   buildSelector('button', 'submit') → [data-gf-form-id="123"] [data-gf-button-type="submit"]
     *
     * @param string $type     Type of element (form, field, element, button)
     * @param string $subtype  Specific subtype/value (optional)
     * @return string
     */
    protected function buildSelector($type, $subtype = '', $scoped = true)
    {
        $form_scope = '[data-gf-form-id="' . absint($this->form_id) . '"]';

        switch ($type) {
            case 'form':
                return $form_scope;

            case 'page':
                return $form_scope . ' [data-gf-page-type="page"]';

            case 'row':
                return $form_scope . ' [data-gf-row="container"]';

            case 'field':
                if ($subtype === 'signature') {
                    return $form_scope . ' [data-gf-field-type="signature"] .gf-signature-canvas';
                }
                if ($subtype === 'inputrichtext') {
                    return $form_scope . ' [data-gf-field-type="inputrichtext"] .gf-richtext-field';
                }
                if ($subtype === 'inputlikert') {
                    return $form_scope . ' [data-gf-field-type="inputlikert"] .gf-likert-table';
                }
                if ($subtype === 'imageselection') {
                    return $form_scope . ' [data-gf-field-type="imageselection"] .gf-imgsel-card';
                }
                return $form_scope . ' [data-gf-field-type="' . $this->sanitizeDataAttribute($subtype) . '"] input, '
                    . $form_scope . ' [data-gf-field-type="' . $this->sanitizeDataAttribute($subtype) . '"] textarea, '
                    . $form_scope . ' [data-gf-field-type="' . $this->sanitizeDataAttribute($subtype) . '"] select';

            case 'element':
                $element_selector = '[data-gf-element="' . $this->sanitizeDataAttribute($subtype) . '"]';
                if ($scoped) {
                    return $form_scope . ' ' . $element_selector;
                }
                $scoped_selector = $form_scope . ' ' . $element_selector;
                $id_selector = '[data-gf-form-id="' . absint($this->form_id) . '"]' . $element_selector;
                return $scoped_selector . ', ' . $id_selector;

            case 'button':
                return $form_scope . ' [data-gf-button-type="' . $this->sanitizeDataAttribute($subtype) . '"]';

            default:
                return $form_scope;
        }
    }

    /**
     * Normalize color values with fallback.
     */
    protected function sanitizeColorValue($value, $fallback)
    {
        if (is_string($value) && trim($value) !== '') {
            return trim($value);
        }
        return $fallback;
    }

    protected function extractBorderColor($style)
    {
        if (isset($style['border']['color']) && $style['border']['color'] !== '') {
            return $style['border']['color'];
        }

        return '';
    }

    protected function extractBorderWidth($style)
    {
        if (isset($style['border']['width'])) {
            $width = $style['border']['width'];
            if (is_array($width) && isset($width['top'])) {
                return $width['top'];
            }

            if (!is_array($width) && !empty($width)) {
                return $width;
            }
        }

        return '1px';
    }

    protected function mixColor($base, $mix, $ratio = 0.5)
    {
        $base_rgb = $this->hexToRgbArray($base);
        $mix_rgb = $this->hexToRgbArray($mix);

        if (!$base_rgb || !$mix_rgb) {
            return $base;
        }

        $ratio = max(0, min(1, (float) $ratio));
        $r = (int) round($base_rgb['r'] * $ratio + $mix_rgb['r'] * (1 - $ratio));
        $g = (int) round($base_rgb['g'] * $ratio + $mix_rgb['g'] * (1 - $ratio));
        $b = (int) round($base_rgb['b'] * $ratio + $mix_rgb['b'] * (1 - $ratio));

        return sprintf('#%02x%02x%02x', $r, $g, $b);
    }

    /**
     * Build rgba() value from hex color.
     */
    protected function buildRgbaColor($color, $alpha = 0.6)
    {
        $rgb = $this->hexToRgbArray($color);
        if (!$rgb) {
            return 'rgba(255, 255, 255, ' . $alpha . ')';
        }

        $alpha = max(0, min(1, (float) $alpha));
        return sprintf('rgba(%d, %d, %d, %.2f)', $rgb['r'], $rgb['g'], $rgb['b'], $alpha);
    }

    protected function hexToRgbArray($color)
    {
        if (!is_string($color)) {
            return null;
        }

        $color = trim($color);
        if (strpos($color, '#') !== 0) {
            return null;
        }

        $hex = substr($color, 1);
        if (strlen($hex) === 3) {
            $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
        }

        if (strlen($hex) !== 6) {
            return null;
        }

        $int = hexdec($hex);

        return array(
            'r' => ($int >> 16) & 255,
            'g' => ($int >> 8) & 255,
            'b' => $int & 255,
        );
    }

    /**
     * Build CSS for Bootstrap floating labels with proper positioning and background
     *
     * Floating labels (.form-floating) need special handling:
     * - Extract color from label CSS
     * - Add background for visibility on different backgrounds
     * - Preserve Bootstrap's transform behavior
     *
     * @param string $label_css Label CSS containing color and typography
     * @return string CSS for floating labels
     */
    protected function buildFloatingLabelCss($label_css)
    {
        // Extract color and font-size properties from label CSS
        $color = $this->extractCssProperty($label_css, 'color');
        $font_size = $this->extractCssProperty($label_css, 'font-size');

        // Build floating label CSS with Bootstrap compatibility
        $css = array();

        // Base positioning - centered vertically like a placeholder in default state
        // Use calc to center the label accounting for input height and padding
        // This positions the label in the true center of the input's visible area
        $css[] = 'position: absolute;';
        $css[] = 'top: calc(50% - 0.75rem);';
        $css[] = 'left: 0.75rem;';
        $css[] = 'z-index: 2;';
        $css[] = 'height: auto;';
        $css[] = 'padding: 0;'; // No padding needed since we're centering
        $css[] = 'overflow: hidden;';
        $css[] = 'text-align: start;';
        $css[] = 'text-overflow: ellipsis;';
        $css[] = 'white-space: nowrap;';
        $css[] = 'pointer-events: none;';
        $css[] = 'border: none;';
        $css[] = 'transform-origin: 0 0;';
        $css[] = 'transition: opacity .1s ease-in-out,transform .1s ease-in-out;';

        // Add color from theme if available
        if (!empty($color)) {
            $css[] = 'color: ' . $color . ';';
        }

        // Use full font-size for default state (as placeholder-like text inside input)
        // Will be scaled down to 80% in the focused/filled state
        if (!empty($font_size)) {
            $css[] = 'font-size: ' . $font_size . ';';
        } else {
            $css[] = 'font-size: 1rem;';
        }

        // Transparent background - let the input background show through
        // Label color visibility relies on theme color selection
        $css[] = 'background-color: transparent;';

        return implode(' ', $css);
    }

    /**
     * Extract a specific CSS property value from a CSS declaration string
     *
     * @param string $css        CSS declaration string (e.g., "color: #000; font-size: 14px;")
     * @param string $property   Property name to extract (e.g., "color")
     * @return string|null Property value or null if not found
     */
    protected function extractCssProperty($css, $property)
    {
        // Match property: value; pattern
        $pattern = '/' . preg_quote($property) . '\s*:\s*([^;]+);/';
        if (preg_match($pattern, $css, $matches)) {
            return trim($matches[1]);
        }
        return null;
    }

    /**
     * Extract background color from form_style property.
     *
     * @param mixed $form_style Form style array or raw value
     * @return string|null Background color or null if not found
     */
    protected function extractFormBackgroundColor($form_style)
    {
        if (empty($form_style)) {
            return null;
        }

        if (is_string($form_style)) {
            $decoded = json_decode($form_style, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                $decoded = @unserialize($form_style);
            }
            $form_style = is_array($decoded) ? $decoded : array();
        }

        if (!is_array($form_style)) {
            return null;
        }

        if (isset($form_style['default']['background-color'])) {
            return $form_style['default']['background-color'];
        }

        // Legacy responsive data: xs/sm/md/lg.
        foreach (array('xs', 'sm', 'md', 'lg') as $breakpoint) {
            if (isset($form_style[$breakpoint]['default']['background-color'])) {
                return $form_style[$breakpoint]['default']['background-color'];
            }
        }

        return null;
    }

    /**
     * Extract background color from field_style property
     *
     * Field style contains multiple field types: text_input, textarea, select_input, checkbox_radio
     * Each has states: default, focus, hover
     * Returns the default state background-color from text_input (most common for floating labels)
     *
     * @param mixed $field_style Field style array or raw value
     * @return string|null Background color or null if not found
     */
    protected function extractFieldBackgroundColor($field_style)
    {
        if (empty($field_style)) {
            return null;
        }

        // Normalize the field_style if it's a string
        if (is_string($field_style)) {
            $decoded = json_decode($field_style, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                $decoded = @unserialize($field_style);
            }
            $field_style = is_array($decoded) ? $decoded : array();
        }

        if (!is_array($field_style)) {
            return null;
        }

        // Look for text_input (most common field type with floating labels)
        if (isset($field_style['text_input']) && is_array($field_style['text_input'])) {
            $text_input = $field_style['text_input'];

            // Check for default state
            if (isset($text_input['default']) && is_array($text_input['default'])) {
                if (isset($text_input['default']['background-color'])) {
                    return $text_input['default']['background-color'];
                }
            }
        }

        return null;
    }

    /**
     * Sanitizes declarations before printing them in a style block.
     *
     * @param string $declarations
     * @return string
     */
    protected function sanitizeDeclarations($declarations)
    {
        return FormTheme::escapeCssDeclarations($declarations);
    }

    /**
     * Builds a CSS rule with sanitized declarations.
     *
     * @param string $selector
     * @param string $declarations
     * @return string
     */
    protected function buildCssRule($selector, $declarations)
    {
        $declarations = $this->sanitizeDeclarations($declarations);
        if ($declarations === '') {
            return '';
        }

        return $selector . ' { ' . $declarations . ' }';
    }

    /**
     * Sanitize a string for use in data attribute values
     *
     * Removes or escapes characters that could break the attribute
     *
     * @param string $value Value to sanitize
     * @return string
     */
    protected function sanitizeDataAttribute($value)
    {
        // Convert to string and trim
        $value = trim((string) $value);

        // Remove quotes, convert to lowercase, replace spaces/underscores
        $value = strtolower($value);
        $value = preg_replace('/[^a-z0-9_-]/', '-', $value);
        $value = preg_replace('/-+/', '-', $value); // Collapse multiple dashes

        return $value;
    }
}
