// Context assembly for the investigation call (ADR-0009 D8). Delegates to #5 buildContextPack so
// the implicated source, nearby tests, manifests/config, and type definitions are selected, ranked,
// and excerpted under a byte budget — every excerpt already redacted by #5 at the IO boundary. The
// failure-frame files and developer-provided targetFiles are folded into the lexical `task` hint so
// the Wave-1 lexical strategy ranks them up (the #5 ContextRequest is reused unchanged; we do not
// add a new "seed files" field to #5). Pure except for the WorkspaceFs seam threaded into #5.
import { buildContextPack, DEFAULT_DISCOVERY_OPTIONS, lexicalRetrievalStrategy, SELECTION_REASON_PRIORITY, } from "@oscharko-dev/keiko-workspace";
import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
// The lexical task hint: the bug description (or a generic fallback) plus the implicated file paths
// so the lexical ranker pulls those files into the pack. Deduped; bounded by the frame cap upstream.
function taskHint(description, evidence) {
    const base = description !== undefined && description.trim().length > 0
        ? description.trim()
        : "investigate failing test and locate the root cause";
    const files = Array.from(new Set(evidence.frames.map((frame) => frame.file)));
    return files.length === 0 ? base : `${base} ${files.join(" ")}`;
}
function toPosix(path) {
    return path.split("\\").join("/");
}
function basename(path) {
    const normalized = toPosix(path);
    const idx = normalized.lastIndexOf("/");
    return idx === -1 ? normalized : normalized.slice(idx + 1);
}
function stem(path) {
    const base = basename(path);
    const idx = base.lastIndexOf(".");
    return idx <= 0 ? base : base.slice(0, idx);
}
function testStem(path) {
    const parts = stem(path).split(".");
    const marker = parts.findIndex((part) => part === "test" || part === "spec");
    return marker === -1 ? parts.join(".") : parts.slice(0, marker).join(".");
}
function underDir(path, dir) {
    const normalized = toPosix(dir);
    return path === normalized || path.startsWith(`${normalized}/`);
}
function toWorkspaceRelative(path, workspace) {
    const normalized = toPosix(path);
    const root = toPosix(workspace.root);
    return normalized.startsWith(`${root}/`) ? normalized.slice(root.length + 1) : normalized;
}
function buildEvidenceIndex(evidence, workspace) {
    const paths = new Set(evidence.frames.map((frame) => toWorkspaceRelative(frame.file, workspace)));
    return { paths, stems: new Set([...paths].map(stem)) };
}
function isEvidenceTarget(path, evidence) {
    return evidence.paths.has(toPosix(path));
}
function isTestCandidate(path, workspace, selectionReason) {
    return (selectionReason === "test" ||
        workspace.testDirs.some((testDir) => underDir(path, testDir)) ||
        stem(path)
            .split(".")
            .some((part) => part === "test" || part === "spec"));
}
function isNearbyEvidenceTest(path, workspace, evidence, selectionReason) {
    if (!isTestCandidate(path, workspace, selectionReason)) {
        return false;
    }
    const candidateStem = testStem(path);
    return evidence.stems.has(candidateStem);
}
function priorityIndex(reason) {
    return SELECTION_REASON_PRIORITY.indexOf(reason);
}
function evidencePriority(ranked, workspace, evidence) {
    const path = toPosix(ranked.file.relativePath);
    if (isEvidenceTarget(path, evidence)) {
        return 0;
    }
    if (isNearbyEvidenceTest(path, workspace, evidence, ranked.selectionReason)) {
        return 1;
    }
    return 2;
}
function isSupportContext(ranked) {
    return ranked.selectionReason === "manifest" || ranked.selectionReason === "config";
}
function focusedContext(ranked, workspace, evidence) {
    const focused = ranked.filter((item) => evidencePriority(item, workspace, evidence) < 2 || isSupportContext(item));
    return focused.length === 0 ? ranked : focused;
}
function createBugRetrievalStrategy(workspace, evidence) {
    const index = buildEvidenceIndex(evidence, workspace);
    return {
        rank: (files, task) => {
            const ranked = focusedContext(lexicalRetrievalStrategy.rank(files, task), workspace, index);
            return [...ranked].sort((a, b) => {
                const byEvidence = evidencePriority(a, workspace, index) - evidencePriority(b, workspace, index);
                if (byEvidence !== 0) {
                    return byEvidence;
                }
                const byReason = priorityIndex(a.selectionReason) - priorityIndex(b.selectionReason);
                if (byReason !== 0) {
                    return byReason;
                }
                return a.file.relativePath.localeCompare(b.file.relativePath);
            });
        },
    };
}
export function buildBugContext(workspace, description, evidence, limits, deps = {}) {
    const request = {
        task: taskHint(description, evidence),
        budgetBytes: limits.contextBudgetBytes,
        maxBytesPerFile: limits.maxBytesPerFile,
        discovery: DEFAULT_DISCOVERY_OPTIONS,
    };
    // buildContextPack defaults to the lexical strategy, but ContextPackDeps requires the field; pass
    // the #5 barrel default explicitly so we hand a complete deps object without reaching past #5.
    const packDeps = {
        fs: deps.fs ?? nodeWorkspaceFs,
        strategy: createBugRetrievalStrategy(workspace, evidence),
    };
    return buildContextPack(workspace, request, packDeps);
}
