/* eslint-disable prettier/prettier */
import {
  copyString,
  hexColorToRGBA,
  isAlphaColor,
  isTransparent,
  parseJson,
  resetRgbOpacity,
  safeDecimalSum,
  safeDecimalMean
} from '../..//util'
import {
  DEFAULT_BASIC_STYLE,
  DEFAULT_TABLE_CELL,
  DEFAULT_TABLE_HEADER
} from '@/views/chart/components/editor/util/chart'
import {
  BaseTooltip,
  DataCellBrushSelection,
  FONT_FAMILY,
  getAutoAdjustPosition,
  getEmptyPlaceholder,
  getPolygonPoints,
  getTooltipDefaultOptions,
  InteractionName,
  InteractionStateName,
  MergedCell,
  MergedCellInfo,
  type Meta,
  type Node,
  type PivotSheet,
  renderPolygon,
  renderText,
  S2DataConfig,
  S2Event,
  S2Options,
  S2Theme,
  SERIES_NUMBER_FIELD,
  EXTRA_FIELD,
  setTooltipContainerStyle,
  SHAPE_STYLE_MAP,
  SpreadSheet,
  Style,
  TableColCell,
  TableDataCell,
  updateShapeAttr,
  ViewMeta
} from '@antv/s2'
import {
  cloneDeep,
  filter,
  find,
  intersection,
  keys,
  map,
  maxBy,
  merge,
  minBy,
  repeat,
  isNumber
} from 'lodash-es'
import { createVNode, render } from 'vue'
import TableTooltip from '@/views/chart/components/editor/common/TableTooltip.vue'
import Exceljs from 'exceljs'
import { saveAs } from 'file-saver'
import { ElMessage } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import Decimal from 'decimal.js'

const { t: i18nt } = useI18n()

export function getCustomTheme(chart: Chart): S2Theme {
  const headerColor = hexColorToRGBA(
    DEFAULT_TABLE_HEADER.tableHeaderBgColor,
    DEFAULT_BASIC_STYLE.alpha
  )
  const headerAlign = DEFAULT_TABLE_HEADER.tableHeaderAlign
  const itemColor = hexColorToRGBA(DEFAULT_TABLE_CELL.tableItemBgColor, DEFAULT_BASIC_STYLE.alpha)
  const itemAlign = DEFAULT_TABLE_CELL.tableItemAlign
  const borderColor = hexColorToRGBA(
    DEFAULT_BASIC_STYLE.tableBorderColor,
    DEFAULT_BASIC_STYLE.alpha
  )
  const scrollBarColor = DEFAULT_BASIC_STYLE.tableScrollBarColor
  const scrollBarHoverColor = resetRgbOpacity(scrollBarColor, 3)
  const textFontFamily =
    chart.fontFamily && chart.fontFamily !== 'inherit' ? chart.fontFamily : FONT_FAMILY
  const theme: S2Theme = {
    background: {
      color: '#00000000'
    },
    splitLine: {
      horizontalBorderColor: borderColor,
      horizontalBorderColorOpacity: 1,
      horizontalBorderWidth: 1,
      verticalBorderColor: borderColor,
      verticalBorderColorOpacity: 1,
      verticalBorderWidth: 1,
      showShadow: false
    },
    cornerCell: {
      cell: {
        backgroundColor: headerColor,
        horizontalBorderColor: borderColor,
        verticalBorderColor: borderColor
      },
      text: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      },
      bolderText: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      },
      measureText: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      }
    },
    rowCell: {
      cell: {
        backgroundColor: headerColor,
        horizontalBorderColor: borderColor,
        verticalBorderColor: borderColor
      },
      text: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        textBaseline: 'middle',
        fontFamily: textFontFamily
      },
      bolderText: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      },
      measureText: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      },
      seriesText: {
        fill: DEFAULT_TABLE_CELL.tableItemBgColor,
        fontSize: DEFAULT_TABLE_CELL.tableItemFontSize,
        textAlign: itemAlign,
        fontFamily: textFontFamily
      }
    },
    colCell: {
      cell: {
        backgroundColor: headerColor,
        horizontalBorderColor: borderColor,
        verticalBorderColor: borderColor
      },
      text: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      },
      bolderText: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      },
      measureText: {
        fill: DEFAULT_TABLE_HEADER.tableHeaderFontColor,
        fontSize: DEFAULT_TABLE_HEADER.tableTitleFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      }
    },
    dataCell: {
      cell: {
        backgroundColor: itemColor,
        horizontalBorderColor: borderColor,
        verticalBorderColor: borderColor
      },
      text: {
        fill: DEFAULT_TABLE_CELL.tableFontColor,
        fontSize: DEFAULT_TABLE_CELL.tableItemFontSize,
        textAlign: itemAlign,
        fontFamily: textFontFamily
      },
      bolderText: {
        fill: DEFAULT_TABLE_CELL.tableFontColor,
        fontSize: DEFAULT_TABLE_CELL.tableItemFontSize,
        textAlign: itemAlign,
        fontFamily: textFontFamily
      },
      measureText: {
        fill: DEFAULT_TABLE_CELL.tableFontColor,
        fontSize: DEFAULT_TABLE_CELL.tableItemFontSize,
        textAlign: headerAlign,
        fontFamily: textFontFamily
      }
    },
    scrollBar: {
      thumbColor: scrollBarColor,
      thumbHoverColor: scrollBarHoverColor,
      size: 8,
      hoverSize: 12
    }
  }

  let customAttr: DeepPartial<ChartAttr>
  if (chart.customAttr) {
    customAttr = parseJson(chart.customAttr)
    const { basicStyle, tableHeader, tableCell } = customAttr
    // basic
    if (basicStyle) {
      const tableBorderColor = basicStyle.tableBorderColor
      const tableScrollBarColor = basicStyle.tableScrollBarColor
      const tmpTheme: S2Theme = {
        splitLine: {
          horizontalBorderColor: tableBorderColor,
          verticalBorderColor: tableBorderColor
        },
        cornerCell: {
          cell: {
            horizontalBorderColor: tableBorderColor,
            verticalBorderColor: tableBorderColor
          }
        },
        colCell: {
          cell: {
            horizontalBorderColor: tableBorderColor,
            verticalBorderColor: tableBorderColor
          }
        },
        dataCell: {
          cell: {
            horizontalBorderColor: tableBorderColor,
            verticalBorderColor: tableBorderColor,
            interactionState: {
              hoverFocus: {
                borderOpacity: basicStyle.showHoverStyle === false ? 0 : 1
              }
            }
          }
        },
        scrollBar: {
          thumbColor: tableScrollBarColor,
          thumbHoverColor: resetRgbOpacity(tableScrollBarColor, 1.5)
        }
      }
      merge(theme, tmpTheme)
    }
    // header
    if (tableHeader) {
      const tableHeaderFontColor = hexColorToRGBA(
        tableHeader.tableHeaderFontColor,
        basicStyle.alpha
      )
      let tableHeaderBgColor = tableHeader.tableHeaderBgColor
      if (!isAlphaColor(tableHeaderBgColor)) {
        tableHeaderBgColor = hexColorToRGBA(tableHeaderBgColor, basicStyle.alpha)
      }
      const fontStyle = tableHeader.isItalic ? 'italic' : 'normal'
      const fontWeight = tableHeader.isBolder === false ? 'normal' : 'bold'
      const { tableHeaderAlign, tableTitleFontSize } = tableHeader
      const tmpTheme: S2Theme = {
        cornerCell: {
          cell: {
            backgroundColor: tableHeaderBgColor
          },
          bolderText: {
            fill: tableHeaderFontColor,
            fontSize: tableTitleFontSize,
            textAlign: tableHeaderAlign,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          text: {
            fill: tableHeaderFontColor,
            fontSize: tableTitleFontSize,
            textAlign: tableHeaderAlign,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          measureText: {
            fill: tableHeaderFontColor,
            fontSize: tableTitleFontSize,
            textAlign: tableHeaderAlign,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          }
        },
        colCell: {
          cell: {
            backgroundColor: tableHeaderBgColor
          },
          bolderText: {
            fill: tableHeaderFontColor,
            fontSize: tableTitleFontSize,
            textAlign: tableHeaderAlign,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          text: {
            fill: tableHeaderFontColor,
            fontSize: tableTitleFontSize,
            textAlign: tableHeaderAlign,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          measureText: {
            fill: tableHeaderFontColor,
            fontSize: tableTitleFontSize,
            textAlign: tableHeaderAlign,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          }
        }
      }
      merge(theme, tmpTheme)
      // 这边设置为 0 的话就会显示表头背景颜色，所以要判断一下表头是否关闭
      if (tableHeader.showHorizonBorder === false && tableHeader.showTableHeader !== false) {
        const tmpTheme: S2Theme = {
          splitLine: {
            horizontalBorderColor: tableHeaderBgColor,
            horizontalBorderWidth: 0,
            horizontalBorderColorOpacity: 0
          },
          colCell: {
            cell: {
              horizontalBorderColor: tableHeaderBgColor,
              horizontalBorderWidth: 0
            }
          }
        }
        merge(theme, tmpTheme)
      }
      if (tableHeader.showVerticalBorder === false && tableHeader.showTableHeader !== false) {
        const tmpTheme: S2Theme = {
          splitLine: {
            verticalBorderColor: tableHeaderBgColor,
            verticalBorderWidth: 0,
            verticalBorderColorOpacity: 0
          },
          colCell: {
            cell: {
              verticalBorderColor: tableHeaderBgColor,
              verticalBorderWidth: 0
            }
          },
          cornerCell: {
            cell: {
              verticalBorderColor: tableHeaderBgColor,
              verticalBorderWidth: 0
            }
          }
        }
        merge(theme, tmpTheme)
      }
    }
    // cell
    if (tableCell) {
      const tableFontColor = hexColorToRGBA(tableCell.tableFontColor, basicStyle.alpha)
      let tableItemBgColor = tableCell.tableItemBgColor
      if (!isAlphaColor(tableItemBgColor)) {
        tableItemBgColor = hexColorToRGBA(tableItemBgColor, basicStyle.alpha)
      }
      let tableItemSubBgColor = tableCell.tableItemSubBgColor
      if (!isAlphaColor(tableItemSubBgColor)) {
        tableItemSubBgColor = hexColorToRGBA(tableItemSubBgColor, basicStyle.alpha)
      }
      const fontStyle = tableCell.isItalic ? 'italic' : 'normal'
      const fontWeight = tableCell.isBolder === false ? 'normal' : 'bold'
      const { tableItemAlign, tableItemFontSize, enableTableCrossBG } = tableCell
      const tmpTheme: S2Theme = {
        rowCell: {
          cell: {
            backgroundColor: tableItemBgColor,
            horizontalBorderColor: tableItemBgColor,
            verticalBorderColor: tableItemBgColor
          },
          bolderText: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontFamily: textFontFamily
          },
          text: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontFamily: textFontFamily
          },
          measureText: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontFamily: textFontFamily
          },
          seriesText: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontFamily: textFontFamily
          }
        },
        dataCell: {
          cell: {
            crossBackgroundColor: enableTableCrossBG ? tableItemSubBgColor : tableItemBgColor,
            backgroundColor: tableItemBgColor
          },
          bolderText: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          text: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          measureText: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          },
          seriesText: {
            fill: tableFontColor,
            textAlign: tableItemAlign,
            fontSize: tableItemFontSize,
            fontStyle,
            fontWeight,
            fontFamily: textFontFamily
          }
        }
      }
      merge(theme, tmpTheme)
      if (tableCell.showHorizonBorder === false) {
        const tmpTheme: S2Theme = {
          dataCell: {
            cell: {
              horizontalBorderColor: tableItemBgColor,
              horizontalBorderWidth: 0
            }
          }
        }
        merge(theme, tmpTheme)
      }
      if (tableCell.showVerticalBorder === false) {
        const tmpTheme: S2Theme = {
          splitLine: {
            verticalBorderWidth: 0,
            verticalBorderColorOpacity: 0
          },
          dataCell: {
            cell: {
              verticalBorderColor: tableItemBgColor,
              verticalBorderWidth: 0
            }
          }
        }
        merge(theme, tmpTheme)
      }
    }
  }

  return theme
}

export function getStyle(chart: Chart, dataConfig: S2DataConfig): Style {
  const style: Style = {}
  let customAttr: DeepPartial<ChartAttr>
  if (chart.customAttr) {
    customAttr = parseJson(chart.customAttr)
    const { basicStyle, tableHeader, tableCell } = customAttr
    style.colCfg = {
      height: tableHeader.tableTitleHeight
    }
    style.cellCfg = {
      height: tableCell.tableItemHeight
    }
    switch (basicStyle.tableColumnMode) {
      case 'adapt': {
        style.layoutWidthType = 'compact'
        break
      }
      case 'field': {
        delete style.layoutWidthType
        const fieldMap =
          basicStyle.tableFieldWidth?.reduce((p, n) => {
            p[n.fieldId] = n
            return p
          }, {}) || {}
        // 下钻字段使用入口字段的宽度
        if (chart.drill) {
          const { xAxis } = parseJson(chart)
          const curDrillField = chart.drillFields[chart.drillFilters.length]
          const drillEnterFieldIndex = xAxis.findIndex(
            item => item.id === chart.drillFilters[0].fieldId
          )
          const drillEnterField = xAxis[drillEnterFieldIndex]
          fieldMap[curDrillField.dataeaseName] = {
            width: fieldMap[drillEnterField.dataeaseName]?.width
          }
        }
        // 铺满
        const totalWidthPercent = dataConfig.meta?.reduce((p, n) => {
          return p + (fieldMap[n.field]?.width ?? 10)
        }, 0)
        const fullFilled = parseInt(totalWidthPercent.toFixed(0)) === 100
        const widthArr = []
        style.colCfg.width = node => {
          const width = node.spreadsheet.container.cfg.el.getBoundingClientRect().width
          if (!basicStyle.tableFieldWidth?.length) {
            const fieldsSize = chart.data.fields.length
            const columnCount = tableHeader.showIndex ? fieldsSize + 1 : fieldsSize
            return width / columnCount
          }
          const baseWidth = width / 100
          const tmpWidth = fieldMap[node.field]
            ? fieldMap[node.field].width * baseWidth
            : baseWidth * 10
          const resultWidth = parseInt(tmpWidth.toFixed(0))
          if (fullFilled) {
            if (widthArr.length === dataConfig.meta.length - 1) {
              const curTotalWidth = widthArr.reduce((p, n) => {
                return p + n
              }, 0)
              const restWidth = width - curTotalWidth
              widthArr.splice(0)
              if (restWidth < resultWidth) {
                return restWidth
              }
            } else {
              widthArr.push(resultWidth)
            }
          }
          return resultWidth
        }
        break
      }
      case 'custom': {
        style.colCfg.width = basicStyle.tableColumnWidth
        break
      }
      case 'colAdapt': {
        style.layoutWidthType = 'colAdaptive'
        const parentNodeWidthMap = {}
        const nodeMaxWidthMap = {}
        const quotaLabelMap = chart.yAxis?.reduce((p, n) => {
          p[n.dataeaseName] = n.chartShowName || n.name
          return p
        }, {}) || {}
        let calcCount = 50
        //只计算最后两层表头的宽度，采样 50 个数据
        style.colCfg.width = node => {
          const spreadsheet = node.spreadsheet
          const colHeaderTheme = spreadsheet.theme.colCell.bolderText
          const padding = spreadsheet.theme.colCell.cell.padding
          const paddingWidth = (padding?.left || 8) + (padding?.right || 8) + 12
          // 小计总计和第一层表头直接根据文本计算宽度
          if (node.isTotals || node.parent.id === 'root') {
            let label = node.label
            if (node.key === EXTRA_FIELD) {
              label = quotaLabelMap[node.label] || label
            }
            const totalsWidth = spreadsheet.measureTextWidth(label, colHeaderTheme) + paddingWidth
            return totalsWidth
          }

          const parentWidth = parentNodeWidthMap[node.parent.id]
          if (!parentWidth || (parentWidth && calcCount < 50)) {
            const parentLabel = node.parent.label
            const parentTextWidth = spreadsheet.measureTextWidth(parentLabel, colHeaderTheme) + paddingWidth
            parentNodeWidthMap[node.parent.id] = parentTextWidth
            const siblings = node.parent.children
            const siblingsTextWidthMap = {}
            const siblingsWidth = siblings.reduce((p, n) => {
              let label = n.label
              if (n.key === EXTRA_FIELD) {
                label = quotaLabelMap[n.label] || label
              }
              const pureTextWidth = spreadsheet.measureTextWidth(label, colHeaderTheme)
              if (n.key === EXTRA_FIELD) {
                siblingsTextWidthMap[n.label] = pureTextWidth
              }
              calcCount++
              return p + pureTextWidth + paddingWidth
            }, 0)
            if (siblingsWidth < parentTextWidth) {
              const offsetWidth = parentTextWidth - siblingsWidth
              const expandOffsetWidth = offsetWidth / Object.keys(siblingsTextWidthMap).length
              for (const key in siblingsTextWidthMap) {
                const tmpWidth = siblingsTextWidthMap[key] + Math.ceil(expandOffsetWidth) + paddingWidth
                const maxWidth = nodeMaxWidthMap[key]
                if (!maxWidth || (maxWidth && tmpWidth > maxWidth)) {
                  nodeMaxWidthMap[key] = tmpWidth
                }
              }
            } else {
              for (const key in siblingsTextWidthMap) {
                const tmpWidth = siblingsTextWidthMap[key] + paddingWidth
                const maxWidth = nodeMaxWidthMap[key]
                if (!maxWidth || (maxWidth && tmpWidth > maxWidth)) {
                  nodeMaxWidthMap[key] = tmpWidth
                }
              }
            }
            return nodeMaxWidthMap[node.label]
          } else {
            const fieldWidth = nodeMaxWidthMap[node.label]
            if (fieldWidth) {
              return fieldWidth
            }
            const textWidth = spreadsheet.measureTextWidth(node.label, colHeaderTheme) + paddingWidth
            return textWidth
          }
        }
        break
      }
      // 查看详情用，均分铺满
      default: {
        delete style.layoutWidthType
        style.colCfg.width = node => {
          const width = node.spreadsheet.container.cfg.el.offsetWidth
          const fieldsSize = node.spreadsheet.dataCfg.meta.length
          if (!fieldsSize) {
            return 0
          }
          const columnCount = tableHeader.showIndex ? fieldsSize + 1 : fieldsSize
          const minWidth = width / columnCount
          return Math.max(minWidth, basicStyle.tableColumnWidth)
        }
      }
    }
  }

  return style
}

export function getCurrentField(valueFieldList: Axis[], field: ChartViewField) {
  let list = []
  let res = null
  try {
    list = parseJson(valueFieldList)
  } catch (err) {
    list = JSON.parse(JSON.stringify(valueFieldList))
  }
  if (list) {
    for (let i = 0; i < list.length; i++) {
      const f = list[i]
      if (field.dataeaseName === f.dataeaseName) {
        res = f
        break
      }
    }
  }

  return res
}

export function getConditions(chart: Chart) {
  const { threshold } = parseJson(chart.senior)
  if (!threshold.enable) {
    return
  }
  const res = {
    text: [],
    background: []
  }
  const conditions = threshold.tableThreshold ?? []

  const allFields = [...chart.xAxis]
  if (chart.type === 'table-normal') {
    allFields.push(...chart.yAxis)
  }
  const fieldIdToName = allFields.reduce((acc, f) => {
    acc[f.id] = f.dataeaseName
    return acc
  }, {})
  const allColumnNames = allFields.map(f => f.dataeaseName)

  if (conditions?.length > 0) {
    const { tableCell, basicStyle, tableHeader } = parseJson(chart.customAttr)
    // 合并单元格时斑马纹失效
    const enableTableCrossBG =
      chart.type === 'table-info'
        ? tableCell.enableTableCrossBG && !tableCell.mergeCells
        : tableCell.enableTableCrossBG
    const valueColor = isAlphaColor(tableCell.tableFontColor)
      ? tableCell.tableFontColor
      : hexColorToRGBA(tableCell.tableFontColor, basicStyle.alpha)
    const valueBgColor = enableTableCrossBG
      ? null
      : isAlphaColor(tableCell.tableItemBgColor)
      ? tableCell.tableItemBgColor
      : hexColorToRGBA(tableCell.tableItemBgColor, basicStyle.alpha)
    const filedValueMap = getFieldValueMap(chart)

    const targetRulesMap = {} // columnName -> Array<{ rule, sourceField }>

    for (let i = 0; i < conditions.length; i++) {
      const fieldItem = conditions[i]
      if (!fieldItem.conditions) continue;

      for (let j = 0; j < fieldItem.conditions.length; j++) {
        const rule = fieldItem.conditions[j]
        let targets = []
        if (rule.target === 'total_row') {
          targets = [...allColumnNames]
          // 明细表和汇总表需要包含序号列
          if (tableHeader.showIndex) {
            targets.push(SERIES_NUMBER_FIELD)
          }
        } else if (rule.target === 'custom' && rule.targetFieldId) {
          const targetName = fieldIdToName[rule.targetFieldId]
          if (targetName) targets = [targetName]
        } else {
          targets = [fieldItem.field.dataeaseName]
        }

        targets.forEach(targetName => {
          if (!targetRulesMap[targetName]) {
            targetRulesMap[targetName] = []
          }
          targetRulesMap[targetName].push({
            rule: rule,
            sourceField: fieldItem.field
          })
        })
      }
    }

    for (const targetName in targetRulesMap) {
      const rules = targetRulesMap[targetName]
      const defaultValueColor = valueColor
      const defaultBgColor = valueBgColor

      res.text.push({
        field: targetName,
        mapping(value, rowData) {
          if (!value && !rowData) {
            return null
          }

          return {
            fill: mappingColor(value, defaultValueColor, rules, 'color', filedValueMap, rowData)
          }
        }
      })
      res.background.push({
        field: targetName,
        mapping(value, rowData) {
          if (!value && !rowData) {
            return null
          }
          const fill = mappingColor(
            value,
            defaultBgColor,
            rules,
            'backgroundColor',
            filedValueMap,
            rowData
          )
          if (isTransparent(fill)) {
            return null
          }
          return { fill }
        }
      })
    }
  }
  return res
}

export function mappingColorCustom(value, defaultColor, field, type, filedValueMap?, rowData?) {
  let color = null
  for (let i = 0; i < field.conditions.length; i++) {
    let flag = false
    const t = field.conditions[i]
    let tv, max, min
    if (t.type === 'dynamic') {
      if (t.term === 'between') {
        max = parseFloat(getValue(t.dynamicMaxField, filedValueMap, rowData))
        min = parseFloat(getValue(t.dynamicMinField, filedValueMap, rowData))
      } else {
        tv = getValue(t.dynamicField, filedValueMap, rowData)
      }
    } else {
      if (t.term === 'between') {
        min = parseFloat(t.min)
        max = parseFloat(t.max)
      } else {
        tv = t.value
      }
    }
    if (field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) {
      tv = parseFloat(tv)
      if (t.term === 'eq') {
        if (value === tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_eq') {
        if (value !== tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'lt') {
        if (value < tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'gt') {
        if (value > tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'le') {
        if (value !== null && value <= tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'ge') {
        if (value !== null && value >= tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'between') {
        if (value !== null && min <= value && value <= max) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'default') {
        color = t[type]
        flag = true
      } else if (t.term === 'null') {
        if (value === null || value === undefined || value === '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_null') {
        if (value !== null && value !== undefined && value !== '') {
          color = t[type]
          flag = true
        }
      }
      if (flag) {
        break
      } else if (i === field.conditions.length - 1) {
        color = defaultColor
      }
    } else if (field.field.deType === 0 || field.field.deType === 5) {
      if (t.term === 'eq') {
        if (value === tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_eq') {
        if (value !== tv) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'like') {
        if (value.includes(tv)) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not like') {
        if (!value.includes(tv)) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'null') {
        if (value === null || value === undefined || value === '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_null') {
        if (value !== null && value !== undefined && value !== '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'default') {
        color = t[type]
        flag = true
      }
      if (flag) {
        break
      } else if (i === field.conditions.length - 1) {
        color = defaultColor
      }
    } else {
      const fc = field.conditions[i]
      if (fc.term === 'null') {
        if (value === null && value === undefined && value === '') {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'not_null') {
        if (value !== null && value !== undefined && value !== '') {
          color = fc[type]
          flag = true
        }
      }
      if (flag) {
        break
      }
      // time
      if (!tv || !value) {
        break
      }
      tv = new Date(tv.replace(/-/g, '/') + ' GMT+8').getTime()
      const v = new Date(value.replace(/-/g, '/') + ' GMT+8').getTime()
      if (fc.term === 'eq') {
        if (v === tv) {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'not_eq') {
        if (v !== tv) {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'lt') {
        if (v < tv) {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'gt') {
        if (v > tv) {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'le') {
        if (v <= tv) {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'ge') {
        if (v >= tv) {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'default') {
        color = fc[type]
        flag = true
      }
      if (flag) {
        break
      } else if (i === field.conditions.length - 1) {
        color = defaultColor
      }
    }
  }
  return color
}

export function mappingColor(value, defaultColor, rules, type, filedValueMap?, rowData?) {
  let color = null

  for (let i = 0; i < rules.length; i++) {
    const { rule, sourceField } = rules[i]
    let flag = false
    const t = rule
    let targetValue, max, min

    let checkValue = value;
    if (sourceField.dataeaseName) {
      checkValue = rowData?.[sourceField.dataeaseName]
      if (checkValue === undefined) {
        checkValue = rowData?.query?.[sourceField.dataeaseName]
      }
    }

    if (t.type === 'dynamic') {
      if (t.term === 'between') {
        max = parseFloat(getValue(t.dynamicMaxField, filedValueMap, rowData))
        min = parseFloat(getValue(t.dynamicMinField, filedValueMap, rowData))
      } else {
        targetValue = getValue(t.dynamicField, filedValueMap, rowData)
      }
    } else {
      if (t.term === 'between') {
        min = parseFloat(t.min)
        max = parseFloat(t.max)
      } else {
        targetValue = t.value
      }
    }

    const val = checkValue;

    if (sourceField.deType === 2 || sourceField.deType === 3 || sourceField.deType === 4) {
      targetValue = parseFloat(targetValue)
      const numVal = parseFloat(val)
      if (t.term === 'eq') {
        if (numVal === targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_eq') {
        if (numVal !== targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'lt') {
        if (numVal < targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'gt') {
        if (numVal > targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'le') {
        if (val !== null && numVal <= targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'ge') {
        if (val !== null && numVal >= targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'between') {
        if (val !== null && min <= numVal && numVal <= max) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'default') {
        color = t[type]
        flag = true
      } else if (t.term === 'null') {
        if (val === null || val === undefined || val === '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_null') {
        if (val !== null && val !== undefined && val !== '') {
          color = t[type]
          flag = true
        }
      }
      if (flag) {
        break
      }
    } else if (sourceField.deType === 0 || sourceField.deType === 5) {
      if (t.term === 'eq') {
        if (val === targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_eq') {
        if (val !== targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'like') {
        if (val && val.includes(targetValue)) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not like') {
        if (val && !val.includes(targetValue)) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'null') {
        if (val === null || val === undefined || val === '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_null') {
        if (val !== null && val !== undefined && val !== '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'default') {
        color = t[type]
        flag = true
      }
      if (flag) {
        break
      }
    } else {
      const fc = rule
      if (fc.term === 'null') {
        if (val === null || val === undefined || val === '') {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'not_null') {
        if (val !== null && val !== undefined && val !== '') {
          color = fc[type]
          flag = true
        }
      }
      if (flag) {
        break
      }
      // time
      if (!targetValue || !val) {
        break
      } else {
          // 特殊时间格式不转换, 包含时或者包含时、分时(不包含秒), 直接比较字符串，因为new Date转换会有误差
          const isSpecialTimeFormat = (dateStyle?: string) =>
            dateStyle === 'H_m_s' || (dateStyle && dateStyle.length > 5 && dateStyle.length < 11)

          let v: number | string
          let compareTv = targetValue;
          if (isSpecialTimeFormat(sourceField?.dateStyle)) {
            v = val
          } else {
            v = new Date(val.replace(/-/g, '/') + ' GMT+8').getTime()
            compareTv = new Date(targetValue.toString().replace(/-/g, '/') + ' GMT+8').getTime()
          }
          if (fc.term === 'eq') {
            if (v === compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'not_eq') {
            if (v !== compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'lt') {
            if (v < compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'gt') {
            if (v > compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'le') {
            if (v <= compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'ge') {
            if (v >= compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'default') {
            color = fc[type]
            flag = true
          }
      }
      if (flag) {
        break
      }
    }
  }

  if (!color) {
      color = defaultColor;
  }
  return color
}

export function getPivotConditions(chart: Chart) {
  const { threshold } = parseJson(chart.senior)
  if (!threshold.enable) {
    return
  }
  const res = {
    text: [],
    background: []
  }
  const conditions = threshold.tableThreshold ?? []

  const dimFields = [...chart.xAxis, ...chart.xAxisExt].map(i => i.dataeaseName)
  const allFields = [...chart.xAxis, ...chart.xAxisExt, ...chart.yAxis]
  const fieldIdToName = allFields.reduce((acc, f) => {
    acc[f.id] = f.dataeaseName
    return acc
  }, {})

  if (conditions?.length > 0) {
    const { tableCell, basicStyle, tableHeader } = parseJson(chart.customAttr)
    const enableTableCrossBG = tableCell.enableTableCrossBG
    // 单元格字体颜色
    const valueColor = isAlphaColor(tableCell.tableFontColor)
      ? tableCell.tableFontColor
      : hexColorToRGBA(tableCell.tableFontColor, basicStyle.alpha)
    // 单元格背景颜色
    const valueBgColor = enableTableCrossBG
      ? null
      : isAlphaColor(tableCell.tableItemBgColor)
      ? tableCell.tableItemBgColor
      : hexColorToRGBA(tableCell.tableItemBgColor, basicStyle.alpha)
    // 列头字体颜色
    const colHeaderValueColor = isAlphaColor(tableHeader.tableHeaderFontColor)
      ? tableHeader.tableHeaderFontColor
      : hexColorToRGBA(tableHeader.tableHeaderFontColor, basicStyle.alpha)
    // 列头背景颜色
    const colHeaderBgColor = isAlphaColor(tableHeader.tableHeaderBgColor)
      ? tableHeader.tableHeaderBgColor
      : hexColorToRGBA(tableHeader.tableHeaderBgColor, basicStyle.alpha)
    // 行头字体颜色
    const rowHeaderValueColor = isAlphaColor(tableHeader.tableHeaderColFontColor)
    ? tableHeader.tableHeaderColFontColor
    : hexColorToRGBA(tableHeader.tableHeaderColFontColor, basicStyle.alpha)
    // 行头背景颜色
    const rowHeaderBgColor = isAlphaColor(tableHeader.tableHeaderColBgColor)
      ? tableHeader.tableHeaderColBgColor
      : hexColorToRGBA(tableHeader.tableHeaderColBgColor, basicStyle.alpha)
    const filedValueMap = getFieldValueMap(chart)

    

    const targetRulesMap = {} // columnName -> Array<{ rule, sourceField }>
    const xFields = chart.xAxis.map(f => f.dataeaseName)
    const xExtFields = chart.xAxisExt.map(f => f.dataeaseName)
    const yFields = chart.yAxis.map(f => f.dataeaseName)
    for (let i = 0; i < conditions.length; i++) {
      const fieldItem = conditions[i]
      if (!fieldItem.conditions) continue;

      for (let j = 0; j < fieldItem.conditions.length; j++) {
        const rule = fieldItem.conditions[j]
        let targets = []
        if (rule.target === 'total_row') {
          if (xFields.includes(fieldItem.field.dataeaseName)) {
            targets.push(...xFields)
            if (basicStyle.quotaPosition === 'row') {
              targets.push(EXTRA_FIELD)
            }
          }
          if (xExtFields.includes(fieldItem.field.dataeaseName)) {
            targets.push(...xExtFields)
            if (basicStyle.quotaPosition !== 'row') {
              targets.push(EXTRA_FIELD)
            }
          }
          targets.push(...yFields)
        } else if (rule.target === 'custom' && rule.targetFieldId) {
          const targetName = fieldIdToName[rule.targetFieldId]
          if (targetName) targets = [targetName]
        } else {
          targets = [fieldItem.field.dataeaseName]
        }

        targets.forEach(targetName => {
          if (!targetRulesMap[targetName]) {
            targetRulesMap[targetName] = []
          }
          targetRulesMap[targetName].push({
            rule: rule,
            sourceField: fieldItem.field
          })
        })
      }
    }

    for (const targetName in targetRulesMap) {
      const rules = targetRulesMap[targetName]
      let defaultValueColor = valueColor
      let defaultBgColor = valueBgColor
      if (xFields.includes(targetName)) {
        defaultValueColor = rowHeaderValueColor
        defaultBgColor = rowHeaderBgColor
      }
      if (xExtFields.includes(targetName)) {
        defaultValueColor = colHeaderValueColor
        defaultBgColor = colHeaderBgColor
      }

      res.text.push({
        field: targetName,
        mapping(value, rowData) {
          if (!value && !rowData) {
            return null
          }
          // 角头
          if (rowData.cornerType) {
            return null
          }

          return {
            fill: mappingPivotColor(value, defaultValueColor, rules.toReversed(), 'color', filedValueMap, rowData)
          }
        }
      })
      res.background.push({
        field: targetName,
        mapping(value, rowData) {
          if (!value && !rowData) {
            return null
          }
          // 角头
          if (rowData.cornerType) {
            return null
          }
          
          const fill = mappingPivotColor(
            value,
            defaultBgColor,
            rules.toReversed(),
            'backgroundColor',
            filedValueMap,
            rowData
          )
          if (isTransparent(fill)) {
            return null
          }
          return { fill }
        }
      })
    }
  }
  return res
}

export function mappingPivotColor(value, defaultColor, rules, type, filedValueMap?, rowData?) {
  let color = null

  for (let i = 0; i < rules.length; i++) {
    const { rule, sourceField } = rules[i]
    let flag = false
    const t = rule
    let targetValue, max, min

    let checkValue = value;
    if (sourceField.dataeaseName) {
      checkValue = rowData?.[sourceField.dataeaseName]
      if (checkValue === undefined) {
        checkValue = rowData?.query?.[sourceField.dataeaseName]
      }
    }

    if (rowData.isGrandTotals || rowData.isSubTotals) {
      if (rule.target !== 'total_row') {
        return null
      }
      checkValue = rowData?.query?.[sourceField.dataeaseName]
    }

    if (rowData.field === EXTRA_FIELD) {
      if (rule.target !== 'total_row') {
        return null
      }
      checkValue = rowData?.query?.[sourceField.dataeaseName]
    }
    if (checkValue === undefined) {
      return null
    }

    if (t.type === 'dynamic') {
      if (t.term === 'between') {
        max = parseFloat(getValue(t.dynamicMaxField, filedValueMap, rowData))
        min = parseFloat(getValue(t.dynamicMinField, filedValueMap, rowData))
      } else {
        targetValue = getValue(t.dynamicField, filedValueMap, rowData)
      }
    } else {
      if (t.term === 'between') {
        min = parseFloat(t.min)
        max = parseFloat(t.max)
      } else {
        targetValue = t.value
      }
    }

    const val = checkValue;

    if (sourceField.deType === 2 || sourceField.deType === 3 || sourceField.deType === 4) {
      targetValue = parseFloat(targetValue)
      const numVal = parseFloat(val)
      if (t.term === 'eq') {
        if (numVal === targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_eq') {
        if (numVal !== targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'lt') {
        if (numVal < targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'gt') {
        if (numVal > targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'le') {
        if (val !== null && numVal <= targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'ge') {
        if (val !== null && numVal >= targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'between') {
        if (val !== null && min <= numVal && numVal <= max) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'default') {
        color = t[type]
        flag = true
      } else if (t.term === 'null') {
        if (val === null || val === undefined || val === '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_null') {
        if (val !== null && val !== undefined && val !== '') {
          color = t[type]
          flag = true
        }
      }
      if (flag) {
        break
      }
    } else if (sourceField.deType === 0 || sourceField.deType === 5) {
      if (t.term === 'eq') {
        if (val === targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_eq') {
        if (val !== targetValue) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'like') {
        if (val && val.includes(targetValue)) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not like') {
        if (val && !val.includes(targetValue)) {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'null') {
        if (val === null || val === undefined || val === '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'not_null') {
        if (val !== null && val !== undefined && val !== '') {
          color = t[type]
          flag = true
        }
      } else if (t.term === 'default') {
        color = t[type]
        flag = true
      }
      if (flag) {
        break
      }
    } else {
      const fc = rule
      if (fc.term === 'null') {
        if (val === null || val === undefined || val === '') {
          color = fc[type]
          flag = true
        }
      } else if (fc.term === 'not_null') {
        if (val !== null && val !== undefined && val !== '') {
          color = fc[type]
          flag = true
        }
      }
      if (flag) {
        break
      }
      // time
      if (!targetValue || !val) {
        break
      } else {
          // 特殊时间格式不转换, 包含时或者包含时、分时(不包含秒), 直接比较字符串，因为new Date转换会有误差
          const isSpecialTimeFormat = (dateStyle?: string) =>
            dateStyle === 'H_m_s' || (dateStyle && dateStyle.length > 5 && dateStyle.length < 11)

          let v: number | string
          let compareTv = targetValue;
          if (isSpecialTimeFormat(sourceField?.dateStyle)) {
            v = val
          } else {
            v = new Date(val.replace(/-/g, '/') + ' GMT+8').getTime()
            compareTv = new Date(targetValue.toString().replace(/-/g, '/') + ' GMT+8').getTime()
          }
          if (fc.term === 'eq') {
            if (v === compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'not_eq') {
            if (v !== compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'lt') {
            if (v < compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'gt') {
            if (v > compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'le') {
            if (v <= compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'ge') {
            if (v >= compareTv) {
              color = fc[type]
              flag = true
            }
          } else if (fc.term === 'default') {
            color = fc[type]
            flag = true
          }
      }
      if (flag) {
        break
      }
    }
  }

  if (!color) {
      color = defaultColor;
  }
  return color
}

function getFieldValueMap(view) {
  const fieldValueMap = {}
  if (view.data && view.data.dynamicAssistLines && view.data.dynamicAssistLines.length > 0) {
    view.data.dynamicAssistLines.forEach(ele => {
      fieldValueMap[ele.summary + '-' + ele.fieldId] = ele.value
    })
  }
  return fieldValueMap
}

function getValue(field, filedValueMap, rowData) {
  if (field.summary === 'value') {
    // 单元格数据
    let value =  rowData?.[field.field?.dataeaseName]
    // 表头数据
    if (value === undefined) {
      value = rowData.query?.[field.field?.dataeaseName]
    }
    return value
  } else {
    return filedValueMap[field.summary + '-' + field.fieldId]
  }
}

export function handleTableEmptyStrategy(chart: Chart) {
  let newData = (chart.data?.tableRow || []) as Record<string, any>[]
  let intersectionArr = []
  const senior = parseJson(chart.senior)
  let emptyDataStrategy = senior?.functionCfg?.emptyDataStrategy
  if (!emptyDataStrategy) {
    emptyDataStrategy = 'breakLine'
  }
  const emptyDataFieldCtrl = senior?.functionCfg?.emptyDataFieldCtrl
  if (emptyDataStrategy !== 'breakLine' && emptyDataFieldCtrl?.length && newData?.length) {
    const deNames = keys(newData[0])
    intersectionArr = intersection(deNames, emptyDataFieldCtrl)
  }
  if (intersectionArr.length) {
    newData = cloneDeep(newData)
    for (let i = newData.length - 1; i >= 0; i--) {
      for (let j = 0, tmp = intersectionArr.length; j < tmp; j++) {
        const deName = intersectionArr[j]
        if (newData[i][deName] === null) {
          if (emptyDataStrategy === 'setZero') {
            newData[i][deName] = 0
          }
          if (emptyDataStrategy === 'ignoreData') {
            newData = filter(newData, (_, index) => index !== i)
            break
          }
        }
      }
    }
  }
  return newData
}

export class SortTooltip extends BaseTooltip {
  show(showOptions) {
    const { iconName } = showOptions
    if (iconName) {
      this.showSortTooltip(showOptions)
      return
    }
    super.show(showOptions)
  }

  showSortTooltip(showOptions) {
    const { position, options, meta, event } = showOptions
    const { enterable } = getTooltipDefaultOptions(options)
    const { autoAdjustBoundary, adjustPosition } = this.spreadsheet.options.tooltip || {}
    this.visible = true
    this.options = showOptions
    const container = this['getContainer']()
    // 用 vue 手动 patch
    const vNode = createVNode(TableTooltip, {
      table: this.spreadsheet,
      meta
    })
    this.spreadsheet.tooltip.container.innerHTML = ''
    const childElement = document.createElement('div')
    this.spreadsheet.tooltip.container.appendChild(childElement)
    render(vNode, childElement)

    const { x, y } = getAutoAdjustPosition({
      spreadsheet: this.spreadsheet,
      position,
      tooltipContainer: container,
      autoAdjustBoundary
    })

    this.position = adjustPosition?.({ position: { x, y }, event }) ?? {
      x,
      y
    }

    setTooltipContainerStyle(container, {
      style: {
        left: `${this.position?.x}px`,
        top: `${this.position?.y}px`,
        pointerEvents: enterable ? 'all' : 'none',
        zIndex: 9999,
        position: 'absolute',
        color: 'black',
        background: 'white',
        fontSize: '16px'
      },
      visible: true
    })
  }
}

const SORT_DEFAULT =
  '<svg t="1711681787276" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4355" width="200" height="200"><path d="M922.345786 372.183628l-39.393195 38.687114L676.138314 211.079416l0 683.909301-54.713113 0L621.425202 129.010259l53.320393 0L922.345786 372.183628zM349.254406 894.989741 101.654214 651.815349l39.393195-38.687114 206.814276 199.792349L347.861686 129.010259l54.713113 0 0 765.978459L349.254406 894.988718z" fill="{fill}" p-id="4356"></path></svg>'
const SORT_UP =
  '<svg t="1711682928245" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11756" width="200" height="200"><path d="M960 704L512 256 64 704z" fill="{fill}" p-id="11757"></path></svg>'
const SORT_DOWN =
  '<svg t="1711681879346" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4655" width="200" height="200"><path d="M64 320l448 448 448-448z" fill="{fill}" p-id="4656"></path></svg>'

function svg2Base64(svg) {
  return `data:image/svg+xml;charset=utf-8;base64,${btoa(svg)}`
}

export function configHeaderInteraction(chart: Chart, option: S2Options) {
  const { tableHeaderFontColor, tableHeaderSort } = parseJson(chart.customAttr).tableHeader
  if (!tableHeaderSort) {
    return
  }
  const iconColor = tableHeaderFontColor ?? '#666'
  const sortDefault = svg2Base64(SORT_DEFAULT.replace('{fill}', iconColor))
  const sortUp = svg2Base64(SORT_UP.replace('{fill}', iconColor))
  const sortDown = svg2Base64(SORT_DOWN.replace('{fill}', iconColor))
  // 防止缓存
  const randomSuffix = Math.random()
  const sortIconMap = {
    asc: `customSortUp${randomSuffix}`,
    desc: `customSortDown${randomSuffix}`
  }
  option.customSVGIcons = [
    {
      name: `customSortDefault${randomSuffix}`,
      svg: sortDefault
    },
    {
      name: `customSortUp${randomSuffix}`,
      svg: sortUp
    },
    {
      name: `customSortDown${randomSuffix}`,
      svg: sortDown
    }
  ]
  option.headerActionIcons = [
    {
      iconNames: [
        `customSortDefault${randomSuffix}`,
        `customSortUp${randomSuffix}`,
        `customSortDown${randomSuffix}`
      ],
      belongsCell: 'colCell',
      displayCondition: (meta, iconName) => {
        if (meta.field === SERIES_NUMBER_FIELD) {
          return false
        }
        // 分组
        if (meta.colIndex === -1) {
          return false
        }
        const sortMethodMap = meta.spreadsheet.store.get('sortMethodMap')
        const sortType = sortMethodMap?.[meta.field]
        if (sortType) {
          return iconName === sortIconMap[sortType]
        }
        return iconName === `customSortDefault${randomSuffix}`
      },
      onClick: props => {
        const { meta, event } = props
        meta.spreadsheet.showTooltip({
          position: {
            x: event.clientX,
            y: event.clientY
          },
          event,
          ...props
        })
        const parent = document.getElementById(chart.container)
        if (parent?.childNodes?.length) {
          const child = Array.from(parent.childNodes)
            .filter(node => node.nodeType === Node.ELEMENT_NODE)
            .find(node => node.classList.contains('antv-s2-tooltip-container'))
          if (child) {
            const left = child.offsetLeft + child.clientWidth
            if (left > parent.offsetWidth) {
              const newLeft = parent.offsetWidth - child.clientWidth - 10
              child.style.left = `${newLeft}px`
            }
          }
        }
      }
    }
  ]
}

export function configTooltip(chart: Chart, option: S2Options) {
  const { tooltip } = parseJson(chart.customAttr)
  const textFontFamily = chart.fontFamily ? chart.fontFamily : FONT_FAMILY
  option.tooltip = {
    ...option.tooltip,
    style: {
      background: tooltip.backgroundColor,
      fontSize: tooltip.fontSize + 'px',
      fontFamily: textFontFamily,
      color: tooltip.color,
      boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 8px 0px',
      borderRadius: '3px',
      padding: '4px 12px',
      opacity: 0.95,
      position: 'absolute'
    },
    adjustPosition: ({ event }) => {
      return getTooltipPosition(event)
    }
  }
}

export function copyContent(s2Instance: SpreadSheet, event, fieldMeta) {
  event.preventDefault()
  const cell = s2Instance.getCell(event.target)
  const valueField = cell.getMeta().valueField
  const cellMeta = cell.getMeta()
  const selectState = s2Instance.interaction.getState()
  let content = ''
  // 多选
  if (selectState.stateName === InteractionStateName.SELECTED) {
    const { cells } = selectState
    if (!cells?.length) {
      return
    }
    if (cells.length === 1) {
      const curCell = cells[0]
      if (cell.getMeta().id === curCell.id) {
        const cellMeta = cell.getMeta()
        const valueField = cellMeta.valueField
        const value = cellMeta.data?.[valueField]
        const metaObj = find(fieldMeta, m => m.field === valueField)
        let fieldVal = value?.toString()
        if (metaObj) {
          fieldVal = metaObj.formatter(value)
        }
        if (fieldVal === undefined || fieldVal === null) {
          const fieldMap = fieldMeta?.reduce((p, n) => {
            p[n.field] = n.name
            return p
          }, {})
          fieldVal = cellMeta.value
          if (fieldMap?.[fieldVal]) {
            fieldVal = fieldMap[fieldVal]
          }
        }
        copyString(fieldVal, true)
      }
      s2Instance.interaction.clearState()
      return
    }
    const brushSelection = s2Instance.interaction.interactions.get(
      InteractionName.BRUSH_SELECTION
    ) as DataCellBrushSelection
    const selectedCells: TableDataCell[] = brushSelection.getScrollBrushRangeCells(cells)
    selectedCells.sort((a, b) => {
      const aMeta = a.getMeta()
      const bMeta = b.getMeta()
      if (aMeta.rowIndex !== bMeta.rowIndex) {
        return aMeta.rowIndex - bMeta.rowIndex
      }
      return aMeta.colIndex - bMeta.colIndex
    })
    // 点击已选的就复制，未选的就忽略
    let validClick = false
    const matrix = selectedCells.reduce((p, n) => {
      if (
        n.getMeta().colIndex === cellMeta.colIndex &&
        n.getMeta().rowIndex === cellMeta.rowIndex
      ) {
        validClick = true
      }
      const arr = p[n.getMeta().rowIndex]
      if (!arr) {
        p[n.getMeta().rowIndex] = [n]
      } else {
        arr.push(n)
      }
      return p
    }, {}) as Record<number, TableDataCell[]>
    if (validClick) {
      keys(matrix).forEach(k => {
        const arr = matrix[k] as TableDataCell[]
        arr.forEach((cell, index) => {
          const cellMeta = cell.getMeta()
          const valueField = cellMeta.valueField
          const value = cellMeta.data?.[valueField]
          const metaObj = find(fieldMeta, m => m.field === valueField)
          let fieldVal = value?.toString()
          if (metaObj) {
            fieldVal = metaObj.formatter(value)
          }
          if (fieldVal === undefined || fieldVal === null) {
            const fieldMap = fieldMeta?.reduce((p, n) => {
              p[n.field] = n.name
              return p
            }, {})
            fieldVal = cellMeta.value
            if (fieldMap?.[fieldVal]) {
              fieldVal = fieldMap[fieldVal]
            }
          }
          if (fieldVal === undefined || fieldVal === null) {
            fieldVal = ''
          }
          if (index !== arr.length - 1) {
            fieldVal += '\t'
          }
          content += fieldVal
        })
        content = content + '\n'
      })
      if (content) {
        copyString(content, true)
      }
    }
    s2Instance.interaction.clearState()
    return
  }
  // 单元格
  if (cellMeta?.data) {
    const value = cellMeta.data[valueField]
    const metaObj = find(fieldMeta, m => m.field === valueField)
    content = value?.toString()
    if (metaObj) {
      content = metaObj.formatter(value)
    }
  } else {
    // 列头&行头
    const fieldMap = fieldMeta?.reduce((p, n) => {
      p[n.field] = n.name
      return p
    }, {})
    content = cellMeta.value
    if (fieldMap?.[content]) {
      content = fieldMap[content]
    }
  }
  if (content) {
    copyString(content, true)
  }
}

function getTooltipPosition(event) {
  const s2Instance = event.s2Instance
  const { x, y } = event
  const result = { x: x + 15, y }
  if (!s2Instance) {
    return result
  }
  const { height, width } = s2Instance.getCanvasElement().getBoundingClientRect()
  const { offsetHeight, offsetWidth } = s2Instance.tooltip.getContainer()
  if (offsetWidth > width) {
    result.x = 0
  }
  if (offsetHeight > height) {
    result.y = 0
  }
  if (!(result.x || result.y)) {
    return result
  }
  if (result.x && result.x + offsetWidth > width) {
    result.x -= result.x + offsetWidth - width
  }
  if (result.y) {
    if (result.y > offsetHeight) {
      if (result.y - offsetHeight >= 15) {
        result.y -= offsetHeight + 15
      } else {
        result.y = 0
      }
    } else {
      result.y += 15
    }
  }
  return result
}

export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) {
  const { layoutResult } = instance.facet
  const { meta, fields } = instance.dataCfg
  const rowLength = fields?.rows?.length || 0
  const colLength = fields?.columns?.length || 0
  const colNums = layoutResult.colLeafNodes.length + rowLength
  if (colNums > 16384) {
    ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
    return
  }
  const workbook = new Exceljs.Workbook()
  const worksheet = workbook.addWorksheet(i18nt('chart.chart_data'))
  const metaMap: Record<string, Meta> = meta?.reduce((p, n) => {
    if (n.field) {
      p[n.field] = n
    }
    return p
  }, {})
  // 角头
  fields.columns?.forEach((column, index) => {
    const cell = worksheet.getCell(index + 1, 1)
    cell.value = metaMap[column]?.name ?? column
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (rowLength >= 2) {
      worksheet.mergeCells(index + 1, 1, index + 1, rowLength)
    }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  fields?.rows?.forEach((row, index) => {
    const cell = worksheet.getCell(colLength + 1, index + 1)
    cell.value = metaMap[row]?.name ?? row
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    cell.border = {
      bottom: { style: 'thick', color: { argb: '00000000' } }
    }
    if (index === fields.rows.length - 1) {
      cell.border.right = { style: 'thick', color: { argb: '00000000' } }
    }
  })
  // 行头
  const { rowLeafNodes, rowsHierarchy, rowNodes } = layoutResult
  const maxColIndex = rowsHierarchy.maxLevel + 1
  const notLeafNodeHeightMap: Record<string, number> = {}
  rowLeafNodes.forEach(node => {
    // 行头的高度由子节点相加决定，也就是行头子节点中包含的叶子节点数量
    let curNode = node.parent
    while (curNode) {
      const height = notLeafNodeHeightMap[curNode.id] ?? 0
      notLeafNodeHeightMap[curNode.id] = height + 1
      curNode = curNode.parent
    }
    const { rowIndex } = node
    const writeRowIndex = rowIndex + 1 + colLength + 1
    const writeColIndex = node.level + 1
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    cell.value = node.label
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (writeColIndex < maxColIndex) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, writeRowIndex, maxColIndex)
    }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })

  const getNodeStartRowIndex = (node: Node) => {
    if (!node.children?.length) {
      return node.rowIndex + 1
    } else {
      return getNodeStartRowIndex(node.children[0])
    }
  }
  rowNodes?.forEach(node => {
    if (node.isLeaf) {
      return
    }
    const rowIndex = getNodeStartRowIndex(node)
    const height = notLeafNodeHeightMap[node.id]
    const writeRowIndex = rowIndex + colLength + 1
    const mergeColCount = node.children[0].level - node.level
    const value = node.label
    const cell = worksheet.getCell(writeRowIndex, node.level + 1)
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (mergeColCount > 1 || height > 1) {
      worksheet.mergeCells(
        writeRowIndex,
        node.level + 1,
        writeRowIndex + height - 1,
        node.level + mergeColCount
      )
    }
  })

  // 列头
  const { colLeafNodes, colNodes, colsHierarchy } = layoutResult
  const maxColHeight = colsHierarchy.maxLevel + 1
  const notLeafNodeWidthMap: Record<string, number> = {}
  colLeafNodes.forEach(node => {
    // 列头的宽度由子节点相加决定，也就是列头子节点中包含的叶子节点数量
    let curNode = node.parent
    while (curNode) {
      const width = notLeafNodeWidthMap[curNode.id] ?? 0
      notLeafNodeWidthMap[curNode.id] = width + 1
      curNode = curNode.parent
    }
    const { colIndex } = node
    const writeRowIndex = node.level + 1
    const writeColIndex = colIndex + 1 + rowLength
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    let value = node.label
    if (node.field === '$$extra$$' && metaMap[value]?.name) {
      value = metaMap[value].name
    }
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (writeRowIndex < maxColHeight) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex)
    }
    cell.border = {
      bottom: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  const getNodeStartColIndex = (node: Node) => {
    if (!node.children?.length) {
      return node.colIndex + 1
    } else {
      return getNodeStartColIndex(node.children[0])
    }
  }
  colNodes.forEach(node => {
    if (node.isLeaf) {
      return
    }
    const colIndex = getNodeStartColIndex(node)
    const width = notLeafNodeWidthMap[node.id]
    const writeRowIndex = node.level + 1
    const mergeRowCount = node.children[0].level - node.level
    const value = node.label
    const writeColIndex = colIndex + rowLength
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (mergeRowCount > 1 || width > 1) {
      worksheet.mergeCells(
        writeRowIndex,
        writeColIndex,
        writeRowIndex + mergeRowCount - 1,
        writeColIndex + width - 1
      )
    }
  })
  const formatterMap = chart.yAxis.reduce((p, n) => {
    if (n.dataeaseName) {
      p[n.dataeaseName] = n.formatterCfg
    }
    return p
  }, {})
  //  单元格数据
  for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) {
    for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) {
      const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex)
      const { fieldValue } = dataCellMeta
      const cell = worksheet.getCell(rowIndex + maxColHeight + 1, rowLength + colIndex + 1)
      cell.alignment = { vertical: 'middle', horizontal: 'center' }
      if (fieldValue === "-" || fieldValue === null || fieldValue === undefined) {
        cell.value = '-'
        continue
      }
      const meta = metaMap[dataCellMeta.valueField]
      const value = meta?.formatter?.(fieldValue) || fieldValue
      if (typeof value === 'number') {
        cell.value = value
      } else if (typeof value === 'string') {
        const formatterCfg = formatterMap?.[dataCellMeta.valueField]
        const result = extractNumber(value, formatterCfg)
        if (typeof result === 'string') {
          cell.value = result
        } else {
          cell.value = result.value
          cell.numFmt = result.numFmt
        }
      }
    }
  }
  const buffer = await workbook.xlsx.writeBuffer()
  const dataBlob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
  })
  saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
}

export async function exportRowQuotaGridPivot(instance: PivotSheet, chart: ChartObj) {
  const { layoutResult } = instance.facet
  const { meta, fields } = instance.dataCfg
  const rowLength = fields?.rows?.length || 0
  const colLength = fields?.columns?.length || 0
  const colNums = layoutResult.colLeafNodes.length + rowLength
  if (colNums > 16384) {
    ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
    return
  }
  const workbook = new Exceljs.Workbook()
  const worksheet = workbook.addWorksheet(i18nt('chart.chart_data'))
  const metaMap: Record<string, Meta> = meta?.reduce((p, n) => {
    if (n.field) {
      p[n.field] = n
    }
    return p
  }, {})
  // 角头
  if (colLength > 1) {
    fields.columns.forEach((column: string, index) => {
      if (index >= colLength - 1) {
        return
      }
      const cell = worksheet.getCell(index + 1, 1)
      cell.value = metaMap[column]?.name ?? column
      cell.alignment = { vertical: 'middle', horizontal: 'center' }
      cell.border = {
        right: { style: 'thick', color: { argb: '00000000' } }
      }
      worksheet.mergeCells(index + 1, 1, index + 1, rowLength + 1)
    })
  }
  fields?.rows?.forEach((row, index) => {
    const cell = worksheet.getCell(colLength === 0 ? 1 : colLength, index + 1)
    cell.value = metaMap[row]?.name ?? row
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    cell.border = { bottom: { style: 'thick', color: { argb: '00000000' } } }
  })
  const quotaColLabel = chart.customAttr.basicStyle.quotaColLabel ?? t('dataset.value')
  const quotaColHeadCell = worksheet.getCell(colLength === 0 ? 1 : colLength, rowLength + 1)
  quotaColHeadCell.value = quotaColLabel
  quotaColHeadCell.alignment = { vertical: 'middle', horizontal: 'center' }
  quotaColHeadCell.border = {
    bottom: { style: 'thick', color: { argb: '00000000' } },
    right: { style: 'thick', color: { argb: '00000000' } }
  }
  // 行头
  const { rowLeafNodes, rowNodes } = layoutResult
  const notLeafNodeHeightMap: Record<string, number> = {}
  rowLeafNodes.forEach(node => {
    // 行头的高度由子节点相加决定，也就是行头子节点中包含的叶子节点数量
    let curNode = node.parent
    while (curNode) {
      const height = notLeafNodeHeightMap[curNode.id] ?? 0
      notLeafNodeHeightMap[curNode.id] = height + 1
      curNode = curNode.parent
    }
    const { rowIndex } = node
    const writeRowIndex = rowIndex + 2 + (colLength === 0 ? 1 : colLength - 1)
    const writeColIndex = node.level + 1
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    let value = node.label
    if (node.field === '$$extra$$' && metaMap[value]?.name) {
      value = metaMap[value].name
    }
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })

  const getNodeStartRowIndex = (node: Node) => {
    if (!node.children?.length) {
      return node.rowIndex + 1
    } else {
      return getNodeStartRowIndex(node.children[0])
    }
  }
  rowNodes?.forEach(node => {
    if (node.isLeaf) {
      return
    }
    const rowIndex = getNodeStartRowIndex(node)
    const height = notLeafNodeHeightMap[node.id]
    const writeRowIndex = rowIndex + 1 + (colLength === 0 ? 1 : colLength - 1)
    const mergeColCount = node.children[0].level - node.level
    const cell = worksheet.getCell(writeRowIndex, node.level + 1)
    cell.value = node.label
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (mergeColCount > 1 || height > 1) {
      worksheet.mergeCells(
        writeRowIndex,
        node.level + 1,
        writeRowIndex + height - 1,
        node.level + mergeColCount
      )
    }
  })

  // 列头
  const { colLeafNodes, colNodes, colsHierarchy } = layoutResult
  const maxColHeight = colsHierarchy.maxLevel + 1
  const notLeafNodeWidthMap: Record<string, number> = {}
  colLeafNodes.forEach(node => {
    // 列头的宽度由子节点相加决定，也就是列头子节点中包含的叶子节点数量
    let curNode = node.parent
    while (curNode) {
      const width = notLeafNodeWidthMap[curNode.id] ?? 0
      notLeafNodeWidthMap[curNode.id] = width + 1
      curNode = curNode.parent
    }
    const { colIndex } = node
    const writeRowIndex = node.level + 1
    const writeColIndex = colIndex + rowLength + 2
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    const value = node.label
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (writeRowIndex < maxColHeight) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex)
    }
    cell.border = {
      bottom: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  const getNodeStartColIndex = (node: Node) => {
    if (!node.children?.length) {
      return node.colIndex + 1
    } else {
      return getNodeStartColIndex(node.children[0])
    }
  }
  colNodes.forEach(node => {
    if (node.isLeaf) {
      return
    }
    const colIndex = getNodeStartColIndex(node)
    const width = notLeafNodeWidthMap[node.id]
    const writeRowIndex = node.level + 1
    const value = node.label
    const writeColIndex = colIndex + rowLength + 1
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (width > 1) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, writeRowIndex, writeColIndex + width - 1)
    }
  })
  const formatterMap = chart.yAxis.reduce((p, n) => {
    if (n.dataeaseName) {
      p[n.dataeaseName] = n.formatterCfg
    }
    return p
  }, {})
  //  单元格数据
  for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) {
    for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) {
      const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex)
      const { fieldValue } = dataCellMeta
      const cell = worksheet.getCell(rowIndex + maxColHeight + 1, rowLength + colIndex + 2)
      cell.alignment = { vertical: 'middle', horizontal: 'center' }
      if (fieldValue === "-" || fieldValue === null || fieldValue === undefined) {
        cell.value = '-'
        continue
      }
      const meta = metaMap[dataCellMeta.valueField]
      const value = meta?.formatter?.(fieldValue) || fieldValue
      if (typeof value === 'number') {
        cell.value = value
      } else if (typeof value === 'string') {
        const formatterCfg = formatterMap?.[dataCellMeta.valueField]
        const result = extractNumber(value, formatterCfg)
        if (typeof result === 'string') {
          cell.value = result
        } else {
          cell.value = result.value
          cell.numFmt = result.numFmt
        }
      }
    }
  }
  const buffer = await workbook.xlsx.writeBuffer()
  const dataBlob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
  })
  saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
}

export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) {
  const layoutResult = instance.facet.layoutResult
  if (layoutResult.colLeafNodes.length + 1 > 16384) {
    ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
    return
  }
  const { meta, fields } = instance.dataCfg
  const colLength = fields?.columns?.length || 0
  const workbook = new Exceljs.Workbook()
  const worksheet = workbook.addWorksheet(i18nt('chart.chart_data'))
  const metaMap: Record<string, Meta> = meta?.reduce((p, n) => {
    if (n.field) {
      p[n.field] = n
    }
    return p
  }, {})

  // 角头
  fields.columns?.forEach((column, index) => {
    const cell = worksheet.getCell(index + 1, 1)
    cell.value = metaMap[column]?.name ?? column
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  const maxColHeight = layoutResult.colsHierarchy.maxLevel + 1
  const rowName = fields?.rows?.map(row => metaMap[row]?.name ?? row).join('/')
  const cell = worksheet.getCell(colLength + 1, 1)
  cell.value = rowName
  cell.alignment = { vertical: 'middle', horizontal: 'center' }
  cell.border = {
    right: { style: 'thick', color: { argb: '00000000' } },
    bottom: { style: 'thick', color: { argb: '00000000' } }
  }
  //行头
  const { rowLeafNodes } = layoutResult
  rowLeafNodes.forEach((node, index) => {
    const cell = worksheet.getCell(maxColHeight + index + 1, 1)
    cell.value = repeat('  ', node.level) + node.label
    cell.alignment = { vertical: 'middle', horizontal: 'left' }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  // 列头
  const notLeafNodeWidthMap: Record<string, number> = {}
  const { colLeafNodes } = layoutResult
  colLeafNodes.forEach(node => {
    let curNode = node.parent
    while (curNode) {
      const width = notLeafNodeWidthMap[curNode.id] ?? 0
      notLeafNodeWidthMap[curNode.id] = width + 1
      curNode = curNode.parent
    }
    const { colIndex } = node
    const writeRowIndex = node.level + 1
    const writeColIndex = colIndex + 1 + 1
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    let value = node.label
    if (node.field === '$$extra$$' && metaMap[value]?.name) {
      value = metaMap[value].name
    }
    cell.value = value
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (writeRowIndex < maxColHeight) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex)
    }
    cell.border = {
      bottom: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  const colNodes = layoutResult.colNodes
  const getNodeStartIndex = (node: Node) => {
    if (!node.children?.length) {
      return node.colIndex + 1
    } else {
      return getNodeStartIndex(node.children[0])
    }
  }
  colNodes.forEach(node => {
    if (node.isLeaf) {
      return
    }
    const colIndex = getNodeStartIndex(node)
    const width = notLeafNodeWidthMap[node.id]
    const writeRowIndex = node.level + 1
    const mergeRowCount = node.children[0].level - node.level
    const writeColIndex = colIndex + 1
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    cell.value = node.label
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (mergeRowCount > 1 || width > 1) {
      worksheet.mergeCells(
        writeRowIndex,
        writeColIndex,
        writeRowIndex + mergeRowCount - 1,
        writeColIndex + width - 1
      )
    }
  })
  const formatterMap = chart.yAxis.reduce((p, n) => {
    if (n.dataeaseName) {
      p[n.dataeaseName] = n.formatterCfg
    }
    return p
  }, {})
  //  单元格数据
  for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) {
    for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) {
      const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex)
      const { fieldValue } = dataCellMeta
      const cell = worksheet.getCell(rowIndex + maxColHeight + 1, colIndex + 1 + 1)
      cell.alignment = { vertical: 'middle', horizontal: 'center' }
      if (fieldValue === "-" || fieldValue === null || fieldValue === undefined) {
        cell.value = '-'
        continue
      }
      const meta = metaMap[dataCellMeta.valueField]
      const value = meta?.formatter?.(fieldValue) || fieldValue
      if (typeof value === 'number') {
        cell.value = value
      } else if (typeof value === 'string') {
        const formatterCfg = formatterMap?.[dataCellMeta.valueField]
        const result = extractNumber(value, formatterCfg)
        if (typeof result === 'string') {
          cell.value = result
        } else {
          cell.value = result.value
          cell.numFmt = result.numFmt
        }
      }
    }
  }
  const buffer = await workbook.xlsx.writeBuffer()
  const dataBlob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
  })
  saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
}

export async function exportRowQuotaTreePivot(instance: PivotSheet, chart: ChartObj) {
  const layoutResult = instance.facet.layoutResult
  if (layoutResult.colLeafNodes.length + 1 > 16384) {
    ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
    return
  }
  const { meta, fields } = instance.dataCfg
  const colLength = fields?.columns?.length || 0
  const workbook = new Exceljs.Workbook()
  const worksheet = workbook.addWorksheet(i18nt('chart.chart_data'))
  const metaMap: Record<string, Meta> = meta?.reduce((p, n) => {
    if (n.field) {
      p[n.field] = n
    }
    return p
  }, {})

  // 角头
  fields.columns?.forEach((column, index) => {
    if (index >= fields.columns.length - 1) {
      return
    }
    const cell = worksheet.getCell(index + 1, 1)
    cell.value = metaMap[column]?.name ?? column
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  const quotaColLabel = chart.customAttr.basicStyle.quotaColLabel ?? t('dataset.value')
  const maxColHeight = layoutResult.colsHierarchy.maxLevel + 1
  const rowName = fields?.rows
    ?.map(row => metaMap[row]?.name ?? row)
    .concat(quotaColLabel)
    .join('/')
  const cell = worksheet.getCell(colLength, 1)
  cell.value = rowName
  cell.alignment = { vertical: 'middle', horizontal: 'center' }
  cell.border = {
    right: { style: 'thick', color: { argb: '00000000' } },
    bottom: { style: 'thick', color: { argb: '00000000' } }
  }
  //行头
  const { rowLeafNodes } = layoutResult
  rowLeafNodes.forEach((node, index) => {
    const cell = worksheet.getCell(maxColHeight + index + 1, 1)
    let value = node.label
    if (node.field === '$$extra$$' && metaMap[value]?.name) {
      value = metaMap[value].name
    }
    cell.value = repeat('  ', node.level) + value
    cell.alignment = { vertical: 'middle', horizontal: 'left' }
    cell.border = {
      right: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  // 列头
  const notLeafNodeWidthMap: Record<string, number> = {}
  const { colLeafNodes } = layoutResult
  colLeafNodes.forEach(node => {
    let curNode = node.parent
    while (curNode) {
      const width = notLeafNodeWidthMap[curNode.id] ?? 0
      notLeafNodeWidthMap[curNode.id] = width + 1
      curNode = curNode.parent
    }
    const { colIndex } = node
    const writeRowIndex = node.level + 1
    const writeColIndex = colIndex + 2
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    cell.value = node.label
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (writeRowIndex < maxColHeight) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex)
    }
    cell.border = {
      bottom: { style: 'thick', color: { argb: '00000000' } }
    }
  })
  const colNodes = layoutResult.colNodes
  const getNodeStartIndex = (node: Node) => {
    if (!node.children?.length) {
      return node.colIndex + 1
    } else {
      return getNodeStartIndex(node.children[0])
    }
  }
  colNodes.forEach(node => {
    if (node.isLeaf) {
      return
    }
    const colIndex = getNodeStartIndex(node)
    const width = notLeafNodeWidthMap[node.id]
    const writeRowIndex = node.level + 1
    const writeColIndex = colIndex + 1
    const cell = worksheet.getCell(writeRowIndex, writeColIndex)
    cell.value = node.label
    cell.alignment = { vertical: 'middle', horizontal: 'center' }
    if (width > 1) {
      worksheet.mergeCells(writeRowIndex, writeColIndex, writeRowIndex, writeColIndex + width - 1)
    }
  })
  const formatterMap = chart.yAxis.reduce((p, n) => {
    if (n.dataeaseName) {
      p[n.dataeaseName] = n.formatterCfg
    }
    return p
  }, {})
  //  单元格数据
  for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) {
    for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) {
      const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex)
      const { fieldValue } = dataCellMeta
      const cell = worksheet.getCell(rowIndex + maxColHeight + 1, colIndex + 2)
      cell.alignment = { vertical: 'middle', horizontal: 'center' }
      if (fieldValue === "-" || fieldValue === null || fieldValue === undefined) {
        cell.value = '-'
        continue
      }
      const meta = metaMap[dataCellMeta.valueField]
      const value = meta?.formatter?.(fieldValue) || fieldValue
      if (typeof value === 'number') {
        cell.value = value
      } else if (typeof value === 'string') {
        const formatterCfg = formatterMap?.[dataCellMeta.valueField]
        const result = extractNumber(value, formatterCfg)
        if (typeof result === 'string') {
          cell.value = result
        } else {
          cell.value = result.value
          cell.numFmt = result.numFmt
        }
      }
    }
  }
  const buffer = await workbook.xlsx.writeBuffer()
  const dataBlob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
  })
  saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
}

function extractNumber(
  formattedValue: string,
  formatterCfg: BaseFormatter
):
  | {
      value: number
      numFmt: string
    }
  | string {
  if (!formatterCfg) {
    return formattedValue
  }
  let result = formattedValue
  if (formatterCfg.type === 'percent') {
    result = result.slice(0, -1) // 去掉百分号
    if (formatterCfg.thousandSeparator) {
      result = result.replace(/,/g, '')
    }
    //科学计数法
    if (result.includes('e')) {
      const valueArr = result.match(/^[+-]?\d+(\.\d+)?(e[+-]?\d+)?/)
      if (!valueArr?.length) {
        return formattedValue
      }
      const valueStr = valueArr[0]
      const value = parseFloat(valueStr)
      let numFmt = '0.'
      const number = valueStr.split('e')[0]
      numFmt += '0'.repeat(number.slice(1).length)
      numFmt += 'E+0"%"'
      return {
        value,
        numFmt
      }
    }
    const value = parseFloat(result) / 100
    let numFmt = '0'
    if (formatterCfg.decimalCount > 0) {
      numFmt += `.${'0'.repeat(formatterCfg.decimalCount)}`
    }
    numFmt += '%'
    return {
      value,
      numFmt
    }
  }
  if (formatterCfg.suffix) {
    const suffix = formatterCfg.suffix
    if (result.endsWith(suffix)) {
      result = result.slice(0, -suffix.length)
    }
  }
  if (formatterCfg.thousandSeparator) {
    result = result.replace(/,/g, '')
  }
  //科学计数法
  if (result.includes('e')) {
    const valueArr = result.match(/^[+-]?\d+(\.\d+)?(e[+-]?\d+)?/)
    if (!valueArr?.length) {
      return formattedValue
    }
    const valueStr = valueArr[0]
    const value = parseFloat(valueStr)
    let numFmt = '0.'
    const number = valueStr.split('e')[0]
    numFmt += '0'.repeat(number.slice(1).length)
    numFmt += 'E+0'
    const suffix = formattedValue.slice(valueStr.length)
    if (suffix) {
      numFmt += `"${suffix}"`
    }
    return {
      value,
      numFmt
    }
  }
  const valueArr = result.match(/^[+-]?\d+(\.\d+)?/)
  if (!valueArr?.length) {
    return formattedValue
  }
  const valueStr = valueArr[0]
  const value = parseFloat(valueStr)
  const unit = result.slice(valueStr.length)
  let numFmt = '#'
  if (formatterCfg.thousandSeparator) {
    numFmt += ',#'
  }
  if (Math.abs(value) < 1) {
    numFmt = '0'
  }
  if (formatterCfg.type === 'value') {
    if (formatterCfg.decimalCount > 0) {
      numFmt += `.${'0'.repeat(formatterCfg.decimalCount)}`
    }
  } else {
    if (valueStr.indexOf('.') > -1) {
      const decimalLength = valueStr.split('.')[1].length
      numFmt += `.${'0'.repeat(decimalLength)}`
    }
  }
  if (unit) {
    numFmt += `"${unit}"`
  }
  numFmt += `"${formatterCfg.suffix}"`
  return {
    value,
    numFmt
  }
}

export async function exportPivotExcel(instance: PivotSheet, chart: ChartObj) {
  const { fields } = instance.dataCfg
  const rowLength = fields?.rows?.length || 0
  const valueLength = fields?.values?.length || 0
  if (!(rowLength && valueLength)) {
    ElMessage.warning(i18nt('chart.pivot_export_invalid_field'))
    return
  }
  const { quotaPosition } = chart.customAttr.basicStyle
  if (chart.customAttr.basicStyle.tableLayoutMode !== 'tree') {
    if (quotaPosition === 'row') {
      exportRowQuotaGridPivot(instance, chart)
    } else {
      exportGridPivot(instance, chart)
    }
  } else {
    if (quotaPosition === 'row') {
      exportRowQuotaTreePivot(instance, chart)
    } else {
      exportTreePivot(instance, chart)
    }
  }
}

export function configMergeCells(chart: Chart, options: S2Options, dataConfig: S2DataConfig) {
  const { mergeCells } = parseJson(chart.customAttr).tableCell
  const { showIndex } = parseJson(chart.customAttr).tableHeader
  if (mergeCells) {
    options.frozenColCount = 0
    options.frozenRowCount = 0
    const fields = chart.data.fields || []
    const fieldsMap =
      fields.reduce((p, n) => {
        p[n.dataeaseName] = n
        return p
      }, {}) || {}
    const quotaIndex = dataConfig.meta.findIndex(m => fieldsMap[m.field]?.groupType === 'q')
    const data = chart.data?.tableRow
    if (quotaIndex === 0 || !data?.length) {
      return
    }
    const mergedColInfo: number[][][] = [[[0, data.length - 1]]]
    const mergedCellsInfo = []
    const axisToMerge = dataConfig.meta.filter((_, i) => i < quotaIndex || quotaIndex === -1)
    axisToMerge.forEach((a, i) => {
      if (mergedColInfo[i - 1]) {
        mergedColInfo[i - 1] = []
      }
      const preMergedColInfo = mergedColInfo[i]
      const curMergedColInfo = []
      mergedColInfo.push(curMergedColInfo)
      preMergedColInfo.forEach(range => {
        const [start, end] = range
        let lastVal = data[start][a.field]
        let lastIndex = start
        for (let index = start; index <= end; index++) {
          const curVal = data[index][a.field]
          if (curVal !== lastVal || index === end) {
            const curRange = index - lastIndex
            if (curRange > 1 || (index === end && curRange === 1 && lastVal === curVal)) {
              const tmpMergeCells = []
              let textIndex = curRange % 2 === 1 ? (curRange - 1) / 2 : curRange / 2 - 1
              if (index === end && lastVal === curVal) {
                textIndex = curRange % 2 === 1 ? (curRange - 1) / 2 : curRange / 2
              }
              for (let j = 0; j < curRange; j++) {
                tmpMergeCells.push({
                  colIndex: showIndex ? i + 1 : i,
                  rowIndex: lastIndex + j,
                  showText: j === textIndex
                })
              }
              if (index === end && lastVal === curVal) {
                tmpMergeCells.push({
                  colIndex: showIndex ? i + 1 : i,
                  rowIndex: index,
                  showText: false
                })
              }
              mergedCellsInfo.push(tmpMergeCells)
              curMergedColInfo.push([
                lastIndex,
                index === end && lastVal === curVal ? index : index - 1
              ])
            }
            lastVal = curVal
            lastIndex = index
          }
        }
      })
    })
    if (showIndex) {
      const indexMergedCells = mergedCellsInfo.filter(cells => cells[0].colIndex === 1)
      indexMergedCells.forEach(cells => {
        const tmpCells = cloneDeep(cells)
        tmpCells.forEach(cell => {
          cell.colIndex = 0
        })
        mergedCellsInfo.unshift(tmpCells)
      })
    }
    options.mergedCellsInfo = mergedCellsInfo
    options.mergedCell = (sheet, cells, meta) => {
      if (showIndex && meta.colIndex === 0) {
        meta.fieldValue = getRowIndex(mergedCellsInfo, meta)
      }
      meta.deFieldType = fieldsMap[meta.valueField]?.deType
      return new CustomMergedCell(sheet, cells, meta)
    }
  }
}

export function getRowIndex(mergedCellsInfo: MergedCellInfo[][], meta: ViewMeta): number {
  if (!mergedCellsInfo?.length) {
    return meta.rowIndex + 1
  }
  let curRangeStartIndex = meta.rowIndex
  const lostCells = mergedCellsInfo.reduce((p, n) => {
    if (n[0].colIndex !== 0) {
      return p
    }
    const start = n[0].rowIndex
    const end = n[n.length - 1].rowIndex
    const lost = end - start
    if (meta.rowIndex >= start && meta.rowIndex <= end) {
      curRangeStartIndex = start
    }
    if (meta.rowIndex > end) {
      return p + lost
    }
    return p
  }, 0)
  return curRangeStartIndex - lostCells + 1
}

class CustomMergedCell extends MergedCell {
  protected drawBackgroundShape() {
    const allPoints = getPolygonPoints(this.cells)
    // 处理条件样式，这里没有用透明度
    // 因为合并的单元格是单独的图层，透明度降低的话会显示底下未合并的单元格，需要单独处理被覆盖的单元格
    const { backgroundColor: fill } = this.getBackgroundColor()
    const cellTheme = this.theme.dataCell.cell
    this.backgroundShape = renderPolygon(this, {
      points: allPoints,
      stroke: cellTheme.horizontalBorderColor,
      fill,
      lineHeight: cellTheme.horizontalBorderWidth
    })
  }


  protected getTextStyle() {
    const textStyle = super.getTextStyle()
    const dataCellAlignConfig = this.theme.dataCellAlignConfig
    if (dataCellAlignConfig) {
      const align = dataCellAlignConfig[this.meta.valueField]
      if (align) {
        textStyle.textAlign = align
      }
    }
    if (textStyle.textAlign === 'custom') {
      textStyle.textAlign = 'left'
    }
    return textStyle
  }

  drawTextShape(): void {
    if (this.meta.deFieldType === 7) {
      drawImage.apply(this)
    } else {
      super.drawTextShape()
    }
  }
}

export class CustomDataCell extends TableDataCell {
  /**
   * 重写这个方法是为了处理底部的汇总行取消 hover 状态时设置 border 为 1,
   * 这样会导致单元格隐藏横边边框失败，出现一条白线
   */
  hideInteractionShape() {
    this.stateShapes.forEach(shape => {
      updateShapeAttr(shape, SHAPE_STYLE_MAP.backgroundOpacity, 0)
      updateShapeAttr(shape, SHAPE_STYLE_MAP.backgroundColor, 'transparent')
      updateShapeAttr(shape, SHAPE_STYLE_MAP.borderOpacity, 0)
      updateShapeAttr(shape, SHAPE_STYLE_MAP.borderWidth, 0)
      updateShapeAttr(shape, SHAPE_STYLE_MAP.borderColor, 'transparent')
    })
  }

  public getBackgroundColor() {
    let bgColorInfo = super.getBackgroundColor()
    if (this.meta.isMergedCell) {
      bgColorInfo = {
        ...bgColorInfo,
        backgroundColorOpacity: 0
      }
    }
    return bgColorInfo
  }

  protected getTextStyle() {
    const textStyle = super.getTextStyle()
    const dataCellAlignConfig = this.theme.dataCellAlignConfig
    if (dataCellAlignConfig) {
      const align = dataCellAlignConfig[this.meta.valueField]
      if (align) {
        textStyle.textAlign = align
      }
    }
    if (textStyle.textAlign === 'custom') {
      textStyle.textAlign = 'left'
    }
    return textStyle
  }

  /**
   * 重写绘制文本内容的方法
   * @protected
   */
  protected drawTextShape() {
    if(this.meta.isMergedCell) {
      return
    }
    if (this.meta.autoWrap) {
      drawTextShape(this, false)
    } else {
      super.drawTextShape()
    }
  }
}

export class CustomTableColCell extends TableColCell {

  protected getTextStyle() {
    const textStyle = super.getTextStyle()
    const colCellAlignConfig = this.theme.colCellAlignConfig
    if (colCellAlignConfig) {
      // 分组单元格居中
      if (this.meta.children?.length) {
        textStyle.textAlign = 'center'
        return textStyle
      }
      const align = colCellAlignConfig[this.meta.field]
      if (align) {
        textStyle.textAlign = align
      }
    }
    if (textStyle.textAlign === 'custom') {
      textStyle.textAlign = 'left'
    }
    return textStyle
  }

  /**
   * 重写是为了表头文本内容的换行
   * @protected
   */
  protected drawTextShape() {
    if (this.meta.autoWrap) {
      drawTextShape(this, true)
    } else {
      super.drawTextShape()
    }
  }
}

/**
 * 绘制文本 换行
 * @param cell
 * @param isHeader
 */
const drawTextShape = (cell, isHeader) => {
  // 换行符
  const lineBreak = '\n'
  // 省略号
  const ellipsis = '...'
  // 用户配置的最大行数
  const maxLines = cell.meta.maxLines ?? 1
  const {
    options: { placeholder }
  } = cell.spreadsheet
  const emptyPlaceholder = getEmptyPlaceholder(this, placeholder)
  // 单元格文本
  const { formattedValue } = cell.getFormattedFieldValue()
  // 获取文本样式
  const textStyle = cloneDeep(cell.getTextStyle())
  textStyle.textAlign = undefined
  // 宽度能放几个字符，就放几个，放不下就换行
  let wrapText = getWrapText(
    formattedValue ? formattedValue?.toString() : emptyPlaceholder,
    textStyle,
    cell.meta.width,
    cell.spreadsheet
  )
  const lines = wrapText.split(lineBreak)
  let extraStyleFontSize = textStyle.fontSize
  // 不是表头，处理文本高度和换行
  if (!isHeader) {
    const textHeight = getWrapTextHeight(
      wrapText.replaceAll(lineBreak, ''),
      textStyle,
      cell.spreadsheet,
      maxLines
    )
    const lineCountInCell = Math.floor(cell.meta.height / textHeight)
    const wrapTextArr = lines.slice(0, lineCountInCell)

    // 根据行数调整换行后的文本内容
    wrapText = lineCountInCell < 1 ? ellipsis : wrapTextArr.join(lineBreak) || ellipsis
    const resultWrapArr = wrapText.split(lineBreak)
    // 控制最大行数
    if (
      !wrapText.endsWith(ellipsis) &&
      (lines.length > maxLines || lines.length > lineCountInCell)
    ) {
      // 第一行的字符个数
      const firstLineStrNumber = resultWrapArr[0].length
      const temp = resultWrapArr.slice(0, Math.min(maxLines, lineCountInCell))
      // 修改最后一行的字符,按照第一行字符个数-1，修改最后一行的字符为...
      temp[temp.length - 1] = temp[temp.length - 1].slice(0, firstLineStrNumber - 1) + ellipsis
      wrapText = temp.join(lineBreak)
    }
    if (wrapText === ellipsis) {
      extraStyleFontSize = 12
    }
  } else {
    const resultWrapArr = wrapText.split(lineBreak)
    // 控制最大行数
    if (lines.length > maxLines) {
      const temp = resultWrapArr.slice(0, maxLines)
      // 第一行的字符个数
      const firstLineStrNumber = resultWrapArr[0].length
      // 修改最后一行的字符
      temp[temp.length - 1] = temp[temp.length - 1].slice(0, firstLineStrNumber - 1) + ellipsis
      wrapText = temp.join(lineBreak)
    }
  }
  // 设置最终文本和其宽度
  cell.actualText = wrapText
  cell.actualTextWidth = cell.spreadsheet.measureTextWidth(wrapText, textStyle)

  // 获取文本位置并渲染文本
  const { y } = cell.getTextAndIconPosition()?.text || cell.getTextPosition()
  const x = getTextStartX(cell, textStyle)
  // 绘制文本
  cell.textShape = renderText(cell, [cell.textShape], x, y, wrapText, textStyle, {
    fontSize: extraStyleFontSize
  })

  // 将文本形状添加到形状数组
  cell.textShapes.push(cell.textShape)
}

/**
 * 计算文本起始X位置
 * @param cell
 * @param textStyle
 */
function getTextStartX(cell, textStyle) {
  // 获取单元格区域
  const area = cell.getCellArea()
  // 计算文本宽度,只计算第一行宽度
  const textWidth = cell.spreadsheet.measureTextWidthRoughly(
    cell.actualText.split('\n')[0],
    textStyle
  )
  const padding = cell.theme.colCell?.cell?.padding ?? { left: 0, right: 0 }
  const align = cell.getTextStyle()?.textAlign ?? 'left'
  const paddingLeft = padding.left || 0
  const paddingRight = padding.right || 0
  // 可用宽度（扣除 padding）
  const availableWidth = area.width - paddingLeft - paddingRight
  switch (align) {
    case 'left':
      return area.x + paddingLeft
    case 'center':
      return area.x + paddingLeft + (availableWidth - textWidth) / 2
    case 'right':
      return area.x + area.width - textWidth - paddingRight
    default:
      return area.x + paddingLeft
  }
}

/**
 * 计算表头高度
 * @param info 单元格信息
 * @param newChart
 * @param tableHeader 表头配置
 * @param basicStyle 表格基础样式
 * @param layoutResult
 */
export const calculateHeaderHeight = (info, newChart, tableHeader, basicStyle, layoutResult) => {
  if (tableHeader.showTableHeader === false) return
  const ev = layoutResult || newChart.facet.layoutResult
  const maxLines = basicStyle.maxLines ?? 1
  const textStyle = { ...newChart.theme.cornerCell.text }
  const sourceText = info.info.meta.value
  let maxHeight = getWrapTextHeight(
    getWrapText(sourceText, textStyle, info.info.resizedWidth, ev.spreadsheet),
    textStyle,
    ev.spreadsheet,
    maxLines
  )

  // 获取最大高度的列，排除当前列
  const maxHeightCol = ev.colLeafNodes
    .filter(n => n.colIndex !== info.info.meta.colIndex)
    .reduce(
      (maxHeightNode, currentNode) => {
        const wrapTextHeight = getWrapTextHeight(
          getWrapText(currentNode.value, textStyle, currentNode.width, currentNode.spreadsheet),
          textStyle,
          currentNode.spreadsheet,
          maxLines
        )
        return wrapTextHeight > maxHeightNode.height
          ? { height: wrapTextHeight, colIndex: currentNode.colIndex }
          : maxHeightNode
      },
      { height: 0 }
    )

  // 使用最大高度
  maxHeight = Math.max(maxHeight, maxHeightCol.height) + textStyle.fontSize + 10.5

  if (layoutResult) {
    if (basicStyle.tableColumnMode === 'adapt') maxHeight -= textStyle.fontSize - 2
    ev.colLeafNodes.forEach(n => (n.height = maxHeight))
    ev.colsHierarchy.height = maxHeight
  }
}

/**
 * 获取换行文本
 * 累加字符串单个字符的宽度，超过单元格宽度时，添加换行
 * @param sourceText
 * @param textStyle
 * @param cellWidth
 * @param spreadsheet
 */
const getWrapText = (sourceText, textStyle, cellWidth, spreadsheet) => {
  if (!sourceText && sourceText !== 0) return ''
  sourceText = sourceText.toString().trim()
  const getTextWidth = text => spreadsheet.measureTextWidthRoughly(text, textStyle)

  let resultWrapText = ''
  let restText = ''
  let restTextWidth = 0
  for (let i = 0; i < sourceText.length; i++) {
    const char = sourceText[i]
    const charWidth = getTextWidth(char)
    restTextWidth += charWidth
    restText += char
    // 中文时，需要单元格宽度减去16个文字宽度，否则会超出单元格宽度
    const cWidth = char.charCodeAt(0) >= 128 ? 12 : 8
    // 添加换行
    if (restTextWidth >= cellWidth - textStyle.fontSize - cWidth) {
      // 最后一个字符不添加换行符
      resultWrapText += restText + (i !== sourceText.length - 1 ? '\n' : '')
      restText = ''
      restTextWidth = 0
    }
  }

  resultWrapText += restText
  return resultWrapText
}
/**
 * 计算文本行高
 * @param wrapText
 * @param textStyle
 * @param spreadsheet
 * @param maxLines 最大行数
 */
const getWrapTextHeight = (wrapText, textStyle, spreadsheet, maxLines) => {
  // 行内最高
  let maxHeight = 0
  // 获取最高字符的高度
  for (const char of wrapText) {
    const h = textStyle.fontSize / (char.charCodeAt(0) >= 128 ? 5 : 2.5)
    maxHeight = Math.max(maxHeight, spreadsheet.measureTextHeight(char, textStyle) + h)
  }
  // 行数
  const lines = wrapText.split('\n').length
  return Math.min(lines, maxLines) * maxHeight
}

// 导出获取汇总行的函数
export function getSummaryRow(data, axis, sumCon = [], customSumResult = {}) {
  const summaryObj = { SUMMARY: true }
  for (let i = 0; i < axis.length; i++) {
    const a = axis[i].dataeaseName
    let savedAxis = find(sumCon, s => s.field === a)
    if (savedAxis) {
      if (savedAxis.summary == undefined) {
        savedAxis.summary = 'sum' // 默认汇总方式为求和
      }
      if (savedAxis.show == undefined) {
        savedAxis.show = true // 默认显示汇总结果
      }
    } else {
      savedAxis = {
        field: a,
        summary: 'sum',
        show: true
      }
    }
    // 如果配置为不显示，则跳过该字段
    if (!savedAxis.show) {
      continue
    }
    // 根据汇总方式处理数据
    switch (savedAxis.summary) {
      case 'sum':
        // 计算字段的总和
        summaryObj[a] = safeDecimalSum(data, a)
        break
      case 'avg':
        // 计算字段的平均值
        summaryObj[a] = safeDecimalMean(data, a)
        break
      case 'max':
        // 计算字段的最大值
        summaryObj[a] = maxBy(
          filter(data, d => parseFloat(d[a]) !== undefined),
          d => parseFloat(d[a]) // 提取数值
        )[a]
        break
      case 'min':
        // 计算字段的最小值
        summaryObj[a] = minBy(
          filter(data, d => parseFloat(d[a]) !== undefined),
          d => parseFloat(d[a]) // 提取数值
        )[a]
        break
      case 'var_pop':
        // 计算总体方差（需要至少2个数据点）
        if (data.length < 2) {
          continue
        } else {
          const mean = safeDecimalMean(data, a) // 计算平均值
          // 计算每个数据点与平均值的差的平方
          const squaredDeviations = map(data, d => {
            const value = new Decimal(d[a] ?? 0) // 获取字段值，如果不存在则使用0
            const dev = value.minus(mean) // 计算差值
            return dev.times(dev) // 计算平方
          })
          // 计算方差（平方差的平均值）
          const variance = squaredDeviations.reduce((acc, val) => acc.plus(val), new Decimal(0))
          summaryObj[a] = variance.dividedBy(data.length - 1).toNumber() // 计算总体方差
        }
        break
      case 'stddev_pop':
        // 计算总体标准差（需要至少2个数据点）
        if (data.length < 2) {
          continue
        } else {
          const mean = safeDecimalMean(data, a) // 计算平均值
          // 计算每个数据点与平均值的差的平方
          const squaredDeviations = map(data, d => {
            const value = new Decimal(d[a] ?? 0) // 获取字段值，如果不存在则使用0
            const dev = value.minus(mean) // 计算差值
            return dev.times(dev) // 计算平方
          })
          // 计算方差（平方差的平均值）
          const variance = squaredDeviations.reduce((acc, val) => acc.plus(val), new Decimal(0))
          summaryObj[a] = variance
            .dividedBy(data.length - 1)
            .sqrt()
            .toNumber() // 计算总体标准差
        }
        break
      case 'custom':
        summaryObj[a] = customSumResult[a]
        break
    }
  }

  // 返回汇总结果对象
  return summaryObj
}

export class SummaryCell extends CustomDataCell {
  getTextStyle() {
    const textStyle = cloneDeep(this.theme.colCell.bolderText)
    const dataCellAlignConfig = this.theme.dataCellAlignConfig
    if (dataCellAlignConfig) {
      const align = dataCellAlignConfig[this.meta.valueField]
      if (align) {
        textStyle.textAlign = align
      }
    } else {
      textStyle.textAlign = this.theme.dataCell.text.textAlign
    }
    return textStyle
  }

  getBackgroundColor() {
    const { backgroundColor, backgroundColorOpacity } = this.theme.colCell.cell
    return { backgroundColor, backgroundColorOpacity }
  }
}

/**
 * 配置空数据样式
 * @param newChart
 * @param basicStyle
 * @param newData
 * @param container
 */
export const configEmptyDataStyle = (newChart, basicStyle, newData, container) => {
  /**
   * 辅助函数：移除空数据dom
   */
  const removeEmptyDom = () => {
    const emptyElement = document.getElementById(container + '_empty')
    if (emptyElement) {
      emptyElement.parentElement.removeChild(emptyElement)
    }
  }
  removeEmptyDom()
  if (newData.length) return
  newChart.on(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, ev => {
    removeEmptyDom()
    if (!newData.length) {
      const emptyDom = document.createElement('div')
      const left = Math.min(newChart.options.width, ev.colsHierarchy.width) / 2 - 32
      emptyDom.id = container + '_empty'
      emptyDom.textContent = i18nt('data_set.no_data')
      emptyDom.setAttribute(
        'style',
        `position: absolute;
        left: ${left}px;
        top: 50%;`
      )
      const parent = document.getElementById(container)
      parent.insertBefore(emptyDom, parent.firstChild)
    }
  })
}

export const getLeafNodes = (tree: Array<ColumnNode>): ColumnNode[] => {
  const result: ColumnNode[] = []
  const inorderTraversal = node => {
    if (!node.children?.length) {
      // 叶子节点，添加到结果数组
      result.push(node)
      return
    }
    // 中序遍历
    for (let i = 0; i < node.children?.length; i++) {
      inorderTraversal(node.children[i])
    }
  }

  // 遍历树中所有节点
  tree.forEach(node => inorderTraversal(node))
  return result
}

export const getColumns = (fields, cols: Array<ColumnNode>) => {
  const result = []
  for (let i = 0; i < cols.length; i++) {
    if (fields.includes(cols[i].key)) {
      result.push(cols[i])
    }
    if (cols[i].children?.length) {
      result.push(...getColumns(fields, cols[i].children as Array<ColumnNode>))
    }
  }
  return result
}

export function drawImage() {
  const img = new Image()
  const { x, y, width, height, fieldValue } = this.meta
  img.src = fieldValue as string
  img.setAttribute('crossOrigin', 'anonymous')
  img.onload = () => {
    !this.cfg.children && (this.cfg.children = [])
    const { width: imgWidth, height: imgHeight } = img
    const ratio = Math.max(imgWidth / width, imgHeight / height)
    // 不铺满，部分留白
    const imgShowWidth = (imgWidth / ratio) * 0.8
    const imgShowHeight = (imgHeight / ratio) * 0.8
    this.textShape = this.addShape('image', {
      attrs: {
        x: x + (imgShowWidth < width ? (width - imgShowWidth) / 2 : 0),
        y: y + (imgShowHeight < height ? (height - imgShowHeight) / 2 : 0),
        width: imgShowWidth,
        height: imgShowHeight,
        img
      }
    })
  }
}

export function calcTreeWidth(node) {
  if (!node.children?.length) {
    return node.width
  }
  return node.children.reduce((pre, cur) => {
    return pre + calcTreeWidth(cur)
  }, 0)
}

export function getStartPosition(node) {
  if (!node.children?.length) {
    return node.x
  }
  return getStartPosition(node.children[0])
}

function getMaxTreeDepth(nodes) {
  if (!nodes?.length) {
    return 0
  }
  return Math.max(
    ...nodes.map(node => {
      if (!node.children?.length) {
        return 1
      }
      return getMaxTreeDepth(node.children) + 1
    })
  )
}

export function summaryRowStyle(newChart, newData, tableCell, tableHeader, showSummary) {
  if (!showSummary || !newData.length) return
  const columns = newChart.dataCfg.fields.columns
  const showHeader = tableHeader.showTableHeader === true
  // 不显示表头时，减少一个表头的高度
  const headerAndSummaryHeight = showHeader ? getMaxTreeDepth(columns) + 1 : 1
  newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
    const totalHeight =
      tableHeader.tableTitleHeight * headerAndSummaryHeight +
      tableCell.tableItemHeight * (newData.length - 1)
    if (totalHeight < newChart.container.cfg.height) {
      newChart.options.height =
        totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight
    }
  })
}

/**
 * 计算分组表头高度
 * @param newChart
 * @param tableHeader
 * @param basicStyle
 */
export const calculateGroupHeaderHeight = (newChart, tableHeader, basicStyle) => {
  let maxGroupHeight = 0
  // 获取分组名字最长的列
  const maxNameMeta = newChart.dataCfg?.meta
    ?.filter(item => !item.field.startsWith('f_'))
    ?.reduce((max, cur) => (cur.name.length > (max?.name.length ?? 0) ? cur : max), null)
  if (maxNameMeta) {
    let colWidth = basicStyle.tableColumnWidth
    const maxNameColumn = findNodeByKey(newChart.dataCfg.fields.columns, maxNameMeta.field)
    const maxNameColumns = []
    if (maxNameColumn) {
      maxNameColumns.push(maxNameColumn)
    }
    const { resizedWidth, meta } = newChart.store.get('resizeColWidthInfo') || {
      resizedWidth: 0,
      width: 0
    }
    const leafKeys = getLeafKeys(maxNameColumns)
    if (basicStyle.tableFieldWidth.length > 0) {
      colWidth = 0
      const fieldWidth = basicStyle.tableFieldWidth
      // fieldWidth中对象的key在leafKeys时，对fieldWidth中对象的width求和
      fieldWidth.forEach(fw => {
        // 调整单元格宽度时，排除掉当前调整的列，使用调整后的宽度
        if (
          leafKeys.filter(key => meta?.key !== key).includes(fw.fieldId) &&
          isNumber(fw.width) &&
          fw.width > 0
        ) {
          colWidth += fw.width
        }
      })
    }
    if (basicStyle.tableColumnMode === 'custom') {
      colWidth = basicStyle.tableColumnWidth * (leafKeys.length === 0 ? 1 : leafKeys.length) || 100
    } else {
      colWidth =
        (newChart.facet?.cfg?.width ? newChart.facet.cfg.width : newChart.options.width) *
        (colWidth / 100)
    }
    // 计算分组表头的高度
    if (colWidth > 0) {
      colWidth = colWidth + resizedWidth
      const nodeHeight = calculateGroupHeaderMaxTextHeight(
        { info: { name: maxNameMeta.name, resizedWidth: colWidth } },
        newChart,
        tableHeader,
        basicStyle,
        null
      )
      maxGroupHeight = Math.max(maxGroupHeight, nodeHeight)
    }
    if (maxGroupHeight > 0) {
      newChart.options.style.colCfg.height = maxGroupHeight
    }
  }
}

// 获取最里层的叶子节点
const getLeafKeys = (columns: any[]): string[] => {
  const keys: string[] = []
  columns.forEach(col => {
    if (col && typeof col === 'object' && Array.isArray(col.children) && col.children.length > 0) {
      keys.push(...getLeafKeys(col.children))
    } else if (col && typeof col === 'object' && col.key) {
      keys.push(col.key)
    }
  })
  return keys
}
// 根据 key 查找节点
const findNodeByKey = (columns: any[], key: string): any | null => {
  for (const col of columns) {
    if (col.key === key) return col
    if (col.children) {
      const found = findNodeByKey(col.children, key)
      if (found) return found
    }
  }
  return null
}

/**
 * 计算分组表头最大文本高度
 * @param info
 * @param newChart
 * @param tableHeader
 * @param basicStyle
 * @param _layoutResult
 */
const calculateGroupHeaderMaxTextHeight = (
  info,
  newChart,
  tableHeader,
  basicStyle,
  _layoutResult
) => {
  if (tableHeader.showTableHeader === false) return
  const maxLines = basicStyle.maxLines ?? 1
  const textStyle = { ...newChart.theme.cornerCell.text, fontSize: tableHeader.tableTitleFontSize }
  const sourceText = info.info.name
  return (
    getWrapTextHeight(
      getWrapText(sourceText, textStyle, info.info.resizedWidth, newChart),
      textStyle,
      newChart,
      maxLines
    ) +
    textStyle.fontSize +
    10.5
  )
}

export const isNumeric = (value: any): boolean => {
  return !isNaN(parseFloat(value)) && isFinite(value)
}
