/**
 * Processes → microcall context (TODO #51 phase 2C, engines 6.32.0+).
 *
 * Phase 2A + 2B closed gaps 1+3 of Dom Williams's 2026-05-06 audit
 * (IMPLEMENTS → advertises, Zod schemas → entity attributes). This
 * module closes gap 2: cross-entity Process flows.
 *
 * GitNexus extracts ~149 Process nodes from idle-meta — each is a
 * heuristic-labelled trace from an entry point (route handler, exported
 * function) through method calls to a terminal (response, throw, leaf
 * method). The orchestrator already loads them into `facts.processes`
 * (filtered to stepCount ≥ 3). What was missing: connecting each
 * process to the matching action stub so per-action microcalls receive
 * pre-grouped cross-method context.
 *
 * The microcall already gets a per-method `candidateSteps` timeline
 * (line-level walker output for the method body). Processes complement
 * that with the *outbound* call chain — which other entities/methods
 * this action invokes. Together they let the LLM emit far more accurate
 * `[call] OtherClass.method` step bullets without re-discovering the
 * call graph from raw source.
 */
export function parseSymbolId(id) {
    // Format: `<Kind>:<filePath>:<lastSegment>#<paramCount>?`
    // Where lastSegment is `<Class>.<method>` for Methods, `<name>` for Functions.
    // The filePath itself can contain colons in rare cases (Windows drive
    // letters) but we don't see those — repo paths are forward-slash relative.
    const hashIdx = id.lastIndexOf('#');
    const head = hashIdx >= 0 ? id.slice(0, hashIdx) : id;
    const paramCount = hashIdx >= 0 ? parseInt(id.slice(hashIdx + 1), 10) : undefined;
    const firstColon = head.indexOf(':');
    if (firstColon < 0)
        return null;
    const kind = head.slice(0, firstColon);
    const rest = head.slice(firstColon + 1);
    const lastColon = rest.lastIndexOf(':');
    if (lastColon < 0)
        return null;
    const filePath = rest.slice(0, lastColon);
    const lastSegment = rest.slice(lastColon + 1);
    const dotIdx = lastSegment.indexOf('.');
    let className;
    let methodName;
    if (dotIdx >= 0) {
        className = lastSegment.slice(0, dotIdx);
        methodName = lastSegment.slice(dotIdx + 1);
    }
    else {
        className = undefined;
        methodName = lastSegment;
    }
    return {
        kind,
        filePath,
        className,
        methodName,
        paramCount: Number.isFinite(paramCount) ? paramCount : undefined,
    };
}
/**
 * True when the symbol id likely refers to the same source location as
 * the action stub. Match is best-effort: paths are compared by tail
 * (process paths are repo-relative; stub paths often include
 * `/run/input/`-style prefixes), method name must match exactly.
 */
function symbolMatchesStub(parsed, stub) {
    if (parsed.methodName !== stub.actionName)
        return false;
    // Strip the `:<lineRange>` suffix the source-location-formatter sometimes adds
    // and compare path tails.
    const stubPath = stub.sourceLocation.filePath.replace(/:[0-9-]+$/, '');
    return stubPath.endsWith(parsed.filePath) || parsed.filePath.endsWith(stubPath);
}
/**
 * Format a single process's step timeline as a human-readable block.
 * Truncates very long process trails to keep prompt size bounded.
 */
export function formatProcess(process, maxSteps = 12) {
    const sortedSteps = [...process.steps].sort((a, b) => a.step - b.step);
    const tail = sortedSteps.length > maxSteps;
    const shown = tail ? sortedSteps.slice(0, maxSteps) : sortedSteps;
    const lines = [];
    lines.push(`- ${process.label} (${process.stepCount} steps, ${process.processType})`);
    for (const s of shown) {
        const parsed = parseSymbolId(s.symbolId);
        const display = parsed
            ? `${parsed.className ? parsed.className + '.' : ''}${parsed.methodName}`
            : s.symbolId;
        lines.push(`    ${s.step}. ${display}`);
    }
    if (tail)
        lines.push(`    … (${process.stepCount - maxSteps} more)`);
    return lines.join('\n');
}
/**
 * Build a per-stub map of formatted process context. Keyed by
 * `${stub.candidateMethodRef.className}::${stub.candidateMethodRef.methodIndex}` —
 * the same key shape the orchestrator uses for `candidateStepsByMethodKey`,
 * so the consumer just plumbs the new map alongside.
 *
 * Only processes whose entry point OR terminal OR any step matches the
 * stub's source method are attached. Each stub gets at most `topN`
 * processes (sorted by stepCount descending — longer chains tend to
 * carry more information). When no processes match, the stub is absent
 * from the map and the orchestrator falls back to its empty default.
 */
export function buildProcessContextByMethodKey(opts) {
    const out = new Map();
    const processes = opts.processes ?? [];
    const stubs = opts.actionStubs ?? [];
    if (processes.length === 0 || stubs.length === 0)
        return out;
    const topN = opts.topN ?? 3;
    for (const stub of stubs) {
        const key = `${stub.candidateMethodRef.className}::${stub.candidateMethodRef.methodIndex}`;
        if (out.has(key))
            continue; // dedup — multiple stubs may share a method
        const matched = [];
        for (const p of processes) {
            const entry = parseSymbolId(p.entryPointId);
            const term = parseSymbolId(p.terminalId);
            // Quick check on entry/terminal first (cheap).
            if (entry && symbolMatchesStub(entry, stub)) {
                matched.push(p);
                continue;
            }
            if (term && symbolMatchesStub(term, stub)) {
                matched.push(p);
                continue;
            }
            // Fallback: any step references the stub's method.
            const hit = p.steps.some((s) => {
                const parsed = parseSymbolId(s.symbolId);
                return parsed !== null && symbolMatchesStub(parsed, stub);
            });
            if (hit)
                matched.push(p);
        }
        if (matched.length === 0)
            continue;
        matched.sort((a, b) => b.stepCount - a.stepCount);
        const top = matched.slice(0, topN);
        const formatted = top.map((p) => formatProcess(p)).join('\n');
        out.set(key, formatted);
    }
    return out;
}
//# sourceMappingURL=processes-to-microcall-context.js.map