/**
 * Alarm subsystem — Thunderbird/Outlook-style reminder popups.
 *
 * Design decisions (2026-04-24):
 *   - One shared subsystem for calendar events + tasks (+ future mail reminders).
 *   - Popup shows item title, scheduled time, Snooze (5 / 15 / 30 min / 1 hr /
 *     custom), Dismiss. Snooze delays the alarm by the chosen interval; Dismiss
 *     suppresses it permanently.
 *   - Dismissed / snoozed state lives in localStorage (per-device). Google
 *     Calendar's own reminders aren't mutated by this — mailx's popup is a
 *     local convenience, not a reminder-authority replacement.
 *   - Default lead time: 10 min before calendar event start; at task due time
 *     for tasks. Per-event overrides from Google Calendar's `reminders.overrides`
 *     are a follow-up (we don't currently fetch that field).
 *
 * Check cadence: every 30 s while the tab/window is focused. No check when
 * hidden — alarm won't fire until user returns, which matches Thunderbird's
 * behavior (focus-gated). OS-level notifications are a separate follow-up.
 */
import { getCalendarEvents, getTasks } from "../lib/api-client.js";
const DISMISSED_KEY = "mailx-alarm-dismissed"; // { uuid: true }
const SNOOZED_KEY = "mailx-alarm-snoozed"; // { uuid: epoch-ms-end }
const CAL_LEAD_MS = 10 * 60 * 1000; // 10 min before cal event start
const LOOKBACK_MS = 60 * 60 * 1000; // fire past-due alarms from the last 60 min
const LOOKAHEAD_MS = 2 * 60 * 60 * 1000; // poll window: upcoming 2 hr
const POLL_INTERVAL_MS = 30_000;
function loadDismissed() {
    try {
        return JSON.parse(localStorage.getItem(DISMISSED_KEY) || "{}");
    }
    catch {
        return {};
    }
}
function saveDismissed(map) {
    try {
        localStorage.setItem(DISMISSED_KEY, JSON.stringify(map));
    }
    catch { /* private mode */ }
}
function loadSnoozed() {
    try {
        return JSON.parse(localStorage.getItem(SNOOZED_KEY) || "{}");
    }
    catch {
        return {};
    }
}
function saveSnoozed(map) {
    try {
        localStorage.setItem(SNOOZED_KEY, JSON.stringify(map));
    }
    catch { /* */ }
}
/** Prune expired entries so the maps don't grow forever. */
function pruneState(now) {
    const snoozed = loadSnoozed();
    let changed = false;
    for (const [uuid, until] of Object.entries(snoozed)) {
        // Snoozed entries older than 7 days have been fired already (or the
        // event is long past); drop them.
        if (until < now - 7 * 86400_000) {
            delete snoozed[uuid];
            changed = true;
        }
    }
    if (changed)
        saveSnoozed(snoozed);
    // Dismissed is sparse — don't bother pruning aggressively.
}
async function collectDueAlarms(now) {
    const dismissed = loadDismissed();
    const snoozed = loadSnoozed();
    const items = [];
    try {
        const events = await getCalendarEvents(now - LOOKBACK_MS, now + LOOKAHEAD_MS);
        for (const ev of events) {
            if (!ev.uuid)
                continue;
            if (dismissed[ev.uuid])
                continue;
            const alarm = (ev.start || 0) - CAL_LEAD_MS;
            const effective = snoozed[ev.uuid] || alarm;
            if (effective <= now && effective > now - LOOKBACK_MS) {
                items.push({
                    uuid: ev.uuid, kind: "calendar",
                    title: ev.title || "(no title)",
                    alarmMs: effective, whenMs: ev.start || 0,
                    htmlLink: ev.htmlLink,
                });
            }
        }
    }
    catch { /* sidebar will surface API errors; alarm path stays quiet */ }
    try {
        const tasks = await getTasks(false);
        for (const t of tasks) {
            if (!t.uuid || !t.dueMs)
                continue;
            if (dismissed[t.uuid])
                continue;
            const effective = snoozed[t.uuid] || t.dueMs;
            if (effective <= now && effective > now - LOOKBACK_MS) {
                items.push({
                    uuid: t.uuid, kind: "task",
                    title: t.title || "(no title)",
                    alarmMs: effective, whenMs: t.dueMs,
                });
            }
        }
    }
    catch { /* */ }
    return items;
}
function formatWhen(ms) {
    if (!ms)
        return "";
    const d = new Date(ms);
    return d.toLocaleString(undefined, { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
}
let currentPopup = null;
let firedThisSession = new Set();
function showPopup(items) {
    if (currentPopup)
        return; // another popup already up
    if (items.length === 0)
        return;
    const overlay = document.createElement("div");
    overlay.className = "alarm-overlay";
    const panel = document.createElement("div");
    panel.className = "alarm-panel";
    panel.innerHTML = `
        <div class="alarm-head">
            <span class="alarm-icon">⏰</span>
            <span class="alarm-title">${items.length} reminder${items.length > 1 ? "s" : ""}</span>
            <button type="button" class="alarm-close" aria-label="Close">&times;</button>
        </div>
        <div class="alarm-list"></div>
        <div class="alarm-foot">
            <label class="alarm-snooze-label">Snooze for
                <select class="alarm-snooze-sel">
                    <option value="5">5 minutes</option>
                    <option value="15" selected>15 minutes</option>
                    <option value="30">30 minutes</option>
                    <option value="60">1 hour</option>
                    <option value="240">4 hours</option>
                    <option value="1440">1 day</option>
                    <option value="custom">custom…</option>
                </select>
            </label>
            <span style="flex:1"></span>
            <button type="button" class="alarm-btn alarm-btn-snooze">Snooze all</button>
            <button type="button" class="alarm-btn alarm-btn-primary alarm-btn-dismiss">Dismiss all</button>
        </div>
    `;
    const list = panel.querySelector(".alarm-list");
    for (const it of items) {
        const row = document.createElement("div");
        row.className = "alarm-row";
        row.dataset.uuid = it.uuid;
        row.innerHTML = `
            <div class="alarm-row-main">
                <span class="alarm-row-kind">${it.kind === "calendar" ? "📅" : "☑"}</span>
                <span class="alarm-row-title"></span>
                <span class="alarm-row-when"></span>
            </div>
            <div class="alarm-row-actions">
                ${it.htmlLink ? `<button type="button" class="alarm-row-link" data-link="${it.htmlLink}" title="View in browser">↗</button>` : ""}
                <button type="button" class="alarm-row-dismiss" title="Dismiss this one">✕</button>
            </div>
        `;
        row.querySelector(".alarm-row-title").textContent = it.title;
        row.querySelector(".alarm-row-when").textContent = formatWhen(it.whenMs);
        list.appendChild(row);
    }
    overlay.appendChild(panel);
    document.body.appendChild(overlay);
    currentPopup = overlay;
    // Pin the mailx window above other apps while a reminder is showing —
    // matches Thunderbird's reminder dialog behavior. Cleared on close so
    // the window doesn't stay always-on-top once the user has acknowledged.
    // No-op in browser fallback (msgapi only present in WebView2).
    const msgapi = window.msgapi;
    try {
        msgapi?.setAlwaysOnTop?.(true);
    }
    catch { /* */ }
    const snoozeMinutes = () => {
        const sel = panel.querySelector(".alarm-snooze-sel");
        if (sel.value === "custom") {
            const v = prompt("Snooze for how many minutes?", "30");
            const n = parseInt(v || "", 10);
            return Number.isFinite(n) && n > 0 ? n : null;
        }
        return parseInt(sel.value, 10);
    };
    const snoozeAll = (minutes) => {
        const now = Date.now();
        const map = loadSnoozed();
        for (const it of items)
            map[it.uuid] = now + minutes * 60_000;
        saveSnoozed(map);
        for (const it of items)
            firedThisSession.delete(it.uuid);
        close();
    };
    const dismissAll = () => {
        const map = loadDismissed();
        for (const it of items)
            map[it.uuid] = true;
        saveDismissed(map);
        close();
    };
    const close = () => {
        overlay.remove();
        currentPopup = null;
        try {
            msgapi?.setAlwaysOnTop?.(false);
        }
        catch { /* */ }
    };
    panel.querySelector(".alarm-close")?.addEventListener("click", close);
    panel.querySelector(".alarm-btn-snooze")?.addEventListener("click", () => {
        const n = snoozeMinutes();
        if (n)
            snoozeAll(n);
    });
    panel.querySelector(".alarm-btn-dismiss")?.addEventListener("click", dismissAll);
    panel.querySelectorAll(".alarm-row-dismiss").forEach(btn => {
        btn.addEventListener("click", () => {
            const row = btn.closest(".alarm-row");
            const uuid = row?.dataset.uuid;
            if (!uuid)
                return;
            const map = loadDismissed();
            map[uuid] = true;
            saveDismissed(map);
            row.remove();
            if (panel.querySelector(".alarm-list")?.childElementCount === 0)
                close();
        });
    });
    panel.querySelectorAll(".alarm-row-link").forEach(btn => {
        btn.addEventListener("click", () => {
            const url = btn.dataset.link;
            if (!url)
                return;
            const api = window.mailxapi;
            if (api?.openExternal)
                api.openExternal(url);
            else
                window.open(url, "_blank");
        });
    });
    // Escape closes without action (same as X) — user can re-open with next
    // poll tick by not acting on it.
    const onKey = (e) => {
        if (e.key === "Escape") {
            e.stopPropagation();
            close();
            document.removeEventListener("keydown", onKey, true);
        }
    };
    document.addEventListener("keydown", onKey, true);
}
async function pollAlarms() {
    if (document.hidden)
        return; // don't fire while tab hidden
    if (currentPopup)
        return; // user hasn't acknowledged last one
    const now = Date.now();
    pruneState(now);
    const due = await collectDueAlarms(now);
    // Only show items we haven't already fired this session (prevents the
    // popup from immediately re-appearing if the user closes it without
    // dismissing — the item still matches the "due" filter next tick).
    const fresh = due.filter(d => !firedThisSession.has(d.uuid));
    if (fresh.length === 0)
        return;
    for (const d of fresh)
        firedThisSession.add(d.uuid);
    showPopup(fresh);
}
/** Start the alarm poller. Called from app.ts on startup. */
export function startAlarmPoller() {
    if (window.__mailxAlarmPollerRunning)
        return;
    window.__mailxAlarmPollerRunning = true;
    // First check after 5 s so startup isn't jittered by a modal.
    setTimeout(() => { pollAlarms().catch(() => { }); }, 5000);
    setInterval(() => { pollAlarms().catch(() => { }); }, POLL_INTERVAL_MS);
    // Re-check on visibility change — if the user comes back to the tab
    // after an hour away, they should see anything they missed immediately.
    document.addEventListener("visibilitychange", () => {
        if (!document.hidden)
            pollAlarms().catch(() => { });
    });
}
//# sourceMappingURL=alarms.js.map