import {
  G2PlotChartView,
  G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import type { Mix, MixOptions } from '@antv/g2plot/esm/plots/mix'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { LINE_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/line/common'
import { useI18n } from '@/hooks/web/useI18n'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import type { Options } from '@antv/g2plot/esm'
import { MixOptions } from '@antv/g2plot'

const { t } = useI18n()
const DEFAULT_DATA = []
/**
 * K线图
 */
export class StockLine extends G2PlotChartView<MixOptions, Mix> {
  properties: EditorProperty[] = [
    'background-overall-component',
    'border-style',
    'basic-style-selector',
    'legend-selector',
    'x-axis-selector',
    'y-axis-selector',
    'title-selector',
    'tooltip-selector',
    'function-cfg',
    'jump-set',
    'linkage'
  ]
  propertyInner = {
    ...LINE_EDITOR_PROPERTY_INNER,
    'function-cfg': ['emptyDataStrategy'],
    'y-axis-selector': [
      'name',
      'color',
      'fontSize',
      'position',
      'axisLabel',
      'axisLine',
      'splitLine',
      'axisLabelFormatter'
    ],
    'legend-selector': ['fontSize', 'color', 'show']
  }
  axis: AxisType[] = ['xAxis', 'yAxis', 'filter', 'extLabel', 'extTooltip']
  axisConfig = {
    xAxis: {
      name: `${t('common.component.date')} / ${t('chart.dimension')}`,
      limit: 1,
      type: 'd'
    },
    yAxis: {
      name: `${t('chart.k_line_yaxis_tip')} / ${t('chart.quota')}`,
      limit: 4,
      type: 'q'
    }
  }

  /**
   * 计算收盘价平均值
   * @param data
   * @param dayCount
   * @param chart
   */
  calculateMovingAverage = (data, dayCount, chart) => {
    const xAxis = chart.xAxis
    const yAxis = chart.yAxis
    // 时间字段
    const xAxisgisbiName = xAxis[0].gisbiName
    // 收盘价字段
    const yAxisgisbiName = yAxis[1].gisbiName
    const result = []
    for (let i = 0; i < data.length; i++) {
      if (i < dayCount) {
        result.push({
          [xAxisgisbiName]: data[i][xAxisgisbiName],
          value: null
        })
      } else {
        const sum = data
          .slice(i - dayCount + 1, i + 1)
          .reduce((sum, item) => sum + item[yAxisgisbiName], 0)
        result.push({
          [xAxisgisbiName]: data[i][xAxisgisbiName],
          value: parseFloat((sum / dayCount).toFixed(3))
        })
      }
    }
    return result
  }

  /**
   * 获取数据集合中对象属性值的最大最小值
   * @param data
   */
  calculateMinMax = data => {
    return data.reduce(
      (acc, current) => {
        // 获取 current 对象的所有属性值
        const values = Object.values(current)
        // 过滤出数字值
        const numericValues: any[] = values.filter(value => typeof value === 'number')
        // 找到 current 对象的数字属性值中的最大值和最小值
        // 如果存在数字值，则计算当前对象的最大值和最小值
        if (numericValues.length > 0) {
          const currentMax = Math.max(...numericValues)
          const currentMin = Math.min(...numericValues)
          // 更新全局最大值和最小值
          acc.maxValue = Math.max(acc.maxValue, currentMax)
          acc.minValue = Math.min(acc.minValue, currentMin)
        }
        return acc
      },
      { maxValue: Number.NEGATIVE_INFINITY, minValue: Number.POSITIVE_INFINITY }
    )
  }

  /**
   * 注册图表事件
   * @param data
   * @param plot
   * @param averagesLineData
   */
  registerEvent = (data, plot, averagesLineData) => {
    // 监听图例点击事件，显示隐藏
    let risingVisible = true
    plot.on('legend-item:click', evt => {
      const { value } = evt.target.get('delegateObject').item
      if (value === 'k') {
        risingVisible = !risingVisible
        plot.chart.geometries.forEach(geom => {
          if (geom.type === 'schema') {
            geom.changeVisible(risingVisible)
          }
        })
      } else {
        const lines = plot.chart.geometries.filter(item => item.type === 'line')
        const points = plot.chart.geometries.filter(item => item.type === 'point')
        let lineIndex = 0
        for (const key of averagesLineData.keys()) {
          lineIndex++
          if (key === value) {
            lines[lineIndex - 1].changeVisible(!lines[lineIndex - 1].visible)
            points[lineIndex - 1].changeVisible(!points[lineIndex - 1].visible)
          }
        }
      }
    })
    // 监听图表渲染事件
    plot.on('afterrender', e => {
      let first = false
      if (plot.chart.options.slider.start === 0.5 && plot.chart.options.slider.end === 1) {
        first = true
      }
      if (e.view?.options?.scales) {
        const startIndex = Math.floor(0.5 * data.length)
        const endIndex = Math.ceil(1 * data.length)
        const filteredData = data.slice(startIndex, endIndex)
        const { maxValue, minValue } = this.calculateMinMax(
          first ? filteredData : e.view.filteredData
        )
        const a = e.view.options.scales
        Object.keys(a).forEach(item => {
          if (a[item].max) {
            a[item].max = maxValue
          }
          if (a[item].min) {
            a[item].min = minValue
          }
        })
      }
    })
    // 监听图例组点击事件，设置缩放
    plot.on('legend-item-group:click', e => {
      if (e.view?.options?.scales) {
        const { maxValue, minValue } = this.calculateMinMax(e.view.filteredData)
        const a = e.view.options.scales
        Object.keys(a).forEach(item => {
          if (a[item].max) {
            a[item].max = maxValue
          }
          if (a[item].min) {
            a[item].min = minValue
          }
        })
      }
    })
    // 监听滑块事件，设置缩放
    plot.on('slider:valuechanged', e => {
      const start = e.gEvent.currentTarget.cfg.component.cfg.start
      const end = e.gEvent.currentTarget.cfg.component.cfg.end
      plot.chart.options.slider.start = start
      plot.chart.options.slider.end = end
      const startIndex = Math.floor(start * data.length)
      const endIndex = Math.ceil(end * data.length)
      const filteredData = data.slice(startIndex, endIndex)
      const { maxValue, minValue } = this.calculateMinMax(filteredData)
      const a = e.view.options.scales
      Object.keys(a).forEach(item => {
        if (a[item].max) {
          a[item].max = maxValue
        }
        if (a[item].min) {
          a[item].min = minValue
        }
      })
    })
  }

  async drawChart(drawOptions: G2PlotDrawOptions<Mix>): Promise<Mix> {
    const { chart, action, container } = drawOptions
    if (!chart.data?.data?.length) {
      return
    }
    const xAxis = chart.xAxis
    const yAxis = chart.yAxis
    if (yAxis.length != 4) {
      return
    }
    const basicStyle = parseJson(chart.customAttr).basicStyle
    const colors = []
    const alpha = basicStyle.alpha
    basicStyle.colors.forEach(ele => {
      colors.push(hexColorToRGBA(ele, alpha))
    })
    const data = parseJson(chart.data?.tableRow)

    // 时间字段
    const xAxisgisbiName = xAxis[0].gisbiName
    const averages = [5, 10, 20, 60, 120, 180]
    const legendItems: any[] = [
      {
        name: '日K',
        value: 'k',
        marker: {
          symbol: (x, y, r) => {
            const width = r * 1
            const height = r
            return [
              // 矩形框
              ['M', x - width - 1 / 2, y - height / 2],
              ['L', x + width + 1 / 2, y - height / 2],
              ['L', x + width + 1 / 2, y + height / 2],
              ['L', x - width - 1 / 2, y + height / 2],
              ['Z'],
              // 中线
              ['M', x, y + 10 / 2],
              ['L', x, y - 10 / 2]
            ]
          },
          style: { fill: 'red', stroke: 'red', lineWidth: 2 }
        }
      }
    ]
    // 计算均线数据
    const averagesLineData = new Map()
    averages.forEach(item => {
      averagesLineData.set('ma' + item, this.calculateMovingAverage(data, item, chart))
    })

    // 将均线数据设置到主数据中
    data.forEach((item: any) => {
      const date = item[xAxisgisbiName]
      for (const [key, value] of averagesLineData) {
        item[key] = value.find(m => m[xAxisgisbiName] === date)?.value
      }
    })

    const averageLines: any[] = []
    let index = 0
    const start = 0.5
    const end = 1
    const startIndex = Math.floor(start * data.length)
    const endIndex = Math.ceil(end * data.length)
    const filteredData = data.slice(startIndex, endIndex)
    const { maxValue, minValue } = this.calculateMinMax(filteredData)
    for (const key of averagesLineData.keys()) {
      index++
      averageLines.push({
        type: 'line',
        top: true,
        options: {
          smooth: false,
          xField: xAxisgisbiName,
          yField: key,
          color: colors[index - 1],
          xAxis: null,
          yAxis: {
            label: false,
            min: minValue,
            max: maxValue,
            grid: null,
            line: null
          },
          lineStyle: {
            lineWidth: 2
          }
        }
      })
      legendItems.push({
        name: key.toUpperCase(),
        value: key,
        marker: { symbol: 'hyphen', style: { stroke: colors[index - 1], lineWidth: 2 } }
      })
    }
    const axis = chart.xAxis ?? []
    let dateFormat: string
    const dateSplit = axis[0]?.datePattern === 'date_split' ? '/' : '-'
    switch (axis[0]?.dateStyle) {
      case 'y':
        dateFormat = 'YYYY'
        break
      case 'y_M':
        dateFormat = 'YYYY' + dateSplit + 'MM'
        break
      case 'y_M_d':
        dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD'
        break
      // case 'H_m_s':
      //   dateFormat = 'HH:mm:ss'
      //   break
      case 'y_M_d_H':
        dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + ' HH'
        break
      case 'y_M_d_H_m':
        dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + ' HH:mm'
        break
      case 'y_M_d_H_m_s':
        dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + ' HH:mm:ss'
        break
      default:
        dateFormat = 'YYYY-MM-dd HH:mm:ss'
    }
    const option = this.setupOptions(chart, {
      data,
      slider: {
        start: 0.5,
        end: 1,
        textStyle: {
          fontFamily: chart.fontFamily
        }
      },
      plots: [
        {
          type: 'stock',
          top: true,

          options: {
            meta: {
              [xAxisgisbiName]: {
                mask: dateFormat
              }
            },
            stockStyle: {
              stroke: 'black',
              lineWidth: 0.5
            },
            yAxis: {
              label: {},
              position: 'left',
              min: minValue,
              max: maxValue
            },
            xField: xAxisgisbiName,
            yField: [
              yAxis[0].gisbiName,
              yAxis[1].gisbiName,
              yAxis[2].gisbiName,
              yAxis[3].gisbiName
            ],
            legend: {
              position: 'top',
              custom: true,
              items: legendItems
            }
          }
        },
        ...averageLines
      ]
    })
    const { Mix: MixClass } = await import('@antv/g2plot/esm/plots/mix')
    const plot = new MixClass(container, option)
    this.registerEvent(data, plot, averagesLineData)
    plot.on('schema:click', evt => {
      const selectSchema = evt.data.data[xAxisgisbiName]
      const paramData = parseJson(chart.data?.data)
      const selectData = paramData.filter(item => item.field === selectSchema)
      const quotaList = []
      selectData.forEach(item => {
        quotaList.push({ ...item.quotaList[0], value: item.value })
      })
      if (selectData.length) {
        const param = {
          x: evt.x,
          y: evt.y,
          data: {
            data: {
              ...evt.data.data,
              value: quotaList[0].value,
              name: selectSchema,
              dimensionList: selectData[0].dimensionList,
              quotaList: quotaList
            }
          }
        }
        action(param)
      }
    })
    return plot
  }

  protected configBasicStyle(chart: Chart, options: MixOptions): MixOptions {
    // size
    const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
    const s = JSON.parse(JSON.stringify(customAttr.basicStyle))
    const smooth = s.lineSmooth
    const point = {
      size: s.lineSymbolSize,
      shape: s.lineSymbol
    }
    const lineStyle = {
      lineWidth: s.lineWidth
    }
    const plots = []
    options.plots.forEach(item => {
      if (item.type === 'stock') {
        plots.push({ ...item })
      }
      if (item.type === 'line') {
        plots.push({ ...item, options: { ...item.options, smooth, point, lineStyle } })
      }
    })
    return {
      ...options,
      plots
    }
  }

  protected configTooltip(chart: Chart, options: MixOptions): MixOptions {
    const tooltipAttr = parseJson(chart.customAttr).tooltip
    const newPlots = []
    const linePlotList = options.plots.filter(item => item.type === 'line')
    linePlotList.forEach(item => {
      newPlots.push(item)
    })
    const stockPlot = options.plots.filter(item => item.type === 'stock')[0]
    if (!tooltipAttr.show) {
      const stockOption = {
        ...stockPlot.options,
        tooltip: {
          showContent: false
        }
      }
      newPlots.push({ ...stockPlot, options: stockOption })
      return {
        ...options,
        plots: newPlots
      }
    }

    const showFiled = chart.data.fields
    const customTooltipItems = originalItems => {
      const formattedItems = originalItems.map(item => {
        const fieldObj = showFiled.find(q => q.gisbiName === item.name)
        const displayName = fieldObj?.chartShowName || fieldObj?.name || item.name
        const formattedName = displayName.startsWith('ma') ? displayName.toUpperCase() : displayName
        tooltipAttr.tooltipFormatter.decimalCount = 3
        const formattedValue = valueFormatter(item.value, tooltipAttr.tooltipFormatter)

        return {
          ...item,
          name: formattedName,
          value: formattedValue,
          color: item.color
        }
      })

      const hasKLine = formattedItems.some(item => !item.name.startsWith('MA'))
      const kLines = formattedItems.filter(item => !item.name.startsWith('MA'))
      return hasKLine
        ? [
            { name: '日K', value: '', marker: true, color: kLines[0]?.color },
            ...kLines,
            ...formattedItems.filter(item => item.name.startsWith('MA'))
          ]
        : formattedItems
    }
    const formatTooltipItem = (item: any) => {
      const size = item.name.startsWith('MA') || !item.value ? 10 : 5
      const markerMarginRight = item.name.startsWith('MA') || !item.value ? 5 : 9
      const markerMarginLeft = item.name.startsWith('MA') || !item.value ? 0 : 2
      return `
        <li style="display: flex; align-items: center; margin-bottom: 10px;">
          <div>
            <span
              style="
                background-color: ${item.color};
                width: ${size}px;
                height: ${size}px;
                border-radius: 50%;
                display: inline-block;
                margin-right: ${markerMarginRight}px;
                margin-left: ${markerMarginLeft}px;
              "></span>
          </div>
          <div style="display: flex; justify-content: space-between; width: 100%;">
            <span style="margin-right: 15px;">${item.name}</span>
            <span>${item.name.startsWith('MA') && item.value === '0' ? '-' : item.value}</span>
          </div>
        </li>
      `
    }
    const generateCustomTooltipContent = (title: string, items: Array<any>) => {
      return `
        <div style="padding: 10px 0;">
          <div style="margin-bottom: 10px;">${title}</div>
          <ul style="list-style: none; padding: 0;">
            ${items.map(formatTooltipItem).join('')}
          </ul>
        </div>
      `
    }
    const stockOption = {
      ...stockPlot.options,
      tooltip: {
        showMarkers: true,
        showCrosshairs: true,
        showNil: true,
        crosshairs: {
          follow: true,
          text: (axisType, value, data) => {
            if (axisType === 'y') {
              return { content: value ? value.toFixed(0) : value }
            }
            return { content: data[0].title, position: 'end' }
          }
        },
        showContent: true,
        customItems: customTooltipItems,
        customContent: generateCustomTooltipContent
      }
    }
    newPlots.push({ ...stockPlot, options: stockOption })
    return {
      ...options,
      plots: newPlots
    }
  }

  protected configXAxis(chart: Chart, options: MixOptions): MixOptions {
    const xAxisOptions = super.configXAxis(chart, options)
    if (!xAxisOptions) {
      return options
    }
    const newPlots = []
    const linePlotList = options.plots.filter(item => item.type === 'line')

    const stockPlot = options.plots.filter(item => item.type === 'stock')[0]
    const newStockPlot = {
      ...stockPlot,
      options: {
        ...stockPlot.options,
        xAxis: xAxisOptions['xAxis']
          ? {
              ...stockPlot.options['xAxis'],
              ...xAxisOptions['xAxis']
            }
          : {
              label: false,
              line: null
            }
      }
    }
    newPlots.push(newStockPlot)
    linePlotList.forEach(item => {
      newPlots.push(item)
    })
    return {
      ...options,
      plots: newPlots
    }
  }
  protected configYAxis(chart: Chart, options: MixOptions): MixOptions {
    const yAxisOptions = super.configYAxis(chart, options)
    if (!yAxisOptions) {
      return options
    }
    const yAxis = parseJson(chart.customStyle).yAxis
    const newPlots = []
    const linePlotList = options.plots.filter(item => item.type === 'line')

    const stockPlot = options.plots.filter(item => item.type === 'stock')[0]
    let label = false
    if (yAxisOptions['yAxis'].label) {
      label = {
        ...yAxisOptions['yAxis'].label,
        formatter: value => {
          return valueFormatter(value, yAxis.axisLabelFormatter)
        }
      }
    }
    const newStockPlot = {
      ...stockPlot,
      options: {
        ...stockPlot.options,
        yAxis: label
          ? {
              ...stockPlot.options['yAxis'],
              ...yAxisOptions['yAxis'],
              label
            }
          : {
              ...yAxisOptions['yAxis'],
              label,
              grid: null,
              line: null
            }
      }
    }
    newPlots.push(newStockPlot)
    linePlotList.forEach(item => {
      newPlots.push(item)
    })
    return {
      ...options,
      plots: newPlots
    }
  }

  protected customConfigEmptyDataStrategy(chart: Chart, options: MixOptions): MixOptions {
    const { data } = options as unknown as Options
    if (!data?.length) {
      return options
    }
    const strategy = parseJson(chart.senior).functionCfg.emptyDataStrategy
    if (strategy === 'ignoreData') {
      for (let i = data.length - 1; i >= 0; i--) {
        const item = data[i]
        Object.keys(item).forEach(key => {
          if (key.startsWith('f_') && item[key] === null) {
            data.splice(i, 1)
          }
        })
      }
    }
    const updateValues = (strategy: 'breakLine' | 'setZero', data: any[]) => {
      data.forEach(obj => {
        Object.keys(obj).forEach(key => {
          if (key.startsWith('f_') && obj[key] === null) {
            obj[key] = strategy === 'breakLine' ? null : 0
          }
        })
      })
    }
    if (strategy === 'breakLine' || strategy === 'setZero') {
      updateValues(strategy, data)
    }
    return options
  }
  protected configLegend(chart: Chart, options: MixOptions): MixOptions {
    let legend = {}
    let customStyle: CustomStyle
    const stockPlot = options.plots.filter(item => item.type === 'stock')[0]
    if (chart.customStyle) {
      customStyle = parseJson(chart.customStyle)
      // legend
      if (customStyle.legend) {
        const l = JSON.parse(JSON.stringify(customStyle.legend))
        if (l.show) {
          legend = {
            ...stockPlot.options.legend,
            itemName: {
              style: {
                fill: l.color,
                fontSize: l.fontSize
              }
            }
          }
        } else {
          legend = false
        }
      }
    }
    const newPlots = []
    const stockOption = {
      ...stockPlot.options,
      legend: legend
    }
    const linePlotList = options.plots.filter(item => item.type === 'line')
    linePlotList.forEach(item => {
      newPlots.push(item)
    })
    newPlots.push({ ...stockPlot, options: stockOption })
    return {
      ...options,
      plots: newPlots
    }
  }

  protected setupOptions(chart: Chart, options: MixOptions): MixOptions {
    return flow(
      this.configTheme,
      this.configBasicStyle,
      this.configXAxis,
      this.configYAxis,
      this.configTooltip,
      this.configLegend,
      this.customConfigEmptyDataStrategy
    )(chart, options)
  }

  constructor(name = 'stock-line') {
    super(name, DEFAULT_DATA)
  }
}
