import { useI18n } from '@/hooks/web/useI18n'
import {
  L7PlotChartView,
  L7PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/l7plot'
import { Choropleth, ChoroplethOptions } from '@antv/l7plot/dist/esm/plots/choropleth'
import { Dot, DotOptions, IPlotLayer } from '@antv/l7plot'
import {
  MAP_AXIS_TYPE,
  MAP_EDITOR_PROPERTY,
  MAP_EDITOR_PROPERTY_INNER,
  MapMouseEvent
} from '@/views/chart/components/js/panel/charts/map/common'
import { flow, getGeoJsonFile, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { cloneDeep, isEmpty } from 'lodash-es'
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
import {
  handleGeoJson,
  mapRendered,
  mapRendering
} from '@/views/chart/components/js/panel/common/common_antv'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { deepCopy } from '@/utils/utils'
import { configCarouselTooltip } from '@/views/chart/components/js/panel/charts/map/tooltip-carousel'
import { getCustomGeoArea } from '@/api/map'
import { TextLayer } from '@antv/l7plot/dist/esm'
import { centroid } from '@turf/centroid'

const { t } = useI18n()

/**
 * 气泡地图
 */
export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
  properties: EditorProperty[] = [...MAP_EDITOR_PROPERTY, 'bubble-animate']
  propertyInner = {
    ...MAP_EDITOR_PROPERTY_INNER,
    'tooltip-selector': [...MAP_EDITOR_PROPERTY_INNER['tooltip-selector'], 'carousel'],
    'basic-style-selector': [...MAP_EDITOR_PROPERTY_INNER['basic-style-selector'], 'areaBaseColor']
  }
  axis = MAP_AXIS_TYPE
  axisConfig: AxisConfig = {
    xAxis: {
      name: `${t('chart.area')} / ${t('chart.dimension')}`,
      type: 'd',
      limit: 1
    },
    yAxis: {
      name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
      type: 'q',
      limit: 1
    }
  }
  constructor() {
    super('bubble-map')
  }

  async drawChart(drawOption: L7PlotDrawOptions<Choropleth>): Promise<Choropleth> {
    const { chart, level, areaId, container, action, scope, gadmName } = drawOption
    if (!areaId) {
      return
    }
    chart.container = container
    let geoJson = {} as FeatureCollection
    let customSubArea: CustomGeoSubArea[] = []
    let data = chart.data?.data
    if (areaId.startsWith('custom_')) {
      customSubArea = (await getCustomGeoArea(areaId)).data || []
      customSubArea.forEach(a => (a.scopeArr = a.scope?.split(',') || []))
      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 = []
      data?.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
            })
          })
        }
      })
      data = 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 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'
        }
      },
      tooltip: {},
      legend: false,
      // 禁用线上地图数据
      customFetchGeoData: () => null
    }
    const context: Record<string, any> = { drawOption, geoJson, customSubArea }
    options = this.setupOptions(chart, options, context)

    const tooltip = deepCopy(options.tooltip)
    options = { ...options, tooltip: { ...tooltip, showComponent: false } }
    const view = new Choropleth(container, options)
    const dotLayer = this.getDotLayer(chart, geoJson, drawOption, customSubArea)
    if (!areaId.startsWith('custom_')) {
      dotLayer.options = { ...dotLayer.options, tooltip }
    }
    this.configZoomButton(chart, view)
    mapRendering(container)
    view.once('loaded', () => {
      // 修改地图鼠标样式为默认
      view.scene.map._canvasContainer.lastElementChild.style.cursor = 'default'
      const { layers } = context
      if (layers) {
        layers.forEach(l => {
          view.addLayer(l)
        })
      }
      dotLayer.addToScene(view.scene)
      dotLayer.once('add', () => {
        mapRendered(container)
      })
      view.scene.map['keyboard'].disable()
      dotLayer.on('dotLayer:click', (ev: MapMouseEvent) => {
        const evData = ev.feature.properties
        let adcode, scope
        if (areaId.startsWith('custom_')) {
          adcode = '156'
          const area = customSubArea.find(a => a.name === evData.name)
          scope = area?.scopeArr
        } else {
          adcode = view.currentDistrictData.features.find(
            i => i.properties.name === ev.feature.properties.name
          )?.properties.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, gadmName: names }
          }
        })
      })
      dotLayer.once('loaded', () => {
        chart.container = container
        configCarouselTooltip(chart, view, data || [], null, customSubArea, drawOption)
      })
    })
    return view
  }

  private getDotLayer(
    chart: Chart,
    geoJson: FeatureCollection,
    drawOption: L7PlotDrawOptions<Choropleth>,
    customSubArea: CustomGeoSubArea[]
  ): IPlotLayer {
    const { areaId } = drawOption
    const { basicStyle, tooltip } = parseJson(chart.customAttr)
    const { bubbleCfg } = parseJson(chart.senior)
    const { offsetHeight, offsetWidth } = document.getElementById(drawOption.container)
    const dotData = []
    const options: DotOptions = {
      source: {
        data: dotData,
        parser: {
          type: 'json',
          x: 'x',
          y: 'y'
        }
      },
      shape: 'circle',
      size: {
        field: 'size',
        value: [5, Math.min(offsetHeight, offsetWidth) / 20]
      },
      visible: true,
      zIndex: 0.05,
      color: hexColorToRGBA(basicStyle.colors[0], basicStyle.alpha),
      name: 'bubbleLayer',
      style: {
        opacity: 1
      },
      state: {
        active: { color: 'rgba(30,90,255,1)' }
      },
      tooltip: {
        showComponent: tooltip.show
      }
    }
    if (areaId.startsWith('custom_')) {
      const geoJsonMap = geoJson.features.reduce((p, n) => {
        if (n.properties['adcode']) {
          p['156' + n.properties['adcode']] = n
        }
        return p
      }, {})
      const { areaMapping } = parseJson(chart.senior)
      const customAreaMap = customSubArea.reduce((p, n) => {
        const mappedName = areaMapping?.[areaId]?.[n.name]
        if (mappedName) {
          n.name = mappedName
        }
        p[n.name] = n
        return p
      }, {})
      chart.data?.data?.forEach(d => {
        const area = customAreaMap[d.name]
        if (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]]
            dotData.push({
              name: area.name,
              size: d.value,
              properties: d,
              x: center.geometry.coordinates[0],
              y: center.geometry.coordinates[1]
            })
          }
        }
      })
      if (options.tooltip && options.tooltip.showComponent) {
        options.tooltip.items = ['name', 'adcode', 'value']
        options.tooltip.customTitle = ({ name }) => {
          return name
        }
        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
          const formatter = formatterMap[head.quotaList?.[0]?.id]
          if (!isEmpty(formatter)) {
            const originValue = parseFloat(head.value as string)
            const value = valueFormatter(originValue, formatter.formatterCfg)
            const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
            result.push({ ...head, name, value: `${value ?? ''}` })
          }
          head.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
        }
        options.tooltip.domStyles = {
          'l7plot-tooltip': {
            'background-color': tooltip.backgroundColor,
            'font-size': `${tooltip.fontSize}px`,
            'line-height': 1.6
          },
          'l7plot-tooltip__name': {
            color: tooltip.color
          },
          'l7plot-tooltip__value': {
            color: tooltip.color
          },
          'l7plot-tooltip__title': {
            color: tooltip.color
          }
        }
      }
    } else {
      const areaMap = chart.data?.data?.reduce((obj, value) => {
        obj[value['field']] = { value: value.value, data: value }
        return obj
      }, {})
      geoJson?.features.forEach(item => {
        const name = item.properties['name']
        if (areaMap?.[name]?.value) {
          dotData.push({
            x: item.properties['centroid'][0],
            y: item.properties['centroid'][1],
            size: areaMap[name].value,
            properties: {
              ...item.properties,
              ...areaMap[name].data
            },
            name: name
          })
        }
      })
    }
    if (bubbleCfg && bubbleCfg.enable) {
      return new Dot({
        ...options,
        size: {
          field: 'size',
          value: [10, Math.min(offsetHeight, offsetWidth) / 10]
        },
        animate: {
          enable: true,
          speed: bubbleCfg.speed,
          rings: bubbleCfg.rings
        }
      })
    }
    return new Dot(options)
  }

  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 } = parseJson(chart.customAttr)
    const senior = parseJson(chart.senior)
    const curAreaNameMapping = senior.areaMapping?.[areaId]
    handleGeoJson(geoJson, curAreaNameMapping)
    options.color = basicStyle.areaBaseColor
    if (!chart.data?.data?.length || !geoJson?.features?.length) {
      options.label && (options.label.field = 'name')
      return options
    }
    const data = chart.data.data
    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')
      }
    })
    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
    }, {})
    //处理label
    options.label = {
      visible: false
    }
    if (label.show) {
      const geoJsonMap = geoJson.features.reduce((p, n) => {
        if (n.properties['adcode']) {
          p['156' + n.properties['adcode']] = n
        }
        return p
      }, {})
      const { areaMapping } = parseJson(chart.senior)
      const labelLocation = []
      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 content = []
          if (label.showDimension) {
            const mappedName = areaMapping?.[drawOption.areaId]?.[area.name]
            if (mappedName) {
              area.name = mappedName
            }
            content.push(area.name)
          }
          if (label.showQuota) {
            areaMap[area.name] &&
              content.push(valueFormatter(areaMap[area.name].value, label.quotaLabelFormatter))
          }
          const center = centroid(areaJson)
          labelLocation.push({
            name: content.join('\n\n'),
            x: center.geometry.coordinates[0],
            y: center.geometry.coordinates[1]
          })
        }
      })
      const areaLabelLayer = new TextLayer({
        name: 'areaLabelLayer',
        source: {
          data: labelLocation,
          parser: {
            type: 'json',
            x: 'x',
            y: 'y'
          }
        },
        field: 'name',
        zIndex: 0.06,
        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]
    }
    return options
  }

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