/**
 * One reading of the sky, from whichever numbers a source happened to report.
 *
 * Every provider has its own vocabulary — NWS sends METAR cloud layers, MET
 * Norway sends a symbol code, Open-Meteo sends a WMO weather code — and the
 * renderer wants none of them. Deriving the condition from cloud cover,
 * precipitation and visibility means one rule for all three sources, and it
 * means a source that stops sending its symbol still produces a usable sky.
 */

import type { WeatherCondition } from "../../../src/server/wire.ts";

export interface ConditionInput {
  /** 0..1. */
  cloudCover: number;
  /** 0..1 intensity. */
  precipitation: number;
  visibilityKm: number | null;
  /** The source said the precipitation was frozen. */
  frozen?: boolean;
  thunder?: boolean;
}

export function deriveCondition(input: ConditionInput): WeatherCondition {
  if (input.thunder === true) return "thunderstorm";
  if (input.precipitation > 0.02) return input.frozen === true ? "snow" : "rain";
  // Fog is checked after precipitation because rain reduces visibility too, and
  // "it is raining" is the more useful thing to say about a rainy afternoon.
  if (input.visibilityKm !== null && input.visibilityKm < 1.5) return "fog";
  if (input.cloudCover >= 0.9) return "overcast";
  if (input.cloudCover >= 0.6) return "cloudy";
  if (input.cloudCover >= 0.25) return "partly-cloudy";
  return "clear";
}

/** Clamp to the 0..1 the wire promises, and turn a missing value into 0. */
export function unitRange(value: number | null | undefined): number {
  if (value === null || value === undefined || !Number.isFinite(value)) return 0;
  return Math.min(1, Math.max(0, value));
}

export function finiteOrNull(value: unknown): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value : null;
}
