/**
 * The NWS reflectivity ramp, as numbers.
 *
 * Two hundred and fifty-six colours, fetched once from the Iowa Environmental
 * Mesonet's own N0Q legend and written out here as packed `0xRRGGBB` integers.
 * They are not a palette somebody liked the look of: they are *the* ramp the
 * product is published in, so a viewer who has seen a weather map before reads
 * this board's rain without being taught anything, and the green-to-yellow-to-
 * red-to-magenta ladder means what it means everywhere else.
 *
 * ### Why numbers rather than a PNG
 *
 * No binary assets under `src/` is a licensing rule, not a stylistic one — see
 * ARCHITECTURE.md and `scripts/check-no-binaries.mjs`. A colour ramp is the most
 * tempting thing in the repo to ship as a 256x1 image, and it is also the
 * cheapest to ship as a list: 256 integers is about 2 KB of source, it diffs,
 * and it needs no `PROVENANCE.json` entry because there is no artifact.
 *
 * ### The index is the dBZ axis, and it is exactly affine
 *
 * `idx` 1 is -31.5 dBZ and every step is half a dBZ, so `idx` 255 is 95.5. That
 * was **checked against all 255 rows of the upstream table**, not assumed:
 * `dbz = -32 + idx / 2` reproduces every one of them. So only the colours are
 * shipped, and the axis is one line of arithmetic that cannot drift out of step
 * with the table it indexes.
 *
 * `idx` 0 is the product's "no data" entry and is black with no dBZ beside it.
 * It is kept in the table so the array index *is* the ramp index, and it is
 * never looked up: `radarRampRgb` clamps to 1.
 *
 * ### What the store actually contains
 *
 * Every one of the 8,782 `echo_cells` rows in the upstream store is at or above
 * **20.0 dBZ** — the collector thresholds at rain before it writes — so in
 * practice only indices 104 and up are ever reached. The rest are here because a
 * ramp with holes in it is a ramp somebody has to check the bounds of.
 *
 * Credit: NOAA/NWS via Iowa State University's IEM. US government work.
 */

import { RADAR_RAIN_DBZ } from "../server/radar.ts";

/** dBZ at ramp index 0.5 steps below index 1. See the header: the axis is affine. */
const RAMP_DBZ_BASE = -32;
const RAMP_DBZ_STEP = 0.5;

/** Packed `0xRRGGBB`, indexed by the product's own ramp index. */
export const RADAR_RAMP: readonly number[] = [
  0x000000, 0x85718f, 0x85728f, 0x86738d, 0x87758b, 0x87768b, 0x887789, 0x897987,
  0x897a87, 0x8a7b85, 0x8b7d84, 0x8b7e84, 0x8c7f82, 0x8d8180, 0x8d8280, 0x8e837e,
  0x8f847c, 0x8f857c, 0x90877b, 0x918879, 0x918979, 0x928b77, 0x938d75, 0x969153,
  0x989457, 0x9b975b, 0x9d9a60, 0xa09d64, 0xa3a068, 0xa5a36d, 0xa8a671, 0xaaa976,
  0xadac7a, 0xb0af7e, 0xb2b283, 0xb7b88c, 0xbabb90, 0xbdbe94, 0xbfc199, 0xc2c49d,
  0xc4c7a2, 0xc7caa6, 0xcacdaa, 0xccd0af, 0xd2d4b4, 0xcfd2b4, 0xc9ccb4, 0xc6c9b4,
  0xc3c7b4, 0xc0c4b4, 0xbdc1b4, 0xb9beb4, 0xb6bbb4, 0xb3b9b4, 0xb0b6b4, 0xadb3b4,
  0xaab0b4, 0xa4abb4, 0xa0a8b4, 0x9da5b4, 0x9aa2b4, 0x97a0b4, 0x949db4, 0x919ab4,
  0x949bb5, 0x9098b4, 0x8c95b3, 0x8892b2, 0x808cb0, 0x7c89af, 0x7886ae, 0x7483ac,
  0x7080ab, 0x6c7daa, 0x6779a9, 0x6376a8, 0x5f73a7, 0x5b70a6, 0x576da4, 0x4f67a2,
  0x4b64a1, 0x4761a0, 0x435e9f, 0x415b9e, 0x4361a2, 0x4568a6, 0x486faa, 0x4a76ae,
  0x4d7db2, 0x4f84b6, 0x518bbb, 0x5699c3, 0x599fc7, 0x5ba6cb, 0x5eadcf, 0x60b4d4,
  0x62bbd8, 0x65c2dc, 0x67c9e0, 0x6ad0e4, 0x6fd6e8, 0x68d6d7, 0x59d6b3, 0x52d6a2,
  0x4bd690, 0x43d67e, 0x3cd66d, 0x35d65b, 0x11d518, 0x11d117, 0x10cd17, 0x10c816,
  0x10c416, 0x0fbc15, 0x0fb714, 0x0eb314, 0x0eaf13, 0x0eab13, 0x0da612, 0x0da212,
  0x0d9e11, 0x0c9911, 0x0c9510, 0x0c9110, 0x0b880f, 0x0b840e, 0x0a800e, 0x0a7c0d,
  0x0a770d, 0x09730c, 0x096f0c, 0x096b0b, 0x08660b, 0x08620a, 0x095e09, 0x327308,
  0x467d08, 0x5b8807, 0x6f9207, 0x849d06, 0x98a806, 0xadb205, 0xc1bd05, 0xd6c704,
  0xead204, 0xffe200, 0xffd800, 0xffd300, 0xffce00, 0xffc900, 0xffc400, 0xffc000,
  0xffbb00, 0xffb600, 0xffb100, 0xffac00, 0xffa700, 0xffa200, 0xff9900, 0xff9400,
  0xff8f00, 0xff8a00, 0xff8500, 0xff8000, 0xff0000, 0xf80000, 0xf10000, 0xea0000,
  0xe30000, 0xd50000, 0xcd0000, 0xc60000, 0xbf0000, 0xb80000, 0xb10000, 0xaa0000,
  0xa30000, 0x9b0000, 0x940000, 0x8d0000, 0x7f0000, 0x780000, 0x710000, 0xffffff,
  0xfff5ff, 0xffeaff, 0xffdfff, 0xffd4ff, 0xffc9ff, 0xffbeff, 0xffb3ff, 0xff9dff,
  0xff92ff, 0xff75ff, 0xfc6bfd, 0xf960fa, 0xf656f7, 0xf34bf4, 0xf040f1, 0xed36ef,
  0xea2bec, 0xe720e9, 0xe10be3, 0xb200ff, 0xac00fc, 0xa400f7, 0x9b00f4, 0x9300ef,
  0x8800ea, 0x8300e8, 0x7900e2, 0x7200dd, 0x6900db, 0x05ecf0, 0x05ebf0, 0x05eaf0,
  0x05dde0, 0x05dce0, 0x05dbe0, 0x05cdd0, 0x05ccd0, 0x04bdc0, 0x04bcc0, 0x04bbc0,
  0x04aeb0, 0x04adb0, 0x049ea0, 0x049da0, 0x049ca0, 0x038e90, 0x038d90, 0x038c90,
  0x037e80, 0x037d80, 0x036f70, 0x036e70, 0x036d70, 0x025f60, 0x025e60, 0x024f50,
  0x024e50, 0x024d50, 0x023f40, 0x023e40, 0x023d40, 0x013030, 0x012f30, 0x012020,
  0x011f20, 0x011e20, 0x3a67b5, 0x3a66b5, 0x3a65b5, 0x3a64b5, 0x3a63b5, 0x3a62b5,
];

/** The dBZ this ramp index stands for. `idx` 0 has none and is not addressable. */
export function dbzForRampIndex(index: number): number {
  return RAMP_DBZ_BASE + index * RAMP_DBZ_STEP;
}

/** The ramp index for a reflectivity, clamped into the addressable range. */
export function rampIndexForDbz(dbz: number): number {
  if (!Number.isFinite(dbz)) return 1;
  const raw = Math.round((dbz - RAMP_DBZ_BASE) / RAMP_DBZ_STEP);
  return Math.min(255, Math.max(1, raw));
}

/**
 * One reflectivity as three 0-255 channels.
 *
 * Returned as a tuple rather than a `THREE.Color` on purpose: this module is
 * imported by the layer that fills a `DataTexture` byte by byte, and a colour
 * object would mean an sRGB-to-linear conversion happening in the wrong place.
 * The bytes written into an `RGBAFormat` / `UnsignedByteType` texture are sRGB,
 * and the texture is told so.
 */
export function radarRampRgb(dbz: number): [number, number, number] {
  const packed = RADAR_RAMP[rampIndexForDbz(dbz)] ?? 0;
  return [(packed >> 16) & 0xff, (packed >> 8) & 0xff, packed & 0xff];
}

/**
 * The part of the ramp anyone will ever see, painted into a canvas.
 *
 * A thirteen-stop colour scale that nobody can read is decoration, so the layer
 * ships its own key rather than assuming the reader already has one. Drawn at
 * runtime, which is the same reason the table above is numbers: there is no
 * image file anywhere in this.
 *
 * `null` with no DOM, which is the posture every canvas builder in this repo
 * takes (`airports.ts`, `assets/fire.ts`, `clouds.ts`) — a headless test builds
 * the layer and gets no key, rather than throwing on `document`.
 */
export function radarRampStrip(
  width = 256,
  height = 12,
  minDbz = RADAR_RAIN_DBZ,
  maxDbz = 75,
): HTMLCanvasElement | null {
  if (typeof document === "undefined") return null;
  const canvas = document.createElement("canvas");
  canvas.width = Math.max(1, Math.round(width));
  canvas.height = Math.max(1, Math.round(height));
  const context = canvas.getContext("2d");
  if (!context) return null;

  for (let x = 0; x < canvas.width; x++) {
    const dbz = minDbz + ((maxDbz - minDbz) * x) / Math.max(1, canvas.width - 1);
    const [r, g, b] = radarRampRgb(dbz);
    context.fillStyle = `rgb(${r},${g},${b})`;
    context.fillRect(x, 0, 1, canvas.height);
  }
  return canvas;
}
