// What's New at login: shows curated release notes the user hasn't seen yet,
// tracked via users.settings.last_seen_version.
//
// Two sources, merged per tenant (2026-08-22):
//   - core notes:  src/data/whatsnew.json — every tenant, every instance.
//     Also the single source of the `beta` flag (LoginScreen reads it too).
//   - pack notes:  each loaded pack may register { pack, label, url } in
//     PACK_UI.whatsNew (e.g. SPEC → /packs/realtor/whatsnew.json). Fetched
//     ONLY when packEnabled(tenant, pack) — so a CRM tenant never sees SPEC
//     notes, and a CRM instance never even serves the file. Pack items render
//     under a small pack label inside the matching version; a version that
//     exists only in a pack file shows with that file's own title.
// Core whatsnew.json must never mention a pack by name — that's the seam.

// Numeric semver compare: -1 / 0 / 1. Both inputs are trusted x.y.z strings
// (the json is ours; the settings value is validated server-side).
const compareVersions = (a, b) => {
    const pa = a.split('.').map(Number);
    const pb = b.split('.').map(Number);
    for (let i = 0; i < 3; i++) {
        if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
    }
    return 0;
};

const fetchJson = (url) =>
    fetch(url).then(r => r.ok ? r.json() : null).catch(() => null);

// Merge core + pack entry lists into one newest-first list:
// { version, date, title, items: [string], packItems: [{label, items}] }
const mergeNotes = (coreEntries, packFeeds) => {
    const byVersion = new Map();
    for (const e of coreEntries) {
        byVersion.set(e.version, { ...e, packItems: [] });
    }
    for (const { label, entries } of packFeeds) {
        for (const e of entries) {
            if (!byVersion.has(e.version)) {
                // Pack-only version: the pack's own title heads the entry.
                byVersion.set(e.version, { version: e.version, date: e.date, title: e.title, items: [], packItems: [] });
            }
            byVersion.get(e.version).packItems.push({ label, items: e.items });
        }
    }
    return [...byVersion.values()].sort((a, b) => compareVersions(b.version, a.version));
};

const WhatsNewModal = ({ currentUser, tenant }) => {
    const [notes, setNotes] = React.useState(null); // { beta, betaNotice, entries: merged[] } once loaded
    const [dismissed, setDismissed] = React.useState(false);
    const [showGuide, setShowGuide] = React.useState(false);

    // Wait for the tenant payload before deciding which pack feeds to pull —
    // packEnabled() is unlocked-by-default while tenant is null, and we don't
    // want a SPEC feed fetched on a CRM tenant during that window.
    const packKey = tenant ? (tenant.packs || []).join(',') : null;
    React.useEffect(() => {
        if (!tenant) return;
        const feeds = (typeof PACK_UI !== 'undefined' ? PACK_UI.whatsNew || [] : [])
            .filter(f => packEnabled(tenant, f.pack));
        Promise.all([fetchJson('/src/data/whatsnew.json'), ...feeds.map(f => fetchJson(f.url))])
            .then(([core, ...packs]) => {
                if (!core?.entries?.length) return; // release notes are decoration — never block the app over them
                const packFeeds = packs
                    .map((data, i) => ({ label: feeds[i].label, entries: data?.entries || [] }))
                    .filter(f => f.entries.length);
                // Core entries may be tagged `module` (e.g. 'sales_billing'): a
                // tenant with that module OFF never sees them (SPEC lightweight
                // tenants got billing release notes on 2026-08-24 — owner's call).
                const coreEntries = core.entries.filter(e => !e.module || moduleOn(tenant, e.module));
                if (!coreEntries.length) return;
                setNotes({ ...core, entries: mergeNotes(coreEntries, packFeeds) });
            });
    }, [packKey]); // eslint-disable-line react-hooks/exhaustive-deps

    if (!notes || dismissed) return null;

    const lastSeen = currentUser.settings?.last_seen_version;
    const newest = notes.entries[0].version;
    if (lastSeen && compareVersions(newest, lastSeen) <= 0) return null;

    // First-time users get only the latest entry (no history dump); returning
    // users get everything that shipped since they last dismissed, capped so
    // a long-absent user isn't scrolled into next week.
    const unseen = lastSeen
        ? notes.entries.filter(e => compareVersions(e.version, lastSeen) > 0).slice(0, 3)
        : notes.entries.slice(0, 1);

    const dismiss = () => {
        setDismissed(true);
        // Fire-and-forget: a failed save just means the modal shows again
        // next login — annoying, not harmful.
        api.updateMySettings({ last_seen_version: newest }).catch(() => {});
    };

    return (
        <Modal isOpen={true} onClose={dismiss} title="What's New">
            <div className="whatsnew-body">
                {unseen.map(entry => (
                    <div key={entry.version} className="whatsnew-entry">
                        <div className="whatsnew-entry-header">
                            <span className="whatsnew-version">v{entry.version}</span>
                            <span className="whatsnew-title">{entry.title}</span>
                        </div>
                        {entry.items.length > 0 && (
                            <ul>
                                {entry.items.map((item, i) => <li key={i}>{item}</li>)}
                            </ul>
                        )}
                        {entry.packItems.map(p => (
                            <div key={p.label} className="whatsnew-pack">
                                <div className="whatsnew-pack-label">{p.label}</div>
                                <ul>
                                    {p.items.map((item, i) => <li key={i}>{item}</li>)}
                                </ul>
                            </div>
                        ))}
                    </div>
                ))}

                <button
                    className="whatsnew-guide-toggle"
                    onClick={() => setShowGuide(v => !v)}
                >
                    <i className="fas fa-compass"></i> Everything this CRM can do
                    <i className={`fas fa-chevron-${showGuide ? 'up' : 'down'}`}></i>
                </button>
                {showGuide && <FeatureGuide />}

                {notes.beta && (
                    <div className="whatsnew-beta-notice">
                        <i className="fas fa-flask"></i> <strong>Beta:</strong> {notes.betaNotice}
                    </div>
                )}

                <div className="whatsnew-actions">
                    <button className="btn btn-primary" onClick={dismiss}>Got it</button>
                </div>
            </div>
        </Modal>
    );
};
