import React, { useEffect, useMemo, useRef, useState } from "react";
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  BarController,
  BarElement,
  Tooltip,
  Legend,
  type ChartOptions,
  type ChartData,
} from "chart.js";
import { Chart } from "react-chartjs-2";
import { useThemeVars } from "@/lib/theme-provider";
import { Card, CardContent, CardHeader, CardTitle, CardAction } from "./card";
import { Empty, EmptyDescription } from "./empty";
import { Skeleton } from "./skeleton";
import { cn } from "@/lib/utils";
import {
  hexToRgba,
  DATASET_ALPHAS,
  FALLBACK_TICK,
  FALLBACK_SECONDARY,
  CHART_SLICE_COUNT,
  CHART_PERIODS,
  formatTooltipDate,
  ChartLegendItem,
  ChartPeriodButton,
  type ChartPeriod,
  type ChartGranularity,
} from "./chart-shared";

ChartJS.register(
  CategoryScale,
  LinearScale,
  BarController,
  BarElement,
  Tooltip,
  Legend,
);

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

export interface ExpenseDataset {
  /** Expense category label, e.g. "Housing", "Food", "Transport" */
  label: string;
  /** One value per data point, aligned to the months array */
  data: number[];
}

export interface ExpenseBarChartData {
  /** Display labels, e.g. ["Jul", "Aug"] for monthly or ["Mar 8", "Mar 9"] for daily */
  months: string[];
  /** Optional ISO date strings per point — used for the tooltip title */
  dates?: string[];
  datasets: ExpenseDataset[];
}

export type ExpensePeriod = ChartPeriod;
export type ExpenseGranularity = ChartGranularity;

export interface ExpenseBarChartProps {
  /** Full dataset — sliced to the selected period */
  expenseData: ExpenseBarChartData | null;
  title?: string;
  /** Show or hide the chart legend */
  showLegend?: boolean;
  /** Show or hide X axis labels */
  showXAxis?: boolean;
  /** Show or hide Y axis labels */
  showYAxis?: boolean;
  /** Legend placement relative to chart */
  legendPosition?: "top" | "bottom";
  /** Default period selector value */
  defaultPeriod?: ExpensePeriod;
  /**
   * Data granularity — controls available period buttons and slice counts.
   * "monthly" (default): shows 3M / 6M / 12M, slices by month count.
   * "daily": shows 1M / 3M / 6M / 12M, slices by day count (1M=30, 3M=90, etc.).
   */
  granularity?: ExpenseGranularity;
  /** Chart canvas height in pixels */
  height?: number;
  /** Width of the card in pixels */
  width?: number | string;
  className?: string;
  /** Show skeleton loading state instead of the chart */
  isLoading?: boolean;
  /**
   * Show the internal period selector buttons (3M / 6M / 12M).
   * Set to `false` when the chart is driven by an external period control.
   * Defaults to `true`.
   */
  showPeriodSelector?: boolean;
}

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

export function ExpenseBarChart({
  expenseData,
  title = "Expenses",
  showLegend = true,
  showXAxis = true,
  showYAxis = true,
  legendPosition = "top",
  defaultPeriod = 6,
  granularity = "monthly",
  height = 280,
  width = "100%",
  className,
  isLoading = false,
  showPeriodSelector = true,
}: ExpenseBarChartProps) {
  const periods = CHART_PERIODS[granularity];
  const [period, setPeriod] = useState<ExpensePeriod>(defaultPeriod);

  // Reset period when granularity changes, but not on initial mount
  // (defaultPeriod handles the initial value).
  const isFirstRender = useRef(true);
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }
    setPeriod(CHART_PERIODS[granularity][0]);
  }, [granularity]);

  const themeVars = useThemeVars();
  const brandSecondary: string =
    (themeVars["--theme-secondary"] as string | undefined) ||
    FALLBACK_SECONDARY;
  const fontFamily: string =
    (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";

  const sliced = useMemo<ExpenseBarChartData | null>(() => {
    if (!expenseData?.months?.length || !expenseData.datasets.length)
      return null;
    const count = Math.min(
      CHART_SLICE_COUNT[granularity][period],
      expenseData.months.length,
    );
    const start = expenseData.months.length - count;
    return {
      months: expenseData.months.slice(start),
      dates: expenseData.dates?.slice(start),
      datasets: expenseData.datasets.map((ds) => ({
        ...ds,
        data: ds.data.slice(start),
      })),
    };
  }, [expenseData, period, granularity]);

  const datasetColors = useMemo(
    () =>
      sliced?.datasets.map((_, i) =>
        hexToRgba(brandSecondary, DATASET_ALPHAS[i % DATASET_ALPHAS.length]),
      ) ?? [],
    [sliced, brandSecondary],
  );

  const chartData = useMemo<ChartData<"bar">>(() => {
    if (!sliced) return { labels: [], datasets: [] };
    return {
      labels: sliced.months,
      datasets: sliced.datasets.map((ds, i) => ({
        label: ds.label,
        data: ds.data,
        backgroundColor: datasetColors[i],
        hoverBackgroundColor: hexToRgba(
          brandSecondary,
          Math.min(DATASET_ALPHAS[i % DATASET_ALPHAS.length] + 0.15, 1),
        ),
        borderWidth: 0,
        borderRadius: 0,
        borderSkipped: false,
        barPercentage: 0.75,
        categoryPercentage: 0.7,
        stack: "expense",
      })),
    };
  }, [sliced, datasetColors, brandSecondary]);

  const options = useMemo<ChartOptions<"bar">>(
    () => ({
      responsive: true,
      maintainAspectRatio: false,
      animation: { duration: 800, easing: "easeOutQuart" },
      layout: { padding: 0 },
      plugins: {
        legend: { display: false },
        tooltip: {
          mode: "index",
          intersect: false,
          padding: 12,
          cornerRadius: 0,
          titleFont: { size: 12, weight: 600 },
          bodyFont: { size: 12, weight: 500 },
          callbacks: {
            title: (tooltipItems) => {
              const idx = tooltipItems[0]?.dataIndex;
              if (idx != null && sliced?.dates?.[idx]) {
                return formatTooltipDate(sliced.dates[idx], granularity);
              }
              return tooltipItems[0]?.label ?? "";
            },
            label: (ctx) => {
              const val = ctx.raw as number;
              if (val === 0) return;
              return ` ${ctx.dataset.label}: $${val.toLocaleString()}`;
            },
          },
        },
      },
      scales: {
        x: {
          display: showXAxis,
          stacked: true,
          grid: { display: false },
          border: { display: false },
          ticks: { font: { size: 12 }, color: FALLBACK_TICK },
        },
        y: {
          display: showYAxis,
          stacked: true,
          grid: { display: false },
          border: { display: false },
          ticks: {
            font: { size: 12 },
            color: FALLBACK_TICK,
            maxTicksLimit: 5,
            padding: 8,
            callback: (v) => `$${Number(v).toLocaleString()}`,
          },
        },
      },
    }),
    [showXAxis, showYAxis, sliced, granularity],
  );

  return (
    <Card
      className={cn("w-full py-4 sm:py-6 gap-2", className)}
      style={{ maxWidth: width, fontFamily }}
    >
      {(title || showPeriodSelector) && (
        <CardHeader className="px-3 sm:px-6">
          {title && <CardTitle className="text-overline">{title}</CardTitle>}
          {showPeriodSelector && (
            <CardAction>
              <div className="flex gap-0.5 sm:gap-1">
                {periods.map((p) => (
                  <ChartPeriodButton
                    key={p}
                    period={p}
                    active={period === p}
                    onClick={() => setPeriod(p)}
                  />
                ))}
              </div>
            </CardAction>
          )}
        </CardHeader>
      )}

      <CardContent className="px-3 sm:px-6">
        {isLoading ? (
          <Skeleton style={{ height, width: "100%" }} />
        ) : !sliced ? (
          <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 gap-x-3 gap-y-1.5 pb-2">
                {sliced.datasets.map((ds, i) => (
                  <ChartLegendItem
                    key={ds.label}
                    label={ds.label}
                    color={datasetColors[i]}
                  />
                ))}
              </div>
            )}
            <div style={{ height, width: "100%", position: "relative" }}>
              <Chart
                key={brandSecondary}
                type="bar"
                data={chartData}
                options={options}
                aria-label={title}
              />
            </div>
            {showLegend && legendPosition === "bottom" && (
              <div className="flex flex-wrap gap-x-3 gap-y-1.5 pt-2">
                {sliced.datasets.map((ds, i) => (
                  <ChartLegendItem
                    key={ds.label}
                    label={ds.label}
                    color={datasetColors[i]}
                  />
                ))}
              </div>
            )}
          </div>
        )}
      </CardContent>
    </Card>
  );
}
