// ---------------------------------------------------------------------------
// BarChart — grouped vertical bar chart on cream paper.
//
// Renders N series (e.g. billed, received, AR) over M categories (months).
// Each category gets a cluster of bars, one per series. The chart grows
// the bars from 0 on mount with a staggered delay so the statement
// doesn't appear all at once. Hover shows the exact value as a small
// ledger callout. Designed to live INSIDE the paper statement — no dark
// chrome, hairline grid only.
//
// The paper variant: cream background, dark ink, hairline rule in
// --surface-line-soft. Three series uses --accent (billed), --success
// (received), --signal (AR outstanding) so each carries its own
// semantic colour rather than three random hues.
// ---------------------------------------------------------------------------

import { useEffect, useRef, useState } from "react";

export interface BarSeries {
  /** Stable id used for keying, hover hit-tests, and legend swatches. */
  id: string;
  /** Display name shown in the legend. */
  label: string;
  /** Hex/HSL colour used for bar fill + legend swatch. */
  color: string;
  /** Values aligned with `categories`. */
  values: number[];
  /** Optional formatter for tooltip + axis labels. Defaults to identity. */
  format?: (n: number) => string;
}

export interface BarChartProps {
  /** Category labels along the X axis (e.g. month abbreviations). */
  categories: string[];
  /** Ordered series. First series renders leftmost in each cluster. */
  series: BarSeries[];
  /** Height in px. Default 260. */
  height?: number;
  /** Optional y-axis tick formatter. If omitted, the chart self-scales
   *  to a "round" max and labels with a compact thousands suffix. */
  formatY?: (n: number) => string;
  /** When true, draws a hairline rule for each y tick. Default true. */
  showGrid?: boolean;
  /** Force a specific Y-axis maximum. When omitted, the chart auto-scales
   *  to a "round" max with a small headroom. */
  yMax?: number;
  /** When true, prints the per-cluster total above each month. Default true. */
  showTotals?: boolean;
  /** Index of the "current" category to highlight (e.g. latest month).
   *  Adds a subtle background band and an eyebrow above. */
  highlightIndex?: number;
}

const PAD_LEFT = 44;
const PAD_RIGHT = 16;
const PAD_TOP = 16;
const PAD_BOTTOM = 28;

const niceMax = (raw: number): number => {
  if (raw <= 0) return 1;
  const exp = Math.floor(Math.log10(raw));
  const base = Math.pow(10, exp);
  const norm = raw / base;
  let nice: number;
  if (norm <= 1) nice = 1;
  else if (norm <= 2) nice = 2;
  else if (norm <= 2.5) nice = 2.5;
  else if (norm <= 5) nice = 5;
  else nice = 10;
  return nice * base;
};

const compact = (n: number): string => {
  const abs = Math.abs(n);
  if (abs >= 1_000_000) return `$${(n / 1_000_000).toFixed(1)}M`;
  if (abs >= 1_000) return `$${(n / 1_000).toFixed(0)}k`;
  return `$${n.toFixed(0)}`;
};

interface Hover {
  cat: number;
  series: number;
  x: number;
  y: number;
}

export function BarChart({
  categories,
  series,
  height = 260,
  formatY,
  showGrid = true,
  yMax: yMaxProp,
  showTotals = true,
  highlightIndex,
}: BarChartProps) {
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const [width, setWidth] = useState(800);
  const [hover, setHover] = useState<Hover | null>(null);

  // Track container width so the SVG reflows with the layout grid.
  useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    const ro = new ResizeObserver((entries) => {
      const w = entries[0]?.contentRect.width ?? 800;
      setWidth(Math.max(320, Math.floor(w)));
    });
    ro.observe(el);
    setWidth(Math.max(320, Math.floor(el.clientWidth)));
    return () => ro.disconnect();
  }, []);

  const innerW = width - PAD_LEFT - PAD_RIGHT;
  const innerH = height - PAD_TOP - PAD_BOTTOM;

  const allValues = series.flatMap((s) => s.values);
  const rawMax = allValues.length ? Math.max(...allValues) : 1;
  // If caller forces a yMax, use it; otherwise compute a "round" max
  // with a small (5%) headroom so the tallest bar doesn't kiss the lid.
  const yMax = yMaxProp ?? niceMax(rawMax * 1.05);
  const yTicks = 4;

  const clusterW = innerW / Math.max(1, categories.length);
  const barGap = 4;
  const barW = Math.max(
    4,
    (clusterW - barGap * (series.length - 1)) / Math.max(1, series.length)
  );

  const yToPx = (v: number) => PAD_TOP + innerH - (v / yMax) * innerH;
  const catCenterX = (i: number) => PAD_LEFT + clusterW * i + clusterW / 2;

  const fmtY = formatY ?? compact;

  // Stagger the bars so they grow from baseline on first paint. We just
  // emit a CSS animation-delay per bar via inline style.
  const barIndex = (catI: number, serI: number) =>
    catI * series.length + serI;

  return (
    <div ref={wrapRef} className="relative w-full" style={{ height }}>
      <svg
        width={width}
        height={height}
        viewBox={`0 0 ${width} ${height}`}
        className="block"
        role="img"
        aria-label="Monthly billed, received, and AR trend"
        onMouseLeave={() => setHover(null)}
      >
        {/* Y-axis grid + labels */}
        {showGrid &&
          Array.from({ length: yTicks + 1 }, (_, i) => {
            const v = (yMax / yTicks) * i;
            const y = yToPx(v);
            return (
              <g key={i}>
                <line
                  x1={PAD_LEFT}
                  x2={width - PAD_RIGHT}
                  y1={y}
                  y2={y}
                  stroke="hsl(30 14% 14% / 0.10)"
                  strokeWidth={1}
                  strokeDasharray={i === 0 ? "" : "2 3"}
                />
                <text
                  x={PAD_LEFT - 10}
                  y={y + 4}
                  textAnchor="end"
                  className="mono"
                  fontSize={11}
                  fontWeight={500}
                  style={{ fill: "hsl(var(--surface-ink-2))" }}
                >
                  {fmtY(v)}
                </text>
              </g>
            );
          })}

        {/* Highlight band on the current month (subtle, like a sticky note) */}
        {highlightIndex !== undefined &&
        highlightIndex >= 0 &&
        highlightIndex < categories.length ? (
          <g>
            <rect
              x={catCenterX(highlightIndex) - clusterW / 2}
              y={PAD_TOP}
              width={clusterW}
              height={innerH}
              fill="hsl(36 92% 56% / 0.06)"
            />
            <line
              x1={catCenterX(highlightIndex)}
              x2={catCenterX(highlightIndex)}
              y1={PAD_TOP}
              y2={PAD_TOP + innerH}
              stroke="hsl(36 92% 56% / 0.45)"
              strokeWidth={1}
              strokeDasharray="2 3"
            />
            <text
              x={catCenterX(highlightIndex)}
              y={PAD_TOP + 12}
              textAnchor="middle"
              className="mono"
              fontSize={9}
              fontWeight={600}
              style={{ fill: "hsl(36 92% 36%)", letterSpacing: "0.18em" }}
            >
              NOW
            </text>
          </g>
        ) : null}

        {/* Bars */}
        {categories.map((cat, ci) =>
          series.map((s, si) => {
            const v = s.values[ci] ?? 0;
            const x =
              catCenterX(ci) -
              (series.length * barW + (series.length - 1) * barGap) / 2 +
              si * (barW + barGap);
            const y = yToPx(v);
            const h = Math.max(0, PAD_TOP + innerH - y);
            const delay = barIndex(ci, si) * 35;
            const isHover =
              hover?.cat === ci && hover?.series === si ? true : false;
            return (
              <rect
                key={`${cat}-${s.id}`}
                x={x}
                y={y}
                width={barW}
                height={h}
                rx={1.5}
                fill={s.color}
                opacity={hover && !isHover ? 0.55 : 1}
                style={{
                  transformOrigin: `${x + barW / 2}px ${PAD_TOP + innerH}px`,
                  animation: `bar-grow 600ms cubic-bezier(0.2,0.8,0.2,1) ${delay}ms both`,
                }}
                onMouseEnter={() =>
                  setHover({
                    cat: ci,
                    series: si,
                    x: x + barW / 2,
                    y,
                  })
                }
              />
            );
          })
        )}

        {/* X-axis labels */}
        {categories.map((cat, i) => (
          <text
            key={cat}
            x={catCenterX(i)}
            y={height - 14}
            textAnchor="middle"
            className="mono"
            fontSize={11}
            fontWeight={500}
            style={{
              fill:
                highlightIndex === i
                  ? "hsl(var(--surface-ink))"
                  : "hsl(var(--surface-ink-2))",
            }}
          >
            {cat}
          </text>
        ))}

        {/* Per-cluster totals above each month — quick read of the spine. */}
        {showTotals &&
          categories.map((_, ci) => {
            const total = series.reduce(
              (s, sr) => s + (sr.values[ci] ?? 0),
              0
            );
            const x = catCenterX(ci);
            const topY = yToPx(
              Math.max(...series.map((s) => s.values[ci] ?? 0))
            );
            const labelY = Math.max(14, topY - 8);
            return (
              <text
                key={`total-${ci}`}
                x={x}
                y={labelY}
                textAnchor="middle"
                className="display mono"
                fontSize={11}
                fontWeight={600}
                style={{
                  fill: "hsl(var(--surface-ink-2))",
                  opacity: 0.75,
                }}
              >
                {fmtY(total)}
              </text>
            );
          })}

        {/* Hover callout */}
        {hover ? (
          (() => {
            const s = series[hover.series]!;
            const v = s.values[hover.cat] ?? 0;
            const catLabel = categories[hover.cat] ?? "";
            const text = `${s.label} · ${catLabel}`;
            const valueText = s.format ? s.format(v) : fmtY(v);
            // Position the chip to the right of the bar; flip left if it
            // would overflow the canvas edge.
            const flipLeft = hover.x > width - 160;
            const chipX = flipLeft ? hover.x - 8 : hover.x + 8;
            const anchor: "end" | "start" = flipLeft ? "end" : "start";
            return (
              <g style={{ pointerEvents: "none" }}>
                <line
                  x1={hover.x}
                  x2={hover.x}
                  y1={PAD_TOP}
                  y2={PAD_TOP + innerH}
                  stroke={s.color}
                  strokeWidth={1}
                  strokeDasharray="2 3"
                  opacity={0.55}
                />
                <text
                  x={chipX}
                  y={Math.max(14, hover.y - 10)}
                  textAnchor={anchor}
                  className="mono"
                  fontSize={10}
                  style={{ fill: "hsl(var(--surface-ink-3))" }}
                >
                  {text}
                </text>
                <text
                  x={chipX}
                  y={Math.max(26, hover.y + 2)}
                  textAnchor={anchor}
                  className="display mono"
                  fontSize={16}
                  style={{ fill: "hsl(var(--surface-ink))" }}
                >
                  {valueText}
                </text>
              </g>
            );
          })()
        ) : null}
      </svg>

      {/* Legend */}
      <div className="flex items-center gap-4 mt-3 flex-wrap">
        {series.map((s) => (
          <div key={s.id} className="flex items-center gap-2">
            <span
              aria-hidden
              className="inline-block h-2 w-2 rounded-sm"
              style={{ backgroundColor: s.color }}
            />
            <span
              className="mono text-[10.5px] uppercase tracking-[0.16em]"
              style={{ color: "hsl(var(--surface-ink-3))" }}
            >
              {s.label}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}