jQuery(function ($) {
    if (!$.ui || !$.ui.autocomplete) {
        return;
    }

    const settings = window.w5eAddressDirectory || {};
    const ajaxUrl = settings.ajaxUrl || '';
    const i18n = settings.i18n || {};

    const noResults = (term) => {
        const template = i18n.noResults || 'Nema rezultata za "%s".';
        return template.replace('%s', term);
    };

    const pickCityFirst = i18n.pickCityFirst || 'Prvo odaberite grad/opštinu.';
    const pickMunicipalityFirst = i18n.pickMunicipalityFirst || 'Prvo odaberite opštinu.';
    const pickPlaceFirst = i18n.pickPlaceFirst || 'Prvo odaberite mesto.';
    const useSingleStepTown = true;

    const fieldSets = [
        {
            city: '#billing_city',
            address: '#billing_address_1',
            address2: '#billing_address_2',
            state: '#billing_state',
            postcode: '#billing_postcode',
        },
        {
            city: '#shipping_city',
            address: '#shipping_address_1',
            address2: '#shipping_address_2',
            state: '#shipping_state',
            postcode: '#shipping_postcode',
        },
        {
            city: '#billing-city',
            address: '#billing-address_1',
            address2: '#billing-address_2',
            state: '#billing-state',
            postcode: '#billing-postcode',
        },
        {
            city: '#shipping-city',
            address: '#shipping-address_1',
            address2: '#shipping-address_2',
            state: '#shipping-state',
            postcode: '#shipping-postcode',
        },
    ];

    const setLoading = (el, active) => {
        $(el).parent().toggleClass('w5e-loading', !!active);
    };

    const setDisabled = (selector, disabled) => {
        $(selector).prop('disabled', !!disabled);
    };

    const ensureTextInputField = ($el) => {
        if (!$el || !$el.length) {
            return $el;
        }

        const isSelect = $el.is('select');
        const isHiddenInput = $el.is('input[type="hidden"]');
        if (!isSelect && !isHiddenInput) {
            return $el;
        }

        const node = $el.get(0);
        if (!node || !node.id) {
            return $el;
        }

        const $input = $('<input type="text" />');

        Array.from(node.attributes || []).forEach((attr) => {
            if (!attr || !attr.name) {
                return;
            }
            const attrName = attr.name.toLowerCase();
            if (attrName === 'type' || attrName === 'disabled' || attrName === 'readonly') {
                return;
            }
            $input.attr(attr.name, attr.value);
        });

        let initialValue = ($el.val() || '').toString();
        if (isSelect) {
            const selectedText = ($el.find('option:selected').text() || '').trim();
            initialValue = /^RS[A-Z0-9]+$/i.test(initialValue) ? selectedText : initialValue;
        }

        const existingClasses = ($input.attr('class') || '')
            .split(/\s+/)
            .filter((cls) => cls && cls !== 'hidden');

        $input
            .attr('type', 'text')
            .attr('class', existingClasses.join(' '))
            .val(initialValue)
            .prop('disabled', false)
            .removeAttr('disabled')
            .prop('readonly', false)
            .removeAttr('readonly')
            .removeAttr('tabindex');

        $el.replaceWith($input);

        const $result = $('#' + node.id);
        const $wrap = getFieldWrapper($result);
        if ($wrap && $wrap.length) {
            $wrap.removeClass('hidden').css('display', '');
        }

        return $result;
    };

    const disableBrowserAutocomplete = ($el) => {
        if (!$el || !$el.length) {
            return;
        }
        $el.attr('autocomplete', 'new-password')
            .attr('autocorrect', 'off')
            .attr('autocapitalize', 'off')
            .attr('data-lpignore', 'true')
            .attr('data-1p-ignore', 'true')
            .attr('data-bwignore', 'true')
            .attr('data-form-type', 'other')
            .prop('spellcheck', false);
    };

    const getFieldMeta = (node) => {
        if (!node || !node.id) {
            return null;
        }
        const id = node.id;
        let scope = '';
        if (id.startsWith('billing')) {
            scope = 'billing';
        } else if (id.startsWith('shipping')) {
            scope = 'shipping';
        } else {
            return null;
        }
        let key = id.replace(/^billing[-_]/, '').replace(/^shipping[-_]/, '');
        key = key.replace('-', '_');
        return { scope, key };
    };

    const updateStoreValue = ($el, value) => {
        const node = $el.get(0);
        const meta = getFieldMeta(node);
        if (!meta || !window.wp || !wp.data || !wp.data.dispatch) {
            return false;
        }
        const patch = { [meta.key]: value };
        const storeKeys = ['wc/store/cart', 'wc/store/checkout', 'wc/checkout', 'wc/store'];
        let updated = false;
        storeKeys.forEach((storeKey) => {
            try {
                const dispatcher = wp.data.dispatch(storeKey);
                if (!dispatcher) {
                    return;
                }
                const fn = meta.scope === 'billing' ? dispatcher.setBillingAddress : dispatcher.setShippingAddress;
                if (typeof fn === 'function') {
                    fn.call(dispatcher, patch);
                    updated = true;
                }
            } catch (e) {
                // noop
            }
        });
        return updated;
    };

    const setInputValue = ($el, value) => {
        const node = $el.get(0);
        if (!node) {
            return;
        }
        const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(node), 'value');
        if (descriptor && typeof descriptor.set === 'function') {
            descriptor.set.call(node, value);
        } else {
            node.value = value;
        }
        node.dispatchEvent(new Event('input', { bubbles: true }));
        node.dispatchEvent(new Event('change', { bubbles: true }));
        updateStoreValue($el, value);
    };

    const normalize = (value) => (value || '').toString().trim().toLowerCase();
    const normalizeKey = (value) =>
        (value || '')
            .toString()
            .normalize('NFD')
            .replace(/[\u0300-\u036f]/g, '')
            .replace(/\s+/g, ' ')
            .trim()
            .toUpperCase();
    const markBound = ($el) => $el.data('w5e-bound', true);
    const isBound = ($el) => !!$el.data('w5e-bound');
    const setSearchSuppressed = ($el, suppressed) => $el.data('w5e-suppress-search', !!suppressed);
    const isSearchSuppressed = ($el) => !!$el.data('w5e-suppress-search');

    const setRenderer = (selector, renderer) => {
        const $el = $(selector);
        const inst = $el.data('ui-autocomplete') || $el.autocomplete('instance');
        if (inst && typeof renderer === 'function') {
            inst._renderItem = renderer;
        }
    };

    const shouldApplyFocusedItemValue = (event) => {
        const originalType = event && event.originalEvent && event.originalEvent.type
            ? event.originalEvent.type
            : '';

        return /^key/.test(originalType);
    };

    const toTitleCase = (str) =>
        str.replace(/([\p{L}'][^\s-]*) */gu, (txt) => {
            return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
        });

    const formatAddressAutocomplete = (ul, item) => {
        if (item.error) {
            return $("<li class='ui-state-disabled'>" + item.info + '</li>').appendTo(ul);
        }

        const label = item.label || item.value || '';
        return $('<li>').append('<div>' + label + '</div>').appendTo(ul);
    };

    const formatCityAutocomplete = (ul, item) => {
        if (item.error) {
            return $("<li class='ui-state-disabled'>" + item.info + '</li>').appendTo(ul);
        }

        const rawPlace = item.place ? item.place : '';
        const label = rawPlace ? toTitleCase(formatPlaceWithPrefix(rawPlace, item.municipality || '', item)) : '';

        return $('<li>').append('<div>' + label + '</div>').appendTo(ul);
    };

    const getMenuElement = ($input) => {
        const inst = $input.data('ui-autocomplete') || $input.autocomplete('instance');
        if (inst && inst.menu && inst.menu.element) {
            return inst.menu.element;
        }
        return $();
    };

    const syncMenuWidth = ($input) => {
        if (!$input || !$input.length) {
            return;
        }
        const $menu = getMenuElement($input);
        if (!$menu || !$menu.length) {
            return;
        }
        const inputWidth = $input.outerWidth();
        if (!inputWidth) {
            return;
        }
        $menu.css({
            width: Math.ceil(inputWidth) + 'px',
            maxWidth: Math.ceil(inputWidth) + 'px',
        });
    };

    const ensureSelectOption = ($select, value) => {
        if (!$select || !$select.length || !$select.is('select') || value === '') {
            return;
        }
        if ($select.find(`option[value=\"${value.replace(/\"/g, '\\\\\"')}\"]`).length === 0) {
            $select.append(new Option(value, value, false, false));
        }
    };

    const getBaseCity = (municipality) => {
        const muni = (municipality || '').toString().trim();
        if (!muni) {
            return '';
        }
        if (muni.includes('-')) {
            const parts = muni
                .split('-')
                .map((part) => part.trim())
                .filter(Boolean);
            if (parts.length) {
                return parts[parts.length - 1];
            }
        }
        return muni;
    };

    const splitPlaceParts = (place) =>
        (place || '')
            .toString()
            .split('-')
            .map((part) => part.trim())
            .filter(Boolean);

    const formatPlaceWithPrefix = (place, municipality) => {
        const baseCity = getBaseCity(municipality);
        if (!place) {
            return place;
        }

        const parts = splitPlaceParts(place);
        if (parts.length >= 2) {
            const first = parts[0];
            const rest = parts.slice(1).join('-');
            const firstKey = normalizeKey(first);
            const restKey = normalizeKey(rest);
            const baseKey = normalizeKey(baseCity);

            if (baseKey && firstKey === baseKey) {
                return `${first}-${rest}`;
            }

            if (baseKey && restKey === baseKey) {
                return `${rest}-${first}`;
            }

            return place;
        }

        if (!baseCity) {
            return place;
        }

        const placeKey = normalizeKey(place);
        const baseKey = normalizeKey(baseCity);
        if (!placeKey || !baseKey) {
            return place;
        }
        if (placeKey.startsWith(baseKey)) {
            return place;
        }
        return `${baseCity}-${place}`;
    };

    const setStateValue = ($state, value) => {
        if (!$state || !$state.length) {
            return;
        }
        if ($state.is('select')) {
            ensureSelectOption($state, value);
        }
        setInputValue($state, value);
    };

    const clearPlaceSelection = ($state, $postcode, $address) => {
        $state.data('w5e-place-id', '');
        $state.data('w5e-selected', '');
        $state.data('w5e-selected-zip', '');
        $state.data('w5e-selected-municipality', '');
        $address.data('w5e-selected', '');
        setStateValue($state, '');
        setInputValue($postcode, '');
        setInputValue($address, '');
    };

    const updatePlaceState = ($city, $state, $postcode, $address) => {
        const municipality = (($city.val && $city.val()) || '').toString().trim();
        const enable = municipality !== '';
        $state.prop('disabled', !enable);
        if (!enable) {
            clearPlaceSelection($state, $postcode, $address);
        }
        updatePostcodeState($state, $postcode);
        updateAddressState($state, $postcode, $address);
    };

    const applyMunicipalitySelection = ($city, $state, $postcode, $address, item) => {
        const selectedMunicipality = (item && (item.municipality || item.value)) || '';
        $city.data('w5e-selected', selectedMunicipality);
        setInputValue($city, selectedMunicipality);
        clearPlaceSelection($state, $postcode, $address);
        updatePlaceState($city, $state, $postcode, $address);
    };

    const applySingleStepPlaceSelection = ($city, $state, $postcode, $address, item) => {
        const selectedPlaceRaw = (item && (item.place || item.value)) || '';
        const selectedZip = (item && item.zip) || '';
        const selectedPlaceId = (item && item.place_id) || '';
        const selectedMunicipality = (item && item.municipality) || '';
        const selectedPlace = formatPlaceWithPrefix(selectedPlaceRaw, selectedMunicipality, item);

        $city.data('w5e-selected', selectedPlace);
        $city.data('w5e-place-id', selectedPlaceId);
        $city.data('w5e-selected-zip', selectedZip);
        $city.data('w5e-selected-municipality', selectedMunicipality);
        setInputValue($city, selectedPlace);

        $state.data('w5e-selected', selectedMunicipality);
        $state.data('w5e-place-id', selectedPlaceId);
        $state.data('w5e-selected-zip', selectedZip);
        $state.data('w5e-selected-municipality', selectedMunicipality);
        setStateValue($state, selectedMunicipality);

        if (selectedZip !== '' || $postcode.val() !== '') {
            setInputValue($postcode, selectedZip);
        }
        setInputValue($address, '');
        updatePostcodeState($state, $postcode);
        updateAddressState($state, $postcode, $address);
    };

    const applyPlaceSelection = ($city, $state, $postcode, $address, item) => {
        const selectedPlaceRaw = (item && (item.place || item.value)) || '';
        const selectedZip = (item && item.zip) || '';
        const selectedPlaceId = (item && item.place_id) || '';
        const selectedMunicipality = (item && item.municipality) || '';
        const selectedPlace = formatPlaceWithPrefix(selectedPlaceRaw, selectedMunicipality, item);

        if (selectedMunicipality !== '') {
            $city.data('w5e-selected', selectedMunicipality);
            setInputValue($city, selectedMunicipality);
        }

        $state.data('w5e-selected', selectedPlace);
        $state.data('w5e-selected-zip', selectedZip);
        $state.data('w5e-place-id', selectedPlaceId);
        $state.data('w5e-selected-municipality', selectedMunicipality);
        setStateValue($state, selectedPlace);

        if (selectedZip !== '' || $postcode.val() !== '') {
            setInputValue($postcode, selectedZip);
        }
        setInputValue($address, '');
        updatePostcodeState($state, $postcode);
        updateAddressState($state, $postcode, $address);
    };

    const applyAddressSelection = ($address, item) => {
        const selectedStreet = (item && item.value) || '';
        $address.data('w5e-selected', selectedStreet);
        setInputValue($address, selectedStreet);
    };

    const applyPostcodeSelection = ($city, $state, $postcode, $address, item) => {
        const selectedZip = (item && (item.zip || item.value)) || '';
        $postcode.data('w5e-selected', selectedZip);
        setInputValue($postcode, selectedZip);
        if (item && (item.place || item.value)) {
            const place = item.place || item.value;
            const municipality = item.municipality || '';
            const payload = {
                place: place,
                zip: selectedZip,
                municipality: municipality,
                place_id: item.place_id || '',
                municipality_is_place: item.municipality_is_place || false,
            };
            if (useSingleStepTown) {
                applySingleStepPlaceSelection($city, $state, $postcode, $address, payload);
            } else {
                applyPlaceSelection($city, $state, $postcode, $address, payload);
            }
        }
    };

    const updatePostcodeState = ($state, $postcode) => {
        const placeId = $state.data('w5e-place-id') || '';
        const placeVal = (($state && $state.val && $state.val()) || '').toString().trim();
        $postcode.prop('disabled', !(placeId || placeVal));
    };

    const updateAddressState = ($state, $postcode, $address) => {
        const placeId = $state.data('w5e-place-id') || '';
        const placeVal = (($state && $state.val && $state.val()) || '').toString().trim();
        const postcodeVal = (($postcode && $postcode.val && $postcode.val()) || '').toString().trim();
        const enable = !!((placeId || placeVal) && postcodeVal);
        $address.prop('disabled', !enable);
        if (!enable) {
            $address.data('w5e-selected', '');
            const currentVal = (($address && $address.val && $address.val()) || '').toString().trim();
            if (currentVal === '') {
                setInputValue($address, '');
            }
        }
    };

    const getFieldWrapper = ($el) => {
        if (!$el || !$el.length) {
            return null;
        }
        const selectors = [
            '.wc-block-components-text-input',
            '.wc-block-components-form-row',
            '.woocommerce-form-row',
            '.form-row',
        ];
        for (const selector of selectors) {
            const $wrap = $el.closest(selector);
            if ($wrap.length) {
                return $wrap;
            }
        }
        return null;
    };

    const applyAntSelectStyle = ($el) => {
        if (!$el || !$el.length) {
            return;
        }
        const $wrap = getFieldWrapper($el);
        if ($wrap && $wrap.length) {
            $wrap.addClass('w5e-ant-select');
        }
        const $inputWrap = $el.closest('.woocommerce-input-wrapper');
        if ($inputWrap.length) {
            $inputWrap.addClass('w5e-ant-select-input');
        }
        $el.addClass('w5e-ant-select-input');
    };

    const forceRequiredFieldUI = (set) => {
        const $city = $(set.city);
        const $state = $(set.state);
        if ($city.length && !$city.data('w5e-town-ui-applied')) {
            const $cityWrap = getFieldWrapper($city);
            if ($cityWrap && $cityWrap.length) {
                $cityWrap.removeClass('hidden').removeAttr('style').show();
                $cityWrap.removeClass('validate-optional').addClass('validate-required');
                $cityWrap.find('.optional').remove();
                $cityWrap.find('label').each(function () {
                    const $label = $(this);
                    $label.contents().filter(function () {
                        return this.nodeType === 3;
                    }).remove();
                    $label.prepend(document.createTextNode('Town'));
                });
            }
            if (!$city.attr('placeholder') || /city|town|district|county|grad|opstin/i.test($city.attr('placeholder'))) {
                $city.attr('placeholder', 'Town');
            }
            $city.data('w5e-town-ui-applied', true);
        }

        if (!$state.length) {
            return;
        }
        if ($state.data('w5e-force-ui-applied')) {
            return;
        }
        $state.prop('required', false).removeAttr('aria-required');
        const $wrap = getFieldWrapper($state);
        if ($wrap && $wrap.length) {
            $wrap.addClass('hidden').attr('aria-hidden', 'true').hide();
        }
        $state.data('w5e-force-ui-applied', true);
    };

    const reorderFields = (set) => {
        const $state = $(set.state);
        const $city = $(set.city);
        const $address = $(set.address);
        const $address2 = $(set.address2);
        const $postcode = $(set.postcode);
        if (!$city.length || !$address.length) {
            return;
        }

        const $stateWrap = $state.length ? getFieldWrapper($state) : null;
        const $cityWrap = getFieldWrapper($city);
        const $addressWrap = getFieldWrapper($address);
        const $address2Wrap = $address2.length ? getFieldWrapper($address2) : null;
        const $postcodeWrap = $postcode.length ? getFieldWrapper($postcode) : null;
        if (!$cityWrap) {
            return;
        }
        const sameRowParent = $addressWrap && $addressWrap.length && $cityWrap.parent().get(0) === $addressWrap.parent().get(0);

        const $afterCity = $cityWrap;

        if ($stateWrap && $stateWrap.length && $stateWrap.parent().get(0) === $cityWrap.parent().get(0)) {
            if ($afterCity.next().get(0) !== $stateWrap.get(0)) {
                $stateWrap.insertAfter($afterCity);
            }
        }

        const $afterPlace = $stateWrap && $stateWrap.length ? $stateWrap : $afterCity;

        if ($postcodeWrap && $postcodeWrap.length) {
            if ($postcodeWrap.parent().get(0) === $cityWrap.parent().get(0)) {
                if ($afterPlace.next().get(0) !== $postcodeWrap.get(0)) {
                    $postcodeWrap.insertAfter($afterPlace);
                }
            }
        }

        const $afterZip = $postcodeWrap && $postcodeWrap.length ? $postcodeWrap : $afterPlace;
        if (sameRowParent) {
            if ($afterZip.next().get(0) !== $addressWrap.get(0)) {
                $addressWrap.insertAfter($afterZip);
            }
        }

        if ($address2Wrap && $address2Wrap.length && $addressWrap && $addressWrap.length) {
            if ($address2Wrap.parent().get(0) !== $addressWrap.parent().get(0)) {
                return;
            }
            if ($addressWrap.next().get(0) !== $address2Wrap.get(0)) {
                $address2Wrap.insertAfter($addressWrap);
            }
        }
    };

    const bindCity = (set) => {
        const $city = $(set.city);
        if (!$city.length || isBound($city)) {
            return;
        }

        const $state = $(set.state);
        const $postcode = $(set.postcode);
        const $address = $(set.address);
        $city
            .autocomplete({
                source(request, response) {
                    const payload = useSingleStepTown
                        ? {
                            action: 'w5e_fetch_place_suggestions',
                            term: request.term,
                        }
                        : {
                            action: 'w5e_fetch_municipality_suggestions',
                            term: request.term,
                        };

                    $.ajax({
                        url: ajaxUrl,
                        dataType: 'json',
                        data: payload,
                        success(data) {
                            if (!Array.isArray(data) || data.length === 0) {
                                response([
                                    {
                                        error: true,
                                        info: noResults(request.term),
                                    },
                                ]);
                                return;
                            }

                            response(data);
                        },
                    });
                },
                minLength: 2,
                search(event) {
                    if (isSearchSuppressed($city)) {
                        setLoading(event.target, false);
                        return false;
                    }
                    setDisabled($postcode.add($address), true);
                    setLoading(event.target, true);
                },
                response(event) {
                    setLoading(event.target, false);
                    syncMenuWidth($city);
                    updatePlaceState($city, $state, $postcode, $address);
                },
                focus(event, ui) {
                    if (!shouldApplyFocusedItemValue(event)) {
                        return false;
                    }
                    if (ui.item && (ui.item.place || ui.item.municipality || ui.item.value)) {
                        const nextValue = useSingleStepTown
                            ? formatPlaceWithPrefix(ui.item.place || ui.item.value || '', ui.item.municipality || '', ui.item)
                            : (ui.item.municipality || ui.item.value || '');
                        setInputValue($city, nextValue);
                    }
                    return false;
                },
                select(event, ui) {
                    if (useSingleStepTown) {
                        applySingleStepPlaceSelection($city, $state, $postcode, $address, ui.item);
                    } else {
                        applyMunicipalitySelection($city, $state, $postcode, $address, ui.item);
                    }
                    updatePlaceState($city, $state, $postcode, $address);
                    setSearchSuppressed($city, true);
                    $city.autocomplete('close');
                    return false;
                },
                change(event, ui) {
                    if (ui.item) {
                        return;
                    }

                    const current = $city.val() || '';
                    const selected = $city.data('w5e-selected') || '';
                    if (selected && normalize(current) === normalize(selected)) {
                        updatePlaceState($city, $state, $postcode, $address);
                        return;
                    }
                    $city.data('w5e-selected', '');
                    $city.data('w5e-place-id', '');
                    $city.data('w5e-selected-zip', '');
                    $city.data('w5e-selected-municipality', '');
                    clearPlaceSelection($state, $postcode, $address);
                    updatePlaceState($city, $state, $postcode, $address);
                },
            })
            .on('click', function () {
                setSearchSuppressed($city, false);
            })
            .on('keydown', function (event) {
                const isLetter = /^[a-zA-ZčćžšđČĆŽŠĐ]$/.test(event.key);
                if (isLetter) {
                    setLoading(event.target, true);
                }
            })
            .on('autocompleteselect', function (event, ui) {
                if (useSingleStepTown) {
                    applySingleStepPlaceSelection($city, $state, $postcode, $address, ui.item);
                } else {
                    applyMunicipalitySelection($city, $state, $postcode, $address, ui.item);
                }
                updatePlaceState($city, $state, $postcode, $address);
                setSearchSuppressed($city, true);
                $city.autocomplete('close');
                return false;
            })
            .on('blur', function () {
                const selected = $city.data('w5e-selected') || '';
                const current = $city.val() || '';
                if (selected && normalize(current) !== normalize(selected)) {
                    setInputValue($city, selected);
                }
                updatePlaceState($city, $state, $postcode, $address);
            });

        setRenderer(set.city, useSingleStepTown ? formatCityAutocomplete : formatAddressAutocomplete);
        markBound($city);
        updatePlaceState($city, $state, $postcode, $address);
    };

    const bindAddress = (set) => {
        const $address = $(set.address);
        if (!$address.length || isBound($address)) {
            return;
        }

        const $postcode = $(set.postcode);
        const $city = $(set.city);
        const $state = $(set.state);

        $address
            .autocomplete({
                source(request, response) {
                    const zipVal = $postcode.val();
                    const placeVal = $state.val();
                    const placeId = $state.data('w5e-place-id') || '';

                    if ((!placeVal || !zipVal) && !placeId) {
                        response([
                            {
                                error: true,
                                info: pickPlaceFirst,
                            },
                        ]);
                        return;
                    }

                    $.ajax({
                        url: ajaxUrl,
                        dataType: 'json',
                        data: {
                            action: 'w5e_fetch_address_suggestions',
                            term: request.term,
                            zip: zipVal,
                            place: placeVal,
                            place_id: placeId,
                        },
                        success(data) {
                            if (!Array.isArray(data) || data.length === 0) {
                                response([
                                    {
                                        error: true,
                                        info: noResults(request.term),
                                    },
                                ]);
                                return;
                            }

                            response(data);
                        },
                    });
                },
                minLength: 1,
                search(event) {
                    if (isSearchSuppressed($address)) {
                        setLoading(event.target, false);
                        return false;
                    }
                    setLoading(event.target, true);
                },
                response(event) {
                    setLoading(event.target, false);
                    syncMenuWidth($address);
                },
                focus(event, ui) {
                    if (!shouldApplyFocusedItemValue(event)) {
                        return false;
                    }
                    if (ui.item && ui.item.value) {
                        setInputValue($address, ui.item.value || '');
                    }
                    return false;
                },
                select(event, ui) {
                    applyAddressSelection($address, ui.item);
                    setSearchSuppressed($address, true);
                    $address.autocomplete('close');
                    return false;
                },
                change(event, ui) {
                    if (!ui.item) {
                        const current = $address.val() || '';
                        const selected = $address.data('w5e-selected') || '';
                        if (selected && normalize(current) === normalize(selected)) {
                            return;
                        }
                        if (current === '') {
                            setInputValue($address, '');
                        }
                    }
                },
            })
            .on('click', function () {
                setSearchSuppressed($address, false);
            })
            .on('keydown', function (event) {
                const isLetter = /^[a-zA-ZčćžšđČĆŽŠĐ]$/.test(event.key);
                if (isLetter) {
                    setLoading(event.target, true);
                }
            })
            .on('autocompleteselect', function (event, ui) {
                applyAddressSelection($address, ui.item);
                setSearchSuppressed($address, true);
                $address.autocomplete('close');
                return false;
            })
            .on('blur', function () {
                const selected = $address.data('w5e-selected') || '';
                const current = $address.val() || '';
                if (selected && normalize(current) !== normalize(selected)) {
                    setInputValue($address, selected);
                }
            });

        setRenderer(set.address, formatAddressAutocomplete);
        markBound($address);
        updateAddressState($state, $postcode, $address);
    };

    const bindPostcode = (set) => {
        const $postcode = $(set.postcode);
        if (!$postcode.length || isBound($postcode)) {
            return;
        }
        const $state = $(set.state);
        const $address = $(set.address);
        $postcode.prop('readonly', false).removeAttr('readonly').removeAttr('tabindex');
        markBound($postcode);
        updatePostcodeState($state, $postcode);
        updateAddressState($state, $postcode, $address);
    };

    const bindMunicipality = (set) => {
        let $state = $(set.state);
        $state = ensureTextInputField($state);
        if (!$state.length || isBound($state)) {
            return;
        }

        if (useSingleStepTown) {
            markBound($state);
            return;
        }

        const $city = $(set.city);
        const $postcode = $(set.postcode);
        const $address = $(set.address);

        $state
            .autocomplete({
                source(request, response) {
                    const municipalityVal = ($city.val() || '').toString().trim();
                    if (!municipalityVal) {
                        response([
                            {
                                error: true,
                                info: pickCityFirst,
                            },
                        ]);
                        return;
                    }
                    $.ajax({
                        url: ajaxUrl,
                        dataType: 'json',
                        data: {
                            action: 'w5e_fetch_place_suggestions',
                            term: request.term,
                            municipality: municipalityVal,
                        },
                        success(data) {
                            if (!Array.isArray(data) || data.length === 0) {
                                response([
                                    {
                                        error: true,
                                        info: noResults(request.term),
                                    },
                                ]);
                                return;
                            }

                            response(data);
                        },
                    });
                },
                minLength: 2,
                search(event) {
                    if (isSearchSuppressed($state)) {
                        setLoading(event.target, false);
                        return false;
                    }
                    const municipalityVal = ($city.val() || '').toString().trim();
                    if (!municipalityVal) {
                        setLoading(event.target, false);
                        return false;
                    }
                    setDisabled($postcode.add($address), true);
                    setLoading(event.target, true);
                },
                response(event) {
                    setLoading(event.target, false);
                    syncMenuWidth($state);
                    updatePostcodeState($state, $postcode);
                    updateAddressState($state, $postcode, $address);
                },
                focus(event, ui) {
                    if (!shouldApplyFocusedItemValue(event)) {
                        return false;
                    }
                    if (ui.item && (ui.item.place || ui.item.value)) {
                        setStateValue($state, ui.item.place || ui.item.value || '');
                    }
                    return false;
                },
                select(event, ui) {
                    applyPlaceSelection($city, $state, $postcode, $address, ui.item);
                    updateAddressState($state, $postcode, $address);
                    setSearchSuppressed($state, true);
                    $state.autocomplete('close');
                    return false;
                },
                change(event, ui) {
                    if (ui.item) {
                        return;
                    }

                    const current = (($state.val && $state.val()) || '').toString();
                    const selected = ($state.data('w5e-selected') || '').toString();

                    if (selected && normalize(current) === normalize(selected)) {
                        const selectedZip = ($state.data('w5e-selected-zip') || '').toString();
                        if (selectedZip !== '' && $postcode.val() !== selectedZip) {
                            setInputValue($postcode, selectedZip);
                        }
                        updateAddressState($state, $postcode, $address);
                        return;
                    }

                    $state.data('w5e-selected', '');
                    $state.data('w5e-place-id', '');
                    $state.data('w5e-selected-zip', '');
                    $state.data('w5e-selected-municipality', '');
                    $address.data('w5e-selected', '');
                    setInputValue($address, '');
                    setInputValue($postcode, '');
                    updateAddressState($state, $postcode, $address);
                },
            })
            .on('click', function () {
                setSearchSuppressed($state, false);
            })
            .on('keydown', function (event) {
                const isLetter = /^[a-zA-ZčćžšđČĆŽŠĐ]$/.test(event.key);
                if (isLetter) {
                    setLoading(event.target, true);
                }
            })
            .on('autocompleteselect', function (event, ui) {
                applyPlaceSelection($city, $state, $postcode, $address, ui.item);
                updateAddressState($state, $postcode, $address);
                setSearchSuppressed($state, true);
                $state.autocomplete('close');
                return false;
            })
            .on('blur', function () {
                const selected = ($state.data('w5e-selected') || '').toString();
                const current = (($state.val && $state.val()) || '').toString();
                if (selected && normalize(current) !== normalize(selected)) {
                    setStateValue($state, selected);
                }
                updateAddressState($state, $postcode, $address);
            });

        setRenderer(set.state, formatCityAutocomplete);
        markBound($state);
        updatePlaceState($city, $state, $postcode, $address);
    };

    const bindAll = () => {
        fieldSets.forEach((set) => {
            if (!$(set.city).length && !$(set.address).length && !$(set.postcode).length) {
                return;
            }
            ensureTextInputField($(set.state));
            applyAntSelectStyle($(set.city));
            applyAntSelectStyle($(set.address));
            applyAntSelectStyle($(set.postcode));
            applyAntSelectStyle($(set.state));
            disableBrowserAutocomplete($(set.city));
            disableBrowserAutocomplete($(set.address));
            disableBrowserAutocomplete($(set.postcode));
            disableBrowserAutocomplete($(set.state));
            forceRequiredFieldUI(set);
            reorderFields(set);
            bindCity(set);
            bindMunicipality(set);
            bindPostcode(set);
            bindAddress(set);
            forceRequiredFieldUI(set);
            reorderFields(set);
        });
    };

    let bindQueued = false;
    let bindRunning = false;
    const scheduleBindAll = () => {
        if (bindQueued || bindRunning) {
            return;
        }
        bindQueued = true;
        setTimeout(() => {
            bindQueued = false;
            runBindAll();
        }, 0);
    };

    const runBindAll = () => {
        if (bindRunning) {
            return;
        }
        bindRunning = true;
        try {
            bindAll();
        } finally {
            bindRunning = false;
        }
    };

    runBindAll();

    $(document.body).on('updated_checkout', scheduleBindAll);

    if (window.MutationObserver) {
        const observer = new MutationObserver(() => scheduleBindAll());
        observer.observe(document.body, { childList: true, subtree: true });
    }
});
