// /agent command - Set the preferred agent for this channel or session.
// Also provides quick agent commands like /plan-agent, /build-agent that switch instantly.
import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectMenuBuilder, ActionRowBuilder, ChannelType, MessageFlags, } from 'discord.js';
import crypto from 'node:crypto';
import { setChannelAgent, setSessionAgent, clearSessionModel, getThreadSession, getSessionAgent, getChannelAgent, } from '../database.js';
import { initializeOpencodeForDirectory } from '../opencode.js';
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
import { createLogger, LogPrefix } from '../logger.js';
const agentLogger = createLogger(LogPrefix.AGENT);
const AGENT_CONTEXT_TTL_MS = 10 * 60 * 1000;
const pendingAgentContexts = new Map();
/**
 * Get the current agent info for a channel/session, including where it comes from.
 * Priority: session > channel > none
 */
export async function getCurrentAgentInfo({ sessionId, channelId, }) {
    if (sessionId) {
        const sessionAgent = await getSessionAgent(sessionId);
        if (sessionAgent) {
            return { type: 'session', agent: sessionAgent };
        }
    }
    if (channelId) {
        const channelAgent = await getChannelAgent(channelId);
        if (channelAgent) {
            return { type: 'channel', agent: channelAgent };
        }
    }
    return { type: 'none' };
}
/**
 * Sanitize an agent name to be a valid Discord command name component.
 * Lowercase, alphanumeric and hyphens only.
 */
export function sanitizeAgentName(name) {
    return name
        .toLowerCase()
        .replace(/[^a-z0-9-]/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, '');
}
const QUICK_AGENT_DESCRIPTION_PATTERN = /^\[agent:([^\]]+)\]/;
/**
 * Build quick-agent command description with an embedded original agent name.
 * Metadata format: [agent:<original-name>] <visible description>
 */
export function buildQuickAgentCommandDescription({ agentName, description, }) {
    const metadataPrefix = `[agent:${agentName}]`;
    if (metadataPrefix.length > 100) {
        return metadataPrefix.slice(0, 100);
    }
    const visibleDescription = description || `Switch to ${agentName} agent`;
    const maxVisibleLength = 100 - metadataPrefix.length - 1;
    if (maxVisibleLength <= 0) {
        return metadataPrefix;
    }
    const trimmedVisible = visibleDescription.slice(0, maxVisibleLength).trim();
    if (!trimmedVisible) {
        return metadataPrefix;
    }
    return `${metadataPrefix} ${trimmedVisible}`;
}
function parseQuickAgentNameFromDescription(description) {
    if (!description) {
        return undefined;
    }
    const match = QUICK_AGENT_DESCRIPTION_PATTERN.exec(description);
    if (!match) {
        return undefined;
    }
    const agentName = match[1]?.trim();
    if (!agentName) {
        return undefined;
    }
    return agentName;
}
async function resolveQuickAgentNameFromInteraction({ command, }) {
    const fromCommandObject = parseQuickAgentNameFromDescription(command.command?.description);
    if (fromCommandObject) {
        return fromCommandObject;
    }
    if (!command.guild) {
        return undefined;
    }
    const fetchedCommand = await command.guild.commands.fetch(command.commandId);
    if (!fetchedCommand) {
        return undefined;
    }
    return parseQuickAgentNameFromDescription(fetchedCommand.description);
}
/**
 * Resolve the context for an agent command (directory, channel, session).
 * Returns null if the command cannot be executed in this context.
 */
export async function resolveAgentCommandContext({ interaction, }) {
    const channel = interaction.channel;
    if (!channel) {
        await interaction.editReply({
            content: 'This command can only be used in a channel',
        });
        return null;
    }
    const isThread = [
        ChannelType.PublicThread,
        ChannelType.PrivateThread,
        ChannelType.AnnouncementThread,
    ].includes(channel.type);
    let projectDirectory;
    let targetChannelId;
    let sessionId;
    if (isThread) {
        const thread = channel;
        const textChannel = await resolveTextChannel(thread);
        const metadata = await getKimakiMetadata(textChannel);
        projectDirectory = metadata.projectDirectory;
        targetChannelId = textChannel?.id || channel.id;
        sessionId = await getThreadSession(thread.id);
    }
    else if (channel.type === ChannelType.GuildText) {
        const textChannel = channel;
        const metadata = await getKimakiMetadata(textChannel);
        projectDirectory = metadata.projectDirectory;
        targetChannelId = channel.id;
    }
    else {
        await interaction.editReply({
            content: 'This command can only be used in text channels or threads',
        });
        return null;
    }
    if (!projectDirectory) {
        await interaction.editReply({
            content: 'This channel is not configured with a project directory',
        });
        return null;
    }
    return {
        dir: projectDirectory,
        channelId: targetChannelId,
        sessionId,
        isThread,
    };
}
/**
 * Set the agent preference for a context (session or channel).
 * When switching agents for a session, clears session model preference
 * so the new agent's model takes effect (agent model > channel model).
 */
export async function setAgentForContext({ context, agentName, }) {
    if (context.isThread && context.sessionId) {
        await setSessionAgent(context.sessionId, agentName);
        // Clear session model so the new agent's model takes effect
        await clearSessionModel(context.sessionId);
        agentLogger.log(`Set agent ${agentName} for session ${context.sessionId} (cleared session model)`);
    }
    else {
        await setChannelAgent(context.channelId, agentName);
        agentLogger.log(`Set agent ${agentName} for channel ${context.channelId}`);
    }
}
export async function handleAgentCommand({ interaction, appId, }) {
    await interaction.deferReply({ flags: MessageFlags.Ephemeral });
    const context = await resolveAgentCommandContext({ interaction, appId });
    if (!context) {
        return;
    }
    try {
        const getClient = await initializeOpencodeForDirectory(context.dir);
        if (getClient instanceof Error) {
            await interaction.editReply({ content: getClient.message });
            return;
        }
        const agentsResponse = await getClient().app.agents({
            directory: context.dir,
        });
        if (!agentsResponse.data || agentsResponse.data.length === 0) {
            await interaction.editReply({ content: 'No agents available' });
            return;
        }
        const agents = agentsResponse.data
            .filter((agent) => {
            const hidden = agent.hidden;
            return (agent.mode === 'primary' || agent.mode === 'all') && !hidden;
        })
            .slice(0, 25);
        if (agents.length === 0) {
            await interaction.editReply({ content: 'No primary agents available' });
            return;
        }
        const currentAgentInfo = await getCurrentAgentInfo({
            sessionId: context.sessionId,
            channelId: context.channelId,
        });
        const currentAgentText = (() => {
            switch (currentAgentInfo.type) {
                case 'session':
                    return `**Current (session override):** \`${currentAgentInfo.agent}\``;
                case 'channel':
                    return `**Current (channel override):** \`${currentAgentInfo.agent}\``;
                case 'none':
                    return '**Current:** none';
            }
        })();
        const contextHash = crypto.randomBytes(8).toString('hex');
        pendingAgentContexts.set(contextHash, context);
        setTimeout(() => {
            pendingAgentContexts.delete(contextHash);
        }, AGENT_CONTEXT_TTL_MS).unref();
        const options = agents.map((agent) => ({
            label: agent.name.slice(0, 100),
            value: agent.name,
            description: (agent.description || `${agent.mode} agent`).slice(0, 100),
        }));
        const selectMenu = new StringSelectMenuBuilder()
            .setCustomId(`agent_select:${contextHash}`)
            .setPlaceholder('Select an agent')
            .addOptions(options);
        const actionRow = new ActionRowBuilder().addComponents(selectMenu);
        await interaction.editReply({
            content: `**Set Agent Preference**\n${currentAgentText}\nSelect an agent:`,
            components: [actionRow],
        });
    }
    catch (error) {
        agentLogger.error('Error loading agents:', error);
        await interaction.editReply({
            content: `Failed to load agents: ${error instanceof Error ? error.message : 'Unknown error'}`,
        });
    }
}
export async function handleAgentSelectMenu(interaction) {
    const customId = interaction.customId;
    if (!customId.startsWith('agent_select:')) {
        return;
    }
    await interaction.deferUpdate();
    const contextHash = customId.replace('agent_select:', '');
    const context = pendingAgentContexts.get(contextHash);
    if (!context) {
        await interaction.editReply({
            content: 'Selection expired. Please run /agent again.',
            components: [],
        });
        return;
    }
    const selectedAgent = interaction.values[0];
    if (!selectedAgent) {
        await interaction.editReply({
            content: 'No agent selected',
            components: [],
        });
        return;
    }
    try {
        await setAgentForContext({ context, agentName: selectedAgent });
        if (context.isThread && context.sessionId) {
            await interaction.editReply({
                content: `Agent preference set for this session next messages: **${selectedAgent}**`,
                components: [],
            });
        }
        else {
            await interaction.editReply({
                content: `Agent preference set for this channel: **${selectedAgent}**\nAll new sessions in this channel will use this agent.`,
                components: [],
            });
        }
        pendingAgentContexts.delete(contextHash);
    }
    catch (error) {
        agentLogger.error('Error saving agent preference:', error);
        await interaction.editReply({
            content: `Failed to save agent preference: ${error instanceof Error ? error.message : 'Unknown error'}`,
            components: [],
        });
    }
}
/**
 * Handle quick agent commands like /plan-agent, /build-agent.
 * These instantly switch to the specified agent without showing a dropdown.
 *
 * The slash command name is sanitized for Discord and can be lossy
 * (for example gpt5.4 -> gpt5-4-agent). To keep the original agent name,
 * registration stores [agent:<name>] metadata in the description and this
 * handler resolves from that metadata first.
 */
export async function handleQuickAgentCommand({ command, appId, }) {
    const fallbackAgentName = command.commandName.replace(/-agent$/, '');
    await command.deferReply({ flags: MessageFlags.Ephemeral });
    const context = await resolveAgentCommandContext({
        interaction: command,
        appId,
    });
    if (!context) {
        return;
    }
    try {
        const resolvedAgentName = (await resolveQuickAgentNameFromInteraction({ command })) ||
            fallbackAgentName;
        // Check current agent and set new one.
        // getCurrentAgentInfo is fast (DB only), use it for the "was X" text.
        const previousAgent = await getCurrentAgentInfo({
            sessionId: context.sessionId,
            channelId: context.channelId,
        });
        const previousAgentName = previousAgent.type !== 'none' ? previousAgent.agent : undefined;
        if (previousAgentName === resolvedAgentName) {
            await command.editReply({
                content: `Already using **${resolvedAgentName}** agent`,
            });
            return;
        }
        // Set the agent preference in DB for this context.
        await setAgentForContext({ context, agentName: resolvedAgentName });
        const previousText = previousAgentName
            ? ` (was **${previousAgentName}**)`
            : '';
        if (context.isThread && context.sessionId) {
            await command.editReply({
                content: `Switched to **${resolvedAgentName}** agent for this session next messages${previousText}`,
            });
        }
        else {
            await command.editReply({
                content: `Switched to **${resolvedAgentName}** agent for this channel${previousText}\nAll new sessions will use this agent.`,
            });
        }
    }
    catch (error) {
        agentLogger.error('Error in quick agent command:', error);
        await command.editReply({
            content: `Failed to switch agent: ${error instanceof Error ? error.message : 'Unknown error'}`,
        });
    }
}
