import { Canvas } from "canvas";
import { ChartConfiguration, TooltipItem } from "chart.js";
import {
  ChartCreationContext,
  ChartJSRuntime,
  ChartRangeDataSource,
  CustomizedDataSet,
  Model,
  PixelDimension,
  UID,
} from "../../src";
import { FIRST_CHART_COLOR, toHex } from "../../src/helpers/color";
import {
  areChartJSExtensionsLoaded,
  registerChartJSExtensions,
} from "../../src/helpers/figures/charts/chart_js_extension";
import { deepCopy, range } from "../../src/helpers/misc";
import { SpreadsheetChildEnv } from "../../src/types/spreadsheet_env";
import { selectFigure } from "./commands_helpers";
import { click, simulateClick } from "./dom_helper";
import { nextTick } from "./helpers";

export function isChartAxisStacked(model: Model, chartId: UID, axis: "x" | "y"): boolean {
  return getChartConfiguration(model, chartId).options?.scales?.[axis]?.stacked;
}

export function getChartConfiguration(model: Model, chartId: UID) {
  const runtime = model.getters.getChartRuntime(chartId) as any;
  return runtime.chartJsConfig;
}

export function getChartLegendLabels(model: Model, chartId: UID) {
  const runtime = model.getters.getChartRuntime(chartId) as any;
  const fakeChart = {
    ...runtime.chartJsConfig,
    isDatasetVisible: (index) => true,
    getDataVisibility: (index) => true,
    getDatasetMeta: (index) => runtime.chartJsConfig.data!.datasets![index],
  };
  return runtime.chartJsConfig.options?.plugins?.legend?.labels?.generateLabels?.(fakeChart as any);
}

export function getCategoryAxisTickLabels(model: Model, chartId: UID) {
  const runtime = model.getters.getChartRuntime(chartId) as any;
  const labels = runtime.chartJsConfig.data.labels;
  const fakeChart = {
    // Category axis callback's tick internal value is the index of the label. We use getLabelForValue to get the actual label
    // https://www.chartjs.org/docs/latest/axes/labelling.html#creating-custom-tick-formats
    getLabelForValue: (index: number) => labels[index],
  };

  return range(0, labels.length).map((index) =>
    runtime.chartJsConfig.options.scales.x.ticks.callback.bind(fakeChart)(index)
  );
}

interface ChartDataInput {
  dataSets?: (CustomizedDataSet & {
    dataRange: string;
    dataSetId?: UID;
    type?: "bar" | "line"; // for combo charts
  })[];
  labelRange?: string;
  dataSetsHaveTitle?: boolean;
}

interface ChartDataOutput {
  dataSource: ChartRangeDataSource<string>;
  dataSetStyles: Record<string, CustomizedDataSet>;
}

export function toChartDataSource(args: ChartDataInput): ChartDataOutput {
  const { labelRange } = args;
  const dataSets =
    args.dataSets?.map((dataSet, i) => ({
      ...dataSet,
      dataSetId: dataSet.dataSetId ?? `${i}`,
    })) ?? [];
  const dataSetStyles: Record<string, CustomizedDataSet> = {};
  for (const { dataSetId, dataRange, ...style } of dataSets) {
    if (Object.keys(style).length !== 0) {
      dataSetStyles[dataSetId] = style;
    }
  }
  const result: ChartDataOutput = {
    dataSource: {
      type: "range",
      dataSets: dataSets.map(({ dataRange, dataSetId }) => ({ dataRange, dataSetId })),
      dataSetsHaveTitle: args.dataSetsHaveTitle ?? true,
    },
    dataSetStyles,
  };
  if ("labelRange" in args) {
    result.dataSource = {
      ...result.dataSource,
      labelRange,
    };
  }
  return result;
}

export async function openChartConfigSidePanel(
  model: Model,
  env: SpreadsheetChildEnv,
  chartId: UID
) {
  const figureId = model.getters.getFigureIdFromChartId(chartId);
  if (!figureId) {
    throw new Error(`No figure found for chart ID: ${chartId}`);
  }
  selectFigure(model, figureId);
  env.openSidePanel("ChartPanel");
  await nextTick();
}

export async function openChartDesignSidePanel(
  model: Model,
  env: SpreadsheetChildEnv,
  fixture: HTMLElement,
  chartId: UID
) {
  if (!fixture.querySelector(".o-chart")) {
    await openChartConfigSidePanel(model, env, chartId);
  }
  await simulateClick(".o-sidePanel-tab.inactive");
}

export function drawChartOnNodeCanvas(
  runtime: ChartJSRuntime,
  size: PixelDimension = { width: 400, height: 400 }
) {
  if (!areChartJSExtensionsLoaded()) {
    registerChartJSExtensions();
  }
  const config = deepCopy(runtime.chartJsConfig);
  config.options!.responsive = false;

  const canvas = new Canvas(size.width, size.height);
  canvas["getAttribute"] = (attribute) => {
    if (attribute === "height") {
      return size.height;
    }
    if (attribute === "width") {
      return size.width;
    }
    throw new Error(`Attribute ${attribute} not implemented in mock`);
  };
  canvas["style"] = new CSSStyleDeclaration();
  canvas["addEventListener"] = () => {};
  const ctx = canvas.getContext("2d")! as unknown as CanvasRenderingContext2D;
  const chart = new window.Chart(ctx, config as ChartConfiguration<any>);
  chart.draw();
  return canvas.toBuffer("image/png");
}

export function getColorPickerValue(fixture: HTMLElement, selector: string) {
  const color = fixture
    .querySelector<HTMLElement>(selector)!
    .querySelector<HTMLElement>(".o-round-color-picker-button")?.style.background;
  return toHex(color ?? "");
}

export async function editColorPicker(fixture: HTMLElement, selector: string, color: string) {
  await click(fixture.querySelector(selector + " .o-round-color-picker-button")!);
  await click(fixture, `.o-color-picker-line-item[data-color='${color}'`);
}

export function getChartTooltipItemFromDataset(
  chart: ChartJSRuntime,
  datasetIndex: number,
  dataIndex: number
): Partial<TooltipItem<any>> {
  const datasetPoint = chart.chartJsConfig!.data!.datasets![datasetIndex].data![dataIndex];
  const y = typeof datasetPoint === "number" ? datasetPoint : datasetPoint?.["y"];
  const x = chart.chartJsConfig!.data.labels![dataIndex];
  const point = chart.chartJsConfig.type === "pie" ? y : { x, y };
  return {
    label: "",
    parsed: point,
    raw: point,
    dataset: chart.chartJsConfig.data!.datasets[datasetIndex],
    datasetIndex,
    dataIndex,
  };
}

export function getChartTooltipValues(
  chart: ChartJSRuntime,
  tooltipItem: Partial<TooltipItem<any>>
) {
  const callbacks = chart.chartJsConfig!.options!.plugins!.tooltip!.callbacks! as any;
  return {
    label: callbacks.label(tooltipItem),
    beforeLabel: callbacks.beforeLabel(tooltipItem),
  };
}

export const GENERAL_CHART_CREATION_CONTEXT: Required<ChartCreationContext> = {
  background: "#123456",
  title: { text: "hello there" },
  ...toChartDataSource({
    dataSets: [{ dataRange: "Sheet1!B1:B4" }],
    dataSetsHaveTitle: true,
  }),
  hierarchicalDataSource: {
    type: "range",
    dataSets: [],
    dataSetsHaveTitle: true,
  },
  auxiliaryRange: "Sheet1!A1:A4",
  legendPosition: "bottom",
  cumulative: true,
  labelsAsText: true,
  aggregated: true,
  stacked: true,
  firstValueAsSubtotal: true,
  showConnectorLines: false,
  showSubTotals: true,
  axesDesign: {},
  fillArea: true,
  showValues: false,
  hideDataMarkers: false,
  funnelColors: [],
  showLabels: false,
  showTotalLine: false,
  valuesDesign: {},
  groupColors: [],
  horizontal: false,
  isDoughnut: false,
  pieHolePercentage: 0,
  headerDesign: { bold: false },
  treemapColoringOptions: { type: "categoryColor", colors: [], useValueBasedGradient: true },
  showHeaders: true,
  zoomable: true,
  humanize: false,
  slicesColors: [],
  bubbleLabelRange: "Sheet1!A1:A4",
  bubbleSizeRange: "Sheet1!A1:A4",
  bubbleColorMode: { color: FIRST_CHART_COLOR },
  annotationLink: "https://www.odoo.com",
  annotationText: "This is an annotation text",
  scorecardKeyValueFormula: "=Sheet1!B1:B4",
  scorecardBaselineFormula: "=Sheet1!A1:A4",
};
