// @unocss-ignore

import type { Ref } from 'vue'
import type mapboxgl from 'mapbox-gl'
import { toRaw } from 'vue'
import { appendScript, appendStylesheet } from '/@/utils/dom'

// Mapbox accessToken
export const accessToken = 'pk.eyJ1IjoiaWZ6bSIsImEiOiJjamswc3M1a2gwYWEwM3Zxa3ZsMjh3djkwIn0.h93-XtdRdtoC1KbVv_ZIow'

// 天地图密钥
export const tk = 'aa0ccd36f2dbb86dbb16cbf63f0034a6'

// 自定义图层 host 注意：renderjs里面不能引用带有 import.env 的任何文件，否则会编译报错
// const host = 'https://hntq.yiring.com/gis'
const host = 'https://foxgis.server.yiring.com'

// 构建天地图图层地址
export function buildTdtTileUrl(id: string, tk: string) {
    return `${host}/api/tdt/cache/${id}/{z}/{y}/{x}/${tk}?v=2022`
}

/**
 * 地图默认中心点
 */
export const DEFAULT_MAP_CENTER: [number, number] = [111.6, 26.270844]
/**
 * 地图默认缩放级别
 */
export const DEFAULT_MAP_ZOOM = 5.5

/**
 * 地图样式
 */
export const defaultStyle: mapboxgl.Style = {
    version: 8,
    zoom: DEFAULT_MAP_ZOOM,
    center: [...DEFAULT_MAP_CENTER],
    sprite: `${host}/api/sprites/hntq/sprite`,
    glyphs: `${host}/api/fonts/{fontstack}/{range}.pbf`,
    sources: {
        'wms-img_w-source': {
            type: 'raster',
            tiles: [buildTdtTileUrl('img', tk)],
            tileSize: 256,
            maxzoom: 18,
        },
        'wms-cia_w-source': {
            type: 'raster',
            tiles: [buildTdtTileUrl('cia', tk)],
            tileSize: 256,
            maxzoom: 18,
        },

        // ============ 省市县边界数据源 ============
        /** 全国 */
        '100000': {
            type: 'vector',
            url: `${host}/api/tilesets/100000/tilejson`,
        },
        /** 湖南省-城市聚合 */
        '430000.city': {
            type: 'vector',
            url: `${host}/api/tilesets/430000.city/tilejson`,
        },
        /** 湖南省-城市聚合名称 */
        '430000.city.label': {
            type: 'vector',
            url: `${host}/api/tilesets/430000.city.label/tilejson`,
        },
        /** 湖南省-县城聚合 */
        '430000.area': {
            type: 'vector',
            url: `${host}/api/tilesets/430000.area/tilejson`,
        },
        /** 湖南省-县城聚合名称 */
        '430000.area.label': {
            type: 'vector',
            url: `${host}/api/tilesets/430000.area.label/tilejson`,
        },
        /** 湖南省-乡镇聚合 */
        '430000.town': {
            type: 'vector',
            url: `${host}/api/tilesets/430000.town/tilejson`,
        },
        /** 湖南省-乡镇聚合名称 */
        '430000.town.label': {
            type: 'vector',
            url: `${host}/api/tilesets/430000.town.label/tilejson`,
        },

        // 聚合数据源
        '430000.combine': {
            type: 'vector',
            url: `${host}/api/tilesets/combine/tilejson`,
        },
    },
    layers: [
        // 默认图层 - 影像底图
        {
            id: 'wms-img_w-layer',
            type: 'raster',
            source: 'wms-img_w-source',
            layout: { visibility: 'none' },
        },
        // 默认图层 - 标注底图
        {
            id: 'wms-cia_w-layer',
            type: 'raster',
            source: 'wms-cia_w-source',
            layout: { visibility: 'none' },
        },
        // 占位图层
        {
            id: 'background',
            type: 'background',
            layout: { visibility: 'visible' },
            paint: { 'background-color': '#FFFFFF' },
        },
        // 天空图层
        {
            id: 'sky',
            type: 'sky',
            paint: {
                'sky-type': 'gradient',
                'sky-gradient': [
                    'interpolate',
                    ['linear'],
                    ['sky-radial-progress'],
                    0.8,
                    'rgba(135, 206, 235, 1.0)',
                    1,
                    'rgba(0,0,0,0.1)',
                ],
                'sky-gradient-center': [0, 0],
                'sky-gradient-radius': 90,
                'sky-opacity': ['interpolate', ['exponential', 0.1], ['zoom'], 5, 0, 22, 1],
            },
        },
        // 色斑渲染占位图层
        {
            id: 'fill-placeholder',
            type: 'background',
            layout: { visibility: 'none' },
        },

        // ============ 全国边界图层 ============
        // 自定义图层
        {
            id: `100000-line`,
            source: '100000',
            'source-layer': '100000',
            type: 'line',
            layout: {
                visibility: 'none',
            },
            paint: {
                'line-width': 1,
            },
        },

        // 河流边界
        {
            id: 'secondary-river',
            type: 'fill',
            source: '430000.combine',
            'source-layer': 'riversecondary',
            layout: { visibility: 'none' },
            paint: { 'fill-color': 'hsl(209, 83%, 79%)' },
        },
        {
            id: 'thirdlevel-river',
            type: 'line',
            source: '430000.combine',
            'source-layer': 'rivertertiary',
            layout: { visibility: 'none' },
            paint: { 'line-color': 'hsl(209, 83%, 79%)' },
        },

        // 乡镇边界
        {
            id: `430000.town-line`,
            source: '430000.town',
            'source-layer': '430000.town',
            type: 'line',
            layout: {
                visibility: 'none',
            },
            paint: {
                'line-width': 1,
                'line-color': '#d5d5d5',
                'line-dasharray': [3, 3],
            },
            minzoom: 7.8,
        },

        // 县城边界
        {
            id: `430000.area-line`,
            source: '430000.area',
            'source-layer': '430000.area',
            type: 'line',
            layout: {
                visibility: 'none',
            },
            paint: {
                'line-width': 1,
                'line-color': '#bfbfbf',
            },
            minzoom: 6.5,
        },

        // 城市边界
        {
            id: `430000.city-line`,
            source: '430000.city',
            'source-layer': '430000.city',
            type: 'line',
            layout: {
                visibility: 'none',
            },
            paint: {
                'line-width': 1,
                'line-color': '#ababab',
            },
            minzoom: 5,
        },

        // 遮罩占位图层
        {
            id: 'mask-placeholder',
            type: 'background',
            layout: { visibility: 'none' },
        },

        // 城市名称
        {
            id: `430000.city-label`,
            source: '430000.city.label',
            'source-layer': '430000.city.label',
            type: 'symbol',
            layout: {
                'text-field': '{name}',
                'text-size': 12,
                'text-allow-overlap': true,
                'text-ignore-placement': true,
                'text-justify': 'center',
                'text-anchor': 'center',
                'text-padding': 0,
                'text-line-height': 1.2,
                'text-letter-spacing': 0.1,
                'text-max-width': 8,
                visibility: 'none',
            },
            paint: {
                'text-color': '#333',
                'text-halo-color': '#fff',
                'text-halo-width': 2,
                'text-halo-blur': 1,
            },
            minzoom: 5,
            maxzoom: 6.5,
        },
        // 区县名称
        {
            id: `430000.area-label`,
            source: '430000.area.label',
            'source-layer': '430000.area.label',
            type: 'symbol',
            layout: {
                'text-field': '{name}',
                'text-size': 12,
                'text-justify': 'center',
                'text-anchor': 'center',
                'text-padding': 0,
                'text-line-height': 1.2,
                'text-letter-spacing': 0.1,
                'text-max-width': 8,
                visibility: 'none',
            },
            paint: {
                'text-color': '#333',
                'text-halo-color': '#fff',
                'text-halo-width': 2,
                'text-halo-blur': 1,
            },
            minzoom: 6.5,
            maxzoom: 8.5,
        },
        // 乡镇名称
        {
            id: `430000.town-label`,
            source: '430000.town.label',
            'source-layer': '430000.town.label',
            type: 'symbol',
            layout: {
                'text-field': '{name}',
                'text-size': 12,
                'text-justify': 'center',
                'text-anchor': 'center',
                'text-padding': 0,
                'text-line-height': 1.2,
                'text-letter-spacing': 0.1,
                'text-max-width': 8,
                visibility: 'visible',
            },
            paint: {
                'text-color': '#333',
                'text-halo-color': '#fff',
                'text-halo-width': 2,
                'text-halo-blur': 1,
            },
            minzoom: 8.5,
        },

        // 数值渲染占位图层
        {
            id: 'symbol-placeholder',
            type: 'background',
            layout: { visibility: 'none' },
        },
    ],
}

export function buildTileJsonURL(tileset: string) {
    return `${host}/api/tilesets/${tileset}/tilejson`
}

export function buildJsonAssetURL(tileset: string) {
    return `${host}/api/assets/json/${tileset}.json`
}

interface LayerOption {
    label: string
    value: string
    image: string
    active?: boolean
}

/**
 * 图层控件
 * 目前仅支持天地图图层
 */
class LayerControl implements mapboxgl.IControl {
    map: mapboxgl.Map | undefined
    container: HTMLElement | undefined
    tk: string
    layers?: LayerOption[]

    constructor(options: Recordable) {
        this.tk = options.tk
        this.layers = options.layers
    }

    onAdd(map: mapboxgl.Map) {
        this.map = map
        this.container = document.createElement('div')
        this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group mapbox-control'

        const layersWrapHtml: string[] = []
        if (this.layers) {
            layersWrapHtml.push('<div class="mapboxgl-ctrl-layer-wrap">')
            this.layers.forEach((layer) => {
                layersWrapHtml.push(`
                    <div class="mapboxgl-ctrl-layer ${layer.active ? 'active' : ''}" data-layer-ids="${layer.value}">
                        <img src="${layer.image}" alt="${layer.label}" />
                        <span>${layer.label}</span>
                    </div>
                `)

                for (const id of layer.value.split(',')) {
                    map.addLayer(
                        {
                            id: `t_${id}`,
                            type: 'raster',
                            source: {
                                type: 'raster',
                                tiles: [buildTdtTileUrl(id, this.tk)],
                                tileSize: 256,
                                maxzoom: 18,
                            },
                            layout: { visibility: layer.active ? 'visible' : 'none' },
                        },
                        'sky',
                    )
                }
            })
            layersWrapHtml.push('</div>')
        }

        this.container.innerHTML = `
            <button type="button" class="mapboxgl-ctrl-layer-button" title="地图样式选择">
                <span class="mapboxgl-ctrl-icon" aria-hidden="true" title="Layer"></span>
                ${layersWrapHtml.join('')}
            </button>
        `
        this.container.addEventListener('click', (e) => {
            const target = e.target as HTMLElement
            const parent = target.parentElement as HTMLElement
            if (parent.classList.contains('mapboxgl-ctrl-layer')) {
                const ids = parent.getAttribute('data-layer-ids') as string
                const list = parent.parentElement?.children as HTMLCollection
                for (let i = 0; i < list.length; i++) {
                    const item = list[i] as HTMLElement
                    if (item.classList.contains('mapboxgl-ctrl-layer')) {
                        if (item.getAttribute('data-layer-ids') === ids) {
                            item.classList.toggle('active')
                        } else {
                            item.classList.remove('active')
                        }
                    }
                }

                this.layers?.forEach((layer) => {
                    for (const id of layer.value.split(',')) {
                        map.setLayoutProperty(`t_${id}`, 'visibility', 'none')
                    }
                })
                if (parent.classList.contains('active')) {
                    for (const id of ids.split(',')) {
                        map.setLayoutProperty(`t_${id}`, 'visibility', 'visible')
                    }
                }
                return
            }

            this.container?.querySelector('button')?.classList.toggle('-active')
        })

        return this.container
    }

    onRemove() {
        this.container?.parentNode?.removeChild(this.container)
        this.map = undefined
    }
}

/**
 * 经纬度坐标拾取控件
 */
class GeolocateInfoControl implements mapboxgl.IControl {
    map: mapboxgl.Map | undefined
    container: HTMLElement | undefined

    toText(lngLat: mapboxgl.LngLat) {
        return `经度：${lngLat.lng.toFixed(6)}, 纬度：${lngLat.lat.toFixed(6)}`
    }

    onAdd(map: mapboxgl.Map) {
        this.map = map
        this.container = document.createElement('div')
        this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-lnglat'
        this.container.textContent = this.toText(map.getCenter())

        map.on('mousemove', (e) => {
            const element = this.container as HTMLElement
            element.textContent = this.toText(e.lngLat)
        })

        return this.container
    }

    onRemove() {
        this.container?.parentNode?.removeChild(this.container)
        this.map = undefined
    }
}

/**
 * 地图复位控件
 */
class ResetControl implements mapboxgl.IControl {
    map: mapboxgl.Map | undefined
    container: HTMLElement | undefined
    options: mapboxgl.FlyToOptions

    constructor(options: mapboxgl.FlyToOptions) {
        this.options = options
    }

    onAdd(map: mapboxgl.Map) {
        this.map = map
        this.container = document.createElement('div')
        this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group mapbox-control'
        this.container.innerHTML = `
            <button type="button" class="mapboxgl-ctrl-reset-button mapboxgl-ctrl-reset" title="地图复位">
                <span class="mapboxgl-ctrl-icon" aria-hidden="true" title="Reset"></span>
            </button>
        `

        this.container.addEventListener('click', () => {
            // alert('map: ' + JSON.stringify(this.map.getCenter()))
            this.map?.flyTo(this.options)
        })

        return this.container
    }

    onRemove() {
        this.container?.parentNode?.removeChild(this.container)
        this.map = undefined
    }
}

/**
 * 测量控件
 */
class MeasureControl implements mapboxgl.IControl {
    map: mapboxgl.Map | undefined
    container: HTMLElement | undefined

    onAdd(map: mapboxgl.Map) {
        this.map = map
        this.container = document.createElement('div')
        this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group mapbox-control'
        this.container.innerHTML = `
            <button type="button" class="mapboxgl-ctrl-reset-button" title="面积计算">
                <span class="iconify" data-icon="carbon:area-custom" data-width="24" data-height="24"></span>
            </button>
        `

        this.container.addEventListener('click', () => {
            this.container?.querySelector('button')?.classList.toggle('-active')
            console.log('TODO: 测量面积功能暂未实现, 敬请期待...')
        })

        return this.container
    }

    onRemove() {
        this.container?.parentNode?.removeChild(this.container)
        this.map = undefined
    }
}

/**
 * 风场控件
 */
export class WindyControl implements mapboxgl.IControl {
    map: mapboxgl.Map | undefined
    container: HTMLElement | undefined
    onOpen: () => Promise<void>
    onClose: Function

    constructor(options: Recordable) {
        this.onOpen = options.onOpen
        this.onClose = options.onClose
    }

    onAdd(map: mapboxgl.Map) {
        this.map = map
        this.container = document.createElement('div')
        this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group mapbox-control'
        this.container.innerHTML = `
            <button type="button" class="mapboxgl-ctrl-reset-button" title="风场">
                <span class="iconify" data-icon="ph:wind-light" data-width="24" data-height="24"></span>
            </button>
        `

        this.container.addEventListener('click', () => {
            const button = this.container?.querySelector('button')
            if (!button?.classList.contains('-active')) {
                this.onOpen().then(() => {
                    button?.classList.add('-active')
                })
            } else {
                this.onClose()
                button?.classList.remove('-active')
            }
        })

        return this.container
    }

    onRemove() {
        this.container?.parentNode?.removeChild(this.container)
        this.map = undefined
    }
}

export interface MapOptions {
    control?: MapControlOptions
    style?: mapboxgl.Style | Recordable
}

export interface MapControlOptions {
    // 导航控件
    navigation?: {
        zoom?: boolean
        compass?: boolean
    }
    // 图层控制
    layer?:
        | boolean
        | {
              tk: string
              layers: LayerOption[]
          }
    // 重置控件
    reset?: boolean | mapboxgl.FlyToOptions
    // 全屏控件
    fullscreen?: boolean
    // 比例尺
    scale?: boolean
    // 经纬度拾取
    info?: boolean
    // 定位控件
    geolocate?: { initial?: boolean; fn?: any }
    // 面积测算控件
    measure?: boolean
    // 距离测算控件
    ruler?: boolean
    // 风场控件
    windy?: boolean
    // 绘制控件
    draw?: boolean
}

/**
 * 加载地图控件
 * @param map 地图实例
 * @param options 控件配置
 */
export function loadMapControl(mapboxgl: any, map: mapboxgl.Map, options?: MapControlOptions) {
    if (!options) {
        return
    }

    // 导航控件
    if (options.navigation) {
        map.addControl(
            new mapboxgl.NavigationControl({
                showCompass: options.navigation.compass,
                showZoom: options.navigation.zoom,
            }),
        )
    }

    // 全屏控件
    if (options.fullscreen) {
        map.addControl(new mapboxgl.FullscreenControl())
    }

    // 地图复位控件
    if (options.reset) {
        if (typeof options.reset === 'boolean') {
            map.addControl(
                new ResetControl({
                    zoom: map.getZoom(),
                    pitch: map.getPitch(),
                    bearing: map.getBearing(),
                    center: map.getCenter(),
                }),
            )
        } else {
            map.addControl(new ResetControl(options.reset))
        }
    }

    // 图层选择控件
    if (options.layer) {
        if (typeof options.layer === 'boolean') {
            map.addControl(
                new LayerControl({
                    tk,
                    layers: [
                        {
                            label: '影像',
                            value: 'img,cia',
                            image: `${host}/api/assets/images/img_w.png`,
                        },
                        {
                            label: '矢量',
                            value: 'vec,cva',
                            image: `${host}/api/assets/images/vec_w.png`,
                            active: true,
                        },
                        {
                            label: '地形',
                            value: 'ter,cta',
                            image: `${host}/api/assets/images/ter_w.png`,
                        },
                    ],
                }),
            )
        } else {
            map.addControl(new LayerControl(options.layer))
        }
    }

    // 面积测量控件
    if (options.measure) {
        map.addControl(new MeasureControl())
    }

    // 经纬度信息控件
    if (options.info) {
        map.addControl(new GeolocateInfoControl(), 'bottom-left')
    }

    // 比例尺控件
    if (options.scale) {
        map.addControl(new mapboxgl.ScaleControl(), 'bottom-left')
    }

    // 定位
    if (options.geolocate) {
        const geolocate = new mapboxgl.GeolocateControl({
            fitBoundsOptions: {
                maxZoom: 15,
                duration: 0,
            },
            positionOptions: {
                enableHighAccuracy: true,
                timeout: 10000,
            },
            trackUserLocation: false,
            showUserHeading: true,
        })
        // #ifdef APP-PLUS
        geolocate.geolocation = options.geolocate
        // #endif
        map.addControl(geolocate, 'bottom-right')

        // 初始化定位
        if (options.geolocate.initial) {
            // 由于定位控件初始化需要时间，所以延迟 0.5 秒后再触发定位
            setTimeout(() => {
                geolocate.trigger()
            }, 500)
        }
    }
}

export function toPointGeoJSON(
    data?: ({ lon: number; lat: number } & Recordable)[],
): GeoJSON.FeatureCollection<GeoJSON.Geometry, GeoJSON.GeoJsonProperties> {
    return {
        type: 'FeatureCollection',
        features: !data
            ? []
            : data.map((item) => {
                  return {
                      type: 'Feature',
                      properties: {
                          ...item,
                      },
                      geometry: {
                          type: 'Point',
                          coordinates: [item.lon, item.lat],
                      },
                  }
              }),
    }
}

export function createPopupHtml(entry: Recordable, mapping: Recordable) {
    const htmls: string[] = ['<div class="mapboxgl-custom-popup">']

    for (const key in entry) {
        const lable = mapping[key]
        if (!lable) {
            continue
        }

        const value = entry[key]
        htmls.push(`
            <div class="popup-row">
                <span class="popup-title">${lable}：</span>
                ${value}
            </div>
        `)
    }

    htmls.push('</div>')
    return htmls.join('')
}

/**
 * 加载地图依赖库
 */
export function loadMapboxLibs() {
    const id = 'mapbox-gl'
    const version = '2.15.0'
    const resource = `static/js/mapbox-gl-js/${version}/${id}`
    return Promise.all([
        appendScript(id, `${resource}.js`),
        appendStylesheet(id, `${resource}.css`),
        appendStylesheet(id, `${resource}.extra.css`),
    ])
}

export const getMapInstance = (ref: Ref<any>): mapboxgl.Map => toRaw(ref.value?.map) as mapboxgl.Map
export function getGeoJSONSource(map: mapboxgl.Map, sourceName: string): mapboxgl.GeoJSONSource {
    return map.getSource(sourceName) as mapboxgl.GeoJSONSource
}
export function getImageSource(map: mapboxgl.Map, sourceName: string): mapboxgl.ImageSource {
    return map.getSource(sourceName) as mapboxgl.ImageSource
}

export const EmptyImage =
    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEXMzMz/+v+u+uEzAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg=='

export function sendRequest(url: string, method = 'GET', data = {}) {
    return new Promise((resolve, reject) => {
        const uri = new URL(url)
        const xhr = new XMLHttpRequest()
        xhr.open(method, uri)
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
        xhr.onload = function () {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                if (xhr.status === 200) {
                    const result = JSON.parse(xhr.responseText)
                    if (result.status === 200) {
                        resolve(result)
                    } else {
                        reject(new Error(xhr.responseText))
                    }
                } else {
                    reject(new Error(JSON.stringify({ status: xhr.status, message: xhr.statusText })))
                }
            }
        }
        xhr.onerror = function () {
            reject(new Error(JSON.stringify({ status: 502, message: '网络错误' })))
        }
        xhr.send(JSON.stringify(data))
        console.debug('[SendRequest]', uri, method, data)
    })
}

/**
 * Mapbox 组件配置参数类型定义
 */
export interface MapboxConfig {
    /**
     * 地图行政区域编码，最小支持到区县级别
     * @example 430000
     * @default ''
     * FIEXD: 目前仅支持湖南省，其他省份暂不支持
     */
    region?: string
    /**
     * Mapbox 构造函数参数
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/
     */
    options?: Partial<mapboxgl.MapboxOptions>
    /**
     * Mapbox 地图样式
     * @link https://docs.mapbox.com/mapbox-gl-js/style-spec/
     */
    style?: Partial<mapboxgl.Style>
    /**
     * 地图资源/数据源来源或归属描述信息
     */
    attribution?: {
        /**
         * 文本
         * @example 湖南省气象台
         * @default ''
         */
        text: string
        /**
         * 位置, 默认为 'bottom-right'
         * @example 'bottom-left', 'bottom-right'
         * @default 'bottom-right'
         */
        align?: 'bottom-left' | 'bottom-right'
    }
    /**
     * 扩展配置
     */
    extra?: {
        control?: MapControlOptions
    }

    // =============== 自定义事件 =================
    /**
     * 地图加载完成事件
     * @param data 事件数据, 由视图层在 Map 加载完成后传入的可序列化 JSON 数据
     */
    onLoaded?: (data: Recordable) => void

    /**
     * 监听请求数据源的请求回执
     * @param id 数据源 ID
     * @param url 请求地址
     * @param data 请求回来的数据（默认情况下将过滤掉一些大字段或数据集，例如：jsonObject、liveVos）
     */
    onSourceRequestHandle?: (data: { id: string; url: string; data: Recordable }) => void

    /**
     * 监听地图事件
     * 可以用来处理地图点击事件、自定义事件等
     * @param e 事件对象
     */
    onMapEvent?: (e: { type: 'click' | 'custom' | string; name: string; data: Recordable }) => void
}

/**
 * Mapbox 组件实例类型定义
 */
export interface MapboxInstance {
    /**
     * 设置地图行政区域编码，例如: 430000, 设置后地图的遮罩层将仅包含该区域的边界
     * FIXED: 暂时仅支持湖南省
     * @param region 地图显示行政区域编码
     */
    setRegion: (region: string) => void
    /**
     * 设置地图组件配置
     * @param config 地图配置
     */
    setConfig: (config: Partial<MapboxConfig>) => void
    /**
     * 地图组件是否准备好了
     */
    isReady: ComputedRef<boolean>

    /**
     * 监听地图事件
     * @param type 事件类型
     * @param layerId 图层 ID
     * @param listener 事件监听器
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#on
     */
    on: (type: string, layerId: string, listener: (...args: any[]) => void) => void
    /**
     * 飞行到指定位置
     * @param options 飞行参数
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#flyto
     */
    flyTo: (options: mapboxgl.FlyToOptions) => void
    /**
     * 添加数据源
     * @param id 数据源 ID
     * @param source 数据源
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addsource
     */
    addSource: (id: string, source: mapboxgl.AnySourceData) => void
    /**
     * 移除数据源
     * @param id 数据源 ID
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#removesource
     */
    removeSource: (id: string) => void
    /**
     * 设置 GeoJSON 数据源，如果数据源不存在则创建
     * @param id 数据源 ID
     * @param url 数据源 URL，通常是一个后端的数据查询接口（色斑图查询接口、站点数据查询接口）
     * @param map 数据映射函数，用于处理后端返回的数据，将其转换为 GeoJSON 数据
     * @param filters 过滤对象，用于过滤响应 onSourceRequestHandle 事件时的数据，默认过滤掉 jsonObject、liveVos 字段
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addsource
     */
    setGeoJSONSourceForRequest: (
        id: string,
        url: string | GeoJSONSourceDataUrlParams,
        map?: (
            data: Recordable,
            util: HandlerUtilType,
        ) => GeoJSON.Feature<GeoJSON.Geometry> | GeoJSON.FeatureCollection<GeoJSON.Geometry> | any,
        filters?: string[],
    ) => void
    /**
     * 设置 GeoJSONSource 数据源 data
     * @param id 数据源 ID
     * @param data 数据
     * @param filter 过滤器, 可选, 过滤 JSON 数据, 使用 json path 语法
     * @link https://docs.mapbox.com/mapbox-gl-js/api/sources/#geojsonsource#setdata
     */
    setGeoJSONSourceData: (id: string, data: mapboxgl.GeoJSONSource['setData'], filter?: string) => void
    /**
     * 设置 VectorTileSource 数据源 tiles
     * @param id 数据源 ID
     * @param tiles 瓦片源
     * @link https://docs.mapbox.com/mapbox-gl-js/api/sources/#vectortilesource#settiles
     */
    setVectorTileSourceTiles: (id: string, tiles: string[]) => void
    /**
     * 添加图层
     * @param layer 图层
     * @param beforeId 在指定图层之前插入
     * @param popup 是否添加点击弹窗提示
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer
     */
    addLayer: (layer: mapboxgl.Layer, beforeId?: string, popup?: boolean) => void
    /**
     * 移除图层
     * @param id 图层 ID
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#removelayer
     */
    removeLayer: (id: string) => void
    /**
     * 设置图层过滤器
     * @param layerId 图层 ID
     * @param filter 过滤器
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#setfilter
     */
    setFilter: (layerId: string, filter: any[]) => void
    /**
     * 设置图层样式
     * @param layerId 图层 ID
     * @param name 样式名称
     * @param value 样式值
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#setpaintproperty
     */
    setPaintProperty: (layerId: string, name: string, value: any) => void
    /**
     * 设置图层样式
     * @param layerId 图层 ID
     * @param name 样式名称
     * @param value 样式值
     * @link https://docs.mapbox.com/mapbox-gl-js/api/map/#map#setlayoutproperty
     */
    setLayoutProperty: (layerId: string, name: string, value: any) => void
}

export interface GeoJSONSourceDataUrlParams {
    url: string
    params: Recordable
}

/**
 * 站点数据类型定义
 */
export interface Station extends Recordable {
    /**
     * key 用于标识站点是什么类型的数据（例如：降水量、气温...）
     */
    key: string
    value: string | number
    suffix?: string
    stationCode?: string
    stationName?: string
    city: string
    cnty: string
    lon: string
    lat: string
    [key: string]: any
}

/**
 * 判断是否为非空对象
 * @param obj 对象
 * @returns 是否为非空对象
 */
function isPlainObject(obj: any): boolean {
    if (typeof obj !== 'object' || obj === null || obj === undefined) {
        return false
    }

    const proto = Object.getPrototypeOf(obj)
    return proto === null || proto === Object.prototype
}

interface PointFeatureMap {
    /**
     * 时序按钮标记
     */
    timing?: boolean
    [key: string]: any
}

/**
 * 数据处理工具
 */
export const HandlerUtil = {
    isPlainObject,
    createDefaultPointFeature(station: Station, map = {} as PointFeatureMap): GeoJSON.Feature<GeoJSON.Geometry> {
        return {
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: [Number(station.lon), Number(station.lat)],
            },
            properties: {
                ...station,
                key: station.key,
                suffix: station.suffix,
                value: station.value,
                popup: HandlerUtil.createDefaultPopupHtml(station, { '{key}': '{value}{suffix}', ...map }),
            },
        }
    },
    createFeatureCollection(
        features: GeoJSON.Feature<GeoJSON.Geometry>[] = [],
    ): GeoJSON.FeatureCollection<GeoJSON.Geometry> {
        return {
            type: 'FeatureCollection',
            features: features || [],
        }
    },
    createDefaultPopupHtml(station: Station, map: Recordable): string {
        const htmls = ['<div class="custom-popup">']

        if (isPlainObject(station)) {
            htmls.push(`<div class="popup-row"><span class="popup-title">站号：</span>${station.stationCode}</div>`)
            htmls.push(
                `<div class="popup-row"><span class="popup-title">站名：</span>${station.city} ${station.cnty} ${station.stationName}</div>`,
            )
        }

        if (isPlainObject(map)) {
            // 时序按钮标记
            const timing = map.timing
            if (timing) {
                delete map.timing
            }

            for (const key in map) {
                htmls.push(`<div class="popup-row"><span class="popup-title">${key}：</span>${map[key]}</div>`)
            }

            if (timing) {
                htmls.push(`<div class="popup-row timing">{{timing}}</div>`)
            }
        }

        htmls.push('</div>')
        return htmls.join('')
    },
}

export type HandlerUtilType = typeof HandlerUtil
