/**
 * Browser-compatible settings for Android/WebView.
 * Replaces @bobfrankston/mailx-settings which depends on node:fs.
 *
 * Settings are stored in IndexedDB and synced to Google Drive
 * via the GDrive API (same API as desktop cloud mode).
 *
 * On first run, settings are fetched from GDrive. Subsequent reads
 * use the local IndexedDB cache. Writes go to both.
 */

import type { AccountConfig, MailxSettings, AutocompleteSettings } from "@bobfrankston/mailx-types";

const IDB_NAME = "mailx-settings";
const IDB_VERSION = 1;
const STORE_NAME = "files";

// ── IndexedDB helpers ──

function openSettingsDb(): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
        const req = indexedDB.open(IDB_NAME, IDB_VERSION);
        req.onupgradeneeded = () => {
            const db = req.result;
            if (!db.objectStoreNames.contains(STORE_NAME)) {
                db.createObjectStore(STORE_NAME);
            }
        };
        req.onsuccess = () => resolve(req.result);
        req.onerror = () => reject(req.error);
    });
}

async function idbRead(key: string): Promise<string | null> {
    const db = await openSettingsDb();
    return new Promise((resolve, reject) => {
        const tx = db.transaction(STORE_NAME, "readonly");
        const req = tx.objectStore(STORE_NAME).get(key);
        req.onsuccess = () => resolve(req.result as string | null);
        req.onerror = () => reject(req.error);
    });
}

async function idbWrite(key: string, value: string): Promise<void> {
    const db = await openSettingsDb();
    return new Promise((resolve, reject) => {
        const tx = db.transaction(STORE_NAME, "readwrite");
        tx.objectStore(STORE_NAME).put(value, key);
        tx.oncomplete = () => resolve();
        tx.onerror = () => reject(tx.error);
    });
}

async function idbDelete(key: string): Promise<void> {
    const db = await openSettingsDb();
    return new Promise((resolve, reject) => {
        const tx = db.transaction(STORE_NAME, "readwrite");
        tx.objectStore(STORE_NAME).delete(key);
        tx.oncomplete = () => resolve();
        tx.onerror = () => reject(tx.error);
    });
}

/** All keys with the given prefix (prev-version pruning). */
async function idbListKeys(prefix: string): Promise<string[]> {
    const db = await openSettingsDb();
    return new Promise((resolve, reject) => {
        const tx = db.transaction(STORE_NAME, "readonly");
        const req = tx.objectStore(STORE_NAME).getAllKeys(IDBKeyRange.bound(prefix, prefix + "￿"));
        req.onsuccess = () => resolve((req.result as IDBValidKey[]).map(String));
        req.onerror = () => reject(req.error);
    });
}

// ── GDrive API ──

/** GDrive folder ID for the ".rmfmail" app folder */
let gDriveFolderId: string | null = null;
/** GDrive folder name (resolved from query — ".rmfmail" today, but kept dynamic in case the query changes) */
let gDriveFolderName: string | null = null;
/** Full path "My Drive/home/.rmfmail" — surfaced in About so the user can verify */
let gDriveFolderPath: string | null = null;
/** Owner email of the matched .rmfmail folder — surfaces wrong-account picks (e.g. spouse's shared folder) */
let gDriveFolderOwner: string | null = null;
/** OAuth token provider — set by bootstrap */
let tokenProvider: (() => Promise<string>) | null = null;

export function setGDriveTokenProvider(provider: () => Promise<string>): void {
    tokenProvider = provider;
}

export function setGDriveFolderId(folderId: string, folderName?: string, ownerEmail?: string, folderPath?: string): void {
    gDriveFolderId = folderId;
    if (folderName) gDriveFolderName = folderName;
    if (ownerEmail) gDriveFolderOwner = ownerEmail;
    if (folderPath) gDriveFolderPath = folderPath;
}

export async function cloudRead(filename: string): Promise<string | null> {
    return gDriveRead(filename);
}
export async function cloudWrite(filename: string, content: string): Promise<boolean> {
    return gDriveWrite(filename, content);
}
/** Cheap freshness probe: one Drive metadata request (no content download).
 *  Returns the file's modifiedTime, or null if unreachable / missing. */
export async function cloudStat(filename: string): Promise<{ id: string; modifiedTime: string } | null> {
    if (!tokenProvider || !gDriveFolderId) return null;
    try {
        const token = await tokenProvider();
        const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
        const res = await globalThis.fetch(
            `https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id,modifiedTime)`,
            { headers: { "Authorization": `Bearer ${token}` } }
        );
        if (!res.ok) return null;
        const data = await res.json() as any;
        const f = data.files?.[0];
        return f?.id ? { id: f.id, modifiedTime: f.modifiedTime || "" } : null;
    } catch (e: any) {
        console.error(`[settings] GDrive stat ${filename}: ${e.message}`);
        return null;
    }
}


/** Find a config file by name in the .rmfmail folder, DETERMINISTICALLY.
 *  Drive allows duplicate names; a second device signed into a different
 *  Google account created its own allowlist.jsonc in the shared folder and
 *  files?.[0] then made every device's pick a coin flip — stale bases and
 *  cross-account clobbers (2026-07-23 wipe). Preference order: a file THIS
 *  account owns, then newest modifiedTime. Duplicates are logged loudly. */
async function gDriveFindFile(filename: string, token: string): Promise<{ id: string; modifiedTime: string; ownedByMe: boolean; capabilitiesCanEdit: boolean } | null> {
    const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
    const res = await globalThis.fetch(
        `https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id,modifiedTime,ownedByMe,capabilities(canEdit))`,
        { headers: { "Authorization": `Bearer ${token}` } }
    );
    if (!res.ok) return null;
    const files: any[] = ((await res.json()) as any).files || [];
    if (files.length === 0) return null;
    if (files.length > 1) {
        console.warn(`[settings] ${filename}: ${files.length} same-named files in the shared folder — using own/newest. Clean the duplicates (owners differ?).`);
        files.sort((a, b) => (b.ownedByMe === true ? 1 : 0) - (a.ownedByMe === true ? 1 : 0) || String(b.modifiedTime).localeCompare(String(a.modifiedTime)));
    }
    const f = files[0];
    return { id: f.id, modifiedTime: f.modifiedTime || '', ownedByMe: f.ownedByMe === true, capabilitiesCanEdit: f.capabilities?.canEdit !== false };
}

async function gDriveRead(filename: string): Promise<string | null> {
    if (!tokenProvider || !gDriveFolderId) return null;
    try {
        const token = await tokenProvider();
        const found = await gDriveFindFile(filename, token);
        const fileId = found?.id;
        if (!fileId) return null;
        // Download content
        const res = await globalThis.fetch(
            `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`,
            { headers: { "Authorization": `Bearer ${token}` } }
        );
        if (!res.ok) return null;
        return res.text();
    } catch (e: any) {
        console.error(`[settings] GDrive read ${filename}: ${e.message}`);
        return null;
    }
}

async function gDriveWrite(filename: string, content: string): Promise<boolean> {
    if (!tokenProvider || !gDriveFolderId) return false;
    try {
        const token = await tokenProvider();
        const found = await gDriveFindFile(filename, token);
        const fileId = found?.id;

        if (fileId) {
            // A file EXISTS but this account can't edit it (someone else's
            // copy in a shared folder). Creating a same-named sibling is how
            // the duplicate-file mess started — never fork; fail loudly and
            // keep the change local-only.
            if (!found!.capabilitiesCanEdit) {
                console.error(`[settings] ${filename}: existing shared file is not editable by this account — NOT creating a duplicate. Change kept locally.`);
                return false;
            }
            // Update existing
            const res = await globalThis.fetch(
                `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`,
                {
                    method: "PATCH",
                    headers: {
                        "Authorization": `Bearer ${token}`,
                        "Content-Type": "application/json",
                    },
                    body: content,
                }
            );
            if (!res.ok) console.error(`[settings] ${filename}: cloud update failed (${res.status})`);
            return res.ok;
        } else {
            // Create new
            const metadata = JSON.stringify({
                name: filename,
                parents: [gDriveFolderId],
                mimeType: "application/json",
            });
            const boundary = "----mailx" + Date.now();
            const body = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n--${boundary}\r\nContent-Type: application/json\r\n\r\n${content}\r\n--${boundary}--`;
            const res = await globalThis.fetch(
                "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart",
                {
                    method: "POST",
                    headers: {
                        "Authorization": `Bearer ${token}`,
                        "Content-Type": `multipart/related; boundary=${boundary}`,
                    },
                    body,
                }
            );
            return res.ok;
        }
    } catch (e: any) {
        console.error(`[settings] GDrive write ${filename}: ${e.message}`);
        return false;
    }
}

// ── Provider defaults (same as desktop mailx-settings) ──

interface ProviderDefaults {
    label: string;
    imap: { host: string; port: number; tls: boolean; auth: "password" | "oauth2" };
    smtp: { host: string; port: number; tls: boolean; auth: "password" | "oauth2" };
}

const PROVIDERS: Record<string, ProviderDefaults> = {
    "gmail.com": {
        label: "Gmail",
        imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
        smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
    },
    "googlemail.com": {
        label: "Gmail",
        imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
        smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
    },
    "outlook.com": {
        label: "Outlook",
        imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
        smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
    },
    "hotmail.com": {
        label: "Hotmail",
        imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
        smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
    },
    "yahoo.com": {
        label: "Yahoo",
        imap: { host: "imap.mail.yahoo.com", port: 993, tls: true, auth: "password" },
        smtp: { host: "smtp.mail.yahoo.com", port: 587, tls: true, auth: "password" },
    },
    "icloud.com": {
        label: "iCloud",
        imap: { host: "imap.mail.me.com", port: 993, tls: true, auth: "password" },
        smtp: { host: "smtp.mail.me.com", port: 587, tls: true, auth: "password" },
    },
};

function normalizeAccount(acct: any, globalName?: string): AccountConfig {
    const email = acct.email || "";
    const domain = email.split("@")[1]?.toLowerCase() || "";
    const provider = PROVIDERS[domain];
    const user = acct.imap?.user || acct.user || email;

    return {
        id: acct.id || domain.split(".")[0] || "account",
        name: acct.name || globalName || email.split("@")[0],
        label: acct.label || provider?.label,
        email,
        imap: {
            host: acct.imap?.host || provider?.imap.host || `imap.${domain}`,
            port: acct.imap?.port || provider?.imap.port || 993,
            tls: acct.imap?.tls ?? provider?.imap.tls ?? true,
            auth: acct.imap?.auth || provider?.imap.auth || "password",
            user: acct.imap?.user || user,
            password: acct.imap?.password || acct.password,
        },
        smtp: {
            host: acct.smtp?.host || provider?.smtp.host || `smtp.${domain}`,
            port: acct.smtp?.port || provider?.smtp.port || 587,
            tls: acct.smtp?.tls ?? provider?.smtp.tls ?? true,
            auth: acct.smtp?.auth || provider?.smtp.auth || "password",
            user: acct.smtp?.user || user,
            password: acct.smtp?.password || acct.password,
        },
        enabled: acct.enabled ?? true,
        defaultSend: acct.defaultSend,
        syncContacts: acct.syncContacts ?? (provider?.imap.auth === "oauth2"),
        relayDomains: acct.relayDomains,
    };
}

// ── Default settings ──

const DEFAULT_PREFERENCES = {
    ui: {
        theme: "system" as const,
        editor: "quill" as const,
        folderWidth: 220,
        listViewerSplit: 40,
        fontSize: 15,
    },
    sync: {
        intervalMinutes: 5,
        historyDays: 30,
        prefetch: true,
    },
    autocomplete: {
        enabled: false,
        provider: "off" as const,
        ollamaUrl: "",
        ollamaModel: "",
        cloudApiKey: "",
        cloudModel: "",
        debounceMs: 600,
        maxTokens: 60,
    },
};

const DEFAULT_ALLOWLIST = {
    senders: [] as string[],
    domains: [] as string[],
    recipients: [] as string[],
    flaggedSenders: [] as string[],
    flaggedDomains: [] as string[],
};

// ── JSONC parser (strips comments and trailing commas) ──

function parseJsonc(text: string): any {
    // Strip /* block comments */ and // line comments, but preserve content inside strings
    let stripped = "";
    let i = 0;
    let inString = false;
    let stringChar = "";
    while (i < text.length) {
        const c = text[i];
        const next = text[i + 1];
        if (inString) {
            stripped += c;
            if (c === "\\" && i + 1 < text.length) {
                stripped += text[i + 1];
                i += 2;
                continue;
            }
            if (c === stringChar) inString = false;
            i++;
            continue;
        }
        if (c === '"' || c === "'") {
            inString = true;
            stringChar = c;
            stripped += c;
            i++;
            continue;
        }
        if (c === "/" && next === "/") {
            // Line comment — skip to end of line
            while (i < text.length && text[i] !== "\n") i++;
            continue;
        }
        if (c === "/" && next === "*") {
            // Block comment — skip to */
            i += 2;
            while (i < text.length - 1 && !(text[i] === "*" && text[i + 1] === "/")) i++;
            i += 2;
            continue;
        }
        stripped += c;
        i++;
    }
    // Strip trailing commas before } or ]
    stripped = stripped.replace(/,(\s*[}\]])/g, "$1");
    return JSON.parse(stripped);
}

// ── Public API ──

// Stale-while-revalidate for SHARED JSONC files. Cache-first reads froze
// these files at their install-day snapshot: once IndexedDB had a copy,
// GDrive was never consulted again, so desktop edits (new allowlist
// entries, preference changes) never reached the phone (Bob 2026-07-22:
// "android doesn't seem to be honoring the email white list"). Reads stay
// instant off the cache; a throttled background pull updates the cache so
// the NEXT read sees the shared copy. Caches are caches, never the source
// of truth. (Per-device files like devices/<id>/state.json are exempt —
// only this device writes them.)
// prev-versioning (Bob's prev/ convention, IndexedDB flavor): before a
// shared config file's cache changes, stash the outgoing version under a
// `prev:` key so a clobber leaves a local trail (the 2026-07-23 allowlist
// wipe had NO local history anywhere — Drive revisions only reach ~30 days).
const PREV_KEEP = 5;
async function stashPrevIdb(filename: string, aboutToBecome: string): Promise<void> {
    try {
        const old = await idbRead(filename);
        if (!old || old === aboutToBecome) return;
        const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
        await idbWrite(`prev:${filename}:${stamp}`, old);
        const keys = await idbListKeys(`prev:${filename}:`);
        for (const k of keys.sort().slice(0, Math.max(0, keys.length - PREV_KEEP))) {
            await idbDelete(k);
        }
    } catch { /* archival never blocks a save */ }
}

const SHARED_REFRESH_MS = 5 * 60_000;
const SHARED_FILES = ["allowlist.jsonc", "preferences.jsonc"] as const;
const __lastSharedRefresh = new Map<string, number>();

/** Pull one shared file from GDrive into the cache. Returns true if the
 *  cached copy changed. `force` bypasses the throttle (startup prime). */
async function pullSharedFile(filename: string, force: boolean): Promise<boolean> {
    const now = Date.now();
    if (!force && now - (__lastSharedRefresh.get(filename) || 0) < SHARED_REFRESH_MS) return false;
    __lastSharedRefresh.set(filename, now);
    try {
        const fresh = await gDriveRead(filename);
        if (!fresh) return false;
        const cached = await idbRead(filename);
        if (fresh === cached) return false;
        await idbWrite(filename, fresh);
        console.log(`[settings] ${filename} refreshed from GDrive (shared copy changed)`);
        return true;
    } catch { /* offline / auth pending — keep serving the cache */ }
    return false;
}

function refreshSharedFile(filename: string, _cached: string): void {
    void pullSharedFile(filename, false);
}

/** Prime the shared configs from GDrive. The lazy stale-while-revalidate
 *  path alone left the phone a step behind: the read that TRIGGERS the pull
 *  is still served the stale cache, so a desktop approval only took effect
 *  on the *second* message opened — and an app update didn't help, because
 *  IndexedDB survives a reinstall (Bob 2026-07-31: "approved a site on my
 *  PC … reinstall didn't pick up the new approval on Android"). Called once
 *  GDrive auth is live at boot and again on resume, so the cache is already
 *  current before the first message is rendered. */
export async function primeSharedConfigs(force = true): Promise<void> {
    for (const f of SHARED_FILES) {
        try { await pullSharedFile(f, force); }
        catch (e: any) { console.warn(`[settings] prime ${f}: ${e?.message || e}`); }
    }
}

/** Load accounts — first from IndexedDB cache, then GDrive */
export async function loadAccounts(): Promise<AccountConfig[]> {
    // Try local cache first
    const cached = await idbRead("accounts.jsonc");
    if (cached) {
        try {
            const data = parseJsonc(cached);
            const raw: any[] = data.accounts || (Array.isArray(data) ? data : []);
            if (raw.length > 0) {
                return raw.map((a: any) => normalizeAccount(a, data.name));
            }
        } catch (e: any) { console.warn(`[settings] Cached accounts.jsonc parse failed: ${e.message}`); }
    }
    // Try GDrive
    const content = await gDriveRead("accounts.jsonc");
    if (content) {
        await idbWrite("accounts.jsonc", content);
        try {
            const data = parseJsonc(content);
            const raw: any[] = data.accounts || (Array.isArray(data) ? data : []);
            return raw.map((a: any) => normalizeAccount(a, data.name));
        } catch (e: any) { console.warn(`[settings] GDrive accounts.jsonc parse failed: ${e.message}`); }
    }
    return [];
}

/** Load accounts directly from GDrive, bypassing local cache */
export async function loadAccountsFromCloud(): Promise<AccountConfig[]> {
    const content = await gDriveRead("accounts.jsonc");
    if (content) {
        await idbWrite("accounts.jsonc", content);
        try {
            const data = parseJsonc(content);
            const raw: any[] = data.accounts || (Array.isArray(data) ? data : []);
            return raw.map((a: any) => normalizeAccount(a, data.name));
        } catch (e: any) { console.warn(`[settings] loadAccountsFromCloud parse failed: ${e.message}`); }
    }
    return [];
}

/** Save accounts to IndexedDB and GDrive */
export async function saveAccounts(accounts: AccountConfig[]): Promise<void> {
    const content = JSON.stringify({ accounts }, null, 2);
    await idbWrite("accounts.jsonc", content);
    await gDriveWrite("accounts.jsonc", content);
}

/** Load preferences */
export async function loadPreferences(): Promise<typeof DEFAULT_PREFERENCES> {
    const cached = await idbRead("preferences.jsonc");
    if (cached) {
        refreshSharedFile("preferences.jsonc", cached);
        try {
            const data = parseJsonc(cached);
            return {
                ui: { ...DEFAULT_PREFERENCES.ui, ...data.ui },
                sync: { ...DEFAULT_PREFERENCES.sync, ...data.sync },
                autocomplete: { ...DEFAULT_PREFERENCES.autocomplete, ...data.autocomplete },
            };
        } catch { /* parse error */ }
    }
    // Try GDrive
    const content = await gDriveRead("preferences.jsonc");
    if (content) {
        await idbWrite("preferences.jsonc", content);
        try {
            const data = parseJsonc(content);
            return {
                ui: { ...DEFAULT_PREFERENCES.ui, ...data.ui },
                sync: { ...DEFAULT_PREFERENCES.sync, ...data.sync },
                autocomplete: { ...DEFAULT_PREFERENCES.autocomplete, ...data.autocomplete },
            };
        } catch { /* parse error */ }
    }
    return { ...DEFAULT_PREFERENCES };
}

/** Save preferences */
export async function savePreferences(prefs: any): Promise<void> {
    const content = JSON.stringify(prefs, null, 2);
    await stashPrevIdb("preferences.jsonc", content);
    await idbWrite("preferences.jsonc", content);
    await gDriveWrite("preferences.jsonc", content);
}

/** Load full settings (accounts + preferences combined) */
export async function loadSettings(): Promise<MailxSettings> {
    const accounts = await loadAccounts();
    const prefs = await loadPreferences();
    return {
        accounts,
        ui: prefs.ui,
        sync: prefs.sync,
        autocomplete: prefs.autocomplete as AutocompleteSettings,
        store: {
            basePath: "indexeddb",
            compressionBoundaryDays: 365,
        },
    };
}

/** Save full settings */
export async function saveSettings(settings: MailxSettings): Promise<void> {
    await saveAccounts(settings.accounts);
    await savePreferences({ ui: settings.ui, sync: settings.sync, autocomplete: settings.autocomplete });
}

/** Load allowlist */
export async function loadAllowlist(): Promise<typeof DEFAULT_ALLOWLIST> {
    const cached = await idbRead("allowlist.jsonc");
    if (cached) {
        refreshSharedFile("allowlist.jsonc", cached);
        try { return parseJsonc(cached); } catch { /* */ }
    }
    const content = await gDriveRead("allowlist.jsonc");
    if (content) {
        await idbWrite("allowlist.jsonc", content);
        try { return parseJsonc(content); } catch { /* */ }
    }
    return { ...DEFAULT_ALLOWLIST };
}

const ALLOWLIST_KEYS = ["senders", "domains", "recipients", "flaggedSenders", "flaggedDomains"] as const;
function allowlistEntryCount(l: any): number {
    return ALLOWLIST_KEYS.reduce((n, k) => n + (Array.isArray(l?.[k]) ? l[k].length : 0), 0);
}

/** Read-modify-write against the FRESHEST copy. The load→mutate→save shape
 *  clobbered the shared file twice (2026-07-22/23): a phone whose cached
 *  base was 12 days stale wrote it back minus everything added since, and a
 *  fresh install whose base was DEFAULT_ALLOWLIST wiped the file to ~nothing
 *  (restored from Drive revisions). Mutations now apply to the live cloud
 *  copy when reachable; the cache is only the offline fallback. */
export async function updateAllowlist(mutate: (list: typeof DEFAULT_ALLOWLIST) => typeof DEFAULT_ALLOWLIST | void): Promise<typeof DEFAULT_ALLOWLIST> {
    let base: typeof DEFAULT_ALLOWLIST | null = null;
    try {
        const fresh = await gDriveRead("allowlist.jsonc");
        if (fresh) {
            await idbWrite("allowlist.jsonc", fresh);
            try { base = parseJsonc(fresh); } catch { /* corrupt cloud copy — fall back */ }
        }
    } catch { /* offline / auth pending */ }
    if (!base) base = await loadAllowlist();
    const out = mutate(base) || base;
    await saveAllowlist(out);
    return out;
}

/** Save allowlist. Backstop shrink guard: a write that would discard more
 *  than half of a substantial cloud copy is the wipe signature (stale or
 *  default base) — keep it out of the shared file. The local cache still
 *  updates so the device's own view is consistent; the next
 *  stale-while-revalidate pull re-syncs it with the cloud. */
export async function saveAllowlist(list: typeof DEFAULT_ALLOWLIST): Promise<void> {
    const content = JSON.stringify(list, null, 2);
    await stashPrevIdb("allowlist.jsonc", content);
    await idbWrite("allowlist.jsonc", content);
    try {
        const cloudRaw = await gDriveRead("allowlist.jsonc");
        if (cloudRaw) {
            const cloudCount = allowlistEntryCount(parseJsonc(cloudRaw));
            const newCount = allowlistEntryCount(list);
            if (cloudCount > 10 && newCount < cloudCount / 2) {
                console.error(`[settings] REFUSING allowlist cloud write: ${cloudCount} → ${newCount} entries (stale/default base — see 2026-07-23 wipe). Local cache updated only.`);
                return;
            }
        }
    } catch { /* cloud unreadable — proceed; the guard only fires when it can verify */ }
    await gDriveWrite("allowlist.jsonc", content);
}

/** Load autocomplete settings */
export async function loadAutocomplete(): Promise<AutocompleteSettings> {
    const prefs = await loadPreferences();
    return prefs.autocomplete as AutocompleteSettings;
}

/** Save autocomplete settings */
export async function saveAutocomplete(settings: AutocompleteSettings): Promise<void> {
    const prefs = await loadPreferences();
    (prefs as any).autocomplete = settings;
    await savePreferences(prefs);
}

/** Get history days — read from preferences */
export async function getHistoryDays(): Promise<number> {
    const prefs = await loadPreferences();
    return prefs.sync.historyDays || 30;
}

/** Get prefetch setting */
export async function getPrefetch(): Promise<boolean> {
    const prefs = await loadPreferences();
    return prefs.sync.prefetch !== false;
}

/** Get storage info */
export function getStorageInfo(): { provider: string; mode: string; folderId?: string; folderName?: string; folderPath?: string; folderOwner?: string } {
    return {
        provider: gDriveFolderId ? "gdrive" : "local",
        mode: gDriveFolderId ? "api" : "local",
        folderId: gDriveFolderId || undefined,
        folderName: gDriveFolderName || undefined,
        folderPath: gDriveFolderPath || undefined,
        folderOwner: gDriveFolderOwner || undefined,
    };
}

/** Clear all cached settings — used for "Reset Store" */
export async function clearSettings(): Promise<void> {
    await idbDelete("accounts.jsonc");
    await idbDelete("preferences.jsonc");
    await idbDelete("allowlist.jsonc");
}

// ── Per-device settings ──

const DEVICE_ID_KEY = "mailx-device-id";

/** Get or create a stable device ID (UUID stored in localStorage) */
export function getDeviceId(): string {
    let id = localStorage.getItem(DEVICE_ID_KEY);
    if (!id) {
        id = crypto.randomUUID();
        localStorage.setItem(DEVICE_ID_KEY, id);
    }
    return id;
}

