package click.kamil.springstatemachineexporter.analysis.index;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Slf4j
public final class PolymorphicMethodExpansionIndexBuilder {

    public PolymorphicMethodExpansionIndex build(CodebaseContext context) {
        if (context == null) {
            return PolymorphicMethodExpansionIndex.empty();
        }

        Map<String, Map<String, List<String>>> entries = new HashMap<>();
        int entryCount = 0;

        for (String typeFqn : context.getIndexedTypeFqns()) {
            TypeDeclaration typeDeclaration = context.getTypeDeclaration(typeFqn);
            if (!isPolymorphicType(typeDeclaration)) {
                continue;
            }
            List<String> implementations = context.getImplementations(typeFqn);
            if (implementations.isEmpty()) {
                continue;
            }

            Set<String> methodNames = collectMethodNames(context, typeDeclaration, implementations);
            for (String methodName : methodNames) {
                List<String> expanded = new ArrayList<>();
                for (String implementationFqn : implementations) {
                    expanded.add(implementationFqn + "." + methodName);
                }
                entries.computeIfAbsent(typeFqn, ignored -> new HashMap<>())
                        .put(methodName, List.copyOf(expanded));
                entryCount++;
            }
        }

        log.info("Built polymorphic method expansion index: {} entries across {} types",
                entryCount, entries.size());
        return new PolymorphicMethodExpansionIndex(entries, entryCount);
    }

    private static Set<String> collectMethodNames(
            CodebaseContext context,
            TypeDeclaration ownerType,
            List<String> implementations) {
        Set<String> methodNames = new LinkedHashSet<>();
        if (ownerType != null) {
            for (var method : ownerType.getMethods()) {
                methodNames.add(method.getName().getIdentifier());
            }
        }
        for (String implementationFqn : implementations) {
            TypeDeclaration implementationType = context.getTypeDeclaration(implementationFqn);
            if (implementationType == null) {
                continue;
            }
            for (var method : implementationType.getMethods()) {
                methodNames.add(method.getName().getIdentifier());
            }
        }
        return methodNames;
    }

    private static boolean isPolymorphicType(TypeDeclaration typeDeclaration) {
        return typeDeclaration != null
                && (typeDeclaration.isInterface()
                || Modifier.isAbstract(typeDeclaration.getModifiers()));
    }
}
