package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;

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

/**
 * Narrows generic superclass collaborator fields to the concrete type supplied by a subclass
 * constructor via {@code super(...)} delegation.
 */
public final class ConstructorInjectionTypeNarrower {

    private ConstructorInjectionTypeNarrower() {
    }

    public static String resolveConcreteReceiverTypeFqn(
            IVariableBinding fieldBinding,
            TypeDeclaration callSiteType,
            CodebaseContext context,
            InjectionPointAnalyzer injectionAnalyzer,
            ConstructorAnalyzer constructorAnalyzer) {
        if (fieldBinding == null
                || !fieldBinding.isField()
                || context == null
                || constructorAnalyzer == null
                || fieldBinding.getDeclaringClass() == null) {
            return null;
        }

        String fieldName = fieldBinding.getName();
        String declaringFqn = fieldBinding.getDeclaringClass().getErasure().getQualifiedName();
        if (declaringFqn == null || declaringFqn.isBlank() || fieldName == null || fieldName.isBlank()) {
            return null;
        }

        Set<String> concreteTypes = new LinkedHashSet<>();
        for (String candidateFqn : candidateSubclassFqns(callSiteType, declaringFqn, context)) {
            TypeDeclaration candidateTd = context.getTypeDeclaration(candidateFqn);
            if (candidateTd == null || context.isSubtypeOrSame(candidateFqn, declaringFqn) == false) {
                continue;
            }
            if (candidateTd.isInterface() || org.eclipse.jdt.core.dom.Modifier.isAbstract(candidateTd.getModifiers())) {
                continue;
            }
            String narrowed = constructorAnalyzer.resolveSubclassConstructorParameterFqnForInheritedField(
                    candidateTd, fieldName, injectionAnalyzer);
            if (narrowed != null && !narrowed.isBlank()) {
                concreteTypes.add(narrowed);
            }
        }

        return concreteTypes.size() == 1 ? concreteTypes.iterator().next() : null;
    }

    private static Set<String> candidateSubclassFqns(
            TypeDeclaration callSiteType,
            String declaringFqn,
            CodebaseContext context) {
        Set<String> candidates = new LinkedHashSet<>();
        if (callSiteType != null) {
            String callSiteFqn = context.getFqn(callSiteType);
            if (callSiteFqn != null && !callSiteFqn.isBlank() && !callSiteFqn.equals(declaringFqn)) {
                candidates.add(callSiteFqn);
            }
        }
        List<String> implementations = context.getImplementations(declaringFqn);
        if (implementations != null) {
            candidates.addAll(implementations);
        }
        return candidates;
    }

    public static String resolveConcreteReceiverTypeFqnFromBinding(
            IVariableBinding fieldBinding,
            ITypeBinding callSiteTypeBinding,
            CodebaseContext context,
            InjectionPointAnalyzer injectionAnalyzer,
            ConstructorAnalyzer constructorAnalyzer) {
        if (callSiteTypeBinding == null) {
            return resolveConcreteReceiverTypeFqn(
                    fieldBinding, null, context, injectionAnalyzer, constructorAnalyzer);
        }
        String callSiteFqn = callSiteTypeBinding.getErasure().getQualifiedName();
        TypeDeclaration callSiteTd = context != null ? context.getTypeDeclaration(callSiteFqn) : null;
        return resolveConcreteReceiverTypeFqn(
                fieldBinding, callSiteTd, context, injectionAnalyzer, constructorAnalyzer);
    }
}
