package io.xpipe.app.comp.base;

import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.platform.BindingsHelper;
import io.xpipe.app.platform.Check;
import io.xpipe.app.util.Hyperlinks;

import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.TraversalDirection;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*;
import javafx.util.Duration;

import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import lombok.Getter;
import org.int4.fx.builders.common.AbstractRegionBuilder;
import org.kordamp.ikonli.javafx.FontIcon;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@Getter
public class OptionsComp extends RegionBuilder<VBox> {

    private final List<Entry> entries;
    private final List<Check> checks;

    public OptionsComp(List<Entry> entries, List<Check> checks) {
        this.entries = entries;
        this.checks = checks;
    }

    @Override
    public VBox createSimple() {
        VBox pane = new VBox();
        pane.getStyleClass().add("options-comp");

        var nameRegions = new ArrayList<Region>();
        var nameMap = new HashMap<Node, Node>();

        Region firstComp = null;
        for (var entry : getEntries()) {
            Region compRegion = entry.comp() != null ? entry.comp().build() : new Region();

            if (firstComp == null) {
                compRegion.getStyleClass().add("first");
                firstComp = compRegion;
            }

            var showVertical = (entry.name() != null
                    && (entry.description() != null || entry.comp() instanceof SimpleTitledPaneComp));
            if (showVertical) {
                var line = new VBox();
                line.prefWidthProperty().bind(pane.widthProperty());

                var name = new Label();
                name.getStyleClass().add("name");
                name.textProperty().bind(entry.name());
                name.setMinWidth(Region.USE_PREF_SIZE);
                name.setMinHeight(Region.USE_PREF_SIZE);
                name.setAlignment(Pos.CENTER_LEFT);
                VBox.setVgrow(line, VBox.getVgrow(compRegion));
                line.spacingProperty()
                        .bind(Bindings.createDoubleBinding(
                                () -> {
                                    return name.isManaged() ? 2.0 : 0.0;
                                },
                                name.managedProperty()));
                name.visibleProperty().bind(compRegion.visibleProperty());
                name.managedProperty().bind(compRegion.managedProperty());
                VBox.setMargin(name, new Insets(0, 0, 0, 1));

                if (entry.description() != null) {
                    var description = new Label();
                    description.setWrapText(true);
                    description.getStyleClass().add("description");
                    description.textProperty().bind(entry.description());
                    description.setAlignment(Pos.CENTER_LEFT);
                    description.setMinHeight(Region.USE_PREF_SIZE);
                    description.visibleProperty().bind(compRegion.visibleProperty());
                    description.managedProperty().bind(compRegion.managedProperty());

                    var vbox = new VBox();
                    vbox.getChildren().add(name);
                    vbox.spacingProperty()
                            .bind(Bindings.createDoubleBinding(
                                    () -> {
                                        return name.isManaged() ? 2.0 : 0.0;
                                    },
                                    name.managedProperty()));

                    vbox.focusTraversableProperty()
                            .bind(Platform.accessibilityActiveProperty().and(compRegion.visibleProperty()));
                    vbox.setAccessibleRole(AccessibleRole.TEXT);
                    var joined = Bindings.createStringBinding(
                            () -> {
                                return entry.name.getValue() + "\n\n"
                                        + entry.description().getValue();
                            },
                            entry.name(),
                            entry.description());
                    vbox.accessibleTextProperty().bind(joined);

                    if (entry.documentationLink() != null) {
                        var fi = new FontIcon("mdi2b-book-open-variant");
                        fi.getStyleClass().add("graphic");
                        var link = new Button("?", fi);
                        link.setMinWidth(Region.USE_PREF_SIZE);
                        link.getStyleClass().add(Styles.BUTTON_OUTLINED);
                        link.getStyleClass().add(Styles.ACCENT);
                        link.getStyleClass().add("long-description");
                        link.accessibleTextProperty()
                                .bind(Bindings.createStringBinding(
                                        () -> {
                                            return AppI18n.get(
                                                    "helpButton", entry.name().getValue());
                                        },
                                        AppI18n.activeLanguage(),
                                        entry.name()));
                        AppFontSizes.xl(link);
                        link.setOnAction(e -> {
                            Hyperlinks.open(entry.documentationLink());
                            e.consume();
                        });

                        var tt = TooltipHelper.create(new SimpleStringProperty(entry.documentationLink()));
                        tt.setShowDelay(Duration.millis(1));
                        Tooltip.install(link, tt);

                        var descriptionBox = new HBox(description, new Spacer(Orientation.HORIZONTAL), link);
                        descriptionBox.getStyleClass().add("description-box");
                        descriptionBox.setSpacing(5);
                        HBox.setHgrow(descriptionBox, Priority.ALWAYS);
                        descriptionBox.setAlignment(Pos.TOP_LEFT);
                        vbox.getChildren().add(descriptionBox);
                        VBox.setMargin(descriptionBox, new Insets(0, 0, 0, 1));
                        descriptionBox.visibleProperty().bind(compRegion.visibleProperty());
                        descriptionBox.managedProperty().bind(compRegion.managedProperty());
                    } else {
                        var descriptionBox = new HBox(description);
                        descriptionBox.getStyleClass().add("description-box");
                        vbox.getChildren().add(descriptionBox);
                        vbox.getChildren().add(new Spacer(2, Orientation.VERTICAL));
                        VBox.setMargin(descriptionBox, new Insets(0, 0, 0, 1));
                        descriptionBox.visibleProperty().bind(compRegion.visibleProperty());
                        descriptionBox.managedProperty().bind(compRegion.managedProperty());
                    }

                    line.getChildren().add(vbox);
                    nameMap.put(compRegion, vbox);
                } else {
                    line.getChildren().add(name);
                    nameMap.put(compRegion, name);
                }

                line.getChildren().add(compRegion);
                compRegion.getStyleClass().add("options-content");

                pane.getChildren().add(line);
            } else if (entry.name() != null) {
                var line = new HBox();
                line.setFillHeight(true);
                line.prefWidthProperty().bind(pane.widthProperty());
                line.setSpacing(8);

                var name = new Label();
                name.textProperty().bind(entry.name());
                name.prefHeightProperty().bind(line.heightProperty());
                name.setMinWidth(Region.USE_PREF_SIZE);
                name.setAlignment(Pos.CENTER_LEFT);
                name.focusTraversableProperty()
                        .bind(Platform.accessibilityActiveProperty().and(compRegion.visibleProperty()));
                name.setAccessibleRole(AccessibleRole.TEXT);
                name.accessibleTextProperty().bind(entry.name());
                nameRegions.add(name);
                name.visibleProperty().bind(compRegion.visibleProperty());
                name.managedProperty().bind(compRegion.managedProperty());
                line.getChildren().add(name);
                nameMap.put(compRegion, name);

                line.getChildren().add(compRegion);
                HBox.setHgrow(compRegion, Priority.ALWAYS);

                pane.getChildren().add(line);
            } else {
                pane.getChildren().add(compRegion);
            }

            var last = entry.equals(entries.getLast());
            if (!last) {
                Spacer spacer = new Spacer(7, Orientation.VERTICAL);
                pane.getChildren().add(spacer);
                spacer.visibleProperty().bind(compRegion.visibleProperty());
                spacer.managedProperty().bind(compRegion.managedProperty());
            }
        }

        if (entries.size() == 1 && firstComp != null) {
            firstComp.visibleProperty().subscribe(v -> {
                pane.setVisible(v);
            });
            firstComp.managedProperty().subscribe(v -> {
                pane.setManaged(v);
            });
        }

        for (Region nameRegion : nameRegions) {
            nameRegion.setPrefWidth(Region.USE_COMPUTED_SIZE);
        }

        if (entries.stream().anyMatch(entry -> entry.name() != null && entry.description() == null)) {
            var nameWidthBinding = Bindings.createDoubleBinding(
                    () -> {
                        return nameRegions.stream()
                                .map(Region::getWidth)
                                .filter(aDouble -> aDouble > 0.0)
                                .max(Double::compareTo)
                                .orElse(Region.USE_COMPUTED_SIZE);
                    },
                    nameRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
            BindingsHelper.preserve(pane, nameWidthBinding);
            nameWidthBinding.addListener((observableValue, number, t1) -> {
                Platform.runLater(() -> {
                    for (Region nameRegion : nameRegions) {
                        nameRegion.setPrefWidth(t1.doubleValue());
                    }
                });
            });
        }

        Region finalFirstComp = firstComp;
        pane.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) {
                return;
            }

            var failed = checks.stream()
                    .filter(check -> check.getValidationResult().getMessages().size() > 0)
                    .findFirst();

            Node target = null;
            if (failed.isPresent()) {
                var targets = failed.get().getTargets();
                if (targets.size() > 0) {
                    target = targets.getFirst();
                }
            } else {
                if (finalFirstComp != null) {
                    target = finalFirstComp;
                }
            }

            if (target != null) {
                var a18y = Platform.accessibilityActiveProperty().get();
                if (a18y) {
                    if (nameMap.containsKey(target)) {
                        nameMap.get(target).requestFocus();
                    } else {
                        target.requestFocus();
                        target.requestFocusTraversal(TraversalDirection.UP);
                    }
                } else {
                    target.requestFocus();
                }
            }
        });

        return pane;
    }

    public record Entry(
            ObservableValue<String> description,
            String documentationLink,
            ObservableValue<String> name,
            AbstractRegionBuilder<?, ?> comp) {}
}
