// =========================================================================
// KALASUTRA AI v3 — MASTER BACKGROUND SERVICE WORKER
// Fully local: Swiss Ephemeris WASM + local modules only. No external API
// calls are made to compute any astrology data (AI report generation is
// the one deliberate exception, gated behind the user's own API key).
// =========================================================================

import SwissEphWrapper from './swisseph.js';
import { getKPHouse, getKP_SSSL, getHouseSignifications, SIGNS, getSign, AVG_SPEEDS, NAK_NAMES } from './kp-core.js';
import { computePanchang } from './panchang-engine.js';
import { getLiveVedhaAndLatta } from './sbc-engine.js';
import { findActiveDasha, generateDashaTree, generatePRDChildren } from './dasha-engine.js';
import { getTransitTaraBala } from './tarabala-engine.js';
import { computeChandraAshtakavarga, getTransitMoonAshtakavargaPoints } from './ashtakavarga-engine.js';

let sweInstance = null;
let isEngineReady = false;
let natalCache = null; // { jd_birth, moonLong, planetSigns, lagnaSign, chandraAVBindus, birthStarName }

// -------------------------------------------------------------------------
// ENGINE MUTEX — Swiss Ephemeris's compiled core uses internal global/
// static state (typical for a C library ported to WASM) and is NOT safe
// to call into from two places at once. The 30s background alarm tick
// and an on-demand popup/overlay request could previously overlap and
// hang the WASM instance indefinitely, with no way to recover short of
// force-reloading the extension. Every function that touches sweInstance
// now runs through this queue, so only one calculation happens at a time.
// -------------------------------------------------------------------------
let engineQueue = Promise.resolve();
function withEngineLock(fn) {
    const run = engineQueue.then(fn, fn);
    engineQueue = run.then(() => {}, () => {}); // never let one failure jam the queue for later callers
    return run;
}
function withTimeout(promise, ms, label) {
    return Promise.race([
        promise,
        new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1000}s`)), ms))
    ]);
}

const FULL_PLANET_NAMES = { Su: "Sun", Mo: "Moon", Ma: "Mars", Me: "Mercury", Ju: "Jupiter", Ve: "Venus", Sa: "Saturn", Ra: "Rahu", Ke: "Ketu" };

// -------------------------------------------------------------------------
// AYANAMSA — resolved once from the user's Engine-tab selection and applied
// identically to BOTH the natal chart (rebuildNatalCache) and every transit
// chart (generateExtensionBaseChart). Previously these used different
// hardcoded sid_modes (1 = Lahiri for natal, 5 = Krishnamurti for transit),
// which silently skewed Dasha/Tara Bala/Ashtakavarga since they compare a
// natal longitude computed under one ayanamsa against a transit longitude
// computed under another.
// -------------------------------------------------------------------------
async function getSidMode() {
    const data = await chrome.storage.local.get('settings');
    const choice = data.settings?.ayanamsa;
    return choice === 'lahiri' ? 1 : 5; // 1 = SE_SIDM_LAHIRI, 5 = SE_SIDM_KRISHNAMURTI (KP default)
}

// -------------------------------------------------------------------------
// PLANET CONFIG — 9 Vedic + 3 Western = 12
// -------------------------------------------------------------------------
function getPlanetConfig(swe) {
    return [
        { id: swe.SE_SUN, sym: "Su" }, { id: swe.SE_MOON, sym: "Mo" },
        { id: swe.SE_MARS, sym: "Ma" }, { id: swe.SE_MERCURY, sym: "Me" },
        { id: swe.SE_JUPITER, sym: "Ju" }, { id: swe.SE_VENUS, sym: "Ve" },
        { id: swe.SE_SATURN, sym: "Sa" }, { id: 10, sym: "Ra" },
        { id: (swe.SE_URANUS !== undefined ? swe.SE_URANUS : 7), sym: "Ur" },
        { id: (swe.SE_NEPTUNE !== undefined ? swe.SE_NEPTUNE : 8), sym: "Ne" },
        { id: (swe.SE_PLUTO !== undefined ? swe.SE_PLUTO : 9), sym: "Pl" }
    ];
}

// -------------------------------------------------------------------------
// SHARED KP CHART CORE — cusps (1-12) + 12 planets with full SgL/StL/SL/SSL
// and A/B/C/D significations. Used for BOTH the live transit chart AND the
// natal (birth) chart, so "Natal KP Data" and "Transit KP Data" in the
// popup are built from the exact same math, just at different moments.
// -------------------------------------------------------------------------
function computeFullKPChart(swe, jd, lat, lon) {
    const flags = swe.SEFLG_SWIEPH | swe.SEFLG_SPEED | swe.SEFLG_SIDEREAL;
    const houses = swe.houses(jd, lat, lon, 'P'.charCodeAt(0));
    const ayanamsa = swe.get_ayanamsa_ut(jd);

    const cusps = {};
    const cuspArray = [];
    for (let i = 1; i <= 12; i++) {
        const cL = (houses.cusps[i] - ayanamsa + 360) % 360;
        cusps[i] = { long: cL, sssl: getKP_SSSL(cL) };
        cuspArray.push(cL);
    }

    const planets = {};
    getPlanetConfig(swe).forEach(p => {
        if (p.sym === 'Ke') return; // derived below from Rahu
        const res = swe.calc_ut(jd, p.id, flags);
        const pLong = res[0];
        const pSpeed = res[3] || 0;
        const isRetro = pSpeed < 0 && p.sym !== 'Su' && p.sym !== 'Mo';
        planets[p.sym] = { long: pLong, kp_house: getKPHouse(pLong, cuspArray), sssl: getKP_SSSL(pLong), speed: pSpeed, isRetro };
    });
    const keL = (planets['Ra'].long + 180) % 360;
    planets['Ke'] = { long: keL, kp_house: getKPHouse(keL, cuspArray), sssl: getKP_SSSL(keL), speed: planets['Ra'].speed, isRetro: true };

    for (const sym in planets) {
        const hs = getHouseSignifications(sym, cusps, planets);
        planets[sym].houseSigStr = hs.str;
        planets[sym].abcd = hs.abcd;
    }
    return { cusps, planets };
}

async function initSwisseph() {
    try {
        sweInstance = new SwissEphWrapper();
        await sweInstance.initSwissEph();
        isEngineReady = true;
        console.log("Kalasutra v3: Swiss Ephemeris Engine Initialized.");

        await rebuildNatalCacheImpl();
        calculateRealTimeTransits();
        chrome.alarms.create('ks_transit_tick', { periodInMinutes: 0.5 });
    } catch (error) {
        console.error("Kalasutra v3: Ephemeris Initialization Error:", error);
        throw error;
    }
}
// Captured so every message handler can `await initReady` before touching
// sweInstance/natalCache. isEngineReady flips true partway through
// initSwisseph (before rebuildNatalCache finishes) — a handler that only
// checked isEngineReady could still race ahead of the natal chart actually
// being built. Awaiting the whole promise closes that gap for good.
let initReady = initSwisseph();

// SELF-HEALING GATE — if the WASM engine's own init call hangs (Swiss
// Ephemeris's compiled core has global state and doesn't handle being
// re-entered cleanly), the ORIGINAL initReady promise would never settle,
// and every future caller awaiting that same promise would be stuck
// forever too — indefinitely, with no way to recover short of manually
// reloading the extension. This gives up on a stuck attempt after a
// bounded wait and kicks off a fresh one instead, so a single bad
// service-worker wake can't permanently wedge everything downstream.
async function ensureEngineReady() {
    try {
        await withTimeout(initReady, 10000, "Engine startup");
    } catch (err) {
        console.error("Kalasutra v3: engine init stuck/failed, retrying:", err.message);
        initReady = initSwisseph();
        await withTimeout(initReady, 10000, "Engine startup (retry)");
    }
}

chrome.alarms.onAlarm.addListener((alarm) => {
    if (alarm.name !== 'ks_transit_tick') return;
    ensureEngineReady()
        .then(() => calculateRealTimeTransits())
        .catch(err => console.error("Kalasutra v3: tick skipped, engine unavailable:", err.message));
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    (async () => {
        try {
            await ensureEngineReady();
        } catch (err) {
            sendResponse({ error: "Engine failed to start: " + err.message });
            return;
        }

        try {
            if (message.action === "REQUEST_ASTRO_UPDATE") {
                calculateRealTimeTransits();
                sendResponse({ status: "Astro update triggered" });
                return;
            }
            if (message.action === "START_INTRADAY_LOOP") {
                // Popup just saved new engine settings (location / ayanamsa / nodes) —
                // recompute immediately instead of waiting for the next 30s alarm tick.
                calculateRealTimeTransits();
                sendResponse({ status: "started" });
                return;
            }
            if (message.action === "REQUEST_CHART_FOR_DATETIME") {
                // Powers the Transit tab's "Calculate" button — computes a one-off
                // chart for an arbitrary date/time, optionally at a different
                // location/timezone than the Engine tab's default (used when the
                // person picks a city via the Transit tab's own city search).
                const [y, m, d] = message.date.split('-').map(Number);
                const [hh, mm] = message.time.split(':').map(Number);
                const customDate = new Date(y, m - 1, d, hh, mm, 0);
                let overrideLoc = null;
                if (message.lat !== undefined && message.lat !== '' && message.lng !== undefined && message.lng !== '') {
                    overrideLoc = { lat: parseFloat(message.lat), lng: parseFloat(message.lng) };
                    if (message.timezone !== undefined && message.timezone !== '') overrideLoc.timezone = parseFloat(message.timezone);
                }
                const data = await generateExtensionBaseChart(customDate, overrideLoc);
                sendResponse({ data });
                return;
            }
            if (message.action === "REQUEST_NATAL_KP_CHART") {
                if (!natalCache || !natalCache.natalChart) { sendResponse({ error: "No birth profile set yet — save your Profile first." }); return; }
                sendResponse({ cusps: natalCache.natalChart.cusps, planets: natalCache.natalChart.planets });
                return;
            }
            if (message.action === "REQUEST_PRD_CHILDREN") {
                // Lazily computes the 5th dasha level (Prana Dasha) for ONE SD node
                // when the user expands it in the tree — see generatePRDChildren().
                if (!natalCache) { sendResponse({ error: "No birth profile set yet." }); return; }
                const data = await chrome.storage.local.get('ks_latest_astro');
                const cusps = data.ks_latest_astro?.cusps;
                const planets = data.ks_latest_astro?.planets;
                const children = generatePRDChildren(message.startJd, message.durDays, message.idx, natalCache.jd_birth, cusps, planets);
                sendResponse({ children });
                return;
            }
            if (message.action === "SAVE_PROFILE") {
                await chrome.storage.local.set({ ks_profile: message.profile });
                await rebuildNatalCache();
                await calculateRealTimeTransits();
                sendResponse({ status: "profile saved" });
                return;
            }
            if (message.action === "REQUEST_DASHA_TREE") {
                if (!natalCache) { sendResponse({ error: "No birth profile set yet." }); return; }
                const data = await chrome.storage.local.get('ks_latest_astro');
                const cusps = data.ks_latest_astro?.cusps;
                const planets = data.ks_latest_astro?.planets;
                const tree = generateDashaTree(natalCache.jd_birth, natalCache.moonLong, cusps, planets);
                sendResponse({ tree });
                return;
            }
        } catch (err) {
            // A handler above threw unexpectedly — still respond, so the
            // client's message channel never hangs open with no reply.
            sendResponse({ error: err.message || String(err) });
        }
    })();
    return true; // keep the message channel open for the async work above
});

// -------------------------------------------------------------------------
// NATAL CHART CACHE — recomputed only when the user's birth profile changes
// (Birth Star/Pada, Lagna, Chandrashtakavarga base, Dasha start point)
// -------------------------------------------------------------------------
async function rebuildNatalCacheImpl() {
    const data = await chrome.storage.local.get('ks_profile');
    const profile = data.ks_profile;
    if (!profile || !profile.birthDate || !profile.birthTime || !isEngineReady) { natalCache = null; return; }

    const swe = sweInstance;
    swe.set_sid_mode(await getSidMode(), 0, 0);
    const flags = swe.SEFLG_SWIEPH | swe.SEFLG_SPEED | swe.SEFLG_SIDEREAL;

    const [by, bm, bd] = profile.birthDate.split('-').map(Number);
    const [bhr, bmin] = profile.birthTime.split(':').map(Number);
    const blat = profile.birthLat !== undefined ? parseFloat(profile.birthLat) : 19.0600;
    const blng = profile.birthLng !== undefined ? parseFloat(profile.birthLng) : 72.8600;
    const tz = profile.timezone !== undefined ? parseFloat(profile.timezone) : 5.5;

    const jd_birth = swe.julday(by, bm, bd, bhr + (bmin / 60), swe.SE_GREG_CAL) - (tz / 24.0);
    const natalChart = computeFullKPChart(swe, jd_birth, blat, blng);
    const lagnaLong = natalChart.cusps[1].long;
    const lagnaSign = getSign(lagnaLong);

    const planetSigns = {};
    for (const sym of ['Su', 'Mo', 'Ma', 'Me', 'Ju', 'Ve', 'Sa']) {
        planetSigns[sym] = getSign(natalChart.planets[sym].long);
    }
    const moonLong = natalChart.planets['Mo'].long;

    const chandraAV = computeChandraAshtakavarga(planetSigns, lagnaSign);
    const moonSSSL = getKP_SSSL(moonLong);

    natalCache = {
        jd_birth, moonLong, lagnaSign, lagnaLong, planetSigns, natalChart,
        chandraAVBindus: chandraAV.bindus,
        birthStarName: `${moonSSSL.NakName} (Pada ${moonSSSL.Pada})`,
        name: profile.name || "", age: profile.age || ""
    };
    await chrome.storage.local.set({ ks_natal_summary: { birthStarName: natalCache.birthStarName, lagna: SIGNS[lagnaSign] } });
}

// -------------------------------------------------------------------------
// MASTER TRANSIT COMPUTATION
// @param {Date} now
// @param {Object|null} overrideLoc  optional { lat, lng, timezone } — used
//   by the Transit tab's one-off "Calculate" button when the person picked
//   a city there instead of using the Engine tab's default location.
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// CONJUNCTIONS + ASPECTS (graha drishti) — classical 9-planet Vedic rules.
// Conjunction = same-sign occupancy. Aspects: every planet casts a full
// (7th house) aspect from its own position; Mars additionally aspects the
// 4th/8th, Jupiter the 5th/9th, Saturn the 3rd/10th. Rahu/Ketu are given
// the same 5th/9th pattern as Jupiter here, following the common
// convention — traditions vary on this point.
// -------------------------------------------------------------------------
const CLASSICAL_PLANETS = ['Su', 'Mo', 'Ma', 'Me', 'Ju', 'Ve', 'Sa', 'Ra', 'Ke'];
const EXTRA_ASPECT_HOUSES = { Ma: [4, 8], Ju: [5, 9], Sa: [3, 10], Ra: [5, 9], Ke: [5, 9] };

function computeConjunctionsAndAspects(planets) {
    const signOf = {};
    CLASSICAL_PLANETS.forEach(sym => { if (planets[sym]) signOf[sym] = getSign(planets[sym].long); });

    const conjunctions = [];
    for (let i = 0; i < CLASSICAL_PLANETS.length; i++) {
        for (let j = i + 1; j < CLASSICAL_PLANETS.length; j++) {
            const a = CLASSICAL_PLANETS[i], b = CLASSICAL_PLANETS[j];
            if (signOf[a] !== undefined && signOf[a] === signOf[b]) conjunctions.push({ a, b, sign: SIGNS[signOf[a]] });
        }
    }

    const aspects = [];
    CLASSICAL_PLANETS.forEach(from => {
        if (signOf[from] === undefined) return;
        const houses = [7, ...(EXTRA_ASPECT_HOUSES[from] || [])];
        houses.forEach(h => {
            const targetSign = (signOf[from] + h - 1) % 12;
            CLASSICAL_PLANETS.forEach(to => {
                if (to === from || signOf[to] === undefined || signOf[to] !== targetSign) return;
                aspects.push({ from, to, house: h });
            });
        });
    });

    return { conjunctions, aspects };
}

async function generateExtensionBaseChartImpl(now, overrideLoc = null) {
    if (!isEngineReady || !sweInstance) throw new Error("Engine not ready");
    const swe = sweInstance;

    const data = await chrome.storage.local.get('settings');
    const settings = data.settings || {};
    const lat = overrideLoc?.lat !== undefined ? overrideLoc.lat : (settings.lat !== undefined ? parseFloat(settings.lat) : 19.0600); // Mumbai default
    const lon = overrideLoc?.lng !== undefined ? overrideLoc.lng : (settings.lng !== undefined ? parseFloat(settings.lng) : 72.8600);
    const timezone = overrideLoc?.timezone !== undefined ? overrideLoc.timezone : (settings.timezone !== undefined ? parseFloat(settings.timezone) : 5.5);

    swe.set_sid_mode(settings.ayanamsa === 'lahiri' ? 1 : 5, 0, 0);

    const y = now.getFullYear(), m = now.getMonth() + 1, d = now.getDate();
    const hr = now.getHours(), min = now.getMinutes(), sec = now.getSeconds();
    const jd = swe.julday(y, m, d, hr + (min / 60) + (sec / 3600), swe.SE_GREG_CAL) - (timezone / 24.0);

    const chart = computeFullKPChart(swe, jd, lat, lon);
    const transitData = { timestamp: now.getTime(), cusps: chart.cusps, planets: chart.planets };

    // Full names + motion classification for the SBC engine
    const planetLongsFullNames = {};
    const planetMotion = {};
    Object.entries(transitData.planets).forEach(([sym, p]) => {
        if (!FULL_PLANET_NAMES[sym]) return;
        planetLongsFullNames[FULL_PLANET_NAMES[sym]] = p.long;
        const avg = AVG_SPEEDS[sym];
        if (sym === 'Su' || sym === 'Mo' || sym === 'Ra' || sym === 'Ke') planetMotion[FULL_PLANET_NAMES[sym]] = "All";
        else if (p.isRetro) planetMotion[FULL_PLANET_NAMES[sym]] = "Retrograde";
        else if (avg && Math.abs(p.speed) > avg * 1.15) planetMotion[FULL_PLANET_NAMES[sym]] = "Fast";
        else planetMotion[FULL_PLANET_NAMES[sym]] = "Normal";
    });

    // --- Panchang ---
    const sunLong = transitData.planets['Su'].long;
    const moonLong = transitData.planets['Mo'].long;
    transitData.panchang = computePanchang({ tjd_ut: jd, y, m, d, sunLong, moonLong, lat, lng: lon, tzOffset: timezone });

    // Backward-compat top-level fields (content.js TradingView overlay reads these directly)
    transitData.tithi = transitData.panchang.tithi;
    transitData.hora = transitData.panchang.hora;
    transitData.choghadiya = transitData.panchang.choghadiya;

    const nIdx = transitData.planets['Mo'].sssl.Nak;
    transitData.moonNakshatraName = NAK_NAMES[nIdx] + " (" + transitData.planets['Mo'].sssl.StL + ")";

    const ascL = transitData.cusps[1].long;
    transitData.lagnaName = `${SIGNS[Math.floor(ascL / 30)]} (${Math.floor(ascL % 30)}°${Math.floor(((ascL % 30) % 1) * 60)}')`;
    const l_ssl = transitData.cusps[1].sssl.SSL;
    transitData.lagnaSSL = l_ssl;
    transitData.lagnaABCD = transitData.planets[l_ssl] ? transitData.planets[l_ssl].abcd : "[-] [-] [-] [-]";

    // --- SBC Vedha + Latta (live) ---
    transitData.sbc = getLiveVedhaAndLatta(planetLongsFullNames, planetMotion);

    // --- Conjunctions + Aspects ---
    const ca = computeConjunctionsAndAspects(transitData.planets);
    transitData.conjunctions = ca.conjunctions;
    transitData.aspects = ca.aspects;

    // --- User Profile derived: Dasha (5-fold), Tara Bala, Ashtakavarga ---
    if (natalCache) {
        transitData.natal = { name: natalCache.name, age: natalCache.age, birthStar: natalCache.birthStarName, lagna: SIGNS[natalCache.lagnaSign] };
        const activeDasha = findActiveDasha(natalCache.jd_birth, natalCache.moonLong, jd);
        // Union of house significations across all 5 running dasha levels
        // (MD/AD/PD/SD/PRD), in level order, deduped — "active significations".
        const seen = new Set();
        const activeSigs = [];
        activeDasha.forEach(level => {
            const sigStr = transitData.planets[level.lord]?.houseSigStr;
            if (!sigStr) return;
            sigStr.split(',').map(s => s.trim()).filter(Boolean).forEach(h => {
                if (!seen.has(h)) { seen.add(h); activeSigs.push(h); }
            });
        });
        transitData.dasha = { active: activeDasha, activeSignifications: activeSigs };
        transitData.taraBala = getTransitTaraBala(natalCache.moonLong, moonLong);
        transitData.ashtakavarga = getTransitMoonAshtakavargaPoints(natalCache.chandraAVBindus, moonLong);
    } else {
        transitData.natal = null;
        transitData.dasha = null;
        transitData.taraBala = null;
        transitData.ashtakavarga = null;
    }

    return transitData;
}

// -------------------------------------------------------------------------
// PUBLIC (LOCKED) ENTRY POINTS — every caller, whether the 30s alarm tick
// or an on-demand popup/overlay message, goes through these. Serializes
// access to sweInstance and bounds each call to 12s so a WASM hang turns
// into a clear error instead of an unrecoverable silent freeze.
// -------------------------------------------------------------------------
function rebuildNatalCache() {
    return withEngineLock(() => withTimeout(rebuildNatalCacheImpl(), 12000, "Building natal chart"));
}
function generateExtensionBaseChart(now, overrideLoc = null) {
    return withEngineLock(() => withTimeout(generateExtensionBaseChartImpl(now, overrideLoc), 12000, "Chart calculation"));
}

async function calculateRealTimeTransits() {
    try {
        const now = new Date();
        const transitData = await generateExtensionBaseChart(now);
        await chrome.storage.local.set({ ks_latest_astro: transitData });
        chrome.runtime.sendMessage({ action: "UPDATE_CHART", data: transitData }).catch(() => {});
        chrome.tabs.query({}, (tabs) => {
            tabs.forEach(tab => {
                chrome.tabs.sendMessage(tab.id, { action: "UPDATE_CHART", data: transitData }).catch(() => {});
            });
        });
    } catch (err) {
        console.error("Kalasutra v3: Astro Engine Crash:", err);
    }
}
