package click.kamil.springstatemachineexporter.analysis.pipeline;

import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.StringLiteral;

/**
 * Shared MessageBuilder / GenericMessage / allowlisted-header peels for event identity.
 * Used by {@code EventIdentityResolver}, call-graph unwrap, and {@code GenericEventDetector}.
 */
public final class MessageCarrierSupport {

    private MessageCarrierSupport() {
    }

    /**
     * Prefer allowlisted {@code setHeader("event"|"smEvent", …)} on a MessageBuilder chain;
     * else peel {@code withPayload}/{@code just}/{@code success}; else GenericMessage ctor arg.
     * Also peels {@code message.getPayload()} to the message expression first.
     *
     * @return the event-bearing expression, or null when nothing is proven
     */
    public static Expression peelEventFromMessageExpr(Expression expression) {
        if (expression == null) {
            return null;
        }
        Expression peeled = peel(expression);
        if (peeled instanceof MethodInvocation mi
                && "getPayload".equals(mi.getName().getIdentifier())
                && mi.arguments().isEmpty()
                && mi.getExpression() != null) {
            return peelEventFromMessageExpr(mi.getExpression());
        }
        Expression headerEvent = findSetHeaderValueOnChain(peeled, null);
        if (headerEvent != null) {
            return peel(headerEvent);
        }
        Expression payload = ReactiveExpressionSupport.peelFactoryPayloadExpression(peeled);
        if (payload != null) {
            return peel(payload);
        }
        return peelGenericMessageCtor(peeled);
    }

    public static Expression peelGenericMessageCtor(Expression expression) {
        Expression peeled = peel(expression);
        if (peeled instanceof ClassInstanceCreation cic
                && LibraryUnwrapRegistry.isGenericMessageType(cic.getType().toString())
                && !cic.arguments().isEmpty()
                && cic.arguments().get(0) instanceof Expression arg) {
            return peel(arg);
        }
        return null;
    }

    /**
     * Walk a MessageBuilder chain for {@code setHeader(key, value)}. When {@code requiredKey}
     * is null, returns the first allowlisted event-header value on the chain.
     */
    public static Expression findSetHeaderValueOnChain(Expression expression, String requiredKey) {
        Expression current = peel(expression);
        while (current instanceof MethodInvocation mi) {
            if (LibraryUnwrapRegistry.isHeaderSetterMethodName(mi.getName().getIdentifier())
                    && mi.arguments().size() >= 2) {
                String key = stringLiteralValue(mi.arguments().get(0));
                if (requiredKey != null) {
                    if (requiredKey.equals(key) && mi.arguments().get(1) instanceof Expression value) {
                        return value;
                    }
                } else if (LibraryUnwrapRegistry.isEventHeaderKey(key)
                        && mi.arguments().get(1) instanceof Expression value) {
                    return value;
                }
            }
            if ("build".equals(mi.getName().getIdentifier()) && mi.arguments().isEmpty()
                    && mi.getExpression() != null) {
                current = peel(mi.getExpression());
                continue;
            }
            if (mi.getExpression() instanceof MethodInvocation next) {
                current = next;
            } else {
                break;
            }
        }
        return null;
    }

    private static String stringLiteralValue(Object arg) {
        if (arg instanceof StringLiteral sl) {
            return sl.getLiteralValue();
        }
        return null;
    }

    private static Expression peel(Expression expression) {
        Expression current = expression;
        while (current instanceof ParenthesizedExpression pe) {
            current = pe.getExpression();
        }
        if (current instanceof CastExpression ce) {
            return peel(ce.getExpression());
        }
        return current;
    }
}
