jest.mock("@skyramp/skyramp", () => ({
    WorkspaceConfigManager: { create: jest.fn() },
}));
import { isFrontendFile, isTestFile, buildScopeAssessmentSection } from "./scopeAssessment.js";
// ---------------------------------------------------------------------------
// isFrontendFile
// ---------------------------------------------------------------------------
describe("isFrontendFile", () => {
    it("returns true for a TSX component in components/", () => {
        expect(isFrontendFile("src/components/Button.tsx")).toBe(true);
    });
    it("returns true for a JSX file in pages/", () => {
        expect(isFrontendFile("src/pages/Home.jsx")).toBe(true);
    });
    it("returns true for a Vue file in views/", () => {
        expect(isFrontendFile("src/views/Dashboard.vue")).toBe(true);
    });
    it("returns false for a TS route handler (API directory)", () => {
        expect(isFrontendFile("src/routes/items.ts")).toBe(false);
    });
    it("returns false for a TS file in a backend service directory", () => {
        expect(isFrontendFile("src/services/AuthService.ts")).toBe(false);
    });
    it("returns true for a TS file in components/ (tsx? matches .ts)", () => {
        // FRONTEND_FILE_PATTERN uses tsx? which matches both .ts and .tsx
        expect(isFrontendFile("src/components/utils.ts")).toBe(true);
    });
    it("returns false for a backend TS file in api/ even with tsx extension", () => {
        expect(isFrontendFile("src/api/handlers/UserHandler.tsx")).toBe(false);
    });
    // Regression cases from three-tier logic
    it("returns true for SvelteKit page in routes/ (svelte is always frontend)", () => {
        expect(isFrontendFile("src/routes/+page.svelte")).toBe(true);
    });
    it("returns false for Next.js API route (.tsx in pages/api/)", () => {
        expect(isFrontendFile("pages/api/users.tsx")).toBe(false);
    });
    it("returns true for a standalone TSX component (no API dir)", () => {
        expect(isFrontendFile("Button.tsx")).toBe(true);
    });
    it("returns true for an App.vue file", () => {
        expect(isFrontendFile("App.vue")).toBe(true);
    });
    // .tsx/.jsx in non-standard dirs (lib/, shared/, helpers/) — tier 2 fires because
    // JSX syntax is inherently frontend; no backend framework uses .tsx outside API dirs.
    it("returns true for a TSX helper in lib/ (JSX implies frontend, no API dir)", () => {
        expect(isFrontendFile("lib/FormHelpers.tsx")).toBe(true);
    });
    it("returns true for a JSX component in shared/", () => {
        expect(isFrontendFile("shared/helpers.jsx")).toBe(true);
    });
    // .ts in a non-frontend dir stays false — plain TypeScript needs directory context.
    it("returns false for a plain TS utility in lib/ (no frontend dir, tier 3 applies)", () => {
        expect(isFrontendFile("lib/utils.ts")).toBe(false);
    });
    it("returns false for a plain TS API client in utils/ (no frontend dir)", () => {
        expect(isFrontendFile("utils/apiClient.ts")).toBe(false);
    });
});
// ---------------------------------------------------------------------------
// isTestFile
// ---------------------------------------------------------------------------
describe("isTestFile", () => {
    it("matches Skyramp-generated test suffix patterns", () => {
        expect(isTestFile("backend/tests/orders_integration_test.py")).toBe(true);
        expect(isTestFile("frontend/tests/cart_ui_test.ts")).toBe(true);
        expect(isTestFile("backend/tests/users_contract_test.py")).toBe(true);
        expect(isTestFile("backend/tests/load_test.py")).toBe(true);
    });
    it("matches Skyramp scenario JSON files", () => {
        expect(isTestFile("tests/scenario_orders.json")).toBe(true);
        expect(isTestFile("scenario_checkout.json")).toBe(true);
    });
    it("matches conventional Vitest/Jest/Playwright spec naming", () => {
        expect(isTestFile("frontend/tests/cart_labels_baseline.spec.ts")).toBe(true);
        expect(isTestFile("src/components/Button.test.tsx")).toBe(true);
        expect(isTestFile("e2e/login.spec.js")).toBe(true);
        expect(isTestFile("packages/foo/Bar.test.jsx")).toBe(true);
    });
    it("matches files under __tests__/ directories", () => {
        expect(isTestFile("src/__tests__/utils.ts")).toBe(true);
        expect(isTestFile("packages/foo/__tests__/Bar.tsx")).toBe(true);
    });
    it("returns false for ordinary source files", () => {
        expect(isTestFile("frontend/src/pages/Cart.tsx")).toBe(false);
        expect(isTestFile("frontend/src/components/Button.tsx")).toBe(false);
        expect(isTestFile("backend/src/handlers/orders.py")).toBe(false);
    });
    it("returns false for non-spec .ts files in tests/ directories that aren't Skyramp-generated", () => {
        // Tests/ directory alone doesn't trigger isTestFile — only the suffix matters.
        expect(isTestFile("frontend/tests/helpers/setup.ts")).toBe(false);
    });
});
// ---------------------------------------------------------------------------
// buildScopeAssessmentSection
// ---------------------------------------------------------------------------
describe("buildScopeAssessmentSection", () => {
    it("produces a valid range in Step C for normal inputs (topN > maxGenerate)", () => {
        const section = buildScopeAssessmentSection(10, 3);
        // minTotal = min(3+1, 10) = 4; range should be "4–10"
        expect(section).toContain("4–10");
        expect(section).toContain("3 (generate) + 1 (min additional) = 4");
    });
    it("clamps minTotal to maxTotal when maxTotal === maxGenerate (regression: no inverted range)", () => {
        // Edge case from review: topN=4, maxGenerate=4 → minTotal must not exceed maxTotal
        const section = buildScopeAssessmentSection(4, 4);
        // minTotal = min(4+1, 4) = 4; range should be "4–4", NOT "5–4"
        expect(section).toContain("4–4");
        // minAdditional = 4 - 4 = 0; baseline should say "0 (min additional)"
        expect(section).toContain("4 (generate) + 0 (min additional) = 4");
        // Must NOT contain an inverted range
        expect(section).not.toMatch(/\b5–4\b/);
    });
    it("clamps minTotal to maxTotal when maxTotal < maxGenerate", () => {
        // Defensive: maxGenerate clamped to topN upstream, but guard applies here too
        const section = buildScopeAssessmentSection(3, 5);
        // effectiveGenerate = min(5, 3) = 3; minTotal = min(3+1, 3) = 3; range "3–3"
        expect(section).toContain("3–3");
        expect(section).not.toMatch(/\b[6-9]–3\b/);
        // Must not show original maxGenerate (5) in the formula
        expect(section).toContain("3 (generate)");
        expect(section).not.toContain("5 (generate)");
    });
    it("embeds UI/E2E confirmation step when isUIOnly=true", () => {
        const section = buildScopeAssessmentSection(10, 3, true);
        expect(section).toContain("frontend-only PR");
        expect(section).toContain("100% UI/E2E");
        expect(section).not.toContain("Step D — Determine UI vs backend split");
    });
    it("embeds UI vs backend split step when isUIOnly=false", () => {
        const section = buildScopeAssessmentSection(10, 3, false);
        expect(section).toContain("Step D — Determine UI vs backend split");
        expect(section).not.toContain("UI/E2E confirmation");
    });
    it("uses defaults matching MAX_RECOMMENDATIONS and MAX_TESTS_TO_GENERATE", () => {
        // Should not throw and should produce a non-empty string
        const section = buildScopeAssessmentSection();
        expect(section.length).toBeGreaterThan(0);
        expect(section).toContain("Budget Plan");
    });
    // ---------------------------------------------------------------------------
    // New branches added in PR 453 (4a) — precomputedUIPct / hasFrontendChanges
    // ---------------------------------------------------------------------------
    it("emits a single Budget Plan line for backend-only PR (precomputedUIPct=0)", () => {
        // isUIOnlyPR=false, hasFrontendChanges=false → precomputedUIPct=0
        const section = buildScopeAssessmentSection(10, 3, false, 0, false);
        expect(section).toContain("Budget Plan: 10 total (3 generate + 7 additional), 0% UI/E2E");
        expect(section).toContain("Use these exact numbers throughout the rest of the prompt.");
        // Must NOT include the step-based assessment
        expect(section).not.toContain("Step A");
        expect(section).not.toContain("Step B");
        expect(section).not.toContain("Step C");
        expect(section).not.toContain("Step D");
    });
    it("emits a single Budget Plan line for UI-only PR (precomputedUIPct=100)", () => {
        const section = buildScopeAssessmentSection(10, 3, true, 100, false);
        expect(section).toContain("Budget Plan: 10 total (3 generate + 7 additional), 100% UI/E2E");
        expect(section).not.toContain("Step A");
        expect(section).not.toContain("Step D");
    });
    it("emits Step D only for mixed PR (hasFrontendChanges=true, precomputedUIPct=undefined)", () => {
        const section = buildScopeAssessmentSection(10, 3, false, undefined, true);
        // Total is pre-determined — shows in the Budget Plan header
        expect(section).toContain("Budget Plan (total already determined)");
        expect(section).toContain("10 total (3 generate + 7 additional)");
        // Step D is kept for UI/E2E split judgment
        expect(section).toContain("Step D");
        // The "Append your UI%" instruction must appear (comment #3222061560)
        expect(section).toContain("Append your UI%");
        // Steps A–C are skipped
        expect(section).not.toContain("Step A");
        expect(section).not.toContain("Step B");
        expect(section).not.toContain("Step C");
    });
    it("clamps additional to 0 when maxGenerate equals maxTotal (precomputed path)", () => {
        // maxGenerate=5, maxTotal=5 → additional must be 0, not negative
        const section = buildScopeAssessmentSection(5, 5, false, 0, false);
        expect(section).toContain("5 generate + 0 additional");
        expect(section).not.toMatch(/\d+ generate \+ -\d+ additional/);
    });
    it("clamps additional to 0 when maxGenerate exceeds maxTotal (defensive, precomputed path)", () => {
        // Should never happen in normal usage but guard must hold
        const section = buildScopeAssessmentSection(3, 5, false, 0, false);
        expect(section).toContain("3 generate + 0 additional");
        expect(section).not.toMatch(/\+ -\d+ additional/);
        // Must not show unclamped maxGenerate
        expect(section).not.toContain("5 generate");
    });
    it("clamps generate in mixed PR path when maxGenerate exceeds maxTotal", () => {
        const section = buildScopeAssessmentSection(3, 5, false, undefined, true);
        expect(section).toContain("3 generate + 0 additional");
        expect(section).not.toContain("5 generate");
        expect(section).not.toMatch(/\+ -\d+ additional/);
    });
});
