import type { MiniChartDto, MiniTone } from "./proposed-api";

// The card face's small chart (B2, card-face principles CF2–CF4, CF8): only
// what the answer names, each row labelled with its name and value, against a
// named reference. Plain SVG, no chart.js.
// Ink (lab review P2 + P3): every mark is light grey except the ones that need
// a look, which are amber — and amber only on a card that needs a look, so it
// always means the same thing. Labels and values stay in the text colour.

/** The drawing width of a normal card's chart; a wide card draws wider (longer bars, same text size). */
const W = 300;
export const WIDE_MINI_WIDTH = 400;
const ROW = 22;
const BAR = 14;

const LABEL = "fill-foreground-default text-[11.5px]";
const VALUE = "text-[11.5px] font-bold";
const REF = "fill-foreground-muted text-[10.5px]";
/** A mark that doesn't need a look: light, so the amber ones are the heaviest ink. */
const NEUTRAL = "fill-surface-stroke-muted";

// A normal card's chart stops growing past 380 px; a wide one fills its column.
const svgClass = (width: number) => (width > W ? "w-full" : "w-full max-w-[380px]");

function fmt(value: number, digits: number): string {
  return Math.abs(value).toFixed(digits);
}

function truncate(label: string, max: number): string {
  return label.length > max ? `${label.slice(0, max - 1)}…` : label;
}

type Sized<K extends MiniChartDto["kind"]> = {
  mini: Extract<MiniChartDto, { kind: K }>;
  width: number;
  /** A row's "warn" tone shows as amber (the card needs a look). */
  warns: (tone: MiniTone) => boolean;
};

/**
 * `width`: the drawing width; the SVG scales to its container (capped on normal cards).
 * `flagged`: the card needs a look; only then are "warn" rows amber.
 */
export function MiniChart({ mini, width = W, flagged }: { mini: MiniChartDto; width?: number; flagged: boolean }) {
  const warns = (tone: MiniTone) => flagged && tone === "warn";
  switch (mini.kind) {
    case "diverging":
      return <Diverging mini={mini} width={width} warns={warns} />;
    case "bars":
      return <Bars mini={mini} width={width} warns={warns} />;
    case "verdict":
      return <Verdict mini={mini} width={width} />;
    case "ranges":
      return <Ranges mini={mini} width={width} warns={warns} />;
  }
}

// Change against a reference line: left is below, right above. Labels and
// values have their own columns so long names never run into a bar's value.
function Diverging({ mini, width, warns }: Sized<"diverging">) {
  if (mini.rows.length === 0) return null;
  const labelWidth = width * 0.36;
  const valueWidth = 46;
  const x0 = labelWidth + 6;
  const x1 = width - valueWidth - 4;
  const mid = (x0 + x1) / 2;
  const half = (x1 - x0) / 2;
  const max = Math.max(10, ...mini.rows.map((r) => Math.abs(r.value))) * 1.25;
  const height = mini.rows.length * ROW + 20;
  const reference = mini.reference === "usual" ? "their usual" : `${mini.referenceLabel ?? "the other group"} = 0`;
  return (
    <svg viewBox={`0 0 ${width} ${height}`} className={svgClass(width)} aria-hidden>
      <line x1={mid} x2={mid} y1={2} y2={mini.rows.length * ROW + 2} className="stroke-foreground-muted" strokeWidth={1} />
      <text x={mid} y={mini.rows.length * ROW + 15} textAnchor="middle" className={REF}>
        {reference}
      </text>
      {mini.rows.map((row, i) => {
        const y = 4 + i * ROW;
        const w = Math.max(2, (Math.abs(row.value) / max) * half);
        const x = row.value < 0 ? mid - w : mid;
        const warn = warns(row.tone);
        const value = `${row.value > 0 ? "+" : row.value < 0 ? "−" : ""}${fmt(row.value, 0)} ${mini.unit}`;
        return (
          <g key={`${row.label}-${i}`}>
            <text x={4} y={y + 11} className={LABEL}>
              {truncate(row.label, Math.floor(labelWidth / 6.1))}
            </text>
            <rect x={x} y={y} width={w} height={BAR} rx={3} className={warn ? "fill-feedback-warning" : NEUTRAL} />
            <text
              x={width - 2}
              y={y + 11}
              textAnchor="end"
              className={`${VALUE} ${warn ? "fill-feedback-warning" : "fill-foreground-default"}`}
            >
              {value}
            </text>
          </g>
        );
      })}
    </svg>
  );
}

// The top three and those far below, with the team median as a labelled line.
function Bars({ mini, width, warns }: Sized<"bars">) {
  if (mini.rows.length === 0) return null;
  const x0 = 64;
  const wMax = width - 110;
  const max = Math.max(...mini.rows.map((r) => r.value), mini.team ?? 0) || 1;
  const gapAt = mini.rows.findIndex((r) => r.tone === "warn");
  const yOf = (i: number) => 4 + i * ROW + (gapAt > 0 && i >= gapAt ? 8 : 0);
  const bottom = yOf(mini.rows.length - 1) + BAR + 4;
  const teamX = mini.team != null ? x0 + (mini.team / max) * wMax : null;
  return (
    <svg viewBox={`0 0 ${width} ${bottom + 14}`} className={svgClass(width)} aria-hidden>
      {mini.rows.map((row, i) => {
        const w = Math.max(2, (row.value / max) * wMax);
        const warn = warns(row.tone);
        return (
          <g key={`${row.label}-${i}`}>
            <text x={4} y={yOf(i) + 11} className={LABEL}>
              {truncate(row.label, 9)}
            </text>
            <rect x={x0} y={yOf(i)} width={w} height={BAR} rx={3} className={warn ? "fill-feedback-warning" : NEUTRAL} />
            <text x={x0 + w + 4} y={yOf(i) + 11} className={`${VALUE} ${warn ? "fill-feedback-warning" : "fill-foreground-default"}`}>
              {row.value.toFixed(mini.precision)}
            </text>
          </g>
        );
      })}
      {teamX != null && mini.team != null ? (
        <>
          <line x1={teamX} x2={teamX} y1={0} y2={bottom} className="stroke-foreground-muted" strokeDasharray="3 3" />
          <text x={teamX} y={bottom + 11} textAnchor="middle" className={REF}>
            {`team ${mini.team.toFixed(mini.precision)}${mini.unit ? ` ${mini.unit}` : ""}`}
          </text>
        </>
      ) : null}
    </svg>
  );
}

// One bar split by verdict, each part with its count and word. A Correlation
// never needs a look, so the parts are greys, darker for a clearer link.
function Verdict({ mini, width }: Omit<Sized<"verdict">, "warns">) {
  const total = mini.segments.reduce((s, seg) => s + seg.names.length, 0);
  if (total === 0) return null;
  const words = { together: "together", opposite: "opposite", unclear: "no clear link yet" };
  const fills = {
    together: "fill-foreground-muted",
    opposite: "fill-foreground-more-muted",
    unclear: NEUTRAL,
  };
  const counts = {
    together: "fill-surface-base",
    opposite: "fill-surface-base",
    unclear: "fill-foreground-default",
  };
  // Each segment starts where the ones before it end.
  const widths = mini.segments.map((seg) => (seg.names.length / total) * (width - 8));
  const starts = widths.map((_, i) => 4 + widths.slice(0, i).reduce((s, w) => s + w, 0));
  return (
    <svg viewBox={`0 0 ${width} 52`} className={svgClass(width)} aria-hidden>
      {mini.segments.map((seg, i) => {
        const w = widths[i];
        const at = starts[i];
        return (
          <g key={seg.verdict}>
            <rect x={at} y={6} width={Math.max(2, w - 3)} height={22} rx={4} className={fills[seg.verdict]} />
            {w > 18 ? (
              <text x={at + 6} y={21} className={`${counts[seg.verdict]} text-[11.5px] font-bold`}>
                {seg.names.length}
              </text>
            ) : null}
            {w > 40 ? (
              <text x={at} y={44} className={LABEL}>
                {words[seg.verdict]}
              </text>
            ) : null}
          </g>
        );
      })}
    </svg>
  );
}

// The middle half of each group's values, widest first, on one labelled scale.
function Ranges({ mini, width, warns }: Sized<"ranges">) {
  if (mini.rows.length === 0) return null;
  const x0 = 64;
  const w = width - 78;
  const span = mini.hi - mini.lo || 1;
  const sx = (v: number) => x0 + ((v - mini.lo) / span) * w;
  const bottom = mini.rows.length * ROW + 4;
  return (
    <svg viewBox={`0 0 ${width} ${bottom + 14}`} className={svgClass(width)} aria-hidden>
      {mini.rows.map((row, i) => {
        const y = 4 + i * ROW;
        return (
          <g key={`${row.label}-${i}`}>
            <text x={4} y={y + 11} className={LABEL}>
              {truncate(row.label, 9)}
            </text>
            <rect
              x={sx(row.q1)}
              y={y}
              width={Math.max(2, sx(row.q3) - sx(row.q1))}
              height={BAR}
              rx={3}
              className={warns(row.tone) ? "fill-feedback-warning" : NEUTRAL}
            />
            <line x1={sx(row.median)} x2={sx(row.median)} y1={y - 2} y2={y + BAR + 2} className="stroke-foreground-default" strokeWidth={2} />
          </g>
        );
      })}
      <text x={x0} y={bottom + 10} className={REF}>
        {`${Math.round(mini.lo)} ${mini.unit}`}
      </text>
      <text x={x0 + w} y={bottom + 10} textAnchor="end" className={REF}>
        {`${Math.round(mini.hi)} ${mini.unit}`}
      </text>
    </svg>
  );
}
