/**
 * Agent Registry Service
 *
 * This service provides agent registration and management functionality.
 * It allows registering agents with roles (director, worker, steward) and
 * querying agents by various criteria.
 *
 * Key features:
 * - Register agents with specific roles
 * - Query agents by role, status, and other filters
 * - Track agent session status
 * - Manage agent metadata
 * - Create dedicated channels for agent messaging (TB-O7a)
 *
 * @module
 */
import { EntityTypeValue, createEntity, createTimestamp, createDirectChannel, generateDirectChannelName, duplicateName, asEntityId, asElementId } from '@stoneforge/core';
// Import shared agent entity types from the API module to avoid duplication
import { isAgentEntity, getAgentMetadata, } from '../api/orchestrator-api.js';
export { isAgentEntity, getAgentMetadata };
// ============================================================================
// Constants
// ============================================================================
/**
 * Key used to store agent metadata in Entity.metadata
 */
const AGENT_META_KEY = 'agent';
/**
 * Prefix for agent channel names
 */
const AGENT_CHANNEL_PREFIX = 'agent-';
/**
 * Generates the channel name for an agent based on agent name
 * Channel names follow the pattern: agent-{agentName}
 */
export function generateAgentChannelName(agentName) {
    return `${AGENT_CHANNEL_PREFIX}${agentName}`;
}
/**
 * Parses an agent name from a channel name
 * Returns null if the name doesn't match the agent channel pattern
 */
export function parseAgentChannelName(channelName) {
    if (!channelName.startsWith(AGENT_CHANNEL_PREFIX)) {
        return null;
    }
    const agentName = channelName.slice(AGENT_CHANNEL_PREFIX.length);
    // Basic validation - must have a non-empty name
    if (!agentName || agentName.length === 0) {
        return null;
    }
    return agentName;
}
// ============================================================================
// Agent Registry Implementation
// ============================================================================
/**
 * Implementation of the Agent Registry service.
 *
 * This implementation uses the QuarryAPI for storage operations,
 * storing agent information as Entity elements with specialized metadata.
 */
export class AgentRegistryImpl {
    api;
    constructor(api) {
        this.api = api;
    }
    // ----------------------------------------
    // Agent Registration
    // ----------------------------------------
    async registerAgent(input) {
        switch (input.role) {
            case 'director':
                return this.registerDirector(input);
            case 'worker':
                return this.registerWorker(input);
            case 'steward':
                return this.registerSteward(input);
            default:
                throw new Error(`Unknown agent role: ${input.role}`);
        }
    }
    async registerDirector(input) {
        const existing = await this.getAgentByName(input.name);
        if (existing) {
            throw duplicateName(input.name, 'agent', { existingId: existing.id });
        }
        const agentMetadata = {
            agentRole: 'director',
            sessionStatus: 'idle',
            maxConcurrentTasks: input.maxConcurrentTasks,
            roleDefinitionRef: input.roleDefinitionRef,
            provider: input.provider,
            model: input.model,
        };
        const entity = await createEntity({
            name: input.name,
            entityType: EntityTypeValue.AGENT,
            createdBy: input.createdBy,
            tags: input.tags,
            metadata: { [AGENT_META_KEY]: agentMetadata },
        });
        return this.registerAgentWithRollback(entity, input.name, input.createdBy);
    }
    async registerWorker(input) {
        const existing = await this.getAgentByName(input.name);
        if (existing) {
            throw duplicateName(input.name, 'agent', { existingId: existing.id });
        }
        const agentMetadata = {
            agentRole: 'worker',
            workerMode: input.workerMode,
            sessionStatus: 'idle',
            maxConcurrentTasks: input.maxConcurrentTasks,
            roleDefinitionRef: input.roleDefinitionRef,
            provider: input.provider,
            model: input.model,
        };
        const entity = await createEntity({
            name: input.name,
            entityType: EntityTypeValue.AGENT,
            createdBy: input.createdBy,
            tags: input.tags,
            metadata: { [AGENT_META_KEY]: agentMetadata },
            reportsTo: input.reportsTo,
        });
        return this.registerAgentWithRollback(entity, input.name, input.createdBy);
    }
    async registerSteward(input) {
        const existing = await this.getAgentByName(input.name);
        if (existing) {
            throw duplicateName(input.name, 'agent', { existingId: existing.id });
        }
        const agentMetadata = {
            agentRole: 'steward',
            stewardFocus: input.stewardFocus,
            triggers: input.triggers,
            sessionStatus: 'idle',
            maxConcurrentTasks: input.maxConcurrentTasks,
            roleDefinitionRef: input.roleDefinitionRef,
            provider: input.provider,
            model: input.model,
        };
        const entity = await createEntity({
            name: input.name,
            entityType: EntityTypeValue.AGENT,
            createdBy: input.createdBy,
            tags: input.tags,
            metadata: { [AGENT_META_KEY]: agentMetadata },
            reportsTo: input.reportsTo,
        });
        return this.registerAgentWithRollback(entity, input.name, input.createdBy);
    }
    // ----------------------------------------
    // Agent Queries
    // ----------------------------------------
    async getAgent(entityId) {
        // Cast EntityId to ElementId - they are both branded string types
        const entity = await this.api.get(asElementId(entityId));
        if (!entity || entity.type !== 'entity' || !isAgentEntity(entity)) {
            return undefined;
        }
        return entity;
    }
    async getAgentByName(name) {
        const entity = await this.api.lookupEntityByName(name);
        if (!entity || !isAgentEntity(entity)) {
            return undefined;
        }
        return entity;
    }
    async listAgents(filter) {
        // Get all entities
        const entities = await this.api.list({ type: 'entity' });
        // Filter to only agent-type entities with valid agent metadata
        let agents = entities.filter((e) => e.entityType === EntityTypeValue.AGENT && isAgentEntity(e));
        // Apply additional filters
        if (filter) {
            agents = this.applyFilters(agents, filter);
        }
        return agents;
    }
    async getAgentsByRole(role) {
        return this.listAgents({ role });
    }
    async getAvailableWorkers() {
        const workers = await this.listAgents({ role: 'worker' });
        return workers.filter((w) => {
            const meta = getAgentMetadata(w);
            const status = meta?.sessionStatus;
            // Consider idle or undefined (never started) as available
            return status === 'idle' || status === undefined;
        });
    }
    async getStewards() {
        return this.listAgents({ role: 'steward' });
    }
    async getDirector() {
        const directors = await this.listAgents({ role: 'director' });
        return directors[0];
    }
    // ----------------------------------------
    // Agent Session Management
    // ----------------------------------------
    async updateAgentSession(entityId, sessionId, status) {
        const agent = await this.getAgent(entityId);
        if (!agent) {
            throw new Error(`Agent not found: ${entityId}`);
        }
        const currentMeta = getAgentMetadata(agent);
        if (!currentMeta) {
            throw new Error(`Entity is not an agent: ${entityId}`);
        }
        const updatedAgentMeta = {
            ...currentMeta,
            sessionId,
            sessionStatus: status,
            lastActivityAt: createTimestamp(),
        };
        // Cast EntityId to ElementId for update
        const updated = await this.api.update(asElementId(entityId), {
            metadata: { ...agent.metadata, [AGENT_META_KEY]: updatedAgentMeta },
        });
        return updated;
    }
    async updateAgentMetadata(entityId, updates) {
        const agent = await this.getAgent(entityId);
        if (!agent) {
            throw new Error(`Agent not found: ${entityId}`);
        }
        const currentMeta = getAgentMetadata(agent);
        if (!currentMeta) {
            throw new Error(`Entity is not an agent: ${entityId}`);
        }
        const updatedAgentMeta = {
            ...currentMeta,
            ...updates,
        };
        // Cast EntityId to ElementId for update
        const updated = await this.api.update(asElementId(entityId), {
            metadata: { ...agent.metadata, [AGENT_META_KEY]: updatedAgentMeta },
        });
        return updated;
    }
    async updateAgent(entityId, updates) {
        const agent = await this.getAgent(entityId);
        if (!agent) {
            throw new Error(`Agent not found: ${entityId}`);
        }
        // Build update object with only provided fields
        const updateData = {};
        if (updates.name !== undefined) {
            updateData.name = updates.name;
        }
        // Cast EntityId to ElementId for update, explicitly type as Entity
        const updated = await this.api.update(asElementId(entityId), updateData);
        return updated;
    }
    async deleteAgent(entityId) {
        const agent = await this.getAgent(entityId);
        if (!agent) {
            throw new Error(`Agent not found: ${entityId}`);
        }
        // Get the agent's channel ID from metadata
        const meta = getAgentMetadata(agent);
        const channelId = meta?.channelId;
        // Delete the agent's channel if it exists
        if (channelId) {
            try {
                await this.api.delete(asElementId(channelId));
            }
            catch {
                // Best-effort channel deletion - continue even if it fails
            }
        }
        // Delete the agent entity
        await this.api.delete(asElementId(entityId));
    }
    // ----------------------------------------
    // Agent Channel Operations (TB-O7a)
    // ----------------------------------------
    async getAgentChannel(agentId) {
        // First try to get the channel ID from the agent's metadata (fast path)
        const channelId = await this.getAgentChannelId(agentId);
        if (channelId) {
            const channel = await this.api.get(asElementId(channelId));
            if (channel && channel.type === 'channel') {
                return channel;
            }
        }
        // Fallback: look up agent to get createdBy, then search for the direct channel
        const agent = await this.getAgent(agentId);
        if (!agent) {
            return undefined;
        }
        // Direct channel name is based on sorted entity IDs
        const channelName = generateDirectChannelName(agent.createdBy, agentId);
        const channels = await this.api.searchChannels(channelName, {
            channelType: 'direct',
        });
        // Find exact match (searchChannels does pattern matching)
        const agentChannel = channels.find((c) => c.name === channelName);
        return agentChannel;
    }
    async getAgentChannelId(agentId) {
        const agent = await this.getAgent(agentId);
        if (!agent) {
            return undefined;
        }
        const meta = getAgentMetadata(agent);
        return meta?.channelId;
    }
    // ----------------------------------------
    // Private Helpers
    // ----------------------------------------
    /**
     * Creates an agent entity, its channel, and links them via metadata.
     * Rolls back on partial failure to prevent orphaned resources.
     */
    async registerAgentWithRollback(entityData, agentName, createdBy) {
        const saved = await this.api.create(entityData);
        const agentEntity = saved;
        const agentEntityId = asEntityId(agentEntity.id);
        let channel;
        try {
            channel = await this.createAgentChannel(agentName, agentEntityId, createdBy);
        }
        catch (channelError) {
            try {
                await this.api.delete(agentEntity.id);
            }
            catch { /* best-effort rollback */ }
            throw channelError;
        }
        try {
            return await this.updateAgentMetadata(agentEntityId, {
                channelId: channel.id,
            });
        }
        catch (metadataError) {
            try {
                await this.api.delete(channel.id);
            }
            catch { /* best-effort */ }
            try {
                await this.api.delete(agentEntity.id);
            }
            catch { /* best-effort */ }
            throw metadataError;
        }
    }
    /**
     * Creates a dedicated channel for an agent and updates the agent's metadata
     * with the channel ID.
     *
     * @param agentName - The name of the agent (used for channel naming)
     * @param agentId - The ID of the agent entity (used for membership)
     * @param createdBy - The entity that created the agent (will be a channel member)
     * @returns The created channel
     */
    async createAgentChannel(agentName, agentId, createdBy) {
        // Look up the creator entity name for the channel display name
        const creatorEntity = await this.api.get(asElementId(createdBy));
        const creatorName = creatorEntity?.name ?? 'operator';
        // Create a direct channel between the agent and the operator
        const channel = await createDirectChannel({
            entityA: createdBy, // Operator (el-0000)
            entityB: agentId, // The new agent
            entityAName: creatorName,
            entityBName: agentName,
            createdBy: createdBy,
            tags: ['agent-channel'],
            metadata: {
                agentId,
                agentName,
                purpose: 'Agent direct messaging channel',
            },
        });
        // Save the channel
        const savedChannel = await this.api.create(channel);
        return savedChannel;
    }
    applyFilters(agents, filter) {
        let result = agents;
        if (filter.role !== undefined) {
            result = result.filter((a) => {
                const meta = getAgentMetadata(a);
                return meta?.agentRole === filter.role;
            });
        }
        if (filter.workerMode !== undefined) {
            result = result.filter((a) => {
                const meta = getAgentMetadata(a);
                return (meta?.agentRole === 'worker' &&
                    meta.workerMode === filter.workerMode);
            });
        }
        if (filter.stewardFocus !== undefined) {
            result = result.filter((a) => {
                const meta = getAgentMetadata(a);
                return (meta?.agentRole === 'steward' &&
                    meta.stewardFocus === filter.stewardFocus);
            });
        }
        if (filter.sessionStatus !== undefined) {
            result = result.filter((a) => {
                const meta = getAgentMetadata(a);
                return meta?.sessionStatus === filter.sessionStatus;
            });
        }
        if (filter.reportsTo !== undefined) {
            result = result.filter((a) => a.reportsTo === filter.reportsTo);
        }
        if (filter.hasSession !== undefined) {
            result = result.filter((a) => {
                const meta = getAgentMetadata(a);
                return (meta?.sessionId !== undefined) === filter.hasSession;
            });
        }
        return result;
    }
}
// ============================================================================
// Factory Function
// ============================================================================
/**
 * Creates an AgentRegistry instance
 */
export function createAgentRegistry(api) {
    return new AgentRegistryImpl(api);
}
//# sourceMappingURL=agent-registry.js.map