import {utils} from '@bsky.app/alf'

export const BLUE_HUE = 211

/**
 * @deprecated use `utils.alpha` from `@bsky.app/alf` instead
 */
export const transparentifyColor = utils.alpha

/**
 * Lighten a hex color by `amount` percentage points of HSL lightness (0-100).
 * If `hex` is not a valid hex color, returns the input unchanged.
 */
export function lighten(hex: string, amount: number): string {
  return adjustLightness(hex, amount)
}

/**
 * Darken a hex color by `amount` percentage points of HSL lightness (0-100).
 * If `hex` is not a valid hex color, returns the input unchanged.
 */
export function darken(hex: string, amount: number): string {
  return adjustLightness(hex, -amount)
}

function adjustLightness(hex: string, delta: number): string {
  try {
    const rgb = hexToRgb(hex)
    if (!rgb) return hex
    const {h, s, l} = rgbToHsl(rgb.r, rgb.g, rgb.b)
    const next = clamp(l + delta, 0, 100)
    const out = hslToRgb(h, s, next)
    return rgbToHex(out.r, out.g, out.b)
  } catch {
    return hex
  }
}

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
    // 8-digit hex carries an alpha channel we don't use; drop it
    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)}`
}

/**
 * Computes the WCAG contrast ratio between two colors, ranging from 1 (no
 * contrast) to 21 (maximum contrast, i.e. black on white). Returns null if
 * either argument is not a valid hex color.
 *
 * @see https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
 */
export function contrastRatio(hexA: string, hexB: string): number | null {
  const rgbA = hexToRgb(hexA)
  const rgbB = hexToRgb(hexB)
  if (!rgbA || !rgbB) return null
  const luminanceA = relativeLuminance(rgbA)
  const luminanceB = relativeLuminance(rgbB)
  const lighter = Math.max(luminanceA, luminanceB)
  const darker = Math.min(luminanceA, luminanceB)
  return (lighter + 0.05) / (darker + 0.05)
}

/**
 * Computes the WCAG relative luminance of an RGB color, ranging from 0 (black)
 * to 1 (white).
 *
 * @see https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
 */
function relativeLuminance({
  r,
  g,
  b,
}: {
  r: number
  g: number
  b: number
}): number {
  const toLinear = (channel: number) => {
    const normalized = channel / 255
    return normalized <= 0.03928
      ? normalized / 12.92
      : ((normalized + 0.055) / 1.055) ** 2.4
  }
  return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b)
}

function rgbToHsl(
  r: number,
  g: number,
  b: number,
): {h: number; s: number; l: number} {
  r /= 255
  g /= 255
  b /= 255
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const l = (max + min) / 2
  let h = 0
  let s = 0
  if (max !== min) {
    const d = max - min
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
    switch (max) {
      case r:
        h = (g - b) / d + (g < b ? 6 : 0)
        break
      case g:
        h = (b - r) / d + 2
        break
      case b:
        h = (r - g) / d + 4
        break
    }
    h /= 6
  }
  return {h: h * 360, s: s * 100, l: l * 100}
}

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))
}
