import { mkdirSync } from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import { dirname, join, resolve } from 'node:path';
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
const DEFAULT_ARTIFACT_DIR = '.goodvibes/remote-artifacts';
const DEFAULT_SESSION_BUNDLE_DIR = '.goodvibes/remote-sessions';
function buildCapabilityCeiling(agent) {
    return Object.freeze({
        allowedTools: [...agent.tools],
        capabilityCeilingTools: [...(agent.capabilityCeilingTools ?? agent.tools)],
        executionProtocol: agent.executionProtocol,
        reviewMode: agent.reviewMode,
        communicationLane: agent.communicationLane,
        orchestrationDepth: agent.orchestrationDepth,
        successCriteria: [...(agent.successCriteria ?? [])],
        requiredEvidence: [...(agent.requiredEvidence ?? [])],
        writeScope: [...(agent.writeScope ?? [])],
    });
}
function buildEvidenceSummary(agent, connection) {
    return Object.freeze({
        toolCallCount: agent.toolCallCount,
        messageCount: connection?.messageCount ?? 0,
        errorCount: connection?.errorCount ?? 0,
        transportState: connection?.transportState ?? 'disconnected',
        connectedAt: connection?.connectedAt,
        lastError: connection?.lastError ?? agent.error,
        hasKnowledgeInjections: (agent.knowledgeInjections?.length ?? 0) > 0,
    });
}
function summarizeOutput(agent) {
    const raw = agent.fullOutput ?? agent.progress ?? agent.error ?? 'No output recorded.';
    const normalized = raw.replace(/\s+/g, ' ').trim();
    return normalized.length <= 180 ? normalized : `${normalized.slice(0, 177)}...`;
}
function buildContract(agent, connection, existing) {
    return Object.freeze({
        id: `runner:${agent.id}`,
        runnerId: agent.id,
        poolId: existing?.poolId,
        taskId: connection?.taskId,
        label: connection?.label ?? `${agent.template} runner`,
        sourceTransport: connection ? 'acp' : 'daemon',
        trustClass: connection ? 'self-hosted-acp' : 'local-daemon',
        template: agent.template,
        parentAgentId: agent.parentAgentId,
        orchestrationGraphId: agent.orchestrationGraphId,
        orchestrationNodeId: agent.orchestrationNodeId,
        capabilityCeiling: buildCapabilityCeiling(agent),
        createdAt: agent.startedAt,
        lastUpdatedAt: agent.completedAt ?? Date.now(),
        transport: Object.freeze({
            state: connection?.transportState ?? 'disconnected',
            connectedAt: connection?.connectedAt,
            messageCount: connection?.messageCount ?? 0,
            errorCount: connection?.errorCount ?? 0,
            lastError: connection?.lastError,
        }),
    });
}
function buildArtifact(agent, contract, connection) {
    return Object.freeze({
        id: `artifact:${agent.id}:${Date.now()}`,
        runnerId: agent.id,
        createdAt: Date.now(),
        runnerContract: contract,
        task: Object.freeze({
            task: agent.task,
            status: agent.status,
            startedAt: agent.startedAt,
            completedAt: agent.completedAt,
            summary: summarizeOutput(agent),
            fullOutput: agent.fullOutput,
            error: agent.error,
            progress: agent.progress,
        }),
        evidence: buildEvidenceSummary(agent, connection),
        knowledgeInjections: Object.freeze([...(agent.knowledgeInjections ?? [])]),
    });
}
function summarizeTaskOutput(task) {
    if (!task)
        return 'No task output recorded.';
    const payload = (typeof task.result === 'string'
        ? task.result
        : task.error
            ?? (task.result !== undefined ? JSON.stringify(task.result) : task.description)
            ?? task.title);
    const normalized = String(payload).replace(/\s+/g, ' ').trim();
    return normalized.length <= 180 ? normalized : `${normalized.slice(0, 177)}...`;
}
function findTaskForContract(contract, store) {
    if (!store)
        return undefined;
    const tasks = store.getState().tasks.tasks;
    if (contract.taskId) {
        const byTaskId = tasks.get(contract.taskId);
        if (byTaskId)
            return byTaskId;
    }
    for (const task of tasks.values()) {
        if (task.owner === contract.runnerId && task.kind === 'acp') {
            return task;
        }
    }
    return undefined;
}
function mapTaskStatus(task) {
    switch (task?.status) {
        case 'queued':
            return 'pending';
        case 'running':
        case 'blocked':
            return 'running';
        case 'completed':
            return 'completed';
        case 'failed':
            return 'failed';
        case 'cancelled':
            return 'cancelled';
        default:
            return 'running';
    }
}
function buildArtifactFromStore(contract, store) {
    const connection = contract.runnerId ? store?.getState().acp.connections.get(contract.runnerId) : undefined;
    const task = findTaskForContract(contract, store);
    return Object.freeze({
        id: `artifact:${contract.runnerId}:${Date.now()}`,
        runnerId: contract.runnerId,
        createdAt: Date.now(),
        runnerContract: contract,
        task: Object.freeze({
            task: task?.description ?? task?.title ?? contract.label,
            status: mapTaskStatus(task),
            startedAt: task?.startedAt ?? contract.createdAt,
            completedAt: task?.endedAt,
            summary: summarizeTaskOutput(task),
            ...(typeof task?.result === 'string' ? { fullOutput: task.result } : {}),
            ...(task?.error ? { error: task.error } : {}),
        }),
        evidence: Object.freeze({
            toolCallCount: 0,
            messageCount: connection?.messageCount ?? contract.transport.messageCount,
            errorCount: connection?.errorCount ?? contract.transport.errorCount,
            transportState: connection?.transportState ?? contract.transport.state,
            connectedAt: connection?.connectedAt ?? contract.transport.connectedAt,
            lastError: connection?.lastError ?? contract.transport.lastError,
            hasKnowledgeInjections: false,
        }),
        knowledgeInjections: Object.freeze([]),
    });
}
export class RemoteRunnerRegistry {
    agentManager;
    contracts = new Map();
    artifacts = new Map();
    pools = new Map();
    constructor(agentManager) {
        this.agentManager = agentManager;
    }
    resolveConnection(agentId, store) {
        return store?.getState().acp.connections.get(agentId);
    }
    upsertContractForAgent(agentId, store) {
        const agent = this.agentManager.getStatus(agentId);
        if (!agent)
            return null;
        const contract = buildContract(agent, this.resolveConnection(agentId, store), this.contracts.get(agentId) ?? undefined);
        this.contracts.set(agentId, contract);
        if (contract.poolId) {
            this.assignRunnerToPool(contract.poolId, contract.runnerId);
        }
        return contract;
    }
    getContract(agentId) {
        return this.contracts.get(agentId) ?? null;
    }
    registerContract(contract) {
        this.contracts.set(contract.runnerId, contract);
        if (contract.poolId) {
            this.assignRunnerToPool(contract.poolId, contract.runnerId);
        }
        return contract;
    }
    listContracts() {
        return [...this.contracts.values()].sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
    }
    createPool(input) {
        const existing = this.pools.get(input.id);
        const now = Date.now();
        const pool = Object.freeze({
            id: input.id,
            label: input.label ?? existing?.label ?? input.id,
            description: input.description ?? existing?.description,
            trustClass: input.trustClass ?? existing?.trustClass ?? 'self-hosted-acp',
            preferredTemplate: input.preferredTemplate ?? existing?.preferredTemplate,
            maxRunners: input.maxRunners ?? existing?.maxRunners,
            runnerIds: existing?.runnerIds ?? [],
            createdAt: existing?.createdAt ?? now,
            lastUpdatedAt: now,
        });
        this.pools.set(pool.id, pool);
        return pool;
    }
    getPool(poolId) {
        return this.pools.get(poolId) ?? null;
    }
    listPools() {
        return [...this.pools.values()].sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
    }
    assignRunnerToPool(poolId, runnerId) {
        const contract = this.contracts.get(runnerId);
        const pool = this.pools.get(poolId) ?? this.createPool({ id: poolId, label: poolId });
        if (pool.maxRunners !== undefined && !pool.runnerIds.includes(runnerId) && pool.runnerIds.length >= pool.maxRunners) {
            return null;
        }
        const nextRunnerIds = pool.runnerIds.includes(runnerId)
            ? pool.runnerIds
            : [...pool.runnerIds, runnerId];
        const nextPool = Object.freeze({
            ...pool,
            runnerIds: nextRunnerIds,
            lastUpdatedAt: Date.now(),
        });
        this.pools.set(poolId, nextPool);
        if (contract && contract.poolId !== poolId) {
            this.contracts.set(runnerId, Object.freeze({ ...contract, poolId, lastUpdatedAt: Date.now() }));
        }
        return nextPool;
    }
    removeRunnerFromPool(poolId, runnerId) {
        const pool = this.pools.get(poolId);
        if (!pool)
            return null;
        const nextPool = Object.freeze({
            ...pool,
            runnerIds: pool.runnerIds.filter((id) => id !== runnerId),
            lastUpdatedAt: Date.now(),
        });
        this.pools.set(poolId, nextPool);
        const contract = this.contracts.get(runnerId);
        if (contract?.poolId === poolId) {
            this.contracts.set(runnerId, Object.freeze({ ...contract, poolId: undefined, lastUpdatedAt: Date.now() }));
        }
        return nextPool;
    }
    captureArtifactForAgent(agentId, store) {
        const agent = this.agentManager.getStatus(agentId);
        if (!agent)
            return null;
        const connection = this.resolveConnection(agentId, store);
        const contract = this.upsertContractForAgent(agentId, store) ?? buildContract(agent, connection);
        const artifact = buildArtifact(agent, contract, connection);
        this.artifacts.set(artifact.id, artifact);
        return artifact;
    }
    captureArtifactForRunner(runnerId, store) {
        const byAgent = this.captureArtifactForAgent(runnerId, store);
        if (byAgent)
            return byAgent;
        const contract = this.contracts.get(runnerId);
        if (!contract)
            return null;
        const artifact = buildArtifactFromStore(contract, store);
        this.artifacts.set(artifact.id, artifact);
        return artifact;
    }
    getArtifact(artifactId) {
        return this.artifacts.get(artifactId) ?? null;
    }
    listArtifacts() {
        return [...this.artifacts.values()].sort((a, b) => b.createdAt - a.createdAt);
    }
    async exportArtifact(artifactId, explicitPath) {
        const artifact = this.getArtifact(artifactId);
        if (!artifact)
            return null;
        const path = explicitPath
            ? resolve(explicitPath)
            : resolve(DEFAULT_ARTIFACT_DIR, `${artifact.id.replace(/[:]/g, '_')}.json`);
        mkdirSync(dirname(path), { recursive: true });
        await writeFile(path, `${JSON.stringify(artifact, null, 2)}\n`, 'utf-8');
        return { artifact, path };
    }
    async importArtifact(path) {
        const raw = await readFile(resolve(path), 'utf-8');
        const parsed = JSON.parse(raw);
        this.artifacts.set(parsed.id, parsed);
        this.contracts.set(parsed.runnerId, parsed.runnerContract);
        return parsed;
    }
    buildSessionBundle(store) {
        this.ensureContractsFromStore(store);
        const activeConnectionIds = store?.getState().acp.activeConnectionIds ?? [];
        return Object.freeze({
            version: 1,
            exportedAt: Date.now(),
            sessionId: store?.getState().session.id || 'unknown-session',
            activeConnectionIds: Object.freeze([...activeConnectionIds]),
            pools: Object.freeze(this.listPools()),
            contracts: Object.freeze(this.listContracts()),
            artifacts: Object.freeze(this.listArtifacts()),
        });
    }
    async exportSessionBundle(store, explicitPath) {
        const bundle = this.buildSessionBundle(store);
        const path = explicitPath
            ? resolve(explicitPath)
            : resolve(DEFAULT_SESSION_BUNDLE_DIR, `${bundle.sessionId}-${bundle.exportedAt}.json`);
        mkdirSync(dirname(path), { recursive: true });
        await writeFile(path, `${JSON.stringify(bundle, null, 2)}\n`, 'utf-8');
        return { bundle, path };
    }
    async importSessionBundle(path) {
        const raw = await readFile(resolve(path), 'utf-8');
        const parsed = JSON.parse(raw);
        for (const pool of parsed.pools) {
            this.pools.set(pool.id, pool);
        }
        for (const contract of parsed.contracts) {
            this.contracts.set(contract.runnerId, contract);
        }
        for (const artifact of parsed.artifacts) {
            this.artifacts.set(artifact.id, artifact);
        }
        return parsed;
    }
    buildReviewSummary(artifactId) {
        const artifact = this.getArtifact(artifactId);
        if (!artifact)
            return null;
        const lines = [
            `Remote Artifact ${artifact.id}`,
            `  runner: ${artifact.runnerId}`,
            `  template: ${artifact.runnerContract.template}`,
            `  status: ${artifact.task.status}`,
            `  summary: ${artifact.task.summary}`,
            `  tools: ${artifact.runnerContract.capabilityCeiling.allowedTools.join(', ') || '(none)'}`,
            `  evidence: toolCalls=${artifact.evidence.toolCallCount} messages=${artifact.evidence.messageCount} errors=${artifact.evidence.errorCount}`,
        ];
        if (artifact.task.error)
            lines.push(`  error: ${artifact.task.error}`);
        if (artifact.knowledgeInjections.length > 0) {
            lines.push(`  knowledge: ${artifact.knowledgeInjections.map((entry) => entry.id).join(', ')}`);
        }
        return lines.join('\n');
    }
    ensureContractsFromStore(store) {
        for (const agent of this.agentManager.list()) {
            if (agent.orchestrationGraphId || agent.parentAgentId || store?.getState().acp.connections.has(agent.id)) {
                this.upsertContractForAgent(agent.id, store);
            }
        }
    }
    clear() {
        this.contracts.clear();
        this.artifacts.clear();
        this.pools.clear();
    }
}
export async function exportRemoteArtifactForAgent(registry, agentId, store, path) {
    const artifact = registry.captureArtifactForAgent(agentId, store);
    if (!artifact)
        return null;
    return registry.exportArtifact(artifact.id, path);
}
export async function importRemoteArtifact(registry, path) {
    try {
        return await registry.importArtifact(path);
    }
    catch (error) {
        logger.debug('RemoteRunnerRegistry.importArtifact failed', { path, error: summarizeError(error) });
        throw error;
    }
}
