import { resolveDiscordAccount } from "../discord/accounts.js";
import { resolveIMessageAccount } from "../imessage/accounts.js";
import { resolveSignalAccount } from "../signal/accounts.js";
import { resolveSlackAccount, resolveSlackReplyToMode } from "../slack/accounts.js";
import { buildSlackThreadingToolContext } from "../slack/threading-tool-context.js";
import { resolveTelegramAccount } from "../telegram/accounts.js";
import { normalizeAccountId } from "../routing/session-key.js";
import { normalizeE164 } from "../utils.js";
import { resolveWhatsAppAccount } from "../web/accounts.js";
import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
import { requireActivePluginRegistry } from "../plugins/runtime.js";
import { resolveDiscordGroupRequireMention, resolveDiscordGroupToolPolicy, resolveGoogleChatGroupRequireMention, resolveGoogleChatGroupToolPolicy, resolveIMessageGroupRequireMention, resolveIMessageGroupToolPolicy, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, resolveTelegramGroupRequireMention, resolveTelegramGroupToolPolicy, resolveWhatsAppGroupRequireMention, resolveWhatsAppGroupToolPolicy, } from "./plugins/group-mentions.js";
import { CHAT_CHANNEL_ORDER, getChatChannelMeta } from "./registry.js";
const formatLower = (allowFrom) => allowFrom
    .map((entry) => String(entry).trim())
    .filter(Boolean)
    .map((entry) => entry.toLowerCase());
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// Channel docks: lightweight channel metadata/behavior for shared code paths.
//
// Rules:
// - keep this module *light* (no monitors, probes, puppeteer/web login, etc)
// - OK: config readers, allowFrom formatting, mention stripping patterns, threading defaults
// - shared code should import from here (and from `src/channels/registry.ts`), not from the plugins registry
//
// Adding a channel:
// - add a new entry to `DOCKS`
// - keep it cheap; push heavy logic into `src/channels/plugins/<id>.ts` or channel modules
const DOCKS = {
    telegram: {
        id: "telegram",
        capabilities: {
            chatTypes: ["direct", "group", "channel", "thread"],
            nativeCommands: true,
            blockStreaming: true,
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => (resolveTelegramAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)),
            formatAllowFrom: ({ allowFrom }) => allowFrom
                .map((entry) => String(entry).trim())
                .filter(Boolean)
                .map((entry) => entry.replace(/^(telegram|tg):/i, ""))
                .map((entry) => entry.toLowerCase()),
        },
        groups: {
            resolveRequireMention: resolveTelegramGroupRequireMention,
            resolveToolPolicy: resolveTelegramGroupToolPolicy,
        },
        threading: {
            resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
            buildToolContext: ({ context, hasRepliedRef }) => {
                const threadId = context.MessageThreadId ?? context.ReplyToId;
                return {
                    currentChannelId: context.To?.trim() || undefined,
                    currentThreadTs: threadId != null ? String(threadId) : undefined,
                    hasRepliedRef,
                };
            },
        },
    },
    whatsapp: {
        id: "whatsapp",
        capabilities: {
            chatTypes: ["direct", "group"],
            polls: true,
            reactions: true,
            media: true,
        },
        commands: {
            enforceOwnerForCommands: true,
            skipWhenConfigEmpty: true,
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => resolveWhatsAppAccount({ cfg, accountId }).allowFrom ?? [],
            formatAllowFrom: ({ allowFrom }) => allowFrom
                .map((entry) => String(entry).trim())
                .filter((entry) => Boolean(entry))
                .map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
                .filter((entry) => Boolean(entry)),
        },
        groups: {
            resolveRequireMention: resolveWhatsAppGroupRequireMention,
            resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
            resolveGroupIntroHint: () => "WhatsApp IDs: SenderId is the participant JID; [message_id: ...] is the message id for reactions (use SenderId as participant).",
        },
        mentions: {
            stripPatterns: ({ ctx }) => {
                const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, "");
                if (!selfE164)
                    return [];
                const escaped = escapeRegExp(selfE164);
                return [escaped, `@${escaped}`];
            },
        },
        threading: {
            buildToolContext: ({ context, hasRepliedRef }) => {
                const channelId = context.From?.trim() || context.To?.trim() || undefined;
                return {
                    currentChannelId: channelId,
                    currentThreadTs: context.ReplyToId,
                    hasRepliedRef,
                };
            },
        },
    },
    discord: {
        id: "discord",
        capabilities: {
            chatTypes: ["direct", "channel", "thread"],
            polls: true,
            reactions: true,
            media: true,
            nativeCommands: true,
            threads: true,
        },
        outbound: { textChunkLimit: 2000 },
        streaming: {
            blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
        },
        elevated: {
            allowFromFallback: ({ cfg }) => cfg.channels?.discord?.dm?.allowFrom,
        },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => (resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) => String(entry)),
            formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom),
        },
        groups: {
            resolveRequireMention: resolveDiscordGroupRequireMention,
            resolveToolPolicy: resolveDiscordGroupToolPolicy,
        },
        mentions: {
            stripPatterns: () => ["<@!?\\d+>"],
        },
        threading: {
            resolveReplyToMode: ({ cfg }) => cfg.channels?.discord?.replyToMode ?? "off",
            buildToolContext: ({ context, hasRepliedRef }) => ({
                currentChannelId: context.To?.trim() || undefined,
                currentThreadTs: context.ReplyToId,
                hasRepliedRef,
            }),
        },
    },
    googlechat: {
        id: "googlechat",
        capabilities: {
            chatTypes: ["direct", "group", "thread"],
            reactions: true,
            media: true,
            threads: true,
            blockStreaming: true,
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => {
                const channel = cfg.channels?.googlechat;
                const normalized = normalizeAccountId(accountId);
                const account = channel?.accounts?.[normalized] ??
                    channel?.accounts?.[Object.keys(channel?.accounts ?? {}).find((key) => key.toLowerCase() === normalized.toLowerCase()) ?? ""];
                return (account?.dm?.allowFrom ?? channel?.dm?.allowFrom ?? []).map((entry) => String(entry));
            },
            formatAllowFrom: ({ allowFrom }) => allowFrom
                .map((entry) => String(entry).trim())
                .filter(Boolean)
                .map((entry) => entry
                .replace(/^(googlechat|google-chat|gchat):/i, "")
                .replace(/^user:/i, "")
                .replace(/^users\//i, "")
                .toLowerCase()),
        },
        groups: {
            resolveRequireMention: resolveGoogleChatGroupRequireMention,
            resolveToolPolicy: resolveGoogleChatGroupToolPolicy,
        },
        threading: {
            resolveReplyToMode: ({ cfg }) => cfg.channels?.googlechat?.replyToMode ?? "off",
            buildToolContext: ({ context, hasRepliedRef }) => {
                const threadId = context.MessageThreadId ?? context.ReplyToId;
                return {
                    currentChannelId: context.To?.trim() || undefined,
                    currentThreadTs: threadId != null ? String(threadId) : undefined,
                    hasRepliedRef,
                };
            },
        },
    },
    feishu: {
        id: "feishu",
        capabilities: {
            chatTypes: ["direct", "group"],
        },
        outbound: { textChunkLimit: 2000 },
        config: {
            resolveAllowFrom: () => [], // TODO: implement allowFrom
            formatAllowFrom: ({ allowFrom }) => allowFrom.map(String),
        },
    },
    "dingtalk-connector": {
        id: "dingtalk-connector",
        capabilities: {
            chatTypes: ["direct", "channel"],
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: () => [],
            formatAllowFrom: ({ allowFrom }) => allowFrom.map(String),
        },
    },
    wecom: {
        id: "wecom",
        capabilities: {
            chatTypes: ["direct", "channel"],
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: () => [],
            formatAllowFrom: ({ allowFrom }) => allowFrom.map(String),
        },
    },
    "openclaw-weixin": {
        id: "openclaw-weixin",
        capabilities: {
            chatTypes: ["direct", "channel"],
            media: true,
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: () => [],
            formatAllowFrom: ({ allowFrom }) => allowFrom.map(String),
        },
    },
    qqbot: {
        id: "qqbot",
        capabilities: {
            chatTypes: ["direct", "channel"],
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: () => [],
            formatAllowFrom: ({ allowFrom }) => allowFrom.map(String),
        },
    },
    slack: {
        id: "slack",
        capabilities: {
            chatTypes: ["direct", "channel", "thread"],
            reactions: true,
            media: true,
            nativeCommands: true,
            threads: true,
        },
        outbound: { textChunkLimit: 4000 },
        streaming: {
            blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
        },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => (resolveSlackAccount({ cfg, accountId }).dm?.allowFrom ?? []).map((entry) => String(entry)),
            formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom),
        },
        groups: {
            resolveRequireMention: resolveSlackGroupRequireMention,
            resolveToolPolicy: resolveSlackGroupToolPolicy,
        },
        mentions: {
            stripPatterns: () => ["<@[^>]+>"],
        },
        threading: {
            resolveReplyToMode: ({ cfg, accountId, chatType }) => resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType),
            allowTagsWhenOff: true,
            buildToolContext: (params) => buildSlackThreadingToolContext(params),
        },
    },
    signal: {
        id: "signal",
        capabilities: {
            chatTypes: ["direct", "group"],
            reactions: true,
            media: true,
        },
        outbound: { textChunkLimit: 4000 },
        streaming: {
            blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
        },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => (resolveSignalAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)),
            formatAllowFrom: ({ allowFrom }) => allowFrom
                .map((entry) => String(entry).trim())
                .filter(Boolean)
                .map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
                .filter(Boolean),
        },
        threading: {
            buildToolContext: ({ context, hasRepliedRef }) => {
                const isDirect = context.ChatType?.toLowerCase() === "direct";
                const channelId = (isDirect ? (context.From ?? context.To) : context.To)?.trim() || undefined;
                return {
                    currentChannelId: channelId,
                    currentThreadTs: context.ReplyToId,
                    hasRepliedRef,
                };
            },
        },
    },
    imessage: {
        id: "imessage",
        capabilities: {
            chatTypes: ["direct", "group"],
            reactions: true,
            media: true,
        },
        outbound: { textChunkLimit: 4000 },
        config: {
            resolveAllowFrom: ({ cfg, accountId }) => (resolveIMessageAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)),
            formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean),
        },
        groups: {
            resolveRequireMention: resolveIMessageGroupRequireMention,
            resolveToolPolicy: resolveIMessageGroupToolPolicy,
        },
        threading: {
            buildToolContext: ({ context, hasRepliedRef }) => {
                const isDirect = context.ChatType?.toLowerCase() === "direct";
                const channelId = (isDirect ? (context.From ?? context.To) : context.To)?.trim() || undefined;
                return {
                    currentChannelId: channelId,
                    currentThreadTs: context.ReplyToId,
                    hasRepliedRef,
                };
            },
        },
    },
};
function buildDockFromPlugin(plugin) {
    return {
        id: plugin.id,
        capabilities: plugin.capabilities,
        commands: plugin.commands,
        outbound: plugin.outbound?.textChunkLimit
            ? { textChunkLimit: plugin.outbound.textChunkLimit }
            : undefined,
        streaming: plugin.streaming
            ? { blockStreamingCoalesceDefaults: plugin.streaming.blockStreamingCoalesceDefaults }
            : undefined,
        elevated: plugin.elevated,
        config: plugin.config
            ? {
                resolveAllowFrom: plugin.config.resolveAllowFrom,
                formatAllowFrom: plugin.config.formatAllowFrom,
            }
            : undefined,
        groups: plugin.groups,
        mentions: plugin.mentions,
        threading: plugin.threading,
        agentPrompt: plugin.agentPrompt,
    };
}
function listPluginDockEntries() {
    const registry = requireActivePluginRegistry();
    const entries = [];
    const seen = new Set();
    for (const entry of registry.channels) {
        const plugin = entry.plugin;
        const id = String(plugin.id).trim();
        if (!id || seen.has(id))
            continue;
        seen.add(id);
        if (CHAT_CHANNEL_ORDER.includes(plugin.id))
            continue;
        const dock = entry.dock ?? buildDockFromPlugin(plugin);
        entries.push({ id: plugin.id, dock, order: plugin.meta.order });
    }
    return entries;
}
export function listChannelDocks() {
    const baseEntries = CHAT_CHANNEL_ORDER.map((id) => ({
        id,
        dock: DOCKS[id],
        order: getChatChannelMeta(id).order,
    }));
    const pluginEntries = listPluginDockEntries();
    const combined = [...baseEntries, ...pluginEntries];
    combined.sort((a, b) => {
        const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id);
        const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id);
        const orderA = a.order ?? (indexA === -1 ? 999 : indexA);
        const orderB = b.order ?? (indexB === -1 ? 999 : indexB);
        if (orderA !== orderB)
            return orderA - orderB;
        return String(a.id).localeCompare(String(b.id));
    });
    return combined.map((entry) => entry.dock);
}
export function getChannelDock(id) {
    const core = DOCKS[id];
    if (core)
        return core;
    const registry = requireActivePluginRegistry();
    const pluginEntry = registry.channels.find((entry) => entry.plugin.id === id);
    if (!pluginEntry)
        return undefined;
    return pluginEntry.dock ?? buildDockFromPlugin(pluginEntry.plugin);
}
