<template>
    <div class="ng-mapy-cz-wrapper w-100">
        <div id="mapy-cz-area" :style="{ height: height + '%' }"></div>
    </div>
</template>

<script>
const mapyCzLoaderUrl = 'https://api.mapy.cz/loader.js'

export default {
    name: 'NgMapyCz',
    props: {
        height: {
            type: Number,
            default: 100,
        },
        lang: {
            type: String,
            default: 'cs',
        },
        poi: {
            type: Boolean,
            default: true,
        },
        maxSuggestItems: {
            type: Number,
            default: 10,
        },
        nearPointerCount: {
            type: Number,
            default: 3,
        },
        center: {
            type: Object,
            default: function () {
                return {}
            },
        },
        item: {
            type: Object,
            default: function () {
                return {}
            },
        },
        markers: {
            type: Array,
            default: function () {
                return []
            },
        },
        markerIconBase64: {
            type: String,
            default: '',
        },
    },
    data: function () {
        return {
            map: null,
            smap: null,
            markerLayer: null,
            pointers: [],
            clusterer: null,
            suggest: null,
            currentSuggestItems: [],
        }
    },
    watch: {
        /**
         * Watch when item is selected (from parent)
         */
        item: function () {
            this.handleItemChanged(false)
        },

        /**
         * Watch center prop changed
         */
        center: function () {
            if (!this.center) {
                return
            }

            const coords = this.transformCoords(this.center.latitude, this.center.longitude)
            this.map.setCenter(coords, true)
        },
    },
    mounted() {
        /**
         * Load external "Loader" script into page
         */
        const mapyCzScriptEl = document.createElement('script')
        mapyCzScriptEl.setAttribute('src', mapyCzLoaderUrl)
        mapyCzScriptEl.onload = () => {
            /**
             * Configure "Loader"
             */
            window.Loader.lang = this.lang
            window.Loader.async = true
            window.Loader.load(null, { poi: this.poi, suggest: true }, async () => {
                /**
                 * Define SMap (static) object
                 */
                this.smap = window.SMap

                /**
                 * Configure SMap instance
                 */
                await this.configureMap()

                /**
                 * Initialize first loaded (default) markers
                 */
                this.initMarkers()

                this.handleMapMarkerPointers(this.pointers, this.map)

                this.setFirstLoadMarker()
            })
        }
        document.head.appendChild(mapyCzScriptEl)
    },
    methods: {
        async configureMap() {
            /**
             * Transform viewport center coords
             */
            const center = this.transformCoords(this.center.latitude, this.center.longitude)

            /**
             * Define SMap instance
             */
            this.map = new window.SMap(window.JAK.gel('mapy-cz-area'), center, 11)
            this.map.addDefaultLayer(this.smap.DEF_SMART_BASE).enable()

            /**
             * Define SMap controls
             */
            this.configureMapControls()

            /**
             * Configure SMap marker layer and clustering
             */
            this.configureMapMarkersLayer()

            /**
             * Override default SuggestProvider class with own and configure suggesting
             */
            await this.configureMapSuggestion()

            /**
             * Configure "near markers" pointers
             */
            this.configureMapMarkerPointers()

            this.$emit('loaded')
        },

        configureMapControls() {
            this.map.addControl(
                new this.smap.Control.Mouse(this.smap.MOUSE_PAN | this.smap.MOUSE_WHEEL | this.smap.MOUSE_ZOOM)
            )
            this.map.addControl(new this.smap.Control.Zoom({}, { showZoomMenu: false }))
        },

        configureMapMarkersLayer() {
            this.clusterer = new this.smap.Marker.Clusterer(this.map, 30)

            this.markerLayer = new this.smap.Layer.Marker()
            this.map.addLayer(this.markerLayer)
            this.markerLayer.setClusterer(this.clusterer)
            this.markerLayer.setReposition()
            this.markerLayer.enable()
        },

        async configureMapSuggestion() {
            const searchInput = window.JAK.gel('ng-pickup-point-search-input')

            const { default: NgSuggestProvider } = await import('./suggestProvider')
            this.suggest = new this.smap.Suggest(searchInput, {
                provider: new NgSuggestProvider({
                    updateParams: (params) => {
                        params.type = 'municipality|street|address|region'
                        params.lang = this.lang
                        params.count = this.maxSuggestItems
                        params.limit = this.maxSuggestItems
                    },
                }),
            })

            this.currentSuggestItems = []
            this.suggest.addListener('request-items', (items) => {
                this.currentSuggestItems = items
            })
            this.suggest.addListener('suggest', (item) => {
                this.handleSuggestItemClick(item.data)
            })
            this.suggest.addListener('enter', () => {
                if (this.currentSuggestItems.length === 0) {
                    return
                }

                this.handleSuggestItemClick(this.currentSuggestItems[0])
            })

            this.suggest.send()
        },

        configureMapMarkerPointers() {
            for (let i = 0; i < this.nearPointerCount; i++) {
                const pointer = new this.smap.Control.Pointer({
                    type: this.smap.Control.Pointer.TYPES.RED,
                    showDistance: true,
                    snapHUDtoScreen: 20,
                })

                /**
                 * Call event when click on pointer
                 */
                pointer.addListener(
                    'pointer-click',
                    () => {
                        const coords = this.transformCoords(pointer._center.y, pointer._center.x)
                        this.map.setCenter(coords, true)

                        /**
                         * Pointer to marker
                         */
                        const selectedItem = this.markers.find((marker) => {
                            const markerCoords = this.transformCoords(
                                marker.coordinates.latitude,
                                marker.coordinates.longitude
                            )
                            return `${markerCoords.y}|${markerCoords.x}` === `${coords.y}|${coords.x}`
                        })

                        this.$emit('marker-selected', selectedItem)
                    },
                    pointer
                )

                this.map.addControl(pointer)
                this.pointers.push(pointer)
            }

            /**
             * Call event when map is updated
             */
            this.map.getSignals().addListener(this, 'map-pan', (e) => {
                const map = e.target

                this.handleMapMarkerPointers(this.pointers, map)
            })
            this.map.getSignals().addListener(this, 'map-redraw', (e) => {
                const map = e.target

                this.handleMapMarkerPointers(this.pointers, map)
            })
        },

        handleMapMarkerPointers(pointers, map) {
            const center = map.getCenter()
            const markers = this.markerLayer.getMarkers()

            /**
             * Get markers in current viewport
             */
            const markersInViewport = markers.filter((marker) => {
                return marker.getCoords().inMap(this.map, true)
            })

            if (markersInViewport.length <= this.nearPointerCount) {
                const nearMarkers = this.sortMarkersByDistanceFromCenter(markers, center, this.nearPointerCount)

                /**
                 * Add pointers if viewport not contain markers
                 */
                nearMarkers.forEach((nearMarker, index) => {
                    const pointer = pointers[index]

                    pointer.setCoords(nearMarker.getCoords())
                })

                return
            }

            /**
             * Remove pointers if viewport contains some markers
             */
            pointers.forEach((pointer) => {
                pointer.setCoords()
            })
        },

        /**
         * Convert classic lat xx.xxxx, long xx.xxxx coords to SMap.Coords instance
         * @param {float} lat
         * @param {float} lon
         * @returns SMap.Coords
         */
        transformCoords(lat, lon) {
            return this.smap.Coords.fromWGS84(lon, lat)
        },

        /**
         * Fill map with markers
         */
        initMarkers() {
            /**
             * Add markers to map
             */
            this.addMarkers(this.markers)

            /**
             * Get markers from map
             */
            const markers = this.markerLayer.getMarkers()

            /**
             * Call event when click on marker
             */
            this.map.getSignals().addListener(this, 'marker-click', (e) => {
                let id = e.target.getId()

                const clickedMarker = markers.find((marker) => {
                    return marker.getId() === id
                })

                if (!clickedMarker) {
                    return
                }

                /**
                 * Convert marker to "item"
                 */
                const selectedItem = this.markers.find((item) => {
                    return item.code === clickedMarker.getId()
                })

                this.$emit('marker-selected', selectedItem)
            })
        },

        /**
         * Create marker instance
         * @param item
         */
        createMarker(item) {
            const markerOptions = {}

            if (this.markerIconBase64) {
                const markerWrapperEl = window.JAK.mel('div')
                markerWrapperEl.classList.add('ng-point')
                const markerIconEl = window.JAK.mel('img', { src: this.markerIconBase64 })
                markerWrapperEl.appendChild(markerIconEl)

                markerOptions.url = markerWrapperEl
            }

            markerOptions.title = item.name

            const coords = this.transformCoords(item.coordinates.latitude, item.coordinates.longitude)
            return new this.smap.Marker(coords, item.code, markerOptions)
        },

        /**
         * Add set of markers
         * @param items
         */
        addMarkers(items) {
            const markers = []
            items.forEach((item) => {
                markers.push(this.createMarker(item))
            })

            this.markerLayer.addMarker(markers)
        },

        handleItemChanged(firstLoad) {
            const markers = this.markerLayer.getMarkers()

            /**
             * Convert item to marker
             */
            const selectedMarker = markers.find((marker) => {
                return marker.getId() === this.item.code
            })

            if (!selectedMarker) {
                return
            }

            /**
             * Remove active state on all markers
             */
            markers.forEach((worker) => {
                worker._dom.active.classList.remove('active')
            })

            /**
             * Add active state on selected marker
             */
            selectedMarker._dom.active.classList.add('active')

            const coords = selectedMarker.getCoords()

            /**
             * Check first-time item selection
             */
            if (firstLoad) {
                this.map.setCenterZoom(coords, 14, true)
                return
            }

            this.map.setCenter(coords, true)
        },

        handleSuggestItemClick(item) {
            this.$emit('searched', {
                latitude: item.latitude,
                longitude: item.longitude,
            })

            /**
             * Calc street/city/... area and get dynamic zoom value of it
             */
            const bboxCoords = item.originalData.bbox
            const bboxACoords = this.transformCoords(bboxCoords[0], bboxCoords[1])
            const bboxBCoords = this.transformCoords(bboxCoords[2], bboxCoords[3])
            const centerCoordsZoom = this.map.computeCenterZoom([bboxACoords, bboxBCoords], true)

            const center = this.transformCoords(centerCoordsZoom[0].y, centerCoordsZoom[0].x)
            const zoom = centerCoordsZoom[1] + 1

            this.map.setCenterZoom(center, zoom, true)
        },

        setFirstLoadMarker() {
            this.handleItemChanged(true)
        },

        /**
         * Sort markers from nearest to defined center
         * @param {[]} markers
         * @param center
         * @param {number} limit
         * @returns {[]}
         */
        sortMarkersByDistanceFromCenter(markers, center, limit) {
            markers.sort((a, b) => {
                return a.getCoords().distance(center) - b.getCoords().distance(center)
            })

            return markers.slice(0, limit)
        },

        handleSearchBtnClick() {
            if (this.currentSuggestItems.length === 0) {
                return
            }

            this.handleSuggestItemClick(this.currentSuggestItems[0])
        },
    },
}
</script>

<style lang="scss" scoped>
// stylelint-disable
::v-deep *,
::v-deep *:before,
::v-deep *:after {
    box-sizing: content-box;
}

::v-deep .smap {
    .cluster {
        > div {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 35px !important;
            height: 35px !important;
            color: #fff !important;
            background-color: var(--primary) !important;
            border: 3px solid rgba(0, 0, 0, 0.3) !important;

            > img {
                display: none !important;
            }
        }
    }
}
</style>

<style lang="scss">
.smap .ng-point {
    cursor: pointer;
    opacity: 0.5;

    &.active {
        opacity: 1;
        animation: bounce 0.5s infinite alternate;
    }
}

.smap-suggest {
    width: 100% !important;

    .item {
        &:hover {
            background: #f8f9fa !important;
        }

        .image {
            display: none !important;
        }

        .text {
            padding: 0.5rem 1rem !important;

            strong,
            em {
                font-size: 1rem !important;
            }

            em {
                color: var(--secondary) !important;
            }
        }
    }
}

@keyframes bounce {
    from {
        transform: translateY(0px);
    }
    to {
        transform: translateY(-5px);
    }
}
</style>
