// https://github.com/colorjs/color-name/blob/ab89e11d535d7ef46ed84e549cf69e1e3a156c83/index.js
export const NamedColors = {
    aliceblue: [240, 248, 255],
    antiquewhite: [250, 235, 215],
    aqua: [0, 255, 255],
    aquamarine: [127, 255, 212],
    azure: [240, 255, 255],
    beige: [245, 245, 220],
    bisque: [255, 228, 196],
    black: [0, 0, 0],
    blanchedalmond: [255, 235, 205],
    blue: [0, 0, 255],
    blueviolet: [138, 43, 226],
    brown: [165, 42, 42],
    burlywood: [222, 184, 135],
    cadetblue: [95, 158, 160],
    chartreuse: [127, 255, 0],
    chocolate: [210, 105, 30],
    coral: [255, 127, 80],
    cornflowerblue: [100, 149, 237],
    cornsilk: [255, 248, 220],
    crimson: [220, 20, 60],
    cyan: [0, 255, 255],
    darkblue: [0, 0, 139],
    darkcyan: [0, 139, 139],
    darkgoldenrod: [184, 134, 11],
    darkgray: [169, 169, 169],
    darkgreen: [0, 100, 0],
    darkgrey: [169, 169, 169],
    darkkhaki: [189, 183, 107],
    darkmagenta: [139, 0, 139],
    darkolivegreen: [85, 107, 47],
    darkorange: [255, 140, 0],
    darkorchid: [153, 50, 204],
    darkred: [139, 0, 0],
    darksalmon: [233, 150, 122],
    darkseagreen: [143, 188, 143],
    darkslateblue: [72, 61, 139],
    darkslategray: [47, 79, 79],
    darkslategrey: [47, 79, 79],
    darkturquoise: [0, 206, 209],
    darkviolet: [148, 0, 211],
    deeppink: [255, 20, 147],
    deepskyblue: [0, 191, 255],
    dimgray: [105, 105, 105],
    dimgrey: [105, 105, 105],
    dodgerblue: [30, 144, 255],
    firebrick: [178, 34, 34],
    floralwhite: [255, 250, 240],
    forestgreen: [34, 139, 34],
    fuchsia: [255, 0, 255],
    gainsboro: [220, 220, 220],
    ghostwhite: [248, 248, 255],
    gold: [255, 215, 0],
    goldenrod: [218, 165, 32],
    gray: [128, 128, 128],
    green: [0, 128, 0],
    greenyellow: [173, 255, 47],
    grey: [128, 128, 128],
    honeydew: [240, 255, 240],
    hotpink: [255, 105, 180],
    indianred: [205, 92, 92],
    indigo: [75, 0, 130],
    ivory: [255, 255, 240],
    khaki: [240, 230, 140],
    lavender: [230, 230, 250],
    lavenderblush: [255, 240, 245],
    lawngreen: [124, 252, 0],
    lemonchiffon: [255, 250, 205],
    lightblue: [173, 216, 230],
    lightcoral: [240, 128, 128],
    lightcyan: [224, 255, 255],
    lightgoldenrodyellow: [250, 250, 210],
    lightgray: [211, 211, 211],
    lightgreen: [144, 238, 144],
    lightgrey: [211, 211, 211],
    lightpink: [255, 182, 193],
    lightsalmon: [255, 160, 122],
    lightseagreen: [32, 178, 170],
    lightskyblue: [135, 206, 250],
    lightslategray: [119, 136, 153],
    lightslategrey: [119, 136, 153],
    lightsteelblue: [176, 196, 222],
    lightyellow: [255, 255, 224],
    lime: [0, 255, 0],
    limegreen: [50, 205, 50],
    linen: [250, 240, 230],
    magenta: [255, 0, 255],
    maroon: [128, 0, 0],
    mediumaquamarine: [102, 205, 170],
    mediumblue: [0, 0, 205],
    mediumorchid: [186, 85, 211],
    mediumpurple: [147, 112, 219],
    mediumseagreen: [60, 179, 113],
    mediumslateblue: [123, 104, 238],
    mediumspringgreen: [0, 250, 154],
    mediumturquoise: [72, 209, 204],
    mediumvioletred: [199, 21, 133],
    midnightblue: [25, 25, 112],
    mintcream: [245, 255, 250],
    mistyrose: [255, 228, 225],
    moccasin: [255, 228, 181],
    navajowhite: [255, 222, 173],
    navy: [0, 0, 128],
    oldlace: [253, 245, 230],
    olive: [128, 128, 0],
    olivedrab: [107, 142, 35],
    orange: [255, 165, 0],
    orangered: [255, 69, 0],
    orchid: [218, 112, 214],
    palegoldenrod: [238, 232, 170],
    palegreen: [152, 251, 152],
    paleturquoise: [175, 238, 238],
    palevioletred: [219, 112, 147],
    papayawhip: [255, 239, 213],
    peachpuff: [255, 218, 185],
    peru: [205, 133, 63],
    pink: [255, 192, 203],
    plum: [221, 160, 221],
    powderblue: [176, 224, 230],
    purple: [128, 0, 128],
    rebeccapurple: [102, 51, 153],
    red: [255, 0, 0],
    rosybrown: [188, 143, 143],
    royalblue: [65, 105, 225],
    saddlebrown: [139, 69, 19],
    salmon: [250, 128, 114],
    sandybrown: [244, 164, 96],
    seagreen: [46, 139, 87],
    seashell: [255, 245, 238],
    sienna: [160, 82, 45],
    silver: [192, 192, 192],
    skyblue: [135, 206, 235],
    slateblue: [106, 90, 205],
    slategray: [112, 128, 144],
    slategrey: [112, 128, 144],
    snow: [255, 250, 250],
    springgreen: [0, 255, 127],
    steelblue: [70, 130, 180],
    tan: [210, 180, 140],
    teal: [0, 128, 128],
    thistle: [216, 191, 216],
    tomato: [255, 99, 71],
    turquoise: [64, 224, 208],
    violet: [238, 130, 238],
    wheat: [245, 222, 179],
    white: [255, 255, 255],
    whitesmoke: [245, 245, 245],
    yellow: [255, 255, 0],
    yellowgreen: [154, 205, 50],
};
const D50 = [0.3457 / 0.3585, 1.0, (1.0 - 0.3457 - 0.3585) / 0.3585];
// https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
// https://drafts.csswg.org/css-syntax-3/#number-token-diagram
const Num = `[+-]?(?:\\d*\\.\\d+|\\d+)(?:[eE][+-]?\\d+)?`;
// Units
const Angle = `(?:(${Num})(deg|grad|rad|turn)?|none)`;
const Percent = `(?:(${Num})(%)?|none)`;
const Alpha = `(?:\\s*(?:\\/|,|\\s)\\s*${Percent})?`;
const Comma = `(?:\\s+|\\s*,\\s*)`;
// Color regular expressions
const RgbRegex = `^rgba?\\(\\s*${Percent}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const HslRegex = `^hsla?\\(\\s*${Angle}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const HwbRegex = `^hwb\\(\\s*${Angle}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const LinearRgbRegex = `^color\\(\\s*srgb-linear${Comma}${Percent}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const XyzRegex = `^color\\(\\s*xyz${Comma}${Percent}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const LabRegex = `^lab\\(\\s*${Percent}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const LchRegex = `^lch\\(\\s*${Percent}${Comma}${Percent}${Comma}${Angle}${Alpha}\\s*\\)$`;
const OklabRegex = `^oklab\\(\\s*${Percent}${Comma}${Percent}${Comma}${Percent}${Alpha}\\s*\\)$`;
const OklchRegex = `^oklch\\(\\s*${Percent}${Comma}${Percent}${Comma}${Angle}${Alpha}\\s*\\)$`;
function getAngle(angle, unit) {
    if (angle === undefined)
        return 0;
    const n = Number(angle);
    switch (unit) {
        case 'deg':
            return n;
        case 'grad':
            return (n / 400) * 360;
        case 'rad':
            return (n / Math.PI) * 180;
        case 'turn':
            return n * 360;
        default:
            return n;
    }
}
function getPercent(number, percent, max) {
    if (number === undefined)
        return 0;
    const n = Number(number);
    return percent ? (n / 100) * max : n;
}
function getAlpha(number, percent) {
    if (number === undefined)
        return 1;
    const n = Number(number);
    return percent ? n / 100 : n;
}
/* Color conversions

                ┌───────┐
             ┌──┤  HSL  │
             │  └───────┘
  ┌───────┐  │  ┌───────┐
  │  RGB  ├──┼──┤  HWB  │                ┌───────┐  ┌───────┐
  └───────┘  │  └───────┘             ┌──┤  LAB  ├──┤  LCH  │
             │  ┌───────┐  ┌───────┐  │  └───────┘  └───────┘
             └──┤ l RGB ├──┤  XYZ  ├──┤
                └───────┘  └───────┘  │  ┌───────┐  ┌───────┐
                                      └──┤ OKLAB ├──┤ OKLCH │
                                         └───────┘  └───────┘

https://www.w3.org/TR/css-color-4/#color-conversion-code
 */
export const ColorSpace = {
    Rgb: 'rgb',
    Hex: 'hex',
    Hsl: 'hsl',
    Hwb: 'hwb',
    LinearRgb: 'linear-rgb',
    Xyz: 'xyz',
    Lab: 'lab',
    Lch: 'lch',
    Oklab: 'oklab',
    Oklch: 'oklch',
};
export const ColorSpaces = [
    ColorSpace.Rgb,
    ColorSpace.Hex,
    ColorSpace.Hsl,
    ColorSpace.Hwb,
    ColorSpace.LinearRgb,
    ColorSpace.Xyz,
    ColorSpace.Lab,
    ColorSpace.Lch,
    ColorSpace.Oklab,
    ColorSpace.Oklch,
];
export class Color {
    space;
    c1;
    c2;
    c3;
    alpha;
    constructor(space, c1, c2, c3, alpha) {
        this.space = space;
        this.c1 = c1;
        this.c2 = c2;
        this.c3 = c3;
        this.alpha = alpha;
        switch (this.space) {
            // r: [0, 255], g: [0, 255], b: [0, 255]
            case ColorSpace.Rgb:
                this.c1 = Math.clamp(c1, 0, 255);
                this.c2 = Math.clamp(c2, 0, 255);
                this.c3 = Math.clamp(c3, 0, 255);
                break;
            // r: [0, 255], g: [0, 255], b: [0, 255]
            case ColorSpace.Hex:
                this.c1 = Math.clamp(c1, 0, 255);
                this.c2 = Math.clamp(c2, 0, 255);
                this.c3 = Math.clamp(c3, 0, 255);
                break;
            // h: [0, 360), s: [0, 100], l: [0, 100]
            case ColorSpace.Hsl:
                this.c1 = (c1 % 360) + (c1 < 0 ? 360 : 0);
                this.c2 = Math.clamp(c2, 0, 100);
                this.c3 = Math.clamp(c3, 0, 100);
                break;
            // h: [0, 360), w: [0, 100], b: [0, 100]
            case ColorSpace.Hwb:
                this.c1 = (c1 % 360) + (c1 < 0 ? 360 : 0);
                this.c2 = Math.clamp(c2, 0, 100);
                this.c3 = Math.clamp(c3, 0, 100);
                break;
            // r: [0, 1], g: [0, 1], b: [0, 1]
            case ColorSpace.LinearRgb:
                this.c1 = Math.clamp(c1, 0, 1);
                this.c2 = Math.clamp(c2, 0, 1);
                this.c3 = Math.clamp(c3, 0, 1);
                break;
            // x: [0, ∞), y: [0, ∞), z: [0, ∞)
            case ColorSpace.Xyz:
                this.c1 = Math.max(0, c1);
                this.c2 = Math.max(0, c2);
                this.c3 = Math.max(0, c3);
                break;
            // l: [0, 100], a: [-125, 125], b: [-125, 125]
            case ColorSpace.Lab:
                this.c1 = Math.clamp(c1, 0, 100);
                this.c2 = Math.clamp(c2, -125, 125);
                this.c3 = Math.clamp(c3, -125, 125);
                break;
            // l: [0, 100], c: [0, 150], h, [0, 360)
            case ColorSpace.Lch:
                this.c1 = Math.clamp(c1, 0, 100);
                this.c2 = Math.clamp(c2, 0, 150);
                this.c3 = (c3 % 360) + (c3 < 0 ? 360 : 0);
                break;
            // l: [0, 1], a: [-0.4, 0.4], b: [-0.4, 0.4]
            case ColorSpace.Oklab:
                this.c1 = Math.clamp(c1, 0, 1);
                this.c2 = Math.clamp(c2, -0.4, 0.4);
                this.c3 = Math.clamp(c3, -0.4, 0.4);
                break;
            // l: [0, 1], c: [0, 0.4], h: [0, 360)
            case ColorSpace.Oklch:
                this.c1 = Math.clamp(c1, 0, 1);
                this.c2 = Math.clamp(c2, 0, 0.4);
                this.c3 = (c3 % 360) + (c3 < 0 ? 360 : 0);
                break;
        }
        // alpha: [0, 1]
        this.alpha = Math.clamp(alpha, 0, 1);
    }
    static fromRgba(r, g, b, a) {
        return new Color(ColorSpace.Rgb, r, g, b, a);
    }
    static parse(str) {
        return (Color.parseNamed(str) ??
            Color.parseHex(str) ??
            Color.parseRgb(str) ??
            Color.parseHsl(str) ??
            Color.parseHwb(str) ??
            Color.parseLinearRgb(str) ??
            Color.parseXyz(str) ??
            Color.parseLab(str) ??
            Color.parseLch(str) ??
            Color.parseOkLab(str) ??
            Color.parseOkLch(str));
    }
    static parseNamed(str) {
        str = str.toLocaleLowerCase();
        if (str in NamedColors) {
            const [r, g, b] = NamedColors[str];
            return new Color(ColorSpace.Rgb, r, g, b, 1);
        }
        return null;
    }
    static parseHex(str) {
        const regex = /^#([0-9a-fA-F]+)$/;
        const match = str.match(regex);
        if (!match)
            return null;
        const hex = match[1];
        if (hex.length === 3 || hex.length === 4) {
            const rgba = hex.split('').map((c) => Number(`0x${c}${c}`));
            return new Color(ColorSpace.Hex, rgba[0], rgba[1], rgba[2], rgba[3] ? rgba[3] / 255 : 1);
        }
        else if (hex.length === 6 || hex.length === 8) {
            const rgba = hex.match(/.{2}/g).map((c) => Number(`0x${c}`));
            return new Color(ColorSpace.Hex, rgba[0], rgba[1], rgba[2], rgba[3] ? rgba[3] / 255 : 1);
        }
        else {
            return null;
        }
    }
    static parseRgb(str) {
        const regex = new RegExp(RgbRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const r = getPercent(match[1], match[2], 255);
        const g = getPercent(match[3], match[4], 255);
        const b = getPercent(match[5], match[6], 255);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Rgb, r, g, b, a);
    }
    static parseHsl(str) {
        const regex = new RegExp(HslRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const h = getAngle(match[1], match[2]);
        const s = getPercent(match[3], match[4], 100);
        const l = getPercent(match[5], match[6], 100);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Hsl, h, s, l, a);
    }
    static parseHwb(str) {
        const regex = new RegExp(HwbRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const h = getAngle(match[1], match[2]);
        const w = getPercent(match[3], match[4], 100);
        const b = getPercent(match[5], match[6], 100);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Hwb, h, w, b, a);
    }
    static parseLinearRgb(str) {
        const regex = new RegExp(LinearRgbRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const r = getPercent(match[1], match[2], 1);
        const g = getPercent(match[3], match[4], 1);
        const b = getPercent(match[5], match[6], 1);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.LinearRgb, r, g, b, a);
    }
    static parseXyz(str) {
        const regex = new RegExp(XyzRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const x = getPercent(match[1], match[2], 1);
        const y = getPercent(match[3], match[4], 1);
        const z = getPercent(match[5], match[6], 1);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Xyz, x, y, z, a);
    }
    static parseLab(str) {
        const regex = new RegExp(LabRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const l = getPercent(match[1], match[2], 100);
        const a = getPercent(match[3], match[4], 125);
        const b = getPercent(match[5], match[6], 125);
        const alpha = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Lab, l, a, b, alpha);
    }
    static parseLch(str) {
        const regex = new RegExp(LchRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const l = getPercent(match[1], match[2], 100);
        const c = getPercent(match[3], match[4], 150);
        const h = getAngle(match[5], match[6]);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Lch, l, c, h, a);
    }
    static parseOkLab(str) {
        const regex = new RegExp(OklabRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const l = getPercent(match[1], match[2], 1);
        const a = getPercent(match[3], match[4], 0.4);
        const b = getPercent(match[5], match[6], 0.4);
        const alpha = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Oklab, l, a, b, alpha);
    }
    static parseOkLch(str) {
        const regex = new RegExp(OklchRegex);
        const match = str.match(regex);
        if (!match)
            return null;
        const l = getPercent(match[1], match[2], 1);
        const c = getPercent(match[3], match[4], 0.4);
        const h = getAngle(match[5], match[6]);
        const a = getAlpha(match[7], match[8]);
        return new Color(ColorSpace.Oklch, l, c, h, a);
    }
    toString() {
        function hex(c) {
            return Math.round(c).toString(16).padStart(2, '0');
        }
        const twoDigits = [
            ColorSpace.LinearRgb,
            ColorSpace.Lab,
            ColorSpace.Lch,
            ColorSpace.Oklab,
            ColorSpace.Oklch,
        ];
        const digits = this.space === ColorSpace.Xyz ? 4 : twoDigits.includes(this.space) ? 2 : 0;
        const c1 = Number.parseFloat(this.c1.toFixed(digits));
        const c2 = Number.parseFloat(this.c2.toFixed(digits));
        const c3 = Number.parseFloat(this.c3.toFixed(digits));
        const alpha = this.alpha === 1 ? '' : ` / ${Number.parseFloat(this.alpha.toFixed(2))}`;
        switch (this.space) {
            case ColorSpace.Rgb:
                return `rgb(${c1} ${c2} ${c3}${alpha})`;
            case ColorSpace.Hex:
                return `#${hex(c1)}${hex(c2)}${hex(c3)}${this.alpha === 1 ? '' : hex(this.alpha * 255)}`;
            case ColorSpace.Hsl:
                return `hsl(${c1} ${c2}% ${c3}%${alpha})`;
            case ColorSpace.Hwb:
                return `hwb(${c1} ${c2}% ${c3}%${alpha})`;
            case ColorSpace.Lab:
                return `lab(${c1} ${c2} ${c3}${alpha})`;
            case ColorSpace.Lch:
                return `lch(${c1} ${c2} ${c3}${alpha})`;
            case ColorSpace.Oklab:
                return `oklab(${c1} ${c2} ${c3}${alpha})`;
            case ColorSpace.Oklch:
                return `oklch(${c1} ${c2} ${c3}${alpha})`;
            case ColorSpace.LinearRgb:
                return `color(srgb-linear ${c1} ${c2} ${c3}${alpha})`;
            case ColorSpace.Xyz:
                return `color(xyz ${c1} ${c2} ${c3}${alpha})`;
        }
    }
    toColor(space) {
        switch (space) {
            case ColorSpace.Rgb:
                return this.rgb();
            case ColorSpace.Hex:
                return this.hex();
            case ColorSpace.Hsl:
                return this.hsl();
            case ColorSpace.Hwb:
                return this.hwb();
            case ColorSpace.LinearRgb:
                return this.linear();
            case ColorSpace.Xyz:
                return this.xyz();
            case ColorSpace.Lab:
                return this.lab();
            case ColorSpace.Lch:
                return this.lch();
            case ColorSpace.Oklab:
                return this.oklab();
            case ColorSpace.Oklch:
                return this.oklch();
        }
    }
    luminance() {
        const color = this.linear();
        return color.c1 * 0.2126 + color.c2 * 0.7152 + color.c3 * 0.0722;
    }
    rgb() {
        switch (this.space) {
            case ColorSpace.Rgb:
                return this;
            case ColorSpace.Hex:
                return this.hexToRgb();
            case ColorSpace.Hsl:
                return this.hslToRgb();
            case ColorSpace.Hwb:
                return this.hwbToRgb();
            default:
                return this.linear().linearToRgb();
        }
    }
    hex() {
        return this.space === ColorSpace.Hex ? this : this.rgb().rgbToHex();
    }
    hsl() {
        return this.space === ColorSpace.Hsl ? this : this.rgb().rgbToHsl();
    }
    hwb() {
        return this.space === ColorSpace.Hwb ? this : this.rgb().rgbToHwb();
    }
    linear() {
        switch (this.space) {
            case ColorSpace.LinearRgb:
                return this;
            case ColorSpace.Rgb:
            case ColorSpace.Hex:
            case ColorSpace.Hsl:
            case ColorSpace.Hwb:
                return this.rgb().rgbToLinear();
            default:
                return this.xyz().xyzToLinear();
        }
    }
    xyz() {
        switch (this.space) {
            case ColorSpace.Xyz:
                return this;
            case ColorSpace.Rgb:
            case ColorSpace.Hex:
            case ColorSpace.Hsl:
            case ColorSpace.Hwb:
            case ColorSpace.LinearRgb:
                return this.linear().linearToXyz();
            case ColorSpace.Lab:
            case ColorSpace.Lch:
                return this.lab().labToXyz();
            case ColorSpace.Oklab:
            case ColorSpace.Oklch:
                return this.oklab().oklabToXyz();
        }
    }
    lab() {
        switch (this.space) {
            case ColorSpace.Lab:
                return this;
            case ColorSpace.Lch:
                return this.lchToLab();
            default:
                return this.xyz().xyzToLab();
        }
    }
    lch() {
        return this.space === ColorSpace.Lch ? this : this.lab().labToLch();
    }
    oklab() {
        switch (this.space) {
            case ColorSpace.Oklab:
                return this;
            case ColorSpace.Oklch:
                return this.oklchToOklab();
            default:
                return this.xyz().xyzToOklab();
        }
    }
    oklch() {
        return this.space === ColorSpace.Oklch ? this : this.oklab().oklabToOklch();
    }
    /* Conversions */
    rgbToHex() {
        return new Color(ColorSpace.Hex, this.c1, this.c2, this.c3, this.alpha);
    }
    hexToRgb() {
        return new Color(ColorSpace.Rgb, this.c1, this.c2, this.c3, this.alpha);
    }
    rgbToHsl() {
        const [r, g, b] = [this.c1 / 255, this.c2 / 255, this.c3 / 255];
        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        const h = this.calculateHue();
        const l = (max + min) / 2;
        const s = l === 0 || l === 1 ? 0 : (max - l) / Math.min(l, 1 - l);
        return new Color(ColorSpace.Hsl, h, s * 100, l * 100, this.alpha);
    }
    hslToRgb() {
        const [h, s, l] = [this.c1, this.c2 / 100, this.c3 / 100];
        const C = (1 - Math.abs(2 * l - 1)) * s;
        const H = (h / 60) % 6;
        const X = C * (1 - Math.abs((H % 2) - 1));
        const [r, g, b] = (() => {
            if (0 <= H && H < 1)
                return [C, X, 0];
            if (1 <= H && H < 2)
                return [X, C, 0];
            if (2 <= H && H < 3)
                return [0, C, X];
            if (3 <= H && H < 4)
                return [0, X, C];
            if (4 <= H && H < 5)
                return [X, 0, C];
            return [C, 0, X];
        })();
        const m = l - C / 2;
        return new Color(ColorSpace.Rgb, (r + m) * 255, (g + m) * 255, (b + m) * 255, this.alpha);
    }
    rgbToHwb() {
        const epsilon = 0.00001;
        const [R, G, B] = [this.c1 / 255, this.c2 / 255, this.c3 / 255];
        let h = this.calculateHue();
        const w = Math.min(R, G, B);
        const b = 1 - Math.max(R, G, B);
        if (w + b >= 1 - epsilon)
            h = 0;
        return new Color(ColorSpace.Hwb, h, w * 100, b * 100, this.alpha);
    }
    hwbToRgb() {
        const [h, white, black] = [this.c1, this.c2 / 100, this.c3 / 100];
        if (white + black >= 1) {
            const l = white / (white + black);
            return new Color(ColorSpace.Rgb, l, l, l, this.alpha);
        }
        const color = new Color(ColorSpace.Hsl, h, 100, 50, this.alpha).rgb();
        const x = 1 - white - black;
        const w = white * 255;
        const r = color.c1 * x + w;
        const g = color.c2 * x + w;
        const b = color.c3 * x + w;
        return new Color(ColorSpace.Rgb, r, g, b, this.alpha);
    }
    rgbToLinear() {
        function channel(c) {
            return c > 0.04045 ? Math.pow((c + 0.055) / 1.055, 2.4) : c / 12.92;
        }
        const r = channel(this.c1 / 255);
        const g = channel(this.c2 / 255);
        const b = channel(this.c3 / 255);
        return new Color(ColorSpace.LinearRgb, r, g, b, this.alpha);
    }
    linearToRgb() {
        function channel(c) {
            if (c < 0.0031308)
                return 12.92 * c;
            return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
        }
        const r = channel(this.c1) * 255;
        const g = channel(this.c2) * 255;
        const b = channel(this.c3) * 255;
        return new Color(ColorSpace.Rgb, r, g, b, this.alpha);
    }
    linearToXyz() {
        const [r, g, b] = [this.c1, this.c2, this.c3];
        const x = (506752 / 1228815) * r + (87881 / 245763) * g + (12673 / 70218) * b;
        const y = (87098 / 409605) * r + (175762 / 245763) * g + (12673 / 175545) * b;
        const z = (7918 / 409605) * r + (87881 / 737289) * g + (1001167 / 1053270) * b;
        return new Color(ColorSpace.Xyz, x, y, z, this.alpha);
    }
    xyzToLinear() {
        const [x, y, z] = [this.c1, this.c2, this.c3];
        const r = (12831 / 3959) * x + (-329 / 214) * y + (-1974 / 3959) * z;
        const g = (-851781 / 878810) * x + (1648619 / 878810) * y + (36819 / 878810) * z;
        const b = (705 / 12673) * x + (-2585 / 12673) * y + (705 / 667) * z;
        return new Color(ColorSpace.LinearRgb, r, g, b, this.alpha);
    }
    xyzToLab() {
        const epsilon = 216 / 24389;
        const kappa = 24389 / 27;
        function channel(c) {
            return c > epsilon ? Math.cbrt(c) : (kappa * c + 16) / 116;
        }
        const x = channel(this.c1 / D50[0]);
        const y = channel(this.c2 / D50[1]);
        const z = channel(this.c3 / D50[2]);
        const l = 116 * y - 16;
        const a = 500 * (x - y);
        const b = 200 * (y - z);
        return new Color(ColorSpace.Lab, l, a, b, this.alpha);
    }
    labToXyz() {
        const epsilon = 216 / 24389;
        const kappa = 24389 / 27;
        const [l, a, b] = [this.c1, this.c2, this.c3];
        const fy = (l + 16) / 116;
        const fy3 = ((l + 16) / 116) ** 3;
        const fx = fy + a / 500;
        const fx3 = fx ** 3;
        const fz = fy - b / 200;
        const fz3 = fz ** 3;
        const x = fx3 > epsilon ? fx3 : (116 * fx - 16) / kappa;
        const y = l > kappa * epsilon ? fy3 : l / kappa;
        const z = fz3 > epsilon ? fz3 : (116 * fz - 16) / kappa;
        return new Color(ColorSpace.Xyz, x * D50[0], y * D50[1], z * D50[2], this.alpha);
    }
    labToLch() {
        const epsilon = 0.0015;
        const [l, a, b] = [this.c1, this.c2, this.c3];
        const c = Math.sqrt(a * a + b * b);
        let h = (Math.atan2(b, a) * 180) / Math.PI;
        if (h < 0)
            h += 360;
        if (c <= epsilon)
            h = 0;
        return new Color(ColorSpace.Lch, l, c, h, this.alpha);
    }
    lchToLab() {
        const [l, c, h] = [this.c1, this.c2, this.c3];
        const a = c * Math.cos((h * Math.PI) / 180);
        const b = c * Math.sin((h * Math.PI) / 180);
        return new Color(ColorSpace.Lab, l, a, b, this.alpha);
    }
    xyzToOklab() {
        const [x, y, z] = [this.c1, this.c2, this.c3];
        const l = Math.cbrt(0.819022437996703 * x + 0.3619062600528904 * y - 0.1288737815209879 * z);
        const m = Math.cbrt(0.0329836539323885 * x + 0.9292868615863434 * y + 0.0361446663506424 * z);
        const s = Math.cbrt(0.0481771893596242 * x + 0.2642395317527308 * y + 0.6335478284694309 * z);
        const L = 0.210454268309314 * l + 0.7936177747023054 * m - 0.0040720430116193 * s;
        const a = 1.9779985324311684 * l - 2.4285922420485799 * m + 0.450593709617411 * s;
        const b = 0.0259040424655478 * l + 0.7827717124575296 * m - 0.8086757549230774 * s;
        return new Color(ColorSpace.Oklab, L, a, b, this.alpha);
    }
    oklabToXyz() {
        const [L, a, b] = [this.c1, this.c2, this.c3];
        const l = (L + 0.3963377773761749 * a + 0.2158037573099136 * b) ** 3;
        const m = (L - 0.1055613458156586 * a - 0.0638541728258133 * b) ** 3;
        const s = (L - 0.0894841775298119 * a - 1.2914855480194092 * b) ** 3;
        const x = 1.2268798758459243 * l + -0.5578149944602171 * m + 0.2813910456659647 * s;
        const y = -0.0405757452148008 * l + 1.112286803280317 * m - 0.0717110580655164 * s;
        const z = -0.0763729366746601 * l - 0.4214933324022432 * m + 1.5869240198367816 * s;
        return new Color(ColorSpace.Xyz, x, y, z, this.alpha);
    }
    oklabToOklch() {
        const epsilon = 0.000004;
        const [l, a, b] = [this.c1, this.c2, this.c3];
        const c = Math.sqrt(a * a + b * b);
        let h = (Math.atan2(b, a) * 180) / Math.PI;
        if (h < 0)
            h += 360;
        if (c <= epsilon)
            h = 0;
        return new Color(ColorSpace.Oklch, l, c, h, this.alpha);
    }
    oklchToOklab() {
        const [l, c, h] = [this.c1, this.c2, this.c3];
        const a = c * Math.cos((h * Math.PI) / 180);
        const b = c * Math.sin((h * Math.PI) / 180);
        return new Color(ColorSpace.Oklab, l, a, b, this.alpha);
    }
    /* Utilities */
    calculateHue() {
        const [r, g, b] = [this.c1 / 255, this.c2 / 255, this.c3 / 255];
        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        const c = max - min;
        const h = (() => {
            if (c === 0)
                return 0;
            if (max === r)
                return (((g - b) / c) % 6) + (b > g ? 6 : 0);
            if (max === g)
                return (b - r) / c + 2;
            return (r - g) / c + 4;
        })();
        return h * 60;
    }
}
