/**
 * A2A (Agent-to-Agent) protocol transport implementation.
 *
 * Client side: sends JSON-RPC 2.0 to external A2A agent endpoints.
 * Server side: runs a local HTTP server accepting A2A JSON-RPC calls.
 */
import { randomUUID } from 'node:crypto';
import * as http from 'node:http';
import { A2A_TASK_NOT_CANCELABLE, A2A_TASK_NOT_FOUND, JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, a2aAgentCardFromDict, a2aAgentCardToDict, a2aMessageFromDict, a2aMessageGetText, a2aMessageToDict, a2aTaskToDict, createA2AAgentCard, createA2ATaskStatus, makeJsonRpcError, makeJsonRpcRequest, makeJsonRpcResponse, } from './a2a-types.js';
export class A2AError extends Error {
    code;
    constructor(code, message) {
        super(`A2A error ${code}: ${message}`);
        this.name = 'A2AError';
        this.code = code;
    }
}
export class A2ATransport {
    config;
    agentName;
    agentCard;
    tasks = new Map();
    _messageCallbacks = [];
    _server;
    _discoveredCards = new Map();
    _closing = false;
    constructor(config) {
        this.config = config;
    }
    // === Transport interface ===
    async register(name) {
        this.agentName = name;
        const host = this.config.serverHost ?? '0.0.0.0';
        const port = this.config.serverPort ?? 5000;
        this.agentCard = createA2AAgentCard(name, this.config.agentDescription ?? `Agent Relay agent: ${name}`, `http://${host}:{PORT}`, this.config.skills ?? []);
        return new Promise((resolve, reject) => {
            const server = http.createServer((req, res) => {
                void this._handleRequest(req, res);
            });
            server.on('error', reject);
            server.listen(port, host, () => {
                this._server = server;
                const addr = server.address();
                const actualPort = typeof addr === 'object' && addr ? addr.port : port;
                this.agentCard.url = `http://${host}:${actualPort}`;
                resolve({ name, url: this.agentCard.url, type: 'a2a' });
            });
        });
    }
    async unregister() {
        this._closing = true;
        if (this._server) {
            await new Promise((resolve, reject) => {
                this._server.close((err) => (err ? reject(err) : resolve()));
            });
            this._server = undefined;
        }
        this._closing = false;
    }
    async sendDm(target, text) {
        const card = await this._discoverAgent(target);
        const message = {
            role: 'user',
            parts: [{ text }],
            messageId: randomUUID(),
        };
        const rpcRequest = makeJsonRpcRequest('message/send', {
            message: a2aMessageToDict(message),
        });
        const headers = {
            'Content-Type': 'application/json',
            ...this._authHeaders(),
        };
        const response = await fetch(card.url, {
            method: 'POST',
            headers,
            body: JSON.stringify(rpcRequest),
        });
        const body = (await response.json());
        if (body.error) {
            throw new A2AError(body.error.code, body.error.message);
        }
        const result = (body.result ?? {});
        return this._a2aResultToRelay(result, card.name);
    }
    async listAgents() {
        const agents = [];
        for (const url of this.config.registry ?? []) {
            try {
                const card = await this._discoverAgent(url);
                agents.push({
                    name: card.name,
                    url: card.url,
                    description: card.description,
                    skills: card.skills.map((s) => ({ id: s.id, name: s.name, description: s.description })),
                });
            }
            catch {
                continue;
            }
        }
        return agents;
    }
    onMessage(callback) {
        this._messageCallbacks.push(callback);
    }
    async connectWs() {
        // A2A uses HTTP, not WebSocket. No-op.
    }
    // === HTTP request handler ===
    async _handleRequest(req, res) {
        if (req.method === 'GET' && req.url === '/.well-known/agent.json') {
            await this._handleAgentCard(res);
            return;
        }
        if (req.method === 'POST' && (req.url === '/' || req.url === '')) {
            await this._handleJsonRpcHttp(req, res);
            return;
        }
        res.writeHead(404, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Not found' }));
    }
    async _handleAgentCard(res) {
        if (!this.agentCard) {
            res.writeHead(503, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: 'Not registered' }));
            return;
        }
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(a2aAgentCardToDict(this.agentCard)));
    }
    async _handleJsonRpcHttp(req, res) {
        let body;
        try {
            const raw = await this._readBody(req);
            body = JSON.parse(raw);
        }
        catch {
            const error = makeJsonRpcError(JSONRPC_PARSE_ERROR, 'Parse error', null);
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(error));
            return;
        }
        const result = await this._dispatchJsonRpc(body);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(result));
    }
    _readBody(req) {
        return new Promise((resolve, reject) => {
            const chunks = [];
            req.on('data', (chunk) => chunks.push(chunk));
            req.on('end', () => resolve(Buffer.concat(chunks).toString()));
            req.on('error', reject);
        });
    }
    // === JSON-RPC dispatch ===
    async _dispatchJsonRpc(request) {
        const rpcId = (request.id ?? null);
        const method = (request.method ?? '');
        const params = (request.params ?? {});
        const handlers = {
            'message/send': (p) => this._handleMessageSend(p),
            'tasks/get': (p) => this._handleTasksGet(p),
            'tasks/cancel': (p) => this._handleTasksCancel(p),
        };
        const handler = handlers[method];
        if (!handler) {
            return makeJsonRpcError(JSONRPC_METHOD_NOT_FOUND, `Method not found: ${method}`, rpcId);
        }
        try {
            const result = await handler(params);
            return makeJsonRpcResponse(result, rpcId);
        }
        catch (err) {
            if (err instanceof A2AError) {
                return makeJsonRpcError(err.code, err.message, rpcId);
            }
            return makeJsonRpcError(JSONRPC_INTERNAL_ERROR, String(err), rpcId);
        }
    }
    async _handleMessageSend(params) {
        const msgData = params.message;
        if (!msgData) {
            throw new A2AError(JSONRPC_INVALID_PARAMS, "Missing 'message' in params");
        }
        const a2aMsg = a2aMessageFromDict(msgData);
        const taskId = a2aMsg.taskId ?? randomUUID();
        const contextId = a2aMsg.contextId ?? randomUUID();
        let task;
        if (this.tasks.has(taskId)) {
            task = this.tasks.get(taskId);
            task.messages.push(a2aMsg);
            task.status = createA2ATaskStatus('working');
        }
        else {
            task = {
                id: taskId,
                contextId,
                status: createA2ATaskStatus('working'),
                messages: [a2aMsg],
                artifacts: [],
            };
            this.tasks.set(taskId, task);
        }
        // Convert to Relay message and invoke callbacks
        const relayMsg = A2ATransport._a2aToRelayMsg(a2aMsg, 'a2a-client');
        await this._invokeCallbacks(relayMsg);
        // Mark completed
        task.status = createA2ATaskStatus('completed');
        return a2aTaskToDict(task);
    }
    async _handleTasksGet(params) {
        const taskId = params.id ?? '';
        if (!taskId || !this.tasks.has(taskId)) {
            throw new A2AError(A2A_TASK_NOT_FOUND, `Task not found: ${taskId}`);
        }
        return a2aTaskToDict(this.tasks.get(taskId));
    }
    async _handleTasksCancel(params) {
        const taskId = params.id ?? '';
        if (!taskId || !this.tasks.has(taskId)) {
            throw new A2AError(A2A_TASK_NOT_FOUND, `Task not found: ${taskId}`);
        }
        const task = this.tasks.get(taskId);
        if (['completed', 'failed', 'canceled'].includes(task.status.state)) {
            throw new A2AError(A2A_TASK_NOT_CANCELABLE, `Task ${taskId} is already ${task.status.state}`);
        }
        task.status = createA2ATaskStatus('canceled');
        return a2aTaskToDict(task);
    }
    // === Agent discovery ===
    async _discoverAgent(url) {
        const normalizedUrl = url.replace(/\/+$/, '');
        if (this._discoveredCards.has(normalizedUrl)) {
            return this._discoveredCards.get(normalizedUrl);
        }
        const cardUrl = `${normalizedUrl}/.well-known/agent.json`;
        const response = await fetch(cardUrl);
        if (!response.ok) {
            throw new A2AError(-1, `Failed to discover agent at ${cardUrl}: HTTP ${response.status}`);
        }
        const data = (await response.json());
        const card = a2aAgentCardFromDict(data);
        if (!card.url) {
            card.url = normalizedUrl;
        }
        this._discoveredCards.set(normalizedUrl, card);
        return card;
    }
    // === Message conversion ===
    static _relayMsgToA2A(text, _sender) {
        return {
            role: 'user',
            parts: [{ text }],
            messageId: randomUUID(),
        };
    }
    static _a2aToRelayMsg(msg, sender = 'unknown') {
        const text = a2aMessageGetText(msg);
        return {
            sender,
            text,
            channel: undefined,
            threadId: msg.contextId,
            messageId: msg.messageId,
        };
    }
    _a2aResultToRelay(result, sender) {
        const messages = result.messages ?? [];
        let text = '';
        if (messages.length > 0) {
            const lastMsg = messages[messages.length - 1];
            const parts = lastMsg.parts ?? [];
            text = parts
                .filter((p) => p.text)
                .map((p) => p.text)
                .join(' ');
        }
        return {
            sender,
            text,
            task_id: result.id,
            status: result.status?.state,
        };
    }
    // === Internal helpers ===
    async _invokeCallbacks(msg) {
        for (const cb of this._messageCallbacks) {
            await cb(msg);
        }
    }
    _authHeaders() {
        const headers = {};
        if (this.config.authToken) {
            if (this.config.authScheme === 'api_key') {
                headers['X-API-Key'] = this.config.authToken;
            }
            else {
                headers['Authorization'] = `Bearer ${this.config.authToken}`;
            }
        }
        return headers;
    }
}
//# sourceMappingURL=a2a-transport.js.map