// @ts-check
import CoreUtils from '../CoreUtils'
import { applyProgressiveReveal } from '../Animations'

export default class Helpers {
  /**
   * @param {import('./Annotations').default} annoCtx
   */
  constructor(annoCtx) {
    this.w = annoCtx.w
    this.annoCtx = annoCtx
  }

  /**
   * @param {Record<string, any>} anno
   * @param {number | null} [annoIndex]
   */
  setOrientations(anno, annoIndex = null) {
    const w = this.w

    if (anno.label.orientation === 'vertical') {
      const i = annoIndex !== null ? annoIndex : 0
      const xAnno = w.dom.baseEl.querySelector(
        `.apexcharts-xaxis-annotations .apexcharts-xaxis-annotation-label[rel='${i}']`,
      )

      if (xAnno !== null) {
        const xAnnoCoord = /** @type {SVGGraphicsElement} */ (xAnno).getBBox()
        xAnno.setAttribute(
          'x',
          String(
            parseFloat(xAnno.getAttribute('x') ?? '0') - xAnnoCoord.height + 4,
          ),
        )

        const yOffset =
          anno.label.position === 'top' ? xAnnoCoord.width : -xAnnoCoord.width
        xAnno.setAttribute(
          'y',
          String(parseFloat(xAnno.getAttribute('y') ?? '0') + yOffset),
        )

        const { x, y } = this.annoCtx.graphics.rotateAroundCenter(xAnno)
        xAnno.setAttribute('transform', `rotate(-90 ${x} ${y})`)
      }
    }
  }

  /**
   * @param {any} annoEl
   * @param {Record<string, any>} anno
   */
  addBackgroundToAnno(annoEl, anno) {
    const w = this.w

    if (!annoEl || !anno.label.text || !String(anno.label.text).trim()) {
      return null
    }

    // We compute the difference between the bounding client rect and the BBox to
    // correctly scale the drawn rectangle when chart is in a container with a
    //  CSS zoom level != 100%.
    const gridEl = w.dom.baseEl.querySelector('.apexcharts-grid')
    if (!gridEl) return null
    const elGridRect = gridEl.getBoundingClientRect()
    const gridBBox = /** @type {SVGGraphicsElement} */ (gridEl).getBBox()
    const zoom = elGridRect.width / gridBBox.width || 1

    const coords = annoEl.getBoundingClientRect()

    let {
      left: pleft,
      right: pright,
      top: ptop,
      bottom: pbottom,
    } = anno.label.style.padding

    if (anno.label.orientation === 'vertical') {
      ;[ptop, pbottom, pleft, pright] = [pleft, pright, ptop, pbottom]
    }

    const x1 = (coords.left - elGridRect.left) / zoom - pleft
    const y1 = (coords.top - elGridRect.top) / zoom - ptop
    const elRect = this.annoCtx.graphics.drawRect(
      x1 - w.globals.barPadForNumericAxis,
      y1,
      coords.width / zoom + pleft + pright,
      coords.height / zoom + ptop + pbottom,
      anno.label.borderRadius,
      anno.label.style.background,
      1,
      anno.label.borderWidth,
      anno.label.borderColor,
      0,
    )

    if (anno.id) {
      elRect.node.classList.add(anno.id)
    }

    return elRect
  }

  annotationsBackground() {
    const w = this.w

    /**
     * @param {Record<string, any>} anno
     * @param {number} i
     * @param {string} type
     */
    const add = (anno, i, type) => {
      const annoLabel = w.dom.baseEl.querySelector(
        `.apexcharts-${type}-annotations .apexcharts-${type}-annotation-label[rel='${i}']`,
      )

      if (annoLabel) {
        const parent = annoLabel.parentNode
        const elRect = this.addBackgroundToAnno(annoLabel, anno)

        if (elRect) {
          parent?.insertBefore(elRect.node, annoLabel)

          // Mirror the label's progressive reveal onto the background rect so
          // it doesn't pop in before the line draw reaches the annotation x.
          const labelX = annoLabel.getAttribute('x')
          if (labelX !== null) {
            applyProgressiveReveal(elRect, parseFloat(labelX), w)
          }

          if (anno.label.mouseEnter) {
            elRect.node.addEventListener(
              'mouseenter',
              anno.label.mouseEnter.bind(this, anno),
            )
          }
          if (anno.label.mouseLeave) {
            elRect.node.addEventListener(
              'mouseleave',
              anno.label.mouseLeave.bind(this, anno),
            )
          }
          if (anno.label.click) {
            elRect.node.addEventListener(
              'click',
              anno.label.click.bind(this, anno),
            )
          }
        }
      }
    }

    /**
     * @param {Record<string, any>} anno
     * @param {number} i
     */
    w.config.annotations.xaxis.forEach(
      (/** @type {any} */ anno, /** @type {any} */ i) => add(anno, i, 'xaxis'),
    )
    /**
     * @param {Record<string, any>} anno
     * @param {number} i
     */
    w.config.annotations.yaxis.forEach(
      (/** @type {any} */ anno, /** @type {any} */ i) => add(anno, i, 'yaxis'),
    )
    /**
     * @param {Record<string, any>} anno
     * @param {number} i
     */
    w.config.annotations.points.forEach(
      (/** @type {any} */ anno, /** @type {any} */ i) => add(anno, i, 'point'),
    )
  }

  /**
   * @param {string} type
   * @param {Record<string, any>} anno
   */
  getY1Y2(type, anno) {
    const w = this.w
    const y = type === 'y1' ? anno.y : anno.y2
    let yP
    let clipped = false

    if (this.annoCtx.invertAxis) {
      const labels = w.config.xaxis.convertedCatToNumeric
        ? w.labelData.categoryLabels
        : w.labelData.labels
      const catIndex = labels.indexOf(y)
      const xLabel = w.dom.baseEl.querySelector(
        `.apexcharts-yaxis-texts-g text:nth-child(${catIndex + 1})`,
      )

      yP = xLabel
        ? parseFloat(xLabel.getAttribute('y') ?? '0')
        : (w.layout.gridHeight / labels.length - 1) * (catIndex + 1) -
          w.globals.barHeight

      if (anno.seriesIndex !== undefined && w.globals.barHeight) {
        yP -=
          (w.globals.barHeight / 2) * (w.seriesData.series.length - 1) -
          w.globals.barHeight * anno.seriesIndex
      }
    } else {
      const seriesIndex = w.globals.seriesYAxisMap[anno.yAxisIndex][0]
      const yPos = w.config.yaxis[anno.yAxisIndex].logarithmic
        ? new CoreUtils(this.w).getLogVal(
            w.config.yaxis[anno.yAxisIndex].logBase,
            y,
            seriesIndex,
          ) / /** @type {any} */ (w.globals).yLogRatio[seriesIndex]
        : (y - w.globals.minYArr[seriesIndex]) /
          (w.globals.yRange[seriesIndex] / w.layout.gridHeight)

      yP =
        w.layout.gridHeight - Math.min(Math.max(yPos, 0), w.layout.gridHeight)
      clipped = yPos > w.layout.gridHeight || yPos < 0

      if (anno.marker && (anno.y === undefined || anno.y === null)) {
        yP = 0
      }

      if (w.config.yaxis[anno.yAxisIndex]?.reversed) {
        yP = yPos
      }
    }

    if (typeof y === 'string' && y.includes('px')) {
      yP = parseFloat(y)
    }

    return { yP, clipped }
  }

  /**
   * @param {string} type
   * @param {Record<string, any>} anno
   */
  getX1X2(type, anno) {
    const w = this.w
    const x = type === 'x1' ? anno.x : anno.x2
    const min = this.annoCtx.invertAxis ? w.globals.minY : w.globals.minX
    const max = this.annoCtx.invertAxis ? w.globals.maxY : w.globals.maxX
    const range = this.annoCtx.invertAxis
      ? w.globals.yRange[0]
      : w.globals.xRange
    let clipped = false

    let xP = this.annoCtx.inversedReversedAxis
      ? (max - x) / (range / w.layout.gridWidth)
      : (x - min) / (range / w.layout.gridWidth)

    if (
      (w.config.xaxis.type === 'category' ||
        w.config.xaxis.convertedCatToNumeric) &&
      !this.annoCtx.invertAxis &&
      !w.axisFlags.dataFormatXNumeric
    ) {
      if (!w.config.chart.sparkline.enabled) {
        xP = this.getStringX(x)
      }
    }

    if (typeof x === 'string' && x.includes('px')) {
      xP = parseFloat(x)
    }

    if ((x === undefined || x === null) && anno.marker) {
      xP = w.layout.gridWidth
    }

    if (
      anno.seriesIndex !== undefined &&
      w.globals.barWidth &&
      !this.annoCtx.invertAxis
    ) {
      xP -=
        (w.globals.barWidth / 2) * (w.seriesData.series.length - 1) -
        w.globals.barWidth * anno.seriesIndex
    }

    if (typeof xP !== 'number') {
      xP = 0
      clipped = true
    }
    if (
      parseFloat(xP.toFixed(10)) > parseFloat(w.layout.gridWidth.toFixed(10))
    ) {
      xP = w.layout.gridWidth
      clipped = true
    } else if (xP < 0) {
      xP = 0
      clipped = true
    }

    return { x: xP, clipped }
  }

  /**
   * @param {number} x
   */
  getStringX(x) {
    const w = this.w
    let rX = x

    if (
      w.config.xaxis.convertedCatToNumeric &&
      w.labelData.categoryLabels.length
    ) {
      const strX = String(x)
      x =
        w.labelData.categoryLabels.findIndex(
          (/** @type {any} */ l) => String(l) === strX,
        ) + 1
    }

    const catIndex = w.labelData.labels
      /**
       * @param {any} item
       */
      .map((/** @type {any} */ item) =>
        Array.isArray(item) ? item.join(' ') : item,
      )
      .indexOf(x)

    const xLabel = w.dom.baseEl.querySelector(
      `.apexcharts-xaxis-texts-g text:nth-child(${catIndex + 1})`,
    )

    if (xLabel) {
      rX = parseFloat(xLabel.getAttribute('x') ?? '0')
    }

    return rX
  }
}
