/**
 * open-meteo.com — opt-in, and off by default.
 *
 * This one needs its reason on the page, because on the numbers it is the best
 * of the three: global, keyless, no contact string, one request, and it reports
 * visibility and cloud cover directly. The problem is not the data — that is
 * CC BY 4.0 — it is the *tier*. Open-Meteo's free API is for non-commercial use,
 * and a product page is a commercial use. Shipping it as the default would put
 * every self-hoster on a footing the project cannot vouch for.
 *
 * So it is here, it works, and turning it on is a decision the operator makes
 * with `TERA_WEATHER_SOURCE=openmeteo`. CONTRACT.md §5.2.
 */

import { getJson } from "../http.ts";
import type { WeatherBody } from "../../../src/server/wire.ts";
import { deriveCondition, finiteOrNull, unitRange } from "./condition.ts";

const BASE = "https://api.open-meteo.com/v1/forecast";

const FIELDS = [
  "temperature_2m",
  "precipitation",
  "cloud_cover",
  "visibility",
  "wind_speed_10m",
  "wind_direction_10m",
  "weather_code",
].join(",");

interface ForecastResponse {
  current?: Record<string, number | string | undefined>;
}

export async function fetchOpenMeteo(lat: number, lng: number): Promise<WeatherBody | null> {
  const url =
    `${BASE}?latitude=${lat.toFixed(4)}&longitude=${lng.toFixed(4)}` +
    `&current=${FIELDS}&wind_speed_unit=kmh&timezone=UTC`;
  const body = await getJson<ForecastResponse>(url);
  const current = body?.current;
  if (current === undefined) return null;

  const num = (key: string): number | null => finiteOrNull(current[key]);
  const cloudCover = unitRange((num("cloud_cover") ?? 0) / 100);
  const precipitation = unitRange((num("precipitation") ?? 0) / 4);
  const visibilityM = num("visibility");
  const visibilityKm = visibilityM === null ? null : visibilityM / 1000;
  const code = num("weather_code") ?? 0;

  return {
    observedAt: isoTime(current["time"]),
    source: "openmeteo",
    synthetic: false,
    location: { lat, lng },
    temperatureC: num("temperature_2m"),
    windKph: num("wind_speed_10m"),
    windDirDeg: num("wind_direction_10m"),
    cloudCover,
    precipitation,
    visibilityKm,
    condition: deriveCondition({
      cloudCover,
      precipitation,
      visibilityKm,
      // WMO 4677 codes: 71-77 and 85-86 are the frozen ones, 95-99 thunder.
      frozen: (code >= 71 && code <= 77) || code === 85 || code === 86,
      thunder: code >= 95,
    }),
    attribution: ["Weather data by Open-Meteo.com, licensed CC BY 4.0"],
  };
}

/** Open-Meteo stamps `2026-08-04T22:00` with no zone marker; we asked for UTC. */
function isoTime(time: string | number | undefined): string {
  if (typeof time !== "string") return new Date().toISOString();
  return time.endsWith("Z") ? time : `${time}Z`;
}
