package click.kamil.springstatemachineexporter.analysis.pipeline;

import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * Chooses which argument of a send-like call is the event expression.
 * Fail-closed when multi-arg shapes are ambiguous.
 */
public final class EventArgumentSelector {

    private EventArgumentSelector() {
    }

    /**
     * @return the event argument expression, or empty when none / ambiguous
     */
    public static Optional<Expression> selectEventArgument(MethodInvocation call) {
        if (call == null || call.arguments().isEmpty()) {
            return Optional.empty();
        }
        List<Expression> args = new ArrayList<>();
        for (Object argObj : call.arguments()) {
            if (argObj instanceof Expression expr) {
                args.add(expr);
            }
        }
        if (args.isEmpty()) {
            return Optional.empty();
        }
        if (args.size() == 1) {
            return Optional.of(args.get(0));
        }

        Expression arg0 = args.get(0);
        Expression arg1 = args.get(1);

        if (looksLikeHeaders(arg0)) {
            for (int i = 1; i < args.size(); i++) {
                if (looksLikeEvent(args.get(i))) {
                    return Optional.of(args.get(i));
                }
            }
        }

        if (looksLikeEvent(arg0) && looksLikeState(arg1)) {
            return Optional.of(arg0);
        }

        Expression soleEnumOrMessage = null;
        int hits = 0;
        for (Expression arg : args) {
            if (looksLikeEnumOrMessageType(arg)) {
                hits++;
                soleEnumOrMessage = arg;
            }
        }
        if (hits == 1) {
            return Optional.of(soleEnumOrMessage);
        }

        return Optional.empty();
    }

    /** True when {@code selected} is the same AST node as {@code call}'s first argument. */
    public static boolean selectedIsFirstArgument(MethodInvocation call, Expression selected) {
        if (call == null || selected == null || call.arguments().isEmpty()) {
            return false;
        }
        return call.arguments().get(0) == selected;
    }

    static boolean looksLikeHeaders(Expression expr) {
        if (expr == null) {
            return false;
        }
        String name = simpleNameOf(expr);
        if (name != null) {
            String lower = name.toLowerCase(Locale.ROOT);
            if (lower.contains("header") || "headers".equals(lower) || "hdrs".equals(lower)) {
                return true;
            }
        }
        String type = typeSimpleName(expr);
        if (type != null) {
            String lower = type.toLowerCase(Locale.ROOT);
            if (lower.contains("header") || "map".equals(lower) || lower.endsWith("map")) {
                return true;
            }
        }
        return false;
    }

    static boolean looksLikeEvent(Expression expr) {
        if (expr == null) {
            return false;
        }
        if (looksLikeEnumOrMessageType(expr)) {
            return true;
        }
        String name = simpleNameOf(expr);
        if (name != null) {
            String lower = name.toLowerCase(Locale.ROOT);
            if ("event".equals(lower)
                    || "payload".equals(lower)
                    || "e".equals(lower)
                    || "ev".equals(lower)
                    || lower.endsWith("event")
                    || lower.endsWith("payload")) {
                return true;
            }
        }
        String text = expr.toString();
        return text != null && (text.contains("valueOf") || text.contains("Event."));
    }

    static boolean looksLikeState(Expression expr) {
        if (expr == null) {
            return false;
        }
        String name = simpleNameOf(expr);
        if (name != null) {
            String lower = name.toLowerCase(Locale.ROOT);
            if ("state".equals(lower)
                    || "source".equals(lower)
                    || "sourcestate".equals(lower)
                    || "current".equals(lower)
                    || lower.endsWith("state")) {
                return true;
            }
        }
        String type = typeSimpleName(expr);
        if (type != null) {
            String lower = type.toLowerCase(Locale.ROOT);
            if (lower.contains("state") && !lower.contains("statemachine")) {
                return true;
            }
        }
        String text = expr.toString();
        return text != null && text.contains("State.");
    }

    static boolean looksLikeEnumOrMessageType(Expression expr) {
        if (expr == null) {
            return false;
        }
        ITypeBinding binding = expr.resolveTypeBinding();
        if (binding != null) {
            if (binding.isEnum()) {
                return true;
            }
            String qn = binding.getQualifiedName();
            if (qn != null && (qn.equals("org.springframework.messaging.Message")
                    || qn.startsWith("org.springframework.messaging.Message<"))) {
                return true;
            }
            String simple = binding.getName();
            if (simple != null && "Message".equals(simple)) {
                return true;
            }
        }
        String type = typeSimpleName(expr);
        if (type != null && ("Message".equals(type) || type.endsWith("Message"))) {
            return true;
        }
        return textSuggestsEnumConstant(expr);
    }

    private static boolean textSuggestsEnumConstant(Expression expr) {
        String text = expr.toString();
        return text != null && (text.contains(".valueOf")
                || text.matches(".*\\b[A-Z][A-Za-z0-9_]*\\.[A-Z][A-Z0-9_]+\\b.*"));
    }

    private static String simpleNameOf(Expression expr) {
        if (expr instanceof SimpleName sn) {
            return sn.getIdentifier();
        }
        return null;
    }

    private static String typeSimpleName(Expression expr) {
        ITypeBinding binding = expr.resolveTypeBinding();
        if (binding != null) {
            String name = binding.getName();
            if (name != null && !name.isBlank()) {
                int generic = name.indexOf('<');
                return generic >= 0 ? name.substring(0, generic) : name;
            }
            String qn = binding.getErasure().getQualifiedName();
            if (qn != null && qn.contains(".")) {
                return qn.substring(qn.lastIndexOf('.') + 1);
            }
            return qn;
        }
        return null;
    }
}
