// Solar System mode (Wave 4) — "Travel to…". Leaves the Earth OSINT globe and
// renders a heliocentric orrery: the Sun + 8 planets as spheres textured with
// real (CC BY 4.0, self-hosted) imagery, at their TRUE current positions
// (Schlyter series), with orbits. Click a destination to fly there.
//
// Cesium's globe is Earth-at-origin, so the mode HIDES the globe + all Earth
// layers and places the Sun at the scene origin; exiting restores everything.

export default function initSolarSystem(ctx) {
  const { viewer, Cesium, lib } = ctx;
  const scene = viewer.scene;

  // ---- astronomy (Schlyter low-precision, heliocentric ecliptic AU) ----
  const D2R = Math.PI / 180;
  const rev = (x) => x - Math.floor(x / 360) * 360;
  const ELEM = {
    Mercury: (d) => ({ N: 48.3313 + 3.24587e-5 * d, i: 7.0047 + 5.00e-8 * d, w: 29.1241 + 1.01444e-5 * d, a: 0.387098, e: 0.205635 + 5.59e-10 * d, M: 168.6562 + 4.0923344368 * d }),
    Venus: (d) => ({ N: 76.6799 + 2.46590e-5 * d, i: 3.3946 + 2.75e-8 * d, w: 54.8910 + 1.38374e-5 * d, a: 0.723330, e: 0.006773 - 1.302e-9 * d, M: 48.0052 + 1.6021302244 * d }),
    Mars: (d) => ({ N: 49.5574 + 2.11081e-5 * d, i: 1.8497 - 1.78e-8 * d, w: 286.5016 + 2.92961e-5 * d, a: 1.523688, e: 0.093405 + 2.516e-9 * d, M: 18.6021 + 0.5240207766 * d }),
    Jupiter: (d) => ({ N: 100.4542 + 2.76854e-5 * d, i: 1.3030 - 1.557e-7 * d, w: 273.8777 + 1.64505e-5 * d, a: 5.20256, e: 0.048498 + 4.469e-9 * d, M: 19.8950 + 0.0830853001 * d }),
    Saturn: (d) => ({ N: 113.6634 + 2.38980e-5 * d, i: 2.4886 - 1.081e-7 * d, w: 339.3939 + 2.97661e-5 * d, a: 9.55475, e: 0.055546 - 9.499e-9 * d, M: 316.9670 + 0.0334442282 * d }),
    Uranus: (d) => ({ N: 74.0005 + 1.3978e-5 * d, i: 0.7733 + 1.9e-8 * d, w: 96.6612 + 3.0565e-5 * d, a: 19.18171 - 1.55e-8 * d, e: 0.047318 + 7.45e-9 * d, M: 142.5905 + 0.011725806 * d }),
    Neptune: (d) => ({ N: 131.7806 + 3.0173e-5 * d, i: 1.7700 - 2.55e-7 * d, w: 272.8461 - 6.027e-6 * d, a: 30.05826 + 3.313e-8 * d, e: 0.008606 + 2.15e-9 * d, M: 260.2471 + 0.005995147 * d }),
  };
  const dayNumber = (t) => Cesium.JulianDate.toDate(t).getTime() / 86400000 - 10956.0;
  function helio(name, d) {
    const el = ELEM[name](d);
    const N = el.N * D2R, i = el.i * D2R, w = el.w * D2R, e = el.e, M = rev(el.M) * D2R;
    let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
    for (let k = 0; k < 6; k++) E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
    const xv = el.a * (Math.cos(E) - e), yv = el.a * Math.sqrt(1 - e * e) * Math.sin(E);
    const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), u = v + w;
    return {
      x: r * (Math.cos(N) * Math.cos(u) - Math.sin(N) * Math.sin(u) * Math.cos(i)),
      y: r * (Math.sin(N) * Math.cos(u) + Math.cos(N) * Math.sin(u) * Math.cos(i)),
      z: r * Math.sin(u) * Math.sin(i),
    };
  }
  function earthHelio(d) {
    const w = 282.9404 + 4.70935e-5 * d, e = 0.016709 - 1.151e-9 * d, M = rev(356.0470 + 0.9856002585 * d) * D2R;
    let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
    const xv = Math.cos(E) - e, yv = Math.sqrt(1 - e * e) * Math.sin(E);
    const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), lon = v + w * D2R;
    return { x: -r * Math.cos(lon), y: -r * Math.sin(lon), z: 0 }; // Earth = −(Sun geocentric)
  }

  // ---- scaling (tuned for a readable orrery, not true scale) ----
  const AU_UNIT = 4.0e7;                         // ~Earth orbit display radius
  const distScale = (au) => AU_UNIT * Math.pow(au, 0.72); // gentle outer compression
  const BODY_UNIT = 3.4e6;                        // exaggerated so bodies read at orrery scale
  const bodyRadius = (km, isSun) => isSun ? 1.6e7 : BODY_UNIT * Math.pow(km / 6371, 0.4);
  function scaledPos(vec) {                       // ecliptic AU {x,y,z} → scene Cartesian3
    const au = Math.hypot(vec.x, vec.y, vec.z) || 1e-6;
    const s = distScale(au) / au;
    return new Cesium.Cartesian3(vec.x * s, vec.y * s, vec.z * s);
  }

  const BODIES = [
    { key: 'sun', name: '☉ Sun', tex: 'textures/2k_sun.jpg', km: 696000, sun: true },
    { key: 'mercury', name: '☿ Mercury', tex: 'textures/2k_mercury.jpg', km: 2439.7 },
    { key: 'venus', name: '♀ Venus', tex: 'textures/2k_venus_atmosphere.jpg', km: 6051.8 },
    { key: 'earth', name: '🜨 Earth', tex: 'textures/2k_earth_daymap.jpg', km: 6371 },
    { key: 'mars', name: '♂ Mars', tex: 'textures/2k_mars.jpg', km: 3389.5 },
    { key: 'jupiter', name: '♃ Jupiter', tex: 'textures/2k_jupiter.jpg', km: 69911 },
    { key: 'saturn', name: '♄ Saturn', tex: 'textures/2k_saturn.jpg', km: 58232, ring: [1.3, 2.3] },
    { key: 'uranus', name: '⛢ Uranus', tex: 'textures/2k_uranus.jpg', km: 25362 },
    { key: 'neptune', name: '♆ Neptune', tex: 'textures/2k_neptune.jpg', km: 24622 },
  ];

  const ds = new Cesium.CustomDataSource('solarsystem');
  ds.show = false;
  viewer.dataSources.add(ds);
  const bodyEntities = {}; // key → entity (for travel-to framing)

  function posOf(key, d) {
    if (key === 'sun') return new Cesium.Cartesian3(0, 0, 0);
    if (key === 'earth') return scaledPos(earthHelio(d));
    return scaledPos(helio(key.charAt(0).toUpperCase() + key.slice(1), d));
  }

  // Orthographic planet disc from an equirectangular texture, drawn onto a canvas
  // used as a BILLBOARD image. Billboards render instantly (no async worker
  // geometry), always face the camera, and are unlit — reliable where a 3D
  // ellipsoid + hidden globe is not. Still reads as a real 3D planet.
  const discCache = {};
  function makeDisc(b) {
    if (discCache[b.key]) return discCache[b.key];
    const S = 256, R = S / 2, cv = document.createElement('canvas'); cv.width = cv.height = S;
    const g = cv.getContext('2d');
    discCache[b.key] = cv;                 // return now; fill in once the texture loads
    const img = new Image();
    img.onload = () => {
      const iw = img.width, ih = img.height, src = document.createElement('canvas');
      src.width = iw; src.height = ih; const sg = src.getContext('2d'); sg.drawImage(img, 0, 0);
      const sd = sg.getImageData(0, 0, iw, ih).data;
      const out = g.createImageData(S, S), od = out.data;
      const lx = -0.5, ly = 0.55, lz = 0.67; // light from upper-left-front
      for (let y = 0; y < S; y++) for (let x = 0; x < S; x++) {
        const nx = (x - R) / R, ny = (R - y) / R, r2 = nx * nx + ny * ny, oi = (y * S + x) * 4;
        if (r2 > 1) { od[oi + 3] = 0; continue; }
        const nz = Math.sqrt(1 - r2);
        const lat = Math.asin(ny), lon = Math.atan2(nx, nz);
        let u = lon / (2 * Math.PI) + 0.5; u -= Math.floor(u);
        const vv = 0.5 - lat / Math.PI;
        const si = (Math.min(ih - 1, (vv * ih) | 0) * iw + Math.min(iw - 1, (u * iw) | 0)) * 4;
        const shade = b.sun ? 1 : Math.max(0.14, nx * lx + ny * ly + nz * lz) * 0.85 + 0.15;
        od[oi] = sd[si] * shade; od[oi + 1] = sd[si + 1] * shade; od[oi + 2] = sd[si + 2] * shade; od[oi + 3] = 255;
      }
      g.putImageData(out, 0, 0);
      // The billboard uploaded a blank canvas at creation time; re-assign the now
      // -drawn canvas so Cesium re-uploads the real planet image to the atlas.
      if (bodyEntities[b.key]) bodyEntities[b.key].billboard.image = cv;
    };
    img.src = b.tex;
    return cv;
  }

  function build(d) {
    ds.entities.removeAll();
    for (const b of BODIES) {
      const pos = posOf(b.key, d);
      const r = bodyRadius(b.km, b.sun);
      // Billboard pixel size = the body's angular size at a reference distance,
      // grown as you approach so a planet fills the view up close.
      const px = Math.max(10, (r * 2) / 90000); // ~screen px at ~ the fly-to range
      const ent = ds.entities.add({
        name: b.name,
        position: pos,
        billboard: {
          image: makeDisc(b),
          scaleByDistance: new Cesium.NearFarScalar(r * 3, px * 8, 2.5e9, Math.max(3, px * 0.05)),
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
        },
        label: {
          text: b.name, font: '13px "Segoe UI", system-ui, sans-serif',
          fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.fromCssColorString('#05080b'),
          outlineWidth: 3, style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 12),
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
        },
      });
      bodyEntities[b.key] = ent;

      // Orbit (skip the Sun): trace one full revolution by sweeping the anomaly.
      // arcType NONE = straight segments in space (not draped on an ellipsoid).
      if (b.key !== 'sun') {
        const pts = [];
        for (let a = 0; a <= 360; a += 3) {
          const v = b.key === 'earth' ? earthHelioAt(d, a) : helioAt(b.key, d, a);
          pts.push(scaledPos(v));
        }
        ds.entities.add({
          polyline: { positions: pts, width: 1.5, arcType: Cesium.ArcType.NONE, material: lib.cz('#6a86c0', 0.6) },
        });
      }
    }
  }
  // Orbit sampling: recompute a body's heliocentric position at mean-anomaly `deg`.
  function helioAt(key, d, deg) {
    const name = key.charAt(0).toUpperCase() + key.slice(1);
    const el = ELEM[name](d);
    const N = el.N * D2R, i = el.i * D2R, w = el.w * D2R, e = el.e, M = deg * D2R;
    let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
    for (let k = 0; k < 6; k++) E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
    const xv = el.a * (Math.cos(E) - e), yv = el.a * Math.sqrt(1 - e * e) * Math.sin(E);
    const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), u = v + w;
    return { x: r * (Math.cos(N) * Math.cos(u) - Math.sin(N) * Math.sin(u) * Math.cos(i)), y: r * (Math.sin(N) * Math.cos(u) + Math.cos(N) * Math.sin(u) * Math.cos(i)), z: r * Math.sin(u) * Math.sin(i) };
  }
  function earthHelioAt(d, deg) {
    const w = 282.9404 + 4.70935e-5 * d, e = 0.016709 - 1.151e-9 * d, M = deg * D2R;
    let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
    const xv = Math.cos(E) - e, yv = Math.sqrt(1 - e * e) * Math.sin(E);
    const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), lon = v + w * D2R;
    return { x: -r * Math.cos(lon), y: -r * Math.sin(lon), z: 0 };
  }

  // ---- mode enter / exit ----
  let active = false;
  const saved = { dataSources: [], primitives: [], globe: true, atmo: true, sky: true, animate: true, light: null, far: 5e8, near: 0.1, collide: true };

  function enter() {
    if (active) return;
    active = true;
    const d = dayNumber(viewer.clock.currentTime);
    build(d);

    // Hide every Earth layer: entity data sources + billboard primitives.
    saved.dataSources = [];
    for (let i = 0; i < viewer.dataSources.length; i++) {
      const s = viewer.dataSources.get(i);
      if (s === ds) continue;
      saved.dataSources.push([s, s.show]); s.show = false;
    }
    saved.primitives = [];
    for (let i = 0; i < scene.primitives.length; i++) {
      const p = scene.primitives.get(i);
      if (p && typeof p.length === 'number' && 'show' in p) { saved.primitives.push([p, p.show]); p.show = false; }
    }
    saved.globe = scene.globe.show; scene.globe.show = false;
    saved.atmo = scene.globe.showGroundAtmosphere; scene.globe.showGroundAtmosphere = false;
    saved.sky = scene.skyAtmosphere.show; scene.skyAtmosphere.show = false;
    saved.animate = viewer.clock.shouldAnimate; viewer.clock.shouldAnimate = false; // freeze the snapshot
    // The scene spans ~1e9 m — well past Cesium's default 5e8 far plane, which
    // would clip every body. Widen it (and drop the surface-collision limit so
    // you can fly far out); restored on exit.
    saved.far = scene.camera.frustum.far; scene.camera.frustum.far = 6e9;
    // Push the near plane out too: with a 6e9 far plane, a 0.1 near destroys depth
    // precision and the starfield/scene stop drawing. 1e5 is fine at orrery scale.
    saved.near = scene.camera.frustum.near; scene.camera.frustum.near = 1e5;
    saved.collide = scene.screenSpaceCameraController.enableCollisionDetection;
    scene.screenSpaceCameraController.enableCollisionDetection = false;
    scene.screenSpaceCameraController.maximumZoomDistance = 6e9;

    // Even "planetarium" lighting: a headlight from the camera so no half-dark planets.
    saved.light = scene.light;
    scene.light = new Cesium.DirectionalLight({ direction: scene.camera.directionWC.clone(), intensity: 2.2 });
    headlight = scene.preRender.addEventListener((s) => {
      scene.light.direction = Cesium.Cartesian3.clone(s.camera.directionWC, scene.light.direction);
    });

    ds.show = true;
    document.getElementById('hud').style.display = 'none';
    panel.style.display = 'block';
    document.getElementById('ss-btn').classList.add('active');

    // Fly out to a clean top-down-ish view of the system (orthonormal dir/up —
    // Cesium needs those, and lookAt at the geocentre is degenerate).
    viewer.camera.flyTo({
      destination: new Cesium.Cartesian3(0.7e8, 0.3e8, 7.5e8),
      orientation: { direction: new Cesium.Cartesian3(0, 0, -1), up: new Cesium.Cartesian3(0, 1, 0) },
      duration: 2.5,
    });
  }

  let headlight = null;
  function exit() {
    if (!active) return;
    active = false;
    ds.show = false;
    for (const [s, show] of saved.dataSources) s.show = show;
    for (const [p, show] of saved.primitives) p.show = show;
    scene.globe.show = saved.globe;
    scene.globe.showGroundAtmosphere = saved.atmo;
    scene.skyAtmosphere.show = saved.sky;
    viewer.clock.shouldAnimate = saved.animate;
    if (headlight) { headlight(); headlight = null; }
    if (saved.light) scene.light = saved.light;
    scene.camera.frustum.far = saved.far;
    if (saved.near != null) scene.camera.frustum.near = saved.near;
    scene.screenSpaceCameraController.enableCollisionDetection = saved.collide;
    scene.screenSpaceCameraController.maximumZoomDistance = Infinity;
    document.getElementById('hud').style.display = '';
    panel.style.display = 'none';
    document.getElementById('ss-btn').classList.remove('active');
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(ctx.CONFIG.camera.lon, ctx.CONFIG.camera.lat, ctx.CONFIG.camera.height),
      duration: 2.0,
    });
  }

  function travelTo(key) {
    const ent = bodyEntities[key];
    if (!ent) return;
    if (key === 'earth') { // Earth = home; offer to drop back to the OSINT globe
      exit();
      return;
    }
    viewer.flyTo(ent, { duration: 2.5, offset: new Cesium.HeadingPitchRange(0, -0.3, bodyRadius(BODIES.find((b) => b.key === key).km) * 6) });
  }

  // ---- UI: enter button + destination panel ----
  const btn = document.createElement('button');
  btn.id = 'ss-btn';
  btn.type = 'button';
  btn.textContent = '◉ Solar System';
  btn.title = 'Leave Earth and travel the solar system';
  btn.addEventListener('click', () => (active ? exit() : enter()));
  document.body.appendChild(btn);

  const panel = document.createElement('aside');
  panel.id = 'ss-panel';
  panel.style.display = 'none';
  panel.innerHTML = `<div class="ss-title">TRAVEL TO</div><div class="ss-dests"></div>
    <div class="ss-foot">Positions computed live · textures © <a href="https://www.solarsystemscope.com/" target="_blank" rel="noopener">Solar System Scope</a> CC BY 4.0</div>`;
  const dests = panel.querySelector('.ss-dests');
  for (const b of BODIES) {
    const d = document.createElement('button');
    d.className = 'ss-dest';
    d.textContent = b.name;
    d.addEventListener('click', () => travelTo(b.key));
    dests.appendChild(d);
  }
  const back = document.createElement('button');
  back.className = 'ss-dest ss-back';
  back.textContent = '↩ Return to Earth (OSINT)';
  back.addEventListener('click', exit);
  panel.appendChild(back);
  document.body.appendChild(panel);

  return { enter, exit, isActive: () => active };
}
