import { describe, test, expect } from "vitest";
import { openLix } from "../../lix/open-lix.js";
import { createVersion } from "../../version/create-version.js";
import { handleFileInsert, handleFileUpdate } from "./file-handlers.js";
import { mockJsonPlugin } from "../../plugin/mock-json-plugin.js";
import { randomNanoId } from "../../database/nano-id.js";
const testUniqueSchema = {
    "x-lix-key": "test_unique_block",
    "x-lix-version": "1.0",
    description: "Test schema that enforces unique block_id",
    type: "object",
    "x-lix-unique": [["/block_id"]],
    properties: {
        block_id: { type: "string" },
        content: { type: "string" },
    },
    required: ["block_id", "content"],
    additionalProperties: false,
};
const uniqueBlockPlugin = {
    key: "test_unique_plugin",
    detectChangesGlob: "**/*",
    detectChanges: ({ after }) => {
        if (!after)
            return [];
        const content = new TextDecoder().decode(after.data ?? new Uint8Array());
        return [
            {
                entity_id: "test-entity",
                schema: testUniqueSchema,
                snapshot_content: {
                    block_id: "shared-block",
                    content,
                },
            },
        ];
    },
    applyChanges: ({ changes }) => {
        const content = changes?.[0]?.snapshot_content?.content;
        return { fileData: new TextEncoder().encode(content ?? "") };
    },
};
describe("file insert", () => {
    test("should handle unknown file types with fallback plugin", async () => {
        const lix = await openLix({
            keyValues: [
                {
                    key: "lix_log_levels",
                    value: ["debug", "info", "warn", "error"],
                },
            ],
        });
        const version = await createVersion({ lix });
        // Insert unknown file type - should be handled by unknown file plugin
        handleFileInsert({
            engine: lix.engine,
            file: {
                id: randomNanoId(),
                path: "/test.unknown",
                data: new TextEncoder().encode("some content"),
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        });
        // Should log "no plugin" warning but still handle the file with fallback plugin
        const logs = await lix.db
            .selectFrom("log")
            .where("key", "=", "lix_file_no_plugin")
            .where("level", "=", "warn")
            .selectAll()
            .execute();
        expect(logs.length).toBe(1);
    });
});
describe("file update", () => {
    test("should handle unknown file types with fallback plugin", async () => {
        const lix = await openLix({
            keyValues: [
                {
                    key: "lix_log_levels",
                    value: ["debug", "info", "warn", "error"],
                },
            ],
        });
        const version = await createVersion({ lix });
        const fileId = randomNanoId();
        // Manually insert the file into the file table first
        await lix.db
            .insertInto("file_by_version")
            .values({
            id: fileId,
            path: "/test.unknown",
            data: new TextEncoder().encode("some data"),
            lixcol_version_id: version.id,
            metadata: null,
            hidden: false,
        })
            .execute();
        // Update with different data - should be handled by unknown file plugin
        handleFileUpdate({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/test.unknown",
                data: new TextEncoder().encode("updated data"),
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        });
        // Should log "no plugin" warning but still handle the file with fallback plugin
        const logs = await lix.db
            .selectFrom("log")
            .where("key", "=", "lix_file_no_plugin")
            .where("level", "=", "warn")
            .selectAll()
            .execute();
        expect(logs.length).toBeGreaterThanOrEqual(1);
    });
    test("should handle deleted entities during file update", async () => {
        const lix = await openLix({
            providePlugins: [mockJsonPlugin],
        });
        const version = await createVersion({ lix });
        const fileId = randomNanoId();
        // Initial JSON data with two users
        const initialJsonData = {
            users: {
                john: { name: "John", age: 30 },
                jane: { name: "Jane", age: 25 },
            },
        };
        // Insert the file initially - this should create entities for both users
        handleFileInsert({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/users.json",
                data: new TextEncoder().encode(JSON.stringify(initialJsonData)),
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        });
        // Verify both entities were created
        const initialEntities = await lix.db
            .selectFrom("state_by_version")
            .where("file_id", "=", fileId)
            .where("schema_key", "=", "mock_json_property")
            .where("version_id", "=", version.id)
            .selectAll()
            .execute();
        expect(initialEntities.length).toBeGreaterThan(0);
        // Updated JSON data with jane removed
        const updatedJsonData = {
            users: {
                john: { name: "John", age: 30 },
                // jane is removed
            },
        };
        // Update the file - this should trigger deletion of jane's entities
        handleFileUpdate({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/users.json",
                data: new TextEncoder().encode(JSON.stringify(updatedJsonData)),
                metadata: null,
            },
            versionId: version.id,
        });
        // Verify that jane's entities were deleted
        const finalEntities = await lix.db
            .selectFrom("state_by_version")
            .where("file_id", "=", fileId)
            .where("schema_key", "=", "mock_json_property")
            .where("version_id", "=", version.id)
            .selectAll()
            .execute();
        // Should have fewer entities after the update (jane's entities removed)
        expect(finalEntities.length).toBeLessThan(initialEntities.length);
        // John's entities should still exist
        const johnEntities = finalEntities.filter((e) => e.entity_id.includes("john"));
        expect(johnEntities.length).toBeGreaterThan(0);
        // Jane's entities should be gone
        const janeEntities = finalEntities.filter((e) => e.entity_id.includes("jane"));
        expect(janeEntities.length).toBe(0);
    });
    test("updates descriptor untracked state when only tracking flag changes", async () => {
        const lix = await openLix({});
        const version = await createVersion({ lix });
        const fileId = randomNanoId();
        const initialData = new TextEncoder().encode("no-op content");
        // Seed a tracked file row
        await lix.db
            .insertInto("file_by_version")
            .values({
            id: fileId,
            path: "/tracked-file.txt",
            data: initialData,
            lixcol_version_id: version.id,
            metadata: null,
            hidden: false,
            lixcol_untracked: false,
        })
            .execute();
        const [descriptorBefore] = await lix.db
            .selectFrom("state_by_version")
            .where("entity_id", "=", fileId)
            .where("schema_key", "=", "lix_file_descriptor")
            .where("version_id", "=", version.id)
            .select(["untracked"])
            .execute();
        expect(Boolean(descriptorBefore?.untracked)).toBe(false);
        // Update only the untracked flag
        handleFileUpdate({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/tracked-file.txt",
                data: initialData,
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
            untracked: true,
        });
        const [descriptorAfter] = await lix.db
            .selectFrom("state_by_version")
            .where("entity_id", "=", fileId)
            .where("schema_key", "=", "lix_file_descriptor")
            .where("version_id", "=", version.id)
            .select(["untracked"])
            .execute();
        expect(Boolean(descriptorAfter?.untracked)).toBe(true);
    });
    test("updates use updateTable under the hood to avoid insert constraint violations", async () => {
        const lix = await openLix({
            providePlugins: [uniqueBlockPlugin],
            keyValues: [
                {
                    key: "lix_deterministic_mode",
                    value: "enabled",
                    lixcol_version_id: "global",
                },
            ],
        });
        const version = await createVersion({ lix });
        const fileId = "test_unique_file";
        const initialData = new TextEncoder().encode("# Title\n\nHello");
        handleFileInsert({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/doc.unique",
                data: initialData,
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        });
        expect(() => handleFileUpdate({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/doc.unique",
                data: new TextEncoder().encode("# Title\n\nHello again"),
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        })).not.toThrow();
    });
    test("updates to entities are retrievable via state history", async () => {
        const lix = await openLix({
            providePlugins: [uniqueBlockPlugin],
            keyValues: [
                {
                    key: "lix_deterministic_mode",
                    value: "enabled",
                    lixcol_version_id: "global",
                },
            ],
        });
        const version = await createVersion({ lix });
        const fileId = "test_unique_file";
        const initialData = new TextEncoder().encode("# Title\n\nHello");
        handleFileInsert({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/doc.unique",
                data: initialData,
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        });
        handleFileUpdate({
            engine: lix.engine,
            file: {
                id: fileId,
                path: "/doc.unique",
                data: new TextEncoder().encode("# Title\n\nHello updated"),
                metadata: null,
                hidden: false,
            },
            versionId: version.id,
        });
        const versionRow = await lix.db
            .selectFrom("version")
            .select(["commit_id"])
            .where("id", "=", version.id)
            .executeTakeFirstOrThrow();
        expect(versionRow.commit_id).toBeTruthy();
        const rows = await lix.db
            .selectFrom("state_history")
            .where("entity_id", "=", "test-entity")
            .where("schema_key", "=", testUniqueSchema["x-lix-key"])
            .where("root_commit_id", "=", versionRow.commit_id)
            .select(["snapshot_content", "commit_id", "depth"])
            .orderBy("depth", "asc")
            .execute();
        expect(rows).toMatchObject([
            {
                snapshot_content: {
                    block_id: "shared-block",
                    content: "# Title\n\nHello updated",
                },
                commit_id: versionRow.commit_id,
                depth: 0,
            },
            {
                snapshot_content: {
                    block_id: "shared-block",
                    content: "# Title\n\nHello",
                },
                depth: 1,
            },
        ]);
    });
});
//# sourceMappingURL=file-handlers.test.js.map