package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstraintClauses;
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Expands entry points whose string parameters flow into switch/map/`valueOf` mappers
 * into concrete binding variants. Covers REST {@code @PathVariable}/{@code @RequestParam}
 * and messaging {@code @Payload} / message-body string parameters (JMS, Kafka, Rabbit, …).
 */
public final class EntryPointBindingExpander {

    private EntryPointBindingExpander() {
    }

    /**
     * Expands an entry point into concrete binding variants from expandable parameters:
     * string switch/if arms and boolean ternary conditions provable in source.
     */
    public static List<Map<String, String>> expandEntryPointBindings(
            EntryPoint entryPoint,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph) {
        List<Map<String, String>> pathVariants = expandPathVariableBindings(entryPoint, context, callGraph);
        List<Map<String, String>> booleanVariants = expandBooleanTernaryBindings(entryPoint, context, callGraph);
        if (pathVariants.isEmpty()) {
            return booleanVariants;
        }
        if (booleanVariants.isEmpty()) {
            return pathVariants;
        }
        List<Map<String, String>> combined = new ArrayList<>();
        for (Map<String, String> pathVariant : pathVariants) {
            for (Map<String, String> booleanVariant : booleanVariants) {
                Map<String, String> merged = new LinkedHashMap<>(pathVariant);
                merged.putAll(booleanVariant);
                combined.add(merged);
            }
        }
        return combined;
    }

    public static List<Map<String, String>> expandPathVariableBindings(
            EntryPoint entryPoint,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph) {
        if (entryPoint == null || entryPoint.getParameters() == null || entryPoint.getParameters().isEmpty()) {
            return List.of();
        }
        List<Map<String, String>> variants = new ArrayList<>();
        variants.add(new LinkedHashMap<>());
        for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
            if (!isExpandableBindingParameter(entryPoint, parameter)) {
                continue;
            }
            if (!isRequestParam(parameter)
                    && !EventCarrierPolicy.isMessagingPayloadParameter(entryPoint, parameter)
                    && isPlaceholderResolvedInPath(entryPoint, parameter.getName())) {
                continue;
            }
            List<Map<String, String>> expanded = new ArrayList<>();
            boolean anyCases = false;
            for (Map<String, String> base : variants) {
                List<String> cases = resolveBindingCasesForParameter(
                        entryPoint.getClassName() + "." + entryPoint.getMethodName(),
                        parameter.getName(),
                        context,
                        callGraph,
                        base);
                if (cases.isEmpty()) {
                    continue;
                }
                anyCases = true;
                for (String caseValue : cases) {
                    Map<String, String> merged = new LinkedHashMap<>(base);
                    merged.put(parameter.getName(), caseValue);
                    expanded.add(merged);
                }
            }
            if (!anyCases) {
                if (variants.stream().allMatch(Map::isEmpty)) {
                    return List.of();
                }
                continue;
            }
            if (expanded.isEmpty()) {
                continue;
            }
            variants = expanded;
        }
        return variants.stream().filter(map -> !map.isEmpty()).toList();
    }

    public static EntryPoint withResolvedPath(EntryPoint entryPoint, Map<String, String> bindings) {
        if (entryPoint == null || bindings == null || bindings.isEmpty()) {
            return entryPoint;
        }
        String name = entryPoint.getName();
        Map<String, String> metadata = entryPoint.getMetadata() == null
                ? new LinkedHashMap<>()
                : new LinkedHashMap<>(entryPoint.getMetadata());
        StringBuilder querySuffix = new StringBuilder();
        for (Map.Entry<String, String> binding : bindings.entrySet()) {
            String placeholder = "{" + binding.getKey() + "}";
            boolean pathPlaceholder = name != null && name.contains(placeholder);
            String path = metadata.get("path");
            if (!pathPlaceholder && path != null) {
                pathPlaceholder = path.contains(placeholder);
            }
            if (pathPlaceholder) {
                if (name != null) {
                    name = name.replace(placeholder, binding.getValue());
                }
                if (path != null) {
                    metadata.put("path", path.replace(placeholder, binding.getValue()));
                }
            } else if (isRequestParamParameter(entryPoint, binding.getKey())
                    || isMessagingPayloadBinding(entryPoint, binding.getKey())
                    || isBooleanBindingValue(binding.getValue())) {
                if (querySuffix.isEmpty()) {
                    querySuffix.append('?');
                } else {
                    querySuffix.append('&');
                }
                querySuffix.append(binding.getKey()).append('=').append(binding.getValue());
            }
        }
        if (!querySuffix.isEmpty()) {
            if (name != null) {
                name = name + querySuffix;
            }
            String path = metadata.get("path");
            if (path != null) {
                metadata.put("path", path + querySuffix);
            }
        }
        return EntryPoint.builder()
                .type(entryPoint.getType())
                .name(name)
                .className(entryPoint.getClassName())
                .methodName(entryPoint.getMethodName())
                .sourceFile(entryPoint.getSourceFile())
                .metadata(metadata)
                .parameters(entryPoint.getParameters())
                .build();
    }

    private static boolean isPlaceholderResolvedInPath(EntryPoint entryPoint, String paramName) {
        String placeholder = "{" + paramName + "}";
        String name = entryPoint.getName();
        if (name != null && !name.contains(placeholder)) {
            return true;
        }
        Map<String, String> metadata = entryPoint.getMetadata();
        if (metadata != null) {
            String path = metadata.get("path");
            if (path != null && !path.contains(placeholder)) {
                return true;
            }
        }
        return false;
    }

    public static List<Map<String, String>> expandBooleanTernaryBindings(
            EntryPoint entryPoint,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph) {
        if (entryPoint == null || entryPoint.getParameters() == null || entryPoint.getParameters().isEmpty()) {
            return List.of();
        }
        for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
            if (!isBooleanType(parameter.getType())) {
                continue;
            }
            if (hasBooleanTernaryForParameter(
                    entryPoint.getClassName() + "." + entryPoint.getMethodName(),
                    parameter.getName(),
                    context,
                    callGraph)) {
                return List.of(
                        Map.of(parameter.getName(), "true"),
                        Map.of(parameter.getName(), "false"));
            }
        }
        return List.of();
    }

    private static boolean isBooleanType(String type) {
        return "boolean".equals(type) || "Boolean".equals(type);
    }

    private static boolean isBooleanBindingValue(String value) {
        return "true".equals(value) || "false".equals(value);
    }

    private static boolean hasBooleanTernaryForParameter(
            String startMethod,
            String paramName,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph) {
        ArrayDeque<String> queue = new ArrayDeque<>();
        Set<String> visited = new HashSet<>();
        queue.add(startMethod);
        while (!queue.isEmpty()) {
            String methodFqn = queue.poll();
            if (!visited.add(methodFqn)) {
                continue;
            }
            if (visited.size() > 12) {
                break;
            }
            if (containsBooleanTernaryCondition(methodFqn, paramName, context)) {
                return true;
            }
            for (CallEdge edge : edgesForMethod(methodFqn, callGraph, context)) {
                if (edge.getTargetMethod() != null) {
                    queue.add(edge.getTargetMethod());
                }
            }
        }
        return false;
    }

    static boolean containsBooleanTernaryCondition(String methodFqn, String paramName, CodebaseContext context) {
        MethodDeclaration method = findMethodDeclaration(methodFqn, context);
        if (method == null || method.getBody() == null) {
            return false;
        }
        if (!hasParameter(method, paramName)) {
            return false;
        }
        final boolean[] found = {false};
        method.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(ConditionalExpression node) {
                if (isBooleanConditionForParameter(node.getExpression(), paramName)) {
                    found[0] = true;
                    return false;
                }
                return super.visit(node);
            }

            @Override
            public boolean visit(MethodInvocation node) {
                for (Object argObj : node.arguments()) {
                    if (argObj instanceof ConditionalExpression cond
                            && isBooleanConditionForParameter(cond.getExpression(), paramName)) {
                        found[0] = true;
                        return false;
                    }
                }
                return super.visit(node);
            }
        });
        return found[0];
    }

    private static boolean isBooleanConditionForParameter(Expression condition, String paramName) {
        if (condition instanceof SimpleName simpleName) {
            return paramName.equals(simpleName.getIdentifier());
        }
        if (condition instanceof PrefixExpression prefixExpression
                && prefixExpression.getOperator() == PrefixExpression.Operator.NOT
                && prefixExpression.getOperand() instanceof SimpleName simpleName) {
            return paramName.equals(simpleName.getIdentifier());
        }
        return false;
    }

    private static boolean isMessagingPayloadBinding(EntryPoint entryPoint, String paramName) {
        if (entryPoint == null || entryPoint.getParameters() == null || paramName == null) {
            return false;
        }
        for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
            if (paramName.equals(parameter.getName())
                    && EventCarrierPolicy.isMessagingPayloadParameter(entryPoint, parameter)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isExpandableBindingParameter(EntryPoint entryPoint, EntryPoint.Parameter parameter) {
        return isPathOrQueryVariable(parameter)
                || EventCarrierPolicy.isMessagingPayloadParameter(entryPoint, parameter);
    }

    private static boolean isPathOrQueryVariable(EntryPoint.Parameter parameter) {
        return hasSimpleAnnotation(parameter, "PathVariable") || hasSimpleAnnotation(parameter, "RequestParam");
    }

    private static boolean isRequestParam(EntryPoint.Parameter parameter) {
        return hasSimpleAnnotation(parameter, "RequestParam");
    }

    private static boolean hasSimpleAnnotation(EntryPoint.Parameter parameter, String simpleName) {
        if (parameter.getAnnotations() == null || simpleName == null) {
            return false;
        }
        return parameter.getAnnotations().stream().anyMatch(a ->
                simpleName.equals(a) || (a != null && a.endsWith("." + simpleName)));
    }

    private static boolean isRequestParamParameter(EntryPoint entryPoint, String paramName) {
        if (entryPoint == null || entryPoint.getParameters() == null) {
            return false;
        }
        for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
            if (paramName.equals(parameter.getName()) && isRequestParam(parameter)) {
                return true;
            }
        }
        return false;
    }

    private static List<String> resolveBindingCasesForParameter(
            String startMethod,
            String paramName,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph,
            Map<String, String> partialBindings) {
        List<String> stringCases = resolveCasesWithParameterAliases(
                startMethod, paramName, context, callGraph, partialBindings,
                EntryPointBindingExpander::extractStringSwitchCasesForBindings);
        if (!stringCases.isEmpty()) {
            return stringCases;
        }
        List<String> mapCases = resolveCasesWithParameterAliases(
                startMethod, paramName, context, callGraph, partialBindings,
                EntryPointBindingExpander::extractStaticMapLookupCasesForBindings);
        if (!mapCases.isEmpty()) {
            return mapCases;
        }
        return resolveCasesWithParameterAliases(
                startMethod, paramName, context, callGraph, partialBindings,
                EntryPointBindingExpander::extractEnumValueOfCasesForBindings);
    }

    @FunctionalInterface
    private interface ParameterCaseExtractor {
        List<String> extract(
                String methodFqn,
                String paramName,
                CodebaseContext context,
                Map<String, String> partialBindings);
    }

    private static List<String> extractStringSwitchCasesForBindings(
            String methodFqn,
            String paramName,
            CodebaseContext context,
            Map<String, String> partialBindings) {
        return extractStringSwitchCases(methodFqn, paramName, context);
    }

    private static List<String> extractEnumValueOfCasesForBindings(
            String methodFqn,
            String paramName,
            CodebaseContext context,
            Map<String, String> partialBindings) {
        return extractEnumValueOfCases(methodFqn, paramName, context, partialBindings);
    }

    private static List<String> extractStaticMapLookupCasesForBindings(
            String methodFqn,
            String paramName,
            CodebaseContext context,
            Map<String, String> partialBindings) {
        return extractStaticMapLookupCases(methodFqn, paramName, context);
    }

    private record ParameterAliasState(String methodFqn, String paramName) {
    }

    private static List<String> resolveCasesWithParameterAliases(
            String startMethod,
            String entryParamName,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph,
            Map<String, String> partialBindings,
            ParameterCaseExtractor extractor) {
        ArrayDeque<ParameterAliasState> queue = new ArrayDeque<>();
        Set<String> visited = new HashSet<>();
        queue.add(new ParameterAliasState(startMethod, entryParamName));
        while (!queue.isEmpty()) {
            ParameterAliasState state = queue.poll();
            String visitKey = state.methodFqn() + "#" + state.paramName();
            if (!visited.add(visitKey)) {
                continue;
            }
            if (visited.size() > 24) {
                break;
            }
            MethodDeclaration method = findMethodDeclaration(state.methodFqn(), context);
            List<String> bindingNames = bindingNamesForParameter(method, state.paramName());
            for (String bindingName : bindingNames) {
                List<String> localCases = extractor.extract(
                        state.methodFqn(), bindingName, context, partialBindings);
                if (!localCases.isEmpty()) {
                    return localCases;
                }
            }
            enqueueParameterAliasSuccessors(state, context, callGraph, partialBindings, queue);
        }
        return List.of();
    }

    /**
     * Call-graph keys are declaring-type FQNs. When analysis starts from a subclass or interface
     * method that is not redeclared on that type, look up edges on superclass / implementation
     * method FQNs so parameter aliases can reach nested {@code Enum.valueOf} sites.
     */
    private static List<CallEdge> edgesForMethod(
            String methodFqn,
            Map<String, List<CallEdge>> callGraph,
            CodebaseContext context) {
        if (methodFqn == null || callGraph == null) {
            return List.of();
        }
        List<CallEdge> direct = callGraph.get(methodFqn);
        if (direct != null && !direct.isEmpty()) {
            return direct;
        }
        if (context == null || !methodFqn.contains(".")) {
            return List.of();
        }
        String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
        String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(className);
        Set<String> visited = new HashSet<>();
        while (typeDeclaration != null) {
            String typeFqn = context.getFqn(typeDeclaration);
            if (typeFqn == null || !visited.add(typeFqn)) {
                break;
            }
            String superclass = context.getSuperclassFqn(typeDeclaration);
            if (superclass == null || superclass.isBlank()) {
                break;
            }
            String superMethod = superclass + "." + methodName;
            List<CallEdge> inherited = callGraph.get(superMethod);
            if (inherited != null && !inherited.isEmpty()) {
                return inherited;
            }
            typeDeclaration = context.getTypeDeclaration(superclass);
        }
        List<CallEdge> fromImplementations = edgesFromInterfaceImplementations(
                className, methodName, callGraph, context);
        if (!fromImplementations.isEmpty()) {
            return fromImplementations;
        }
        return List.of();
    }

    /**
     * When the entry/method FQN is an interface with no graph edges, use concrete implementors
     * (same idea as {@code CallGraphPathFinder}). Limited to interfaces — applying this to every
     * class with empty edges fans out across deep subclass trees and can explode binding BFS.
     */
    private static List<CallEdge> edgesFromInterfaceImplementations(
            String typeFqn,
            String methodName,
            Map<String, List<CallEdge>> callGraph,
            CodebaseContext context) {
        if (typeFqn == null || methodName == null || context == null) {
            return List.of();
        }
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(typeFqn);
        if (typeDeclaration == null || !typeDeclaration.isInterface()) {
            return List.of();
        }
        List<String> implementations = context.getImplementations(typeFqn);
        if (implementations == null || implementations.isEmpty()) {
            return List.of();
        }
        // Fail closed when multiple implementors contribute edges — merging mixed valueOf sites
        // invents binding variants that no single controller path can reach.
        List<CallEdge> soleImplEdges = null;
        for (String impl : implementations) {
            if (impl == null || impl.isBlank() || impl.equals(typeFqn)) {
                continue;
            }
            List<CallEdge> edges = callGraph.get(impl + "." + methodName);
            if (edges == null || edges.isEmpty()) {
                continue;
            }
            if (soleImplEdges != null) {
                return List.of();
            }
            soleImplEdges = edges;
        }
        return soleImplEdges == null ? List.of() : soleImplEdges;
    }

    private static List<String> bindingNamesForParameter(MethodDeclaration method, String paramName) {
        if (paramName == null || paramName.isBlank()) {
            return List.of();
        }
        LinkedHashSet<String> names = new LinkedHashSet<>();
        names.add(paramName);
        if (method != null) {
            names.addAll(collectIntraMethodParameterAliases(method, paramName));
        }
        return List.copyOf(names);
    }

    /**
     * Names assigned directly from a parameter within the same method, including transitive renames
     * ({@code String code = event}, {@code String normalized = code.trim()}).
     */
    static Set<String> collectIntraMethodParameterAliases(MethodDeclaration method, String paramName) {
        Set<String> aliases = new LinkedHashSet<>();
        if (method == null || method.getBody() == null || paramName == null || paramName.isBlank()) {
            return aliases;
        }
        Set<String> known = new LinkedHashSet<>();
        known.add(paramName);
        boolean growing = true;
        while (growing) {
            growing = false;
            int before = aliases.size();
            method.getBody().accept(new ASTVisitor() {
                @Override
                public boolean visit(VariableDeclarationFragment node) {
                    Expression initializer = node.getInitializer();
                    if (initializer != null && expressionReferencesKnownBinding(initializer, known)) {
                        aliases.add(node.getName().getIdentifier());
                    }
                    return super.visit(node);
                }

                @Override
                public boolean visit(Assignment node) {
                    if (node.getLeftHandSide() instanceof SimpleName left
                            && expressionReferencesKnownBinding(node.getRightHandSide(), known)) {
                        aliases.add(left.getIdentifier());
                    }
                    return super.visit(node);
                }
            });
            known.addAll(aliases);
            growing = aliases.size() > before;
        }
        return aliases;
    }

    private static boolean expressionReferencesKnownBinding(Expression expression, Set<String> knownNames) {
        if (expression instanceof SimpleName simpleName) {
            return knownNames.contains(simpleName.getIdentifier());
        }
        if (expression instanceof MethodInvocation invocation
                && invocation.arguments().isEmpty()
                && invocation.getExpression() instanceof SimpleName receiver) {
            String methodName = invocation.getName().getIdentifier();
            if (("toUpperCase".equals(methodName) || "toLowerCase".equals(methodName) || "trim".equals(methodName))
                    && knownNames.contains(receiver.getIdentifier())) {
                return true;
            }
        }
        return false;
    }

    private static void enqueueParameterAliasSuccessors(
            ParameterAliasState state,
            CodebaseContext context,
            Map<String, List<CallEdge>> callGraph,
            Map<String, String> partialBindings,
            ArrayDeque<ParameterAliasState> queue) {
        List<CallEdge> edges = edgesForMethod(state.methodFqn(), callGraph, context);
        if (edges.isEmpty()) {
            return;
        }
        MethodDeclaration callerMethod = findMethodDeclaration(state.methodFqn(), context);
        List<String> forwardedNames = bindingNamesForParameter(callerMethod, state.paramName());
        for (CallEdge edge : edges) {
            if (edge.getTargetMethod() == null
                    || !isEdgeCompatibleWithBindings(edge, partialBindings, state.methodFqn(), context)) {
                continue;
            }
            List<String> args = edge.getArguments();
            if (args == null || args.isEmpty()) {
                continue;
            }
            List<String> targetParamNames = getParameterNames(edge.getTargetMethod(), context);
            if (targetParamNames == null) {
                continue;
            }
            int limit = Math.min(args.size(), targetParamNames.size());
            for (int i = 0; i < limit; i++) {
                if (!argumentForwardsAnyParameter(args.get(i), forwardedNames)) {
                    continue;
                }
                MethodDeclaration targetMethod = findMethodDeclaration(edge.getTargetMethod(), context);
                if (targetMethod != null
                        && isBooleanReturnType(targetMethod)
                        && !isTrivialSingleArgStringHelper(targetMethod)) {
                    continue;
                }
                queue.add(new ParameterAliasState(edge.getTargetMethod(), targetParamNames.get(i)));
            }
        }
    }

    private static boolean isEdgeCompatibleWithBindings(
            CallEdge edge,
            Map<String, String> partialBindings,
            String callerMethodFqn,
            CodebaseContext context) {
        if (partialBindings == null || partialBindings.isEmpty()) {
            return true;
        }
        String constraint = edge.getConstraint();
        if (constraint == null || constraint.isBlank()) {
            return true;
        }
        constraint = inlineBooleanPredicateConstraint(constraint, callerMethodFqn, context);
        return BooleanConstraintEvaluator.isCompatibleWithBindings(constraint, partialBindings);
    }

    private static boolean argumentForwardsAnyParameter(String argument, List<String> paramNames) {
        if (argument == null || paramNames == null || paramNames.isEmpty()) {
            return false;
        }
        for (String paramName : paramNames) {
            if (argumentForwardsParameter(argument, paramName)) {
                return true;
            }
        }
        return false;
    }

    private static boolean argumentForwardsParameter(String argument, String paramName) {
        if (argument == null || paramName == null) {
            return false;
        }
        String trimmed = argument.trim();
        if (paramName.equals(trimmed)) {
            return true;
        }
        return ("this." + paramName).equals(trimmed);
    }

    private static List<String> getParameterNames(String methodFqn, CodebaseContext context) {
        MethodDeclaration method = findMethodDeclaration(methodFqn, context);
        if (method == null) {
            return null;
        }
        List<String> names = new ArrayList<>();
        for (Object paramObj : method.parameters()) {
            if (paramObj instanceof SingleVariableDeclaration param) {
                names.add(param.getName().getIdentifier());
            }
        }
        return names;
    }

    static List<String> extractEnumValueOfCases(
            String methodFqn,
            String paramName,
            CodebaseContext context,
            Map<String, String> partialBindings) {
        MethodDeclaration method = findMethodDeclaration(methodFqn, context);
        if (method == null || method.getBody() == null || context == null) {
            return List.of();
        }
        if (!tracksBindingName(method, paramName)) {
            return List.of();
        }
        LinkedHashSet<String> enumTypes = new LinkedHashSet<>();
        method.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if (!"valueOf".equals(node.getName().getIdentifier()) || node.arguments().size() != 1) {
                    return super.visit(node);
                }
                Expression argument = (Expression) node.arguments().get(0);
                if (!argumentReferencesParameter(argument, paramName, method, context)) {
                    return super.visit(node);
                }
                if (!isValueOfSiteCompatibleWithBindings(node, partialBindings, context)) {
                    return super.visit(node);
                }
                String enumType = resolveValueOfEnumType(node, context);
                if (enumType != null) {
                    enumTypes.add(enumType);
                }
                return super.visit(node);
            }
        });
        if (enumTypes.size() != 1) {
            return List.of();
        }
        List<String> constants = context.getEnumValues(enumTypes.iterator().next());
        if (constants == null || constants.isEmpty()) {
            return List.of();
        }
        LinkedHashSet<String> pathValues = new LinkedHashSet<>();
        for (String constant : constants) {
            if (constant == null || constant.isBlank()) {
                continue;
            }
            int lastDot = constant.lastIndexOf('.');
            pathValues.add(lastDot >= 0 ? constant.substring(lastDot + 1) : constant);
        }
        return List.copyOf(pathValues);
    }

    static List<String> extractStaticMapLookupCases(String methodFqn, String paramName, CodebaseContext context) {
        MethodDeclaration method = findMethodDeclaration(methodFqn, context);
        if (method == null || method.getBody() == null) {
            return List.of();
        }
        if (!tracksBindingName(method, paramName)) {
            return List.of();
        }
        LinkedHashSet<String> keys = new LinkedHashSet<>();
        method.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                String lookupMethod = node.getName().getIdentifier();
                if (!"get".equals(lookupMethod) && !"getOrDefault".equals(lookupMethod)) {
                    return super.visit(node);
                }
                if (node.arguments().isEmpty()) {
                    return super.visit(node);
                }
                Expression argument = (Expression) node.arguments().get(0);
                if (!argumentReferencesParameter(argument, paramName, method, context)) {
                    return super.visit(node);
                }
                Map<String, String> entries = resolveCompileTimeMapEntries(node.getExpression(), method, context);
                if (entries != null) {
                    keys.addAll(entries.keySet());
                }
                return super.visit(node);
            }
        });
        return List.copyOf(keys);
    }

    private static Map<String, String> resolveCompileTimeMapEntries(
            Expression receiver,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        CompilationUnit compilationUnit = null;
        TypeDeclaration defaultDeclaringType = null;
        if (ownerMethod != null) {
            if (ownerMethod.getRoot() instanceof CompilationUnit cu) {
                compilationUnit = cu;
            }
            defaultDeclaringType = AstUtils.findEnclosingType(ownerMethod);
        }
        return resolveCompileTimeMapEntries(
                receiver, defaultDeclaringType, compilationUnit, context);
    }

    private static Map<String, String> resolveCompileTimeMapEntries(
            Expression receiver,
            TypeDeclaration defaultDeclaringType,
            CompilationUnit compilationUnit,
            CodebaseContext context) {
        if (receiver == null) {
            return null;
        }
        TypeDeclaration literalDeclaringType = defaultDeclaringType != null
                ? defaultDeclaringType
                : AstUtils.findEnclosingType(receiver);
        Map<String, String> direct = extractMapOfLiteralEntries(
                receiver, literalDeclaringType, context);
        if (direct != null) {
            return direct;
        }
        if (receiver instanceof MethodInvocation methodCall) {
            Map<String, String> factory = extractCompileTimeMapFromStaticMethodCall(
                    methodCall, literalDeclaringType, compilationUnit, context);
            if (factory != null) {
                return factory;
            }
        }
        String fieldName = null;
        TypeDeclaration declaringType = null;
        if (receiver instanceof FieldAccess fieldAccess) {
            fieldName = fieldAccess.getName().getIdentifier();
            org.eclipse.jdt.core.dom.IVariableBinding fieldBinding = fieldAccess.resolveFieldBinding();
            if (fieldBinding != null && fieldBinding.isField()) {
                org.eclipse.jdt.core.dom.ITypeBinding declaringClass = fieldBinding.getDeclaringClass();
                declaringType = resolveFieldDeclaringType(declaringClass, compilationUnit, context);
            }
            if (declaringType == null) {
                Expression qualifier = fieldAccess.getExpression();
                if (qualifier instanceof SimpleName typeName) {
                    declaringType = context.getTypeDeclaration(typeName.getIdentifier(), compilationUnit);
                    if (declaringType == null) {
                        declaringType = context.getTypeDeclaration(typeName.getIdentifier());
                    }
                } else if (qualifier instanceof QualifiedName qualifiedName) {
                    declaringType = context.getTypeDeclaration(qualifiedName.getFullyQualifiedName());
                }
            }
        } else if (receiver instanceof SimpleName simpleName) {
            fieldName = simpleName.getIdentifier();
            org.eclipse.jdt.core.dom.IBinding binding = simpleName.resolveBinding();
            if (binding instanceof org.eclipse.jdt.core.dom.IVariableBinding variableBinding
                    && variableBinding.isField()) {
                org.eclipse.jdt.core.dom.ITypeBinding declaringClass = variableBinding.getDeclaringClass();
                declaringType = resolveFieldDeclaringType(declaringClass, compilationUnit, context);
            }
            if (declaringType == null && compilationUnit != null) {
                declaringType = context.resolveStaticImport(fieldName, compilationUnit);
            }
            if (declaringType == null) {
                declaringType = AstUtils.findEnclosingType(receiver);
            }
        } else if (receiver instanceof QualifiedName qualifiedName) {
            fieldName = qualifiedName.getName().getIdentifier();
            Name qualifier = qualifiedName.getQualifier();
            if (qualifier instanceof SimpleName typeName) {
                declaringType = context.getTypeDeclaration(typeName.getIdentifier(), compilationUnit);
                if (declaringType == null) {
                    declaringType = context.getTypeDeclaration(typeName.getIdentifier());
                }
            } else if (qualifier instanceof QualifiedName typeQualifiedName) {
                declaringType = context.getTypeDeclaration(typeQualifiedName.getFullyQualifiedName());
            }
        }
        if (fieldName == null || declaringType == null) {
            return null;
        }
        if (!isStaticMapFieldReceiver(receiver)) {
            return null;
        }
        return extractStaticFieldMapInitializer(declaringType, fieldName, context);
    }

    private static TypeDeclaration resolveFieldDeclaringType(
            org.eclipse.jdt.core.dom.ITypeBinding declaringClass,
            CompilationUnit compilationUnit,
            CodebaseContext context) {
        if (declaringClass == null) {
            return null;
        }
        org.eclipse.jdt.core.dom.ITypeBinding erased = declaringClass.getErasure();
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(erased.getQualifiedName());
        if (typeDeclaration != null) {
            return typeDeclaration;
        }
        if (compilationUnit != null) {
            typeDeclaration = context.getTypeDeclaration(erased.getName(), compilationUnit);
            if (typeDeclaration != null) {
                return typeDeclaration;
            }
        }
        return context.getTypeDeclaration(erased.getName());
    }

    private static boolean isStaticMapFieldReceiver(Expression receiver) {
        if (receiver instanceof FieldAccess fieldAccess) {
            org.eclipse.jdt.core.dom.IVariableBinding fieldBinding = fieldAccess.resolveFieldBinding();
            return fieldBinding == null || Modifier.isStatic(fieldBinding.getModifiers());
        }
        if (receiver instanceof SimpleName simpleName) {
            org.eclipse.jdt.core.dom.IBinding binding = simpleName.resolveBinding();
            if (binding instanceof org.eclipse.jdt.core.dom.IVariableBinding variableBinding
                    && variableBinding.isField()) {
                return Modifier.isStatic(variableBinding.getModifiers());
            }
            return true;
        }
        if (receiver instanceof QualifiedName) {
            return true;
        }
        return false;
    }

    private static Map<String, String> extractStaticFieldMapInitializer(
            TypeDeclaration typeDecl,
            String fieldName,
            CodebaseContext context) {
        final Map<String, String>[] found = new Map[1];
        context.forEachSuperclass(typeDecl, current -> {
            if (found[0] != null) {
                return;
            }
            Map<String, String> entries = extractStaticFieldMapInitializerOnType(current, fieldName, context);
            if (entries != null) {
                found[0] = entries;
            }
        });
        if (found[0] != null) {
            return found[0];
        }
        return extractStaticFieldMapInitializerFromInterfaces(typeDecl, fieldName, context, new LinkedHashSet<>());
    }

    private static Map<String, String> extractStaticFieldMapInitializerFromInterfaces(
            TypeDeclaration typeDecl,
            String fieldName,
            CodebaseContext context,
            Set<String> visited) {
        if (typeDecl == null) {
            return null;
        }
        String typeFqn = context.getFqn(typeDecl);
        if (typeFqn != null && !visited.add(typeFqn)) {
            return null;
        }
        CompilationUnit compilationUnit = typeDecl.getRoot() instanceof CompilationUnit cu ? cu : null;
        for (Object interfaceTypeObj : typeDecl.superInterfaceTypes()) {
            if (!(interfaceTypeObj instanceof Type interfaceType)) {
                continue;
            }
            String interfaceName = interfaceType.toString();
            TypeDeclaration interfaceDecl = context.getTypeDeclaration(interfaceName, compilationUnit);
            if (interfaceDecl == null) {
                interfaceDecl = context.getTypeDeclaration(interfaceName);
            }
            if (interfaceDecl == null) {
                continue;
            }
            Map<String, String> entries = extractStaticFieldMapInitializerOnType(interfaceDecl, fieldName, context);
            if (entries != null) {
                return entries;
            }
            Map<String, String> inherited = extractStaticFieldMapInitializerFromInterfaces(
                    interfaceDecl, fieldName, context, visited);
            if (inherited != null) {
                return inherited;
            }
        }
        return null;
    }

    private static Map<String, String> extractStaticFieldMapInitializerOnType(
            TypeDeclaration typeDecl,
            String fieldName,
            CodebaseContext context) {
        for (FieldDeclaration field : typeDecl.getFields()) {
            if (!isStaticField(field)) {
                continue;
            }
            for (Object fragmentObj : field.fragments()) {
                if (fragmentObj instanceof VariableDeclarationFragment fragment
                        && fieldName.equals(fragment.getName().getIdentifier())) {
                    Expression initializer = fragment.getInitializer();
                    if (initializer != null) {
                        Map<String, String> direct = extractMapOfLiteralEntries(initializer, typeDecl, context);
                        if (direct != null) {
                            return direct;
                        }
                        Map<String, String> doubleBrace = extractDoubleBraceMapPutEntries(initializer, typeDecl, context);
                        if (doubleBrace != null) {
                            return doubleBrace;
                        }
                        CompilationUnit compilationUnit = typeDecl.getRoot() instanceof CompilationUnit cu ? cu : null;
                        Map<String, String> factory = extractCompileTimeMapFromStaticMethodCall(
                                initializer, typeDecl, compilationUnit, context);
                        if (factory != null) {
                            return factory;
                        }
                    }
                    return extractStaticBlockMapPutEntries(typeDecl, fieldName, context);
                }
            }
        }
        return null;
    }

    private static boolean isStaticField(FieldDeclaration field) {
        ASTNode parent = field.getParent();
        if (parent instanceof TypeDeclaration typeDeclaration && typeDeclaration.isInterface()) {
            return true;
        }
        for (Object modifierObj : field.modifiers()) {
            if (modifierObj instanceof Modifier modifier && modifier.isStatic()) {
                return true;
            }
        }
        return false;
    }

    private static Map<String, String> extractStaticBlockMapPutEntries(
            TypeDeclaration typeDecl,
            String fieldName,
            CodebaseContext context) {
        LinkedHashMap<String, String> entries = new LinkedHashMap<>();
        boolean[] foundPut = {false};
        boolean[] invalid = {false};
        for (Object bodyDeclObj : typeDecl.bodyDeclarations()) {
            if (!(bodyDeclObj instanceof Initializer initializer) || !isStaticInitializer(initializer)) {
                continue;
            }
            Block block = initializer.getBody();
            if (block == null) {
                continue;
            }
            LinkedHashSet<String> mapBuilderLocals = new LinkedHashSet<>();
            LinkedHashSet<String> linkedLocals = new LinkedHashSet<>();
            block.accept(new ASTVisitor() {
                @Override
                public boolean visit(VariableDeclarationStatement node) {
                    for (Object fragmentObj : node.fragments()) {
                        if (!(fragmentObj instanceof VariableDeclarationFragment fragment)) {
                            continue;
                        }
                        if (isPlainHashMapCreation(fragment.getInitializer())) {
                            mapBuilderLocals.add(fragment.getName().getIdentifier());
                        }
                    }
                    return super.visit(node);
                }

                @Override
                public boolean visit(Assignment node) {
                    if (invalid[0] || node.getOperator() != Assignment.Operator.ASSIGN) {
                        return super.visit(node);
                    }
                    String assignedField = assignmentTargetName(node.getLeftHandSide());
                    if (!fieldName.equals(assignedField)) {
                        return super.visit(node);
                    }
                    Expression right = node.getRightHandSide();
                    Map<String, String> direct = extractMapOfLiteralEntries(right, typeDecl, context);
                    if (direct != null) {
                        entries.putAll(direct);
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    Map<String, String> doubleBrace = extractDoubleBraceMapPutEntries(right, typeDecl, context);
                    if (doubleBrace != null) {
                        entries.putAll(doubleBrace);
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    CompilationUnit compilationUnit = block.getRoot() instanceof CompilationUnit cu ? cu : null;
                    Map<String, String> factory = extractCompileTimeMapFromStaticMethodCall(
                            right, typeDecl, compilationUnit, context);
                    if (factory != null) {
                        entries.putAll(factory);
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    if (right instanceof SimpleName localName
                            && mapBuilderLocals.contains(localName.getIdentifier())) {
                        linkedLocals.add(localName.getIdentifier());
                    }
                    return super.visit(node);
                }
            });
            LinkedHashSet<String> putReceivers = new LinkedHashSet<>();
            putReceivers.add(fieldName);
            putReceivers.addAll(linkedLocals);
            block.accept(new ASTVisitor() {
                @Override
                public boolean visit(MethodInvocation node) {
                    if (invalid[0] || !mapReceiverReferencesAnyField(node.getExpression(), putReceivers)) {
                        return super.visit(node);
                    }
                    String methodName = node.getName().getIdentifier();
                    if ("put".equals(methodName) && node.arguments().size() == 2) {
                        String key = resolveCompileTimeStringKey(
                                (Expression) node.arguments().get(0), typeDecl, context);
                        if (key == null) {
                            invalid[0] = true;
                            return super.visit(node);
                        }
                        entries.put(key, "*");
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    if ("putAll".equals(methodName) && node.arguments().size() == 1) {
                        CompilationUnit compilationUnit = block.getRoot() instanceof CompilationUnit cu
                                ? cu
                                : null;
                        Map<String, String> merged = resolveCompileTimeMapEntries(
                                (Expression) node.arguments().get(0), typeDecl, compilationUnit, context);
                        if (merged == null || merged.isEmpty()) {
                            invalid[0] = true;
                            return super.visit(node);
                        }
                        entries.putAll(merged);
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    return super.visit(node);
                }
            });
        }
        if (invalid[0] || !foundPut[0] || entries.isEmpty()) {
            return null;
        }
        return entries;
    }

    private static String assignmentTargetName(Expression expression) {
        if (expression instanceof SimpleName simpleName) {
            return simpleName.getIdentifier();
        }
        if (expression instanceof FieldAccess fieldAccess) {
            return fieldAccess.getName().getIdentifier();
        }
        return null;
    }

    private static boolean isPlainHashMapCreation(Expression expression) {
        if (!(expression instanceof ClassInstanceCreation creation)) {
            return false;
        }
        if (creation.getAnonymousClassDeclaration() != null) {
            return false;
        }
        org.eclipse.jdt.core.dom.ITypeBinding typeBinding = creation.resolveTypeBinding();
        if (typeBinding != null) {
            String qualifiedName = typeBinding.getErasure().getQualifiedName();
            return qualifiedName.endsWith("HashMap") || qualifiedName.endsWith("LinkedHashMap");
        }
        String typeName = creation.getType().toString();
        return typeName.contains("HashMap") || typeName.contains("LinkedHashMap");
    }

    private static boolean mapReceiverReferencesAnyField(Expression receiver, Set<String> fieldNames) {
        if (receiver == null) {
            return false;
        }
        if (receiver instanceof SimpleName simpleName) {
            return fieldNames.contains(simpleName.getIdentifier());
        }
        if (receiver instanceof FieldAccess fieldAccess) {
            return fieldNames.contains(fieldAccess.getName().getIdentifier());
        }
        if (receiver instanceof QualifiedName qualifiedName) {
            return fieldNames.contains(qualifiedName.getName().getIdentifier());
        }
        return false;
    }

    private static Map<String, String> extractDoubleBraceMapPutEntries(
            Expression initializer,
            TypeDeclaration declaringType,
            CodebaseContext context) {
        if (!(initializer instanceof ClassInstanceCreation creation)) {
            return null;
        }
        AnonymousClassDeclaration anonymous = creation.getAnonymousClassDeclaration();
        if (anonymous == null) {
            return null;
        }
        LinkedHashMap<String, String> entries = new LinkedHashMap<>();
        boolean[] foundPut = {false};
        boolean[] invalid = {false};
        for (Object bodyDeclObj : anonymous.bodyDeclarations()) {
            if (!(bodyDeclObj instanceof Initializer instanceInitializer)
                    || isStaticInitializer(instanceInitializer)) {
                continue;
            }
            Block block = instanceInitializer.getBody();
            if (block == null) {
                continue;
            }
            block.accept(new ASTVisitor() {
                @Override
                public boolean visit(MethodInvocation node) {
                    if (invalid[0]) {
                        return super.visit(node);
                    }
                    Expression receiver = node.getExpression();
                    if (receiver != null && !(receiver instanceof ThisExpression)) {
                        return super.visit(node);
                    }
                    String methodName = node.getName().getIdentifier();
                    if ("put".equals(methodName) && node.arguments().size() == 2) {
                        String key = resolveCompileTimeStringKey(
                                (Expression) node.arguments().get(0), declaringType, context);
                        if (key == null) {
                            invalid[0] = true;
                            return super.visit(node);
                        }
                        entries.put(key, "*");
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    if ("putAll".equals(methodName) && node.arguments().size() == 1) {
                        CompilationUnit compilationUnit = block.getRoot() instanceof CompilationUnit cu
                                ? cu
                                : null;
                        Map<String, String> merged = resolveCompileTimeMapEntries(
                                (Expression) node.arguments().get(0), declaringType, compilationUnit, context);
                        if (merged == null || merged.isEmpty()) {
                            invalid[0] = true;
                            return super.visit(node);
                        }
                        entries.putAll(merged);
                        foundPut[0] = true;
                        return super.visit(node);
                    }
                    return super.visit(node);
                }
            });
        }
        if (invalid[0] || !foundPut[0] || entries.isEmpty()) {
            return null;
        }
        return entries;
    }

    private static boolean isStaticInitializer(Initializer initializer) {
        for (Object modifierObj : initializer.modifiers()) {
            if (modifierObj instanceof Modifier modifier && modifier.isStatic()) {
                return true;
            }
        }
        return false;
    }

    private static Map<String, String> extractCompileTimeMapFromStaticMethodCall(
            Expression expression,
            TypeDeclaration defaultDeclaringType,
            CompilationUnit compilationUnit,
            CodebaseContext context) {
        return extractCompileTimeMapFromStaticMethodCall(
                expression, defaultDeclaringType, compilationUnit, context, new LinkedHashSet<>());
    }

    private static Map<String, String> extractCompileTimeMapFromStaticMethodCall(
            Expression expression,
            TypeDeclaration defaultDeclaringType,
            CompilationUnit compilationUnit,
            CodebaseContext context,
            Set<String> visitingMethods) {
        if (!(expression instanceof MethodInvocation invocation)) {
            return null;
        }
        org.eclipse.jdt.core.dom.IMethodBinding binding = invocation.resolveMethodBinding();
        String methodName = invocation.getName().getIdentifier();
        Expression receiver = invocation.getExpression();
        TypeDeclaration declaringType = null;
        if (binding != null) {
            if (!Modifier.isStatic(binding.getModifiers())) {
                return null;
            }
            if (binding.getDeclaringClass() != null) {
                declaringType = context.getTypeDeclaration(binding.getDeclaringClass().getQualifiedName());
            }
        }
        if (declaringType == null && receiver != null) {
            declaringType = resolveHelperDeclaringType(receiver, compilationUnit, context);
        }
        if (declaringType == null) {
            declaringType = defaultDeclaringType;
        }
        if (declaringType == null) {
            return null;
        }
        MethodDeclaration method = context.findMethodDeclaration(declaringType, methodName, true);
        if (method == null || method.getBody() == null || !isStaticMethod(method)) {
            return null;
        }
        String methodKey = binding != null && binding.getKey() != null
                ? binding.getKey()
                : context.getFqn(declaringType) + "#" + methodName;
        if (!visitingMethods.add(methodKey)) {
            return null;
        }
        Expression returnExpression = extractSingleReturnExpression(method.getBody());
        if (returnExpression == null) {
            return null;
        }
        Map<String, String> direct = extractMapOfLiteralEntries(returnExpression, declaringType, context);
        if (direct != null) {
            return direct;
        }
        Map<String, String> doubleBrace = extractDoubleBraceMapPutEntries(returnExpression, declaringType, context);
        if (doubleBrace != null) {
            return doubleBrace;
        }
        if (returnExpression instanceof MethodInvocation) {
            return extractCompileTimeMapFromStaticMethodCall(
                    returnExpression, declaringType, compilationUnit, context, visitingMethods);
        }
        return resolveCompileTimeMapEntries(returnExpression, declaringType, compilationUnit, context);
    }

    private static boolean isStaticMethod(MethodDeclaration method) {
        for (Object modifierObj : method.modifiers()) {
            if (modifierObj instanceof Modifier modifier && modifier.isStatic()) {
                return true;
            }
        }
        return false;
    }

    private static Expression extractSingleReturnExpression(Block body) {
        Expression[] found = {null};
        boolean[] multiple = {false};
        body.accept(new ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement node) {
                if (found[0] != null) {
                    multiple[0] = true;
                } else {
                    found[0] = node.getExpression();
                }
                return super.visit(node);
            }
        });
        return multiple[0] ? null : found[0];
    }

    private static Map<String, String> extractMapOfLiteralEntries(
            Expression expression,
            TypeDeclaration declaringType,
            CodebaseContext context) {
        if (!(expression instanceof MethodInvocation mapInvocation)) {
            return null;
        }
        String factoryMethod = mapInvocation.getName().getIdentifier();
        if (!isMapFactoryReceiver(mapInvocation.getExpression())) {
            return null;
        }
        if ("of".equals(factoryMethod)) {
            return extractAlternatingLiteralPairs(mapInvocation.arguments(), declaringType, context);
        }
        if ("ofEntries".equals(factoryMethod)) {
            return extractMapEntryCallArguments(mapInvocation.arguments(), declaringType, context);
        }
        return null;
    }

    private static Map<String, String> extractMapEntryCallArguments(
            List<?> arguments,
            TypeDeclaration declaringType,
            CodebaseContext context) {
        Map<String, String> entries = new LinkedHashMap<>();
        for (Object argObj : arguments) {
            if (!(argObj instanceof MethodInvocation entryCall)) {
                return null;
            }
            if (!"entry".equals(entryCall.getName().getIdentifier())) {
                return null;
            }
            if (!isMapFactoryReceiver(entryCall.getExpression())) {
                return null;
            }
            if (entryCall.arguments().size() != 2) {
                return null;
            }
            String key = resolveCompileTimeStringKey(
                    (Expression) entryCall.arguments().get(0), declaringType, context);
            if (key == null) {
                return null;
            }
            entries.put(key, "*");
        }
        return entries.isEmpty() ? null : entries;
    }

    private static boolean isMapFactoryReceiver(Expression receiver) {
        if (receiver instanceof SimpleName simpleName) {
            return "Map".equals(simpleName.getIdentifier());
        }
        if (receiver instanceof QualifiedName qualifiedName) {
            String fqn = qualifiedName.getFullyQualifiedName();
            return fqn.endsWith(".Map");
        }
        return false;
    }

    private static Map<String, String> extractAlternatingLiteralPairs(
            List<?> arguments,
            TypeDeclaration declaringType,
            CodebaseContext context) {
        if (arguments.size() < 2) {
            return null;
        }
        Map<String, String> entries = new LinkedHashMap<>();
        for (int i = 0; i + 1 < arguments.size(); i += 2) {
            String key = resolveCompileTimeStringKey((Expression) arguments.get(i), declaringType, context);
            if (key == null) {
                return null;
            }
            entries.put(key, "*");
        }
        return entries.isEmpty() ? null : entries;
    }

    private static String resolveCompileTimeStringKey(
            Expression expression,
            TypeDeclaration defaultDeclaringType,
            CodebaseContext context) {
        return resolveCompileTimeStringKey(expression, defaultDeclaringType, context, new LinkedHashSet<>());
    }

    private static String resolveCompileTimeStringKey(
            Expression expression,
            TypeDeclaration defaultDeclaringType,
            CodebaseContext context,
            Set<String> visiting) {
        if (expression instanceof StringLiteral stringLiteral) {
            return stringLiteral.getLiteralValue();
        }
        String fieldName = null;
        TypeDeclaration fieldDeclaringType = defaultDeclaringType;
        if (expression instanceof SimpleName simpleName) {
            fieldName = simpleName.getIdentifier();
            org.eclipse.jdt.core.dom.IBinding binding = simpleName.resolveBinding();
            if (binding instanceof org.eclipse.jdt.core.dom.IVariableBinding variableBinding
                    && variableBinding.isField()) {
                org.eclipse.jdt.core.dom.ITypeBinding declaringClass = variableBinding.getDeclaringClass();
                if (declaringClass != null && context != null) {
                    TypeDeclaration resolved = context.getTypeDeclaration(declaringClass.getQualifiedName());
                    if (resolved != null) {
                        fieldDeclaringType = resolved;
                    }
                }
            } else if (defaultDeclaringType != null && context != null) {
                ASTNode root = defaultDeclaringType.getRoot();
                if (root instanceof CompilationUnit compilationUnit) {
                    TypeDeclaration staticImport = context.resolveStaticImport(fieldName, compilationUnit);
                    if (staticImport != null) {
                        fieldDeclaringType = staticImport;
                    }
                }
            }
        } else if (expression instanceof FieldAccess fieldAccess) {
            fieldName = fieldAccess.getName().getIdentifier();
            org.eclipse.jdt.core.dom.IVariableBinding fieldBinding = fieldAccess.resolveFieldBinding();
            if (fieldBinding != null && fieldBinding.isField()) {
                org.eclipse.jdt.core.dom.ITypeBinding declaringClass = fieldBinding.getDeclaringClass();
                if (declaringClass != null && context != null) {
                    TypeDeclaration resolved = context.getTypeDeclaration(declaringClass.getQualifiedName());
                    if (resolved != null) {
                        fieldDeclaringType = resolved;
                    }
                }
            } else {
                TypeDeclaration resolved = resolveTypeForFieldQualifier(
                        fieldAccess.getExpression(), defaultDeclaringType, context);
                if (resolved != null) {
                    fieldDeclaringType = resolved;
                }
            }
        } else if (expression instanceof QualifiedName qualifiedName) {
            fieldName = qualifiedName.getName().getIdentifier();
            TypeDeclaration resolved = resolveTypeForFieldQualifier(
                    qualifiedName.getQualifier(), defaultDeclaringType, context);
            if (resolved != null) {
                fieldDeclaringType = resolved;
            }
        }
        if (fieldName == null) {
            return null;
        }
        return resolveStaticFinalStringLiteralField(fieldDeclaringType, fieldName, context, visiting);
    }

    private static TypeDeclaration resolveTypeForFieldQualifier(
            Expression qualifier,
            TypeDeclaration defaultDeclaringType,
            CodebaseContext context) {
        if (context == null) {
            return null;
        }
        if (qualifier instanceof SimpleName typeName) {
            if (defaultDeclaringType != null) {
                ASTNode root = defaultDeclaringType.getRoot();
                if (root instanceof CompilationUnit compilationUnit) {
                    TypeDeclaration resolved = context.getTypeDeclaration(typeName.getIdentifier(), compilationUnit);
                    if (resolved != null) {
                        return resolved;
                    }
                }
            }
            return context.getTypeDeclaration(typeName.getIdentifier());
        }
        if (qualifier instanceof QualifiedName qualifiedName) {
            return context.getTypeDeclaration(qualifiedName.getFullyQualifiedName());
        }
        return null;
    }

    private static String resolveStaticFinalStringLiteralField(
            TypeDeclaration typeDecl,
            String fieldName,
            CodebaseContext context,
            Set<String> visiting) {
        if (typeDecl == null || context == null) {
            return null;
        }
        String visitKey = context.getFqn(typeDecl) + "#" + fieldName;
        if (visiting.contains(visitKey)) {
            return null;
        }
        visiting.add(visitKey);
        for (FieldDeclaration field : typeDecl.getFields()) {
            if (!isStaticField(field)) {
                continue;
            }
            for (Object fragmentObj : field.fragments()) {
                if (fragmentObj instanceof VariableDeclarationFragment fragment
                        && fieldName.equals(fragment.getName().getIdentifier())) {
                    Expression initializer = fragment.getInitializer();
                    if (initializer instanceof StringLiteral stringLiteral) {
                        return stringLiteral.getLiteralValue();
                    }
                    return resolveCompileTimeStringKey(initializer, typeDecl, context, visiting);
                }
            }
        }
        return null;
    }

    private static boolean isValueOfSiteCompatibleWithBindings(
            MethodInvocation valueOfInvocation,
            Map<String, String> partialBindings,
            CodebaseContext context) {
        if (partialBindings == null || partialBindings.isEmpty()) {
            return true;
        }
        String branchConstraint = AstUtils.findConditionConstraint(valueOfInvocation);
        if (branchConstraint == null || branchConstraint.isBlank()) {
            return true;
        }
        branchConstraint = inlineBooleanPredicateConstraint(
                branchConstraint, methodFqnFromNode(valueOfInvocation, context), context);
        return BooleanConstraintEvaluator.isCompatibleWithBindings(branchConstraint, partialBindings);
    }

    private static boolean argumentReferencesParameter(Expression argument, String paramName) {
        return argumentReferencesParameter(argument, paramName, null, null);
    }

    private static boolean argumentReferencesParameter(
            Expression argument,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (argument instanceof SimpleName simpleName) {
            return paramName.equals(simpleName.getIdentifier());
        }
        if (argument instanceof MethodInvocation invocation
                && invocation.arguments().isEmpty()
                && invocation.getExpression() instanceof SimpleName simpleName) {
            String methodName = invocation.getName().getIdentifier();
            if (("toUpperCase".equals(methodName) || "toLowerCase".equals(methodName) || "trim".equals(methodName))
                    && paramName.equals(simpleName.getIdentifier())) {
                return true;
            }
        }
        if (argument instanceof MethodInvocation invocation
                && ownerMethod != null
                && context != null
                && isSingleArgHelperForwardingParameter(invocation, paramName, ownerMethod, context)) {
            return true;
        }
        return false;
    }

    private static boolean isSingleArgHelperForwardingParameter(
            MethodInvocation helperInvocation,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (helperInvocation.arguments().size() != 1) {
            return false;
        }
        Expression forwarded = (Expression) helperInvocation.arguments().get(0);
        if (!argumentReferencesParameter(forwarded, paramName, ownerMethod, context)) {
            return false;
        }
        MethodDeclaration helper = findHelperMethod(ownerMethod, helperInvocation, context);
        return helper != null && isTrivialSingleArgStringHelper(helper);
    }

    private static MethodDeclaration findHelperMethod(
            MethodDeclaration ownerMethod,
            MethodInvocation helperInvocation,
            CodebaseContext context) {
        String helperName = helperInvocation.getName().getIdentifier();
        CompilationUnit compilationUnit = ownerMethod.getRoot() instanceof CompilationUnit cu ? cu : null;
        Expression receiver = helperInvocation.getExpression();
        if (receiver == null) {
            MethodDeclaration ownerHelper = findInstanceHelperOnOwnerType(ownerMethod, helperName, context);
            if (ownerHelper != null) {
                return ownerHelper;
            }
            return findStaticImportHelper(helperName, compilationUnit, context);
        }
        TypeDeclaration declaringType = resolveHelperDeclaringType(receiver, compilationUnit, context);
        if (declaringType == null) {
            return null;
        }
        return context.findMethodDeclaration(declaringType, helperName, true);
    }

    private static MethodDeclaration findInstanceHelperOnOwnerType(
            MethodDeclaration ownerMethod,
            String helperName,
            CodebaseContext context) {
        ASTNode parent = ownerMethod.getParent();
        if (!(parent instanceof TypeDeclaration typeDeclaration)) {
            return null;
        }
        return context.findMethodDeclaration(typeDeclaration, helperName, true);
    }

    private static MethodDeclaration findStaticImportHelper(
            String helperName,
            CompilationUnit compilationUnit,
            CodebaseContext context) {
        if (compilationUnit == null || helperName == null || helperName.isBlank()) {
            return null;
        }
        for (Object importObject : compilationUnit.imports()) {
            if (!(importObject instanceof ImportDeclaration importDeclaration) || !importDeclaration.isStatic()) {
                continue;
            }
            String importedName = importDeclaration.getName().getFullyQualifiedName();
            if (importDeclaration.isOnDemand()) {
                MethodDeclaration helper = findHelperOnType(importedName, helperName, context);
                if (helper != null) {
                    return helper;
                }
                continue;
            }
            if (!importedName.endsWith("." + helperName)) {
                continue;
            }
            String className = importedName.substring(0, importedName.lastIndexOf('.'));
            return findHelperOnType(className, helperName, context);
        }
        return null;
    }

    private static MethodDeclaration findHelperOnType(
            String typeName,
            String helperName,
            CodebaseContext context) {
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(typeName);
        if (typeDeclaration == null) {
            return null;
        }
        return context.findMethodDeclaration(typeDeclaration, helperName, true);
    }

    private static TypeDeclaration resolveHelperDeclaringType(
            Expression receiver,
            CompilationUnit compilationUnit,
            CodebaseContext context) {
        org.eclipse.jdt.core.dom.ITypeBinding binding = receiver.resolveTypeBinding();
        if (binding != null) {
            TypeDeclaration typeDeclaration = context.getTypeDeclaration(binding.getQualifiedName());
            if (typeDeclaration != null) {
                return typeDeclaration;
            }
        }
        if (receiver instanceof QualifiedName qualifiedName) {
            return context.getTypeDeclaration(qualifiedName.getFullyQualifiedName());
        }
        if (receiver instanceof SimpleName simpleName) {
            return context.getTypeDeclaration(simpleName.getIdentifier(), compilationUnit);
        }
        return null;
    }

    private static boolean isTrivialSingleArgStringHelper(MethodDeclaration helper) {
        if (helper.parameters().size() != 1 || helper.getBody() == null) {
            return false;
        }
        String helperParam = null;
        Object paramObj = helper.parameters().get(0);
        if (paramObj instanceof SingleVariableDeclaration param) {
            helperParam = param.getName().getIdentifier();
        }
        if (helperParam == null) {
            return false;
        }
        Set<String> known = new LinkedHashSet<>();
        known.add(helperParam);
        known.addAll(collectIntraMethodParameterAliases(helper, helperParam));
        final boolean[] returnsKnownBinding = {false};
        helper.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement node) {
                if (expressionReferencesKnownBinding(node.getExpression(), known)) {
                    returnsKnownBinding[0] = true;
                }
                return super.visit(node);
            }
        });
        return returnsKnownBinding[0];
    }

    private static String resolveValueOfEnumType(MethodInvocation valueOfInvocation, CodebaseContext context) {
        Expression receiver = valueOfInvocation.getExpression();
        if (receiver != null) {
            org.eclipse.jdt.core.dom.ITypeBinding binding = receiver.resolveTypeBinding();
            if (binding != null && binding.isEnum()) {
                return binding.getQualifiedName();
            }
        }
        if (receiver instanceof QualifiedName qualifiedName) {
            return qualifiedName.getFullyQualifiedName();
        }
        if (receiver instanceof SimpleName simpleName && context != null) {
            List<String> values = context.getEnumValues(simpleName.getIdentifier());
            if (values != null && !values.isEmpty()) {
                return simpleName.getIdentifier();
            }
        }
        return null;
    }

    static List<String> extractStringSwitchCases(String methodFqn, String paramName, CodebaseContext context) {
        MethodDeclaration method = findMethodDeclaration(methodFqn, context);
        if (method == null || method.getBody() == null) {
            return List.of();
        }
        if (!tracksBindingName(method, paramName)) {
            return List.of();
        }
        LinkedHashSet<String> cases = new LinkedHashSet<>();
        method.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(SwitchStatement node) {
                collectCasesFromSwitch(node.getExpression(), node.statements(), paramName, cases, method, context);
                return super.visit(node);
            }

            @Override
            public boolean visit(SwitchExpression node) {
                collectCasesFromSwitch(node.getExpression(), node.statements(), paramName, cases, method, context);
                return super.visit(node);
            }

            @Override
            public boolean visit(IfStatement node) {
                collectCasesFromIfChain(node, paramName, cases, method, context);
                return super.visit(node);
            }
        });
        return List.copyOf(cases);
    }

    private static void collectCasesFromSwitch(
            Expression selector,
            List<?> statements,
            String paramName,
            Set<String> cases,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (!switchSelectorReferencesParameter(selector, paramName, ownerMethod, context)) {
            return;
        }
        for (Object statementObj : statements) {
            if (statementObj instanceof SwitchCase switchCase && !switchCase.isDefault()) {
                TypeDeclaration declaringType = ownerMethod != null
                        ? AstUtils.findEnclosingType(ownerMethod)
                        : null;
                for (Object exprObj : switchCase.expressions()) {
                    if (exprObj instanceof Expression caseExpression) {
                        String key = resolveCompileTimeStringKey(caseExpression, declaringType, context);
                        if (key != null) {
                            cases.add(key);
                        }
                    }
                }
            }
        }
    }

    private static boolean switchSelectorReferencesParameter(
            Expression selector,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (selector instanceof SimpleName simpleName) {
            return paramName.equals(simpleName.getIdentifier());
        }
        if (selector instanceof MethodInvocation invocation
                && invocation.arguments().isEmpty()
                && invocation.getExpression() instanceof SimpleName receiver) {
            String methodName = invocation.getName().getIdentifier();
            if (("toUpperCase".equals(methodName) || "toLowerCase".equals(methodName) || "trim".equals(methodName))
                    && paramName.equals(receiver.getIdentifier())) {
                return true;
            }
        }
        if (ownerMethod != null && context != null && selector instanceof MethodInvocation invocation) {
            return isSingleArgHelperForwardingParameter(invocation, paramName, ownerMethod, context);
        }
        return false;
    }

    private static void collectCasesFromIfChain(
            IfStatement ifStatement,
            String paramName,
            Set<String> cases,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        IfStatement current = ifStatement;
        while (current != null) {
            String literal = extractEqualsLiteralForParameter(
                    current.getExpression(), paramName, ownerMethod, context);
            if (literal == null) {
                literal = extractLiteralFromBooleanPredicateHelper(
                        current.getExpression(), paramName, ownerMethod, context);
            }
            if (literal != null) {
                cases.add(literal);
            }
            Statement elseStatement = current.getElseStatement();
            if (elseStatement instanceof IfStatement nestedIf) {
                current = nestedIf;
            } else {
                if (elseStatement != null) {
                    String elseLiteral = extractEqualsLiteralForParameterFromStatement(
                            elseStatement, paramName, ownerMethod, context);
                    if (elseLiteral != null) {
                        cases.add(elseLiteral);
                    }
                }
                break;
            }
        }
    }

    private static String extractEqualsLiteralForParameterFromStatement(
            Statement statement,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (statement instanceof IfStatement ifStatement) {
            return extractEqualsLiteralForParameter(
                    ifStatement.getExpression(), paramName, ownerMethod, context);
        }
        return null;
    }

    private static String extractEqualsLiteralForParameter(Expression expression, String paramName) {
        return extractEqualsLiteralForParameter(expression, paramName, null, null);
    }

    private static String extractEqualsLiteralForParameter(
            Expression expression,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        String fromObjects = extractObjectsEqualsLiteral(expression, paramName, ownerMethod, context);
        if (fromObjects != null) {
            return fromObjects;
        }
        if (!(expression instanceof MethodInvocation invocation)) {
            return null;
        }
        String methodName = invocation.getName().getIdentifier();
        if (!"equals".equals(methodName) && !"equalsIgnoreCase".equals(methodName)) {
            return null;
        }
        if (invocation.arguments().size() != 1) {
            return null;
        }
        Expression argument = (Expression) invocation.arguments().get(0);
        Expression receiver = invocation.getExpression();
        TypeDeclaration declaringType = ownerMethod != null
                ? AstUtils.findEnclosingType(ownerMethod)
                : null;

        String receiverLiteral = resolveEqualsSideLiteral(receiver, declaringType, context);
        if (receiverLiteral != null
                && argument instanceof SimpleName simpleName
                && paramName.equals(simpleName.getIdentifier())) {
            return receiverLiteral;
        }
        if (receiverLiteral != null
                && ownerMethod != null
                && context != null
                && switchSelectorReferencesParameter(argument, paramName, ownerMethod, context)) {
            return receiverLiteral;
        }
        if (receiver instanceof SimpleName simpleName
                && paramName.equals(simpleName.getIdentifier())) {
            String argumentLiteral = resolveEqualsSideLiteral(argument, declaringType, context);
            if (argumentLiteral != null) {
                return argumentLiteral;
            }
        }
        if (ownerMethod != null
                && context != null
                && switchSelectorReferencesParameter(receiver, paramName, ownerMethod, context)) {
            String argumentLiteral = resolveEqualsSideLiteral(argument, declaringType, context);
            if (argumentLiteral != null) {
                return argumentLiteral;
            }
        }
        return null;
    }

    private static String resolveEqualsSideLiteral(
            Expression expression,
            TypeDeclaration declaringType,
            CodebaseContext context) {
        if (expression instanceof StringLiteral stringLiteral) {
            return stringLiteral.getLiteralValue();
        }
        if (declaringType != null && context != null) {
            return resolveCompileTimeStringKey(expression, declaringType, context);
        }
        return null;
    }

    private static String extractObjectsEqualsLiteral(
            Expression expression,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (!(expression instanceof MethodInvocation invocation)) {
            return null;
        }
        if (!isObjectsEqualsInvocation(invocation)) {
            return null;
        }
        if (invocation.arguments().size() != 2) {
            return null;
        }
        Expression left = (Expression) invocation.arguments().get(0);
        Expression right = (Expression) invocation.arguments().get(1);
        TypeDeclaration declaringType = ownerMethod != null
                ? AstUtils.findEnclosingType(ownerMethod)
                : null;
        String leftLiteral = resolveEqualsSideLiteral(left, declaringType, context);
        if (leftLiteral != null
                && parameterExpressionReferencesBinding(right, paramName, ownerMethod, context)) {
            return leftLiteral;
        }
        String rightLiteral = resolveEqualsSideLiteral(right, declaringType, context);
        if (rightLiteral != null
                && parameterExpressionReferencesBinding(left, paramName, ownerMethod, context)) {
            return rightLiteral;
        }
        return null;
    }

    private static boolean isObjectsEqualsInvocation(MethodInvocation invocation) {
        if (!"equals".equals(invocation.getName().getIdentifier())) {
            return false;
        }
        Expression receiver = invocation.getExpression();
        if (receiver == null) {
            return false;
        }
        if (receiver instanceof SimpleName simpleName) {
            return "Objects".equals(simpleName.getIdentifier());
        }
        if (receiver instanceof QualifiedName qualifiedName) {
            String fqn = qualifiedName.getFullyQualifiedName();
            return "java.util.Objects".equals(fqn) || fqn.endsWith(".Objects");
        }
        return false;
    }

    private static boolean parameterExpressionReferencesBinding(
            Expression expression,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (expression instanceof SimpleName simpleName) {
            return paramName.equals(simpleName.getIdentifier());
        }
        return ownerMethod != null
                && context != null
                && switchSelectorReferencesParameter(expression, paramName, ownerMethod, context);
    }

    private static boolean tracksBindingName(MethodDeclaration method, String name) {
        if (hasParameter(method, name)) {
            return true;
        }
        for (Object paramObj : method.parameters()) {
            if (paramObj instanceof SingleVariableDeclaration param) {
                String paramName = param.getName().getIdentifier();
                if (collectIntraMethodParameterAliases(method, paramName).contains(name)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean hasParameter(MethodDeclaration method, String paramName) {
        for (Object paramObj : method.parameters()) {
            if (paramObj instanceof SingleVariableDeclaration param
                    && paramName.equals(param.getName().getIdentifier())) {
                return true;
            }
        }
        return false;
    }

    private static String extractLiteralFromBooleanPredicateHelper(
            Expression expression,
            String paramName,
            MethodDeclaration ownerMethod,
            CodebaseContext context) {
        if (!(expression instanceof MethodInvocation invocation) || ownerMethod == null || context == null) {
            return null;
        }
        if (invocation.arguments().size() != 1) {
            return null;
        }
        Expression forwarded = (Expression) invocation.arguments().get(0);
        if (!parameterExpressionReferencesBinding(forwarded, paramName, ownerMethod, context)) {
            return null;
        }
        MethodDeclaration helper = findHelperMethod(ownerMethod, invocation, context);
        if (helper == null || !isBooleanReturnType(helper)) {
            return null;
        }
        return extractBooleanPredicateLiteral(helper, context, new LinkedHashSet<>());
    }

    private static String extractBooleanPredicateLiteral(
            MethodDeclaration helper,
            CodebaseContext context,
            Set<String> visiting) {
        if (helper == null || helper.getBody() == null || !isBooleanReturnType(helper)) {
            return null;
        }
        String helperFqn = methodFqnFromNode(helper, context);
        if (helperFqn != null && !visiting.add(helperFqn)) {
            return null;
        }
        String helperParam = singleParameterName(helper);
        if (helperParam == null) {
            return null;
        }
        final int[] returnCount = {0};
        final int[] resolvedCount = {0};
        LinkedHashSet<String> literals = new LinkedHashSet<>();
        helper.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement node) {
                returnCount[0]++;
                if (node.getExpression() != null) {
                    String literal = resolveBooleanPredicateLiteralFromReturn(
                            node.getExpression(), helperParam, helper, context, visiting);
                    if (literal != null) {
                        resolvedCount[0]++;
                        literals.add(literal);
                    }
                }
                return super.visit(node);
            }
        });
        if (returnCount[0] == 0 || returnCount[0] != resolvedCount[0] || literals.size() != 1) {
            return null;
        }
        return literals.iterator().next();
    }

    private static String resolveBooleanPredicateLiteralFromReturn(
            Expression expression,
            String bindingParam,
            MethodDeclaration ownerMethod,
            CodebaseContext context,
            Set<String> visiting) {
        String direct = extractEqualsLiteralForParameter(expression, bindingParam, ownerMethod, context);
        if (direct != null) {
            return direct;
        }
        if (!(expression instanceof MethodInvocation invocation) || ownerMethod == null || context == null) {
            return null;
        }
        if (invocation.arguments().size() != 1) {
            return null;
        }
        Expression forwarded = (Expression) invocation.arguments().get(0);
        if (!parameterExpressionReferencesBinding(forwarded, bindingParam, ownerMethod, context)) {
            return null;
        }
        MethodDeclaration nested = findHelperMethod(ownerMethod, invocation, context);
        if (nested == null || !isBooleanReturnType(nested)) {
            return null;
        }
        return extractBooleanPredicateLiteral(nested, context, visiting);
    }

    private static String extractSingleEqualsLiteralFromBooleanHelper(
            MethodDeclaration helper,
            CodebaseContext context) {
        return extractBooleanPredicateLiteral(helper, context, new LinkedHashSet<>());
    }

    static String inlineBooleanPredicateConstraint(
            String constraint,
            String callerMethodFqn,
            CodebaseContext context) {
        if (constraint == null || constraint.isBlank() || context == null) {
            return constraint;
        }
        List<String> parts = ConstraintClauses.splitAndClauses(constraint);
        if (parts.isEmpty()) {
            return constraint;
        }
        List<String> inlined = new ArrayList<>();
        for (String part : parts) {
            String expanded = tryInlineBooleanPredicate(part.trim(), callerMethodFqn, context);
            inlined.add(expanded != null ? expanded : part.trim());
        }
        return String.join(" && ", inlined);
    }

    private static String tryInlineBooleanPredicate(
            String constraint,
            String callerMethodFqn,
            CodebaseContext context) {
        if (constraint.startsWith("!(") && constraint.endsWith(")")) {
            String inner = constraint.substring(2, constraint.length() - 1).trim();
            String inlinedInner = tryInlineBooleanPredicate(inner, callerMethodFqn, context);
            if (inlinedInner != null) {
                return "!(" + inlinedInner + ")";
            }
        }
        Matcher matcher = Pattern.compile(
                "^(?:(?<qual>[\\w.]+)\\.)?(?<method>\\w+)\\s*\\(\\s*(?<arg>[\\w.]+)\\s*\\)$")
                .matcher(constraint);
        if (!matcher.matches()) {
            return null;
        }
        MethodDeclaration caller = findMethodDeclaration(callerMethodFqn, context);
        if (caller == null) {
            return null;
        }
        String qualifier = matcher.group("qual");
        String helperName = matcher.group("method");
        String argumentName = matcher.group("arg");
        MethodDeclaration helper = resolveBooleanPredicateHelper(caller, qualifier, helperName, context);
        if (helper == null) {
            return null;
        }
        if (extractBooleanPredicateLiteral(helper, context, new LinkedHashSet<>()) == null) {
            return null;
        }
        return inlineBooleanPredicateExpression(helper, argumentName, caller, context, new LinkedHashSet<>());
    }

    private static String inlineBooleanPredicateExpression(
            MethodDeclaration helper,
            String argumentName,
            MethodDeclaration ownerMethod,
            CodebaseContext context,
            Set<String> visiting) {
        if (helper == null || helper.getBody() == null) {
            return null;
        }
        String helperFqn = methodFqnFromNode(helper, context);
        if (helperFqn != null && !visiting.add(helperFqn)) {
            return null;
        }
        String helperParam = singleParameterName(helper);
        if (helperParam == null) {
            return null;
        }
        LinkedHashSet<String> inlinedReturns = new LinkedHashSet<>();
        helper.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement node) {
                if (node.getExpression() != null) {
                    String inlined = inlineBooleanPredicateReturnExpression(
                            node.getExpression(), helperParam, argumentName, ownerMethod, context, visiting);
                    if (inlined != null) {
                        inlinedReturns.add(inlined);
                    }
                }
                return super.visit(node);
            }
        });
        return inlinedReturns.size() == 1 ? inlinedReturns.iterator().next() : null;
    }

    private static String inlineBooleanPredicateReturnExpression(
            Expression expression,
            String helperParam,
            String argumentName,
            MethodDeclaration ownerMethod,
            CodebaseContext context,
            Set<String> visiting) {
        if (extractEqualsLiteralForParameter(expression, helperParam, ownerMethod, context) != null) {
            return substituteParameterName(expression.toString(), helperParam, argumentName);
        }
        if (!(expression instanceof MethodInvocation invocation)) {
            return null;
        }
        if (invocation.arguments().size() != 1) {
            return null;
        }
        Expression forwarded = (Expression) invocation.arguments().get(0);
        if (!parameterExpressionReferencesBinding(forwarded, helperParam, ownerMethod, context)) {
            return null;
        }
        MethodDeclaration nested = findHelperMethod(ownerMethod, invocation, context);
        if (nested == null || !isBooleanReturnType(nested)) {
            return null;
        }
        String nestedArgument = substituteParameterName(forwarded.toString(), helperParam, argumentName);
        return inlineBooleanPredicateExpression(nested, nestedArgument, ownerMethod, context, visiting);
    }

    private static String substituteParameterName(String expression, String parameterName, String argumentName) {
        return expression.replaceAll("\\b" + Pattern.quote(parameterName) + "\\b", argumentName);
    }

    private static MethodDeclaration resolveBooleanPredicateHelper(
            MethodDeclaration caller,
            String qualifier,
            String helperName,
            CodebaseContext context) {
        if (qualifier != null && !qualifier.isBlank()) {
            MethodDeclaration helper = findHelperOnType(qualifier, helperName, context);
            if (helper != null) {
                return helper;
            }
            CompilationUnit compilationUnit = caller.getRoot() instanceof CompilationUnit cu ? cu : null;
            TypeDeclaration typeDeclaration = context.getTypeDeclaration(qualifier, compilationUnit);
            if (typeDeclaration != null) {
                helper = context.findMethodDeclaration(typeDeclaration, helperName, true);
                if (helper != null) {
                    return helper;
                }
                return findHelperOnType(context.getFqn(typeDeclaration), helperName, context);
            }
            return null;
        }
        MethodDeclaration helper = findInstanceHelperOnOwnerType(caller, helperName, context);
        if (helper != null) {
            return helper;
        }
        CompilationUnit compilationUnit = caller.getRoot() instanceof CompilationUnit cu ? cu : null;
        return findStaticImportHelper(helperName, compilationUnit, context);
    }

    private static String methodFqnFromNode(ASTNode node, CodebaseContext context) {
        MethodDeclaration method = AstUtils.findEnclosingMethod(node);
        TypeDeclaration type = AstUtils.findEnclosingType(node);
        if (method == null || type == null || context == null) {
            return null;
        }
        return context.getFqn(type) + "." + method.getName().getIdentifier();
    }

    private static String singleParameterName(MethodDeclaration method) {
        if (method.parameters().size() != 1) {
            return null;
        }
        Object paramObj = method.parameters().get(0);
        if (paramObj instanceof SingleVariableDeclaration param) {
            return param.getName().getIdentifier();
        }
        return null;
    }

    private static boolean isBooleanReturnType(MethodDeclaration method) {
        Type returnType = method.getReturnType2();
        if (returnType instanceof PrimitiveType primitiveType) {
            return primitiveType.getPrimitiveTypeCode() == PrimitiveType.BOOLEAN;
        }
        if (returnType instanceof SimpleType simpleType) {
            String name = simpleType.getName().getFullyQualifiedName();
            return "boolean".equals(name) || "Boolean".equals(name);
        }
        return false;
    }

    private static MethodDeclaration findMethodDeclaration(String methodFqn, CodebaseContext context) {
        if (methodFqn == null || !methodFqn.contains(".")) {
            return null;
        }
        String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
        String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(className);
        if (typeDeclaration == null) {
            return null;
        }
        return context.findMethodDeclaration(typeDeclaration, methodName, true);
    }
}
