// @ts-check
import Animations from '../modules/Animations'
import Graphics from '../modules/Graphics'
import Fill from '../modules/Fill'
import Series from '../modules/Series'
import Utils from '../utils/Utils'
import Helpers from './common/treemap/Helpers'
import Filters from '../modules/Filters'

/**
 * ApexCharts HeatMap Class.
 * @module HeatMap
 **/

export default class HeatMap {
  /**
   * @param {import('../types/internal').ChartStateW} w
   * @param {import('../types/internal').ChartContext} ctx
   * @param {import('../types/internal').XYRatios} xyRatios
   */
  constructor(w, ctx, xyRatios) {
    this.ctx = ctx
    this.w = w

    this.xRatio = xyRatios.xRatio
    this.yRatio = xyRatios.yRatio

    this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation

    this.helpers = new Helpers(w, ctx)
    this.rectRadius = this.w.config.plotOptions.heatmap.radius
    this.strokeWidth = this.w.config.stroke.show
      ? this.w.config.stroke.width
      : 0
  }

  /**
   * @param {any[]} series
   */
  draw(series) {
    const w = this.w
    const graphics = new Graphics(this.w, this.ctx)

    const ret = graphics.group({
      class: 'apexcharts-heatmap',
    })

    ret.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)

    // width divided into equal parts
    const xDivision = w.layout.gridWidth / w.globals.dataPoints
    const yDivision = w.layout.gridHeight / w.seriesData.series.length

    let y1 = 0
    let rev = false

    this.negRange = this.helpers.checkColorRange()

    const heatSeries = series.slice()

    if (w.config.yaxis[0].reversed) {
      rev = true
      heatSeries.reverse()
    }

    for (
      let i = rev ? 0 : heatSeries.length - 1;
      rev ? i < heatSeries.length : i >= 0;
      rev ? i++ : i--
    ) {
      // el to which series will be drawn
      const elSeries = graphics.group({
        class: `apexcharts-series apexcharts-heatmap-series`,
        seriesName: Utils.escapeString(w.seriesData.seriesNames[i]),
        rel: i + 1,
        'data:realIndex': i,
      })
      Series.addCollapsedClassToSeries(this.w, elSeries, i)

      // Set up event delegation once per series group instead of per-cell listeners
      graphics.setupEventDelegation(elSeries, '.apexcharts-heatmap-rect')

      if (w.config.chart.dropShadow.enabled) {
        const shadow = w.config.chart.dropShadow
        const filters = new Filters(this.w)
        filters.dropShadow(elSeries, shadow, i)
      }

      let x1 = 0
      const shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity

      let j = 0
      for (let dIndex = 0; dIndex < w.globals.dataPoints; dIndex++) {
        // Recognize gaps and align values based on x axis

        if (w.seriesData.seriesX.length && !w.globals.allSeriesHasEqualX) {
          if (
            w.globals.minX + w.globals.minXDiff * dIndex <
            w.seriesData.seriesX[i][j]
          ) {
            x1 = x1 + xDivision
            continue
          }
        }

        // Stop loop if index is out of array length
        if (j >= heatSeries[i].length) break

        const heatColor = this.helpers.getShadeColor(
          w.config.chart.type,
          i,
          j,
          this.negRange,
        )
        let color = heatColor.color
        const heatColorProps = heatColor.colorProps

        if (w.config.fill.type === 'image') {
          const fill = new Fill(this.w)

          color = fill.fillPath({
            seriesNumber: i,
            dataPointIndex: j,
            opacity: /** @type {any} */ (w.globals).hasNegs
              ? heatColorProps.percent < 0
                ? 1 - (1 + heatColorProps.percent / 100)
                : shadeIntensity + heatColorProps.percent / 100
              : heatColorProps.percent / 100,
            patternID: Utils.randomId(),
            width: w.config.fill.image.width
              ? w.config.fill.image.width
              : xDivision,
            height: w.config.fill.image.height
              ? w.config.fill.image.height
              : yDivision,
          })
        }

        const radius = this.rectRadius

        const rect = graphics.drawRect(x1, y1, xDivision, yDivision, radius)
        rect.attr({
          cx: x1,
          cy: y1,
        })
        rect.node.classList.add('apexcharts-heatmap-rect')
        elSeries.add(rect)

        rect.attr({
          fill: color,
          i,
          index: i,
          j,
          val: series[i][j],
          'stroke-width': this.strokeWidth,
          stroke: w.config.plotOptions.heatmap.useFillColorAsStroke
            ? color
            : w.globals.stroke.colors[0],
          color,
        })

        if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
          let speed = 1
          if (!w.globals.resized) {
            speed = w.config.chart.animations.speed
          }
          this.animateHeatMap(rect, x1, y1, xDivision, yDivision, speed)
        }

        if (w.globals.dataChanged) {
          let speed = 1
          if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
            speed = this.dynamicAnim.speed

            let colorFrom =
              w.globals.previousPaths[i] &&
              w.globals.previousPaths[i][j] &&
              w.globals.previousPaths[i][j].color

            if (!colorFrom) colorFrom = 'rgba(255, 255, 255, 0)'

            this.animateHeatColor(
              rect,
              Utils.isColorHex(colorFrom)
                ? colorFrom
                : Utils.rgb2hex(colorFrom),
              Utils.isColorHex(color) ? color : Utils.rgb2hex(color),
              speed,
            )
          }
        }

        const formatter = w.config.dataLabels.formatter
        const formattedText = formatter(w.seriesData.series[i][j], {
          value: w.seriesData.series[i][j],
          seriesIndex: i,
          dataPointIndex: j,
          w,
        })

        const dataLabels = this.helpers.calculateDataLabels({
          text: formattedText,
          x: x1 + xDivision / 2,
          y: y1 + yDivision / 2,
          i,
          j,
          colorProps: heatColorProps,
          series: heatSeries,
        })
        if (dataLabels !== null) {
          elSeries.add(dataLabels)
        }

        x1 = x1 + xDivision
        j++
      }

      y1 = y1 + yDivision

      ret.add(elSeries)
    }

    // adjust yaxis labels for heatmap
    const yAxisScale = /** @type {any[]} */ (
      w.globals.yAxisScale[0].result.slice()
    )
    if (w.config.yaxis[0].reversed) {
      yAxisScale.unshift('')
    } else {
      yAxisScale.push('')
    }
    w.globals.yAxisScale[0].result = yAxisScale

    return ret
  }

  /**
   * @param {any} el
   * @param {number} x
   * @param {number} y
   * @param {number} width
   * @param {number} height
   * @param {number} speed
   */
  animateHeatMap(el, x, y, width, height, speed) {
    const animations = new Animations(this.w)
    animations.animateRect(
      el,
      {
        x: x + width / 2,
        y: y + height / 2,
        width: 0,
        height: 0,
      },
      {
        x,
        y,
        width,
        height,
      },
      speed,
      () => {
        animations.animationCompleted(el)
      },
    )
  }

  /**
   * @param {any} el
   * @param {string} colorFrom
   * @param {string} colorTo
   * @param {number} speed
   */
  animateHeatColor(el, colorFrom, colorTo, speed) {
    el.attr({
      fill: colorFrom,
    })
      .animate(speed)
      .attr({
        fill: colorTo,
      })
  }
}
