import { EventEmitter } from "node:events";
import { Client, GatewayIntentBits, Partials, AttachmentBuilder, } from "discord.js";
import fs from "node:fs";
import { logger } from "../logger.js";
/**
 * Discord channel adapter.
 * Connects via WebSocket gateway -- no public URL needed.
 * Supports multimodal inbound (images, files) and outbound (images, files).
 */
export class DiscordAdapter extends EventEmitter {
    name = "discord";
    config;
    client = null;
    _allowedChannelIds = [];
    constructor(config) {
        super();
        this.config = config;
    }
    /**
     * Set the allowed Discord channel IDs for message filtering.
     * Called after owner onboarding completes with IDs from owner.json.
     * An empty array means accept all messages (guild + DM).
     */
    setAllowedChannelIds(ids) {
        this._allowedChannelIds = ids;
    }
    async start() {
        const discordConfig = this.config.channels.discord;
        if (!discordConfig?.botToken) {
            throw new Error("Discord bot token not configured");
        }
        this.client = new Client({
            intents: [
                GatewayIntentBits.Guilds,
                GatewayIntentBits.GuildMessages,
                GatewayIntentBits.DirectMessages,
                GatewayIntentBits.MessageContent,
            ],
            partials: [Partials.Channel], // Required for DMs
        });
        this.client.on("messageCreate", (msg) => {
            // Ignore bot's own messages
            if (msg.author.bot)
                return;
            // Filter by allowed channels/DMs if configured
            if (this._allowedChannelIds.length > 0 && !this._allowedChannelIds.includes(msg.channelId)) {
                // Allow DMs through regardless
                if (!msg.channel.isDMBased())
                    return;
            }
            if (/^\/stop$/.exec(msg.content)) {
                const sender = `${msg.author.displayName} (${msg.author.id})`;
                this.emit("stop_request", { channel: "discord", sender });
                return;
            }
            const sleepMatch = /^\/sleep\s+(\d+)$/.exec(msg.content);
            if (sleepMatch) {
                const minutes = Number(sleepMatch[1]);
                const sender = `${msg.author.displayName} (${msg.author.id})`;
                this.emit("sleep_request", { channel: "discord", sender, minutes });
                return;
            }
            if (/^\/console$/.exec(msg.content)) {
                const sender = `${msg.author.displayName} (${msg.author.id})`;
                this.emit("console_request", { channel: "discord", sender });
                return;
            }
            this.processInboundMessage(msg);
        });
        this.client.on("error", (err) => {
            this.emit("error", err);
        });
        await this.client.login(discordConfig.botToken);
        logger.channel("discord", `Logged in as ${this.client.user?.tag ?? "unknown"}`);
    }
    async stop() {
        if (this.client) {
            await this.client.destroy();
            this.client = null;
        }
    }
    async sendMessage(to, message, attachments) {
        if (!this.client) {
            throw new Error("Discord client not started");
        }
        const channel = await this.client.channels.fetch(to);
        if (!channel?.isTextBased()) {
            throw new Error(`Discord channel ${to} is not a text channel`);
        }
        if (!("send" in channel)) {
            throw new Error(`Cannot send to Discord channel ${to}`);
        }
        // Build Discord attachments from local paths or URLs
        const discordAttachments = [];
        if (attachments) {
            for (const att of attachments) {
                if (att.localPath && fs.existsSync(att.localPath)) {
                    discordAttachments.push(new AttachmentBuilder(att.localPath, {
                        name: att.filename ?? att.localPath.split("/").pop(),
                    }));
                }
                else if (att.url) {
                    discordAttachments.push(new AttachmentBuilder(att.url, {
                        name: att.filename ?? "attachment",
                    }));
                }
            }
        }
        // Split long messages (Discord has a 2000 char limit)
        const MAX_LENGTH = 2000;
        if (message.trim()) {
            if (message.length <= MAX_LENGTH) {
                await channel.send({
                    content: message,
                    files: discordAttachments,
                });
            }
            else {
                // Send attachments with first chunk
                const chunks = [];
                for (let i = 0; i < message.length; i += MAX_LENGTH) {
                    chunks.push(message.substring(i, i + MAX_LENGTH));
                }
                for (let i = 0; i < chunks.length; i++) {
                    await channel.send({
                        content: chunks[i],
                        files: i === 0 ? discordAttachments : undefined,
                    });
                }
            }
        }
        else if (discordAttachments.length > 0) {
            await channel.send({ files: discordAttachments });
        }
    }
    async sendTypingIndicator(to) {
        if (!this.client)
            return;
        try {
            const channel = await this.client.channels.fetch(to);
            if (channel?.isTextBased() && "sendTyping" in channel) {
                await channel.sendTyping();
            }
        }
        catch {
            // best-effort
        }
    }
    processInboundMessage(msg) {
        try {
            const senderDisplay = `${msg.author.displayName} (${msg.author.id})`;
            const commandMessage = {
                id: `discord-${msg.id}`,
                channel: "discord",
                sender: senderDisplay,
                text: msg.content,
                attachments: [],
                timestamp: msg.createdAt.toISOString(),
                meta: {
                    channelId: msg.channelId,
                    guildId: msg.guildId,
                    authorId: msg.author.id,
                    username: msg.author.username,
                },
            };
            for (const att of msg.attachments.values()) {
                const mimeType = att.contentType ?? "application/octet-stream";
                const filename = att.name;
                const type = mimeType.startsWith("image/")
                    ? "image"
                    : mimeType.startsWith("audio/")
                        ? "audio"
                        : mimeType.startsWith("video/")
                            ? "video"
                            : "file";
                commandMessage.attachments.push({
                    type,
                    url: att.url,
                    filename,
                    mimeType,
                });
            }
            if (commandMessage.text || commandMessage.attachments.length > 0) {
                this.emit("message", commandMessage);
            }
        }
        catch (err) {
            logger.warn(`Discord: Error processing message: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
}
//# sourceMappingURL=discord.js.map