package click.kamil.springstatemachineexporter.ast.common;

import click.kamil.springstatemachineexporter.analysis.index.AccessorInlining;
import click.kamil.springstatemachineexporter.analysis.index.AccessorKind;
import click.kamil.springstatemachineexporter.analysis.index.AccessorNaming;
import click.kamil.springstatemachineexporter.analysis.index.AccessorSummary;
import click.kamil.springstatemachineexporter.analysis.index.GetterChainEdge;
import click.kamil.springstatemachineexporter.analysis.pipeline.LibraryUnwrapRegistry;
import click.kamil.springstatemachineexporter.analysis.pipeline.ReactiveExpressionSupport;
import click.kamil.springstatemachineexporter.analysis.pipeline.ResolutionBudget;
import org.eclipse.jdt.core.dom.*;
import java.util.*;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JdtDataFlowModel implements DataFlowModel {
    private final CodebaseContext context;
    private final Map<MethodDeclaration, ControlFlowGraph> cfgCache = new HashMap<>();
    private final Map<MethodDeclaration, ReachingDefinitions> rdCache = new HashMap<>();
    private final Map<ASTNode, Map<IVariableBinding, Expression>> objectFieldBindings = new HashMap<>();

    public JdtDataFlowModel(CodebaseContext context) {
        this.context = context;
    }

    public void clearAnalysisCaches() {
        context.getClassCompatibilityCache().clearMemoCache();
    }

    private MethodDeclaration findEnclosingMethod(ASTNode node) {
        return AstUtils.findEnclosingMethod(node);
    }

    private ReachingDefinitions getReachingDefinitionsForMethod(MethodDeclaration method) {
        if (method == null) return null;
        return rdCache.computeIfAbsent(method, m -> {
            ControlFlowGraph cfg = cfgCache.computeIfAbsent(m, CfgBuilder::build);
            return new ReachingDefinitions(cfg);
        });
    }

    @Override
    public List<Expression> getReachingDefinitions(Expression expr) {
        return getReachingDefinitions(expr, new HashSet<>(), new HashMap<>(), new HashMap<>(), 0);
    }

    private List<Expression> getReachingDefinitions(
            Expression expr, 
            Set<ASTNode> visited, 
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        if (expr == null || ResolutionBudget.defaults().isDataflowExhausted(depth) || !visited.add(expr)) {
            return List.of();
        }

        if (expr instanceof LambdaExpression lambda) {
            List<Expression> resolved = resolveFunctionalBoundExpression(
                    lambda, visited, paramBindings, instanceFieldBindings, depth);
            visited.remove(expr);
            return resolved;
        }

        // 1. Handle Ternary / Conditional Expression
        if (expr instanceof ConditionalExpression ce) {
            String condVal = resolveValue(ce.getExpression(), context);
            if ("true".equals(condVal)) {
                List<Expression> resolved = getReachingDefinitions(ce.getThenExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
                visited.remove(expr);
                return resolved;
            } else if ("false".equals(condVal)) {
                List<Expression> resolved = getReachingDefinitions(ce.getElseExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
                visited.remove(expr);
                return resolved;
            } else {
                visited.remove(expr);
                return List.of(ce);
            }
        }

        // 2. Handle Field constant / field initializer propagation
        IVariableBinding vb = getVariableBinding(expr);
        if (vb != null && vb.isField()) {
            if (instanceFieldBindings.containsKey(vb)) {
                Expression fieldVal = instanceFieldBindings.get(vb);
                List<Expression> resolved = getReachingDefinitions(fieldVal, visited, paramBindings, instanceFieldBindings, depth + 1);
                visited.remove(expr);
                return resolved;
            }

            Expression fieldInit = findFieldInitializer(vb, expr);
            if (fieldInit != null) {
                List<Expression> resolved = getReachingDefinitions(fieldInit, visited, paramBindings, instanceFieldBindings, depth + 1);
                visited.remove(expr);
                return resolved;
            }
        }

        // 3. Handle Parameter Variable Bindings (Propagate values through parameters)
        if (expr instanceof SimpleName sn) {
            IBinding b = sn.resolveBinding();
            if (b instanceof IVariableBinding paramVb) {
                if (paramBindings.containsKey(paramVb)) {
                    Expression argExpr = paramBindings.get(paramVb);
                    List<Expression> resolved = getReachingDefinitions(argExpr, visited, paramBindings, instanceFieldBindings, depth + 1);
                    visited.remove(expr);
                    return resolved;
                }
            }

            Map<String, String> namedBindings = context.getAnalysisNamedBindings();
            if (namedBindings != null && namedBindings.containsKey(sn.getIdentifier())) {
                String boundValue = namedBindings.get(sn.getIdentifier());
                Expression synthetic = AstUtils.parseExpression(AstUtils.toParseSnippet(boundValue));
                if (synthetic != null) {
                    List<Expression> resolved = getReachingDefinitions(
                            synthetic, visited, paramBindings, instanceFieldBindings, depth + 1);
                    visited.remove(expr);
                    return resolved;
                }
            }

            // Fallback: Local Reaching Definitions
            MethodDeclaration md = findEnclosingMethod(sn);
            ReachingDefinitions rd = getReachingDefinitionsForMethod(md);
            if (rd != null) {
                Set<ASTNode> defs = rd.getReachingDefinitions(sn, sn.getIdentifier());
                if (!defs.isEmpty()) {
                    List<Expression> exprs = new ArrayList<>();
                    for (ASTNode def : defs) {
                        Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
                        if (defExpr != null) {
                            exprs.addAll(getReachingDefinitions(defExpr, visited, paramBindings, instanceFieldBindings, depth + 1));
                        } else {
                            exprs.add(expr);
                        }
                    }
                    visited.remove(expr);
                    return exprs;
                }
            }
        }

        // Handle ClassInstanceCreation
        if (expr instanceof ClassInstanceCreation cic) {
            getOrCreateFieldBindings(cic, paramBindings, instanceFieldBindings);
            visited.remove(expr);
            return List.of(cic);
        }

        // Handle CastExpression
        if (expr instanceof CastExpression ce) {
            List<Expression> resolved = getReachingDefinitions(ce.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
            visited.remove(expr);
            return resolved;
        }

        // Handle ParenthesizedExpression
        if (expr instanceof ParenthesizedExpression pe) {
            List<Expression> resolved = getReachingDefinitions(pe.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
            visited.remove(expr);
            return resolved;
        }

        // Handle SwitchExpression
        if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
            List<Expression> results = new ArrayList<>();
            List<Expression> selectorDefs = getReachingDefinitions(se.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
            String selectorVal = null;
            if (!selectorDefs.isEmpty()) {
                selectorVal = resolveValue(selectorDefs.get(0), context);
            }
            
            boolean active = (selectorVal == null);
            for (Object stmtObj : se.statements()) {
                if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
                    if (selectorVal != null) {
                        active = false;
                        for (Object expObj : sc.expressions()) {
                            Expression caseExpr = (Expression) expObj;
                            String caseVal = resolveValue(caseExpr, context);
                            if (caseVal != null && (caseVal.equals(selectorVal) || selectorVal.endsWith("." + caseVal) || caseVal.endsWith("." + selectorVal))) {
                                active = true;
                                break;
                            }
                        }
                        if (sc.isDefault()) {
                            active = true;
                        }
                    }
                } else if (active) {
                    if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
                        results.addAll(getReachingDefinitions(ys.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
                    } else if (stmtObj instanceof ExpressionStatement es) {
                        results.addAll(getReachingDefinitions(es.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
                    }
                }
            }
            visited.remove(expr);
            return results;
        }

        // 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
        if (expr instanceof MethodInvocation mi) {
            Expression factoryPayload = ReactiveExpressionSupport.peelFactoryPayloadExpression(mi);
            if (factoryPayload != null) {
                List<Expression> resolved = getReachingDefinitions(
                        factoryPayload, visited, paramBindings, instanceFieldBindings, depth + 1);
                visited.remove(expr);
                return resolved;
            }

            String mName = mi.getName().getIdentifier();
            if (LibraryUnwrapRegistry.isReactiveTransformMethod(mName) && !mi.arguments().isEmpty()) {
                Expression arg = (Expression) mi.arguments().get(0);
                if (arg instanceof LambdaExpression lambda && lambda.parameters().size() == 1) {
                    Expression source = ReactiveExpressionSupport.peelFactoryPayloadExpression(mi.getExpression());
                    if (source != null) {
                        Object parameter = lambda.parameters().get(0);
                        if (parameter instanceof SingleVariableDeclaration svd) {
                            IVariableBinding paramBinding = svd.resolveBinding();
                            if (paramBinding != null) {
                                Map<IVariableBinding, Expression> transformBindings = new HashMap<>(paramBindings);
                                transformBindings.put(paramBinding, source);
                                Expression bodyExpression = ReactiveExpressionSupport.lambdaBodyExpression(lambda);
                                if (bodyExpression != null) {
                                    Expression nestedFactory = ReactiveExpressionSupport.peelFactoryPayloadExpression(bodyExpression);
                                    if (nestedFactory != null) {
                                        List<Expression> resolved = getReachingDefinitions(
                                                nestedFactory, visited, transformBindings, instanceFieldBindings, depth + 1);
                                        if (!resolved.isEmpty()) {
                                            visited.remove(expr);
                                            return resolved;
                                        }
                                    }
                                    List<Expression> resolved = getReachingDefinitions(
                                            bodyExpression, visited, transformBindings, instanceFieldBindings, depth + 1);
                                    if (!resolved.isEmpty()) {
                                        visited.remove(expr);
                                        return resolved;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (isFunctionalSamInvocation(mi)) {
                List<Expression> functional = resolveFunctionalInterfaceInvocation(
                        mi, visited, paramBindings, instanceFieldBindings, depth);
                if (!functional.isEmpty()) {
                    visited.remove(expr);
                    return functional;
                }
            }

            List<Expression> inlined = tryInlineAccessorInvocation(
                    mi, new HashSet<>(visited), paramBindings, instanceFieldBindings, depth);
            if (!inlined.isEmpty()) {
                visited.remove(expr);
                return inlined;
            }

            IMethodBinding mb = mi.resolveMethodBinding();
            if (mb != null) {
                List<TargetMethod> targets = resolveTargets(mi, mb, visited, paramBindings, instanceFieldBindings, depth);
                if (!targets.isEmpty()) {
                    List<Expression> results = new ArrayList<>();
                    for (TargetMethod target : targets) {
                        MethodDeclaration md = target.declaration;
                        if (md != null && md.getBody() != null) {
                            Map<IVariableBinding, Expression> newParamBindings = new HashMap<>(paramBindings);
                            List<?> parameters = md.parameters();
                            List<?> arguments = mi.arguments();
                            int count = Math.min(parameters.size(), arguments.size());
                            for (int i = 0; i < count; i++) {
                                SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
                                IVariableBinding paramVb = svd.resolveBinding();
                                if (paramVb != null) {
                                    newParamBindings.put(paramVb, (Expression) arguments.get(i));
                                }
                            }

                            // Evaluate mutator/setter method body
                            evaluateMethodMutations(md, mi.arguments(), target.fieldBindings);

                            List<ReturnStatement> returns = findReturnStatements(md.getBody());
                            if (!returns.isEmpty()) {
                                for (ReturnStatement rs : returns) {
                                    if (rs.getExpression() != null) {
                                        Expression retExpr = rs.getExpression();
                                        Expression unwrapped = retExpr;
                                        while (unwrapped instanceof ParenthesizedExpression pe) {
                                            unwrapped = pe.getExpression();
                                        }
                                        while (unwrapped instanceof CastExpression ce) {
                                            unwrapped = ce.getExpression();
                                        }
                                        if (unwrapped instanceof ThisExpression) {
                                            if (mi.getExpression() != null) {
                                                results.addAll(getReachingDefinitions(mi.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
                                            } else {
                                                results.add(unwrapped);
                                            }
                                        } else {
                                            results.addAll(getReachingDefinitions(retExpr, visited, newParamBindings, target.fieldBindings, depth + 1));
                                        }
                                    }
                                }
                            } else {
                                collectSideEffectExpressions(
                                        md.getBody(), results, visited, newParamBindings, target.fieldBindings, depth);
                            }
                        }
                    }
                    if (!results.isEmpty()) {
                        visited.remove(expr);
                        return results;
                    }
                }
            }
        }

        // Handle Super Method Invocations
        if (expr instanceof SuperMethodInvocation smi) {
            IMethodBinding mb = smi.resolveMethodBinding();
            if (mb != null && mb.getDeclaringClass() != null) {
                String superFqn = mb.getDeclaringClass().getErasure().getQualifiedName();
                String methodName = smi.getName().getIdentifier();
                Optional<AccessorSummary> accessor = context.getAccessorIndex().lookup(superFqn, methodName);
                if (accessor.isPresent() && accessor.get().isGetter()) {
                    Expression fieldValue = readFieldValue(accessor.get(), instanceFieldBindings, smi);
                    if (fieldValue != null) {
                        visited.remove(expr);
                        return getReachingDefinitions(
                                fieldValue, visited, paramBindings, instanceFieldBindings, depth + 1);
                    }
                }
            }
            if (mb != null) {
                MethodDeclaration md = findMethodDeclaration(mb);
                if (md != null && md.getBody() != null) {
                    Map<IVariableBinding, Expression> newParamBindings = new HashMap<>(paramBindings);
                    List<?> parameters = md.parameters();
                    List<?> arguments = smi.arguments();
                    int count = Math.min(parameters.size(), arguments.size());
                    for (int i = 0; i < count; i++) {
                        SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
                        IVariableBinding paramVb = svd.resolveBinding();
                        if (paramVb != null) {
                            newParamBindings.put(paramVb, (Expression) arguments.get(i));
                        }
                    }

                    List<ReturnStatement> returns = findReturnStatements(md.getBody());
                    if (!returns.isEmpty()) {
                        List<Expression> results = new ArrayList<>();
                        for (ReturnStatement rs : returns) {
                            if (rs.getExpression() != null) {
                                results.addAll(getReachingDefinitions(rs.getExpression(), visited, newParamBindings, instanceFieldBindings, depth + 1));
                            }
                        }
                        visited.remove(expr);
                        return results;
                    }
                }
            }
        }

        visited.remove(expr);
        return List.of(expr);
    }

    private List<Expression> tryInlineAccessorInvocation(
            MethodInvocation mi,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        if (!context.isInlineAccessors()) {
            return List.of();
        }
        if (ResolutionBudget.defaults().isAccessorInlineExhausted(depth)) {
            return List.of();
        }

        String methodName = mi.getName().getIdentifier();
        IMethodBinding methodBinding = mi.resolveMethodBinding();
        if (methodBinding == null || methodBinding.getDeclaringClass() == null) {
            return List.of();
        }

        String declaringFqn = methodBinding.getDeclaringClass().getErasure().getQualifiedName();
        Optional<AccessorSummary> accessor = context.getAccessorIndex().lookup(declaringFqn, methodName);
        if (accessor.isEmpty() && !looksLikeIndexedAccessorName(methodName)) {
            return List.of();
        }

        List<Expression> receiverDefs = List.of();

        if (accessor.isEmpty()) {
            if (!methodBinding.getDeclaringClass().isInterface()) {
                return List.of();
            }
            Expression receiver = mi.getExpression();
            if (receiver == null) {
                return List.of();
            }
            receiverDefs = getReachingDefinitions(receiver, visited, paramBindings, instanceFieldBindings, depth + 1);
            accessor = AccessorInlining.lookupAccessor(context, mi, receiverDefs);
            if (accessor.isEmpty()) {
                List<Expression> anonymous = inlineAnonymousFromReceiverDefs(
                        methodName, receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
                if (!anonymous.isEmpty()) {
                    return anonymous;
                }
                return List.of();
            }
        }

        if (receiverDefs.isEmpty() && mi.getExpression() != null) {
            receiverDefs = getReachingDefinitions(
                    mi.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
        }

        if (accessor.isPresent()) {
            String accessorOwner = accessor.get().ownerFqn();
            if (accessorOwner.equals(declaringFqn)) {
                Optional<AccessorSummary> concreteAccessor =
                        lookupConcreteAccessorOverride(declaringFqn, methodName, receiverDefs);
                if (concreteAccessor.isPresent()) {
                    accessor = concreteAccessor;
                } else if (shouldSkipAccessorDueToConcreteOverride(declaringFqn, methodName, receiverDefs)) {
                    List<Expression> concreteOverride = inlineConcreteMethodOverride(
                            methodName, receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
                    if (!concreteOverride.isEmpty()) {
                        return concreteOverride;
                    }
                    return List.of();
                }
            }
        }

        if (accessor.isEmpty()) {
            List<Expression> anonymous = inlineAnonymousFromReceiverDefs(
                    methodName, receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
            if (!anonymous.isEmpty()) {
                return anonymous;
            }
            return List.of();
        }
        if (!accessor.get().isGetter()) {
            return List.of();
        }

        if (accessor.get().kind() == click.kamil.springstatemachineexporter.analysis.index.AccessorKind.CONSTANT_GETTER) {
            return inlineConstantGetter(accessor.get(), visited, paramBindings, instanceFieldBindings, depth);
        }

        if (accessor.get().kind() == click.kamil.springstatemachineexporter.analysis.index.AccessorKind.RECORD_COMPONENT) {
            return inlineRecordComponentRead(mi, accessor.get(), receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
        }

        List<Expression> indexedHop = tryIndexedGetterChainHop(
                mi, accessor.get(), visited, paramBindings, instanceFieldBindings, depth);
        if (!indexedHop.isEmpty()) {
            return indexedHop;
        }

        return inlineAccessorGetter(mi, accessor.get(), receiverDefs, visited, paramBindings, instanceFieldBindings, depth);
    }

    /**
     * Scan-time {@link GetterChainEdge} fast path for trivial field-default getter hops.
     * Skipped when the receiver may have been mutated before this use site.
     */
    private List<Expression> tryIndexedGetterChainHop(
            MethodInvocation mi,
            AccessorSummary accessor,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        if (accessor.kind() != AccessorKind.GETTER || hasReceiverMutationsBeforeUse(mi)) {
            return List.of();
        }
        Optional<GetterChainEdge> edge = context.getGetterChainIndex().lookup(accessor.ownerFqn(), accessor.methodName());
        if (edge.isEmpty() || edge.get().fieldInitializerCic() == null) {
            return List.of();
        }
        ClassInstanceCreation fieldDefaultCic = edge.get().fieldInitializerCic();
        return getReachingDefinitions(
                fieldDefaultCic, visited, paramBindings, instanceFieldBindings, depth + 1);
    }

    private boolean hasReceiverMutationsBeforeUse(MethodInvocation useSite) {
        Expression receiver = useSite.getExpression();
        if (!(receiver instanceof SimpleName receiverName)) {
            return false;
        }
        MethodDeclaration enclosingMethod = findEnclosingMethod(useSite);
        if (enclosingMethod == null || enclosingMethod.getBody() == null) {
            return false;
        }
        int usePosition = useSite.getStartPosition();
        final boolean[] mutationFound = { false };
        enclosingMethod.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if (mutationFound[0] || node.getStartPosition() >= usePosition) {
                    return false;
                }
                if (!(node.getExpression() instanceof SimpleName callReceiver)) {
                    return true;
                }
                if (!callReceiver.getIdentifier().equals(receiverName.getIdentifier())) {
                    return true;
                }
                String callee = node.getName().getIdentifier();
                if (AccessorNaming.isBeanStyleAccessorName(callee) && callee.startsWith("set")) {
                    mutationFound[0] = true;
                    return false;
                }
                return true;
            }
        });
        return mutationFound[0];
    }

    private List<Expression> inlineConstantGetter(
            AccessorSummary accessor,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(accessor.ownerFqn());
        if (typeDeclaration == null) {
            return List.of();
        }
        MethodDeclaration methodDeclaration = context.findMethodDeclaration(typeDeclaration, accessor.methodName(), true);
        if (methodDeclaration == null || methodDeclaration.getBody() == null) {
            return List.of();
        }
        List<Expression> results = new ArrayList<>();
        for (Object statementObj : methodDeclaration.getBody().statements()) {
            if (statementObj instanceof ReturnStatement returnStatement && returnStatement.getExpression() != null) {
                results.addAll(getReachingDefinitions(
                        returnStatement.getExpression(),
                        visited,
                        paramBindings,
                        instanceFieldBindings,
                        depth + 1));
            }
        }
        return results;
    }

    private Optional<AccessorSummary> lookupConcreteAccessorOverride(
            String declaringFqn,
            String methodName,
            List<Expression> receiverDefs) {
        for (Expression receiverDef : receiverDefs) {
            String concreteFqn = concreteReceiverTypeFqn(receiverDef);
            if (concreteFqn == null || concreteFqn.equals(declaringFqn)) {
                continue;
            }
            Optional<AccessorSummary> concreteAccessor = context.getAccessorIndex().lookup(concreteFqn, methodName);
            if (concreteAccessor.isPresent() && concreteAccessor.get().isGetter()) {
                return concreteAccessor;
            }
        }
        return Optional.empty();
    }

    private boolean shouldSkipAccessorDueToConcreteOverride(
            String declaringFqn,
            String methodName,
            List<Expression> receiverDefs) {
        for (Expression receiverDef : receiverDefs) {
            String concreteFqn = concreteReceiverTypeFqn(receiverDef);
            if (concreteFqn == null || concreteFqn.equals(declaringFqn)) {
                continue;
            }
            TypeDeclaration concreteType = context.getTypeDeclaration(concreteFqn);
            if (concreteType != null && context.hasMethod(concreteType, methodName)) {
                return true;
            }
        }
        return false;
    }

    private List<Expression> inlineConcreteMethodOverride(
            String methodName,
            List<Expression> receiverDefs,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        List<Expression> results = new ArrayList<>();
        for (Expression receiverDef : receiverDefs) {
            String concreteFqn = concreteReceiverTypeFqn(receiverDef);
            if (concreteFqn == null) {
                continue;
            }
            TypeDeclaration concreteType = context.getTypeDeclaration(concreteFqn);
            if (concreteType == null) {
                continue;
            }
            MethodDeclaration method = context.findMethodDeclaration(concreteType, methodName, true);
            if (method == null || method.getBody() == null) {
                continue;
            }
            for (Object statement : method.getBody().statements()) {
                if (statement instanceof ReturnStatement returnStatement
                        && returnStatement.getExpression() != null) {
                    results.addAll(getReachingDefinitions(
                            returnStatement.getExpression(),
                            visited,
                            paramBindings,
                            instanceFieldBindings,
                            depth + 1));
                }
            }
        }
        return results;
    }

    private String concreteReceiverTypeFqn(Expression receiverDef) {
        if (receiverDef instanceof ClassInstanceCreation cic) {
            return AccessorInlining.resolveTypeFqn(cic);
        }
        ITypeBinding typeBinding = receiverDef.resolveTypeBinding();
        if (typeBinding != null) {
            return typeBinding.getErasure().getQualifiedName();
        }
        return null;
    }

    private boolean looksLikeIndexedAccessorName(String methodName) {
        return (methodName.startsWith("get") && methodName.length() > 3)
                || (methodName.startsWith("set") && methodName.length() > 3)
                || (methodName.startsWith("is") && methodName.length() > 2);
    }

    private List<Expression> inlineRecordComponentRead(
            MethodInvocation mi,
            AccessorSummary accessor,
            List<Expression> receiverDefs,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        Expression receiver = mi.getExpression();
        List<Expression> candidates = receiverDefs.isEmpty() && receiver != null ? List.of(receiver) : receiverDefs;
        List<Expression> results = new ArrayList<>();

        for (Expression candidate : candidates) {
            if (candidate instanceof ClassInstanceCreation cic) {
                Expression componentValue = readRecordComponentValue(cic, accessor.fieldName());
                if (componentValue != null) {
                    results.addAll(getReachingDefinitions(componentValue, visited, paramBindings, instanceFieldBindings, depth + 1));
                }
                continue;
            }
            if (candidate instanceof MethodInvocation receiverCall) {
                List<Expression> nested = tryInlineAccessorInvocation(
                        receiverCall, new HashSet<>(visited), paramBindings, instanceFieldBindings, depth + 1);
                for (Expression nestedValue : nested) {
                    if (nestedValue instanceof ClassInstanceCreation cic) {
                        Expression componentValue = readRecordComponentValue(cic, accessor.fieldName());
                        if (componentValue != null) {
                            results.addAll(getReachingDefinitions(
                                    componentValue, visited, paramBindings, instanceFieldBindings, depth + 1));
                        }
                    } else {
                        results.addAll(getReachingDefinitions(nestedValue, visited, paramBindings, instanceFieldBindings, depth + 1));
                    }
                }
            }
        }
        return results;
    }

    private Expression readRecordComponentValue(ClassInstanceCreation cic, String componentName) {
        org.eclipse.jdt.core.dom.RecordDeclaration record = findRecordDeclaration(AccessorInlining.resolveTypeFqn(cic));
        if (record == null) {
            return null;
        }
        int componentIndex = -1;
        List<?> components = record.recordComponents();
        for (int i = 0; i < components.size(); i++) {
            Object componentObj = components.get(i);
            String name = recordComponentName(componentObj);
            if (componentName.equals(name)) {
                componentIndex = i;
                break;
            }
        }
        if (componentIndex < 0 || componentIndex >= cic.arguments().size()) {
            return null;
        }
        return (Expression) cic.arguments().get(componentIndex);
    }

    private String recordComponentName(Object componentObj) {
        if (componentObj instanceof SingleVariableDeclaration svd) {
            return svd.getName().getIdentifier();
        }
        try {
            java.lang.reflect.Method getNameMethod = componentObj.getClass().getMethod("getName");
            Object nameObj = getNameMethod.invoke(componentObj);
            if (nameObj instanceof SimpleName sn) {
                return sn.getIdentifier();
            }
        } catch (ReflectiveOperationException ignored) {
            // fall through
        }
        return null;
    }

    private List<Expression> inlineAccessorGetter(
            MethodInvocation mi,
            AccessorSummary accessor,
            List<Expression> receiverDefs,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        Expression receiver = mi.getExpression();
        List<Expression> results = new ArrayList<>();

        if (receiver == null || receiver instanceof ThisExpression) {
            Expression fieldValue = readFieldValue(accessor, instanceFieldBindings, mi);
            if (fieldValue != null) {
                results.addAll(getReachingDefinitions(fieldValue, visited, paramBindings, instanceFieldBindings, depth + 1));
            }
            return results;
        }

        List<Expression> candidates = receiverDefs.isEmpty() ? List.of(receiver) : receiverDefs;
        for (Expression candidate : candidates) {
            if (candidate instanceof ClassInstanceCreation cic) {
                Map<IVariableBinding, Expression> fieldBindings =
                        getOrCreateFieldBindings(cic, paramBindings, instanceFieldBindings);

                if (receiver instanceof SimpleName sn) {
                    applyFlowSensitiveMutationsBeforeUse(sn, mi, cic, fieldBindings, paramBindings, visited, depth);
                } else if (receiver instanceof FieldAccess fa) {
                    applyFlowSensitiveFieldMutationsBeforeUse(fa, mi, fieldBindings, paramBindings, visited, depth);
                }

                Expression fieldValue = readFieldValue(accessor, fieldBindings, mi);
                if (fieldValue != null) {
                    results.addAll(getReachingDefinitions(fieldValue, visited, paramBindings, fieldBindings, depth + 1));
                }
                if (results.isEmpty() && cic.getAnonymousClassDeclaration() != null) {
                    results.addAll(inlineAnonymousGetterReturns(
                            cic, accessor.methodName(), visited, paramBindings, fieldBindings, depth));
                }
            } else {
                TypeDeclaration receiverType = resolveExpressionTypeDeclaration(
                        candidate, paramBindings, instanceFieldBindings);
                if (receiverType != null) {
                    Map<IVariableBinding, Expression> typeFieldBindings =
                            buildTypeDefaultFieldBindings(receiverType);
                    if (receiver instanceof SimpleName sn && candidate == sn) {
                        applyFlowSensitiveMutationsForLocalReceiver(
                                sn, mi, accessor, typeFieldBindings, paramBindings, visited, depth);
                    }
                    Expression fieldValue = readFieldValue(accessor, typeFieldBindings, mi);
                    if (fieldValue != null) {
                        results.addAll(getReachingDefinitions(
                                fieldValue, visited, paramBindings, typeFieldBindings, depth + 1));
                    }
                    if (results.isEmpty()) {
                        results.addAll(inlineGetterMethodReturns(
                                accessor, receiverType, visited, paramBindings, typeFieldBindings, depth));
                    }
                    if (results.isEmpty() && receiverType.isInterface()) {
                        results.addAll(inlineConcreteAccessorFromReceiverDefs(
                                accessor, receiverDefs, visited, paramBindings, typeFieldBindings, depth));
                    }
                }
            }
        }
        return results;
    }

    private List<Expression> inlineAnonymousGetterReturns(
            ClassInstanceCreation cic,
            String methodName,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> fieldBindings,
            int depth) {
        if (cic.getAnonymousClassDeclaration() == null || methodName == null) {
            return List.of();
        }
        for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
            if (declObj instanceof MethodDeclaration methodDeclaration
                    && methodName.equals(methodDeclaration.getName().getIdentifier())
                    && methodDeclaration.getBody() != null) {
                List<Expression> results = new ArrayList<>();
                for (ReturnStatement rs : findReturnStatements(methodDeclaration.getBody())) {
                    if (rs.getExpression() != null) {
                        results.addAll(getReachingDefinitions(
                                rs.getExpression(), visited, paramBindings, fieldBindings, depth + 1));
                    }
                }
                return results;
            }
        }
        return List.of();
    }

    private List<Expression> inlineConcreteAccessorFromReceiverDefs(
            AccessorSummary accessor,
            List<Expression> receiverDefs,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> fieldBindings,
            int depth) {
        List<Expression> results = new ArrayList<>();
        for (Expression receiverDef : receiverDefs) {
            String concreteFqn = concreteReceiverTypeFqn(receiverDef);
            if (concreteFqn == null || concreteFqn.equals(accessor.ownerFqn())) {
                continue;
            }
            TypeDeclaration concreteType = context.getTypeDeclaration(concreteFqn);
            if (concreteType == null) {
                continue;
            }
            Map<IVariableBinding, Expression> concreteBindings = buildTypeDefaultFieldBindings(concreteType);
            if (receiverDef instanceof ClassInstanceCreation cic) {
                concreteBindings = getOrCreateFieldBindings(cic, paramBindings, fieldBindings);
            }
            Expression fieldValue = readFieldValue(accessor, concreteBindings, null);
            if (fieldValue != null) {
                results.addAll(getReachingDefinitions(
                        fieldValue, visited, paramBindings, concreteBindings, depth + 1));
            }
            if (results.isEmpty()) {
                results.addAll(inlineGetterMethodReturns(
                        accessor, concreteType, visited, paramBindings, concreteBindings, depth));
            }
        }
        return results;
    }

    private List<Expression> inlineAnonymousFromReceiverDefs(
            String methodName,
            List<Expression> receiverDefs,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        if (receiverDefs == null || methodName == null) {
            return List.of();
        }
        for (Expression receiverDef : receiverDefs) {
            if (receiverDef instanceof ClassInstanceCreation cic) {
                List<Expression> inlined = inlineAnonymousGetterReturns(
                        cic, methodName, visited, paramBindings, instanceFieldBindings, depth);
                if (!inlined.isEmpty()) {
                    return inlined;
                }
            }
        }
        return List.of();
    }

    private void applyFlowSensitiveMutationsForLocalReceiver(
            SimpleName receiverName,
            MethodInvocation useSite,
            AccessorSummary accessor,
            Map<IVariableBinding, Expression> fieldBindings,
            Map<IVariableBinding, Expression> paramBindings,
            Set<ASTNode> visited,
            int depth) {
        MethodDeclaration enclosingMethod = findEnclosingMethod(useSite);
        if (enclosingMethod == null) {
            return;
        }
        ASTNode defNode = findVariableDefinitionNode(enclosingMethod, receiverName.getIdentifier());
        if (defNode instanceof SingleVariableDeclaration) {
            String fieldName = accessor.fieldName();
            if (fieldName == null || fieldName.isEmpty()) {
                return;
            }
            String setterName = "set" + Character.toUpperCase(fieldName.charAt(0))
                    + fieldName.substring(1);
            Expression setterArg = findLocalSetterArgument(
                    enclosingMethod,
                    receiverName.getIdentifier(),
                    accessor.methodName(),
                    setterName,
                    accessor.fieldName());
            if (setterArg != null) {
                IVariableBinding fieldBinding = findFieldVariableBinding(
                        accessor.declaringFqn(), accessor.fieldName(), useSite);
                if (fieldBinding != null) {
                    List<Expression> resolved = getReachingDefinitions(
                            setterArg, visited, paramBindings, fieldBindings, depth + 1);
                    fieldBindings.put(fieldBinding, resolved.isEmpty() ? setterArg : resolved.get(0));
                }
            }
            return;
        }
        if (defNode != null) {
            applyIntermediateMutations(
                    receiverName, defNode, useSite, enclosingMethod, fieldBindings, paramBindings, visited, depth);
        }
    }

    private ASTNode findVariableDefinitionNode(MethodDeclaration methodDeclaration, String varName) {
        for (Object paramObj : methodDeclaration.parameters()) {
            if (paramObj instanceof SingleVariableDeclaration svd
                    && varName.equals(svd.getName().getIdentifier())) {
                return svd;
            }
        }
        if (methodDeclaration.getBody() == null) {
            return null;
        }
        final ASTNode[] found = new ASTNode[1];
        methodDeclaration.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(VariableDeclarationStatement node) {
                for (Object fragmentObj : node.fragments()) {
                    if (fragmentObj instanceof VariableDeclarationFragment fragment
                            && varName.equals(fragment.getName().getIdentifier())) {
                        found[0] = node;
                        return false;
                    }
                }
                return super.visit(node);
            }
        });
        return found[0];
    }

    private List<Expression> inlineGetterMethodReturns(
            AccessorSummary accessor,
            TypeDeclaration ownerType,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> fieldBindings,
            int depth) {
        MethodDeclaration getter = context.findMethodDeclaration(ownerType, accessor.methodName(), true);
        if (getter == null || getter.getBody() == null) {
            return List.of();
        }
        List<Expression> results = new ArrayList<>();
        for (ReturnStatement rs : findReturnStatements(getter.getBody())) {
            if (rs.getExpression() != null) {
                results.addAll(getReachingDefinitions(
                        rs.getExpression(), visited, paramBindings, fieldBindings, depth + 1));
            }
        }
        return results;
    }

    private Map<IVariableBinding, Expression> buildTypeDefaultFieldBindings(TypeDeclaration typeDeclaration) {
        Map<IVariableBinding, Expression> fieldBindings = new HashMap<>();
        if (typeDeclaration == null) {
            return fieldBindings;
        }
        for (FieldDeclaration fieldDeclaration : typeDeclaration.getFields()) {
            for (Object fragmentObj : fieldDeclaration.fragments()) {
                if (fragmentObj instanceof VariableDeclarationFragment fragment
                        && fragment.getInitializer() != null) {
                    IVariableBinding binding = fragment.resolveBinding();
                    if (binding != null) {
                        fieldBindings.put(binding, fragment.getInitializer());
                    }
                }
            }
        }
        return fieldBindings;
    }

    private TypeDeclaration resolveExpressionTypeDeclaration(
            Expression expr,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings) {
        if (expr == null) {
            return null;
        }
        ITypeBinding typeBinding = expr.resolveTypeBinding();
        if (typeBinding == null) {
            IVariableBinding variableBinding = getVariableBinding(expr);
            if (variableBinding != null && variableBinding.getType() != null) {
                typeBinding = variableBinding.getType();
            }
        }
        if (typeBinding == null) {
            return null;
        }
        String fqn = typeBinding.getErasure().getQualifiedName();
        TypeDeclaration td = context.getTypeDeclaration(fqn);
        if (td == null && expr.getRoot() instanceof CompilationUnit cu) {
            td = context.getTypeDeclaration(fqn, cu);
        }
        return td;
    }

    private void applyFlowSensitiveMutationsBeforeUse(
            SimpleName receiverName,
            MethodInvocation useSite,
            ClassInstanceCreation creationSite,
            Map<IVariableBinding, Expression> fieldBindings,
            Map<IVariableBinding, Expression> paramBindings,
            Set<ASTNode> visited,
            int depth) {
        MethodDeclaration enclosingMethod = findEnclosingMethod(useSite);
        if (enclosingMethod == null) {
            return;
        }
        ReachingDefinitions rd = getReachingDefinitionsForMethod(enclosingMethod);
        if (rd == null) {
            return;
        }
        Set<ASTNode> defs = rd.getReachingDefinitions(receiverName, receiverName.getIdentifier());
        for (ASTNode def : defs) {
            Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
            if (defExpr == creationSite || isExpressionWrapping(defExpr, creationSite)) {
                applyIntermediateMutations(receiverName, def, useSite, enclosingMethod, fieldBindings, paramBindings, visited, depth);
            }
        }
    }

    private void applyFlowSensitiveFieldMutationsBeforeUse(
            FieldAccess fieldReceiver,
            MethodInvocation useSite,
            Map<IVariableBinding, Expression> fieldBindings,
            Map<IVariableBinding, Expression> paramBindings,
            Set<ASTNode> visited,
            int depth) {
        Expression qualifier = fieldReceiver.getExpression();
        if (qualifier != null && !(qualifier instanceof ThisExpression)) {
            return;
        }
        MethodDeclaration enclosingMethod = findEnclosingMethod(useSite);
        if (enclosingMethod == null || enclosingMethod.getBody() == null) {
            return;
        }

        int useStart = useSite.getStartPosition();
        String targetFieldName = fieldReceiver.getName().getIdentifier();
        enclosingMethod.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if (node.getStartPosition() >= useStart) {
                    return false;
                }
                if (!isThisFieldAccess(node.getExpression(), targetFieldName)) {
                    return super.visit(node);
                }

                List<Expression> receiverDefs = getReachingDefinitions(
                        node.getExpression(), visited, paramBindings, fieldBindings, depth + 1);
                Optional<AccessorSummary> accessor = AccessorInlining.lookupAccessor(context, node, receiverDefs);
                if (accessor.isPresent() && accessor.get().isSetter()) {
                    inlineAccessorSetter(node, accessor.get(), fieldBindings, paramBindings, visited, depth);
                    return super.visit(node);
                }

                IMethodBinding mb = node.resolveMethodBinding();
                if (mb != null) {
                    List<TargetMethod> targets = resolveTargets(node, mb, visited, paramBindings, fieldBindings, depth + 1);
                    for (TargetMethod target : targets) {
                        if (target.declaration != null) {
                            evaluateMethodMutations(target.declaration, node.arguments(), fieldBindings);
                        }
                    }
                }
                return super.visit(node);
            }
        });
    }

    private boolean isThisFieldAccess(Expression expr, String fieldName) {
        if (!(expr instanceof FieldAccess fa)) {
            return false;
        }
        Expression receiver = fa.getExpression();
        return fa.getName().getIdentifier().equals(fieldName)
                && (receiver == null || receiver instanceof ThisExpression);
    }

    private Expression readFieldValue(
            AccessorSummary accessor,
            Map<IVariableBinding, Expression> fieldBindings,
            ASTNode contextNode) {
        IVariableBinding fieldBinding = findFieldVariableBinding(accessor.declaringFqn(), accessor.fieldName(), contextNode);
        if (fieldBinding != null && fieldBindings.containsKey(fieldBinding)) {
            return fieldBindings.get(fieldBinding);
        }
        if (fieldBinding != null) {
            return findFieldInitializer(fieldBinding, contextNode);
        }
        return null;
    }

    private IVariableBinding findFieldVariableBinding(String declaringFqn, String fieldName, ASTNode contextNode) {
        if (declaringFqn == null || fieldName == null) {
            return null;
        }

        TypeDeclaration typeDeclaration = context.getTypeDeclaration(declaringFqn);
        if (typeDeclaration != null) {
            IVariableBinding binding = findFieldOnType(typeDeclaration, fieldName, new HashSet<>());
            if (binding != null) {
                return binding;
            }
        }

        org.eclipse.jdt.core.dom.RecordDeclaration recordDeclaration = findRecordDeclaration(declaringFqn);
        if (recordDeclaration != null) {
            for (Object componentObj : recordDeclaration.recordComponents()) {
                if (componentObj instanceof SingleVariableDeclaration component
                        && component.getName().getIdentifier().equals(fieldName)) {
                    return component.resolveBinding();
                }
            }
        }
        return null;
    }

    private IVariableBinding findFieldOnType(TypeDeclaration typeDeclaration, String fieldName, Set<String> visitedTypes) {
        if (typeDeclaration == null) {
            return null;
        }
        String typeFqn = context.getFqn(typeDeclaration);
        if (!visitedTypes.add(typeFqn)) {
            return null;
        }

        for (FieldDeclaration fieldDeclaration : typeDeclaration.getFields()) {
            for (Object fragmentObj : fieldDeclaration.fragments()) {
                if (fragmentObj instanceof VariableDeclarationFragment fragment
                        && fragment.getName().getIdentifier().equals(fieldName)) {
                    IVariableBinding binding = fragment.resolveBinding();
                    if (binding != null) {
                        return binding;
                    }
                }
            }
        }

        String superFqn = context.getSuperclassFqn(typeDeclaration);
        if (superFqn != null && !"java.lang.Object".equals(superFqn)) {
            TypeDeclaration superType = context.getTypeDeclaration(superFqn);
            if (superType != null) {
                return findFieldOnType(superType, fieldName, visitedTypes);
            }
        }
        return null;
    }

    private org.eclipse.jdt.core.dom.RecordDeclaration findRecordDeclaration(String fqn) {
        return context.getRecordDeclaration(fqn);
    }

    private void inlineAccessorSetter(
            MethodInvocation mi,
            AccessorSummary accessor,
            Map<IVariableBinding, Expression> fieldBindings,
            Map<IVariableBinding, Expression> paramBindings,
            Set<ASTNode> visited,
            int depth) {
        if (mi.arguments().isEmpty()) {
            return;
        }
        Expression argument = (Expression) mi.arguments().get(0);
        List<Expression> resolvedArgument = getReachingDefinitions(argument, visited, paramBindings, fieldBindings, depth + 1);
        Expression value = resolvedArgument.isEmpty() ? argument : resolvedArgument.get(0);

        IVariableBinding fieldBinding = findFieldVariableBinding(accessor.declaringFqn(), accessor.fieldName(), mi);
        if (fieldBinding != null) {
            fieldBindings.put(fieldBinding, value);
        }
    }

    private IVariableBinding getVariableBinding(Expression expr) {
        if (expr instanceof SimpleName sn) {
            IBinding b = sn.resolveBinding();
            if (b instanceof IVariableBinding varBinding) return varBinding;
        } else if (expr instanceof QualifiedName qn) {
            IBinding b = qn.resolveBinding();
            if (b instanceof IVariableBinding varBinding) return varBinding;
        } else if (expr instanceof FieldAccess fa) {
            return fa.resolveFieldBinding();
        } else if (expr instanceof SuperFieldAccess sfa) {
            return sfa.resolveFieldBinding();
        }
        return null;
    }

    private Expression findFieldInitializer(IVariableBinding vb, ASTNode contextNode) {
        if (vb == null) return null;
        ITypeBinding declaringClass = vb.getDeclaringClass();
        if (declaringClass == null) return null;
        String classFqn = declaringClass.getErasure().getQualifiedName();
        if (classFqn == null || classFqn.isEmpty()) return null;

        TypeDeclaration td = context.getTypeDeclaration(classFqn);
        if (td == null) {
            CompilationUnit cu = null;
            ASTNode root = contextNode.getRoot();
            if (root instanceof CompilationUnit) {
                cu = (CompilationUnit) root;
            }
            td = context.getTypeDeclaration(classFqn, cu);
        }

        if (td != null) {
            // 1. Try field declaration initializer
            for (FieldDeclaration fd : td.getFields()) {
                for (Object fragObj : fd.fragments()) {
                    if (fragObj instanceof VariableDeclarationFragment fragment) {
                        if (fragment.getName().getIdentifier().equals(vb.getName())) {
                            if (fragment.getInitializer() != null) {
                                return fragment.getInitializer();
                            }
                        }
                    }
                }
            }
            // 2. Try constructor assignments
            for (MethodDeclaration md : td.getMethods()) {
                if (md.isConstructor() && md.getBody() != null) {
                    final Expression[] assignedExpr = new Expression[1];
                    md.getBody().accept(new ASTVisitor() {
                        @Override
                        public boolean visit(Assignment assignment) {
                            Expression lhs = assignment.getLeftHandSide();
                            boolean matches = false;
                            if (lhs instanceof SimpleName snLhs && snLhs.getIdentifier().equals(vb.getName())) {
                                IBinding b = snLhs.resolveBinding();
                                if (b instanceof IVariableBinding resolvedVb && resolvedVb.getKey().equals(vb.getKey())) {
                                    matches = true;
                                }
                            } else if (lhs instanceof FieldAccess fa && fa.getName().getIdentifier().equals(vb.getName()) && fa.getExpression() instanceof ThisExpression) {
                                IBinding b = fa.getName().resolveBinding();
                                if (b instanceof IVariableBinding resolvedVb && resolvedVb.getKey().equals(vb.getKey())) {
                                    matches = true;
                                }
                            }
                            if (matches) {
                                assignedExpr[0] = assignment.getRightHandSide();
                            }
                            return super.visit(assignment);
                        }
                    });
                    if (assignedExpr[0] != null) {
                        return assignedExpr[0];
                    }
                }
            }
        }
        return null;
    }

    private Map<IVariableBinding, Expression> getOrCreateFieldBindings(
            ClassInstanceCreation cic,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings) {
        Map<IVariableBinding, Expression> bindings = objectFieldBindings.get(cic);
        if (bindings == null) {
            bindings = buildFieldBindingsFromCic(cic, paramBindings, instanceFieldBindings);
            objectFieldBindings.put(cic, bindings);
        }
        return bindings;
    }

    private Map<IVariableBinding, Expression> getOrCreateFieldBindings(ClassInstanceCreation cic) {
        Map<IVariableBinding, Expression> bindings = objectFieldBindings.get(cic);
        if (bindings == null) {
            bindings = buildFieldBindingsFromCic(cic);
            objectFieldBindings.put(cic, bindings);
        }
        return bindings;
    }

    private Map<IVariableBinding, Expression> buildFieldBindingsFromCic(ClassInstanceCreation cic) {
        return buildFieldBindingsFromCic(cic, new HashMap<>(), new HashMap<>());
    }

    private Map<IVariableBinding, Expression> buildFieldBindingsFromCic(
            ClassInstanceCreation cic,
            Map<IVariableBinding, Expression> callerParamBindings,
            Map<IVariableBinding, Expression> callerInstanceFieldBindings) {
        Map<IVariableBinding, Expression> fieldBindings = new HashMap<>();
        IMethodBinding cb = cic.resolveConstructorBinding();
        if (cb == null) return fieldBindings;

        List<Expression> resolvedArguments = new ArrayList<>();
        for (Object argObj : cic.arguments()) {
            Expression arg = (Expression) argObj;
            List<Expression> resolved = getReachingDefinitions(arg, new HashSet<>(), callerParamBindings, callerInstanceFieldBindings, 0);
            if (!resolved.isEmpty()) {
                resolvedArguments.add(resolved.get(0));
            } else {
                resolvedArguments.add(arg);
            }
        }

        evaluateConstructor(cb, resolvedArguments, new HashMap<>(), fieldBindings, 0);
        return fieldBindings;
    }

    private void evaluateConstructor(
            IMethodBinding cb,
            List<?> arguments,
            Map<IVariableBinding, Expression> callerParamValues,
            Map<IVariableBinding, Expression> fieldBindings,
            int depth) {
        if (cb == null || ResolutionBudget.defaults().isConstructorExhausted(depth)) return;
        MethodDeclaration cd = findMethodDeclaration(cb);
        if (cd == null || cd.getBody() == null) return;

        Map<IVariableBinding, Expression> currentParamValues = new HashMap<>(callerParamValues);
        List<?> parameters = cd.parameters();
        int count = Math.min(parameters.size(), arguments.size());
        for (int i = 0; i < count; i++) {
            SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
            IVariableBinding paramVb = svd.resolveBinding();
            if (paramVb != null) {
                Expression arg = (Expression) arguments.get(i);
                Expression resolvedArg = resolveParamValue(arg, callerParamValues);
                currentParamValues.put(paramVb, resolvedArg);
            }
        }

        log.debug("EVAL_CTOR: cd={}, currentParamValues={}", cd.getName(), currentParamValues);

        List<?> statements = cd.getBody().statements();
        if (!statements.isEmpty()) {
            Object firstStmt = statements.get(0);
            if (firstStmt instanceof ConstructorInvocation ci) {
                IMethodBinding targetCb = ci.resolveConstructorBinding();
                if (targetCb != null) {
                    evaluateConstructor(targetCb, ci.arguments(), currentParamValues, fieldBindings, depth + 1);
                }
            } else if (firstStmt instanceof SuperConstructorInvocation sci) {
                IMethodBinding targetCb = sci.resolveConstructorBinding();
                if (targetCb != null) {
                    evaluateConstructor(targetCb, sci.arguments(), currentParamValues, fieldBindings, depth + 1);
                }
            }
        }

        cd.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(Assignment assignment) {
                Expression lhs = assignment.getLeftHandSide();
                Expression rhs = assignment.getRightHandSide();
                IVariableBinding fieldVb = null;
                if (lhs instanceof FieldAccess fa) {
                    if (fa.getExpression() instanceof ThisExpression) {
                        IBinding b = fa.getName().resolveBinding();
                        if (b instanceof IVariableBinding vb && vb.isField()) {
                            fieldVb = vb;
                        }
                    }
                } else if (lhs instanceof SimpleName sn) {
                    IBinding b = sn.resolveBinding();
                    if (b instanceof IVariableBinding vb && vb.isField()) {
                        fieldVb = vb;
                    }
                }

                if (fieldVb != null) {
                    Expression resolvedRhs = resolveParamValue(rhs, currentParamValues);
                    List<Expression> evaluated = getReachingDefinitions(resolvedRhs, new HashSet<>(), currentParamValues, fieldBindings, 0);
                    if (!evaluated.isEmpty()) {
                        fieldBindings.put(fieldVb, evaluated.get(0));
                    } else {
                        fieldBindings.put(fieldVb, resolvedRhs);
                    }
                }
                return super.visit(assignment);
            }
        });
    }

    private Expression resolveParamValue(Expression expr, Map<IVariableBinding, Expression> paramValues) {
        if (expr instanceof SimpleName sn) {
            IBinding b = sn.resolveBinding();
            if (b instanceof IVariableBinding vb && paramValues.containsKey(vb)) {
                return paramValues.get(vb);
            }
        } else if (expr instanceof CastExpression ce) {
            Expression resolved = resolveParamValue(ce.getExpression(), paramValues);
            if (resolved != ce.getExpression()) {
                return resolved;
            }
        } else if (expr instanceof ParenthesizedExpression pe) {
            Expression resolved = resolveParamValue(pe.getExpression(), paramValues);
            if (resolved != pe.getExpression()) {
                return resolved;
            }
        }
        return expr;
    }

    private static class TargetMethod {
        final MethodDeclaration declaration;
        final Map<IVariableBinding, Expression> fieldBindings;

        TargetMethod(MethodDeclaration declaration, Map<IVariableBinding, Expression> fieldBindings) {
            this.declaration = declaration;
            this.fieldBindings = fieldBindings;
        }
    }

    private List<TargetMethod> resolveTargets(
            MethodInvocation mi,
            IMethodBinding mb,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        
        Expression receiver = mi.getExpression();
        boolean isThisReceiver = (receiver == null || receiver instanceof ThisExpression);

        List<TargetMethod> targets = new ArrayList<>();

        if (receiver != null) {
            // Find concrete ClassInstanceCreation definitions in the reaching definitions path of the receiver
            List<Expression> receiverDefs = getReachingDefinitions(receiver, visited, paramBindings, instanceFieldBindings, depth + 1);
            List<ClassInstanceCreation> concreteCics = new ArrayList<>();
            for (Expression rDef : receiverDefs) {
                if (rDef instanceof ClassInstanceCreation cic) {
                    concreteCics.add(cic);
                }
            }

            if (!concreteCics.isEmpty()) {
                // Type narrowing is active! Only trace through the concrete instantiated types.
                for (ClassInstanceCreation cic : concreteCics) {
                    ITypeBinding concreteType = null;
                    if (cic.resolveConstructorBinding() != null) {
                        concreteType = cic.resolveConstructorBinding().getDeclaringClass();
                    }
                    if (concreteType == null) {
                        concreteType = cic.resolveTypeBinding();
                    }
                    if (concreteType != null) {
                        MethodDeclaration md = findMethodDeclarationInType(concreteType, mb);
                        if (md != null) {
                            Map<IVariableBinding, Expression> concreteFieldBindings = 
                                getOrCreateFieldBindings(cic);
                            
                            // Apply flow-sensitive intermediate local variable mutations
                            if (receiver instanceof SimpleName sn) {
                                MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
                                if (enclosingMethod != null) {
                                    ReachingDefinitions rd = getReachingDefinitionsForMethod(enclosingMethod);
                                    if (rd != null) {
                                        Set<ASTNode> defs = rd.getReachingDefinitions(sn, sn.getIdentifier());
                                        for (ASTNode def : defs) {
                                            Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
                                            if (defExpr == cic || isExpressionWrapping(defExpr, cic)) {
                                                applyIntermediateMutations(sn, def, mi, enclosingMethod, concreteFieldBindings, paramBindings, visited, depth);
                                            }
                                        }
                                    }
                                }
                            }
                            
                            targets.add(new TargetMethod(md, concreteFieldBindings));
                        }
                    }
                }
                return targets;
            }
        }

        // Fallback: Polymorphic target discovery (including interface implementations)
        ITypeBinding declaringClass = mb.getDeclaringClass();
        if (declaringClass != null) {
            String declaringClassFqn = declaringClass.getErasure().getQualifiedName();
            if (declaringClassFqn != null && !declaringClassFqn.isEmpty()) {
                // Find all known implementation subclasses in the codebase
                List<String> implClassFqns = context.getImplementations(declaringClassFqn);
                List<String> currentPath = activeAnalysisPath();
                if (currentPath != null && implClassFqns != null && !implClassFqns.isEmpty()) {
                    Set<String> contextTypes = getCompatibleContextTypes(currentPath, declaringClassFqn);
                    List<String> filteredImpls = new ArrayList<>();
                    for (String implFqn : implClassFqns) {
                        if (contextTypes.contains(implFqn)) {
                            filteredImpls.add(implFqn);
                        }
                    }
                    if (!filteredImpls.isEmpty()) {
                        implClassFqns = filteredImpls;
                    }
                }
                if (implClassFqns != null && !implClassFqns.isEmpty()) {
                    for (String implFqn : implClassFqns) {
                        TypeDeclaration td = context.getTypeDeclaration(implFqn);
                        if (td != null) {
                            MethodDeclaration md = findMethodDeclarationInType(td.resolveBinding(), mb);
                            if (md != null) {
                                // For polymorphic targets with unknown instance, we don't have concrete field bindings
                                Map<IVariableBinding, Expression> targetFieldBindings = isThisReceiver ? new HashMap<>(instanceFieldBindings) : new HashMap<>();
                                targets.add(new TargetMethod(md, targetFieldBindings));
                            }
                        }
                    }
                }
            }
            
            // If the declaring class is a class (not an interface), or if the declared method itself is concrete/has a body,
            // we should also include the declared method itself in the polymorphic targets.
            MethodDeclaration declaredMd = findMethodDeclaration(mb);
            if (declaredMd != null && declaredMd.getBody() != null) {
                boolean alreadyAdded = false;
                for (TargetMethod tm : targets) {
                    if (tm.declaration == declaredMd) {
                        alreadyAdded = true;
                        break;
                    }
                }
                if (!alreadyAdded) {
                    Map<IVariableBinding, Expression> targetFieldBindings = isThisReceiver ? new HashMap<>(instanceFieldBindings) : new HashMap<>();
                    targets.add(new TargetMethod(declaredMd, targetFieldBindings));
                }
            }
        }

        // If no polymorphic subclass targets were found, fallback to the declared method itself
        if (targets.isEmpty()) {
            MethodDeclaration md = findMethodDeclaration(mb);
            if (md != null) {
                Map<IVariableBinding, Expression> targetFieldBindings = isThisReceiver ? new HashMap<>(instanceFieldBindings) : new HashMap<>();
                targets.add(new TargetMethod(md, targetFieldBindings));
            }
        }

        return targets;
    }

    private List<String> activeAnalysisPath() {
        return context.getAnalysisCallPath();
    }

    private boolean isExpressionWrapping(Expression outer, Expression inner) {
        Expression current = outer;
        while (current != null) {
            if (current == inner) return true;
            if (current instanceof ParenthesizedExpression pe) {
                current = pe.getExpression();
            } else if (current instanceof CastExpression ce) {
                current = ce.getExpression();
            } else {
                break;
            }
        }
        return false;
    }

    private ControlFlowGraph.CfgNode findCfgNode(ControlFlowGraph cfg, ASTNode astNode) {
        ASTNode current = astNode;
        while (current != null) {
            for (ControlFlowGraph.CfgNode node : cfg.getNodes()) {
                if (node.getAstNode() == current) {
                    return node;
                }
            }
            current = current.getParent();
        }
        return null;
    }

    private boolean isTargetVariable(Expression expr, IVariableBinding targetVar, String targetName) {
        if (expr instanceof SimpleName sn) {
            IBinding b = sn.resolveBinding();
            if (b instanceof IVariableBinding vb) {
                if (targetVar != null && vb.getKey().equals(targetVar.getKey())) {
                    return true;
                }
            }
            if (targetName != null && sn.getIdentifier().equals(targetName)) {
                return true;
            }
        }
        return false;
    }

    private void evaluateMethodMutations(
            MethodDeclaration md,
            List<?> arguments,
            Map<IVariableBinding, Expression> fieldBindings) {
        if (md == null || md.getBody() == null) return;

        ASTNode parent = md.getParent();
        if (parent instanceof AbstractTypeDeclaration typeDeclaration) {
            String ownerFqn = context.getFqn(typeDeclaration);
            Optional<AccessorSummary> accessor = context.getAccessorIndex().lookup(ownerFqn, md.getName().getIdentifier());
            if (accessor.isPresent() && accessor.get().isSetter() && !arguments.isEmpty()) {
                IVariableBinding fieldBinding = findFieldVariableBinding(
                        accessor.get().declaringFqn(),
                        accessor.get().fieldName(),
                        md);
                if (fieldBinding != null) {
                    fieldBindings.put(fieldBinding, (Expression) arguments.get(0));
                    return;
                }
            }
        }

        Map<IVariableBinding, Expression> currentParamValues = new HashMap<>();
        List<?> parameters = md.parameters();
        int count = Math.min(parameters.size(), arguments.size());
        for (int i = 0; i < count; i++) {
            SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
            IVariableBinding paramVb = svd.resolveBinding();
            if (paramVb != null) {
                Expression arg = (Expression) arguments.get(i);
                currentParamValues.put(paramVb, arg);
            }
        }

        md.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(Assignment assignment) {
                Expression lhs = assignment.getLeftHandSide();
                Expression rhs = assignment.getRightHandSide();
                IVariableBinding fieldVb = null;
                if (lhs instanceof FieldAccess fa) {
                    if (fa.getExpression() instanceof ThisExpression) {
                        IBinding b = fa.getName().resolveBinding();
                        if (b instanceof IVariableBinding vb && vb.isField()) {
                            fieldVb = vb;
                        }
                    }
                } else if (lhs instanceof SimpleName sn) {
                    IBinding b = sn.resolveBinding();
                    if (b instanceof IVariableBinding vb && vb.isField()) {
                        fieldVb = vb;
                    }
                }

                if (fieldVb != null) {
                    Expression resolvedRhs = resolveParamValue(rhs, currentParamValues);
                    fieldBindings.put(fieldVb, resolvedRhs);
                }
                return super.visit(assignment);
            }
        });
    }

    private void findAndApplyMutations(
            ASTNode node,
            IVariableBinding targetVar,
            String targetName,
            Map<IVariableBinding, Expression> fieldBindings,
            Map<IVariableBinding, Expression> paramBindings,
            Set<ASTNode> visited,
            int depth) {
        node.accept(new ASTVisitor() {
            @Override
            public boolean visit(Assignment assignment) {
                Expression lhs = assignment.getLeftHandSide();
                Expression rhs = assignment.getRightHandSide();
                IVariableBinding fieldVb = null;
                if (lhs instanceof FieldAccess fa) {
                    if (isTargetVariable(fa.getExpression(), targetVar, targetName)) {
                        IBinding b = fa.getName().resolveBinding();
                        if (b instanceof IVariableBinding vb && vb.isField()) {
                            fieldVb = vb;
                        }
                    }
                } else if (lhs instanceof QualifiedName qn) {
                    if (isTargetVariable(qn.getQualifier(), targetVar, targetName)) {
                        IBinding b = qn.getName().resolveBinding();
                        if (b instanceof IVariableBinding vb && vb.isField()) {
                            fieldVb = vb;
                        }
                    }
                }

                if (fieldVb != null) {
                    List<Expression> resolvedRhs = getReachingDefinitions(rhs, visited, paramBindings, fieldBindings, depth + 1);
                    if (!resolvedRhs.isEmpty()) {
                        fieldBindings.put(fieldVb, resolvedRhs.get(0));
                    } else {
                        fieldBindings.put(fieldVb, rhs);
                    }
                }
                return super.visit(assignment);
            }

            @Override
            public boolean visit(MethodInvocation mi) {
                Expression receiver = mi.getExpression();
                if (receiver != null && isTargetVariable(receiver, targetVar, targetName)) {
                    List<Expression> receiverDefs = getReachingDefinitions(
                            receiver, visited, paramBindings, fieldBindings, depth + 1);
                    Optional<AccessorSummary> accessor = AccessorInlining.lookupAccessor(context, mi, receiverDefs);
                    if (accessor.isPresent() && accessor.get().isSetter()) {
                        inlineAccessorSetter(mi, accessor.get(), fieldBindings, paramBindings, visited, depth);
                        return super.visit(mi);
                    }

                    IMethodBinding mb = mi.resolveMethodBinding();
                    if (mb != null) {
                        List<TargetMethod> targets = resolveTargets(mi, mb, visited, paramBindings, fieldBindings, depth + 1);
                        for (TargetMethod target : targets) {
                            if (target.declaration != null) {
                                evaluateMethodMutations(target.declaration, mi.arguments(), fieldBindings);
                            }
                        }
                    }
                }
                return super.visit(mi);
            }
        });
    }

    private void applyIntermediateMutations(
            SimpleName receiverSimpleName,
            ASTNode defNode,
            ASTNode useNode,
            MethodDeclaration enclosingMethod,
            Map<IVariableBinding, Expression> fieldBindings,
            Map<IVariableBinding, Expression> paramBindings,
            Set<ASTNode> visited,
            int depth) {
        
        IBinding b = receiverSimpleName.resolveBinding();
        IVariableBinding targetVar = (b instanceof IVariableBinding vb) ? vb : null;
        String targetName = receiverSimpleName.getIdentifier();
        
        ControlFlowGraph cfg = cfgCache.computeIfAbsent(enclosingMethod, CfgBuilder::build);
        if (cfg == null) return;
        
        ControlFlowGraph.CfgNode defCfgNode = findCfgNode(cfg, defNode);
        if (defCfgNode == null && defNode instanceof SingleVariableDeclaration) {
            defCfgNode = cfg.getEntryNode();
        }
        ControlFlowGraph.CfgNode useCfgNode = findCfgNode(cfg, useNode);
        
        if (defCfgNode != null && useCfgNode != null) {
            Set<ControlFlowGraph.CfgNode> reachFromD = new HashSet<>();
            Queue<ControlFlowGraph.CfgNode> queue = new LinkedList<>();
            queue.add(defCfgNode);
            reachFromD.add(defCfgNode);
            while (!queue.isEmpty()) {
                ControlFlowGraph.CfgNode curr = queue.poll();
                for (ControlFlowGraph.CfgNode succ : curr.getSuccessors()) {
                    if (reachFromD.add(succ)) {
                        queue.add(succ);
                    }
                }
            }

            Set<ControlFlowGraph.CfgNode> reachToU = new HashSet<>();
            queue.clear();
            queue.add(useCfgNode);
            reachToU.add(useCfgNode);
            while (!queue.isEmpty()) {
                ControlFlowGraph.CfgNode curr = queue.poll();
                for (ControlFlowGraph.CfgNode pred : curr.getPredecessors()) {
                    if (reachToU.add(pred)) {
                        queue.add(pred);
                    }
                }
            }

            Set<ControlFlowGraph.CfgNode> pathNodes = new HashSet<>(reachFromD);
            pathNodes.retainAll(reachToU);

            List<ControlFlowGraph.CfgNode> orderedPathNodes = new ArrayList<>(pathNodes);
            List<ControlFlowGraph.CfgNode> allNodes = cfg.getNodes();
            orderedPathNodes.sort(Comparator.comparingInt(allNodes::indexOf));

            for (ControlFlowGraph.CfgNode node : orderedPathNodes) {
                if (node != defCfgNode && node != useCfgNode && node.getAstNode() != null) {
                    findAndApplyMutations(node.getAstNode(), targetVar, targetName, fieldBindings, paramBindings, visited, depth);
                }
            }
        }
    }

    private MethodDeclaration findMethodDeclarationInType(ITypeBinding typeBinding, IMethodBinding mb) {
        if (typeBinding == null || mb == null) return null;
        
        // 1. Try to find it in the class hierarchy (including typeBinding itself)
        ITypeBinding current = typeBinding;
        while (current != null) {
            MethodDeclaration md = findMethodInTypeDeclarationOnly(current, mb);
            if (md != null) return md;
            current = current.getSuperclass();
        }
        
        // 2. Try to find it in the interface hierarchy of typeBinding
        Set<String> visited = new HashSet<>();
        Queue<ITypeBinding> queue = new LinkedList<>();
        queue.add(typeBinding);
        while (!queue.isEmpty()) {
            ITypeBinding tb = queue.poll();
            if (tb == null) continue;
            String key = tb.getKey();
            if (key != null && !visited.add(key)) continue;
            
            MethodDeclaration md = findMethodInTypeDeclarationOnly(tb, mb);
            if (md != null) return md;
            
            for (ITypeBinding interf : tb.getInterfaces()) {
                queue.add(interf);
            }
        }
        
        return null;
    }

    private MethodDeclaration findMethodInTypeDeclarationOnly(ITypeBinding typeBinding, IMethodBinding mb) {
        String classFqn = typeBinding.getErasure().getQualifiedName();
        if (classFqn != null && !classFqn.isEmpty()) {
            TypeDeclaration td = context.getTypeDeclaration(classFqn);
            if (td != null) {
                for (MethodDeclaration md : td.getMethods()) {
                    IMethodBinding candidateMb = md.resolveBinding();
                    if (candidateMb != null && candidateMb.getKey().equals(mb.getKey())) {
                        return md;
                    }
                }
                for (MethodDeclaration md : td.getMethods()) {
                    if (md.getName().getIdentifier().equals(mb.getName()) && 
                        md.parameters().size() == mb.getParameterTypes().length) {
                        return md;
                    }
                }
            }
        }
        return null;
    }

    private MethodDeclaration findMethodDeclaration(IMethodBinding mb) {
        if (mb == null) return null;
        return findMethodDeclarationInType(mb.getDeclaringClass(), mb);
    }

    private static boolean isFunctionalSamInvocation(MethodInvocation mi) {
        String methodName = mi.getName().getIdentifier();
        if (!isKnownSamMethodName(methodName)) {
            return false;
        }
        if ("get".equals(methodName) && !mi.arguments().isEmpty()) {
            return false;
        }
        Expression receiver = mi.getExpression();
        if (receiver == null) {
            return false;
        }
        ITypeBinding typeBinding = receiver.resolveTypeBinding();
        if (typeBinding == null) {
            return false;
        }
        ITypeBinding erasure = typeBinding.getErasure();
        return isKnownFunctionalInterfaceName(erasure.getQualifiedName());
    }

    private static boolean isKnownSamMethodName(String methodName) {
        return switch (methodName) {
            case "run", "get", "call", "accept", "apply", "test" -> true;
            default -> false;
        };
    }

    private static boolean isKnownFunctionalInterfaceName(String fqn) {
        if (fqn == null || fqn.isBlank()) {
            return false;
        }
        String simple = fqn.contains(".") ? fqn.substring(fqn.lastIndexOf('.') + 1) : fqn;
        return switch (simple) {
            case "Runnable", "Supplier", "Function", "Callable", "Consumer", "BiConsumer", "Predicate", "BiFunction" ->
                    true;
            default -> fqn.contains("Supplier") || fqn.contains("Function") || fqn.contains("Callable");
        };
    }

    private List<Expression> resolveFunctionalInterfaceInvocation(
            MethodInvocation mi,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        Expression receiver = mi.getExpression();
        if (receiver instanceof SimpleName sn) {
            IBinding binding = sn.resolveBinding();
            if (binding instanceof IVariableBinding variableBinding && paramBindings.containsKey(variableBinding)) {
                return resolveFunctionalBoundExpression(
                        paramBindings.get(variableBinding), visited, paramBindings, instanceFieldBindings, depth + 1);
            }
        }
        List<Expression> receiverDefs = getReachingDefinitions(receiver, visited, paramBindings, instanceFieldBindings, depth + 1);
        List<Expression> results = new ArrayList<>();
        for (Expression receiverDef : receiverDefs) {
            results.addAll(resolveFunctionalBoundExpression(
                    receiverDef, visited, paramBindings, instanceFieldBindings, depth + 1));
        }
        return results;
    }

    private List<Expression> resolveFunctionalBoundExpression(
            Expression bound,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        if (bound == null) {
            return List.of();
        }
        if (bound instanceof LambdaExpression lambda) {
            ASTNode body = lambda.getBody();
            if (body instanceof Expression bodyExpr) {
                List<Expression> sideEffectArgs = collectVoidCallArgumentDefinitions(
                        bodyExpr, visited, paramBindings, instanceFieldBindings, depth);
                if (!sideEffectArgs.isEmpty()) {
                    return sideEffectArgs;
                }
                return getReachingDefinitions(bodyExpr, visited, paramBindings, instanceFieldBindings, depth);
            }
            if (body instanceof Block block) {
                List<Expression> results = new ArrayList<>();
                collectSideEffectExpressions(block, results, visited, paramBindings, instanceFieldBindings, depth);
                return results;
            }
        }
        return getReachingDefinitions(bound, visited, paramBindings, instanceFieldBindings, depth);
    }

    private List<Expression> collectVoidCallArgumentDefinitions(
            Expression bodyExpr,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> instanceFieldBindings,
            int depth) {
        if (!(bodyExpr instanceof MethodInvocation mi) || !isVoidMethodInvocation(mi)) {
            return List.of();
        }
        List<Expression> results = new ArrayList<>();
        for (Object argObj : mi.arguments()) {
            results.addAll(getReachingDefinitions(
                    (Expression) argObj, visited, paramBindings, instanceFieldBindings, depth + 1));
        }
        return results;
    }

    private static boolean isVoidMethodInvocation(MethodInvocation mi) {
        IMethodBinding binding = mi.resolveMethodBinding();
        return binding != null && binding.getReturnType() != null && "void".equals(binding.getReturnType().getName());
    }

    private void collectSideEffectExpressions(
            Block body,
            List<Expression> results,
            Set<ASTNode> visited,
            Map<IVariableBinding, Expression> paramBindings,
            Map<IVariableBinding, Expression> fieldBindings,
            int depth) {
        if (body == null) {
            return;
        }
        body.accept(new ASTVisitor() {
            @Override
            public boolean visit(ExpressionStatement node) {
                Expression statementExpr = node.getExpression();
                if (statementExpr instanceof MethodInvocation nested) {
                    for (Object argObj : nested.arguments()) {
                        results.addAll(getReachingDefinitions(
                                (Expression) argObj, visited, paramBindings, fieldBindings, depth + 1));
                    }
                }
                results.addAll(getReachingDefinitions(
                        statementExpr, visited, paramBindings, fieldBindings, depth + 1));
                return super.visit(node);
            }
        });
    }

    private List<ReturnStatement> findReturnStatements(ASTNode node) {
        List<ReturnStatement> returns = new ArrayList<>();
        node.accept(new ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement rs) {
                returns.add(rs);
                return super.visit(rs);
            }
            @Override
            public boolean visit(LambdaExpression le) {
                return false;
            }
            @Override
            public boolean visit(AnonymousClassDeclaration acd) {
                return false;
            }
            @Override
            public boolean visit(TypeDeclarationStatement tds) {
                return false;
            }
        });
        return returns;
    }

    public Expression findLocalSetterArgument(
            MethodDeclaration md,
            String varName,
            String getterName,
            String resolvedSetterName,
            String resolvedFieldName) {
        if (md == null || md.getBody() == null) {
            return null;
        }

        ControlFlowGraph cfg = cfgCache.computeIfAbsent(md, CfgBuilder::build);
        MethodInvocation getterUse = findGetterUseOnVariable(cfg, varName, getterName);

        List<ControlFlowGraph.CfgNode> nodesToScan;
        ControlFlowGraph.CfgNode getterCfgNode = null;
        if (getterUse != null && getterUse.getExpression() instanceof SimpleName receiver) {
            getterCfgNode = findCfgNode(cfg, getterUse);
            nodesToScan = collectCfgNodesBetweenDefAndUse(md, receiver, getterUse, cfg);
        } else {
            nodesToScan = orderedStatementNodes(cfg);
        }

        Expression lastSetterArg = null;
        for (ControlFlowGraph.CfgNode node : nodesToScan) {
            if (getterCfgNode != null && node == getterCfgNode) {
                continue;
            }
            Expression arg = extractSetterArgument(node.getAstNode(), varName, resolvedSetterName, resolvedFieldName);
            if (arg != null) {
                lastSetterArg = arg;
            }
        }
        return lastSetterArg;
    }

    private List<ControlFlowGraph.CfgNode> orderedStatementNodes(ControlFlowGraph cfg) {
        List<ControlFlowGraph.CfgNode> result = new ArrayList<>();
        for (ControlFlowGraph.CfgNode node : cfg.getNodes()) {
            if (node.getType() == ControlFlowGraph.CfgNodeType.STATEMENT && node.getAstNode() != null) {
                result.add(node);
            }
        }
        result.sort(Comparator.comparingInt(cfg.getNodes()::indexOf));
        return result;
    }

    private MethodInvocation findGetterUseOnVariable(ControlFlowGraph cfg, String varName, String getterName) {
        if (getterName == null || getterName.isEmpty()) {
            return null;
        }
        MethodInvocation lastMatch = null;
        for (ControlFlowGraph.CfgNode node : orderedStatementNodes(cfg)) {
            MethodInvocation match = findGetterInvocationInNode(node.getAstNode(), varName, getterName);
            if (match != null) {
                lastMatch = match;
            }
        }
        return lastMatch;
    }

    private MethodInvocation findGetterInvocationInNode(ASTNode node, String varName, String getterName) {
        Expression expr = unwrapCfgStatementExpression(node);
        if (expr == null) {
            return null;
        }
        final MethodInvocation[] found = new MethodInvocation[1];
        if (expr instanceof MethodInvocation mi && matchesGetterInvocation(mi, varName, getterName)) {
            found[0] = mi;
        }
        expr.accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation nested) {
                if (nested != expr && matchesGetterInvocation(nested, varName, getterName)) {
                    found[0] = nested;
                }
                return super.visit(nested);
            }
        });
        return found[0];
    }

    private boolean matchesGetterInvocation(MethodInvocation mi, String varName, String getterName) {
        if (!(mi.getExpression() instanceof SimpleName sn) || !sn.getIdentifier().equals(varName)) {
            return false;
        }
        String methodId = mi.getName().getIdentifier();
        if (methodId.equals(getterName) || methodId.equalsIgnoreCase(getterName)) {
            return true;
        }
        if (!getterName.startsWith("get") && !getterName.startsWith("is")) {
            return methodId.equals("get" + getterName) || methodId.equalsIgnoreCase("get" + getterName);
        }
        return false;
    }

    private List<ControlFlowGraph.CfgNode> collectCfgNodesBetweenDefAndUse(
            MethodDeclaration md,
            SimpleName receiver,
            MethodInvocation useSite,
            ControlFlowGraph cfg) {
        ReachingDefinitions rd = getReachingDefinitionsForMethod(md);
        if (rd == null) {
            return orderedStatementNodes(cfg);
        }

        ControlFlowGraph.CfgNode useCfgNode = findCfgNode(cfg, useSite);
        if (useCfgNode == null) {
            return orderedStatementNodes(cfg);
        }

        Set<ControlFlowGraph.CfgNode> pathNodes = new HashSet<>();
        Set<ASTNode> defs = rd.getReachingDefinitions(receiver, receiver.getIdentifier());
        for (ASTNode def : defs) {
            ControlFlowGraph.CfgNode defCfgNode = findCfgNode(cfg, def);
            if (defCfgNode == null) {
                continue;
            }
            Set<ControlFlowGraph.CfgNode> reachFromDef = forwardReach(defCfgNode);
            Set<ControlFlowGraph.CfgNode> reachToUse = backwardReach(useCfgNode);
            Set<ControlFlowGraph.CfgNode> between = new HashSet<>(reachFromDef);
            between.retainAll(reachToUse);
            pathNodes.addAll(between);
        }

        if (pathNodes.isEmpty()) {
            return orderedStatementNodes(cfg);
        }

        List<ControlFlowGraph.CfgNode> ordered = new ArrayList<>();
        for (ControlFlowGraph.CfgNode node : cfg.getNodes()) {
            if (pathNodes.contains(node)) {
                ordered.add(node);
            }
        }
        return ordered;
    }

    private Set<ControlFlowGraph.CfgNode> forwardReach(ControlFlowGraph.CfgNode start) {
        Set<ControlFlowGraph.CfgNode> reached = new HashSet<>();
        Queue<ControlFlowGraph.CfgNode> queue = new LinkedList<>();
        queue.add(start);
        reached.add(start);
        while (!queue.isEmpty()) {
            ControlFlowGraph.CfgNode current = queue.poll();
            for (ControlFlowGraph.CfgNode succ : current.getSuccessors()) {
                if (reached.add(succ)) {
                    queue.add(succ);
                }
            }
        }
        return reached;
    }

    private Set<ControlFlowGraph.CfgNode> backwardReach(ControlFlowGraph.CfgNode end) {
        Set<ControlFlowGraph.CfgNode> reached = new HashSet<>();
        Queue<ControlFlowGraph.CfgNode> queue = new LinkedList<>();
        queue.add(end);
        reached.add(end);
        while (!queue.isEmpty()) {
            ControlFlowGraph.CfgNode current = queue.poll();
            for (ControlFlowGraph.CfgNode pred : current.getPredecessors()) {
                if (reached.add(pred)) {
                    queue.add(pred);
                }
            }
        }
        return reached;
    }

    private Expression extractSetterArgument(
            ASTNode node,
            String varName,
            String resolvedSetterName,
            String resolvedFieldName) {
        Expression expr = unwrapCfgStatementExpression(node);
        if (expr == null) {
            return null;
        }

        Expression direct = matchSetterExpression(expr, varName, resolvedSetterName, resolvedFieldName);
        if (direct != null) {
            return direct;
        }
        if (expr instanceof Assignment assignment) {
            return matchAssignmentSetter(assignment, varName, resolvedFieldName);
        }

        final Expression[] found = new Expression[1];
        expr.accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation nested) {
                Expression arg = matchSetterExpression(nested, varName, resolvedSetterName, resolvedFieldName);
                if (arg != null) {
                    found[0] = arg;
                }
                return super.visit(nested);
            }

            @Override
            public boolean visit(Assignment assignment) {
                Expression arg = matchAssignmentSetter(assignment, varName, resolvedFieldName);
                if (arg != null) {
                    found[0] = arg;
                }
                return super.visit(assignment);
            }
        });
        return found[0];
    }

    private Expression unwrapCfgStatementExpression(ASTNode node) {
        if (node instanceof ExpressionStatement es) {
            return es.getExpression();
        }
        if (node instanceof VariableDeclarationStatement vds && !vds.fragments().isEmpty()) {
            VariableDeclarationFragment frag = (VariableDeclarationFragment) vds.fragments().get(0);
            return frag.getInitializer();
        }
        if (node instanceof Assignment assignment) {
            return assignment;
        }
        if (node instanceof Expression expr) {
            return expr;
        }
        return null;
    }

    private Expression matchSetterExpression(
            Expression expr,
            String varName,
            String resolvedSetterName,
            String resolvedFieldName) {
        if (!(expr instanceof MethodInvocation mi)) {
            return null;
        }
        String methodId = mi.getName().getIdentifier();
        if (!methodId.equals(resolvedSetterName)
                && !methodId.equalsIgnoreCase("set" + resolvedFieldName)
                && !methodId.equalsIgnoreCase(resolvedFieldName)) {
            return null;
        }
        if (mi.getExpression() instanceof SimpleName sn && sn.getIdentifier().equals(varName)
                && !mi.arguments().isEmpty()) {
            return (Expression) mi.arguments().get(0);
        }
        return null;
    }

    private Expression matchAssignmentSetter(Assignment assignment, String varName, String resolvedFieldName) {
        Expression lhs = assignment.getLeftHandSide();
        if (lhs instanceof FieldAccess fa) {
            if (fa.getExpression() instanceof SimpleName faSn && faSn.getIdentifier().equals(varName)) {
                if (fa.getName().getIdentifier().equalsIgnoreCase(resolvedFieldName)) {
                    return assignment.getRightHandSide();
                }
            }
        } else if (lhs instanceof QualifiedName qqn) {
            if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) {
                if (qqn.getName().getIdentifier().equalsIgnoreCase(resolvedFieldName)) {
                    return assignment.getRightHandSide();
                }
            }
        }
        return null;
    }

    @Override
    public String resolveValue(Expression expr, CodebaseContext context) {
        if (expr == null) return null;

        List<Expression> defs = getReachingDefinitions(expr);
        if (defs.isEmpty()) {
            return context.resolveExpression(expr);
        }

        String firstVal = null;
        for (Expression def : defs) {
            String val = context.resolveExpression(def);
            if (val != null) {
                if (firstVal == null) {
                    firstVal = val;
                } else if (!firstVal.equals(val)) {
                    return null;
                }
            }
        }
        return firstVal != null ? firstVal : context.resolveExpression(expr);
    }

    private Set<String> getCompatibleContextTypes(List<String> currentPath, String declaringClassFqn) {
        String cacheKey = "contextTypes:" + currentPath + "#" + declaringClassFqn;
        return context.getClassCompatibilityCache().memoize(
                cacheKey, ignored -> computeCompatibleContextTypes(currentPath, declaringClassFqn));
    }

    private Set<String> computeCompatibleContextTypes(List<String> currentPath, String declaringClassFqn) {
        Set<String> contextTypes = new HashSet<>();
        List<String> implementations = context.getImplementations(declaringClassFqn);
        
        // 1. Direct class name from the path itself (prioritize concrete implementations in the path)
        for (String methodFqn : currentPath) {
            int lastDot = methodFqn.lastIndexOf('.');
            if (lastDot > 0) {
                String pathClass = methodFqn.substring(0, lastDot);
                if (implementations.contains(pathClass)) {
                    contextTypes.add(pathClass);
                }
            }
        }
        if (!contextTypes.isEmpty()) {
            return contextTypes;
        }
        
        // 2. Fallback: Inspect fields and local variables/parameters of the classes in the path
        for (String methodFqn : currentPath) {
            int lastDot = methodFqn.lastIndexOf('.');
            if (lastDot > 0) {
                String pathClass = methodFqn.substring(0, lastDot);
                if (pathClass.equals(declaringClassFqn) || implementations.contains(pathClass)) {
                    contextTypes.add(pathClass);
                }
                
                TypeDeclaration td = context.getTypeDeclaration(pathClass);
                if (td != null) {
                    // Check fields
                    for (FieldDeclaration fd : td.getFields()) {
                        String fieldType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(fd.getType());
                        TypeDeclaration fieldTd = context.getTypeDeclaration(fieldType);
                        if (fieldTd == null) {
                            CompilationUnit cu = (CompilationUnit) td.getRoot();
                            fieldTd = context.getTypeDeclaration(fieldType, cu);
                        }
                        if (fieldTd != null) {
                            String fieldFqn = context.getFqn(fieldTd);
                            if (fieldFqn.equals(declaringClassFqn) || implementations.contains(fieldFqn)) {
                                contextTypes.add(fieldFqn);
                            }
                        }
                    }
                    
                    // Check method parameters
                    for (MethodDeclaration md : td.getMethods()) {
                        for (Object paramObj : md.parameters()) {
                            if (paramObj instanceof SingleVariableDeclaration svd) {
                                String paramType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(svd.getType());
                                TypeDeclaration paramTd = context.getTypeDeclaration(paramType);
                                if (paramTd == null) {
                                    CompilationUnit cu = (CompilationUnit) td.getRoot();
                                    paramTd = context.getTypeDeclaration(paramType, cu);
                                }
                                if (paramTd != null) {
                                    String paramFqn = context.getFqn(paramTd);
                                    if (paramFqn.equals(declaringClassFqn) || implementations.contains(paramFqn)) {
                                        contextTypes.add(paramFqn);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return contextTypes;
    }
}
