/**
 * Worker entry point — runs the entire mailx service layer off the main thread.
 *
 * Hosts: wa-sqlite DB, sync manager, providers, service, settings.
 * Main thread only handles UI rendering and proxies TCP/native bridge calls.
 */

import { initWorkerTcp, handleTcpMessage, WorkerTcpTransport } from "./worker-tcp-transport.js";
import { WebMailxDB } from "./db.js";
import { WebMessageStore } from "./web-message-store.js";
import { WebMailxService } from "./web-service.js";
import { SyncManager } from "./sync-manager.js";
import { dispatch } from "./web-jsonrpc.js";
import type { AccountConfig } from "@bobfrankston/mailx-types";
import {
    loadAccounts, loadAccountsFromCloud, loadSettings, clearSettings,
    setGDriveTokenProvider, setGDriveFolderId
} from "./web-settings.js";

// ── Globals ──

let db: WebMailxDB;
let bodyStore: WebMessageStore;
let syncManager: SyncManager;
let service: WebMailxService;

const post = (msg: any) => (self as any).postMessage(msg);

// ── Event emission → main thread ──

function emitEvent(event: any): void {
    post({ type: "event", event });
}

function vlog(msg: string): void {
    // Route through main thread for logit (fetch works in workers but keep consistent)
    try {
        fetch(`https://rmf39.aaz.lt/logit/${encodeURIComponent("V/" + msg.substring(0, 800))}?log=mailx-android&silent=true`).catch(() => {});
    } catch { /* ignore */ }
}

// ── Native bridge proxy ──

let nativeReqCounter = 0;
const pendingNative = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>();

function nativeRequest(op: string, args?: any[]): Promise<any> {
    const reqId = ++nativeReqCounter;
    return new Promise((resolve, reject) => {
        pendingNative.set(reqId, { resolve, reject });
        post({ type: "native", reqId, op, args });
        setTimeout(() => {
            if (pendingNative.has(reqId)) {
                pendingNative.delete(reqId);
                reject(new Error(`Native ${op} timeout`));
            }
        }, 60000);
    });
}

function handleNativeResponse(msg: any): void {
    const pending = pendingNative.get(msg.reqId);
    if (pending) {
        pendingNative.delete(msg.reqId);
        if (msg.error) pending.reject(new Error(msg.error));
        else pending.resolve(msg.result);
    }
}

// ── Token provider (proxied through main thread → C# OAuth) ──

function createTokenProvider(email: string): () => Promise<string> {
    return async () => {
        console.log(`[oauth] Refreshing token for ${email}`);
        return nativeRequest("refreshToken", [email]);
    };
}

// ── GDrive folder lookup ──

async function findGDriveMailxFolder(tokenProvider: () => Promise<string>): Promise<{ id: string; name: string; path: string; ownerEmail?: string } | null> {
    const token = await tokenProvider();
    const headers = { "Authorization": `Bearer ${token}` };
    // Two-step path traversal: My Drive root → home/ → .rmfmail
    const homeQ = encodeURIComponent("name='home' and mimeType='application/vnd.google-apps.folder' and 'root' in parents and trashed=false");
    const homeRes = await fetch(
        `https://www.googleapis.com/drive/v3/files?q=${homeQ}&fields=files(id,name)&spaces=drive`,
        { headers }
    );
    if (!homeRes.ok) return null;
    const home = (await homeRes.json() as any).files?.[0];
    if (!home?.id) return null;
    const q = encodeURIComponent(`name='.rmfmail' and mimeType='application/vnd.google-apps.folder' and '${home.id}' in parents and trashed=false`);
    const r = await fetch(
        `https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id,name,owners(emailAddress))&spaces=drive`,
        { headers }
    );
    if (!r.ok) return null;
    const data = await r.json();
    const folder = data.files?.[0];
    if (!folder?.id) return null;
    const homeName = home.name || "home";
    const folderName = folder.name || ".rmfmail";
    return {
        id: folder.id,
        name: folderName,
        path: `My Drive/${homeName}/${folderName}`,
        ownerEmail: folder.owners?.[0]?.emailAddress,
    };
}

// ── Message handler ──

(self as any).onmessage = async (e: MessageEvent) => {
    const msg = e.data;

    if (msg.type === "tcp-response" || msg.type === "tcp-data" ||
        msg.type === "tcp-close" || msg.type === "tcp-error") {
        handleTcpMessage(msg);
        return;
    }

    if (msg.type === "native-response") {
        handleNativeResponse(msg);
        return;
    }

    if (msg.type === "rpc") {
        try {
            const result = await dispatch(service as any, {
                _action: msg.action,
                _cbid: String(msg.id),
                ...msg.params
            });
            post({ type: "rpc-response", id: msg.id, result: result.result, error: result.error });
        } catch (e: any) {
            post({ type: "rpc-response", id: msg.id, error: e.message });
        }
        return;
    }

    if (msg.type === "init") {
        try {
            await initialize();
            post({ type: "init-complete" });
        } catch (e: any) {
            console.error(`[worker] Init failed: ${e.message}\n${e.stack}`);
            post({ type: "init-error", error: e.message });
        }
        return;
    }
};

// ── Initialization ──

async function initialize(): Promise<void> {
    console.log("[worker] Initializing service layer...");

    // Wire up TCP proxy for IMAP/SMTP
    initWorkerTcp(post);

    // Initialize DB (wa-sqlite runs in this Worker thread — no main thread blocking)
    db = new WebMailxDB("mailx");
    await db.waitReady();
    bodyStore = new WebMessageStore();

    // Create sync manager with injected deps
    syncManager = new SyncManager(db, bodyStore, {
        emitEvent,
        vlog,
        createTcpTransport: () => new WorkerTcpTransport(),
    });
    service = new WebMailxService(db, bodyStore, syncManager);

    // Load accounts
    let accounts = await loadAccounts();
    console.log(`[worker] ${accounts.length} account(s) found`);

    let gmailTokenProvider: (() => Promise<string>) | null = null;
    for (const account of accounts) {
        if (!account.enabled) continue;
        const domain = account.email?.split("@")[1]?.toLowerCase() || "";
        if (domain === "gmail.com" || domain === "googlemail.com") {
            const tp = createTokenProvider(account.email);
            syncManager.setTokenProvider(account.id, tp);
            if (!gmailTokenProvider) gmailTokenProvider = tp;
        }
        await syncManager.addAccount(account);
    }

    // GDrive setup — read shared accounts from cloud
    if (gmailTokenProvider) {
        setGDriveTokenProvider(gmailTokenProvider);
        try {
            console.log("[worker] Looking up GDrive .rmfmail folder...");
            const folder = await findGDriveMailxFolder(gmailTokenProvider);
            if (!folder) {
                emitEvent({
                    type: "fatal",
                    key: "gdrive-folder-missing",
                    message: "GDrive folder '.rmfmail' not found — app cannot start. Create it or sign in with the correct Google account.",
                });
            } else {
                setGDriveFolderId(folder.id, folder.name, folder.ownerEmail, folder.path);
                console.log(`[worker] GDrive ${folder.path} folder: ${folder.id} (owner=${folder.ownerEmail || "?"})`);

                // Load accounts from cloud
                const cloudAccounts = await loadAccountsFromCloud();
                if (cloudAccounts && cloudAccounts.length > 0) {
                    console.log(`[worker] GDrive returned ${cloudAccounts.length} accounts`);
                    for (const account of cloudAccounts) {
                        if (!account.enabled) continue;
                        const domain = account.email?.split("@")[1]?.toLowerCase() || "";
                        if (domain === "gmail.com" || domain === "googlemail.com") {
                            const tp = createTokenProvider(account.email);
                            syncManager.setTokenProvider(account.id, tp);
                            if (!gmailTokenProvider) gmailTokenProvider = tp;
                        }
                        await syncManager.addAccount(account);
                    }
                }
            }
        } catch (e: any) {
            emitEvent({
                type: "fatal",
                key: "gdrive-setup-failed",
                message: `GDrive setup failed — app cannot start: ${e.message}`,
            });
        }
    }

    // Periodic sync (runs in Worker — never blocks UI)
    setInterval(() => {
        console.log("[sync] periodic poll");
        syncManager.syncAll().catch((e: any) => console.error(`[worker] Periodic sync error: ${e.message}`));
    }, 2 * 60 * 1000);

    // Initial sync after 1 second
    setTimeout(() => {
        syncManager.syncAll().catch((e: any) => console.error(`[worker] Initial sync error: ${e.message}`));
    }, 1000);

    console.log("[worker] Service layer ready");
    emitEvent({ type: "connected" });
}
