import { describe, it, expect, afterEach } from "vitest";
import { readFileSync, existsSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { writePerModuleRouteTypesForFile, writeCombinedRouteTypes, } from "../generate-route-types";
const fixtureDir = join(__dirname, "__fixtures__", "app");
// Track generated files for cleanup
const generatedFiles = [];
function genPathFor(filePath) {
    return filePath.replace(/\.tsx?$/, ".gen.ts");
}
function namedRoutesGenPathFor(filePath) {
    return filePath.replace(/\.tsx?$/, ".named-routes.gen.ts");
}
afterEach(() => {
    for (const f of generatedFiles) {
        try {
            if (existsSync(f))
                unlinkSync(f);
        }
        catch { }
    }
    generatedFiles.length = 0;
});
describe("generate-cli e2e fixtures", () => {
    // -----------------------------------------------------------------------
    // Per-module generation
    // -----------------------------------------------------------------------
    describe("writePerModuleRouteTypesForFile", () => {
        it("generates correct routes for main urls module with includes", () => {
            const mainUrls = join(fixtureDir, "urls.tsx");
            writePerModuleRouteTypesForFile(mainUrls);
            const genFile = genPathFor(mainUrls);
            generatedFiles.push(genFile);
            expect(existsSync(genFile)).toBe(true);
            const content = readFileSync(genFile, "utf-8");
            // Header
            expect(content).toMatch(/^\/\/ Auto-generated by @rangojs\/router/);
            // Local routes
            expect(content).toContain('home: "/",');
            expect(content).toContain('about: "/about",');
            // API include (prefixed names and patterns)
            expect(content).toContain('"api.health"');
            expect(content).toContain("/api/health");
            expect(content).toContain('"api.detail"');
            expect(content).toContain("/api/:id");
            expect(content).toContain('"api.search"');
            expect(content).toContain("/api/search");
            // Docs include
            expect(content).toContain('"docs.page"');
            expect(content).toContain("/docs/:slug");
            // 3-level deep include (app -> docs -> guides)
            expect(content).toContain('"docs.guides.index"');
            expect(content).toContain("/docs/guides");
            expect(content).toContain('"docs.guides.detail"');
            expect(content).toContain("/docs/guides/:guideId");
        });
        it("generates only local routes for a sub-module", () => {
            const apiUrls = join(fixtureDir, "api", "urls.tsx");
            writePerModuleRouteTypesForFile(apiUrls);
            const genFile = genPathFor(apiUrls);
            generatedFiles.push(genFile);
            expect(existsSync(genFile)).toBe(true);
            const content = readFileSync(genFile, "utf-8");
            // API routes without prefix (module-level view)
            expect(content).toContain('health: "/health",');
            expect(content).toContain("detail:");
            expect(content).toContain("/:id");
            expect(content).toContain("search:");
            expect(content).toContain("/search");
            // Should NOT contain any prefixed names from parent
            expect(content).not.toContain("api.");
            expect(content).not.toContain("home");
            expect(content).not.toContain("about");
        });
        it("keeps parameterized patterns as plain strings (params extracted at type level)", () => {
            const apiUrls = join(fixtureDir, "api", "urls.tsx");
            writePerModuleRouteTypesForFile(apiUrls);
            const genFile = genPathFor(apiUrls);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // /:id stays as plain string — ExtractParams resolves params at the type level
            expect(content).toContain('detail: "/:id",');
        });
        it("extracts search schemas", () => {
            const apiUrls = join(fixtureDir, "api", "urls.tsx");
            writePerModuleRouteTypesForFile(apiUrls);
            const genFile = genPathFor(apiUrls);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // search route should have search: { q: "string", page: "number?" }
            expect(content).toContain('search: { q: "string", page: "number?" }');
        });
        it("handles 3-level deep includes (app -> docs -> guides)", () => {
            const mainUrls = join(fixtureDir, "urls.tsx");
            writePerModuleRouteTypesForFile(mainUrls);
            const genFile = genPathFor(mainUrls);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // Guides routes should have chained prefixes
            expect(content).toContain('"docs.guides.index"');
            expect(content).toContain('"docs.guides.detail"');
            // Patterns should have chained path prefixes
            expect(content).toContain("/docs/guides");
            expect(content).toContain("/docs/guides/:guideId");
        });
        it("preserves param patterns in deeply nested routes", () => {
            const mainUrls = join(fixtureDir, "urls.tsx");
            writePerModuleRouteTypesForFile(mainUrls);
            const genFile = genPathFor(mainUrls);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // docs.page has /:slug in the pattern string
            expect(content).toContain('"docs.page": "/docs/:slug",');
            // docs.guides.detail has /:guideId in the pattern string
            expect(content).toContain('"docs.guides.detail": "/docs/guides/:guideId",');
        });
        it("propagates search schemas through includes", () => {
            const mainUrls = join(fixtureDir, "urls.tsx");
            writePerModuleRouteTypesForFile(mainUrls);
            const genFile = genPathFor(mainUrls);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // api.search should carry its search schema through the include
            expect(content).toContain('"api.search"');
            expect(content).toContain('q: "string"');
            expect(content).toContain('page: "number?"');
        });
        it("generates routes for the docs sub-module with nested include", () => {
            const docsUrls = join(fixtureDir, "docs", "urls.tsx");
            writePerModuleRouteTypesForFile(docsUrls);
            const genFile = genPathFor(docsUrls);
            generatedFiles.push(genFile);
            expect(existsSync(genFile)).toBe(true);
            const content = readFileSync(genFile, "utf-8");
            // Local route
            expect(content).toContain("page:");
            expect(content).toContain("/:slug");
            // Included guides routes (one level of prefix)
            expect(content).toContain('"guides.index"');
            expect(content).toContain("/guides");
            expect(content).toContain('"guides.detail"');
            expect(content).toContain("/guides/:guideId");
        });
        it("generates routes for the leaf guides module", () => {
            const guidesUrls = join(fixtureDir, "docs", "guides", "urls.tsx");
            writePerModuleRouteTypesForFile(guidesUrls);
            const genFile = genPathFor(guidesUrls);
            generatedFiles.push(genFile);
            expect(existsSync(genFile)).toBe(true);
            const content = readFileSync(genFile, "utf-8");
            // Leaf module routes without any prefix
            expect(content).toContain('index: "/",');
            expect(content).toContain('detail: "/:guideId",');
            // Should not contain parent prefixes
            expect(content).not.toContain("docs.");
            expect(content).not.toContain("guides.");
        });
    });
    // -----------------------------------------------------------------------
    // Named-routes generation for router files
    // -----------------------------------------------------------------------
    describe("writeCombinedRouteTypes (router file)", () => {
        it("generates named-routes.gen.ts with full merged route map", () => {
            const routerFile = join(fixtureDir, "router.tsx");
            writeCombinedRouteTypes(fixtureDir, [routerFile]);
            const genFile = namedRoutesGenPathFor(routerFile);
            generatedFiles.push(genFile);
            expect(existsSync(genFile)).toBe(true);
            const content = readFileSync(genFile, "utf-8");
            // Header
            expect(content).toMatch(/^\/\/ Auto-generated by @rangojs\/router/);
            // Global augmentation
            expect(content).toContain("declare global");
            expect(content).toContain("namespace RSCRouter");
            expect(content).toContain("GeneratedRouteMap");
            expect(content).toContain("NamedRoutes");
            // All routes from the full tree
            expect(content).toContain('home: "/"');
            expect(content).toContain('about: "/about"');
            expect(content).toContain("/api/health");
            expect(content).toContain("/api/:id");
            expect(content).toContain("/api/search");
            expect(content).toContain("/docs/:slug");
            expect(content).toContain("/docs/guides");
            expect(content).toContain("/docs/guides/:guideId");
        });
        it("includes search schemas and param patterns in named-routes output", () => {
            const routerFile = join(fixtureDir, "router.tsx");
            writeCombinedRouteTypes(fixtureDir, [routerFile]);
            const genFile = namedRoutesGenPathFor(routerFile);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // Parameterized routes are plain strings (params extracted at type level)
            expect(content).toContain('"api.detail": "/api/:id",');
            expect(content).toContain('"docs.page": "/docs/:slug",');
            expect(content).toContain('"docs.guides.detail": "/docs/guides/:guideId",');
            // api.search has search schema (object format)
            expect(content).toContain('q: "string"');
            expect(content).toContain('page: "number?"');
        });
        it("sorts routes alphabetically by name", () => {
            const routerFile = join(fixtureDir, "router.tsx");
            writeCombinedRouteTypes(fixtureDir, [routerFile]);
            const genFile = namedRoutesGenPathFor(routerFile);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // Extract route names in order from the generated file
            const nameMatches = [...content.matchAll(/^\s+(?:"([^"]+)"|(\w+)):/gm)];
            const names = nameMatches.map((m) => m[1] ?? m[2]);
            // Verify sorted order
            const sorted = [...names].sort((a, b) => a.localeCompare(b));
            expect(names).toEqual(sorted);
        });
    });
    // -----------------------------------------------------------------------
    // Deduplication
    // -----------------------------------------------------------------------
    describe("first-wins dedup", () => {
        it("keeps first definition when route name appears twice", () => {
            // The main urls module includes api which has a "detail" route.
            // The docs module also has a "page" route. These are distinct names.
            // To test dedup, generate for api/urls which has unique names.
            const apiUrls = join(fixtureDir, "api", "urls.tsx");
            writePerModuleRouteTypesForFile(apiUrls);
            const genFile = genPathFor(apiUrls);
            generatedFiles.push(genFile);
            const content = readFileSync(genFile, "utf-8");
            // Each name should appear exactly once in the routes object
            const detailMatches = content.match(/\bdetail\b/g);
            // "detail" appears in the route entry key and potentially in the path/params
            // but the key itself should only appear once as a route definition
            const detailKeyMatches = content.match(/^\s+detail:/gm);
            expect(detailKeyMatches).toHaveLength(1);
        });
    });
});
//# sourceMappingURL=generate-cli.test.js.map