package click.kamil.springstatemachineexporter.analysis.enricher;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
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.service.ExternalTriggerPolicy;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;

import java.util.List;

/**
 * Central rules for transition linking outcomes derived from existing trigger evidence only.
 */
public final class CallChainLinkPolicy {

    private CallChainLinkPolicy() {
    }

    public static boolean shouldFailClosedOnAmbiguousCallGraphWiden(TriggerPoint trigger) {
        if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) {
            return false;
        }
        List<String> poly = trigger.getPolymorphicEvents();
        if (poly == null || poly.size() <= 1) {
            return false;
        }
        String event = trigger.getEvent();
        if (event != null && event.startsWith("ENUM_SET:")) {
            return true;
        }
        if (event != null && event.contains(".valueOf(")) {
            return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
                    && !isTrustedEnumPolymorphicWiden(trigger);
        }
        return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
                && event != null;
    }

    /**
     * True when every concrete polymorphic candidate belongs to the trigger's declared event type.
     * Used to allow machine-scoped symbolic expansion for generic dispatchers while rejecting
     * cross-package or unqualified widens.
     */
    static boolean isTrustedEnumPolymorphicWiden(TriggerPoint trigger) {
        if (trigger == null) {
            return false;
        }
        String eventTypeFqn = trigger.getEventTypeFqn();
        if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
            return false;
        }
        List<String> poly = trigger.getPolymorphicEvents();
        if (poly == null || poly.size() <= 1) {
            return false;
        }
        for (String pe : poly) {
            if (pe == null || pe.startsWith("<SYMBOLIC:") || pe.startsWith("ENUM_SET:") || !pe.contains(".")) {
                return false;
            }
            String enumType = pe.substring(0, pe.lastIndexOf('.'));
            if (!MachineEnumCanonicalizer.enumTypesMatch(eventTypeFqn, enumType)) {
                return false;
            }
        }
        return true;
    }

    public static LinkResolution resolveLinkResolution(
            TriggerPoint trigger,
            List<MatchedTransition> matched,
            boolean ambiguousSource) {
        if (trigger != null && trigger.isExternal()) {
            return LinkResolution.UNRESOLVED_EXTERNAL;
        }
        if (ambiguousSource
                || (trigger != null && trigger.isAmbiguous())
                || shouldFailClosedOnAmbiguousCallGraphWiden(trigger)) {
            return LinkResolution.AMBIGUOUS_WIDEN;
        }
        if (matched != null && !matched.isEmpty()) {
            return LinkResolution.RESOLVED;
        }
        return LinkResolution.NO_MATCH;
    }

    public static TriggerPoint applyRestExternalPolicy(TriggerPoint trigger, CallChain chain, CodebaseContext context) {
        if (trigger == null || chain.getEntryPoint() == null) {
            return trigger;
        }
        EntryPoint entryPoint = chain.getEntryPoint();
        String entryMethod = entryPoint.getClassName() + "." + entryPoint.getMethodName();
        boolean external = ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, entryMethod, trigger.getEvent(), context);
        if (external == trigger.isExternal()) {
            return trigger;
        }
        return trigger.toBuilder().external(external).build();
    }
}
