package click.kamil.springstatemachineexporter.analysis.enricher.routing;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;

/**
 * Decides whether a call chain is provably shared infrastructure that may intentionally
 * attach to multiple state machines when they share the same transition event.
 * <p>
 * Shared infrastructure requires source-derived evidence only: no <em>concrete</em> machine enum
 * types at the sendEvent site and no vertical domain ownership in the call chain classes/packages.
 * Unbound generic parameters ({@code StateMachine<S,E>} on a shared base class) and erasure
 * ({@code Object}) are treated as absent — not as proven types. Event-name matching alone is never
 * sufficient outside this policy.
 */
public final class SharedServiceRoutingPolicy {

    private SharedServiceRoutingPolicy() {
    }

    /**
     * @return true when the chain may multi-attach to every machine that shares its transition event
     */
    public static boolean isProvablySharedInfrastructure(CallChain chain) {
        if (chain == null) {
            return false;
        }
        MachineRoutingEvidence evidence = MachineRoutingEvidence.from(chain);
        if (isConcreteMachineType(evidence.eventTypeFqn()) || isConcreteMachineType(evidence.stateTypeFqn())) {
            return false;
        }
        return !PackageNameRoutingHeuristics.hasVerticalDomainOwnership(chain);
    }

    /**
     * True when a trigger type FQN is a real state/event enum (or similar), not an unbound
     * type variable / erasure placeholder from a shared generic base class.
     */
    public static boolean isConcreteMachineType(String typeFqn) {
        if (typeFqn == null || typeFqn.isBlank()) {
            return false;
        }
        String erased = typeFqn;
        int generic = erased.indexOf('<');
        if (generic >= 0) {
            erased = erased.substring(0, generic);
        }
        if (erased.length() == 1 && Character.isUpperCase(erased.charAt(0))) {
            return false;
        }
        return !"java.io.Serializable".equals(erased) && !"Serializable".equals(erased)
                && !isNonConcreteMachineErasure(erased);
    }

    /**
     * Prefer a concrete trigger type; otherwise adopt the machine type only when it is also
     * concrete. Non-concrete machine types ({@code String}/{@code Object}/type-vars) must not be
     * stamped onto triggers — that makes multi-machine affinity treat them as ambiguous shared
     * generics and fail-closed, wiping package-proven String-machine chains.
     */
    public static String concreteOrFallback(String triggerTypeFqn, String machineTypeFqn) {
        if (isConcreteMachineType(triggerTypeFqn)) {
            return triggerTypeFqn;
        }
        if (isConcreteMachineType(machineTypeFqn)) {
            return machineTypeFqn;
        }
        return triggerTypeFqn;
    }

    private static boolean isNonConcreteMachineErasure(String erased) {
        return "java.lang.Object".equals(erased) || "Object".equals(erased)
                || "java.lang.String".equals(erased) || "String".equals(erased);
    }
}
