/* global React */
const { useState, useEffect, useMemo, useRef, useCallback } = React;

// =========================================================
// Data layer — loads forecast JSON + simulates station/comparison
// =========================================================

const MODEL_META = {
  MASTER: { id: 'MASTER', name: 'MASTER',   sub: 'Počasí Meteo'},
  ALADIN: { id: 'ALADIN', name: 'ALADIN',   sub: 'ČHMÚ'},
  YRno:   { id: 'YRno',   name: 'Yr.no',    sub: 'Norsko'},
  WRF:    { id: 'WRF',    name: 'WRF',      sub: 'Počasí Meteo'},
  ICON:   { id: 'ICON',   name: 'ICON EU',  sub: 'Německo'},
  COSMO:  { id: 'COSMO',  name: 'ICON DE',  sub: 'Německo'},
  GFS:    { id: 'GFS',    name: 'GFS',      sub: 'USA'},
  ECMWF:  { id: 'ECMWF',  name: 'ECMWF',    sub: 'Evropa'},
  WEA:    { id: 'WEA',    name: 'WEA',      sub: 'ensemble'},
};

const MODEL_ORDER = ['MASTER','ALADIN','YRno','WRF','ICON','COSMO','GFS','ECMWF','WEA'];

// parse "05/08/26 16:00:00"
function parseDat(s) {
  if (!s) return null;
  const [d, t] = s.split(' ');
  const [mm, dd, yy] = d.split('/').map(Number);
  const [h, m, sec] = t.split(':').map(Number);
  return new Date(2000 + yy, mm - 1, dd, h, m, sec || 0);
}

// Icon mapping — accepts both "04.png" and "skoro_zatazeno.png" forms
function iconUrl(name) {
  if (!name) return 'icons/a04.png';
  // bare numeric/short like "04.png" → "a04.png", "02d.png" → "a02d.png"
  if (/^\d/.test(name) || /^\d\d[dn]?\.png$/.test(name)) {
    return 'icons/a' + name;
  }
  return 'icons/' + name;
}

// Czech weekday short
const WD_SHORT = ['Ne','Po','Út','St','Čt','Pá','So'];
const WD_LONG  = ['Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota'];

function condFromIcon(icon) {
  const n = (icon||'').toLowerCase();
  if (n.includes('01')) return 'Jasno';
  if (n.includes('02')) return 'Skoro jasno';
  if (n.includes('03')) return 'Polojasno';
  if (n.includes('04')) return 'Oblačno';
  if (n.includes('skoro_zatazeno_dest')) return 'Skoro zataženo, déšť';
  if (n.includes('skoro_zatazeno_snez')) return 'Skoro zataženo, sníh';
  if (n.includes('skoro_zatazeno_bourka')) return 'Skoro zataženo, bouřka';
  if (n.includes('skoro_zatazeno')) return 'Skoro zataženo';
  if (n.includes('zatazeno_bourka')) return 'Zataženo, bouřka';
  if (n.includes('mlha')) return 'Mlha';
  if (n.includes('46') || n.includes('rain')) return 'Déšť';
  if (n.includes('60') || n.includes('70')) return 'Přeháňky';
  return 'Oblačno';
}

// Current-weather icon + label — the same rule as the station page (/ms,
// obloha.js conditions + condIconUrl), so both pages always show one icon:
// thunder, rain/snow and fog measured by the station win; otherwise the MASTER
// forecast for the hour nearest now (±90 min), whatever model is picked here;
// otherwise the given fallback hour.
function currentWeather(models, station, fallbackHour) {
  const recs = station?.raw || [];
  const L = station?.latest || {};
  let rain5 = 0, lightning = false;
  for (let i = Math.max(0, recs.length - 2); i < recs.length; i++) {
    if (recs[i].calculatedRain > rain5) rain5 = recs[i].calculatedRain;
  }
  for (let i = Math.max(0, recs.length - 3); i < recs.length; i++) if (recs[i].BlP > 0) lightning = true;
  const rainRate = rain5 * 12;                       // mm/h from the last 5 min step
  const t = L.TeplotaVnejsi, rh = L.VlhkostVnejsi, td = L.RosnyBod;
  if (lightning) return { icon: '11.png', cond: 'Bouřka v okolí' };
  if (rainRate > 0) {
    if (t != null && t <= 1) return { icon: '13.png', cond: 'Sněží' };
    return rainRate >= 4 ? { icon: '10.png', cond: 'Silný déšť' }
      : { icon: '46.png', cond: rainRate >= 1 ? 'Prší' : 'Mrholí / slabý déšť' };
  }
  if (rh != null && rh >= 97 && t != null && td != null && t - td < 1) return { icon: '15.png', cond: 'Mlha' };
  const master = (models || []).find(m => m.id === 'MASTER')?.data?.hourly || [];
  const now = Date.now();
  let best = null, bd = Infinity;
  for (const h of master) {
    const d = Math.abs(h.time - now);
    if (d < bd) { bd = d; best = h; }
  }
  const h = best && bd <= 90 * 60e3 && best.icon ? best : fallbackHour;
  return h ? { icon: h.icon, cond: h.cond || condFromIcon(h.icon) } : null;
}

async function loadModelData(modelId) {
  try {
    const r = await fetch(`${window.PM_DATA || 'data/'}${modelId}_data.json`, { cache: 'no-cache' });
    if (!r.ok) return null;
    const raw = await r.json();
    // structure: { data: [ { data: [...hourly], data_dne: [...daily] } ] }
    const inner = raw.data && raw.data[0] ? raw.data[0] : null;
    if (!inner) return null;
    const now = new Date();
    // step 1 — build all hourly entries
    const allHourly = (inner.data || []).map(h => ({
      time: parseDat(h.Dat),
      icon: h.Ik,
      temp: h.Te,
      humidity: h.Vl,
      pressure: h.Tl,
      precipProb: h.SP,
      precip: h.S,
      // SK = srážky NAČÍTANÉ od začátku předpovědi (0,1 → 1 → 4,5 … mm), ne sníh
      precipSum: h.SK,
      cloud: h.O,
      wind: h.V,
      gust: h.VN,
      windDir: h.VS,
      windDeg: h.VSS,
      cond: condFromIcon(h.Ik),
    })).filter(h => h.time);
    // step 2 — slice to current time onward (find the entry closest-not-after now,
    // or fall back to the very first if all are in the future)
    let startIdx = 0;
    for (let i = 0; i < allHourly.length; i++) {
      if (allHourly[i].time <= now) startIdx = i;
      else break;
    }
    const hourly = allHourly.slice(startIdx);
    // step 3 — also keep an "all" reference so callers can look back if needed
    const daily = (inner.data_dne || []).map(d => ({
      date: parseDat(d.Dat_dne),
      icon: d.IkD,
      tmax: d.Tmax,
      tmin: d.Tmin,
      tavg: d.Tprum,
      pressure: d.Tlprum,
      cloud: d.Oprum,
      gust: d.VNmax,
      wind: d.Vmax,
      humidity: d.Vlprum,
      precip: d.S_den ?? 0,
      cond: condFromIcon(d.IkD),
    })).filter(d => d.date);
    // keep only today + future
    const today = new Date(); today.setHours(0,0,0,0);
    const dailyFuture = daily.filter(d => d.date >= today);
    return { hourly, daily: dailyFuture.length ? dailyFuture : daily };
  } catch (e) {
    console.warn('Failed model', modelId, e);
    return null;
  }
}

async function loadAvailableModels() {
  try {
    const r = await fetch(`${window.PM_DATA || 'data/'}available_models.json`, { cache: 'no-cache' });
    if (!r.ok) return [];
    const j = await r.json();
    return (j.models || []).map(m => m.id);
  } catch { return MODEL_ORDER; }
}

async function loadSiteInfo() {
  try {
    const r = await fetch(`${window.PM_DATA || 'data/'}site_info.json`, { cache: 'no-cache' });
    if (!r.ok) return null;
    return await r.json();
  } catch { return null; }
}

// Simulated meteostation snapshot — derived from MASTER first hour with offsets
function deriveStation(hourly) {
  if (!hourly || !hourly.length) return null;
  const h = hourly[0];
  // realistic small deviations — timeRange is the live "last 30 minutes" window
  const now = new Date();
  const half = new Date(now.getTime() - 30*60*1000);
  const fmt = (d) => String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0');
  return {
    timeRange: `${fmt(half)} – ${fmt(now)}`,
    temp:      +(h.temp + 0.4).toFixed(1),
    humidity:  Math.max(0, Math.min(100, h.humidity - 3)),
    pressure:  h.pressure + 1,
    wind:      +(h.wind * 0.85).toFixed(1),
    gust:      +(h.gust * 0.92).toFixed(1),
    windDir:   h.windDir,
    windDeg:   h.windDeg,
    precip:    0.0,
    solar:     412,
    uv:        3.4,
    feels:     +(h.temp - 1.6).toFixed(1),
  };
}

// =========================================================
// Auto-pick: which model is closest to station right now?
// =========================================================
// „V souladu“: the model's current hour is within this of the measurement
// (base units: °C, %, hPa, m/s, m/s). The widget Soulad s předpovědí counts
// with the same numbers, so AUTO and the widget always agree.
const SOULAD_TOL = { temp: 1.0, humidity: 7, pressure: 2, wind: 1.5, gust: 2.5 };
const hasVal = (v) => v != null && !isNaN(v);
function scoreModel(station, firstHour) {
  if (!station || !firstHour) return null;
  // weighted closeness 0..1 per quantity (1 = perfect), only where both exist
  const q = [
    ['T', 'temp', 0.40, 4], ['H', 'humidity', 0.15, 25], ['P', 'pressure', 0.15, 6],
    ['W', 'wind', 0.15, 5], ['G', 'gust', 0.15, 8],
  ];
  const parts = {}, diffs = {};
  let sum = 0, wsum = 0, inTol = 0, judged = 0;
  for (const [k, f, w, scale] of q) {
    const a = firstHour[f], b = station[f];
    if (!hasVal(a) || !hasVal(b)) continue;
    const d = Math.abs(a - b);
    const part = 1 - Math.min(1, d / scale);
    parts[k] = +part.toFixed(2);
    diffs['d' + k] = +d.toFixed(k === 'H' || k === 'P' ? 0 : 1);
    sum += w * part; wsum += w;
    judged++;
    if (d <= SOULAD_TOL[f]) inTol++;
  }
  if (!judged) return null;
  return { total: +((sum / wsum) * 100).toFixed(1), ok: inTol, judged, parts, diffs };
}
// Order of models, best first (AUTO = the first): more quantities in
// agreement with the station (Lukáš 22. 10.: „přednost model, který má více
// souladů“), then the higher weighted score; unscored at the end.
function compareFit(a, b) {
  const sa = a && a.score, sb = b && b.score;
  if (!sa || !sb) return (sb ? 1 : 0) - (sa ? 1 : 0);
  return (sb.ok - sa.ok) || (sb.total - sa.total);
}

window.WeatherData = {
  MODEL_META, MODEL_ORDER, WD_SHORT, WD_LONG,
  parseDat, iconUrl, condFromIcon, currentWeather,
  loadModelData, loadAvailableModels, loadSiteInfo,
  deriveStation, scoreModel, compareFit, SOULAD_TOL
};
