import BoundaryTransitionRecordCodecPort from "../../ports/BoundaryTransitionRecordCodecPort.js";
import BoundaryTransitionRecord from "../../domain/services/provenance/BTR.js";
import BtrSigningBytes from "../../domain/services/provenance/BtrSigningBytes.js";
import MessageCodecError from "../../domain/errors/MessageCodecError.js";
import VersionVector from "../../domain/crdt/VersionVector.js";
import defaultCborCodec from "../codecs/CborCodec.js";
import { hydrateDecodedPatch } from "../../domain/services/PatchHydrator.js";
const BTR_RECORD_LABEL = 'BoundaryTransitionRecord';
function decodeErrorReason(error) {
    return error instanceof Error ? error.message : String(error);
}
function isRecord(value) {
    if (typeof value !== 'object' || value === null || Array.isArray(value) || value instanceof Uint8Array) {
        return false;
    }
    return true;
}
function readObject(value, label) {
    if (!isRecord(value)) {
        throw new MessageCodecError(`${label} must be an object`, {
            code: 'E_BTR_WIRE_INVALID_OBJECT',
        });
    }
    return value;
}
function readString(source, field, label) {
    const value = source[field];
    if (typeof value !== 'string') {
        throw new MessageCodecError(`${label}.${field} must be a string`, {
            code: 'E_BTR_WIRE_INVALID_STRING',
        });
    }
    return value;
}
function readNumber(source, field, label) {
    const value = source[field];
    if (typeof value !== 'number') {
        throw new MessageCodecError(`${label}.${field} must be a number`, {
            code: 'E_BTR_WIRE_INVALID_NUMBER',
        });
    }
    return value;
}
function readBytes(source, field, label) {
    const value = source[field];
    if (!(value instanceof Uint8Array)) {
        throw new MessageCodecError(`${label}.${field} must be bytes`, {
            code: 'E_BTR_WIRE_INVALID_BYTES',
        });
    }
    return new Uint8Array(value);
}
function readProvenanceEntry(value, label) {
    const source = readObject(value, label);
    return {
        patch: hydrateDecodedPatch(source['patch']),
        sha: readString(source, 'sha', label),
    };
}
function readProvenanceEntries(source, label) {
    const value = source['P'];
    if (!Array.isArray(value)) {
        throw new MessageCodecError(`${label}.P must be an array`, {
            code: 'E_BTR_WIRE_INVALID_PROVENANCE',
        });
    }
    return value.map((entry, index) => readProvenanceEntry(entry, `${label}.P[${String(index)}]`));
}
function decodeBtrWireRecord(value) {
    const source = readObject(value, BTR_RECORD_LABEL);
    return {
        version: readNumber(source, 'version', BTR_RECORD_LABEL),
        h_in: readString(source, 'h_in', BTR_RECORD_LABEL),
        h_out: readString(source, 'h_out', BTR_RECORD_LABEL),
        U_0: readBytes(source, 'U_0', BTR_RECORD_LABEL),
        P: readProvenanceEntries(source, BTR_RECORD_LABEL),
        t: readString(source, 't', BTR_RECORD_LABEL),
        kappa: readString(source, 'kappa', BTR_RECORD_LABEL),
    };
}
function toBtrCanonicalContext(context) {
    const entries = context instanceof VersionVector
        ? [...context.entries()]
        : Object.entries(context);
    const canonicalContext = {};
    for (const [writerId, counter] of entries.sort(([left], [right]) => left.localeCompare(right))) {
        canonicalContext[writerId] = counter;
    }
    return canonicalContext;
}
function toBtrCanonicalDot(dot) {
    return {
        writerId: dot.writerId,
        counter: dot.counter,
    };
}
function toBtrCanonicalOperation(op) {
    switch (op.type) {
        case 'NodeAdd':
            return {
                type: op.type,
                node: op.node,
                dot: toBtrCanonicalDot(op.dot),
            };
        case 'NodeRemove':
            return {
                type: op.type,
                node: op.node,
                observedDots: [...op.observedDots].sort(),
            };
        case 'EdgeAdd':
            return {
                type: op.type,
                from: op.from,
                to: op.to,
                label: op.label,
                dot: toBtrCanonicalDot(op.dot),
            };
        case 'EdgeRemove':
            return {
                type: op.type,
                from: op.from,
                to: op.to,
                label: op.label,
                observedDots: [...op.observedDots].sort(),
            };
        case 'PropSet':
            return {
                type: op.type,
                node: op.node,
                key: op.key,
                value: op.value,
            };
        case 'NodePropSet':
            return {
                type: op.type,
                node: op.node,
                key: op.key,
                value: op.value,
            };
        case 'EdgePropSet':
            return {
                type: op.type,
                from: op.from,
                to: op.to,
                label: op.label,
                key: op.key,
                value: op.value,
            };
        case 'BlobValue':
            return {
                type: op.type,
                node: op.node,
                oid: op.oid,
            };
        default:
            return failUnsupportedBtrOperation(op);
    }
}
function failUnsupportedBtrOperation(_op) {
    throw new MessageCodecError('Unsupported BTR operation type', {
        code: 'E_BTR_WIRE_UNSUPPORTED_OPERATION',
    });
}
function sortedStrings(values) {
    return values === undefined ? undefined : [...values].sort();
}
function toBtrCanonicalPatch(patch) {
    const reads = sortedStrings(patch.reads);
    const writes = sortedStrings(patch.writes);
    return {
        schema: patch.schema,
        writer: patch.writer,
        lamport: patch.lamport,
        context: toBtrCanonicalContext(patch.context),
        ops: patch.ops.map(toBtrCanonicalOperation),
        ...(reads === undefined ? {} : { reads }),
        ...(writes === undefined ? {} : { writes }),
    };
}
function toBtrWireProvenanceEntry(entry) {
    return {
        patch: toBtrCanonicalPatch(entry.patch),
        sha: entry.sha,
    };
}
function toBtrWireSigningEnvelope(envelope) {
    return {
        version: envelope.version,
        h_in: envelope.h_in,
        h_out: envelope.h_out,
        U_0: envelope.U_0,
        P: envelope.P.map(toBtrWireProvenanceEntry),
        t: envelope.t,
    };
}
function toBtrWireRecord(record) {
    return {
        ...toBtrWireSigningEnvelope(record.envelope),
        kappa: record.kappa,
    };
}
export default class BtrCodecAdapter extends BoundaryTransitionRecordCodecPort {
    #codec;
    constructor(codec = defaultCborCodec) {
        super();
        this.#codec = codec;
    }
    signingBytes(envelope) {
        const bytes = this.#codec.encode(toBtrWireSigningEnvelope(envelope));
        return BtrSigningBytes.fromCanonicalBtrSigningEncoder(bytes);
    }
    encodeRecord(record) {
        return this.#codec.encode(toBtrWireRecord(record));
    }
    decodeRecord(bytes) {
        try {
            const wire = decodeBtrWireRecord(this.#codec.decode(bytes));
            return {
                kind: 'decoded_boundary_transition_record',
                record: new BoundaryTransitionRecord({
                    version: wire.version,
                    h_in: wire.h_in,
                    h_out: wire.h_out,
                    U_0: wire.U_0,
                    P: wire.P,
                    t: wire.t,
                    kappa: wire.kappa,
                }),
            };
        }
        catch (error) {
            return {
                kind: 'boundary_transition_record_decode_failed',
                reason: decodeErrorReason(error),
            };
        }
    }
}
export { BtrCodecAdapter };
