/**
 * Equality-literal and instanceof narrowing resolvers.
 *
 * Handles equality comparisons against literals (null, undefined, booleans,
 * strings, numbers), instanceof checks, and equality literal target resolution.
 */
import * as ts from "typescript";
import { stableIrTypeKeyIfDeterministic } from "../types.js";
import { referenceTypeIdentity } from "../types/type-ops.js";
import { simpleBindingContributesTypeIdentity } from "../../program/binding-registry.js";
import { tsbindgenClrTypeNameToTsTypeName } from "../../tsbindgen/names.js";
import { getAccessPathTarget, getCurrentTypeForAccessPath, } from "./access-paths.js";
import { unwrapExpr, isEqualityOperator, isInequalityOperator, makeTypeNarrowing, filterTypeByResolvedCandidates, } from "./narrowing-resolvers-typeof.js";
const narrowingOpaqueTypeIds = new WeakMap();
let nextNarrowingOpaqueTypeId = 0;
const narrowingVisitKey = (type) => {
    const stableKey = stableIrTypeKeyIfDeterministic(type);
    if (stableKey)
        return stableKey;
    if (type.kind === "referenceType") {
        const identity = referenceTypeIdentity(type);
        if (identity) {
            return `ref:${identity}`;
        }
        if (!type.structuralMembers) {
            return `unresolved-ref:${type.name}/${type.typeArguments?.length ?? 0}`;
        }
    }
    const existing = narrowingOpaqueTypeIds.get(type);
    if (existing !== undefined)
        return `opaque:${existing}`;
    const next = nextNarrowingOpaqueTypeId;
    nextNarrowingOpaqueTypeId += 1;
    narrowingOpaqueTypeIds.set(type, next);
    return `opaque:${next}`;
};
export const tryResolveEqualityLiteralTarget = (expr) => {
    const current = unwrapExpr(expr);
    if (current.kind === ts.SyntaxKind.NullKeyword) {
        return { kind: "exact", type: { kind: "primitiveType", name: "null" } };
    }
    if (current.kind === ts.SyntaxKind.Identifier &&
        current.text === "undefined") {
        return {
            kind: "exact",
            type: { kind: "primitiveType", name: "undefined" },
        };
    }
    if (current.kind === ts.SyntaxKind.TrueKeyword ||
        current.kind === ts.SyntaxKind.FalseKeyword) {
        return {
            kind: "exact",
            type: {
                kind: "literalType",
                value: current.kind === ts.SyntaxKind.TrueKeyword,
            },
        };
    }
    if (ts.isStringLiteral(current) ||
        ts.isNoSubstitutionTemplateLiteral(current)) {
        return {
            kind: "exact",
            type: { kind: "literalType", value: current.text },
        };
    }
    if (ts.isNumericLiteral(current)) {
        return {
            kind: "exact",
            type: { kind: "literalType", value: Number(current.text) },
        };
    }
    return undefined;
};
export const candidateMatchesEqualityLiteral = (candidate, literal) => {
    if (literal.kind === "nullish") {
        return ((candidate.kind === "primitiveType" && candidate.name === "null") ||
            (candidate.kind === "primitiveType" && candidate.name === "undefined") ||
            (candidate.kind === "literalType" && candidate.value === null));
    }
    const target = literal.type;
    if (target.kind === "primitiveType") {
        return candidate.kind === "primitiveType" && candidate.name === target.name;
    }
    if (target.kind === "literalType") {
        return candidate.kind === "literalType" && candidate.value === target.value;
    }
    return false;
};
export const narrowTypeByEqualityLiteral = (currentType, literal, wantEqual, ctx) => {
    if (!currentType)
        return undefined;
    return filterTypeByResolvedCandidates(currentType, (candidate) => candidateMatchesEqualityLiteral(candidate, literal) === wantEqual, ctx);
};
export const tryResolveEqualityLiteralNarrowing = (expr, ctx, whenTruthy) => {
    const unwrapped = unwrapExpr(expr);
    if (!ts.isBinaryExpression(unwrapped))
        return undefined;
    const operator = unwrapped.operatorToken.kind;
    const isEquality = isEqualityOperator(operator);
    const isInequality = isInequalityOperator(operator);
    if (!isEquality && !isInequality)
        return undefined;
    const leftTarget = getAccessPathTarget(unwrapped.left, ctx);
    const rightTarget = getAccessPathTarget(unwrapped.right, ctx);
    const leftLiteral = tryResolveEqualityLiteralTarget(unwrapped.left);
    const rightLiteral = tryResolveEqualityLiteralTarget(unwrapped.right);
    const literal = leftTarget && rightLiteral
        ? rightLiteral
        : rightTarget && leftLiteral
            ? leftLiteral
            : undefined;
    const narrowedTarget = leftTarget ?? rightTarget;
    if (!literal || !narrowedTarget)
        return undefined;
    const useNullishPair = literal.kind === "exact" &&
        literal.type.kind === "primitiveType" &&
        (literal.type.name === "null" || literal.type.name === "undefined") &&
        (operator === ts.SyntaxKind.EqualsEqualsToken ||
            operator === ts.SyntaxKind.ExclamationEqualsToken);
    const effectiveLiteral = useNullishPair
        ? { kind: "nullish" }
        : literal;
    const wantEqual = whenTruthy ? isEquality : isInequality;
    const currentType = getCurrentTypeForAccessPath(narrowedTarget, ctx);
    const targetType = narrowTypeByEqualityLiteral(currentType, effectiveLiteral, wantEqual, ctx);
    if (!targetType)
        return undefined;
    return makeTypeNarrowing(narrowedTarget, targetType);
};
export const resolveInstanceofTargetType = (expr, ctx) => {
    const tryResolveExplicitGlobalBindingTargetType = (identifier) => {
        const simpleBinding = ctx.bindings.getExactBindingByKind(identifier.text, "global");
        if (!simpleBinding ||
            !simpleBindingContributesTypeIdentity(simpleBinding)) {
            return undefined;
        }
        const identityClrType = simpleBinding.staticType ?? simpleBinding.type;
        return {
            kind: "referenceType",
            name: tsbindgenClrTypeNameToTsTypeName(identityClrType),
            resolvedClrType: identityClrType,
        };
    };
    const isAnonymousStructuralReferenceType = (type) => type.kind === "referenceType" &&
        type.name.startsWith("__Anon_") &&
        (type.structuralMembers?.length ?? 0) > 0;
    const tryResolveConstructTargetFromTypeNode = (node) => {
        if (!node) {
            return undefined;
        }
        if (ts.isParenthesizedTypeNode(node)) {
            return tryResolveConstructTargetFromTypeNode(node.type);
        }
        if (ts.isConstructorTypeNode(node)) {
            return ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(node.type));
        }
        if (ts.isTypeLiteralNode(node)) {
            const candidates = new Map();
            for (const member of node.members) {
                if (!ts.isConstructSignatureDeclaration(member) || !member.type) {
                    continue;
                }
                const target = ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(member.type));
                if (target.kind === "unknownType") {
                    continue;
                }
                const preferred = selectPreferredTargetType(target) ?? target;
                const key = stableIrTypeKeyIfDeterministic(preferred);
                if (key) {
                    candidates.set(key, preferred);
                }
            }
            return candidates.size === 1
                ? Array.from(candidates.values())[0]
                : undefined;
        }
        if (ts.isIntersectionTypeNode(node)) {
            const candidates = new Map();
            for (const member of node.types) {
                const target = tryResolveConstructTargetFromTypeNode(member);
                if (!target) {
                    continue;
                }
                const key = stableIrTypeKeyIfDeterministic(target);
                if (key) {
                    candidates.set(key, target);
                }
            }
            return candidates.size === 1
                ? Array.from(candidates.values())[0]
                : undefined;
        }
        return undefined;
    };
    const tryResolveConstructTargetFromDecl = (declId) => {
        const handleRegistryGetter = ctx.binding
            ._getHandleRegistry;
        if (typeof handleRegistryGetter !== "function") {
            return undefined;
        }
        const declInfo = handleRegistryGetter.call(ctx.binding).getDecl(declId);
        if (!declInfo) {
            return undefined;
        }
        const nodes = [
            declInfo.typeNode,
            declInfo.valueDeclNode &&
                ts.isVariableDeclaration(declInfo.valueDeclNode)
                ? declInfo.valueDeclNode.type
                : undefined,
        ];
        for (const node of nodes) {
            const target = tryResolveConstructTargetFromTypeNode(node);
            if (target) {
                return target;
            }
        }
        return undefined;
    };
    const tryResolvePrototypeTargetType = (type, seen) => {
        const typeKey = narrowingVisitKey(type);
        if (seen.has(typeKey)) {
            return undefined;
        }
        const prototypeType = ctx.typeSystem.typeOfMember(type, {
            kind: "byName",
            name: "prototype",
        });
        if (!prototypeType || prototypeType.kind === "unknownType") {
            return undefined;
        }
        const nextSeen = new Set(seen);
        nextSeen.add(typeKey);
        return selectPreferredTargetType(prototypeType, nextSeen);
    };
    const selectPreferredTargetType = (type, seen = new Set()) => {
        if (type.kind === "unknownType" ||
            type.kind === "objectType" ||
            type.kind === "anyType") {
            return undefined;
        }
        if (isAnonymousStructuralReferenceType(type)) {
            return undefined;
        }
        if (type.kind === "referenceType") {
            const prototypeTarget = tryResolvePrototypeTargetType(type, seen);
            return prototypeTarget ?? type;
        }
        if (type.kind === "arrayType" || type.kind === "tupleType") {
            return type;
        }
        if (type.kind === "intersectionType") {
            const prototypeTarget = tryResolvePrototypeTargetType(type, seen);
            if (prototypeTarget) {
                return prototypeTarget;
            }
            const candidates = new Map();
            for (const member of type.types) {
                const selected = selectPreferredTargetType(member, seen);
                if (!selected) {
                    continue;
                }
                const key = stableIrTypeKeyIfDeterministic(selected);
                if (key) {
                    candidates.set(key, selected);
                }
            }
            return candidates.size === 1
                ? Array.from(candidates.values())[0]
                : undefined;
        }
        return undefined;
    };
    const unwrapped = unwrapExpr(expr);
    if (ts.isIdentifier(unwrapped)) {
        const explicitBindingTarget = tryResolveExplicitGlobalBindingTargetType(unwrapped);
        if (explicitBindingTarget) {
            return explicitBindingTarget;
        }
        const declId = ctx.binding.resolveIdentifier(unwrapped);
        if (declId) {
            const constructorTarget = tryResolveConstructTargetFromDecl(declId);
            if (constructorTarget) {
                return constructorTarget;
            }
            const preferredDeclType = selectPreferredTargetType(ctx.typeSystem.typeOfDecl(declId));
            if (preferredDeclType) {
                return preferredDeclType;
            }
            const preferredValueReadType = selectPreferredTargetType(ctx.typeSystem.typeOfValueRead(declId));
            if (preferredValueReadType) {
                return preferredValueReadType;
            }
        }
        return undefined;
    }
    const accessTarget = getAccessPathTarget(unwrapped, ctx);
    if (!accessTarget)
        return undefined;
    const type = getCurrentTypeForAccessPath(accessTarget, ctx);
    if (!type)
        return undefined;
    const preferred = selectPreferredTargetType(type);
    if (preferred) {
        return preferred;
    }
    return undefined;
};
//# sourceMappingURL=narrowing-resolvers-equality.js.map