<?php

namespace GriffinForms\Frontend\Html\Forms\Fields;

use GriffinForms\Admin\Choices\Field as FieldChoices;

final class InputAddress extends \GriffinForms\Frontend\Html\Forms\Fields\Format
{
    protected $labels = array();
    protected $line2_enabled = true;
    protected $country_options = array();
    protected $subdivisions = array();
    protected $current_value = array();

    protected function setFieldSettings()
    {
        parent::setFieldSettings();
        $this->setLabels();
        $this->setLine2Enabled();
    }

    protected function setLabels()
    {
        $defaults = $this->getDefaultLabels();
        $stored = isset($this->field_settings['labels']) && is_array($this->field_settings['labels'])
            ? $this->field_settings['labels']
            : array();

        $labels = array();
        foreach ($defaults as $key => $fallback) {
            if (isset($stored[$key])) {
                $label = trim((string) $stored[$key]);
                $labels[$key] = ($label === '') ? $fallback : $label;
            } else {
                $labels[$key] = $fallback;
            }
        }

        $this->labels = $labels;
    }

    protected function setLine2Enabled()
    {
        if (isset($this->field_settings['enable_line2'])) {
            $this->line2_enabled = (int) $this->field_settings['enable_line2'] === 1;
        } else {
            $this->line2_enabled = true;
        }
    }

    protected function setValue()
    {
        parent::setValue();
        $this->current_value = $this->normalizeAddressValue($this->value);
    }

    protected function setGetValue()
    {
        parent::setGetValue();
        $this->current_value = $this->normalizeAddressValue($this->value);
    }

    protected function syncRenderedValue(int $iteration = 0): void
    {
        parent::syncRenderedValue($iteration);
        $this->current_value = $this->normalizeAddressValue($this->value);
    }

    protected function setFieldOptions()
    {
        parent::setFieldOptions();
        $choices = FieldChoices::getInstance();

        $countries = $choices->getChoices('address_countries', false);
        $countries = is_array($countries) ? $countries : array();
        $mode = isset($this->validations['country_filter_mode']) ? $this->validations['country_filter_mode'] : 'all_countries';

        if ('limit_to_countries' === $mode && !empty($this->validations['allowed_countries'])) {
            $allowed = array_map('strtoupper', (array) $this->validations['allowed_countries']);
            $filtered = array();
            foreach ($countries as $code => $name) {
                if (in_array(strtoupper($code), $allowed, true)) {
                    $filtered[$code] = $name;
                }
            }
            $countries = $filtered;
        } elseif ('exclude_countries' === $mode && !empty($this->validations['excluded_countries'])) {
            $excluded = array_map('strtoupper', (array) $this->validations['excluded_countries']);
            foreach ($countries as $code => $name) {
                if (in_array(strtoupper($code), $excluded, true)) {
                    unset($countries[$code]);
                }
            }
        }

        $this->country_options = $countries;
        $subs = $choices->getChoices('address_subdivisions', false);
        $this->subdivisions = is_array($subs) ? $subs : array();
    }

    protected function getDefaultLabels()
    {
        return array(
            'line1'   => $this->lang->getText('address_line1_default_label'),
            'line2'   => $this->lang->getText('address_line2_default_label'),
            'city'    => $this->lang->getText('address_city_default_label'),
            'state'   => $this->lang->getText('address_state_default_label'),
            'postal'  => $this->lang->getText('address_postal_default_label'),
            'country' => $this->lang->getText('address_country_default_label')
        );
    }

    protected function normalizeAddressValue($value)
    {
        $defaults = $this->getEmptyAddress();

        if (is_array($value)) {
            $merged = array_merge($defaults, $value);
        } elseif (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            $merged = is_array($decoded) ? array_merge($defaults, $decoded) : $defaults;
        } else {
            $merged = $defaults;
        }

        foreach ($merged as $key => $val) {
            $merged[$key] = is_string($val) ? trim($val) : '';
        }

        return $merged;
    }

    protected function getEmptyAddress()
    {
        return array(
            'line1'   => '',
            'line2'   => '',
            'city'    => '',
            'state'   => '',
            'postal'  => '',
            'country' => ''
        );
    }

    protected function formControl($iteration = 0)
    {
        $this->setFieldOptions();
        $container_class = 'griffinforms-address-wrapper-' . $this->id;
        $readonly = $this->isReadonly();
        $disabled = $this->isDisabled();
        $attr_readonly = $readonly ? ' readonly="readonly"' : '';
        $attr_disabled = $disabled ? ' disabled="disabled"' : '';
        $required_attr = $this->isRequired() ? ' required="required"' : '';
        $current = $this->current_value;
        $line2 = $current['line2'];
        $state_value = $current['state'];
        $country_value = $current['country'];

        $html  = '<div class="' . esc_attr($container_class) . ' gf-address-wrapper"';
        $html .= ' data-gf-address-field="' . absint($this->id) . '"';
        $html .= ' data-gf-address-iteration="' . absint($iteration) . '">';

        $html .= '<div class="row g-3">';

        // Address Line 1
        $line1_id = $this->html_ids['formcontrol'] . '-line1-' . $iteration;
        $html .= '<div class="col-12">';
        $html .= '<div class="form-floating">';
        $html .= '<input type="text" class="form-control gf-address-line1"';
        $html .= ' id="' . esc_attr($line1_id) . '"';
        $html .= ' placeholder=" "';
        $html .= ' data-gf-address-key="line1"';
        $html .= ' value="' . esc_attr($current['line1']) . '"';
        $html .= ' autocomplete="address-line1"' . $attr_readonly . $attr_disabled . $required_attr . '>';
        $html .= '<label for="' . esc_attr($line1_id) . '" data-gf-element="floating-label" data-gf-address-label="line1" data-gf-original-label="' . esc_attr($this->labels['line1']) . '">' . esc_html($this->labels['line1']) . '</label>';
        $html .= '</div>';
        $html .= '</div>';

        // Address Line 2 (optional)
        if ($this->line2_enabled) {
            $line2_id = $this->html_ids['formcontrol'] . '-line2-' . $iteration;
            $html .= '<div class="col-12 gf-address-line2-label">';
            $html .= '<div class="form-floating">';
            $html .= '<input type="text" class="form-control gf-address-line2"';
            $html .= ' id="' . esc_attr($line2_id) . '"';
            $html .= ' placeholder=" "';
            $html .= ' data-gf-address-key="line2"';
            $html .= ' value="' . esc_attr($line2) . '"';
            $html .= ' autocomplete="address-line2"' . $attr_readonly . $attr_disabled . '>';
            $html .= '<label for="' . esc_attr($line2_id) . '" data-gf-element="floating-label" data-gf-address-label="line2" data-gf-original-label="' . esc_attr($this->labels['line2']) . '">' . esc_html($this->labels['line2']) . '</label>';
            $html .= '</div>';
            $html .= '</div>';
        }

        // City & Postal Code group
        $city_id = $this->html_ids['formcontrol'] . '-city-' . $iteration;
        $postal_id = $this->html_ids['formcontrol'] . '-postal-' . $iteration;
        $html .= '<div class="col-12">';
        $html .= '<div class="input-group gf-address-city-postal-group">';
        $html .= '<div class="form-floating flex-grow-1">';
        $html .= '<input type="text" class="form-control gf-address-city" style="border-top-right-radius:0 !important; border-bottom-right-radius:0 !important;"';
        $html .= ' id="' . esc_attr($city_id) . '"';
        $html .= ' placeholder=" "';
        $html .= ' data-gf-address-key="city"';
        $html .= ' value="' . esc_attr($current['city']) . '"';
        $html .= ' autocomplete="address-level2"' . $attr_readonly . $attr_disabled . '>';
        $html .= '<label for="' . esc_attr($city_id) . '" data-gf-element="floating-label" data-gf-address-label="city" data-gf-original-label="' . esc_attr($this->labels['city']) . '">' . esc_html($this->labels['city']) . '</label>';
        $html .= '</div>';

        $html .= '<div class="form-floating flex-grow-0" style="flex:0 0 200px;">';
        $html .= '<input type="text" class="form-control gf-address-postal" style="border-top-left-radius:0 !important; border-bottom-left-radius:0 !important;"';
        $html .= ' id="' . esc_attr($postal_id) . '"';
        $html .= ' placeholder=" "';
        $html .= ' data-gf-address-key="postal"';
        $html .= ' value="' . esc_attr($current['postal']) . '"';
        $html .= ' autocomplete="postal-code"' . $attr_readonly . $attr_disabled . '>';
        $html .= '<label for="' . esc_attr($postal_id) . '" data-gf-element="floating-label" data-gf-address-label="postal" data-gf-original-label="' . esc_attr($this->labels['postal']) . '">' . esc_html($this->labels['postal']) . '</label>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</div>';

        // State / Province & Country row
        $state_select_id = $this->html_ids['formcontrol'] . '-state-select-' . $iteration;
        $state_input_id  = $this->html_ids['formcontrol'] . '-state-input-' . $iteration;
        $html .= '<div class="col-md-6">';
        $html .= '<div class="gf-address-state-floating">';

        // Select wrapper
        $html .= '<div class="form-floating gf-address-state-select-wrap d-none">';
        $html .= '<select class="form-select gf-address-state-select"';
        $html .= ' id="' . esc_attr($state_select_id) . '" data-gf-address-key="state"';
        $html .= $attr_disabled . '>';
        $html .= '<option value="">' . esc_html($this->labels['state']) . '</option>';
        $html .= '</select>';
        $html .= '<label for="' . esc_attr($state_select_id) . '" data-gf-element="floating-label" data-gf-address-label="state" data-gf-original-label="' . esc_attr($this->labels['state']) . '">' . esc_html($this->labels['state']) . '</label>';
        $html .= '</div>';

        // Input wrapper
        $html .= '<div class="form-floating gf-address-state-input-wrap">';
        $html .= '<input type="text" class="form-control gf-address-state-input"';
        $html .= ' id="' . esc_attr($state_input_id) . '" data-gf-address-key="state"';
        $html .= ' placeholder=" "';
        $html .= ' value="' . esc_attr($state_value) . '" autocomplete="address-level1"' . $attr_readonly . $attr_disabled . '>';
        $html .= '<label for="' . esc_attr($state_input_id) . '" data-gf-element="floating-label" data-gf-address-label="state" data-gf-original-label="' . esc_attr($this->labels['state']) . '">' . esc_html($this->labels['state']) . '</label>';
        $html .= '</div>';

        $html .= '</div>';
        $html .= '</div>';

        $country_id = $this->html_ids['formcontrol'] . '-country-' . $iteration;
        $html .= '<div class="col-md-6">';
        $html .= '<div class="form-floating">';
        $html .= '<select class="form-select gf-address-country" id="' . esc_attr($country_id) . '" data-gf-address-key="country"';
        $html .= $attr_disabled . ' autocomplete="country-name">';
        $html .= '<option value="">' . esc_html($this->lang->getText('address_country_placeholder')) . '</option>';
        foreach ($this->country_options as $code => $name) {
            $selected = (strtoupper($country_value) === strtoupper($code)) ? ' selected' : '';
            $html .= '<option value="' . esc_attr($code) . '"' . $selected . '>' . esc_html($name) . '</option>';
        }
        $html .= '</select>';
        $html .= '<label for="' . esc_attr($country_id) . '" data-gf-element="floating-label" data-gf-address-label="country" data-gf-original-label="' . esc_attr($this->labels['country']) . '">' . esc_html($this->labels['country']) . '</label>';
        $html .= '</div>';
        $html .= '</div>';

        $html .= '</div>'; // row

        $hidden_attrs  = ' class="' . esc_attr($this->html_ids['formcontrol']) . '"';
        $hidden_attrs .= ' data-gf-formpage="' . absint($this->data['formpage']) . '"';
        $hidden_attrs .= $attr_disabled;

        $html .= '<input type="hidden"' . $hidden_attrs . ' data-gf-address-json="1" value="' . esc_attr(wp_json_encode($current)) . '">';
        $html .= '</div>';

        return $html;
    }

    protected function subItemJs()
    {
        $subdivisions = array();
        foreach ($this->subdivisions as $country => $states) {
            if (is_array($states)) {
                $subdivisions[$country] = $states;
            }
        }

        $config = array(
            'fieldId'      => $this->id,
            'subdivisions' => $subdivisions,
            'line2Enabled' => $this->line2_enabled,
            'readonly'     => $this->isReadonly(),
            'disabled'     => $this->isDisabled()
        );

        $config_json = wp_json_encode($config);
        ?>
        (function($){
            const cfg = <?php echo $config_json; ?>;
            const formControlClass = ".<?php echo esc_js($this->html_ids['formcontrol']); ?>";
            const containerSelector = ".griffinforms-address-wrapper-<?php echo absint($this->id); ?>";
            const defaults = {line1:"", line2:"", city:"", state:"", postal:"", country:""};

            function parseAddress(raw) {
                if (!raw || typeof raw !== "string") {
                    return $.extend({}, defaults);
                }
                try {
                    const obj = JSON.parse(raw);
                    if ($.isPlainObject(obj)) {
                        return $.extend({}, defaults, obj);
                    }
                } catch (e) {
                    // ignore
                }
                return $.extend({}, defaults);
            }

            window["gfAddressParse<?php echo absint($this->id); ?>"] = parseAddress;

            function populateStateOptions(container, country, stateValue) {
                const select = container.find(".gf-address-state-select");
                const input = container.find(".gf-address-state-input");
                const selectWrap = container.find(".gf-address-state-select-wrap");
                const inputWrap = container.find(".gf-address-state-input-wrap");
                const list = cfg.subdivisions[country] || null;

                if (list && Object.keys(list).length > 0) {
                    select.empty();
                    select.append($("<option>", { value: "", text: "<?php echo esc_js($this->lang->getText('address_state_placeholder')); ?>" }));
                    $.each(list, function(code, label) {
                        select.append($("<option>", { value: code, text: label }));
                    });
                    if (stateValue && list[stateValue]) {
                        select.val(stateValue);
                    } else {
                        select.val("");
                    }
                    selectWrap.removeClass("d-none");
                    select.removeClass("d-none");
                    inputWrap.addClass("d-none");
                    input.addClass("d-none");
                    if (cfg.disabled || cfg.readonly) {
                        select.prop("disabled", true);
                    } else {
                        select.prop("disabled", false);
                    }
                } else {
                    selectWrap.addClass("d-none");
                    select.addClass("d-none");
                    select.prop("disabled", false);
                    inputWrap.removeClass("d-none");
                    input.removeClass("d-none");
                    input.val(stateValue || "");
                    if (cfg.disabled) {
                        input.prop("disabled", true);
                    } else {
                        input.prop("disabled", false);
                    }
                }
            }

            function updateHidden(container) {
                const hidden = container.find(formControlClass).first();
                if (!hidden.length) {
                    return;
                }
                const current = $.extend({}, defaults);
                Object.keys(defaults).forEach(function(key) {
                    const candidates = container.find('[data-gf-address-key="' + key + '"]');
                    if (!candidates.length) {
                        return;
                    }

                    let control = candidates.filter(":visible").first();
                    if (!control.length) {
                        control = candidates.not(".d-none").first();
                    }
                    if (!control.length) {
                        control = candidates.first();
                    }

                    current[key] = ($.trim(control.val() || "") || "");
                });
                hidden.val(JSON.stringify(current)).trigger("change");
            }

            function initAddress(container) {
                const hidden = container.find(formControlClass).first();
                const data = parseAddress(hidden.val());
                const countrySelect = container.find(".gf-address-country");

                // Prefill other inputs
                container.find(".gf-address-line1").val(data.line1);
                container.find(".gf-address-line2").val(data.line2);
                container.find(".gf-address-city").val(data.city);
                container.find(".gf-address-postal").val(data.postal);

                if (countrySelect.find("option[value=\"" + data.country + "\"]").length) {
                    countrySelect.val(data.country);
                } else {
                    countrySelect.val("");
                }
                if (!countrySelect.val() && window.GFLocaleUtils && typeof window.GFLocaleUtils.applyDefaults === "function") {
                    window.GFLocaleUtils.applyDefaults(container[0]);
                }

                if (cfg.disabled || cfg.readonly) {
                    countrySelect.prop("disabled", true);
                }

                populateStateOptions(container, countrySelect.val(), data.state);
                updateHidden(container);

                container.find("[data-gf-address-key]").on("change input", function() {
                    const key = $(this).data("gf-address-key");
                if (key === "country") {
                        populateStateOptions(container, $(this).val(), "");
                    }
                    updateHidden(container);
                });
            }

            function initAll() {
                $(containerSelector).each(function() {
                    initAddress($(this));
                });
            }

            $(document).on("griffinforms:fieldChanged", function(event, detail) {
                if (!detail || parseInt(detail.fieldId, 10) !== cfg.fieldId) {
                    return;
                }
                if (detail.reason === "iteration_added") {
                    const target = $(containerSelector).last();
                    initAddress(target);
                }
            });

            initAll();
        })(jQuery);
        <?php
    }

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

    protected function isCountryFilter()
    {
        if (!isset($this->validations['type']) || $this->validations['type'] !== 'custom-parameters') {
            return false;
        }
        $mode = isset($this->validations['country_filter_mode']) ? $this->validations['country_filter_mode'] : 'all_countries';
        return $mode === 'limit_to_countries' || $mode === 'exclude_countries';
    }

    protected function requiredJs($error)
    {
        $required_labels = array(
            'line1'   => $this->labels['line1'],
            'city'    => $this->labels['city'],
            'state'   => $this->labels['state'],
            'postal'  => $this->labels['postal'],
            'country' => $this->labels['country']
        );
        $base_required_message = $this->lang->getText('requiredError', $this->validations);
        ?>
        const addressWrapperSelector<?php echo absint($this->id); ?> = ".griffinforms-address-wrapper-<?php echo absint($this->id); ?>";
        const addressRequiredLabels<?php echo absint($this->id); ?> = <?php echo wp_json_encode($required_labels); ?>;
        const addressRequiredKeys<?php echo absint($this->id); ?> = Object.keys(addressRequiredLabels<?php echo absint($this->id); ?>);
        const addressBaseRequiredMessage<?php echo absint($this->id); ?> = "<?php echo esc_js($base_required_message); ?>";

        $(actionBtn).hover(function(){
            $(formControl).each(function(){
                validateAddressRequired($(this));
            });
        });

        $(document).on("input change", addressWrapperSelector<?php echo absint($this->id); ?> + " [data-gf-address-key]", function(){
            const hidden = $(this).closest(addressWrapperSelector<?php echo absint($this->id); ?>).find(formControl).first();
            validateAddressRequired(hidden);
        });

        function highlightMissingAddressParts(hiddenInput, missingKeys) {
            const container = hiddenInput.closest(addressWrapperSelector<?php echo absint($this->id); ?>);
            if (!container.length) {
                return;
            }

            $.each(addressRequiredLabels<?php echo absint($this->id); ?>, function(key) {
                const candidates = container.find('[data-gf-address-key="' + key + '"]');
                if (!candidates.length) {
                    return;
                }

                const target = candidates.filter(':visible').first().length ? candidates.filter(':visible').first() : candidates.first();
                if ($.inArray(key, missingKeys) !== -1) {
                    target.addClass("border-danger bg-danger bg-opacity-10");
                    target.addClass("gf-validation-invalid");
                } else {
                    target.removeClass("border-danger bg-danger bg-opacity-10");
                    target.removeClass("gf-validation-invalid");
                }
            });

            const alert = hiddenInput.siblings(".griffinforms-alert-required").length
                ? hiddenInput.siblings(".griffinforms-alert-required").first()
                : hiddenInput.closest(".griffinforms-formcontrol-container").find(".griffinforms-alert-required").first();

            if (!alert.length) {
                return;
            }

            if (missingKeys.length) {
                const labels = $.map(missingKeys, function(key) {
                    return addressRequiredLabels<?php echo absint($this->id); ?>[key];
                });
                alert.text(addressBaseRequiredMessage<?php echo absint($this->id); ?> + " (" + labels.join(", ") + ")");
            } else {
                alert.text(addressBaseRequiredMessage<?php echo absint($this->id); ?>);
            }
        }

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

            const data = window["gfAddressParse<?php echo absint($this->id); ?>"](hiddenInput.val());
            const missing = [];

            $.each(addressRequiredKeys<?php echo absint($this->id); ?>, function(index, key) {
                const value = data[key] ? String(data[key]).trim() : "";
                if (value === "") {
                    missing.push(key);
                }
            });

            if (missing.length) {
                addAlert(hiddenInput, "<?php echo esc_js($error); ?>");
            } else {
                removeAlert(hiddenInput, "<?php echo esc_js($error); ?>");
            }

            highlightMissingAddressParts(hiddenInput, missing);
        }
        <?php
    }

    protected function countryFilterJs($error)
    {
        $mode = isset($this->validations['country_filter_mode']) ? $this->validations['country_filter_mode'] : 'all_countries';
        $allowed = isset($this->validations['allowed_countries']) ? $this->validations['allowed_countries'] : array();
        $excluded = isset($this->validations['excluded_countries']) ? $this->validations['excluded_countries'] : array();

        ?>
        const addressMode<?php echo absint($this->id); ?> = "<?php echo esc_js($mode); ?>";
        const addressAllowed<?php echo absint($this->id); ?> = <?php echo wp_json_encode($allowed); ?>.map(code => code.toUpperCase());
        const addressExcluded<?php echo absint($this->id); ?> = <?php echo wp_json_encode($excluded); ?>.map(code => code.toUpperCase());

        $(document).on("change", ".griffinforms-address-wrapper-<?php echo absint($this->id); ?> .gf-address-country", function(){
            const hidden = $(this).closest(".griffinforms-address-wrapper-<?php echo absint($this->id); ?>").find(formControl).first();
            checkCountryFilter(hidden);
        });

        $(actionBtn).hover(function(){
            $(formControl).each(function(){
                checkCountryFilter($(this));
            });
        });

        function checkCountryFilter(hiddenInput) {
            const data = window["gfAddressParse<?php echo absint($this->id); ?>"](hiddenInput.val());
            const country = (data.country || "").toUpperCase();

            if (!country) {
                removeAlert(hiddenInput, "<?php echo esc_js($error); ?>");
                return;
            }

            if (addressMode<?php echo absint($this->id); ?> === "limit_to_countries") {
                if (addressAllowed<?php echo absint($this->id); ?>.length && !addressAllowed<?php echo absint($this->id); ?>.includes(country)) {
                    addAlert(hiddenInput, "<?php echo esc_js($error); ?>");
                    return;
                }
            }

            if (addressMode<?php echo absint($this->id); ?> === "exclude_countries") {
                if (addressExcluded<?php echo absint($this->id); ?>.includes(country)) {
                    addAlert(hiddenInput, "<?php echo esc_js($error); ?>");
                    return;
                }
            }

            removeAlert(hiddenInput, "<?php echo esc_js($error); ?>");
        }
        <?php
    }
}
