jest.mock("@skyramp/skyramp", () => ({
    WorkspaceConfigManager: { create: jest.fn() },
}));
import { buildAnalysisOutputText, getStepTraceCallers, ANALYSIS_STEP_READ_FILES, ANALYSIS_STEP_RESOLVE_PATHS, ANALYSIS_STEP_EXTRACT, ANALYSIS_STEP_DRAFT, ANALYSIS_STEP_CALL_TOOL, } from "./analysisOutputPrompt.js";
import { AnalysisScope } from "../../types/RepositoryAnalysis.js";
// ---------------------------------------------------------------------------
// Minimal fixture factory
// ---------------------------------------------------------------------------
function baseParams(overrides = {}) {
    return {
        sessionId: "test-session-id",
        repositoryPath: "/repo",
        analysisScope: AnalysisScope.CurrentBranchDiff,
        scannedEndpoints: [],
        wsBaseUrl: "http://localhost:3000",
        wsAuthHeader: "Authorization",
        wsAuthType: "",
        wsSchemaPath: "",
        routerMountContext: [],
        parsedDiff: {
            changedFiles: [],
            newEndpoints: [],
            modifiedEndpoints: [],
        },
        ...overrides,
    };
}
// ---------------------------------------------------------------------------
// Static step label constants
// ---------------------------------------------------------------------------
describe("analysisOutputPrompt — static step label constants", () => {
    it("exports sequential main-step labels", () => {
        expect(ANALYSIS_STEP_READ_FILES).toBe("1");
        expect(ANALYSIS_STEP_EXTRACT).toBe("2");
        expect(ANALYSIS_STEP_DRAFT).toBe("3");
        expect(ANALYSIS_STEP_CALL_TOOL).toBe("4");
    });
    it("exports sequential sub-step label for RESOLVE_PATHS", () => {
        expect(ANALYSIS_STEP_RESOLVE_PATHS).toBe("1.1");
    });
});
// ---------------------------------------------------------------------------
// getStepTraceCallers() — dynamic label for the conditional sub-step
// ---------------------------------------------------------------------------
describe("getStepTraceCallers()", () => {
    it("returns '2.1' when non-frontend unmatched backend files are present in diff scope", () => {
        const params = baseParams({
            unmatchedFiles: ["server/helpers/DataUtils.java"],
        });
        expect(getStepTraceCallers(params)).toBe("2.1");
    });
    it("returns null when unmatchedFiles is empty", () => {
        const params = baseParams({ unmatchedFiles: [] });
        expect(getStepTraceCallers(params)).toBeNull();
    });
    it("returns null when unmatchedFiles is undefined", () => {
        const params = baseParams({ unmatchedFiles: undefined });
        expect(getStepTraceCallers(params)).toBeNull();
    });
    it("returns null when scope is full_repo even if unmatchedFiles is non-empty", () => {
        const params = baseParams({
            analysisScope: AnalysisScope.FullRepo,
            unmatchedFiles: ["src/services/SomeService.ts"],
        });
        expect(getStepTraceCallers(params)).toBeNull();
    });
    it("returns null when unmatchedFiles contains only frontend component files", () => {
        const params = baseParams({
            unmatchedFiles: ["src/components/Button.tsx", "src/pages/Dashboard.jsx"],
        });
        expect(getStepTraceCallers(params)).toBeNull();
    });
    it("returns null when unmatchedFiles contains only non-code files", () => {
        const params = baseParams({
            unmatchedFiles: ["README.md", "package.json", "docker-compose.yml"],
        });
        expect(getStepTraceCallers(params)).toBeNull();
    });
});
// ---------------------------------------------------------------------------
// Trace-callers sub-step output (Step 2.1)
// ---------------------------------------------------------------------------
describe("buildAnalysisOutputText — unmatchedFiles / trace-callers sub-step", () => {
    it("includes the trace-callers block when unmatchedFiles is non-empty and scope is CurrentBranchDiff", () => {
        const params = baseParams({
            unmatchedFiles: [
                "server/src/main/java/helpers/DataUtils.java",
                "server/src/main/java/helpers/MustacheHelper.java",
            ],
        });
        const output = buildAnalysisOutputText(params);
        const label = getStepTraceCallers(params);
        expect(label).not.toBeNull();
        expect(output).toContain(`### Step ${label}: Trace callers of changed non-route files`);
        expect(output).toContain("DataUtils.java");
        expect(output).toContain("MustacheHelper.java");
        expect(output).toContain("/execute");
    });
    it("lists each unmatched file as a bullet in the trace-callers block", () => {
        const params = baseParams({
            unmatchedFiles: ["src/services/OrderService.ts", "src/utils/pricingHelper.ts"],
        });
        const output = buildAnalysisOutputText(params);
        expect(output).toContain("- `src/services/OrderService.ts`");
        expect(output).toContain("- `src/utils/pricingHelper.ts`");
    });
    it("omits the trace-callers block when unmatchedFiles is empty", () => {
        const params = baseParams({ unmatchedFiles: [] });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).toBeNull();
        expect(output).not.toContain("Trace callers of changed non-route files");
    });
    it("omits the trace-callers block when unmatchedFiles is undefined", () => {
        const params = baseParams({ unmatchedFiles: undefined });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).toBeNull();
        expect(output).not.toContain("Trace callers of changed non-route files");
    });
    it("omits the trace-callers block when scope is full_repo even if unmatchedFiles is non-empty", () => {
        const params = baseParams({
            analysisScope: AnalysisScope.FullRepo,
            unmatchedFiles: ["src/services/SomeService.ts"],
        });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).toBeNull();
        expect(output).not.toContain("Trace callers of changed non-route files");
    });
    it("trace-callers sub-step appears after the extract step in the output (phase 4b — no Step 2.5)", () => {
        // This test uses no diff — falls back to the source-files branch.
        const params = baseParams({
            unmatchedFiles: ["src/utils/helper.ts"],
        });
        const output = buildAnalysisOutputText(params);
        const label = getStepTraceCallers(params);
        expect(label).not.toBeNull();
        expect(output).toContain(`Step ${label}`);
        expect(output).not.toContain("Step 2.5");
    });
    it("critical-patterns checklist is present in Step 2 regardless of diff availability (phase 4b)", () => {
        // Step 2.5 was merged into Step 2 — patterns appear inline in ALL diff-scope branches.
        const withDiff = baseParams({ unmatchedFiles: ["src/utils/foo.ts"], diffFilePath: "/tmp/pr.diff" });
        const withDiffNoUnmatched = baseParams({ unmatchedFiles: [], diffFilePath: "/tmp/pr.diff" });
        const noDiff = baseParams({ unmatchedFiles: [] });
        const uiOnly = baseParams({
            parsedDiff: {
                changedFiles: ["src/components/Cart.tsx", "src/components/OrderList.vue"],
                newEndpoints: [],
                modifiedEndpoints: [],
            },
            unmatchedFiles: [],
        });
        expect(buildAnalysisOutputText(withDiff)).toContain("While reading, also note these patterns");
        expect(buildAnalysisOutputText(withDiffNoUnmatched)).toContain("While reading, also note these patterns");
        expect(buildAnalysisOutputText(noDiff)).toContain("While reading, also note these patterns");
        expect(buildAnalysisOutputText(uiOnly)).toContain("While reading, also note these patterns");
        // Step 2.5 header should never appear
        expect(buildAnalysisOutputText(withDiff)).not.toContain("Step 2.5");
        expect(buildAnalysisOutputText(noDiff)).not.toContain("Step 2.5");
        expect(buildAnalysisOutputText(uiOnly)).not.toContain("Step 2.5");
    });
    it("omits trace-callers block when unmatchedFiles contains only frontend component files (UI-only PR)", () => {
        const params = baseParams({
            unmatchedFiles: [
                "src/components/Button.tsx",
                "src/pages/Dashboard.jsx",
                "src/views/UserProfile.vue",
                "src/routes/Settings.svelte",
            ],
        });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).toBeNull();
        expect(output).not.toContain("Trace callers of changed non-route files");
    });
    it("omits trace-callers block when unmatchedFiles contains only non-code files (docs/config)", () => {
        const params = baseParams({
            unmatchedFiles: [
                "README.md",
                "package.json",
                "docker-compose.yml",
                ".github/workflows/ci.yml",
            ],
        });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).toBeNull();
        expect(output).not.toContain("Trace callers of changed non-route files");
    });
    it("emits trace-callers for backend code files but excludes frontend/non-code siblings", () => {
        // Mixed PR: one Java helper + one React component + one config file.
        // Only the Java file should appear in the trace-callers bullets.
        const params = baseParams({
            unmatchedFiles: [
                "server/helpers/DataUtils.java",
                "client/components/ActionButton.tsx",
                "package.json",
            ],
        });
        const output = buildAnalysisOutputText(params);
        const label = getStepTraceCallers(params);
        expect(label).not.toBeNull();
        expect(output).toContain(`Step ${label}`);
        expect(output).toContain("DataUtils.java");
        expect(output).not.toContain("ActionButton.tsx");
        expect(output).not.toContain("package.json");
    });
    it("omits trace-callers when unmatchedFiles contains frontend .ts/.js files and isUIOnly is true", () => {
        // Files in recognized frontend directories (components/, pages/) are classified
        // as frontend by isFrontendFile() — making this a UI-only PR.
        const params = baseParams({
            parsedDiff: {
                changedFiles: ["src/components/AuthForm.ts", "src/pages/LoginPage.ts"],
                newEndpoints: [],
                modifiedEndpoints: [],
            },
            unmatchedFiles: ["src/components/AuthForm.ts", "src/pages/LoginPage.ts"],
        });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).toBeNull();
        expect(output).not.toContain("Trace callers of changed non-route files");
    });
    it("does NOT treat backend .ts/.js files as UI-only (directory-aware heuristic)", () => {
        // Files in src/services/ or src/hooks/ are NOT in frontend directories —
        // they should NOT trigger isUIOnly, so trace-callers should fire.
        const params = baseParams({
            parsedDiff: {
                changedFiles: ["src/services/auth.service.ts", "src/hooks/useAuth.ts"],
                newEndpoints: [],
                modifiedEndpoints: [],
            },
            unmatchedFiles: ["src/services/auth.service.ts", "src/hooks/useAuth.ts"],
        });
        const output = buildAnalysisOutputText(params);
        expect(getStepTraceCallers(params)).not.toBeNull();
        expect(output).toContain("Trace callers of changed non-route files");
    });
});
describe("buildAnalysisOutputText — scanner fallback", () => {
    it("includes manual endpoint discovery in diff scope when candidate route files are present despite scanned endpoints", () => {
        const params = baseParams({
            analysisScope: AnalysisScope.CurrentBranchDiff,
            scannedEndpoints: [{ path: "/openapi-only", methods: ["GET"], sourceFile: "" }],
            candidateRouteFiles: ["src/routes/items.ts"],
        });
        const output = buildAnalysisOutputText(params);
        expect(output).toContain("Scanner Fallback — Manual Endpoint Discovery Required");
        expect(output).toContain("may have missed route/controller files");
        expect(output).toContain("src/routes/items.ts");
        expect(output).toContain("supplemental gap check");
        expect(output).toContain("keep the existing catalog as the primary endpoint source");
        expect(output).not.toContain("use it as the authoritative endpoint list");
    });
    it("tells fallback discovery not to turn GraphQL artifacts into REST endpoints", () => {
        const params = baseParams({
            analysisScope: AnalysisScope.CurrentBranchDiff,
            scannedEndpoints: [],
            candidateRouteFiles: ["src/graphql/users.resolver.ts"],
        });
        const output = buildAnalysisOutputText(params);
        expect(output).toContain("GraphQL: schema/resolver artifacts are unsupported for REST test generation");
        expect(output).toContain("do not invent REST endpoints from them");
        expect(output).toContain("use it as the authoritative endpoint list");
        expect(output).not.toContain("Query/Mutation resolvers mapping to endpoints");
    });
});
