import { resolveChunkMode } from "../../auto-reply/chunk.js";
import { buildCommandTextFromArgs, findCommandByNativeName, listNativeCommandSpecsForConfig, parseCommandArgs, resolveCommandArgMenu, } from "../../auto-reply/commands-registry.js";
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
import { dispatchReplyWithDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
import { listSkillCommandsForAgents } from "../../auto-reply/skill-commands.js";
import { formatAllowlistMatchMeta } from "../../channels/allowlist-match.js";
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
import { resolveConversationLabel } from "../../channels/conversation-label.js";
import { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "../../config/commands.js";
import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
import { danger, logVerbose } from "../../globals.js";
import { buildPairingReply } from "../../pairing/pairing-messages.js";
import { readChannelAllowFromStore, upsertChannelPairingRequest, } from "../../pairing/pairing-store.js";
import { resolveAgentRoute } from "../../routing/resolve-route.js";
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
import { normalizeAllowList, normalizeAllowListLower, resolveSlackAllowListMatch, resolveSlackUserAllowed, } from "./allow-list.js";
import { resolveSlackChannelConfig } from "./channel-config.js";
import { buildSlackSlashCommandMatcher, resolveSlackSlashCommandConfig } from "./commands.js";
import { normalizeSlackChannelType } from "./context.js";
import { isSlackChannelAllowedByPolicy } from "./policy.js";
import { deliverSlackSlashReplies } from "./replies.js";
const SLACK_COMMAND_ARG_ACTION_ID = "openclaw_cmdarg";
const SLACK_COMMAND_ARG_VALUE_PREFIX = "cmdarg";
function chunkItems(items, size) {
    if (size <= 0) {
        return [items];
    }
    const rows = [];
    for (let i = 0; i < items.length; i += size) {
        rows.push(items.slice(i, i + size));
    }
    return rows;
}
function encodeSlackCommandArgValue(parts) {
    return [
        SLACK_COMMAND_ARG_VALUE_PREFIX,
        encodeURIComponent(parts.command),
        encodeURIComponent(parts.arg),
        encodeURIComponent(parts.value),
        encodeURIComponent(parts.userId),
    ].join("|");
}
function parseSlackCommandArgValue(raw) {
    if (!raw) {
        return null;
    }
    const parts = raw.split("|");
    if (parts.length !== 5 || parts[0] !== SLACK_COMMAND_ARG_VALUE_PREFIX) {
        return null;
    }
    const [, command, arg, value, userId] = parts;
    if (!command || !arg || !value || !userId) {
        return null;
    }
    const decode = (text) => {
        try {
            return decodeURIComponent(text);
        }
        catch {
            return null;
        }
    };
    const decodedCommand = decode(command);
    const decodedArg = decode(arg);
    const decodedValue = decode(value);
    const decodedUserId = decode(userId);
    if (!decodedCommand || !decodedArg || !decodedValue || !decodedUserId) {
        return null;
    }
    return {
        command: decodedCommand,
        arg: decodedArg,
        value: decodedValue,
        userId: decodedUserId,
    };
}
function buildSlackCommandArgMenuBlocks(params) {
    const rows = chunkItems(params.choices, 5).map((choices) => ({
        type: "actions",
        elements: choices.map((choice) => ({
            type: "button",
            action_id: SLACK_COMMAND_ARG_ACTION_ID,
            text: { type: "plain_text", text: choice.label },
            value: encodeSlackCommandArgValue({
                command: params.command,
                arg: params.arg,
                value: choice.value,
                userId: params.userId,
            }),
        })),
    }));
    return [
        {
            type: "section",
            text: { type: "mrkdwn", text: params.title },
        },
        ...rows,
    ];
}
export function registerSlackMonitorSlashCommands(params) {
    const { ctx, account } = params;
    const cfg = ctx.cfg;
    const runtime = ctx.runtime;
    const supportsInteractiveArgMenus = typeof ctx.app.action === "function";
    const slashCommand = resolveSlackSlashCommandConfig(ctx.slashCommand ?? account.config.slashCommand);
    const handleSlashCommand = async (p) => {
        const { command, ack, respond, prompt, commandArgs, commandDefinition } = p;
        try {
            if (!prompt.trim()) {
                await ack({
                    text: "Message required.",
                    response_type: "ephemeral",
                });
                return;
            }
            await ack();
            if (ctx.botUserId && command.user_id === ctx.botUserId) {
                return;
            }
            const channelInfo = await ctx.resolveChannelName(command.channel_id);
            const rawChannelType = channelInfo?.type ?? (command.channel_name === "directmessage" ? "im" : undefined);
            const channelType = normalizeSlackChannelType(rawChannelType, command.channel_id);
            const isDirectMessage = channelType === "im";
            const isGroupDm = channelType === "mpim";
            const isRoom = channelType === "channel" || channelType === "group";
            const isRoomish = isRoom || isGroupDm;
            if (!ctx.isChannelAllowed({
                channelId: command.channel_id,
                channelName: channelInfo?.name,
                channelType,
            })) {
                await respond({
                    text: "This channel is not allowed.",
                    response_type: "ephemeral",
                });
                return;
            }
            const storeAllowFrom = await readChannelAllowFromStore("slack").catch(() => []);
            const effectiveAllowFrom = normalizeAllowList([...ctx.allowFrom, ...storeAllowFrom]);
            const effectiveAllowFromLower = normalizeAllowListLower(effectiveAllowFrom);
            let commandAuthorized = true;
            let channelConfig = null;
            if (isDirectMessage) {
                if (!ctx.dmEnabled || ctx.dmPolicy === "disabled") {
                    await respond({
                        text: "Slack DMs are disabled.",
                        response_type: "ephemeral",
                    });
                    return;
                }
                if (ctx.dmPolicy !== "open") {
                    const sender = await ctx.resolveUserName(command.user_id);
                    const senderName = sender?.name ?? undefined;
                    const allowMatch = resolveSlackAllowListMatch({
                        allowList: effectiveAllowFromLower,
                        id: command.user_id,
                        name: senderName,
                    });
                    const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
                    if (!allowMatch.allowed) {
                        if (ctx.dmPolicy === "pairing") {
                            const { code, created } = await upsertChannelPairingRequest({
                                channel: "slack",
                                id: command.user_id,
                                meta: { name: senderName },
                            });
                            if (created) {
                                logVerbose(`slack pairing request sender=${command.user_id} name=${senderName ?? "unknown"} (${allowMatchMeta})`);
                                await respond({
                                    text: buildPairingReply({
                                        channel: "slack",
                                        idLine: `Your Slack user id: ${command.user_id}`,
                                        code,
                                    }),
                                    response_type: "ephemeral",
                                });
                            }
                        }
                        else {
                            logVerbose(`slack: blocked slash sender ${command.user_id} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`);
                            await respond({
                                text: "You are not authorized to use this command.",
                                response_type: "ephemeral",
                            });
                        }
                        return;
                    }
                    commandAuthorized = true;
                }
            }
            if (isRoom) {
                channelConfig = resolveSlackChannelConfig({
                    channelId: command.channel_id,
                    channelName: channelInfo?.name,
                    channels: ctx.channelsConfig,
                    defaultRequireMention: ctx.defaultRequireMention,
                });
                if (ctx.useAccessGroups) {
                    const channelAllowlistConfigured = Boolean(ctx.channelsConfig) && Object.keys(ctx.channelsConfig ?? {}).length > 0;
                    const channelAllowed = channelConfig?.allowed !== false;
                    if (!isSlackChannelAllowedByPolicy({
                        groupPolicy: ctx.groupPolicy,
                        channelAllowlistConfigured,
                        channelAllowed,
                    })) {
                        await respond({
                            text: "This channel is not allowed.",
                            response_type: "ephemeral",
                        });
                        return;
                    }
                    // When groupPolicy is "open", only block channels that are EXPLICITLY denied
                    // (i.e., have a matching config entry with allow:false). Channels not in the
                    // config (matchSource undefined) should be allowed under open policy.
                    const hasExplicitConfig = Boolean(channelConfig?.matchSource);
                    if (!channelAllowed && (ctx.groupPolicy !== "open" || hasExplicitConfig)) {
                        await respond({
                            text: "This channel is not allowed.",
                            response_type: "ephemeral",
                        });
                        return;
                    }
                }
            }
            const sender = await ctx.resolveUserName(command.user_id);
            const senderName = sender?.name ?? command.user_name ?? command.user_id;
            const channelUsersAllowlistConfigured = isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
            const channelUserAllowed = channelUsersAllowlistConfigured
                ? resolveSlackUserAllowed({
                    allowList: channelConfig?.users,
                    userId: command.user_id,
                    userName: senderName,
                })
                : false;
            if (channelUsersAllowlistConfigured && !channelUserAllowed) {
                await respond({
                    text: "You are not authorized to use this command here.",
                    response_type: "ephemeral",
                });
                return;
            }
            const ownerAllowed = resolveSlackAllowListMatch({
                allowList: effectiveAllowFromLower,
                id: command.user_id,
                name: senderName,
            }).allowed;
            if (isRoomish) {
                commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
                    useAccessGroups: ctx.useAccessGroups,
                    authorizers: [
                        { configured: effectiveAllowFromLower.length > 0, allowed: ownerAllowed },
                        { configured: channelUsersAllowlistConfigured, allowed: channelUserAllowed },
                    ],
                });
                if (ctx.useAccessGroups && !commandAuthorized) {
                    await respond({
                        text: "You are not authorized to use this command.",
                        response_type: "ephemeral",
                    });
                    return;
                }
            }
            if (commandDefinition && supportsInteractiveArgMenus) {
                const menu = resolveCommandArgMenu({
                    command: commandDefinition,
                    args: commandArgs,
                    cfg,
                });
                if (menu) {
                    const commandLabel = commandDefinition.nativeName ?? commandDefinition.key;
                    const title = menu.title ?? `Choose ${menu.arg.description || menu.arg.name} for /${commandLabel}.`;
                    const blocks = buildSlackCommandArgMenuBlocks({
                        title,
                        command: commandLabel,
                        arg: menu.arg.name,
                        // @ts-ignore -- cherry-pick upstream type mismatch
                        // @ts-ignore -- cherry-pick upstream type mismatch
                        choices: menu.choices,
                        userId: command.user_id,
                    });
                    await respond({
                        text: title,
                        blocks,
                        response_type: "ephemeral",
                    });
                    return;
                }
            }
            const channelName = channelInfo?.name;
            const roomLabel = channelName ? `#${channelName}` : `#${command.channel_id}`;
            const route = resolveAgentRoute({
                cfg,
                channel: "slack",
                accountId: account.accountId,
                teamId: ctx.teamId || undefined,
                peer: {
                    kind: isDirectMessage ? "dm" : isRoom ? "channel" : "group",
                    id: isDirectMessage ? command.user_id : command.channel_id,
                },
            });
            const untrustedChannelMetadata = isRoomish
                ? buildUntrustedChannelMetadata({
                    source: "slack",
                    label: "Slack channel description",
                    entries: [channelInfo?.topic, channelInfo?.purpose],
                })
                : undefined;
            const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter((entry) => Boolean(entry));
            const groupSystemPrompt = systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
            const ctxPayload = finalizeInboundContext({
                Body: prompt,
                RawBody: prompt,
                CommandBody: prompt,
                CommandArgs: commandArgs,
                From: isDirectMessage
                    ? `slack:${command.user_id}`
                    : isRoom
                        ? `slack:channel:${command.channel_id}`
                        : `slack:group:${command.channel_id}`,
                To: `slash:${command.user_id}`,
                ChatType: isDirectMessage ? "direct" : "channel",
                ConversationLabel: resolveConversationLabel({
                    ChatType: isDirectMessage ? "direct" : "channel",
                    SenderName: senderName,
                    GroupSubject: isRoomish ? roomLabel : undefined,
                    From: isDirectMessage
                        ? `slack:${command.user_id}`
                        : isRoom
                            ? `slack:channel:${command.channel_id}`
                            : `slack:group:${command.channel_id}`,
                }) ?? (isDirectMessage ? senderName : roomLabel),
                GroupSubject: isRoomish ? roomLabel : undefined,
                GroupSystemPrompt: isRoomish ? groupSystemPrompt : undefined,
                UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined,
                SenderName: senderName,
                SenderId: command.user_id,
                Provider: "slack",
                Surface: "slack",
                WasMentioned: true,
                MessageSid: command.trigger_id,
                Timestamp: Date.now(),
                SessionKey: `agent:${route.agentId}:${slashCommand.sessionPrefix}:${command.user_id}`.toLowerCase(),
                CommandTargetSessionKey: route.sessionKey,
                AccountId: route.accountId,
                CommandSource: "native",
                CommandAuthorized: commandAuthorized,
                OriginatingChannel: "slack",
                OriginatingTo: `user:${command.user_id}`,
            });
            const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
                cfg,
                agentId: route.agentId,
                channel: "slack",
                accountId: route.accountId,
            });
            const { counts } = await dispatchReplyWithDispatcher({
                ctx: ctxPayload,
                cfg,
                dispatcherOptions: {
                    ...prefixOptions,
                    deliver: async (payload) => {
                        await deliverSlackSlashReplies({
                            replies: [payload],
                            respond,
                            ephemeral: slashCommand.ephemeral,
                            textLimit: ctx.textLimit,
                            chunkMode: resolveChunkMode(cfg, "slack", route.accountId),
                            tableMode: resolveMarkdownTableMode({
                                cfg,
                                channel: "slack",
                                accountId: route.accountId,
                            }),
                        });
                    },
                    onError: (err, info) => {
                        runtime.error?.(danger(`slack slash ${info.kind} reply failed: ${String(err)}`));
                    },
                },
                replyOptions: {
                    skillFilter: channelConfig?.skills,
                    onModelSelected,
                },
            });
            if (counts.final + counts.tool + counts.block === 0) {
                await deliverSlackSlashReplies({
                    replies: [],
                    respond,
                    ephemeral: slashCommand.ephemeral,
                    textLimit: ctx.textLimit,
                    chunkMode: resolveChunkMode(cfg, "slack", route.accountId),
                    tableMode: resolveMarkdownTableMode({
                        cfg,
                        channel: "slack",
                        accountId: route.accountId,
                    }),
                });
            }
        }
        catch (err) {
            runtime.error?.(danger(`slack slash handler failed: ${String(err)}`));
            await respond({
                text: "Sorry, something went wrong handling that command.",
                response_type: "ephemeral",
            });
        }
    };
    const nativeEnabled = resolveNativeCommandsEnabled({
        providerId: "slack",
        providerSetting: account.config.commands?.native,
        globalSetting: cfg.commands?.native,
    });
    const nativeSkillsEnabled = resolveNativeSkillsEnabled({
        providerId: "slack",
        providerSetting: account.config.commands?.nativeSkills,
        globalSetting: cfg.commands?.nativeSkills,
    });
    const skillCommands = nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
    const nativeCommands = nativeEnabled
        ? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "slack" })
        : [];
    if (nativeCommands.length > 0) {
        for (const command of nativeCommands) {
            ctx.app.command(`/${command.name}`, async ({ command: cmd, ack, respond }) => {
                const commandDefinition = findCommandByNativeName(command.name, "slack");
                const rawText = cmd.text?.trim() ?? "";
                const commandArgs = commandDefinition
                    ? parseCommandArgs(commandDefinition, rawText)
                    : rawText
                        ? { raw: rawText }
                        : undefined;
                const prompt = commandDefinition
                    ? buildCommandTextFromArgs(commandDefinition, commandArgs)
                    : rawText
                        ? `/${command.name} ${rawText}`
                        : `/${command.name}`;
                await handleSlashCommand({
                    command: cmd,
                    ack,
                    respond,
                    prompt,
                    commandArgs,
                    commandDefinition: commandDefinition ?? undefined,
                });
            });
        }
    }
    else if (slashCommand.enabled) {
        ctx.app.command(buildSlackSlashCommandMatcher(slashCommand.name), async ({ command, ack, respond }) => {
            await handleSlashCommand({
                command,
                ack,
                respond,
                prompt: command.text?.trim() ?? "",
            });
        });
    }
    else {
        logVerbose("slack: slash commands disabled");
    }
    if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) {
        return;
    }
    const registerArgAction = (actionId) => {
        ctx.app.action(actionId, async (args) => {
            const { ack, body, respond } = args;
            const action = args.action;
            await ack();
            const respondFn = respond ??
                (async (payload) => {
                    if (!body.channel?.id || !body.user?.id) {
                        return;
                    }
                    await ctx.app.client.chat.postEphemeral({
                        token: ctx.botToken,
                        channel: body.channel.id,
                        user: body.user.id,
                        text: payload.text,
                        blocks: payload.blocks,
                    });
                });
            const parsed = parseSlackCommandArgValue(action?.value);
            if (!parsed) {
                await respondFn({
                    text: "Sorry, that button is no longer valid.",
                    response_type: "ephemeral",
                });
                return;
            }
            if (body.user?.id && parsed.userId !== body.user.id) {
                await respondFn({
                    text: "That menu is for another user.",
                    response_type: "ephemeral",
                });
                return;
            }
            const commandDefinition = findCommandByNativeName(parsed.command, "slack");
            const commandArgs = {
                values: { [parsed.arg]: parsed.value },
            };
            const prompt = commandDefinition
                ? buildCommandTextFromArgs(commandDefinition, commandArgs)
                : `/${parsed.command} ${parsed.value}`;
            const user = body.user;
            const userName = user && "name" in user && user.name
                ? user.name
                : user && "username" in user && user.username
                    ? user.username
                    : (user?.id ?? "");
            const triggerId = "trigger_id" in body ? body.trigger_id : undefined;
            const commandPayload = {
                user_id: user?.id ?? "",
                user_name: userName,
                channel_id: body.channel?.id ?? "",
                channel_name: body.channel?.name ?? body.channel?.id ?? "",
                trigger_id: triggerId ?? String(Date.now()),
            };
            await handleSlashCommand({
                command: commandPayload,
                ack: async () => { },
                respond: respondFn,
                prompt,
                commandArgs,
                commandDefinition: commandDefinition ?? undefined,
            });
        });
    };
    registerArgAction(SLACK_COMMAND_ARG_ACTION_ID);
}
