package io.xpipe.app.browser.file;

import io.xpipe.app.browser.icon.BrowserIconManager;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.RegionStructure;
import io.xpipe.app.comp.RegionStructureBuilder;
import io.xpipe.app.comp.augment.ContextMenuAugment;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.PrettyImageHelper;
import io.xpipe.app.comp.base.TextFieldComp;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.MenuHelper;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;

import atlantafx.base.theme.Styles;

public class BrowserNavBarComp extends RegionStructureBuilder<HBox, BrowserNavBarComp.Structure> {

    private static final PseudoClass INVISIBLE = PseudoClass.getPseudoClass("invisible");
    private final BrowserFileSystemTabModel model;

    public BrowserNavBarComp(BrowserFileSystemTabModel model) {
        this.model = model;
    }

    @Override
    public Structure createBase() {
        var pathBar = createPathBar();

        var graphic = Bindings.createStringBinding(
                () -> {
                    if (model.getCurrentDirectory() == null) {
                        return null;
                    }

                    var icon = new BrowserEntry(model.getCurrentDirectory(), model.getFileList()).getIcon();
                    BrowserIconManager.loadIfNecessary(icon);
                    return icon;
                },
                PlatformThread.sync(model.getCurrentPath()));
        var breadcrumbsGraphic = PrettyImageHelper.ofFixedSize(graphic, 24, 24)
                .style("path-graphic")
                .build();

        var homeButton = new ButtonComp(null, breadcrumbsGraphic, null)
                .describe(d -> d.nameKey("directoryOptions"))
                .apply(new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, () -> {
                    return model.getInOverview().get() ? null : new BrowserContextMenu(model, null, false);
                }))
                .build();
        homeButton.getStyleClass().add(Styles.LEFT_PILL);
        homeButton.getStyleClass().add("path-graphic-button");
        AppFontSizes.sm(homeButton);

        var historyButton = new ButtonComp(null, new LabelGraphic.IconGraphic("mdi2h-history"), null)
                .describe(
                        d -> d.nameKey("history").shortcut(new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN)))
                .style(Styles.RIGHT_PILL)
                .apply(new ContextMenuAugment<>(
                        event -> event.getButton() == MouseButton.PRIMARY, null, this::createContextMenu))
                .build();
        AppFontSizes.xs(historyButton);

        var breadcrumbs = new BrowserBreadcrumbBar(model);

        var pathRegion = pathBar.build();
        var breadcrumbsRegion = breadcrumbs.build();
        breadcrumbsRegion.setOnMouseClicked(event -> {
            pathRegion.requestFocus();
            event.consume();
        });
        breadcrumbsRegion.setFocusTraversable(false);
        breadcrumbsRegion
                .visibleProperty()
                .bind(Bindings.createBooleanBinding(
                        () -> {
                            return !pathRegion.isFocused()
                                    && !model.getInOverview().get();
                        },
                        pathRegion.focusedProperty(),
                        PlatformThread.sync(model.getInOverview())));
        var stack = new StackPane(pathRegion, breadcrumbsRegion);
        StackPane.setMargin(breadcrumbsRegion, new Insets(0, 10, 0, 10));
        stack.setMinWidth(0);
        pathRegion.prefHeightProperty().bind(stack.heightProperty());

        stack.widthProperty().addListener((observable, oldValue, newValue) -> {
            setAlignment(stack, breadcrumbsRegion);
        });
        model.getCurrentPath().addListener((observable, oldValue, newValue) -> {
            PlatformThread.runLaterIfNeeded(() -> {
                setAlignment(stack, breadcrumbsRegion);
            });
        });
        setAlignment(stack, breadcrumbsRegion);

        // Prevent overflow
        var clip = new Rectangle();
        clip.setSmooth(false);
        clip.widthProperty().bind(stack.widthProperty());
        clip.heightProperty().bind(stack.heightProperty());
        stack.setClip(clip);

        HBox.setHgrow(stack, Priority.ALWAYS);

        var topBox = new HBox(homeButton, stack, historyButton);
        topBox.setFillHeight(true);
        topBox.setAlignment(Pos.CENTER);
        homeButton.minWidthProperty().bind(pathRegion.heightProperty());
        homeButton.maxWidthProperty().bind(pathRegion.heightProperty());
        homeButton.minHeightProperty().bind(pathRegion.heightProperty());
        homeButton.maxHeightProperty().bind(pathRegion.heightProperty());
        historyButton.minHeightProperty().bind(pathRegion.heightProperty());
        historyButton.maxHeightProperty().bind(pathRegion.heightProperty());
        topBox.setPickOnBounds(false);
        HBox.setHgrow(topBox, Priority.ALWAYS);

        return new Structure(topBox, pathRegion, historyButton);
    }

    private void setAlignment(StackPane stackPane, Region region) {
        var off = region.getWidth() - stackPane.getWidth();
        if (off <= -20) {
            stackPane.setAlignment(Pos.CENTER_LEFT);
        } else {
            stackPane.setAlignment(Pos.CENTER_RIGHT);
        }
    }

    private RegionBuilder<TextField> createPathBar() {
        var path = new SimpleStringProperty();
        model.getCurrentPath().subscribe((newValue) -> {
            PlatformThread.runLaterIfNeeded(() -> {
                path.set(newValue != null ? newValue.toString() : null);
            });
        });
        path.addListener((observable, oldValue, newValue) -> {
            ThreadHelper.runFailableAsync(() -> {
                BooleanScope.executeExclusive(model.getBusy(), () -> {
                    var changed = model.cdSyncOrRetry(newValue != null && !newValue.isBlank() ? newValue : null, true);
                    changed.ifPresent(s -> {
                        Platform.runLater(() -> path.set(!s.isBlank() ? s : null));
                    });
                });
            });
        });
        var pathBar = new TextFieldComp(path, true).style(Styles.CENTER_PILL).style("path-text");
        pathBar.describe(d -> d.nameKey("currentPath"));
        pathBar.apply(struc -> {
            struc.focusedProperty().subscribe(val -> {
                struc.pseudoClassStateChanged(
                        INVISIBLE, !val && !model.getInOverview().get());

                if (val) {
                    Platform.runLater(() -> {
                        struc.end();
                    });
                }
            });

            struc.addEventHandler(KeyEvent.KEY_PRESSED, ke -> {
                if (ke.getCode().equals(KeyCode.ENTER)) {
                    ke.consume();
                }
            });

            model.getInOverview().subscribe(val -> {
                // Pseudo classes do not apply if set instantly before shown
                // If we start a new tab with a directory set, we have to set the pseudo class one pulse later
                Platform.runLater(() -> {
                    struc.pseudoClassStateChanged(INVISIBLE, !val && !struc.isFocused());
                });
            });
        });
        return pathBar;
    }

    private ContextMenu createContextMenu() {
        var cm = MenuHelper.createContextMenu();

        var f = model.getHistory().getForwardHistory(8).stream().toList();
        for (int i = f.size() - 1; i >= 0; i--) {
            if (f.get(i) == null) {
                continue;
            }

            var mi = new MenuItem(f.get(i).toString());
            int target = i + 1;
            mi.setOnAction(event -> {
                ThreadHelper.runFailableAsync(() -> {
                    BooleanScope.executeExclusive(model.getBusy(), () -> {
                        model.forthSync(target);
                    });
                });
                event.consume();
            });
            cm.getItems().add(mi);
        }
        if (!f.isEmpty()) {
            cm.getItems().add(new SeparatorMenuItem());
        }

        if (model.getHistory().getCurrent() != null) {
            var current = new MenuItem(model.getHistory().getCurrent().toString());
            current.setDisable(true);
            cm.getItems().add(current);
        }

        var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream()
                .toList();
        if (!b.isEmpty()) {
            cm.getItems().add(new SeparatorMenuItem());
        }
        for (int i = 0; i < b.size(); i++) {
            if (b.get(i) == null) {
                continue;
            }

            var mi = new MenuItem(b.get(i).toString());
            int target = i + 1;
            mi.setOnAction(event -> {
                ThreadHelper.runFailableAsync(() -> {
                    BooleanScope.executeExclusive(model.getBusy(), () -> {
                        model.backSync(target);
                    });
                });
                event.consume();
            });
            cm.getItems().add(mi);
        }

        cm.addEventHandler(Menu.ON_SHOWING, e -> {
            Node content = cm.getSkin().getNode();
            if (content instanceof Region r) {
                r.setMaxHeight(600);
            }
        });
        return cm;
    }

    public record Structure(HBox box, TextField textField, Button historyButton) implements RegionStructure<HBox> {

        @Override
        public HBox get() {
            return box;
        }
    }
}
