import { L as LightModeColorTokens, b as VegaColorSchemaOverridingController } from './dark-mode-style-controller-504fdc2b.js';
import './static-subject-title-52f93124.js';
import './change-manager-6a7eb88c.js';
import './global-slimmer-registry-17c4efd4.js';
import './observer-3959f9dd.js';
import './tinycolor-polyfill-7be0eb93.js';
import './vega-nonce-manager-497e5eb5.js';
import './try-get-document-bef0f526.js';
import './index-3b327f29.js';

// Self-contained hex-color validation: this package is consumed by vega-stencil
// (which has its own `isValidHexColor` in `utils/ui.ts`), but we cannot import
// across the package boundary. Pattern matches vega-stencil's exactly.
const HEX_COLOR_PATTERN = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
/** Returns true if the string is a valid 3- or 6-digit hex color (with optional `#`). */
function isValidHexColor(hex) {
  return HEX_COLOR_PATTERN.test(hex);
}
/**
 * sRGB linearization constants per IEC 61966-2-1
 *
 * Threshold on companded (sRGB) channel value (0–1) below which the linear form is used.
 *  Note: WCAG 2.0/2.1 reference uses 0.03928 (pre-amendment IEC 61966-2-1). The 2001
 *  amendment updated to 0.04045; difference is negligible (<0.3% of sRGB gamut).
 */
const SRGB_THRESHOLD = 0.03928;
/** Equivalent threshold on linearized channel value (0–1) — used for the inverse transform. */
const SRGB_LINEAR_THRESHOLD = 0.0031308;
const SRGB_LINEAR_FACTOR = 12.92;
const SRGB_GAMMA_OFFSET = 0.055;
const SRGB_GAMMA_DIVISOR = 1.055;
const SRGB_GAMMA_EXPONENT = 2.4;
/** ITU-R BT.709 luminance coefficients */
const LUMINANCE_R = 0.2126;
const LUMINANCE_G = 0.7152;
const LUMINANCE_B = 0.0722;
/** WCAG contrast ratio offset */
const CONTRAST_OFFSET = 0.05;
const MAX_RGB = 255;
// ────────────────────────────────────────────────────────────────────────────────
// sRGB ↔ linear
// ────────────────────────────────────────────────────────────────────────────────
/**
 * Convert a companded sRGB channel value (0–1) to its linear-light equivalent.
 *
 * @param {number} c - Companded sRGB channel value (0–1).
 * @returns {number} Linearized channel value.
 */
function srgbChannelToLinear(c) {
  return c <= SRGB_THRESHOLD
    ? c / SRGB_LINEAR_FACTOR
    : ((c + SRGB_GAMMA_OFFSET) / SRGB_GAMMA_DIVISOR) ** SRGB_GAMMA_EXPONENT;
}
/**
 * Convert a linear-light channel value (0–1) to its companded sRGB equivalent.
 *
 * @param {number} c - Linear channel value (0–1).
 * @returns {number} Companded sRGB channel value.
 */
function linearChannelToSrgb(c) {
  return c <= SRGB_LINEAR_THRESHOLD
    ? SRGB_LINEAR_FACTOR * c
    : SRGB_GAMMA_DIVISOR * c ** (1 / SRGB_GAMMA_EXPONENT) - SRGB_GAMMA_OFFSET;
}
// ────────────────────────────────────────────────────────────────────────────────
// WCAG luminance / contrast
// ────────────────────────────────────────────────────────────────────────────────
/**
 * Calculate the relative luminance of a color per WCAG 2.0.
 *
 * @param {RgbColor} color - RGB color to compute luminance for.
 * @returns {number} Relative luminance value.
 * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
 */
function getRelativeLuminance(color) {
  const r = srgbChannelToLinear(color.r / MAX_RGB);
  const g = srgbChannelToLinear(color.g / MAX_RGB);
  const b = srgbChannelToLinear(color.b / MAX_RGB);
  return LUMINANCE_R * r + LUMINANCE_G * g + LUMINANCE_B * b;
}
/**
 * Calculate the contrast ratio between two colors per WCAG 2.0. The operation is
 * symmetric, so callers don't need to sort the arguments.
 *
 * @param {RgbColor} a - First color.
 * @param {RgbColor} b - Second color.
 * @returns {number} WCAG contrast ratio.
 * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
 */
function getContrastRatio(a, b) {
  const la = getRelativeLuminance(a);
  const lb = getRelativeLuminance(b);
  const lighter = Math.max(la, lb);
  const darker = Math.min(la, lb);
  return (lighter + CONTRAST_OFFSET) / (darker + CONTRAST_OFFSET);
}
// ────────────────────────────────────────────────────────────────────────────────
// Compositing & format helpers
// ────────────────────────────────────────────────────────────────────────────────
/**
 * Composite a semi-transparent foreground color over an opaque background using the
 * standard alpha compositing formula.
 *
 * @param {RgbColor} fg - Foreground color.
 * @param {number} fgAlpha - Foreground alpha (0–1).
 * @param {RgbColor} bg - Background color.
 * @returns {RgbColor} Composited opaque color.
 */
function alphaComposite(fg, fgAlpha, bg) {
  return {
    r: Math.round(fg.r * fgAlpha + bg.r * (1 - fgAlpha)),
    g: Math.round(fg.g * fgAlpha + bg.g * (1 - fgAlpha)),
    b: Math.round(fg.b * fgAlpha + bg.b * (1 - fgAlpha)),
  };
}
/**
 * Convert an RGB color to its hex string representation.
 *
 * @param {RgbColor} color - RGB color to convert.
 * @returns {string} Hex color string with leading `#`.
 */
function rgbToHex(color) {
  /**
   * Convert a single channel to a two-digit hex string.
   *
   * @param {number} n - Channel value (0–255).
   * @returns {string} Two-digit hex string.
   */
  const toHex = (n) => n.toString(16).padStart(2, '0');
  return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
}
/**
 * Parse a 3- or 6-char hex string into an RGB triplet.
 *
 * Throws on malformed input rather than returning a silently-NaN-poisoned color — a bad
 * hex here is a programmer error (invalid `actionColor` on `VegaThemeOverrideColors`) and
 * a fail-fast surface is easier to debug than garbage-propagating contrast ratios.
 *
 * @param {string} hex - Hex color string (3 or 6 digits, optional `#` prefix).
 * @returns {RgbColor} Parsed RGB triplet.
 */
function hexToRgb(hex) {
  if (!isValidHexColor(hex)) {
    throw new Error(`Invalid hex color: "${hex}". Expected 3- or 6-digit hex with optional leading #.`);
  }
  const cleaned = hex.replace(/^#/, '');
  const full = cleaned.length === 3
    ? cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2]
    : cleaned;
  return {
    r: parseInt(full.substring(0, 2), 16),
    g: parseInt(full.substring(2, 4), 16),
    b: parseInt(full.substring(4, 6), 16),
  };
}
/**
 * Format a token value back to the `r g b / a` shape consumed by the theme layer.
 *
 * @param {RgbColor} rgb - RGB color.
 * @param {number} alpha - Alpha value (0–1).
 * @returns {string} Formatted token value string.
 */
function formatTokenValue(rgb, alpha) {
  // Round to 2 decimals — matches tinycolor's output precision and keeps CSS var readable.
  const a = Math.round(alpha * 100) / 100;
  return `${rgb.r} ${rgb.g} ${rgb.b} / ${a}`;
}
/**
 * Inverse of {@link formatTokenValue}. Returns `null` on unparseable input.
 *
 * @param {string} value - Token value string in `r g b / a` format.
 * @returns {ParsedTokenValue | null} Parsed result or null.
 */
function parseTokenValue(value) {
  const match = value.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s*\/\s*([\d.]+)\s*$/);
  if (!match) {
    return null;
  }
  return {
    rgb: {
      r: parseInt(match[1], 10),
      g: parseInt(match[2], 10),
      b: parseInt(match[3], 10),
    },
    a: parseFloat(match[4]),
  };
}
// ────────────────────────────────────────────────────────────────────────────────
// OKLab / OKLCH conversions
// ────────────────────────────────────────────────────────────────────────────────
/**
 * Convert an RGB color to its OKLab representation.
 *
 * @param {RgbColor} rgb - RGB color to convert.
 * @returns {OklabColor} OKLab color.
 */
function rgbToOklab(rgb) {
  const r = srgbChannelToLinear(rgb.r / MAX_RGB);
  const g = srgbChannelToLinear(rgb.g / MAX_RGB);
  const b = srgbChannelToLinear(rgb.b / MAX_RGB);
  const l_ = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
  const m_ = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
  const s_ = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
  return {
    L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
    a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
    b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
  };
}
/**
 * Convert an OKLab color to its RGB representation.
 *
 * @param {OklabColor} lab - OKLab color to convert.
 * @returns {RgbColor} RGB color.
 */
function oklabToRgb(lab) {
  const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
  const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
  const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
  const l = l_ ** 3;
  const m = m_ ** 3;
  const s = s_ ** 3;
  const rLinear = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
  const gLinear = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
  const bLinear = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
  /**
   * Clamp and round a linear-light channel to a byte (0–255).
   *
   * @param {number} linear - Linear-light channel value.
   * @returns {number} Clamped byte value (0–255).
   */
  const toByte = (linear) => Math.max(0, Math.min(MAX_RGB, Math.round(linearChannelToSrgb(linear) * MAX_RGB)));
  return { r: toByte(rLinear), g: toByte(gLinear), b: toByte(bLinear) };
}
/**
 * Convert an OKLab color to its OKLCH (cylindrical) representation.
 *
 * @param {OklabColor} lab - OKLab color to convert.
 * @returns {OklchColor} OKLCH color.
 */
function oklabToOklch(lab) {
  return {
    L: lab.L,
    C: Math.sqrt(lab.a * lab.a + lab.b * lab.b),
    h: (Math.atan2(lab.b, lab.a) * 180) / Math.PI,
  };
}
/**
 * Convert an OKLCH color to its OKLab (Cartesian) representation.
 *
 * @param {OklchColor} lch - OKLCH color to convert.
 * @returns {OklabColor} OKLab color.
 */
function oklchToOklab(lch) {
  const hRad = (lch.h * Math.PI) / 180;
  return {
    L: lch.L,
    a: lch.C * Math.cos(hRad),
    b: lch.C * Math.sin(hRad),
  };
}
/**
 * Perceptual color distance in OKLab (Euclidean ΔE, since OKLab is designed to be uniform).
 *
 * @param {OklabColor} a - First OKLab color.
 * @param {OklabColor} b - Second OKLab color.
 * @returns {number} Euclidean distance in OKLab space.
 */
function oklabDeltaE(a, b) {
  const dL = a.L - b.L;
  const deltaA = a.a - b.a;
  const db = a.b - b.b;
  return Math.sqrt(dL * dL + deltaA * deltaA + db * db);
}

/**
 * Light mode bg-primary ({@link LightModeColorTokens}'s `bg-primary` = `252, 252, 252, 1`).
 * Duplicated here to avoid a circular dependency with `token-checks.ts`, which imports from
 * this file. Must stay in sync with `LIGHT_BG_PRIMARY` in `token-checks.ts`.
 */
const LIGHT_BG_PRIMARY = { r: 252, g: 252, b: 252 };
/**
 * Resolve a surface to its effective opaque foreground color via alpha compositing.
 *
 * @param {ContextSurface} surface - Surface to resolve.
 * @param {RgbColor} bg - Background color for alpha compositing.
 * @returns {RgbColor} Effective foreground RGB.
 */
function effectiveFgRgb$1(surface, bg) {
  return surface.a < 1 ? alphaComposite(surface.rgb, surface.a, bg) : surface.rgb;
}
// ────────────────────────────────────────────────────────────────────────────────
// WCAG AA thresholds
// ────────────────────────────────────────────────────────────────────────────────
/** WCAG 2.1 AA minimum contrast ratio for normal-size text. */
const WCAG_AA_NORMAL_TEXT = 4.5;
/** WCAG 2.1 AA minimum contrast ratio for UI components and large text. */
const WCAG_AA_NON_TEXT_CONTRAST = 3;
/**
 * Abstract strategy for computing the rendered fg/bg pair from a token and its paired surface.
 * Each subclass implements the compositing logic specific to its WCAG check category.
 */
class ContrastCheckStrategy {
}
/**
 * Token is a background; paired text sits on top.
 * Contrast is measured between the composited background and the effective foreground text.
 */
class BgTextStrategy extends ContrastCheckStrategy {
  constructor() {
    super(...arguments);
    this.requiredRatio = WCAG_AA_NORMAL_TEXT;
  }
  /**
   * Compute fg/bg: token is background, paired text on top.
   *
   * @param {ParsedTokenValue} token - Background token value.
   * @param {ContextSurface} paired - Foreground text surface.
   * @returns {FgBgPair} Rendered foreground and background.
   */
  computeFgBg(token, paired) {
    const bg = token.a < 1 ? alphaComposite(token.rgb, token.a, LIGHT_BG_PRIMARY) : token.rgb;
    const fg = effectiveFgRgb$1(paired, bg);
    return { fg, bg };
  }
}
/**
 * Token is foreground text; paired surface is the background behind it.
 * Contrast is measured between the composited text color and its background.
 */
class TextBgStrategy extends ContrastCheckStrategy {
  constructor() {
    super(...arguments);
    this.requiredRatio = WCAG_AA_NORMAL_TEXT;
  }
  /**
   * Compute fg/bg: token is foreground text, paired surface behind.
   *
   * @param {ParsedTokenValue} token - Foreground text token value.
   * @param {ContextSurface} paired - Background surface.
   * @returns {FgBgPair} Rendered foreground and background.
   */
  computeFgBg(token, paired) {
    const pairedBg = paired.a < 1 ? alphaComposite(paired.rgb, paired.a, LIGHT_BG_PRIMARY) : paired.rgb;
    const fg = token.a < 1 ? alphaComposite(token.rgb, token.a, pairedBg) : token.rgb;
    return { fg, bg: pairedBg };
  }
}
/**
 * Token is a UI component (border, ring); `compositedOn` may override the composite surface.
 * For box-shadow rings that paint outside an element (on the page background) but are compared
 * against the element's fill, `compositedOn` ensures the alpha composite uses the correct surface.
 */
class NonTextContrastStrategy extends ContrastCheckStrategy {
  constructor() {
    super(...arguments);
    this.requiredRatio = WCAG_AA_NON_TEXT_CONTRAST;
  }
  /**
   * Compute fg/bg: token is UI component, optional composite surface override.
   *
   * @param {ParsedTokenValue} token - UI component token value.
   * @param {ContextSurface} paired - Adjacent surface for contrast.
   * @param {ContextSurface | undefined} compositedOn - Override composite surface.
   * @returns {FgBgPair} Rendered foreground and background.
   */
  computeFgBg(token, paired, compositedOn) {
    const pairedBg = paired.a < 1 ? alphaComposite(paired.rgb, paired.a, LIGHT_BG_PRIMARY) : paired.rgb;
    let compositeSurface = pairedBg;
    if (compositedOn && token.a < 1) {
      compositeSurface =
        compositedOn.a < 1
          ? alphaComposite(compositedOn.rgb, compositedOn.a, LIGHT_BG_PRIMARY)
          : compositedOn.rgb;
    }
    const fg = token.a < 1 ? alphaComposite(token.rgb, token.a, compositeSurface) : token.rgb;
    return { fg, bg: pairedBg };
  }
}
/** Strategy registry — maps check type to its strategy instance. */
const STRATEGY_MAP = {
  'bg-text': new BgTextStrategy(),
  'text-bg': new TextBgStrategy(),
  'non-text-contrast': new NonTextContrastStrategy(),
};

// ────────────────────────────────────────────────────────────────────────────────
// Fixed paired-surface table — built by the consumer from a design-token map
// ────────────────────────────────────────────────────────────────────────────────
/**
 * Names of the static design tokens that have no `overrideGetter` — the surfaces every
 * actionColor-derived token may be paired with. Source-of-truth for which static tokens
 * the checker must know about. Kept here (not in the consumer) so adding new pair targets
 * is a one-line change in this package.
 *
 * Each entry's real SCSS consumer is documented in the catalogue's inline comment.
 * `bg-action-tertiary` is α=0 (fully transparent) — composites collapse to `bg-primary`.
 */
const FIXED_PAIRED_SURFACE_TOKENS = [
  'text-on-action',
  'text-primary',
  'text-secondary',
  'text-inverted-primary',
  'text-inverted-secondary',
  'bg-primary',
  'bg-page',
  'bg-secondary',
  'bg-tertiary',
  'bg-quaternary',
  'bg-action-secondary',
  'bg-action-tertiary',
  'bg-action-tertiary-hover',
  'bg-tile',
  'bg-tile-hover',
  'bg-tile-selected',
  'bg-tile-selected-hover',
  'bg-transparent',
  'bg-danger',
  'bg-divider-secondary',
  'bg-table-row-hover',
  'bg-toggle-switch',
  'bg-status-error',
  'bg-status-info',
  'bg-status-success',
  'bg-status-warning',
  'bg-table-header',
];
/**
 * Parse the comma-separated `r, g, b, a` shape used by the generated `*-color-tokens.ts` files.
 *
 * @param {string} value - Comma-separated string in `"r, g, b, a"` format (e.g. `"252, 252, 254, 0.9"`).
 * @returns {ContextSurface} Parsed {@link ContextSurface} with `rgb` and `a` fields.
 * @throws {Error} If the string has fewer than 4 parts or contains non-numeric values.
 */
function parseCommaTokenValue(value) {
  const parts = value.split(',').map((s) => parseFloat(s.trim()));
  if (parts.length < 4 || parts.some(isNaN)) {
    throw new Error(`Malformed token value: "${value}". Expected "r, g, b, a" format.`);
  }
  const [r, g, b, a] = parts;
  return { rgb: { r, g, b }, a };
}
/**
 * Build the fixed paired-surface table from the consumer's static design-token map.
 *
 * @param {Record<string, string>} designTokens - Source design tokens (token name → "r, g, b, a").
 * @returns {Readonly<Record<string, ContextSurface>>} Frozen table for the checker.
 */
function buildFixedPairedSurfaces(designTokens) {
  const out = {};
  for (const name of FIXED_PAIRED_SURFACE_TOKENS) {
    const raw = designTokens[name];
    if (raw === undefined) {
      throw new Error(`buildFixedPairedSurfaces: missing required design token "${name}" in the supplied map.`);
    }
    out[name] = parseCommaTokenValue(raw);
  }
  return Object.freeze(out);
}
// ────────────────────────────────────────────────────────────────────────────────
// Pure surface / contrast helpers (parameterized on fixed-surface table)
// ────────────────────────────────────────────────────────────────────────────────
/**
 * Resolve a `pairedWith` name to its rendered {@link ContextSurface}.
 *
 * Lookup order:
 *  1. **`tokenMap` (actionColor-derived)** — covers every token that has an
 *     `overrideGetter` and therefore depends on `actionColor`.
 *  2. **`fixedSurfaces`** — covers the static design tokens that have no `overrideGetter`.
 *
 * @param {string} pairedWith - Token name to resolve.
 * @param {Map<string, string>} tokenMap - Resolved token map for dynamic lookups.
 * @param {Readonly<Record<string, ContextSurface>>} fixedSurfaces - Static surface table.
 * @returns {ContextSurface | undefined} Resolved surface, or undefined if not found.
 */
function resolvePairedSurface(pairedWith, tokenMap, fixedSurfaces) {
  const dynamicValue = tokenMap.get(pairedWith);
  if (dynamicValue) {
    const parsed = parseTokenValue(dynamicValue);
    if (parsed) {
      return { rgb: parsed.rgb, a: parsed.a };
    }
  }
  return fixedSurfaces[pairedWith];
}
/**
 * Resolve a paired surface to its rendered foreground RGB when it sits on top of `bg`.
 * For opaque surfaces this is a no-op; for `text-on-action` (α<1) it performs the alpha
 * composite the browser does at paint time, so the contrast ratio we compute matches
 * what axe / Chrome DevTools see.
 *
 * @param {ContextSurface} surface - Surface to resolve.
 * @param {RgbColor} bg - Background color for alpha compositing.
 * @returns {RgbColor} Effective foreground RGB.
 */
function effectiveFgRgb(surface, bg) {
  return surface.a < 1 ? alphaComposite(surface.rgb, surface.a, bg) : surface.rgb;
}
/**
 * Return the WCAG AA required contrast ratio for a check type.
 *
 * @param {TokenCheckConfig['type']} type - Check type.
 * @returns {number} Required contrast ratio.
 */
function requiredRatioFor(type) {
  return type === 'non-text-contrast' ? WCAG_AA_NON_TEXT_CONTRAST : WCAG_AA_NORMAL_TEXT;
}
/**
 * Compute the contrast ratio + rendered colors for a single token check under a given
 * `actionColor`. Returns null when the token has no override value, the value can't be
 * parsed, or the paired surface fails to resolve (catalogue typo).
 *
 * @param {TokenCheckConfig} checkConfig - Token check configuration.
 * @param {Map<string, string>} tokenMap - Resolved token map for surface lookups.
 * @param {Readonly<Record<string, ContextSurface>>} fixedSurfaces - Static surface table.
 * @returns {ContrastResult | null} Contrast result or null.
 */
function computeTokenContrast(checkConfig, tokenMap, fixedSurfaces) {
  const tokenValue = tokenMap.get(checkConfig.tokenName);
  if (!tokenValue) {
    return null;
  }
  const parsed = parseTokenValue(tokenValue);
  if (!parsed) {
    return null;
  }
  const paired = resolvePairedSurface(checkConfig.pairedWith, tokenMap, fixedSurfaces);
  if (!paired) {
    return null;
  }
  const compositedOn = checkConfig.compositedOn
    ? resolvePairedSurface(checkConfig.compositedOn, tokenMap, fixedSurfaces)
    : undefined;
  const strategy = STRATEGY_MAP[checkConfig.type];
  const { fg, bg } = strategy.computeFgBg(parsed, paired, compositedOn);
  const contrastRatio = Math.round(getContrastRatio(fg, bg) * 100) / 100;
  return { fg, bg, contrastRatio };
}

/**
 * Search engine for finding perceptually-closest feasible colors in OKLCH space and
 * resolving minimum-alpha / RGB-shift fixes for WCAG contrast requirements.
 *
 * The constructor takes the static `fixedSurfaces` table and the `bg-primary` RGB used
 * as the underlying composite surface for semi-transparent paint. Both come from the
 * design-token map supplied by the consumer.
 */
class ColorSearchEngine {
  constructor(fixedSurfaces) {
    this.fixedSurfaces = fixedSurfaces;
    const bgPrimary = fixedSurfaces['bg-primary'];
    if (!bgPrimary) {
      throw new Error('ColorSearchEngine: fixedSurfaces is missing required key "bg-primary".');
    }
    this.lightBgPrimary = bgPrimary.rgb;
  }
  /**
   * Find the closest feasible color (by OKLab ΔE) within a (hue ± 5°, chroma × 0.6–1.4) grid.
   *
   * @param {RgbColor} origin - Starting RGB color to search from.
   * @param {Function} isFeasible - Predicate returning true if a candidate RGB meets constraints.
   * @param {boolean} assumeMonotone - `true`: binary search on L (fast, monotone predicate required);
   *   `false`: linear scan on L (handles internal-band feasibility).
   * @returns {RgbColor | undefined} Closest feasible RGB, or `undefined` if none found in envelope.
   */
  searchClosestFeasibleInOklch(origin, isFeasible, assumeMonotone = true) {
    const originalLab = rgbToOklab(origin);
    const { L: L0, C: C0, h: H0 } = oklabToOklch(originalLab);
    /**
     * Try a candidate OKLCH point and return its RGB + perceptual distance if feasible.
     *
     * @param {OklchColor} lch - Candidate OKLCH color.
     * @returns {{ rgb: RgbColor; deltaE: number } | undefined} Result or undefined.
     */
    const tryCandidate = (lch) => {
      const rgb = oklabToRgb(oklchToOklab(lch));
      if (!isFeasible(rgb))
        return undefined;
      return { rgb, deltaE: oklabDeltaE(originalLab, rgbToOklab(rgb)) };
    };
    /**
     * Binary search on L axis from both endpoints inward (monotone assumption).
     *
     * @param {number} C - Chroma value.
     * @param {number} h - Hue angle in degrees.
     * @returns {{ rgb: RgbColor; deltaE: number } | undefined} Best candidate or undefined.
     */
    const searchLAxisMonotone = (C, h) => {
      const atL0 = tryCandidate({ L: L0, C, h });
      if (atL0)
        return atL0;
      let best;
      // Search darker direction: [0, L₀]
      const darkEnd = tryCandidate({ L: 0, C, h });
      if (darkEnd) {
        best = darkEnd;
        let lo = 0;
        let hi = L0;
        for (let i = 0; i < ColorSearchEngine.SUGGESTION_SEARCH_ITERATIONS; i++) {
          const mid = (lo + hi) / 2;
          const midResult = tryCandidate({
            L: mid,
            C,
            h,
          });
          if (midResult) {
            lo = mid;
            best = midResult;
          }
          else {
            hi = mid;
          }
        }
      }
      // Search lighter direction: [L₀, 1]
      const lightEnd = tryCandidate({ L: 1, C, h });
      if (lightEnd) {
        let lightBest = lightEnd;
        let lo = L0;
        let hi = 1;
        for (let i = 0; i < ColorSearchEngine.SUGGESTION_SEARCH_ITERATIONS; i++) {
          const mid = (lo + hi) / 2;
          const midResult = tryCandidate({
            L: mid,
            C,
            h,
          });
          if (midResult) {
            hi = mid;
            lightBest = midResult;
          }
          else {
            lo = mid;
          }
        }
        if (!best || lightBest.deltaE < best.deltaE) {
          best = lightBest;
        }
      }
      return best;
    };
    /**
     * Linear scan on L ∈ [0,1]; handles non-monotone feasibility regions.
     *
     * @param {number} C - Chroma value.
     * @param {number} h - Hue angle in degrees.
     * @returns {{ rgb: RgbColor; deltaE: number } | undefined} Best candidate or undefined.
     */
    const searchLAxisLinear = (C, h) => {
      let best;
      for (let i = 0; i <= ColorSearchEngine.L_SCAN_STEPS; i++) {
        const L = i / ColorSearchEngine.L_SCAN_STEPS;
        const result = tryCandidate({ L, C, h });
        if (result && (!best || result.deltaE < best.deltaE)) {
          best = result;
        }
      }
      return best;
    };
    const searchLAxis = assumeMonotone ? searchLAxisMonotone : searchLAxisLinear;
    let bestRgb;
    let bestDeltaE = Infinity;
    /**
     * Update best candidate if the new one has a smaller perceptual distance.
     *
     * @param {{ rgb: RgbColor; deltaE: number } | undefined} candidate - Candidate to consider.
     */
    const consider = (candidate) => {
      if (candidate && candidate.deltaE < bestDeltaE) {
        bestDeltaE = candidate.deltaE;
        bestRgb = candidate.rgb;
      }
    };
    if (C0 < ColorSearchEngine.ACHROMATIC_CHROMA_EPSILON) {
      consider(searchLAxis(0, 0));
      return bestRgb;
    }
    for (const hueOffset of ColorSearchEngine.SUGGESTION_HUE_OFFSETS_DEG) {
      const h = H0 + hueOffset;
      for (const cScale of ColorSearchEngine.SUGGESTION_CHROMA_SCALES) {
        const C = Math.max(0, C0 * cScale);
        consider(searchLAxis(C, h));
      }
    }
    return bestRgb;
  }
  /**
   * Binary-search for the minimum alpha that makes composite contrast ≥ threshold.
   * Result is ceil-rounded to 2 decimals. Returns `undefined` if α=1.0 still fails.
   *
   * @param {RgbColor} rgb - Token's base RGB color.
   * @param {number} currentAlpha - Current alpha (lower bound for search).
   * @param {TokenCheckConfig} checkConfig - Token check configuration (pairing, type, compositedOn).
   * @param {number} threshold - Required WCAG contrast ratio.
   * @param {Map<string, string>} tokenMap - Resolved token map for surface lookups.
   * @returns {number | undefined} Minimum alpha (ceil-rounded), or `undefined` if infeasible.
   */
  findMinimumAlphaForContrast(rgb, currentAlpha, checkConfig, threshold, tokenMap) {
    const paired = resolvePairedSurface(checkConfig.pairedWith, tokenMap, this.fixedSurfaces);
    if (!paired) {
      return undefined;
    }
    const lightBgPrimary = this.lightBgPrimary;
    const fixedSurfaces = this.fixedSurfaces;
    /**
     * Compute contrast at a given alpha for binary search.
     *
     * @param {number} alpha - Alpha value to test.
     * @returns {number} WCAG contrast ratio at the given alpha.
     */
    const contrastAt = (alpha) => {
      if (checkConfig.type === 'bg-text') {
        const bg = alpha < 1 ? alphaComposite(rgb, alpha, lightBgPrimary) : rgb;
        return getContrastRatio(effectiveFgRgb(paired, bg), bg);
      }
      // Composite paired surface onto bg-primary if semi-transparent.
      const pairedBg = paired.a < 1 ? alphaComposite(paired.rgb, paired.a, lightBgPrimary) : paired.rgb;
      // compositedOn: fg composites onto a different surface, contrast still vs pairedBg.
      let compositeSurface = pairedBg;
      if (checkConfig.compositedOn) {
        const overrideSurface = resolvePairedSurface(checkConfig.compositedOn, tokenMap, fixedSurfaces);
        if (overrideSurface) {
          compositeSurface =
            overrideSurface.a < 1
              ? alphaComposite(overrideSurface.rgb, overrideSurface.a, lightBgPrimary)
              : overrideSurface.rgb;
        }
      }
      const fg = alpha < 1 ? alphaComposite(rgb, alpha, compositeSurface) : rgb;
      return getContrastRatio(fg, pairedBg);
    };
    if (contrastAt(1) < threshold) {
      return undefined;
    }
    let lo = currentAlpha;
    let hi = 1;
    for (let i = 0; i < ColorSearchEngine.ALPHA_ASCENT_ITERATIONS; i++) {
      const mid = (lo + hi) / 2;
      if (contrastAt(mid) >= threshold) {
        hi = mid;
      }
      else {
        lo = mid;
      }
    }
    return Math.min(1, Math.ceil(hi * 100) / 100);
  }
  /**
   * Find the closest opaque RGB (by OKLab ΔE) satisfying WCAG threshold against all configs.
   *
   * @param {RgbColor} originalRgb - Starting RGB color.
   * @param {TokenCheckConfig[]} configs - Token check configs (one per paired surface).
   * @param {Map<string, string>} tokenMap - Resolved token map for surface lookups.
   * @returns {RgbColor | undefined} Closest feasible RGB, or `undefined` if none found.
   */
  findRgbPatchForContrastAll(originalRgb, configs, tokenMap) {
    // Pre-resolve surfaces for the feasibility predicate.
    const surfaces = [];
    for (const config of configs) {
      const paired = resolvePairedSurface(config.pairedWith, tokenMap, this.fixedSurfaces);
      if (!paired)
        continue;
      const pairedBg = paired.a < 1 ? alphaComposite(paired.rgb, paired.a, this.lightBgPrimary) : paired.rgb;
      surfaces.push({ bg: pairedBg, threshold: requiredRatioFor(config.type), type: config.type, paired });
    }
    if (surfaces.length === 0) {
      return undefined;
    }
    /**
     * Check if a candidate RGB satisfies all surface constraints.
     *
     * @param {RgbColor} rgb - Candidate RGB color.
     * @returns {boolean} True if all constraints are satisfied.
     */
    const isFeasible = (rgb) => surfaces.every((s) => s.type === 'bg-text'
      ? getContrastRatio(effectiveFgRgb(s.paired, rgb), rgb) >= s.threshold
      : getContrastRatio(rgb, s.bg) >= s.threshold);
    return this.searchClosestFeasibleInOklch(originalRgb, isFeasible, false);
  }
}
/** Binary-search iterations on L axis; 10 → ~0.001 precision. */
ColorSearchEngine.SUGGESTION_SEARCH_ITERATIONS = 10;
/** Hue offsets (degrees) sampled during search; ±5° preserves brand hue family. */
ColorSearchEngine.SUGGESTION_HUE_OFFSETS_DEG = [-5, -2.5, 0, 2.5, 5];
/** Chroma multipliers sampled during search; allows modest saturation drift. */
ColorSearchEngine.SUGGESTION_CHROMA_SCALES = [0.6, 0.8, 1.0, 1.2, 1.4];
/** Chroma below which the origin is treated as achromatic (hue/chroma grid skipped). */
ColorSearchEngine.ACHROMATIC_CHROMA_EPSILON = 0.001;
/** Uniform L-axis steps for linear scan; 100 → 0.01 precision, enough for narrow bands. */
ColorSearchEngine.L_SCAN_STEPS = 100;
/** 2⁻⁷ ≈ 0.008 precision; matches 2-decimal output rounding. */
ColorSearchEngine.ALPHA_ASCENT_ITERATIONS = 7;

// ╔════════════════════════════════════════════════════════════════════════════╗
// ║ AUTO-GENERATED FILE — DO NOT EDIT BY HAND.                                ║
// ║ Source: vega-color-token-contrast-checker/src/generator/                  ║
// ║ Run `yarn prepare:a11y-checks` to regenerate.                              ║
// ╚════════════════════════════════════════════════════════════════════════════╝
const ACTION_COLOR_CHECKS = [
  // consumed by: vega-progress-tracker, vega-time-picker
  {
    tokenName: 'bg-action',
    pairedWith: 'text-inverted-primary',
    type: 'bg-text',
  },
  // consumed by: vega-button, vega-button-circle, vega-calendar, vega-carousel, vega-checkbox, vega-counter-badge, vega-date-picker, vega-progress-tracker, vega-radio, vega-toggle-switch
  {
    tokenName: 'bg-action',
    pairedWith: 'text-on-action',
    type: 'bg-text',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'bg-action-active',
    pairedWith: 'text-inverted-primary',
    type: 'bg-text',
  },
  // consumed by: vega-button, vega-button-circle, vega-calendar, vega-checkbox, vega-date-picker, vega-radio
  {
    tokenName: 'bg-action-active',
    pairedWith: 'text-on-action',
    type: 'bg-text',
  },
  // consumed by: vega-date-picker
  {
    tokenName: 'bg-action-focus',
    pairedWith: 'text-on-action',
    type: 'bg-text',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'bg-action-hover',
    pairedWith: 'text-inverted-primary',
    type: 'bg-text',
  },
  // consumed by: vega-button, vega-button-circle, vega-calendar, vega-checkbox, vega-date-picker, vega-radio, vega-toggle-switch
  {
    tokenName: 'bg-action-hover',
    pairedWith: 'text-on-action',
    type: 'bg-text',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'bg-action-secondary-active',
    pairedWith: 'text-link-active',
    type: 'bg-text',
  },
  // consumed by: vega-calendar
  {
    tokenName: 'bg-action-secondary-active',
    pairedWith: 'text-primary',
    type: 'bg-text',
  },
  // consumed by: vega-calendar
  {
    tokenName: 'bg-action-secondary-active',
    pairedWith: 'text-secondary',
    type: 'bg-text',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'bg-action-secondary-hover',
    pairedWith: 'text-link-hover',
    type: 'bg-text',
  },
  // consumed by: vega-calendar
  {
    tokenName: 'bg-action-secondary-hover',
    pairedWith: 'text-primary',
    type: 'bg-text',
  },
  // consumed by: vega-calendar
  {
    tokenName: 'bg-action-secondary-hover',
    pairedWith: 'text-secondary',
    type: 'bg-text',
  },
  // consumed by: vega-date-picker
  {
    tokenName: 'bg-date-picker-range',
    pairedWith: 'text-primary',
    type: 'bg-text',
  },
  // consumed by: vega-nav
  {
    tokenName: 'bg-selected',
    pairedWith: 'text-primary',
    type: 'bg-text',
  },
  // consumed by: vega-table
  {
    tokenName: 'bg-table-selected-row',
    pairedWith: 'text-primary',
    type: 'bg-text',
  },
  // consumed by: vega-app-footer, vega-date-picker, vega-divider, vega-dropdown, vega-nav, vega-tab-group, vega-table, vega-time-picker
  {
    tokenName: 'bg-divider',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-action-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-action-tertiary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-app-footer, vega-app-header-button, vega-button, vega-button-circle, vega-button-group, vega-button-link, vega-calendar, vega-carousel, vega-checkbox, vega-chip, vega-code-block, vega-color-picker, vega-color-swatch, vega-combo-box, vega-date-picker, vega-dropdown, vega-file-uploader, vega-image-uploader, vega-input, vega-input-range, vega-input-select, vega-nav, vega-pagination, vega-progress-tracker, vega-radio, vega-rich-text-content, vega-rich-text-editor, vega-segment-control, vega-selection-chip, vega-selection-tile, vega-signature-capture, vega-tab-group, vega-textarea, vega-time-picker, vega-toggle-switch
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-app-header-button, vega-carousel, vega-rich-text-editor
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-file-uploader, vega-image-uploader
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-nav
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-selected',
    type: 'non-text-contrast',
  },
  // consumed by: vega-app-header-button, vega-image-uploader, vega-nav
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-tertiary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip, vega-selection-tile
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-tile',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-tile
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-tile-selected',
    type: 'non-text-contrast',
  },
  // consumed by: vega-toggle-switch
  {
    tokenName: 'border-color-action',
    pairedWith: 'bg-toggle-switch',
    type: 'non-text-contrast',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'border-color-action-active',
    pairedWith: 'bg-action-tertiary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-checkbox, vega-file-uploader, vega-progress-tracker, vega-radio
  {
    tokenName: 'border-color-action-active',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-file-uploader
  {
    tokenName: 'border-color-action-active',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'border-color-action-hover',
    pairedWith: 'bg-action-tertiary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'border-color-action-hover',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-calendar, vega-date-picker, vega-table, vega-time-picker
  {
    tokenName: 'border-color-divider',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-divider',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-divider',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-divider',
    pairedWith: 'bg-table-header',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-divider',
    pairedWith: 'bg-table-selected-row',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button-group
  {
    tokenName: 'border-color-divider-secondary',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-image-uploader, vega-input-range, vega-table
  {
    tokenName: 'border-color-input-field',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-input-field',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-input-field',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-table
  {
    tokenName: 'border-color-input-field',
    pairedWith: 'bg-table-selected-row',
    type: 'non-text-contrast',
  },
  // consumed by: vega-image-uploader
  {
    tokenName: 'border-color-input-field',
    pairedWith: 'bg-tertiary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-app-header-button, vega-color-picker, vega-date-picker, vega-image-uploader, vega-input, vega-input-range, vega-input-select, vega-rich-text-content, vega-rich-text-editor, vega-signature-capture, vega-textarea, vega-time-picker
  {
    tokenName: 'border-color-input-field-focus',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-app-header-button
  {
    tokenName: 'border-color-input-field-focus',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-image-uploader
  {
    tokenName: 'border-color-input-field-focus',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-image-uploader
  {
    tokenName: 'border-color-input-field-focus',
    pairedWith: 'bg-tertiary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-file-uploader, vega-image-uploader
  {
    tokenName: 'border-color-input-field-hover',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-file-uploader, vega-image-uploader
  {
    tokenName: 'border-color-input-field-hover',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-rich-text-editor
  {
    tokenName: 'border-color-tile-selected',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-calendar
  {
    tokenName: 'border-divider',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-calendar, vega-card, vega-code-block
  {
    tokenName: 'border-divider-secondary',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-code-block
  {
    tokenName: 'border-divider-secondary',
    pairedWith: 'bg-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-checkbox, vega-color-picker, vega-combo-box, vega-date-picker, vega-dropdown, vega-input, vega-input-range, vega-input-select, vega-popover, vega-radio, vega-rich-text-editor, vega-selection-tile, vega-signature-capture, vega-textarea, vega-time-picker
  {
    tokenName: 'border-input-field',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-tile
  {
    tokenName: 'border-input-field',
    pairedWith: 'bg-tile',
    type: 'non-text-contrast',
  },
  // consumed by: vega-combo-box, vega-rich-text-editor, vega-selection-tile, vega-signature-capture, vega-textarea
  {
    tokenName: 'border-input-field-focus',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-rich-text-editor
  {
    tokenName: 'border-input-field-focus',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-tile
  {
    tokenName: 'border-input-field-focus',
    pairedWith: 'bg-tile',
    type: 'non-text-contrast',
  },
  // consumed by: vega-date-picker
  {
    tokenName: 'border-input-field-hover',
    pairedWith: 'bg-date-picker-range',
    type: 'non-text-contrast',
  },
  // consumed by: vega-checkbox, vega-color-swatch, vega-combo-box, vega-date-picker, vega-input, vega-input-range, vega-input-select, vega-radio, vega-rich-text-editor, vega-selection-tile, vega-signature-capture, vega-textarea, vega-time-picker
  {
    tokenName: 'border-input-field-hover',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-rich-text-editor
  {
    tokenName: 'border-input-field-hover',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-tile
  {
    tokenName: 'border-input-field-hover',
    pairedWith: 'bg-tile-hover',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'border-secondary-btn',
    pairedWith: 'bg-action-secondary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-date-picker
  {
    tokenName: 'border-secondary-btn',
    pairedWith: 'bg-date-picker-range',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link, vega-carousel, vega-date-picker, vega-dropdown
  {
    tokenName: 'border-secondary-btn',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-carousel, vega-dropdown
  {
    tokenName: 'border-secondary-btn',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'border-secondary-btn-active',
    pairedWith: 'bg-action-secondary-active',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'border-secondary-btn-active',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'border-secondary-btn-hover',
    pairedWith: 'bg-action-secondary-hover',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link, vega-carousel
  {
    tokenName: 'border-secondary-btn-hover',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-carousel
  {
    tokenName: 'border-secondary-btn-hover',
    pairedWith: 'bg-quaternary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip
  {
    tokenName: 'border-tile-focus',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip
  {
    tokenName: 'border-tile-focus',
    pairedWith: 'bg-tile',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip, vega-selection-tile
  {
    tokenName: 'border-tile-selected',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip, vega-selection-tile
  {
    tokenName: 'border-tile-selected',
    pairedWith: 'bg-tile-selected',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip
  {
    tokenName: 'border-tile-selected-focus',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip
  {
    tokenName: 'border-tile-selected-focus',
    pairedWith: 'bg-tile-selected',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip, vega-selection-tile
  {
    tokenName: 'border-tile-selected-hover',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-selection-chip, vega-selection-tile
  {
    tokenName: 'border-tile-selected-hover',
    pairedWith: 'bg-tile-selected-hover',
    type: 'non-text-contrast',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'ring-input-focus',
    pairedWith: 'bg-action',
    type: 'non-text-contrast',
    compositedOn: 'bg-primary',
  },
  // consumed by: vega-color-picker, vega-progress-tracker
  {
    tokenName: 'ring-input-focus',
    pairedWith: 'bg-primary',
    type: 'non-text-contrast',
  },
  // consumed by: vega-button, vega-button-circle, vega-button-link
  {
    tokenName: 'text-link',
    pairedWith: 'bg-action-secondary',
    type: 'text-bg',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'text-link',
    pairedWith: 'bg-action-tertiary',
    type: 'text-bg',
  },
  // consumed by: vega-app-footer, vega-breadcrumb, vega-button-group, vega-button-link, vega-calendar, vega-date-picker, vega-dropdown, vega-input-select, vega-item-toggle, vega-pagination, vega-rich-text-content, vega-tab-group
  {
    tokenName: 'text-link',
    pairedWith: 'bg-primary',
    type: 'text-bg',
  },
  // consumed by: vega-app-header-button, vega-dropdown
  {
    tokenName: 'text-link',
    pairedWith: 'bg-quaternary',
    type: 'text-bg',
  },
  // consumed by: vega-image-uploader
  {
    tokenName: 'text-link',
    pairedWith: 'bg-secondary',
    type: 'text-bg',
  },
  // consumed by: vega-image-uploader
  {
    tokenName: 'text-link',
    pairedWith: 'bg-tertiary',
    type: 'text-bg',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'text-link-active',
    pairedWith: 'bg-action-tertiary',
    type: 'text-bg',
  },
  // consumed by: vega-app-footer, vega-button-link, vega-rich-text-content
  {
    tokenName: 'text-link-active',
    pairedWith: 'bg-primary',
    type: 'text-bg',
  },
  // consumed by: vega-app-header-button
  {
    tokenName: 'text-link-active',
    pairedWith: 'bg-quaternary',
    type: 'text-bg',
  },
  // consumed by: vega-file-uploader
  {
    tokenName: 'text-link-active',
    pairedWith: 'bg-secondary',
    type: 'text-bg',
  },
  // consumed by: vega-app-footer, vega-button-link, vega-rich-text-content
  {
    tokenName: 'text-link-focus',
    pairedWith: 'bg-primary',
    type: 'text-bg',
  },
  // consumed by: vega-progress-tracker
  {
    tokenName: 'text-link-hover',
    pairedWith: 'bg-action-tertiary',
    type: 'text-bg',
  },
  // consumed by: vega-app-footer, vega-button-link, vega-dropdown, vega-item-toggle, vega-rich-text-content
  {
    tokenName: 'text-link-hover',
    pairedWith: 'bg-primary',
    type: 'text-bg',
  },
  // consumed by: vega-app-header-button, vega-dropdown
  {
    tokenName: 'text-link-hover',
    pairedWith: 'bg-quaternary',
    type: 'text-bg',
  },
  // consumed by: vega-file-uploader, vega-image-uploader
  {
    tokenName: 'text-link-hover',
    pairedWith: 'bg-secondary',
    type: 'text-bg',
  },
  // consumed by: vega-button-link
  {
    tokenName: 'text-link-visited',
    pairedWith: 'bg-action-secondary',
    type: 'text-bg',
  },
  // consumed by: vega-app-footer, vega-button-link, vega-chip, vega-rich-text-content
  {
    tokenName: 'text-link-visited',
    pairedWith: 'bg-primary',
    type: 'text-bg',
  },
];

/**
 * WCAG 2.1 AA contrast checker for `actionColor`-driven theme overrides. Combines
 * the static token-pair catalogue (generated at build time) with consumer-supplied
 * design-token values and an override-themes generator, and exposes a single
 * pre-bound instance ready for `validate(input)` / `computeContrastCapPatches(input)`.
 *
 * Stateless against per-call inputs; holds a process-lifetime fixability cache keyed
 * by `(tokenName, pairedWith, compositedOn)` — that property depends only on the
 * catalogue and the override formulas, not on the user's input, so it can be probed
 * once.
 *
 * @template TInput Shape of the override-color input. In vega-stencil this is
 *   `VegaThemeOverrideColors`; tests can use a stricter or looser shape.
 */
class VegaColorTokenContrastChecker {
  constructor(deps) {
    this.fixabilityCache = null;
    this.deps = deps;
    this.fixedSurfaces = buildFixedPairedSurfaces(deps.fixedSurfaceTokens);
    this.search = new ColorSearchEngine(this.fixedSurfaces);
  }
  // ────────────────────────────────────────────────────────────────────────
  // Public API
  // ────────────────────────────────────────────────────────────────────────
  /**
   * Build the resolved token map for an input by collapsing
   * {@link CheckerDeps.generateOverrideThemes} into a `Map<tokenName, value>`,
   * dropping entries with empty values.
   *
   * Public so tests can assert on the materialized map and so the wrapper can
   * re-export it as a convenience helper.
   *
   * @param {TInput} input - Override-color payload.
   * @returns {Map<string, string>} Token name → CSS `r g b / a` value.
   */
  buildTokenMap(input) {
    const themes = this.deps.generateOverrideThemes(input);
    const map = new Map();
    for (const entry of themes) {
      if (entry.value) {
        map.set(entry.name, entry.value);
      }
    }
    return map;
  }
  /**
   * Validate overrides against WCAG 2.1 AA (light mode).
   *
   * @param {TInput} input - Override-color payload to validate.
   * @returns {ContrastCheckResult} Accessibility result, including failing details and optional patches.
   */
  validate(input) {
    if (!VegaColorTokenContrastChecker.isValidActionColor(input)) {
      return { isAccessible: false, details: [], invalidInput: true, contrastCapPatches: {} };
    }
    const tokenMap = this.buildTokenMap(input);
    const details = this.collectFailures(tokenMap);
    const contrastCapPatches = this.computeContrastCapPatches(input);
    const result = {
      isAccessible: details.length === 0,
      details,
      contrastCapPatches,
    };
    const actionColor = input.actionColor;
    if (actionColor) {
      result.suggestedActionColor = result.isAccessible
        ? actionColor
        : this.findSuggestedActionColor(input, actionColor);
    }
    return result;
  }
  /**
   * Produce contrast-cap patches for tokens whose formula caps contrast below threshold.
   *
   * Two-tier strategy (minimal impact first):
   *   1. α-ascent: raise alpha to meet contrast (preserves brand hue).
   *   2. RGB shift: closest opaque color satisfying ALL surfaces (linear scan).
   *
   * @param {TInput} input - Override-color payload (must include `actionColor`).
   * @returns {Record<string, string>} Map of tokenName → patched CSS value; empty if no repair needed.
   */
  computeContrastCapPatches(input) {
    const patch = {};
    if (!input.actionColor || !VegaColorTokenContrastChecker.isValidActionColor(input)) {
      return patch;
    }
    const tokenMap = this.buildTokenMap(input);
    // Group all checks by token; identify tokens needing patch.
    const allChecksByToken = new Map();
    const tokensNeedingPatch = new Set();
    for (const checkConfig of ACTION_COLOR_CHECKS) {
      const allList = allChecksByToken.get(checkConfig.tokenName);
      if (allList) {
        allList.push(checkConfig);
      }
      else {
        allChecksByToken.set(checkConfig.tokenName, [checkConfig]);
      }
      if (!this.getFixabilityRecord(checkConfig).fixable) {
        tokensNeedingPatch.add(checkConfig.tokenName);
      }
    }
    for (const tokenName of tokensNeedingPatch) {
      const tokenValue = tokenMap.get(tokenName);
      if (!tokenValue) {
        continue;
      }
      const parsed = parseTokenValue(tokenValue);
      if (!parsed) {
        continue;
      }
      // All checks as constraints (not just unfixable) to avoid regressing passing checks.
      // Safe: tokensNeedingPatch is derived from the same ACTION_COLOR_CHECKS loop that populates allChecksByToken.
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const allConfigs = allChecksByToken.get(tokenName);
      // Tier 1: α-ascent — find minimum α satisfying all checks simultaneously.
      let strictestAlpha;
      let tierOneFeasible = true;
      for (const checkConfig of allConfigs) {
        const threshold = requiredRatioFor(checkConfig.type);
        const repairedAlpha = this.search.findMinimumAlphaForContrast(parsed.rgb, parsed.a, checkConfig, threshold, tokenMap);
        if (repairedAlpha === undefined) {
          tierOneFeasible = false;
          break;
        }
        if (strictestAlpha === undefined || repairedAlpha > strictestAlpha) {
          strictestAlpha = repairedAlpha;
        }
      }
      if (tierOneFeasible && strictestAlpha !== undefined) {
        // Verify the max α still satisfies all checks jointly.
        const candidateValue = formatTokenValue(parsed.rgb, strictestAlpha);
        if (this.verifyCandidateSatisfiesAll(tokenName, candidateValue, allConfigs, tokenMap)) {
          patch[tokenName] = candidateValue;
          continue;
        }
      }
      // Tier 2: RGB shift at α=1, satisfying all surfaces (linear scan).
      const repairedRgb = this.search.findRgbPatchForContrastAll(parsed.rgb, allConfigs, tokenMap);
      if (repairedRgb !== undefined) {
        patch[tokenName] = formatTokenValue(repairedRgb, 1);
      }
    }
    return patch;
  }
  /**
   * Compute contrast for a single check using the supplied token map.
   *
   * @param {TokenCheckConfig} check - Check configuration.
   * @param {Map<string, string>} tokenMap - Resolved token map.
   * @returns {ContrastResult | null} Contrast result or null when token/surface can't be resolved.
   */
  computeTokenContrast(check, tokenMap) {
    return computeTokenContrast(check, tokenMap, this.fixedSurfaces);
  }
  /**
   * Resolve a `pairedWith` name to a {@link ContextSurface} using this checker's
   * static surface table.
   *
   * @param {string} pairedWith - Token name to resolve.
   * @param {Map<string, string>} tokenMap - Resolved token map (dynamic lookup wins).
   * @returns {ContextSurface | undefined} Resolved surface, or undefined.
   */
  resolvePairedSurface(pairedWith, tokenMap) {
    return resolvePairedSurface(pairedWith, tokenMap, this.fixedSurfaces);
  }
  /**
   * Get the per-check fixability record (cached over the process lifetime).
   *
   * @param {TokenCheckConfig} config - Check configuration.
   * @returns {FixabilityRecord} `{ fixable, maxContrast }`.
   */
  getFixabilityRecord(config) {
    var _a;
    if (!this.fixabilityCache) {
      this.fixabilityCache = this.buildFixabilityCache();
    }
    return ((_a = this.fixabilityCache.get(VegaColorTokenContrastChecker.fixabilityCacheKey(config))) !== null && _a !== void 0 ? _a : {
      fixable: true,
      maxContrast: Infinity,
    });
  }
  /**
   * Collect failing fixable tokens under the supplied token map.
   *
   * @param {Map<string, string>} tokenMap - Resolved token map.
   * @param {boolean} earlyExit - If true, return after first failure.
   * @returns {ColorContrastDetail[]} Array of failing token detail entries.
   */
  collectFailures(tokenMap, earlyExit = false) {
    const failures = [];
    for (const checkConfig of ACTION_COLOR_CHECKS) {
      if (!this.getFixabilityRecord(checkConfig).fixable) {
        continue;
      }
      const computed = this.computeTokenContrast(checkConfig, tokenMap);
      if (!computed) {
        continue;
      }
      const threshold = requiredRatioFor(checkConfig.type);
      if (computed.contrastRatio >= threshold) {
        continue;
      }
      failures.push({
        tokenName: checkConfig.tokenName,
        contrastRatio: computed.contrastRatio,
        requiredRatio: threshold,
      });
      if (earlyExit) {
        return failures;
      }
    }
    return failures;
  }
  /**
   * Fast check: all fixable tokens pass contrast under the supplied token map.
   *
   * @param {Map<string, string>} tokenMap - Resolved token map.
   * @returns {boolean} True if all fixable tokens pass.
   */
  isAccessible(tokenMap) {
    return this.collectFailures(tokenMap, true).length === 0;
  }
  // ────────────────────────────────────────────────────────────────────────
  // Private helpers
  // ────────────────────────────────────────────────────────────────────────
  /** Empty `actionColor` is allowed (means "no override"); when present it must be a valid hex. */
  static isValidActionColor(input) {
    return !input.actionColor || isValidHexColor(input.actionColor);
  }
  /** Produce a new input with a different `actionColor`, preserving every other field. */
  withActionColor(input, actionColor) {
    return Object.assign(Object.assign({}, input), { actionColor });
  }
  static fixabilityCacheKey(config) {
    var _a;
    return `${config.tokenName}|${config.pairedWith}|${(_a = config.compositedOn) !== null && _a !== void 0 ? _a : ''}`;
  }
  buildFixabilityCache() {
    const cache = new Map();
    // Probe maps depend only on `actionColor`; synthesize a minimal TInput for each
    // probe via the `withActionColor` helper applied to an empty object. TInput must
    // extend ActionColorInput, so the only required structural shape is `actionColor`.
    const baseInput = {};
    const probeMaps = VegaColorTokenContrastChecker.FIXABILITY_PROBE_COLORS.map((probe) => this.buildTokenMap(this.withActionColor(baseInput, probe)));
    for (const check of ACTION_COLOR_CHECKS) {
      let maxContrast = 0;
      for (const tokenMap of probeMaps) {
        const computed = this.computeTokenContrast(check, tokenMap);
        if (computed && computed.contrastRatio > maxContrast) {
          maxContrast = computed.contrastRatio;
        }
      }
      const required = requiredRatioFor(check.type);
      cache.set(VegaColorTokenContrastChecker.fixabilityCacheKey(check), {
        fixable: maxContrast >= required,
        maxContrast: Math.round(maxContrast * 100) / 100,
      });
    }
    return cache;
  }
  /**
   * Find the closest accessible actionColor (by OKLab ΔE) within ±5° hue tolerance.
   *
   * @param {TInput} input - Current override input.
   * @param {string} actionColor - Current action color hex (already extracted by validate()).
   * @returns {string | undefined} Suggested hex, or undefined if none feasible.
   */
  findSuggestedActionColor(input, actionColor) {
    const suggestedRgb = this.search.searchClosestFeasibleInOklch(hexToRgb(actionColor), (rgb) => this.isAccessible(this.buildTokenMap(this.withActionColor(input, rgbToHex(rgb)))));
    return suggestedRgb === undefined ? undefined : rgbToHex(suggestedRgb);
  }
  /** Verify a candidate value passes all checks for a token. */
  verifyCandidateSatisfiesAll(tokenName, candidateValue, allConfigs, tokenMap) {
    const patchedMap = new Map(tokenMap);
    patchedMap.set(tokenName, candidateValue);
    for (const config of allConfigs) {
      const threshold = requiredRatioFor(config.type);
      // Route through `this.computeTokenContrast` (not the free function) so test
      // suites can intercept the verification phase via a spy on the public method.
      const result = this.computeTokenContrast(config, patchedMap);
      if (!result || result.contrastRatio < threshold) {
        return false;
      }
    }
    return true;
  }
}
VegaColorTokenContrastChecker.FIXABILITY_PROBE_COLORS = ['#000000', '#808080', '#ffffff'];

// Thin wrapper around `vega-color-token-contrast-checker/core`. The
/**
 * Process-lifetime singleton: a {@link VegaColorTokenContrastChecker} pre-bound
 * to vega-stencil's design-token table and override-themes generator. Consumers
 * call `.validate(overrideColors)` and `.computeContrastCapPatches(overrideColors)`
 * directly — no per-call configuration needed.
 */
const vegaAccessibilityValidator = new VegaColorTokenContrastChecker({
  fixedSurfaceTokens: LightModeColorTokens,
  /**
   * Generate override theme arrays from the supplied override colors.
   *
   * @param {VegaThemeOverrideColors} overrideColors The override colors input.
   * @returns {ReturnType<typeof VegaColorSchemaOverridingController.generateOverrideThemes>} The override theme arrays.
   */
  generateOverrideThemes: (overrideColors) => VegaColorSchemaOverridingController.generateOverrideThemes(overrideColors),
});

export { vegaAccessibilityValidator };
