/**
 * A color packed into 32 bits as RGBA in little-endian byte order, i.e. the
 * bytes in memory run R, G, B, A. That is exactly the layout a canvas
 * ImageData expects, so the `Uint32Array` framebuffer can be reinterpreted as
 * an ImageData with zero per-pixel conversion at blit time.
 */
export type Color = number

export namespace Color {
  export function rgb(r: number, g: number, b: number, a = 255): Color {
    // The shifts coerce the (possibly fractional) inputs to int32 and pack the
    // channels. Left as a *signed* int32 on purpose: an unsigned `>>> 0` would
    // push opaque colors (alpha 255) past V8's Smi range, so every one would be
    // heap-boxed -- and the per-pixel sky/raster loops mint millions per frame,
    // enough to trigger visible GC pauses. Signed keeps them small Smis. Every
    // consumer extracts channels with `&`/`>>>` and stores through ToUint32, so
    // the sign is invisible to them.
    return (a << 24) | (b << 16) | (g << 8) | r
  }

  export function r(c: Color): number {
    return c & 0xFF
  }

  export function g(c: Color): number {
    return (c >>> 8) & 0xFF
  }

  export function b(c: Color): number {
    return (c >>> 16) & 0xFF
  }

  export function a(c: Color): number {
    return (c >>> 24) & 0xFF
  }

  /** Multiply RGB by a scalar (for shading), keeping alpha. */
  export function scale(c: Color, s: number): Color {
    return rgb(r(c) * s, g(c) * s, b(c) * s, a(c))
  }

  /** Linear blend between two colors, t in 0..1. Used for fog and bilinear.
   *  Alpha is interpolated too: bilinear texture sampling relies on it so the
   *  sprite alpha cutout still sees transparent edges (opaque colors blend to
   *  opaque, so fog/sky are unaffected). */
  export function lerp(from: Color, to: Color, t: number): Color {
    return rgb(
      r(from) + (r(to) - r(from)) * t,
      g(from) + (g(to) - g(from)) * t,
      b(from) + (b(to) - b(from)) * t,
      a(from) + (a(to) - a(from)) * t,
    )
  }
}
