// Ships layer (SPEC §6.4). Demo fleet of time-dynamic vessels crossing the
// clock window, plus an optional guarded live-AIS path. Every demo vessel name
// ends in " [DEMO]". Ships are Cesium entities (free InfoBox + time dynamics).

export default function create(ctx) {
  const { viewer, CONFIG, lib, ui, Cesium, start, stop, now } = ctx;
  const C = CONFIG.colors;
  const glyph = lib.shipGlyph();

  const shipsDS = new Cesium.CustomDataSource('ships');
  viewer.dataSources.add(shipsDS);

  // ---- helpers ----
  const at = (h) => Cesium.JulianDate.addHours(now, h, new Cesium.JulianDate());

  // Build a SampledPositionProperty from [lon, lat, hourOffset] waypoints.
  function track(waypoints) {
    const p = new Cesium.SampledPositionProperty();
    for (const [lon, lat, h] of waypoints) {
      p.addSample(at(h), Cesium.Cartesian3.fromDegrees(lon, lat, 0));
    }
    return p;
  }

  // Reverse a track's geographic direction while keeping time increasing.
  const reverse = (wp) => wp.slice().reverse().map(([lon, lat, h]) => [lon, lat, -h]);

  // Fixed billboard rotation from the great-circle bearing of first→last waypoint.
  function rotationOf(wp) {
    const a = wp[0], b = wp[wp.length - 1];
    return lib.headingToRotation(lib.bearingDeg(a[1], a[0], b[1], b[0]));
  }

  const labelGraphic = (extra = {}) => ({
    font: '11px sans-serif',
    fillColor: Cesium.Color.WHITE,
    outlineColor: Cesium.Color.BLACK,
    outlineWidth: 2,
    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
    pixelOffset: new Cesium.Cartesian2(0, -14),
    scale: 0.9,
    // Fade labels out at the global overview so the dense strait declutters;
    // they sharpen as you zoom into the theater.
    translucencyByDistance: new Cesium.NearFarScalar(1.5e6, 1.0, 6.0e6, 0.0),
    disableDepthTestDistance: Number.POSITIVE_INFINITY,
    ...extra,
  });

  function describe(flag, type, speed, note) {
    return (
      `<table class="cesium-infoBox-defaultTable"><tbody>` +
      `<tr><th>Flag</th><td>${flag}</td></tr>` +
      `<tr><th>Type</th><td>${type}</td></tr>` +
      `<tr><th>Speed</th><td>${speed}</td></tr>` +
      `<tr><th>Route</th><td>${note}</td></tr>` +
      `</tbody></table><p style="opacity:.7">Illustrative demo vessel — not live AIS.</p>`
    );
  }

  // Add a moving vessel entity from a waypoint track.
  function addVessel(name, wp, color, meta, availability) {
    return shipsDS.entities.add({
      name,
      position: track(wp),
      availability,
      billboard: {
        image: glyph,
        color: lib.cz(color),
        rotation: rotationOf(wp),
        scale: 0.95,
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
      },
      label: labelGraphic({ text: name }),
      description: describe(meta.flag, meta.type, meta.speed, meta.note),
    });
  }

  // ---- HUD toggle ----
  // Registered BEFORE the fleet is built so the later ui.setStatus('ships', …)
  // lands on an existing row (setStatus no-ops if the row isn't registered yet).
  // The callback controls whichever fleet is on screen: the demo fleet, or the
  // live-AIS fleet once it takes over (tracked by liveActive) — so it never
  // resurrects the hidden demo fleet on top of live vessels.
  let aisDS = null;
  let liveActive = false;
  ui.addLayer('ships', 'Ships [DEMO]', true, (on) => {
    if (liveActive) { if (aisDS) aisDS.show = on; }
    else { shipsDS.show = on; }
  });

  let vessels = 0;

  try {
    // 1) Tankers/LNG through the Hormuz deep channel, two opposing lanes.
    // laneIn: waypoints run 56.9°E → 53.5°E as time advances, i.e. longitude
    // DECREASES → the vessel sails WEST, inbound to the Persian Gulf.
    const laneIn1 = [[56.9, 25.4, -6], [56.4, 26.2, -2.5], [55.6, 26.55, 0], [54.5, 26.2, 3], [53.5, 25.7, 6]];
    const laneIn2 = [[56.95, 25.25, -5], [56.45, 26.05, -1.5], [55.65, 26.4, 0.5], [54.55, 26.05, 3.5], [53.55, 25.55, 6]];
    // Opposing lanes: reversed tracks (longitude increases → sailing EAST,
    // outbound to the Gulf of Oman), nudged north to separate the two lanes.
    const shiftLat = (wp, d) => wp.map(([lon, lat, h]) => [lon, lat + d, h]);
    const laneOut1 = shiftLat(reverse(laneIn1), 0.18);
    const laneOut2 = shiftLat(reverse(laneIn2), 0.18);

    addVessel('GULF PIONEER [DEMO]', laneIn1, C.shipNormal,
      { flag: 'Marshall Islands', type: 'Crude oil tanker (VLCC)', speed: '12.4 kn', note: 'Westbound — inbound to the Persian Gulf' });
    addVessel('AL RUWAIS [DEMO]', laneIn2, C.shipNormal,
      { flag: 'Qatar', type: 'LNG carrier (Q-Max)', speed: '15.1 kn', note: 'Westbound — inbound to the Persian Gulf' });
    addVessel('STAR OF MUSCAT [DEMO]', laneOut1, C.shipNormal,
      { flag: 'Panama', type: 'Product tanker (LR2)', speed: '11.8 kn', note: 'Eastbound — outbound to the Gulf of Oman' });
    addVessel('NORDIC AURORA [DEMO]', laneOut2, C.shipNormal,
      { flag: 'Liberia', type: 'LNG carrier', speed: '14.6 kn', note: 'Eastbound — outbound to the Gulf of Oman' });
    vessels += 4;

    // 2) "Toll-route" LPG carrier hugging the northern (Iranian-waters) coastline.
    const toll = [[56.5, 26.55, -6], [56.0, 26.75, -3], [55.4, 26.95, 0], [54.9, 26.8, 3], [54.5, 26.6, 6]];
    addVessel('PERSIS CARRIER [DEMO]', toll, C.shipToll,
      { flag: 'Iran', type: 'LPG carrier', speed: '10.2 kn',
        note: 'Hugging northern coast — illustrative "IRGC escort toll-booth" storyline' });
    vessels += 1;

    // 3) Dark vessel — AIS goes silent between now−2h and now+1h. Availability is two
    //    intervals; a separate dashed polyline visualizes the gap while scrubbing.
    const darkWp = [[56.2, 25.6, -6], [55.7, 26.35, -2], [54.9, 26.3, 1], [54.0, 25.9, 6]];
    const darkAvail = new Cesium.TimeIntervalCollection([
      new Cesium.TimeInterval({ start: start, stop: at(-2) }),
      new Cesium.TimeInterval({ start: at(1), stop: stop }),
    ]);
    addVessel('SHADOW MERIDIAN [DEMO]', darkWp, C.shipDark,
      { flag: 'Unregistered', type: 'Suspected sanctioned crude tanker', speed: '9.7 kn',
        note: 'AIS dark gap now−2h → now+1h (illustrative dark-vessel detection)' }, darkAvail);
    vessels += 1;

    // Static dashed track across the gap: last-seen (h=−2) → reacquired (h=+1).
    const lastSeen = darkWp[1];   // 55.7, 26.35 at h=−2
    const reacq = darkWp[2];      // 54.9, 26.3 at h=+1
    const midLon = (lastSeen[0] + reacq[0]) / 2;
    const midLat = (lastSeen[1] + reacq[1]) / 2;
    shipsDS.entities.add({
      name: 'AIS DARK GAP [DEMO]',
      position: Cesium.Cartesian3.fromDegrees(midLon, midLat, 0),
      polyline: {
        positions: Cesium.Cartesian3.fromDegreesArray([lastSeen[0], lastSeen[1], reacq[0], reacq[1]]),
        width: 2,
        material: new Cesium.PolylineDashMaterialProperty({ color: lib.cz(C.shipDark), dashLength: 16 }),
      },
      label: labelGraphic({
        text: 'AIS DARK GAP [DEMO]',
        fillColor: lib.cz(C.shipDark),
        pixelOffset: new Cesium.Cartesian2(0, 0),
        verticalOrigin: Cesium.VerticalOrigin.MIDDLE,
      }),
      description: describe('—', 'Dark-vessel track gap', 'n/a',
        'Straight-line inference between last-seen and reacquired AIS fixes.'),
    });

    // 4) Idle tankers clustered at the Fujairah anchorage — the "floating parking lot".
    const idle = [
      [56.52, 25.12], [56.58, 25.18], [56.63, 25.15],
      [56.55, 25.22], [56.61, 25.10], [56.67, 25.20],
    ];
    idle.forEach(([lon, lat], i) => {
      shipsDS.entities.add({
        name: `FUJAIRAH IDLE ${i + 1} [DEMO]`,
        position: Cesium.Cartesian3.fromDegrees(lon, lat, 0),
        billboard: {
          image: glyph,
          color: lib.cz(C.shipIdle),
          scale: 0.85,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
        },
        // No label — six identical anchored tankers would just stack. The
        // vessel is still identified via its InfoBox on click.
        description: describe('Various', 'Tanker at anchor', '0.0 kn',
          'Idle at Fujairah anchorage ("floating parking lot").'),
      });
    });
    vessels += idle.length;

    ui.setStatus('ships', `${vessels} vessels [DEMO]`, 'ok');
  } catch (err) {
    console.error('[godsigh] ships demo fleet failed:', err);
    ui.setStatus('ships', 'demo fleet error', 'err');
  }

  // ---- Optional live AIS (guarded, default off) ----
  // Only attempts a socket if a key is configured. On the first position report
  // the demo fleet is hidden in favor of live vessels. Never blocks init.
  if (CONFIG.AISSTREAM_API_KEY) {
    startLiveAIS();
  }

  function startLiveAIS() {
    try {
      aisDS = new Cesium.CustomDataSource('ais-live');
      viewer.dataSources.add(aisDS);
      const byMMSI = new Map(); // MMSI -> entity
      const CAP = 500;

      const ws = new WebSocket('wss://stream.aisstream.io/v0/stream');
      ws.onopen = () => {
        ws.send(JSON.stringify({
          APIKey: CONFIG.AISSTREAM_API_KEY,
          BoundingBoxes: [[[10, 30], [35, 70]]],
          FilterMessageTypes: ['PositionReport'],
        }));
      };
      ws.onmessage = (evt) => {
        let msg;
        try { msg = JSON.parse(evt.data); } catch { return; }
        // Verify shape at runtime rather than trusting field names blindly.
        const meta = msg.MetaData || {};
        const pr = msg.Message && msg.Message.PositionReport;
        if (!pr) return;
        const mmsi = meta.MMSI != null ? String(meta.MMSI) : String(pr.UserID);
        const lat = pr.Latitude, lon = pr.Longitude;
        if (mmsi == null || !isFinite(lat) || !isFinite(lon)) return;

        if (!liveActive) {
          liveActive = true;
          shipsDS.show = false; // hide demo fleet once real data flows
          // Leave the HUD checkbox checked: the ships layer is still visible
          // (now live vessels), and the toggle now controls that live fleet.
        }

        const pos = Cesium.Cartesian3.fromDegrees(lon, lat, 0);
        const name = (meta.ShipName || `MMSI ${mmsi}`).trim();
        let ent = byMMSI.get(mmsi);
        if (ent) {
          ent.position = pos;
          if (isFinite(pr.Cog)) ent.billboard.rotation = lib.headingToRotation(pr.Cog);
          // Move to the end so keys().next() yields a true least-recently-updated
          // victim on eviction (Map preserves insertion order).
          byMMSI.delete(mmsi);
          byMMSI.set(mmsi, ent);
        } else {
          if (byMMSI.size >= CAP) {
            const oldest = byMMSI.keys().next().value; // least-recently-updated
            const e = byMMSI.get(oldest);
            if (e) aisDS.entities.remove(e);
            byMMSI.delete(oldest);
          }
          ent = aisDS.entities.add({
            name,
            position: pos,
            billboard: {
              image: glyph,
              color: lib.cz(C.shipNormal),
              rotation: isFinite(pr.Cog) ? lib.headingToRotation(pr.Cog) : 0,
              scale: 0.8,
              disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            label: labelGraphic({ text: name, scale: 0.7 }),
            description: `<p>Live AIS — MMSI ${mmsi}</p>`,
          });
          byMMSI.set(mmsi, ent);
        }
        ui.setStatus('ships', `${byMMSI.size} live AIS`, 'ok');
      };
      ws.onerror = () => ui.setStatus('ships', 'live AIS error — demo fleet', 'warn');
      ws.onclose = () => { if (liveActive) ui.setStatus('ships', 'live AIS closed', 'warn'); };
    } catch (err) {
      console.error('[godsigh] live AIS init failed:', err);
    }
  }

  return { id: 'ships' };
}
