<script>
    import { nanoid } from 'nanoid'
    import { getLocation } from './hook'
    import { useUserStoreWithOut } from '@/store/modules/user'
    import { getRegionConfig } from '@/components/Map/Mapbox/regions'

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

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

                // change options 锁，结合 event，配合数组实现先进先出队列控制
                changeLock: false,
                changeOptionsQueue: [],
            }
        },
        computed: {
            isReady() {
                return this.loaded
            },
        },
        created() {
            Message.loading()
        },
        mounted() {
            this.$emit('register', this)
            this.timeId = setTimeout(() => {
                if (!this.loaded) {
                    Message.hideLoading()
                    Message.toast('地图加载超时')
                }
            }, 30 * 1000)
        },
        unmounted() {
            this.timeId && clearTimeout(this.timeId)
        },
        methods: {
            onMapLoad(data) {
                this.loaded = true
                Message.hideLoading()
                // console.debug('✨ Map Loaded', data)

                // 触发 onLoaded 事件
                this.onLoadedEvent?.(data)
            },
            onSourceRequestHandle(data) {
                // console.debug('✅ Request Success Handle', data)
                // 触发 onSourceRequestHandle 事件
                this.onSourceRequestHandleEvent?.(data)
            },
            async onSourceRequestErrorHandle(data) {
                console.debug('❌ Request Error Handle', data)
                // 触发 onSourceRequestErrorHandle 事件
                this.onSourceRequestErrorHandleEvent?.(data)

                // 检查请求错误的原因
                if (data) {
                    try {
                        const { status, message } = JSON.parse(data)
                        if (status === 401) {
                            // 跳转到登录页
                            const userStore = useUserStoreWithOut()
                            await userStore.logout()
                            Message.toast('登录信息过期,请重新登录!')
                        } else {
                            Message.toast(message)
                        }
                    } catch (e) {
                        console.error(e)
                        Message.toast(data)
                    }
                }
            },
            // 监听地图事件(on,custom...)
            onEventHandle(event) {
                console.debug('📢 Map Event', event)

                if (this.events[`on-${event.type}-${event.layer}`]) {
                    this.events[`on-${event.type}-${event.layer}`]?.(event)
                } else {
                    // 触发 onMapEvent 事件
                    this.onMapEvent?.(event)
                }
            },
            // 尝试触发事件, 通过 changeLock 控制
            tryTriggerChange() {
                if (!this.changeLock && this.changeOptionsQueue.length) {
                    this.changeLock = true
                    const item = this.changeOptionsQueue.shift()
                    this[`${item.fn}Options`] = item
                }
            },
            // 监听地图配置从逻辑层到视图层的响应式变化事件
            onMapOptionsChangeEvent(event) {
                // console.debug('🔊 Map Options Change Event', event)
                // 必须重置Options （在app真机环境下存在兼容问题）
                this[`${event.type}Options`] = undefined
                this.changeLock = false
                this.tryTriggerChange()
            },
            /**
             * 解析区域编码获得区域配置
             * @param {string} code 区域编码
             */
            analysisRegion(code) {
                const region = code
                const regionConfig = getRegionConfig(region)
                return {
                    region,
                    regionConfig,
                }
            },
            // 设置地图区域编码，用于覆盖遮罩层
            setRegion(value, flyTo = true) {
                const { region, regionConfig } = this.analysisRegion(value)
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'region',
                    region,
                    regionConfig,
                    flyTo,
                })

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },
            // 设置地图配置
            setConfig(config) {
                const { region, regionConfig } = this.analysisRegion(config?.region)

                this.options = {
                    container: this.id,
                    style: config?.style,
                    options: config?.options,
                    attribution: config?.attribution,
                    extra: config?.extra,
                    region,
                    regionConfig,
                    autoFlyToRegionCenter: config?.autoFlyToRegionCenter,
                }

                this.onMapEvent = config?.onMapEvent || noop
                this.onLoadedEvent = config?.onLoaded || noop
                this.onSourceRequestHandleEvent = config?.onSourceRequestHandle || noop
                this.onSourceRequestErrorHandleEvent = config?.onSourceRequestErrorHandle || 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: 'remove',
                    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()
            },
            updateRasterImage(id, url) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'updateRasterImage',
                    id,
                    url,
                })

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

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

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },
            // 4. setFilter 方法
            setFilter(layerId, filter) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'setFilter',
                    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()
            },

            fitBounds(bounds, options) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'fitBounds',
                    bounds,
                    options,
                })

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

            addMarker(id, lngLat, popup, popupDefaultOpen, imageUrl, iconSize) {
                // 加入到 change 事件队列中
                this.changeOptionsQueue.push({
                    fn: 'addMarker',
                    id,
                    lngLat,
                    popup,
                    popupDefaultOpen,
                    imageUrl,
                    iconSize,
                })

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

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

                // 尝试触发 change 事件
                this.tryTriggerChange()
            },
            // 获取当前位置
            getCurrentPosition() {
                getLocation().then((res) => {
                    // 更新定位
                    this.position = {
                        coords: res,
                        _: Date.now(),
                    }
                })
            },
        },
    }
</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"
        :updateRasterImageOptions="updateRasterImageOptions"
        :change:updateRasterImageOptions="mapbox.changeUpdateRasterImageOptions"
        :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"
        :regionOptions="regionOptions"
        :change:regionOptions="mapbox.changeRegionOptions"
        :addMarkerOptions="addMarkerOptions"
        :change:addMarkerOptions="mapbox.changeAddMarkerOptions"
        :fitBoundsOptions="fitBoundsOptions"
        :change:fitBoundsOptions="mapbox.changeFitBoundsOptions"
    />
    <!-- #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>
