/**
 * API client — all operations go through the IPC bridge (mailxapi).
 * mailxapi is injected by the launcher (msger on desktop, MAUI on Android).
 */
function getIpc() {
    if (typeof mailxapi !== "undefined" && mailxapi?.isApp)
        return mailxapi;
    if (window.opener?.mailxapi?.isApp)
        return window.opener.mailxapi;
    // Compose iframe — check parent
    if (window.parent?.mailxapi?.isApp)
        return window.parent.mailxapi;
    return null;
}
/** Build a proxy bridge that forwards every method call to the parent window
 *  via postMessage. Used when the compose iframe's own attempt to reach
 *  msger's IPC silently drops messages — empirically, `sendMessage` and
 *  `saveDraft` both hit this (user-visible: "Sending…" spinner forever;
 *  "Draft save failed: mailxapi timeout"). The main window's bridge is
 *  provably fine, so the iframe routes through it. */
function buildRelayBridge() {
    const pending = new Map();
    window.addEventListener("message", (ev) => {
        if (!ev.data || ev.data.type !== "mailx-ipc-result" || !ev.data.id)
            return;
        const entry = pending.get(ev.data.id);
        if (!entry)
            return;
        pending.delete(ev.data.id);
        clearTimeout(entry.timer);
        if (ev.data.ok)
            entry.resolve(ev.data.result);
        else
            entry.reject(new Error(ev.data.error || "parent-relay ipc error"));
    });
    const call = (method, args) => {
        const id = `ipc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                pending.delete(id);
                reject(new Error(`parent-relay timeout: ${method}`));
            }, 120000);
            pending.set(id, { resolve, reject, timer });
            try {
                window.parent.postMessage({ type: "mailx-ipc", id, method, args }, "*");
            }
            catch (e) {
                clearTimeout(timer);
                pending.delete(id);
                reject(e);
            }
        });
    };
    // Proxy: any property access returns a function that forwards to parent.
    // `isApp` / `platform` / other non-function reads return sensible defaults
    // so existing getIpc-style checks still work.
    return new Proxy({}, {
        get(_t, prop) {
            if (prop === "isApp")
                return true;
            if (prop === "platform")
                return window.parent?.mailxapi?.platform || "webview2";
            if (prop === "onEvent") {
                // Event subscription can't be relayed simply — iframes that need
                // events are rare. Fall back to direct parent bridge for onEvent
                // since the subscription path doesn't hit the broken send path.
                return (handler) => window.parent?.mailxapi?.onEvent?.(handler);
            }
            return (...args) => call(prop, args);
        }
    });
}
let cachedRelayBridge = null;
function ipc() {
    // Direct bridge is fine for the top window (main mailx app). The iframe
    // (compose) can't trust its own bridge resolution because msger-routed
    // sendMessage / saveDraft IPCs disappear without trace. So when we're in
    // a child frame with a parent bridge, go through the parent.
    const inIframe = window.parent && window.parent !== window;
    if (inIframe && window.parent?.mailxapi?.isApp) {
        if (!cachedRelayBridge)
            cachedRelayBridge = buildRelayBridge();
        return cachedRelayBridge;
    }
    const bridge = getIpc();
    if (!bridge)
        throw new Error("IPC bridge not available");
    return bridge;
}
// ── Abort controller for message-list requests ──
let messageListAbort = null;
export function abortMessageListRequests() {
    if (messageListAbort) {
        messageListAbort.abort();
        messageListAbort = null;
    }
}
// ── API Methods ──
export function getAccounts() {
    return ipc().getAccounts();
}
export function getFolders(accountId) {
    return ipc().getFolders(accountId);
}
export function getMessages(accountId, folderId, page = 1, pageSize = 50, flaggedOnly = false, sort, sortDir) {
    abortMessageListRequests();
    return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, undefined, flaggedOnly);
}
export function getUnifiedInbox(page = 1, pageSize = 50) {
    abortMessageListRequests();
    return ipc().getUnifiedInbox(page, pageSize);
}
export function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0, includeTrashSpam = false) {
    return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);
}
/** Abort any in-flight server (IMAP) search. Fire-and-forget — the daemon
 *  bumps a generation counter its per-folder loop checks between batches. */
export function cancelServerSearch() {
    return ipc().cancelServerSearch?.();
}
export function getMessage(accountId, uid, allowRemote = false, folderId) {
    return ipc().getMessage(accountId, uid, allowRemote, folderId);
}
export function updateFlags(accountId, uid, flags) {
    return ipc().updateFlags(accountId, uid, flags);
}
export function triggerSync() {
    return ipc().syncAll();
}
export function syncAccount(accountId) {
    return ipc().syncAccount(accountId);
}
export function reauthenticate(accountId) {
    return ipc().reauthenticate(accountId);
}
export function reauthGoogleScopes() {
    return ipc().reauthGoogleScopes();
}
export function getSyncPending() {
    return ipc().getSyncPending();
}
export function getDiagnostics() {
    return ipc().getDiagnostics?.() ?? Promise.resolve([]);
}
/** Account that supplies `feature` data (calendar / tasks / contacts).
 *  Resolution: per-feature primary flag → catch-all `primary` → first account.
 *  Pass e.g. "calendar" to honor `primaryCalendar:true` overrides; omit for
 *  back-compat single-flag behavior. */
export function getPrimaryAccount(feature) {
    return ipc().getPrimaryAccount?.(feature) ?? Promise.resolve(null);
}
// Calendar / Tasks: two-way cache. Reads return local-cached rows; writes
// commit locally and queue a push to Google. Service layer handles drain.
export function getCalendarEvents(fromMs, toMs) {
    return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);
}
/** List the user's selected Google calendars (id, name, color, primary) so
 *  the sidebar can render a checkbox + icon per calendar. */
export function getCalendars() {
    return ipc().getCalendars?.() ?? Promise.resolve([]);
}
export function createCalendarEvent(ev) {
    return ipc().createCalendarEvent?.(ev);
}
export function updateCalendarEvent(uuid, patch) {
    return ipc().updateCalendarEvent?.(uuid, patch);
}
export function deleteCalendarEvent(uuid) {
    return ipc().deleteCalendarEvent?.(uuid);
}
export function getTasks(includeCompleted = false) {
    return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);
}
export function createTask(t) {
    return ipc().createTask?.(t);
}
export function updateTask(uuid, patch) {
    return ipc().updateTask?.(uuid, patch);
}
export function deleteTask(uuid) {
    return ipc().deleteTask?.(uuid);
}
export function drainStoreSync() {
    return ipc().drainStoreSync?.();
}
/** Report the currently-viewed message as spam → appends a row to
 *  `~/.mailx/spam.csv`. Placeholder: no folder move, no flag change, no
 *  auto-delete. Training data for a smarter pass later. */
export function recordSpamReport(accountId, uid, folderId) {
    return ipc().recordSpamReport?.(accountId, uid, folderId);
}
export function getOutboxStatus() {
    return ipc().getOutboxStatus();
}
export function listQueuedOutgoing() {
    return ipc().listQueuedOutgoing();
}
export function cancelQueuedOutgoing(p) {
    return ipc().cancelQueuedOutgoing(p);
}
export function searchContacts(query) {
    return ipc().searchContacts(query);
}
export function hasCcHistoryTo(email) {
    return ipc().hasCcHistoryTo(email);
}
export function hasBccHistoryTo(email) {
    return ipc().hasBccHistoryTo(email);
}
export function listContacts(query, page = 1, pageSize = 100) {
    return ipc().listContacts(query, page, pageSize);
}
export function upsertContact(name, email) {
    return ipc().upsertContact(name, email);
}
export function deleteContact(email) {
    return ipc().deleteContact(email);
}
export function addPreferredContact(entry) {
    return ipc().addPreferredContact(entry.name, entry.email, entry.source, entry.organization);
}
export function getPriorityLists() {
    return ipc().getPriorityLists();
}
export function setPrioritySender(email, value, name) {
    return ipc().setPrioritySender(email, value, name);
}
export function setPriorityDomain(domain, value) {
    return ipc().setPriorityDomain(domain, value);
}
export function addToDenylist(email) {
    return ipc().addToDenylist(email);
}
export function openLocalPath(which) {
    return ipc().openLocalPath(which);
}
/** Open an absolute file path (under ~/.rmfmail) in the OS default
 *  *text* editor — Notepad on Windows, TextEdit on Mac, $EDITOR or
 *  xdg-open(.txt) on Linux. Distinct from the file's default app,
 *  which for .eml is usually Outlook / a mail client. */
export function openInTextEditor(path) {
    return ipc().openInTextEditor?.(path) ?? Promise.resolve({ ok: false, opener: "none", reason: "no host" });
}
export function allowRemoteContent(type, value) {
    return ipc().allowRemoteContent(type, value);
}
export function getUserDict() {
    return ipc().getUserDict?.() ?? Promise.resolve([]);
}
export function addUserDictWord(word) {
    return ipc().addUserDictWord?.(word) ?? Promise.resolve([]);
}
export function addUserDictWords(words) {
    return ipc().addUserDictWords?.(words) ?? Promise.resolve([]);
}
export function removeUserDictWord(word) {
    return ipc().removeUserDictWord?.(word) ?? Promise.resolve([]);
}
export function flagSenderOrDomain(type, value) {
    return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
}
export function deleteMessage(accountId, uid) {
    return ipc().deleteMessage?.(accountId, uid);
}
export function deleteMessages(accountId, uids) {
    if (uids.length === 1)
        return deleteMessage(accountId, uids[0]);
    return ipc().deleteMessages?.(accountId, uids);
}
export function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
    if (uids.length === 1)
        return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);
    return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
}
export function markAsSpamMessages(accountId, uids) {
    return ipc().markAsSpamMessages?.(accountId, uids);
}
export function undeleteMessage(accountId, uid, folderId) {
    return ipc().undeleteMessage?.(accountId, uid, folderId);
}
export function moveMessage(accountId, uid, targetFolderId, targetAccountId) {
    return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
}
export function restartServer() {
    return ipc().restart?.();
}
export function markFolderRead(accountId, folderId) {
    return ipc().markFolderRead?.(accountId, folderId);
}
export function createFolder(accountId, parentPath, name) {
    return ipc().createFolder?.(accountId, parentPath, name);
}
export function renameFolder(accountId, folderId, newName) {
    return ipc().renameFolder?.(accountId, folderId, newName);
}
export function deleteFolder(accountId, folderId) {
    return ipc().deleteFolder?.(accountId, folderId);
}
export function moveFolderToTrash(accountId, folderId) {
    return ipc().moveFolderToTrash?.(accountId, folderId);
}
export function emptyFolder(accountId, folderId) {
    return ipc().emptyFolder?.(accountId, folderId);
}
/** Ship a named event to the Node log as `[client] <tag> <data>`. Fire and
 *  forget — never awaits, never throws, never blocks the caller. Tries two
 *  paths so a broken primary channel can't swallow the trace:
 *    1. Direct bridge call (self / opener / parent mailxapi).
 *    2. parent.postMessage fallback — the main window listens and relays.
 *  The fallback matters because the whole point of tracing is to diagnose
 *  a broken iframe bridge; a single-path tracer that goes through that same
 *  bridge is useless in exactly the case we need it. */
export function logClientEvent(tag, data) {
    let delivered = false;
    try {
        const bridge = typeof globalThis.mailxapi !== "undefined" && globalThis.mailxapi?.isApp ? globalThis.mailxapi
            : window.opener?.mailxapi?.isApp ? window.opener.mailxapi
                : window.parent?.mailxapi?.isApp ? window.parent.mailxapi
                    : null;
        if (bridge?.logClientEvent) {
            bridge.logClientEvent(tag, data);
            delivered = true;
        }
    }
    catch { /* never throw from tracing */ }
    try {
        if (window.parent && window.parent !== window) {
            window.parent.postMessage({ type: "mailx-trace", tag, data, bridged: delivered }, "*");
        }
    }
    catch { /* */ }
}
export function sendMessage(body) {
    return ipc().sendMessage?.(body);
}
export function saveDraft(body) {
    return ipc().saveDraft?.(body);
}
const eventHandlers = [];
export function onEvent(handler) {
    eventHandlers.push(handler);
    return () => {
        const i = eventHandlers.indexOf(handler);
        if (i >= 0)
            eventHandlers.splice(i, 1);
    };
}
const storeSubs = new Map();
export function subscribeStore(topic, handler) {
    let set = storeSubs.get(topic);
    if (!set) {
        set = new Set();
        storeSubs.set(topic, set);
    }
    set.add(handler);
    return () => {
        const s = storeSubs.get(topic);
        if (s) {
            s.delete(handler);
            if (s.size === 0)
                storeSubs.delete(topic);
        }
    };
}
function deliverStore(event) {
    const exact = storeSubs.get(event.topic);
    if (exact)
        for (const h of exact) {
            try {
                h(event);
            }
            catch (e) {
                console.error("[store-bus]", e);
            }
        }
    const wild = storeSubs.get("*");
    if (wild)
        for (const h of wild) {
            try {
                h(event);
            }
            catch (e) {
                console.error("[store-bus]", e);
            }
        }
}
export function connectEvents() {
    ipc().onEvent((event) => {
        if (event && event._event === "store")
            deliverStore(event);
        for (const h of eventHandlers)
            h(event);
    });
}
// ── Autocomplete ──
export function autocomplete(body, signal) {
    return ipc().autocomplete?.(body);
}
export function getAutocompleteSettings() {
    return ipc().getAutocompleteSettings?.();
}
export function saveAutocompleteSettings(settings) {
    return ipc().saveAutocompleteSettings?.(settings);
}
export function getVersion() {
    return ipc().getVersion();
}
export function getSettings() {
    return ipc().getSettings();
}
export function saveSettings(settings) {
    return ipc().saveSettingsData?.(settings);
}
export function repairAccounts() {
    return ipc().repairAccounts?.();
}
export function deleteDraft(accountId, draftUid, draftId) {
    return ipc().deleteDraft?.(accountId, draftUid, draftId);
}
export function addContact(name, email) {
    return ipc().addContact?.(name, email);
}
export function getThreadMessages(accountId, threadId) {
    return ipc().getThreadMessages?.(accountId, threadId);
}
export function readJsoncFile(name) {
    return ipc().readJsoncFile?.(name);
}
export function writeJsoncFile(name, content) {
    return ipc().writeJsoncFile?.(name, content);
}
export function formatJsonc(content) {
    return ipc().formatJsonc?.(content);
}
export function readConfigHelp(name) {
    return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: "" });
}
export function unsubscribeOneClick(url) {
    return ipc().unsubscribeOneClick?.(url);
}
export function openInWord(editId, html) {
    return ipc().openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
}
export function closeWordEdit(editId) {
    return ipc().closeWordEdit?.(editId) ?? Promise.resolve();
}
/** Show an OS-level always-on-top reminder popup via msger. Returns the
 *  label of the button the user clicked. Empty string when the host
 *  isn't available (browser fallback) — caller should fall back to an
 *  in-WebView popup in that case. */
export function showReminderPopup(opts) {
    return ipc().showReminderPopup?.(opts) ?? Promise.resolve({ button: "", reason: "no host" });
}
/** Read + delete a pending mailto: drop file, if any (P115). Used at app
 *  startup so a `mailx --mailto <url>` that just spawned us doesn't lose
 *  its compose payload to the daemon-fires-before-app-registers race
 *  window. Subsequent live clicks arrive via the `openMailto` event. */
export function consumePendingMailto() {
    return ipc().consumePendingMailto?.() ?? Promise.resolve(null);
}
/** Run an AI text transform (translate / proofread / summarize). Returns
 *  empty `text` with a `reason` when the feature is disabled or the provider
 *  errors — caller should surface `reason` in a status bar, not throw. */
export function aiTransform(req) {
    return ipc().aiTransform?.(req) ?? Promise.resolve({ text: "", reason: "AI not available in this host" });
}
export function setupAccount(name, email, password) {
    return ipc().setupAccount?.(name, email, password);
}
export async function getAttachment(accountId, uid, attachmentId, folderId) {
    return ipc().getAttachment(accountId, uid, attachmentId, folderId);
}
/** Desktop: have the Node service save the attachment and open it with the
 *  OS default app. Returns undefined when the host has no such method (real
 *  browser / --server mode) so the caller can fall back to a blob download. */
export async function openAttachment(accountId, uid, attachmentId, folderId) {
    const fn = ipc().openAttachment;
    return fn ? fn(accountId, uid, attachmentId, folderId) : undefined;
}
export async function getDeviceAccounts() {
    return ipc().getDeviceAccounts?.() ?? [];
}
// Legacy exports for backward compatibility
export const connectWebSocket = connectEvents;
export const onWsEvent = onEvent;
//# sourceMappingURL=api-client.js.map