package io.xpipe.app.comp.base;

import io.xpipe.app.comp.BaseRegionBuilder;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.AppOperationMode;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.update.AppDistributionType;
import io.xpipe.app.update.UpdateAvailableDialog;
import io.xpipe.app.update.UpdateHandler;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.ThreadHelper;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;

import lombok.AllArgsConstructor;

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

@AllArgsConstructor
public class SideMenuBarComp extends RegionBuilder<VBox> {

    private final Property<AppLayoutModel.Entry> value;
    private final List<AppLayoutModel.Entry> entries;
    private final ObservableList<AppLayoutModel.QueueEntry> queueEntries;

    @Override
    public VBox createSimple() {
        var vbox = new VBox();
        vbox.setFillWidth(true);

        for (AppLayoutModel.Entry e : entries) {
            var b = new IconButtonComp(e.icon(), () -> {
                // Don't allow switching prior to startup
                if (AppOperationMode.isInStartup() || AppOperationMode.isInShutdown()) {
                    return;
                }

                if (e.action() != null) {
                    e.action().run();
                }

                if (e.comp() != null) {
                    value.setValue(e);
                }
            });

            var stack = createStyle(e, b, false);
            var shortcut = e.combination();
            if (shortcut != null) {
                stack.apply(struc -> struc.getProperties().put("shortcut", shortcut));
            }
            b.describe(d -> {
                d.name(e.name());
                if (shortcut != null) {
                    d.shortcut(shortcut);
                }
            });
            vbox.getChildren().add(stack.build());
        }

        {
            var b = new IconButtonComp("mdi2u-update", () -> {
                ThreadHelper.runFailableAsync(() -> {
                    var r = UpdateAvailableDialog.showIfNeeded(false);
                    if (!r) {
                        AppPrefs.get().selectCategory("about");
                        UpdateHandler uh = AppDistributionType.get().getUpdateHandler();
                        uh.prepareUpdate();
                    }
                });
            });
            b.describe(d -> d.nameKey("updateAvailableTooltip"));
            var stack = createStyle(null, b, false);
            var h = AppDistributionType.get().getUpdateHandler();
            stack.hide(Bindings.createBooleanBinding(
                    () -> {
                        return h.getPreparedUpdate().getValue() == null;
                    },
                    h.getPreparedUpdate(),
                    h.getBusy()));
            vbox.getChildren().add(stack.build());
        }

        if (!AppProperties.get().isStaging()) {
            var b = new IconButtonComp("mdoal-insights", () -> Hyperlinks.open(Hyperlinks.GITHUB_PTB));
            b.describe(d -> d.nameKey("ptbAvailableTooltip"));
            var stack = createStyle(null, b, false);
            stack.hide(AppLayoutModel.get().getPtbAvailable().not());
            vbox.getChildren().add(stack.build());
        }

        var filler = new Button();
        filler.setDisable(true);
        filler.setMaxHeight(3000);
        vbox.getChildren().add(filler);
        VBox.setVgrow(filler, Priority.ALWAYS);
        vbox.getStyleClass().add("sidebar-comp");

        var topQueueButtons = new VBox();
        var bottomQueueButtons = new VBox();
        queueEntries.addListener((ListChangeListener<? super AppLayoutModel.QueueEntry>) c -> {
            var l = new ArrayList<>(c.getList());
            PlatformThread.runLaterIfNeeded(() -> {
                topQueueButtons.getChildren().clear();
                bottomQueueButtons.getChildren().clear();
                for (int i = l.size() - 1; i >= 0; i--) {
                    var item = l.get(i);
                    var b = new IconButtonComp(item.getIcon(), null);
                    b.describe(d -> d.name(item.getName()));
                    b.apply(struc -> {
                        struc.setOnAction(e -> {
                            struc.setDisable(true);
                            item.execute();
                            struc.setDisable(false);
                            e.consume();
                        });
                    });
                    var stack = createStyle(null, b, !item.isTop());
                    (item.isTop() ? topQueueButtons : bottomQueueButtons)
                            .getChildren()
                            .add(stack.build());
                }
            });
        });
        vbox.getChildren().addFirst(topQueueButtons);
        vbox.getChildren().add(bottomQueueButtons);
        vbox.setMinHeight(0);
        vbox.setPrefHeight(0);

        return vbox;
    }

    private BaseRegionBuilder<?, ?> createStyle(AppLayoutModel.Entry e, IconButtonComp b, boolean highlight) {
        var selected = PseudoClass.getPseudoClass("selected");

        b.apply(struc -> {
            AppFontSizes.lg(struc);
            struc.setAlignment(Pos.CENTER);

            struc.pseudoClassStateChanged(selected, value.getValue().equals(e));
            value.addListener((c, o, n) -> {
                PlatformThread.runLaterIfNeeded(() -> {
                    struc.pseudoClassStateChanged(selected, n.equals(e));
                });
            });
        });

        var selectedBorder = Bindings.createObjectBinding(
                () -> {
                    var c = Platform.getPreferences()
                            .getAccentColor()
                            .desaturate()
                            .desaturate();
                    return new Background(new BackgroundFill(c, new CornerRadii(8), new Insets(17, 1, 15, 2)));
                },
                Platform.getPreferences().accentColorProperty());
        var hoverBorder = Bindings.createObjectBinding(
                () -> {
                    var c = Platform.getPreferences()
                            .getAccentColor()
                            .darker()
                            .desaturate()
                            .desaturate();
                    return new Background(new BackgroundFill(c, new CornerRadii(8), new Insets(17, 1, 15, 2)));
                },
                Platform.getPreferences().accentColorProperty());
        var noneBorder = Bindings.createObjectBinding(
                () -> {
                    return Background.fill(Color.TRANSPARENT);
                },
                Platform.getPreferences().accentColorProperty());

        var indicator = RegionBuilder.empty().style("indicator");
        var stack = new StackComp(List.of(indicator, b)).apply(struc -> struc.setAlignment(Pos.CENTER_RIGHT));
        stack.apply(struc -> {
            var indicatorRegion = (Region) struc.getChildren().getFirst();
            var buttonRegion = (Region) struc.getChildren().get(1);
            indicatorRegion.setMaxWidth(7);
            indicatorRegion.prefHeightProperty().bind(buttonRegion.heightProperty());
            indicatorRegion
                    .backgroundProperty()
                    .bind(Bindings.createObjectBinding(
                            () -> {
                                if (struc.isHover()) {
                                    return hoverBorder.get();
                                }

                                if (highlight || value.getValue().equals(e)) {
                                    return selectedBorder.get();
                                }

                                return noneBorder.get();
                            },
                            struc.hoverProperty(),
                            value,
                            hoverBorder,
                            selectedBorder,
                            noneBorder));
        });
        return stack;
    }
}
