// Shared color parsing for both engines. Users pass colors the natural ways — a CSS-ish hex string
// ('#10131a', '#abc', '#rrggbbaa'), a packed number (0xff0000), or normalized components
// ([r,g,b] / [r,g,b,a] in 0..1). The engines want different forms (2D wants 0..1 floats, 3D wants a
// packed int), so this module is the single place that converts.

export type ColorInput =
  | string
  | number
  | readonly [number, number, number]
  | readonly [number, number, number, number]

const parseHexString = (input: string): [number, number, number, number] => {
  let s = input.trim()
  if (s[0] === "#") s = s.slice(1)
  // #rgb / #rgba shorthand → double each nibble
  if (s.length === 3 || s.length === 4) s = s.split("").map((c) => c + c).join("")
  const int = parseInt(s, 16)
  if (s.length === 8) return [ (int >>> 24) & 255, (int >> 16) & 255, (int >> 8) & 255, int & 255 ]
  // default 6-digit (rrggbb); anything malformed falls back to opaque black
  if (s.length === 6) return [ (int >> 16) & 255, (int >> 8) & 255, int & 255, 255 ]
  return [ 0, 0, 0, 255 ]
}

/** Normalize any ColorInput to four 0..1 float components (array inputs pass through losslessly). */
const toFloats = (c: ColorInput): [number, number, number, number] => {
  if (typeof c === "number") return [ ((c >> 16) & 255) / 255, ((c >> 8) & 255) / 255, (c & 255) / 255, 1 ]
  if (typeof c === "string") {
    const b = parseHexString(c)
    return [ b[0] / 255, b[1] / 255, b[2] / 255, b[3] / 255 ]
  }
  return [ c[0], c[1], c[2], (c as readonly number[])[3] ?? 1 ]
}

const toBytes = (c: ColorInput): [number, number, number, number] => {
  const f = toFloats(c)
  return [ Math.round(f[0] * 255), Math.round(f[1] * 255), Math.round(f[2] * 255), Math.round(f[3] * 255) ]
}

export const Color = {
  /** [r,g,b] in 0..1 — for the 2D engine's float color setters. */
  toRgb01(c: ColorInput): [number, number, number] {
    const f = toFloats(c)
    return [ f[0], f[1], f[2] ]
  },
  /** [r,g,b,a] in 0..1. */
  toRgba01(c: ColorInput): [number, number, number, number] {
    return toFloats(c)
  },
  /** 0xRRGGBB packed int — for the 3D engine's color uniforms / skybox. */
  toPackedRgb(c: ColorInput): number {
    const b = toBytes(c)
    return (b[0] << 16) | (b[1] << 8) | b[2]
  },
  /** 0xRRGGBBAA packed int (unsigned). */
  toPackedRgba(c: ColorInput): number {
    const b = toBytes(c)
    return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) >>> 0
  },
  /** '#rrggbb' (or '#rrggbbaa' when alpha < 1) — for the 3D material color uniforms. */
  toHexString(c: ColorInput): string {
    const b = toBytes(c)
    const hex = (n: number) => n.toString(16).padStart(2, "0")
    const base = `#${hex(b[0])}${hex(b[1])}${hex(b[2])}`
    return b[3] >= 255 ? base : base + hex(b[3])
  },
}
