import { isNumber } from 'lodash-es'
import { DEFAULT_TITLE_STYLE } from '../editor/util/chart'
import { equalsAny, includesAny } from '../editor/util/StringUtils'
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
import { useMapStoreWithOut } from '@/store/modules/map'
import { getGeoJson } from '@/api/map'
import { computed, toRaw } from 'vue'
import { Options } from '@antv/g2plot/esm'
import { PickOptions } from '@antv/g2plot/esm/core/plot'
import { innerExportDataSetDetails, innerExportDetails } from '@/api/chart'
import { ElMessage } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import { useLinkStoreWithOut } from '@/store/modules/link'
import { useAppStoreWithOut } from '@/store/modules/app'
import { Decimal } from 'decimal.js'

const appStore = useAppStoreWithOut()
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)

const { t } = useI18n()
// 同时支持将hex和rgb，转换成rgba
export function hexColorToRGBA(hex, alpha) {
  let rgb = [] // 定义rgb数组
  if (hex.indexOf('#') > -1) {
    if (/^\#[0-9A-F]{3}$/i.test(hex)) {
      // 判断传入是否为#三位十六进制数
      let sixHex = '#'
      hex.replace(/[0-9A-F]/gi, function (kw) {
        sixHex += kw + kw // 把三位16进制数转化为六位
      })
      hex = sixHex // 保存回hex
    }
    if (/^#[0-9A-F]{6}$/i.test(hex)) {
      // 判断传入是否为#六位十六进制数
      hex.replace(/[0-9A-F]{2}/gi, function (kw) {
        // eslint-disable-next-line no-eval
        rgb.push(eval('0x' + kw)) // 十六进制转化为十进制并存如数组
      })
      return `rgba(${rgb.join(',')},${alpha / 100})` // 输出RGB格式颜色
    } else {
      return 'rgb(0,0,0)'
    }
  } else {
    rgb = hex.match(/\d+/g)
    return `rgba(${rgb.join(',')},${alpha / 100})`
  }
}

export function digToHex(dig) {
  let prefix = ''
  const num = parseInt((dig * 2.55).toString())
  if (num < 16) {
    prefix = '0'
  }
  return prefix.concat(num.toString(16).toUpperCase())
}

export function customSort(custom, data) {
  const indexArr = []
  const joinArr = []
  for (let i = 0; i < custom.length; i++) {
    const ele = custom[i]
    for (let j = 0; j < data.length; j++) {
      const d = data[j]
      if (ele === d.field) {
        joinArr.push(d)
        indexArr.push(j)
      }
    }
  }
  // 取得 joinArr 就是两者的交集
  const indexArrData = []
  for (let i = 0; i < data.length; i++) {
    indexArrData.push(i)
  }
  const indexResult = []
  for (let i = 0; i < indexArrData.length; i++) {
    if (indexArr.indexOf(indexArrData[i]) === -1) {
      indexResult.push(indexArrData[i])
    }
  }

  const subArr = []
  for (let i = 0; i < indexResult.length; i++) {
    subArr.push(data[indexResult[i]])
  }

  return joinArr.concat(subArr)
}

export function customColor(custom, res) {
  const result = []
  for (let i = 0; i < res.length; i++) {
    const r = res[i]
    let flag = false
    for (let j = 0; j < custom.length; j++) {
      const c = custom[j]
      if (r.name === c.name) {
        flag = true
        result.push(c)
      }
    }
    if (!flag) {
      result.push(r)
    }
  }
  return result
}

export function getColors(chart, colors, reset) {
  // 自定义颜色，先按照没有设定的情况，并排好序，当做最终结果
  let seriesColors = []
  let series
  if (chart.type.includes('stack')) {
    if (chart.data) {
      const data = chart.data.data
      const stackData = []
      for (let i = 0; i < data.length; i++) {
        const s = data[i]
        stackData.push(s.category)
      }
      const sArr = stackData.filter(function (item, index, stackData) {
        return stackData.indexOf(item, 0) === index
      })

      for (let i = 0; i < sArr.length; i++) {
        const s = sArr[i]
        seriesColors.push({
          name: s,
          color: colors[i % colors.length],
          isCustom: false
        })
      }
    }
  } else if (
    includesAny(chart.type, 'bar', 'scatter', 'radar', 'area') &&
    !chart.type.includes('group')
  ) {
    if (Object.prototype.toString.call(chart.yAxis) === '[object Array]') {
      series = JSON.parse(JSON.stringify(chart.yAxis))
    } else {
      series = JSON.parse(chart.yAxis)
    }
    if (series) {
      for (let i = 0; i < series.length; i++) {
        const s = series[i]
        seriesColors.push({
          name: s.name,
          color: colors[i % colors.length],
          isCustom: false
        })
      }
    }
  } else if (equalsAny(chart.type, 'bar-group', 'line')) {
    // 拿到data中的category，并去重，然后构建seriesColor
    if (chart.data) {
      const data = chart.data.data
      const s = []
      if (data) {
        data.forEach(cur => {
          if (s.indexOf(cur.category) < 0) {
            s.push(cur.category)
          }
        })
      }
      for (let i = 0; i < s.length; i++) {
        seriesColors.push({
          name: s[i],
          color: colors[i % colors.length],
          isCustom: false
        })
      }
    }
  } else {
    if (chart.data) {
      const data = chart.data.data
      for (let i = 0; i < data.length; i++) {
        const s = data[i]
        seriesColors.push({
          name: s.field,
          color: colors[i % colors.length],
          isCustom: false
        })
      }
    }
  }
  // 如果有自定义，则与上述中的结果合并。
  // res，custom，以custom为准，去掉res中不存在的，并将custom中name一样的color赋值给res，不存在的name，即新增值，使用i % colors.length，从配色方案中选
  if (!reset) {
    let sc = null
    if (Object.prototype.toString.call(chart.customAttr) === '[object Object]') {
      sc = JSON.parse(JSON.stringify(chart.customAttr)).color.seriesColors
    } else {
      sc = JSON.parse(chart.customAttr)['color'].seriesColors
    }
    if (sc && sc.length > 0) {
      seriesColors = customColor(sc, seriesColors)
    }
    // 根据isCustom字段，修正color
    for (let i = 0; i < seriesColors.length; i++) {
      if (!seriesColors[i].isCustom) {
        seriesColors[i].color = colors[i % colors.length]
      }
    }
  }
  return seriesColors
}

export function antVCustomColor(chart) {
  const colors = []
  if (chart.customAttr) {
    const customAttr = JSON.parse(JSON.stringify(chart.customAttr))
    // color
    if (customAttr.basicStyle) {
      const basicStyle = JSON.parse(JSON.stringify(customAttr.basicStyle))

      const customColors = getColors(chart, basicStyle.colors, false)
      for (let i = 0; i < customColors.length; i++) {
        colors.push(hexColorToRGBA(customColors[i].color, basicStyle.alpha))
      }
    }
  }
  return colors
}

export function getRemark(chart) {
  const remark = {} as any
  if (chart.customStyle) {
    const customStyle = JSON.parse(JSON.stringify(chart.customStyle))
    if (customStyle.text) {
      const title = JSON.parse(JSON.stringify(customStyle.text))
      remark.show = title.remarkShow ? title.remarkShow : DEFAULT_TITLE_STYLE.remarkShow
      remark.content = title.remark ? title.remark : DEFAULT_TITLE_STYLE.remark
      remark.bgFill = title.remarkBackgroundColor
        ? title.remarkBackgroundColor
        : DEFAULT_TITLE_STYLE.remarkBackgroundColor
    }
  }
  return remark
}

export const quotaViews = ['label', 'richTextView', 'indicator', 'gauge', 'liquid']
// 地图
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map']
// 分布图
const distributionChartTypes = [
  'pie',
  'pie-donut',
  'pie-rose',
  'pie-donut-rose',
  'radar',
  'treemap',
  'word-cloud'
]
// 关系图
const relationChartTypes = ['scatter', 'quadrant', 'funnel', 'sankey', 'circle-packing']
// 不支持指标累加的图表
export const notSupportAccumulateViews = [
  ...quotaViews,
  ...mapChartTypes,
  ...distributionChartTypes,
  ...relationChartTypes,
  'table-info',
  't-heatmap',
  'percentage-bar-stack',
  'percentage-bar-stack-horizontal',
  'progress-bar',
  'stock-line'
]

export function handleEmptyDataStrategy<O extends PickOptions>(chart: Chart, options: O): O {
  const { data } = options as unknown as Options
  const isChartMix = chart.type.includes('chart-mix')
  if (!data?.length) {
    return options
  }
  const strategy = parseJson(chart.senior).functionCfg.emptyDataStrategy
  if (strategy === 'ignoreData') {
    if (isChartMix) {
      for (let i = 0; i < data.length; i++) {
        handleIgnoreData(data[i] as Record<string, any>[])
      }
    } else {
      handleIgnoreData(data)
    }
    return options
  }
  const { yAxis, xAxisExt, extStack, extBubble } = chart
  const multiDimension = yAxis?.length >= 2 || xAxisExt?.length > 0 || extStack?.length > 0
  switch (strategy) {
    case 'breakLine': {
      if (isChartMix) {
        if (data[0]) {
          if (xAxisExt?.length > 0 || extStack?.length > 0) {
            handleBreakLineMultiDimension(data[0] as Record<string, any>[])
          }
        }
        if (data[1]) {
          if (extBubble?.length > 0) {
            handleBreakLineMultiDimension(data[1] as Record<string, any>[])
          }
        }
      } else {
        if (multiDimension) {
          handleBreakLineMultiDimension(data)
        }
      }
      return {
        ...options,
        connectNulls: false
      }
    }
    case 'setZero': {
      if (isChartMix) {
        if (data[0]) {
          if (xAxisExt?.length > 0 || extStack?.length > 0) {
            handleSetZeroMultiDimension(data[0] as Record<string, any>[])
          } else {
            handleSetZeroSingleDimension(data[0] as Record<string, any>[])
          }
        }
        if (data[1]) {
          if (extBubble?.length > 0) {
            handleSetZeroMultiDimension(data[1] as Record<string, any>[], true)
          } else {
            handleSetZeroSingleDimension(data[1] as Record<string, any>[], true)
          }
        }
      } else {
        if (multiDimension) {
          // 多维度置0
          handleSetZeroMultiDimension(data)
        } else {
          // 单维度置0
          handleSetZeroSingleDimension(data)
        }
      }
      break
    }
  }
  return options
}

function handleBreakLineMultiDimension(data) {
  const dimensionInfoMap = new Map()
  const subDimensionSet = new Set()
  const quotaMap = new Map<string, { id: string }[]>()
  for (let i = 0; i < data.length; i++) {
    const item = data[i]
    const dimensionInfo = dimensionInfoMap.get(item.field)
    if (dimensionInfo) {
      dimensionInfo.set.add(item.category)
    } else {
      dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i })
    }
    subDimensionSet.add(item.category)
    quotaMap.set(item.category, item.quotaList)
  }
  // Map 是按照插入顺序排序的，所以插入索引往后推
  let insertCount = 0
  dimensionInfoMap.forEach((dimensionInfo, field) => {
    if (dimensionInfo.set.size < subDimensionSet.size) {
      let subInsertIndex = 0
      subDimensionSet.forEach(dimension => {
        if (!dimensionInfo.set.has(dimension)) {
          data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, {
            field,
            value: null,
            category: dimension,
            quotaList: quotaMap.get(dimension as string)
          })
        }
        subInsertIndex++
      })
      insertCount += subDimensionSet.size - dimensionInfo.set.size
    }
  })
}

function handleSetZeroMultiDimension(data: Record<string, any>[], isExt = false) {
  const dimensionInfoMap = new Map()
  const subDimensionSet = new Set()
  const quotaMap = new Map<string, { id: string }[]>()
  for (let i = 0; i < data.length; i++) {
    const item = data[i]
    if (item.value === null) {
      item.value = 0
      if (isExt) {
        item.valueExt = 0
      }
    }
    const dimensionInfo = dimensionInfoMap.get(item.field)
    if (dimensionInfo) {
      dimensionInfo.set.add(item.category)
    } else {
      dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i })
    }
    subDimensionSet.add(item.category)
    quotaMap.set(item.category, item.quotaList)
  }
  let insertCount = 0
  dimensionInfoMap.forEach((dimensionInfo, field) => {
    if (dimensionInfo.set.size < subDimensionSet.size) {
      let subInsertIndex = 0
      subDimensionSet.forEach(dimension => {
        if (!dimensionInfo.set.has(dimension)) {
          const _temp = {
            field,
            value: 0,
            category: dimension,
            quotaList: quotaMap.get(dimension as string)
          } as any
          if (isExt) {
            _temp.valueExt = 0
          }

          data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, _temp)
        }
        subInsertIndex++
      })
      insertCount += subDimensionSet.size - dimensionInfo.set.size
    }
  })
}

function handleSetZeroSingleDimension(data: Record<string, any>[], isExt = false) {
  data.forEach(item => {
    if (item.value === null) {
      if (!isExt) {
        item.value = 0
      } else {
        item.valueExt = 0
      }
    }
  })
}

function handleIgnoreData(data: Record<string, any>[]) {
  for (let i = data.length - 1; i >= 0; i--) {
    const item = data[i]
    if (item.value === null) {
      data.splice(i, 1)
    }
  }
}

export function resetRgbOpacity(sourceColor: string, times: number): string {
  if (sourceColor?.startsWith('rgb')) {
    const numbers = sourceColor.match(/(\d(\.\d+)?)+/g)
    if (numbers?.length === 4) {
      const opacity = parseFloat(numbers[3])
      if (isNumber(opacity)) {
        let resultOpacity = parseFloat((opacity * times).toFixed(2))
        if (resultOpacity > 1) {
          resultOpacity = 1
        }
        const colorArr = numbers.slice(0, 3).concat(resultOpacity.toString())
        return `rgba(${colorArr.join(',')})`
      }
    }
  }
  return sourceColor
}

export function parseJson<T>(str: T | JSONString<T>): T {
  if (typeof str !== 'string') {
    return str as T
  }
  return JSON.parse(str) as T
}

type FlowFunction<P, R> = (param: P, result: R, context?: Record<string, any>, thisArg?: any) => R

export function flow<P, R>(...flows: FlowFunction<P, R>[]): FlowFunction<P, R> {
  return (param: P, result: R, context?: Record<string, any>, thisArg?: any) => {
    return flows.reduce((result: R, flow: FlowFunction<P, R>) => {
      if (thisArg) {
        return flow.call(thisArg, param, result, context)
      } else {
        return flow(param, result, context)
      }
    }, result)
  }
}

export const isParent = (type: any, parentType: any) => {
  let _type = type
  while (_type) {
    if (_type === parentType) {
      return true
    }
    _type = _type.__proto__
  }
  return false
}

export const getGeoJsonFile = async (areaId: string): Promise<FeatureCollection> => {
  const mapStore = useMapStoreWithOut()
  let geoJson = mapStore.mapCache[areaId]
  if (!geoJson) {
    const res = await getGeoJson(areaId)
    geoJson = res.data
    mapStore.setMap({ id: areaId, geoJson })
  }
  return toRaw(geoJson)
}

const getExcelDownloadRequest = (data, type?) => {
  let fields = JSON.parse(JSON.stringify(data.fields))
  // liquid gauge 只需要导出一个字段
  if (['gauge', 'liquid'].includes(type) && fields.length > 1) {
    fields = fields.slice(1)
  }
  const tableRow = JSON.parse(JSON.stringify(data.tableRow))
  const excelHeader = fields.map(item => item.chartShowName ?? item.name)
  const excelTypes = fields.map(item => item.deType)
  const excelHeaderKeys = fields.map(item => item.gisbiName)
  let excelData = tableRow.map(item => excelHeaderKeys.map(i => item[i]))
  let detailFields = []
  if (data.detailFields?.length) {
    detailFields = data.detailFields.map(item => {
      return {
        name: item.name,
        deType: item.deType,
        gisbiName: item.gisbiName
      }
    })
    excelData = tableRow.map(item => {
      return excelHeaderKeys.map(i => {
        if (i === 'detail' && !item[i] && Array.isArray(item['details'])) {
          const arr = item['details']
          if (arr?.length) {
            return arr.map(ele => detailFields.map(field => ele[field.gisbiName]))
          }
          return null
        }
        return item[i]
      })
    })
  }
  return {
    header: excelHeader,
    details: excelData,
    excelTypes: excelTypes,
    excelHeaderKeys: excelHeaderKeys,
    detailFields: detailFields
  }
}

function getChartExcelTitle(preFix, viewTitle) {
  const now = new Date()
  const pad = n => n.toString().padStart(2, '0')
  const year = now.getFullYear()
  const month = pad(now.getMonth() + 1) // 月份从 0 开始
  const day = pad(now.getDate())
  const hour = pad(now.getHours())
  const minute = pad(now.getMinutes())
  const second = pad(now.getSeconds())
  return `${preFix}_${viewTitle}_${year}${month}${day}_${hour}${minute}${second}`
}

export const exportExcelDownload = (chart, preFix, callBack?) => {
  const excelName = getChartExcelTitle(preFix, chart.title)
  let request: any = {
    proxy: null,
    dvId: chart.sceneId,
    viewId: chart.id,
    viewInfo: chart,
    viewName: excelName,
    busiFlag: chart.busiFlag,
    downloadType: chart.downloadType
  }
  if (chart.type.includes('chart-mix')) {
    const req1 = getExcelDownloadRequest(chart.data.left)
    const req2 = getExcelDownloadRequest(chart.data.right)
    request = {
      ...request,
      multiInfo: [req1, req2]
    }
    if (chart.downloadType === 'dataset') {
      delete request.multiInfo
    }
  } else {
    const req = getExcelDownloadRequest(chart.data, chart.type)
    request = {
      ...request,
      ...req
    }
  }

  if (chart.type.includes('symbolic-map')) {
    request.detailFields = []
  }

  const linkStore = useLinkStoreWithOut()

  if (isDataEaseBi.value || appStore.getIsIframe) {
    request.dataEaseBi = true
  }
  const method = request.downloadType === 'dataset' ? innerExportDataSetDetails : innerExportDetails
  if (request.viewInfo?.customAttr?.basicStyle?.tablePageMode) {
    request.viewInfo.customAttr.basicStyle.tablePageMode = 'page'
  }
  method(request)
    .then(res => {
      if (linkStore.getLinkToken || isDataEaseBi.value || appStore.getIsIframe) {
        const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' })
        const link = document.createElement('a')
        link.style.display = 'none'
        link.href = URL.createObjectURL(blob)
        link.download = excelName + '.xlsx' // 下载的文件名
        document.body.appendChild(link)
        link.click()
        document.body.removeChild(link)
      } else {
        callBack && callBack(res)
      }
    })
    .catch(() => {
      console.error('Excel download error')
      callBack('error')
    })
}

export const copyString = (content: string, notify = false) => {
  let clipboard = navigator.clipboard as Pick<Clipboard, 'writeText'>
  if (!clipboard || window.top !== window.self) {
    clipboard = {
      writeText: data => {
        return new Promise<void>(resolve => {
          const textareaDom = document.createElement('textarea')
          textareaDom.setAttribute('style', 'z-index: -1;position: fixed;opacity: 0;')
          textareaDom.value = data
          document.body.appendChild(textareaDom)
          textareaDom.select()
          document.execCommand('copy')
          textareaDom.remove()
          resolve()
        })
      }
    }
  }
  clipboard.writeText(content).then(() => {
    if (notify) {
      ElMessage.success(t('commons.copy_success'))
    }
  })
}

/**
 * 计算动态区间和颜色
 * @param minValue
 * @param maxValue
 * @param intervals
 * @param colors
 */
export const getDynamicColorScale = (
  minValue: number,
  maxValue: number,
  intervals: number,
  colors?: string[]
) => {
  const step = (maxValue - minValue) / intervals

  const colorScale = []
  for (let i = 0; i < intervals; i++) {
    colorScale.push({
      value: [minValue + i * step, minValue + (i + 1) * step],
      color: colors?.[i],
      label: `${(minValue + i * step).toFixed(0)} - ${(minValue + (i + 1) * step).toFixed(0)}`
    })
  }

  return colorScale
}
/**
 * 过滤掉不在区间的数据
 * @param data
 * @param maxValue
 * @param minValue
 */
export const filterChartDataByRange = (data: any[], maxValue: number, minValue: number) => {
  return data.filter(
    item =>
      item.value === null ||
      item.value === undefined ||
      (item.value >= minValue && item.value <= maxValue)
  )
}

/**
 * 获取数据最大最小值
 * @param data
 * @param field 值字段
 * @param maxValue
 * @param minValue
 * @param callback
 */
export const getMaxAndMinValueByData = (
  data: any[],
  field: string,
  maxValue: number,
  minValue: number,
  callback: (max: number, min: number) => void
) => {
  // 定义一个辅助函数来计算最大值或最小值
  const calculateExtreme = (isMax: boolean) => {
    return data.reduce(
      (extreme, current) => {
        return isMax
          ? current[field] > extreme
            ? current[field]
            : extreme
          : current[field] < extreme
          ? current[field]
          : extreme
      },
      isMax ? Number.MIN_SAFE_INTEGER : Number.MAX_SAFE_INTEGER
    )
  }
  if (minValue === null || maxValue === null) {
    let maxResult = maxValue
    let minResult = minValue
    if (maxResult === null) {
      maxResult = calculateExtreme(true)
    }
    if (minResult === null) {
      minResult = calculateExtreme(false)
    }
    callback(maxResult, minResult)
  }
  if (minValue === 0 && maxValue === 0) {
    const maxResult = calculateExtreme(true)
    const minResult = calculateExtreme(false)
    callback(maxResult, minResult)
  }
}

export const stepsColor = (start, end, steps, gamma) => {
  let i
  let j
  let ms
  let me
  const output = []
  const so = []
  gamma = gamma || 1
  const normalize = function (channel) {
    return Math.pow(channel / 255, gamma)
  }
  start = parseColor(start).map(normalize)
  end = parseColor(end).map(normalize)
  for (i = 0; i < steps; i++) {
    ms = steps - 1 === 0 ? 0 : i / (steps - 1)
    me = 1 - ms
    for (j = 0; j < 3; j++) {
      so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16))
    }
    output.push('#' + so.join(''))
  }
  function parseColor(hexStr) {
    return hexStr.length === 4
      ? hexStr
          .substr(1)
          .split('')
          .map(function (s) {
            return 0x11 * parseInt(s, 16)
          })
      : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) {
          return parseInt(s, 16)
        })
  }
  function pad(s) {
    return s.length === 1 ? '0' + s : s
  }
  return output
}

export const getMapColorCases = colorCases => {
  const cloneColorCases = JSON.parse(JSON.stringify(colorCases))
  return cloneColorCases.map(colorItem => {
    let curColors = colorItem.colors
    if (['fresh', 'red', 'spiritual'].includes(colorItem.value)) {
      curColors = colorItem.colors.reverse()
    }
    const len = curColors.length
    const start = curColors[0]
    const end = curColors[len - 1]
    const itemResult = {
      name: colorItem.name,
      value: colorItem.value + '_split_gradient',
      baseColors: [start, end],
      colors: stepsColor(start, end, 9, 1)
    }
    return itemResult
  })
}

export function getColor(chart: Chart) {
  const basicStyle = parseJson(chart.customAttr).basicStyle
  const { seriesColor } = basicStyle
  if (seriesColor?.length) {
    const { yAxis } = chart
    const seriesMap = seriesColor.reduce((p, n) => {
      p[n.id] = n
      return p
    }, {})
    yAxis?.forEach((axis, index) => {
      const curAxisColor = seriesMap[axis.id]
      if (curAxisColor) {
        if (index + 1 > basicStyle.colors.length) {
          basicStyle.colors.push(curAxisColor.color)
        } else {
          basicStyle.colors[index] = curAxisColor.color
        }
      }
    })
    const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
    return color
  }
}

export function setupSeriesColor(chart: ChartObj): ChartBasicStyle['seriesColor'] {
  const result: ChartBasicStyle['seriesColor'] = []
  const seriesSet = new Set<string>()
  const colors = chart.customAttr.basicStyle.colors
  const yAxis = chart.yAxis
  yAxis?.forEach(axis => {
    if (seriesSet.has(axis.id)) {
      return
    }
    seriesSet.add(axis.id)
    result.push({
      id: axis.id,
      name: axis.chartShowName ?? axis.name,
      color: colors[(seriesSet.size - 1) % colors.length]
    })
  })
  return result
}

export function getGroupColor<O extends PickOptions = Options>(chart: Chart, options: O) {
  const { basicStyle } = parseJson(chart.customAttr)
  const { seriesColor } = basicStyle
  if (!seriesColor?.length) {
    return
  }
  const seriesMap = seriesColor.reduce((p, n) => {
    p[n.id] = n
    return p
  }, {})
  const { yAxis, xAxisExt } = chart
  const { data } = options as unknown as Options
  if (xAxisExt?.length) {
    const seriesSet = new Set()
    data?.forEach(d => d.category !== null && seriesSet.add(d.category))
    const tmp = [...seriesSet]
    tmp.forEach((c, i) => {
      const curAxisColor = seriesMap[c as string]
      if (curAxisColor) {
        if (i + 1 > basicStyle.colors.length) {
          basicStyle.colors.push(curAxisColor.color)
        } else {
          basicStyle.colors[i] = curAxisColor.color
        }
      }
    })
  } else {
    yAxis?.forEach((axis, index) => {
      const curAxisColor = seriesMap[axis.id]
      if (curAxisColor) {
        if (index + 1 > basicStyle.colors.length) {
          basicStyle.colors.push(curAxisColor.color)
        } else {
          basicStyle.colors[index] = curAxisColor.color
        }
      }
    })
  }
  const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
  return color
}

export function setUpGroupSeriesColor(
  chart: ChartObj,
  data?: any[]
): ChartBasicStyle['seriesColor'] {
  const result: ChartBasicStyle['seriesColor'] = []
  const seriesSet = new Set<string>()
  const colors = chart.customAttr.basicStyle.colors
  const { yAxis, xAxisExt } = chart
  if (xAxisExt?.length) {
    data?.forEach(d => {
      if (d.value === null || d.category === null || seriesSet.has(d.category)) {
        return
      }
      seriesSet.add(d.category)
      result.push({
        id: d.category,
        name: d.category,
        color: colors[(seriesSet.size - 1) % colors.length]
      })
    })
  } else {
    yAxis?.forEach(axis => {
      if (seriesSet.has(axis.id)) {
        return
      }
      seriesSet.add(axis.id)
      result.push({
        id: axis.id,
        name: axis.chartShowName ?? axis.name,
        color: colors[(seriesSet.size - 1) % colors.length]
      })
    })
  }
  return result
}

export function getStackColor<O extends PickOptions = Options>(chart: Chart, options: O) {
  const { basicStyle } = parseJson(chart.customAttr)
  const { seriesColor } = basicStyle
  if (!seriesColor?.length) {
    return
  }
  const seriesMap = seriesColor.reduce((p, n) => {
    p[n.id] = n
    return p
  }, {})
  const { yAxis, extStack } = chart
  const { data } = options as unknown as Options
  if (extStack?.length) {
    const seriesSet = new Set()
    data?.forEach(d => d.category !== null && seriesSet.add(d.category))
    const tmp = [...seriesSet]
    tmp.forEach((c, i) => {
      const curAxisColor = seriesMap[c as string]
      if (curAxisColor) {
        if (i + 1 > basicStyle.colors.length) {
          basicStyle.colors.push(curAxisColor.color)
        } else {
          basicStyle.colors[i] = curAxisColor.color
        }
      }
    })
  } else {
    yAxis?.forEach((axis, index) => {
      const curAxisColor = seriesMap[axis.id]
      if (curAxisColor) {
        if (index + 1 > basicStyle.colors.length) {
          basicStyle.colors.push(curAxisColor.color)
        } else {
          basicStyle.colors[index] = curAxisColor.color
        }
      }
    })
  }
  const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
  return color
}

export function setUpStackSeriesColor(
  chart: ChartObj,
  data?: any[]
): ChartBasicStyle['seriesColor'] {
  const result: ChartBasicStyle['seriesColor'] = []
  const seriesSet = new Set<string>()
  const colors = chart.customAttr.basicStyle.colors
  const { yAxis, extStack } = chart
  if (extStack?.length) {
    data?.forEach(d => {
      if (d.value === null || d.category === null || seriesSet.has(d.category)) {
        return
      }
      seriesSet.add(d.category)
      result.push({
        id: d.category,
        name: d.category,
        color: colors[(seriesSet.size - 1) % colors.length]
      })
    })
  } else {
    yAxis?.forEach(axis => {
      if (seriesSet.has(axis.id)) {
        return
      }
      seriesSet.add(axis.id)
      result.push({
        id: axis.id,
        name: axis.chartShowName ?? axis.name,
        color: colors[(seriesSet.size - 1) % colors.length]
      })
    })
  }
  return result
}

export function getSingleDimensionColor<O extends PickOptions = Options>(chart: Chart, options: O) {
  const { basicStyle } = parseJson(chart.customAttr)
  const { seriesColor } = basicStyle
  if (!seriesColor?.length) {
    return
  }
  const seriesMap = seriesColor.reduce((p, n) => {
    p[n.id] = n
    return p
  }, {})
  const { xAxis, yAxis } = chart
  const { data } = options as unknown as Options
  if (xAxis?.length && yAxis?.length) {
    const seriesSet = new Set()
    data?.forEach(d => d.field !== null && seriesSet.add(d.field))
    const tmp = [...seriesSet]
    tmp.forEach((c, i) => {
      const curAxisColor = seriesMap[c as string]
      if (curAxisColor) {
        if (i + 1 > basicStyle.colors.length) {
          basicStyle.colors.push(curAxisColor.color)
        } else {
          basicStyle.colors[i] = curAxisColor.color
        }
      }
    })
  }
  const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
  return color
}

export function setUpSingleDimensionSeriesColor(
  chart: ChartObj,
  data?: any[]
): ChartBasicStyle['seriesColor'] {
  const result: ChartBasicStyle['seriesColor'] = []
  const seriesSet = new Set<string>()
  const colors = chart.customAttr.basicStyle.colors
  const { xAxis, yAxis } = chart
  if (!(xAxis?.length && yAxis?.length)) {
    return result
  }
  data?.forEach(item => {
    if (seriesSet.has(item.field)) {
      return
    }
    seriesSet.add(item.field)
    result.push({
      id: item.field,
      name: item.field,
      color: colors[(seriesSet.size - 1) % colors.length]
    })
  })
  return result
}

export function isAlphaColor(color: string): boolean {
  if (!color?.trim()) {
    return false
  }
  if (color.startsWith('#')) {
    return color.length === 9
  }
  if (color.startsWith('rgb')) {
    return color.split(',').length === 4
  }
  return false
}

export function getColorFormAlphaColor(color: string): string {
  if (isAlphaColor(color)) {
    if (color.startsWith('#')) {
      return color.slice(0, 7)
    }
    if (color.startsWith('rgb') || color.startsWith('RGB')) {
      const list = color.split(',')
      return list[0] + ',' + list[1] + ',' + list[2] + ')'
    }
  }
  return color
}

export function isTransparent(color: string): boolean {
  if (!color?.trim()) {
    return true
  }
  if (color.startsWith('#')) {
    const tmp = color.substring(1, color.length)
    if (tmp.length === 3 || tmp.length === 6) {
      return false
    }
    if (tmp.length === 8) {
      return tmp.substring(6, 8) === '00'
    }
  }
  if (color.startsWith('rgb')) {
    const tmp = color.split(',')
    if (tmp.length !== 4) {
      return false
    }
    const alpha = tmp[3].substring(0, tmp[3].length - 1)
    return alpha.trim() === '0'
  }
  return false
}

export function convertToAlphaColor(color: string, alpha: number): string {
  if (!color?.trim()) {
    return 'rgba(255,255,255,1)'
  }
  if (color.startsWith('#')) {
    let colorStr = color.trim().substring(1)
    if (colorStr.length === 3) {
      const tmp = colorStr.split('')
      colorStr = `${tmp[0]}${tmp[0]}${tmp[1]}${tmp[1]}${tmp[2]}${tmp[2]}`
    }
    if (colorStr.length !== 6) {
      return '#FFFFFFFF'
    }
    const alphaHex = parseInt((alpha * 2.55).toFixed(0))
      .toString(16)
      .toUpperCase()
    return `#${colorStr}${alphaHex}`
  }
  if (color.startsWith('rgb')) {
    const rgb = color.match(/\d+/g)
    return `rgba(${rgb.join(',')},${alpha / 100})`
  }
  return 'rgba(255,255,255,1)'
}

export function svgStrToUrl(svgStr: string): string {
  let file = ''
  try {
    if (svgStr) {
      const blob = new Blob([svgStr], { type: 'image/svg+xml' })
      file = URL.createObjectURL(blob)
    }
  } catch (e) {}
  return file
}

/**
 * 获取非空数据的最小值
 * @param sourceData
 * @param field
 * @private
 */
export function filterEmptyMinValue(sourceData, field) {
  let notEmptyMinValue = 0
  getMaxAndMinValueByData(
    sourceData.filter(item => item[field]),
    'value',
    0,
    0,
    (max, min) => {
      notEmptyMinValue = min
    }
  )
  return notEmptyMinValue
}

/**
 * 获取折线条件样式
 * @param chart
 */
export function getLineConditions(chart) {
  const { threshold } = parseJson(chart.senior)
  const conditions = []
  if (threshold.enable) {
    threshold.lineThreshold?.forEach(item =>
      item.conditions?.forEach(c =>
        conditions.push({
          fieldId: item.fieldId,
          term: c.term,
          value: c.value,
          color: c.color,
          min: c.min,
          max: c.max
        })
      )
    )
  }
  return conditions
}

/**
 * 根据折线阈值条件获取新的标签颜色
 * @param conditions
 * @param value
 * @param fieldId
 */
export function getLineLabelColorByCondition(conditions, value, fieldId) {
  const fieldConditions = conditions.filter(item => item.fieldId === fieldId)
  let color = undefined
  if (fieldConditions.length) {
    fieldConditions.some(item => {
      if (
        (item.term === 'lt' && value < item.value) ||
        (item.term === 'le' && value <= item.value) ||
        (item.term === 'gt' && value > item.value) ||
        (item.term === 'ge' && value >= item.value) ||
        (item.term === 'between' && value >= item.min && value <= item.max)
      ) {
        color = item.color
        return true
      }
    })
  }
  return color
}

/**
 * 获取文本在画布中的测量信息
 * @param chart 图表内容
 * @param text 测量文本
 * @param font 文本样式
 * @param type 测量类型，高度宽度
 **/
export const measureText = (chart, text, font, type) => {
  const container = document.getElementById(chart.container)
  const canvas = container.querySelector('canvas')
  const ctx = canvas.getContext('2d')
  const { fontWeight, fontSize, fontFamily } = font
  ctx.font = [fontWeight, `${fontSize}px`, fontFamily].join(' ').trim()
  const textMetrics = ctx.measureText(text)
  if (type === 'height') {
    return textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent
  }
  if (type === 'width') {
    return textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft
  }
  return 0
}

/**
 * 获取十六进制颜色值
 * @param hex
 * @param alpha
 */
export const hexToRgba = (hex, alpha = 1) => {
  if (!hex.startsWith('#')) {
    return hex
  }
  // 去掉 # 号
  hex = hex.replace('#', '')
  // 转换为 RGB 分量
  const r = parseInt(hex.slice(0, 2), 16)
  const g = parseInt(hex.slice(2, 4), 16)
  const b = parseInt(hex.slice(4, 6), 16)
  const hexAlpha = hex.slice(6, 8)
  const a = hexAlpha ? parseInt(hex.slice(6, 8), 16) / 255 : alpha
  // 返回 RGBA 格式
  return `rgba(${r}, ${g}, ${b}, ${a})`
}

// 安全计算数值字段的总和，使用 Decimal 避免浮点数精度问题
export function safeDecimalSum(data, field) {
  // 使用 reduce 累加所有行的指定字段值
  return data
    .reduce((acc, row) => {
      // 将字段值转换为 Decimal 类型并累加到累加器
      return acc.plus(new Decimal(row[field] ?? 0))
    }, new Decimal(0))
    .toNumber() // 最终结果转换为普通数字返回
}

// 安全计算数值字段的平均值，使用 Decimal 避免浮点数精度问题
export function safeDecimalMean(data, field) {
  // 如果数据为空，直接返回 0
  if (!data.length) return 0
  // 计算所有行的指定字段值的总和
  const sum = data.reduce((acc, row) => {
    // 将字段值转换为 Decimal 类型并累加到累加器
    return acc.plus(new Decimal(row[field] ?? 0))
  }, new Decimal(0))
  // 将总和除以数据行数，得到平均值，并转换为普通数字返回
  return sum.dividedBy(data.length).toNumber()
}
