// @ts-check
import Graphics from '../Graphics'
import AxesUtils from './AxesUtils'
import { BrowserAPIs } from '../../ssr/BrowserAPIs.js'
import { SVGNS } from '../../svg/math'

/**
 * ApexCharts YAxis Class for drawing Y-Axis.
 *
 * @module YAxis
 **/

export default class YAxis {
  /**
   * @param {import('../../types/internal').ChartStateW} w
   * @param {any} [elgrid]
   */
  constructor(w, { theme = null, timeScale = null } = {}, elgrid) {
    this.w = w
    this.elgrid = elgrid

    this.xaxisFontSize = w.config.xaxis.labels.style.fontSize
    this.axisFontFamily = w.config.xaxis.labels.style.fontFamily
    this.xaxisForeColors = w.config.xaxis.labels.style.colors
    this.isCategoryBarHorizontal =
      w.config.chart.type === 'bar' && w.config.plotOptions.bar.horizontal
    this.xAxisoffX =
      w.config.xaxis.position === 'bottom' ? w.layout.gridHeight : 0
    /** @type {any} */
    this.drawnLabels = []
    this.axesUtils = new AxesUtils(w, { theme, timeScale })
  }

  /**
   * @param {number} realIndex
   */
  drawYaxis(realIndex) {
    const w = this.w
    const graphics = new Graphics(this.w)
    const yaxisStyle = w.config.yaxis[realIndex].labels.style
    const {
      fontSize: yaxisFontSize,
      fontFamily: yaxisFontFamily,
      fontWeight: yaxisFontWeight,
    } = yaxisStyle

    const elYaxis = graphics.group({
      class: 'apexcharts-yaxis',
      rel: realIndex,
      transform: `translate(${w.globals.translateYAxisX[realIndex]}, 0)`,
    })

    if (this.axesUtils.isYAxisHidden(realIndex)) return elYaxis

    const elYaxisTexts = graphics.group({ class: 'apexcharts-yaxis-texts-g' })
    elYaxis.add(elYaxisTexts)

    const tickAmount = w.globals.yAxisScale[realIndex].result.length - 1
    const labelsDivider = w.layout.gridHeight / tickAmount
    const lbFormatter = w.formatters.yLabelFormatters[realIndex]
    const labels = this.axesUtils.checkForReversedLabels(
      realIndex,
      w.globals.yAxisScale[realIndex].result.slice(),
    )

    if (w.config.yaxis[realIndex].labels.show) {
      let lY = w.layout.translateY + w.config.yaxis[realIndex].labels.offsetY
      if (w.globals.isBarHorizontal) lY = 0
      else if (w.config.chart.type === 'heatmap') lY -= labelsDivider / 2
      lY += parseInt(yaxisFontSize, 10) / 3

      let firstLabel = null
      for (let i = tickAmount; i >= 0; i--) {
        const val = lbFormatter(labels[i], i, w)
        let xPad = w.config.yaxis[realIndex].labels.padding
        if (w.config.yaxis[realIndex].opposite && w.config.yaxis.length !== 0)
          xPad *= -1

        const textAnchor = this.getTextAnchor(
          w.config.yaxis[realIndex].labels.align,
          w.config.yaxis[realIndex].opposite,
        )
        const yColors = this.axesUtils.getYAxisForeColor(
          yaxisStyle.colors,
          realIndex,
        )
        const foreColor = Array.isArray(yColors) ? yColors[i] : yColors

        const existingYLabels = Array.from(
          w.dom.baseEl.querySelectorAll(
            `.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-label tspan`,
          ),
        ).map((label) => label.textContent)

        const label = graphics.drawText({
          x: xPad,
          y: lY,
          text:
            existingYLabels.includes(val) &&
            !w.config.yaxis[realIndex].labels.showDuplicates
              ? ''
              : val,
          textAnchor,
          fontSize: yaxisFontSize,
          fontFamily: yaxisFontFamily,
          fontWeight: yaxisFontWeight,
          maxWidth: w.config.yaxis[realIndex].labels.maxWidth,
          foreColor,
          isPlainText: false,
          cssClass: `apexcharts-yaxis-label ${yaxisStyle.cssClass}`,
        })

        elYaxisTexts.add(label)
        this.addTooltip(label, val)

        if (firstLabel === null) {
          firstLabel = label
        }

        if (w.config.yaxis[realIndex].labels.rotate !== 0) {
          this.rotateLabel(
            graphics,
            label,
            firstLabel,
            w.config.yaxis[realIndex].labels.rotate,
          )
        }

        lY += labelsDivider
      }
    }

    this.addYAxisTitle(graphics, elYaxis, realIndex)
    this.addAxisBorder(graphics, elYaxis, realIndex, tickAmount, labelsDivider)

    return elYaxis
  }

  /**
   * @param {string} align
   * @param {boolean} opposite
   */
  getTextAnchor(align, opposite) {
    if (align === 'left') return 'start'
    if (align === 'center') return 'middle'
    if (align === 'right') return 'end'
    return opposite ? 'start' : 'end'
  }

  /**
   * @param {any} label
   * @param {any} val
   */
  addTooltip(label, val) {
    const elTooltipTitle = BrowserAPIs.createElementNS(SVGNS, 'title')
    elTooltipTitle.textContent = Array.isArray(val) ? val.join(' ') : val
    label.node.appendChild(elTooltipTitle)
  }

  /**
   * @param {import('../Graphics').default} graphics
   * @param {any} label
   * @param {any} firstLabel
   * @param {number} rotate
   */
  rotateLabel(graphics, label, firstLabel, rotate) {
    const firstLabelCenter = graphics.rotateAroundCenter(firstLabel.node)
    const labelCenter = graphics.rotateAroundCenter(label.node)
    label.node.setAttribute(
      'transform',
      `rotate(${rotate} ${firstLabelCenter.x} ${labelCenter.y})`,
    )
  }

  /**
   * @param {import('../Graphics').default} graphics
   * @param {any} elYaxis
   * @param {number} realIndex
   */
  addYAxisTitle(graphics, elYaxis, realIndex) {
    const w = this.w
    if (w.config.yaxis[realIndex].title.text !== undefined) {
      const elYaxisTitle = graphics.group({ class: 'apexcharts-yaxis-title' })
      const x = w.config.yaxis[realIndex].opposite
        ? w.globals.translateYAxisX[realIndex]
        : 0
      const elYAxisTitleText = graphics.drawText({
        x,
        y:
          w.layout.gridHeight / 2 +
          w.layout.translateY +
          w.config.yaxis[realIndex].title.offsetY,
        text: w.config.yaxis[realIndex].title.text,
        textAnchor: 'end',
        foreColor: w.config.yaxis[realIndex].title.style.color,
        fontSize: w.config.yaxis[realIndex].title.style.fontSize,
        fontWeight: w.config.yaxis[realIndex].title.style.fontWeight,
        fontFamily: w.config.yaxis[realIndex].title.style.fontFamily,
        cssClass: `apexcharts-yaxis-title-text ${w.config.yaxis[realIndex].title.style.cssClass}`,
      })
      elYaxisTitle.add(elYAxisTitleText)
      elYaxis.add(elYaxisTitle)
    }
  }

  /**
   * @param {import('../Graphics').default} graphics
   * @param {any} elYaxis
   * @param {number} realIndex
   * @param {number} tickAmount
   * @param {number} labelsDivider
   */
  addAxisBorder(graphics, elYaxis, realIndex, tickAmount, labelsDivider) {
    const w = this.w
    const axisBorder = w.config.yaxis[realIndex].axisBorder
    let x = 31 + axisBorder.offsetX
    if (w.config.yaxis[realIndex].opposite) x = -31 - axisBorder.offsetX

    if (axisBorder.show) {
      const elVerticalLine = graphics.drawLine(
        x,
        w.layout.translateY + axisBorder.offsetY - 2,
        x,
        w.layout.gridHeight + w.layout.translateY + axisBorder.offsetY + 2,
        axisBorder.color,
        0,
        axisBorder.width,
      )
      elYaxis.add(elVerticalLine)
    }

    if (w.config.yaxis[realIndex].axisTicks.show) {
      this.axesUtils.drawYAxisTicks(
        x,
        tickAmount,
        axisBorder,
        w.config.yaxis[realIndex].axisTicks,
        realIndex,
        labelsDivider,
        elYaxis,
      )
    }
  }

  /**
   * @param {number} realIndex
   */
  drawYaxisInversed(realIndex) {
    const w = this.w
    const graphics = new Graphics(this.w)

    const elXaxis = graphics.group({
      class: 'apexcharts-xaxis apexcharts-yaxis-inversed',
    })

    const elXaxisTexts = graphics.group({
      class: 'apexcharts-xaxis-texts-g',
      transform: `translate(${w.layout.translateXAxisX}, ${w.layout.translateXAxisY})`,
    })

    elXaxis.add(elXaxisTexts)

    let tickAmount = w.globals.yAxisScale[realIndex].result.length - 1
    const labelsDivider = w.layout.gridWidth / tickAmount + 0.1
    let l = labelsDivider + w.config.xaxis.labels.offsetX
    const lbFormatter = w.formatters.xLabelFormatter
    let labels = this.axesUtils.checkForReversedLabels(
      realIndex,
      w.globals.yAxisScale[realIndex].result.slice(),
    )
    const timescaleLabels = w.labelData.timescaleLabels

    if (timescaleLabels.length > 0) {
      this.xaxisLabels = timescaleLabels.slice()
      labels = timescaleLabels.slice()
      tickAmount = labels.length
    }

    if (w.config.xaxis.labels.show) {
      for (
        let i = timescaleLabels.length ? 0 : tickAmount;
        timescaleLabels.length ? i < timescaleLabels.length : i >= 0;
        timescaleLabels.length ? i++ : i--
      ) {
        let val = lbFormatter?.(labels[i], i, w)
        let x =
          w.layout.gridWidth +
          w.globals.padHorizontal -
          (l - labelsDivider + w.config.xaxis.labels.offsetX)

        if (timescaleLabels.length) {
          const label = this.axesUtils.getLabel(
            labels,
            timescaleLabels,
            x,
            i,
            this.drawnLabels,
            this.xaxisFontSize,
          )
          x = label.x
          val = label.text
          this.drawnLabels.push(label.text)
          if (i === 0 && w.globals.skipFirstTimelinelabel) val = ''
          if (i === labels.length - 1 && w.globals.skipLastTimelinelabel)
            val = ''
        }

        const elTick = graphics.drawText({
          x,
          y:
            this.xAxisoffX +
            w.config.xaxis.labels.offsetY +
            30 -
            (w.config.xaxis.position === 'top'
              ? w.layout.xAxisHeight + w.config.xaxis.axisTicks.height - 2
              : 0),
          text: val,
          textAnchor: 'middle',
          foreColor: Array.isArray(this.xaxisForeColors)
            ? this.xaxisForeColors[realIndex]
            : this.xaxisForeColors,
          fontSize: this.xaxisFontSize,
          fontFamily: this.axisFontFamily,
          fontWeight: w.config.xaxis.labels.style.fontWeight,
          isPlainText: false,
          cssClass: `apexcharts-xaxis-label ${w.config.xaxis.labels.style.cssClass}`,
        })

        elXaxisTexts.add(elTick)
        this.addTooltip(elTick, val)
        l += labelsDivider
      }
    }

    this.inversedYAxisTitleText(elXaxis)
    this.inversedYAxisBorder(elXaxis)

    return elXaxis
  }

  /**
   * @param {any} parent
   */
  inversedYAxisBorder(parent) {
    const w = this.w
    const graphics = new Graphics(this.w)
    const axisBorder = w.config.xaxis.axisBorder

    if (axisBorder.show) {
      let lineCorrection = 0
      if (w.config.chart.type === 'bar' && w.axisFlags.isXNumeric)
        lineCorrection -= 15

      const elHorzLine = graphics.drawLine(
        w.globals.padHorizontal + lineCorrection + axisBorder.offsetX,
        this.xAxisoffX,
        w.layout.gridWidth,
        this.xAxisoffX,
        axisBorder.color,
        0,
        axisBorder.height,
      )

      if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
        this.elgrid.elGridBorders.add(elHorzLine)
      } else {
        parent.add(elHorzLine)
      }
    }
  }

  /**
   * @param {any} parent
   */
  inversedYAxisTitleText(parent) {
    const w = this.w
    const graphics = new Graphics(this.w)

    if (w.config.xaxis.title.text !== undefined) {
      const elYaxisTitle = graphics.group({
        class: 'apexcharts-xaxis-title apexcharts-yaxis-title-inversed',
      })
      const elYAxisTitleText = graphics.drawText({
        x: w.layout.gridWidth / 2 + w.config.xaxis.title.offsetX,
        y:
          this.xAxisoffX +
          parseFloat(this.xaxisFontSize) +
          parseFloat(w.config.xaxis.title.style.fontSize) +
          w.config.xaxis.title.offsetY +
          20,
        text: w.config.xaxis.title.text,
        textAnchor: 'middle',
        fontSize: w.config.xaxis.title.style.fontSize,
        fontFamily: w.config.xaxis.title.style.fontFamily,
        fontWeight: w.config.xaxis.title.style.fontWeight,
        foreColor: w.config.xaxis.title.style.color,
        cssClass: `apexcharts-xaxis-title-text ${w.config.xaxis.title.style.cssClass}`,
      })

      elYaxisTitle.add(elYAxisTitleText)
      parent.add(elYaxisTitle)
    }
  }

  /**
   * @param {number} realIndex
   * @param {boolean} yAxisOpposite
   */
  yAxisTitleRotate(realIndex, yAxisOpposite) {
    const w = this.w
    const graphics = new Graphics(this.w)
    const elYAxisLabelsWrap = w.dom.baseEl.querySelector(
      `.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-texts-g`,
    )
    const yAxisLabelsCoord = elYAxisLabelsWrap
      ? elYAxisLabelsWrap.getBoundingClientRect()
      : { width: 0, height: 0 }
    const yAxisTitle = w.dom.baseEl.querySelector(
      `.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-title text`,
    )
    const yAxisTitleCoord = yAxisTitle
      ? yAxisTitle.getBoundingClientRect()
      : { width: 0, height: 0 }

    if (yAxisTitle) {
      const x = this.xPaddingForYAxisTitle(
        realIndex,
        yAxisLabelsCoord,
        yAxisTitleCoord,
        yAxisOpposite,
      )
      yAxisTitle.setAttribute('x', String(x.xPos - (yAxisOpposite ? 10 : 0)))
      const titleRotatingCenter = graphics.rotateAroundCenter(yAxisTitle)
      yAxisTitle.setAttribute(
        'transform',
        `rotate(${
          yAxisOpposite
            ? w.config.yaxis[realIndex].title.rotate * -1
            : w.config.yaxis[realIndex].title.rotate
        } ${titleRotatingCenter.x} ${titleRotatingCenter.y})`,
      )
    }
  }

  /**
   * @param {number} realIndex
   * @param {{width: number, height: number}} yAxisLabelsCoord
   * @param {{width: number, height: number}} yAxisTitleCoord
   * @param {boolean} yAxisOpposite
   */
  xPaddingForYAxisTitle(
    realIndex,
    yAxisLabelsCoord,
    yAxisTitleCoord,
    yAxisOpposite,
  ) {
    const w = this.w
    let x = 0
    let padd = 10

    if (w.config.yaxis[realIndex].title.text === undefined || realIndex < 0) {
      return { xPos: x, padd: 0 }
    }

    if (yAxisOpposite) {
      x =
        yAxisLabelsCoord.width +
        w.config.yaxis[realIndex].title.offsetX +
        yAxisTitleCoord.width / 2 +
        padd / 2
    } else {
      x =
        yAxisLabelsCoord.width * -1 +
        w.config.yaxis[realIndex].title.offsetX +
        padd / 2 +
        yAxisTitleCoord.width / 2
      if (w.globals.isBarHorizontal) {
        padd = 25
        x =
          yAxisLabelsCoord.width * -1 -
          w.config.yaxis[realIndex].title.offsetX -
          padd
      }
    }

    return { xPos: x, padd }
  }

  /**
   * @param {Array<{width: number, height: number}>} yaxisLabelCoords
   * @param {Array<{width: number, height: number}>} yTitleCoords
   */
  setYAxisXPosition(yaxisLabelCoords, yTitleCoords) {
    const w = this.w
    let xLeft = 0
    let xRight = 0
    let leftOffsetX = 18
    let rightOffsetX = 1

    if (w.config.yaxis.length > 1) this.multipleYs = true

    /**
     * @param {ApexYAxis} yaxe
     * @param {number} index
     */
    w.config.yaxis.forEach((yaxe, index) => {
      const shouldNotDrawAxis =
        w.globals.ignoreYAxisIndexes.includes(index) ||
        !yaxe.show ||
        yaxe.floating ||
        yaxisLabelCoords[index].width === 0
      const axisWidth =
        yaxisLabelCoords[index].width + yTitleCoords[index].width

      if (!yaxe.opposite) {
        xLeft = w.layout.translateX - leftOffsetX
        if (!shouldNotDrawAxis) leftOffsetX += axisWidth + 20
        w.globals.translateYAxisX[index] = xLeft + yaxe.labels.offsetX
      } else {
        if (w.globals.isBarHorizontal) {
          xRight = w.layout.gridWidth + w.layout.translateX - 1
          w.globals.translateYAxisX[index] = xRight - yaxe.labels.offsetX
        } else {
          xRight = w.layout.gridWidth + w.layout.translateX + rightOffsetX
          if (!shouldNotDrawAxis) rightOffsetX += axisWidth + 20
          w.globals.translateYAxisX[index] = xRight - yaxe.labels.offsetX + 20
        }
      }
    })
  }

  setYAxisTextAlignments() {
    const w = this.w
    const yaxis = Array.from(
      w.dom.baseEl.getElementsByClassName('apexcharts-yaxis'),
    )

    yaxis.forEach((y, index) => {
      const yaxe = w.config.yaxis[index]
      if (yaxe && !yaxe.floating && yaxe.labels.align !== undefined) {
        const yAxisInner = w.dom.baseEl.querySelector(
          `.apexcharts-yaxis[rel='${index}'] .apexcharts-yaxis-texts-g`,
        )
        const yAxisTexts = Array.from(
          w.dom.baseEl.querySelectorAll(
            `.apexcharts-yaxis[rel='${index}'] .apexcharts-yaxis-label`,
          ),
        )
        const rect = /** @type {Element} */ (yAxisInner).getBoundingClientRect()

        yAxisTexts.forEach((label) => {
          label.setAttribute('text-anchor', yaxe.labels.align)
        })

        if (yaxe.labels.align === 'left' && !yaxe.opposite) {
          ;/** @type {Element} */ (yAxisInner).setAttribute(
            'transform',
            `translate(-${rect.width}, 0)`,
          )
        } else if (yaxe.labels.align === 'center') {
          ;/** @type {Element} */ (yAxisInner).setAttribute(
            'transform',
            `translate(${(rect.width / 2) * (!yaxe.opposite ? -1 : 1)}, 0)`,
          )
        } else if (yaxe.labels.align === 'right' && yaxe.opposite) {
          ;/** @type {Element} */ (yAxisInner).setAttribute(
            'transform',
            `translate(${rect.width}, 0)`,
          )
        }
      }
    })
  }
}
