import { valueFormatter } from '@/views/chart/components/js/formatter'
import { hexToRgba, parseJson } from '@/views/chart/components/js/util'
import { isEmpty } from 'lodash-es'

export const clearExtremum = chart => {
  // 清除图表标注
  const pointElement = document.getElementById(chartPointParentId(chart))
  if (pointElement) {
    pointElement.remove()
    pointElement.parentNode?.removeChild(pointElement)
  }
}

/**
 * 判断给定的RGBA字符串表示的颜色是亮色还是暗色
 * 通过计算RGB颜色值的加权平均值（灰度值），判断颜色的明暗
 * 如果给定的字符串不包含有效的RGBA值，则原样返回该字符串
 *
 * @param rgbaString 一个RGBA颜色字符串，例如 "rgba(255, 255, 255, 1)"
 * @param greyValue 灰度值默认128
 * @returns 如果计算出的灰度值大于等于128，则返回true，表示亮色；否则返回false，表示暗色。
 *          如果rgbaString不包含有效的RGBA值，则返回原字符串
 */
const isColorLight = (rgbaString: string, greyValue = 128) => {
  const lastRGBA = getRgbaColorLastRgba(rgbaString)
  if (!isEmpty(lastRGBA)) {
    // 计算灰度值的公式
    const grayLevel = lastRGBA.r * 0.299 + lastRGBA.g * 0.587 + lastRGBA.b * 0.114
    return grayLevel >= greyValue
  } else {
    return false
  }
}

/**
 * 从给定的rgba颜色字符串中提取最后一个rgba值
 * @param rgbaString 包含一个或多个rgba颜色值的字符串
 * @returns 返回最后一个解析出的rgba对象，如果未找到rgba值，则返回null
 */
const getRgbaColorLastRgba = (rgbaString: string) => {
  const rgbaPattern = /rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/g
  let match: string[]
  let lastRGBA = null
  while ((match = rgbaPattern.exec(rgbaString)) !== null) {
    const r = parseInt(match[1])
    const g = parseInt(match[2])
    const b = parseInt(match[3])
    const a = parseFloat(match[4])
    lastRGBA = { r, g, b, a }
  }
  return lastRGBA
}

function createExtremumDiv(id, value, formatterCfg, chart) {
  // 空值不处理
  if (!value && value != 0) {
    return
  }
  // 装标注的div
  const parentElement = document.getElementById(chartPointParentId(chart))
  if (parentElement) {
    // 标注div
    const oldElement = document.getElementById(id)
    if (oldElement) {
      oldElement.remove()
      oldElement.parentNode?.removeChild(oldElement)
    }
    const div = document.createElement('div')
    div.id = id
    div.className = 'child'
    div.setAttribute(
      'style',
      `width: auto;
        height: auto;
        border-radius: 2px;
        position: relative;
        padding: 4px 5px 4px 5px;
        display:none;
        transform: translateX(-50%);
        opacity: 1;
        transition: opacity 0.2s ease-in-out;
        white-space:nowrap;
        overflow:auto;`
    )
    div.textContent = valueFormatter(value, formatterCfg)
    const span = document.createElement('span')
    span.setAttribute(
      'style',
      `width: 0px;
        height: 12px;
        border: 4px solid transparent;
        border-top-color: red;
        position: absolute;
        left: calc(50% - 4px);
        margin-top:4px;`
    )
    div.appendChild(span)
    parentElement.appendChild(div)
  }
}
/**
 * 没有子类别字段的图表
 * @param chart
 */
const noChildrenFieldChart = chart => {
  return ['area', 'bar'].includes(chart.type)
}

/**
 * 支持最值图表的折线图，面积图，柱状图，分组柱状图
 * @param chart
 */
export const supportExtremumChartType = chart => {
  return ['line', 'area', 'bar', 'bar-group'].includes(chart.type)
}

const chartContainerId = chart => {
  return chart.container + '_'
}

const chartPointParentId = chart => {
  return chart.container + '_point_' + chart.id + '_'
}

function removeDivsWithPrefix(parentDivId, prefix) {
  const parentDiv = document.getElementById(parentDivId)
  if (!parentDiv) {
    console.error('Parent div not found')
    return
  }
  const childDivs = parentDiv.getElementsByTagName('div')
  for (let i = childDivs.length - 1; i >= 0; i--) {
    const div = childDivs[i]
    if (div.id && div.id.startsWith(prefix)) {
      div.parentNode.removeChild(div)
    }
  }
}

export const extremumEvt = (newChart, chart, _options, container) => {
  chart.container = container
  clearExtremum(chart)
  if (!supportExtremumChartType(chart)) {
    return
  }
  const { label: labelAttr } = parseJson(chart.customAttr)
  const { yAxis } = parseJson(chart)
  newChart.once('beforerender', ev => {
    ev.view.on('beforepaint', () => {
      newChart.chart.geometries[0]?.beforeMappingData.forEach(i => {
        i.forEach(item => {
          delete item._origin.EXTREME
        })
        const { minItem, maxItem } = findMinMax(
          i.filter(item => item?._origin?.value !== null && item?._origin?.value !== undefined)
        )
        if (!minItem || !maxItem) {
          return
        }
        let showExtremum = false
        if (noChildrenFieldChart(chart) || yAxis.length > 1) {
          const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find(
            d =>
              (d.chartShowName ? d.chartShowName : d.name === minItem._origin.category) ||
              (d.chartShowName ? d.chartShowName : d.name === maxItem._origin.category)
          )
          showExtremum = seriesLabelFormatter?.showExtremum
        } else {
          if (['bar-group'].includes(chart.type)) {
            showExtremum = labelAttr.showExtremum
          } else {
            showExtremum = labelAttr.seriesLabelFormatter[0]?.showExtremum
          }
        }
        if (showExtremum) {
          minItem._origin.EXTREME = true
          maxItem._origin.EXTREME = true
        }
      })
    })
    newChart.chart.geometries[0].on('afteranimate', () => {
      createExtremumPoint(chart, ev)
    })
  })
  newChart.on('legend-item:click', ev => {
    const legendHideData = ev.view
      .getController('legend')
      .components[0].component.cfg.items.filter(l => l.unchecked)
    if (legendHideData.length > 0) {
      legendHideData.forEach(l => {
        const seriesKey = chartContainerId(chart) + chartPointParentId(chart) + l.id
        removeDivsWithPrefix(chartPointParentId(chart), seriesKey)
      })
    }
  })
}

const findMinMax = (data): { minItem; maxItem } => {
  return data.reduce(
    ({ minItem, maxItem }, currentItem) => {
      if (minItem === undefined || currentItem._origin.value < minItem._origin.value) {
        minItem = currentItem
      }
      if (maxItem === undefined || currentItem._origin.value > maxItem._origin.value) {
        maxItem = currentItem
      }
      delete minItem?._origin.EXTREME
      delete maxItem?._origin.EXTREME
      return { minItem, maxItem }
    },
    { minItem: undefined, maxItem: undefined }
  )
}
export const createExtremumPoint = (chart, ev) => {
  // 获取标注样式
  const { label: labelAttr, basicStyle } = parseJson(chart.customAttr)
  const pointSize = basicStyle.lineSymbolSize
  const { yAxis } = parseJson(chart)
  clearExtremum(chart)
  // 创建标注父元素
  const divParentElement = document.getElementById(chartPointParentId(chart))
  if (!divParentElement) {
    const divParent = document.createElement('div')
    divParent.id = chartPointParentId(chart)
    divParent.style.position = 'fixed'
    divParent.style.zIndex = '1'
    divParent.style.opacity = '0'
    divParent.style.transition = 'opacity 0.2s ease-in-out'
    divParent.style.overflow = 'visible'
    divParent.style['pointer-events'] = 'none'
    // 将父标注加入到图表中
    const containerElement = document.getElementById(chart.container)
    containerElement.insertBefore(divParent, containerElement.firstChild)
    // 处理最值闪烁的问题
    let opacity = 0
    const animate = () => {
      // 增加不透明度
      opacity += 0.19
      if (opacity >= 1) {
        cancelAnimationFrame(animationFrameId)
        return
      }
      divParent.style.opacity = opacity + ''
      animationFrameId = requestAnimationFrame(animate)
    }
    let animationFrameId = requestAnimationFrame(animate)
  }
  let geometriesDataArray = []
  // 获取数据点
  const intervalPoint = ev.view
    .getGeometries()
    .find((intervalItem: { type: string }) => intervalItem.type === 'interval')
  if (intervalPoint) {
    geometriesDataArray = intervalPoint.dataArray
  }
  const pointPoint = ev.view
    .getGeometries()
    .find((pointItem: { type: string }) => pointItem.type === 'point')
  if (pointPoint) {
    geometriesDataArray = pointPoint.dataArray
  }
  performChunk(geometriesDataArray, pointObjList => {
    if (pointObjList && pointObjList.length > 0) {
      const pointObj = pointObjList[0]
      const [minItem, maxItem] = pointObjList.filter(i => i._origin.EXTREME)
      let attr
      let showExtremum = false
      if (noChildrenFieldChart(chart) || yAxis.length > 1) {
        const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find(d =>
          d.chartShowName
            ? d.chartShowName === pointObj._origin.category
            : d.name === pointObj._origin.category
        )
        showExtremum = seriesLabelFormatter?.showExtremum
        attr = seriesLabelFormatter
      } else {
        if (['bar-group'].includes(chart.type)) {
          showExtremum = labelAttr.showExtremum
        } else {
          showExtremum = labelAttr.seriesLabelFormatter[0]?.showExtremum
          attr = labelAttr.seriesLabelFormatter[0]
        }
      }
      const fontSize = attr ? attr.fontSize : labelAttr.fontSize
      if (!minItem) {
        return
      }
      const maxKey =
        chartContainerId(chart) +
        chartPointParentId(chart) +
        pointObj._origin.category +
        '_' +
        (maxItem ? maxItem._origin.value : minItem._origin.value)
      const minKey =
        chartContainerId(chart) +
        chartPointParentId(chart) +
        pointObj._origin.category +
        '_' +
        minItem._origin.value
      // 最值标注
      if (showExtremum && labelAttr.show) {
        if (maxItem) {
          createExtremumDiv(
            maxKey,
            maxItem._origin.value,
            attr ? attr.formatterCfg : labelAttr.labelFormatter,
            chart
          )
        }
        createExtremumDiv(
          minKey,
          minItem._origin.value,
          attr ? attr.formatterCfg : labelAttr.labelFormatter,
          chart
        )
        pointObjList.forEach(point => {
          const pointElement = document.getElementById(
            chartContainerId(chart) +
              chartPointParentId(chart) +
              point._origin.category +
              '_' +
              point._origin.value
          )
          if (pointElement && point._origin.EXTREME) {
            pointElement.style.position = 'absolute'
            const top =
              (point.y[1] ? point.y[1] : point.y) - (fontSize + (pointSize ? pointSize : 0) + 12)
            pointElement.style.top = top + 'px'
            pointElement.style.left = point.x + 'px'
            pointElement.style.zIndex = '10'
            pointElement.style.fontSize = fontSize + 'px'
            pointElement.style.lineHeight = fontSize + 'px'
            // 渐变颜色时需要获取最后一个rgba的值作为背景
            const color = point.color.startsWith('#')
              ? hexToRgba(point.color, basicStyle.alpha / 100)
              : getRgbaColorLastRgba(point.color)
            const { r, b, g, a } = color
            pointElement.style.backgroundColor = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'
            pointElement.style.color = isColorLight(point.color) ? '#000' : '#fff'
            pointElement.children[0]['style'].borderTopColor =
              'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'
            pointElement.style.display = 'table'
            // 显示箭头
            const childNode = pointElement.childNodes[1]
            // 最值在数据点下方显示
            const translateYValue = Math.ceil(point.y + Math.abs(Math.floor(top)) + 6)
            // 最值dom高度超过50%时，最值dom向下
            if (top < 0 && (Math.abs(top) / point.y) * 100 >= 50) {
              pointElement.style.transform = `translateX(-50%) translateY(${translateYValue}px)`
              childNode.style.marginTop = '-16px'
              childNode.style.transform = 'rotate(180deg)'
            } else {
              childNode.style.display = 'block'
            }
          }
        })
      } else {
        removeDivElement(maxKey)
        removeDivElement(minKey)
      }
    }
  })
}

function removeDivElement(key) {
  const element = document.getElementById(key)
  if (element) {
    element.remove()
    element.parentNode?.removeChild(element)
  }
}

/**
 * 当浏览器不支持requestIdleCallback时，使用setTimeout模拟
 * 该模拟函数会在浏览器空闲时执行回调函数，避免阻塞主线程
 * 模拟的回调函数会在16毫秒后执行，模拟浏览器的空闲时间
 * 该模拟函数会返回一个定时器ID，可以用来取消定时器
 * @param cb 回调函数，接收一个IdleDeadline对象作为参数
 * @returns 返回一个定时器ID，可以用来取消定时器
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
 */
if (typeof window.requestIdleCallback !== 'function') {
  window.requestIdleCallback = function (cb: IdleRequestCallback): number {
    return window.setTimeout(() => {
      cb({
        timeRemaining: () => Math.max(0, 50 - (Date.now() % 50)),
        didTimeout: false
      })
    }, 16)
  }
  window.cancelIdleCallback = function (id: number) {
    clearTimeout(id)
  }
}
/**
 * 用于分批处理数据，利用requestIdleCallback在浏览器空闲期间执行任务，避免阻塞主线程
 * @param dataList
 * @param taskHandler
 */
function performChunk(dataList, taskHandler) {
  if (typeof dataList === 'number') {
    dataList = { length: dataList }
  }
  if (dataList.length === 0) return
  let i = 0
  function _run() {
    if (i >= dataList.length) return
    // 请求浏览器空闲期间执行的回调函数
    if (typeof window.requestIdleCallback == 'function') {
      requestIdleCallback(idle => {
        // 在当前空闲期间内尽可能多地处理任务，直到时间耗尽或所有任务处理完毕
        while (idle.timeRemaining() > 0 && i < dataList.length) {
          taskHandler(dataList[i], i)
          i++
        }
        _run()
      })
    }
  }
  _run()
}
