"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LogDecoder = void 0;
const model_1 = require("../../model");
const Log_1 = require("../Log");
const json_crdt_patch_1 = require("../../../json-crdt-patch");
const constants_1 = require("./constants");
class LogDecoder {
    constructor(opts = {}) {
        this.opts = opts;
    }
    decode(blob, params = {}) {
        switch (params.format) {
            case 'ndjson': {
                const components = this.decodeNdjsonComponents(blob);
                const result = this.deserialize(components, params);
                return result;
            }
            default: {
                // 'seq.cbor'
                const components = this.decodeSeqCborComponents(blob);
                const result = this.deserialize(components, params);
                return result;
            }
        }
    }
    decodeNdjsonComponents(blob) {
        const decoder = this.opts.jsonDecoder;
        if (!decoder)
            throw new Error('NO_JSON_DECODER');
        const reader = decoder.reader;
        reader.reset(blob);
        const components = [];
        while (reader.x < blob.length) {
            components.push(decoder.readAny());
            const nl = reader.u8();
            if (nl !== '\n'.charCodeAt(0))
                throw new Error('NDJSON_UNEXPECTED_NEWLINE');
        }
        return components;
    }
    decodeSeqCborComponents(blob) {
        const decoder = this.opts.cborDecoder;
        if (!decoder)
            throw new Error('NO_CBOR_DECODER');
        const reader = decoder.reader;
        reader.reset(blob);
        const components = [];
        while (reader.x < blob.length)
            components.push(decoder.val());
        return components;
    }
    deserialize(components, params = {}) {
        let view = components[0];
        if (view === null && params.sidecarView !== undefined)
            view = params.sidecarView;
        const [, header, model, , ...frontier] = components;
        const result = {};
        if (params.view)
            result.view = view;
        if (params.history)
            result.history = this.deserializeHistory(components);
        if (params.frontier) {
            if (!model)
                result.history = this.deserializeHistory(components);
            if (result.history) {
                result.frontier = result.history;
            }
            else if (model) {
                const modelFormat = header[1];
                const start = () => {
                    const isSidecar = modelFormat === constants_1.FileModelEncoding.SidecarBinary;
                    if (isSidecar) {
                        const decoder = this.opts.sidecarDecoder;
                        if (!decoder)
                            throw new Error('NO_SIDECAR_DECODER');
                        if (!(model instanceof Uint8Array))
                            throw new Error('NOT_BLOB');
                        return decoder.decode(view, model);
                    }
                    return this.deserializeModel(model);
                };
                const log = new Log_1.Log(start);
                const end = log.end;
                if (frontier && frontier.length)
                    for (const patch of frontier)
                        end.applyPatch(this.deserializePatch(patch));
                result.frontier = log;
            }
            else {
                throw new Error('NO_MODEL');
            }
        }
        return result;
    }
    deserializeHistory(components) {
        const [, header, , history, ...frontier] = components;
        const [startSerialized] = history;
        const start = () => {
            if (!history || !startSerialized) {
                // TODO: Handle case where new model should be started with server clock: `return Model.withServerClock()`.
                // TODO: Handle case where model has to be started with extensions...
                return model_1.Model.create(void 0, 2 /* SESSION.GLOBAL */);
            }
            return this.deserializeModel(startSerialized);
        };
        const log = new Log_1.Log(start, void 0, header[0]);
        const end = log.end;
        if (history) {
            const [, patches] = history;
            if (patches)
                for (const patch of patches)
                    end.applyPatch(this.deserializePatch(patch));
        }
        if (frontier.length)
            for (const patch of frontier)
                end.applyPatch(this.deserializePatch(patch));
        return log;
    }
    deserializeModel(serialized) {
        if (!serialized)
            throw new Error('NO_MODEL');
        if (serialized instanceof Uint8Array)
            return model_1.Model.fromBinary(serialized);
        if (Array.isArray(serialized)) {
            const decoder = this.opts.structuralCompactDecoder;
            if (!decoder)
                throw new Error('NO_STRUCTURAL_COMPACT_DECODER');
            return decoder.decode(serialized);
        }
        if (typeof serialized === 'object') {
            const decoder = this.opts.structuralVerboseDecoder;
            if (!decoder)
                throw new Error('NO_STRUCTURAL_VERBOSE_DECODER');
            return decoder.decode(serialized);
        }
        throw new Error('UNKNOWN_MODEL');
    }
    deserializePatch(serialized) {
        if (!serialized)
            throw new Error('NO_MODEL');
        if (serialized instanceof Uint8Array)
            return json_crdt_patch_1.Patch.fromBinary(serialized);
        if (Array.isArray(serialized)) {
            const decodeCompact = this.opts.patchCompactDecoder;
            if (!decodeCompact)
                throw new Error('NO_PATCH_COMPACT_DECODER');
            return decodeCompact(serialized);
        }
        if (typeof serialized === 'object') {
            const decodeVerbose = this.opts.patchVerboseDecoder;
            if (!decodeVerbose)
                throw new Error('NO_PATCH_VERBOSE_DECODER');
            return decodeVerbose(serialized);
        }
        throw new Error('UNKNOWN_MODEL');
    }
}
exports.LogDecoder = LogDecoder;
//# sourceMappingURL=LogDecoder.js.map