"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_CONTEXT_PACK_EXCERPT_CHARS = exports.DEFAULT_CONTEXT_PACK_EXCERPT_LINES = exports.DEFAULT_CONTEXT_PACK_ENTRY_LIMIT = exports.DEFAULT_CONTEXT_PACK_BUDGET_BYTES = exports.MAXIMUM_CONTEXT_PACK_BUDGET_BYTES = exports.MINIMUM_CONTEXT_PACK_BUDGET_BYTES = void 0;
exports.buildContextPack = buildContextPack;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../errors");
const paths_1 = require("../paths");
const planning_document_utils_1 = require("../planning/planning-document-utils");
const output_safety_1 = require("../output/output-safety");
exports.MINIMUM_CONTEXT_PACK_BUDGET_BYTES = 1024;
exports.MAXIMUM_CONTEXT_PACK_BUDGET_BYTES = 65_536;
exports.DEFAULT_CONTEXT_PACK_BUDGET_BYTES = 32_768;
exports.DEFAULT_CONTEXT_PACK_ENTRY_LIMIT = 12;
exports.DEFAULT_CONTEXT_PACK_EXCERPT_LINES = 24;
exports.DEFAULT_CONTEXT_PACK_EXCERPT_CHARS = 1600;
const REPOSITORY_PATH_PATTERN = /(?:^|[\s`'"(])((?:\.day-shift|src|tests_v2|tests|scripts|schemas|apps)\/[A-Za-z0-9_./-]+\.[A-Za-z0-9_-]+)/g;
function buildContextPack(options) {
    const artifactPath = (0, paths_1.parseRepositoryRelativePath)(options.artifactPath).normalizedPath;
    const rawText = readRepositoryFile(options.repositoryRoot, artifactPath);
    const parsed = (0, planning_document_utils_1.parseMarkdownFrontMatter)(rawText);
    const sections = (0, planning_document_utils_1.parseMarkdownSections)(parsed.bodyText);
    const candidates = [{
            path: artifactPath,
            sourceDomain: "selected-artifact",
            relevanceBasis: "selected-artifact",
            rank: 0,
            confidence: "high",
            heuristic: false
        }];
    candidates.push(...extractUpstreamCandidates(parsed.frontMatterLines));
    candidates.push(...extractSectionCandidates(sections.get("Implementation Target Paths"), evidence("target-path", "declared-target", 20, "high")));
    candidates.push(...extractSectionCandidates(sections.get("Dependencies"), evidence("dependency", "declared-dependency", 30, "high")));
    candidates.push(...extractSectionCandidates(sections.get("Validation Commands"), evidence("validation", "validation-reference", 40, "medium")));
    for (const sectionName of ["Current Behavior", "Desired Behavior", "Acceptance Criteria"]) {
        candidates.push(...extractSectionCandidates(sections.get(sectionName), evidence("heuristic", "heuristic-text-match", 70, "low", true)));
    }
    candidates.push(...discoverImplementationEvidence(options.repositoryRoot, artifactPath));
    for (const target of candidates.filter((candidate) => candidate.sourceDomain === "target-path")) {
        candidates.push(...discoverDirectSourceRelationships(options.repositoryRoot, target.path));
    }
    const normalized = candidates.map((candidate) => normalizeCandidate(options.repositoryRoot, artifactPath, candidate));
    const existing = dedupeByStrongestEvidence(normalized.flatMap((item) => item.candidate === null ? [] : [item.candidate]));
    const missing = dedupeExclusions(normalized.flatMap((item) => item.exclusion === null ? [] : [item.exclusion]));
    const ranked = [...existing].sort(compareCandidates);
    const entryLimit = normalizeEntryLimit(options.entryLimit);
    const byteBudget = normalizeByteBudget(options.byteBudget);
    const entryLimited = ranked.slice(entryLimit).map((candidate) => createExclusion(candidate, "entry-limit"));
    const eligible = ranked.slice(0, entryLimit);
    for (let includedCount = eligible.length; includedCount >= 0; includedCount -= 1) {
        const entries = eligible.slice(0, includedCount).map((candidate) => createEntry(options.repositoryRoot, candidate));
        const budgetExcluded = eligible.slice(includedCount).map((candidate) => createExclusion(candidate, "byte-budget-exhausted"));
        const pack = stabilizeRenderedBytes({
            selectedArtifactPath: artifactPath,
            byteBudget,
            renderedBytes: 0,
            complete: missing.length === 0 && entryLimited.length === 0 && budgetExcluded.length === 0,
            defaultEntryLimit: entryLimit,
            candidateCount: ranked.length + missing.length,
            entries,
            excludedCandidates: [...missing, ...entryLimited, ...budgetExcluded].sort(compareExclusions)
        });
        if (pack.renderedBytes <= byteBudget)
            return pack;
    }
    throw (0, errors_1.createUsageError)(`Context pack budget ${byteBudget} bytes cannot preserve required exclusion evidence.`, [
        "Increase --context-pack-budget and retry. No repository or selection state was changed."
    ]);
}
function evidence(sourceDomain, relevanceBasis, rank, confidence, heuristic = false) {
    return { sourceDomain, relevanceBasis, rank, confidence, heuristic };
}
function extractUpstreamCandidates(frontMatterLines) {
    const result = [];
    for (const line of frontMatterLines) {
        const match = line.match(/^\s+(?:ref|[A-Za-z0-9_]+_overview|task_definition):\s*["']?([^"'\s]+)["']?\s*$/);
        if (match !== null)
            result.push({ path: match[1], ...evidence("upstream", "explicit-upstream-trace", 10, "high") });
    }
    return result;
}
function extractSectionCandidates(sectionText, metadata) {
    if (sectionText === undefined)
        return [];
    return extractRepositoryPaths(sectionText).map((candidatePath) => ({ path: candidatePath, ...metadata }));
}
function extractRepositoryPaths(text) {
    return [...text.matchAll(REPOSITORY_PATH_PATTERN)].map((match) => match[1]);
}
function discoverImplementationEvidence(repositoryRoot, artifactPath) {
    const directory = node_path_1.default.posix.dirname(artifactPath);
    const name = node_path_1.default.posix.basename(artifactPath);
    const paths = name === "task-definition.md"
        ? [node_path_1.default.posix.join(directory, "implementation-summary.md")]
        : name === "milestone-overview.md"
            ? listMatchingFiles(repositoryRoot, node_path_1.default.posix.join(directory, "tasks"), "implementation-summary.md")
            : name === "phase-overview.md"
                ? listMatchingFiles(repositoryRoot, node_path_1.default.posix.join(directory, "milestones"), "reconciliation.md")
                : [];
    return paths.map((candidatePath) => ({ path: candidatePath, ...evidence("implementation", "recent-implementation", 50, "medium") }));
}
function discoverDirectSourceRelationships(repositoryRoot, targetPath) {
    if (!/\.[cm]?[jt]sx?$/.test(targetPath))
        return [];
    const absolutePath = safeResolveExistingFile(repositoryRoot, targetPath);
    if (absolutePath === null)
        return [];
    const result = [];
    for (const match of node_fs_1.default.readFileSync(absolutePath, "utf8").matchAll(/(?:from\s+|require\(\s*)["'](\.[^"']+)["']/g)) {
        const related = resolveImportCandidate(repositoryRoot, targetPath, match[1]);
        if (related !== null)
            result.push({ path: related, ...evidence("source-relationship", "direct-source-relationship", 35, "medium") });
    }
    return result;
}
function resolveImportCandidate(repositoryRoot, sourcePath, importValue) {
    const base = node_path_1.default.posix.normalize(node_path_1.default.posix.join(node_path_1.default.posix.dirname(sourcePath), importValue));
    return [base, `${base}.ts`, `${base}.tsx`, `${base}.js`, node_path_1.default.posix.join(base, "index.ts")]
        .find((candidate) => safeResolveExistingFile(repositoryRoot, candidate) !== null) ?? null;
}
function normalizeCandidate(repositoryRoot, sourceArtifactPath, candidate) {
    const value = candidate.path.trim();
    const resolvedValue = value.startsWith("../") || value.startsWith("./")
        ? node_path_1.default.posix.normalize(node_path_1.default.posix.join(node_path_1.default.posix.dirname(sourceArtifactPath), value))
        : value.replace(/^\.\//, "");
    try {
        const normalizedPath = (0, paths_1.parseRepositoryRelativePath)(resolvedValue).normalizedPath;
        const normalizedCandidate = { ...candidate, path: normalizedPath };
        return safeResolveExistingFile(repositoryRoot, normalizedPath) === null
            ? { candidate: null, exclusion: createExclusion(normalizedCandidate, "missing-source") }
            : { candidate: normalizedCandidate, exclusion: null };
    }
    catch {
        return { candidate: null, exclusion: null };
    }
}
function dedupeByStrongestEvidence(candidates) {
    const byPath = new Map();
    for (const candidate of candidates) {
        const current = byPath.get(candidate.path);
        if (current === undefined || compareCandidates(candidate, current) < 0)
            byPath.set(candidate.path, candidate);
    }
    return [...byPath.values()];
}
function dedupeExclusions(exclusions) {
    const byKey = new Map();
    for (const exclusion of exclusions)
        byKey.set(`${exclusion.path}:${exclusion.reason}`, exclusion);
    return [...byKey.values()];
}
function compareCandidates(left, right) {
    return left.rank - right.rank || left.path.localeCompare(right.path) || left.sourceDomain.localeCompare(right.sourceDomain);
}
function compareExclusions(left, right) {
    return left.path.localeCompare(right.path) || left.reason.localeCompare(right.reason);
}
function createExclusion(candidate, reason) {
    return {
        path: candidate.path,
        relevanceBasis: candidate.relevanceBasis,
        confidence: candidate.confidence,
        heuristic: candidate.heuristic,
        relevant: !candidate.heuristic,
        reason
    };
}
function createEntry(repositoryRoot, candidate) {
    const absolute = safeResolveExistingFile(repositoryRoot, candidate.path);
    if (absolute === null)
        throw new Error(`Context pack evidence '${candidate.path}' is unavailable.`);
    const rawText = node_fs_1.default.readFileSync(absolute, "utf8");
    const lines = rawText.split(/\r?\n/);
    const endLine = Math.min(lines.length, exports.DEFAULT_CONTEXT_PACK_EXCERPT_LINES);
    return {
        path: candidate.path,
        sourceDomain: candidate.sourceDomain,
        relevanceBasis: candidate.relevanceBasis,
        trust: isProductPolicyPath(candidate.path) ? "product-policy" : "repository-authored-evidence",
        trustLane: candidate.sourceDomain === "selected-artifact"
            ? "selected-artifact"
            : isProductPolicyPath(candidate.path) ? "product-policy" : "repository-prose",
        confidence: candidate.confidence,
        heuristic: candidate.heuristic,
        contentHash: (0, node_crypto_1.createHash)("sha256").update(rawText).digest("hex"),
        freshness: inspectFreshness(repositoryRoot, candidate, absolute, rawText),
        locator: { startLine: 1, endLine },
        excerpt: (0, output_safety_1.sanitizeOutputText)(lines.slice(0, endLine).join("\n").slice(0, exports.DEFAULT_CONTEXT_PACK_EXCERPT_CHARS), { repositoryRoot, environment: process.env })
    };
}
function inspectFreshness(repositoryRoot, candidate, absolutePath, rawText) {
    const modifiedAt = node_fs_1.default.statSync(absolutePath).mtime.toISOString();
    if (candidate.sourceDomain !== "implementation")
        return { status: "current", modifiedAt, staleBecausePath: null };
    const changedPaths = extractRepositoryPaths((0, planning_document_utils_1.parseMarkdownSections)((0, planning_document_utils_1.parseMarkdownFrontMatter)(rawText).bodyText).get("Changed Paths") ?? "");
    const newer = changedPaths
        .map((changedPath) => ({ changedPath, absolute: safeResolveExistingFile(repositoryRoot, changedPath) }))
        .find((item) => item.absolute !== null && node_fs_1.default.statSync(item.absolute).mtimeMs > node_fs_1.default.statSync(absolutePath).mtimeMs);
    return newer === undefined
        ? { status: "current", modifiedAt, staleBecausePath: null }
        : { status: "stale", modifiedAt, staleBecausePath: newer.changedPath };
}
function stabilizeRenderedBytes(source) {
    let pack = source;
    for (let attempt = 0; attempt < 8; attempt += 1) {
        const renderedBytes = Buffer.byteLength(JSON.stringify(pack), "utf8");
        if (renderedBytes === pack.renderedBytes)
            return pack;
        pack = { ...pack, renderedBytes };
    }
    return pack;
}
function isProductPolicyPath(candidatePath) {
    return candidatePath.startsWith(".day-shift/workflows/")
        || candidatePath.startsWith(".day-shift/prompts/")
        || candidatePath.startsWith(".day-shift/references/");
}
function listMatchingFiles(repositoryRoot, directoryPath, fileName) {
    const absoluteDirectory = node_path_1.default.resolve(repositoryRoot, directoryPath);
    if (!absoluteDirectory.startsWith(`${node_path_1.default.resolve(repositoryRoot)}${node_path_1.default.sep}`) || !node_fs_1.default.existsSync(absoluteDirectory))
        return [];
    const matches = [];
    const visit = (directory) => {
        for (const entry of node_fs_1.default.readdirSync(directory, { withFileTypes: true })) {
            const absolute = node_path_1.default.join(directory, entry.name);
            if (entry.isDirectory())
                visit(absolute);
            else if (entry.isFile() && entry.name === fileName)
                matches.push(node_path_1.default.relative(repositoryRoot, absolute).split(node_path_1.default.sep).join("/"));
        }
    };
    visit(absoluteDirectory);
    return matches.sort();
}
function safeResolveExistingFile(repositoryRoot, candidatePath) {
    try {
        const absolute = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(candidatePath));
        return node_fs_1.default.existsSync(absolute) && node_fs_1.default.statSync(absolute).isFile() ? absolute : null;
    }
    catch {
        return null;
    }
}
function readRepositoryFile(repositoryRoot, candidatePath) {
    const absolute = safeResolveExistingFile(repositoryRoot, candidatePath);
    if (absolute === null)
        throw new Error(`Context pack evidence '${candidatePath}' is unavailable.`);
    return node_fs_1.default.readFileSync(absolute, "utf8");
}
function normalizeEntryLimit(value) {
    if (value === undefined)
        return exports.DEFAULT_CONTEXT_PACK_ENTRY_LIMIT;
    if (!Number.isInteger(value) || value < 1 || value > 50)
        throw (0, errors_1.createUsageError)("Context pack entry limit must be an integer from 1 to 50.");
    return value;
}
function normalizeByteBudget(value) {
    if (value === undefined)
        return exports.DEFAULT_CONTEXT_PACK_BUDGET_BYTES;
    if (!Number.isInteger(value) || value < exports.MINIMUM_CONTEXT_PACK_BUDGET_BYTES || value > exports.MAXIMUM_CONTEXT_PACK_BUDGET_BYTES) {
        throw (0, errors_1.createUsageError)(`Context pack budget must be an integer from ${exports.MINIMUM_CONTEXT_PACK_BUDGET_BYTES} to ${exports.MAXIMUM_CONTEXT_PACK_BUDGET_BYTES} bytes.`);
    }
    return value;
}
