/**
 * WCAG 2.1 contrast math, byte-equivalent twin of `app/Support/ColorContrast.php`.
 * OKLCH → OKLab → linear sRGB (Björn Ottosson reference) → WCAG relative
 * luminance → contrast ratio. Linear values feed luminance directly — WCAG's
 * gamma-decode step is folded into the linear-sRGB output here.
 *
 * Sprint 1's `themePalette.ts` parity discipline applies: any rounding here
 * must mirror the PHP twin's rounding direction. Contrast ratio is a single
 * float; PHP `parity` just checks |ts - php| < 0.05.
 */

export type Grade = 'AAA' | 'AA' | 'AA-large' | 'fail';

export type OklchTriple = { l: number; c: number; h: number };

const OKLCH_RE =
    /oklch\(\s*([-+\d.]+%?)\s*[,\s]\s*([-+\d.]+)\s*[,\s]\s*([-+\d.]+)(?:\s*\/\s*[-+\d.]+%?)?\s*\)/i;

export function parseOklch(css: string): OklchTriple | null {
    const m = css.trim().match(OKLCH_RE);
    if (!m) return null;
    const rawL = m[1];
    const l = rawL.endsWith('%') ? Number(rawL.slice(0, -1)) / 100 : Number(rawL);
    const c = Number(m[2]);
    const h = Number(m[3]);
    if (!Number.isFinite(l) || !Number.isFinite(c) || !Number.isFinite(h)) {
        return null;
    }
    return { l, c, h };
}

export function oklchToLinearSrgb(
    l: number,
    c: number,
    h: number,
): [number, number, number] {
    const hRad = (h * Math.PI) / 180;
    const a = c * Math.cos(hRad);
    const b = c * Math.sin(hRad);

    const lLms = l + 0.3963377774 * a + 0.2158037573 * b;
    const mLms = l - 0.1055613458 * a - 0.0638541728 * b;
    const sLms = l - 0.0894841775 * a - 1.291485548 * b;

    const lLms3 = lLms * lLms * lLms;
    const mLms3 = mLms * mLms * mLms;
    const sLms3 = sLms * sLms * sLms;

    const r = 4.0767416621 * lLms3 - 3.3077115913 * mLms3 + 0.2309699292 * sLms3;
    const g = -1.2684380046 * lLms3 + 2.6097574011 * mLms3 - 0.3413193965 * sLms3;
    const bLin = -0.0041960863 * lLms3 - 0.7034186147 * mLms3 + 1.707614701 * sLms3;

    return [r, g, bLin];
}

export function relativeLuminance(rgb: [number, number, number]): number {
    const r = clamp01(rgb[0]);
    const g = clamp01(rgb[1]);
    const b = clamp01(rgb[2]);
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function clamp01(n: number): number {
    if (!Number.isFinite(n)) return 0;
    if (n < 0) return 0;
    if (n > 1) return 1;
    return n;
}

function luminanceOf(css: string): number | null {
    const parsed = parseOklch(css);
    if (!parsed) return null;
    return relativeLuminance(oklchToLinearSrgb(parsed.l, parsed.c, parsed.h));
}

export function ratio(fg: string, bg: string): number {
    const fgLum = luminanceOf(fg);
    const bgLum = luminanceOf(bg);
    if (fgLum === null || bgLum === null) return 1;
    const light = Math.max(fgLum, bgLum);
    const dark = Math.min(fgLum, bgLum);
    return (light + 0.05) / (dark + 0.05);
}

export function grade(r: number): Grade {
    if (r >= 7) return 'AAA';
    if (r >= 4.5) return 'AA';
    if (r >= 3) return 'AA-large';
    return 'fail';
}
