/**
 * Pure colour helpers for rendering Tag chips (ADR-0013). A Tag's colour is a
 * freeform hex, so chips must stay calm and legible for ANY hue — the product is
 * institutional and WCAG-AA (PRODUCT.md). The chip vocabulary is one system
 * everywhere: a soft tint of the colour, a colour dot, and hue-preserving dark
 * ink whose contrast against the tint is guaranteed ≥ 4.5:1. Kept DB-free +
 * side-effect-free so it unit-tests cleanly and is shared by every chip surface.
 */

type Rgb = [number, number, number];

/** Slate-500 — a neutral fallback for an unparseable colour. */
const FALLBACK = "#64748b";
/** Minimum WCAG contrast for the chip's ink against its tint background. */
const MIN_CONTRAST = 4.5;

/** Parse `#rgb` / `#rrggbb` (any case, surrounding space) → [r,g,b] 0-255, or null. */
function parseHex(input: string): Rgb | null {
  const hex = input.trim().toLowerCase().replace(/^#/, "");
  const full =
    hex.length === 3
      ? hex.split("").map((c) => c + c).join("")
      : hex;
  if (!/^[0-9a-f]{6}$/.test(full)) return null;
  return [
    parseInt(full.slice(0, 2), 16),
    parseInt(full.slice(2, 4), 16),
    parseInt(full.slice(4, 6), 16),
  ];
}

function toHex(rgb: Rgb): string {
  return "#" + rgb.map((c) => Math.round(c).toString(16).padStart(2, "0")).join("");
}

/** Normalise a hex to lowercase `#rrggbb`, or null if unparseable. */
export function normaliseHex(input: string): string | null {
  const rgb = parseHex(input);
  return rgb ? toHex(rgb) : null;
}

/** The dot colour for a Tag — its normalised hex, or a neutral fallback. */
export function tagDotColour(colour: string): string {
  return normaliseHex(colour) ?? FALLBACK;
}

function luminanceRgb([r, g, b]: Rgb): number {
  const lin = [r, g, b].map((c) => {
    const s = c / 255;
    return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
  });
  return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
}

/** Relative luminance (WCAG) of a hex colour, 0 → 1; unparseable → 1 (light). */
export function luminance(input: string): number {
  const rgb = parseHex(input);
  return rgb ? luminanceRgb(rgb) : 1;
}

function contrast(a: Rgb, b: Rgb): number {
  const la = luminanceRgb(a);
  const lb = luminanceRgb(b);
  const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
  return (hi + 0.05) / (lo + 0.05);
}

/** Blend `colour` over white at `alpha` (0-1) → the resulting opaque rgb. */
function overWhite(rgb: Rgb, alpha: number): Rgb {
  return rgb.map((c) => c * alpha + 255 * (1 - alpha)) as Rgb;
}

/** Mix `rgb` toward black by `t` (0 = unchanged, 1 = black). */
function towardBlack(rgb: Rgb, t: number): Rgb {
  return rgb.map((c) => c * (1 - t)) as Rgb;
}

/**
 * A dark, hue-preserving ink for `colour` that reads at ≥ 4.5:1 on `bg`. Starts
 * from the colour itself (dark tags keep their exact hue) and darkens toward
 * black only as far as legibility requires.
 */
function readableInk(rgb: Rgb, bg: Rgb): string {
  for (let t = 0; t <= 1.0001; t += 0.05) {
    const candidate = towardBlack(rgb, t);
    if (contrast(candidate, bg) >= MIN_CONTRAST) return toHex(candidate);
  }
  return "#000000";
}

export interface ChipTokens {
  backgroundColor: string;
  borderColor: string;
  color: string;
  dot: string;
}

/**
 * Inline tokens for a soft Tag chip: a light tint of the colour, a slightly
 * stronger border, a colour dot, and dark hue-preserving ink guaranteed legible
 * on the tint. One quiet, AA-safe vocabulary for every chip in the app.
 */
export function tagChipTokens(colour: string): ChipTokens {
  const rgb = parseHex(colour) ?? parseHex(FALLBACK)!;
  const tint = overWhite(rgb, 0.14);
  const border = overWhite(rgb, 0.34);
  return {
    backgroundColor: toHex(tint),
    borderColor: toHex(border),
    color: readableInk(rgb, tint),
    dot: toHex(rgb),
  };
}
