// Generic adsb.lol query-layer factory (free OSINT feeds). Powers the emergency
// squawk, rare-type hunter, and shadow-aircraft layers — each is a spec that
// names one or more adsb.lol v2 endpoints (proxy/adsb/<path>), merged by hex and
// drawn as a BillboardCollection primitive with a click overlay. Live-only
// (paused when scrubbed off-now or the tab is hidden), like the aircraft layer.
//
// spec: { id, name, category, defaultOn, pollMs,
//         endpoints: ['sqk/7700', 'type/U2', ...],  // fetched sequentially (anti-burst)
//         color(a) -> hex, scale(a)|number, label(a) -> string|null,
//         describe(a) -> [[k,v],...], status(n) -> string, emptyStatus? -> string }

export function createAdsbLayer(ctx, spec) {
  const { viewer, Cesium, lib, ui } = ctx;
  const bc = viewer.scene.primitives.add(new Cesium.BillboardCollection());
  const glyph = lib.aircraftGlyph();
  const pollMs = spec.pollMs || 90000;

  let enabled = spec.defaultOn !== false;
  let isLive = true;
  let inFlight = false;
  let timer = null;
  let lastOk = true;

  bc.show = enabled;
  ui.addLayer(spec.id, spec.name, enabled, (on) => {
    enabled = on;
    bc.show = on && isLive;
    if (on && isLive) kick();
  }, spec.category);
  ui.setStatus(spec.id, 'loading…', 'warn');

  function canPoll() { return enabled && isLive && !document.hidden; }
  function schedule(ms) { if (timer) clearTimeout(timer); timer = setTimeout(tick, ms); }
  function kick() { if (!canPoll() || timer || inFlight) return; schedule(0); }

  async function tick() {
    timer = null;
    if (!canPoll() || inFlight) return;
    inFlight = true;
    try { await poll(); } finally { inFlight = false; }
    // On a total failure (e.g. adsb.lol rate-limited the whole batch), retry
    // soon instead of waiting the full interval.
    if (canPoll()) schedule(lastOk ? pollMs : 9000);
  }

  const altFeet = (a) => (typeof a.alt_baro === 'number' ? a.alt_baro : 0);

  async function poll() {
    // Fetch endpoints in PARALLEL and merge by hex. The proxy already spaces the
    // upstream calls (rate-limit politeness), so parallel is safe here — and it
    // means one slow/hung endpoint (adsb.lol can be flaky) never stalls the rest.
    const byHex = new Map();
    let anyOk = false;
    const results = await Promise.allSettled(
      spec.endpoints.map((ep) => fetch(`proxy/adsb/${ep}`).then((r) => {
        if (!r.ok) throw new Error(String(r.status));
        return r.json().then((d) => ({ ep, d }));
      }))
    );
    for (const r of results) {
      if (r.status !== 'fulfilled') continue;
      anyOk = true;
      const { ep, d } = r.value;
      for (const a of (Array.isArray(d.ac) ? d.ac : [])) {
        if (a.lat == null || a.lon == null || byHex.has(a.hex)) continue;
        a._src = ep;
        byHex.set(a.hex, a);
      }
    }
    lastOk = anyOk;
    if (!anyOk) { ui.setStatus(spec.id, 'busy — retrying shortly', 'warn'); return; }

    // Optional client-side filter (e.g. rare-types filters the shared mil feed
    // by ICAO type — one cached call, no per-type rate-limit risk).
    let list = [...byHex.values()];
    if (spec.filter) list = list.filter(spec.filter);

    bc.removeAll();
    for (const a of list) {
      bc.add({
        position: Cesium.Cartesian3.fromDegrees(a.lon, a.lat, altFeet(a) * 0.3048),
        image: glyph,
        color: lib.cz(spec.color(a)),
        rotation: lib.headingToRotation(a.track),
        scale: typeof spec.scale === 'function' ? spec.scale(a) : (spec.scale || 0.8),
        disableDepthTestDistance: 50000, // occlude when behind the globe (far side)
        id: {
          layer: spec.id, hex: a.hex, flight: (a.flight || '').trim(),
          t: a.t, reg: a.r, altFt: altFeet(a), gs: a.gs, track: a.track,
          squawk: a.squawk, src: a._src,
        },
      });
    }
    const n = list.length;
    const txt = n === 0 && spec.emptyStatus ? spec.emptyStatus() : spec.status(n);
    ui.setStatus(spec.id, txt, n > 0 && spec.alertOnHits ? 'err' : 'ok');
  }

  function onClockTick(currentTime, live) {
    isLive = live;
    if (!isLive) { bc.show = false; return; }
    bc.show = enabled;
    if (enabled) kick();
  }

  function handlePick(picked) {
    if (picked?.id?.layer !== spec.id) return false;
    ui.showPickOverlay(spec.pickTitle ? spec.pickTitle(picked.id) : (picked.id.flight || picked.id.hex || '—'),
      spec.describe(picked.id));
    return true;
  }
  function clearPick() { ui.hidePickOverlay(); }

  document.addEventListener('visibilitychange', () => { if (!document.hidden) kick(); });
  kick();

  return { id: spec.id, onClockTick, handlePick, clearPick };
}
