package click.kamil.springstatemachineexporter.analysis.index;

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

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Slf4j
public final class FieldTypeIndexBuilder {

    private CodebaseContext context;

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

        Map<String, Map<String, String>> direct = new HashMap<>();
        for (String ownerFqn : context.getIndexedTypeFqns()) {
            TypeDeclaration typeDeclaration = context.getTypeDeclaration(ownerFqn);
            if (typeDeclaration != null) {
                indexDeclaredFields(ownerFqn, typeDeclaration, direct);
            }
        }

        Map<String, Map<String, String>> effective = new HashMap<>();
        int fieldCount = 0;
        Map<String, Map<String, String>> cache = new HashMap<>();
        for (String ownerFqn : direct.keySet()) {
            Map<String, String> merged = mergeEffective(ownerFqn, direct, cache, new HashSet<>());
            if (!merged.isEmpty()) {
                effective.put(ownerFqn, merged);
                fieldCount += merged.size();
            }
        }

        log.info("Built field-type index: {} fields across {} types", fieldCount, effective.size());
        this.context = null;
        return new FieldTypeIndex(effective, fieldCount);
    }

    private void indexDeclaredFields(
            String ownerFqn,
            TypeDeclaration typeDeclaration,
            Map<String, Map<String, String>> direct) {
        Map<String, String> fields = direct.computeIfAbsent(ownerFqn, ignored -> new HashMap<>());
        for (FieldDeclaration fieldDeclaration : typeDeclaration.getFields()) {
            String fieldType = fieldDeclaration.getType().toString();
            for (Object fragmentObj : fieldDeclaration.fragments()) {
                if (fragmentObj instanceof VariableDeclarationFragment fragment) {
                    fields.put(fragment.getName().getIdentifier(), fieldType);
                }
            }
        }
        for (TypeDeclaration nested : typeDeclaration.getTypes()) {
            indexDeclaredFields(context.getFqn(nested), nested, direct);
        }
    }

    private Map<String, String> mergeEffective(
            String ownerFqn,
            Map<String, Map<String, String>> direct,
            Map<String, Map<String, String>> cache,
            Set<String> visiting) {
        if (cache.containsKey(ownerFqn)) {
            return cache.get(ownerFqn);
        }
        if (!visiting.add(ownerFqn)) {
            return Map.of();
        }

        Map<String, String> merged = new HashMap<>();
        TypeDeclaration ownerType = context.getTypeDeclaration(ownerFqn);
        if (ownerType != null) {
            String superFqn = context.getSuperclassFqn(ownerType);
            if (superFqn != null && !"java.lang.Object".equals(superFqn)) {
                merged.putAll(mergeEffective(superFqn, direct, cache, visiting));
            }
        }

        Map<String, String> own = direct.get(ownerFqn);
        if (own != null) {
            merged.putAll(own);
        }

        visiting.remove(ownerFqn);
        cache.put(ownerFqn, Map.copyOf(merged));
        return merged;
    }
}
