<template>
    <div class="address-questions">
        <div class="row mb-4" aria-live="polite">
            <div class="col">
                <!-- Temporarily changing id from autocomplete to streetAddress -->
                <textboxQuestion
                    id="streetAddressField"
                    cmsWidgetName="StreetAddressQuestionWidget"
                    v-model="addressModel.streetAddress"
                    ref="autocomplete"
                    customInputId="streetAddress"
                    placeholderText="Search"
                    aria-haspopup=""
                    hasIcon
                    validationRules="street-address-required"
                    autocomplete="street-address"
                    @keydown.enter.prevent />
            </div>
        </div>
        <transition name="fade" mode="out-in">
            <div
                class="row mb-4"
                v-show="showAddressFields && showApartmentNumberOrBusinessNameField"
                aria-live="polite">
                <div class="col">
                    <textboxQuestion
                        customInputId="apartmentNumberOrBusinessName"
                        cmsWidgetName="ApartmentNumberOrBusinessNameQuestionWidget"
                        v-model="addressModel.apartmentNumberOrBusinessName"
                        ref="apartmentNumberOrBusinessName" />
                </div>
            </div>
        </transition>
        <transition name="fade" mode="out-in">
            <div class="row mb-4" v-show="showAddressFields" aria-live="polite">
                <div class="col">
                    <textboxQuestion
                        customInputId="city"
                        cmsWidgetName="CityQuestionWidget"
                        v-model="addressModel.city"
                        ref="city"
                        validationRules="city-required"
                        autocomplete="address-level2" />
                </div>
            </div>
        </transition>
        <transition name="fade" mode="out-in">
            <div class="row mb-4" v-show="showAddressFields" aria-live="polite">
                <div class="col">
                    <dropdownQuestion
                        customDropdownId="state"
                        cmsWidgetName="StateQuestionWidget"
                        v-model="addressModel.state"
                        ref="state"
                        :options="stateOptions"
                        validationRules="state-required"
                        autocomplete="address-level1" />
                </div>
                <div class="col">
                    <textboxQuestion
                        customInputId="zipCode"
                        cmsWidgetName="ZipQuestionWidget"
                        v-model="addressModel.zipCode"
                        ref="zipCode"
                        mask="#####"
                        validationRules="zip-code-required|zip-code-format"
                        autocomplete="postal-code" />
                </div>
            </div>
        </transition>
        <alert
            ref="alertVerificationWarning"
            v-if="displayVerificationWarning"
            class="mb-4"
            cmsWidgetName="AlertVerificationWarningWidget"
            alertClass="alert-warning"
            v-bind:isDismissible="false" />
        <alert
            ref="alertNoMatchWarning"
            v-if="displayNoMatchWarning"
            class="mb-4"
            cmsWidgetName="AlertNoMatchWarningWidget"
            alertClass="alert-warning"
            v-bind:isDismissible="false" />
    </div>
</template>

<script>
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
import dropdownQuestion from "@/digital-components/dropdown-question/dropdown-question";
import alert from "@/ux-components/alert/alert";
import { applicationConfig } from "@/constants/application-config.js";
import { defineRule } from "vee-validate";
import { required, regex } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";

// DEFINE VALIDATION RULES
defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQUIRED));
defineRule("city-required", required(errorMessages.CITY_REQUIRED));
defineRule("state-required", required(errorMessages.STATE_REQUIRED));
defineRule("zip-code-required", required(errorMessages.ZIP_REQUIRED));
defineRule("zip-code-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.ZIP_FORMAT));

export default {
    name: "address-questions",
    emits: ["update:modelValue"], // The component emits an event
    props: {
        modelValue: {
            type: Object,
            default: () => ({
                streetAddress: "",
                apartmentNumberOrBusinessName: "",
                city: "",
                state: "",
                zipCode: "",
            }),
        },
        validationRules: String,
        captureApartmentNumberOrBusinessName: {
            type: Boolean,
            default: false,
        },
        preserveCityAndStateOnReset: {
            type: Boolean,
            default: false,
        },
    },
    data() {
        return {
            autocomplete: null,
            autocompleteListener: null,
            showAddressFields: false,
            matchFound: null, // null = no attempted match, true = match was found, false = match was not found
            displayVerificationWarning: false,
            displayNoMatchWarning: false,
            alertHeadlineVerificationWarning: "",
            alertCopyVerificationWarning: "",
            alertHeadlineNoMatchWarning: "",
            alertCopyNoMatchWarning: "",
        };
    },
    computed: {
        stateOptions: {
            get: function () {
                return {
                    AL: "Alabama",
                    AK: "Alaska",
                    AZ: "Arizona",
                    AR: "Arkansas",
                    CA: "California",
                    CO: "Colorado",
                    CT: "Connecticut",
                    DE: "Delaware",
                    DC: "District Of Columbia",
                    FL: "Florida",
                    GA: "Georgia",
                    HI: "Hawaii",
                    ID: "Idaho",
                    IL: "Illinois",
                    IN: "Indiana",
                    IA: "Iowa",
                    KS: "Kansas",
                    KY: "Kentucky",
                    LA: "Louisiana",
                    ME: "Maine",
                    MD: "Maryland",
                    MA: "Massachusetts",
                    MI: "Michigan",
                    MN: "Minnesota",
                    MS: "Mississippi",
                    MO: "Missouri",
                    MT: "Montana",
                    NE: "Nebraska",
                    NV: "Nevada",
                    NH: "New Hampshire",
                    NJ: "New Jersey",
                    NM: "New Mexico",
                    NY: "New York",
                    NC: "North Carolina",
                    ND: "North Dakota",
                    OH: "Ohio",
                    OK: "Oklahoma",
                    OR: "Oregon",
                    PA: "Pennsylvania",
                    RI: "Rhode Island",
                    SC: "South Carolina",
                    SD: "South Dakota",
                    TN: "Tennessee",
                    TX: "Texas",
                    UT: "Utah",
                    VT: "Vermont",
                    VA: "Virginia",
                    WA: "Washington",
                    WV: "West Virginia",
                    WI: "Wisconsin",
                    WY: "Wyoming",
                };
            },
        },
        addressModel: {
            get: function () {
                return this.modelValue;
            },
            set: function (newValue) {
                this.$emit("update:modelValue", newValue);
            },
        },
        showApartmentNumberOrBusinessNameField: {
            get: function () {
                return this.captureApartmentNumberOrBusinessName;
            },
        },
        addressField1: {
            get: function () {
                return document.getElementById("streetAddress");
            },
        },
    },
    methods: {
        loadGooglePlacesAutocompleteScript() {
            // Load the Google Places Autocomplete script
            const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
            this.$loadScript(
                `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=Function.prototype`
            ).then(() => {
                // When loaded, trigger the setup
                this.initializeAutocomplete();
            });
        },
        initializeAutocomplete() {
            // Initialize the Google Places Autocomplete
            this.autocomplete = new window.google.maps.places.Autocomplete(this.addressField1, {
                componentRestrictions: { country: ["us"] },
                fields: ["address_components"],
                types: ["address"],
            });

            // Set up the Autocomplete place_changed event to call our method to fill in the address
            this.autocompleteListener = window.google.maps.event.addListener(
                this.autocomplete,
                "place_changed",
                this.fillInAddress
            );

            // When the Street Address textbox receives input for the first time,
            // append the search results list container to the bottom of the textbox
            // and disable browser autofill
            this.addressField1.addEventListener(
                "keydown",
                (e) => {
                    // Make place results box stick to the input on scroll
                    const streetAddressField = document.getElementById("streetAddressField");
                    const autocompleteResultsContainer =
                        document.getElementsByClassName("pac-container")[0];

                    if (autocompleteResultsContainer) {
                        streetAddressField.appendChild(autocompleteResultsContainer);
                    }
                },
                { once: true }
            );

            this.addressField1.addEventListener("keydown", (e) => {
                // If a match has been previously attempted then do nothing
                if (this.matchFound !== null) {
                    return;
                }

                // When either of the two enter keys or the tab key are pressed
                if (e.code === "Enter" || e.code === "NumpadEnter" || e.code === "Tab") {
                    window.google.maps.event.trigger(this.autocomplete, "place_changed");
                }
            });

            const addressFields = document.querySelectorAll(
                ".address-questions input, .address-questions select"
            );

            for (let addressField of addressFields) {
                addressField.addEventListener("input", (e) => {
                    switch (e.inputType) {
                        case "insertText":
                        case "insertFromPaste":
                        case "deleteContentBackward":
                            // If the user enters text by typing, or pasting text, or deleting text already entered, disable *autofill*
                            addressField.setAttribute("autocomplete", "off");
                            addressField.setAttribute("autocomplete", "new-password");
                            break;
                        default:
                            if (addressField.id == "streetAddress") {
                                this.showAddressFields = true;
                                this.addressField1.classList.remove("has-icon");
                            }

                            // For some reason we have to explicitly tell FireFox to set it to "off" which doesn't actually disable autofill
                            // then set it to "new-password" which does disable it for both WebKit and FireFox. ¯\_(ツ)_/¯
                            addressField.setAttribute("autocomplete", "off");
                            addressField.setAttribute("autocomplete", "new-password");
                    }
                });
            }
        },
        findAddressComponentByType(place, componentName, componentLength) {
            const component = place.address_components.find((component) =>
                component.types.find((type) => type == componentName)
            );
            if (component) {
                return component[componentLength] ?? "";
            } else {
                return "";
            }
        },
        fillInAddress(place) {
            if (!place) {
                place = this.autocomplete.getPlace();
            }

            const selectedItem = document.querySelector(".pac-item-selected");
            if (!place && !selectedItem) {
                this.fillInAddressUsingFirstItem();
                return;
            }

            if (place && place.address_components) {
                this.matchFound = true;
                const self = this;

                this.$nextTick(function () {
                    self.showAddressFields = true;

                    const streetNumber = this.findAddressComponentByType(
                        place,
                        "street_number",
                        "long_name"
                    );
                    const route = this.findAddressComponentByType(place, "route", "short_name");
                    const city = this.findAddressComponentByType(place, "locality", "long_name");

                    let state = "";
                    let zipCode = "";

                    // If city has an exact match, fill in the state from the autocomplete.
                    if (city != "") {
                        state = this.findAddressComponentByType(
                            place,
                            "administrative_area_level_1",
                            "short_name"
                        );
                    }

                    // If state has an exact match, fill in the zipCode from the autocomplete.
                    if (state != "") {
                        zipCode = this.findAddressComponentByType(
                            place,
                            "postal_code",
                            "long_name"
                        );
                    }

                    const isAddressComplete =
                        (streetNumber ?? "") !== "" &&
                        (route ?? "") !== "" &&
                        (city ?? "") !== "" &&
                        (state ?? "") !== "" &&
                        (zipCode ?? "") !== "";

                    self.addressModel.streetAddress =
                        streetNumber && route ? `${streetNumber} ${route}` : `${route}`;
                    self.addressModel.city = city;
                    self.addressModel.state = state;
                    self.addressModel.zipCode = zipCode;

                    this.$refs.autocomplete.handleChange(streetNumber);
                    this.$refs.apartmentNumberOrBusinessName.handleChange(route);
                    this.$refs.city.handleChange(city);
                    this.$refs.state.handleChange(state);
                    this.$refs.zipCode.handleChange(zipCode);

                    if (!this.displayVerificationWarning) {
                        this.displayVerificationWarning = place.partial_match || !isAddressComplete;
                    }

                    // After filling in the address fields, disable the address autocomplete
                    this.unloadAutocomplete();
                });
            }
        },
        fillInAddressUsingFirstItem() {
            // Fill-in the address using first item in the list.
            const item = document.querySelector(".pac-container .pac-item > .pac-item-query");
            const item2 = document.querySelector(".pac-container .pac-item > span:nth-child(3)");

            if (item != null) {
                let firstResult = `${item.textContent}, ${item2.textContent}`;

                const geocoder = new window.google.maps.Geocoder();
                const self = this;
                geocoder.geocode(
                    {
                        address: firstResult,
                    },
                    function (results, status) {
                        if (status === window.google.maps.GeocoderStatus.OK) {
                            self.displayVerificationWarning = true;
                            self.fillInAddress(results[0]);
                        }
                    }
                );
            } else {
                this.matchFound = false;
            }
        },
        resetAlerts() {
            this.displayVerificationWarning = false;
        },
        unloadAutocomplete() {
            if (this.autocompleteListener && this.autocomplete) {
                window.google.maps.event.removeListener(this.autocompleteListener);
                this.autocompleteListener = null;

                window.google.maps.event.clearInstanceListeners(this.autocomplete);
                this.autocomplete = null;

                this.addressField1.onchange = null;

                const pacContainer = document.querySelector(".pac-container");
                if (pacContainer) {
                    pacContainer.remove();
                }
            }
        },
    },
    mounted() {
        //If we already have a full address, show it
        this.showAddressFields =
            (this.addressModel.streetAddress ?? "") !== "" &&
            (this.addressModel.city ?? "") !== "" &&
            (this.addressModel.state ?? "") !== "" &&
            (this.addressModel.zipCode ?? "") !== "";

        if (!this.showAddressFields) {
            this.loadGooglePlacesAutocompleteScript();
        }
    },
    beforeUpdate() {
        // It is necessary to set focus on the street address on this lifecycle hook when this component is used in a modal.
        if (!this.addressField1.matches(":focus")) {
            this.addressField1.focus();
        }
    },
    unmounted() {
        this.unloadAutocomplete();
    },
    watch: {
        matchFound: {
            handler(newValue) {
                if (newValue === null) {
                    this.displayNoMatchWarning = false;
                    this.displayVerificationWarning = false;
                    this.unwatchAddress();
                    return;
                }

                if (!newValue) {
                    this.displayNoMatchWarning = true;

                    this.addressModel.city = "";
                    if (!this.preserveCityAndStateOnReset) {
                        this.addressModel.state = "";
                        this.addressModel.zipCode = "";
                    }

                    this.displayVerificationWarning = false;

                    // Only deep watch the Address Model after a failed match
                    this.unwatchAddress = this.$watch(
                        "addressModel",
                        () => {
                            // When the address model changes reset to "no attempted match"
                            this.matchFound = null;
                            this.$nextTick();
                        },
                        { deep: true, flush: "post" }
                    );
                }
            },
            deep: true,
        },
    },
    components: {
        textboxQuestion,
        dropdownQuestion,
        alert,
    },
};
</script>

<style lang="scss">
.address-questions {
    margin-top: 0.5rem;
}

#streetAddressField {
    position: relative;

    .pac-container {
        top: 76px !important; // Height of #streetAddressField
        left: 0 !important;
    }
}
</style>
