<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(),
                onLoadedEvent: noop,
                onSourceRequestHandleEvent: noop,
                options: {},
                // FIXED: 定义为 undefined，避免在 Hook 中通过计算属性取值是出现两次变化的问题
                loaded: undefined,

                // 以下是一些 mapbox 的方法所需参数，方便传递到 renderjs 中
                events: {},
                eventOptions: undefined,
                addSourceOptions: undefined,
                addLayerOptions: undefined,
                removeOptions: undefined,
                setGeoJSONSourceForRequestOptions: undefined,
                setGeoJSONSourceDataOptions: undefined,
                setVectorTileSourceTilesOptions: undefined,
                setFilterOptions: undefined,
                setPaintPropertyOptions: undefined,
                setLayoutPropertyOptions: undefined,
                flyToOptions: undefined,

                // change options 锁，结合 event，配合数组实现先进先出队列控制
                changeLock: false,
                changeOptionsQueue: [],
            }
        },
        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.onLoadedEvent?.(data)
            },
            onSourceRequestHandle(data) {
                console.log('✨ Request Handle', data)
                // 触发 onSourceRequestHandle 事件
                this.onSourceRequestHandleEvent?.(data)
            },
            // 尝试触发事件, 通过 changeLock 控制
            tryTriggerChange() {
                if (!this.changeLock && this.changeOptionsQueue.length) {
                    const item = this.changeOptionsQueue.shift()
                    this[`${item.fn}Options`] = item
                    this.changeLock = true
                }
            },
            // 监听地图配置从逻辑层到视图层的响应式变化事件
            onMapOptionsChangeEvent(event) {
                console.log('✨ Map Event', event)

                this.changeLock = false
                this.tryTriggerChange()
            },
            setConfig(config) {
                this.options = {
                    container: this.id,
                    style: config?.style,
                    options: config?.options,
                    attribution: config?.attribution,
                }

                this.onLoadedEvent = config?.onLoaded || noop
                this.onSourceRequestHandleEvent = config?.onSourceRequestHandle || noop
            },

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

                this.$nextTick(() => {
                    // 尝试触发 event 事件
                    this.tryTriggerChange()
                })
                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.changeOptionsQueue.push({
                    fn: 'addSource',
                    id,
                    source,
                })

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },
            setGeoJSONSourceForRequest(id, url, handler, filters) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'setGeoJSONSourceForRequest',
                    id,
                    url,
                    handler: handler.toString(),
                    filters,
                })

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },
            setVectorTileSourceTiles(id, tiles) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'setVectorTileSourceTiles',
                    id,
                    tiles,
                })

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },

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

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },

            // 7. flyTo 方法
            flyTo(options) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'flyTo',
                    ...options,
                })

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },
        },
    }
</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>
