import type mapboxgl from 'mapbox-gl'
import qs from 'qs'
import { isEmpty, isNull, isObject, isString, isUndefined, merge, omitBy } from 'lodash-es'
import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM, HandlerUtil } from './index'
import type { GeoJSONSourceDataUrlParams, HandlerUtilType, MapboxConfig, MapboxInstance } from './index'
import { API_URL, API_URL_PREFIX } from '/@/utils/net'
import { isProdMode } from '@/utils/env'
import { useUserStoreWithOut } from '@/store/modules/user'

// 组件名称
export const name = 'Mapbox'

/**
 * 注册 Mapbox 实例
 * @param config Mapbox 配置项
 */
export function useMapbox<T extends MapboxInstance, P extends MapboxConfig>(
    config: P,
): [(instance: T) => void, MapboxInstance] {
    const instanceRef = ref<T>()

    function register(instance: T) {
        if (isProdMode()) {
            if (instance === unref(instanceRef)) {
                return
            }

            onUnmounted(() => {
                instanceRef.value = null
            })
        }

        instanceRef.value = instance

        config && Object.keys(config)?.length && instance?.setConfig(config)
    }

    function getInstance() {
        const instance = unref(instanceRef)
        if (!instance) {
            console.warn('Mapbox instance is undefined!')
        }
        return instance as T
    }

    return [
        register,
        {
            setRegion: (region: string, flyTo?: boolean) => getInstance()?.setRegion(region, flyTo),
            setConfig: (config: Partial<P>) => getInstance()?.setConfig(config),
            isReady: computed(() => instanceRef?.value?.isReady) as unknown as ComputedRef<boolean>,

            on: (type: string, layerId: string, listener: (...args: any[]) => void) =>
                getInstance()?.on(type, layerId, listener),
            addSource: (id: string, source: mapboxgl.AnySourceData) => getInstance()?.addSource(id, source),
            removeSource: (id: string) => getInstance()?.removeSource(id),
            setGeoJSONSourceForRequest: (
                id: string,
                config: string | GeoJSONSourceDataUrlParams,
                handler?: (
                    data: Recordable,
                    util: HandlerUtilType,
                ) => GeoJSON.Feature<GeoJSON.Geometry> | GeoJSON.FeatureCollection<GeoJSON.Geometry>,
                filters?: string[],
            ) =>
                getInstance()?.setGeoJSONSourceForRequest(id, buildGeoJSONSourceDataUrl(config), handler, [
                    ...(filters || ['jsonObject', 'liveVos']),
                ]),
            setGeoJSONSourceData: (id: string, data: mapboxgl.GeoJSONSource['setData'], filter?: string) =>
                getInstance()?.setGeoJSONSourceData(id, data, filter),
            setVectorTileSourceTiles: (id: string, tiles: string[]) =>
                getInstance()?.setVectorTileSourceTiles(id, tiles),
            updateRasterImage: (id: string, url: string) => getInstance()?.updateRasterImage(id, url),
            addLayer: (layer: mapboxgl.Layer, beforeId?: string, popup?: boolean) =>
                getInstance()?.addLayer(layer, beforeId, popup),
            removeLayer: (id: string) => getInstance()?.removeLayer(id),
            setPaintProperty: (layerId: string, name: string, value: any) =>
                getInstance()?.setPaintProperty(layerId, name, value),
            setLayoutProperty: (layerId: string, name: string, value: any) =>
                getInstance()?.setLayoutProperty(layerId, name, value),
            setFilter: (layerId: string, filter: any[]) => getInstance()?.setFilter(layerId, filter),
            flyTo: (options: mapboxgl.FlyToOptions) => getInstance()?.flyTo(options),
            addMarker: (id: string, lnglat: [number, number], popup?: string, popupDefaultOpen?: boolean) =>
                getInstance()?.addMarker(id, lnglat, popup, popupDefaultOpen),
            removeMarker: (id: string) => getInstance()?.removeMarker(id),
            removePopup: () => getInstance()?.removePopup(),
            loadImage: (url: string, callback: (error: Error | null, image: HTMLImageElement) => void) =>
                getInstance()?.loadImage(url, callback),
            addImage: (id: string, image: HTMLImageElement) => getInstance()?.addImage(id, image),
        },
    ]
}

/**
 * 添加默认的 GeoJSON Source - 空白数据源
 * @param map 地图实例
 * @param id source id
 * @param features features
 */
export function addDefaultGeoJSONSource(map: MapboxInstance, id: string, features?: GeoJSON.Feature[]) {
    map.addSource(id, {
        type: 'geojson',
        data: HandlerUtil.createFeatureCollection(features),
    })
}

/**
 * 添加默认的色斑 Fill Layer
 * @param map 地图实例
 * @param id layer id
 * @param layer layer config
 * @param beforeId before layer id
 */
export function addDefaultSplotLayer(
    map: MapboxInstance,
    id: string,
    layer?: Partial<mapboxgl.Layer>,
    beforeId: string = 'fill-placeholder',
    popop: boolean = true,
) {
    map.addLayer(
        merge(
            {
                id,
                type: 'fill',
                source: id,
                paint: {
                    'fill-color': {
                        type: 'identity',
                        property: 'color',
                    },
                    'fill-opacity': 1,
                },
            },
            layer,
        ),
        beforeId,
        popop,
    )
}

/**
 * 添加默认的点位 Symbol Layer - 默认支持 Symbol 的 Click + Popup 事件
 * @param map 地图实例
 * @param id layer id
 * @param layer layer config
 * @param beforeId before layer id
 * @param popop 是否支持 Popup
 */
export function addDefaultSymbolLayer(
    map: MapboxInstance,
    id: string,
    layer?: Partial<mapboxgl.Layer>,
    beforeId: string = 'symbol-placeholder',
    popop: boolean = true,
) {
    map.addLayer(
        merge(
            {
                id,
                type: 'symbol',
                source: id,
                layout: {
                    'text-field': '{value}',
                    // 'text-font': ['PingFang SC', 'Helvetica', 'Helvetica Neue', 'Microsoft YaHei', 'sans-serif'],
                    'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
                    'text-size': 14,
                    'text-offset': [0, 1.2],
                    'icon-image': 'point.normal',
                    'icon-anchor': 'top',
                    'icon-size': 0.4,
                },
                paint: {
                    'text-color': '#3766fd',
                    'text-halo-color': '#fff',
                    'text-halo-width': 1,
                },
            },
            layer,
        ),
        beforeId,
        popop,
    )
}

export function flyTo(
    map: MapboxInstance,
    center: [number, number] = DEFAULT_MAP_CENTER,
    zoom: number = DEFAULT_MAP_ZOOM,
) {
    map.flyTo({
        center,
        zoom,
        speed: 0.2,
        essential: true,
    })
}

/**
 * 构建 GeoJSON Source Data Url - 默认添加 token + 过滤空参数
 * @param config 接口配置
 * @returns source url
 */
export function buildGeoJSONSourceDataUrl(config: GeoJSONSourceDataUrlParams | string): string {
    const userStore = useUserStoreWithOut()
    const baseURL = `${API_URL}${API_URL_PREFIX}`
    const defaultParams = {
        Authorization: userStore.getToken,
    }

    const url = typeof config === 'string' ? config : config.url
    const params = typeof config === 'string' ? defaultParams : { ...defaultParams, ...config.params }
    const query = qs.stringify(
        omitBy(
            params,
            (value) =>
                isNull(value) ||
                isUndefined(value) ||
                (isString(value) && isEmpty(value)) ||
                (isObject(value) && isEmpty(value)),
        ),
        { addQueryPrefix: true, encode: true, encodeValuesOnly: true },
    )
    return `${baseURL}${url}${query}`
}

/**
 * 获取当前位置
 * @returns UniApp.GetLocationSuccess
 */
export function getLocation() {
    return new Promise<UniApp.GetLocationSuccess>((resolve, reject) => {
        // 获取用户定位
        uni.getLocation({
            success: (res) => {
                resolve(res)

                // TODO: Test
                // resolve({
                //     longitude: 112.989113,
                //     latitude: 28.039308,
                // })
            },
            fail: (err) => {
                Message.toast('获取位置失败')
                reject(err)
            },
        })
    })
}
