var innovaphone = innovaphone || {};

innovaphone.ChartVisualizer ??= class extends innovaphone.ui1.Node {
  constructor(start) {
    super();

    this.createNode("canvas", "width:100%;height:100%;");
    this.ChartJs = new Chart(this.container, this.#config);

    start.onschemechanged.attach(() => {
      this.switchScheme();
    });
    this.switchScheme();
  }

  options = { maintainAspectRatio: false, responsive: false, animation: false };
  plugins = [];
  data = { labels: [], datasets: [] };

  ChartJs;
  #config = {
    type: "line",
    data: this.data,
    options: this.options,
    plugins: this.plugins,
  };

  get chartType() {
    return this.#config.type;
  }

  datasetTemplate = {
    pointStyle: "circle",
    borderWidth: 2,
    pointRadius: 4,
    pointHoverRadius: 8,
    pointHitRadius: 8,
    tension: 0.1,
  };

  #customizationMaps = {
    GRAPH_TYPES: ["line", "bar", "pie", "doughnut"],
    GRAPH_COLORS: [
      "#FF5733",
      "#33FF57",
      "#3357FF",
      "#FF33A8",
      "#FFD700",
      "#00FFFF",
      "#FF8C00",
      "#8A2BE2",
      "#DC143C",
      "#00FF7F",
    ],
    LIVE_TICK_LIMIT: 0,
  };

  set liveTickLimit(value) {
    if (typeof value === "number") {
      this.#customizationMaps.LIVE_TICK_LIMIT = value;
    }
  }
  get liveTickLimit() {
    return this.#customizationMaps.LIVE_TICK_LIMIT;
  }

  set graphColors(value = null) {
    if (!value) return;

    value = Array.isArray(value) ? value : [value];

    this.#customizationMaps.GRAPH_COLORS = value;
  }
  get graphColors() {
    return this.#customizationMaps.GRAPH_COLORS;
  }

  #stateMaps = {
    MODES: ["default", "sum", "trim", "live"],
    activeMode: "default",

    visibleBackgroundState: { x: {}, y: {} },
    backgroundVisible: true,
  };

  #queuedUpdate = null;
  #update() {
    clearTimeout(this.#queuedUpdate);

    this.#queuedUpdate = setTimeout(() => {
      this.update();
    }, 1);
  }

  // HELPER
  update() {
    this.#config.data = this.processedData();

    this.ChartJs.update();
  }

  findDataset(label) {
    return this.data.datasets.findIndex((ds) => ds.label === label);
  }

  processedData() {
    const computeDataset = () =>
      Array.isArray(datasets)
        ? datasets.map((chart) => {
          const c = structuredClone(this.datasetTemplate);

          if (chart && typeof chart === "object") {
            Object.assign(c, chart);
            c.data = Array.isArray(chart.data) ? chart.data.slice() : [];
          } else {
            c.data = [];
          }

          return c;
        })
        : [];

    const { datasets, labels } = this.data;

    let res;

    switch (this.#stateMaps.activeMode) {
      case this.#stateMaps.MODES[1]:
        const sumData = [];
        const dsLabels = [];
        const backgroundColors = [];
        const borderColors = [];

        datasets.forEach((ds) => {
          sumData.push(ds.data.reduce((sum, curr) => sum + curr));
          dsLabels.push(ds.label);
          backgroundColors.push(ds.backgroundColor);
          borderColors.push(ds.borderColor);
        });

        res = {
          labels: dsLabels,
          datasets: [{
            label: `SUM from ${labels[0]} - ${labels[labels.length - 1]}`,
            data: sumData,
            backgroundColor: backgroundColors,
            borderColor: borderColors,
          }],
        };
        break;
      case this.#stateMaps.MODES[2]:
        let lowest = labels.length;
        datasets.forEach((set) => {
          (set.data ?? []).some((val, index) => {
            if (val !== 0 && index < lowest) {
              lowest = index;
              return true;
            } else {
              return false;
            }
          });
        });

        if (lowest === labels.length) {
          res = {
            labels: labels.slice(0),
            datasets: computeDataset(),
          };
        } else {
          res = {
            labels: labels.slice(lowest),
            datasets: Array.isArray(datasets)
              ? datasets.map((chart) => {
                const trimmed = structuredClone(this.datasetTemplate);

                if (chart && typeof chart === "object") {
                  Object.assign(trimmed, chart);
                  trimmed.data = Array.isArray(chart.data)
                    ? chart.data.slice(Math.max(0, lowest | 0))
                    : [];
                } else {
                  trimmed.data = [];
                }

                return trimmed;
              })
              : [],
          };
        }
        break;
      default:
        res = {
          labels: labels.slice(0),
          datasets: computeDataset(),
        };
        break;
    }

    return res;
  }

  switchScheme() {
    const fontColor = getComputedStyle(document.body).getPropertyValue("--c3");
    const scales = this.options.scales ?? (this.options.scales = {});

    const dimensions = ["x", "y"];
    const elements = ["border", "grid", "ticks"];

    dimensions.forEach((dimension) => {
      scales[dimension] ??= {};
      elements.forEach((ele) => {
        scales[dimension][ele] = scales[dimension][ele] || {};
        scales[dimension][ele].color = fontColor;
      });
    });
  }

  getColor(index) {
    let nextColor = this.#customizationMaps
      .GRAPH_COLORS[index % this.#customizationMaps.GRAPH_COLORS.length];

    if (this.data.datasets?.[index - 1]?.borderColor === nextColor) {
      nextColor = this.getColor(index - 1);
    }

    return nextColor;
  }

  shadeColor(hex, percent) {
    const rgb = [];

    for (let i = 1; i < 6; i += 2) {
      let colValue = Math.round(
        parseInt(parseInt(hex.substring(i, i + 2), 16) * percent),
      );

      colValue = Math.min(255, Math.max(0, colValue));
      rgb.push(colValue.toString(16).padStart(2, "0"));
    }

    return "#" + rgb.reduce((prev, curr) => prev + curr);
  }

  // Lib Functions
  setChartType(value) {
    value = value.toLowerCase();
    this.#config.type = this.#customizationMaps.GRAPH_TYPES.includes(value)
      ? value
      : "line";

    this.#config.options.scales.x.offset = this.#config.type === "bar";

    this.#update();
  }

  setDisplayMode(value = "default") {
    value = value.toLowerCase();
    this.#stateMaps.activeMode = this.#stateMaps.MODES.includes(value)
      ? value
      : "default";

    this.#update();
  }

  setLabels(labels) {
    this.data.labels = labels;

    this.#update();
  }

  addDataset(id, dsData) {
    if (this.findDataset(id) >= 0) {
      console.info(`Dataset with name '${id}' already exisits!`);
      return null;
    } else {
      const newDataset = structuredClone(this.datasetTemplate);
      const color = this.getColor(this.data.datasets.length);

      this.data.datasets.push(Object.assign(newDataset, {
        label: id,
        data: dsData,
        borderColor: color,
        backgroundColor: this.shadeColor(color, 1.4),
      }));

      this.#update();

      return newDataset;
    }
  }

  removeDataset(id) {
    const chartIndex = typeof id === "number" ? id : this.findDataset(id);

    if (chartIndex >= 0) {
      this.data.datasets.splice(chartIndex, 1);
    }

    this.#update();
  }

  addDatapoint(id, dpData) {
    const chartIndex = typeof id === "number" ? id : this.findDataset(id);

    if (chartIndex >= 0) {
      const dsData = this.data.datasets[chartIndex].data;
      dsData.push(dpData);

      if (
        this.#stateMaps.activeMode === "live" &&
        this.#customizationMaps.LIVE_TICK_LIMIT > 0 &&
        dsData.length > this.#customizationMaps.LIVE_TICK_LIMIT
      ) {
        dsData.shift();
      }
    }

    this.#update();
  }

  clear() {
    this.data.datasets.splice(0);

    this.#update();
  }

  setBackground(visible) {
    if (this.#stateMaps.backgroundVisible === visible) return;

    const scales = ["x", "y"];

    if (this.#stateMaps.backgroundVisible) {
      scales.forEach((dimension) => {
        const visibleState = this.#stateMaps.visibleBackgroundState[dimension];
        const { border, grid, ticks } = this.options.scales[dimension];

        visibleState.border = border.display ?? true;
        visibleState.grid = border.grid ?? true;
        visibleState.ticks = border.ticks ?? true;

        border.display = false;
        grid.display = false;
        ticks.display = false;
      });
    } else {
      scales.forEach((dimension) => {
        const visibleState = this.#stateMaps.visibleBackgroundState[dimension];
        const { border, grid, ticks } = this.options.scales[dimension];

        border.display = visibleState.border;
        grid.display = visibleState.grid;
        ticks.display = visibleState.ticks;
      });
    }

    this.#stateMaps.backgroundVisible = visible;

    this.#update();
  }
};
