// GODSIGH bootstrap: viewer, basemaps, clock, live/scrub gating, picking, and
// dynamic layer loading.
//
// LAYER CONTRACT — each module in js/layers/*.js default-exports a factory:
//
//   export default function create(ctx) {
//     const { viewer, CONFIG, lib, ui, Cesium, start, stop, now } = ctx;
//     // register HUD row(s) with ui.addLayer(id, name, defaultOn, onToggle)
//     // report progress/failure with ui.setStatus(id, text, 'ok'|'warn'|'err')
//     return {
//       id: 'x',                              // string
//       onClockTick(currentTime, isLive) {},  // optional; called ~1/s
//       handlePick(picked) { return false; }, // optional; true if it consumed the click
//       clearPick() {},                       // optional; hide any custom overlay
//     };
//   }

import { CONFIG } from './config.js';
import * as lib from './lib.js';
import * as ui from './ui.js';

const Cesium = window.Cesium;

// ---- clock window: now-6h .. now+6h ----
const now = Cesium.JulianDate.now();
const start = Cesium.JulianDate.addHours(now, -CONFIG.windowHours, new Cesium.JulianDate());
const stop = Cesium.JulianDate.addHours(now, CONFIG.windowHours, new Cesium.JulianDate());

const viewer = new Cesium.Viewer('cesiumContainer', {
  baseLayer: false, // we manage imagery ourselves — no Cesium ion token
  baseLayerPicker: false,
  geocoder: false,
  sceneModePicker: false,
  navigationHelpButton: false,
  homeButton: false,
  fullscreenButton: false,
  timeline: true,
  animation: true,
  infoBox: true,
  selectionIndicator: true,
  shouldAnimate: true,
});

// ---- basemaps ----
const imagery = viewer.imageryLayers;
const darkLayer = imagery.addImageryProvider(new Cesium.UrlTemplateImageryProvider({
  url: CONFIG.basemaps.dark.url,
  subdomains: CONFIG.basemaps.dark.subdomains,
  credit: CONFIG.basemaps.dark.credit,
  maximumLevel: CONFIG.basemaps.dark.maximumLevel,
}));
const photoLayer = imagery.addImageryProvider(new Cesium.UrlTemplateImageryProvider({
  url: CONFIG.basemaps.photo.url,
  credit: CONFIG.basemaps.photo.credit,
  maximumLevel: CONFIG.basemaps.photo.maximumLevel,
}));
photoLayer.show = false;

let currentMode = 'data';
function setMode(mode) {
  const photo = mode === 'photo';
  currentMode = photo ? 'photo' : 'data';
  photoLayer.show = photo;
  darkLayer.show = !photo;
  // Data Mode = flat high-contrast asset map (always readable).
  // Photo Mode = realistic imagery with a live day/night terminator.
  viewer.scene.globe.enableLighting = photo;
  viewer.scene.globe.showGroundAtmosphere = photo;
  document.getElementById('mode-data').classList.toggle('active', !photo);
  document.getElementById('mode-photo').classList.toggle('active', photo);
}
document.getElementById('mode-data').addEventListener('click', () => setMode('data'));
document.getElementById('mode-photo').addEventListener('click', () => setMode('photo'));

// ---- clock configuration ----
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = now.clone();
viewer.clock.clockRange = Cesium.ClockRange.CLAMPED;
viewer.clock.multiplier = 1;
viewer.timeline.zoomTo(start, stop);

// ---- shareable URL state (#c=lon,lat,height,heading,pitch&m=d|p&L=on,ids&t=offsetSec) ----
function clampToWindow(jd) {
  if (Cesium.JulianDate.lessThan(jd, start)) return start.clone();
  if (Cesium.JulianDate.greaterThan(jd, stop)) return stop.clone();
  return jd;
}

let pendingLayerList = null; // ON-layer ids from the hash, applied after layers load

function applyHashState() {
  const state = { cameraApplied: false, modeApplied: false };
  const raw = location.hash.replace(/^#/, '');
  if (!raw) return state;
  const params = new URLSearchParams(raw);

  const c = params.get('c');
  if (c) {
    const parts = c.split(',');
    const p = parts.map(Number);
    // Reject blank tokens too — Number('') is 0, which would silently apply a
    // bogus camera from a truncated hash instead of falling back to the default.
    if (parts.length === 5 && parts.every((s) => s.trim() !== '') && p.every(Number.isFinite)) {
      viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(p[0], p[1], p[2]),
        orientation: { heading: Cesium.Math.toRadians(p[3]), pitch: Cesium.Math.toRadians(p[4]), roll: 0 },
      });
      state.cameraApplied = true;
    }
  }

  const m = params.get('m');
  if (m === 'p') { setMode('photo'); state.modeApplied = true; }
  else if (m === 'd') { setMode('data'); state.modeApplied = true; }

  const t = params.get('t');
  if (t !== null && t !== '' && Number.isFinite(Number(t))) {
    viewer.clock.currentTime = clampToWindow(
      Cesium.JulianDate.addSeconds(now, Number(t), new Cesium.JulianDate()));
  }

  if (params.has('L')) pendingLayerList = params.get('L');
  return state;
}

// Parse once at boot; malformed hashes are ignored silently.
let hashState = { cameraApplied: false, modeApplied: false };
try { hashState = applyHashState(); } catch (err) { /* ignore malformed hash */ }

// Default camera opens on the Gulf — only when the hash didn't provide one.
if (!hashState.cameraApplied) {
  viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(CONFIG.camera.lon, CONFIG.camera.lat, CONFIG.camera.height),
  });
}

function serializeState() {
  const cam = viewer.camera;
  const carto = cam.positionCartographic;
  const lon = Cesium.Math.toDegrees(carto.longitude).toFixed(3);
  const lat = Cesium.Math.toDegrees(carto.latitude).toFixed(3);
  const height = Math.round(carto.height);
  const heading = (((Cesium.Math.toDegrees(cam.heading) % 360) + 360) % 360).toFixed(3);
  const pitch = Cesium.Math.toDegrees(cam.pitch).toFixed(3);
  const mode = currentMode === 'photo' ? 'p' : 'd';
  const onLayers = ui.getLayerIds().filter((id) => ui.getLayerChecked(id));
  const curMs = Cesium.JulianDate.toDate(viewer.clock.currentTime).getTime();
  const offset = Math.round((curMs - Date.now()) / 1000);
  const t = Math.abs(offset) <= CONFIG.liveThresholdSec ? 0 : offset; // 0 = live
  return `#c=${lon},${lat},${height},${heading},${pitch}&m=${mode}&L=${onLayers.join(',')}&t=${t}`;
}

function startHashSync() {
  // Poll once a second; rewrite only when the serialized state actually changed
  // (replaceState keeps it out of history). Covers camera, mode, layers, scrub.
  setInterval(() => {
    const hash = serializeState();
    if (hash !== location.hash) history.replaceState(null, '', hash);
  }, 1000);
}

// ---- context handed to every layer factory ----
const ctx = { viewer, CONFIG, lib, ui, Cesium, start, stop, now };

// ---- dynamic layer loading (order = draw/registration order in the HUD) ----
const LAYER_MODULES = [
  './layers/satellites.js',
  './layers/infra.js',
  './layers/events.js',
  './layers/quakes.js',
  './layers/fires.js',
  './layers/ships.js',
  './layers/aircraft.js',
  './layers/military.js',
];
const activeLayers = [];

async function loadLayers() {
  const results = await Promise.allSettled(LAYER_MODULES.map((p) => import(p)));
  results.forEach((r, i) => {
    if (r.status === 'rejected') {
      console.error(`[godsigh] layer ${LAYER_MODULES[i]} failed to load:`, r.reason);
      return;
    }
    const factory = r.value.default;
    if (typeof factory !== 'function') {
      console.warn(`[godsigh] ${LAYER_MODULES[i]} has no default export factory yet`);
      return;
    }
    try {
      const layer = factory(ctx);
      if (layer) activeLayers.push(layer);
    } catch (err) {
      console.error(`[godsigh] layer ${LAYER_MODULES[i]} threw during init:`, err);
    }
  });
}

// ---- live/scrub gating (throttled ~1/s) ----
let lastTickMs = 0;
viewer.clock.onTick.addEventListener((clock) => {
  const curMs = Cesium.JulianDate.toDate(clock.currentTime).getTime();
  if (lastTickMs && Math.abs(curMs - lastTickMs) < 1000) return;
  lastTickMs = curMs;
  const isLive = Math.abs(curMs - Date.now()) <= CONFIG.liveThresholdSec * 1000;
  ui.setLiveChip(isLive);
  for (const l of activeLayers) l.onClockTick?.(clock.currentTime, isLive);
});

// ---- picking: aircraft (primitives) get a custom overlay; entities use InfoBox ----
const clickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
clickHandler.setInputAction((movement) => {
  const picked = viewer.scene.pick(movement.position);
  let consumed = false;
  for (const l of activeLayers) {
    if (l.handlePick?.(picked)) { consumed = true; break; }
  }
  if (consumed) {
    viewer.selectedEntity = undefined; // suppress InfoBox when a layer handled it
  } else {
    for (const l of activeLayers) l.clearPick?.();
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

if (!hashState.modeApplied) setMode('data');
ui.setLiveChip(true);
loadLayers().then(() => {
  // Restore layer on/off from the hash once every layer has registered its row.
  if (pendingLayerList !== null) {
    const wanted = new Set(pendingLayerList.split(',').filter(Boolean));
    for (const id of ui.getLayerIds()) ui.setLayerChecked(id, wanted.has(id));
  }
  startHashSync();
});

window.__godsigh = { viewer, ctx, activeLayers }; // debug handle
