/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { strict as assert } from "node:assert";
import { createIdCompressor } from "@fluidframework/id-compressor";
import { MockFluidDataStoreRuntime } from "@fluidframework/test-runtime-utils";
import { TreeConfiguration, } from "../../simple-tree/index.js";
import { TreeFactory } from "../../treeFactory.js";
import { SchemaFactoryRecursive,
// eslint-disable-next-line import/no-internal-modules
 } from "../../simple-tree/schemaFactoryRecursive.js";
import { hydrate } from "./utils.js";
// TODO:
// Ensure the following have tests:
// Recursive
// Co-Recursive
// Regular under recursive.
// Recursive under regular.
// All of the above for insertable and node APIs.
// All of the above package exported schema with API extractor.
// Ensure implicit construction and explicit construction work in all the above (or implicit fails to build in some cases (but only shallowly) and everything else works )
// Recursion through ImplicitAllowedTypes (part of co-recursion)
// Recursion through ImplicitFieldSchema (part of union and as part of co-recursion)
const sf = new SchemaFactoryRecursive("recursive");
describe("SchemaFactoryRecursive", () => {
    describe("objectRecursive", () => {
        it("End-to-end with recursive object", () => {
            const factory = new TreeFactory({});
            const schema = new SchemaFactoryRecursive("com.example");
            /**
             * Example Recursive type
             */
            class Box extends schema.objectRecursive("Box", {
                /**
                 * Doc comment on a schema based field. Intellisense should work when referencing the field.
                 */
                text: schema.string,
                /**
                 * Example optional field.
                 * Works the same as before.
                 */
                child: schema.optionalRecursive([() => Box]),
            }) {
            }
            const config = new TreeConfiguration(Box, () => new Box({ text: "hi", child: undefined }));
            const tree = factory.create(new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() }), "tree");
            const view = tree.schematize(config);
            assert.equal(view.root?.text, "hi");
            const stuff = view.root.child;
            assert.equal(stuff, undefined);
            view.root.child = new Box({
                text: "hi2",
                child: new Box({ text: "hi3", child: new Box({ text: "hi4", child: undefined }) }),
            });
            {
            }
            const stuff2 = view.root.child?.child?.child;
            assert.equal(stuff2?.text, "hi4");
        });
        it("objects", () => {
            class ObjectRecursive extends sf.objectRecursive("Object", {
                x: sf.optionalRecursive([() => ObjectRecursive]),
            }) {
            }
            const tree = hydrate(ObjectRecursive, new ObjectRecursive({ x: undefined }));
            const data = Reflect.ownKeys(tree);
            // TODO: are empty optional fields supposed to show up as keys in simple-tree? They currently are included, but maybe thats a bug?
            // Currently optional fields must be provided explicitly when constructing nodes, but this is planned to change (with default field defaults), which will make it seem less like the should be included.
            // Additionally all the lower level abstractions omit empty fields when iterating (especially map nodes which would be infinite if they didn't): if this layer is supposed to differ it should be explicit about it.
            assert.deepEqual(data, ["x"]);
            tree.x = new ObjectRecursive({ x: undefined });
            tree.x = tree.x?.x?.x?.x ?? new ObjectRecursive({ x: undefined });
            const tree2 = hydrate(ObjectRecursive, new ObjectRecursive({ x: new ObjectRecursive({ x: undefined }) }));
        });
        it("other under recursive object", () => {
            class Other extends sf.object("Other", {
                y: sf.number,
            }) {
            }
            class ObjectRecursive extends sf.objectRecursive("Object", {
                x: sf.optionalRecursive([() => ObjectRecursive]),
                a: Other,
                b: [Other],
                c: [() => Other],
                d: sf.optional(Other),
                e: sf.optional([() => Other]),
            }) {
            }
            const tree2 = hydrate(ObjectRecursive, new ObjectRecursive({
                x: undefined,
                a: new Other({ y: 5 }),
                b: new Other({ y: 5 }),
                c: new Other({ y: 5 }),
                d: new Other({ y: 5 }),
                e: new Other({ y: 5 }),
            }));
        });
        it("object nested construction", () => {
            class ObjectRecursive extends sf.objectRecursive("Object", {
                x: sf.optionalRecursive([() => ObjectRecursive]),
            }) {
            }
            {
                const field = ObjectRecursive.info.x;
            }
            const tree = hydrate(ObjectRecursive, new ObjectRecursive({ x: undefined }));
            tree.x = new ObjectRecursive({ x: undefined });
            // tree.x = new ObjectRecursive({ x: { x: undefined } });
            // tree.x = new ObjectRecursive({ x: { x: { x: { x: { x: undefined } } } } });
            tree.x = new ObjectRecursive({ x: undefined });
            tree.x = new ObjectRecursive({ x: new ObjectRecursive({ x: undefined }) });
            tree.x = new ObjectRecursive({
                x: new ObjectRecursive({ x: new ObjectRecursive({ x: undefined }) }),
            });
            tree.x = tree.x?.x?.x?.x ?? new ObjectRecursive({ x: undefined });
        });
        it("co-recursive objects with implicit field", () => {
            class A extends sf.objectRecursive("A", {
                a: sf.optionalRecursive([() => B]),
            }) {
            }
            class B extends sf.object("B", {
                // Implicit required field
                b: A,
            }) {
            }
            {
                const tree = hydrate(B, { b: new A({ a: undefined }) });
                assert.equal(tree.b.a, undefined);
            }
            {
                const tree = hydrate(B, new B({ b: new A({ a: undefined }) }));
                assert.equal(tree.b.a, undefined);
            }
            {
                const tree = hydrate(A, new A({ a: undefined }));
                assert.equal(tree.a, undefined);
            }
            {
                const tree = hydrate(A, new A({ a: new B({ b: new A({ a: undefined }) }) }));
                assert.equal(tree.a.b.a, undefined);
            }
            {
                const tree = hydrate(A, new A({ a: { b: new A({ a: undefined }) } }));
                assert.equal(tree.a.b.a, undefined);
            }
        });
        it("co-recursive objects with explicit non-recursive field", () => {
            class A extends sf.objectRecursive("A", {
                a: sf.optionalRecursive([() => B]),
            }) {
            }
            class B extends sf.object("B", {
                b: sf.optional(A),
            }) {
            }
            {
                const tree = hydrate(B, { b: new A({ a: undefined }) });
                assert.equal(tree.b.a, undefined);
            }
            {
                const tree = hydrate(B, new B({ b: new A({ a: undefined }) }));
                assert.equal(tree.b.a, undefined);
            }
            {
                const tree = hydrate(A, new A({ a: undefined }));
                assert.equal(tree.a, undefined);
            }
            {
                const tree = hydrate(A, new A({ a: new B({ b: new A({ a: undefined }) }) }));
                assert.equal(tree.a.b.a, undefined);
            }
            {
                const tree = hydrate(A, new A({ a: { b: new A({ a: undefined }) } }));
                assert.equal(tree.a.b.a, undefined);
            }
        });
        it("recursive object with implicit recursive field", () => {
            class A extends sf.objectRecursive("A", {
                a: [() => B, sf.number],
            }) {
            }
            class B extends sf.object("B", {
                b: sf.optional(A),
            }) {
            }
            {
                const tree = hydrate(B, { b: new A({ a: 5 }) });
                assert.equal(tree.b.a, 5);
            }
            {
                const tree = hydrate(A, new A({ a: 5 }));
                assert.equal(tree.a, 5);
            }
            {
                const tree = hydrate(A, new A({ a: new B({ b: new A({ a: 6 }) }) }));
                assert.equal(tree.a.b.a, 6);
            }
        });
    });
    describe("ValidateRecursiveSchema", () => {
        it("Valid cases", () => {
            {
                class Test extends sf.arrayRecursive("Test", [() => Test]) {
                }
            }
            {
                class Test extends sf.objectRecursive("Test", {
                    x: sf.optionalRecursive([() => Test]),
                }) {
                }
            }
            {
                class Test extends sf.mapRecursive("Test", [() => Test]) {
                }
            }
        });
        it("Invalid cases", () => {
            {
                class Test extends sf.arrayRecursive("Test", () => Test) {
                }
            }
            {
                class Test extends sf.objectRecursive("Test", sf.optionalRecursive([() => Test])) {
                }
            }
            {
                class MapRecursive extends sf.mapRecursive("Test", sf.optionalRecursive([() => MapRecursive])) {
                }
            }
        });
    });
    describe("arrayRecursive", () => {
        it("simple", () => {
            class ArrayRecursive extends sf.arrayRecursive("List", [() => ArrayRecursive]) {
            }
            // Explicit constructor call
            {
                const data = hydrate(ArrayRecursive, new ArrayRecursive([]));
                assert.deepEqual([...data], []);
            }
            // Nested
            {
                const data = hydrate(ArrayRecursive, new ArrayRecursive([new ArrayRecursive([])]));
                assert.equal(data.length, 1);
                assert.deepEqual([...data[0]], []);
                // @ts-expect-error ListRecursive should not be implicitly constructable (for now).
                const _check = [];
                data.insertAtEnd(new ArrayRecursive([]));
                data[0].insertAtEnd(new ArrayRecursive([]));
            }
        });
    });
    describe("mapRecursive", () => {
        it("simple", () => {
            class MapRecursive extends sf.mapRecursive("Map", [() => MapRecursive]) {
            }
            const node = hydrate(MapRecursive, new MapRecursive([]));
            const data = [...node];
            assert.deepEqual(data, []);
            // Nested
            {
                const _check = new MapRecursive([]);
            }
            node.set("x", new MapRecursive([]));
            node.get("x")?.set("x", new MapRecursive(new Map()));
        });
    });
    it("recursive under non-recursive", () => {
        class ArrayRecursive extends sf.arrayRecursive("List", [() => ArrayRecursive]) {
        }
        class Root extends sf.object("Root", {
            r: ArrayRecursive,
        }) {
        }
        const r = hydrate(Root, { r: new ArrayRecursive([]) });
        assert.deepEqual([...r.r], []);
    });
});
//# sourceMappingURL=schemaFactoryRecursive.spec.js.map