// @ts-check
import Config from './settings/Config'
import Globals from './settings/Globals'
import { Environment } from '../utils/Environment'

/** @typedef {import('../types/internal').ChartStateW} ChartStateW */
/** @typedef {import('../types/internal').ChartContext} ChartContext */

/**
 * ApexCharts Base Class for extending user options with pre-defined ApexCharts config.
 *
 * @module Base
 **/
export default class Base {
  /**
   * @param {object} opts
   */
  constructor(opts) {
    this.opts = opts
  }

  /**
   * Build and return the full chart state object `w`.
   * @returns {import('../types/internal').ChartStateW}
   */
  init() {
    const config = new Config(this.opts).init({ responsiveOverride: false })
    const globals = new Globals().init(config)

    const w = {
      config,
      globals,
      dom: {}, // DOM node cache — lives here, not inside globals
      interact: {
        // Tool mode (derived from toolbar config at construction, updated by Toolbar)
        zoomEnabled:
          config.chart.toolbar.autoSelected === 'zoom' &&
          config.chart.toolbar.tools.zoom &&
          config.chart.zoom.enabled,
        panEnabled:
          config.chart.toolbar.autoSelected === 'pan' &&
          config.chart.toolbar.tools.pan,
        selectionEnabled:
          config.chart.toolbar.autoSelected === 'selection' &&
          config.chart.toolbar.tools.selection,
        // Zoom / pan state (user-driven, must persist across re-renders)
        zoomed: false,
        selection: undefined,
        visibleXRange: undefined,
        selectedDataPoints: [],
        // Mouse / pointer state
        mousedown: false,
        clientX: null,
        clientY: null,
        lastClientPosition: {},
        lastWheelExecution: 0,
        // Tooltip capture state
        capturedSeriesIndex: -1,
        capturedDataPointIndex: -1,
        // Timescale zoom bounds (reset per render by TimeScale)
        disableZoomIn: false,
        disableZoomOut: false,
        // Device detection (set once at construction)
        isTouchDevice: Environment.isBrowser()
          ? 'ontouchstart' in window || navigator.maxTouchPoints > 0
          : false,
      },
      formatters: {
        // Populated by Formatters.setLabelFormatters() each render
        xLabelFormatter: undefined,
        yLabelFormatters: [],
        xaxisTooltipFormatter: undefined,
        ttKeyFormatter: undefined,
        ttVal: undefined,
        ttZFormatter: undefined,
        legendFormatter: undefined,
      },
      // Candlestick / boxplot OHLC arrays — written by Data.handleCandleStickBoxData()
      // each render; empty for all other chart types.
      candleData: {
        seriesCandleO: [],
        seriesCandleH: [],
        seriesCandleM: [],
        seriesCandleL: [],
        seriesCandleC: [],
      },
      // Range chart arrays — written by Data.handleRangeData() each render;
      // empty for all other chart types.
      rangeData: {
        seriesRangeStart: [],
        seriesRangeEnd: [],
        seriesRange: [],
      },
      // Label / category data — written by Data.parseData() and TimeScale each render.
      labelData: {
        labels: [],
        categoryLabels: [],
        timescaleLabels: [], // written by TimeScale.calculateTimeScaleMinMax()
        hasXaxisGroups: false,
        groups: [],
        seriesGroups: [],
      },
      // Axis / parsing behaviour flags — written by Data.parseData() each render.
      axisFlags: {
        isXNumeric: false,
        dataFormatXNumeric: false,
        isDataXYZ: false,
        isRangeData: false,
        isRangeBar: false,
        isMultiLineX: false,
        noLabelsProvided: false,
        dataWasParsed: false,
      },
      // Parsed series data — written by Data.parseData() each render.
      // Note: initialSeries and originalSeries are intentionally excluded —
      // they are persistent (survive re-renders) and remain on w.globals.
      seriesData: {
        series: [], // main y-values array
        seriesNames: [],
        seriesX: [],
        seriesZ: [],
        seriesColors: [],
        seriesGoals: [],
        stackedSeriesTotals: [],
        stackedSeriesTotalsByGroups: [],
      },
      // Grid / axis layout computed by Dimensions.plotCoords() each render.
      // gridWidth/gridHeight/translateX/translateY are also used as starting
      // points by Dimensions on the next render (accumulated values), so the
      // shim must be bidirectional — reads and writes both route correctly.
      layout: {
        gridHeight: 0,
        gridWidth: 0,
        translateX: 0,
        translateY: 0,
        translateXAxisX: 0,
        translateXAxisY: 0,
        rotateXLabels: false,
        xAxisHeight: 0,
        xAxisLabelsHeight: 0,
        xAxisGroupLabelsHeight: 0,
        xAxisLabelsWidth: 0,
        yLabelsCoords: [],
        yTitleCoords: [],
      },
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Public API shims — PERMANENT.
    //
    // `w.globals` is part of ApexCharts' documented public API: user code in
    // formatter callbacks, custom tooltips, event handlers, etc. routinely
    // reads `ctx.w.globals.series`, `opt.w.globals.labels`, and similar.
    // These Object.defineProperty accessors keep every sliced-out property
    // visible on `w.globals` so external code never breaks, regardless of
    // where the canonical data now lives internally.
    //
    // Do NOT remove these shims. They are not scaffolding — they are the
    // stable, versioned surface between internal typed slices and the outside
    // world.
    // ─────────────────────────────────────────────────────────────────────────

    // w.globals.dom → w.dom
    Object.defineProperty(globals, 'dom', {
      get() {
        return w.dom
      },
      set(v) {
        w.dom = v
      },
      enumerable: false,
      configurable: true,
    })

    // w.globals.<formatter> → w.formatters.<formatter>
    for (const key of [
      'xLabelFormatter',
      'yLabelFormatters',
      'xaxisTooltipFormatter',
      'ttKeyFormatter',
      'ttVal',
      'ttZFormatter',
      'legendFormatter',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.formatters)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.formatters)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<interact> → w.interact.<interact>
    for (const key of [
      'zoomEnabled',
      'panEnabled',
      'selectionEnabled',
      'zoomed',
      'selection',
      'visibleXRange',
      'selectedDataPoints',
      'mousedown',
      'clientX',
      'clientY',
      'lastClientPosition',
      'lastWheelExecution',
      'capturedSeriesIndex',
      'capturedDataPointIndex',
      'disableZoomIn',
      'disableZoomOut',
      'isTouchDevice',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.interact)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.interact)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<layout> → w.layout.<layout>
    // These override the own-properties set in Globals.globalVars() (gridWidth, gridHeight,
    // translateX, translateY, etc.) — Object.defineProperty replaces them with accessors.
    for (const key of [
      'gridHeight',
      'gridWidth',
      'translateX',
      'translateY',
      'translateXAxisX',
      'translateXAxisY',
      'rotateXLabels',
      'xAxisHeight',
      'xAxisLabelsHeight',
      'xAxisGroupLabelsHeight',
      'xAxisLabelsWidth',
      'yLabelsCoords',
      'yTitleCoords',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.layout)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.layout)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<seriesData> → w.seriesData.<seriesData>
    // Note: gl.series (main y-values) shim coexists with the seriesNS.data proxy in
    // Globals._attachNamespaces() — seriesNS.data reads/writes gl.series which routes
    // through this shim to w.seriesData.series (proxy-of-a-proxy, both bidirectional).
    for (const key of [
      'series',
      'seriesNames',
      'seriesX',
      'seriesZ',
      'seriesColors',
      'seriesGoals',
      'stackedSeriesTotals',
      'stackedSeriesTotalsByGroups',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.seriesData)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.seriesData)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<axisFlags> → w.axisFlags.<axisFlags>
    for (const key of [
      'isXNumeric',
      'dataFormatXNumeric',
      'isDataXYZ',
      'isRangeData',
      'isRangeBar',
      'isMultiLineX',
      'noLabelsProvided',
      'dataWasParsed',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.axisFlags)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.axisFlags)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<labelData> → w.labelData.<labelData>
    for (const key of [
      'labels',
      'categoryLabels',
      'timescaleLabels',
      'hasXaxisGroups',
      'groups',
      'seriesGroups',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.labelData)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.labelData)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<rangeData> → w.rangeData.<rangeData>
    for (const key of ['seriesRangeStart', 'seriesRangeEnd', 'seriesRange']) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.rangeData)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.rangeData)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    // w.globals.<candleData> → w.candleData.<candleData>
    for (const key of [
      'seriesCandleO',
      'seriesCandleH',
      'seriesCandleM',
      'seriesCandleL',
      'seriesCandleC',
    ]) {
      Object.defineProperty(globals, key, {
        get() {
          return /** @type {Record<string,any>} */ (w.candleData)[key]
        },
        set(v) {
          /** @type {Record<string,any>} */ (w.candleData)[key] = v
        },
        enumerable: false,
        configurable: true,
      })
    }

    return /** @type {import('../types/internal').ChartStateW} */ (/** @type {unknown} */ (w))
  }
}
