// Gmail mailbox client (Gmail REST API v1).
const BASE = "https://gmail.googleapis.com/gmail/v1/users/me";
// Map friendly folder names to Gmail system label ids; pass real label ids through.
const SYSTEM_LABELS = {
    inbox: "INBOX", sent: "SENT", drafts: "DRAFT", draft: "DRAFT",
    spam: "SPAM", trash: "TRASH", starred: "STARRED", important: "IMPORTANT", unread: "UNREAD",
};
function normalizeLabelId(folder) {
    return SYSTEM_LABELS[folder.toLowerCase()] ?? folder;
}
function header(headers, name) {
    return headers?.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
}
function decodeBody(data) {
    if (!data)
        return "";
    return Buffer.from(data.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
}
/** Walk the MIME tree collecting the first text/plain and text/html bodies. */
function extractBodies(part, acc) {
    if (!part)
        return;
    const mime = part.mimeType ?? "";
    if (part.filename)
        acc.attachments = true;
    if (mime === "text/plain" && !acc.text)
        acc.text = decodeBody(part.body?.data);
    else if (mime === "text/html" && !acc.html)
        acc.html = decodeBody(part.body?.data);
    for (const child of part.parts ?? [])
        extractBodies(child, acc);
}
export class GmailClient {
    async gfetch(accessToken, path, init) {
        const res = await fetch(`${BASE}${path}`, {
            ...init,
            headers: {
                Authorization: `Bearer ${accessToken}`,
                ...(init?.body ? { "Content-Type": "application/json" } : {}),
                ...(init?.headers ?? {}),
            },
        });
        const text = await res.text();
        const data = text ? JSON.parse(text) : {};
        if (!res.ok) {
            const msg = data?.error?.message ?? `HTTP ${res.status}`;
            throw new Error(`Gmail API error: ${msg}`);
        }
        return data;
    }
    summarize(m, labelNames) {
        const h = m.payload?.headers;
        const labelIds = m.labelIds ?? [];
        return {
            id: m.id,
            threadId: m.threadId,
            from: header(h, "From"),
            to: header(h, "To"),
            subject: header(h, "Subject"),
            snippet: m.snippet ?? "",
            date: header(h, "Date"),
            unread: labelIds.includes("UNREAD"),
            labels: labelIds
                .filter((id) => !id.startsWith("CATEGORY_"))
                .map((id) => labelNames.get(id) ?? id),
            hasAttachments: false,
        };
    }
    async labelMaps(accessToken) {
        const data = (await this.gfetch(accessToken, "/labels"));
        const byId = new Map();
        const byName = new Map();
        for (const l of data.labels ?? []) {
            byId.set(l.id, l.name);
            byName.set(l.name.toLowerCase(), l.id);
        }
        return { byId, byName };
    }
    async listMessages(accessToken, opts) {
        const limit = Math.min(Math.max(opts.limit ?? 25, 1), 50);
        const params = new URLSearchParams({ maxResults: String(limit) });
        if (opts.query)
            params.set("q", opts.query);
        if (opts.folder && opts.folder.toLowerCase() !== "all") {
            params.set("labelIds", normalizeLabelId(opts.folder));
        }
        const list = (await this.gfetch(accessToken, `/messages?${params.toString()}`));
        const ids = (list.messages ?? []).map((x) => x.id);
        if (ids.length === 0)
            return [];
        const { byId } = await this.labelMaps(accessToken);
        const metas = await Promise.all(ids.map((id) => this.gfetch(accessToken, `/messages/${id}?format=metadata&metadataHeaders=From&metadataHeaders=To&metadataHeaders=Subject&metadataHeaders=Date`)));
        return metas.map((m) => this.summarize(m, byId));
    }
    async getMessage(accessToken, id) {
        const m = (await this.gfetch(accessToken, `/messages/${id}?format=full`));
        const { byId } = await this.labelMaps(accessToken);
        const summary = this.summarize(m, byId);
        const bodies = { text: "", html: "", attachments: false };
        extractBodies(m.payload, bodies);
        return {
            ...summary,
            cc: header(m.payload?.headers, "Cc"),
            bodyText: bodies.text,
            bodyHtml: bodies.html,
            hasAttachments: bodies.attachments,
        };
    }
    async sendMessage(accessToken, input) {
        const lines = [
            `To: ${input.to}`,
            input.cc ? `Cc: ${input.cc}` : "",
            input.bcc ? `Bcc: ${input.bcc}` : "",
            `Subject: ${input.subject}`,
            "MIME-Version: 1.0",
            `Content-Type: ${input.html ? "text/html" : "text/plain"}; charset=UTF-8`,
            "",
            input.body,
        ].filter(Boolean);
        const raw = Buffer.from(lines.join("\r\n"), "utf8").toString("base64url");
        const body = { raw };
        if (input.replyToMessageId) {
            const orig = (await this.gfetch(accessToken, `/messages/${input.replyToMessageId}?format=metadata`));
            if (orig.threadId)
                body.threadId = orig.threadId;
        }
        const sent = (await this.gfetch(accessToken, "/messages/send", {
            method: "POST",
            body: JSON.stringify(body),
        }));
        return { id: sent.id };
    }
    async tagMessage(accessToken, id, add, remove) {
        const { byName } = await this.labelMaps(accessToken);
        const resolve = async (name) => {
            const builtin = name.toUpperCase();
            if (["INBOX", "UNREAD", "STARRED", "IMPORTANT", "SPAM", "TRASH"].includes(builtin))
                return builtin;
            const existing = byName.get(name.toLowerCase());
            if (existing)
                return existing;
            const created = (await this.gfetch(accessToken, "/labels", {
                method: "POST",
                body: JSON.stringify({ name, labelListVisibility: "labelShow", messageListVisibility: "show" }),
            }));
            byName.set(name.toLowerCase(), created.id);
            return created.id;
        };
        const addLabelIds = await Promise.all(add.map(resolve));
        const removeLabelIds = await Promise.all(remove.map(resolve));
        await this.gfetch(accessToken, `/messages/${id}/modify`, {
            method: "POST",
            body: JSON.stringify({ addLabelIds, removeLabelIds }),
        });
    }
    async deleteMessage(accessToken, id) {
        await this.gfetch(accessToken, `/messages/${id}/trash`, { method: "POST" });
    }
    async listFolders(accessToken) {
        const data = (await this.gfetch(accessToken, "/labels"));
        return (data.labels ?? [])
            .filter((l) => !l.id.startsWith("CATEGORY_"))
            .map((l) => ({ id: l.id, name: l.name }));
    }
    async getProfileEmail(accessToken) {
        const data = (await this.gfetch(accessToken, "/profile"));
        return data.emailAddress ?? "";
    }
}
//# sourceMappingURL=gmail.js.map