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.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;

public class PlantUml implements StateMachineExporter {

    private 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("@startuml\n");
        sb.append("!pragma layout smetana\n");
        sb.append("set separator none\n");
        sb.append("hide empty description\n");
        sb.append("hide stereotype\n");
        
        // --- Premium Modern Styles ---
        sb.append("skinparam state {\n");
        sb.append("  BackgroundColor white\n");
        sb.append("  BorderColor #94a3b8\n");
        sb.append("  BorderThickness 1\n");
        sb.append("  FontName Inter\n");
        sb.append("  FontSize 9\n"); // Sleek but readable
        sb.append("  FontStyle bold\n");
        sb.append("  RoundCorner 20\n");
        sb.append("  Padding 1\n"); 
        sb.append("}\n");
        sb.append("skinparam shadowing false\n");
        sb.append("skinparam ArrowFontName JetBrains Mono\n");
        sb.append("skinparam ArrowFontSize 8\n");
        sb.append("skinparam ArrowColor #cbd5e1\n");
        sb.append("skinparam ArrowThickness 1\n");
        sb.append("skinparam dpi 110\n");
        sb.append("skinparam svgLinkTarget _self\n");
        
        sb.append("\n");

        for (String start : startStates) {
            sb.append("[*] --> ").append(simplify(options.formatStateLabel(start))).append("\n");
        }
        sb.append("\n");

        Set<String> statesWithChoice = new java.util.LinkedHashSet<>();
        for (Transition t : transitions) {
            if (t.getType() == TransitionType.CHOICE && t.getSourceStates() != null) {
                for (State source : t.getSourceStates()) {
                    statesWithChoice.add(simplify(options.formatState(source)));
                }
            }
        }

        for (String state : statesWithChoice) {
            if (options.isRenderChoicesAsDiamonds()) {
                sb.append("state ").append(state).append(" <<choice>>\n");
            }
        }

        Set<String> junctionStates = transitions.stream()
                .filter(t -> t.getType() == TransitionType.JUNCTION)
                .flatMap(t -> t.getSourceStates().stream())
                .map(s -> simplify(options.formatState(s)))
                .collect(Collectors.toCollection(java.util.LinkedHashSet::new));
        for (String junction : junctionStates) {
            if (options.isRenderChoicesAsDiamonds()) {
                sb.append("state ").append(junction).append(" <<choice>>\n");
            }
        }

        sb.append("\n");

        for (Transition t : transitions) {
            if (t.getSourceStates() == null || t.getSourceStates().isEmpty())
                continue;

            boolean allowNoTarget = t.getType() == TransitionType.JOIN || t.getType() == TransitionType.FORK;

            List<String> targets;
            if (t.getTargetStates() == null || t.getTargetStates().isEmpty()) {
                if (!allowNoTarget) {
                    targets = t.getSourceStates().stream().map(s -> options.formatState(s)).toList();
                } else {
                    continue;
                }
            } else {
                targets = t.getTargetStates().stream().map(s -> options.formatState(s)).toList();
            }

            for (State rawSourceState : t.getSourceStates()) {
                String source = simplify(options.formatState(rawSourceState));

                for (String rawTarget : targets) {
                    String target = simplify(rawTarget);

                    String styleClass = getStyleClass(t, source, Collections.emptyMap());
                    String color = getLegacyColor(t, source, Collections.emptyMap());
                    sb.append(source).append(" -[#").append(color).append(",bold]-> ").append(target);
                    
                    sb.append(" <<").append(styleClass).append(">>");


                    String label = buildLabel(t, options);
                    if (options.isEmbedIdentifiers()) {
                        String eventStr = t.getEvent() != null ? options.formatEvent(t.getEvent()) : null;
                        String linkId = eventStr != null && !eventStr.isBlank() ? "link_" + normalize(source) + "__" + normalize(eventStr) : "link_anon_" + normalize(source) + "_" + normalize(target);
                        
                        sb.append(" : [[#").append(linkId).append(" ");
                        if (!label.isEmpty()) {
                            String sanitized = label.replace("[", "&#91;")
                                                  .replace("]", "&#93;")
                                                  .replace("<", "&lt;")
                                                  .replace(">", "&gt;");
                            sb.append(sanitized);
                        } else {
                            sb.append(".");
                        }
                        sb.append("]]");
                    } else if (!label.isEmpty()) {
                        sb.append(" : ").append(label);
                    }
                    sb.append("\n");
                }
            }
        }

        sb.append("\n");
        for (String end : endStates) {
            sb.append(simplify(options.formatStateLabel(end))).append(" --> [*]\n");
        }

        sb.append("@enduml\n");
        return sb.toString();
    }

    private String normalize(String name) {
        if (name == null) return "";
        return name.replaceAll("[^a-zA-Z0-9]", "_");
    }

    private String getLegacyColor(Transition t, String source, Map<String, String> choiceStateClassMap) {
        if (t.getType() == null)
            return "94a3b8";
        return switch (t.getType()) {
            case CHOICE -> "FF6347";
            case EXTERNAL -> "1E90FF";
            case INTERNAL -> "32CD32";
            case LOCAL -> "FFA500";
            case JUNCTION -> "FF69B4";
            case JOIN -> "8A2BE2";
            case FORK -> "20B2AA";
        };
    }

    private String getStyleClass(Transition t, String source, Map<String, String> choiceStateClassMap) {
        if (t.getType() == null)
            return "default";
        return switch (t.getType()) {
            case CHOICE -> "choice_type";
            case EXTERNAL -> "external";
            case INTERNAL -> "internal";
            case LOCAL -> "local";
            case JUNCTION -> "junction";
            case JOIN -> "join";
            case FORK -> "fork";
        };
    }

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

    @Override
    public String simplify(String state) {
        if (state == null) return "";
        if (state.contains("\"")) return state;
        return state.replaceAll("[^a-zA-Z0-9_.]", "");
    }

    private String buildLabel(Transition t, ExportOptions options) {
        List<String> parts = new ArrayList<>();
        if (t.getEvent() != null) {
            String eventStr = options.formatEvent(t.getEvent());
            if (eventStr != null && !eventStr.isBlank()) {
                parts.add(eventStr);
            }
        }
        if (t.getGuards() != null && !t.getGuards().isEmpty()) {
            StringBuilder guardsBuilder = new StringBuilder();
            guardsBuilder.append("[");
            for (int i = 0; i < t.getGuards().size(); i++) {
                if (i > 0) guardsBuilder.append(" && ");
                var guard = t.getGuards().get(i);
                if (options.isUseLambdaGuards() && guard.isLambda()) {
                    guardsBuilder.append(LAMBDA);
                } else {
                    guardsBuilder.append(guard.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
                }
            }
            guardsBuilder.append("]");
            parts.add(guardsBuilder.toString());
        }
        if (t.getActions() != null && !t.getActions().isEmpty()) {
            parts.add("/ " + t.getActions().stream()
                    .map(a -> (options.isUseLambdaGuards() && a.isLambda()) ? LAMBDA : a.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim())
                    .collect(Collectors.joining(", ")));
        }
        Optional.ofNullable(t.getOrder())
                .filter(order -> t.getType() == TransitionType.CHOICE || t.getType() == TransitionType.JUNCTION)
                .map(order -> "(order=" + order + ")")
                .ifPresent(parts::add);
        return String.join(" ", parts);
    }
}
