/**
 * compute-preflight — pure function that turns a candidate's code +
 * a `PackageSurfaceResolver` + the framework-level package-surface
 * manifest into a `SymbolPreflightReport`.
 *
 * Stage 1 of the W0198 two-stage grader: lifts "does symbol X export
 * from package Y" entirely out of LLM judgment. The LLM rubric runs
 * after this and is told the preflight's findings as ground truth.
 *
 * The function is pure (no I/O beyond the resolver's): it parses the
 * candidate's imports, asks the resolver about each in-scope package,
 * and emits one finding per imported binding.
 *
 * Per-binding decision tree:
 *
 *   1. Drop the binding if it isn't a `named` import. Default,
 *      namespace, and side-effect imports are intentionally not
 *      checked — the package surface only includes named exports
 *      (per the design's "named bindings only" rule), so default /
 *      namespace imports cannot be answered against it without
 *      false-deducting legitimate code.
 *
 *   2. Drop the binding if its `source` package is not in the
 *      framework-level manifest. Out-of-scope packages don't get
 *      findings — they are silently passed through to the LLM rubric.
 *
 *   3. Resolve the package surface. If the resolver throws a typed
 *      `PackageSurfaceResolverError`, every binding from that package
 *      becomes `unresolved` with the matching reason. **Never deduct.**
 *
 *   4. If the binding is in the surface, emit `exists` (no deduction).
 *
 *   5. Otherwise, emit `missing` (deterministic deduction).
 *
 * Deduction is `total = min(missing_count * perMissing, cap)`. The
 * scoring bridge (Phase 5) computes the per-dimension score from this
 * report; this function stays a pure data factory.
 */
import { PackageSurfaceResolverError, } from "../../_vendor/ailf-core/index.js";
import { parseImports } from "./parse-imports.js";
/** Default deduction config — `−20 per missing, capped at 60`. */
export const DEFAULT_DEDUCTION = {
    perMissing: 20,
    cap: 60,
};
export async function computePreflight(input) {
    const perMissing = input.deduction?.perMissing ?? DEFAULT_DEDUCTION.perMissing;
    const cap = input.deduction?.cap ?? DEFAULT_DEDUCTION.cap;
    const inScope = new Set(input.packageSurface.packages.map((p) => p.pkg));
    const imports = parseImports(input.code);
    // Bucket named imports by package so we resolve each surface at most once.
    const namedBySource = new Map();
    for (const binding of imports) {
        if (binding.kind !== "named")
            continue;
        if (!inScope.has(binding.source))
            continue;
        let bindings = namedBySource.get(binding.source);
        if (!bindings) {
            bindings = [];
            namedBySource.set(binding.source, bindings);
        }
        if (!bindings.includes(binding.imported))
            bindings.push(binding.imported);
    }
    const findings = [];
    for (const [pkg, bindings] of namedBySource) {
        let surface;
        try {
            surface = await input.resolver.resolveExports(pkg);
        }
        catch (err) {
            const reason = unresolvedReasonFor(err);
            for (const binding of bindings) {
                findings.push({ result: "unresolved", pkg, binding, reason });
            }
            continue;
        }
        const surfaceNames = new Map();
        for (const sym of surface.symbols)
            surfaceNames.set(sym.name, sym.source);
        for (const binding of bindings) {
            const source = surfaceNames.get(binding);
            if (source) {
                findings.push({
                    result: "exists",
                    pkg,
                    version: surface.version,
                    binding,
                    source,
                });
            }
            else {
                findings.push({
                    result: "missing",
                    pkg,
                    version: surface.version,
                    binding,
                });
            }
        }
    }
    const missingCount = findings.filter((f) => f.result === "missing").length;
    const total = Math.min(missingCount * perMissing, cap);
    return {
        candidate: { ...input.candidate },
        findings,
        deduction: { perMissing, cap, total },
    };
}
function unresolvedReasonFor(err) {
    if (err instanceof PackageSurfaceResolverError) {
        return err.reason;
    }
    // Anything else from the resolver is treated as a parse failure —
    // fail-loud, never-deduct.
    return "parse-failed";
}
