// App-wide colour-contrast helpers (KAR-630).
//
// Single source of truth for "given a coloured background, what text token
// goes on top". Lifted out of `lib/agenda/colors.ts` so non-agenda surfaces
// (project type tags, status badges, risk chips, …) share the same rule.
//
// Pure module — no React, no DOM. Default text tokens come from the DCT
// palette amendment (2026-05-22, see CLAUDE.md "Color System"):
//   dark text  → #353A41 (Text Primary)
//   light text → #FFFFFF (Canvas)
//
// Tailwind users: prefer the design tokens (`text-foreground` / `text-white`)
// in static markup. Use `contrastText(bg)` when the background is
// data-driven (DB-stored hex, user-picked colour, dynamic status palette).

// ─── Parsing ─────────────────────────────────────────────────────────────────
export function hexToRgb(hex: string): [number, number, number] {
  const clean = hex.replace(/^#/, '').trim()
  let h: string
  if (clean.length === 3 && /^[0-9A-Fa-f]{3}$/.test(clean)) {
    h = clean.split('').map((c) => c + c).join('')
  } else if (clean.length === 6 && /^[0-9A-Fa-f]{6}$/.test(clean)) {
    h = clean
  } else {
    throw new Error(`hexToRgb: invalid hex "${hex}" (expected #RGB or #RRGGBB)`)
  }
  return [
    parseInt(h.slice(0, 2), 16),
    parseInt(h.slice(2, 4), 16),
    parseInt(h.slice(4, 6), 16),
  ]
}

// ─── Perceived-luminance gate (cheap binary classifier) ──────────────────────
// Uses the ITU-R BT.601 weights (0.299/0.587/0.114) the agenda module already
// shipped with — keeps existing behaviour for migrated callers. Threshold 150
// has been validated against the DCT fill palette (see contrast.test.ts).
const LIGHT_THRESHOLD = 150

export function isLight(hex: string): boolean {
  const [r, g, b] = hexToRgb(hex)
  return 0.299 * r + 0.587 * g + 0.114 * b > LIGHT_THRESHOLD
}

// ─── Contrast text picker ────────────────────────────────────────────────────
export interface ContrastTextOptions {
  /** Token used on light backgrounds. Defaults to DCT Text Primary. */
  dark?: string
  /** Token used on dark backgrounds. Defaults to canvas white. */
  light?: string
}

/**
 * Returns the dark or light text token that should sit on top of `bg`. Use
 * this whenever the background colour is data-driven — every component that
 * receives a hex from the DB, from a user picker, or from a colour-keyed
 * dictionary should compute its foreground colour through this helper rather
 * than maintain a hand-paired colour table.
 */
export function contrastText(bg: string, opts: ContrastTextOptions = {}): string {
  return isLight(bg) ? (opts.dark ?? '#353A41') : (opts.light ?? '#FFFFFF')
}

// ─── WCAG-2 contrast ratio (for audits + assertions) ─────────────────────────
// Implements the WCAG-2 relative-luminance + contrast-ratio formulas. The
// helpers above use the cheaper BT.601 brightness gate for the binary
// "light vs dark" decision; the WCAG ratio is the right tool for "does this
// pair pass AA?" assertions in tests and palette audits.
function srgbToLinear(channel: number): number {
  const c = channel / 255
  return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
}

export function relativeLuminance(hex: string): number {
  const [r, g, b] = hexToRgb(hex)
  return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b)
}

export function contrastRatio(a: string, b: string): number {
  const la = relativeLuminance(a)
  const lb = relativeLuminance(b)
  const lighter = Math.max(la, lb)
  const darker = Math.min(la, lb)
  return (lighter + 0.05) / (darker + 0.05)
}

export interface WcagOptions {
  /** Apply the 3:1 large-text threshold instead of the 4.5:1 normal-text one. */
  largeText?: boolean
}

/**
 * Returns true if the pair meets WCAG 2.1 AA (4.5:1 for normal text, 3:1 for
 * large text). Use in palette tests + audits, not in render-path style logic
 * (the binary `isLight` is cheaper).
 */
export function meetsWcagAA(fg: string, bg: string, opts: WcagOptions = {}): boolean {
  const threshold = opts.largeText ? 3 : 4.5
  return contrastRatio(fg, bg) >= threshold
}
