/**
 * API client — all operations go through the IPC bridge (mailxapi).
 * mailxapi is injected by the launcher (msger on desktop, MAUI on Android).
 */

declare const mailxapi: any;

function getIpc(): any {
    if (typeof mailxapi !== "undefined" && mailxapi?.isApp) return mailxapi;
    if ((window as any).opener?.mailxapi?.isApp) return (window as any).opener.mailxapi;
    // Compose iframe — check parent
    if ((window as any).parent?.mailxapi?.isApp) return (window as any).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(): any {
    const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void; timer: any }>();
    window.addEventListener("message", (ev: MessageEvent) => {
        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: string, args: any[]): Promise<any> => {
        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 as any).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: string) {
            if (prop === "isApp") return true;
            if (prop === "platform") return (window.parent as any)?.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: any) => (window.parent as any)?.mailxapi?.onEvent?.(handler);
            }
            return (...args: any[]) => call(prop, args);
        }
    });
}

let cachedRelayBridge: any = null;
function ipc(): any {
    // 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 as any)?.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: AbortController | null = null;

export function abortMessageListRequests(): void {
    if (messageListAbort) {
        messageListAbort.abort();
        messageListAbort = null;
    }
}

// ── API Methods ──

export function getAccounts() {
    return ipc().getAccounts();
}

export function getFolders(accountId: string) {
    return ipc().getFolders(accountId);
}

export function getMessages(accountId: string, folderId: number, page = 1, pageSize = 50, flaggedOnly = false, sort?: string, sortDir?: string) {
    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: string, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0) {
    return ipc().searchMessages(query, page, pageSize);
}

export function getMessage(accountId: string, uid: number, allowRemote = false, folderId?: number) {
    return ipc().getMessage(accountId, uid, allowRemote, folderId);
}

export function updateFlags(accountId: string, uid: number, flags: string[]) {
    return ipc().updateFlags(accountId, uid, flags);
}

export function triggerSync() {
    return ipc().syncAll();
}

export function syncAccount(accountId: string) {
    return ipc().syncAccount(accountId);
}

export function reauthenticate(accountId: string) {
    return ipc().reauthenticate(accountId);
}

export function reauthGoogleScopes(): Promise<{ cleared: number }> {
    return (ipc() as any).reauthGoogleScopes();
}

export function getSyncPending() {
    return ipc().getSyncPending();
}

export function getDiagnostics(): Promise<any> {
    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?: string): Promise<any> {
    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: number, toMs: number): Promise<any[]> {
    return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);
}
export function createCalendarEvent(ev: {
    title: string; startMs: number; endMs: number; allDay?: boolean;
    location?: string; notes?: string;
}): Promise<{ uuid: string }> {
    return ipc().createCalendarEvent?.(ev);
}
export function updateCalendarEvent(uuid: string, patch: any): Promise<{ ok: boolean }> {
    return ipc().updateCalendarEvent?.(uuid, patch);
}
export function deleteCalendarEvent(uuid: string): Promise<{ ok: boolean }> {
    return ipc().deleteCalendarEvent?.(uuid);
}
export function getTasks(includeCompleted = false): Promise<any[]> {
    return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);
}
export function createTask(t: { title: string; notes?: string; dueMs?: number }): Promise<{ uuid: string }> {
    return ipc().createTask?.(t);
}
export function updateTask(uuid: string, patch: any): Promise<{ ok: boolean }> {
    return ipc().updateTask?.(uuid, patch);
}
export function deleteTask(uuid: string): Promise<{ ok: boolean }> {
    return ipc().deleteTask?.(uuid);
}
export function drainStoreSync(): Promise<{ ok: boolean }> {
    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: string, uid: number, folderId: number): Promise<{ ok: boolean }> {
    return ipc().recordSpamReport?.(accountId, uid, folderId);
}

export function getOutboxStatus() {
    return (ipc() as any).getOutboxStatus();
}

export function listQueuedOutgoing() {
    return (ipc() as any).listQueuedOutgoing();
}

export function cancelQueuedOutgoing(p: string) {
    return (ipc() as any).cancelQueuedOutgoing(p);
}

export function searchContacts(query: string) {
    return ipc().searchContacts(query);
}

export function hasCcHistoryTo(email: string): Promise<{ hasCc: boolean }> {
    return (ipc() as any).hasCcHistoryTo(email);
}

export function hasBccHistoryTo(email: string): Promise<{ hasBcc: boolean }> {
    return (ipc() as any).hasBccHistoryTo(email);
}

export function listContacts(query: string, page = 1, pageSize = 100) {
    return (ipc() as any).listContacts(query, page, pageSize);
}

export function upsertContact(name: string, email: string) {
    return (ipc() as any).upsertContact(name, email);
}

export function deleteContact(email: string) {
    return (ipc() as any).deleteContact(email);
}

export function addPreferredContact(entry: { name: string; email: string; source?: string; organization?: string }) {
    return (ipc() as any).addPreferredContact(entry.name, entry.email, entry.source, entry.organization);
}

export function addToDenylist(email: string) {
    return (ipc() as any).addToDenylist(email);
}

export function openLocalPath(which: "config" | "log") {
    return (ipc() as any).openLocalPath(which);
}

export function allowRemoteContent(type: string, value: string) {
    return ipc().allowRemoteContent(type, value);
}
export function flagSenderOrDomain(type: "sender" | "domain", value: string): Promise<{ flagged: boolean }> {
    return (ipc() as any).flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
}

export function deleteMessage(accountId: string, uid: number) {
    return ipc().deleteMessage?.(accountId, uid);
}

export function deleteMessages(accountId: string, uids: number[]) {
    if (uids.length === 1) return deleteMessage(accountId, uids[0]);
    return ipc().deleteMessages?.(accountId, uids);
}

export function moveMessages(accountId: string, uids: number[], targetFolderId: number, targetAccountId?: string) {
    if (uids.length === 1) return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);
    return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
}

export function markAsSpamMessages(accountId: string, uids: number[]): Promise<{ targetFolderId: number; moved: number }> {
    return ipc().markAsSpamMessages?.(accountId, uids);
}

export function undeleteMessage(accountId: string, uid: number, folderId: number) {
    return ipc().undeleteMessage?.(accountId, uid, folderId);
}

export function moveMessage(accountId: string, uid: number, targetFolderId: number, targetAccountId?: string) {
    return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
}

export function restartServer() {
    return ipc().restart?.();
}

export function markFolderRead(accountId: string, folderId: number) {
    return ipc().markFolderRead?.(accountId, folderId);
}

export function createFolder(accountId: string, parentPath: string, name: string) {
    return ipc().createFolder?.(accountId, parentPath, name);
}

export function renameFolder(accountId: string, folderId: number, newName: string) {
    return ipc().renameFolder?.(accountId, folderId, newName);
}

export function deleteFolder(accountId: string, folderId: number) {
    return ipc().deleteFolder?.(accountId, folderId);
}

export function emptyFolder(accountId: string, folderId: number) {
    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: string, data?: any): void {
    let delivered = false;
    try {
        const bridge = typeof (globalThis as any).mailxapi !== "undefined" && (globalThis as any).mailxapi?.isApp ? (globalThis as any).mailxapi
            : (window as any).opener?.mailxapi?.isApp ? (window as any).opener.mailxapi
            : (window as any).parent?.mailxapi?.isApp ? (window as any).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 as any).postMessage({ type: "mailx-trace", tag, data, bridged: delivered }, "*");
        }
    } catch { /* */ }
}

export function sendMessage(body: any) {
    return ipc().sendMessage?.(body);
}

export function saveDraft(body: any) {
    return ipc().saveDraft?.(body);
}

// ── Events ──

type EventHandler = (event: any) => void;
const eventHandlers: EventHandler[] = [];

export function onEvent(handler: EventHandler): void {
    eventHandlers.push(handler);
}

export function connectEvents(): void {
    ipc().onEvent((event: any) => {
        for (const h of eventHandlers) h(event);
    });
}

// ── Autocomplete ──

export function autocomplete(body: { subject: string; to: string; bodyText: string; cursorOffset: number }, signal?: AbortSignal) {
    return ipc().autocomplete?.(body);
}

export function getAutocompleteSettings() {
    return ipc().getAutocompleteSettings?.();
}

export function saveAutocompleteSettings(settings: any) {
    return ipc().saveAutocompleteSettings?.(settings);
}

export function getVersion() {
    return ipc().getVersion();
}

export function getSettings() {
    return ipc().getSettings();
}

export function saveSettings(settings: any) {
    return ipc().saveSettingsData?.(settings);
}

export function repairAccounts() {
    return ipc().repairAccounts?.();
}

export function deleteDraft(accountId: string, draftUid: number, draftId?: string) {
    return ipc().deleteDraft?.(accountId, draftUid, draftId);
}

export function addContact(name: string, email: string) {
    return ipc().addContact?.(name, email);
}

export function getThreadMessages(accountId: string, threadId: string) {
    return ipc().getThreadMessages?.(accountId, threadId);
}

export function readJsoncFile(name: string): Promise<{ content: string | null }> {
    return ipc().readJsoncFile?.(name);
}
export function writeJsoncFile(name: string, content: string): Promise<{ ok: boolean }> {
    return ipc().writeJsoncFile?.(name, content);
}
export function formatJsonc(content: string): Promise<{ content: string }> {
    return (ipc() as any).formatJsonc?.(content);
}
export function readConfigHelp(name: string): Promise<{ content: string }> {
    return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: "" });
}
export function unsubscribeOneClick(url: string): Promise<{ ok: boolean; status: number; statusText: string }> {
    return ipc().unsubscribeOneClick?.(url);
}
export function openInWord(editId: string, html: string): Promise<{ ok: boolean; path: string; opener: string }> {
    return (ipc() as any).openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
}
export function closeWordEdit(editId: string): Promise<void> {
    return (ipc() as any).closeWordEdit?.(editId) ?? Promise.resolve();
}

/** 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: { action: "translate" | "proofread" | "summarize"; text: string; targetLang?: string }): Promise<{ text: string; reason?: string }> {
    return ipc().aiTransform?.(req) ?? Promise.resolve({ text: "", reason: "AI not available in this host" });
}

export function setupAccount(name: string, email: string, password: string) {
    return ipc().setupAccount?.(name, email, password);
}

export async function getAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{ content: string; contentType: string; filename: string }> {
    return ipc().getAttachment(accountId, uid, attachmentId, folderId);
}

export async function getDeviceAccounts(): Promise<{ email: string; name: string }[]> {
    return ipc().getDeviceAccounts?.() ?? [];
}

// Legacy exports for backward compatibility
export const connectWebSocket = connectEvents;
export const onWsEvent = onEvent;
