/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { strict as assert, fail } from "assert";
import { compareArrays } from "@fluidframework/core-utils";
import { MockHandle } from "@fluidframework/test-runtime-utils";
import { decode, readValue,
// eslint-disable-next-line import/no-internal-modules
 } from "../../../../feature-libraries/chunked-forest/codec/chunkDecoding.js";
import { NodeShape,
// eslint-disable-next-line import/no-internal-modules
 } from "../../../../feature-libraries/chunked-forest/codec/nodeShape.js";
import { EncoderCache, InlineArrayShape, NestedArrayShape, anyFieldEncoder, anyNodeEncoder, asNodesEncoder, compressedEncode, encodeValue,
// eslint-disable-next-line import/no-internal-modules
 } from "../../../../feature-libraries/chunked-forest/codec/compressedEncode.js";
import { testTrees as schemalessTestTrees } from "../../../cursorTestSuite.js";
import { jsonableTreesFromFieldCursor } from "../fieldCursorTestUtilities.js";
import { EncodedFieldBatch, validVersions, version,
// eslint-disable-next-line import/no-internal-modules
 } from "../../../../feature-libraries/chunked-forest/codec/format.js";
import { IdentifierToken, handleShapesAndIdentifiers,
// eslint-disable-next-line import/no-internal-modules
 } from "../../../../feature-libraries/chunked-forest/codec/chunkEncodingGeneric.js";
import { brand } from "../../../../util/index.js";
import { typeboxValidator } from "../../../../external-utilities/index.js";
import { cursorForJsonableTreeField } from "../../../../feature-libraries/index.js";
// eslint-disable-next-line import/no-internal-modules
import { fieldKinds } from "../../../../feature-libraries/default-schema/index.js";
import { makeVersionedValidatedCodec } from "../../../../codec/index.js";
import { takeJsonSnapshot, useSnapshotDirectory } from "../../../snapshots/index.js";
import { checkFieldEncode, checkNodeEncode } from "./checkEncode.js";
const anyNodeShape = new NodeShape(undefined, undefined, [], anyFieldEncoder);
const onlyTypeShape = new NodeShape(undefined, false, [], undefined);
const constantFooShape = new NodeShape(brand("foo"), false, [], undefined);
export function makeFieldBatchCodec(options, cache) {
    return makeVersionedValidatedCodec(options, validVersions, EncodedFieldBatch, {
        encode: (data) => {
            return compressedEncode(data, cache);
        },
        decode: (data) => {
            // TODO: consider checking data is in schema.
            return decode(data).map((chunk) => chunk.cursor());
        },
    });
}
describe("compressedEncode", () => {
    // This is a good smoke test for compressedEncode,
    // but also provides good coverage of anyNodeEncoder, anyFieldEncoder as well as AnyShape which they are built on.
    describe("schemaless test trees", () => {
        useSnapshotDirectory("chunked-forest-compressed-schemaless");
        for (const [name, jsonable] of schemalessTestTrees) {
            it(name, () => {
                const input = [cursorForJsonableTreeField([jsonable])];
                const cache = new EncoderCache((fieldShaper, schemaName) => anyNodeShape, (treeShaper, field) => anyFieldEncoder, fieldKinds);
                const codec = makeFieldBatchCodec({ jsonValidator: typeboxValidator }, cache);
                const result = codec.encode(input);
                const decoded = codec.decode(result);
                const decodedJson = decoded.map(jsonableTreesFromFieldCursor);
                assert.deepEqual([[jsonable]], decodedJson);
                // This makes it clear when the format changes.
                // This can include compression/heuristic changes which are non breaking,
                // but does not handle ensuring different old versions stull load (for example encoded with different heuristics).
                // TODO: add a new test suite with a library of encoded test data which we can parse to cover that.
                takeJsonSnapshot(result);
            });
        }
    });
    const mockHandle = new MockHandle("x");
    describe("encodeValue", () => {
        const testValues = [
            ["none", undefined, false, []],
            ["optional none", undefined, undefined, [false]],
            ["optional some", 5, undefined, [true, 5]],
            ["handle", mockHandle, undefined, [true, mockHandle]],
            ["required", false, true, [false]],
            ["constant", 5, [5], []],
        ];
        for (const [name, value, shape, encoded] of testValues) {
            it(name, () => {
                const buffer = [];
                encodeValue(value, shape, buffer);
                assert.deepEqual(buffer, encoded);
                const processed = handleShapesAndIdentifiers(version, [buffer]);
                assert(processed.data.length === 1);
                const stream = { data: processed.data[0], offset: 0 };
                const decoded = readValue(stream, shape);
                assert(stream.offset === stream.data.length);
                assert.deepEqual(decoded, value);
            });
        }
    });
    it("anyNodeEncoder", () => {
        const cache = new EncoderCache(() => anyNodeShape, () => fail(), fieldKinds);
        const buffer = checkNodeEncode(anyNodeEncoder, cache, { type: brand("foo") });
        assert.deepEqual(buffer, [anyNodeShape, new IdentifierToken("foo"), false, []]);
    });
    describe("InlineArrayShape", () => {
        it("empty", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const buffer = checkFieldEncode(InlineArrayShape.empty, cache, []);
            assert(compareArrays(buffer, []));
        });
        it("non-empty", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const shape = new InlineArrayShape(1, asNodesEncoder(onlyTypeShape));
            const buffer = checkFieldEncode(shape, cache, [{ type: brand("foo") }]);
            assert.deepEqual(buffer, [new IdentifierToken("foo")]);
        });
        it("multiple", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const shape = new InlineArrayShape(2, asNodesEncoder(onlyTypeShape));
            const buffer = checkFieldEncode(shape, cache, [
                { type: brand("foo") },
                { type: brand("bar") },
            ]);
            assert.deepEqual(buffer, [new IdentifierToken("foo"), new IdentifierToken("bar")]);
        });
        it("nested", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const shapeInner = new InlineArrayShape(2, asNodesEncoder(onlyTypeShape));
            const shapeOuter = new InlineArrayShape(2, shapeInner);
            const buffer = checkFieldEncode(shapeOuter, cache, [
                { type: brand("a") },
                { type: brand("b") },
                { type: brand("c") },
                { type: brand("d") },
            ]);
            assert.deepEqual(buffer, [
                new IdentifierToken("a"),
                new IdentifierToken("b"),
                new IdentifierToken("c"),
                new IdentifierToken("d"),
            ]);
        });
    });
    describe("NestedArrayShape", () => {
        it("empty", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const buffer = checkFieldEncode(new NestedArrayShape(onlyTypeShape), cache, []);
            assert.deepEqual(buffer, [0]);
        });
        it("non-empty", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const shape = new NestedArrayShape(onlyTypeShape);
            const buffer = checkFieldEncode(shape, cache, [{ type: brand("foo") }]);
            assert.deepEqual(buffer, [[new IdentifierToken("foo")]]);
        });
        it("multiple", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const shape = new NestedArrayShape(onlyTypeShape);
            const buffer = checkFieldEncode(shape, cache, [
                { type: brand("foo") },
                { type: brand("bar") },
            ]);
            assert.deepEqual(buffer, [[new IdentifierToken("foo"), new IdentifierToken("bar")]]);
        });
        // tests for case where child items have 0 encoded size: length can't be inferred from nested array so number is used instead.
        it("single zero sized content", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const buffer = checkFieldEncode(new NestedArrayShape(constantFooShape), cache, [
                { type: brand("foo") },
            ]);
            assert(compareArrays(buffer, [1]));
        });
        it("multiple zero sized content", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const buffer = checkFieldEncode(new NestedArrayShape(constantFooShape), cache, [
                { type: brand("foo") },
                { type: brand("foo") },
                { type: brand("foo") },
            ]);
            assert(compareArrays(buffer, [3]));
        });
    });
    describe("anyFieldEncoder", () => {
        it("empty", () => {
            const cache = new EncoderCache(() => fail(), () => fail(), fieldKinds);
            const buffer = checkFieldEncode(anyFieldEncoder, cache, []);
            // For size purposes, this should remain true
            assert.equal(buffer.length, 1, "empty field should only take up one space in the output");
            // The exact encoding schema here is not important as long as the above holds (and the entry here is small):
            // The current encoding scheme uses an empty inline array for encoding 0 size fields:
            assert(compareArrays(buffer, [InlineArrayShape.empty]));
        });
        it("one item", () => {
            const cache = new EncoderCache(() => onlyTypeShape, () => fail(), fieldKinds);
            const buffer = checkFieldEncode(anyFieldEncoder, cache, [{ type: brand("foo") }]);
            // Should use anyNodeEncoder, which will lookup the shape from cache:
            assert.deepEqual(buffer, [onlyTypeShape, new IdentifierToken("foo")]);
        });
        it("multi item", () => {
            const cache = new EncoderCache(() => onlyTypeShape, () => fail(), fieldKinds);
            const buffer = checkFieldEncode(anyFieldEncoder, cache, [
                { type: brand("foo") },
                { type: brand("bar") },
            ]);
            // Should use nestedArray, which will lookup the shape from cache:
            assert.deepEqual(buffer, [
                cache.nestedArray(anyNodeEncoder),
                [
                    onlyTypeShape,
                    new IdentifierToken("foo"),
                    onlyTypeShape,
                    new IdentifierToken("bar"),
                ],
            ]);
        });
    });
});
//# sourceMappingURL=compressedEncode.spec.js.map