package click.kamil.springstatemachineexporter.analysis.index;

import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * Resolves accessor return constants using only scan-time information: indexed accessors,
 * literal method bodies, and field initializers. No dataflow or constructor tracing.
 */
public final class ScanTimeAccessorConstants {

    private ScanTimeAccessorConstants() {
    }

    public static List<String> resolve(
            CodebaseContext context,
            AccessorIndex accessorIndex,
            String ownerFqn,
            String methodName) {
        if (context == null || accessorIndex == null || ownerFqn == null || methodName == null) {
            return List.of();
        }
        Set<String> deduped = new LinkedHashSet<>();
        Optional<AccessorSummary> indexed = accessorIndex.lookup(ownerFqn, methodName);
        if (indexed.isPresent() && indexed.get().isGetter()) {
            if (indexed.get().kind() == AccessorKind.CONSTANT_GETTER) {
                deduped.addAll(extractReturnConstants(context, ownerFqn, methodName));
            } else if (indexed.get().fieldName() != null) {
                deduped.addAll(extractFieldInitializerConstants(
                        context, ownerFqn, indexed.get().fieldName()));
            }
        }
        if (deduped.isEmpty()) {
            deduped.addAll(extractReturnConstants(context, ownerFqn, methodName));
        }
        return List.copyOf(deduped);
    }

    private static List<String> extractReturnConstants(
            CodebaseContext context,
            String ownerFqn,
            String methodName) {
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(ownerFqn);
        if (typeDeclaration == null) {
            return List.of();
        }
        MethodDeclaration methodDeclaration = context.findMethodDeclaration(typeDeclaration, methodName, true);
        if (methodDeclaration == null || methodDeclaration.getBody() == null) {
            return List.of();
        }
        List<String> constants = new ArrayList<>();
        ConstantResolver constantResolver = context.getConstantResolver();
        methodDeclaration.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement node) {
                Expression expression = node.getExpression();
                if (expression != null) {
                    addResolvedExpression(constantResolver, context, expression, constants);
                }
                return false;
            }
        });
        return constants;
    }

    private static List<String> extractFieldInitializerConstants(
            CodebaseContext context,
            String ownerFqn,
            String fieldName) {
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(ownerFqn);
        if (typeDeclaration == null) {
            return List.of();
        }
        List<String> constants = new ArrayList<>();
        collectFieldInitializerConstants(context, typeDeclaration, fieldName, constants);
        return constants;
    }

    private static void collectFieldInitializerConstants(
            CodebaseContext context,
            TypeDeclaration typeDeclaration,
            String fieldName,
            List<String> constants) {
        ConstantResolver constantResolver = context.getConstantResolver();
        for (FieldDeclaration fieldDeclaration : typeDeclaration.getFields()) {
            for (Object fragmentObj : fieldDeclaration.fragments()) {
                if (fragmentObj instanceof VariableDeclarationFragment fragment
                        && fragment.getName().getIdentifier().equals(fieldName)
                        && fragment.getInitializer() != null) {
                    addResolvedExpression(
                            constantResolver, context, fragment.getInitializer(), constants);
                }
            }
        }
        if (constants.isEmpty()) {
            String superFqn = context.getSuperclassFqn(typeDeclaration);
            if (superFqn != null && !"java.lang.Object".equals(superFqn)) {
                TypeDeclaration superType = context.getTypeDeclaration(superFqn);
                if (superType != null) {
                    collectFieldInitializerConstants(context, superType, fieldName, constants);
                }
            }
        }
    }

    private static void addResolvedExpression(
            ConstantResolver constantResolver,
            CodebaseContext context,
            Expression expression,
            List<String> constants) {
        if (constantResolver == null || expression == null) {
            return;
        }
        String resolved = constantResolver.resolve(expression, context);
        if (resolved == null) {
            return;
        }
        if (resolved.startsWith("ENUM_SET:")) {
            for (String value : resolved.substring(9).split(",")) {
                addUnique(constants, parseEnumSetElement(value));
            }
        } else if (resolved.startsWith("MAP:")) {
            List<String> mapValues = ConstantResolver.decodeMapLiteralValues(resolved);
            if (mapValues != null) {
                for (String mapValue : mapValues) {
                    addUnique(constants, mapValue);
                }
            }
        } else if (resolved.startsWith("ARRAY:")) {
            for (String arrayValue : resolved.substring(6).split("\\|", -1)) {
                addUnique(constants, arrayValue);
            }
        } else {
            addUnique(constants, resolved);
        }
    }

    private static String parseEnumSetElement(String value) {
        int lastDot = value.lastIndexOf('.');
        int secondLastDot = lastDot > 0 ? value.lastIndexOf('.', lastDot - 1) : -1;
        return secondLastDot >= 0 ? value.substring(secondLastDot + 1) : value;
    }

    private static void addUnique(List<String> constants, String value) {
        if (value != null && !constants.contains(value)) {
            constants.add(value);
        }
    }
}
