/**
 * Reconcile scope-resolution's ownership view into the SemanticModel.
 *
 * For migrated languages (Python in particular) the legacy `parse` phase
 * emits class-body callables without `ownerId` because
 * `parsing-processor`'s `resolveEnclosingOwner` is language-dependent and
 * not every extractor carries the enclosing-class info at parse time.
 * Scope-resolution later calls `provider.populateOwners(parsed)`, which
 * stamps the correct `ownerId` onto `parsed.localDefs[i]`. This pass
 * mirrors those corrections into `model.methods` and `model.fields` so
 * downstream passes can consult `SemanticModel` as the single
 * authoritative owner-keyed index — no parallel scope-resolution
 * registry is needed.
 *
 * ## Single-source-of-truth invariant (I9)
 *
 * After this pass runs, every `def in parsed.localDefs` with a non-
 * undefined `ownerId` is reachable via either:
 *   - `model.methods.lookupAllByOwner(ownerId, simpleName)` — if the
 *     def is a Method / Function / Constructor, OR
 *   - `model.fields.lookupAllByOwner(ownerId, simpleName)` — if the
 *     def is a Property / Variable / Const / Static.
 *
 * This invariant is the foundation of Contract Invariant I9
 * (`contract/scope-resolver.ts`): scope-resolution passes MUST read
 * symbol-keyed lookups exclusively from `SemanticModel`.
 *
 * ## Idempotency
 *
 * The pass skips registration when `(ownerId, simpleName)` already
 * contains a def with matching `nodeId`. Safe to call multiple times
 * or after a language whose legacy extractor does populate `ownerId`
 * (C#) — no duplicates are introduced.
 *
 * ## Transitional shim
 *
 * This reconciliation pass is an explicit shim. The architectural end
 * state is for the legacy extractor to emit the correct `ownerId` for
 * every language at parse time, removing the need for a second pass.
 * See ARCHITECTURE.md § "Semantic-model source of truth" for the
 * follow-up plan.
 */
import { simpleQualifiedName } from '../graph-bridge/ids.js';
const NESTED_TYPE_KINDS = new Set([
    'Class',
    'Interface',
    'Enum',
    'Struct',
    'Union',
    'Trait',
    'TypeAlias',
    'Typedef',
    'Record',
    'Delegate',
    'Annotation',
    'Template',
    'Namespace',
]);
export function reconcileOwnership(parsedFiles, model) {
    let methodsRegistered = 0;
    let fieldsRegistered = 0;
    let nestedTypesRegistered = 0;
    let skippedAlreadyPresent = 0;
    for (const parsed of parsedFiles) {
        for (const def of parsed.localDefs) {
            const ownerId = def.ownerId;
            if (ownerId === undefined)
                continue;
            const simple = simpleQualifiedName(def);
            if (simple === undefined)
                continue;
            if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
                const existing = model.methods.lookupAllByOwner(ownerId, simple);
                if (existing.some((e) => e.nodeId === def.nodeId)) {
                    skippedAlreadyPresent++;
                    continue;
                }
                model.methods.register(ownerId, simple, def);
                methodsRegistered++;
            }
            else if (def.type === 'Property' ||
                def.type === 'Variable' ||
                def.type === 'Const' ||
                def.type === 'Static') {
                const existing = model.fields.lookupAllByOwner(ownerId, simple);
                if (existing.some((e) => e.nodeId === def.nodeId)) {
                    skippedAlreadyPresent++;
                    continue;
                }
                model.fields.register(ownerId, simple, def);
                fieldsRegistered++;
            }
            else if (NESTED_TYPE_KINDS.has(def.type)) {
                const existing = model.types.lookupAllByOwner(ownerId, simple);
                if (existing.some((e) => e.nodeId === def.nodeId)) {
                    skippedAlreadyPresent++;
                    continue;
                }
                model.types.registerByOwner(ownerId, simple, def);
                nestedTypesRegistered++;
            }
        }
    }
    return { methodsRegistered, fieldsRegistered, nestedTypesRegistered, skippedAlreadyPresent };
}
/**
 * Debug-mode parity validator. Runs only when
 * `VALIDATE_SEMANTIC_MODEL !== '0'` AND `NODE_ENV !== 'production'`.
 *
 * Iterates every def in `parsedFiles[i].localDefs` with an `ownerId`
 * and asserts it is reachable via `model.methods.lookupAllByOwner` or
 * `model.fields.lookupFieldByOwner`. On mismatch: emits a warning via
 * `onWarn` — never throws, mirroring the pipeline's soft-fail posture.
 *
 * This is the enforcement of Contract Invariant I9 at runtime. In
 * production it is a no-op; in development it surfaces drift between
 * `parsed.localDefs` and `SemanticModel` that would otherwise silently
 * produce wrong edges.
 */
export function validateOwnershipParity(parsedFiles, model, onWarn) {
    if (process.env.NODE_ENV === 'production')
        return 0;
    if (process.env.VALIDATE_SEMANTIC_MODEL === '0')
        return 0;
    let mismatches = 0;
    for (const parsed of parsedFiles) {
        for (const def of parsed.localDefs) {
            const ownerId = def.ownerId;
            if (ownerId === undefined)
                continue;
            const simple = simpleQualifiedName(def);
            if (simple === undefined)
                continue;
            if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
                const found = model.methods.lookupAllByOwner(ownerId, simple);
                if (!found.some((d) => d.nodeId === def.nodeId)) {
                    onWarn(`semantic-model parity: ${def.type} ${def.nodeId} (${parsed.filePath}) ` +
                        `owned by ${ownerId} as "${simple}" not in MethodRegistry`);
                    mismatches++;
                }
            }
            else if (def.type === 'Property' ||
                def.type === 'Variable' ||
                def.type === 'Const' ||
                def.type === 'Static') {
                const found = model.fields.lookupAllByOwner(ownerId, simple);
                if (!found.some((d) => d.nodeId === def.nodeId)) {
                    onWarn(`semantic-model parity: ${def.type} ${def.nodeId} (${parsed.filePath}) ` +
                        `owned by ${ownerId} as "${simple}" not in FieldRegistry`);
                    mismatches++;
                }
            }
            else if (NESTED_TYPE_KINDS.has(def.type)) {
                const found = model.types.lookupAllByOwner(ownerId, simple);
                if (!found.some((d) => d.nodeId === def.nodeId)) {
                    onWarn(`semantic-model parity: ${def.type} ${def.nodeId} (${parsed.filePath}) ` +
                        `owned by ${ownerId} as "${simple}" not in TypeRegistry owner index`);
                    mismatches++;
                }
            }
        }
    }
    return mismatches;
}
