// Earthquakes layer (Wave 2) — REAL data from the USGS all_day GeoJSON feed.
// Time-anchored like the events layer, but with genuine events: a quake's
// availability starts at its own origin time, so scrubbing the slider back
// before it happened makes it vanish. Fetched directly (USGS sends ACAO:*),
// refreshed every 5 min, deduped by feature id across refreshes.

export default function create(ctx) {
  const { viewer, CONFIG, lib, ui, Cesium, start, stop } = ctx;
  const Q = CONFIG.quakes;

  const ds = new Cesium.CustomDataSource('quakes');
  viewer.dataSources.add(ds);

  const amber = lib.cz(CONFIG.colors.quakeMin);
  const red = lib.cz(CONFIG.colors.quakeMax);
  const [magLo, magHi] = Q.magRamp;

  // id → { entity, sig }, so refreshes update the delta instead of duplicating.
  // The sig lets us detect USGS revisions (magnitude/place/depth/time) and
  // rebuild that entity, rather than leaving a stale one on screen.
  const byId = new Map();
  const sigOf = (f) => {
    const p = f.properties || {};
    const c = (f.geometry && f.geometry.coordinates) || [];
    return `${p.mag}|${p.place}|${c[2]}|${p.time}`;
  };

  let enabled = true;
  let timer = null;
  let inFlight = false;

  ui.addLayer('quakes', 'Earthquakes (USGS)', true, (on) => {
    enabled = on;
    ds.show = on;
  });
  ui.setStatus('quakes', 'loading USGS feed…', 'warn');

  // Magnitude → color (amber→red over magRamp) and base pixel size.
  function magColor(mag) {
    const t = Cesium.Math.clamp((mag - magLo) / (magHi - magLo), 0, 1);
    return Cesium.Color.lerp(amber, red, t, new Cesium.Color());
  }
  const baseSize = (mag) => Math.max(3, 4 + mag * 2.2);

  // Availability start clamped into the fixed clock window [start, stop].
  // Older-than-window quakes are visible the whole time; in-window ones appear
  // as the slider crosses their origin time; (impossible) future ones clamp out.
  function availabilityFor(timeMs) {
    const jd = Cesium.JulianDate.fromDate(new Date(timeMs));
    let s = jd;
    if (Cesium.JulianDate.lessThan(s, start)) s = start.clone();
    if (Cesium.JulianDate.greaterThan(s, stop)) s = stop.clone();
    return new Cesium.TimeIntervalCollection([
      new Cesium.TimeInterval({ start: s, stop }),
    ]);
  }

  function buildEntity(f) {
    const p = f.properties || {};
    const g = f.geometry || {};
    const coords = g.coordinates || [];
    const lon = coords[0];
    const lat = coords[1];
    const depth = coords[2];
    const mag = p.mag;
    const timeMs = p.time;
    const place = p.place || 'Unknown location'; // raw: used in name/label (text contexts)
    const url = lib.safeUrl(p.url);
    const size = baseSize(mag);
    const color = magColor(mag);
    const recentAtBuild = typeof timeMs === 'number' && (Date.now() - timeMs) < 3_600_000;

    // Quakes under an hour old pulse; the callback is age-aware so it settles
    // on its own once the quake passes the 1 h mark (no rebuild needed).
    const pixelSize = recentAtBuild
      ? new Cesium.CallbackProperty(() => {
          const fresh = typeof timeMs === 'number' && (Date.now() - timeMs) < 3_600_000;
          return fresh ? size + 3 * Math.sin(Date.now() / 300) : size;
        }, false)
      : size;

    const ent = new Cesium.Entity({
      id: `quake:${f.id}`,
      name: `M${mag.toFixed(1)} — ${place}`,
      position: Cesium.Cartesian3.fromDegrees(lon, lat),
      availability: availabilityFor(timeMs),
      point: {
        pixelSize,
        color,
        outlineColor: Cesium.Color.BLACK,
        outlineWidth: 1,
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
      },
      description:
        `<table class="cesium-infoBox-defaultTable"><tbody>` +
        `<tr><th>Magnitude</th><td>M${mag.toFixed(1)}</td></tr>` +
        `<tr><th>Place</th><td>${lib.escapeHtml(place)}</td></tr>` +
        `<tr><th>Depth</th><td>${depth != null ? depth.toFixed(1) + ' km' : '—'}</td></tr>` +
        `<tr><th>Time (UTC)</th><td>${timeMs != null ? new Date(timeMs).toISOString().replace('T', ' ').replace('.000Z', ' UTC') : '—'}</td></tr>` +
        `</tbody></table>` +
        (url ? `<p><a href="${lib.escapeHtml(url)}" target="_blank" rel="noopener">USGS event page ↗</a></p>` : ''),
    });

    // Labels only for the notable quakes, to keep the globe legible.
    if (mag >= Q.labelMinMag) {
      ent.label = new Cesium.LabelGraphics({
        text: `M${mag.toFixed(1)} ${place}`,
        font: '11px "Segoe UI", system-ui, sans-serif',
        fillColor: Cesium.Color.WHITE,
        outlineColor: Cesium.Color.fromCssColorString('#0a0f14'),
        outlineWidth: 3,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        pixelOffset: new Cesium.Cartesian2(0, -8),
        translucencyByDistance: new Cesium.NearFarScalar(2.0e6, 1.0, 1.2e7, 0.0),
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
      });
    }
    return ent;
  }

  function apply(features) {
    // Keep only quakes with a real magnitude and finite coordinates.
    const usable = features.filter((f) => {
      const m = f && f.properties && f.properties.mag;
      const c = f && f.geometry && f.geometry.coordinates;
      return typeof m === 'number' && isFinite(m) && Array.isArray(c) &&
        isFinite(c[0]) && isFinite(c[1]) && f.id != null;
    });

    // Cap by dropping the smallest magnitudes first.
    usable.sort((a, b) => b.properties.mag - a.properties.mag);
    const kept = usable.slice(0, Q.cap);

    const keepIds = new Set(kept.map((f) => `quake:${f.id}`));
    // Remove entities that are no longer in the kept set (revised away or capped out).
    for (const [id, rec] of byId) {
      if (!keepIds.has(id)) {
        ds.entities.remove(rec.entity);
        byId.delete(id);
      }
    }
    // Add newcomers and rebuild any quake USGS has revised (magnitude/place/etc.);
    // unchanged ones are left in place (dedupe).
    let maxMag = -Infinity;
    for (const f of kept) {
      if (f.properties.mag > maxMag) maxMag = f.properties.mag;
      const id = `quake:${f.id}`;
      const sig = sigOf(f);
      const existing = byId.get(id);
      if (existing) {
        if (existing.sig === sig) continue; // unchanged — keep it
        ds.entities.remove(existing.entity); // revised — rebuild below
        byId.delete(id);
      }
      const ent = buildEntity(f);
      ds.entities.add(ent);
      byId.set(id, { entity: ent, sig });
    }

    const count = byId.size;
    const maxTxt = count ? ` · max M${maxMag.toFixed(1)}` : '';
    ui.setStatus('quakes', `${count} quakes${maxTxt} · 24h`, 'ok');
  }

  async function poll() {
    let res;
    try {
      res = await fetch(Q.url);
    } catch {
      ui.setStatus('quakes', 'network error — retrying', 'warn');
      return;
    }
    if (!res.ok) {
      ui.setStatus('quakes', `HTTP ${res.status} — retrying`, 'warn');
      return;
    }
    let data;
    try {
      data = await res.json();
    } catch {
      ui.setStatus('quakes', 'bad response — retrying', 'err');
      return;
    }
    const features = Array.isArray(data && data.features) ? data.features : [];
    apply(features);
  }

  // Self-scheduling loop — keeps polling regardless of scrub state, because
  // quakes are historical records, not a live-only feed.
  async function tick() {
    timer = null;
    if (inFlight) return;
    inFlight = true;
    try {
      await poll();
    } finally {
      inFlight = false;
    }
    timer = setTimeout(tick, Q.refreshMs);
  }

  tick();

  return { id: 'quakes' };
}
