import { expect, test, describe, vi, beforeEach } from "vitest";
import { createProject as typescriptProject, ts, } from "@ts-morph/bootstrap";
import { Declaration, insertBundleNested, loadProjectInMemory, newProject, Pattern, VariableReference, } from "@inlang/sdk";
import { compileProject, getFallbackMap } from "./compile-project.js";
import virtual from "@rollup/plugin-virtual";
import { rolldown } from "rolldown";
beforeEach(() => {
    // reset the imports to make sure that the runtime is reloaded
    vi.resetModules();
    vi.clearAllMocks();
    // mocking DOM globals
    // @ts-expect-error - global variable definition
    globalThis.window = undefined;
});
function hasFallbackCycle(fallbackMap, start) {
    const visited = new Set();
    let current = start;
    while (current) {
        if (visited.has(current))
            return true;
        visited.add(current);
        current = fallbackMap[current];
    }
    return false;
}
test("emitGitignore", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    const _default = await compileProject({
        project,
    });
    const _true = await compileProject({
        project,
        compilerOptions: { emitGitIgnore: true },
    });
    const _false = await compileProject({
        project,
        compilerOptions: { emitGitIgnore: false },
    });
    expect(_default).toHaveProperty(".gitignore");
    expect(_true).toHaveProperty(".gitignore");
    expect(_false).not.toHaveProperty(".gitignore");
});
test("emits messages/package.json with sideEffects:false for message-modules", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    const messageModules = await compileProject({
        project,
    });
    const localeModules = await compileProject({
        project,
        compilerOptions: { outputStructure: "locale-modules" },
    });
    // message-modules: the message modules are side-effect-free, so bundlers can
    // drop unused re-exports from the `m` barrel per entry instead of emitting one
    // shared chunk with every message. `type: "module"` keeps the generated ESM
    // files in the new `messages/` package scope from defaulting to CommonJS.
    expect(messageModules).toHaveProperty("messages/package.json");
    expect(JSON.parse(messageModules["messages/package.json"])).toEqual({
        type: "module",
        sideEffects: false,
    });
    // scoped to message-modules (the structure with the re-export barrel)
    expect(localeModules).not.toHaveProperty("messages/package.json");
});
test("emitPrettierIgnore", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    const _default = await compileProject({
        project,
    });
    const _true = await compileProject({
        project,
        compilerOptions: { emitPrettierIgnore: true },
    });
    const _false = await compileProject({
        project,
        compilerOptions: { emitPrettierIgnore: false },
    });
    expect(_default).toHaveProperty(".prettierignore");
    expect(_true).toHaveProperty(".prettierignore");
    expect(_false).not.toHaveProperty(".prettierignore");
});
test("emitReadme", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    const projectPath = "./project.inlang";
    const _default = await compileProject({
        project,
        projectPath,
    });
    const _true = await compileProject({
        project,
        projectPath,
        compilerOptions: { emitReadme: true },
    });
    const _false = await compileProject({
        project,
        projectPath,
        compilerOptions: { emitReadme: false },
    });
    const _noProjectPath = await compileProject({
        project,
    });
    expect(_default).toHaveProperty("README.md");
    expect(_true).toHaveProperty("README.md");
    expect(_false).not.toHaveProperty("README.md");
    // README should still be emitted even without projectPath
    expect(_noProjectPath).toHaveProperty("README.md");
    // but should not contain the "Compiled from:" line
    expect(_noProjectPath["README.md"]).not.toContain("Compiled from:");
});
test("emitReadme includes project path", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    const projectPath = "./my-custom-project.inlang";
    const output = await compileProject({
        project,
        projectPath,
    });
    expect(output["README.md"]).toContain(projectPath);
    expect(output["README.md"]).toContain("Compiled from:");
    expect(output["README.md"]).toContain("Paraglide JS");
});
test("throws during compile for invalid routeStrategies match patterns", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    await expect(compileProject({
        project,
        compilerOptions: {
            routeStrategies: [{ match: "/api/:path(.*", exclude: true }],
        },
    })).rejects.toThrow(`Invalid routeStrategies[0].match "/api/:path(.*"`);
});
// https://github.com/opral/paraglide-js/issues/539
test("omits async_hooks import when disableAsyncLocalStorage is true", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    const output = await compileProject({
        project,
        compilerOptions: { disableAsyncLocalStorage: true },
    });
    expect(output["server.js"]).not.toContain('const { AsyncLocalStorage } = await import("async_hooks");');
});
// https://github.com/opral/paraglide-js/issues/544
test("fallback map should not create cycles for language-only + regional baseLocale", () => {
    const fallbackMap = getFallbackMap(["it", "it-IT", "fr", "fr-FR"], "it-IT");
    expect(hasFallbackCycle(fallbackMap, "it")).toBe(false);
    expect(hasFallbackCycle(fallbackMap, "it-IT")).toBe(false);
    expect(fallbackMap["it"]).toBe("it-IT");
    expect(fallbackMap["it-IT"]).toBeUndefined();
});
test("emitTsDeclarations generates declaration files", async () => {
    const output = await compileProject({
        project,
        compilerOptions: {
            emitTsDeclarations: true,
            outputStructure: "locale-modules",
        },
    });
    expect(output).toHaveProperty("messages/_index.d.ts");
    expect(output).toHaveProperty("messages.d.ts");
    expect(output).toHaveProperty("registry.d.ts");
    expect(output["messages/_index.d.ts"]).toContain("sad_penguin_bundle");
    expect(output["messages/_index.d.ts"]).toContain("relative_time_dynamic");
    expect(output["registry.d.ts"]).toContain("RelativeTimeFormatUnit");
    expect(output["registry.d.ts"]).toContain("relativetime");
    const tsProject = await typescriptProject({
        useInMemoryFileSystem: true,
        compilerOptions: {
            module: ts.ModuleKind.Node16,
            moduleResolution: ts.ModuleResolutionKind.Node16,
            strict: true,
        },
    });
    for (const [fileName, code] of Object.entries(output)) {
        if (fileName.endsWith(".d.ts")) {
            tsProject.createSourceFile(fileName, code);
        }
    }
    tsProject.createSourceFile("test.ts", `
			import { relative_time_dynamic } from "./messages.js";
			import { relativetime } from "./registry.js";

			relative_time_dynamic({ duration: -3, unit: "hour" }) satisfies string;
			relativetime("en", -3, { unit: "hour", style: "short" }) satisfies string;
		`);
    const program = tsProject.createProgram();
    const diagnostics = ts.getPreEmitDiagnostics(program);
    for (const diagnostic of diagnostics) {
        console.error(diagnostic.messageText, diagnostic.file?.fileName);
    }
    expect(diagnostics.length).toEqual(0);
});
test("emitTsDeclarations quotes unsafe export aliases", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en"],
                baseLocale: "en",
            },
        }),
    });
    await insertBundleNested(project.db, createBundleNested({
        id: "greeting.hello",
        messages: [
            {
                locale: "en",
                variants: [{ pattern: [{ type: "text", value: "Hello" }] }],
            },
        ],
    }));
    for (const outputStructure of [
        "message-modules",
        "locale-modules",
    ]) {
        const output = await compileProject({
            project,
            compilerOptions: {
                emitTsDeclarations: true,
                outputStructure,
            },
        });
        const declarationFile = outputStructure === "message-modules"
            ? "messages/greeting_hello.d.ts"
            : "messages/_index.d.ts";
        expect(output[declarationFile]).toContain(`export { greeting_hello as "greeting.hello" };`);
        const tsProject = await typescriptProject({
            useInMemoryFileSystem: true,
            compilerOptions: {
                module: ts.ModuleKind.Node16,
                moduleResolution: ts.ModuleResolutionKind.Node16,
                strict: true,
            },
        });
        for (const [fileName, code] of Object.entries(output)) {
            if (fileName.endsWith(".d.ts")) {
                tsProject.createSourceFile(fileName, code);
            }
        }
        tsProject.createSourceFile("test.ts", `
				import { "greeting.hello" as greetingHello } from "./messages.js";

				greetingHello() satisfies string;
			`);
        const program = tsProject.createProgram();
        const diagnostics = ts.getPreEmitDiagnostics(program);
        for (const diagnostic of diagnostics) {
            console.error(diagnostic.messageText, diagnostic.file?.fileName);
        }
        expect(diagnostics.length).toEqual(0);
    }
});
test("handles message bundles with a : in the id", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    await insertBundleNested(project.db, createBundleNested({
        id: "hello:world",
        messages: [
            {
                locale: "en",
                variants: [{ pattern: [{ type: "text", value: "Hello world!" }] }],
            },
        ],
    }));
    const output = await compileProject({
        project,
    });
    const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
		 export * as runtime from "./paraglide/runtime.js"`);
    const { m } = await importCode(code);
    expect(m["hello:world"]()).toBe("Hello world!");
});
test("emits .parts() on bundle functions for markup messages", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                locales: ["en", "de"],
                baseLocale: "en",
            },
        }),
    });
    await insertBundleNested(project.db, createBundleNested({
        id: "balance",
        declarations: [
            {
                type: "input-variable",
                name: "amount",
            },
        ],
        messages: [
            {
                locale: "en",
                variants: [
                    {
                        pattern: [
                            { type: "text", value: "You have " },
                            {
                                type: "markup-start",
                                name: "strong",
                                options: [],
                                attributes: [],
                            },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "amount" },
                            },
                            { type: "text", value: " coins" },
                            {
                                type: "markup-end",
                                name: "strong",
                                options: [],
                                attributes: [],
                            },
                            { type: "text", value: "." },
                        ],
                    },
                ],
            },
        ],
    }));
    const output = await compileProject({
        project,
    });
    const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
		 export * as runtime from "./paraglide/runtime.js"`);
    const { m } = await importCode(code);
    expect(m.balance({ amount: 7 }, { locale: "en" })).toBe("You have 7 coins.");
    expect(m.balance.parts({ amount: 7 }, { locale: "en" })).toEqual([
        { type: "text", value: "You have " },
        { type: "markup-start", name: "strong", options: {}, attributes: {} },
        { type: "text", value: "7" },
        { type: "text", value: " coins" },
        { type: "markup-end", name: "strong", options: {}, attributes: {} },
        { type: "text", value: "." },
    ]);
});
// https://github.com/opral/inlang-paraglide-js/issues/347
test("can emit message bundles with more than 255 characters", async () => {
    const project = await loadProjectInMemory({
        blob: await newProject({
            settings: {
                baseLocale: "en",
                locales: ["en", "de"],
            },
        }),
    });
    await insertBundleNested(project.db, createBundleNested({
        // 300 characters long id
        id: "a".repeat(300),
        messages: [
            {
                locale: "en",
                variants: [
                    {
                        pattern: [{ type: "text", value: "Hello" }],
                    },
                ],
            },
        ],
    }));
    const output = await compileProject({
        project,
        compilerOptions: {
            urlPatterns: [],
        },
    });
    const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
		 export * as runtime from "./paraglide/runtime.js"`);
    const { m } = await importCode(code);
    expect(m["a".repeat(300)]()).toBe("Hello");
});
describe.each([
    // useTsImports must be true to test emitTs. Otherwise, rolldown can't resolve the imports
    {
        outputStructure: "locale-modules",
        strategy: ["globalVariable", "baseLocale"],
    },
    {
        outputStructure: "message-modules",
        strategy: ["globalVariable", "baseLocale"],
    },
])("options", async (compilerOptions) => {
    const output = await compileProject({ project, compilerOptions });
    describe("tree-shaking", () => {
        test("should tree-shake unused messages", async () => {
            const code = await bundleCode(output, `import * as m from "./paraglide/messages.js"

					console.log(m.sad_penguin_bundle())`);
            const log = vi.spyOn(console, "log").mockImplementation(() => { });
            // all required code for the message to be rendered is included like sourceLanguageTag.
            // but, all other messages except of 'sad_penguin_bundle' are tree-shaken away.
            for (const { id } of mockBundles) {
                if (id === "sad_penguin_bundle") {
                    expect(code).toContain(id);
                }
                else {
                    expect(code).not.toContain(id);
                }
            }
            eval(code);
            expect(log).toHaveBeenCalledWith("A simple message.");
        });
        // https://github.com/opral/inlang-paraglide-js/issues/345
        test("importing { m } works and tree-shakes unused messages", async () => {
            const code = await bundleCode(output, `import { m } from "./paraglide/messages.js"
					console.log(m.sad_penguin_bundle())`);
            const log = vi.spyOn(console, "log").mockImplementation(() => { });
            // all required code for the message to be rendered is included like sourceLanguageTag.
            // but, all other messages except of 'sad_penguin_bundle' are tree-shaken away.
            for (const { id } of mockBundles) {
                if (id === "sad_penguin_bundle") {
                    expect(code).toContain(id);
                }
                else {
                    expect(code).not.toContain(id);
                }
            }
            eval(code);
            expect(log).toHaveBeenCalledWith("A simple message.");
        });
        test("should not treeshake messages that are used", async () => {
            const code = await bundleCode(output, `import * as m from "./paraglide/messages.js"
		
			console.log(
				m.sad_penguin_bundle(),
				m.depressed_dog({ name: "Samuel" }),
				m.insane_cats({ name: "Samuel", count: 5 })
			)`);
            const log = vi.spyOn(console, "log").mockImplementation(() => { });
            for (const id of mockBundles.map((m) => m.id)) {
                if (["sad_penguin_bundle", "depressed_dog", "insane_cats"].includes(id)) {
                    expect(code).toContain(id);
                }
                else {
                    expect(code).not.toContain(id);
                }
            }
            eval(code);
            expect(log).toHaveBeenCalledWith("A simple message.", "Good morning Samuel!", "Hello Samuel! You have 5 messages.");
        });
    });
    // https://github.com/opral/inlang-paraglide-js/issues/379
    test("plurals work", async () => {
        const project = await loadProjectInMemory({
            blob: await newProject({
                settings: { locales: ["en", "de"], baseLocale: "en" },
            }),
        });
        await insertBundleNested(project.db, createBundleNested({
            id: "plural_test",
            declarations: [
                { type: "input-variable", name: "count" },
                {
                    type: "local-variable",
                    name: "countPlural",
                    value: {
                        arg: { type: "variable-reference", name: "count" },
                        annotation: {
                            type: "function-reference",
                            name: "plural",
                            options: [],
                        },
                        type: "expression",
                    },
                },
            ],
            messages: [
                {
                    locale: "en",
                    selectors: [{ type: "variable-reference", name: "countPlural" }],
                    variants: [
                        {
                            matches: [
                                { type: "literal-match", value: "one", key: "countPlural" },
                            ],
                            pattern: [{ type: "text", value: "There is one cat." }],
                        },
                        {
                            matches: [
                                {
                                    type: "literal-match",
                                    value: "other",
                                    key: "countPlural",
                                },
                            ],
                            pattern: [{ type: "text", value: "There are many cats." }],
                        },
                    ],
                },
            ],
        }));
        const { m } = await importCode(await bundleCode(await compileProject({
            project,
            compilerOptions,
        }), `export * as m from "./paraglide/messages.js"`));
        expect(m.plural_test({ count: 1 })).toBe("There is one cat.");
        expect(m.plural_test({ count: 2 })).toBe("There are many cats.");
    });
    describe("e2e", async () => {
        // The compiled output needs to be bundled into one file to be dynamically imported.
        const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
		     export * as runtime from "./paraglide/runtime.js"`);
        // test is a direct result of a bug
        test("locales should include locales with a hyphen", async () => {
            const { runtime } = await importCode(code);
            expect(runtime.locales).toContain("en-US");
        });
        test("should set the baseLocale as default getLocale value", async () => {
            const { runtime } = await importCode(code);
            expect(runtime.getLocale()).toBe(runtime.baseLocale);
        });
        test("should return the correct message for the current locale", async () => {
            const { m, runtime } = await importCode(code);
            console.log(m);
            runtime.setLocale("en");
            expect(m.sad_penguin_bundle()).toBe("A simple message.");
            runtime.setLocale("de");
            expect(m.sad_penguin_bundle()).toBe("Eine einfache Nachricht.");
        });
        test("overwriteGetLocale() works", async () => {
            const { m, runtime } = await importCode(code);
            let locale = "en";
            runtime.overwriteGetLocale(() => locale);
            expect(m.sad_penguin_bundle()).toBe("A simple message.");
            locale = "de";
            expect(m.sad_penguin_bundle()).toBe("Eine einfache Nachricht.");
        });
        test("overwriteSetLocale() works", async () => {
            const { runtime } = await importCode(code);
            let locale = "en";
            runtime.overwriteSetLocale((newLocale) => {
                locale = newLocale;
            });
            runtime.setLocale("de");
            expect(locale).toBe("de");
        });
        test.skip("defining onSetLocale should be possible and should be called when the locale changes", async () => {
            const { runtime } = await importCode(code);
            const mockOnSetLocale = vi.fn().mockImplementation(() => { });
            runtime.onSetLocale((locale) => {
                mockOnSetLocale(locale);
            });
            runtime.setLocale("de");
            expect(mockOnSetLocale).toHaveBeenLastCalledWith("de");
            runtime.setLocale("en");
            expect(mockOnSetLocale).toHaveBeenLastCalledWith("en");
            expect(mockOnSetLocale).toHaveBeenCalledTimes(2);
        });
        test.skip("Calling onSetLocale() multiple times should override the previous callback", async () => {
            const cb1 = vi.fn().mockImplementation(() => { });
            const cb2 = vi.fn().mockImplementation(() => { });
            const { runtime } = await importCode(code);
            runtime.onSetLocale(cb1);
            runtime.setLocale("en");
            expect(cb1).toHaveBeenCalledTimes(1);
            runtime.onSetLocale(cb2);
            runtime.setLocale("de");
            expect(cb2).toHaveBeenCalledTimes(1);
            expect(cb1).toHaveBeenCalledTimes(1);
        });
        test("should return the correct message if a locale is set in the message options", async () => {
            const { m, runtime } = await importCode(code);
            // set the language tag to de to make sure that the message options override the runtime language tag
            runtime.setLocale("de");
            expect(m.sad_penguin_bundle()).toBe("Eine einfache Nachricht.");
            expect(m.sad_penguin_bundle(undefined, { locale: "en" })).toBe("A simple message.");
        });
        test("relativetime formatter works with literal units and locale overrides", async () => {
            const { m, runtime } = await importCode(code);
            runtime.setLocale("de");
            expect(m.relative_time_literal({ duration: -1 })).toBe("Updated gestern.");
            expect(m.relative_time_literal({ duration: -1 }, { locale: "en" })).toBe("Updated yesterday.");
        });
        test("relativetime formatter works with dynamic units", async () => {
            const { m, runtime } = await importCode(code);
            runtime.setLocale("en");
            expect(m.relative_time_dynamic({ duration: -3, unit: "hour" })).toBe("Updated 3 hr. ago.");
            runtime.setLocale("de");
            expect(m.relative_time_dynamic({ duration: 2, unit: "weeks" })).toBe("Updated in 2 Wochen.");
        });
        test("runtime.isLocale should only return `true` if a locale is passed to it", async () => {
            const { runtime } = await importCode(code);
            for (const tag of runtime.locales) {
                expect(runtime.isLocale(tag)).toBe(true);
            }
            expect(runtime.isLocale("")).toBe(false);
            expect(runtime.isLocale("pl")).toBe(false);
            expect(runtime.isLocale("--")).toBe(false);
        });
        test("falls back to base locale", async () => {
            const project = await loadProjectInMemory({
                blob: await newProject({
                    settings: { locales: ["en", "de", "en-US"], baseLocale: "en" },
                }),
            });
            await insertBundleNested(project.db, createBundleNested({
                id: "missingInGerman",
                messages: [
                    {
                        locale: "en",
                        variants: [
                            { pattern: [{ type: "text", value: "A simple message." }] },
                        ],
                    },
                ],
            }));
            const output = await compileProject({
                project,
                compilerOptions,
            });
            const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
					export * as runtime from "./paraglide/runtime.js"`);
            const { m, runtime } = await importCode(code);
            runtime.setLocale("de");
            expect(m.missingInGerman()).toBe("A simple message.");
            runtime.setLocale("en-US");
            expect(m.missingInGerman()).toBe("A simple message.");
        });
        test("arbitrary module identifiers work", async () => {
            const project = await loadProjectInMemory({
                blob: await newProject({
                    settings: { locales: ["en", "de"], baseLocale: "en" },
                }),
            });
            await insertBundleNested(project.db, createBundleNested({
                id: "$502.23-hello_world",
                messages: [
                    {
                        locale: "en",
                        variants: [
                            { pattern: [{ type: "text", value: "A simple message." }] },
                        ],
                    },
                ],
            }));
            const output = await compileProject({
                project,
                compilerOptions,
            });
            const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
					export * as runtime from "./paraglide/runtime.js"`);
            const { m } = await importCode(code);
            expect(m["$502.23-hello_world"]()).toBe("A simple message.");
        });
        test("falls back to parent locale if message doesn't exist", async () => {
            const project = await loadProjectInMemory({
                blob: await newProject({
                    settings: { locales: ["en", "en-US"], baseLocale: "en" },
                }),
            });
            await insertBundleNested(project.db, createBundleNested({
                id: "exists_in_both",
                messages: [
                    {
                        locale: "en",
                        variants: [
                            { pattern: [{ type: "text", value: "A simple message." }] },
                        ],
                    },
                    {
                        locale: "en-US",
                        variants: [
                            {
                                pattern: [
                                    {
                                        type: "text",
                                        value: "A simple message for Americans.",
                                    },
                                ],
                            },
                        ],
                    },
                ],
            }));
            await insertBundleNested(project.db, createBundleNested({
                id: "missing_in_en_US",
                messages: [
                    {
                        locale: "en",
                        variants: [
                            { pattern: [{ type: "text", value: "Fallback message." }] },
                        ],
                    },
                ],
            }));
            const output = await compileProject({
                project,
                compilerOptions,
            });
            const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
					export * as runtime from "./paraglide/runtime.js"`);
            const { m, runtime } = await importCode(code);
            runtime.setLocale("en-US");
            expect(m.exists_in_both()).toBe("A simple message for Americans.");
            runtime.setLocale("en-US");
            expect(m.missing_in_en_US()).toBe("Fallback message.");
        });
        test("arbitrary module identifiers", async () => {
            const project = await loadProjectInMemory({
                blob: await newProject({
                    settings: { locales: ["en"], baseLocale: "en" },
                }),
            });
            await insertBundleNested(project.db, createBundleNested({
                id: "happy🍌",
                messages: [
                    {
                        locale: "en",
                        variants: [{ pattern: [{ type: "text", value: "Hello" }] }],
                    },
                ],
            }));
            const output = await compileProject({
                project,
                compilerOptions,
            });
            const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
					export * as runtime from "./paraglide/runtime.js"`);
            const { m } = await importCode(code);
            expect(m["happy🍌"]()).toBe("Hello");
        });
    });
    test("relativetime dynamic unit casts generated options without narrowing public inputs", () => {
        const relativeTimeDynamicFile = compilerOptions.outputStructure === "locale-modules"
            ? "messages/en.js"
            : "messages/relative_time_dynamic.js";
        expect(output).toHaveProperty(relativeTimeDynamicFile);
        const relativeTimeDynamicModule = output[relativeTimeDynamicFile];
        expect(relativeTimeDynamicModule).toContain('unit: /** @type {import("../registry.js").RelativeTimeFormatUnit} */ (i?.unit)');
        expect(relativeTimeDynamicModule).toContain("{ duration: NonNullable<unknown>, unit: NonNullable<unknown> }");
    });
    test("case sensitivity handling for bundle IDs", async () => {
        const project = await loadProjectInMemory({
            blob: await newProject({
                settings: { locales: ["en"], baseLocale: "en" },
            }),
        });
        // Create two bundles with the same name but different case
        await insertBundleNested(project.db, createBundleNested({
            id: "Helloworld",
            messages: [
                {
                    locale: "en",
                    variants: [
                        {
                            pattern: [
                                { type: "text", value: "Hello from uppercase bundle" },
                            ],
                        },
                    ],
                },
            ],
        }));
        await insertBundleNested(project.db, createBundleNested({
            id: "helloworld",
            messages: [
                {
                    locale: "en",
                    variants: [
                        {
                            pattern: [
                                { type: "text", value: "Hello from lowercase bundle" },
                            ],
                        },
                    ],
                },
            ],
        }));
        const output = await compileProject({
            project,
            compilerOptions,
        });
        const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
				export { helloworld, Helloworld } from "./paraglide/messages.js"`);
        const imported = await importCode(code);
        // Both message functions should be available
        expect(imported.helloworld()).toBe("Hello from lowercase bundle");
        expect(imported.Helloworld()).toBe("Hello from uppercase bundle");
        // They should also be available through the m namespace
        expect(imported.m.helloworld()).toBe("Hello from lowercase bundle");
        expect(imported.m.Helloworld()).toBe("Hello from uppercase bundle");
    });
    // whatever the strictest users use, this is the ultimate nothing gets stricter than this
    // (to avoid developers opening issues "i get a ts warning in my code")
    const superStrictRuleOutAnyErrorTsSettings = {
        outDir: "dist",
        declaration: true,
        allowJs: true,
        checkJs: true,
        noImplicitAny: true,
        noUnusedLocals: true,
        noUnusedParameters: true,
        noImplicitReturns: true,
        noImplicitThis: true,
        noUncheckedIndexedAccess: true,
        noPropertyAccessFromIndexSignature: true,
        module: ts.ModuleKind.Node16,
        strict: true,
    };
    test("./messages.js types", async () => {
        const project = await typescriptProject({
            useInMemoryFileSystem: true,
            compilerOptions: superStrictRuleOutAnyErrorTsSettings,
        });
        for (const [fileName, code] of Object.entries(output)) {
            if (fileName.endsWith(".js") || fileName.endsWith(".ts")) {
                project.createSourceFile(fileName, code);
            }
        }
        project.createSourceFile("test.ts", `
    import * as m from "./messages.js"

    // --------- MESSAGES ---------

    // the return value of a message should be a string
    m.insane_cats({ name: "John", count: 5 }) satisfies string
      
    // @ts-expect-error - missing all params
    m.insane_cats()
      
    // @ts-expect-error - one param missing
    m.insane_cats({ name: "John" })

    // a message without params shouldn't require params
    m.sad_penguin_bundle() satisfies string

    // dynamic relativetime unit inputs stay consistent with other formatter inputs
    m.relative_time_literal({ duration: -1 }) satisfies string
    m.relative_time_dynamic({ duration: -3, unit: "hour" }) satisfies string
    m.relative_time_dynamic({ duration: -3, unit: "not-a-relative-time-unit" }) satisfies string

		// --------- MATCH TYPE INFERENCE ---------
		// known match values should be accepted
		m.auth_password_error({ type: "invalid" }) satisfies string
		m.auth_password_error({ type: "empty" }) satisfies string
		m.auth_password_error({ type: "min_length" }) satisfies string
		m.auth_password_error({ type: "secure" }) satisfies string

		// @ts-expect-error - unknown match value
		m.auth_password_error({ type: "typo" })

		// quoted keys should still get literal unions
		m.error_with_dash({ "error-type": "network" }) satisfies string
		m.error_with_dash({ "error-type": "timeout" }) satisfies string

		// @ts-expect-error - unknown match value for quoted key
		m.error_with_dash({ "error-type": "offline" })

		// catchall should widen match value typing
		m.auth_password_error_catchall({ type: "invalid" }) satisfies string
		m.auth_password_error_catchall({ type: "typo" }) satisfies string

		// multiple match keys should each get literal unions
		m.multi_key_status_type({ type: "invalid", status: "ready" }) satisfies string
		m.multi_key_status_type({ type: "empty", status: "done" }) satisfies string
		m.multi_key_status_type({ type: "secure", status: "failed" }) satisfies string

		// @ts-expect-error - invalid type literal
		m.multi_key_status_type({ type: "typo", status: "ready" })

		// @ts-expect-error - invalid status literal
		m.multi_key_status_type({ type: "invalid", status: "pending" })

		// numeric matches should accept both number and string forms
		m.numeric_input_match({ input: 1 }) satisfies string
		m.numeric_input_match({ input: 2 }) satisfies string
		m.numeric_input_match({ input: "1" }) satisfies string
		m.numeric_input_match({ input: "2" }) satisfies string

		// @ts-expect-error - invalid numeric literal
		m.numeric_input_match({ input: 3 })

		// @ts-expect-error - invalid string literal
		m.numeric_input_match({ input: "3" })

		// @ts-expect-error - no loose boolean coercion
		m.numeric_input_match({ input: true })

		// empty matches should widen to NonNullable<unknown>
		m.empty_matches_catchall({ type: "invalid" }) satisfies string
		m.empty_matches_catchall({ type: "typo" }) satisfies string

		// --------- MESSAGE OPTIONS ---------
		// the locale option should be optional
		m.sad_penguin_bundle({}, {}) satisfies string

		// the locale option should be allowed
		m.sad_penguin_bundle({}, { locale: "en" }) satisfies string

		// the locale option must be a valid language tag
		// @ts-expect-error - invalid language tag
		m.sad_penguin_bundle({}, { locale: "---" })
  `);
        const program = project.createProgram();
        const diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {
            // async_hooks is a node module that is not available in the browser
            return !d.messageText
                .toString()
                .includes("Cannot find module 'async_hooks'");
        });
        for (const diagnostic of diagnostics) {
            console.error(diagnostic.messageText, diagnostic.file?.fileName);
        }
        expect(diagnostics.length).toEqual(0);
    });
    test("./messages.js types (single locale should not emit TS6133)", async () => {
        const singleLocaleProject = await loadProjectInMemory({
            blob: await newProject({
                settings: { locales: ["en"], baseLocale: "en" },
            }),
        });
        await insertBundleNested(singleLocaleProject.db, createBundleNested({
            id: "single_locale_message",
            messages: [
                {
                    locale: "en",
                    variants: [{ pattern: [{ type: "text", value: "Hello" }] }],
                },
            ],
        }));
        const singleLocaleOutput = await compileProject({
            project: singleLocaleProject,
            compilerOptions,
        });
        const project = await typescriptProject({
            useInMemoryFileSystem: true,
            compilerOptions: superStrictRuleOutAnyErrorTsSettings,
        });
        for (const [fileName, code] of Object.entries(singleLocaleOutput)) {
            if (fileName.endsWith(".js") || fileName.endsWith(".ts")) {
                project.createSourceFile(fileName, code);
            }
        }
        project.createSourceFile("test.ts", `import { single_locale_message } from "./messages.js"; single_locale_message();`);
        const program = project.createProgram();
        const diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {
            return !d.messageText
                .toString()
                .includes("Cannot find module 'async_hooks'");
        });
        const ts6133LocaleDiagnostics = diagnostics.filter((d) => {
            return (d.code === 6133 &&
                d.messageText
                    .toString()
                    .includes("'locale' is declared but its value is never read"));
        });
        expect(ts6133LocaleDiagnostics.length).toEqual(0);
    });
    test("#625: number formatter digit options should pass checkJs", async () => {
        const projectWithNumberOptions = await loadProjectInMemory({
            blob: await newProject({
                settings: {
                    baseLocale: "en",
                    locales: ["en"],
                },
            }),
        });
        await insertBundleNested(projectWithNumberOptions.db, createBundleNested({
            id: "formatted_percentage",
            declarations: [
                {
                    type: "input-variable",
                    name: "value",
                },
                {
                    type: "local-variable",
                    name: "formattedValue",
                    value: {
                        type: "expression",
                        arg: { type: "variable-reference", name: "value" },
                        annotation: {
                            type: "function-reference",
                            name: "number",
                            options: [
                                {
                                    name: "minimumFractionDigits",
                                    value: { type: "literal", value: "1" },
                                },
                                {
                                    name: "maximumFractionDigits",
                                    value: { type: "literal", value: "1" },
                                },
                            ],
                        },
                    },
                },
            ],
            messages: [
                {
                    locale: "en",
                    variants: [
                        {
                            pattern: [
                                {
                                    type: "expression",
                                    arg: {
                                        type: "variable-reference",
                                        name: "formattedValue",
                                    },
                                },
                                { type: "text", value: "%" },
                            ],
                        },
                    ],
                },
            ],
        }));
        const output = await compileProject({
            project: projectWithNumberOptions,
            compilerOptions,
        });
        const tsProject = await typescriptProject({
            useInMemoryFileSystem: true,
            compilerOptions: superStrictRuleOutAnyErrorTsSettings,
        });
        for (const [fileName, code] of Object.entries(output)) {
            if (fileName.endsWith(".js") || fileName.endsWith(".ts")) {
                tsProject.createSourceFile(fileName, code);
            }
        }
        const program = tsProject.createProgram();
        const diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {
            return !d.messageText
                .toString()
                .includes("Cannot find module 'async_hooks'");
        });
        for (const diagnostic of diagnostics) {
            console.error(diagnostic.messageText, diagnostic.file?.fileName);
        }
        expect(diagnostics.length).toEqual(0);
    });
    test("#694: pattern-level annotations compile to registry calls and pass checkJs", async () => {
        const projectWithPatternAnnotation = await loadProjectInMemory({
            blob: await newProject({
                settings: {
                    baseLocale: "en",
                    locales: ["en"],
                },
            }),
        });
        await insertBundleNested(projectWithPatternAnnotation.db, createBundleNested({
            id: "views",
            declarations: [
                {
                    type: "input-variable",
                    name: "count",
                },
            ],
            messages: [
                {
                    locale: "en",
                    variants: [
                        {
                            pattern: [
                                {
                                    type: "expression",
                                    arg: { type: "variable-reference", name: "count" },
                                    annotation: {
                                        type: "function-reference",
                                        name: "number",
                                        options: [
                                            {
                                                name: "maximumFractionDigits",
                                                value: { type: "literal", value: "1" },
                                            },
                                        ],
                                    },
                                },
                                { type: "text", value: " views" },
                            ],
                        },
                    ],
                },
            ],
        }));
        const output = await compileProject({
            project: projectWithPatternAnnotation,
            compilerOptions,
        });
        expect(Object.values(output).join("\n")).toContain('registry.number("en", i?.count, { maximumFractionDigits: 1 })');
        const tsProject = await typescriptProject({
            useInMemoryFileSystem: true,
            compilerOptions: superStrictRuleOutAnyErrorTsSettings,
        });
        for (const [fileName, code] of Object.entries(output)) {
            if (fileName.endsWith(".js") || fileName.endsWith(".ts")) {
                tsProject.createSourceFile(fileName, code);
            }
        }
        const program = tsProject.createProgram();
        const diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {
            return !d.messageText
                .toString()
                .includes("Cannot find module 'async_hooks'");
        });
        for (const diagnostic of diagnostics) {
            console.error(diagnostic.messageText, diagnostic.file?.fileName);
        }
        expect(diagnostics.length).toEqual(0);
    });
    test("relativetime formatter options should pass checkJs", async () => {
        const projectWithRelativeTimeOptions = await loadProjectInMemory({
            blob: await newProject({
                settings: {
                    baseLocale: "en",
                    locales: ["en"],
                },
            }),
        });
        await insertBundleNested(projectWithRelativeTimeOptions.db, createBundleNested({
            id: "formatted_relative_time",
            declarations: [
                {
                    type: "input-variable",
                    name: "duration",
                },
                {
                    type: "input-variable",
                    name: "unit",
                },
                {
                    type: "local-variable",
                    name: "formattedDuration",
                    value: {
                        type: "expression",
                        arg: { type: "variable-reference", name: "duration" },
                        annotation: {
                            type: "function-reference",
                            name: "relativetime",
                            options: [
                                {
                                    name: "unit",
                                    value: { type: "variable-reference", name: "unit" },
                                },
                                {
                                    name: "style",
                                    value: { type: "literal", value: "short" },
                                },
                            ],
                        },
                    },
                },
            ],
            messages: [
                {
                    locale: "en",
                    variants: [
                        {
                            pattern: [
                                {
                                    type: "expression",
                                    arg: {
                                        type: "variable-reference",
                                        name: "formattedDuration",
                                    },
                                },
                            ],
                        },
                    ],
                },
            ],
        }));
        const output = await compileProject({
            project: projectWithRelativeTimeOptions,
            compilerOptions,
        });
        const tsProject = await typescriptProject({
            useInMemoryFileSystem: true,
            compilerOptions: superStrictRuleOutAnyErrorTsSettings,
        });
        for (const [fileName, code] of Object.entries(output)) {
            if (fileName.endsWith(".js") || fileName.endsWith(".ts")) {
                tsProject.createSourceFile(fileName, code);
            }
        }
        const program = tsProject.createProgram();
        const diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {
            return !d.messageText
                .toString()
                .includes("Cannot find module 'async_hooks'");
        });
        for (const diagnostic of diagnostics) {
            console.error(diagnostic.messageText, diagnostic.file?.fileName);
        }
        expect(diagnostics.length).toEqual(0);
    });
});
async function bundleCode(output, file) {
    output["runtime.js"] = output["runtime.js"].replace('import "@inlang/paraglide-js/urlpattern-polyfill";', "/** @type {any} */const URLPattern = {};").replace('const { AsyncLocalStorage } = await import("async_hooks");', "const AsyncLocalStorage = class {};");
    const bundle = await rolldown({
        input: ["main.js"],
        plugins: [
            // @ts-expect-error - rollup types are not up to date
            virtual({
                ...Object.fromEntries(Object.entries(output).map(([fileName, code]) => [
                    "paraglide/" + fileName,
                    code,
                ])),
                "main.js": file,
            }),
        ],
    });
    const compiled = await bundle.generate({ format: "esm" });
    const code = compiled.output[0].code;
    return code;
}
async function importCode(code) {
    // The random comment ensures that each code is a unique module
    // and state is not shared between imports
    const randomComment = `// ${Math.random()}`;
    const codeWithComment = `${randomComment}\n${code}`;
    return await import(`data:application/javascript;base64,${Buffer.from(codeWithComment, "utf8").toString("base64")}`);
}
const project = await loadProjectInMemory({
    blob: await newProject({
        settings: {
            baseLocale: "en",
            locales: ["en", "de", "en-US"],
        },
    }),
});
const mockBundles = [
    createBundleNested({
        id: "sad_penguin_bundle",
        messages: [
            {
                locale: "en",
                variants: [{ pattern: [{ type: "text", value: "A simple message." }] }],
            },
            {
                locale: "de",
                variants: [
                    { pattern: [{ type: "text", value: "Eine einfache Nachricht." }] },
                ],
            },
        ],
    }),
    createBundleNested({
        id: "Sad_penguin_bundle",
        messages: [
            {
                locale: "en",
                variants: [
                    { pattern: [{ type: "text", value: "Capital Sad penguin" }] },
                ],
            },
            {
                locale: "de",
                variants: [
                    {
                        pattern: [
                            { type: "text", value: "Grossgeschriebenes Sad penguin" },
                        ],
                    },
                ],
            },
        ],
    }),
    {
        id: "depressed_dog",
        declarations: [
            {
                type: "input-variable",
                name: "name",
            },
        ],
        messages: [
            {
                id: "depressed_dog_en",
                bundleId: "depressed_dog",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "depressed_dog_en_variant_one",
                        messageId: "depressed_dog_en",
                        matches: [],
                        pattern: [
                            { type: "text", value: "Good morning " },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "name" },
                            },
                            { type: "text", value: "!" },
                        ],
                    },
                ],
            },
            {
                id: "depressed_dog_de",
                bundleId: "depressed_dog",
                locale: "de",
                selectors: [],
                variants: [
                    {
                        id: "depressed_dog_de_variant_one",
                        messageId: "depressed_dog_de",
                        matches: [],
                        pattern: [
                            { type: "text", value: "Guten Morgen " },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "name" },
                            },
                            { type: "text", value: "!" },
                        ],
                    },
                ],
            },
        ],
    },
    {
        id: "insane_cats",
        declarations: [
            {
                type: "input-variable",
                name: "name",
            },
            {
                type: "input-variable",
                name: "count",
            },
        ],
        messages: [
            {
                id: "insane_cats_en",
                bundleId: "insane_cats",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "insane_cats_en_variant_one",
                        messageId: "insane_cats_en",
                        matches: [],
                        pattern: [
                            { type: "text", value: "Hello " },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "name" },
                            },
                            { type: "text", value: "! You have " },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "count" },
                            },
                            { type: "text", value: " messages." },
                        ],
                    },
                ],
            },
            {
                id: "insane_cats_de",
                bundleId: "insane_cats",
                locale: "de",
                selectors: [],
                variants: [
                    {
                        id: "insane_cats_de_variant_one",
                        messageId: "insane_cats_de",
                        matches: [],
                        pattern: [
                            { type: "text", value: "Hallo " },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "name" },
                            },
                            { type: "text", value: "! Du hast " },
                            {
                                type: "expression",
                                arg: { type: "variable-reference", name: "count" },
                            },
                            { type: "text", value: " Nachrichten." },
                        ],
                    },
                ],
            },
        ],
    },
    createBundleNested({
        id: "relative_time_literal",
        declarations: [
            {
                type: "input-variable",
                name: "duration",
            },
            {
                type: "local-variable",
                name: "formattedDuration",
                value: {
                    type: "expression",
                    arg: { type: "variable-reference", name: "duration" },
                    annotation: {
                        type: "function-reference",
                        name: "relativetime",
                        options: [
                            { name: "unit", value: { type: "literal", value: "day" } },
                            { name: "numeric", value: { type: "literal", value: "auto" } },
                        ],
                    },
                },
            },
        ],
        messages: [
            {
                locale: "en",
                variants: [
                    {
                        pattern: [
                            { type: "text", value: "Updated " },
                            {
                                type: "expression",
                                arg: {
                                    type: "variable-reference",
                                    name: "formattedDuration",
                                },
                            },
                            { type: "text", value: "." },
                        ],
                    },
                ],
            },
            {
                locale: "de",
                variants: [
                    {
                        pattern: [
                            { type: "text", value: "Updated " },
                            {
                                type: "expression",
                                arg: {
                                    type: "variable-reference",
                                    name: "formattedDuration",
                                },
                            },
                            { type: "text", value: "." },
                        ],
                    },
                ],
            },
        ],
    }),
    createBundleNested({
        id: "relative_time_dynamic",
        declarations: [
            {
                type: "input-variable",
                name: "duration",
            },
            {
                type: "input-variable",
                name: "unit",
            },
            {
                type: "local-variable",
                name: "formattedDuration",
                value: {
                    type: "expression",
                    arg: { type: "variable-reference", name: "duration" },
                    annotation: {
                        type: "function-reference",
                        name: "relativetime",
                        options: [
                            {
                                name: "unit",
                                value: { type: "variable-reference", name: "unit" },
                            },
                            { name: "style", value: { type: "literal", value: "short" } },
                        ],
                    },
                },
            },
        ],
        messages: [
            {
                locale: "en",
                variants: [
                    {
                        pattern: [
                            { type: "text", value: "Updated " },
                            {
                                type: "expression",
                                arg: {
                                    type: "variable-reference",
                                    name: "formattedDuration",
                                },
                            },
                            { type: "text", value: "." },
                        ],
                    },
                ],
            },
            {
                locale: "de",
                variants: [
                    {
                        pattern: [
                            { type: "text", value: "Updated " },
                            {
                                type: "expression",
                                arg: {
                                    type: "variable-reference",
                                    name: "formattedDuration",
                                },
                            },
                            { type: "text", value: "." },
                        ],
                    },
                ],
            },
        ],
    }),
    {
        id: "auth_password_error",
        declarations: [
            {
                type: "input-variable",
                name: "type",
            },
        ],
        messages: [
            {
                id: "auth_password_error_en",
                bundleId: "auth_password_error",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "auth_password_error_en_variant_invalid",
                        messageId: "auth_password_error_en",
                        matches: [{ type: "literal-match", key: "type", value: "invalid" }],
                        pattern: [
                            {
                                type: "text",
                                value: "The password provided is not valid",
                            },
                        ],
                    },
                    {
                        id: "auth_password_error_en_variant_empty",
                        messageId: "auth_password_error_en",
                        matches: [{ type: "literal-match", key: "type", value: "empty" }],
                        pattern: [
                            {
                                type: "text",
                                value: "You must provide a password",
                            },
                        ],
                    },
                    {
                        id: "auth_password_error_en_variant_min",
                        messageId: "auth_password_error_en",
                        matches: [
                            {
                                type: "literal-match",
                                key: "type",
                                value: "min_length",
                            },
                        ],
                        pattern: [
                            {
                                type: "text",
                                value: "Your password is not secure enough",
                            },
                        ],
                    },
                    {
                        id: "auth_password_error_en_variant_secure",
                        messageId: "auth_password_error_en",
                        matches: [{ type: "literal-match", key: "type", value: "secure" }],
                        pattern: [
                            {
                                type: "text",
                                value: "Your password is not secure enough",
                            },
                        ],
                    },
                ],
            },
        ],
    },
    {
        id: "error_with_dash",
        declarations: [
            {
                type: "input-variable",
                name: "error-type",
            },
        ],
        messages: [
            {
                id: "error_with_dash_en",
                bundleId: "error_with_dash",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "error_with_dash_en_variant_network",
                        messageId: "error_with_dash_en",
                        matches: [
                            { type: "literal-match", key: "error-type", value: "network" },
                        ],
                        pattern: [{ type: "text", value: "Network error" }],
                    },
                    {
                        id: "error_with_dash_en_variant_timeout",
                        messageId: "error_with_dash_en",
                        matches: [
                            {
                                type: "literal-match",
                                key: "error-type",
                                value: "timeout",
                            },
                        ],
                        pattern: [{ type: "text", value: "Timeout error" }],
                    },
                ],
            },
        ],
    },
    {
        id: "auth_password_error_catchall",
        declarations: [
            {
                type: "input-variable",
                name: "type",
            },
        ],
        messages: [
            {
                id: "auth_password_error_catchall_en",
                bundleId: "auth_password_error_catchall",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "auth_password_error_catchall_en_variant_invalid",
                        messageId: "auth_password_error_catchall_en",
                        matches: [{ type: "literal-match", key: "type", value: "invalid" }],
                        pattern: [
                            {
                                type: "text",
                                value: "The password provided is not valid",
                            },
                        ],
                    },
                    {
                        id: "auth_password_error_catchall_en_variant_catchall",
                        messageId: "auth_password_error_catchall_en",
                        matches: [{ type: "catchall-match", key: "type" }],
                        pattern: [
                            {
                                type: "text",
                                value: "Unknown password error",
                            },
                        ],
                    },
                ],
            },
        ],
    },
    {
        id: "multi_key_status_type",
        declarations: [
            {
                type: "input-variable",
                name: "type",
            },
            {
                type: "input-variable",
                name: "status",
            },
        ],
        messages: [
            {
                id: "multi_key_status_type_en",
                bundleId: "multi_key_status_type",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "multi_key_status_type_en_variant_ready_invalid",
                        messageId: "multi_key_status_type_en",
                        matches: [
                            { type: "literal-match", key: "type", value: "invalid" },
                            { type: "literal-match", key: "status", value: "ready" },
                        ],
                        pattern: [{ type: "text", value: "Ready invalid" }],
                    },
                    {
                        id: "multi_key_status_type_en_variant_done_empty",
                        messageId: "multi_key_status_type_en",
                        matches: [
                            { type: "literal-match", key: "type", value: "empty" },
                            { type: "literal-match", key: "status", value: "done" },
                        ],
                        pattern: [{ type: "text", value: "Done empty" }],
                    },
                    {
                        id: "multi_key_status_type_en_variant_failed_secure",
                        messageId: "multi_key_status_type_en",
                        matches: [
                            { type: "literal-match", key: "type", value: "secure" },
                            { type: "literal-match", key: "status", value: "failed" },
                        ],
                        pattern: [{ type: "text", value: "Failed secure" }],
                    },
                ],
            },
        ],
    },
    {
        id: "empty_matches_catchall",
        declarations: [
            {
                type: "input-variable",
                name: "type",
            },
        ],
        messages: [
            {
                id: "empty_matches_catchall_en",
                bundleId: "empty_matches_catchall",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "empty_matches_catchall_en_variant_invalid",
                        messageId: "empty_matches_catchall_en",
                        matches: [{ type: "literal-match", key: "type", value: "invalid" }],
                        pattern: [{ type: "text", value: "Invalid" }],
                    },
                    {
                        id: "empty_matches_catchall_en_variant_any",
                        messageId: "empty_matches_catchall_en",
                        matches: [],
                        pattern: [{ type: "text", value: "Any" }],
                    },
                ],
            },
        ],
    },
    {
        id: "numeric_input_match",
        declarations: [
            {
                type: "input-variable",
                name: "input",
            },
        ],
        messages: [
            {
                id: "numeric_input_match_en",
                bundleId: "numeric_input_match",
                locale: "en",
                selectors: [],
                variants: [
                    {
                        id: "numeric_input_match_en_variant_1",
                        messageId: "numeric_input_match_en",
                        matches: [{ type: "literal-match", key: "input", value: "1" }],
                        pattern: [{ type: "text", value: "Thing 1" }],
                    },
                    {
                        id: "numeric_input_match_en_variant_2",
                        messageId: "numeric_input_match_en",
                        matches: [{ type: "literal-match", key: "input", value: "2" }],
                        pattern: [{ type: "text", value: "Thing 2" }],
                    },
                ],
            },
        ],
    },
];
for (const bundle of mockBundles) {
    await insertBundleNested(project.db, bundle);
}
function createBundleNested(args) {
    return {
        id: args.id,
        declarations: args.declarations ?? [],
        messages: args.messages.map((message) => ({
            id: args.id + "_" + message.locale,
            bundleId: args.id,
            locale: message.locale,
            selectors: message.selectors ?? [],
            variants: message.variants.map((variant) => ({
                id: args.id +
                    "_" +
                    message.locale +
                    "_" +
                    Math.random().toString(36).slice(2),
                messageId: args.id,
                matches: variant.matches ?? [],
                pattern: variant.pattern,
            })),
        })),
    };
}
//# sourceMappingURL=compile-project.test.js.map