import React, { useMemo } from "react";
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  LineController,
  LineElement,
  PointElement,
  Tooltip,
  type ChartOptions,
  type ChartData,
} from "chart.js";
import { Chart } from "react-chartjs-2";
import { useThemeVars } from "@/lib/theme-provider";
import {
  FALLBACK_TICK,
  FALLBACK_PRIMARY,
  FALLBACK_BG,
  ChartLegendItem,
  formatAbbrev,
  formatChartDateLabel,
  formatTooltipDate,
} from "./chart-shared";
import { Card, CardContent, CardHeader, CardTitle } from "./card";
import { Empty, EmptyDescription } from "./empty";
import { Spinner } from "./spinner";
import { cn } from "@/lib/utils";
import { formatCurrency } from "@/lib/format-currency";

ChartJS.register(
  CategoryScale,
  LinearScale,
  LineController,
  LineElement,
  PointElement,
  Tooltip,
);

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Format ISO date string to "Jan 15" label — delegates to chart-shared. */
function formatDateLabel(iso: string): string {
  return formatChartDateLabel(iso);
}

/** Format ISO date string for tooltip "Jan 15, 2024" — delegates to chart-shared. */
function formatDateTooltip(iso: string): string {
  return formatTooltipDate(iso, "daily");
}

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface CashBalanceDataPoint {
  /** ISO date string e.g. "2024-01-15" */
  x: string;
  y: number;
}

/**
 * Optional secondary line datasets rendered on a right-hand Y-axis.
 * Useful for overlaying income / expense lines against the balance.
 */
export interface ExtraLineDataset {
  label: string;
  data: CashBalanceDataPoint[];
  color: string;
  dashed?: boolean;
}

export interface CashBalanceLineChartProps {
  chartData?: CashBalanceDataPoint[] | null;
  title?: string;
  /** Chart canvas height in pixels */
  height?: number;
  /** Width of the card */
  width?: number | string;
  className?: string;
  /** Show skeleton loading state instead of the chart */
  isLoading?: boolean;
  /** Show or hide X axis labels */
  showXAxis?: boolean;
  /** Show or hide Y axis labels */
  showYAxis?: boolean;
  /** Show the latest balance value prominently below the title */
  showBalanceValue?: boolean;
  /** Show or hide the chart legend */
  showLegend?: boolean;
  /** Legend placement relative to the chart */
  legendPosition?: "top" | "bottom";
  /**
   * Optional secondary line datasets (e.g. income / expense) rendered on a
   * right-hand Y-axis so their scale stays independent from the balance line.
   */
  extraDatasets?: ExtraLineDataset[];
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function CashBalanceLineChart({
  chartData,
  title = "Cash Balance",
  height = 200,
  width = "100%",
  className,
  isLoading = false,
  showXAxis = true,
  showYAxis = true,
  showBalanceValue = false,
  showLegend = false,
  legendPosition = "bottom",
  extraDatasets,
}: CashBalanceLineChartProps) {
  const themeVars = useThemeVars();
  const brandPrimary =
    (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
  const fontFamily =
    (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";

  const hasData = Array.isArray(chartData) && chartData.length > 0;

  // Indices to show on x-axis (max ~6 ticks)
  const tickIndices = useMemo<Set<number>>(() => {
    if (!hasData) return new Set();
    const count = chartData!.length;
    const step = Math.max(1, Math.floor(count / 6));
    const indices = new Set<number>();
    for (let i = 0; i < count; i += step) indices.add(i);
    indices.add(count - 1);
    return indices;
  }, [hasData, chartData]);

  const hasExtra = Array.isArray(extraDatasets) && extraDatasets.length > 0;

  const data = useMemo<ChartData<"line">>(() => {
    if (!hasData) return { labels: [], datasets: [] };

    const mainDs = {
      label: title,
      data: chartData!.map((p) => p.y),
      fill: false,
      borderColor: brandPrimary,
      backgroundColor: "transparent",
      borderWidth: 2.5,
      borderDash: [],
      tension: 0.4,
      pointRadius: 0,
      pointHoverRadius: 6,
      pointHoverBackgroundColor: FALLBACK_BG,
      pointHoverBorderColor: brandPrimary,
      pointHoverBorderWidth: 3,
      pointHitRadius: 10,
      yAxisID: "y",
    };

    const extraDs = hasExtra
      ? extraDatasets!.map((ds) => ({
          label: ds.label,
          data: ds.data.map((p) => p.y),
          fill: false,
          borderColor: ds.color,
          backgroundColor: "transparent",
          borderWidth: 1.5,
          borderDash: ds.dashed ? ([5, 4] as number[]) : ([] as number[]),
          tension: 0.4,
          pointRadius: 0,
          pointHoverRadius: 4,
          pointHoverBackgroundColor: FALLBACK_BG,
          pointHoverBorderColor: ds.color,
          pointHoverBorderWidth: 2,
          pointHitRadius: 10,
          yAxisID: "y2",
        }))
      : [];

    return {
      labels: chartData!.map((p) => p.x),
      datasets: [mainDs, ...extraDs],
    };
  }, [hasData, chartData, brandPrimary, title, hasExtra, extraDatasets]);

  const options = useMemo<ChartOptions<"line">>(
    () => ({
      responsive: true,
      maintainAspectRatio: false,
      animation: { duration: 1200, easing: "easeOutQuart" },
      plugins: {
        legend: { display: false },
        tooltip: {
          enabled: true,
          mode: "index",
          intersect: false,
          displayColors: hasExtra,
          padding: 12,
          cornerRadius: 0,
          titleFont: { size: 11, weight: 600, family: fontFamily },
          bodyFont: { size: 12, weight: 500, family: fontFamily },
          callbacks: {
            title: (items) => {
              const iso = items[0]?.label;
              return iso ? formatDateTooltip(iso) : "";
            },
            label: (ctx) => {
              const value = formatCurrency((ctx.parsed.y ?? 0), { decimals: 2 });
              return hasExtra ? ` ${ctx.dataset.label}: ${value}` : value;
            },
          },
        },
      },
      interaction: { mode: "index", intersect: false },
      scales: {
        x: {
          type: "category",
          display: showXAxis,
          grid: { display: false },
          border: { display: false },
          ticks: {
            autoSkip: false,
            maxRotation: 0,
            minRotation: 0,
            color: FALLBACK_TICK,
            font: { size: 12, family: fontFamily },
            callback: function (_, index) {
              if (!tickIndices.has(index) || !chartData) return "";
              return formatDateLabel(chartData[index].x);
            },
          },
        },
        y: {
          display: showYAxis,
          position: "left",
          grid: { display: false },
          border: { display: false },
          ticks: {
            beginAtZero: false,
            maxTicksLimit: 5,
            padding: 8,
            color: FALLBACK_TICK,
            font: { size: 12, family: fontFamily },
            callback: (v) => formatAbbrev(Number(v)),
          },
        },
        y2: {
          display: hasExtra && showYAxis,
          position: "right" as const,
          beginAtZero: true,
          grid: { display: false },
          border: { display: false },
          ticks: {
            maxTicksLimit: 5,
            padding: 8,
            color: FALLBACK_TICK,
            font: { size: 12, family: fontFamily },
            callback: (v) => formatAbbrev(Number(v)),
          },
        },
      },
    }),
    [tickIndices, chartData, showXAxis, showYAxis, fontFamily, hasExtra],
  );

  const latestValue = hasData ? chartData![chartData!.length - 1].y : null;

  return (
    <Card
      className={cn("w-full py-4 sm:py-6 gap-2", className)}
      style={{ maxWidth: width, fontFamily }}
    >
      {(title || (showBalanceValue && latestValue !== null)) && (
        <CardHeader className="px-3 sm:px-6">
          <div className="flex flex-col gap-0.5">
            {title && (
              <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
                {title}
              </CardTitle>
            )}
            {showBalanceValue && latestValue !== null && (
              <p className="text-lg font-bold tabular-nums leading-tight">
                {formatCurrency(latestValue, { decimals: 2 })}
              </p>
            )}
          </div>
        </CardHeader>
      )}

      <CardContent className="px-3 sm:px-6">
        {isLoading ? (
          <div
            className="flex items-center justify-center text-muted-foreground"
            style={{ height, width: "100%" }}
          >
            <Spinner size="lg" />
          </div>
        ) : !hasData ? (
          <Empty className="flex-none p-4" style={{ height }}>
            <EmptyDescription>No data available</EmptyDescription>
          </Empty>
        ) : (
          <div className="flex flex-col gap-2">
            {showLegend && legendPosition === "top" && (
              <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5">
                {title && (
                  <ChartLegendItem
                    label={title}
                    color={brandPrimary}
                    lineStyle="solid"
                  />
                )}
                {extraDatasets?.map((ds) => (
                  <ChartLegendItem
                    key={ds.label}
                    label={ds.label}
                    color={ds.color}
                    lineStyle={ds.dashed ? "dashed" : "solid"}
                  />
                ))}
              </div>
            )}
            <div style={{ height, width: "100%", position: "relative" }}>
              <Chart
                key={brandPrimary}
                type="line"
                data={data}
                options={options}
                aria-label={title}
              />
            </div>
            {showLegend && legendPosition === "bottom" && (
              <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5">
                {title && (
                  <ChartLegendItem
                    label={title}
                    color={brandPrimary}
                    lineStyle="solid"
                  />
                )}
                {extraDatasets?.map((ds) => (
                  <ChartLegendItem
                    key={ds.label}
                    label={ds.label}
                    color={ds.color}
                    lineStyle={ds.dashed ? "dashed" : "solid"}
                  />
                ))}
              </div>
            )}
          </div>
        )}
      </CardContent>
    </Card>
  );
}
