/* global React */
// Radar widget — the pocasimeteo map (radar, satellite, lightning, warnings,
// regional temperatures) animated over the last 1–3 h.
//
// Frames come from the archive on this server (kamerypocasi.cz, the same host
// as this page): /radar/snimky.json lists them oldest first, one every ~5 min
// ({ snimky: [{ cas: unix s, url }] }), /radar/snimek/<RRRRMMDDTHHMMZ>.webp
// serves each frame (immutable, cached long). Server side:
// /usr/local/lib/kp-radar/radar.wsgi reading /var/lib/tartar/radar.
//
// Plays the frames in a loop and holds on the newest one; the slider picks a
// frame (pauses). The top legend band can be cropped (config.showLegend) so the
// map fills small tiles. Click → full-size modal with the same controls.
// Config: hours 1|2|3 (default 1), showLegend (default on), autoplay (default on).
(function () {
  const { useState, useEffect, useRef } = React;

  const LIST_URL = '/radar/snimky.json';
  const FRAME_MS = 450;            // one frame while playing
  const HOLD_MS = 2200;            // pause on the newest frame before the loop restarts
  const REFRESH_MS = 60 * 1000;    // the server caches the list for 60 s
  const STALE_S = 20 * 60;         // newest frame older than this = the map isn't updating

  const hhmm = (s) => new Date(s * 1000).toLocaleTimeString('cs-CZ', { hour: '2-digit', minute: '2-digit' });
  const ago = (sec) => {
    const m = Math.max(0, Math.round(sec / 60));
    if (m < 1) return 'teď';
    if (m < 60) return `před ${m} min`;
    return `před ${Math.floor(m / 60)} h ${m % 60} min`;
  };

  // Frame list, refreshed every minute while the tab is visible.
  function useRadarFrames(hours) {
    const [frames, setFrames] = useState(null);   // null = still loading
    const [error, setError] = useState(false);
    useEffect(() => {
      let alive = true;
      const load = async () => {
        try {
          const r = await fetch(`${LIST_URL}?hodin=${hours}`, { cache: 'no-cache' });
          if (!r.ok) throw new Error('HTTP ' + r.status);
          const list = ((await r.json()).snimky || []).filter(f => f && f.url && f.cas);
          if (!alive) return;
          setFrames(prev => (prev && prev.length === list.length && prev.every((f, i) => f.url === list[i].url))
            ? prev : list);
          setError(false);
        } catch (e) {
          if (alive) setError(true);
        }
      };
      load();
      const timer = setInterval(() => { if (document.visibilityState === 'visible') load(); }, REFRESH_MS);
      const onVis = () => { if (document.visibilityState === 'visible') load(); };
      document.addEventListener('visibilitychange', onVis);
      return () => { alive = false; clearInterval(timer); document.removeEventListener('visibilitychange', onVis); };
    }, [hours]);
    return { frames, error };
  }

  // Preload every frame (newest first, so the still picture shows up quickly).
  // Playback only steps onto frames that are already loaded — no blank flashes.
  function usePreload(frames) {
    const cache = useRef(new Map());
    const [loaded, setLoaded] = useState(0);
    useEffect(() => {
      if (!frames) return;
      const keep = new Set(frames.map(f => f.url));
      for (const url of [...cache.current.keys()]) if (!keep.has(url)) cache.current.delete(url);
      [...frames].reverse().forEach(f => {
        if (cache.current.has(f.url)) return;
        const entry = { ok: false, img: new Image() };
        cache.current.set(f.url, entry);
        entry.img.decoding = 'async';
        entry.img.onload = () => { entry.ok = true; setLoaded(n => n + 1); };
        entry.img.src = f.url;
      });
    }, [frames]);
    return { isLoaded: (url) => !!cache.current.get(url)?.ok, loaded };
  }

  function RadarBar({ list, idx, playing, onToggle, onSeek, now, style }) {
    const last = list.length - 1;
    const cur = list[idx];
    const stale = now - list[last].cas > STALE_S;
    const rel = idx === last ? ago(now - cur.cas) : `−${Math.round((list[last].cas - cur.cas) / 60)} min`;
    return (
      <div className="radar-bar" style={style}>
        <button type="button" className="radar-play" onClick={onToggle} disabled={last < 1}
                aria-label={playing ? 'Pozastavit' : 'Přehrát'} title={playing ? 'Pozastavit' : 'Přehrát animaci'}>
          <i className={`bi bi-${playing ? 'pause-fill' : 'play-fill'}`} aria-hidden="true"/>
        </button>
        <input type="range" className="radar-seek" min="0" max={Math.max(0, last)} step="1" value={idx}
               style={{ '--p': `${last > 0 ? (idx / last) * 100 : 100}%` }}
               disabled={last < 1} onChange={(e) => onSeek(Number(e.target.value))} aria-label="Čas snímku"/>
        <span className={`radar-time${stale ? ' radar-stale' : ''}`}
              title={stale ? 'Mapa se neobnovuje – poslední snímek je starý' : `Snímek ${hhmm(cur.cas)}`}>
          <b>{hhmm(cur.cas)}</b><span className="radar-rel"> {rel}</span>
        </span>
      </div>
    );
  }

  window.RadarWidget = function RadarWidget({ config = {} }) {
    const hours = [1, 2, 3].includes(config.hours) ? config.hours : 1;
    const showLegend = config.showLegend !== false;
    const autoplay = config.autoplay !== false;
    const { frames, error } = useRadarFrames(hours);
    const { isLoaded, loaded } = usePreload(frames);
    // Map size in px from the measured space (like the webcam): the largest
    // that fits the width and the height minus the 40 px controls row, in the
    // frame's ratio (16:9 without the legend, the whole map with it). CSS
    // (--radar-w with container units) only covers the first frame — on some
    // widths headless Chromium resolved it to 0 × 0.
    const [boxRef, box] = window.useBoxRef();
    const ratio = showLegend ? 1329 / 969 : 16 / 9;
    const mapW = box && box.w > 0 ? Math.floor(Math.max(0, Math.min(box.w, (box.h - 40) * ratio))) : null;
    const [selCas, setSelCas] = useState(null);    // shown frame; null = follow the newest
    const [playing, setPlaying] = useState(autoplay);
    const [open, setOpen] = useState(false);
    const [now, setNow] = useState(() => Date.now() / 1000);

    useEffect(() => { setPlaying(autoplay); }, [autoplay]);
    useEffect(() => {
      const t = setInterval(() => setNow(Date.now() / 1000), 30 * 1000);
      return () => clearInterval(t);
    }, []);

    const list = frames || [];
    const last = list.length - 1;
    let idx = selCas == null ? last : list.findIndex(f => f.cas === selCas);
    if (idx < 0) idx = last;

    // Playback: step to the next frame once it's loaded; hold on the newest, then loop.
    useEffect(() => {
      if (!playing || last < 1) return undefined;
      const atEnd = idx >= last;
      const next = atEnd ? 0 : idx + 1;
      if (!isLoaded(list[next].url)) return undefined;   // re-runs when it loads
      const t = setTimeout(() => setSelCas(next === last ? null : list[next].cas), atEnd ? HOLD_MS : FRAME_MS);
      return () => clearTimeout(t);
    }, [playing, idx, frames, loaded]);   // eslint-disable-line react-hooks/exhaustive-deps

    const seek = (i) => { setPlaying(false); setSelCas(i >= last ? null : list[i].cas); };
    const toggle = () => setPlaying(p => !p);

    if (!frames) {
      return error
        ? <div className="webcam-empty radar-empty"><i className="bi bi-cloud-slash"></i><div>Mapa počasí teď není k dispozici</div></div>
        : <div className="radar-loading">Načítám mapu…</div>;
    }
    if (!list.length) {
      return <div className="webcam-empty radar-empty"><i className="bi bi-hourglass-split"></i><div>Zatím žádné snímky mapy</div></div>;
    }
    const cur = list[idx];
    const bar = <RadarBar list={list} idx={idx} playing={playing} onToggle={toggle} onSeek={seek} now={now}/>;
    const tileBar = <RadarBar list={list} idx={idx} playing={playing} onToggle={toggle} onSeek={seek} now={now}
                              style={mapW != null ? { width: Math.max(mapW, Math.min(box.w, 240)) } : undefined}/>;
    return (
      <div ref={boxRef} className={`radar${showLegend ? '' : ' radar-crop'}`}>
        <button type="button" className="radar-map" onClick={() => setOpen(true)} title="Zvětšit mapu"
                style={mapW != null ? { width: mapW } : undefined}>
          <span className="radar-frame">
            <img src={cur.url} alt={`Mapa počasí ${hhmm(cur.cas)}`} draggable="false"/>
          </span>
        </button>
        {tileBar}
        {open && window.Modal && (
          <window.Modal onClose={() => setOpen(false)} kind="radar" bodyClass="radar-modal-body"
                        title={<>Radar – Satelit – Výstrahy<span className="chart-unit"> · {hhmm(cur.cas)}</span></>}>
            <img src={cur.url} alt={`Mapa počasí ${hhmm(cur.cas)}`} draggable="false"/>
            {bar}
          </window.Modal>
        )}
      </div>
    );
  };
})();
