// No external mocks needed — the extractor is pure fs + TS Compiler API.
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { extractSourceRoutes } from "./sourceRouteExtractor.js";
function makeTmpRepo(files) {
    const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-route-test-"));
    for (const f of files) {
        const fullPath = path.join(dir, f.path);
        fs.mkdirSync(path.dirname(fullPath), { recursive: true });
        fs.writeFileSync(fullPath, f.content);
    }
    return dir;
}
function cleanup(dir) {
    fs.rmSync(dir, { recursive: true, force: true });
}
// ── Tests ──────────────────────────────────────────────────────────────────
describe("extractSourceRoutes", () => {
    it("returns empty when no router files found", () => {
        const repo = makeTmpRepo([
            {
                path: "src/components/Button.tsx",
                content: "export default function Button() { return <button/>; }",
            },
        ]);
        expect(extractSourceRoutes(repo)).toEqual([]);
        cleanup(repo);
    });
    it("extracts a single React Router Route with element prop", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Routes, Route } from 'react-router-dom';
import Cart from './pages/Cart';

export default function App() {
  return (
    <Routes>
      <Route path="/cart" element={<Cart />} />
    </Routes>
  );
}
`,
            },
            {
                path: "src/pages/Cart.tsx",
                content: "export default function Cart() { return <div/>; }",
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0]).toMatchObject({
            path: "/cart",
            componentName: "Cart",
        });
        expect(routes[0].componentFile).toMatch(/src\/pages\/Cart\.tsx$/);
        cleanup(repo);
    });
    it("extracts multiple Routes from one file", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Routes, Route } from 'react-router-dom';
import Cart from './pages/Cart';
import Orders from './pages/Orders';
import ProductDetail from './pages/ProductDetail';

export default function App() {
  return (
    <Routes>
      <Route path="/cart" element={<Cart />} />
      <Route path="/orders" element={<Orders />} />
      <Route path="/products/:id" element={<ProductDetail />} />
    </Routes>
  );
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(3);
        expect(routes.map((r) => r.path).sort()).toEqual([
            "/cart",
            "/orders",
            "/products/:id",
        ]);
        cleanup(repo);
    });
    it("extracts Route with component prop (alternate syntax)", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Cart from './pages/Cart';

export default function App() {
  return <Route path="/cart" component={Cart} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/cart");
        expect(routes[0].componentName).toBe("Cart");
    });
    it("recognizes SentryRoute and other configured wrapper components", () => {
        const repo = makeTmpRepo([
            {
                path: "src/AppRouter.tsx",
                content: `
import { Switch } from 'react-router-dom';
import { SentryRoute } from './components/SentryRoute';
import Workspaces from './pages/Workspaces';

export default function AppRouter() {
  return (
    <Switch>
      <SentryRoute path="/workspaces" component={Workspaces} />
    </Switch>
  );
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/workspaces");
        expect(routes[0].componentName).toBe("Workspaces");
    });
    it("resolves identifier-valued path attribute via local const (basic case)", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Workspaces from './pages/Workspaces';

const WORKSPACE_URL = "/workspace";

export default function App() {
  return <Route path={WORKSPACE_URL} element={<Workspaces />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/workspace");
    });
    it("skips Route with template literal path whose parts are not module-scoped", () => {
        // Const declarations inside a function aren't visible to the resolver
        // (we only walk module-scope consts). The route is correctly skipped.
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Foo from './pages/Foo';

export default function App() {
  const prefix = '/admin';
  return <Route path={\`\${prefix}/foo\`} element={<Foo />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toEqual([]);
    });
    it("skips Route without a path attribute", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Foo from './pages/Foo';

export default function App() {
  return <Route element={<Foo />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toEqual([]);
    });
    it("resolves component file path via relative import", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Cart from './pages/Cart';

export default function App() {
  return <Route path="/cart" element={<Cart />} />;
}
`,
            },
            {
                path: "src/pages/Cart.tsx",
                content: "export default function Cart() { return <div/>; }",
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes[0].componentFile).toMatch(/src\/pages\/Cart\.tsx$/);
        cleanup(repo);
    });
    it("leaves componentFile undefined when import not found", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
// Cart referenced but not imported in this file
export default function App() {
  return <Route path="/cart" element={<Cart />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].componentName).toBe("Cart");
        expect(routes[0].componentFile).toBeUndefined();
        cleanup(repo);
    });
    it("ignores non-Route JSX elements", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Routes, Route, Link } from 'react-router-dom';
import Cart from './pages/Cart';

export default function App() {
  return (
    <Routes>
      <Link to="/somewhere">Click</Link>
      <Route path="/cart" element={<Cart />} />
    </Routes>
  );
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/cart");
        cleanup(repo);
    });
    it("discovers routes across multiple candidate router files", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Foo from './pages/Foo';
export default function App() {
  return <Route path="/foo" element={<Foo />} />;
}
`,
            },
            {
                path: "src/router.tsx",
                content: `
import { Route } from 'react-router-dom';
import Bar from './pages/Bar';
export const Bar2 = <Route path="/bar" element={<Bar />} />;
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes.map((r) => r.path).sort()).toEqual(["/bar", "/foo"]);
        cleanup(repo);
    });
    it("resolves import with explicit .tsx extension", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Cart from './pages/Cart.tsx';
export default function App() {
  return <Route path="/cart" element={<Cart />} />;
}
`,
            },
            {
                path: "src/pages/Cart.tsx",
                content: "export default function Cart() { return <div/>; }",
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes[0].componentFile).toMatch(/src\/pages\/Cart\.tsx$/);
        cleanup(repo);
    });
    // ── Slice 3b: constant resolution ─────────────────────────────────────────
    it("resolves identifier-valued path via cross-file import (named)", () => {
        const repo = makeTmpRepo([
            {
                path: "src/constants/routes.ts",
                content: `
export const APPLICATIONS_URL = "/applications";
export const WORKSPACE_URL = "/workspace";
`,
            },
            {
                path: "src/AppRouter.tsx",
                content: `
import { Route } from 'react-router-dom';
import { APPLICATIONS_URL } from './constants/routes';
import Apps from './pages/Apps';

export default function AppRouter() {
  return <Route path={APPLICATIONS_URL} element={<Apps />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/applications");
        cleanup(repo);
    });
    it("resolves identifier-valued path via DEFAULT import (export default)", () => {
        // Regression: default imports were recorded with exportedName=null and
        // resolved with `name` (the local identifier) — but the resolver only
        // looks up named exports, so default-exported path constants never resolved.
        // Fix: mark default imports as exportedName="default" and handle
        // `export default <expr>` in the resolver.
        const repo = makeTmpRepo([
            {
                path: "src/constants/orders.ts",
                content: `export default "/orders";`,
            },
            {
                path: "src/AppRouter.tsx",
                content: `
import { Route } from 'react-router-dom';
import ORDERS_URL from './constants/orders';
import Orders from './pages/Orders';

export default function AppRouter() {
  return <Route path={ORDERS_URL} element={<Orders />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/orders");
        cleanup(repo);
    });
    it("resolves identifier-valued path via DEFAULT import (export { X as default })", () => {
        const repo = makeTmpRepo([
            {
                path: "src/constants/cart.ts",
                content: `
const CART_URL = "/cart";
export { CART_URL as default };
`,
            },
            {
                path: "src/AppRouter.tsx",
                content: `
import { Route } from 'react-router-dom';
import CART_URL from './constants/cart';
import Cart from './pages/Cart';

export default function AppRouter() {
  return <Route path={CART_URL} element={<Cart />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/cart");
        cleanup(repo);
    });
    it("rejects absolute import specs (path-traversal hardening)", () => {
        // A hostile repo could write `import X from "/etc/passwd"` and trigger
        // arbitrary host-path file reads during route extraction. The collector
        // skips absolute import specs entirely. Verify by writing an import that
        // *would* otherwise resolve to outside repositoryPath if absolute paths
        // were honored.
        const repo = makeTmpRepo([
            {
                path: "src/constants/bad.ts",
                content: `export const BAD = "/should-not-resolve";`,
            },
            {
                path: "src/AppRouter.tsx",
                content: `
import { Route } from 'react-router-dom';
import { BAD } from "/etc/passwd";
import Page from './pages/Page';

export default function AppRouter() {
  return <Route path={BAD} element={<Page />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        // The path constant doesn't resolve (absolute import rejected), so the
        // identifier remains unresolved and the route is skipped. Zero routes.
        expect(routes).toHaveLength(0);
        cleanup(repo);
    });
    it("resolves template literal that references another resolvable constant", () => {
        const repo = makeTmpRepo([
            {
                path: "src/constants/routes.ts",
                content: `
export const USER_AUTH_URL = "/user";
export const AUTH_LOGIN_URL = \`\${USER_AUTH_URL}/login\`;
`,
            },
            {
                path: "src/AppRouter.tsx",
                content: `
import { Route } from 'react-router-dom';
import { AUTH_LOGIN_URL } from './constants/routes';
import Login from './pages/Login';

export default function AppRouter() {
  return <Route path={AUTH_LOGIN_URL} element={<Login />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/user/login");
        cleanup(repo);
    });
    it("skips identifier path when constant cannot be resolved", () => {
        const repo = makeTmpRepo([
            {
                path: "src/AppRouter.tsx",
                content: `
import { Route } from 'react-router-dom';
import { UNKNOWN_URL } from 'some-package';
import Foo from './pages/Foo';

export default function AppRouter() {
  return <Route path={UNKNOWN_URL} element={<Foo />} />;
}
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toEqual([]);
        cleanup(repo);
    });
    // ── Slice 3b: Vue Router object literals ──────────────────────────────────
    it("extracts Vue Router routes from object-literal array", () => {
        const repo = makeTmpRepo([
            {
                path: "src/router.ts",
                content: `
import { createRouter } from 'vue-router';
import Login from '@/routes/login.vue';
import Dashboard from '@/routes/dashboard.vue';

export const defaultRoutes = [
  { name: 'login', path: '/login', component: Login },
  { name: 'dashboard', path: '/dashboard', component: Dashboard },
];
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes.map((r) => r.path).sort()).toEqual(["/dashboard", "/login"]);
        cleanup(repo);
    });
    it("ignores object literals that aren't route declarations", () => {
        const repo = makeTmpRepo([
            {
                path: "src/router.ts",
                content: `
const config = { path: '/somewhere', enabled: true };
const settings = { timeout: 30 };
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toEqual([]);
        cleanup(repo);
    });
    // ── Slice 3b: defineModule aggregation ────────────────────────────────────
    it("aggregates defineModule routes with module id as URL prefix", () => {
        const repo = makeTmpRepo([
            {
                path: "src/modules/notifications/index.ts",
                content: `
import { defineModule } from '@directus/extensions';
import NotificationsCollection from './routes/collection.vue';

export default defineModule({
  id: 'notifications',
  name: 'Notifications',
  routes: [
    { name: 'notifications-collection', path: '', component: NotificationsCollection },
  ],
});
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/notifications");
        cleanup(repo);
    });
    it("aggregates defineModule routes with nested path", () => {
        const repo = makeTmpRepo([
            {
                path: "src/modules/users/index.ts",
                content: `
import { defineModule } from '@directus/extensions';
import UserDetail from './routes/detail.vue';

export default defineModule({
  id: 'users',
  routes: [
    { name: 'user-detail', path: ':id', component: UserDetail },
  ],
});
`,
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes).toHaveLength(1);
        expect(routes[0].path).toBe("/users/:id");
        cleanup(repo);
    });
    it("resolves import that is a directory with index.tsx", () => {
        const repo = makeTmpRepo([
            {
                path: "src/App.tsx",
                content: `
import { Route } from 'react-router-dom';
import Cart from './pages/Cart';
export default function App() {
  return <Route path="/cart" element={<Cart />} />;
}
`,
            },
            {
                path: "src/pages/Cart/index.tsx",
                content: "export default function Cart() { return <div/>; }",
            },
        ]);
        const routes = extractSourceRoutes(repo);
        expect(routes[0].componentFile).toMatch(/src\/pages\/Cart\/index\.tsx$/);
        cleanup(repo);
    });
});
