import { appendFile, mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
import { pathToFileURL } from 'node:url';
export function normalizeBaileysImportError(error) {
    if (error instanceof Error) {
        const message = error.message;
        if (message.includes("Cannot find package '@whiskeysockets/baileys'") ||
            message.includes("Cannot find module '@whiskeysockets/baileys'")) {
            return new Error('WhatsApp phone control requires the optional package @whiskeysockets/baileys, but it is not installed. This usually means the package manager skipped the WhatsApp transport because its GitHub dependency was blocked during install. Reinstall in an environment that can reach GitHub, or keep using ShortcutXL without WhatsApp phone control.');
        }
    }
    return error instanceof Error ? error : new Error(String(error));
}
function unwrapMessageContainer(message) {
    let current = message;
    for (let index = 0; index < 8 && current; index += 1) {
        const next = current.ephemeralMessage?.message ??
            current.viewOnceMessage?.message ??
            current.viewOnceMessageV2?.message ??
            current.viewOnceMessageV2Extension?.message ??
            current.documentWithCaptionMessage?.message ??
            current.editedMessage?.message ??
            current.protocolMessage?.editedMessage;
        if (!next || next === current) {
            return current;
        }
        current = next;
    }
    return current;
}
function extractMessageText(message) {
    const current = unwrapMessageContainer(message);
    if (!current) {
        return null;
    }
    const directCandidates = [
        current.conversation,
        current.extendedTextMessage?.text,
        current.imageMessage?.caption,
        current.videoMessage?.caption,
        current.documentMessage?.caption,
        current.buttonsResponseMessage?.selectedDisplayText,
        current.buttonsResponseMessage?.selectedButtonId,
        current.listResponseMessage?.title,
        current.templateButtonReplyMessage?.selectedDisplayText,
        current.interactiveResponseMessage?.body?.text,
        current.interactiveResponseMessage?.nativeFlowResponseMessage?.paramsJson
    ];
    for (const candidate of directCandidates) {
        if (typeof candidate === 'string' && candidate.trim().length > 0) {
            return candidate;
        }
    }
    return null;
}
async function appendJsonlLog(filePath, entry) {
    if (!filePath) {
        return;
    }
    await mkdir(dirname(filePath), { recursive: true });
    await appendFile(filePath, `${JSON.stringify({ ...entry, timestamp: new Date().toISOString() })}\n`, 'utf8');
}
function createSilentBaileysLogger() {
    const logger = {
        level: 'silent',
        child() {
            return logger;
        },
        trace() { },
        debug() { },
        info() { },
        warn() { },
        error() { }
    };
    return logger;
}
function getDisconnectStatusCode(update) {
    const error = update?.lastDisconnect?.error;
    return error?.output?.statusCode ?? error?.data ?? error?.statusCode;
}
function shouldReconnectBaileysSession(statusCode) {
    return ![401, 403, 411, 440].includes(statusCode ?? -1);
}
function normalizeJidUser(jid) {
    if (typeof jid !== 'string' || jid.trim().length === 0) {
        return null;
    }
    const localPart = jid.split('@', 1)[0];
    if (!localPart) {
        return null;
    }
    return localPart.split(':', 1)[0] || null;
}
function normalizeTransportFactory(module, modulePath) {
    const candidates = [
        module.default,
        module.createTransportFactory,
        module.createTransport
    ];
    const factory = candidates.find((candidate) => typeof candidate === 'function');
    if (!factory) {
        throw new Error(`WhatsApp transport module ${modulePath} must export a function as default, createTransportFactory, or createTransport`);
    }
    return factory;
}
export async function loadWhatsAppTransportFactory(modulePath) {
    const imported = (await import(pathToFileURL(modulePath).href));
    return normalizeTransportFactory(imported, modulePath);
}
export async function createBaileysTransportWithModule(baileys, options) {
    await mkdir(options.authDir, { recursive: true });
    const { state, saveCreds } = await baileys.useMultiFileAuthState(options.authDir);
    const { version } = await baileys.fetchLatestBaileysVersion();
    const ownChatUsers = new Set();
    const recentOutboundIds = new Set();
    const listeners = new Set();
    const logger = createSilentBaileysLogger();
    let disposed = false;
    let socket = null;
    let socketState = 'closed';
    let reconnectTimer = null;
    let reconnectAttempts = 0;
    let openPromise = null;
    const refreshOwnChatUsers = () => {
        ownChatUsers.clear();
        for (const jid of [state.creds.me?.id, state.creds.me?.lid]) {
            const normalized = normalizeJidUser(jid);
            if (normalized) {
                ownChatUsers.add(normalized);
            }
        }
    };
    refreshOwnChatUsers();
    const classifySelfChat = (chatId) => typeof chatId === 'string' && ownChatUsers.has(normalizeJidUser(chatId) ?? '__missing__');
    const createMessageListener = (owningSocket) => (upsert) => {
        if (socket !== owningSocket) {
            return;
        }
        refreshOwnChatUsers();
        void appendJsonlLog(options.logFilePath, {
            type: 'baileys_messages_upsert',
            upsertType: upsert?.type,
            messageCount: Array.isArray(upsert?.messages) ? upsert.messages.length : 0
        });
        const messages = Array.isArray(upsert?.messages) ? upsert.messages : [];
        for (const entry of messages) {
            const messageId = entry?.key?.id;
            if (typeof messageId === 'string' && recentOutboundIds.has(messageId)) {
                recentOutboundIds.delete(messageId);
                void appendJsonlLog(options.logFilePath, {
                    type: 'baileys_message_ignored',
                    reason: 'recent_outbound',
                    messageId
                });
                continue;
            }
            const chatId = entry?.key?.remoteJid;
            const fromMe = entry?.key?.fromMe === true;
            const text = extractMessageText(entry?.message);
            const isSelfChat = typeof chatId === 'string' && classifySelfChat(chatId);
            void appendJsonlLog(options.logFilePath, {
                type: 'baileys_message_received',
                messageId,
                chatId,
                fromMe,
                isSelfChat,
                messageKeys: entry?.message && typeof entry.message === 'object'
                    ? Object.keys(entry.message).slice(0, 20)
                    : [],
                textPreview: typeof text === 'string' ? text.slice(0, 160) : null
            });
            if (typeof chatId !== 'string' || !text) {
                void appendJsonlLog(options.logFilePath, {
                    type: 'baileys_message_skipped',
                    reason: typeof chatId !== 'string' ? 'missing_chat_id' : 'missing_text',
                    messageId
                });
                continue;
            }
            for (const listener of listeners) {
                void listener({ chatId, text, messageId, isSelfChat });
            }
        }
    };
    const scheduleReconnect = (reason, statusCode) => {
        if (disposed || reconnectTimer || openPromise) {
            return;
        }
        reconnectAttempts += 1;
        const delayMs = Math.min(1_000 * 2 ** (reconnectAttempts - 1), 10_000);
        options.onInfo?.(`WhatsApp connection dropped${statusCode ? ` (${statusCode})` : ''}; retrying in ${Math.round(delayMs / 1000)}s.`);
        void appendJsonlLog(options.logFilePath, {
            type: 'baileys_reconnect_scheduled',
            reason,
            statusCode,
            delayMs,
            reconnectAttempts
        });
        reconnectTimer = setTimeout(() => {
            reconnectTimer = null;
            void ensureSocketOpen(`reconnect:${reason}`);
        }, delayMs);
    };
    const attachSocket = (nextSocket) => {
        socket = nextSocket;
        socketState = 'opening';
        const messageListener = createMessageListener(nextSocket);
        nextSocket.ev.on('creds.update', () => {
            if (socket !== nextSocket) {
                return;
            }
            refreshOwnChatUsers();
            void saveCreds();
        });
        nextSocket.ev.on('connection.update', (update) => {
            if (socket !== nextSocket) {
                return;
            }
            if (typeof update?.qr === 'string' && update.qr.trim().length > 0) {
                options.onInfo?.('WhatsApp link required. Scan the QR in your terminal.');
                void appendJsonlLog(options.logFilePath, {
                    type: 'baileys_connection_update',
                    connection: update?.connection ?? null,
                    hasQr: true
                });
                void options.onQr?.(update.qr);
                return;
            }
            if (update?.connection === 'open') {
                refreshOwnChatUsers();
                socketState = 'open';
                reconnectAttempts = 0;
                if (reconnectTimer) {
                    clearTimeout(reconnectTimer);
                    reconnectTimer = null;
                }
                options.onInfo?.('WhatsApp linked-device session connected.');
                void appendJsonlLog(options.logFilePath, {
                    type: 'baileys_connection_update',
                    connection: 'open',
                    ownChatUsers: [...ownChatUsers]
                });
                return;
            }
            if (update?.connection === 'close') {
                socketState = 'closed';
                const statusCode = getDisconnectStatusCode(update);
                void appendJsonlLog(options.logFilePath, {
                    type: 'baileys_connection_update',
                    connection: 'close',
                    statusCode
                });
                if (!shouldReconnectBaileysSession(statusCode)) {
                    options.onInfo?.(`WhatsApp session ended${statusCode ? ` (${statusCode})` : ''}. Re-link may be required.`);
                    return;
                }
                scheduleReconnect('connection.close', statusCode);
            }
        });
        nextSocket.ev.on('messages.upsert', messageListener);
    };
    const ensureSocketOpen = async (reason) => {
        if (disposed) {
            return;
        }
        const explicitReconnect = reason.startsWith('reconnect:') || reason === 'send-retry';
        if (!explicitReconnect && socket && socketState === 'open') {
            return;
        }
        if (openPromise) {
            await openPromise;
            return;
        }
        openPromise = (async () => {
            if (explicitReconnect && reconnectTimer) {
                clearTimeout(reconnectTimer);
                reconnectTimer = null;
            }
            if (socket && (explicitReconnect || socketState === 'closed' || reason === 'initial')) {
                try {
                    socket.ws?.close?.();
                }
                catch { }
            }
            options.onInfo?.(reason === 'initial'
                ? 'Connecting WhatsApp linked-device session...'
                : 'Reconnecting WhatsApp linked-device session...');
            void appendJsonlLog(options.logFilePath, {
                type: 'baileys_socket_open_start',
                reason
            });
            const nextSocket = baileys.makeWASocket({
                auth: {
                    creds: state.creds,
                    keys: state.keys
                },
                version,
                logger,
                printQRInTerminal: false,
                browser: ['shortcutxl', 'remote-control', '0.1']
            });
            attachSocket(nextSocket);
        })();
        try {
            await openPromise;
        }
        finally {
            openPromise = null;
        }
    };
    return {
        async connect() {
            await mkdir(options.authDir, { recursive: true });
            await ensureSocketOpen('initial');
        },
        async close() {
            disposed = true;
            if (reconnectTimer) {
                clearTimeout(reconnectTimer);
                reconnectTimer = null;
            }
            try {
                socket?.ws?.close?.();
            }
            catch { }
            socketState = 'closed';
            socket = null;
        },
        isSelfChat(chatId) {
            refreshOwnChatUsers();
            return classifySelfChat(chatId);
        },
        async sendText(chatId, text) {
            refreshOwnChatUsers();
            if (!classifySelfChat(chatId)) {
                const error = new Error(`Refusing to send WhatsApp message to non-self chat ${chatId}. Outbound phone control must stay on Message yourself.`);
                void appendJsonlLog(options.logFilePath, {
                    type: 'baileys_send_rejected_non_self_chat',
                    chatId,
                    error: error.message
                });
                throw error;
            }
            await ensureSocketOpen('send');
            const activeSocket = socket;
            if (!activeSocket) {
                throw new Error('WhatsApp socket is not connected');
            }
            try {
                const result = await activeSocket.sendMessage(chatId, { text });
                const outboundId = result?.key?.id;
                if (typeof outboundId === 'string' && outboundId.length > 0) {
                    recentOutboundIds.add(outboundId);
                    setTimeout(() => {
                        recentOutboundIds.delete(outboundId);
                    }, 30_000);
                }
            }
            catch (error) {
                scheduleReconnect('send.error');
                try {
                    await ensureSocketOpen('send-retry');
                    const retriedSocket = socket;
                    if (!retriedSocket) {
                        throw error;
                    }
                    const result = await retriedSocket.sendMessage(chatId, { text });
                    const outboundId = result?.key?.id;
                    if (typeof outboundId === 'string' && outboundId.length > 0) {
                        recentOutboundIds.add(outboundId);
                        setTimeout(() => {
                            recentOutboundIds.delete(outboundId);
                        }, 30_000);
                    }
                }
                catch (retryError) {
                    void appendJsonlLog(options.logFilePath, {
                        type: 'baileys_send_failed',
                        chatId,
                        error: retryError instanceof Error
                            ? `${retryError.name}: ${retryError.message}`
                            : String(retryError)
                    });
                    throw retryError instanceof Error ? retryError : new Error(String(retryError));
                }
            }
        },
        onMessage(listener) {
            listeners.add(listener);
            return () => {
                listeners.delete(listener);
            };
        }
    };
}
async function createBaileysTransport(options) {
    try {
        const baileysPackageName = '@whiskeysockets/baileys';
        const baileys = (await import(baileysPackageName));
        return await createBaileysTransportWithModule(baileys, options);
    }
    catch (error) {
        throw normalizeBaileysImportError(error);
    }
}
export const createDefaultWhatsAppTransport = createBaileysTransport;
//# sourceMappingURL=transport.js.map