<script>
    import { nanoid } from 'nanoid'

    // FIXED: 重要说明
    // renderjs 组件暂不支持 setup 组件写法，对 ts 支持也不友好，这里的写法需要参考 vue2 的写法

    // 总共分为两块 script
    // 1. 逻辑层 Script
    // 2. 视图层 Script
    // 通信方式
    // 1. 通过在逻辑层 data 中设置的属性向视图层传递数据
    // 2. 通过在逻辑层 methods 定义方法，在视图层调用传递参数（注意：只能传递可进行 JSON 序列化的数据）

    function noop() {}

    export default {
        emits: ['register'],
        data() {
            return {
                id: nanoid(),
                onLoaded: noop,
                options: {},
                // FIXED: 定义为 undefined，避免在 Hook 中通过计算属性取值是出现两次变化的问题
                loaded: undefined,

                // 以下是一些 mapbox 的方法所需参数，方便传递到 renderjs 中
                events: {},
                eventOptions: undefined,
                eventOptionsQueue: [],
                addSourceOptions: undefined,
                addSourceOptionsQueue: [],
                addLayerOptions: undefined,
                addLayerOptionsQueue: [],
                removeOptions: undefined,
                removeOptionsQueue: [],
                setGeoJSONSourceForRequestOptions: undefined,
                setGeoJSONSourceForRequestOptionsQueue: [],
                setGeoJSONSourceDataOptions: undefined,
                setGeoJSONSourceDataOptionsQueue: [],
                setVectorTileSourceTilesOptions: undefined,
                setVectorTileSourceTilesOptionsQueue: [],
                setFilterOptions: undefined,
                setFilterOptionsQueue: [],
                setPaintPropertyOptions: undefined,
                setPaintPropertyOptionsQueue: [],
                setLayoutPropertyOptions: undefined,
                setLayoutPropertyOptionsQueue: [],
                flyToOptions: undefined,
                flyToOptionsQueue: [],

                // change options 锁，结合 event，配合数组实现先进先出队列控制
                changeLock: {},
            }
        },
        computed: {
            isReady() {
                return this.loaded
            },
        },
        created() {
            Message.loading()
        },
        mounted() {
            this.$emit('register', this)
        },
        methods: {
            onMapLoad(data) {
                this.loaded = true
                Message.hideLoading()
                console.log('✨ Map Loaded', data)

                // 触发 onLoaded 事件
                this.onLoaded?.(this, data)
            },
            // 尝试触发事件, 通过 changeLock 控制
            tryChange(event) {
                if (this[`${event.type}OptionsQueue`]?.length && !this.changeLock[event.type]) {
                    this.changeLock[event.type] = true
                    this[`${event.type}Options`] = this[`${event.type}OptionsQueue`].shift()
                }
            },
            // 监听地图配置从逻辑层到视图层的响应式变化事件
            onMapOptionsChangeEvent(event) {
                console.log('✨ Map Event', event)

                this.changeLock[event.type] = false
                this.tryChange(event)
            },
            setConfig(config) {
                this.options = {
                    container: this.id,
                    style: config?.style,
                    options: config?.options,
                    attribution: config?.attribution,
                }

                this.onLoaded = config?.onLoaded || noop
            },

            // 重新实现 mapbox 的一些方法，方便传递到 renderjs 中
            customEvent(event, type, layer, ...args) {
                // 加入到 event 事件队列中
                this.eventOptionsQueue.push({
                    event,
                    type,
                    layer,
                    ...args,
                })

                // 尝试触发 event 事件
                this.tryChange({ type: 'event' })
                return `${event}-${type}-${layer}`
            },

            // 1. on/once/off 事件监听处理
            on(type, layer, listener, popupTemplate) {
                const key = this.customEvent('on', type, layer, popupTemplate)
                this.events[key] = listener
            },
            onEvent(event) {
                const key = `on-${event.type}-${event.layer}`
                this.events[key]?.(event)
            },
            once(type, layer, listener) {
                const key = this.customEvent('once', type, layer)
                this.events[key] = listener
            },
            onceEvent(event) {
                const key = `once-${event.type}-${event.layer}`
                this.events[key]?.(event)
                delete this.events[key]
            },
            off(type, layer) {
                const key = this.customEvent('off', type, layer)
                delete this.events[key]
            },

            // 2. addSource/removeSource 方法
            addSource(id, source) {
                // 加入到 change 事件队列中
                this.addSourceOptionsQueue.push({
                    id,
                    source,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'addSource' })
            },
            removeSource(id) {
                // 加入到 change 事件队列中
                this.removeOptionsQueue.push({
                    id,
                    type: 'source',
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'remove' })
            },
            setGeoJSONSourceForRequest(id, url, handler) {
                // 加入到 change 事件队列中
                this.setGeoJSONSourceForRequestOptionsQueue.push({
                    id,
                    url,
                    handler: handler.toString(),
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'setGeoJSONSourceForRequest' })
            },
            setGeoJSONSourceData(id, data, filter) {
                // 加入到 change 事件队列中
                this.setGeoJSONSourceDataOptionsQueue.push({
                    id,
                    data,
                    filter,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'setGeoJSONSourceData' })
            },
            setVectorTileSourceTiles(id, tiles) {
                // 加入到 change 事件队列中
                this.setVectorTileSourceTilesOptionsQueue.push({
                    id,
                    tiles,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'setVectorTileSourceTiles' })
            },

            // 3. addLayer/removeLayer 方法
            addLayer(layer, beforeId) {
                // 加入到 change 事件队列中
                this.addLayerOptionsQueue.push({
                    layer,
                    beforeId,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'addLayer' })
            },
            removeLayer(id) {
                // 加入到 change 事件队列中
                this.removeOptionsQueue.push({
                    id,
                    type: 'layer',
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'remove' })
            },

            // 4. setFilter 方法
            setFilter(layerId, filter) {
                // 加入到 change 事件队列中
                this.setFilterOptionsQueue.push({
                    layerId,
                    filter,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'setFilter' })
            },

            // 5. setPaintProperty 方法
            setPaintProperty(layerId, paintProperty, value) {
                // 加入到 change 事件队列中
                this.setPaintPropertyOptionsQueue.push({
                    layerId,
                    paintProperty,
                    value,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'setPaintProperty' })
            },

            // 6. setLayoutProperty 方法
            setLayoutProperty(layerId, layoutProperty, value) {
                // 加入到 change 事件队列中
                this.setLayoutPropertyOptionsQueue.push({
                    layerId,
                    layoutProperty,
                    value,
                })

                // 尝试触发 change 事件
                this.tryChange({ type: 'setLayoutProperty' })
            },

            // 7. flyTo 方法
            flyTo(options) {
                // 加入到 change 事件队列中
                this.flyToOptionsQueue.push(options)

                // 尝试触发 change 事件
                this.tryChange({ type: 'flyTo' })
            },
        },
    }
</script>

<!-- renderjs 视图层模块 -->
<script module="mapbox" lang="renderjs" src="./mapbox.module.js"></script>

<template>
    <!-- #ifdef APP-PLUS || H5 -->
    <view
        class="map wrap"
        :id="id"
        :options="options"
        :change:options="mapbox.changeOptions"
        :eventOptions="eventOptions"
        :change:eventOptions="mapbox.changeEventOptions"
        :addSourceOptions="addSourceOptions"
        :change:addSourceOptions="mapbox.changeAddSourceOptions"
        :setGeoJSONSourceForRequestOptions="setGeoJSONSourceForRequestOptions"
        :change:setGeoJSONSourceForRequestOptions="mapbox.changeSetGeoJSONSourceForRequestOptions"
        :setGeoJSONSourceDataOptions="setGeoJSONSourceDataOptions"
        :change:setGeoJSONSourceDataOptions="mapbox.changeSetGeoJSONSourceDataOptions"
        :setVectorTileSourceTilesOptions="setVectorTileSourceTilesOptions"
        :change:setVectorTileSourceTilesOptions="mapbox.changeSetVectorTileSourceTilesOptions"
        :addLayerOptions="addLayerOptions"
        :change:addLayerOptions="mapbox.changeAddLayerOptions"
        :setFilterOptions="setFilterOptions"
        :change:setFilterOptions="mapbox.changeSetFilterOptions"
        :setPaintPropertyOptions="setPaintPropertyOptions"
        :change:setPaintPropertyOptions="mapbox.changeSetPaintPropertyOptions"
        :setLayoutPropertyOptions="setLayoutPropertyOptions"
        :change:setLayoutPropertyOptions="mapbox.changeSetLayoutPropertyOptions"
        :removeOptions="removeOptions"
        :change:removeOptions="mapbox.changeRemoveOptions"
        :flyToOptions="flyToOptions"
        :change:flyToOptions="mapbox.changeFlyToOptions"
    />
    <!-- #endif -->
    <!-- #ifndef APP-PLUS || H5 -->
    <view class="empty wrap">非 APP、H5 环境不支持</view>
    <!-- #endif -->
</template>

<style lang="less" scoped>
    .wrap {
        display: flex;
        width: 100%;
        height: 100%;

        /* #ifdef APP */
        height: 100vh;
        /* #endif */
    }

    .empty {
        justify-content: center;
        align-items: center;
    }
</style>
