"use strict";
// =============================================================================
// Pure Agent - WeCom (Enterprise WeChat) Platform Adapter
// Ported from Hermes gateway/platforms/wecom.py
// Custom WebSocket protocol for bidirectional communication
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.WeComAdapter = void 0;
const config_1 = require("../config");
const base_1 = require("./base");
const logger_1 = require("../../logger");
const platform_registry_1 = require("../platform-registry");
// ---------------------------------------------------------------------------
// WeComAdapter
// ---------------------------------------------------------------------------
class WeComAdapter extends base_1.BasePlatformAdapter {
    _botId;
    _secret;
    _wsUrl;
    _ws = null;
    _deviceId;
    _reqIdCounter = 0;
    _pendingRequests = new Map();
    constructor(config) {
        super(config_1.Platform.WECOM, config);
        this._botId = config.extra?.bot_id || process.env.WECOM_BOT_ID || '';
        this._secret = config.extra?.secret || process.env.WECOM_SECRET || '';
        this._wsUrl = config.extra?.websocket_url || process.env.WECOM_WEBSOCKET_URL || 'wss://openws.work.weixin.qq.com';
        this._deviceId = `pa-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    }
    // -------------------------------------------------------------------------
    // Connection
    // -------------------------------------------------------------------------
    async connect() {
        if (!this._botId || !this._secret) {
            (0, logger_1.getLogger)().error('WeCom: missing bot_id or secret');
            return false;
        }
        try {
            await this._connectWebSocket();
            this._connected = true;
            (0, logger_1.getLogger)().info(`WeCom: connected (bot: ${this._botId})`);
            return true;
        }
        catch (err) {
            (0, logger_1.getLogger)().error(`WeCom: connection failed: ${err.message}`);
            return false;
        }
    }
    async disconnect() {
        this._connected = false;
        if (this._ws) {
            this._ws.close();
            this._ws = null;
        }
        // Reject all pending requests
        for (const [, pending] of this._pendingRequests) {
            clearTimeout(pending.timer);
            pending.reject(new Error('Disconnected'));
        }
        this._pendingRequests.clear();
        (0, logger_1.getLogger)().info('WeCom: disconnected');
    }
    // -------------------------------------------------------------------------
    // WebSocket connection
    // -------------------------------------------------------------------------
    async _connectWebSocket() {
        return new Promise((resolve, reject) => {
            const ws = new WebSocket(this._wsUrl);
            this._ws = ws;
            const timeout = setTimeout(() => {
                ws.close();
                reject(new Error('WebSocket connect timeout'));
            }, 15000);
            ws.onopen = () => {
                // Send subscribe frame
                const reqId = this._nextReqId();
                const subscribeFrame = {
                    cmd: 'aibot_subscribe',
                    headers: { req_id: reqId },
                    body: {
                        bot_id: this._botId,
                        secret: this._secret,
                        device_id: this._deviceId,
                    },
                };
                const pending = {
                    resolve: () => { clearTimeout(timeout); resolve(); },
                    reject: (err) => { clearTimeout(timeout); reject(err); },
                    timer: setTimeout(() => {
                        this._pendingRequests.delete(reqId);
                        reject(new Error('Subscribe timeout'));
                    }, 10000),
                };
                this._pendingRequests.set(reqId, pending);
                ws.send(JSON.stringify(subscribeFrame));
            };
            ws.onmessage = (event) => {
                try {
                    const frame = JSON.parse(typeof event.data === 'string' ? event.data : event.data.toString());
                    this._onFrame(frame);
                }
                catch (err) {
                    (0, logger_1.getLogger)().warn(`WeCom: failed to parse frame`);
                }
            };
            ws.onclose = () => {
                this._connected = false;
                if (this._ws === ws)
                    this._ws = null;
                (0, logger_1.getLogger)().info('WeCom: WebSocket closed');
                // Auto-reconnect after delay
                setTimeout(() => {
                    if (!this._connected && this._botId) {
                        this.connect().catch(() => { });
                    }
                }, 5000);
            };
            ws.onerror = (err) => {
                (0, logger_1.getLogger)().error(`WeCom: WebSocket error: ${err}`);
            };
        });
    }
    // -------------------------------------------------------------------------
    // Frame processing
    // -------------------------------------------------------------------------
    _onFrame(frame) {
        const cmd = frame.cmd;
        const reqId = frame.headers?.req_id;
        // Handle responses to our requests
        if (reqId && this._pendingRequests.has(reqId)) {
            const pending = this._pendingRequests.get(reqId);
            clearTimeout(pending.timer);
            this._pendingRequests.delete(reqId);
            if (frame.body?.errcode && frame.body.errcode !== 0) {
                pending.reject(new Error(`WeCom error ${frame.body.errcode}: ${frame.body.errmsg}`));
            }
            else {
                pending.resolve(frame.body);
            }
            return;
        }
        // Handle incoming messages
        if (cmd === 'aibot_msg_callback' || cmd === 'aibot_callback') {
            this._onMessage(frame.body).catch(err => {
                (0, logger_1.getLogger)().error(`WeCom message error: ${err.message}`);
            });
        }
        // Handle ping
        if (cmd === 'ping') {
            this._sendFrame({ cmd: 'pong', headers: { req_id: reqId }, body: {} });
        }
    }
    async _onMessage(body) {
        if (!body)
            return;
        const chatId = body.chatid || body.from?.userid || '';
        const userId = body.from?.userid || '';
        const chatType = body.chattype === 'group' ? 'group' : 'dm';
        const msgType = body.msgtype || 'text';
        const msgId = body.msgid || '';
        let text = '';
        switch (msgType) {
            case 'text':
                text = body.text?.content || '';
                break;
            case 'mixed':
                // Mixed message: extract text from items
                text = (body.mixed?.msg_item || [])
                    .filter((item) => item.text?.content)
                    .map((item) => item.text.content)
                    .join(' ');
                break;
            case 'voice':
                text = body.voice?.content || '[Voice message]';
                break;
            case 'image':
                text = '[Image]';
                break;
            case 'file':
            case 'appmsg':
                text = `[File: ${body.appmsg?.title || body.file?.name || 'attachment'}]`;
                break;
            default:
                text = `[${msgType} message]`;
        }
        // Strip @mention in group
        if (chatType === 'group') {
            text = text.replace(/^@\S+\s*/, '').trim();
        }
        if (!text)
            return;
        const event = {
            text,
            messageType: msgType === 'voice' ? base_1.MessageType.VOICE :
                msgType === 'image' ? base_1.MessageType.PHOTO :
                    base_1.MessageType.TEXT,
            platform: config_1.Platform.WECOM,
            chatId,
            chatType,
            userId,
            userName: userId,
            messageId: msgId,
            timestamp: Date.now(),
            rawMessage: body,
        };
        await this.handleMessage(event);
    }
    // -------------------------------------------------------------------------
    // Send message
    // -------------------------------------------------------------------------
    async send(chatId, content, replyTo) {
        if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
            return { success: false, error: 'WebSocket not connected' };
        }
        const reqId = this._nextReqId();
        const truncated = content.length > 4000 ? content.slice(0, 3950) + '\n\n... [truncated]' : content;
        const frame = {
            cmd: 'aibot_send_msg',
            headers: { req_id: reqId },
            body: {
                chatid: chatId,
                msgtype: 'markdown',
                markdown: { content: truncated },
            },
        };
        return new Promise((resolve) => {
            const timer = setTimeout(() => {
                this._pendingRequests.delete(reqId);
                resolve({ success: false, error: 'Send timeout' });
            }, 15000);
            this._pendingRequests.set(reqId, {
                resolve: () => { clearTimeout(timer); resolve({ success: true, messageId: `wecom_${Date.now()}` }); },
                reject: (err) => { clearTimeout(timer); resolve({ success: false, error: err.message }); },
                timer,
            });
            this._sendFrame(frame);
        });
    }
    async getChatInfo(chatId) {
        return { name: `wecom:${chatId}`, type: 'group' };
    }
    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------
    _sendFrame(frame) {
        if (this._ws && this._ws.readyState === WebSocket.OPEN) {
            this._ws.send(JSON.stringify(frame));
        }
    }
    _nextReqId() {
        return `req_${++this._reqIdCounter}_${Date.now()}`;
    }
}
exports.WeComAdapter = WeComAdapter;
// Auto-register
platform_registry_1.platformRegistry.register({
    name: 'wecom',
    label: 'WeCom (Enterprise WeChat)',
    factory: (config) => new WeComAdapter(config),
    checkFn: () => !!(process.env.WECOM_BOT_ID || process.env.WECOM_SECRET),
    requiredEnv: ['WECOM_BOT_ID', 'WECOM_SECRET'],
    installHint: 'Set WECOM_BOT_ID and WECOM_SECRET environment variables',
    emoji: '💼',
    maxMessageLength: 4000,
});
//# sourceMappingURL=wecom.js.map