/**
 * WhatsApp channel connector (baileys / WhatsApp Web).
 *
 * Connects using the multi-file auth state (QR link on first run, then
 * persisted credentials). Emits a QR data-URL for the UI, surfaces inbound
 * text messages, and sends replies.
 *
 * baileys + qrcode are lazy-imported by the default factories so unit tests can
 * inject fakes without importing the native-ish deps.
 */
import { join } from "node:path";
import { homedir } from "node:os";
import { rm } from "node:fs/promises";
/** Extract plain text from a baileys message, if any. */
export function extractText(message) {
    if (!message)
        return "";
    const m = message;
    if (m.conversation)
        return m.conversation;
    if (m.extendedTextMessage?.text)
        return m.extendedTextMessage.text;
    if (m.imageMessage?.caption)
        return m.imageMessage.caption;
    if (m.videoMessage?.caption)
        return m.videoMessage.caption;
    // Unwrap ephemeral / view-once wrappers.
    if (m.ephemeralMessage?.message)
        return extractText(m.ephemeralMessage.message);
    if (m.viewOnceMessage?.message)
        return extractText(m.viewOnceMessage.message);
    return "";
}
/** Bare number portion of a JID, e.g. "49170...:5@s.whatsapp.net" → "49170...". */
export function bareJid(jid) {
    if (!jid)
        return "";
    return jid.split("@")[0].split(":")[0];
}
function tsToMillis(ts) {
    if (ts == null)
        return Date.now();
    if (typeof ts === "number")
        return ts * 1000;
    if (typeof ts === "object" && typeof ts.toNumber === "function") {
        return ts.toNumber() * 1000;
    }
    return Date.now();
}
export class WhatsAppConnector {
    id = "whatsapp";
    label = "WhatsApp";
    authDir;
    deps;
    sock = null;
    _status = "stopped";
    _qr = null;
    /** Bare ids that identify this account (phone-number JID and LID alias). */
    selfBares = new Set();
    events = null;
    stopping = false;
    constructor(deps = {}) {
        this.deps = deps;
        this.authDir = deps.authDir ?? join(homedir(), ".jait", "channels", "whatsapp");
    }
    status() { return this._status; }
    currentQr() { return this._qr; }
    log(msg, ...args) {
        (this.deps.log ?? ((m, ...a) => console.log("[whatsapp]", m, ...a)))(msg, ...args);
    }
    setStatus(status, detail) {
        this._status = status;
        if (status === "qr")
            this._qr = detail?.qr ?? this._qr;
        if (status === "connected" || status === "stopped")
            this._qr = null;
        this.events?.onStatus(status, detail);
    }
    async start(events) {
        this.events = events;
        this.stopping = false;
        await this.connect();
    }
    async loadAuth() {
        if (this.deps.loadAuthState)
            return this.deps.loadAuthState(this.authDir);
        const baileys = await import("baileys");
        return baileys.useMultiFileAuthState(this.authDir);
    }
    async buildSocket(auth) {
        if (this.deps.makeSocket) {
            return this.deps.makeSocket({ auth, printQRInTerminal: false, browser: ["Jait", "Chrome", "1.0.0"] });
        }
        const baileys = await import("baileys");
        const makeWASocket = (baileys.default ?? baileys.makeWASocket);
        const browser = (baileys.Browsers?.appropriate?.("Jait")) ?? ["Jait", "Chrome", "1.0.0"];
        return makeWASocket({ auth, printQRInTerminal: false, browser, syncFullHistory: false });
    }
    async encodeQr(text) {
        if (this.deps.encodeQr)
            return this.deps.encodeQr(text);
        const qrcode = await import("qrcode");
        return qrcode.toDataURL(text, { margin: 2, width: 512 });
    }
    async connect() {
        this.setStatus("connecting");
        const { state, saveCreds } = await this.loadAuth();
        const sock = await this.buildSocket(state);
        this.sock = sock;
        sock.ev.on("creds.update", () => { void saveCreds(); });
        sock.ev.on("connection.update", (arg) => {
            const u = arg;
            if (u.qr) {
                void this.encodeQr(u.qr)
                    .then((dataUrl) => { this.setStatus("qr", { qr: dataUrl }); })
                    .catch((err) => { this.log("QR encode failed:", err); });
            }
            if (u.connection === "open") {
                this.selfBares = new Set([sock.user?.id, sock.user?.lid].map(bareJid).filter(Boolean));
                this.setStatus("connected");
                this.log(`connected as ${[...this.selfBares].join(", ") || "?"}`);
            }
            else if (u.connection === "close") {
                const code = u.lastDisconnect?.error?.output?.statusCode;
                const loggedOut = code === (this.deps.loggedOutStatusCode ?? 401);
                if (this.stopping) {
                    this.setStatus("stopped");
                }
                else if (loggedOut) {
                    this.log("logged out — clearing credentials");
                    void rm(this.authDir, { recursive: true, force: true }).catch(() => { });
                    this.setStatus("error", { error: "Logged out. Re-link to reconnect." });
                }
                else {
                    this.log(`connection closed (code ${code ?? "?"}) — reconnecting`);
                    void this.connect().catch((err) => {
                        this.setStatus("error", { error: err instanceof Error ? err.message : String(err) });
                    });
                }
            }
        });
        sock.ev.on("messages.upsert", (arg) => {
            const payload = arg;
            if (payload.type && payload.type !== "notify")
                return;
            for (const m of payload.messages ?? []) {
                const inbound = this.toInbound(m);
                if (inbound)
                    this.events?.onInbound(inbound);
            }
        });
    }
    /** Convert a baileys message to an InboundMessage, or null if not text. */
    toInbound(m) {
        const text = extractText(m.message);
        if (!text.trim())
            return null;
        const conversationId = m.key.remoteJid ?? "";
        if (!conversationId || conversationId === "status@broadcast")
            return null;
        const senderId = m.key.participant ?? conversationId;
        const isSelfChat = this.selfBares.has(bareJid(conversationId));
        return {
            channelId: this.id,
            conversationId,
            senderId,
            senderName: m.pushName ?? undefined,
            text,
            timestamp: tsToMillis(m.messageTimestamp),
            fromMe: Boolean(m.key.fromMe),
            isSelfChat,
        };
    }
    async send(msg) {
        if (!this.sock)
            throw new Error("WhatsApp is not connected");
        await this.sock.sendMessage(msg.conversationId, { text: msg.text });
    }
    async stop() {
        this.stopping = true;
        try {
            this.sock?.end?.(undefined);
            this.sock?.ws?.close();
        }
        catch { /* best effort */ }
        this.sock = null;
        this.setStatus("stopped");
    }
}
//# sourceMappingURL=connector.js.map