/*!
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * Copyright (C) 2026 PUDDY Inc.
 *
 * Part of STARMAP "Personal Sky". This file dynamically links the Swiss
 * Ephemeris (Copyright Astrodienst AG), compiled to WebAssembly by
 * swisseph-wasm (GPL-3.0-or-later, Copyright 2024 prolaxu), and therefore
 * forms a COMBINED WORK with it. It is free software: you may redistribute
 * and/or modify it under the GNU Affero General Public License v3.0 or (at
 * your option) any later version, with NO WARRANTY. See the GNU AGPL:
 * https://www.gnu.org/licenses/agpl-3.0.html
 *
 * The Swiss Ephemeris is used UNMODIFIED, under its free AGPL-3.0 option
 * (not the paid commercial license). Complete Corresponding Source for the
 * engine + this code: https://starmap.puddystudios.com/source.html
 */

/* STARMAP - Personal Sky (client-side topocentric recompute).
 * No UI of its own: the page's location box (city / address / zip / device
 * location) calls PersonalSky.setLocation(), and the Topocentric frame then
 * shows the sky for that observer. With no location chosen, the topocentric
 * values are the shipped defaults (New York City). A module worker runs the
 * Swiss Ephemeris WASM engine on-device and refines the topocentric instant
 * of every Moon event of the viewed year: lunations, quarters, eclipses, and
 * Moon ingresses. Coordinates and results persist in localStorage only -
 * nothing leaves the browser.
 *
 * Page contract (inline script in index.html):
 *   PersonalSky.init({getData, rerender, rechrome, base, year, frame})
 *   PersonalSky.setLocation({lat, lon, alt}, label)
 */
'use strict';
(function () {
  var ctx = null, worker = null, computing = false, appliedKey = null, pendingLoc = null;
  var GEO_LS = 'starmap.geo', CACHE_PREFIX = 'starmap.ps.', CACHE_INDEX = 'starmap.ps.keys';
  var TOPO_TYPES = { new_moon: 1, first_quarter: 1, full_moon: 1, last_quarter: 1, solar_eclipse: 1, lunar_eclipse: 1 };

  function storedGeo() { try { return JSON.parse(localStorage.getItem(GEO_LS)); } catch (e) { return null; } }
  function coordsKey(c) { return ctx.year + '.' + (+c.lat).toFixed(5) + ',' + (+c.lon).toFixed(5); }
  function readCache(c) { try { return JSON.parse(localStorage.getItem(CACHE_PREFIX + coordsKey(c))); } catch (e) { return null; } }
  function writeCache(c, map) {
    try {
      var key = CACHE_PREFIX + coordsKey(c);
      localStorage.setItem(key, JSON.stringify(map));
      var idx = []; try { idx = JSON.parse(localStorage.getItem(CACHE_INDEX)) || []; } catch (e) {}
      idx = idx.filter(function (k) { return k !== key; }); idx.push(key);
      while (idx.length > 6) { localStorage.removeItem(idx.shift()); }
      localStorage.setItem(CACHE_INDEX, JSON.stringify(idx));
    } catch (e) {}
  }
  function status(msg) { var s = document.getElementById('loc-status'); if (s) s.textContent = msg || ''; }

  function moonJobs() {
    var jobs = [], data = ctx.getData() || [];
    for (var i = 0; i < data.length; i++) {
      var e = data[i];
      if (TOPO_TYPES[e.type] || (e.type === 'ingress' && e.body === 'Moon')) {
        jobs.push({ id: e.utc + '|' + e.type, type: e.type, utc: e.utc, sign: e.sign || null });
      }
    }
    return jobs;
  }

  function patch(map) {
    var data = ctx.getData() || [], n = 0;
    for (var i = 0; i < data.length; i++) {
      var e = data[i], v = map[e.utc + '|' + e.type];
      if (!v) continue;
      e.utc_topo = v; n++;
    }
    return n;
  }

  function drain() { if (pendingLoc) { var p = pendingLoc; pendingLoc = null; setLocation(p.coords, p.label); } }

  function setLocation(coords, label) {
    if (!ctx) return; // init() will re-apply from storage
    coords = { lat: +coords.lat, lon: +coords.lon, alt: +coords.alt || 0 };
    if (!(coords.lat >= -90 && coords.lat <= 90 && coords.lon >= -180 && coords.lon <= 180)) return;
    var key = coordsKey(coords);
    if (appliedKey === key) return;
    var cached = readCache(coords);
    if (cached) {
      patch(cached);
      appliedKey = key;
      ctx.rechrome(); ctx.rerender();
      return;
    }
    if (computing) { pendingLoc = { coords: coords, label: label }; return; }
    computing = true;
    status('Computing your sky on your device...');
    try {
      if (!worker) worker = new Worker(ctx.base + 'personal-sky-worker.js', { type: 'module' });
    } catch (e) { computing = false; status('Personal Sky engine unavailable in this browser'); return; }
    worker.onmessage = function (ev) {
      computing = false;
      var r = ev.data;
      if (!r.ok) { status('Engine error - Topocentric stays on its default observer'); drain(); return; }
      var map = {};
      for (var i = 0; i < r.results.length; i++) map[r.results[i].id] = r.results[i].utc_topo;
      writeCache(coords, map);
      patch(map);
      appliedKey = key;
      status('Your sky - ' + r.results.length + ' Moon events recomputed on your device in ' + (r.ms / 1000).toFixed(1) + 's');
      ctx.rechrome(); ctx.rerender();
      drain();
    };
    worker.onerror = function () { computing = false; status('Engine failed to load - Topocentric stays on its default observer'); drain(); };
    worker.postMessage({ base: ctx.base, coords: coords, jobs: moonJobs() });
  }

  window.PersonalSky = {
    init: function (c) {
      ctx = c;
      var g = storedGeo();
      if (g) setLocation(g, g.label); // remembered location: personalize immediately (cache-first)
    },
    setLocation: setLocation,
  };
  if (window.__psPendingCtx) { var c = window.__psPendingCtx; delete window.__psPendingCtx; window.PersonalSky.init(c); }
})();
