package click.kamil.springstatemachineexporter.exporter;

import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
import click.kamil.springstatemachineexporter.model.TransitionType;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Scxml implements StateMachineExporter {

    private static final String LAMBDA = "λ";

    @Override
    public String export(click.kamil.springstatemachineexporter.analysis.model.AnalysisResult result, ExportOptions options) {
        return export(result.getName(), result.getTransitions(), result.getStartStates(), result.getEndStates(), options);
    }

    @Override
    public String export(String name,
                         List<Transition> transitions,
                         Set<String> startStates,
                         Set<String> endStates,
                         ExportOptions options) {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        sb.append("<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"");
        sb.append(startStates.isEmpty() ? "" : simplify(options.formatStateLabel(startStates.iterator().next())));
        sb.append("\">\n");

        Set<String> allStates = new java.util.LinkedHashSet<>();
        for (Transition t : transitions) {
            t.getSourceStates().forEach(s -> allStates.add(options.formatState(s)));
            t.getTargetStates().forEach(s -> allStates.add(options.formatState(s)));
        }
        startStates.forEach(s -> allStates.add(options.formatStateLabel(s)));
        endStates.forEach(s -> allStates.add(options.formatStateLabel(s)));

        for (String state : allStates) {
            sb.append("  <state id=\"").append(simplify(state)).append("\">\n");

            for (Transition t : transitions) {
                if (t.getSourceStates() == null || t.getSourceStates().stream().noneMatch(s -> options.formatState(s).equals(state)))
                    continue;

                boolean allowNoTarget = t.getType() == TransitionType.JOIN || t.getType() == TransitionType.FORK;
                List<State> targets;

                if (t.getTargetStates() == null || t.getTargetStates().isEmpty()) {
                    if (!allowNoTarget) {
                        targets = List.of(State.of(state)); // self-loop
                    } else {
                        continue;
                    }
                } else {
                    targets = t.getTargetStates();
                }

                for (State target : targets) {
                    sb.append(renderTransition(t, target, options));
                }
            }
            sb.append("  </state>\n");
        }

        sb.append("</scxml>\n");
        return sb.toString();
    }

    @Override
    public String getFileExtension() {
        return ".scxml.xml";
    }

    private String renderTransition(Transition t, State target, ExportOptions options) {
        StringBuilder sb = new StringBuilder();
        sb.append("    <transition target=\"").append(simplify(options.formatState(target))).append("\"");

        if (t.getEvent() != null) {
            String eventStr = options.formatEvent(t.getEvent());
            if (eventStr != null && !eventStr.isBlank()) {
                sb.append(" event=\"").append(escapeXml(eventStr)).append("\"");
            }
        }

        String guard = getGuardText(t, options.isUseLambdaGuards());
        if (!guard.isBlank()) {
            sb.append(" cond=\"").append(escapeXml(guard)).append("\"");
        }

        // Choice/junction order → XML comment
        if ((t.getType() == TransitionType.CHOICE || t.getType() == TransitionType.JUNCTION) && t.getOrder() != null) {
            sb.append(">\n");
            sb.append("      <!-- order=").append(t.getOrder()).append(" -->\n");
            sb.append("    </transition>\n");
        } else {
            sb.append("/>\n");
        }

        return sb.toString();
    }

    private static String getGuardText(Transition t, boolean useLambdaGuards) {
        if (t.getGuards() == null || t.getGuards().isEmpty())
            return "";
        
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < t.getGuards().size(); i++) {
            if (i > 0) sb.append(" && ");
            var guard = t.getGuards().get(i);
            if (useLambdaGuards && guard.isLambda()) {
                sb.append(LAMBDA);
            } else {
                sb.append(guard.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
            }
        }
        return sb.toString();
    }

    private static String escapeXml(String s) {
        if (s == null)
            return "";
        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
                .replace("\"", "&quot;").replace("'", "&apos;");
    }
}
