-- VDS_ColorMath
-- ============================================================================
-- Pure perceptual colour math (OKLab). No WoW dependencies; safe to dofile in
-- tests. sRGB inputs are 0..1 floats (the swatchColorStart shape from the dye
-- catalog). Lua 5.1 compatible.
--
-- Why OKLab: perceptually-uniform lightness + chroma, so the engine's value-gap
-- (anti-mud) and saturation (pop-vs-ground) judgements match the eye instead of
-- raw RGB. See docs/VDS_ColorTheory.md.

VDS = VDS or {}
VDS.ColorMath = VDS.ColorMath or {}
local CM = VDS.ColorMath

local function lin(c)  -- sRGB 0..1 -> linear-light
    if c <= 0.04045 then return c / 12.92 end
    return ((c + 0.055) / 1.055) ^ 2.4
end

-- r,g,b in 0..1 -> OKLab L, a, b
function CM.OKLab(r, g, b)
    local R, G, B = lin(r), lin(g), lin(b)
    local l = (0.4122214708 * R + 0.5363325363 * G + 0.0514459929 * B) ^ (1 / 3)
    local m = (0.2119034982 * R + 0.6806995451 * G + 0.1073969566 * B) ^ (1 / 3)
    local s = (0.0883024619 * R + 0.2817188376 * G + 0.6299787005 * B) ^ (1 / 3)
    return  0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
            1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
            0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s
end

-- perceptual lightness 0..1
function CM.L(r, g, b)
    local L = CM.OKLab(r, g, b)
    return L
end

-- perceptual chroma -- distance from grey in the a/b plane (saturation, honest)
function CM.chroma(r, g, b)
    local _, a, b2 = CM.OKLab(r, g, b)
    return math.sqrt(a * a + b2 * b2)
end

-- warm(+) / cool(-) undertone -- OKLab b (yellow/blue) with a touch of a
-- (red/green). Carries the undertone even at near-zero chroma (warm cream vs
-- cool blue-grey). ~0 = achromatic, no temperature.
function CM.warmth(r, g, b)
    local _, a, b2 = CM.OKLab(r, g, b)
    return b2 + 0.4 * a
end

-- |dL| between two {r,g,b} colours -- the anti-mud value gap (>= 0.15 = crisp)
function CM.valueGap(c1, c2)
    return math.abs(CM.L(c1.r, c1.g, c1.b) - CM.L(c2.r, c2.g, c2.b))
end

-- perceptual distance between two {r,g,b} colours (clash / too-similar test)
function CM.deltaE(c1, c2)
    local L1, a1, b1 = CM.OKLab(c1.r, c1.g, c1.b)
    local L2, a2, b2 = CM.OKLab(c2.r, c2.g, c2.b)
    return math.sqrt((L1 - L2) ^ 2 + (a1 - a2) ^ 2 + (b1 - b2) ^ 2)
end
