import { openWarpWorldline } from "../../../index.js";
import QueryError from "../../../src/domain/errors/QueryError.js";
import WebCryptoAdapter from "../../../src/infrastructure/adapters/WebCryptoAdapter.js";
import { EXIT_CODES, notFoundError, parseCommandArgs, usageError } from "../infrastructure.js";
import { opticWitnessSchema } from "../schemas.js";
import { createPersistence, listGraphNames, resolveGraphName } from "../shared.js";
const OPTIC_WITNESS_OPTIONS = {
    property: { type: 'string' },
};
export default async function handleOptic({ options, args, }) {
    const selection = parseOpticWitnessArgs(args);
    const opened = await openOpticWorldline(options);
    return await runOpticWitness(opened, selection);
}
async function openOpticWorldline(options) {
    const { persistence } = await createPersistence(options.repo);
    const graphName = await resolveOpticGraphName(persistence, options.graph);
    return {
        graphName,
        worldline: await openWarpWorldline({
            persistence,
            worldlineName: graphName,
            writerId: options.writer,
            crypto: new WebCryptoAdapter(),
        }),
    };
}
async function runOpticWitness(opened, selection) {
    let basisEvidence = null;
    try {
        basisEvidence = await prepareBasisEvidence(opened);
        const coordinate = await opened.worldline.coordinate();
        const result = await readSelection(coordinate.optic(), selection);
        return completeWitnessResult({
            graphName: opened.graphName,
            basis: basisEvidence,
            result,
            selection,
        });
    }
    catch (error) {
        return obstructedWitnessResult({
            graphName: opened.graphName,
            basis: basisEvidence,
            selection,
            error,
        });
    }
}
async function prepareBasisEvidence(opened) {
    const basis = await opened.worldline.prepareOpticBasis();
    return {
        basisId: `checkpoint-tail:${opened.graphName}:${basis.checkpointSha}`,
        checkpointSha: basis.checkpointSha,
    };
}
function completeWitnessResult(options) {
    return {
        payload: witnessPayload(options),
        exitCode: EXIT_CODES.OK,
    };
}
function obstructedWitnessResult(options) {
    if (!(options.error instanceof QueryError)) {
        throw options.error;
    }
    return {
        payload: obstructedWitnessPayload({
            graphName: options.graphName,
            basis: options.basis,
            selection: options.selection,
            error: options.error,
        }),
        exitCode: EXIT_CODES.INTERNAL,
    };
}
function parseOpticWitnessArgs(args) {
    const subcommand = args[0];
    if (subcommand !== 'witness') {
        throw usageError('optic requires subcommand: witness');
    }
    const { values, positionals } = parseCommandArgs(args.slice(1), OPTIC_WITNESS_OPTIONS, opticWitnessSchema, { allowPositionals: true });
    const nodeId = positionals[0];
    if (nodeId === undefined || nodeId.length === 0) {
        throw usageError('optic witness requires a node id');
    }
    if (positionals.length > 1) {
        throw usageError('optic witness accepts exactly one node id');
    }
    return { nodeId, propertyKey: values.propertyKey };
}
async function resolveOpticGraphName(persistence, graph) {
    const graphName = await resolveGraphName(persistence, graph);
    if (typeof graph === 'string' && graph.length > 0) {
        const graphNames = await listGraphNames(persistence);
        if (!graphNames.includes(graph)) {
            throw notFoundError(`Graph not found: ${graph}`);
        }
    }
    return graphName;
}
async function readSelection(optic, selection) {
    const node = optic.node(selection.nodeId);
    if (selection.propertyKey === null) {
        return await node.read();
    }
    return await node.prop(selection.propertyKey).read();
}
function witnessPayload(options) {
    const { readIdentity } = options.result;
    return {
        command: 'optic witness',
        graph: options.graphName,
        selection: selectionPayload(options.selection),
        basisId: options.basis.basisId,
        checkpointSha: options.basis.checkpointSha,
        completeness: 'complete',
        obstruction: null,
        read: readPayload(options.result),
        evidence: evidencePayload(readIdentity),
        readIdentity,
    };
}
function obstructedWitnessPayload(options) {
    return {
        command: 'optic witness',
        graph: options.graphName,
        selection: selectionPayload(options.selection),
        basisId: options.basis?.basisId ?? null,
        checkpointSha: options.basis?.checkpointSha ?? null,
        completeness: 'obstructed',
        obstruction: {
            code: options.error.code,
            message: options.error.message,
            context: options.error.context,
        },
        read: null,
        evidence: {
            touchedShardIds: [],
            tailWitnessRange: {
                count: 0,
                first: null,
                last: null,
            },
            tailWitnesses: [],
        },
    };
}
function selectionPayload(selection) {
    return {
        nodeId: selection.nodeId,
        propertyKey: selection.propertyKey,
    };
}
function readPayload(result) {
    if ('key' in result) {
        return {
            kind: 'node-property',
            nodeId: result.nodeId,
            key: result.key,
            exists: result.exists,
            value: result.value,
        };
    }
    return {
        kind: 'node',
        nodeId: result.nodeId,
        alive: result.alive,
    };
}
function evidencePayload(readIdentity) {
    return {
        touchedShardIds: readIdentity.checkpointIndexShards,
        tailWitnessRange: tailWitnessRange(readIdentity),
        tailWitnesses: readIdentity.tailWitnesses,
    };
}
function tailWitnessRange(readIdentity) {
    const witnesses = readIdentity.tailWitnesses;
    const first = witnesses[0] ?? null;
    const last = witnesses[witnesses.length - 1] ?? null;
    return {
        count: witnesses.length,
        first,
        last,
    };
}
