"use client";

import type { ReactNode } from "react";
import type { ChartShape } from "./chart-shape";

// Draws a ChartShape with everything the builder already knows: the title, the
// metric(s), who and which lifts (tag members included), the period, and — on
// the plot — the names of the panels, rows, columns, tabs and radar axes. The
// marks themselves stay neutral sketches (dashed lines, even bars, grey cells):
// values only exist once the chart is built, and made-up values next to real
// names would read as a real result. Anything not picked yet draws as a grey
// block.

const W = 320;
const H = 150;
const GAP = 8;
const SKETCH = "var(--foreground-platinum-green-bright)";
const NEUTRAL = "var(--surface-stroke)";
const MUTED = "var(--foreground-muted)";
const STRONG = "var(--foreground-default)";
const FONT = 9;

/** What the preview can name from the picks. */
export type PreviewLabels = {
  /** The value axis: the metric (a Correlation's Y metric). */
  metric: string | null;
  /** A Correlation's X metric. */
  metricX?: string | null;
  /** Every athlete the chart covers, sorted. */
  athletes: readonly string[];
  /** Every lift the chart covers, sorted. */
  lifts: readonly string[];
  timeframe: string;
  /** Profile: one name per radar axis. */
  axes?: readonly string[];
  /** Profile: the two groups' names. */
  groups?: { profile: string; reference: string };
};

// Fixed curve heights (0..1): a shape to sketch, not data.
const WAVE = [0.35, 0.52, 0.44, 0.62, 0.55, 0.72, 0.66, 0.8];

/** Shortens a name to fit `max` characters. */
function fit(name: string, max: number): string {
  return name.length > max ? `${name.slice(0, Math.max(1, max - 1)).trimEnd()}…` : name;
}

/**
 * The name for slot `index` of `count` drawn slots: the name itself, or "+N"
 * on the last slot when there are more names than slots, or null (draw a block).
 */
function slotName(names: readonly string[], index: number, count: number): string | null {
  if (names.length > count && index === count - 1) return `+${names.length - count + 1}`;
  return names[index] ?? null;
}

function Block({ x, y, width }: { x: number; y: number; width: number }) {
  return <rect x={x} y={y} width={width} height={6} rx={3} fill={NEUTRAL} />;
}

// A label, or a grey block where the name isn't known yet.
function Label({
  x,
  y,
  text,
  max,
  anchor = "start",
  strong = false,
  blockWidth = 40,
}: {
  x: number;
  y: number;
  text: string | null | undefined;
  max: number;
  anchor?: "start" | "middle" | "end";
  strong?: boolean;
  blockWidth?: number;
}) {
  if (!text) {
    const bx = anchor === "end" ? x - blockWidth : anchor === "middle" ? x - blockWidth / 2 : x;
    return <Block x={bx} y={y - 6} width={blockWidth} />;
  }
  return (
    <text
      x={x}
      y={y}
      textAnchor={anchor}
      fontSize={FONT}
      fontWeight={strong ? 600 : 400}
      fill={strong ? STRONG : MUTED}
    >
      {fit(text, max)}
    </text>
  );
}

function Frame({ x, y, width, height }: { x: number; y: number; width: number; height: number }) {
  return <rect x={x} y={y} width={width} height={height} rx={6} fill="none" stroke={NEUTRAL} />;
}

// A dashed sketch of a line across a box; `shift` varies it between panels.
function Sketch({ x, y, width, height, shift = 0 }: { x: number; y: number; width: number; height: number; shift?: number }) {
  const points = WAVE.map((_, i) => {
    const f = WAVE[(i + shift) % WAVE.length];
    return `${(x + (width * i) / (WAVE.length - 1)).toFixed(1)},${(y + height - f * height).toFixed(1)}`;
  });
  return (
    <polyline
      points={points.join(" ")}
      fill="none"
      stroke={SKETCH}
      strokeOpacity={0.6}
      strokeWidth={2}
      strokeDasharray="5 4"
      strokeLinecap="round"
    />
  );
}

// Lays `count` boxes out in up to three columns and draws each with `draw`.
function Grid({ count, draw }: { count: number; draw: (box: { x: number; y: number; width: number; height: number; index: number }) => ReactNode }) {
  const columns = Math.min(count, 3);
  const rows = Math.ceil(count / columns);
  const width = (W - GAP * (columns - 1)) / columns;
  const height = (H - GAP * (rows - 1)) / rows;
  return (
    <>
      {Array.from({ length: count }, (_, index) => (
        <g key={index}>
          {draw({
            x: (index % columns) * (width + GAP),
            y: Math.floor(index / columns) * (height + GAP),
            width,
            height,
            index,
          })}
        </g>
      ))}
    </>
  );
}

function Body({ shape, labels }: { shape: ChartShape; labels: PreviewLabels }) {
  switch (shape.kind) {
    case "singleTrend": {
      const series = [labels.athletes[0], labels.lifts[0]].filter(Boolean).join(" · ");
      return (
        <>
          <Label x={0} y={8} text={labels.metric} max={30} strong blockWidth={72} />
          <Label x={W} y={8} text={series || null} max={34} anchor="end" blockWidth={60} />
          {[0.38, 0.68].map((f) => (
            <line key={f} x1={0} x2={W} y1={H * f} y2={H * f} stroke={NEUTRAL} />
          ))}
          <line x1={0} x2={W} y1={H - 14} y2={H - 14} stroke={NEUTRAL} />
          <Sketch x={0} y={18} width={W} height={H - 38} />
          <Label x={W} y={H - 2} text={labels.timeframe} max={30} anchor="end" />
        </>
      );
    }
    case "trendPanels": {
      const names = shape.by === "athlete" ? labels.athletes : labels.lifts;
      return (
        <Grid
          count={shape.panels}
          draw={({ x, y, width, height, index }) => (
            <>
              <Frame x={x} y={y} width={width} height={height} />
              <Label x={x + 6} y={y + 12} text={slotName(names, index, shape.panels)} max={Math.floor(width / 5.2)} strong blockWidth={width * 0.45} />
              <Sketch x={x + 6} y={y + 20} width={width - 12} height={height - 26} shift={index} />
            </>
          )}
        />
      );
    }
    case "trendMatrix": {
      const labelWidth = 70;
      const top = 16;
      const cellWidth = (W - labelWidth) / shape.columns;
      const rowHeight = (H - top) / shape.rows;
      return (
        <>
          {Array.from({ length: shape.columns }, (_, c) => (
            <Label
              key={`h${c}`}
              x={labelWidth + c * cellWidth + cellWidth / 2}
              y={9}
              text={slotName(labels.lifts, c, shape.columns)}
              max={Math.floor(cellWidth / 5.2)}
              anchor="middle"
              strong
              blockWidth={cellWidth - 14}
            />
          ))}
          {Array.from({ length: shape.rows }, (_, r) => (
            <g key={r}>
              <Label x={0} y={top + r * rowHeight + rowHeight / 2 + 3} text={slotName(labels.athletes, r, shape.rows)} max={13} blockWidth={labelWidth - 14} />
              {Array.from({ length: shape.columns }, (_, c) => (
                <rect
                  key={c}
                  x={labelWidth + c * cellWidth + 2}
                  y={top + r * rowHeight + 2}
                  width={cellWidth - 4}
                  height={rowHeight - 4}
                  rx={4}
                  fill={NEUTRAL}
                  fillOpacity={0.45}
                />
              ))}
            </g>
          ))}
        </>
      );
    }
    case "ranking": {
      const top = shape.tabs > 0 ? 18 : 12;
      const rowHeight = (H - top) / shape.bars;
      const labelWidth = 76;
      return (
        <>
          {shape.tabs > 0
            ? Array.from({ length: shape.tabs }, (_, i) => {
                const name = slotName(labels.lifts, i, shape.tabs);
                return (
                  <g key={`t${i}`}>
                    <rect x={i * 72} y={0} width={66} height={13} rx={6.5} fill={i === 0 ? SKETCH : "none"} fillOpacity={0.18} stroke={i === 0 ? SKETCH : NEUTRAL} />
                    <Label x={i * 72 + 33} y={9.5} text={name} max={12} anchor="middle" strong={i === 0} blockWidth={40} />
                  </g>
                );
              })
            : <Label x={0} y={8} text={labels.lifts.length ? labels.lifts[0] : null} max={30} strong blockWidth={60} />}
          {Array.from({ length: shape.bars }, (_, i) => {
            const y = top + i * rowHeight;
            return (
              <g key={i}>
                <Label x={0} y={y + rowHeight / 2 + 3} text={slotName(labels.athletes, i, shape.bars)} max={14} blockWidth={labelWidth - 16} />
                <rect
                  x={labelWidth}
                  y={y + rowHeight * 0.22}
                  width={W - labelWidth}
                  height={rowHeight * 0.56}
                  rx={3}
                  fill="none"
                  stroke={NEUTRAL}
                  strokeDasharray="4 3"
                />
              </g>
            );
          })}
        </>
      );
    }
    case "distribution": {
      const names = shape.by === "athlete" ? labels.athletes : labels.lifts;
      const rowHeight = (H - 12) / shape.rows;
      const plotX = 80;
      return (
        <>
          {Array.from({ length: shape.rows }, (_, i) => {
            const y = i * rowHeight + rowHeight / 2;
            return (
              <g key={i}>
                <Label x={0} y={y + 3} text={slotName(names, i, shape.rows)} max={14} blockWidth={56} />
                <line x1={plotX + 20} x2={W - 20} y1={y} y2={y} stroke={NEUTRAL} />
                <rect x={plotX + 70} y={y - rowHeight * 0.25} width={110} height={rowHeight * 0.5} rx={3} fill="none" stroke={SKETCH} strokeOpacity={0.6} strokeDasharray="4 3" />
              </g>
            );
          })}
          <Label x={W} y={H - 1} text={labels.metric} max={34} anchor="end" />
        </>
      );
    }
    case "correlation":
      return (
        <Grid
          count={shape.panels}
          draw={({ x, y, width, height, index }) => {
            const single = shape.panels === 1;
            return (
              <>
                <Frame x={x} y={y} width={width} height={height} />
                <Label x={x + 6} y={y + 12} text={slotName(labels.lifts, index, shape.panels)} max={Math.floor(width / 5.2)} strong blockWidth={width * 0.4} />
                {WAVE.concat(WAVE).map((_, i) => {
                  const t = (i + 0.5) / (WAVE.length * 2);
                  const wobble = WAVE[(i + index * 3) % WAVE.length] - 0.55;
                  return (
                    <circle
                      key={i}
                      cx={x + 8 + t * (width - 16)}
                      cy={y + height - 16 - Math.min(Math.max(t * 0.8 + wobble * 0.4, 0.05), 0.95) * (height - 36)}
                      r={2.2}
                      fill={NEUTRAL}
                    />
                  );
                })}
                {single ? (
                  <>
                    <Label x={x + width - 6} y={y + 12} text={labels.metric ? `↑ ${labels.metric}` : null} max={28} anchor="end" />
                    <Label x={x + width - 6} y={y + height - 4} text={labels.metricX ? `${labels.metricX} →` : null} max={36} anchor="end" />
                  </>
                ) : null}
              </>
            );
          }}
        />
      );
    case "radar": {
      const cx = W / 2;
      const cy = H / 2 - 7;
      const radius = H / 2 - 27;
      const point = (i: number, f: number) => {
        const angle = -Math.PI / 2 + (i * 2 * Math.PI) / shape.axes;
        return [cx + Math.cos(angle) * radius * f, cy + Math.sin(angle) * radius * f] as const;
      };
      const polygon = (factor: (i: number) => number) =>
        Array.from({ length: shape.axes }, (_, i) => point(i, factor(i)).map((v) => v.toFixed(1)).join(",")).join(" ");
      const axisNames = labels.axes ?? [];
      return (
        <>
          {Array.from({ length: shape.axes }, (_, i) => {
            const [x, y] = point(i, 1);
            const [lx, ly] = point(i, 1.16);
            const anchor = Math.abs(lx - cx) < 4 ? "middle" : lx > cx ? "start" : "end";
            return (
              <g key={i}>
                <line x1={cx} y1={cy} x2={x} y2={y} stroke={NEUTRAL} />
                <Label x={lx} y={ly + 3} text={axisNames[i]} max={16} anchor={anchor} blockWidth={28} />
              </g>
            );
          })}
          <polygon points={polygon(() => 1)} fill="none" stroke={NEUTRAL} />
          <polygon points={polygon(() => 0.62)} fill="none" stroke={MUTED} strokeDasharray="4 3" />
          <polygon points={polygon(() => 0.8)} fill={SKETCH} fillOpacity={0.08} stroke={SKETCH} strokeOpacity={0.7} strokeDasharray="5 4" strokeWidth={1.5} />
          {/* Legend: the two groups. */}
          <rect x={0} y={H - 9} width={10} height={4} rx={2} fill={SKETCH} />
          <Label x={14} y={H - 4} text={labels.groups?.profile} max={22} strong blockWidth={50} />
          <rect x={W / 2} y={H - 9} width={10} height={4} rx={2} fill={MUTED} />
          <Label x={W / 2 + 14} y={H - 4} text={labels.groups?.reference} max={22} blockWidth={50} />
        </>
      );
    }
  }
}

/**
 * The builder's preview: the chart's title and who · what · when above the
 * plot, then the layout with every name the picks already give.
 */
export function ChartPreview({
  shape,
  labels,
  title,
  facts,
}: {
  shape: ChartShape;
  labels: PreviewLabels;
  title: string | null;
  /** The header's details, e.g. metric, who, which lifts, period. Empty ones are skipped. */
  facts: readonly string[];
}) {
  const shown = facts.filter((fact) => fact.trim().length > 0);
  return (
    <div>
      {title ? (
        <p className="body-prominent truncate text-foreground-default">{title}</p>
      ) : (
        <span aria-hidden className="block h-3 w-32 rounded-full bg-surface-stroke" />
      )}
      {shown.length > 0 ? (
        <p className="body-small mt-0.5 text-foreground-muted">{shown.join(" · ")}</p>
      ) : null}
      <svg viewBox={`-2 -2 ${W + 4} ${H + 4}`} className="mt-3 w-full overflow-visible" aria-hidden>
        <Body shape={shape} labels={labels} />
      </svg>
    </div>
  );
}
