package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.analysis.validation.AnalysisCanonicalFormValidator;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Transition;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Re-applies machine-scoped enum canonicalization and validates the analysis model.
 * Must run after property resolution so placeholders cannot bypass validation.
 */
public final class AnalysisResultFinalizer {

    private AnalysisResultFinalizer() {
    }

    public static void finalizeResult(AnalysisResult result, CodebaseContext context) {
        if (result == null) {
            return;
        }
        StateMachineTypeResolver.MachineTypes machineTypes = JsonExportContextFactory.resolveMachineTypes(
                result.getName(),
                result.getStateTypeFqn(),
                result.getEventTypeFqn(),
                context);
        finalizeResult(result, context, machineTypes);
    }

    public static void finalizeResult(
            AnalysisResult result,
            CodebaseContext context,
            StateMachineTypeResolver.MachineTypes machineTypes) {
        if (result == null || machineTypes == null) {
            return;
        }

        persistMachineTypes(result, machineTypes);
        applyCanonicalizationFields(result, context, machineTypes);

        if (context != null) {
            AnalysisCanonicalFormValidator.enforce(result, context);
        } else {
            AnalysisCanonicalFormValidator.enforceWithMachineTypes(result, machineTypes);
        }
    }

    private static void persistMachineTypes(
            AnalysisResult result,
            StateMachineTypeResolver.MachineTypes machineTypes) {
        result.setStateTypeFqn(machineTypes.stateTypeFqn());
        result.setEventTypeFqn(machineTypes.eventTypeFqn());
    }

    /**
     * Canonicalizes transitions, states, triggers, and matched transitions without validation.
     * Safe to call before transition relinking when machine types are known.
     */
    public static void applyCanonicalization(
            AnalysisResult result,
            StateMachineTypeResolver.MachineTypes machineTypes) {
        applyCanonicalization(result, null, machineTypes);
    }

    public static void applyCanonicalization(
            AnalysisResult result,
            CodebaseContext context,
            StateMachineTypeResolver.MachineTypes machineTypes) {
        if (result == null || machineTypes == null) {
            return;
        }
        if (machineTypes.stateTypeFqn() == null && machineTypes.eventTypeFqn() == null) {
            return;
        }
        applyCanonicalizationFields(result, context, machineTypes);
    }

    private static void applyCanonicalizationFields(
            AnalysisResult result,
            CodebaseContext context,
            StateMachineTypeResolver.MachineTypes machineTypes) {
        MachineEnumCanonicalizer.canonicalizeTransitions(result.getTransitions(), machineTypes, context);

        if (result.getStates() != null) {
            result.setStates(MachineEnumCanonicalizer.canonicalizeStates(
                    result.getStates(), machineTypes.stateTypeFqn(), context));
        }
        result.setStartStates(MachineEnumCanonicalizer.canonicalizeStateLabels(
                result.getStartStates(), machineTypes.stateTypeFqn(), context));
        result.setEndStates(MachineEnumCanonicalizer.canonicalizeStateLabels(
                result.getEndStates(), machineTypes.stateTypeFqn(), context));

        if (result.getMetadata() != null) {
            CodebaseMetadata metadata = result.getMetadata();
            List<TriggerPoint> triggers = canonicalizeTriggers(metadata.getTriggers(), context, machineTypes);
            List<CallChain> callChains = canonicalizeCallChains(
                    metadata.getCallChains(), context, machineTypes, result.getTransitions());
            result.setMetadata(CodebaseMetadata.builder()
                    .triggers(triggers)
                    .entryPoints(metadata.getEntryPoints())
                    .callChains(callChains)
                    .properties(metadata.getProperties())
                    .build());
        }
    }

    private static List<TriggerPoint> canonicalizeTriggers(
            List<TriggerPoint> triggers,
            CodebaseContext context,
            StateMachineTypeResolver.MachineTypes machineTypes) {
        if (triggers == null) {
            return null;
        }
        return triggers.stream()
                .map(trigger -> MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
                        trigger, machineTypes, context))
                .collect(Collectors.toList());
    }

    private static List<CallChain> canonicalizeCallChains(
            List<CallChain> callChains,
            CodebaseContext context,
            StateMachineTypeResolver.MachineTypes machineTypes,
            List<Transition> transitions) {
        if (callChains == null) {
            return null;
        }
        return callChains.stream()
                .map(chain -> {
                    TriggerPoint trigger = chain.getTriggerPoint() == null
                            ? null
                            : MachineEnumCanonicalizer.prepareCallChainTrigger(
                                    chain.getTriggerPoint(),
                                    machineTypes,
                                    context,
                                    transitions,
                                    chain.getPathBindings());
                    if (trigger != null) {
                        trigger = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(
                                trigger, machineTypes, context);
                    }
                    List<MatchedTransition> matched = canonicalizeMatchedTransitions(
                            chain.getMatchedTransitions(), machineTypes, context);
                    return chain.toBuilder()
                            .triggerPoint(trigger)
                            .matchedTransitions(matched)
                            .build();
                })
                .collect(Collectors.toList());
    }

    private static List<MatchedTransition> canonicalizeMatchedTransitions(
            List<MatchedTransition> matchedTransitions,
            StateMachineTypeResolver.MachineTypes machineTypes,
            CodebaseContext context) {
        if (matchedTransitions == null) {
            return null;
        }
        return matchedTransitions.stream()
                .map(matched -> MatchedTransition.builder()
                        .event(MachineEnumCanonicalizer.canonicalizeLabel(
                                matched.getEvent(), machineTypes.eventTypeFqn(), context))
                        .sourceState(MachineEnumCanonicalizer.canonicalizeLabel(
                                matched.getSourceState(), machineTypes.stateTypeFqn(), context))
                        .targetState(MachineEnumCanonicalizer.canonicalizeLabel(
                                matched.getTargetState(), machineTypes.stateTypeFqn(), context))
                        .build())
                .collect(Collectors.toList());
    }
}
