import {
  L7PlotChartView,
  L7PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/l7plot'
import type { Choropleth, ChoroplethOptions } from '@antv/l7plot/dist/esm/plots/choropleth'
import {
  filterChartDataByRange,
  filterEmptyMinValue,
  flow,
  getDynamicColorScale,
  getGeoJsonFile,
  getMaxAndMinValueByData,
  hexColorToRGBA,
  parseJson
} from '@/views/chart/components/js/util'
import {
  handleGeoJson,
  mapRendered,
  mapRendering
} from '@/views/chart/components/js/panel/common/common_antv'
import type { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
import { cloneDeep, defaultsDeep, isEmpty } from 'lodash-es'
import { useI18n } from '@/hooks/web/useI18n'
import { valueFormatter } from '../../../formatter'
import {
  MAP_AXIS_TYPE,
  MAP_EDITOR_PROPERTY,
  MAP_EDITOR_PROPERTY_INNER,
  MapMouseEvent
} from '@/views/chart/components/js/panel/charts/map/common'
import type { CategoryLegendListItem } from '@antv/l7plot-component/dist/lib/types/legend'
import createDom from '@antv/dom-util/esm/create-dom'
import {
  CONTAINER_TPL,
  ITEM_TPL,
  LIST_CLASS
} from '@antv/l7plot-component/dist/esm/legend/category/constants'
import substitute from '@antv/util/esm/substitute'
import { configCarouselTooltip } from '@/views/chart/components/js/panel/charts/map/tooltip-carousel'
import { getCustomGeoArea } from '@/api/map'
import { centroid } from '@turf/centroid'
import { TextLayer } from '@antv/l7plot/dist/esm'

const { t } = useI18n()

/**
 * 地图
 */
export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
  properties: EditorProperty[] = [...MAP_EDITOR_PROPERTY, 'legend-selector']
  propertyInner: EditorPropertyInner = {
    ...MAP_EDITOR_PROPERTY_INNER,
    'basic-style-selector': [
      'colors',
      'alpha',
      'areaBorderColor',
      'areaBaseColor',
      'zoom',
      'gradient-color'
    ],
    'legend-selector': ['icon', 'fontSize', 'color'],
    'tooltip-selector': [...MAP_EDITOR_PROPERTY_INNER['tooltip-selector'], 'carousel']
  }
  axis = MAP_AXIS_TYPE
  axisConfig: AxisConfig = {
    xAxis: {
      name: `${t('chart.area')} / ${t('chart.dimension')}`,
      type: 'd',
      limit: 1
    },
    yAxis: {
      name: `${t('chart.chart_data')} / ${t('chart.quota')}`,
      type: 'q',
      limit: 1
    }
  }

  constructor() {
    super('map', [])
  }

  async drawChart(drawOption: L7PlotDrawOptions<Choropleth>): Promise<Choropleth> {
    const { chart, level, container, action, scope } = drawOption
    const { areaId, gadmName } = drawOption
    if (!areaId) {
      return
    }
    chart.container = container
    let sourceData = JSON.parse(JSON.stringify(chart.data?.data || []))
    const { misc } = parseJson(chart.customAttr)
    const { legend } = parseJson(chart.customStyle)
    let geoJson = {} as FeatureCollection
    // 自定义区域，去除非区域数据，优先级最高
    let customSubArea: CustomGeoSubArea[] = []
    if (areaId.startsWith('custom_')) {
      customSubArea = (await getCustomGeoArea(areaId)).data || []
      geoJson = cloneDeep(await getGeoJsonFile('156'))
      const areaNameMap = geoJson.features.reduce((p, n) => {
        p['156' + n.properties.adcode] = n.properties.name
        return p
      }, {})
      const { areaMapping } = parseJson(chart.senior)
      const areaMap = customSubArea.reduce((p, n) => {
        const mappedName = areaMapping?.[areaId]?.[n.name]
        if (mappedName) {
          n.name = mappedName
        }
        p[n.name] = n
        n.scopeArr = n.scope?.split(',') || []
        return p
      }, {})
      const fakeData = []
      sourceData.forEach(d => {
        const area = areaMap[d.name]
        if (area) {
          area.scopeArr.forEach(adcode => {
            fakeData.push({
              ...d,
              name: areaNameMap[adcode],
              field: areaNameMap[adcode],
              scope: area.scopeArr,
              areaName: d.name
            })
          })
        }
      })
      sourceData = fakeData
    } else {
      if (scope) {
        geoJson = cloneDeep(await getGeoJsonFile('156'))
        geoJson.features = geoJson.features.filter(f => scope.includes('156' + f.properties.adcode))
      } else {
        geoJson = cloneDeep(await getGeoJsonFile(areaId))
      }
    }
    if (areaId.startsWith('geo_') && geoJson?.features?.length) {
      const levelNames = Object.keys(geoJson?.features[0]?.properties).filter(key =>
        key.startsWith('NAME_')
      )
      const nameKey = levelNames[levelNames.length - 1]
      geoJson.features.forEach(item => {
        if (item.properties[nameKey]) {
          item.properties['name'] = item.properties[nameKey]
        }
      })
      if (areaId.length > 7) {
        geoJson.features = geoJson.features.filter(f => {
          const names = Object.keys(f.properties)
            .filter(key => key.startsWith('NAME_'))
            .map(key => f.properties[key])
            .filter(Boolean)
            .join('@')
          if (isEmpty(names) || !gadmName) {
            return true
          }
          return names.replace(/@[^@]*$/, '') === gadmName
        })
      }
    }
    let data = []
    // 自定义图例
    if (!misc.mapAutoLegend && legend.show) {
      let minValue = misc.mapLegendMin
      let maxValue = misc.mapLegendMax
      let legendNumber = 9
      if (misc.mapLegendRangeType === 'custom') {
        maxValue = 0
        minValue = 0
        legendNumber = misc.mapLegendNumber
      }
      getMaxAndMinValueByData(sourceData, 'value', maxValue, minValue, (max, min) => {
        maxValue = max
        minValue = min
        action({
          from: 'map',
          data: {
            max: maxValue,
            min: minValue ?? filterEmptyMinValue(sourceData, 'value'),
            legendNumber: legendNumber
          }
        })
      })
      data = filterChartDataByRange(sourceData, maxValue, minValue)
      if (chart.drill) {
        getMaxAndMinValueByData(sourceData, 'value', 0, 0, (max, min) => {
          data = filterChartDataByRange(sourceData, max, min)
        })
      }
    } else {
      data = sourceData
    }
    let options: ChoroplethOptions = {
      preserveDrawingBuffer: true,
      map: {
        type: 'mapbox',
        style: 'blank'
      },
      geoArea: {
        type: 'geojson'
      },
      source: {
        data: data,
        joinBy: {
          sourceField: 'name',
          geoField: 'name',
          geoData: geoJson
        }
      },
      viewLevel: {
        level,
        adcode: 'all'
      },
      autoFit: true,
      chinaBorder: false,
      color: {
        field: 'value'
      },
      style: {
        opacity: 1,
        lineWidth: 0.6,
        lineOpacity: 1
      },
      label: {
        field: '_DE_LABEL_',
        style: {
          textAnchor: 'center'
        }
      },
      state: {
        active: { stroke: 'green', lineWidth: 1 }
      },
      tooltip: {},
      // 禁用线上地图数据
      customFetchGeoData: () => null
    }
    const context: Record<string, any> = { drawOption, geoJson, customSubArea }
    options = this.setupOptions(chart, options, context)
    const { Choropleth } = await import('@antv/l7plot/dist/esm/plots/choropleth')
    const view = new Choropleth(container, options)
    this.configZoomButton(chart, view)
    mapRendering(container)
    view.once('loaded', () => {
      mapRendered(container)
      const { layers } = context
      if (layers) {
        layers.forEach(l => {
          view.addLayer(l)
        })
      }
      view.scene.map['keyboard'].disable()
      view.on('fillAreaLayer:click', (ev: MapMouseEvent) => {
        const evData = ev.feature.properties
        if (areaId.startsWith('custom_')) {
          evData.name = evData.areaName
          evData.adcode = '156'
        }
        let adcode = evData.adcode
        let names = ''
        if (adcode + '' !== '156' && !areaId.startsWith('156')) {
          adcode = 'geo_' + adcode
          names = Object.keys(evData)
            .filter(key => key.startsWith('NAME_'))
            .map(key => evData[key])
            .filter(Boolean)
            .join('@')
        }
        action({
          x: ev.x,
          y: ev.y,
          data: {
            data: evData,
            extra: { adcode, scope: evData.scope, gadmName: names }
          }
        })
      })
      chart.container = container
      configCarouselTooltip(chart, view, data, null, customSubArea, drawOption)
    })
    return view
  }

  private configBasicStyle(
    chart: Chart,
    options: ChoroplethOptions,
    context: Record<string, any>
  ): ChoroplethOptions {
    const { areaId }: L7PlotDrawOptions<any> = context.drawOption
    const geoJson: FeatureCollection = context.geoJson
    const { basicStyle, label, misc } = parseJson(chart.customAttr)
    const senior = parseJson(chart.senior)
    const curAreaNameMapping = senior.areaMapping?.[areaId]
    handleGeoJson(geoJson, curAreaNameMapping)
    options.color = {
      field: 'value',
      value: [basicStyle.colors[0]],
      scale: {
        type: 'quantize',
        unknown: basicStyle.areaBaseColor
      }
    }
    if (!chart.data?.data?.length || !geoJson?.features?.length) {
      options.label && (options.label.field = 'name')
      return options
    }
    const sourceData = options.source.data
    const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
    const { legend } = parseJson(chart.customStyle)
    let data = sourceData
    let colorScale = []
    let minValue = misc.mapAutoLegend ? 0 : misc.mapLegendMin
    let maxValue = misc.mapAutoLegend ? 0 : misc.mapLegendMax
    let mapLegendNumber = misc.mapLegendNumber
    if (legend.show) {
      getMaxAndMinValueByData(sourceData, 'value', maxValue, minValue, (max, min) => {
        maxValue = max
        minValue = min
        mapLegendNumber = 9
      })
      // 非自动，过滤数据
      if (!misc.mapAutoLegend) {
        data = filterChartDataByRange(sourceData, maxValue, minValue)
      } else {
        mapLegendNumber = 9
      }
      mapLegendNumber = misc.mapAutoLegend
        ? this.calculateAutoLegendNumber(sourceData)
        : mapLegendNumber
      // 定义最大值、最小值、区间数量和对应的颜色
      colorScale = getDynamicColorScale(minValue, maxValue, mapLegendNumber, colors)
    } else {
      colorScale = colors
    }
    const areaMap = data.reduce((obj, value) => {
      obj[value['field']] = value.value
      return obj
    }, {})
    geoJson.features.forEach(item => {
      const name = item.properties['name']
      // trick, maybe move to configLabel, here for perf
      if (label.show) {
        const content = []
        if (label.showDimension) {
          content.push(name)
        }
        if (label.showQuota) {
          ;(areaMap[name] || areaMap[name] === 0) &&
            content.push(valueFormatter(areaMap[name], label.quotaLabelFormatter))
        }
        item.properties['_DE_LABEL_'] = content.join('\n\n')
      }
    })
    if (colorScale.length) {
      options.color['value'] = colorScale.map(item =>
        item.color && item.value ? new ColorWrapper(item.color, item.value) : new ColorWrapper(item)
      )
      if (colorScale[0].value && !misc.mapAutoLegend) {
        options.color['scale']['domain'] = [
          minValue ?? filterEmptyMinValue(sourceData, 'value'),
          maxValue
        ]
      }
    }
    return options
  }

  /**
   * 自动图例数量计算
   * 根据数据的最大值、最小值和数据条数动态确定合适的图例数量
   * @param data 源数据
   */
  private calculateAutoLegendNumber(data: any[]): number {
    if (!data || data.length === 0) {
      return 1
    }
    // 提取有效数值
    const values = data
      .map(item => parseFloat(item.value))
      .filter(v => !isNaN(v) && v !== null && v !== undefined)
    if (values.length === 0) {
      return 1
    }
    // 计算最大值和最小值
    const maxValue = Math.max(...values)
    const minValue = Math.min(...values)
    // 如果所有数据都相同，只需要 1 个图例
    if (maxValue === minValue) {
      return 1
    }
    // 根据数据条数和值范围计算合适的图例数量
    const dataCount = values.length
    // 基础图例数量：根据数据量决定
    let legendNumber: number
    if (dataCount <= 5) {
      // 数据很少，每个数据一个图例
      legendNumber = dataCount
    } else if (dataCount <= 9) {
      // 数据适中，按数据量
      legendNumber = dataCount
    } else {
      // 数据较多，根据值范围平均分配，最多 9 个
      // 计算每个区间的跨度
      legendNumber = Math.max(9, Math.ceil(Math.sqrt(dataCount)))
    }
    // 确保图例数量在 1-9 之间
    return Math.max(1, Math.min(9, legendNumber))
  }

  // 内部函数 创建自定义图例的内容
  private createLegendCustomContent = showItems => {
    const containerDom = createDom(CONTAINER_TPL) as HTMLElement
    const listDom = containerDom.getElementsByClassName(LIST_CLASS)[0] as HTMLElement
    showItems.forEach(item => {
      let value = '-'
      if (item.value !== '') {
        if (Array.isArray(item.value)) {
          const arr = item.value.every(Number.isNaN) ? item.color.value || [] : item.value
          value = arr
            .map(v => (Number.isNaN(v) || String(v) === 'NaN' ? 'NaN' : parseFloat(v).toFixed(0)))
            .join('-')
        } else {
          const tmp = item.value as string
          value = Number.isNaN(tmp) || tmp === 'NaN' ? 'NaN' : parseFloat(tmp).toFixed(0)
        }
      }
      if (value && value !== '') {
        const substituteObj = { ...item, value }

        const domStr = substitute(ITEM_TPL, substituteObj)
        const itemDom = createDom(domStr)
        // 给 legend 形状用的
        itemDom.style.setProperty('--bgColor', item.color)
        listDom.appendChild(itemDom)
      }
    })
    return listDom
  }

  private customConfigLegend(chart: Chart, options: ChoroplethOptions): ChoroplethOptions {
    const { basicStyle, misc } = parseJson(chart.customAttr)
    const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
    if (basicStyle.suspension === false && basicStyle.showZoom === undefined) {
      return options
    }
    const { legend } = parseJson(chart.customStyle)
    if (!legend.show) {
      return options
    }
    const LEGEND_SHAPE_STYLE_MAP = {
      circle: {
        borderRadius: '50%'
      },
      square: {},
      triangle: {
        border: 'unset',
        borderLeft: '5px solid transparent',
        borderRight: '5px solid transparent',
        borderBottom: '10px solid var(--bgColor)',
        background: 'unset'
      },
      diamond: {
        transform: 'rotate(45deg)'
      }
    }
    const customLegend = {
      position: 'bottomleft',
      domStyles: {
        'l7plot-legend__category-value': {
          fontSize: legend.fontSize + 'px',
          color: legend.color,
          'font-family': chart.fontFamily ? chart.fontFamily : undefined
        },
        'l7plot-legend__category-marker': {
          ...LEGEND_SHAPE_STYLE_MAP[legend.icon],
          width: legend.size + 'px',
          height: legend.size + 'px',
          ...(legend.icon === 'triangle'
            ? {
                ...LEGEND_SHAPE_STYLE_MAP[legend.icon]['triangle'],
                borderLeft: `${legend.size / 2}px solid transparent`,
                borderRight: `${legend.size / 2}px solid transparent`,
                borderBottom: `${legend.size}px solid var(--bgColor)`
              }
            : { border: '0.01px solid #f4f4f4' }),
          ...(legend.icon === 'diamond'
            ? {
                transform: 'rotate(45deg)',
                marginBottom: `${legend.size / 4}px`
              }
            : {})
        }
      }
    }
    // 不是自动图例、自定义图例区间、不是下钻时
    if (!misc.mapAutoLegend && misc.mapLegendRangeType === 'custom' && !chart.drill) {
      // 获取图例区间数据
      const items = []
      // 区间数组
      const ranges = misc.mapLegendCustomRange
        .slice(0, -1)
        .map((item, index) => [item, misc.mapLegendCustomRange[index + 1]])
      ranges.forEach((range, index) => {
        const tmpRange = [range[0], range[1]]
        const colorIndex = index % colors.length
        // 当区间第一个值小于最小值时，颜色取地图底色
        const isLessThanMin = range[0] < ranges[0][0] && range[1] < ranges[0][0]
        let rangeColor = colors[colorIndex]
        if (isLessThanMin) {
          rangeColor = basicStyle.areaBaseColor
        }
        items.push({
          value: tmpRange,
          color: rangeColor
        })
      })
      customLegend['customContent'] = () => {
        if (items?.length) {
          return this.createLegendCustomContent(items)
        }
        return ''
      }
      options.color['value'] = ({ value }) => {
        const item = items.find(item => value >= item.value[0] && value <= item.value[1])
        return item ? item.color : basicStyle.areaBaseColor
      }
      options.color.scale.domain = [ranges[0][0], ranges[ranges.length - 1][1]]
    } else {
      customLegend['customContent'] = (_: string, items: CategoryLegendListItem[]) => {
        // 去重逻辑
        const uniqueItems = items.reduce(
          (acc, item) => {
            const valueKey = JSON.stringify(item.value)
            if (!acc.seen.has(valueKey)) {
              acc.seen.add(valueKey)
              acc.result.push(item)
            }
            return acc
          },
          { seen: new Set(), result: [] }
        ).result
        // 限制最多显示 30 个元素
        const showItems = uniqueItems.length > 30 ? uniqueItems.slice(0, 30) : uniqueItems
        if (showItems?.length) {
          if (showItems.length === 1) {
            const domain = options.color.scale.domain
            if (domain) {
              showItems[0].value = domain?.slice(0, 2)
            } else {
              const firstValue = showItems[0].value?.[0]
              const secondValue = showItems[0].value?.[1]
              if (
                firstValue !== undefined &&
                secondValue !== undefined &&
                !Number.isNaN(firstValue) &&
                !Number.isNaN(secondValue)
              ) {
                showItems[0].value = [firstValue, secondValue]
              } else {
                const v = firstValue ?? secondValue
                showItems[0].value = [v, v]
              }
            }
          }
          return this.createLegendCustomContent(showItems)
        }
        return ''
      }
    }
    // 下钻时按照数据值计算图例
    if (chart.drill) {
      getMaxAndMinValueByData(options.source.data, 'value', 0, 0, (max, min) => {
        options.color.scale.domain = [min, max]
      })
    }
    defaultsDeep(options, { legend: customLegend })
    return options
  }

  protected configCustomArea(
    chart: Chart,
    options: ChoroplethOptions,
    context: Record<string, any>
  ): ChoroplethOptions {
    const { drawOption, customSubArea, geoJson } = context
    if (!drawOption.areaId.startsWith('custom_')) {
      return options
    }
    const customAttr = parseJson(chart.customAttr)
    const { label } = customAttr
    const data = chart.data.data
    const areaMap = data?.reduce((obj, value) => {
      obj[value['field']] = value
      return obj
    }, {})
    const geoJsonMap = geoJson.features.reduce((p, n) => {
      if (n.properties['adcode']) {
        p['156' + n.properties['adcode']] = n
      }
      return p
    }, {})
    customSubArea.forEach(area => {
      const areaJsonArr = []
      area.scopeArr?.forEach(adcode => {
        const json = geoJsonMap[adcode]
        json && areaJsonArr.push(json)
      })
      if (areaJsonArr.length) {
        const areaJson: FeatureCollection = {
          type: 'FeatureCollection',
          features: areaJsonArr
        }
        const center = centroid(areaJson)
        // 轮播用
        area.centroid = [center.geometry.coordinates[0], center.geometry.coordinates[1]]
      }
    })
    //处理label
    options.label = {
      visible: false
    }
    if (label.show) {
      const labelLocation = []
      customSubArea.forEach(area => {
        if (area.centroid) {
          const content = []
          if (label.showDimension) {
            content.push(area.name)
          }
          if (label.showQuota) {
            ;(areaMap[area.name] || areaMap[area.name] === 0) &&
              content.push(valueFormatter(areaMap[area.name].value, label.quotaLabelFormatter))
          }
          labelLocation.push({
            name: content.join('\n\n'),
            x: area.centroid[0],
            y: area.centroid[1]
          })
        }
      })
      const areaLabelLayer = new TextLayer({
        name: 'areaLabelLayer',
        source: {
          data: labelLocation,
          parser: {
            type: 'json',
            x: 'x',
            y: 'y'
          }
        },
        field: 'name',
        style: {
          fill: label.color,
          fontSize: label.fontSize,
          opacity: 1,
          fontWeight: 'bold',
          textAnchor: 'center',
          textAllowOverlap: label.fullDisplay,
          padding: !label.fullDisplay ? [2, 2] : undefined
        }
      })
      context.layers = [areaLabelLayer]
    }
    // 处理tooltip
    const subAreaMap = customSubArea.reduce((p, n) => {
      n.scopeArr.forEach(a => {
        p[a] = n.name
      })
      return p
    }, {})
    if (options.tooltip && options.tooltip.showComponent) {
      options.tooltip.items = ['name', 'adcode', 'value']
      options.tooltip.customTitle = ({ name, adcode }) => {
        adcode = '156' + adcode
        return subAreaMap[adcode] ?? name
      }
      const tooltip = customAttr.tooltip
      const formatterMap = tooltip.seriesTooltipFormatter
        ?.filter(i => i.show)
        .reduce((pre, next) => {
          pre[next.id] = next
          return pre
        }, {}) as Record<string, SeriesFormatter>
      options.tooltip.customItems = originalItem => {
        const result = []
        if (isEmpty(formatterMap)) {
          return result
        }
        const head = originalItem.properties
        if (!head) {
          return result
        }
        const { adcode } = head
        const areaName = subAreaMap['156' + adcode]
        const valItem = areaMap[areaName]
        if (!valItem) {
          return result
        }
        const formatter = formatterMap[valItem.quotaList?.[0]?.id]
        if (!isEmpty(formatter)) {
          const originValue = parseFloat(valItem.value as string)
          const value = valueFormatter(originValue, formatter.formatterCfg)
          const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
          result.push({ ...valItem, name, value: `${value ?? ''}` })
        }
        valItem.dynamicTooltipValue?.forEach(item => {
          const formatter = formatterMap[item.fieldId]
          if (formatter) {
            const value = valueFormatter(parseFloat(item.value), formatter.formatterCfg)
            const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
            result.push({ color: 'grey', name, value: `${value ?? ''}` })
          }
        })
        return result
      }
    }
    return options
  }

  setupDefaultOptions(chart: ChartObj): ChartObj {
    chart.customAttr.basicStyle.areaBaseColor = '#f4f4f4'
    return chart
  }

  protected setupOptions(
    chart: Chart,
    options: ChoroplethOptions,
    context: Record<string, any>
  ): ChoroplethOptions {
    return flow(
      this.configEmptyDataStrategy,
      this.configLabel,
      this.configStyle,
      this.configTooltip,
      this.configBasicStyle,
      this.customConfigLegend,
      this.configCustomArea
    )(chart, options, context, this)
  }
}

class ColorWrapper {
  private color: string
  value?: any

  constructor(color: string, value?: any) {
    this.color = color
    if (value !== undefined) {
      this.value = value
    }
  }

  toString(): string {
    return this.color
  }
}
