// @ts-check
import DateTime from '../utils/DateTime'
import Dimensions from './dimensions/Dimensions'
import Graphics from './Graphics'
import Utils from '../utils/Utils'

const MINUTES_IN_DAY = 24 * 60
const SECONDS_IN_DAY = MINUTES_IN_DAY * 60
const MIN_ZOOM_DAYS = 10 / SECONDS_IN_DAY

/**
 * ApexCharts TimeScale Class for generating time ticks for x-axis.
 *
 * @module TimeScale
 **/

class TimeScale {
  /**
   * @param {import('../types/internal').ChartStateW} w
   * @param {import('../types/internal').ChartContext} ctx
   */
  constructor(w, ctx) {
    this.w = w
    this.ctx = ctx // needed: new Dimensions(this.ctx)
    /** @type {any} */
    this.timeScaleArray = []
    this.utc = this.w.config.xaxis.labels.datetimeUTC
  }

  /**
   * @param {number} minX
   * @param {number} maxX
   */
  calculateTimeScaleTicks(minX, maxX) {
    const w = this.w

    // null check when no series to show
    if (w.globals.allSeriesCollapsed) {
      w.labelData.labels = []
      w.labelData.timescaleLabels = []
      return []
    }

    const dt = new DateTime(this.w)

    const daysDiff = (maxX - minX) / (1000 * SECONDS_IN_DAY)
    this.determineInterval(daysDiff)

    w.interact.disableZoomIn = false
    w.interact.disableZoomOut = false

    if (daysDiff < MIN_ZOOM_DAYS) {
      w.interact.disableZoomIn = true
    } else if (daysDiff > 50000) {
      w.interact.disableZoomOut = true
    }

    const timeIntervals = dt.getTimeUnitsfromTimestamp(minX, maxX)

    const daysWidthOnXAxis = w.layout.gridWidth / daysDiff
    const hoursWidthOnXAxis = daysWidthOnXAxis / 24
    const minutesWidthOnXAxis = hoursWidthOnXAxis / 60
    const secondsWidthOnXAxis = minutesWidthOnXAxis / 60

    const numberOfHours = Math.floor(daysDiff * 24)
    const numberOfMinutes = Math.floor(daysDiff * MINUTES_IN_DAY)
    const numberOfSeconds = Math.floor(daysDiff * SECONDS_IN_DAY)
    const numberOfDays = Math.floor(daysDiff)
    const numberOfMonths = Math.floor(daysDiff / 30)
    const numberOfYears = Math.floor(daysDiff / 365)

    const firstVal = {
      minMillisecond: timeIntervals.minMillisecond,
      minSecond: timeIntervals.minSecond,
      minMinute: timeIntervals.minMinute,
      minHour: timeIntervals.minHour,
      minDate: timeIntervals.minDate,
      minMonth: timeIntervals.minMonth,
      minYear: timeIntervals.minYear,
    }

    const currentMillisecond = firstVal.minMillisecond
    const currentSecond = firstVal.minSecond
    const currentMinute = firstVal.minMinute
    const currentHour = firstVal.minHour
    const currentMonthDate = firstVal.minDate
    const currentDate = firstVal.minDate
    const currentMonth = firstVal.minMonth
    const currentYear = firstVal.minYear

    const params = {
      firstVal,
      currentMillisecond,
      currentSecond,
      currentMinute,
      currentHour,
      currentMonthDate,
      currentDate,
      currentMonth,
      currentYear,
      daysWidthOnXAxis,
      hoursWidthOnXAxis,
      minutesWidthOnXAxis,
      secondsWidthOnXAxis,
      numberOfSeconds,
      numberOfMinutes,
      numberOfHours,
      numberOfDays,
      numberOfMonths,
      numberOfYears,
    }

    switch (this.tickInterval) {
      case 'years': {
        this.generateYearScale(params)
        break
      }
      case 'months':
      case 'half_year': {
        this.generateMonthScale(params)
        break
      }
      case 'months_days':
      case 'months_fortnight':
      case 'days':
      case 'week_days': {
        this.generateDayScale(params)
        break
      }
      case 'hours': {
        this.generateHourScale(params)
        break
      }
      case 'minutes_fives':
      case 'minutes':
        this.generateMinuteScale(params)
        break
      case 'seconds_tens':
      case 'seconds_fives':
      case 'seconds':
        this.generateSecondScale(params)
        break
    }

    // first, we will adjust the month values index
    // as in the upper function, it is starting from 0
    // we will start them from 1
    const adjustedMonthInTimeScaleArray = this.timeScaleArray.map(
      (/** @type {any} */ ts) => {
        const defaultReturn = {
          position: ts.position,
          unit: ts.unit,
          year: ts.year,
          day: ts.day ? ts.day : 1,
          hour: ts.hour ? ts.hour : 0,
          month: ts.month + 1,
        }
        if (ts.unit === 'month') {
          return {
            ...defaultReturn,
            day: 1,
            value: ts.value + 1,
          }
        } else if (ts.unit === 'day' || ts.unit === 'hour') {
          return {
            ...defaultReturn,
            value: ts.value,
          }
        } else if (ts.unit === 'minute') {
          return {
            ...defaultReturn,
            value: ts.value,
            minute: ts.value,
          }
        } else if (ts.unit === 'second') {
          return {
            ...defaultReturn,
            value: ts.value,
            minute: ts.minute,
            second: ts.second,
          }
        }

        return ts
      },
    )

    const filteredTimeScale = adjustedMonthInTimeScaleArray.filter(
      (/** @type {any} */ ts) => {
        let modulo = 1
        let ticks = Math.ceil(w.layout.gridWidth / 120)
        const value = ts.value
        if (w.config.xaxis.tickAmount !== undefined) {
          ticks = w.config.xaxis.tickAmount
        }
        if (adjustedMonthInTimeScaleArray.length > ticks) {
          modulo = Math.floor(adjustedMonthInTimeScaleArray.length / ticks)
        }

        let shouldNotSkipUnit = false // there is a big change in unit i.e days to months
        let shouldNotPrint = false // should skip these values

        switch (this.tickInterval) {
          case 'years':
            // make years label denser
            if (ts.unit === 'year') {
              shouldNotSkipUnit = true
            }
            break
          case 'half_year':
            modulo = 7
            if (ts.unit === 'year') {
              shouldNotSkipUnit = true
            }
            break
          case 'months':
            modulo = 1
            if (ts.unit === 'year') {
              shouldNotSkipUnit = true
            }
            break
          case 'months_fortnight':
            modulo = 15
            if (ts.unit === 'year' || ts.unit === 'month') {
              shouldNotSkipUnit = true
            }
            if (value === 30) {
              shouldNotPrint = true
            }
            break
          case 'months_days':
            modulo = 10
            if (ts.unit === 'month') {
              shouldNotSkipUnit = true
            }
            if (value === 30) {
              shouldNotPrint = true
            }
            break
          case 'week_days':
            modulo = 8
            if (ts.unit === 'month') {
              shouldNotSkipUnit = true
            }
            break
          case 'days':
            modulo = 1
            if (ts.unit === 'month') {
              shouldNotSkipUnit = true
            }
            break
          case 'hours':
            if (ts.unit === 'day') {
              shouldNotSkipUnit = true
            }
            break
          case 'minutes_fives':
            if (value % 5 !== 0) {
              shouldNotPrint = true
            }
            break
          case 'seconds_tens':
            if (value % 10 !== 0) {
              shouldNotPrint = true
            }
            break
          case 'seconds_fives':
            if (value % 5 !== 0) {
              shouldNotPrint = true
            }
            break
        }

        if (
          this.tickInterval === 'hours' ||
          this.tickInterval === 'minutes_fives' ||
          this.tickInterval === 'seconds_tens' ||
          this.tickInterval === 'seconds_fives'
        ) {
          if (!shouldNotPrint) {
            return true
          }
        } else {
          if ((value % modulo === 0 || shouldNotSkipUnit) && !shouldNotPrint) {
            return true
          }
        }
      },
    )

    return filteredTimeScale
  }

  /**
   * @param {Array<Record<string, any>>} filteredTimeScale
   */
  recalcDimensionsBasedOnFormat(filteredTimeScale) {
    const w = this.w
    const reformattedTimescaleArray = this.formatDates(filteredTimeScale)

    const removedOverlappingTS = this.removeOverlappingTS(
      reformattedTimescaleArray,
    )

    w.labelData.timescaleLabels = removedOverlappingTS.slice()

    // at this stage, we need to re-calculate coords of the grid as timeline labels may have altered the xaxis labels coords
    // The reason we can't do this prior to this stage is because timeline labels depends on gridWidth, and as the ticks are calculated based on available gridWidth, there can be unknown number of ticks generated for different minX and maxX
    // Dependency on Dimensions(), need to refactor correctly
    // TODO - find an alternate way to avoid calling this Heavy method twice
    const dimensions = new Dimensions(this.w, this.ctx)
    // Phase 1: return value captured; writer stub is a no-op.
    const layoutState = dimensions.plotCoords()
    this.ctx._writeLayoutCoords(layoutState.layout)
  }

  /**
   * @param {number} daysDiff
   */
  determineInterval(daysDiff) {
    const yearsDiff = daysDiff / 365
    const hoursDiff = daysDiff * 24
    const minutesDiff = hoursDiff * 60
    const secondsDiff = minutesDiff * 60
    switch (true) {
      case yearsDiff > 5:
        this.tickInterval = 'years'
        break
      case daysDiff > 800:
        this.tickInterval = 'half_year'
        break
      case daysDiff > 180:
        this.tickInterval = 'months'
        break
      case daysDiff > 90:
        this.tickInterval = 'months_fortnight'
        break
      case daysDiff > 60:
        this.tickInterval = 'months_days'
        break
      case daysDiff > 30:
        this.tickInterval = 'week_days'
        break
      case daysDiff > 2:
        this.tickInterval = 'days'
        break
      case hoursDiff > 2.4:
        this.tickInterval = 'hours'
        break
      case minutesDiff > 15:
        this.tickInterval = 'minutes_fives'
        break
      case minutesDiff > 5:
        this.tickInterval = 'minutes'
        break
      case minutesDiff > 1:
        this.tickInterval = 'seconds_tens'
        break
      case secondsDiff > 20:
        this.tickInterval = 'seconds_fives'
        break
      default:
        this.tickInterval = 'seconds'
        break
    }
  }

  /** @param {{firstVal: any, currentMonth: any, currentYear: any, daysWidthOnXAxis: any, numberOfYears: any}} opts */
  generateYearScale({
    firstVal,
    currentMonth,
    currentYear,
    daysWidthOnXAxis,
    numberOfYears,
  }) {
    let firstTickValue = firstVal.minYear
    let firstTickPosition = 0
    const dt = new DateTime(this.w)

    const unit = 'year'

    if (firstVal.minDate > 1 || firstVal.minMonth > 0) {
      const remainingDays = dt.determineRemainingDaysOfYear(
        firstVal.minYear,
        firstVal.minMonth,
        firstVal.minDate,
      )

      // remainingDaysofFirstMonth is used to reacht the 2nd tick position
      const remainingDaysOfFirstYear =
        dt.determineDaysOfYear(firstVal.minYear) - remainingDays + 1

      // calculate the first tick position
      firstTickPosition = remainingDaysOfFirstYear * daysWidthOnXAxis
      firstTickValue = firstVal.minYear + 1
      // push the first tick in the array
      this.timeScaleArray.push({
        position: firstTickPosition,
        value: firstTickValue,
        unit,
        year: firstTickValue,
        month: 1,
      })
    } else if (firstVal.minDate === 1 && firstVal.minMonth === 0) {
      // push the first tick in the array
      this.timeScaleArray.push({
        position: firstTickPosition,
        value: firstTickValue,
        unit,
        year: currentYear,
        month: Utils.monthMod(currentMonth + 1),
      })
    }

    let year = firstTickValue
    let pos = firstTickPosition

    // keep drawing rest of the ticks
    for (let i = 0; i < numberOfYears; i++) {
      year++
      pos = dt.determineDaysOfYear(year - 1) * daysWidthOnXAxis + pos
      this.timeScaleArray.push({
        position: pos,
        value: year,
        unit,
        year,
        month: 1,
      })
    }
  }

  /** @param {{firstVal: any, currentMonthDate: any, currentMonth: any, currentYear: any, daysWidthOnXAxis: any, numberOfMonths: any}} opts */
  generateMonthScale({
    firstVal,
    currentMonthDate,
    currentMonth,
    currentYear,
    daysWidthOnXAxis,
    numberOfMonths,
  }) {
    let firstTickValue = currentMonth
    let firstTickPosition = 0
    const dt = new DateTime(this.w)
    let unit = 'month'
    let yrCounter = 0

    if (firstVal.minDate > 1) {
      // remainingDaysofFirstMonth is used to reacht the 2nd tick position
      const remainingDaysOfFirstMonth =
        dt.determineDaysOfMonths(currentMonth + 1, firstVal.minYear) -
        currentMonthDate +
        1

      // calculate the first tick position
      firstTickPosition = remainingDaysOfFirstMonth * daysWidthOnXAxis
      firstTickValue = Utils.monthMod(currentMonth + 1)

      let year = currentYear + yrCounter
      let month = Utils.monthMod(firstTickValue)
      let value = firstTickValue
      // it's Jan, so update the year
      if (firstTickValue === 0) {
        unit = 'year'
        value = year
        month = 1
        yrCounter += 1
        year = year + yrCounter
      }

      // push the first tick in the array
      this.timeScaleArray.push({
        position: firstTickPosition,
        value,
        unit,
        year,
        month,
      })
    } else {
      // push the first tick in the array
      this.timeScaleArray.push({
        position: firstTickPosition,
        value: firstTickValue,
        unit,
        year: currentYear,
        month: Utils.monthMod(currentMonth),
      })
    }

    let month = firstTickValue + 1
    let pos = firstTickPosition

    // keep drawing rest of the ticks
    for (let i = 0, j = 1; i < numberOfMonths; i++, j++) {
      month = Utils.monthMod(month)

      if (month === 0) {
        unit = 'year'
        yrCounter += 1
      } else {
        unit = 'month'
      }
      const year = this._getYear(currentYear, month, yrCounter)

      pos = dt.determineDaysOfMonths(month, year) * daysWidthOnXAxis + pos
      const monthVal = month === 0 ? year : month
      this.timeScaleArray.push({
        position: pos,
        value: monthVal,
        unit,
        year,
        month: month === 0 ? 1 : month,
      })
      month++
    }
  }

  /** @param {{firstVal: any, currentMonth: any, currentYear: any, hoursWidthOnXAxis: any, numberOfDays: any}} opts */
  generateDayScale({
    firstVal,
    currentMonth,
    currentYear,
    hoursWidthOnXAxis,
    numberOfDays,
  }) {
    const dt = new DateTime(this.w)
    let unit = 'day'
    let firstTickValue = firstVal.minDate + 1
    let date = firstTickValue

    /**
     * @param {number} dateVal
     * @param {number} month
     * @param {number} year
     */
    const changeMonth = (dateVal, month, year) => {
      const monthdays = dt.determineDaysOfMonths(month + 1, year)

      if (dateVal > monthdays) {
        month = month + 1
        date = 1
        unit = 'month'
        val = month
        return month
      }

      return month
    }

    const remainingHours = 24 - firstVal.minHour
    const yrCounter = 0

    // calculate the first tick position
    let firstTickPosition = remainingHours * hoursWidthOnXAxis

    let val = firstTickValue
    let month = changeMonth(date, currentMonth, currentYear)

    if (firstVal.minHour === 0 && firstVal.minDate === 1) {
      // the first value is the first day of month
      firstTickPosition = 0
      val = Utils.monthMod(firstVal.minMonth)
      unit = 'month'
      date = firstVal.minDate
      // numberOfDays++
      // removed the above line to fix https://github.com/apexcharts/apexcharts.js/issues/305#issuecomment-1019520513
    } else if (
      firstVal.minDate !== 1 &&
      firstVal.minHour === 0 &&
      firstVal.minMinute === 0
    ) {
      // fixes apexcharts/apexcharts.js/issues/1730
      firstTickPosition = 0
      firstTickValue = firstVal.minDate
      date = firstTickValue
      val = firstTickValue
      // in case it's the last date of month, we need to check it
      month = changeMonth(date, currentMonth, currentYear)

      if (val !== 1) {
        unit = 'day'
      }
    }

    // push the first tick in the array
    this.timeScaleArray.push({
      position: firstTickPosition,
      value: val,
      unit,
      year: this._getYear(currentYear, month, yrCounter),
      month: Utils.monthMod(month),
      day: date,
    })

    let pos = firstTickPosition
    // keep drawing rest of the ticks
    for (let i = 0; i < numberOfDays; i++) {
      date += 1
      unit = 'day'
      month = changeMonth(
        date,
        month,
        this._getYear(currentYear, month, yrCounter),
      )

      const year = this._getYear(currentYear, month, yrCounter)

      pos = 24 * hoursWidthOnXAxis + pos
      const value = date === 1 ? Utils.monthMod(month) : date
      this.timeScaleArray.push({
        position: pos,
        value,
        unit,
        year,
        month: Utils.monthMod(month),
        day: value,
      })
    }
  }

  /** @param {{firstVal: any, currentDate: any, currentMonth: any, currentYear: any, minutesWidthOnXAxis: any, numberOfHours: any}} opts */
  generateHourScale({
    firstVal,
    currentDate,
    currentMonth,
    currentYear,
    minutesWidthOnXAxis,
    numberOfHours,
  }) {
    const dt = new DateTime(this.w)

    const yrCounter = 0
    let unit = 'hour'

    /**
     * @param {number} dateVal
     * @param {number} month
     */
    const changeDate = (dateVal, month) => {
      const monthdays = dt.determineDaysOfMonths(month + 1, currentYear)
      if (dateVal > monthdays) {
        date = 1
        month = month + 1
      }
      return { month, date }
    }

    /**
     * @param {number} dateVal
     * @param {number} month
     */
    const changeMonth = (dateVal, month) => {
      const monthdays = dt.determineDaysOfMonths(month + 1, currentYear)
      if (dateVal > monthdays) {
        month = month + 1
        return month
      }

      return month
    }

    // factor in minSeconds as well
    const remainingMins = 60 - (firstVal.minMinute + firstVal.minSecond / 60.0)

    let firstTickPosition = remainingMins * minutesWidthOnXAxis
    let firstTickValue = firstVal.minHour + 1
    let hour = firstTickValue

    if (remainingMins === 60) {
      firstTickPosition = 0
      firstTickValue = firstVal.minHour
      hour = firstTickValue
    }

    let date = currentDate

    // we need to apply date switching logic here as well, to avoid duplicated labels
    if (hour >= 24) {
      hour = 0
      date += 1
      unit = 'day'
      // Unit changed to day , Value should align unit
      firstTickValue = date
    }

    const checkNextMonth = changeDate(date, currentMonth)

    let month = checkNextMonth.month
    month = changeMonth(date, month)

    // Sync firstTickValue with date after potential month rollover
    // (changeDate may have reset date to 1 for months shorter than 31 days)
    if (unit === 'day') {
      firstTickValue = date
    }

    // push the first tick in the array
    this.timeScaleArray.push({
      position: firstTickPosition,
      value: firstTickValue,
      unit,
      day: date,
      hour,
      year: currentYear,
      month: Utils.monthMod(month),
    })

    hour++

    let pos = firstTickPosition
    // keep drawing rest of the ticks
    for (let i = 0; i < numberOfHours; i++) {
      unit = 'hour'

      if (hour >= 24) {
        hour = 0
        date += 1
        unit = 'day'

        const checkNextMonth = changeDate(date, month)

        month = checkNextMonth.month
        month = changeMonth(date, month)
      }

      const year = this._getYear(currentYear, month, yrCounter)
      pos = 60 * minutesWidthOnXAxis + pos
      const val = hour === 0 ? date : hour
      this.timeScaleArray.push({
        position: pos,
        value: val,
        unit,
        hour,
        day: date,
        year,
        month: Utils.monthMod(month),
      })

      hour++
    }
  }

  /** @param {{currentMillisecond: any, currentSecond: any, currentMinute: any, currentHour: any, currentDate: any, currentMonth: any, currentYear: any, minutesWidthOnXAxis: any, secondsWidthOnXAxis: any, numberOfMinutes: any}} opts */
  generateMinuteScale({
    currentMillisecond,
    currentSecond,
    currentMinute,
    currentHour,
    currentDate,
    currentMonth,
    currentYear,
    minutesWidthOnXAxis,
    secondsWidthOnXAxis,
    numberOfMinutes,
  }) {
    const dt = new DateTime(this.w)
    const yrCounter = 0
    const unit = 'minute'

    const remainingSecs = 60 - currentSecond
    let firstTickPosition =
      (remainingSecs - currentMillisecond / 1000) * secondsWidthOnXAxis
    let minute = currentMinute + 1

    if (currentSecond === 0 && currentMillisecond === 0) {
      firstTickPosition = 0
      minute = currentMinute
    }

    let date = currentDate
    let month = currentMonth
    const year = currentYear
    let hour = currentHour

    let pos = firstTickPosition
    for (let i = 0; i < numberOfMinutes; i++) {
      if (minute >= 60) {
        minute = 0
        hour += 1
        if (hour === 24) {
          hour = 0
          date += 1
          const monthDays = dt.determineDaysOfMonths(
            month + 1,
            this._getYear(year, month, yrCounter),
          )
          if (date > monthDays) {
            date = 1
            month += 1
          }
        }
      }

      this.timeScaleArray.push({
        position: pos,
        value: minute,
        unit,
        hour,
        minute,
        day: date,
        year: this._getYear(year, month, yrCounter),
        month: Utils.monthMod(month),
      })

      pos += minutesWidthOnXAxis
      minute++
    }
  }

  /** @param {{currentMillisecond: any, currentSecond: any, currentMinute: any, currentHour: any, currentDate: any, currentMonth: any, currentYear: any, secondsWidthOnXAxis: any, numberOfSeconds: any}} opts */
  generateSecondScale({
    currentMillisecond,
    currentSecond,
    currentMinute,
    currentHour,
    currentDate,
    currentMonth,
    currentYear,
    secondsWidthOnXAxis,
    numberOfSeconds,
  }) {
    const yrCounter = 0
    const unit = 'second'

    const remainingMillisecs = 1000 - currentMillisecond
    let firstTickPosition = (remainingMillisecs / 1000) * secondsWidthOnXAxis

    let second = currentSecond + 1

    if (currentMillisecond === 0) {
      firstTickPosition = 0
      second = currentSecond
    }
    let minute = currentMinute
    const date = currentDate
    const month = currentMonth
    const year = currentYear
    let hour = currentHour

    let pos = firstTickPosition
    for (let i = 0; i < numberOfSeconds; i++) {
      if (second >= 60) {
        minute++
        second = 0
        if (minute >= 60) {
          hour++
          minute = 0
          if (hour === 24) {
            hour = 0
          }
        }
      }

      this.timeScaleArray.push({
        position: pos,
        value: second,
        unit,
        hour,
        minute,
        second,
        day: date,
        year: this._getYear(year, month, yrCounter),
        month: Utils.monthMod(month),
      })

      pos += secondsWidthOnXAxis
      second++
    }
  }

  /**
   * @param {Record<string, any>} ts
   * @param {string | number} value
   */
  createRawDateString(ts, value) {
    let raw = ts.year

    if (ts.month === 0) {
      // invalid month, correct it
      ts.month = 1
    }
    raw += '-' + ('0' + ts.month.toString()).slice(-2)

    // unit is day
    if (ts.unit === 'day') {
      raw += '-' + ('0' + value).slice(-2)
    } else {
      raw += '-' + ('0' + (ts.day ? ts.day : '1')).slice(-2)
    }

    // unit is hour
    if (ts.unit === 'hour') {
      raw += 'T' + ('0' + value).slice(-2)
    } else {
      raw += 'T' + ('0' + (ts.hour ? ts.hour : '0')).slice(-2)
    }

    if (ts.unit === 'minute') {
      raw += ':' + ('0' + value).slice(-2)
    } else {
      raw += ':' + (ts.minute ? ('0' + ts.minute).slice(-2) : '00')
    }

    if (ts.unit === 'second') {
      raw += ':' + ('0' + value).slice(-2)
    } else {
      raw += ':00'
    }

    if (this.utc) {
      raw += '.000Z'
    }
    return raw
  }

  /**
   * @param {Array<Record<string, any>>} filteredTimeScale
   */
  formatDates(filteredTimeScale) {
    const w = this.w

    /**
     * @param {Record<string, any>} ts
     */
    const reformattedTimescaleArray = filteredTimeScale.map(
      (/** @type {any} */ ts) => {
        let value = ts.value.toString()

        const dt = new DateTime(this.w)

        const raw = this.createRawDateString(ts, value)

        let dateToFormat = dt.getDate(dt.parseDate(raw))
        if (!this.utc) {
          // Fixes #1726, #1544, #1485, #1255
          dateToFormat = dt.getDate(dt.parseDateWithTimezone(raw))
        }

        if (w.config.xaxis.labels.format === undefined) {
          let customFormat = 'dd MMM'
          const dtFormatter = w.config.xaxis.labels.datetimeFormatter
          if (ts.unit === 'year') customFormat = dtFormatter.year
          if (ts.unit === 'month') customFormat = dtFormatter.month
          if (ts.unit === 'day') customFormat = dtFormatter.day
          if (ts.unit === 'hour') customFormat = dtFormatter.hour
          if (ts.unit === 'minute') customFormat = dtFormatter.minute
          if (ts.unit === 'second') customFormat = dtFormatter.second

          value = dt.formatDate(dateToFormat, customFormat)
        } else {
          value = dt.formatDate(dateToFormat, w.config.xaxis.labels.format)
        }

        return {
          dateString: raw,
          position: ts.position,
          value,
          unit: ts.unit,
          year: ts.year,
          month: ts.month,
        }
      },
    )

    return reformattedTimescaleArray
  }

  /**
   * @param {any[]} arr
   */
  removeOverlappingTS(arr) {
    const graphics = new Graphics(this.w)

    let equalLabelLengthFlag = false // These labels got same length?
    /** @type {number | undefined} */
    let constantLabelWidth // If true, what is the constant length to use
    if (
      arr.length > 0 && // check arr length
      arr[0].value && // check arr[0] contains value
      /**
       * @param {Record<string, any>} lb
       */
      arr.every((lb) => lb.value.length === arr[0].value.length) // check every arr label value is the same as the first one
    ) {
      equalLabelLengthFlag = true // These labels got same length
      constantLabelWidth = graphics.getTextRects(
        arr[0].value,
        this.w.config.xaxis.labels.style.fontSize,
      ).width // The constant label width to use
    }

    let lastDrawnIndex = 0

    /**
     * @param {Record<string, any>} item
     * @param {number} index
     */
    let filteredArray = arr.map((item, index) => {
      if (index > 0 && this.w.config.xaxis.labels.hideOverlappingLabels) {
        const prevLabelWidth = !equalLabelLengthFlag // if vary in label length
          ? graphics.getTextRects(
              /** @type {any} */ (arr[lastDrawnIndex]).value,
              this.w.config.xaxis.labels.style.fontSize,
            ).width // get individual length
          : constantLabelWidth // else: use constant length
        const prevPos = arr[lastDrawnIndex].position
        const pos = item.position

        if (pos > prevPos + prevLabelWidth + 10) {
          lastDrawnIndex = index
          return item
        } else {
          return null
        }
      } else {
        return item
      }
    })

    /**
     * @param {any} f
     */
    filteredArray = filteredArray.filter((/** @type {any} */ f) => f !== null)

    return filteredArray
  }

  /**
   * @param {number} currentYear
   * @param {number} month
   * @param {number} yrCounter
   */
  _getYear(currentYear, month, yrCounter) {
    return currentYear + Math.floor(month / 12) + yrCounter
  }
}

export default TimeScale
