/**
 * Build a `(filePath, name) → graphNodeId` lookup over the graph's
 * Function/Method/Class/Constructor nodes. Two keys per node:
 *
 *   - simple name (`User` / `save`) — legacy fallback
 *   - qualified name when derivable from the node id (`User.save`)
 *
 * The qualified key is the authoritative one when two classes in the
 * same file define a method with the same simple name
 * (`class User: def save` + `class Document: def save`). Without it,
 * the simple-name key collides and every `document.save()` CALLS edge
 * would silently target `User.save`. Method node ids encode the
 * qualifier (`Method:file.py:User.save#1`), so we parse it back out.
 *
 * Language-agnostic seam. Any language provider migrating to the
 * registry-primary path can consume this to translate scope-resolution
 * `SymbolDefinition.nodeId` values into the legacy graph-node ID
 * format that downstream consumers (queries, edges, MCP) expect.
 */
import { templateConstraintsIdTag } from '../../utils/template-arguments.js';
/**
 * Parse a qualified name out of a Function/Method node id.
 *
 * Node id format: `${label}:${filePath}:${qualifiedName}${arityTag}`,
 * where `arityTag` is `#<n>` (or empty). Strips the known-length
 * label + filePath prefix so colons inside `filePath` (Windows
 * `C:\...`) don't break the parse. Returns `undefined` when the id
 * doesn't match the expected shape.
 */
function parseQualifiedFromId(id, label, filePath) {
    const prefix = `${label}:${filePath}:`;
    if (!id.startsWith(prefix))
        return undefined;
    const suffix = id.slice(prefix.length);
    if (suffix.length === 0)
        return undefined;
    const hash = suffix.indexOf('#');
    return hash === -1 ? suffix : suffix.slice(0, hash);
}
/**
 * Build a qualified-key string in a separate keyspace from simple-key
 * strings. Prefix `<q>` can't appear in a valid filePath on any OS, so
 * no collision between the two keyspaces is possible.
 *
 * Includes the node label so a top-level `def save` (Function,
 * qualifier = `save`) doesn't alias a class method `User.save` (Method,
 * simple name = `save`) whose Function-typed qualifier would collapse
 * to the same simple-key slot in a single map.
 */
export function qualifiedKey(filePath, label, qualifiedName) {
    return `<q>:${filePath}::${label}::${qualifiedName}`;
}
/** Simple-name key (legacy fallback keyspace — no `<q>` prefix). */
export function simpleKey(filePath, name) {
    return `${filePath}::${name}`;
}
export function buildGraphNodeLookup(graph) {
    const lookup = new Map();
    for (const node of graph.iterNodes()) {
        const props = node.properties;
        if (props.filePath === undefined || props.name === undefined)
            continue;
        if (!isLinkableLabel(node.label))
            continue;
        // Primary key: fully-qualified name + label, in a separate
        // keyspace from simple names. Class nodes carry `qualifiedName`
        // in their properties (set by the parsing processor).
        // Method/Function nodes do not, so derive the qualifier from the
        // node id — that's where the parse-phase encoded it. Including
        // the label avoids a collision when a free Function's qualifier
        // happens to equal a Method's simple name (e.g. top-level
        // `def save` vs `class User: def save`).
        const qualified = props.qualifiedName ?? parseQualifiedFromId(node.id, node.label, props.filePath);
        if (qualified !== undefined && qualified.length > 0) {
            const qKey = qualifiedKey(props.filePath, node.label, qualified);
            if (!lookup.has(qKey))
                lookup.set(qKey, node.id);
            // Overload-disambiguating key: include parameter types so two
            // same-arity overloads (e.g. `Lookup(int)` vs `Lookup(string)`)
            // map to distinct graph nodes. Legacy parse-phase encodes the
            // type tag into the node id; we register both that node id and
            // a parameter-types-suffixed key so resolveDefGraphId can find
            // the right overload by matching its def's parameterTypes.
            const pTypes = props.parameterTypes;
            if (pTypes !== undefined && pTypes.length > 0 && node.label === 'Method') {
                const pKey = qualifiedKey(props.filePath, node.label, `${qualified}~${pTypes.join(',')}`);
                // Each overload is unique — set unconditionally.
                lookup.set(pKey, node.id);
            }
            // SFINAE / `requires`-clause disambiguation (issue #1579) — register
            // a constraint-fingerprinted key so resolveDefGraphId can locate the
            // correct overload by hashing the def's `templateConstraints`. Mirrors
            // the parameter-types key but keys on the opaque constraint payload
            // instead, separating two `process<T>` overloads whose
            // `parameterTypes=['T']` would otherwise collide.
            const tConstraints = props.templateConstraints;
            if (tConstraints !== undefined && (node.label === 'Function' || node.label === 'Method')) {
                const cKey = qualifiedKey(props.filePath, node.label, `${qualified}${templateConstraintsIdTag(tConstraints)}`);
                lookup.set(cKey, node.id);
            }
            if ((node.label === 'Class' ||
                node.label === 'Struct' ||
                node.label === 'Interface' ||
                node.label === 'Enum' ||
                node.label === 'Record') &&
                props.templateArguments !== undefined &&
                props.templateArguments.length > 0) {
                const tKey = qualifiedKey(props.filePath, node.label, `${qualified}~${props.templateArguments.join(',')}`);
                if (!lookup.has(tKey))
                    lookup.set(tKey, node.id);
            }
        }
        // Fallback key: simple name. First-wins within a file — used when
        // the caller doesn't know the qualifier (unqualified free-call
        // fallback, cross-file resolution where MethodRegistry already
        // disambiguated the owner).
        const sKey = simpleKey(props.filePath, props.name);
        if (!lookup.has(sKey))
            lookup.set(sKey, node.id);
    }
    return lookup;
}
export function isLinkableLabel(label) {
    return (label === 'Function' ||
        label === 'Method' ||
        label === 'Constructor' ||
        label === 'Class' ||
        label === 'Interface' ||
        label === 'Struct' ||
        label === 'Enum' ||
        // Trait nodes are linkable so MRO builders can bridge PHP/Rust trait
        // defs between scope-resolution DefIds and the graph's node ids.
        // IMPLEMENTS edges from classes to traits are otherwise invisible to
        // the scope-resolution MRO pass.
        label === 'Trait' ||
        // Variable / Property are linkable too — receiver-bound write/read
        // ACCESSES edges target field nodes (e.g. `user.name = "x"` →
        // ACCESSES edge to User's `name` Variable/Property node).
        label === 'Variable' ||
        label === 'Property' ||
        // Const is linkable so the value-receiver-owner bridge in
        // `receiver-bound-calls.ts` Case 5 can translate the scope-resolution
        // `Variable` def for `export const fooService = {...}` to the canonical
        // `Const:filePath:name` graph node id, against which object-literal
        // method symbols register their `ownerId` (PR #1718 / issue #1358).
        label === 'Const');
}
