<template>
    <div class="address-questions">
        <div class="row mb-4" aria-live="polite">
            <div class="col">
                <textboxQuestion
                    id="streetAddressField"
                    cmsWidgetName="StreetAddressQuestionWidget"
                    v-model="addressModel.streetAddress"
                    ref="autocomplete"
                    customInputId="autocomplete"
                    placeholderText="Search"
                    aria-haspopup=""
                    hasIcon
                    validationRules="street-address-required"
                    @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" />
                </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" />
                </div>
                <div class="col">
                    <textboxQuestion
                        customInputId="zipCode"
                        cmsWidgetName="ZipQuestionWidget"
                        v-model="addressModel.zipCode"
                        ref="zipCode"
                        mask="#####"
                        validationRules="zip-code-required|zip-code-format" />
                </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("autocomplete");
            },
        },
    },
    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: ["geocode"],
            });

            // 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 focus,
            // append the search results list container to the bottom of the textbox
            // and disable browser autofill
            this.addressField1.addEventListener("focus", (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);
                }

                // Unfortunately this is the only place we can set the autocomplete attribute without the
                // Google Places object resetting it to "off" which does nothing to prevent browser autofill
                this.addressField1.setAttribute("autocomplete", "do-not-autofill");
            });

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

                const event = new Event("place_changed");

                // When either of the two enter keys or the tab key are pressed
                if (e.code === "Enter" || e.code === "NumpadEnter" || e.code === "Tab") {
                    // Grab the selected item
                    const selectedItem = document.querySelector(
                        ".pac-container .pac-item-selected"
                    );

                    if (selectedItem !== null) {
                        // If an item was selected then fill in the address with the selected item
                        // by triggering the "place_changed" event of the Autocomplete object
                        this.autocomplete.dispatchEvent(event);
                    } else {
                        // Otherwise fill-in the address using first item from the list.
                        this.fillInAddressUsingFirstItem();
                    }
                } else {
                    return;
                }
            });

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

                // Get the address that the user clicked on (if any)
                const clickedAddress = document.querySelector(".pac-container .pac-item:hover");

                // If the Street Address field changed without clicking (i.e. by pressing Tab, or clicking outside the field)
                if (clickedAddress === null) {
                    // Fill-in the address using first item in the list.
                    this.fillInAddressUsingFirstItem();
                }
            });
        },
        fillInAddress(place) {
            if (!place) {
                place = this.autocomplete.getPlace();
            }

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

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

                    for (const component of place.address_components) {
                        const componentType = component.types[0];

                        switch (componentType) {
                            case "street_number": {
                                self.addressModel.streetAddress = component.long_name;
                                break;
                            }
                            case "route": {
                                self.addressModel.streetAddress += " " + component.short_name;
                                break;
                            }
                            case "locality": {
                                self.addressModel.city = component.long_name;
                                break;
                            }
                            case "administrative_area_level_1": {
                                self.addressModel.state = component.short_name;
                                break;
                            }
                            case "postal_code": {
                                self.addressModel.zipCode = component.long_name;
                                break;
                            }
                        }
                    }

                    // After filling in the address fields, disable the address autocomplete
                    this.unloadAutocomplete();

                    // Restore focus to the first address field
                    this.addressField1.focus();
                });
            }
        },
        fillInAddressUsingFirstItem() {
            // Fill-in the address using first item in the list.
            const item = document.querySelector(".pac-container .pac-item");
            if (item != null) {
                const firstResult = item.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.fillInAddress(results[0]);
                            self.displayVerificationWarning = true;
                        }
                    }
                );
            } 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();
        }
    },
    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" }
                    );
                }
            },
        },
    },
    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>
