// data.jsx — demo data (until the station sends its first row), sensor metadata
// and shared helpers.

// Calibrated threshold of the Forte de Copacabana model in the firmware
// (components/ml_inference/sensor_means.h). Reference only: it was trained at sea level in Rio.
const RAIN_THRESHOLD = 0.296;

// sensors_ok bits (firmware/components/sensors/include/sensors.h)
const SENSORS = [
  { bit: 1,  id: 'SHT41',    label: 'Temp + umidade' },
  { bit: 2,  id: 'BMP585',   label: 'Pressão' },
  { bit: 4,  id: 'MLX90614', label: 'IR do céu' },
  { bit: 8,  id: 'BH1750',   label: 'Luz' },
  { bit: 16, id: 'Biruta',   label: 'Direção do vento' },
  { bit: 32, id: 'Bateria',  label: 'ADC da célula' },
];

const SEASON_PT = { autumn: 'Outono', winter: 'Inverno', spring: 'Primavera', summer: 'Verão' };

// Southern hemisphere
function seasonForDate(d) {
  const m = d.getMonth();
  if (m >= 2 && m <= 4) return 'autumn';
  if (m >= 5 && m <= 7) return 'winter';
  if (m >= 8 && m <= 10) return 'spring';
  return 'summer';
}

const COMPASS = ['N', 'NNE', 'NE', 'ENE', 'L', 'ESE', 'SE', 'SSE', 'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO'];
function compassName(deg) {
  if (deg == null || !Number.isFinite(deg)) return '—';
  return COMPASS[Math.round(deg / 22.5) % 16];
}

// Coerce an API value (maybe null) to a finite number or null.
function num(v) {
  if (v == null) return null;
  const n = typeof v === 'number' ? v : parseFloat(v);
  return Number.isFinite(n) ? n : null;
}

// Sum of `key` over rows newer than `hours` before `nowSec`.
function sumOver(rows, key, hours, nowSec) {
  const cutoff = nowSec - hours * 3600;
  let total = 0, any = false;
  for (const r of rows) {
    if (r.ts >= cutoff && r[key] != null && Number.isFinite(r[key])) { total += r[key]; any = true; }
  }
  return any ? total : null;
}

// Rain since local midnight (America/Sao_Paulo, UTC-3, no DST since 2019).
function rainToday(rows, nowSec) {
  const local = nowSec - 3 * 3600;
  const midnight = local - (local % 86400) + 3 * 3600;
  return sumOver(rows, 'rain_mm', (nowSec - midnight) / 3600, nowSec);
}

// ── Demo data: plausible Sumidouro (serra, ~360 m) week ─────────────────────

function mulberry32(seed) {
  let a = seed;
  return function () {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

function generateDemo(hours = 168) {
  const rng = mulberry32(11);
  const rows = [];
  const step = 300;
  const end = Math.floor(Date.now() / 1000 / step) * step;
  const start = end - hours * 3600;
  let seq = 1;
  for (let ts = start; ts <= end; ts += step, seq++) {
    const hAgo = (end - ts) / 3600;
    const localH = (((ts - 3 * 3600) % 86400) + 86400) % 86400 / 3600;
    const diurnal = Math.cos((localH - 14.5) / 24 * Math.PI * 2);           // 1 at 14h30
    const sun = Math.max(0, Math.sin((localH - 6) / 12.5 * Math.PI));        // 6h .. 18h30
    const storm = hAgo < 5 ? (5 - hAgo) / 5 : (hAgo > 70 && hAgo < 76 ? 1 - Math.abs(hAgo - 73) / 3 : 0);

    const cloud = Math.min(100, Math.max(0, 30 + Math.sin(hAgo / 15) * 25 + storm * 60 + (rng() - 0.5) * 10));
    const temp = 19 + diurnal * 6 - storm * 3.5 + (rng() - 0.5) * 0.4;
    const hum = Math.min(99, Math.max(30, 74 - diurnal * 20 + storm * 22 + (rng() - 0.5) * 3));
    const pres = 968.5 + 0.8 * Math.cos((localH - 10) / 12 * Math.PI * 2) - storm * 3 + Math.sin(hAgo / 40) * 1.5;
    const wind = Math.max(0, 3 + sun * 7 + storm * 12 + (rng() - 0.5) * 3);
    const raining = storm > 0.35 && rng() < 0.8;
    const rainMm = raining ? Math.round(rng() * 6 * storm) * 0.2794 : 0;
    const lux = sun * 95000 * (1 - cloud / 130) + (rng() - 0.5) * 800;
    const prob = Math.min(0.95, Math.max(0.02, 0.06 + storm * 0.7 + (cloud / 100) * 0.15 + (rng() - 0.5) * 0.04));
    const batt = 3.86 + sun * 0.25 + (rng() - 0.5) * 0.01;

    rows.push({
      seq, ts, utc: new Date(ts * 1000).toISOString().replace('.000', ''), time_src: 'gps', firmware: '3.0.0',
      temperature: temp, humidity: hum, pressure: pres,
      sky_temperature: -16 + cloud * 0.28 + (rng() - 0.5), ambient_temperature: temp + 0.6,
      cloud_cover: cloud, lux: Math.max(0, lux),
      wind_dir_deg: (Math.round((110 + Math.sin(hAgo / 9) * 40 + (rng() - 0.5) * 40) / 22.5) * 22.5 + 360) % 360,
      wind_speed_kmh: wind, wind_gust_kmh: wind * (1.6 + rng() * 0.8),
      rain_mm: rainMm, interval_s: 300,
      battery_voltage: batt, battery_percent: Math.min(100, Math.max(0, (batt - 3.3) / 0.9 * 100)),
      charging: sun > 0.15 ? 1 : 0, input_present: sun > 0.05 ? 1 : 0,
      sensors_ok: 63, gps_fix: 1, latitude: -22.0489, longitude: -42.6764, altitude_m: 361 + (rng() - 0.5) * 6,
      satellites: 9 + Math.round(rng() * 4), hdop: 0.8 + rng() * 0.5,
      rain_probability: prob, rain_prediction: prob >= RAIN_THRESHOLD ? 1 : 0,
      photo: '', prev_awake_ms: 7000 + Math.round(rng() * 3000), prev_wifi_rssi: -68 - Math.round(rng() * 8),
      queue_backlog: 0, boot_count: seq, received_at: ts + 20,
    });
  }
  return rows;
}

Object.assign(window, {
  RAIN_THRESHOLD, SENSORS, SEASON_PT, seasonForDate, compassName, num, sumOver, rainToday, generateDemo,
});
