// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { PassThrough } from 'node:stream';
import { createError, ErrorCodes } from '@lumenflow/core/error-handler';
import { CONFIG_FILES } from '@lumenflow/core/wu-constants';
import { parseYAML } from '@lumenflow/core/wu-yaml';
const HTTP_METHOD_POST = 'POST';
const HTTP_HEADER = {
    AUTHORIZATION: 'authorization',
    CONTENT_TYPE: 'content-type',
};
const CONTENT_TYPE_JSON = 'application/json';
const CONTROL_PLANE_HEARTBEAT_PATH = '/api/v1/heartbeat';
const DEFAULT_MAX_QUEUED_COMMANDS = 25;
class CapturedResponse {
    statusCode = 200;
    body = '';
    setHeader(_name, _value) {
        return this;
    }
    write(chunk) {
        this.body += Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk;
        return true;
    }
    end(chunk) {
        if (chunk !== undefined) {
            this.write(chunk);
        }
        return this;
    }
    toResult() {
        try {
            return {
                statusCode: this.statusCode,
                body: this.body.length > 0 ? JSON.parse(this.body) : {},
            };
        }
        catch {
            return {
                statusCode: this.statusCode,
                body: this.body,
            };
        }
    }
}
function asNonEmptyString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
}
function asFiniteNumber(value) {
    return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
function normalizeEndpoint(endpoint) {
    return endpoint.endsWith('/') ? endpoint.slice(0, endpoint.length - 1) : endpoint;
}
function isExecutionContext(value) {
    return typeof value === 'object' && value !== null;
}
function resolveControlPlaneConfig(workspaceRoot, environment) {
    const workspaceConfigPath = join(workspaceRoot, CONFIG_FILES.WORKSPACE_CONFIG);
    if (!existsSync(workspaceConfigPath)) {
        return null;
    }
    let parsedWorkspace;
    try {
        parsedWorkspace = parseYAML(readFileSync(workspaceConfigPath, { encoding: 'utf-8' }));
    }
    catch {
        return null;
    }
    const workspaceId = asNonEmptyString(parsedWorkspace?.id);
    const endpoint = asNonEmptyString(parsedWorkspace?.control_plane?.endpoint);
    const tokenEnv = asNonEmptyString(parsedWorkspace?.control_plane?.auth?.token_env);
    if (!workspaceId || !endpoint || !tokenEnv) {
        return null;
    }
    const token = asNonEmptyString(environment[tokenEnv]);
    if (!token) {
        return null;
    }
    try {
        void new URL(endpoint);
    }
    catch {
        return null;
    }
    return {
        workspaceId,
        endpoint: normalizeEndpoint(endpoint),
        token,
    };
}
function normalizeQueuedCommand(value) {
    if (typeof value !== 'object' || value === null) {
        return null;
    }
    const command = value;
    const commandId = asNonEmptyString(command.command_id);
    const toolName = asNonEmptyString(command.tool_name);
    if (!commandId || !toolName || !isExecutionContext(command.context)) {
        return null;
    }
    return {
        command_id: commandId,
        tool_name: toolName,
        input: command.input,
        context: command.context,
        enqueued_at: asNonEmptyString(command.enqueued_at) ?? undefined,
    };
}
export function normalizeHeartbeatPollResponse(raw) {
    const payload = typeof raw === 'object' && raw !== null ? raw : {};
    const queuedCommands = Array.isArray(payload.queued_commands)
        ? payload.queued_commands
            .map((entry) => normalizeQueuedCommand(entry))
            .filter((entry) => entry !== null)
        : [];
    return {
        status: 'ok',
        server_time: asNonEmptyString(payload.server_time) ?? new Date().toISOString(),
        ...(asFiniteNumber(payload.next_heartbeat_ms) !== undefined
            ? { next_heartbeat_ms: asFiniteNumber(payload.next_heartbeat_ms) }
            : {}),
        ...(typeof payload.assignment === 'object' && payload.assignment !== null
            ? {
                assignment: payload.assignment,
            }
            : {}),
        ...(asFiniteNumber(payload.budget_remaining_usd) !== undefined
            ? { budget_remaining_usd: asFiniteNumber(payload.budget_remaining_usd) }
            : {}),
        ...(asFiniteNumber(payload.coalesced_signals) !== undefined
            ? { coalesced_signals: asFiniteNumber(payload.coalesced_signals) }
            : {}),
        queued_commands: queuedCommands,
    };
}
async function dispatchQueuedCommandThroughHandleRequest(surface, command) {
    if (typeof surface.handleRequest !== 'function') {
        return {
            command_id: command.command_id,
            tool_name: command.tool_name,
            ok: false,
            status_code: 500,
            body: {
                error: {
                    message: 'Queued command dispatch requires a local surface with handleRequest or dispatchQueuedToolCommand.',
                },
            },
        };
    }
    const request = new PassThrough();
    request.method = HTTP_METHOD_POST;
    request.url = `/tools/${encodeURIComponent(command.tool_name)}`;
    request.headers = {
        [HTTP_HEADER.CONTENT_TYPE]: `${CONTENT_TYPE_JSON}; charset=utf-8`,
    };
    request.end(JSON.stringify({
        input: command.input ?? {},
        context: command.context,
    }));
    const response = new CapturedResponse();
    await surface.handleRequest(request, response);
    const dispatchResult = response.toResult();
    return {
        command_id: command.command_id,
        tool_name: command.tool_name,
        ok: dispatchResult.statusCode >= 200 && dispatchResult.statusCode < 300,
        status_code: dispatchResult.statusCode,
        body: dispatchResult.body,
    };
}
export async function dispatchQueuedAgentCommands(commands, surface) {
    const results = [];
    for (const command of commands) {
        try {
            if (typeof surface.dispatchQueuedToolCommand === 'function') {
                const dispatchResult = await surface.dispatchQueuedToolCommand({
                    tool_name: command.tool_name,
                    input: command.input,
                    context: command.context,
                });
                results.push({
                    command_id: command.command_id,
                    tool_name: command.tool_name,
                    ok: dispatchResult.statusCode >= 200 && dispatchResult.statusCode < 300,
                    status_code: dispatchResult.statusCode,
                    body: dispatchResult.body,
                });
                continue;
            }
            results.push(await dispatchQueuedCommandThroughHandleRequest(surface, command));
        }
        catch (error) {
            results.push({
                command_id: command.command_id,
                tool_name: command.tool_name,
                ok: false,
                status_code: 500,
                body: {
                    error: {
                        message: error instanceof Error
                            ? error.message
                            : 'Queued command dispatch failed with an unknown error.',
                    },
                },
            });
        }
    }
    return results;
}
export async function pollAgentSessionHeartbeat(options) {
    const { sessionId, localSurface, workspaceId, endpoint, token, workspaceRoot = process.cwd(), environment = process.env, agentId, wuId, health, maxQueuedCommands = DEFAULT_MAX_QUEUED_COMMANDS, fetchFn = fetch, } = options;
    const resolvedControlPlaneConfig = workspaceId && endpoint && token
        ? {
            workspaceId,
            endpoint: normalizeEndpoint(endpoint),
            token,
        }
        : resolveControlPlaneConfig(workspaceRoot, environment);
    if (!resolvedControlPlaneConfig) {
        throw createError(ErrorCodes.CONFIG_ERROR, 'Unable to poll agent heartbeat because control-plane workspace config or auth token is missing.');
    }
    const response = await fetchFn(`${resolvedControlPlaneConfig.endpoint}${CONTROL_PLANE_HEARTBEAT_PATH}`, {
        method: HTTP_METHOD_POST,
        headers: {
            [HTTP_HEADER.AUTHORIZATION]: `Bearer ${resolvedControlPlaneConfig.token}`,
            [HTTP_HEADER.CONTENT_TYPE]: CONTENT_TYPE_JSON,
        },
        body: JSON.stringify({
            workspace_id: resolvedControlPlaneConfig.workspaceId,
            session_id: sessionId,
            ...(agentId ? { agent_id: agentId } : {}),
            ...(wuId ? { wu_id: wuId } : {}),
            ...(health ? { health } : {}),
        }),
    });
    if (!response.ok) {
        throw createError(ErrorCodes.COMMAND_EXECUTION_FAILED, `Agent heartbeat polling failed: HTTP ${response.status}.`, { status: response.status });
    }
    const heartbeat = normalizeHeartbeatPollResponse(await response.json().catch(() => ({})));
    const queuedCommands = heartbeat.queued_commands.slice(0, Math.max(0, maxQueuedCommands));
    const dispatchResults = await dispatchQueuedAgentCommands(queuedCommands, localSurface);
    return {
        heartbeat,
        dispatch_results: dispatchResults,
    };
}
//# sourceMappingURL=agent-session-heartbeat.js.map