function clamp01(t: number): number {
  if (!Number.isFinite(t)) return 0;
  return Math.min(1, Math.max(0, t));
}

type Rgb = { r: number; g: number; b: number };

function toByte(v: number): number {
  if (!Number.isFinite(v)) return 0;
  return Math.min(255, Math.max(0, Math.round(v)));
}

export function hexToRgb(hex: string): Rgb | null {
  const h = String(hex || '').trim();
  const m = /^#?([0-9a-fA-F]{6})$/.exec(h);
  if (!m) return null;
  const raw = m[1];
  const n = parseInt(raw, 16);
  // eslint-disable-next-line no-bitwise
  const r = (n >> 16) & 0xff;
  // eslint-disable-next-line no-bitwise
  const g = (n >> 8) & 0xff;
  // eslint-disable-next-line no-bitwise
  const b = n & 0xff;
  return { r, g, b };
}

export function rgbToHex(rgb: Rgb): string {
  const r = toByte(rgb.r).toString(16).padStart(2, '0');
  const g = toByte(rgb.g).toString(16).padStart(2, '0');
  const b = toByte(rgb.b).toString(16).padStart(2, '0');
  return `#${r}${g}${b}`.toUpperCase();
}

/**
 * 仅允许线性插值（Hard Rule）
 */
export function lerpRgb(a: Rgb, b: Rgb, t: number): Rgb {
  const tt = clamp01(t);
  return {
    r: a.r + (b.r - a.r) * tt,
    g: a.g + (b.g - a.g) * tt,
    b: a.b + (b.b - a.b) * tt,
  };
}

export function lerpHex(topHex: string, bottomHex: string, t: number, fallbackHex = '#E8F1EC'): string {
  const top = hexToRgb(topHex);
  const bottom = hexToRgb(bottomHex);
  if (!top || !bottom) return fallbackHex;
  return rgbToHex(lerpRgb(top, bottom, t));
}

