package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstraintClauses;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Carrier-aware constraint helpers shared by call-graph filtering and transition linking.
 * Payload/event binding clauses are classified by primary identifier via {@link EventCarrierPolicy},
 * not by a parallel name-alternation regex.
 */
public final class CarrierConstraintSupport {

    private CarrierConstraintSupport() {
    }

    /**
     * Removes AND-clauses whose primary identifier is an event/payload carrier,
     * leaving machine-domain discriminators for {@link BooleanConstraintEvaluator#isCompatibleWithMachineDomain}.
     */
    public static String stripNonMachineClauses(String constraint) {
        if (constraint == null || constraint.isBlank()) {
            return constraint;
        }
        List<String> kept = new ArrayList<>();
        for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
            String ident = ConstraintClauses.primaryIdent(clause);
            if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) {
                kept.add("true");
                continue;
            }
            kept.add(clause.trim());
        }
        return joinAndTidy(kept);
    }

    /**
     * True when the constraint contradicts entry/path bindings on overlapping carrier keys.
     * Unrelated locals (e.g. {@code command} vs binding {@code commandKey}) are ignored.
     */
    public static boolean contradictsCarrierBindings(String constraint, Map<String, String> bindings) {
        if (constraint == null || constraint.isBlank() || bindings == null || bindings.isEmpty()) {
            return false;
        }
        Set<String> carrierIdentsInConstraint = carrierPrimaryIdents(constraint);
        if (carrierIdentsInConstraint.isEmpty()) {
            return false;
        }
        Map<String, String> carrierBindings = new LinkedHashMap<>();
        for (Map.Entry<String, String> entry : bindings.entrySet()) {
            String key = entry.getKey();
            if (key == null || !EventCarrierPolicy.isCarrierParamName(key)) {
                continue;
            }
            if (!containsIgnoreCase(carrierIdentsInConstraint, key)) {
                continue;
            }
            carrierBindings.put(key, entry.getValue());
        }
        if (carrierBindings.isEmpty()) {
            return false;
        }
        String carrierConstraint = carrierOnlyConstraint(constraint, carrierBindings.keySet());
        if (carrierConstraint == null || carrierConstraint.isBlank()) {
            return false;
        }
        return !BooleanConstraintEvaluator.isCompatibleWithKnownBindings(carrierConstraint, carrierBindings);
    }

    /**
     * Keeps AND-clauses whose primary identifier overlaps {@code carrierKeys}; machine-domain
     * discriminators and unrelated locals are dropped so carrier binding checks are not skewed
     * by if-else negations on other parameters.
     */
    static String carrierOnlyConstraint(String constraint, Set<String> carrierKeys) {
        if (constraint == null || constraint.isBlank() || carrierKeys == null || carrierKeys.isEmpty()) {
            return "";
        }
        List<String> kept = new ArrayList<>();
        for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
            String ident = ConstraintClauses.primaryIdent(clause);
            if (ident == null || !containsIgnoreCase(carrierKeys, ident)) {
                continue;
            }
            kept.add(clause.trim());
        }
        return joinAndTidy(kept);
    }

    private static Set<String> carrierPrimaryIdents(String constraint) {
        Set<String> idents = new LinkedHashSet<>();
        for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
            String ident = ConstraintClauses.primaryIdent(clause);
            if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) {
                idents.add(ident);
            }
        }
        return idents;
    }

    private static boolean containsIgnoreCase(Set<String> idents, String key) {
        for (String ident : idents) {
            if (ident.equalsIgnoreCase(key)) {
                return true;
            }
        }
        return false;
    }

    private static String joinAndTidy(List<String> clauses) {
        if (clauses.isEmpty()) {
            return "";
        }
        String joined = String.join(" && ", clauses);
        joined = joined.replaceAll("\\s+", " ").trim();
        joined = joined.replaceAll("(?:^|\\s)&&\\s*", " && ");
        joined = joined.replaceAll("^\\s*&&\\s*", "");
        joined = joined.replaceAll("\\s*&&\\s*$", "");
        return joined.trim();
    }
}
