import { resolveDefaultAgentId } from "../agents/agent-scope.js";
import { listBindings } from "./bindings.js";
import { buildAgentMainSessionKey, buildAgentPeerSessionKey, DEFAULT_ACCOUNT_ID, DEFAULT_MAIN_KEY, normalizeAgentId, sanitizeAgentId, } from "./session-key.js";
export { DEFAULT_ACCOUNT_ID, DEFAULT_AGENT_ID } from "./session-key.js";
function normalizeToken(value) {
    return (value ?? "").trim().toLowerCase();
}
/** Normalize and lowercase unicode-invisible chars for DM pattern matching. */
function normalizeDmText(text) {
    return (text ?? "").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "").toLowerCase();
}
/** Returns the first agentId whose dmChat.mentionPatterns matches the text, or null. */
function resolveAgentByDmContent(cfg, text) {
    const cleaned = normalizeDmText(text);
    if (!cleaned)
        return null;
    const agents = listAgents(cfg);
    for (const agent of agents) {
        const patterns = agent?.dmChat?.mentionPatterns;
        if (!Array.isArray(patterns) || patterns.length === 0)
            continue;
        const matched = patterns.some((pattern) => {
            if (!pattern)
                return false;
            try {
                return new RegExp(pattern, "i").test(cleaned);
            }
            catch {
                return false;
            }
        });
        if (matched && agent?.id?.trim())
            return agent.id.trim();
    }
    return null;
}
function normalizeId(value) {
    return (value ?? "").trim();
}
function normalizeAccountId(value) {
    const trimmed = (value ?? "").trim();
    return trimmed ? trimmed : DEFAULT_ACCOUNT_ID;
}
function matchesAccountId(match, actual) {
    const trimmed = (match ?? "").trim();
    if (!trimmed) {
        return actual === DEFAULT_ACCOUNT_ID;
    }
    if (trimmed === "*") {
        return true;
    }
    return trimmed === actual;
}
export function buildAgentSessionKey(params) {
    const channel = normalizeToken(params.channel) || "unknown";
    const peer = params.peer;
    return buildAgentPeerSessionKey({
        agentId: params.agentId,
        mainKey: DEFAULT_MAIN_KEY,
        channel,
        accountId: params.accountId,
        peerKind: peer?.kind ?? "dm",
        peerId: peer ? normalizeId(peer.id) || "unknown" : null,
        dmScope: params.dmScope,
        identityLinks: params.identityLinks,
    });
}
function listAgents(cfg) {
    const agents = cfg.agents?.list;
    return Array.isArray(agents) ? agents : [];
}
function pickFirstExistingAgentId(cfg, agentId) {
    const trimmed = (agentId ?? "").trim();
    if (!trimmed) {
        return sanitizeAgentId(resolveDefaultAgentId(cfg));
    }
    const normalized = normalizeAgentId(trimmed);
    const agents = listAgents(cfg);
    if (agents.length === 0) {
        return sanitizeAgentId(trimmed);
    }
    const match = agents.find((agent) => normalizeAgentId(agent.id) === normalized);
    if (match?.id?.trim()) {
        return sanitizeAgentId(match.id.trim());
    }
    return sanitizeAgentId(resolveDefaultAgentId(cfg));
}
function matchesChannel(match, channel) {
    const key = normalizeToken(match?.channel);
    if (!key) {
        return false;
    }
    return key === channel;
}
function matchesPeer(match, peer) {
    const m = match?.peer;
    if (!m) {
        return false;
    }
    const kind = normalizeToken(m.kind);
    const id = normalizeId(m.id);
    if (!kind || !id) {
        return false;
    }
    return kind === peer.kind && id === peer.id;
}
function matchesGuild(match, guildId) {
    const id = normalizeId(match?.guildId);
    if (!id) {
        return false;
    }
    return id === guildId;
}
function matchesTeam(match, teamId) {
    const id = normalizeId(match?.teamId);
    if (!id) {
        return false;
    }
    return id === teamId;
}
export function resolveAgentRoute(input) {
    const channel = normalizeToken(input.channel);
    const accountId = normalizeAccountId(input.accountId);
    const peer = input.peer ? { kind: input.peer.kind, id: normalizeId(input.peer.id) } : null;
    const guildId = normalizeId(input.guildId);
    const teamId = normalizeId(input.teamId);
    const bindings = listBindings(input.cfg).filter((binding) => {
        if (!binding || typeof binding !== "object") {
            return false;
        }
        if (!matchesChannel(binding.match, channel)) {
            return false;
        }
        return matchesAccountId(binding.match?.accountId, accountId);
    });
    const dmScope = input.cfg.session?.dmScope ?? "main";
    const identityLinks = input.cfg.session?.identityLinks;
    const choose = (agentId, matchedBy) => {
        const resolvedAgentId = pickFirstExistingAgentId(input.cfg, agentId);
        const sessionKey = buildAgentSessionKey({
            agentId: resolvedAgentId,
            channel,
            accountId,
            peer,
            dmScope,
            identityLinks,
        }).toLowerCase();
        const mainSessionKey = buildAgentMainSessionKey({
            agentId: resolvedAgentId,
            mainKey: DEFAULT_MAIN_KEY,
        }).toLowerCase();
        return {
            agentId: resolvedAgentId,
            channel,
            accountId,
            sessionKey,
            mainSessionKey,
            matchedBy,
        };
    };
    if (peer) {
        const peerMatch = bindings.find((b) => matchesPeer(b.match, peer));
        if (peerMatch) {
            return choose(peerMatch.agentId, "binding.peer");
        }
    }
    // Thread parent inheritance: if peer (thread) didn't match, check parent peer binding
    const parentPeer = input.parentPeer
        ? { kind: input.parentPeer.kind, id: normalizeId(input.parentPeer.id) }
        : null;
    if (parentPeer && parentPeer.id) {
        const parentPeerMatch = bindings.find((b) => matchesPeer(b.match, parentPeer));
        if (parentPeerMatch)
            return choose(parentPeerMatch.agentId, "binding.peer.parent");
    }
    if (guildId) {
        const guildMatch = bindings.find((b) => matchesGuild(b.match, guildId));
        if (guildMatch) {
            return choose(guildMatch.agentId, "binding.guild");
        }
    }
    if (teamId) {
        const teamMatch = bindings.find((b) => matchesTeam(b.match, teamId));
        if (teamMatch) {
            return choose(teamMatch.agentId, "binding.team");
        }
    }
    const accountMatch = bindings.find((b) => b.match?.accountId?.trim() !== "*" && !b.match?.peer && !b.match?.guildId && !b.match?.teamId);
    if (accountMatch) {
        return choose(accountMatch.agentId, "binding.account");
    }
    const anyAccountMatch = bindings.find((b) => b.match?.accountId?.trim() === "*" && !b.match?.peer && !b.match?.guildId && !b.match?.teamId);
    if (anyAccountMatch) {
        return choose(anyAccountMatch.agentId, "binding.channel");
    }
    // Content-based DM routing: if this is a DM and messageText was provided,
    // check each agent's dmChat.mentionPatterns for a match before falling back to default.
    const isDm = peer?.kind === "dm" || peer?.kind === "direct";
    if (isDm && input.messageText) {
        const dmAgentId = resolveAgentByDmContent(input.cfg, input.messageText);
        if (dmAgentId) {
            return choose(dmAgentId, "dm.mention");
        }
    }
    return choose(resolveDefaultAgentId(input.cfg), "default");
}
