/**
 * @deprecated Hover colors are no longer precomputed. Background hover uses
 * `hover-layer hover-layer-{subtle|default|strong}` pseudo-element utilities.
 * This module is kept only for compatibility with older tooling.
 *
 * Two-step compositing (both in sRGB, channels normalized to 0–1):
 *
 *   1) Flatten a semi-transparent base onto the parent surface `P` with plain
 *      source-over, so the hover blend sees the same pixel the browser would:
 *        Cb' = αb·Cb + (1 − αb)·Cp
 *
 *   2) Apply CSS `mix-blend-mode: overlay` of the hover token `Cs/αs` over the
 *      now-opaque `Cb'` and source-over composite the result:
 *        blend(Cb', Cs) = Cb' ≤ 0.5 ?  2·Cb'·Cs  :  1 − 2·(1−Cb')·(1−Cs)
 *        Co            = αs·blend(Cb', Cs) + (1 − αs)·Cb'
 *
 * The parent surface is only consulted when the base has alpha < 1; otherwise
 * step 1 is a no-op. Inputs are resolved token hex strings; outputs are
 * opaque hex strings suitable for unocss `theme.colors`.
 */

/**
 * Ordered list of hover overlay tiers used throughout the library.
 */
export const HOVER_VARIANTS = Object.freeze(['subtle', 'default', 'strong']);

/**
 * @deprecated Prefixes (under the dot-path token tree) previously expanded into
 * precomputed hover colors.
 */
export const HOVER_MIX_TOKEN_PREFIXES = Object.freeze([
  'background.',
  'content.',
]);

/**
 * @deprecated Use {@link HOVER_MIX_TOKEN_PREFIXES}. Kept as the first prefix
 * for backwards-compatible consumers.
 */
export const HOVER_MIX_TOKEN_PREFIX = HOVER_MIX_TOKEN_PREFIXES[0];

/** @deprecated Suffix previously used in generated CSS variable names. */
export const HOVER_MIX_CLASS_SUFFIX = 'hover';

/** Clamp a number to the 0–255 integer range. */
const clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));

/** Format a 0–255 number as a two-digit lowercase hex byte. */
const toHex2 = (n) => clamp255(n).toString(16).padStart(2, '0');

/** Parse a hex color string (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`) to `{r,g,b,a}`. */
export const parseHexColorToRgba = (hex) => {
  if (typeof hex !== 'string') return null;
  const s = hex.trim();
  if (!s.startsWith('#')) return null;
  const h = s.slice(1);
  if (![3, 4, 6, 8].includes(h.length) || !/^[0-9a-fA-F]+$/.test(h))
    return null;
  if (h.length === 3 || h.length === 4) {
    return {
      r: parseInt(h[0] + h[0], 16),
      g: parseInt(h[1] + h[1], 16),
      b: parseInt(h[2] + h[2], 16),
      a: h.length === 4 ? parseInt(h[3] + h[3], 16) / 255 : 1,
    };
  }
  return {
    r: parseInt(h.slice(0, 2), 16),
    g: parseInt(h.slice(2, 4), 16),
    b: parseInt(h.slice(4, 6), 16),
    a: h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1,
  };
};

/** Serialize `{r,g,b,a}` to a hex string. Drops alpha when fully opaque. */
export const rgbaToHex = ({ r, g, b, a = 1 }) => {
  const base = `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`;
  if (a >= 1) return base;
  return `${base}${toHex2(a * 255)}`;
};

/**
 * CSS `mix-blend-mode: overlay` for one sRGB channel (inputs in 0–1).
 * Mirrors the non-separable blend: `overlay(Cb, Cs)` equals `hardLight(Cs, Cb)`
 * per the W3C Compositing spec.
 */
const overlayBlendChannel = (cb, cs) =>
  cb <= 0.5 ? 2 * cb * cs : 1 - 2 * (1 - cb) * (1 - cs);

/** Convert a 0–255 sRGB channel to the 0–1 unit range used by blend math. */
const toUnit = (v) => Math.max(0, Math.min(1, v / 255));

/**
 * Flatten a (possibly) semi-transparent color onto an opaque parent via the
 * standard source-over formula (`Co = αs·Cs + (1 − αs)·Cp`). Returns the
 * opaque on-screen color; passes `top` through unchanged when it is already
 * fully opaque.
 *
 * @param {{r: number, g: number, b: number, a: number}} top - Semi-transparent source (0–255 channels; 0–1 alpha).
 * @param {{r: number, g: number, b: number, a: number}} parent - Opaque parent surface (alpha ignored).
 * @returns {{r: number, g: number, b: number, a: 1}} Opaque result color.
 */
export const flattenAlpha = (top, parent) => {
  if (top.a >= 1) return { ...top, a: 1 };
  return {
    r: top.a * top.r + (1 - top.a) * parent.r,
    g: top.a * top.g + (1 - top.a) * parent.g,
    b: top.a * top.b + (1 - top.a) * parent.b,
    a: 1,
  };
};

/**
 * Composite `top` over `bottom` using CSS `mix-blend-mode: overlay` followed
 * by the default source-over compositor. Matches the pixel produced by the
 * `v-hover` directive when its overlay span reaches `opacity: 1`.
 *
 * `bottom` MUST be opaque (alpha 1). Pre-flatten with `flattenAlpha` if it
 * isn't — that's what `mixHoverColor` does.
 *
 * @param {{r: number, g: number, b: number, a: 1}} bottom - Opaque backdrop (0–255 channels).
 * @param {{r: number, g: number, b: number, a: number}} top - Overlay color with 0–1 alpha.
 * @returns {{r: number, g: number, b: number, a: 1}} Composited opaque color.
 */
export const blendOverlay = (bottom, top) => {
  const ch = (base, src) => {
    const cb = toUnit(base);
    const cs = toUnit(src);
    const blended = overlayBlendChannel(cb, cs);
    // Opaque-backdrop simplification of source-over with a blend mode:
    //   Co = αs·B(Cb, Cs) + (1 − αs)·Cb
    return (top.a * blended + (1 - top.a) * cb) * 255;
  };

  return {
    r: ch(bottom.r, top.r),
    g: ch(bottom.g, top.g),
    b: ch(bottom.b, top.b),
    a: 1,
  };
};

/**
 * Produce one hover-mixed color as a hex string using the CSS `overlay` blend
 * mode — i.e. the exact color the `v-hover` directive paints when hovered.
 * Works for both `background.*` (surface tints) and `content.*` (text) base
 * tokens. Semi-transparent base tokens are first flattened onto
 * `parentSurfaceHex` (typically `background.surface.primary`) so the blend
 * sees the real rendered pixel.
 *
 * @param {string} baseHex - Base color from a `background.*` or `content.*` token (may have alpha).
 * @param {string} overlayHex - Semi-transparent `overlay.hover.*` color.
 * @param {string} [parentSurfaceHex] - Opaque surface the base sits on. Required
 *   when `baseHex` has alpha < 1; ignored when it is fully opaque.
 * @returns {string | null} Composited opaque color as hex, or `null` if any input is unparseable.
 */
export const mixHoverColor = (baseHex, overlayHex, parentSurfaceHex) => {
  const base = parseHexColorToRgba(baseHex);
  const ov = parseHexColorToRgba(overlayHex);
  if (!base || !ov) return null;

  let opaqueBase = base;
  if (base.a < 1) {
    const parent = parentSurfaceHex
      ? parseHexColorToRgba(parentSurfaceHex)
      : null;
    if (!parent) return null;
    opaqueBase = flattenAlpha(base, parent);
  }

  return rgbaToHex(blendOverlay(opaqueBase, ov));
};

/**
 * Build a map of CSS variable name → hex color for every combination of
 * base colors × hover variant.
 *
 * @param {object} args
 * @param {Record<string, string>} [args.baseColors] - Map of kebab var-name (no `--` prefix) to hex.
 *   e.g. `{ 'background-surface-primary': '#F5F2E9', 'content-on-surface-primary': '#1C1C1C' }`.
 * @param {Record<string, string>} [args.bgColors] - Deprecated alias of `baseColors`.
 * @param {Record<'subtle'|'default'|'strong', string>} args.overlays - Overlay hex per variant.
 * @param {string} [args.parentSurface] - Opaque surface (hex) that semi-transparent
 *   base tokens are rendered on top of. Typically `background.surface.primary`.
 * @returns {Record<string, string>} Map of `${baseVar}-${suffix}-${variant}` → hex.
 */
export const buildHoverMixVars = ({
  baseColors,
  bgColors,
  overlays,
  parentSurface,
}) => {
  const out = {};
  const source = baseColors ?? bgColors ?? {};
  for (const [varName, baseHex] of Object.entries(source)) {
    for (const variant of HOVER_VARIANTS) {
      const overlayHex = overlays?.[variant];
      if (!overlayHex) continue;
      const mixed = mixHoverColor(baseHex, overlayHex, parentSurface);
      if (!mixed) continue;
      out[`${varName}-${HOVER_MIX_CLASS_SUFFIX}-${variant}`] = mixed;
    }
  }
  return out;
};

/**
 * Compute the default UnoCSS `theme.colors` entries for every hover-mix var,
 * using the same `oklch(var(--…))` pattern as other token-backed colors.
 *
 * @param {Iterable<string>} varNames - Iterable of var names (without `--`).
 * @returns {Record<string, string>}
 */
export const buildHoverMixColors = (varNames) => {
  const out = {};
  for (const name of varNames) {
    out[name] = `oklch(var(--${name}))`;
  }
  return out;
};
