import { resolveAgentName } from './facade.js';
function stripSigil(value) {
    return value.startsWith('@') || value.startsWith('#') ? value.slice(1) : value;
}
function runHandler(handler, event) {
    void Promise.resolve(handler(event)).catch(() => {
        // Listener handler errors are isolated from the event source.
    });
}
// ---------------------------------------------------------------------------
// Message predicates
// ---------------------------------------------------------------------------
export class MessageCreatedPredicate {
    channel;
    mentioned;
    in(channel) {
        this.channel = stripSigil(channel);
        return this;
    }
    mentions(agent) {
        this.mentioned = resolveAgentName(agent);
        return this;
    }
    subscribe(context, handler) {
        return context.events.on('messageCreated', (event) => {
            if (this.channel && stripSigil(event.channel) !== this.channel)
                return;
            if (this.mentioned && !messageMentions(event, this.mentioned))
                return;
            runHandler(handler, event);
        });
    }
}
function messageMentions(event, name) {
    const mentions = event.message.mentions ?? [];
    if (mentions.some((mention) => stripSigil(mention) === name))
        return true;
    return event.message.text?.includes(`@${name}`) ?? false;
}
export class MessageReadPredicate {
    subscribe(context, handler) {
        return context.events.on('messageRead', (event) => runHandler(handler, event));
    }
}
export class MessageReactedPredicate {
    subscribe(context, handler) {
        const off1 = context.events.on('reactionAdded', (event) => runHandler(handler, event));
        const off2 = context.events.on('reactionRemoved', (event) => runHandler(handler, event));
        return () => {
            off1();
            off2();
        };
    }
}
export function createEnrichedEvents(base) {
    const enriched = Object.create(base);
    enriched.message = {
        created: () => new MessageCreatedPredicate(),
        read: () => new MessageReadPredicate(),
        reacted: () => new MessageReactedPredicate(),
    };
    return enriched;
}
// ---------------------------------------------------------------------------
// Action predicates
// ---------------------------------------------------------------------------
export class ActionPredicate {
    action;
    caller;
    phase = 'action.invoked';
    constructor(action) {
        this.action = action;
    }
    calledBy(agent) {
        this.caller = resolveAgentName(agent);
        return this;
    }
    completed() {
        this.phase = 'action.completed';
        return this;
    }
    failed() {
        this.phase = 'action.failed';
        return this;
    }
    denied() {
        this.phase = 'action.denied';
        return this;
    }
    subscribe(context, handler) {
        return context.onActionEvent((event) => {
            if (event.action !== this.action)
                return;
            if (event.type !== this.phase)
                return;
            if (this.caller && event.caller.name !== this.caller)
                return;
            runHandler(handler, event);
        });
    }
}
// ---------------------------------------------------------------------------
// Agent session predicates (status + tools)
// ---------------------------------------------------------------------------
export class StatusPredicate {
    agentId;
    status;
    constructor(agentId, status) {
        this.agentId = agentId;
        this.status = status;
    }
    subscribe(context, handler) {
        return context.onSessionEvent(({ agentId, event }) => {
            if (agentId !== this.agentId)
                return;
            const matches = (event.type === 'status.changed' && event.status === this.status) ||
                event.type === `status.${this.status}`;
            if (matches)
                runHandler(handler, event);
        });
    }
}
export class ToolCalledPredicate {
    agentId;
    tool;
    filter;
    constructor(agentId, tool) {
        this.agentId = agentId;
        this.tool = tool;
    }
    where(filter) {
        this.filter = filter;
        return this;
    }
    subscribe(context, handler) {
        return context.onSessionEvent(({ agentId, event }) => {
            if (agentId !== this.agentId)
                return;
            if (event.type !== 'tool.called' || event.tool !== this.tool)
                return;
            if (this.filter && !this.filter(event))
                return;
            runHandler(handler, event);
        });
    }
}
const PUBLIC_MESSAGE_TYPE = {
    messageCreated: 'message.created',
    messageUpdated: 'message.updated',
    threadReply: 'thread.reply',
    dmReceived: 'dm.received',
    groupDmReceived: 'group_dm.received',
};
function buildEnvelope(message, channelName) {
    const channel = message.channel ?? (channelName ? { name: stripSigil(channelName) } : undefined);
    return {
        ...(message.from ? { from: message.from } : {}),
        ...(message.target ? { to: message.target } : {}),
        ...(channel ? { channel } : {}),
        ...(message.parentId ? { parent: message.parentId } : {}),
    };
}
/** Map a raw messaging event to its public form, or `undefined` if not surfaced. */
export function toPublicMessagingEvent(raw) {
    const messageType = PUBLIC_MESSAGE_TYPE[raw.type];
    if (messageType && 'message' in raw) {
        const channelName = 'channel' in raw && typeof raw.channel === 'string' ? raw.channel : undefined;
        return { type: messageType, message: raw.message, envelope: buildEnvelope(raw.message, channelName) };
    }
    if (raw.type === 'messageRead') {
        return {
            type: 'message.read',
            messageId: raw.messageId,
            agentName: raw.agentName,
            ...(raw.readAt ? { readAt: raw.readAt } : {}),
        };
    }
    if (raw.type === 'reactionAdded' || raw.type === 'reactionRemoved') {
        return {
            type: 'message.reacted',
            messageId: raw.messageId,
            emoji: raw.emoji,
            agentName: raw.agentName,
            action: raw.type === 'reactionAdded' ? 'added' : 'removed',
        };
    }
    return undefined;
}
function toPublicActionEvent(raw) {
    return {
        type: raw.type,
        action: raw.action,
        agent: raw.caller,
        input: raw.input,
        output: raw.output,
        error: raw.error,
        reason: raw.reason,
        at: raw.at,
    };
}
function toPublicSessionEvent(agentId, event) {
    if (event.type.startsWith('status.')) {
        return {
            type: `agent.${event.type}`,
            agentId,
            ...('status' in event && event.status ? { status: event.status } : {}),
            ...('reason' in event && event.reason ? { reason: event.reason } : {}),
        };
    }
    return { type: event.type, agentId, event };
}
/** Match a selector (`'*'`, `'message.*'`, or an exact dotted name) against an event type. */
export function matchesSelector(selector, type) {
    if (selector === '*')
        return true;
    if (selector.endsWith('.*'))
        return type.startsWith(selector.slice(0, -1));
    return selector === type;
}
export function createListenerHub(baseEvents, actions) {
    const sessionHandlers = new Set();
    const events = createEnrichedEvents(baseEvents);
    const context = {
        events: baseEvents,
        onActionEvent: (handler) => actions.onEvent?.(handler) ?? (() => { }),
        onSessionEvent: (handler) => {
            sessionHandlers.add(handler);
            return () => sessionHandlers.delete(handler);
        },
    };
    const addListener = (selector, handler) => {
        // Open the event stream — `events.on(...)` only registers handlers; the
        // socket is opened by `events.connect()` (idempotent). Agent-scoped clients
        // stream through their own connection; workspace-key clients stream all
        // workspace-visible events through the workspace stream.
        try {
            context.events.connect();
        }
        catch {
            // No stream available (no agent token and no workspace stream).
        }
        if (typeof selector !== 'string') {
            return selector.subscribe(context, handler);
        }
        const offs = [
            context.events.on('any', (raw) => {
                const evt = toPublicMessagingEvent(raw);
                if (evt && matchesSelector(selector, evt.type))
                    runHandler(handler, evt);
            }),
            context.onActionEvent((raw) => {
                const evt = toPublicActionEvent(raw);
                if (matchesSelector(selector, evt.type))
                    runHandler(handler, evt);
            }),
            context.onSessionEvent(({ agentId, event }) => {
                const evt = toPublicSessionEvent(agentId, event);
                if (matchesSelector(selector, evt.type))
                    runHandler(handler, evt);
            }),
        ];
        return () => offs.forEach((off) => off());
    };
    return {
        events,
        on: (predicate, handler) => predicate.subscribe(context, handler),
        addListener,
        action: (name) => new ActionPredicate(name),
        agent: (input) => createAgentHandle(input),
        emitSessionEvent: (agentId, event) => {
            for (const handler of sessionHandlers) {
                try {
                    handler({ agentId, event });
                }
                catch {
                    // Session listener errors are isolated.
                }
            }
        },
    };
}
export function createAgentHandle(input) {
    const handle = input.handle ?? input.name;
    return {
        id: input.id,
        name: input.name,
        handle,
        token: input.token,
        status: {
            becomes: (status) => new StatusPredicate(input.id, status),
        },
        tools: {
            called: (tool) => new ToolCalledPredicate(input.id, tool),
        },
    };
}
//# sourceMappingURL=listeners.js.map