/**
 * astro_audit — meta-tool that runs all Astro tools in one call.
 *
 * Runs in parallel:
 *   - astro_config_analyze
 *   - astro_analyze_islands
 *   - astro_hydration_audit
 *   - astro_route_map
 *   - astro_actions_audit (if available)
 *   - astro_content_collections (if available)
 *   - astro_migration_check (if available)
 *   - 13 Astro patterns via search_patterns
 *
 * Returns a unified health report with letter score and per-section gates.
 * Mirrors the react_quickstart pattern.
 */
import { getCodeIndex } from "./index-tools.js";
import { analyzeIslandsFromIndex, hydrationAuditFromIndex } from "./astro-islands.js";
import { buildRouteEntries } from "./astro-routes.js";
import { extractAstroConventions } from "./astro-config.js";
// ---------------------------------------------------------------------------
// Astro pattern names (13 patterns from pattern-tools.ts)
// ---------------------------------------------------------------------------
export const ASTRO_PATTERNS = [
    "astro-client-on-astro",
    "astro-glob-usage",
    "astro-set-html-xss",
    "astro-img-element",
    "astro-missing-getStaticPaths",
    "astro-legacy-content-collections",
    "astro-no-image-dimensions",
    "astro-inline-script-no-is-inline",
    "astro-env-secret-in-client",
    "astro-hardcoded-site-url",
    "astro-missing-lang-attr",
    "astro-form-without-action",
    "astro-view-transitions-deprecated",
];
// ---------------------------------------------------------------------------
// Scoring helpers
// ---------------------------------------------------------------------------
export function deriveOverallScore(gates) {
    // 'skipped' gates do not contribute to scoring — they reflect missing input
    // (e.g., no middleware.ts), not a quality signal. Active gates count as before.
    const active = Object.values(gates).filter((g) => g !== "skipped");
    const failCount = active.filter((g) => g === "fail").length;
    const warnCount = active.filter((g) => g === "warn").length;
    const total = active.length;
    // Scale warn thresholds by active gate count (~30% warn → C, ~10% warn → B)
    // so that adding sub-gates doesn't artificially worsen the score.
    const warnCThreshold = Math.max(3, Math.ceil(total * 0.3));
    const warnBThreshold = 1;
    if (failCount >= 2)
        return "D";
    if (failCount === 1)
        return "C";
    if (warnCount >= warnCThreshold)
        return "C";
    if (warnCount >= warnBThreshold)
        return "B";
    return "A";
}
function hydrationScoreToGate(score, errors, warnings) {
    if (score === "D" || errors >= 1)
        return "fail";
    if (score === "C" || warnings >= 3)
        return "warn";
    if (score === "B" || warnings >= 1)
        return "warn";
    return "pass";
}
// ---------------------------------------------------------------------------
// Optional tool loader — gracefully handles missing modules
// ---------------------------------------------------------------------------
/**
 * Attempt to import an optional Astro tool module and call its named export.
 * Returns null if the module doesn't exist (file not yet implemented).
 *
 * We resolve the module path at runtime using createRequire to avoid TypeScript
 * compile-time module resolution for not-yet-implemented tool files.
 */
async function tryImportOptionalTool(moduleSuffix, exportName, toolArgs) {
    // Use createRequire for dynamic resolution so TypeScript doesn't reject the path.
    // The try/catch swallows MODULE_NOT_FOUND for tools not yet implemented.
    try {
        const { createRequire } = await import("node:module");
        const requireFn = createRequire(import.meta.url);
        const resolvedPath = requireFn.resolve(`./${moduleSuffix}.js`);
        const mod = (await import(resolvedPath));
        const fn = mod[exportName];
        if (typeof fn === "function") {
            return await fn(toolArgs);
        }
    }
    catch {
        // Module doesn't exist yet or export is missing — skip gracefully
    }
    return null;
}
// ---------------------------------------------------------------------------
// Core logic (works with a CodeIndex directly — testable without getCodeIndex)
// ---------------------------------------------------------------------------
export async function astroAuditFromIndex(index, skip, patternCounts) {
    // Run all tools in parallel
    const [configResult, islandsResult, hydrationResult, routeResult] = await Promise.all([
        // Config
        skip.has("config")
            ? Promise.resolve(null)
            : extractAstroConventions([], index.root).catch(() => null),
        // Islands
        skip.has("islands")
            ? Promise.resolve(null)
            : Promise.resolve(analyzeIslandsFromIndex(index)).catch(() => null),
        // Hydration
        skip.has("hydration")
            ? Promise.resolve(null)
            : Promise.resolve(hydrationAuditFromIndex(index)).catch(() => null),
        // Routes
        skip.has("routes")
            ? Promise.resolve(null)
            : Promise.resolve(buildRouteEntries(index)).catch(() => null),
    ]);
    // Optional tools (astro_actions_audit, astro_content_collections, astro_migration_check)
    // These tool files may not exist yet — attempt dynamic import, fall back gracefully.
    const [actionsResult, contentResult, migrationResult] = await Promise.all([
        skip.has("actions")
            ? Promise.resolve(null)
            : tryImportOptionalTool(
            /* module path resolved at runtime — gracefully skipped if file doesn't exist */
            "./astro-actions", "astroActionsAudit", { repo: index.repo }),
        skip.has("content")
            ? Promise.resolve(null)
            : tryImportOptionalTool("./astro-content-collections", "astroContentCollections", { repo: index.repo }),
        skip.has("migration")
            ? Promise.resolve(null)
            : tryImportOptionalTool("./astro-migration", "astroMigrationCheck", { repo: index.repo }),
    ]);
    // Astro 5 sub-tools (Tasks 2-7). Each call is wrapped so a thrown error keeps
    // the gate at 'skipped' rather than failing the whole audit.
    const [middlewareResult, sessionsResult, dbResult, envResult, imageResult, svgResult] = await Promise.all([
        skip.has("middleware") ? Promise.resolve(null) : tryImportOptionalTool("./astro-middleware", "astroMiddlewareAudit", { project_root: index.root }),
        skip.has("sessions") ? Promise.resolve(null) : tryImportOptionalTool("./astro-sessions", "astroSessionsAudit", { project_root: index.root }),
        skip.has("db") ? Promise.resolve(null) : tryImportOptionalTool("./astro-db-audit", "astroDbAudit", { project_root: index.root }),
        skip.has("env") ? Promise.resolve(null) : tryImportOptionalTool("./astro-env-validator", "astroEnvValidator", { project_root: index.root }),
        skip.has("image") ? Promise.resolve(null) : tryImportOptionalTool("./astro-image-audit", "astroImageAudit", { project_root: index.root }),
        skip.has("svg") ? Promise.resolve(null) : tryImportOptionalTool("./astro-svg-components", "astroSvgComponents", { project_root: index.root }),
    ]);
    // ---------------------------------------------------------------------------
    // Build sections
    // ---------------------------------------------------------------------------
    const sections = {};
    const recommendations = [];
    // Config section
    let configGate = "pass";
    if (!skip.has("config")) {
        if (!configResult) {
            configGate = "fail";
            recommendations.push("Config analysis failed — ensure astro.config.mjs/ts exists and is parseable.");
        }
        else {
            sections.config = {
                output_mode: configResult.conventions.output_mode,
                integrations: configResult.conventions.integrations,
                issue_count: configResult.issues.length,
            };
            if (configResult.issues.length > 0) {
                configGate = "fail";
                recommendations.push(`Fix ${configResult.issues.length} config issue(s): ${configResult.issues[0]}`);
            }
        }
    }
    // Islands section
    if (!skip.has("islands") && islandsResult) {
        sections.islands = {
            total_islands: islandsResult.summary.total_islands,
            by_directive: islandsResult.summary.by_directive,
            total_js_budget_kb: islandsResult.summary.budget?.total_js_budget_kb ?? 0,
        };
        if (islandsResult.summary.warnings.length > 0) {
            recommendations.push(`Islands: ${islandsResult.summary.warnings[0]}`);
        }
    }
    // Hydration section
    let hydrationGate = "pass";
    if (!skip.has("hydration")) {
        if (!hydrationResult) {
            hydrationGate = "fail";
        }
        else {
            const errors = hydrationResult.issues.filter((i) => i.severity === "error").length;
            const warnings = hydrationResult.issues.filter((i) => i.severity === "warning").length;
            sections.hydration = {
                score: hydrationResult.score,
                total_issues: hydrationResult.issues.length,
                errors,
                warnings,
            };
            hydrationGate = hydrationScoreToGate(hydrationResult.score, errors, warnings);
            if (errors > 0) {
                const topError = hydrationResult.issues.find((i) => i.severity === "error");
                if (topError)
                    recommendations.push(`Hydration error (${topError.code}): ${topError.fix}`);
            }
            else if (warnings >= 3) {
                recommendations.push(`${warnings} hydration warning(s) — review client:load usage`);
            }
        }
    }
    // Routes section
    let routesGate = "pass";
    if (!skip.has("routes")) {
        if (!routeResult) {
            routesGate = "fail";
        }
        else {
            sections.routes = {
                total_routes: routeResult.routes.length,
                warnings: routeResult.warnings,
            };
            if (routeResult.warnings.length >= 3) {
                routesGate = "fail";
                recommendations.push(`${routeResult.warnings.length} route issue(s): ${routeResult.warnings[0]}`);
            }
            else if (routeResult.warnings.length > 0) {
                routesGate = "warn";
                recommendations.push(`Route warning: ${routeResult.warnings[0]}`);
            }
        }
    }
    // Actions section — optional tool
    let actionsGate = "pass";
    if (!skip.has("actions") && actionsResult !== null) {
        const ar = actionsResult;
        sections.actions = {
            total_actions: ar.total_actions ?? 0,
            score: ar.score ?? "A",
            total_issues: ar.total_issues ?? (ar.issues ? ar.issues.length : 0),
        };
        if (sections.actions.score === "D" || sections.actions.score === "C") {
            actionsGate = "fail";
        }
        else if (sections.actions.score === "B" || sections.actions.total_issues > 0) {
            actionsGate = "warn";
        }
    }
    // Content section — optional tool
    let contentGate = "pass";
    if (!skip.has("content") && contentResult !== null) {
        const cr = contentResult;
        const totalCollections = cr.total_collections ?? (cr.collections ? cr.collections.length : 0);
        const collectionsWithIssues = cr.collections_with_issues ?? 0;
        sections.content = {
            total_collections: totalCollections,
            total_entries: cr.total_entries ?? 0,
            collections_with_issues: collectionsWithIssues,
        };
        if (collectionsWithIssues > 0) {
            contentGate = "warn";
            recommendations.push(`${collectionsWithIssues} content collection(s) have issues`);
        }
    }
    // Migration section — optional tool
    let migrationGate = "pass";
    if (!skip.has("migration") && migrationResult !== null) {
        const mr = migrationResult;
        const breakingCount = mr.total_breaking_changes ?? (mr.breaking_changes ? mr.breaking_changes.length : 0);
        sections.migration = {
            current_version: mr.current_version ?? null,
            target_version: mr.target_version ?? "latest",
            total_breaking_changes: breakingCount,
            estimated_hours: mr.estimated_hours ?? "0",
        };
        if (breakingCount > 0) {
            migrationGate = "warn";
            recommendations.push(`${breakingCount} breaking change(s) to address for migration to ${sections.migration.target_version}`);
        }
    }
    // Patterns section
    let patternsGate = "pass";
    if (!skip.has("patterns") && patternCounts !== null && patternCounts !== undefined) {
        const fired = patternCounts.filter((r) => r.count > 0);
        const totalMatches = fired.reduce((sum, r) => sum + r.count, 0);
        sections.patterns = {
            total_matches: totalMatches,
            patterns_fired: fired.map((r) => r.pattern),
        };
        if (fired.length >= 3) {
            patternsGate = "fail";
            recommendations.push(`${fired.length} anti-pattern(s) detected (${fired[0].pattern}, …) — run search_patterns for details`);
        }
        else if (fired.length > 0) {
            patternsGate = "warn";
            recommendations.push(`Anti-pattern: ${fired[0].pattern} (${fired[0].count} match(es))`);
        }
    }
    // ---------------------------------------------------------------------------
    // Assemble gates
    // ---------------------------------------------------------------------------
    // Helper: translate sub-tool result with `issues` array into gate + section.
    function gateFromIssues(result, errorThreshold = 1) {
        if (!result || typeof result !== "object")
            return "skipped"; // narrowed below
        const issues = result.issues ?? [];
        const errors = issues.filter((i) => i.severity === "error").length;
        if (errors >= errorThreshold)
            return "fail";
        if (issues.length > 0)
            return "warn";
        return "pass";
    }
    let middlewareGate = "skipped";
    let sessionsGate = "skipped";
    let dbGate = "skipped";
    let envGate = "skipped";
    let imageGate = "skipped";
    let svgGate = "skipped";
    if (middlewareResult && typeof middlewareResult === "object") {
        const r = middlewareResult;
        if (r.handlers.length > 0 || r.issues.length > 0) {
            middlewareGate = gateFromIssues(r);
            sections.middleware = { handlers: r.handlers, routes_protected_count: r.routes_protected_count, issues_total: r.summary.issues_total };
        }
    }
    if (sessionsResult && typeof sessionsResult === "object") {
        const r = sessionsResult;
        if (r.config_found || r.usage_count > 0) {
            sessionsGate = gateFromIssues(r);
            sections.sessions = { adapter: r.adapter, sessions_enabled: r.sessions_enabled, usage_count: r.usage_count, issues_total: r.summary.issues_total };
        }
    }
    if (dbResult && typeof dbResult === "object") {
        const r = dbResult;
        if (r.config_file || r.tables.length > 0) {
            dbGate = gateFromIssues(r);
            sections.db = { tables_total: r.tables.length, n_plus_one_count: r.n_plus_one.length, missing_indexes_count: r.missing_indexes.length, issues_total: r.summary.issues_total };
        }
    }
    if (envResult && typeof envResult === "object") {
        const r = envResult;
        if (r.config_found && r.declared_vars.length + r.used_vars.length > 0) {
            envGate = gateFromIssues(r);
            sections.env = { declared: r.declared_vars.length, used: r.used_vars.length, missing_count: r.missing.length, unused_count: r.unused.length, issues_total: r.summary.issues };
        }
    }
    if (imageResult && typeof imageResult === "object") {
        const r = imageResult;
        if (r.summary.files_scanned > 0 && (r.raw_img_count > 0 || r.image_component_count > 0 || r.issues.length > 0)) {
            imageGate = gateFromIssues(r);
            sections.image = { raw_img_count: r.raw_img_count, image_component_count: r.image_component_count, issues_total: r.summary.issues_total };
        }
    }
    if (svgResult && typeof svgResult === "object") {
        const r = svgResult;
        if (r.imports.length > 0 || r.issues.length > 0) {
            svgGate = gateFromIssues(r);
            sections.svg = { imports_total: r.imports.length, unused_count: r.unused.length, issues_total: r.summary.issues_total };
        }
    }
    const gates = {
        config: configGate,
        hydration: hydrationGate,
        routes: routesGate,
        actions: actionsGate,
        content: contentGate,
        migration: migrationGate,
        patterns: patternsGate,
        middleware: middlewareGate,
        sessions: sessionsGate,
        db: dbGate,
        env: envGate,
        image: imageGate,
        svg: svgGate,
    };
    // Trim recommendations to top 5
    const topRecommendations = recommendations.slice(0, 5);
    return {
        score: deriveOverallScore(gates),
        gates,
        sections,
        recommendations: topRecommendations,
    };
}
// ---------------------------------------------------------------------------
// Main meta-tool (MCP entry point)
// ---------------------------------------------------------------------------
export async function astroAudit(args) {
    const repo = args.repo ?? "";
    const skip = new Set(args.skip ?? []);
    const index = await getCodeIndex(repo);
    if (!index) {
        // Return a minimal failure result when no index is available
        return {
            score: "D",
            gates: {
                config: "fail",
                hydration: "fail",
                routes: "fail",
                actions: "pass",
                content: "pass",
                migration: "pass",
                patterns: "fail",
                middleware: "skipped",
                sessions: "skipped",
                db: "skipped",
                env: "skipped",
                image: "skipped",
                svg: "skipped",
            },
            sections: {},
            recommendations: ["Run index_folder to index the repository first."],
        };
    }
    // Patterns — run all 13 in parallel, aggregate (requires real repo string)
    let patternCounts;
    if (!skip.has("patterns")) {
        try {
            const { searchPatterns } = await import("./pattern-tools.js");
            patternCounts = await Promise.all(ASTRO_PATTERNS.map(async (pat) => {
                try {
                    const r = await searchPatterns(repo, pat, { max_results: 20 });
                    return { pattern: pat, count: r.matches.length };
                }
                catch {
                    return { pattern: pat, count: 0 };
                }
            }));
        }
        catch {
            patternCounts = undefined;
        }
    }
    return astroAuditFromIndex(index, skip, patternCounts);
}
//# sourceMappingURL=astro-audit.js.map