import { getOperatorContract, getPeerContract } from '@pellux/goodvibes-contracts';
import { createClientTransport } from './client-transport.js';
import { createHttpJsonTransport } from './http-json-transport.js';
import { appendTelemetryQuery, buildSessionEnsureBody, buildSessionMessageBody, buildSteerSessionMessageBody, buildTaskSubmitBody, buildTransportUrl, connectTelemetryStream, createJsonRequestInit, normalizeTelemetryQuery, readControlPlaneSnapshot, } from './http-helpers.js';
import { createOperatorRemoteClient } from './operator-remote-client.js';
import { createPeerRemoteClient } from './peer-remote-client.js';
import { createEventSourceConnector, requestJsonRaw, } from './shared.js';
import { createRemoteUiRuntimeEvents } from './ui-runtime-events.js';
import { assertObjectField, assertObjectOrNullField, assertArrayField, assertRuntimeTaskArray, assertSharedApprovalArray, assertProviderRuntimeSnapshotArray, assertProviderRuntimeSnapshot, assertProviderUsageSnapshot, assertTelemetrySnapshot, assertTelemetryListResponse, assertTelemetryMetricsSnapshot, normalizeSharedSessionRecord, normalizeSharedSessionMessage, normalizeSharedSessionInput, normalizeSharedSessionSubmission, normalizeTelemetryQueryForSdk, } from './daemon-http-client-validators.js';
function isNotFoundError(error) {
    return Boolean(typeof error === 'object'
        && error !== null
        && 'transport' in error
        && typeof error.transport?.status === 'number'
        && error.transport?.status === 404);
}
async function withNullOnNotFound(run) {
    try {
        return await run();
    }
    catch (error) {
        if (isNotFoundError(error))
            return null;
        throw error;
    }
}
function asContractInput(input) {
    return input;
}
function createOperatorClient(transport, events) {
    const paths = transport.paths;
    const token = transport.authToken ?? null;
    const fetchImpl = transport.fetchImpl;
    const operatorApi = createOperatorRemoteClient(transport, getOperatorContract());
    return {
        sessions: {
            current: async () => await requestJsonRaw(fetchImpl, buildTransportUrl(paths.controlPlaneUrl, '/api/session'), createJsonRequestInit(token)),
            list: async (limit = 100) => (await operatorApi.sessions.list({ limit })).sessions.map((entry) => normalizeSharedSessionRecord(entry)),
            get: async (sessionId) => {
                const response = await withNullOnNotFound(() => operatorApi.sessions.get(sessionId));
                return response?.session ? normalizeSharedSessionRecord(response.session) : null;
            },
            messages: async (sessionId, limit = 100) => (await operatorApi.sessions.messages.list(sessionId, { limit })).messages.map((entry) => normalizeSharedSessionMessage(entry)),
            inputs: async (sessionId, limit = 100) => {
                const response = await operatorApi.invoke('sessions.inputs.list', { sessionId, limit });
                return (response.inputs ?? []).map((entry) => normalizeSharedSessionInput(entry));
            },
            ensureSession: async (input = {}) => normalizeSharedSessionRecord(assertObjectField(await operatorApi.sessions.create(buildSessionEnsureBody(input)), 'session', 'sessions.create')),
            close: async (sessionId) => {
                const response = await withNullOnNotFound(() => operatorApi.sessions.close(sessionId));
                return response?.session ? normalizeSharedSessionRecord(response.session) : null;
            },
            reopen: async (sessionId) => {
                const response = await withNullOnNotFound(() => operatorApi.sessions.reopen(sessionId));
                return response?.session ? normalizeSharedSessionRecord(response.session) : null;
            },
            submitMessage: async (sessionId, input) => normalizeSharedSessionSubmission(await operatorApi.invoke('sessions.messages.create', { sessionId, ...buildSessionMessageBody(input) })),
            steerMessage: async (sessionId, input) => normalizeSharedSessionSubmission(await operatorApi.invoke('sessions.steer', { sessionId, ...buildSteerSessionMessageBody(input) })),
            followUpMessage: async (sessionId, input) => normalizeSharedSessionSubmission(await operatorApi.invoke('sessions.followUp', { sessionId, ...buildSessionMessageBody(input) })),
            cancelInput: async (sessionId, inputId) => {
                const response = await withNullOnNotFound(() => operatorApi.sessions.inputs.cancel(sessionId, inputId));
                return response?.input ? normalizeSharedSessionInput(response.input) : null;
            },
        },
        tasks: {
            snapshot: async () => {
                const { tasks } = await operatorApi.tasks.list();
                return { tasks: assertRuntimeTaskArray(tasks, 'tasks.list') };
            },
            list: async (limit = 100) => assertRuntimeTaskArray((await operatorApi.tasks.list({ limit })).tasks, 'tasks.list'),
            get: async (taskId) => {
                const response = await withNullOnNotFound(() => operatorApi.tasks.get(taskId));
                if (response?.task == null)
                    return null;
                return assertRuntimeTaskArray([response.task], 'tasks.get')[0] ?? null;
            },
            running: async () => assertRuntimeTaskArray((await operatorApi.tasks.list()).tasks, 'tasks.list').filter((task) => task.status === 'running'),
            submit: async (input) => await operatorApi.invoke('tasks.create', buildTaskSubmitBody(input)),
            cancel: async (taskId) => await operatorApi.invoke('tasks.cancel', { taskId }),
            retry: async (taskId) => await operatorApi.invoke('tasks.retry', { taskId }),
        },
        approvals: {
            list: async (limit = 100) => assertSharedApprovalArray((await operatorApi.approvals.list({ limit })).approvals, 'approvals.list'),
            get: async (approvalId) => {
                const approvals = await operatorApi.approvals.list({ limit: 200 });
                return (assertSharedApprovalArray(approvals.approvals, 'approvals.list').find((entry) => entry.id === approvalId) ?? null);
            },
            claim: async (approvalId, actor, actorSurface = 'transport', note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('approvals.claim', { approvalId, actor, actorSurface, ...(note ? { note } : {}) }));
                return response?.approval ?? null;
            },
            approve: async (approvalId, actor, actorSurface = 'transport', note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('approvals.approve', { approvalId, actor, actorSurface, ...(note ? { note } : {}) }));
                return response?.approval ?? null;
            },
            deny: async (approvalId, actor, actorSurface = 'transport', note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('approvals.deny', { approvalId, actor, actorSurface, ...(note ? { note } : {}) }));
                return response?.approval ?? null;
            },
            cancel: async (approvalId, actor, actorSurface = 'transport', note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('approvals.cancel', { approvalId, actor, actorSurface, ...(note ? { note } : {}) }));
                return response?.approval ?? null;
            },
        },
        providers: {
            listIds: async () => assertProviderRuntimeSnapshotArray((await operatorApi.providers.list()).providers, 'providers.list')
                .map((provider) => provider.providerId),
            runtimeSnapshots: async () => assertProviderRuntimeSnapshotArray((await operatorApi.providers.list()).providers, 'providers.list'),
            runtimeSnapshot: async (providerId) => {
                const response = await withNullOnNotFound(() => operatorApi.providers.get(providerId));
                if (response == null)
                    return null;
                return assertProviderRuntimeSnapshot(response, 'providers.get');
            },
            usageSnapshot: async (providerId) => {
                const response = await withNullOnNotFound(() => operatorApi.providers.usage(providerId));
                if (response == null)
                    return null;
                return assertProviderUsageSnapshot(response, 'providers.usage');
            },
            accountSnapshot: async () => await operatorApi.accounts.snapshot(),
            localAuthSnapshot: async () => await operatorApi.localAuth.status(),
            snapshot: async () => {
                const [providerResponse, accountSnapshot, localAuthSnapshot] = await Promise.all([
                    operatorApi.providers.list(),
                    operatorApi.accounts.snapshot(),
                    operatorApi.localAuth.status(),
                ]);
                const runtimeSnapshots = assertProviderRuntimeSnapshotArray(providerResponse.providers, 'providers.list');
                return {
                    providerIds: runtimeSnapshots.map((provider) => provider.providerId),
                    runtimeSnapshots,
                    accountSnapshot,
                    localAuthSnapshot: localAuthSnapshot,
                };
            },
        },
        controlPlane: {
            snapshot: async () => await readControlPlaneSnapshot(fetchImpl, paths, token),
            currentAuth: async () => await operatorApi.control.auth.current(),
            recentEvents: async (limit = 6) => (await operatorApi.invoke('control.messages.list', { limit })).messages ?? [],
        },
        telemetry: {
            snapshot: async (query = 20) => assertTelemetrySnapshot(await operatorApi.telemetry.snapshot(normalizeTelemetryQueryForSdk(query, 20)), 'telemetry.snapshot'),
            events: async (query = 100) => assertTelemetryListResponse(await operatorApi.telemetry.events(normalizeTelemetryQueryForSdk(query, 100)), 'telemetry.events'),
            errors: async (query = 100) => assertTelemetryListResponse(await operatorApi.telemetry.errors(normalizeTelemetryQueryForSdk(query, 100)), 'telemetry.errors'),
            traces: async (query = 100) => assertTelemetryListResponse(await operatorApi.telemetry.traces(normalizeTelemetryQueryForSdk(query, 100)), 'telemetry.traces'),
            metrics: async (query = 100) => assertTelemetryMetricsSnapshot(await operatorApi.telemetry.metrics(normalizeTelemetryQueryForSdk(query, 100)), 'telemetry.metrics'),
            otlpTraces: async (query = 100) => await operatorApi.telemetry.otlp.traces(normalizeTelemetryQueryForSdk(query, 100)),
            otlpLogs: async (query = 100) => await operatorApi.telemetry.otlp.logs(normalizeTelemetryQueryForSdk(query, 100)),
            otlpMetrics: async (query = 100) => await operatorApi.telemetry.otlp.metrics(normalizeTelemetryQueryForSdk(query, 100)),
            stream: async (handlers, query = 100) => {
                const normalized = normalizeTelemetryQuery(query, 100);
                const url = new URL(paths.telemetryStreamUrl);
                appendTelemetryQuery(url, normalized);
                return await connectTelemetryStream(fetchImpl, url.toString(), token, handlers);
            },
        },
        events,
        shellPaths: paths,
    };
}
function createPeerClient(transport) {
    const operatorApi = createOperatorRemoteClient(transport, getOperatorContract());
    const peerApi = createPeerRemoteClient(transport, getPeerContract());
    function createPeerTokenClient(tokenValue) {
        return createPeerRemoteClient(createHttpJsonTransport({
            baseUrl: transport.baseUrl,
            authToken: tokenValue,
            fetchImpl: transport.fetchImpl,
        }), getPeerContract());
    }
    async function readRemoteSnapshot() {
        return await operatorApi.invoke('remote.snapshot');
    }
    async function readNodeHostContractValue() {
        return (await operatorApi.invoke('remote.node_host.contract')).contract;
    }
    async function readPeers(limit = 500) {
        return (await operatorApi.invoke('remote.peers.list', { limit })).peers ?? [];
    }
    return {
        pairing: {
            listRequests: async (limit = 100) => (await operatorApi.invoke('remote.pair.requests.list', { limit })).requests ?? [],
            request: async (input) => await peerApi.invoke('pair.request', asContractInput(input)),
            approve: async (requestId, actor, note) => await withNullOnNotFound(() => operatorApi.invoke('remote.pair.requests.approve', { requestId, actor, ...(note ? { note } : {}) })),
            reject: async (requestId, actor, note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('remote.pair.requests.reject', { requestId, actor, ...(note ? { note } : {}) }));
                return response?.request ?? null;
            },
            verify: async (requestId, challenge, remoteAddress) => await withNullOnNotFound(() => peerApi.invoke('pair.verify', { requestId, challenge, ...(remoteAddress ? { remoteAddress } : {}) })),
        },
        peers: {
            list: async (kind, limit = 200) => (await operatorApi.invoke('remote.peers.list', {
                ...(kind ? { kind } : {}),
                limit,
            })).peers ?? [],
            get: async (peerId) => {
                const response = await operatorApi.invoke('remote.peers.list', { limit: 500 });
                return (response.peers ?? []).find((peer) => peer.id === peerId) ?? null;
            },
            getSnapshot: async (peerId) => {
                const [snapshot, contract, peers] = await Promise.all([
                    readRemoteSnapshot(),
                    readNodeHostContractValue(),
                    readPeers(),
                ]);
                const pairRequests = (snapshot.distributed?.pairRequests ?? []).filter((entry) => entry.peerId === peerId || entry.requestedId === peerId);
                const work = (snapshot.distributed?.work ?? []).filter((entry) => entry.peerId === peerId);
                const peer = peers.find((entry) => entry.id === peerId) ?? null;
                if (!peer && pairRequests.length === 0 && work.length === 0) {
                    return null;
                }
                return {
                    peerId,
                    peer,
                    pairRequests,
                    work,
                    nodeHostContract: contract,
                };
            },
            heartbeat: async (tokenValue, input = {}) => await createPeerTokenClient(tokenValue).invoke('peer.heartbeat', asContractInput(input)),
            rotateToken: async (peerId, actor, label, scopes) => await withNullOnNotFound(() => operatorApi.invoke('remote.peers.token.rotate', {
                peerId,
                actor,
                ...(label ? { label } : {}),
                ...(scopes ? { scopes } : {}),
            })),
            revokeToken: async (peerId, actor, tokenId, note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('remote.peers.token.revoke', {
                    peerId,
                    actor,
                    ...(tokenId ? { tokenId } : {}),
                    ...(note ? { note } : {}),
                }));
                return response?.peer ?? null;
            },
            disconnect: async (peerId, actor, note, requeueClaimedWork = true) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('remote.peers.disconnect', {
                    peerId,
                    actor,
                    ...(note ? { note } : {}),
                    requeueClaimedWork,
                }));
                return response?.peer ?? null;
            },
        },
        work: {
            list: async (limit = 200, peerId) => (await operatorApi.invoke('remote.work.list', {
                ...(peerId ? { peerId } : {}),
                limit,
            })).work ?? [],
            invoke: async (input) => await operatorApi.invoke('remote.peers.invoke', asContractInput(input)),
            claim: async (tokenValue, input = {}) => (await createPeerTokenClient(tokenValue).invoke('work.pull', asContractInput(input))).work ?? [],
            complete: async (tokenValue, workId, input = {}) => {
                const response = await withNullOnNotFound(() => createPeerTokenClient(tokenValue).invoke('work.complete', { workId, ...input }));
                return response?.work ?? null;
            },
            cancel: async (workId, actor, note) => {
                const response = await withNullOnNotFound(() => operatorApi.invoke('remote.work.cancel', {
                    workId,
                    actor,
                    ...(note ? { reason: note } : {}),
                }));
                return response?.work ?? null;
            },
        },
        getSnapshot: async () => {
            const [remoteSnapshot, contract, peers] = await Promise.all([
                readRemoteSnapshot(),
                readNodeHostContractValue(),
                readPeers(),
            ]);
            return {
                capturedAt: Date.now(),
                nodeHostContract: contract,
                remoteSnapshot,
                pairRequests: remoteSnapshot.distributed?.pairRequests ?? [],
                peers,
                work: remoteSnapshot.distributed?.work ?? [],
            };
        },
        getNodeHostContract: async () => await readNodeHostContractValue(),
    };
}
export function createHttpTransport(options) {
    const httpClient = createHttpJsonTransport({
        baseUrl: options.baseUrl,
        authToken: options.authToken,
        fetchImpl: options.fetchImpl,
    });
    const fetchImpl = httpClient.fetchImpl;
    const paths = httpClient.paths;
    const events = createRemoteUiRuntimeEvents(createEventSourceConnector(httpClient.baseUrl, options.authToken, fetchImpl));
    const operator = createOperatorClient(httpClient, events);
    const peer = createPeerClient(httpClient);
    const transport = createClientTransport('http', operator, peer);
    return Object.freeze({
        ...transport,
        async snapshot() {
            const [currentSession, tasks, approvals, sessions, controlPlane, providers, remoteSnapshot, nodeHostContract, peerSnapshot] = await Promise.all([
                operator.sessions.current(),
                operator.tasks.snapshot(),
                operator.approvals.list(),
                operator.sessions.list(),
                operator.controlPlane.snapshot(),
                operator.providers.snapshot(),
                requestJsonRaw(fetchImpl, paths.remoteUrl, createJsonRequestInit(options.authToken)),
                peer.getNodeHostContract(),
                peer.getSnapshot(),
            ]);
            return {
                kind: 'http',
                operator: {
                    currentSession,
                    tasks,
                    approvals,
                    sessions,
                    controlPlane,
                    providers,
                    shellPaths: paths,
                },
                peer: {
                    ...peerSnapshot,
                    nodeHostContract,
                    remoteSnapshot,
                },
            };
        },
    });
}
