package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.index.AccessorFieldResolver;
import click.kamil.springstatemachineexporter.analysis.index.AccessorSummary;
import click.kamil.springstatemachineexporter.analysis.pipeline.AccessorResolver;
import click.kamil.springstatemachineexporter.analysis.pipeline.GetterBodyConstantScanner;
import click.kamil.springstatemachineexporter.analysis.pipeline.LibraryUnwrapRegistry;
import click.kamil.springstatemachineexporter.analysis.pipeline.ResolutionBudget;
import click.kamil.springstatemachineexporter.analysis.pipeline.SwitchConstSupport;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
import java.util.function.Function;

@Slf4j
public class ConstantExtractor {
    private final CodebaseContext context;
    private final ConstantResolver constantResolver;
    private final Function<MethodInvocation, String> calledMethodResolver;
    private VariableTracer variableTracer;
    private ConstructorAnalyzer constructorAnalyzer;
    private ExpressionAccessClassifier expressionAccessClassifier;
    private AccessorResolver accessorResolver;

    public ConstantExtractor(CodebaseContext context, ConstantResolver constantResolver, Function<MethodInvocation, String> calledMethodResolver) {
        this.context = context;
        this.constantResolver = constantResolver;
        this.calledMethodResolver = calledMethodResolver;
    }

    public void setVariableTracer(VariableTracer variableTracer) {
        this.variableTracer = variableTracer;
    }

    public void setConstructorAnalyzer(ConstructorAnalyzer constructorAnalyzer) {
        this.constructorAnalyzer = constructorAnalyzer;
    }

    public void setExpressionAccessClassifier(ExpressionAccessClassifier expressionAccessClassifier) {
        this.expressionAccessClassifier = expressionAccessClassifier;
    }

    public void setAccessorResolver(AccessorResolver accessorResolver) {
        this.accessorResolver = accessorResolver;
    }

    public void extractConstantsFromExpression(Expression expr, List<String> constants) {
        if (constantResolver != null) {
            String resolved = constantResolver.resolve(expr, context);
            if (resolved != null && !shouldInspectExpressionStructure(expr, resolved)) {
                addResolvedConstant(resolved, constants);
                return;
            }
        }

        if (expr instanceof StringLiteral sl) {
            constants.add(sl.getLiteralValue());
        } else if (expr instanceof ConditionalExpression ce) {
            extractConstantsFromExpression(ce.getThenExpression(), constants);
            extractConstantsFromExpression(ce.getElseExpression(), constants);
        } else if (expr instanceof ParenthesizedExpression pe) {
            extractConstantsFromExpression(pe.getExpression(), constants);
        } else if (expr instanceof CastExpression ce) {
            extractConstantsFromExpression(ce.getExpression(), constants);
        } else if (expr instanceof Assignment assignment) {
            extractConstantsFromExpression(assignment.getRightHandSide(), constants);
        } else if (expr instanceof SwitchExpression se) {
            extractConstantsFromSwitchLike(se, constants);
        } else if (expr instanceof ArrayAccess aa) {
            extractConstantsFromExpression(aa.getArray(), constants);
        } else if (expr instanceof ArrayCreation ac && ac.getInitializer() != null) {
            for (Object expObj : ac.getInitializer().expressions()) {
                extractConstantsFromArgument((Expression) expObj, constants);
            }
        } else if (expr instanceof ArrayInitializer ai) {
            for (Object expObj : ai.expressions()) {
                extractConstantsFromArgument((Expression) expObj, constants);
            }
        } else if (expr instanceof MethodInvocation mi) {
            if (variableTracer != null) {
                int sizeBefore = constants.size();
                for (Expression def : variableTracer.traceVariableAll(mi)) {
                    if (def != null && !def.toString().equals(mi.toString())) {
                        extractConstantsFromExpression(def, constants);
                    }
                }
                if (constants.size() > sizeBefore) {
                    return;
                }
            }

            String methodName = mi.getName().getIdentifier();
            
            if (log.isTraceEnabled()) {
                log.trace("Processing mi: {}", mi);
            }
            if (("get".equals(methodName) || "getOrDefault".equals(methodName)) && mi.getExpression() != null) {
                if (isKeyedLookupMethod(mi)) {
                    extractMapReceiverConstants(mi.getExpression(), constants);
                    return;
                }
                if ("get".equals(methodName) && mi.arguments().isEmpty()) {
                    extractMapReceiverConstants(mi.getExpression(), constants);
                    return;
                }
            }
            if (LibraryUnwrapRegistry.isUnwrapMethodName(methodName)) {
                for (Object argObj : mi.arguments()) {
                    extractConstantsFromExpression((Expression) argObj, constants);
                }
                return;
            }

            if ((methodName.startsWith("get") || methodName.equals("type") || methodName.equals("event"))
                    && mi.getExpression() instanceof SimpleName sn
                    && variableTracer != null) {
                org.eclipse.jdt.core.dom.MethodDeclaration md =
                        click.kamil.springstatemachineexporter.ast.common.AstUtils.findEnclosingMethod(mi);
                TypeDeclaration td = findEnclosingType(mi);
                if (md != null && td != null) {
                    String methodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
                    Expression setterArg = variableTracer.traceLocalSetter(methodFqn, sn.getIdentifier(), methodName);
                    if (setterArg != null) {
                        extractConstantsFromExpression(setterArg, constants);
                        if (!constants.isEmpty()) {
                            return;
                        }
                    }
                }
            }
            
            List<Expression> receivers = new ArrayList<>();
            if (mi.getExpression() != null) {
                if (mi.getExpression() instanceof ClassInstanceCreation) {
                    receivers.add(mi.getExpression());
                } else if (variableTracer != null) {
                    receivers.addAll(variableTracer.traceVariableAll(mi.getExpression()));
                }
            }
            
            for (Expression receiverExpr : receivers) {
                if (receiverExpr instanceof ClassInstanceCreation cic) {
                    if (cic.getAnonymousClassDeclaration() != null) {
                        for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
                            if (declObj instanceof MethodDeclaration mdecl) {
                                if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) {
                                    mdecl.getBody().accept(new ASTVisitor() {
                                        @Override
                                        public boolean visit(ReturnStatement rs) {
                                            if (rs.getExpression() != null) {
                                                extractConstantsFromExpression(rs.getExpression(), constants);
                                            }
                                            return super.visit(rs);
                                        }
                                    });
                                }
                            }
                        }
                    } else if (constructorAnalyzer != null) {
                        boolean handledByLombok = false;
                        if (methodName.startsWith("get") && methodName.length() > 3) {
                            String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
                            TypeDeclaration typeDecl = context.getTypeDeclaration(typeName);
                            if (typeDecl != null) {
                                String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                                int fieldIndex = constructorAnalyzer.findConstructorArgumentIndexForLombokField(typeDecl, fieldName);
                                if (fieldIndex >= 0 && fieldIndex < cic.arguments().size()) {
                                    extractConstantsFromArgument((Expression) cic.arguments().get(fieldIndex), constants);
                                    handledByLombok = true;
                                }
                            }
                        }
                        if (!handledByLombok && methodName != null && !methodName.isEmpty()) {
                            List<String> narrowed = constructorAnalyzer.resolveInlineInstantiationGetterResult(
                                    cic, methodName, context, new HashSet<>());
                            if (narrowed.isEmpty()) {
                                String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
                                CompilationUnit contextCu = expr.getRoot() instanceof CompilationUnit ? (CompilationUnit) expr.getRoot() : null;
                                TypeDeclaration typeDecl = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName);
                                if (typeDecl == null) typeDecl = context.getTypeDeclaration(typeName);
                                if (typeDecl != null) {
                                    narrowed = resolveMethodReturnConstant(context.getFqn(typeDecl), methodName, 0, new HashSet<>(), contextCu);
                                }
                            }
                            if (narrowed != null && !narrowed.isEmpty()) {
                                constants.addAll(narrowed);
                            }
                        }
                    }
                }
            }
            
            Expression currentMi = mi;
            while (currentMi instanceof MethodInvocation chainMi) {
                String chainName = chainMi.getName().getIdentifier();
                if (LibraryUnwrapRegistry.isHeaderSetterMethodName(chainName) && chainMi.arguments().size() == 2) {
                    extractConstantsFromExpression((Expression) chainMi.arguments().get(1), constants);
                } else if (LibraryUnwrapRegistry.isEventSetterMethodName(chainName) && chainMi.arguments().size() == 1) {
                    extractConstantsFromExpression((Expression) chainMi.arguments().get(0), constants);
                }
                currentMi = chainMi.getExpression();
            }
            
            TypeDeclaration td = findEnclosingType(mi);
            if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof ThisExpression)) {
                List<String> values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new HashSet<>(), null);
                if (values != null) constants.addAll(values);
            }
        }
    }

    private boolean shouldInspectExpressionStructure(Expression expr, String resolved) {
        if (expr == null || resolved == null) {
            return false;
        }
        // Always walk switch arms — a single concrete from CR (e.g. default-only) must not
        // short-circuit poly collection via SwitchConstSupport.
        if (expr instanceof SwitchExpression) {
            return true;
        }
        if (!(resolved.startsWith("<SYMBOLIC:") || resolved.contains("SYMBOLIC:") || resolved.endsWith(".*>"))) {
            return false;
        }
        return expr instanceof ConditionalExpression;
    }

    public void extractConstantsFromArgument(Expression argObj, List<String> constants) {
        if (argObj instanceof ClassInstanceCreation innerCic) {
            String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(innerCic.getType());
            if ("String".equals(typeName)) {
                extractConstantsFromExpression(argObj, constants);
            }
        } else {
            extractConstantsFromExpression(argObj, constants);
        }
    }

    public List<String> resolveClassConstantReturns(String className, CodebaseContext context, CompilationUnit contextCu) {
        if (className != null && className.contains(".")) {
            String prefix = className.substring(0, className.lastIndexOf('.'));
            String suffix = className.substring(className.lastIndexOf('.') + 1);
            TypeDeclaration prefixTd = contextCu != null ? context.getTypeDeclaration(prefix, contextCu) : context.getTypeDeclaration(prefix);
            if (prefixTd == null) prefixTd = context.getTypeDeclaration(prefix);
            boolean isKnownType = prefixTd != null || context.getEnumValues(prefix) != null;
            if (isKnownType) {
                String simplePrefix = prefix.contains(".") ? prefix.substring(prefix.lastIndexOf('.') + 1) : prefix;
                return Collections.singletonList(simplePrefix + "." + suffix);
            }
        }

        TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(className, contextCu) : context.getTypeDeclaration(className);
        if (td == null) td = context.getTypeDeclaration(className);
        if (td == null) return null;
        
        final List<String> resolvedConstants = new ArrayList<>();
        for (MethodDeclaration md : td.getMethods()) {
            if (md.getBody() != null) {
                md.getBody().accept(new ASTVisitor() {
                    public boolean visit(ReturnStatement node) {
                        if (variableTracer != null) {
                            List<Expression> tracedReturns = variableTracer.traceVariableAll(node.getExpression());
                            for (Expression tracedRet : tracedReturns) {
                                extractConstantsFromExpression(tracedRet, resolvedConstants);
                            }
                        }
                        return super.visit(node);
                    }
                });
            }
            if (!resolvedConstants.isEmpty()) {
                return resolvedConstants;
            }
        }
        return null;
    }

    public List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
        return resolveMethodReturnConstant(className, methodName, depth, visited, contextCu, ResolutionBudget.defaults());
    }

    public List<String> resolveMethodReturnConstant(
            String className,
            String methodName,
            int depth,
            Set<String> visited,
            CompilationUnit contextCu,
            ResolutionBudget budget) {
        return resolveMethodReturnConstant(className, methodName, depth, visited, contextCu, budget, true);
    }

    public List<String> resolveMethodReturnConstant(
            String className,
            String methodName,
            int depth,
            Set<String> visited,
            CompilationUnit contextCu,
            ResolutionBudget budget,
            boolean allowWidenToImplementations) {
        final boolean debug = log.isDebugEnabled();
        if (debug) {
            log.debug("ConstantExtractor.resolveMethodReturnConstant CALLED: className={}, methodName={}", className, methodName);
            log.debug("Entering resolveMethodReturnConstant with className={}, methodName={}, depth={}", className, methodName, depth);
        }
        if (budget == null) {
            budget = ResolutionBudget.defaults();
        }
        final ResolutionBudget activeBudget = budget;
        if (activeBudget.isMethodReturnExhausted(depth)) {
            if (debug) {
                log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName);
            }
            return Collections.emptyList();
        }
        String fqn = className + "." + methodName;
        if (!visited.add(fqn)) {
            if (debug) {
                log.debug("Exiting resolveMethodReturnConstant early (cycle detected) for fqn={}", fqn);
            }
            return Collections.emptyList();
        }

        Optional<AccessorSummary> indexedAccessor = lookupIndexedGetter(className, methodName, activeBudget);
        TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
        if (tempTd == null) {
            tempTd = context.getTypeDeclaration(className);
        }
        final TypeDeclaration td = tempTd;
        boolean widenToImplementations = allowWidenToImplementations
                && (td == null
                || td.isInterface()
                || Modifier.isAbstract(td.getModifiers()));

        if (widenToImplementations && depth == 0) {
            Optional<List<String>> precomputed = context.getPolymorphicAccessorIndex()
                    .widenedReturnConstants(className, methodName);
            if (precomputed.isPresent()) {
                visited.remove(fqn);
                return new ArrayList<>(precomputed.get());
            }
        }

        List<String> constants = new ArrayList<>();
        if (indexedAccessor.isPresent() && indexedAccessor.get().isGetter()) {
            boolean indexedOnRequestedType = className.equals(indexedAccessor.get().ownerFqn());
            if (!widenToImplementations || indexedOnRequestedType) {
                if (indexedAccessor.get().kind() == click.kamil.springstatemachineexporter.analysis.index.AccessorKind.CONSTANT_GETTER) {
                    List<String> constantGetterValues = extractConstantGetterReturn(
                            indexedAccessor.get().ownerFqn(), methodName, contextCu, visited);
                    if (!constantGetterValues.isEmpty()) {
                        if (!widenToImplementations) {
                            visited.remove(fqn);
                            return constantGetterValues;
                        }
                        constants.addAll(constantGetterValues);
                    }
                }
                if (constants.isEmpty()) {
                    List<String> indexedConstants = resolveIndexedGetterFieldConstants(
                            indexedAccessor.get(), className, contextCu, visited);
                    if (!indexedConstants.isEmpty()) {
                        if (!widenToImplementations) {
                            visited.remove(fqn);
                            return indexedConstants;
                        }
                        constants.addAll(indexedConstants);
                    }
                }
            }
        }
        if (td != null) {
            MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
            if (md != null && md.getBody() != null) {
                for (Object stmtObj : md.getBody().statements()) {
                    if (stmtObj instanceof SwitchStatement switchStatement) {
                        extractConstantsFromSwitchStatement(switchStatement, constants);
                    }
                }
                md.getBody().accept(new ASTVisitor() {
                    @Override
                    public boolean visit(ReturnStatement node) {
                        Expression retExpr = node.getExpression();
                        if (retExpr != null) {
                            boolean handled = false;
                            if (retExpr instanceof MethodInvocation mi && calledMethodResolver != null) {
                                String called = calledMethodResolver.apply(mi);
                                if (called != null && called.contains(".")) {
                                    if (visited.contains(called)) {
                                        handled = true;
                                    } else {
                                        String cName = called.substring(0, called.lastIndexOf('.'));
                                        String mName = called.substring(called.lastIndexOf('.') + 1);
                                        
                                        TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
                                        if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
                                        
                                        if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
                                            List<String> delegationResult = resolveMethodReturnConstant(
                                                    cName, mName, depth + 1, visited, contextCu, activeBudget, allowWidenToImplementations);
                                            constants.addAll(delegationResult);
                                            handled = true;
                                        }
                                    }
                                }
                            } else if (retExpr instanceof SuperMethodInvocation smi) {
                                String superFqn = context.getSuperclassFqn(td);
                                if (superFqn != null) {
                                    String called = superFqn + "." + smi.getName().getIdentifier();
                                    if (visited.contains(called)) {
                                        handled = true;
                                    } else {
                                        String cName = superFqn;
                                        String mName = smi.getName().getIdentifier();
                                        TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
                                        if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
                                        
                                        if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
                                            List<String> delegationResult = resolveMethodReturnConstant(
                                                    cName, mName, depth + 1, visited, contextCu, activeBudget, allowWidenToImplementations);
                                            constants.addAll(delegationResult);
                                            handled = true;
                                        }
                                    }
                                }
                            } else if (retExpr instanceof SwitchExpression switchExpression) {
                                // Prefer SwitchConstSupport arms — DFM may collapse unbound switches to
                                // a single default/last arm and hide poly. Opaque (throw) arms → fail-closed.
                                if (SwitchConstSupport.hasOpaqueArm(switchExpression)) {
                                    handled = true;
                                } else {
                                    extractConstantsFromSwitchLike(switchExpression, constants);
                                    String evaluated = constantResolver.evaluateSwitch(
                                            switchExpression, Map.of(), context);
                                    if (evaluated != null && evaluated.startsWith("ENUM_SET:")) {
                                        for (String eVal : AstUtils.parseEnumSet(evaluated)) {
                                            String parsed = parseEnumSetElement(eVal);
                                            if (!constants.contains(parsed)) {
                                                constants.add(parsed);
                                            }
                                        }
                                    }
                                    handled = !constants.isEmpty();
                                }
                            }
                            if (!handled && variableTracer != null && constructorAnalyzer != null) {
                                List<Expression> tracedRetExprs = variableTracer.traceVariableAll(retExpr);
                                if (tracedRetExprs.isEmpty() && retExpr != null) {
                                    tracedRetExprs = List.of(retExpr);
                                }
                                for (Expression tracedRetExpr : tracedRetExprs) {
                                    extractConstantsFromExpression(tracedRetExpr, constants);
                                    String val = constantResolver.resolve(tracedRetExpr, context);
                                    if (val != null) {
                                        if (val.startsWith("ENUM_SET:")) {
                                            for (String eVal : val.substring(9).split(",")) {
                                                String parsed = parseEnumSetElement(eVal);
                                                if (!constants.contains(parsed)) {
                                                    constants.add(parsed);
                                                }
                                            }
                                        } else if (val.startsWith("<SYMBOLIC:") || val.contains("SYMBOLIC:") || val.endsWith(".*>")) {
                                            if (!constants.contains(val)) {
                                                constants.add(val);
                                            }
                                        } else {
                                            if (!constants.contains(val)) constants.add(val);
                                        }
                                    } else if (tracedRetExpr instanceof SimpleName sn) {
                                        List<String> consts = constructorAnalyzer.traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
                                        if (!consts.isEmpty()) {
                                            constants.addAll(consts);
                                        }
                                    } else if (tracedRetExpr instanceof FieldAccess fa) {
                                        List<String> consts = constructorAnalyzer.traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited);
                                        if (!consts.isEmpty()) {
                                            constants.addAll(consts);
                                        }
                                    }
                                }
                            }
                        }
                        return super.visit(node);
                    }
                });
            }
            if (widenToImplementations) {
                List<String> impls = context.getImplementations(className);
                if (impls != null) {
                    for (String implName : impls) {
                        List<String> delegationResult = resolveMethodReturnConstant(
                                implName, methodName, depth + 1, visited, contextCu, activeBudget, allowWidenToImplementations);
                        for (String value : delegationResult) {
                            if (!constants.contains(value)) {
                                constants.add(value);
                            }
                        }
                    }
                }
            }
        } else if (widenToImplementations) {
            List<String> impls = context.getImplementations(className);
            if (impls != null) {
                for (String implName : impls) {
                    List<String> delegationResult = resolveMethodReturnConstant(
                            implName, methodName, depth + 1, visited, contextCu, activeBudget);
                    for (String value : delegationResult) {
                        if (!constants.contains(value)) {
                            constants.add(value);
                        }
                    }
                }
            }
        }
        visited.remove(fqn);
        if (debug) {
            log.debug("resolveMethodReturnConstant className={} methodName={} returns: {}", className, methodName, constants);
        }
        return constants;
    }

    public List<String> resolveIndexedGetterFieldConstants(
            AccessorSummary accessor,
            String requestingTypeFqn,
            CompilationUnit contextCu,
            Set<String> visited) {
        if (accessor != null && isSafeForPrecomputedFieldConstants(accessor)) {
            Optional<List<String>> precomputed = context.getIndexedFieldConstantsIndex()
                    .lookup(accessor.ownerFqn(), accessor.methodName());
            if (precomputed.isPresent()) {
                return new ArrayList<>(precomputed.get());
            }
        }
        return AccessorFieldResolver.resolveFieldConstants(
                accessor,
                requestingTypeFqn,
                context,
                constructorAnalyzer,
                contextCu,
                visited,
                this::extractConstantsFromExpression);
    }

    public Optional<AccessorSummary> lookupIndexedGetter(String className, String methodName) {
        return lookupIndexedGetter(className, methodName, ResolutionBudget.defaults());
    }

    private Optional<AccessorSummary> lookupIndexedGetter(String className, String methodName, ResolutionBudget budget) {
        if (accessorResolver != null) {
            return accessorResolver.lookupIndexedGetter(className, methodName);
        }
        if (className == null || className.isBlank()) {
            return Optional.empty();
        }
        return context.getAccessorIndex().lookup(className, methodName);
    }

    private List<String> extractConstantGetterReturn(
            String ownerFqn,
            String methodName,
            CompilationUnit contextCu,
            Set<String> visited) {
        return GetterBodyConstantScanner.extractConstantGetterReturn(
                context, this, constantResolver, ownerFqn, methodName, contextCu, visited);
    }


    private boolean isSafeForPrecomputedFieldConstants(AccessorSummary accessor) {
        if (accessor.fieldName() == null || accessor.declaringFqn() == null) {
            return accessor.kind() == click.kamil.springstatemachineexporter.analysis.index.AccessorKind.CONSTANT_GETTER;
        }
        if (!accessor.ownerFqn().equals(accessor.declaringFqn())) {
            return false;
        }
        TypeDeclaration ownerType = context.getTypeDeclaration(accessor.ownerFqn());
        return ownerType != null
                && !ownerType.isInterface()
                && !Modifier.isAbstract(ownerType.getModifiers());
    }

    public String parseEnumSetElement(String eVal) {
        if (eVal != null && (eVal.startsWith("<SYMBOLIC:") || eVal.contains("SYMBOLIC:") || eVal.endsWith(".*>"))) {
            return eVal;
        }
        return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal;
    }

    public void extractConstantsFromSwitchStatement(SwitchStatement switchStatement, List<String> constants) {
        if (switchStatement == null) {
            return;
        }
        extractConstantsFromSwitchLike(switchStatement, constants);
    }

    private void extractConstantsFromSwitchLike(ASTNode switchNode, List<String> constants) {
        if (SwitchConstSupport.hasOpaqueArm(switchNode)) {
            return;
        }
        for (Expression armExpr : SwitchConstSupport.armPayloadExpressions(switchNode)) {
            extractConstantsFromExpression(armExpr, constants);
        }
    }

    private boolean isKeyedLookupMethod(MethodInvocation mi) {
        if (expressionAccessClassifier != null) {
            return expressionAccessClassifier.isKeyedLookup(mi, null);
        }
        return "getOrDefault".equals(mi.getName().getIdentifier())
                || ("get".equals(mi.getName().getIdentifier()) && !mi.arguments().isEmpty());
    }

    /**
     * Resolves constants from the map/collection receiver of {@code .get(key)}.
     * Uses {@link ConstantResolver} for static fields ({@code ROUTES = Map.of(...)}) first;
     * falls back to dataflow tracing for locals ({@code Map map = ...; map.get(k)}).
     */
    private void extractMapReceiverConstants(Expression receiver, List<String> constants) {
        if (tryAddResolvedExpression(receiver, constants)) {
            return;
        }
        if (receiver instanceof MethodInvocation || receiver instanceof ArrayCreation || receiver instanceof ArrayInitializer) {
            extractConstantsFromExpression(receiver, constants);
            if (!constants.isEmpty()) {
                return;
            }
        }
        if (variableTracer == null) {
            return;
        }
        String receiverText = receiver.toString();
        for (Expression traced : variableTracer.traceVariableAll(receiver)) {
            if (traced == null || receiverText.equals(traced.toString())) {
                continue;
            }
            if (tryAddResolvedExpression(traced, constants)) {
                continue;
            }
            extractConstantsFromExpression(traced, constants);
        }
    }

    private boolean tryAddResolvedExpression(Expression expr, List<String> constants) {
        if (constantResolver == null) {
            return false;
        }
        String resolved = constantResolver.resolve(expr, context);
        if (resolved == null) {
            return false;
        }
        addResolvedConstant(resolved, constants);
        return true;
    }

    private void addResolvedConstant(String resolved, List<String> constants) {
        if (resolved.startsWith("ENUM_SET:")) {
            for (String eVal : resolved.substring(9).split(",")) {
                String parsed = parseEnumSetElement(eVal);
                if (!constants.contains(parsed)) {
                    constants.add(parsed);
                }
            }
        } else if (resolved.startsWith("MAP:")) {
            List<String> mapValues = ConstantResolver.decodeMapLiteralValues(resolved);
            if (mapValues != null) {
                for (String mapValue : mapValues) {
                    if (!constants.contains(mapValue)) {
                        constants.add(mapValue);
                    }
                }
            }
        } else if (resolved.startsWith("ARRAY:")) {
            for (String arrayValue : resolved.substring(6).split("\\|", -1)) {
                if (!constants.contains(arrayValue)) {
                    constants.add(arrayValue);
                }
            }
        } else if (!constants.contains(resolved)) {
            constants.add(resolved);
        }
    }

    private TypeDeclaration findEnclosingType(ASTNode node) {
        return click.kamil.springstatemachineexporter.ast.common.AstUtils.findEnclosingType(node);
    }
}
