package click.kamil.springstatemachineexporter.analysis.enricher;

import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.Transition;

import java.util.ArrayList;
import java.util.List;

/**
 * Filters codebase-wide analysis artifacts to those relevant for a single state machine config.
 */
public final class MachineScopeFilter {

    private static final BeanResolutionEngine ROUTING = new HeuristicBeanResolutionEngine();

    private MachineScopeFilter() {
    }

    public static List<TriggerPoint> filterTriggersForMachine(
            List<TriggerPoint> triggers, String machineName, CodebaseContext context) {
        return filterTriggersForMachine(triggers, machineName, context, null);
    }

    public static List<TriggerPoint> filterTriggersForMachine(
            List<TriggerPoint> triggers,
            String machineName,
            CodebaseContext context,
            List<Transition> machineTransitions) {
        if (triggers == null || machineName == null) {
            return triggers == null ? List.of() : triggers;
        }
        List<TriggerPoint> filtered = new ArrayList<>();
        for (TriggerPoint trigger : triggers) {
            if (isTriggerForMachine(trigger, machineName, context, machineTransitions)) {
                filtered.add(trigger);
            }
        }
        return filtered;
    }

    public static List<CallChain> filterCallChainsForMachine(
            List<CallChain> chains, String machineName, CodebaseContext context) {
        return filterCallChainsForMachine(chains, machineName, context, null);
    }

    public static List<CallChain> filterCallChainsForMachine(
            List<CallChain> chains,
            String machineName,
            CodebaseContext context,
            List<Transition> machineTransitions) {
        if (chains == null || machineName == null) {
            return chains == null ? List.of() : chains;
        }
        List<CallChain> filtered = new ArrayList<>();
        String machineEventTypeFqn = resolveMachineEventTypeFqn(machineName, context, machineTransitions);
        for (CallChain chain : chains) {
            if (ROUTING.hasProvenMachineAffinity(chain, machineName, context, machineTransitions)) {
                filtered.add(chain);
                continue;
            }
            TriggerPoint trigger = chain.getTriggerPoint();
            if (trigger != null
                    && machineEventTypeFqn != null
                    && CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, context)) {
                filtered.add(chain);
            }
        }
        return filtered;
    }

    private static String resolveMachineEventTypeFqn(
            String machineName,
            CodebaseContext context,
            List<Transition> machineTransitions) {
        if (context != null && machineName != null) {
            String[] types = StateMachineTypeResolver.resolve(machineName, context);
            if (types != null && types.length > 1 && types[1] != null && !types[1].isBlank()) {
                return types[1];
            }
        }
        if (machineTransitions == null) {
            return null;
        }
        for (Transition transition : machineTransitions) {
            Event event = transition.getEvent();
            if (event == null) {
                continue;
            }
            String fullIdentifier = event.fullIdentifier() != null ? event.fullIdentifier() : event.rawName();
            if (fullIdentifier != null && fullIdentifier.contains(".")) {
                return fullIdentifier.substring(0, fullIdentifier.lastIndexOf('.'));
            }
        }
        return null;
    }

    public static List<EntryPoint> filterEntryPointsForMachine(
            List<EntryPoint> entryPoints, String machineName, CodebaseContext context) {
        if (entryPoints == null || machineName == null) {
            return entryPoints == null ? List.of() : entryPoints;
        }
        List<EntryPoint> filtered = new ArrayList<>();
        for (EntryPoint entryPoint : entryPoints) {
            if (isEntryPointForMachine(entryPoint, machineName, context)) {
                filtered.add(entryPoint);
            }
        }
        return filtered;
    }

    private static boolean isEntryPointForMachine(
            EntryPoint entryPoint, String machineName, CodebaseContext context) {
        if (entryPoint == null) {
            return false;
        }
        if (EntryPointScopeResolver.hasPathVariablePlaceholder(entryPoint)) {
            EntryPointScopeResolver.Affinity affinity =
                    EntryPointScopeResolver.resolveAffinity(entryPoint, machineName, context);
            return affinity != EntryPointScopeResolver.Affinity.AGAINST_MACHINE;
        }

        EntryPointScopeResolver.Affinity affinity =
                EntryPointScopeResolver.resolveAffinity(entryPoint, machineName, context);
        if (affinity == EntryPointScopeResolver.Affinity.FOR_MACHINE) {
            return true;
        }
        if (affinity == EntryPointScopeResolver.Affinity.AGAINST_MACHINE) {
            return false;
        }
        return false;
    }

    private static boolean isTriggerForMachine(
            TriggerPoint trigger, String machineName, CodebaseContext context, List<Transition> machineTransitions) {
        if (trigger == null) {
            return false;
        }
        CallChain probe = CallChain.builder().triggerPoint(trigger).methodChain(List.of()).build();
        return ROUTING.hasProvenMachineAffinity(probe, machineName, context, machineTransitions);
    }
}
