// Star Map palette — ported from apps/desktop/src/app/starmap/color.ts so the
// TUI overlay derives the same memory ink (complement of the theme primary) and
// the same age fade (rgba(ink, alpha) over the background) as the desktop panel.

interface Rgb {
  b: number
  g: number
  r: number
}

function hexToRgb(hex: string): Rgb {
  let s = hex.trim().replace(/^#/, '')

  if (s.length === 3) {
    s = s
      .split('')
      .map(c => c + c)
      .join('')
  }

  const n = parseInt(s, 16)

  if (Number.isNaN(n) || s.length < 6) {
    return { b: 0, g: 215, r: 255 }
  }

  return { b: n & 255, g: (n >> 8) & 255, r: (n >> 16) & 255 }
}

function rgbToHex(c: Rgb): string {
  const h = (v: number) =>
    Math.max(0, Math.min(255, Math.round(v)))
      .toString(16)
      .padStart(2, '0')

  return `#${h(c.r)}${h(c.g)}${h(c.b)}`
}

function mix(a: Rgb, b: Rgb, t: number): Rgb {
  const p = Math.max(0, Math.min(1, t))

  return { b: a.b + (b.b - a.b) * p, g: a.g + (b.g - a.g) * p, r: a.r + (b.r - a.r) * p }
}

function luminance(c: Rgb): number {
  return (0.2126 * c.r + 0.7152 * c.g + 0.114 * c.b) / 255
}

function rgbToHsl(c: Rgb): [number, number, number] {
  const r = c.r / 255
  const g = c.g / 255
  const b = c.b / 255
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const l = (max + min) / 2
  const d = max - min

  if (!d) {
    return [0, 0, l]
  }

  const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
  const h = (max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4) * 60

  return [h, s, l]
}

function hslToRgb(h: number, s: number, l: number): Rgb {
  const hue = ((h % 360) + 360) % 360
  const c = (1 - Math.abs(2 * l - 1)) * s
  const x = c * (1 - Math.abs(((hue / 60) % 2) - 1))
  const m = l - c / 2

  const [r, g, b] =
    hue < 60
      ? [c, x, 0]
      : hue < 120
        ? [x, c, 0]
        : hue < 180
          ? [0, c, x]
          : hue < 240
            ? [0, x, c]
            : hue < 300
              ? [x, 0, c]
              : [c, 0, x]

  return { b: (b + m) * 255, g: (g + m) * 255, r: (r + m) * 255 }
}

function complementaryInk(c: Rgb): Rgb {
  const [h, s, l] = rgbToHsl(c)

  return hslToRgb(h + 165, Math.max(s, 0.5), Math.max(0.5, Math.min(0.7, l)))
}

export interface StarmapPalette {
  bg: Rgb
  dim: Rgb
  label: Rgb
  memory: Rgb
  skill: Rgb
}

/** Derive the Star Map inks from the theme primary + foreground color. */
export function deriveStarmapPalette(primaryHex: string, fgHex: string): StarmapPalette {
  const primary = hexToRgb(primaryHex)
  const dark = luminance(hexToRgb(fgHex)) > 0.55
  const base: Rgb = dark ? { b: 255, g: 255, r: 255 } : { b: 0, g: 0, r: 0 }
  const bg: Rgb = dark ? { b: 12, g: 8, r: 8 } : { b: 250, g: 250, r: 250 }

  return {
    bg,
    dim: mix(base, bg, 0.7),
    label: mix(base, bg, 0.35),
    // Memories are drillable, so they wear the primary "clickable" ink; skills
    // are dead-ends and get the muted complement.
    memory: mix(primary, base, dark ? 0.12 : 0.18),
    skill: mix(complementaryInk(primary), bg, 0.45)
  }
}

/** Fade an explicit hex ink toward the background by alpha (for category bars). */
export function fadeHex(palette: StarmapPalette, hex: string, alpha: number): string {
  const base = hexToRgb(hex)

  return rgbToHex(alpha >= 0.999 ? base : mix(palette.bg, base, alpha))
}

/** Fade a base ink toward the background by alpha (rgba-over-bg), as a hex. */
export function fadeInk(palette: StarmapPalette, style: string, alpha: number): string | undefined {
  const base =
    style === 'skill'
      ? palette.skill
      : style === 'memory'
        ? palette.memory
        : style === 'label'
          ? palette.label
          : style === 'dim'
            ? palette.dim
            : null

  if (!base) {
    return undefined
  }

  return rgbToHex(alpha >= 0.999 ? base : mix(palette.bg, base, alpha))
}
