/**
 * Reminder dismissed/snoozed state — shared across devices.
 *
 * The alarm subsystem (client/components/alarms.ts) keys every reminder by a
 * per-occurrence string (`providerId:startMs[@offsetMs]` for calendar,
 * `taskUuid:dueMs` for tasks). Those keys are built from Google-stable ids,
 * so the SAME occurrence produces the SAME key on every device — which is
 * what makes cross-device sync meaningful: dismiss a reminder on the desktop
 * and the laptop's poller sees the dismissal before it fires its own popup.
 *
 * State lives in `reminders.jsonc` in the shared GDrive folder (same rails
 * as allowlist.jsonc / userdict.csv). Values are epoch-ms timestamps:
 *   dismissed[key] = when the user dismissed it   (retention: 30 days)
 *   snoozed[key]   = fire again after this time   (retention: 7 days past)
 *
 * Merge is per-key max — a dismissal or longer snooze on ANY device wins.
 * Deletions don't propagate (union semantics); the only deletion in the
 * alarm flow is the Open-button re-evaluate, which by construction touches
 * a key that was never pushed as dismissed. Timestamps double as the prune
 * criterion so the file can't grow forever.
 *
 * This module is dependency-free (mailx-types) so the desktop service
 * (mailx-settings) and the Android/web service (mailx-store-web) share one
 * merge implementation.
 */
export const REMINDER_STATE_FILE = "reminders.jsonc";
/** Dismissed entries older than this are pruned — by then the occurrence is
 *  long past the alarm lookback window on every device. */
const DISMISSED_RETENTION_MS = 30 * 86400_000;
/** Snoozed entries whose until-time is this far past are pruned. */
const SNOOZED_RETENTION_MS = 7 * 86400_000;
/** Coerce arbitrary parsed JSON (or the client's localStorage maps, whose
 *  legacy dismissed values are boolean `true`) into a clean ReminderState.
 *  Legacy booleans become `now` so they get a full retention window. */
export function normalizeReminderState(raw, now) {
    const out = { dismissed: {}, snoozed: {} };
    if (raw && typeof raw === "object") {
        for (const [k, v] of Object.entries(raw.dismissed || {})) {
            if (v === true)
                out.dismissed[k] = now;
            else if (Number.isFinite(v) && v > 0)
                out.dismissed[k] = v;
        }
        for (const [k, v] of Object.entries(raw.snoozed || {})) {
            if (Number.isFinite(v) && v > 0)
                out.snoozed[k] = v;
        }
    }
    return out;
}
/** Union merge with per-key max, pruned by retention. Inputs must already
 *  be normalized. Pure — does not mutate its arguments. */
export function mergeReminderStates(a, b, now) {
    const out = { dismissed: {}, snoozed: {} };
    const dismissedCutoff = now - DISMISSED_RETENTION_MS;
    const snoozedCutoff = now - SNOOZED_RETENTION_MS;
    for (const src of [a.dismissed, b.dismissed]) {
        for (const [k, v] of Object.entries(src)) {
            if (v < dismissedCutoff)
                continue;
            if (!(k in out.dismissed) || v > out.dismissed[k])
                out.dismissed[k] = v;
        }
    }
    for (const src of [a.snoozed, b.snoozed]) {
        for (const [k, v] of Object.entries(src)) {
            if (v < snoozedCutoff)
                continue;
            if (!(k in out.snoozed) || v > out.snoozed[k])
                out.snoozed[k] = v;
        }
    }
    return out;
}
/** Deep-equal for change detection so a no-op merge skips the cloud write. */
export function reminderStatesEqual(a, b) {
    const mapsEqual = (x, y) => {
        const xk = Object.keys(x);
        if (xk.length !== Object.keys(y).length)
            return false;
        return xk.every(k => y[k] === x[k]);
    };
    return mapsEqual(a.dismissed, b.dismissed) && mapsEqual(a.snoozed, b.snoozed);
}
//# sourceMappingURL=reminder-state.js.map