<?php

namespace GriffinForms\Frontend\Html\Forms\Fields;

use GriffinForms\Frontend\Html\Forms\Fields\Traits\VariableValues;
use GriffinForms\Frontend\Html\Forms\Fields\Traits\HtmlAttrs;

class Format extends \GriffinForms\Frontend\Html\Forms\Format
{
    use VariableValues;
    use HtmlAttrs;

    protected $item;
    protected $validations = array('type' => 'none');
    protected $edit_mode = false;
    protected $submission_context = array();
    protected $submission_values = array();
    protected $base_value;
    protected $default_render_value;
    protected $hydration_parse_failed = false;
    protected $current_iteration = 0;
    protected $value;
    protected $get_value;
    protected $iterations;
    protected $field_options;
    protected $field_settings;
    protected $alerts;
    protected $errors = [];
    protected $bs_field_container_classes = '';
    protected $bs_field_label_classes = 'text-dark';
    protected $bs_field_input_wrapper_classes = '';
    protected $bs_field_description_classes = '';
    protected $bs_field_alert_classes = 'text-danger small';
    
    protected function setValue()
    {
        $this->value = '';
        $value = $this->item->getProp('default_value');
                
        if (!is_array($value) or empty('default_value')) {
            $this->base_value = $this->value;
            $this->default_render_value = $this->value;
            return;
        }
        
        if (!array_key_exists('value_type', $value)) {
            return;
        }
        
        if ($value['value_type'] === 'none') {
            return;
        }
        
        if ($value['value_type'] === 'custom') {
            $this->value = $value['custom'];
            $this->base_value = $this->value;
            $this->default_render_value = $this->value;
            return;
        }
        
        if ($value['value_type'] === 'variable') {
            $value_method = 'get' . str_replace('_', '', $value['variable']);
            $this->value = $this->$value_method();
            $this->base_value = $this->value;
            $this->default_render_value = $this->value;
            return;
        }

        $this->base_value = $this->value;
        $this->default_render_value = $this->value;
    }
    
    protected function setGetValue()
    {
        $value = $this->item->getProp('default_value');

        if (isset($value['get_value']) and $value['get_value']) {
            if (isset($_GET[$value['get_name']])) {
                $this->value = sanitize_text_field(wp_unslash($_GET[$value['get_name']]));
            }
        }

        $this->base_value = $this->value;
        $this->default_render_value = $this->value;
    }
    
    protected function setIterations()
    {
        $iterations = $this->item->getProp('iterations');
        
        if (is_array($iterations) and key_exists('allow', $iterations)) {
            $this->iterations = $this->item->getProp('iterations');
        } else {
            $this->iterations = array('allow' => 0);
        }
        
        if (key_exists('min', $this->iterations) and $this->iterations['min'] == 0) {
            $this->iterations['min'] = 1;
        }
    }
    
    protected function setValidations()
    {
        if (is_array($this->item->getProp('validations'))) {
            $this->validations = $this->item->getProp('validations');
        }
    }
    
    protected function setFieldOptions()
    {
        if (is_array($this->item->getProp('field_options'))) {
            $this->field_options = $this->item->getProp('field_options');
        } else {
            $this->field_options = array();
        }
    }

    protected function setFieldSettings()
    {
        if (is_array($this->item->getProp('field_settings')) and !empty($this->item->getProp('field_settings'))) {
            $this->field_settings = $this->item->getProp('field_settings');
        } else {
            $this->field_settings = array();
        }
    }
    
    final protected function setSql()
    {
        $sql_class = '\GriffinForms\Frontend\Sql\Field';
        $this->sql = $sql_class::getInstance();
    }        
    
    final protected function setLang()
    {
        $lang_class = '\GriffinForms\Frontend\Language\Field';
        $this->lang = $lang_class::getInstance();
    }
    
    final protected function setAlerts()
    {
        $alerts_class = '\GriffinForms\Frontend\Html\Alerts\Field';
        $this->alerts = $alerts_class::getInstance();
    }
    
    protected function setData()
    {
        $this->data['field'] = $this->id;
    }

    protected function setEditMode()
    {
        $this->edit_mode = !empty($this->data['edit_mode']);
    }

    protected function setSubmissionContext()
    {
        $context = $this->data['submission_context'] ?? array();
        if (!is_array($context)) {
            $context = array();
        }

        $this->submission_context = $context;
        $values = $context['field_values'] ?? array();
        $this->submission_values = is_array($values) ? $values : array();
    }
    
    public function setItem()
    {
        $this->item = new \GriffinForms\Items\Field();
        $this->item->loadItem($this->id);
    }
    
    protected function setHtmlIds()
    {
        $prefix = 'griffinforms-' . strtolower($this->meta) . '-';
        $this->html_ids = array(
            'container' => $prefix . $this->id,
            'formcontrol' => 'griffinforms-formcontrol-' . $this->id, 
            'formcontrol-container' => $prefix . 'formcontrol-container-' . $this->id, 
            'add_btn' => $prefix . 'addbtn-' . $this->id,
            'del_btn' => $prefix . 'delbtn-' . $this->id,
            'btns' => $prefix . 'btns-' . $this->id,
            'script' => $prefix . $this->id . '-js',
            'action_btn' => 'griffinforms-formpage-nextbtn-' . $this->data['formpage']
        );
    }
    
    protected function isRequired()
    {
        if ($this->item->getProp('is_required')) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isReadonly()
    {
        if ($this->item->getProp('is_readonly')) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isDisabled()
    {
        if ($this->item->getProp('is_disabled')) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isAutoComplete()
    {
        if ($this->data['autocomplete']) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isPattern()
    {
        if (array_key_exists('type', $this->validations)
            and $this->validations['type'] === 'pattern'
            and array_key_exists('pattern', $this->validations)
        ) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isIterations()
    {
        if ($this->iterations['allow']) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isUnique()
    {
        if ($this->iterations['allow']
           and $this->iterations['unique']
        ) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function isDbValidate()
    {
        return isset($this->validations['type']) && $this->validations['type'] === 'db-validate';
    }

    protected function isCustomValidations()
    {
        return isset($this->validations['type']) && $this->validations['type'] === 'custom-parameters';
    }
    
    public function html()
    {
        $is_theme_set = $this->data['is_theme_set'] ?? false;
        $html = '<div id="' . esc_attr($this->html_ids['container']) . '" ';
        $description = $this->item->getProp('description');
        $margin_class = empty($description) ? 'mb-2' : 'mb-3 gf-field-has-description';
        $container_classes = 'griffinforms-field-container ' . esc_attr($margin_class);

        if ($this->item->getProp('is_hidden')) {
            $container_classes .= ' d-none';
        }

        if (!$is_theme_set && !empty($this->bs_field_container_classes)) {
            $container_classes .= ' ' . esc_attr($this->bs_field_container_classes);
        }

        $html .= 'class="' . esc_attr($container_classes) . '"';
        // Add data attribute for field type styling
        $field_type = $this->getFieldType();
        if (!empty($field_type)) {
        $html .= ' data-gf-field-type="' . esc_attr($field_type) . '"';
        }
        $html .= '>';

        $html .= $this->fieldLabel($is_theme_set);

        if ($this->iterations['allow']) {
            $render_iterations = $this->getRenderIterationCount();
            $i = 0;
            do {
                $this->value = $this->resolveRenderedValue($i);
                $this->current_iteration = $i;
                $this->syncRenderedValue($i);
                $input_wrapper_classes = esc_attr($this->html_ids['formcontrol-container']) . ' griffinforms-formcontrol-container mb-2';
                if (!$is_theme_set && !empty($this->bs_field_input_wrapper_classes)) {
                    $input_wrapper_classes .= ' ' . esc_attr($this->bs_field_input_wrapper_classes);
                }
                $html .= '<div class="' . $input_wrapper_classes . '" style="position:relative;">';
                $html .= $this->fieldLoader();
                $html .= $this->formControl($i);
                $html .= $this->errorAlerts($is_theme_set);
                $html .= '</div>';
                $i++;
            } while ($i < $render_iterations);

            if ($this->shouldRenderIterationButtons($render_iterations)) {
                $html .= $this->iterationBtns($is_theme_set);
            }
        } else {
            $this->value = $this->resolveRenderedValue(0);
            $this->current_iteration = 0;
            $this->syncRenderedValue(0);
            $input_wrapper_classes = esc_attr($this->html_ids['formcontrol-container']) . ' griffinforms-formcontrol-container';
            if (!$is_theme_set && !empty($this->bs_field_input_wrapper_classes)) {
                $input_wrapper_classes .= ' ' . esc_attr($this->bs_field_input_wrapper_classes);
            }
            $html .= '<div class="' . $input_wrapper_classes . '" style="position:relative;">';
            $html .= $this->fieldLoader();
            $html .= $this->formControl();
            $html .= $this->errorAlerts($is_theme_set);
            $html .= '</div>';
        }

        $html .= $this->helpText($is_theme_set);
        $html .= $this->hydrationNotice($is_theme_set);

        $html .= '</div>';
        return $html;
    }
    
    protected function fieldLabel($is_theme_set = false)
    {
        $heading = (string) $this->item->getProp('heading');
        if ('' === $heading) {
            return;
        }

        $label_classes = 'form-label';
        if (!$is_theme_set && !empty($this->bs_field_label_classes)) {
            $label_classes .= ' ' . esc_attr($this->bs_field_label_classes);
        }

        $html  = '<label class="' . esc_attr($label_classes) . '" data-gf-element="label" data-gf-original-label="' . esc_attr($heading) . '">';
        $html .= esc_html($heading);

        if ($this->isRequired()) {
            $html .= $this->requiredIndicatorHtml();
        }

        $html .= '</label>';

        return $html;
    }
    
    protected function requiredIndicatorHtml(): string
    {
        $settings = \GriffinForms\Settings::getInstance();
        $mode     = (string) $settings->getOption('required_indicator_mode', 'asterisk');

        if ($mode === 'asterisk') {
            return ' <span class="gf-required-indicator" aria-hidden="true">*</span>';
        }

        $text = (string) $settings->getOption('required_indicator_custom_text', '');
        if ($text === '' || $text === 'none') {
            return '';
        }
        return ' <span class="gf-required-indicator" aria-hidden="true">' . esc_html($text) . '</span>';
    }

    protected function fieldLoader()
    {
        $html = '<div class="spinner-border spinner-border-sm gf-inline-spinner" data-gf-element="field-spinner" role="status" style="position:absolute; top:1rem; right:2rem; display:none;">';
        $html .= '<span class="visually-hidden">Loading...</span>';
        $html .= '</div>';
        return $html;
    }
    
    protected function helpText($is_theme_set = false)
    {
        $description = $this->item->getProp('description');
        if (empty($description)) {
            return '';
        }

        $description_classes = 'small';
        if (!$is_theme_set && !empty($this->bs_field_description_classes)) {
            $description_classes .= ' ' . esc_attr($this->bs_field_description_classes);
        }

        $html = '<div class="' . esc_attr($description_classes) . '" data-gf-element="description">';
        $html .= esc_html($description);
        $html .= '</div>';
        return $html;
    }

    protected function hydrationNotice($is_theme_set = false)
    {
        if (!$this->edit_mode || !$this->hydration_parse_failed) {
            return '';
        }

        $notice_classes = 'small mt-2';
        if (!$is_theme_set) {
            $notice_classes .= ' alert alert-warning';
        }

        $html = '<div class="' . esc_attr($notice_classes) . '" role="note" data-gf-element="hydration-notice">';
        $html .= esc_html__('The original submitted value for this field could not be restored. Please re-enter it.', 'griffinforms-form-builder');
        $html .= '</div>';
        return $html;
    }
    
    protected function iterationBtns($is_theme_set = false)
    {
        $add_attrs = array(
            'id' => $this->html_ids['add_btn'],
            'href' => '#',
            'data-gf-element' => 'iteration-add',
        );
        $del_attrs = array(
            'id' => $this->html_ids['del_btn'],
            'href' => '#',
            'data-gf-element' => 'iteration-delete',
        );

        if (!$is_theme_set) {
            $add_attrs['class'] = 'btn btn-sm btn-link';
            $del_attrs['class'] = 'btn btn-sm btn-link';
        }

        $html = '<div id="' . $this->html_ids['btns'] . '" class="text-end" style="display:flex; justify-content:flex-end; align-items:center; gap:0.75rem; flex-wrap:wrap;">';
        $html .= '<a' . $this->buildAttributeString($add_attrs) . '>';
        $html .= $this->iterations['add_button_label'];
        $html .= '</a>';
        $html .= '<a' . $this->buildAttributeString($del_attrs) . '>';
        $html .= 'Delete';
        $html .= '</a>';
        $html .= '</div>';
        return $html;
    }

    protected function buildAttributeString(array $attributes)
    {
        $parts = array();

        foreach ($attributes as $name => $value) {
            if ($value === null || $value === '') {
                continue;
            }

            $parts[] = ' ' . $name . '="' . esc_attr($value) . '"';
        }

        return implode('', $parts);
    }
    
    protected function alertHtml($type, $is_theme_set = false)
    {
        $alert_classes = 'griffinforms-alert-' . esc_attr($type);
        if (!$is_theme_set && !empty($this->bs_field_alert_classes)) {
            $alert_classes .= ' ' . esc_attr($this->bs_field_alert_classes);
        }

        $form_attr = '';
        if (!empty($this->data['form'])) {
            $form_attr = ' data-gf-form-id="' . absint($this->data['form']) . '"';
        }

        $html = '<div class="' . esc_attr($alert_classes) . '" role="alert" style="display:none;" data-gf-element="error"' . $form_attr . '>';
        $html .= $this->lang->getText($type . 'Error', $this->validations);
        $html .= '</div>';
        return $html;
    }

    protected function hasSubmissionEditValue(): bool
    {
        return $this->edit_mode && $this->getSubmissionValue() !== null;
    }

    protected function getSubmissionValue()
    {
        $field_id = absint($this->id);
        if ($field_id <= 0 || empty($this->submission_values) || !array_key_exists($field_id, $this->submission_values)) {
            return null;
        }

        return $this->submission_values[$field_id];
    }

    protected function resolveRenderedValue(int $iteration = 0)
    {
        $submission_value = $this->getSubmissionValueForIteration($iteration);
        if ($submission_value === null) {
            return $this->default_render_value;
        }

        return $this->normalizeSubmissionValue($submission_value);
    }

    protected function getSubmissionValueForIteration(int $iteration = 0)
    {
        $raw = $this->getSubmissionValue();
        if ($raw === null) {
            return null;
        }

        if (!is_array($raw)) {
            return $raw;
        }

        $iterations_enabled = is_array($this->iterations) && !empty($this->iterations['allow']);
        if ($iterations_enabled && $this->isSequentialArray($raw)) {
            if (array_key_exists($iteration, $raw)) {
                return $raw[$iteration];
            }

            return null;
        }

        return $raw;
    }

    protected function normalizeSubmissionValue($value)
    {
        $field_type = strtolower((string) $this->item->getProp('field_type'));

        if ($field_type === 'inputaddress') {
            return $this->normalizeAddressSubmissionValue($value);
        }

        if ($field_type === 'inputcheckbox' || $field_type === 'imageselection') {
            return $this->normalizeCheckboxSubmissionValue($value);
        }

        if ($field_type === 'fileupload' || $field_type === 'payment') {
            return $this->normalizeStructuredSubmissionValue($value);
        }

        if ($field_type === 'inputlikert' || $field_type === 'inputrating' || $field_type === 'signature') {
            return is_string($value) ? $value : (is_array($value) ? $value : '');
        }

        if (is_array($value)) {
            $flat = $this->flattenSubmissionValue($value);
            if (empty($flat)) {
                return '';
            }
            return (string) reset($flat);
        }

        if (is_bool($value)) {
            return $value ? '1' : '0';
        }

        if (is_scalar($value)) {
            return (string) $value;
        }

        return '';
    }

    protected function normalizeCheckboxSubmissionValue($value): array
    {
        $looks_like_json = is_string($value) && $this->looksLikeJsonPayload($value);
        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                $value = $decoded;
            }
        }

        $values = $this->flattenSubmissionValue($value);
        $values = array_map('strval', $values);
        $values = array_values(array_filter(array_map('trim', $values), static function ($item) {
            return $item !== '';
        }));

        if ($looks_like_json && empty($values)) {
            $this->hydration_parse_failed = true;
        }

        return $values;
    }

    protected function normalizeAddressSubmissionValue($value): array
    {
        $defaults = array(
            'line1' => '',
            'line2' => '',
            'city' => '',
            'state' => '',
            'postal' => '',
            'country' => ''
        );

        if (is_array($value) && $this->isSequentialArray($value) && count($value) === 1 && isset($value[0])) {
            $value = $value[0];
        }

        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
                if ($this->isSequentialArray($value) && count($value) === 1 && isset($value[0]) && is_array($value[0])) {
                    $value = $value[0];
                }
            } elseif ($this->looksLikeJsonPayload($value)) {
                $this->hydration_parse_failed = true;
            }
        }

        if (!is_array($value)) {
            return $defaults;
        }

        $merged = array_merge($defaults, $value);
        foreach ($merged as $key => $item) {
            $merged[$key] = is_scalar($item) ? trim((string) $item) : '';
        }

        return $merged;
    }

    protected function normalizeStructuredSubmissionValue($value)
    {
        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $decoded;
            }
            if ($this->looksLikeJsonPayload($value)) {
                $this->hydration_parse_failed = true;
            }
        }

        if (is_array($value)) {
            return $value;
        }

        if (!empty($value)) {
            $this->hydration_parse_failed = true;
        }

        return array();
    }

    protected function looksLikeJsonPayload(string $value): bool
    {
        $value = ltrim($value);
        if ($value === '') {
            return false;
        }

        $first = substr($value, 0, 1);
        return in_array($first, array('{', '['), true);
    }

    protected function flattenSubmissionValue($value): array
    {
        if (!is_array($value)) {
            return is_scalar($value) ? array((string) $value) : array();
        }

        $flat = array();
        array_walk_recursive($value, static function ($item) use (&$flat) {
            if (is_scalar($item)) {
                $flat[] = (string) $item;
            }
        });

        if (!empty($flat)) {
            return $flat;
        }

        foreach ($value as $item) {
            if (is_scalar($item)) {
                $flat[] = (string) $item;
            }
        }

        return $flat;
    }

    protected function isSequentialArray(array $value): bool
    {
        return array_keys($value) === range(0, count($value) - 1);
    }

    protected function syncRenderedValue(int $iteration = 0): void
    {
        $this->current_iteration = $iteration;
    }

    protected function getRenderIterationCount(): int
    {
        $min = absint($this->iterations['min'] ?? 1);
        if ($min <= 0) {
            $min = 1;
        }

        $render_count = $min;

        if ($this->edit_mode) {
            $submitted_count = $this->getSubmissionIterationCount();
            if ($submitted_count > 0) {
                $render_count = max($render_count, $submitted_count);
            }
        }

        $max = absint($this->iterations['max'] ?? 0);
        if ($max > 0) {
            $render_count = min($render_count, $max);
        }

        return max(1, $render_count);
    }

    protected function getSubmissionIterationCount(): int
    {
        $raw = $this->getSubmissionValue();
        if (!is_array($raw)) {
            return $raw === null || $raw === '' ? 0 : 1;
        }

        if ($this->isSequentialArray($raw)) {
            return count($raw);
        }

        return 1;
    }

    protected function shouldRenderIterationButtons(int $render_iterations): bool
    {
        $max = absint($this->iterations['max'] ?? 0);
        if ($max <= 0) {
            return false;
        }

        return $render_iterations < $max;
    }
    
        
    protected function errorAlerts($is_theme_set = false)
    {
        $html = '';
        foreach ($this->errors as $error) {
            $check = 'is' . $error;
            if ($this->$check()) {
                $html .= $this->alertHtml($error, $is_theme_set);
            }
        }
        return $html;
    }
    
    protected function validationsJs()
    {
        foreach ($this->errors as $error) {
            $check = 'is' . $error;
            if ($this->$check()) {
                $js = $error . 'Js';
                $this->$js($error);
            }
        }
    }
    
    public function itemJs()
    {
        $formControl = $this->html_ids['formcontrol'];
        $controlContainer = $this->html_ids['formcontrol-container'];
        $actionBtn = esc_attr($this->html_ids['action_btn']);
        $alertsObjName = "mfFieldAlerts" . absint($this->id);
        $formPageFields = "page" . absint($this->data['formpage']) . "Fields";
        $itemId = $this->id;

        // Start buffering
        ob_start();
        ?>
        jQuery(document).ready(function($) {
            const formControl = ".<?php echo esc_attr($formControl); ?>";
            const controlContainer = ".<?php echo esc_attr($controlContainer); ?>";
            const actionBtn = "#<?php echo esc_attr($actionBtn); ?>";
            const alertsObjName = "<?php echo esc_js($alertsObjName); ?>";
            const gfFormId = <?php echo isset($this->data['form']) ? absint($this->data['form']) : 0; ?>;
            const gfFieldId = <?php echo absint($itemId); ?>;
            
            sessionStorage.setItem(alertsObjName, JSON.stringify({}));
            addFieldToPage();
            
            function addFieldToPage() {
                let pageFields = sessionStorage.getItem("<?php echo esc_js($formPageFields); ?>");
                pageFields = JSON.parse(pageFields);
                pageFields.push(<?php echo absint($itemId); ?>);
                sessionStorage.setItem("<?php echo esc_js($formPageFields); ?>", JSON.stringify(pageFields));
            }

            function isThemedControl(item) {
                const control = $(item);
                const formContainer = control.closest(".griffinforms-form-container");
                return formContainer.length && !formContainer.hasClass("gf-no-theme");
            }

            function resolveValidationGroup(control) {
                const groups = control.closest(".griffinforms-phone-input-group, .griffinforms-password-input-group, .gf-address-wrapper");
                return groups.length ? groups.first() : $();
            }

            function syncValidationGroupState(control) {
                const group = resolveValidationGroup(control);
                if (!group.length) {
                    return;
                }

                const hasInvalid = group.find(".gf-validation-invalid").length > 0;
                group.toggleClass("gf-validation-invalid-group", hasInvalid);
            }

            function toggleInvalidVisualState(item, isInvalid) {
                const control = $(item);
                control.toggleClass("border-danger bg-danger bg-opacity-10", !!isInvalid);

                if (isThemedControl(control)) {
                    control.toggleClass("gf-validation-invalid", !!isInvalid);
                    syncValidationGroupState(control);
                }
            }
            
            function addAlert(item, alertType) {
                const fieldAlerts = JSON.parse(sessionStorage.getItem(alertsObjName));
                let key = $(item).index(formControl);
                
                if (!(key in fieldAlerts)) {
                    fieldAlerts[key] = {};
                }
                
                fieldAlerts[key][alertType] = true;
                toggleInvalidVisualState(item, true);
                sessionStorage.setItem(alertsObjName, JSON.stringify(fieldAlerts));
                showAlert(item, alertType);
            }
            
            function removeAlert(item, alertType) {
                const fieldAlerts = JSON.parse(sessionStorage.getItem(alertsObjName));
                let key = $(item).index(formControl);
                
                if (key in fieldAlerts) {
                    if (alertType in fieldAlerts[key]) {
                        delete fieldAlerts[key][alertType];
                    }
                }
                
                if ($.isEmptyObject(fieldAlerts[key])) {
                    toggleInvalidVisualState(item, false);
                }
                
                sessionStorage.setItem(alertsObjName, JSON.stringify(fieldAlerts));
                hideAlert(item, alertType);
            }
            
            function showAlert(item, alertType) {
                const control = $(item);
                let alert = control.siblings(".griffinforms-alert-" + alertType);

                if (!alert.length) {
                    alert = control.closest(".griffinforms-formcontrol-container").find(".griffinforms-alert-" + alertType).first();
                }

                if (alert.length) {
                    alert.fadeIn();
                }
            }
            
            function hideAlert(item, alertType) {
                const control = $(item);
                let alert = control.siblings(".griffinforms-alert-" + alertType);

                if (!alert.length) {
                    alert = control.closest(".griffinforms-formcontrol-container").find(".griffinforms-alert-" + alertType).first();
                }

                if (alert.length) {
                    alert.hide();
                }
            }
            
            function removeErrors(iteration) {
                const fieldErrors = JSON.parse(sessionStorage.getItem(alertsObjName));
                
                if ($.isEmptyObject(fieldErrors)) {
                    return;
                }
                
                if (fieldErrors.hasOwnProperty(iteration)) {
                    delete fieldErrors[iteration];
                    sessionStorage.setItem(alertsObjName, JSON.stringify(fieldErrors));
                }
            }
            
            function addWait(item) {
                item.siblings(".spinner-border").show();
                item.prop("disabled", true);

                if (window.gfRequestLocks && typeof window.gfRequestLocks.lock === "function") {
                    window.gfRequestLocks.lock(String(gfFormId), "validation", { overlay: false });
                } else {
                    $(actionBtn).prop("disabled", true);
                }
            }
            
            function removeWait(item) {
                item.siblings(".spinner-border").hide();
                item.prop("disabled", false);

                if (window.gfRequestLocks && typeof window.gfRequestLocks.unlock === "function") {
                    window.gfRequestLocks.unlock(String(gfFormId), "validation", { overlay: false });
                } else {
                    $(actionBtn).prop("disabled", false);
                }
            }

            <?php $this->fieldChangeJs(); ?>
            
            <?php if ($this->iterations['allow']) { ?>
                <?php $this->iterationsJs(); ?>
            <?php } ?>
            
            <?php if (method_exists($this, 'subItemJs')) { ?>
                <?php $this->subItemJs(); ?>
            <?php } ?>

            setTimeout(function() {
                triggerFieldChanged({ reason: "initial_render" });
            }, 0);
            
            <?php $this->validationsJs(); ?>
        });

        <?php

        $inline_js = ob_get_clean();
        wp_add_inline_script('griffinforms-frontend', $inline_js);
    }
    
    protected function fieldChangeJs()
    {
        echo '
            function triggerFieldChanged(detail) {
                if (!gfFormId || !gfFieldId) {
                    return;
                }

                const payload = $.extend({
                    formId: gfFormId,
                    fieldId: gfFieldId
                }, detail || {});

                $(document).trigger("griffinforms:fieldChanged", payload);
            }

            function bindDefaultFieldChangeEvents() {
                $(document).on("input change", formControl + ", " + controlContainer + " input, " + controlContainer + " textarea, " + controlContainer + " select", function() {
                    triggerFieldChanged({ element: this });
                });
            }

            bindDefaultFieldChangeEvents();
        ';
    }

    protected function iterationsJs()
    {
        echo '
        // Function to reset most input values (text, email, number, etc.) when cloning
        function resetClonedFieldValues(field) {
            field.find("input[type=\'text\'], input[type=\'email\'], input[type=\'url\'], input[type=\'number\'], input[type=\'date\'], textarea").val("");
            field.find("input[type=\'checkbox\'], input[type=\'radio\']").prop("checked", false);
            field.find("input[type=\'hidden\']").val("");
        }

        // Function to reset radio clone: clears selection and resets hidden value
        function resetRadioClone(field) {
            field.find(\'input[type="radio"]\').prop("checked", false);
            field.find(\'input[type="hidden"]\').val("");
        }

        function resetClonedValidationState(field) {
            field.find(".griffinforms-alert-account-registration").remove();
            field.find("input, textarea, select").removeClass("border-danger bg-danger bg-opacity-10");
        }

        hideDelBtn();
        hideAddBtn();

        $(document).on("click", "#' . esc_attr($this->html_ids['add_btn']) . '", function(event) {
            event.preventDefault();
            addFormControl();
        });
        $(document).on("click", "#' . esc_attr($this->html_ids['del_btn']) . '", function(event) {
            event.preventDefault();
            delFormControl();
        });

        function getCount() {
            return $(controlContainer).length;
        }

        function hideDelBtn() {
            if (getCount() === ' . absint($this->iterations['min']) . ') {
                $("#' . esc_attr($this->html_ids['del_btn']) . '").hide();
            } else {
                $("#' . esc_attr($this->html_ids['del_btn']) . '").show();
            }
        }

        function hideAddBtn() {
            if (getCount() === ' . absint($this->iterations['max']) . ') {
                $("#' . esc_attr($this->html_ids['add_btn']) . '").hide();
            } else {
                $("#' . esc_attr($this->html_ids['add_btn']) . '").show();
            }
        }

        function addFormControl() {
            let newField = $(controlContainer).first().clone(true, true);

            // Reset most input values when cloning
            resetClonedFieldValues(newField);
            resetClonedValidationState(newField);

            if ("inputradio" === "' . esc_js($this->item->getProp('field_type')) . '") {
                resetRadioClone(newField);
            }

            newField.insertBefore("#' . esc_attr($this->html_ids['btns']) . '");
            hideAddBtn();
            hideDelBtn();
            refreshIds();
            triggerFieldChanged({ reason: "iteration_added" });
        }

        function delFormControl() {
            removeErrors($(controlContainer).length - 1);
            $(controlContainer).last().remove();
            hideAddBtn();
            hideDelBtn();
            triggerFieldChanged({ reason: "iteration_removed" });
        }
        
        function refreshIds() {
            if ("inputcheckbox" === "' . esc_js($this->item->getProp('field_type')) . '") {
                $(controlContainer).each(function(iteration) {
                    $(this).find(".form-check").each(function(index) {
                        let id = formControl.replace(".", "") + "_" + iteration + "_" + index;
                        $(this).children("input").attr("id", id);
                        $(this).children("label").attr("for", id);
                    });
                });
            }            
            
            if ("inputradio" === "' . esc_js($this->item->getProp('field_type')) . '") {
                $(controlContainer).each(function(iteration) {
                    $(this).find(".form-check").each(function(index) {
                        let id = formControl.replace(".", "") + "_" + iteration + "_" + index;
                        let name = formControl.replace(".", "") + "_" + iteration;
                        $(this).children("input").attr("id", id);
                        $(this).children("input").attr("name", name);
                        $(this).children("label").attr("for", id);
                    });
                });
            }
        }';
    }
    
    /**
     * Injects JavaScript for client-side regex pattern validation.
     *
     * - Applies the pattern defined in the field's validations.
     * - Supports case sensitivity based on 'case_sensitive' validation setting.
     * - Attaches validation to keyup and hover events.
     * - Adds or removes validation error styling and alerts.
     *
     * @param string $error The error type key used for styling and messaging.
     */
    protected function patternJs($error)
    {
        // Set regex flags based on case sensitivity validation. Can we added as future feature.
        // For example, if 'case_sensitive' is set to true, we can add 'u' flag for unicode.
        $flags = 'u';
        if (!isset($this->validations['case_sensitive']) || !$this->validations['case_sensitive']) {
            $flags .= 'i';
        }

        // Escape the pattern for safe JavaScript injection
        $escaped_pattern = addslashes($this->validations['pattern']);

        // Inject JavaScript for pattern validation
        echo '
            jQuery(document).ready(function($) {
                $(formControl).keyup(checkPattern);
                $(actionBtn).hover(checkPattern);

                function checkPattern() {
                    // Load the raw pattern from PHP
                    let rawPattern = \'' . esc_js($escaped_pattern) . '\';

                    if (!rawPattern || rawPattern.trim() === "") {
                        console.error("Regex pattern is empty.");
                        return;
                    }

                    let value = $.trim($(this).val());
                    if (value === "") { return; }

                    try {
                        let formattedPattern = rawPattern;
                        console.log("Regex in use:", formattedPattern); // Debugging output
                        
                        // Apply regex with configured flags
                        let pattern = new RegExp(formattedPattern, \'' . esc_js($flags) . '\');
                        let result = pattern.test(value);

                        if (result) {
                            removeAlert($(this), "' . esc_js($error) . '");
                        } else {
                            addAlert($(this), "' . esc_js($error) . '");
                        }
                    } catch (e) {
                        console.error("Invalid Regex Pattern:", formattedPattern, e);
                    }
                }
            });
        ';
    }
    
    protected function uniqueJs($error)
    {
        echo '
            $(formControl).keyup(checkUnique);
            $(formControl).change(checkUnique);
            $(actionBtn).hover(checkUnique);
            
            function checkUnique() {
                const values = [];
                $(formControl).each(function(){
                    let value = $.trim($(this).val());
                    values.push(value);
                });
                
                $(formControl).each(function(){
                    let occurance = 0;
                    let value = $.trim($(this).val());
                    
                    if(value === "") {
                        removeAlert($(this), "' . esc_js($error) . '");
                        return;
                    }
                    
                    ';

                    // Checkboxes and radio save values as array. Therefore, when empty, their value is [].
                    if ($this->item->getProp('field_type') === 'inputcheckbox') {
                        echo 'if (value === "[]") {
                            removeAlert($(this), "' . esc_js($error) . '");
                            return;
                        }';
                    }

                    echo '
                    
                    $.each(values, function(iteration, val){
                        if (value === val) {
                            occurance++;
                        }
                    });
                    
                    if (occurance > 1) {
                        addAlert($(this), "' . esc_js($error) . '");
                    } else {
                        removeAlert($(this), "' . esc_js($error) . '");
                    }
                });
            }
       ';
    }
    
    protected function requiredJs($error)
    {
        echo '
            $(actionBtn).hover(function(){
                $(formControl).each(checkRequired);
            });
            $(formControl).keyup(checkRequired);
            
            function checkRequired() {
                let value = $.trim($(this).val());
                if (value === "") {
                    addAlert($(this), "' . esc_js($error) . '");
                } else {
                    removeAlert($(this), "' . esc_js($error) . '");
                }
            }
        ';
    }
    
    protected function dbValidateJs($error)
    {
        $nonce = wp_create_nonce('db_validate');
        echo '
            let dbValidateTimer;
            let lastDbValidateValue = null;

            $(formControl).on("input change", function() {
                const el = this;
                clearTimeout(dbValidateTimer);
                dbValidateTimer = setTimeout(function() {
                    checkDbValidate(el);
                }, 400);
            });

            $(formControl).on("blur", function() {
                clearTimeout(dbValidateTimer);
                checkDbValidate(this);
            });

            function checkDbValidate(element) {
                const formControl = $(element);
                const value = $.trim(formControl.val());
                if (value === "") { return; }
                if (value === lastDbValidateValue) { return; }
                lastDbValidateValue = value;

                const table = "' . esc_js($this->validations['table']) . '";
                const column = "' . esc_js($this->validations['column']) . '";
                const matchType = "' . esc_js($this->validations['match_type']) . '";
                const ajaxurl = "' . esc_url(admin_url('admin-ajax.php')) . '";

                addWait(formControl);

                let data = {
                    "action": "dbValidate",
                    "value": value,
                    "table": table,
                    "column": column,
                    "nonce": "' . esc_js($nonce) . '"
                };

                $.post(ajaxurl, data, function(response) {

                    let parsedResponse;
                    try {
                        parsedResponse = typeof response === "object" ? response : JSON.parse(response);
                    } catch (e) {
                        removeWait(formControl);
                        return;
                    }

                    if (!parsedResponse || typeof parsedResponse.count === "undefined") {
                        removeWait(formControl);
                        return;
                    }

                    let results = parseInt(parsedResponse.count);

                    if (matchType === "in_column") {
                        if (results > 0) {
                            removeAlert(formControl, "' . esc_js($error) . '");
                        } else {
                            addAlert(formControl, "' . esc_js($error) . '");
                        }
                    }

                    if (matchType === "not_in_column") {
                        if (results > 0) {
                            addAlert(formControl, "' . esc_js($error) . '");
                        } else {
                            removeAlert(formControl, "' . esc_js($error) . '");
                        }
                    }

                    removeWait(formControl);
                }).fail(function() {
                    removeWait(formControl);
                });
            }
        ';
    }

    /**
     * Get the field type key for data attribute styling
     *
     * Returns the field type in a format suitable for CSS data attribute selectors.
     * Child classes should override this method if their meta/class name doesn't
     * correspond directly to the field type used in the theme schema.
     *
     * @return string Field type key (e.g., 'text_input', 'textarea', 'select_input', 'checkbox_radio')
     */
    protected function getFieldType()
    {
        // Default: derive from the meta property (e.g., 'inputtext' -> 'text_input')
        // Child classes can override for custom mappings

        // Map class names to field type keys
        $class_name = strtolower($this->meta);
        $type_map = array(
            'inputtext' => 'text_input',
            'inputemail' => 'email_input',
            'inputnumber' => 'number_input',
            'inputpassword' => 'password_input',
            'inputurl' => 'url_input',
            'inputphone' => 'phone_input',
            'inputdate' => 'date_input',
            'inputtime' => 'time_input',
            'inputaddress' => 'address_input',
            'textarea' => 'textarea',
            'selectfield' => 'select_input',
            'inputcheckbox' => 'checkbox_radio',
            'inputradio' => 'checkbox_radio',
            'inputrating'    => 'inputrating',
            'inputrichtext'  => 'inputrichtext',
            'inputlikert'    => 'inputlikert',
            'imageselection' => 'imageselection',
            'fileupload'     => 'file_upload',
            'termsconditions' => 'terms_conditions',
            'signature'      => 'signature',
        );

        return isset($type_map[$class_name]) ? $type_map[$class_name] : '';
    }
}
