/**
 * WhatsApp MCP server — exposes WhatsApp lifecycle tools to the admin agent.
 *
 * Tools call the platform's Hono API routes (localhost) which manage the
 * Baileys connection lifecycle in the server process.
 */
import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
initStderrTee("whatsapp");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const PLATFORM_PORT = process.env.PLATFORM_PORT;
if (!PLATFORM_PORT) {
    throw new Error("PLATFORM_PORT environment variable is required — set by getMcpServers() in claude-agent.ts");
}
const BASE_URL = `http://127.0.0.1:${PLATFORM_PORT}`;
// Maxy account ID — used for file path validation in send-document.
// Set by getMcpServers() in claude-agent.ts alongside PLATFORM_PORT.
const MAXY_ACCOUNT_ID = process.env.ACCOUNT_ID;
const server = new McpServer({
    name: "maxy-whatsapp",
    version: "0.1.0",
});
async function callApi(path, method = "POST", body) {
    const res = await fetch(`${BASE_URL}${path}`, {
        method,
        headers: body ? { "Content-Type": "application/json" } : undefined,
        body: body ? JSON.stringify(body) : undefined,
    });
    return res.json();
}
function textResult(text, isError = false) {
    return {
        content: [{ type: "text", text }],
        ...(isError ? { isError: true } : {}),
    };
}
// ─── whatsapp-login-start ──────────────────────────────────────────────
// @ts-expect-error — MCP SDK server.tool() type inference exceeds depth limit with Zod v4
server.tool("whatsapp-login-start", "Start WhatsApp QR code pairing. Initiates a Baileys WebSocket, generates a QR code for the user to scan with their phone. QR code renders automatically in the chat — do not call render-component.", {
    accountId: z.string().optional().describe('Account ID (default: "default")'),
    force: z.boolean().optional().describe("Force re-link even if already connected"),
}, async ({ accountId, force }) => {
    try {
        const result = await callApi("/api/whatsapp/login/start", "POST", { accountId, force });
        if (result.error)
            return textResult(`Login start failed: ${result.error}`, true);
        let response = result.message;
        if (result.qrRaw) {
            response += "\n\nQR code displayed to user. Proceed to whatsapp-login-wait to poll for scan completion.";
            response += `\n__RENDER__${JSON.stringify({ name: "qr-code", data: { value: result.qrRaw, label: "Scan with WhatsApp" } })}`;
        }
        if (result.selfPhone) {
            response += `\nLinked phone: ${result.selfPhone}`;
        }
        return textResult(response);
    }
    catch (err) {
        return textResult(`Login start failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-login-wait ───────────────────────────────────────────────
server.tool("whatsapp-login-wait", "Wait for WhatsApp QR code to be scanned and connection to be established. Call after whatsapp-login-start. Polls for up to 60 seconds.", {
    accountId: z.string().optional().describe('Account ID (default: "default")'),
    timeoutMs: z.number().optional().describe("Max wait time in ms (default: 60000)"),
}, async ({ accountId, timeoutMs }) => {
    try {
        const result = await callApi("/api/whatsapp/login/wait", "POST", { accountId, timeoutMs });
        if (result.error)
            return textResult(`Login wait failed: ${result.error}`, true);
        let response = result.message;
        if (result.connected && result.selfPhone) {
            response += `\nLinked phone: ${result.selfPhone}`;
        }
        return textResult(response);
    }
    catch (err) {
        return textResult(`Login wait failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-status ───────────────────────────────────────────────────
server.tool("whatsapp-status", "Get WhatsApp connection status for all linked accounts. Shows connected/disconnected state, linked phone number, and reconnection attempts.", {}, async () => {
    try {
        const result = await callApi("/api/whatsapp/status", "GET");
        if (result.error)
            return textResult(`Status check failed: ${result.error}`, true);
        const accounts = result.accounts ?? [];
        if (accounts.length === 0) {
            return textResult("No WhatsApp accounts configured. Say \"connect WhatsApp\" to get started.");
        }
        const lines = accounts.map((a) => {
            const status = a.connected ? "✓ Connected" : "✗ Disconnected";
            const phone = a.selfPhone ? ` (${a.selfPhone})` : "";
            const name = a.accountName ? ` — ${a.accountName}` : "";
            const error = a.lastError ? ` — ${a.lastError}` : "";
            const retries = a.reconnectAttempts > 0 ? ` [${a.reconnectAttempts} reconnect attempts]` : "";
            return `${a.accountId}${name}: ${status}${phone}${error}${retries}`;
        });
        return textResult(lines.join("\n"));
    }
    catch (err) {
        return textResult(`Status check failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-activity ────────────────────────────────────────────────
server.tool("whatsapp-activity", "Get WhatsApp channel activity — message counts (inbound and outbound) and recent events since last restart. Shows per-account usage metrics so you can report on channel activity when asked. Counters reset on server restart.", {
    accountId: z.string().optional().describe('Filter to a specific account (default: all accounts)'),
}, async ({ accountId }) => {
    try {
        const query = accountId ? `?accountId=${encodeURIComponent(accountId)}` : "";
        const result = await callApi(`/api/whatsapp/activity${query}`, "GET");
        if (result.error)
            return textResult(`Activity check failed: ${result.error}`, true);
        const accounts = result.accounts ?? [];
        if (accounts.length === 0) {
            return textResult("No WhatsApp activity recorded since last restart.");
        }
        const lines = [];
        for (const a of accounts) {
            const lastAt = a.lastEventAt ? new Date(a.lastEventAt).toISOString() : "never";
            lines.push(`${a.accountId}: ${a.total} messages (${a.inbound} inbound, ${a.outbound} outbound) — last activity: ${lastAt}`);
        }
        const events = result.recentEvents ?? [];
        if (events.length > 0) {
            lines.push("");
            lines.push(`Recent events (${events.length}):`);
            // Show last 10 for readability
            const recent = events.slice(-10);
            for (const e of recent) {
                const ts = new Date(e.timestamp).toISOString();
                lines.push(`  ${ts} ${e.direction} account=${e.accountId} jid=${e.jid}${e.messageType ? ` type=${e.messageType}` : ""}`);
            }
            if (events.length > 10) {
                lines.push(`  ... and ${events.length - 10} more events`);
            }
        }
        return textResult(lines.join("\n"));
    }
    catch (err) {
        return textResult(`Activity check failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-disconnect ───────────────────────────────────────────────
server.tool("whatsapp-disconnect", "Disconnect a WhatsApp account. Closes the Baileys WebSocket connection cleanly.", {
    accountId: z.string().optional().describe('Account ID to disconnect (default: "default")'),
}, async ({ accountId }) => {
    try {
        const result = await callApi("/api/whatsapp/disconnect", "POST", { accountId });
        if (result.error)
            return textResult(`Disconnect failed: ${result.error}`, true);
        return textResult(`WhatsApp account "${result.accountId}" disconnected.`);
    }
    catch (err) {
        return textResult(`Disconnect failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-send ─────────────────────────────────────────────────────
// @ts-expect-error — MCP SDK server.tool() type inference exceeds depth limit with Zod v4
server.tool("whatsapp-send", "Send a WhatsApp message to a phone number or group. The target can be an E.164 phone number or a WhatsApp group JID.", {
    to: z.string().describe("Target phone number (E.164) or group JID"),
    text: z.string().describe("Message text to send"),
    accountId: z.string().optional().describe('Account to send from (default: "default")'),
}, async ({ to, text, accountId }) => {
    try {
        const result = await callApi("/api/whatsapp/send", "POST", { to, text, accountId });
        if (result.error)
            return textResult(`Send failed: ${result.error}`, true);
        return textResult(`Message sent via WhatsApp${result.messageId ? ` (ID: ${result.messageId})` : ""}`);
    }
    catch (err) {
        return textResult(`Send failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-config ──────────────────────────────────────────────────
// @ts-expect-error — MCP SDK server.tool() type inference exceeds depth limit with Zod v4
server.tool("whatsapp-config", "Manage WhatsApp configuration. Actions: add-admin-phone (register a phone as admin — messages from it route to admin agent), remove-admin-phone (demote to public routing), list-admin-phones (show current admin phones), set-public-agent (set which agent handles WhatsApp messages from non-admin phones — requires the agent slug), get-public-agent (show the currently configured public agent), list-public-agents (return available public agents — slug and display name for each), update-config (change any WhatsApp config field — pass field names and values in the fields parameter), get-config (return the full current WhatsApp config object), schema (return all config field definitions — names, types, defaults, descriptions, constraints), list-groups (return WhatsApp groups the account belongs to — names, JIDs, participant counts). Phone numbers must be E.164 format (e.g. +441234567890).", {
    action: z.enum(["add-admin-phone", "remove-admin-phone", "list-admin-phones", "set-public-agent", "get-public-agent", "list-public-agents", "update-config", "get-config", "schema", "list-groups"]).describe("The config action to perform"),
    phone: z.string().optional().describe("Phone number in E.164 format (required for add/remove admin phone)"),
    slug: z.string().optional().describe("Agent slug (required for set-public-agent)"),
    fields: z.string().optional().describe('JSON string of config fields to update (required for update-config, e.g. \'{"dmPolicy":"open","allowFrom":["*"]}\')'),
    accountId: z.string().optional().describe('Account ID for list-groups (default: "default")'),
}, async ({ action, phone, slug, fields, accountId }) => {
    try {
        // Parse fields JSON string for update-config
        let parsedFields;
        if (action === "update-config" && fields) {
            try {
                parsedFields = JSON.parse(fields);
            }
            catch {
                return textResult('Invalid JSON in "fields" parameter. Expected a JSON object, e.g. {"dmPolicy":"open","allowFrom":["*"]}', true);
            }
        }
        const result = await callApi("/api/whatsapp/config", "POST", { action, phone, slug, fields: parsedFields, accountId });
        if (!result.ok)
            return textResult(result.error ?? "Config operation failed.", true);
        if (action === "list-admin-phones") {
            const phones = result.phones ?? [];
            if (phones.length === 0)
                return textResult("No admin phones configured. Use add-admin-phone to register one.");
            return textResult(`Admin phones:\n${phones.map((p) => `  ${p}`).join("\n")}`);
        }
        if (action === "get-public-agent") {
            const agentSlug = result.slug;
            if (!agentSlug)
                return textResult("No public agent configured for WhatsApp. Use set-public-agent to assign one.");
            return textResult(`WhatsApp public agent: ${agentSlug}`);
        }
        if (action === "list-public-agents") {
            const agents = result.agents ?? [];
            if (agents.length === 0)
                return textResult("No public agents found. Create a public agent first using the public-agent-manager skill.");
            const lines = agents.map((a) => `  ${a.slug} — ${a.displayName}`);
            return textResult(`Public agents (${agents.length}):\n${lines.join("\n")}`);
        }
        if (action === "schema") {
            return textResult(result.text ?? "Schema unavailable.");
        }
        if (action === "get-config") {
            const waConfig = result.config;
            if (!waConfig)
                return textResult("No WhatsApp config found. Connect WhatsApp first.");
            return textResult(JSON.stringify(waConfig, null, 2));
        }
        if (action === "list-groups") {
            const groups = result.groups ?? [];
            if (groups.length === 0)
                return textResult("No WhatsApp groups found for this account. Groups appear once the connected phone joins them.");
            const lines = groups.map((g) => `  ${g.name} (${g.participantCount} members) — ${g.jid}`);
            return textResult(`WhatsApp groups (${groups.length}):\n${lines.join("\n")}`);
        }
        return textResult(result.message ?? "Done.");
    }
    catch (err) {
        return textResult(`Config operation failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-send-document ───────────────────────────────────────────
server.tool("whatsapp-send-document", "Send a file as a WhatsApp document message. The file is read from disk and sent as a downloadable document attachment. The file must be within the account directory tree.", {
    to: z.string().describe("Target phone number (E.164) or group JID"),
    filePath: z.string().describe("Absolute path to the file to send"),
    caption: z.string().optional().describe("Optional caption text to accompany the document"),
    accountId: z.string().optional().describe('Account to send from (default: "default")'),
}, async ({ to, filePath, caption, accountId }) => {
    try {
        const result = await callApi("/api/whatsapp/send-document", "POST", {
            to, filePath, caption, accountId, maxyAccountId: MAXY_ACCOUNT_ID,
        });
        if (result.error)
            return textResult(`Send failed: ${result.error}`, true);
        return textResult(`Document sent via WhatsApp${result.messageId ? ` (ID: ${result.messageId})` : ""}`);
    }
    catch (err) {
        return textResult(`Send failed: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-conversations ──────────────────────────────────────────
server.tool("whatsapp-conversations", "List WhatsApp conversations with recent message activity. Returns each conversation's JID, type (group or DM), last message timestamp, and message count. Messages are from the in-memory store (clears on server restart). Use this to see which conversations have recent activity before retrieving specific messages with whatsapp-messages.", {
    accountId: z.string().optional().describe('Account ID (default: "default")'),
}, async ({ accountId }) => {
    try {
        const id = accountId ?? "default";
        console.error(`[whatsapp-mcp] whatsapp-conversations called for account ${id}`);
        const result = await callApi(`/api/whatsapp/conversations?accountId=${encodeURIComponent(id)}`, "GET");
        if (result.error)
            return textResult(`Failed to list conversations: ${result.error}`, true);
        const conversations = result.conversations ?? [];
        if (conversations.length === 0) {
            return textResult("No conversations with stored messages. Messages are stored when they arrive while the server is running.");
        }
        const lines = conversations.map((c) => {
            const type = c.type === "group" ? "[group]" : "[DM]";
            const ts = c.lastMessageTimestamp
                ? new Date(c.lastMessageTimestamp * 1000).toISOString()
                : "unknown";
            return `${type} ${c.jid} — ${c.messageCount} messages, last: ${ts}`;
        });
        return textResult(`Conversations (${conversations.length}):\n${lines.join("\n")}`);
    }
    catch (err) {
        console.error(`[whatsapp-mcp] whatsapp-conversations error: ${err instanceof Error ? err.message : String(err)}`);
        return textResult(`Failed to list conversations: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-messages ───────────────────────────────────────────────
server.tool("whatsapp-messages", "Retrieve stored WhatsApp messages for a specific conversation. Returns messages in chronological order (oldest first) with sender, body, timestamp, and reply context. Messages are from the in-memory store (clears on server restart). Use whatsapp-conversations first to see available JIDs.", {
    jid: z.string().describe("Conversation JID (phone@s.whatsapp.net for DMs, id@g.us for groups)"),
    accountId: z.string().optional().describe('Account ID (default: "default")'),
    limit: z.number().optional().describe("Max number of most recent messages to return (default: all)"),
}, async ({ jid, accountId, limit }) => {
    try {
        const id = accountId ?? "default";
        console.error(`[whatsapp-mcp] whatsapp-messages called for account ${id} jid=${jid} limit=${limit ?? "all"}`);
        const params = new URLSearchParams({ accountId: id, jid });
        if (limit !== undefined)
            params.set("limit", String(limit));
        const result = await callApi(`/api/whatsapp/messages?${params}`, "GET");
        if (result.error)
            return textResult(`Failed to get messages: ${result.error}`, true);
        const messages = result.messages ?? [];
        if (messages.length === 0) {
            return textResult(`No stored messages for ${jid}. Messages appear when they arrive while the server is running.`);
        }
        const lines = messages.map((m) => {
            const ts = new Date(m.timestamp * 1000).toISOString();
            const direction = m.fromMe ? "→" : "←";
            const sender = m.senderName ? `${m.senderName} (${m.sender})` : m.sender;
            let line = `[${ts}] ${direction} ${sender}: ${m.body}`;
            if (m.quoted?.body) {
                line += `\n  ↳ replying to: "${m.quoted.body}"`;
            }
            return line;
        });
        return textResult(`Messages for ${jid} (${messages.length}):\n${lines.join("\n")}`);
    }
    catch (err) {
        console.error(`[whatsapp-mcp] whatsapp-messages error: ${err instanceof Error ? err.message : String(err)}`);
        return textResult(`Failed to get messages: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── whatsapp-group-info ─────────────────────────────────────────────
server.tool("whatsapp-group-info", "Get metadata for a WhatsApp group: subject (name), description, participant list with admin flags, creation date, and owner. Queries WhatsApp servers directly (requires active connection). The JID must be a group JID ending in @g.us — use whatsapp-conversations or whatsapp-config action list-groups to find group JIDs.", {
    jid: z.string().describe("Group JID (must end with @g.us)"),
    accountId: z.string().optional().describe('Account ID (default: "default")'),
}, async ({ jid, accountId }) => {
    try {
        const id = accountId ?? "default";
        console.error(`[whatsapp-mcp] whatsapp-group-info called for account ${id} jid=${jid}`);
        const params = new URLSearchParams({ accountId: id, jid });
        const result = await callApi(`/api/whatsapp/group-info?${params}`, "GET");
        if (result.error)
            return textResult(`Failed to get group info: ${result.error}`, true);
        const parts = (result.participants ?? []).map((p) => {
            const role = p.admin === "superadmin" ? " (super admin)" : p.admin === "admin" ? " (admin)" : "";
            return `  ${p.jid}${role}`;
        });
        const lines = [
            `Group: ${result.subject}`,
            result.description ? `Description: ${result.description}` : null,
            result.owner ? `Owner: ${result.owner}` : null,
            result.creation ? `Created: ${new Date(result.creation * 1000).toISOString()}` : null,
            `Participants (${result.participantCount}):`,
            ...parts,
        ].filter(Boolean);
        return textResult(lines.join("\n"));
    }
    catch (err) {
        console.error(`[whatsapp-mcp] whatsapp-group-info error: ${err instanceof Error ? err.message : String(err)}`);
        return textResult(`Failed to get group info: ${err instanceof Error ? err.message : String(err)}`, true);
    }
});
// ─── Start server ──────────────────────────────────────────────────────
const transport = new StdioServerTransport();
await server.connect(transport);
//# sourceMappingURL=index.js.map