const HEX_PATTERN = /^([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/

export function hexToRgb(
  hex: string,
): {r: number; g: number; b: number} | null {
  try {
    if (typeof hex !== 'string') return null
    const h = hex.startsWith('#') ? hex.slice(1) : hex
    if (!HEX_PATTERN.test(h)) return null
    const rgb = h.length === 8 ? h.slice(0, 6) : h
    const expanded =
      rgb.length === 3
        ? rgb
            .split('')
            .map((c) => c + c)
            .join('')
        : rgb
    const num = parseInt(expanded, 16)
    return {r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255}
  } catch {
    return null
  }
}

export function rgbToHex(r: number, g: number, b: number): string {
  const c = (n: number) => clamp(Math.round(n), 0, 255)
  return `#${((1 << 24) + (c(r) << 16) + (c(g) << 8) + c(b)).toString(16).slice(1)}`
}

export function hslToHex(h: number, s: number, l: number): string {
  const {r, g, b} = hslToRgb(h, s, l)
  return rgbToHex(r, g, b)
}

function hslToRgb(
  h: number,
  s: number,
  l: number,
): {r: number; g: number; b: number} {
  h /= 360
  s /= 100
  l /= 100
  if (s === 0) {
    const v = l * 255
    return {r: v, g: v, b: v}
  }
  const q = l < 0.5 ? l * (1 + s) : l + s - l * s
  const p = 2 * l - q
  const f = (t: number) => {
    if (t < 0) t += 1
    if (t > 1) t -= 1
    if (t < 1 / 6) return p + (q - p) * 6 * t
    if (t < 1 / 2) return q
    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
    return p
  }
  return {r: f(h + 1 / 3) * 255, g: f(h) * 255, b: f(h - 1 / 3) * 255}
}

function clamp(n: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, n))
}
