import { getChannelPlugin } from "../../channels/plugins/index.js";
import { recordSessionMetaFromInbound, resolveStorePath } from "../../config/sessions.js";
import { parseDiscordTarget } from "../../discord/targets.js";
import { parseIMessageTarget, normalizeIMessageHandle } from "../../imessage/targets.js";
import { buildAgentSessionKey, } from "../../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
import { resolveSlackAccount } from "../../slack/accounts.js";
import { createSlackWebClient } from "../../slack/client.js";
import { normalizeAllowListLower } from "../../slack/monitor/allow-list.js";
import { resolveSignalPeerId, resolveSignalRecipient, resolveSignalSender, } from "../../signal/identity.js";
import { parseSlackTarget } from "../../slack/targets.js";
import { buildTelegramGroupPeerId } from "../../telegram/bot/helpers.js";
import { resolveTelegramTargetChatType } from "../../telegram/inline-buttons.js";
import { parseTelegramTarget } from "../../telegram/targets.js";
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
// Cache Slack channel type lookups to avoid repeated API calls.
const SLACK_CHANNEL_TYPE_CACHE = new Map();
function looksLikeUuid(value) {
    if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value))
        return true;
    const compact = value.replace(/-/g, "");
    if (!/^[0-9a-f]+$/i.test(compact))
        return false;
    return /[a-f]/i.test(compact);
}
function normalizeThreadId(value) {
    if (value == null)
        return undefined;
    if (typeof value === "number") {
        if (!Number.isFinite(value))
            return undefined;
        return String(Math.trunc(value));
    }
    const trimmed = value.trim();
    return trimmed ? trimmed : undefined;
}
function stripProviderPrefix(raw, channel) {
    const trimmed = raw.trim();
    const lower = trimmed.toLowerCase();
    const prefix = `${channel.toLowerCase()}:`;
    if (lower.startsWith(prefix))
        return trimmed.slice(prefix.length).trim();
    return trimmed;
}
function stripKindPrefix(raw) {
    return raw.replace(/^(user|channel|group|conversation|room|dm):/i, "").trim();
}
function inferPeerKind(params) {
    const resolvedKind = params.resolvedTarget?.kind;
    if (resolvedKind === "user")
        return "dm";
    if (resolvedKind === "channel")
        return "channel";
    if (resolvedKind === "group") {
        const plugin = getChannelPlugin(params.channel);
        const chatTypes = plugin?.capabilities?.chatTypes ?? [];
        const supportsChannel = chatTypes.includes("channel");
        const supportsGroup = chatTypes.includes("group");
        if (supportsChannel && !supportsGroup)
            return "channel";
        return "group";
    }
    return "dm";
}
function buildBaseSessionKey(params) {
    return buildAgentSessionKey({
        agentId: params.agentId,
        channel: params.channel,
        peer: params.peer,
        dmScope: params.cfg.session?.dmScope ?? "main",
        identityLinks: params.cfg.session?.identityLinks,
    });
}
// Best-effort mpim detection: allowlist/config, then Slack API (if token available).
async function resolveSlackChannelType(params) {
    const channelId = params.channelId.trim();
    if (!channelId)
        return "unknown";
    const cached = SLACK_CHANNEL_TYPE_CACHE.get(`${params.accountId ?? "default"}:${channelId}`);
    if (cached)
        return cached;
    const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId });
    const groupChannels = normalizeAllowListLower(account.dm?.groupChannels);
    const channelIdLower = channelId.toLowerCase();
    if (groupChannels.includes(channelIdLower) ||
        groupChannels.includes(`slack:${channelIdLower}`) ||
        groupChannels.includes(`channel:${channelIdLower}`) ||
        groupChannels.includes(`group:${channelIdLower}`) ||
        groupChannels.includes(`mpim:${channelIdLower}`)) {
        SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "group");
        return "group";
    }
    const channelKeys = Object.keys(account.channels ?? {});
    if (channelKeys.some((key) => {
        const normalized = key.trim().toLowerCase();
        return (normalized === channelIdLower ||
            normalized === `channel:${channelIdLower}` ||
            normalized.replace(/^#/, "") === channelIdLower);
    })) {
        SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "channel");
        return "channel";
    }
    const token = account.botToken?.trim() ||
        (typeof account.config.userToken === "string" ? account.config.userToken.trim() : "");
    if (!token) {
        SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "unknown");
        return "unknown";
    }
    try {
        const client = createSlackWebClient(token);
        const info = await client.conversations.info({ channel: channelId });
        const channel = info.channel;
        const type = channel?.is_im ? "dm" : channel?.is_mpim ? "group" : "channel";
        SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, type);
        return type;
    }
    catch {
        SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "unknown");
        return "unknown";
    }
}
async function resolveSlackSession(params) {
    const parsed = parseSlackTarget(params.target, { defaultKind: "channel" });
    if (!parsed)
        return null;
    const isDm = parsed.kind === "user";
    let peerKind = isDm ? "dm" : "channel";
    if (!isDm && /^G/i.test(parsed.id)) {
        // Slack mpim/group DMs share the G-prefix; detect to align session keys with inbound.
        const channelType = await resolveSlackChannelType({
            cfg: params.cfg,
            accountId: params.accountId,
            channelId: parsed.id,
        });
        if (channelType === "group")
            peerKind = "group";
        if (channelType === "dm")
            peerKind = "dm";
    }
    const peer = {
        kind: peerKind,
        id: parsed.id,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "slack",
        peer,
    });
    const threadId = normalizeThreadId(params.threadId ?? params.replyToId);
    const threadKeys = resolveThreadSessionKeys({
        baseSessionKey,
        threadId,
    });
    return {
        sessionKey: threadKeys.sessionKey,
        baseSessionKey,
        peer,
        chatType: peerKind === "dm" ? "direct" : "channel",
        from: peerKind === "dm"
            ? `slack:${parsed.id}`
            : peerKind === "group"
                ? `slack:group:${parsed.id}`
                : `slack:channel:${parsed.id}`,
        to: peerKind === "dm" ? `user:${parsed.id}` : `channel:${parsed.id}`,
        threadId,
    };
}
function resolveDiscordSession(params) {
    const parsed = parseDiscordTarget(params.target, { defaultKind: "channel" });
    if (!parsed)
        return null;
    const isDm = parsed.kind === "user";
    const peer = {
        kind: isDm ? "dm" : "channel",
        id: parsed.id,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "discord",
        peer,
    });
    const explicitThreadId = normalizeThreadId(params.threadId);
    const threadCandidate = explicitThreadId ?? normalizeThreadId(params.replyToId);
    // Discord threads use their own channel id; avoid adding a :thread suffix.
    const threadKeys = resolveThreadSessionKeys({
        baseSessionKey,
        threadId: threadCandidate,
        useSuffix: false,
    });
    return {
        sessionKey: threadKeys.sessionKey,
        baseSessionKey,
        peer,
        chatType: isDm ? "direct" : "channel",
        from: isDm ? `discord:${parsed.id}` : `discord:channel:${parsed.id}`,
        to: isDm ? `user:${parsed.id}` : `channel:${parsed.id}`,
        threadId: explicitThreadId ?? undefined,
    };
}
function resolveTelegramSession(params) {
    const parsed = parseTelegramTarget(params.target);
    const chatId = parsed.chatId.trim();
    if (!chatId)
        return null;
    const parsedThreadId = parsed.messageThreadId;
    const fallbackThreadId = normalizeThreadId(params.threadId);
    const resolvedThreadId = parsedThreadId ?? (fallbackThreadId ? Number.parseInt(fallbackThreadId, 10) : undefined);
    // Telegram topics are encoded in the peer id (chatId:topic:<id>).
    const chatType = resolveTelegramTargetChatType(params.target);
    // If the target is a username and we lack a resolvedTarget, default to DM to avoid group keys.
    const isGroup = chatType === "group" ||
        (chatType === "unknown" &&
            params.resolvedTarget?.kind &&
            params.resolvedTarget.kind !== "user");
    const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : chatId;
    const peer = {
        kind: isGroup ? "group" : "dm",
        id: peerId,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "telegram",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: isGroup ? `telegram:group:${peerId}` : `telegram:${chatId}`,
        to: `telegram:${chatId}`,
        threadId: resolvedThreadId,
    };
}
function resolveWhatsAppSession(params) {
    const normalized = normalizeWhatsAppTarget(params.target);
    if (!normalized)
        return null;
    const isGroup = isWhatsAppGroupJid(normalized);
    const peer = {
        kind: isGroup ? "group" : "dm",
        id: normalized,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "whatsapp",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: normalized,
        to: normalized,
    };
}
function resolveSignalSession(params) {
    const stripped = stripProviderPrefix(params.target, "signal");
    const lowered = stripped.toLowerCase();
    if (lowered.startsWith("group:")) {
        const groupId = stripped.slice("group:".length).trim();
        if (!groupId)
            return null;
        const peer = { kind: "group", id: groupId };
        const baseSessionKey = buildBaseSessionKey({
            cfg: params.cfg,
            agentId: params.agentId,
            channel: "signal",
            peer,
        });
        return {
            sessionKey: baseSessionKey,
            baseSessionKey,
            peer,
            chatType: "group",
            from: `group:${groupId}`,
            to: `group:${groupId}`,
        };
    }
    let recipient = stripped.trim();
    if (lowered.startsWith("username:")) {
        recipient = stripped.slice("username:".length).trim();
    }
    else if (lowered.startsWith("u:")) {
        recipient = stripped.slice("u:".length).trim();
    }
    if (!recipient)
        return null;
    const uuidCandidate = recipient.toLowerCase().startsWith("uuid:")
        ? recipient.slice("uuid:".length)
        : recipient;
    const sender = resolveSignalSender({
        sourceUuid: looksLikeUuid(uuidCandidate) ? uuidCandidate : null,
        sourceNumber: looksLikeUuid(uuidCandidate) ? null : recipient,
    });
    const peerId = sender ? resolveSignalPeerId(sender) : recipient;
    const displayRecipient = sender ? resolveSignalRecipient(sender) : recipient;
    const peer = { kind: "dm", id: peerId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "signal",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: "direct",
        from: `signal:${displayRecipient}`,
        to: `signal:${displayRecipient}`,
    };
}
function resolveIMessageSession(params) {
    const parsed = parseIMessageTarget(params.target);
    if (parsed.kind === "handle") {
        const handle = normalizeIMessageHandle(parsed.to);
        if (!handle)
            return null;
        const peer = { kind: "dm", id: handle };
        const baseSessionKey = buildBaseSessionKey({
            cfg: params.cfg,
            agentId: params.agentId,
            channel: "imessage",
            peer,
        });
        return {
            sessionKey: baseSessionKey,
            baseSessionKey,
            peer,
            chatType: "direct",
            from: `imessage:${handle}`,
            to: `imessage:${handle}`,
        };
    }
    const peerId = parsed.kind === "chat_id"
        ? String(parsed.chatId)
        : parsed.kind === "chat_guid"
            ? parsed.chatGuid
            : parsed.chatIdentifier;
    if (!peerId)
        return null;
    const peer = { kind: "group", id: peerId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "imessage",
        peer,
    });
    const toPrefix = parsed.kind === "chat_id"
        ? "chat_id"
        : parsed.kind === "chat_guid"
            ? "chat_guid"
            : "chat_identifier";
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: "group",
        from: `imessage:group:${peerId}`,
        to: `${toPrefix}:${peerId}`,
    };
}
function resolveMatrixSession(params) {
    const stripped = stripProviderPrefix(params.target, "matrix");
    const isUser = params.resolvedTarget?.kind === "user" || stripped.startsWith("@") || /^user:/i.test(stripped);
    const rawId = stripKindPrefix(stripped);
    if (!rawId)
        return null;
    const peer = { kind: isUser ? "dm" : "channel", id: rawId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "matrix",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isUser ? "direct" : "channel",
        from: isUser ? `matrix:${rawId}` : `matrix:channel:${rawId}`,
        to: `room:${rawId}`,
    };
}
function resolveMSTeamsSession(params) {
    let trimmed = params.target.trim();
    if (!trimmed)
        return null;
    trimmed = trimmed.replace(/^(msteams|teams):/i, "").trim();
    const lower = trimmed.toLowerCase();
    const isUser = lower.startsWith("user:");
    const rawId = stripKindPrefix(trimmed);
    if (!rawId)
        return null;
    const conversationId = rawId.split(";")[0] ?? rawId;
    const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId);
    const peer = {
        kind: isUser ? "dm" : isChannel ? "channel" : "group",
        id: conversationId,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "msteams",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isUser ? "direct" : isChannel ? "channel" : "group",
        from: isUser
            ? `msteams:${conversationId}`
            : isChannel
                ? `msteams:channel:${conversationId}`
                : `msteams:group:${conversationId}`,
        to: isUser ? `user:${conversationId}` : `conversation:${conversationId}`,
    };
}
function resolveMattermostSession(params) {
    let trimmed = params.target.trim();
    if (!trimmed)
        return null;
    trimmed = trimmed.replace(/^mattermost:/i, "").trim();
    const lower = trimmed.toLowerCase();
    const isUser = lower.startsWith("user:") || trimmed.startsWith("@");
    if (trimmed.startsWith("@")) {
        trimmed = trimmed.slice(1).trim();
    }
    const rawId = stripKindPrefix(trimmed);
    if (!rawId)
        return null;
    const peer = { kind: isUser ? "dm" : "channel", id: rawId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "mattermost",
        peer,
    });
    const threadId = normalizeThreadId(params.replyToId ?? params.threadId);
    const threadKeys = resolveThreadSessionKeys({
        baseSessionKey,
        threadId,
    });
    return {
        sessionKey: threadKeys.sessionKey,
        baseSessionKey,
        peer,
        chatType: isUser ? "direct" : "channel",
        from: isUser ? `mattermost:${rawId}` : `mattermost:channel:${rawId}`,
        to: isUser ? `user:${rawId}` : `channel:${rawId}`,
        threadId,
    };
}
function resolveBlueBubblesSession(params) {
    const stripped = stripProviderPrefix(params.target, "bluebubbles");
    const lower = stripped.toLowerCase();
    const isGroup = lower.startsWith("chat_id:") ||
        lower.startsWith("chat_guid:") ||
        lower.startsWith("chat_identifier:") ||
        lower.startsWith("group:");
    const rawPeerId = isGroup
        ? stripKindPrefix(stripped)
        : stripped.replace(/^(imessage|sms|auto):/i, "");
    // BlueBubbles inbound group ids omit chat_* prefixes; strip them to align sessions.
    const peerId = isGroup
        ? rawPeerId.replace(/^(chat_id|chat_guid|chat_identifier):/i, "")
        : rawPeerId;
    if (!peerId)
        return null;
    const peer = {
        kind: isGroup ? "group" : "dm",
        id: peerId,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "bluebubbles",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: isGroup ? `group:${peerId}` : `bluebubbles:${peerId}`,
        to: `bluebubbles:${stripped}`,
    };
}
function resolveNextcloudTalkSession(params) {
    let trimmed = params.target.trim();
    if (!trimmed)
        return null;
    trimmed = trimmed.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").trim();
    trimmed = trimmed.replace(/^room:/i, "").trim();
    if (!trimmed)
        return null;
    const peer = { kind: "group", id: trimmed };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "nextcloud-talk",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: "group",
        from: `nextcloud-talk:room:${trimmed}`,
        to: `nextcloud-talk:${trimmed}`,
    };
}
function resolveZaloSession(params) {
    const trimmed = stripProviderPrefix(params.target, "zalo")
        .replace(/^(zl):/i, "")
        .trim();
    if (!trimmed)
        return null;
    const isGroup = trimmed.toLowerCase().startsWith("group:");
    const peerId = stripKindPrefix(trimmed);
    const peer = { kind: isGroup ? "group" : "dm", id: peerId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "zalo",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
        to: `zalo:${peerId}`,
    };
}
function resolveZalouserSession(params) {
    const trimmed = stripProviderPrefix(params.target, "zalouser")
        .replace(/^(zlu):/i, "")
        .trim();
    if (!trimmed)
        return null;
    const isGroup = trimmed.toLowerCase().startsWith("group:");
    const peerId = stripKindPrefix(trimmed);
    // Keep DM vs group aligned with inbound sessions for Zalo Personal.
    const peer = { kind: isGroup ? "group" : "dm", id: peerId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "zalouser",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: isGroup ? `zalouser:group:${peerId}` : `zalouser:${peerId}`,
        to: `zalouser:${peerId}`,
    };
}
function resolveNostrSession(params) {
    const trimmed = stripProviderPrefix(params.target, "nostr").trim();
    if (!trimmed)
        return null;
    const peer = { kind: "dm", id: trimmed };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "nostr",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: "direct",
        from: `nostr:${trimmed}`,
        to: `nostr:${trimmed}`,
    };
}
function normalizeTlonShip(raw) {
    const trimmed = raw.trim();
    if (!trimmed)
        return trimmed;
    return trimmed.startsWith("~") ? trimmed : `~${trimmed}`;
}
function resolveTlonSession(params) {
    let trimmed = stripProviderPrefix(params.target, "tlon");
    trimmed = trimmed.trim();
    if (!trimmed)
        return null;
    const lower = trimmed.toLowerCase();
    let isGroup = lower.startsWith("group:") || lower.startsWith("room:") || lower.startsWith("chat/");
    let peerId = trimmed;
    if (lower.startsWith("group:") || lower.startsWith("room:")) {
        peerId = trimmed.replace(/^(group|room):/i, "").trim();
        if (!peerId.startsWith("chat/")) {
            const parts = peerId.split("/").filter(Boolean);
            if (parts.length === 2) {
                peerId = `chat/${normalizeTlonShip(parts[0])}/${parts[1]}`;
            }
        }
        isGroup = true;
    }
    else if (lower.startsWith("dm:")) {
        peerId = normalizeTlonShip(trimmed.slice("dm:".length));
        isGroup = false;
    }
    else if (lower.startsWith("chat/")) {
        peerId = trimmed;
        isGroup = true;
    }
    else if (trimmed.includes("/")) {
        const parts = trimmed.split("/").filter(Boolean);
        if (parts.length === 2) {
            peerId = `chat/${normalizeTlonShip(parts[0])}/${parts[1]}`;
            isGroup = true;
        }
    }
    else {
        peerId = normalizeTlonShip(trimmed);
    }
    const peer = { kind: isGroup ? "group" : "dm", id: peerId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "tlon",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: isGroup ? `tlon:group:${peerId}` : `tlon:${peerId}`,
        to: `tlon:${peerId}`,
    };
}
/**
 * Feishu ID formats:
 * - oc_xxx: chat_id (group chat)
 * - ou_xxx: user open_id (DM)
 * - on_xxx: user union_id (DM)
 * - cli_xxx: app_id (not a valid send target)
 */
function resolveFeishuSession(params) {
    let trimmed = stripProviderPrefix(params.target, "feishu").trim();
    if (!trimmed)
        return null;
    // Strip kind prefixes like "user:", "group:", "chat:"
    const lower = trimmed.toLowerCase();
    let isGroup = false;
    if (lower.startsWith("group:") || lower.startsWith("chat:")) {
        trimmed = trimmed.replace(/^(group|chat):/i, "").trim();
        isGroup = true;
    }
    else if (lower.startsWith("user:") || lower.startsWith("dm:")) {
        trimmed = trimmed.replace(/^(user|dm):/i, "").trim();
        isGroup = false;
    }
    // Detect by Feishu ID prefix
    const idLower = trimmed.toLowerCase();
    if (idLower.startsWith("oc_")) {
        // oc_ = chat_id, always group/channel chat
        isGroup = true;
    }
    else if (idLower.startsWith("ou_") || idLower.startsWith("on_")) {
        // ou_ = open_id, on_ = union_id, both are user IDs (DM)
        isGroup = false;
    }
    const peer = {
        kind: isGroup ? "group" : "dm",
        id: trimmed,
    };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: "feishu",
        peer,
    });
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType: isGroup ? "group" : "direct",
        from: isGroup ? `feishu:group:${trimmed}` : `feishu:${trimmed}`,
        to: trimmed,
    };
}
function resolveFallbackSession(params) {
    const trimmed = stripProviderPrefix(params.target, params.channel).trim();
    if (!trimmed)
        return null;
    const peerKind = inferPeerKind({
        channel: params.channel,
        resolvedTarget: params.resolvedTarget,
    });
    const peerId = stripKindPrefix(trimmed);
    if (!peerId)
        return null;
    const peer = { kind: peerKind, id: peerId };
    const baseSessionKey = buildBaseSessionKey({
        cfg: params.cfg,
        agentId: params.agentId,
        channel: params.channel,
        peer,
    });
    const chatType = peerKind === "dm" ? "direct" : peerKind === "channel" ? "channel" : "group";
    const from = peerKind === "dm" ? `${params.channel}:${peerId}` : `${params.channel}:${peerKind}:${peerId}`;
    const toPrefix = peerKind === "dm" ? "user" : "channel";
    return {
        sessionKey: baseSessionKey,
        baseSessionKey,
        peer,
        chatType,
        from,
        to: `${toPrefix}:${peerId}`,
    };
}
export async function resolveOutboundSessionRoute(params) {
    const target = params.target.trim();
    if (!target)
        return null;
    switch (params.channel) {
        case "slack":
            return await resolveSlackSession({ ...params, target });
        case "discord":
            return resolveDiscordSession({ ...params, target });
        case "telegram":
            return resolveTelegramSession({ ...params, target });
        case "whatsapp":
            return resolveWhatsAppSession({ ...params, target });
        case "signal":
            return resolveSignalSession({ ...params, target });
        case "imessage":
            return resolveIMessageSession({ ...params, target });
        case "matrix":
            return resolveMatrixSession({ ...params, target });
        case "msteams":
            return resolveMSTeamsSession({ ...params, target });
        case "mattermost":
            return resolveMattermostSession({ ...params, target });
        case "bluebubbles":
            return resolveBlueBubblesSession({ ...params, target });
        case "nextcloud-talk":
            return resolveNextcloudTalkSession({ ...params, target });
        case "zalo":
            return resolveZaloSession({ ...params, target });
        case "zalouser":
            return resolveZalouserSession({ ...params, target });
        case "nostr":
            return resolveNostrSession({ ...params, target });
        case "tlon":
            return resolveTlonSession({ ...params, target });
        case "feishu":
            return resolveFeishuSession({ ...params, target });
        default:
            return resolveFallbackSession({ ...params, target });
    }
}
export async function ensureOutboundSessionEntry(params) {
    const storePath = resolveStorePath(params.cfg.session?.store, {
        agentId: params.agentId,
    });
    const ctx = {
        From: params.route.from,
        To: params.route.to,
        SessionKey: params.route.sessionKey,
        AccountId: params.accountId ?? undefined,
        ChatType: params.route.chatType,
        Provider: params.channel,
        Surface: params.channel,
        MessageThreadId: params.route.threadId,
        OriginatingChannel: params.channel,
        OriginatingTo: params.route.to,
    };
    try {
        await recordSessionMetaFromInbound({
            storePath,
            sessionKey: params.route.sessionKey,
            ctx,
        });
    }
    catch {
        // Do not block outbound sends on session meta writes.
    }
}
