import React, { useState, useRef, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  AgCartesianSeriesTooltipRendererParams,
  AgChartState,
  AgChartsInstance,
} from "ag-charts-enterprise";
import { getData } from "./data";
import "ag-charts-enterprise";
import clone from "clone";

const dateFormatter = new Intl.DateTimeFormat("en-GB");
const tooltip = {
  renderer: ({ datum, xKey, yKey }: AgCartesianSeriesTooltipRendererParams) => {
    return {
      data: [
        {
          label: dateFormatter.format(datum[xKey]),
          value: `${Math.round(datum[yKey] / 100) / 10 + "k"}`,
        },
      ],
    };
  },
};
let state: AgChartState = {
  version: "11.0.0",
  zoom: {
    rangeX: {
      start: {
        __type: "date",
        value: new Date("2021-01-01").getTime(),
      },
    },
  },
  legend: [
    {
      seriesId: "tate-modern",
      visible: false,
    },
    {
      seriesId: "tate-liverpool",
      visible: false,
    },
  ],
};

const ChartExample = () => {
  const chartRef = useRef<AgChartsInstance>(null);
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    title: {
      text: "Total Visitors to Tate Galleries",
    },
    footnote: {
      text: "Source: Department for Digital, Culture, Media & Sport",
    },
    data: getData(),
    navigator: {
      enabled: true,
    },
    zoom: {
      enabled: true,
    },
    series: [
      {
        type: "area",
        xKey: "date",
        yKey: "Tate Modern",
        yName: "Tate Modern",
        id: "tate-modern",
        tooltip,
      },
      {
        type: "area",
        xKey: "date",
        yKey: "Tate Britain",
        yName: "Tate Britain",
        id: "tate-britain",
        tooltip,
      },
      {
        type: "area",
        xKey: "date",
        yKey: "Tate Liverpool",
        yName: "Tate Liverpool",
        id: "tate-liverpool",
        tooltip,
      },
      {
        type: "area",
        xKey: "date",
        yKey: "Tate St Ives",
        yName: "Tate St Ives",
        id: "tate-st-ives",
        tooltip,
      },
    ],
    axes: [
      {
        type: "time",
        position: "bottom",
      },
      {
        type: "number",
        position: "left",
        title: {
          text: "Total visitors",
        },
        label: {
          formatter: (params) => {
            return params.value / 1000 + "k";
          },
        },
      },
    ],
  });

  const saveState = () => {
    const newState = chartRef.current!.getState();
    state = newState;
    console.log("Saved", state);
  };

  const restoreState = () => {
    chartRef.current!.setState(state).then(() => {
      console.log(`Restored`, state);
    });
  };

  return (
    <Fragment>
      <div className="toolbar">
        <button onClick={saveState}>Save</button>
        <button onClick={restoreState}>Restore</button>
      </div>
      <AgCharts ref={chartRef} options={options as any} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);

/** DARK MODE START **/
import { AgCharts as __chartAPI } from "ag-charts-community";

let darkmode =
  (localStorage["documentation:darkmode"] ||
    String(matchMedia("(prefers-color-scheme: dark)").matches)) === "true";

const isAgThemeOrUndefined = (theme) => {
  return (
    theme == null || (typeof theme === "string" && theme.startsWith("ag-"))
  );
};

const getDarkmodeTheme = (theme = "ag-default", preset) => {
  const baseTheme =
    preset === "price-volume" ? "ag-financial" : theme.replace(/-dark$/, "");
  return darkmode ? baseTheme + "-dark" : baseTheme;
};

__chartAPI.optionsMutationFn = function update(options, preset) {
  const nextOptions = { ...options };
  const theme = options.theme;
  if (isAgThemeOrUndefined(theme)) {
    nextOptions.theme = getDarkmodeTheme(theme, preset);
  } else if (
    typeof theme === "object" &&
    isAgThemeOrUndefined(theme.baseTheme)
  ) {
    nextOptions.theme = {
      ...options.theme,
      baseTheme: getDarkmodeTheme(theme.baseTheme, preset),
    };
  }
  return nextOptions;
};

const applyDarkmode = () => {
  document.documentElement.setAttribute("data-dark-mode", darkmode);
  const charts = document.querySelectorAll("[data-ag-charts]");
  charts.forEach((element) => {
    const chart = __chartAPI.getInstance(element.parentElement);
    if (chart == null) return;
    // This is just needed to trigger the theme update
    chart.update(chart.getOptions());
  });
  return charts.length !== 0;
};

if (!applyDarkmode()) {
  /* React defers updates. Rather than try and hook into the API, just wait until the darkmode is applied. */
  const observer = new MutationObserver(() => {
    if (applyDarkmode()) {
      observer.disconnect();
    }
  });
  observer.observe(document.body, {
    attributes: true,
    childList: true,
    subtree: true,
  });
}
window.addEventListener("message", (event) => {
  if (event.data && event.data.type === "color-scheme-change") {
    darkmode = event.data.darkmode;
    applyDarkmode();
  }
});
/** DARK MODE END **/
/** CONSOLE LOG START **/
const PRIMITIVE_TYPES = [
  "string",
  "number",
  "boolean",
  "undefined",
  "null",
  "NaN",
  "symbol",
];
const OBJECT_PROPERTIES_LIMIT = 3;

function getType(value) {
  if (value === null) return "null";
  if (Number.isNaN(value)) return "NaN";
  if (Array.isArray(value)) return "array";
  if (value instanceof Date) return "date";
  if (value instanceof RegExp) return "regexp";
  if (value instanceof Map) return "map";
  if (value instanceof Set) return "set";
  if (value instanceof WeakMap) return "weakmap";
  if (value instanceof WeakSet) return "weakset";
  if (value instanceof Promise) return "promise";
  if (value instanceof Error) return "error";
  if (typeof value === "object") return "object";
  return typeof value;
}

function safeStringify(obj, space = 2) {
  const seen = new WeakSet();
  const isLoggableArray = getType(obj) === "array" && obj.every(isLoggableType);
  const getObjectValue = (value) => {
    if (seen.has(value)) {
      return "[Circular]";
    } else if (value === undefined) {
      return "undefined";
    }
    seen.add(value);

    // Include custom class names if available
    if (
      value.constructor &&
      value.constructor.name &&
      getType(value) !== "object"
    ) {
      return `${value.constructor.name}Class { ... }`;
    }

    return value;
  };
  return isLoggableArray
    ? JSON.stringify(obj)
    : JSON.stringify(
        obj,
        (_, value) => {
          const valueType = getType(value);
          let newValue = value;
          if (valueType === "object") {
            newValue = getObjectValue(newValue);
          } else if (valueType === "array") {
            newValue = value.map((item) => {
              return getType(item === "object") ? getObjectValue(item) : item;
            });
          }

          return newValue;
        },
        space,
      );
}

function isPrimitiveType(value) {
  return PRIMITIVE_TYPES.includes(getType(value));
}

function isLoggableType(value) {
  const valueType = getType(value);

  return (
    isPrimitiveType(value) ||
    (valueType === "array" && value.every(isPrimitiveType)) ||
    (valueType === "object" &&
      Object.values(value).every(isPrimitiveType) &&
      Object.keys(value).length <= OBJECT_PROPERTIES_LIMIT)
  );
}

function getConsoleValue(value) {
  return isPrimitiveType(value)
    ? value
    : {
        __consoleLogObject: true,
        isLoggable: isLoggableType(value),
        argType: getType(value),
        safeString: safeStringify(value),
      };
}

function getConsoleLogData(args) {
  return args.map(getConsoleValue);
}

const originalConsoleLog = console.log;
console.log = (...args) => {
  try {
    window.parent.postMessage({
      type: "console-log",
      pageName: "api-state",
      exampleName: "legend-state-save-restore",
      data: getConsoleLogData(args),
    });
  } catch {
    // Posting is best-effort and shouldn't block normal console logging.
  }
  originalConsoleLog(...args);
};
/** CONSOLE LOG END **/
