// ---------------------------------------------------------------------------
// Core logic (exported for testing)
// ---------------------------------------------------------------------------
/**
 * Format a discovery report as markdown.
 */
export function formatDiscoveryMarkdown(report) {
    const lines = [];
    // Header
    lines.push("## 🔍 Agent Discoverability Report");
    lines.push("");
    if (report.baseUrl) {
        lines.push(`**Base URL:** ${report.baseUrl}`);
    }
    lines.push("**Mode:** Agentic");
    lines.push("");
    // Retrieval summary table
    lines.push("### Retrieval Summary");
    lines.push("");
    lines.push("| Metric | Value |");
    lines.push("|---|---|");
    lines.push(`| Recall (canonical docs found) | ${pct(report.overall.avgRecall)} (${report.totalHits}/${report.totalCanonicalDocs}) |`);
    lines.push(`| Precision (relevant docs fetched) | ${pct(report.overall.avgPrecision)} |`);
    lines.push(`| F1 Score | ${report.overall.avgF1.toFixed(2)} |`);
    lines.push(`| Invisible docs | ${report.invisibleDocs.length} |`);
    lines.push("");
    // Per-area breakdown
    if (report.areas.length > 0) {
        lines.push("### Per-Area Breakdown");
        lines.push("");
        lines.push("| Area | Recall | Precision | F1 | Tasks |");
        lines.push("|---|---|---|---|---|");
        for (const area of sortedAreas(report.areas)) {
            lines.push(`| ${area.area} | ${pct(area.avgRecall)} | ${pct(area.avgPrecision)} | ${area.avgF1.toFixed(2)} | ${area.taskCount} |`);
        }
        lines.push("");
    }
    // Invisible documents
    if (report.invisibleDocs.length > 0) {
        lines.push("### Invisible Documents (never retrieved by any task)");
        lines.push("");
        for (const doc of report.invisibleDocs) {
            const refs = doc.referencedBy.join(", ");
            lines.push(`- \`${doc.slug}\` — referenced by ${refs}`);
        }
        lines.push("");
    }
    // Recommendations
    if (report.recommendations.length > 0) {
        lines.push("### Recommendations");
        lines.push("");
        for (let i = 0; i < report.recommendations.length; i++) {
            lines.push(`${i + 1}. ${report.recommendations[i]}`);
        }
        lines.push("");
    }
    return lines.join("\n");
}
/**
 * Generate a structured discovery report from a score summary.
 *
 * @param summary - Parsed score-summary.json
 * @param areaFilter - Optional area names to include (all if empty)
 */
export function generateDiscoveryReport(summary, areaFilter) {
    const metrics = summary.retrievalMetrics;
    if (!metrics) {
        throw new Error("score-summary.json does not contain retrievalMetrics. " +
            "Run an agentic evaluation first: pnpm pipeline -- --mode agentic");
    }
    // Apply area filter
    const areas = areaFilter && areaFilter.length > 0
        ? metrics.areas.filter((a) => areaFilter.includes(a.area))
        : metrics.areas;
    if (areaFilter && areaFilter.length > 0 && areas.length === 0) {
        throw new Error(`No retrieval data found for area(s): ${areaFilter.join(", ")}. ` +
            `Available areas: ${metrics.areas.map((a) => a.area).join(", ")}`);
    }
    // Recompute overall metrics for filtered areas
    const overall = areas.length === metrics.areas.length
        ? metrics.overall
        : computeOverall(areas);
    // Build invisible docs list with task references
    const invisibleDocs = buildInvisibleDocs(areas);
    // Compute totals for the summary table
    const allTasks = areas.flatMap((a) => a.tasks);
    const allExpected = new Set(allTasks.flatMap((t) => t.expected));
    const allHits = new Set(allTasks.flatMap((t) => t.hits));
    const totalCanonicalDocs = allExpected.size;
    const totalHits = allHits.size;
    // Generate recommendations
    const recommendations = generateRecommendations(invisibleDocs, areas, overall);
    return {
        areas,
        baseUrl: summary.source?.baseUrl,
        invisibleDocs,
        overall,
        recommendations,
        timestamp: summary.timestamp,
        totalCanonicalDocs,
        totalHits,
    };
}
// ---------------------------------------------------------------------------
// Helpers (alphabetical for perfectionist/sort-modules)
// ---------------------------------------------------------------------------
function buildInvisibleDocs(areas) {
    // Collect all invisible slugs and map them to the tasks that reference them
    const slugToTasks = new Map();
    for (const area of areas) {
        for (const task of area.tasks) {
            for (const slug of task.missed) {
                // Check if this slug is globally invisible (never retrieved by ANY task)
                const isGloballyInvisible = areas.every((a) => a.tasks.every((t) => !t.retrieved.includes(slug)));
                if (isGloballyInvisible) {
                    if (!slugToTasks.has(slug)) {
                        slugToTasks.set(slug, new Set());
                    }
                    slugToTasks.get(slug).add(task.taskId);
                }
            }
        }
    }
    return [...slugToTasks.entries()]
        .map(([slug, tasks]) => ({
        referencedBy: [...tasks].sort(),
        slug,
    }))
        .sort((a, b) => b.referencedBy.length - a.referencedBy.length);
}
function computeOverall(areas) {
    if (areas.length === 0) {
        return { avgF1: 0, avgPrecision: 0, avgRecall: 0 };
    }
    // Weight by task count for fair averaging
    const totalTasks = areas.reduce((s, a) => s + a.taskCount, 0);
    if (totalTasks === 0) {
        return { avgF1: 0, avgPrecision: 0, avgRecall: 0 };
    }
    const avgRecall = areas.reduce((s, a) => s + a.avgRecall * a.taskCount, 0) / totalTasks;
    const avgPrecision = areas.reduce((s, a) => s + a.avgPrecision * a.taskCount, 0) / totalTasks;
    const avgF1 = areas.reduce((s, a) => s + a.avgF1 * a.taskCount, 0) / totalTasks;
    return { avgF1, avgPrecision, avgRecall };
}
function generateRecommendations(invisibleDocs, areas, overall) {
    const recs = [];
    // Recommend adding invisible docs to llms.txt
    const highImpactInvisible = invisibleDocs.filter((d) => d.referencedBy.length > 0);
    for (const doc of highImpactInvisible.slice(0, 5)) {
        const taskWord = doc.referencedBy.length === 1 ? "task" : "tasks";
        recs.push(`Add \`${doc.slug}\` to llms.txt (referenced by ${doc.referencedBy.length} ${taskWord})`);
    }
    // Recommend cross-linking for invisible docs
    if (invisibleDocs.length > 0) {
        recs.push(`Improve cross-linking to ${invisibleDocs.length} invisible document${invisibleDocs.length === 1 ? "" : "s"}`);
    }
    // Flag low-recall areas
    const lowRecallAreas = areas.filter((a) => a.avgRecall < 0.5);
    for (const area of lowRecallAreas) {
        recs.push(`Investigate low recall in \`${area.area}\` (${pct(area.avgRecall)}) — agents miss most canonical docs`);
    }
    // Flag low-precision areas
    const lowPrecisionAreas = areas.filter((a) => a.avgPrecision < 0.5);
    for (const area of lowPrecisionAreas) {
        recs.push(`Review search relevance for \`${area.area}\` (precision ${pct(area.avgPrecision)}) — agents fetch many irrelevant docs`);
    }
    // Overall recommendation
    if (overall.avgF1 < 0.6) {
        recs.push("Overall F1 is below 0.60 — consider a documentation restructure for agent accessibility");
    }
    return recs;
}
function pct(value) {
    return `${Math.round(value * 100)}%`;
}
function sortedAreas(areas) {
    return [...areas].sort((a, b) => a.area.localeCompare(b.area));
}
