// Per-instance dialog registries.
//
// Background: AgentDialogHandler dispatches "agent:dialog:show" as a broadcast
// CustomEvent. Every mounted AgentTerminal receives it and decides whether to
// accept based on agent-id match + focus. Pre-multi-pane there was at most
// one mount per agent, so the registries below were keyed only by agent id.
//
// With multi-pane, the same agent can be shown in two panes. We must:
//   1. Keep the agent "mounted" in the global registry as long as ANY pane
//      mounts it. Removing one mount must not flip "mounted" to false.
//   2. When a dialog fires, exactly ONE pane accepts. Election order:
//      focused pane first; otherwise the first visible (insertion-order) mount.
//
// Globals retain the same names for back-compat with consumer reads, but
// values are now Maps keyed by terminalInstanceId. Old consumers that read
// the legacy shapes (string[] / Record<id, entry>) get adapter views via
// the get* helpers below.
import { normalizeDialogAgentId } from "./agentMessageHelpers";
function globals() {
    return globalThis;
}
function getMountedMap() {
    const g = globals();
    if (!g.__agentDialogMountedAgentsMap) {
        g.__agentDialogMountedAgentsMap = new Map();
    }
    return g.__agentDialogMountedAgentsMap;
}
function getVisibleMap() {
    const g = globals();
    if (!g.__agentDialogVisibleCallbacksMap) {
        g.__agentDialogVisibleCallbacksMap = new Map();
    }
    return g.__agentDialogVisibleCallbacksMap;
}
// Keep the legacy `__agentDialogMountedAgents: string[]` view in sync for any
// reader still using the old shape. The list contains every agent id with at
// least one mounted instance.
function syncMountedLegacy() {
    const list = [];
    for (const [agentId, set] of getMountedMap()) {
        if (set.size > 0)
            list.push(agentId);
    }
    globals().__agentDialogMountedAgents = list;
}
// Keep the legacy `__agentDialogVisibleCallbacks: Record<id, entry>` in sync.
// For each agent we expose the elected entry (focused first, else earliest
// visible).
function syncVisibleLegacy() {
    const flat = {};
    for (const [agentId, entries] of getVisibleMap()) {
        const elected = electInstance(agentId, entries);
        if (elected) {
            flat[agentId] = {
                callbackId: elected.entry.callbackId,
                terminalInstanceId: elected.instanceId,
            };
        }
    }
    globals().__agentDialogVisibleCallbacks = flat;
}
function electInstance(_agentId, entries) {
    let chosen = null;
    for (const [instanceId, entry] of entries) {
        if (!chosen) {
            chosen = { instanceId, entry };
            continue;
        }
        // Focused beats unfocused.
        if (entry.isFocused && !chosen.entry.isFocused) {
            chosen = { instanceId, entry };
            continue;
        }
        if (!entry.isFocused && chosen.entry.isFocused) {
            continue;
        }
        // Same focus state: earlier registration wins.
        if (entry.registeredAt < chosen.entry.registeredAt) {
            chosen = { instanceId, entry };
        }
    }
    return chosen;
}
export function registerMountedInstance(agentId, instanceId) {
    const normalized = normalizeDialogAgentId(agentId);
    if (!normalized)
        return;
    const map = getMountedMap();
    let set = map.get(normalized);
    if (!set) {
        set = new Set();
        map.set(normalized, set);
    }
    set.add(instanceId);
    syncMountedLegacy();
}
export function unregisterMountedInstance(agentId, instanceId) {
    const normalized = normalizeDialogAgentId(agentId);
    if (!normalized)
        return;
    const map = getMountedMap();
    const set = map.get(normalized);
    if (!set)
        return;
    set.delete(instanceId);
    if (set.size === 0) {
        map.delete(normalized);
    }
    syncMountedLegacy();
}
/** Returns true if at least one terminal is mounted for the given agent id. */
export function isAgentDialogMounted(agentId) {
    const set = getMountedMap().get(normalizeDialogAgentId(agentId));
    return !!set && set.size > 0;
}
export function getMountedAgentIds() {
    const out = [];
    for (const [agentId, set] of getMountedMap()) {
        if (set.size > 0)
            out.push(agentId);
    }
    return out;
}
export function setVisibleDialogEntry(agentId, instanceId, callbackId, isFocused) {
    const normalized = normalizeDialogAgentId(agentId);
    if (!normalized)
        return;
    const map = getVisibleMap();
    let entries = map.get(normalized);
    if (!entries) {
        entries = new Map();
        map.set(normalized, entries);
    }
    if (callbackId) {
        const existing = entries.get(instanceId);
        entries.set(instanceId, {
            callbackId,
            isFocused,
            registeredAt: existing?.registeredAt ?? Date.now(),
        });
    }
    else {
        entries.delete(instanceId);
        if (entries.size === 0)
            map.delete(normalized);
    }
    syncVisibleLegacy();
}
export function clearVisibleDialogEntriesForInstance(agentIds, instanceId) {
    const map = getVisibleMap();
    for (const id of agentIds) {
        const normalized = normalizeDialogAgentId(id);
        if (!normalized)
            continue;
        const entries = map.get(normalized);
        if (!entries)
            continue;
        entries.delete(instanceId);
        if (entries.size === 0)
            map.delete(normalized);
    }
    syncVisibleLegacy();
}
export function updateInstanceFocus(agentIds, instanceId, isFocused) {
    const map = getVisibleMap();
    let changed = false;
    for (const id of agentIds) {
        const normalized = normalizeDialogAgentId(id);
        if (!normalized)
            continue;
        const entries = map.get(normalized);
        if (!entries)
            continue;
        const entry = entries.get(instanceId);
        if (!entry)
            continue;
        if (entry.isFocused !== isFocused) {
            entries.set(instanceId, { ...entry, isFocused });
            changed = true;
        }
    }
    if (changed)
        syncVisibleLegacy();
}
/**
 * Determines whether the given instance should accept an incoming
 * `agent:dialog:show` event for the given agent id. Election rule:
 *   - Among all mounted instances for the agent, the focused one wins.
 *   - If none is focused, the earliest-registered one wins (first visible).
 *
 * Note: this consults the *mounted* registry (via the visibility entries
 * we've registered alongside it). A terminal mount that has not yet called
 * `setVisibleDialogEntry` won't participate in election; that's fine because
 * those mounts also wouldn't have a callback attached.
 */
export function isElectedDialogReceiver(agentId, instanceId) {
    const normalized = normalizeDialogAgentId(agentId);
    if (!normalized)
        return true;
    // Prefer election over visibility entries (which only exist while a dialog
    // is active). For incoming requests we elect over the *mounted* set.
    const mountedSet = getMountedMap().get(normalized);
    if (!mountedSet || mountedSet.size === 0)
        return false;
    if (mountedSet.size === 1)
        return mountedSet.has(instanceId);
    // Fall back to the visible map for focus / insertion order; if a dialog is
    // already shown elsewhere, that one keeps owning subsequent events.
    const entries = getVisibleMap().get(normalized);
    if (entries && entries.size > 0) {
        const chosen = electInstance(normalized, entries);
        if (chosen)
            return chosen.instanceId === instanceId;
    }
    // No active dialog: pick the first-mounted instance via insertion order.
    // Set iteration preserves insertion order in JS, so the first iterated id wins.
    const firstId = mountedSet.values().next().value;
    return firstId === instanceId;
}
//# sourceMappingURL=agentDialogRegistry.js.map