/**
 * Virtual Marking Pass
 *
 * Recomputes local TypeScript class override/shadow relationships after all IR
 * collection passes have attached final emitted member names. This pass is the
 * single source of truth for local class hierarchies:
 * - no legacy overload helper bridges
 * - no widened-signature compatibility bridges
 * - no source-name-only matching
 *
 * A derived member overrides a local base member only when the emitted CLR name
 * and full signature match exactly after base-class type-argument substitution.
 * Otherwise, a same-name local member is marked as shadowing.
 */
import { getIrMemberPublicName, irTypesEqual, substituteIrType, } from "../types.js";
import { stripNullishForInference } from "../type-system/type-system-state-helpers.js";
const VOID_TYPE = { kind: "voidType" };
const normalizeBaseClassName = (raw) => {
    const withoutTypeArgs = raw.split("<")[0] ?? raw;
    const simple = withoutTypeArgs.split(".").pop() ?? withoutTypeArgs;
    return simple.trim();
};
const buildClassTypeSubstitution = (baseClass, superClass) => {
    const typeParameters = baseClass.typeParameters ?? [];
    if (typeParameters.length === 0) {
        return undefined;
    }
    if (!superClass ||
        superClass.kind !== "referenceType" ||
        !superClass.typeArguments ||
        superClass.typeArguments.length !== typeParameters.length) {
        return undefined;
    }
    const substitution = new Map();
    for (let index = 0; index < typeParameters.length; index += 1) {
        const typeParameter = typeParameters[index];
        const typeArgument = superClass.typeArguments[index];
        if (!typeParameter || !typeArgument) {
            return undefined;
        }
        substitution.set(typeParameter.name, typeArgument);
    }
    return substitution;
};
const buildCanonicalMethodTypeSubstitution = (typeParameters) => {
    if (!typeParameters || typeParameters.length === 0) {
        return undefined;
    }
    const substitution = new Map();
    for (let index = 0; index < typeParameters.length; index += 1) {
        const typeParameter = typeParameters[index];
        if (!typeParameter)
            continue;
        substitution.set(typeParameter.name, {
            kind: "typeParameterType",
            name: `__method_${index}`,
        });
    }
    return substitution;
};
const canonicalizeMethodType = (type, methodTypeParameters, classSubstitution) => {
    if (!type) {
        return undefined;
    }
    let current = type;
    if (classSubstitution && classSubstitution.size > 0) {
        current = substituteIrType(current, classSubstitution);
    }
    const methodSubstitution = buildCanonicalMethodTypeSubstitution(methodTypeParameters);
    if (!methodSubstitution || methodSubstitution.size === 0) {
        return current;
    }
    return substituteIrType(current, methodSubstitution);
};
const buildSelfTypeSubstitution = (classDecl, instantiatedType) => {
    const typeParameters = classDecl.typeParameters ?? [];
    if (typeParameters.length === 0) {
        return undefined;
    }
    if (!instantiatedType.typeArguments ||
        instantiatedType.typeArguments.length !== typeParameters.length) {
        return undefined;
    }
    const substitution = new Map();
    for (let index = 0; index < typeParameters.length; index += 1) {
        const typeParameter = typeParameters[index];
        const typeArgument = instantiatedType.typeArguments[index];
        if (!typeParameter || !typeArgument) {
            return undefined;
        }
        substitution.set(typeParameter.name, typeArgument);
    }
    return substitution;
};
const typesEqual = (left, right, classRegistry) => {
    if (!left || !right) {
        return left === right;
    }
    if (irTypesEqual(left, right)) {
        return true;
    }
    if (!classRegistry ||
        left.kind !== "referenceType" ||
        right.kind !== "referenceType") {
        return false;
    }
    const leftClassName = normalizeBaseClassName(left.name);
    const rightClassName = normalizeBaseClassName(right.name);
    const leftClass = classRegistry.get(leftClassName);
    const rightClass = classRegistry.get(rightClassName);
    if (!leftClass || !rightClass || leftClass !== rightClass) {
        return false;
    }
    const leftArgs = left.typeArguments ?? [];
    const rightArgs = right.typeArguments ?? [];
    if (leftArgs.length !== rightArgs.length) {
        return false;
    }
    for (let index = 0; index < leftArgs.length; index += 1) {
        if (!typesEqual(leftArgs[index], rightArgs[index], classRegistry)) {
            return false;
        }
    }
    return true;
};
const isLocalReferenceSubtype = (derivedType, baseType, classRegistry) => {
    if (!derivedType || !baseType) {
        return false;
    }
    if (typesEqual(derivedType, baseType, classRegistry)) {
        return true;
    }
    if (derivedType.kind !== "referenceType" ||
        baseType.kind !== "referenceType") {
        return false;
    }
    const derivedClass = classRegistry.get(normalizeBaseClassName(derivedType.name));
    if (!derivedClass?.superClass ||
        derivedClass.superClass.kind !== "referenceType") {
        return false;
    }
    const selfSubstitution = buildSelfTypeSubstitution(derivedClass, derivedType);
    const instantiatedSuper = selfSubstitution && selfSubstitution.size > 0
        ? substituteIrType(derivedClass.superClass, selfSubstitution)
        : derivedClass.superClass;
    return isLocalReferenceSubtype(instantiatedSuper, baseType, classRegistry);
};
const parametersExactlyMatch = (baseParameter, derivedParameter, baseMethodTypeParameters, derivedMethodTypeParameters, classRegistry, classSubstitution) => baseParameter.isOptional === derivedParameter.isOptional &&
    baseParameter.isRest === derivedParameter.isRest &&
    baseParameter.passing === derivedParameter.passing &&
    typesEqual(canonicalizeMethodType(baseParameter.type, baseMethodTypeParameters, classSubstitution), canonicalizeMethodType(derivedParameter.type, derivedMethodTypeParameters), classRegistry);
const methodsExactlyMatch = (baseMethod, derivedMethod, classRegistry, classSubstitution) => {
    if (getIrMemberPublicName(baseMethod) !== getIrMemberPublicName(derivedMethod)) {
        return false;
    }
    const baseTypeParameters = baseMethod.typeParameters;
    const derivedTypeParameters = derivedMethod.typeParameters;
    if ((baseTypeParameters?.length ?? 0) !== (derivedTypeParameters?.length ?? 0)) {
        return false;
    }
    if (baseMethod.parameters.length !== derivedMethod.parameters.length) {
        return false;
    }
    for (let index = 0; index < baseMethod.parameters.length; index += 1) {
        const baseParameter = baseMethod.parameters[index];
        const derivedParameter = derivedMethod.parameters[index];
        if (!baseParameter || !derivedParameter) {
            return false;
        }
        if (!parametersExactlyMatch(baseParameter, derivedParameter, baseTypeParameters, derivedTypeParameters, classRegistry, classSubstitution)) {
            return false;
        }
    }
    const canonicalBaseReturn = canonicalizeMethodType(baseMethod.returnType ?? VOID_TYPE, baseTypeParameters, classSubstitution);
    const canonicalDerivedReturn = canonicalizeMethodType(derivedMethod.returnType ?? VOID_TYPE, derivedTypeParameters);
    return (typesEqual(canonicalBaseReturn, canonicalDerivedReturn, classRegistry) ||
        isLocalReferenceSubtype(canonicalDerivedReturn, canonicalBaseReturn, classRegistry));
};
const propertiesExactlyMatch = (baseProperty, derivedProperty, classRegistry, classSubstitution) => {
    if (baseProperty.name !== derivedProperty.name) {
        return false;
    }
    const baseType = classSubstitution && baseProperty.type
        ? substituteIrType(baseProperty.type, classSubstitution)
        : baseProperty.type;
    const canonicalBaseType = baseType
        ? (stripNullishForInference(baseType) ?? baseType)
        : baseType;
    const canonicalDerivedType = derivedProperty.type
        ? (stripNullishForInference(derivedProperty.type) ?? derivedProperty.type)
        : derivedProperty.type;
    return (typesEqual(canonicalBaseType, canonicalDerivedType, classRegistry) ||
        isLocalReferenceSubtype(canonicalDerivedType, canonicalBaseType, classRegistry));
};
const buildEmptyPlan = () => ({
    methodUpdates: new Map(),
    propertyUpdates: new Map(),
    overriddenBaseMethodIndices: new Set(),
    overriddenBasePropertyIndices: new Set(),
});
const analyzeLocalOverrides = (classDecl, classRegistry) => {
    if (classDecl.superClass?.kind !== "referenceType") {
        return buildEmptyPlan();
    }
    const baseClassName = normalizeBaseClassName(classDecl.superClass.name);
    const baseClass = classRegistry.get(baseClassName);
    if (!baseClass) {
        return buildEmptyPlan();
    }
    const methodUpdates = new Map();
    const propertyUpdates = new Map();
    const overriddenBaseMethodIndices = new Set();
    const overriddenBasePropertyIndices = new Set();
    const classSubstitution = buildClassTypeSubstitution(baseClass, classDecl.superClass);
    const baseMethods = [];
    const baseProperties = [];
    for (let index = 0; index < baseClass.members.length; index += 1) {
        const member = baseClass.members[index];
        if (!member) {
            continue;
        }
        if (member.kind === "methodDeclaration") {
            if (member.isStatic) {
                continue;
            }
            baseMethods.push({ index, member });
            continue;
        }
        if (member.kind === "propertyDeclaration") {
            if (member.isStatic) {
                continue;
            }
            baseProperties.push({ index, member });
        }
    }
    for (let index = 0; index < classDecl.members.length; index += 1) {
        const member = classDecl.members[index];
        if (!member) {
            continue;
        }
        if (member.kind === "methodDeclaration") {
            if (member.isStatic) {
                continue;
            }
            const sameNameBaseMethods = baseMethods.filter((baseMethod) => getIrMemberPublicName(baseMethod.member) ===
                getIrMemberPublicName(member));
            if (sameNameBaseMethods.length === 0) {
                continue;
            }
            const matchedBaseMethod = sameNameBaseMethods.find((baseMethod) => methodsExactlyMatch(baseMethod.member, member, classRegistry, classSubstitution));
            if (matchedBaseMethod) {
                methodUpdates.set(index, { isOverride: true });
                overriddenBaseMethodIndices.add(matchedBaseMethod.index);
            }
            else {
                methodUpdates.set(index, { isShadow: true });
            }
            continue;
        }
        if (member.kind === "propertyDeclaration") {
            if (member.isStatic) {
                continue;
            }
            const sameNameBaseProperties = baseProperties.filter((baseProperty) => baseProperty.member.name === member.name);
            if (sameNameBaseProperties.length === 0) {
                continue;
            }
            const matchedBaseProperty = sameNameBaseProperties.find((baseProperty) => propertiesExactlyMatch(baseProperty.member, member, classRegistry, classSubstitution));
            if (matchedBaseProperty) {
                propertyUpdates.set(index, { isOverride: true });
                overriddenBasePropertyIndices.add(matchedBaseProperty.index);
            }
            else {
                propertyUpdates.set(index, { isShadow: true });
            }
        }
    }
    return {
        methodUpdates,
        propertyUpdates,
        baseClassName,
        overriddenBaseMethodIndices,
        overriddenBasePropertyIndices,
    };
};
const applyMethodUpdate = (member, update, isVirtual) => {
    if (!update && !isVirtual) {
        return member;
    }
    const { isOverride: _oldOverride, isShadow: _oldShadow, isVirtual: _oldVirtual, ...rest } = member;
    return {
        ...rest,
        ...(update?.isOverride ? { isOverride: true } : {}),
        ...(update?.isShadow ? { isShadow: true } : {}),
        ...(isVirtual || member.isVirtual ? { isVirtual: true } : {}),
    };
};
const applyPropertyUpdate = (member, update, isVirtual) => {
    if (!update && !isVirtual) {
        return member;
    }
    const { isOverride: _oldOverride, isShadow: _oldShadow, isVirtual: _oldVirtual, ...rest } = member;
    return {
        ...rest,
        ...(update?.isOverride ? { isOverride: true } : {}),
        ...(update?.isShadow ? { isShadow: true } : {}),
        ...(isVirtual || member.isVirtual ? { isVirtual: true } : {}),
    };
};
const transformStatement = (stmt, overridePlans, virtualMethodIndices, virtualPropertyIndices) => {
    if (stmt.kind !== "classDeclaration") {
        return stmt;
    }
    const plan = overridePlans.get(stmt.name);
    const methodVirtuals = virtualMethodIndices.get(stmt.name);
    const propertyVirtuals = virtualPropertyIndices.get(stmt.name);
    const transformedMembers = stmt.members.map((member, index) => {
        if (member.kind === "methodDeclaration") {
            return applyMethodUpdate(member, plan?.methodUpdates.get(index), methodVirtuals?.has(index) ?? false);
        }
        if (member.kind === "propertyDeclaration") {
            return applyPropertyUpdate(member, plan?.propertyUpdates.get(index), propertyVirtuals?.has(index) ?? false);
        }
        return member;
    });
    return {
        ...stmt,
        members: transformedMembers,
    };
};
/**
 * Run virtual marking pass on all modules.
 */
export const runVirtualMarkingPass = (modules) => {
    const classRegistry = new Map();
    for (const module of modules) {
        for (const stmt of module.body) {
            if (stmt.kind === "classDeclaration") {
                classRegistry.set(stmt.name, stmt);
            }
        }
        for (const exp of module.exports) {
            if (exp.kind === "declaration" &&
                exp.declaration.kind === "classDeclaration") {
                classRegistry.set(exp.declaration.name, exp.declaration);
            }
        }
    }
    const overridePlans = new Map();
    const virtualMethodIndices = new Map();
    const virtualPropertyIndices = new Map();
    for (const classDecl of classRegistry.values()) {
        const plan = analyzeLocalOverrides(classDecl, classRegistry);
        overridePlans.set(classDecl.name, plan);
        if (!plan.baseClassName) {
            continue;
        }
        if (plan.overriddenBaseMethodIndices.size > 0) {
            const indices = virtualMethodIndices.get(plan.baseClassName) ?? new Set();
            for (const index of plan.overriddenBaseMethodIndices) {
                indices.add(index);
            }
            virtualMethodIndices.set(plan.baseClassName, indices);
        }
        if (plan.overriddenBasePropertyIndices.size > 0) {
            const indices = virtualPropertyIndices.get(plan.baseClassName) ?? new Set();
            for (const index of plan.overriddenBasePropertyIndices) {
                indices.add(index);
            }
            virtualPropertyIndices.set(plan.baseClassName, indices);
        }
    }
    const transformedModules = modules.map((module) => ({
        ...module,
        body: module.body.map((stmt) => transformStatement(stmt, overridePlans, virtualMethodIndices, virtualPropertyIndices)),
        exports: module.exports.map((exp) => exp.kind === "declaration"
            ? {
                ...exp,
                declaration: transformStatement(exp.declaration, overridePlans, virtualMethodIndices, virtualPropertyIndices),
            }
            : exp),
    }));
    return { ok: true, modules: transformedModules };
};
//# sourceMappingURL=virtual-marking-pass.js.map