package click.kamil.springstatemachineexporter.analysis.resolver;

import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Shared AND-clause splitting and primary-identifier extraction for constraint strings
 * emitted by the analysis pipeline (carrier strip, binding compatibility).
 */
public final class ConstraintClauses {

    private static final Pattern EQUALS_CALL = Pattern.compile(
            "(?i)(?:\"[^\"]+\"|'[^']+')\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"
                    + "|"
                    + "(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*(?:\"[^\"]+\"|'[^']+')\\s*\\)");

    private static final Pattern EQ_COMPARE = Pattern.compile(
            "(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*(?:==|!=)\\s*(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)"
                    + "|"
                    + "(?i)(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)\\s*(?:==|!=)\\s*([A-Za-z_][A-Za-z0-9_]*)");

    /**
     * {@code Objects.equals(a, b)} / {@code Objects.equalsIgnoreCase(a, b)} with either side a
     * quoted literal and the other a simple identifier (both argument orders).
     */
    private static final Pattern OBJECTS_EQUALS = Pattern.compile(
            "(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*"
                    + "(?:\"([^\"]+)\"|'([^']+)'|([A-Za-z_][A-Za-z0-9_]*))"
                    + "\\s*,\\s*"
                    + "(?:\"([^\"]+)\"|'([^']+)'|([A-Za-z_][A-Za-z0-9_]*))"
                    + "\\s*\\)");

    private static final Pattern QUOTED_LITERAL = Pattern.compile("\"([^\"]+)\"|'([^']+)'");

    private ConstraintClauses() {
    }

    /**
     * Splits a constraint on top-level {@code &&} (paren-aware).
     */
    public static List<String> splitAndClauses(String constraint) {
        List<String> clauses = new ArrayList<>();
        if (constraint == null || constraint.isBlank()) {
            return clauses;
        }
        StringBuilder current = new StringBuilder();
        int depth = 0;
        for (int i = 0; i < constraint.length(); i++) {
            char c = constraint.charAt(i);
            if (c == '(') {
                depth++;
                current.append(c);
                continue;
            }
            if (c == ')') {
                depth = Math.max(0, depth - 1);
                current.append(c);
                continue;
            }
            if (depth == 0
                    && c == '&'
                    && i + 1 < constraint.length()
                    && constraint.charAt(i + 1) == '&') {
                String piece = current.toString().trim();
                if (!piece.isEmpty()) {
                    clauses.add(piece);
                }
                current.setLength(0);
                i++;
                continue;
            }
            current.append(c);
        }
        String tail = current.toString().trim();
        if (!tail.isEmpty()) {
            clauses.add(tail);
        }
        if (clauses.isEmpty() && !constraint.isBlank()) {
            clauses.add(constraint.trim());
        }
        return clauses;
    }

    /**
     * Primary parameter/local identifier in a comparison or equals clause.
     */
    public static String primaryIdent(String clause) {
        if (clause == null || clause.isBlank()) {
            return null;
        }
        String trimmed = clause.trim();
        Matcher objects = OBJECTS_EQUALS.matcher(trimmed);
        if (objects.find()) {
            String leftIdent = objects.group(3);
            String rightIdent = objects.group(6);
            if (leftIdent != null && looksLikeSimpleIdent(leftIdent)) {
                return leftIdent;
            }
            if (rightIdent != null && looksLikeSimpleIdent(rightIdent)) {
                return rightIdent;
            }
        }
        Matcher equals = EQUALS_CALL.matcher(trimmed);
        if (equals.find()) {
            String a = equals.group(1);
            String b = equals.group(2);
            return a != null ? a : b;
        }
        Matcher eq = EQ_COMPARE.matcher(trimmed);
        if (eq.find()) {
            String a = eq.group(1);
            String b = eq.group(2);
            String left = a != null ? a : b;
            if (left != null && looksLikeSimpleIdent(left)) {
                return left;
            }
        }
        return null;
    }

    /**
     * First quoted string literal in a clause (equals / {@code ==} / {@code Objects.equals}).
     */
    public static String quotedLiteral(String clause) {
        if (clause == null || clause.isBlank()) {
            return null;
        }
        String trimmed = clause.trim();
        Matcher objects = OBJECTS_EQUALS.matcher(trimmed);
        if (objects.find()) {
            String left = firstNonNull(objects.group(1), objects.group(2));
            if (left != null) {
                return left;
            }
            String right = firstNonNull(objects.group(4), objects.group(5));
            if (right != null) {
                return right;
            }
        }
        Matcher quoted = QUOTED_LITERAL.matcher(trimmed);
        if (quoted.find()) {
            return firstNonNull(quoted.group(1), quoted.group(2));
        }
        return null;
    }

    /**
     * Quoted literals from AND-clauses whose primary identifier is an event/payload carrier.
     */
    public static List<String> carrierLiterals(String constraint) {
        List<String> literals = new ArrayList<>();
        if (constraint == null || constraint.isBlank()) {
            return literals;
        }
        for (String clause : splitAndClauses(constraint)) {
            String ident = primaryIdent(clause);
            if (ident == null || !EventCarrierPolicy.isCarrierParamName(ident)) {
                continue;
            }
            String literal = quotedLiteral(clause);
            if (literal != null && !literal.isBlank()) {
                literals.add(literal);
            }
        }
        return literals;
    }

    /**
     * Concrete expander/path-binding values for carrier keys (skips self-keys, placeholders,
     * and method-call shaped values). Used by bound valueOf, unbound-widen gates, and poly-narrow.
     */
    public static List<String> carrierLiteralsFromPathBindings(Map<String, String> pathBindings) {
        if (pathBindings == null || pathBindings.isEmpty()) {
            return List.of();
        }
        LinkedHashSet<String> literals = new LinkedHashSet<>();
        for (Map.Entry<String, String> entry : pathBindings.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if (key == null || value == null || value.isBlank()) {
                continue;
            }
            if (!EventCarrierPolicy.isCarrierParamName(key)) {
                continue;
            }
            if (value.equals(key) || value.contains("{") || value.contains("(")) {
                continue;
            }
            String cleaned = value;
            if (cleaned.startsWith("\"") && cleaned.endsWith("\"") && cleaned.length() >= 2) {
                cleaned = cleaned.substring(1, cleaned.length() - 1);
            }
            if (!cleaned.isBlank()) {
                literals.add(cleaned);
            }
        }
        return new ArrayList<>(literals);
    }

    /**
     * Union of carrier literals from constraint text and path bindings (constraint first).
     */
    public static List<String> boundCarrierLiterals(String constraint, Map<String, String> pathBindings) {
        List<String> fromConstraint = carrierLiterals(constraint);
        if (fromConstraint.size() == 1) {
            return fromConstraint;
        }
        List<String> fromBindings = carrierLiteralsFromPathBindings(pathBindings);
        if (fromBindings.size() == 1) {
            return fromBindings;
        }
        if (!fromConstraint.isEmpty()) {
            return fromConstraint;
        }
        return fromBindings;
    }

    /**
     * Union of primary identifiers across AND clauses (for binding overlap checks).
     */
    public static Set<String> equalityVariables(String constraint) {
        Set<String> vars = new LinkedHashSet<>();
        for (String clause : splitAndClauses(constraint)) {
            String ident = primaryIdent(clause);
            if (ident != null) {
                vars.add(ident);
            }
        }
        return vars;
    }

    private static boolean looksLikeSimpleIdent(String value) {
        if (value == null || value.isBlank() || value.contains(".")) {
            return false;
        }
        char first = value.charAt(0);
        return Character.isJavaIdentifierStart(first)
                && value.chars().allMatch(Character::isJavaIdentifierPart)
                && (Character.isLowerCase(first)
                || EventCarrierPolicy.isCarrierParamName(value)
                || EventCarrierPolicy.isMachineDiscriminatorName(value));
    }

    private static String firstNonNull(String a, String b) {
        return a != null ? a : b;
    }
}
