import { ErrorBoundary } from "react-error-boundary";
import { Text, Title } from "../../tremor/Text";
import { Button } from "../../tremor/Button";

import { TableChart } from "../TableChart";
import React, { useMemo, useRef, useState } from "react";

import { Chart } from "react-chartjs-2";
import { Chart as ChartJS, Colors } from "chart.js";
import "chart.js/auto";

import { useTheme } from "../../layouts/Dashboard/useTheme";
import "chartjs-adapter-spacetime";

import { MetricChart, MetricPlugin } from "../MetricChart";
import TextChart from "./TextChart";
import ImageChart from "./ImageChart";
import { DividerChart, DividerPlugin } from "./DividerChart";
import { FunnelController, TrapezoidElement, } from "chartjs-chart-funnel";
import ChartDataLabels from "chartjs-plugin-datalabels";
import zoomPlugin from "chartjs-plugin-zoom";
import { PolygonController, PolylineController, MarkerController, MapPlugin, MapController } from "@onvo-ai/chartjs-chart-map";
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
import { WordCloudController, WordElement } from 'chartjs-chart-wordcloud';
import { SankeyController, Flow } from 'chartjs-chart-sankey';

import { useDashboard } from "../../layouts/Dashboard/useDashboard";
import { useBackend } from "../../layouts";

function transparentize(hexColor: string, opacity: number) {
  // Convert opacity to hex value
  let alphaHex = Math.round(opacity * 255).toString(16);
  if (alphaHex.length === 1) {
    alphaHex = "0" + alphaHex;
  }

  // Return the hex color with the alpha value
  return hexColor + alphaHex;
}

function convertFunctionStringsToFunctions(config: any): any {
  // Helper function to handle each item in the config
  const convertItem = (item: any): any => {
    // If it's a string that looks like a function definition
    if (typeof item === 'string' && item.startsWith('function(')) {
      // Use the Function constructor to create an actual function
      try {
        // Extract the function body and parameters
        const funcBody = item.substring(item.indexOf('{') + 1, item.lastIndexOf('}'));
        const funcParams = item.substring(item.indexOf('(') + 1, item.indexOf(')')).split(',');

        // Create a new function using the Function constructor
        return new Function(...funcParams, funcBody);
      } catch (error) {
        console.error('Error converting function string to function:', error);
        return item; // Return original if conversion fails
      }
    } else if (Array.isArray(item)) {
      // If it's an array, process each element
      return item.map(convertItem);
    } else if (item !== null && typeof item === 'object' && typeof item !== 'function') {
      // If it's an object (and not a function itself), process each property
      const result: Record<string, any> = {};
      for (const key in item) {
        if (Object.prototype.hasOwnProperty.call(item, key)) {
          result[key] = convertItem(item[key]);
        }
      }
      return result;
    }

    // Return unchanged for all other types (including actual functions)
    return item;
  };

  // Start the conversion by directly passing config to convertItem.
  // convertItem itself will build a new, transformed object structure,
  // preserving actual functions and converting stringified ones.
  // This removes the problematic JSON.parse(JSON.stringify(config)) which stripped functions.
  return convertItem(config);
}

ChartJS.register(
  PolygonController, PolylineController, MarkerController, MapController,
  FunnelController,
  TrapezoidElement,
  MetricChart,
  MetricPlugin,
  TextChart,
  ImageChart,
  DividerPlugin,
  DividerChart,
  ChartDataLabels,
  zoomPlugin,
  WordCloudController,
  WordElement,
  SankeyController,
  Flow,
  MatrixController,
  MatrixElement,
  Colors
);

export interface ChartBaseProps {

  json: any;
  title: string;
  pagination?: {
    currentPage: number;
    pageSize: number;
    sortColumn?: string;
    sortDirection?: "ASC" | "DESC";
  };
  stats?: {
    totalPages: number;
  };
  setPagination?: React.Dispatch<
    React.SetStateAction<{
      currentPage: number;
      pageSize: number;
      sortColumn?: string;
      sortDirection?: "ASC" | "DESC";
    }>
  >;
  padding?: number;
}


const ChartBase: React.FC<ChartBaseProps> = ({
  json,
  title,
  pagination,
  stats,
  setPagination,
  padding
}) => {
  const chartRef = useRef<any>();
  const [zoomed, setZoomed] = useState(false);
  const theme = useTheme();
  const { dashboard } = useDashboard();
  const { googleMapsLoaded } = useBackend();


  const resetZoom = () => {
    chartRef.current?.resetZoom();
    setZoomed(false);
  };

  let chartConfig = useMemo(() => {
    if (!json) {
      return undefined;
    }

    let output = Object.assign({}, json, {});
    if (!output.options) {
      output.options = {};
    }
    if (!output.options.plugins) {
      output.options.plugins = {};
    }
    let subtitle =
      output.options.plugins?.subtitle?.display !== false &&
      output.options.plugins?.subtitle?.text?.trim &&
      output.options.plugins?.subtitle?.text?.trim() !== "";

    output.options.plugins.title = {
      display: (title || output.options.plugins.title?.text || "").trim() === "" ? false : true,
      text: title || output.options.plugins.title?.text || "",
      align: output.options.plugins.title?.align || "start",
      position: output.options.plugins.title?.position || "top",
      fullSize: true,
      font: {
        size: output.type === "text" ? 24 : 18,
        weight: 600,
      },
      color: theme === "dark" ? (dashboard?.settings?.dark_text || "#ddd") : (dashboard?.settings?.light_text || "#111"),
      padding: output.options.plugins.title?.padding || {
        bottom: 5,
      },
    };

    if (subtitle) {
      output.options.plugins.subtitle = {
        display: output.options.plugins?.subtitle?.display || true,
        text: output.options.plugins.subtitle?.text || "",
        align: output.options.plugins.subtitle?.align || "start",
        position: output.options.plugins.subtitle?.position || "top",
        fullSize: true,
        font: {
          size: 12,
          weight: 400,
        },
        padding: output.options.plugins.subtitle?.padding || {
          bottom: 10,
        },
      };
    }

    if (["line", "bar", "scatter"].indexOf(json.type) >= 0) {

      output.options.plugins.zoom = {
        pan: {
          enabled: true,
          mode: output.type === "scatter" ? "xy" : "x",
          modifierKey: "shift",
        },
        zoom: {
          drag: {
            enabled: true,
            backgroundColor: "rgba(59, 130, 246, 0.2)",
            borderColor: "rgb(59, 130, 246)",
            borderWidth: 2,
          },
          pinch: {
            enabled: true,
          },
          mode: output.type === "scatter" ? "xy" : "x",
          onZoomComplete: ({ chart }: { chart: any }) => {
            setZoomed(true);
          }
        },
      };
    }

    if (output.type === "matrix") {
      output.data.datasets = output.data.datasets.map((dataset: any, index: number) => {
        return {
          ...dataset,
          backgroundColor: (context: any) => {
            const value = context.dataset.data[context.dataIndex].v;
            const max = Math.max(...dataset.data.map((d: any) => d.v));
            const opacity = (value / max);
            var alpha = opacity === undefined ? 0.5 : (1 - opacity);
            return transparentize(dataset.baseColor || "#22c55e", alpha);
          },
          borderWidth: 2,
          borderColor: theme === "dark" ? "#0f172a" : "#ffffff",
          width: ({ chart }: { chart: any }) => (chart.chartArea || {}).width / chart.scales.x.ticks.length,
          height: ({ chart }: { chart: any }) => (chart.chartArea || {}).height / chart.scales.y.ticks.length
        };
      });
    }
    output.options.theme = theme;

    if (!output.options.layout) {
      output.options.layout = {};
    }
    output.options.layout.padding = padding;

    return output;
  }, [json, title, theme]);

  const options = useMemo(() => chartConfig ? convertFunctionStringsToFunctions(chartConfig.options) : undefined, [chartConfig]);

  if (chartConfig && chartConfig.type === "map" && !googleMapsLoaded) {
    return <></>
  }

  return (
    <ErrorBoundary
      fallbackRender={({ error }) => (
        <div className="onvo-chart-base-error-fallback onvo-flex onvo-h-full onvo-w-full onvo-flex-col onvo-items-center onvo-justify-center">
          <Title className="onvo-error-title">Error rendering chart</Title>
          <Text className="onvo-error-message">{error.message}</Text>
        </div>
      )}
    >
      {chartConfig ? (
        chartConfig.type === "table" ? (
          <TableChart
            data={chartConfig}
            pagination={pagination}
            stats={stats}
            setPagination={setPagination}
            padding={padding}
          />
        ) : (
          <div
            className={
              "onvo-relative onvo-w-full onvo-h-full onvo-flex onvo-flex-col"
            }
          >
            <Chart
              ref={chartRef}
              plugins={chartConfig.type === "map" ? [MapPlugin] : []}
              key={theme} {
              ...{ ...chartConfig, options: options }
              }
            />
            <div
              className={
                "onvo-foreground-color onvo-shadow-xl onvo-border-2 onvo-border-blue-500 dark:onvo-border-blue-500 onvo-absolute -onvo-bottom-5 onvo-left-[50%] onvo-w-[300px] -onvo-ml-[150px] onvo-overflow-y-hidden onvo-shrink-0 z-50 onvo-px-2 onvo-transition-all onvo-rounded-full onvo-flex onvo-flex-row onvo-items-center onvo-justify-between " +
                (zoomed ? "onvo-h-10 onvo-opacity-100" : "onvo-h-0 onvo-opacity-0")
              }
            >
              <Text className="onvo-text-xs onvo-font-semibold ml-2">
                Hold{" "}
                <span className="onvo-rounded-[4px] onvo-px-1 onvo-py-0.5 onvo-border-solid onvo-border onvo-shadow-sm onvo-border-gray-200 onvo-bg-gray-100 dark:onvo-border-gray-600 dark:onvo-bg-gray-700 dark:onvo-text-gray-400">
                  Shift
                </span>{" "}
                and drag to pan
              </Text>
              <Button
                className="!onvo-px-2 !onvo-py-0.5 !onvo-rounded-full !onvo-bg-blue-500 dark:!onvo-bg-blue-500 onvo-text-gray-600 dark:onvo-text-gray-300"
                onClick={resetZoom}
              >
                Reset zoom
              </Button>
            </div>
          </div>
        )
      ) : (
        <></>
      )}

    </ErrorBoundary>
  );
};

export default ChartBase;
