<template>
    <div>
        <div id="map" />
    </div>
</template>

<script>
export default {
    name: 'google-map',
    props: {
        markers: Array,
        zipCode: String
    },
    data() {
        return {
            country: 'USA',
            geocoder: null,
            infoWindow: null
        };
    },
    computed: {
        zipCodeAddress() {
            return `${this.zipCode ?? ''} ${this.country}`;
        }
    },
    watch: {
        async markers(newMarkers) {
            await this.createMapWithMarkersForAddresses(newMarkers);
        }
    },
    async beforeMount() {
        await this.createMapWithMarkersForAddresses(this.markers);
    },
    methods: {
        async getMap(center) {
            const { Map } = await window.google.maps.importLibrary('maps');
            return new Map(document.getElementById('map'), {
                center,
                mapId: 'map_id',
                mapTypeControl: false
            });
        },
        getBounds(locations) {
            const bounds = new window.google.maps.LatLngBounds();
            locations?.forEach((location) => bounds.extend(location));
            return bounds;
        },
        async setGeocoder() {
            if (this.geocoder == null) {
                const { Geocoder } = await window.google.maps.importLibrary('geocoding');
                this.geocoder = new Geocoder();
            }
        },
        async addMarkersToMap(map, markers) {
            const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
            markers?.forEach((marker, index) => {
                const pinElement = new PinElement({
                    glyphText: String.fromCharCode('A'.charCodeAt(0) + index),
                    glyphColor: '#000000',
                    borderColor: '#000000'
                });
                const markerElement = new AdvancedMarkerElement({
                    map,
                    position: marker.position,
                    title: marker.title,
                    content: pinElement.element,
                    zIndex: 1000 - index
                });
                this.createMarkerInfoWindows(markerElement, marker);
            });
        },
        createMarkerInfoWindows(markerElement, marker) {
            const directionsUrl = this.createGetDirectionsUrl(marker);
            const content = `<div>
                                <span style="color: black; font-weight:bold"> ${marker.title} </span>
                                ${marker.addressLines?.map((address) => `<br/> ${address} `).join()}
                                <br /> <a href="${directionsUrl}" target="_blank">Get directions</a>
                             </div>`;

            const infoWindow = new window.google.maps.InfoWindow({ content });

            markerElement.addListener('click', () => {
                this.infoWindow?.close();
                this.infoWindow = infoWindow;
                infoWindow.open(markerElement.map, markerElement);
            });
        },
        createGetDirectionsUrl(marker) {
            return `https://maps.google.com/maps?saddr=&daddr=${encodeURIComponent(marker.fullAddress)}`;
        },
        async getLocationsFromAddresses(markers) {
            await this.setGeocoder();

            const geocodeShopAddressPromises = markers?.map((marker) => this.geocoder.geocode({ address: marker.fullAddress })) ?? [];
            const geocodeShopAddressResults = await Promise.all(geocodeShopAddressPromises);

            return markers?.map((marker, index) => {
                const results = geocodeShopAddressResults[index]?.results;
                let position = null;
                if (results && results.length > 0) {
                    position = results[0].geometry?.location;
                }

                return { ...marker, position };
            }).filter((marker) => marker.position != null) ?? [];
        },
        async getBoundsFromAddress(address) {
            await this.setGeocoder();
            const result = await this.geocoder.geocode({ address });
            if (result?.results?.length > 0) {
                return result.results[0].geometry?.bounds ?? null;
            } else {
                return null;
            }
        },
        async createMapWithMarkersForAddresses(markers) {
            const markerPositions = await this.getLocationsFromAddresses(markers);
            const zipBounds = await this.getBoundsFromAddress(this.zipCodeAddress);
            const map = await this.getMap(zipBounds?.getCenter());

            await this.addMarkersToMap(map, markerPositions);
            let positionsToDisplay = markerPositions.map((marker) => marker.position);
            if(zipBounds) {
                positionsToDisplay.push(zipBounds.getNorthEast());
                positionsToDisplay.push(zipBounds.getSouthWest());
            }

            const bounds = this.getBounds(positionsToDisplay);
            map.fitBounds(bounds);
        }
    }
};
</script>

<style lang="scss" scoped>

#map {
    height: 250px;
    width: 100%;
}
</style>
