// Celestial layer (Wave 3.2) — the sky around the world: the 12 zodiac signs as
// a labeled ecliptic ring, plus the Sun, Moon and naked-eye planets, all placed
// in their TRUE directions and locked to the stars (inertial frame), not spun
// with the globe. A "distant visual" you see when zoomed out.
//
// Everything is computed (no external data): Sun/Moon from Cesium's Simon1994
// model, planets from Schlyter's low-precision series, the zodiac from the
// ecliptic geometry. Directions are correct; distances are COMPRESSED to one
// display radius so they ring the Earth instead of vanishing at true scale.

export default function create(ctx) {
  const { viewer, Cesium, lib, ui } = ctx;
  const scene = viewer.scene;
  const ds = new Cesium.CustomDataSource('celestial');
  viewer.dataSources.add(ds);

  const R = 8.0e7;                 // display radius (~80,000 km) — a ring beyond GEO
  const OBLIQ = Cesium.Math.toRadians(23.4393); // ecliptic obliquity
  const cosE = Math.cos(OBLIQ), sinE = Math.sin(OBLIQ);

  // Shared per-frame inertial→fixed rotation (computed once per tick, reused by
  // every object's position callback).
  let frameIcrf = Cesium.Matrix3.IDENTITY.clone();
  scene.postUpdate.addEventListener((s, time) => {
    const m = Cesium.Transforms.computeIcrfToFixedMatrix(time);
    if (Cesium.defined(m)) frameIcrf = m;
  });

  // Inertial unit direction → fixed-frame position at radius `rad`.
  const scratch = new Cesium.Cartesian3();
  function fixedPos(dirInertial, rad) {
    Cesium.Matrix3.multiplyByVector(frameIcrf, dirInertial, scratch);
    return Cesium.Cartesian3.multiplyByScalar(scratch, rad, new Cesium.Cartesian3());
  }
  // A CallbackProperty position from a function(time) -> inertial unit vector.
  function skyPosition(dirFn, rad = R) {
    return new Cesium.CallbackProperty((time) => fixedPos(dirFn(time), rad), false);
  }

  // Ecliptic (lon λ, lat β=0) → inertial equatorial unit vector.
  function eclipticDir(lonRad, latRad = 0) {
    const cl = Math.cos(lonRad), sl = Math.sin(lonRad), cb = Math.cos(latRad), sb = Math.sin(latRad);
    const xe = cb * cl;
    const ye = cb * sl * cosE - sb * sinE;
    const ze = cb * sl * sinE + sb * cosE;
    return new Cesium.Cartesian3(xe, ye, ze);
  }

  // Days since 2000 Jan 0.0 UTC (Schlyter's epoch).
  // d = JD − 2451543.5 (Schlyter epoch); unix-epoch JD is 2440587.5 → offset 10956.0.
  const dayNumber = (time) => Cesium.JulianDate.toDate(time).getTime() / 86400000 - 10956.0;

  // ---- planets (Schlyter low-precision) ----
  const rev = (x) => x - Math.floor(x / 360) * 360;
  const D2R = Math.PI / 180;
  // Orbital elements as functions of day d (degrees / AU).
  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 }),
  };
  function sunRect(d) { // Sun's geocentric ecliptic rectangular (AU)
    const w = 282.9404 + 4.70935e-5 * d, e = 0.016709 - 1.151e-9 * d, M = rev(356.0470 + 0.9856002585 * d);
    const E = M + (180 / Math.PI) * e * Math.sin(M * D2R) * (1 + e * Math.cos(M * D2R));
    const xv = Math.cos(E * D2R) - e, yv = Math.sqrt(1 - e * e) * Math.sin(E * D2R);
    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) };
  }
  function planetDir(name, time) {
    const d = dayNumber(time);
    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 < 5; 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;
    // Heliocentric ecliptic rectangular.
    const xh = r * (Math.cos(N) * Math.cos(u) - Math.sin(N) * Math.sin(u) * Math.cos(i));
    const yh = r * (Math.sin(N) * Math.cos(u) + Math.cos(N) * Math.sin(u) * Math.cos(i));
    const zh = r * Math.sin(u) * Math.sin(i);
    // Geocentric = heliocentric + (Earth→Sun) = helio + sun-geocentric.
    const s = sunRect(d);
    const xg = xh + s.x, yg = yh + s.y, zg = zh;
    const lon = Math.atan2(yg, xg), lat = Math.atan2(zg, Math.hypot(xg, yg));
    return eclipticDir(lon, lat);
  }
  const sunDir = (time) => {
    const p = Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(time);
    return Cesium.Cartesian3.normalize(p, new Cesium.Cartesian3());
  };
  const moonDir = (time) => {
    const p = Cesium.Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame(time);
    return Cesium.Cartesian3.normalize(p, new Cesium.Cartesian3());
  };

  // ---- build entities ----
  const bodyLabel = (extra) => ({
    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.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -12),
    ...extra,
  });

  const BODIES = [
    { name: '☉ Sun', dir: sunDir, color: '#ffcf40', size: 22, rad: R },
    { name: '☾ Moon', dir: moonDir, color: '#d8d8e0', size: 16, rad: R * 0.9 },
    { name: '☿ Mercury', dir: (t) => planetDir('Mercury', t), color: '#b0a080', size: 7 },
    { name: '♀ Venus', dir: (t) => planetDir('Venus', t), color: '#f5e8c0', size: 10 },
    { name: '♂ Mars', dir: (t) => planetDir('Mars', t), color: '#ff6b4d', size: 8 },
    { name: '♃ Jupiter', dir: (t) => planetDir('Jupiter', t), color: '#e8c090', size: 12 },
    { name: '♄ Saturn', dir: (t) => planetDir('Saturn', t), color: '#e8dcb0', size: 10 },
  ];
  for (const b of BODIES) {
    ds.entities.add({
      name: b.name,
      position: skyPosition(b.dir, b.rad || R),
      point: { pixelSize: b.size, color: lib.cz(b.color), outlineColor: Cesium.Color.BLACK, outlineWidth: 1 },
      label: bodyLabel({ text: b.name, fillColor: lib.cz(b.color) }),
    });
  }

  // Zodiac: 12 sign labels at the centre of each 30° sector + the ecliptic ring.
  const SIGNS = ['♈ Aries', '♉ Taurus', '♊ Gemini', '♋ Cancer', '♌ Leo', '♍ Virgo',
    '♎ Libra', '♏ Scorpio', '♐ Sagittarius', '♑ Capricorn', '♒ Aquarius', '♓ Pisces'];
  for (let k = 0; k < 12; k++) {
    const lon = Cesium.Math.toRadians(k * 30 + 15);
    ds.entities.add({
      name: SIGNS[k],
      position: fixedPosStatic(eclipticDir(lon), R * 1.06), // static ok: sky barely moves in ±6h
      label: bodyLabel({ text: SIGNS[k], font: '12px "Segoe UI", system-ui, sans-serif', fillColor: lib.cz('#8fb7ff'), pixelOffset: new Cesium.Cartesian2(0, 0) }),
    });
  }
  // Ecliptic ring + 30° boundary ticks (rebuilt each frame in the fixed frame).
  ds.entities.add({
    name: 'Ecliptic',
    polyline: {
      width: 1.5,
      material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.25, color: lib.cz('#8fb7ff', 0.5) }),
      positions: new Cesium.CallbackProperty(() => {
        const pts = [];
        for (let a = 0; a <= 360; a += 4) pts.push(fixedPos(eclipticDir(Cesium.Math.toRadians(a)), R * 1.02));
        return pts;
      }, false),
    },
  });

  // A single static-ish snapshot position (zodiac labels don't need per-frame update
  // at this zoom; recompute once so they sit in the current fixed frame).
  function fixedPosStatic(dirInertial, rad) {
    return skyPosition(() => dirInertial, rad); // still callback so it tracks Earth rotation
  }

  ui.addLayer('celestial', 'Sky: zodiac · planets', false, (on) => { ds.show = on; }, 'Space');
  ui.setStatus('celestial', 'Sun · Moon · 5 planets · zodiac', 'ok');
  ds.show = false;

  return { id: 'celestial' };
}
