package io.xpipe.app.browser;

import io.xpipe.app.browser.file.BrowserConnectionListComp;
import io.xpipe.app.browser.file.BrowserConnectionListFilterComp;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.file.BrowserFileSystemTabComp;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.FileSystemStore;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.hub.comp.StoreEntryWrapper;
import io.xpipe.app.hub.comp.StoreFilter;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.platform.BindingsHelper;
import io.xpipe.app.platform.InputHelper;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.FileReference;
import io.xpipe.app.util.ObservableSubscriber;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.FilePath;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;

import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class BrowserFileChooserSessionComp extends ModalOverlayContentComp {

    private final BrowserFileChooserSessionModel model;
    private final Predicate<DataStoreEntry> filter;

    public BrowserFileChooserSessionComp(BrowserFileChooserSessionModel model, Predicate<DataStoreEntry> filter) {
        this.model = model;
        this.filter = filter;
    }

    public static void open(
            Supplier<DataStoreEntryRef<? extends FileSystemStore>> store,
            Supplier<FilePath> initialPath,
            Consumer<FileReference> file,
            boolean save,
            boolean directory,
            Predicate<DataStoreEntry> filter) {
        var model = new BrowserFileChooserSessionModel(directory);
        model.setOnFinish(fileStores -> {
            file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
        });
        var comp = new BrowserFileChooserSessionComp(model, filter)
                .style("browser")
                .style("chooser");
        var selection = new SimpleStringProperty();
        model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
            selection.set(
                    c.getList().size() > 0
                            ? c.getList().getFirst().getRawFileEntry().getPath().toString()
                            : null);
        });
        var selectionField = new TextFieldComp(selection);
        selectionField.apply(struc -> {
            struc.setEditable(false);
            AppFontSizes.base(struc);
        });
        selectionField.style("chooser-selection");
        selectionField.hgrow();
        var modal = ModalOverlay.of(save ? "saveFileTitle" : "openFileTitle", comp);
        modal.setRequireCloseButtonForClose(true);
        modal.addButtonBarComp(selectionField);
        modal.addButton(new ModalButton("select", () -> model.finishChooser(), true, true));
        modal.show();
        ThreadHelper.runAsync(() -> {
            model.openFileSystemAsync(store.get(), null, (sc) -> initialPath.get(), model.getBusy());
        });
    }

    @Override
    protected void setModalOverlay(ModalOverlay modalOverlay) {
        super.setModalOverlay(modalOverlay);
        if (modalOverlay == null) {
            model.closeFileSystem();
        }
    }

    @Override
    protected Region createSimple() {
        Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
            return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
                    && storeEntryWrapper.getEntry().getValidity().isUsable()
                    && filter.test(storeEntryWrapper.getEntry());
        };
        BiConsumer<StoreEntryWrapper, BooleanProperty> action = (w, busy) -> {
            ThreadHelper.runFailableAsync(() -> {
                var entry = w.getEntry();
                if (!entry.getValidity().isUsable()) {
                    return;
                }

                // Don't open same system again
                var current = model.getSelectedEntry().getValue();
                if (current != null && entry.ref().equals(current.getEntry())) {
                    return;
                }

                if (entry.getStore() instanceof ShellStore) {
                    model.openFileSystemAsync(entry.ref(), null, null, busy);
                }
            });
        };

        var category = new SimpleObjectProperty<>(
                StoreViewState.get().getActiveCategory().getValue());
        var filter = new SimpleObjectProperty<StoreFilter>();
        var filterTrigger = new ObservableSubscriber();
        var bookmarkTopBar = new BrowserConnectionListFilterComp(filterTrigger, category, filter);
        var bookmarksList = new BrowserConnectionListComp(
                BindingsHelper.map(
                        model.getSelectedEntry(), v -> v != null ? v.getEntry().get() : null),
                applicable,
                action,
                category,
                filter);
        var bookmarksContainer = new StackComp(List.of(bookmarksList)).style("bookmarks-container");
        bookmarksContainer
                .apply(struc -> {
                    var rec = new Rectangle();
                    rec.widthProperty().bind(struc.widthProperty());
                    rec.heightProperty().bind(struc.heightProperty());
                    rec.setArcHeight(7);
                    rec.setArcWidth(7);
                    rec.setSmooth(false);
                    struc.getChildren().getFirst().setClip(rec);
                })
                .vgrow();

        var stack = RegionBuilder.of(() -> {
            var s = new StackPane();
            model.getSelectedEntry().subscribe(selected -> {
                PlatformThread.runLaterIfNeeded(() -> {
                    if (selected != null) {
                        s.getChildren().setAll(new BrowserFileSystemTabComp(selected, false).build());
                    } else {
                        s.getChildren().clear();
                    }
                });
            });
            InputHelper.onKeyCombination(
                    s, new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), false, keyEvent -> {
                        filterTrigger.trigger();
                        keyEvent.consume();
                    });
            return s;
        });

        var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer)).style("left");
        var splitPane = new LeftSplitPaneComp(vertical, stack)
                .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
                .applyStructure(struc -> {
                    struc.getLeft().setMinWidth(270);
                    struc.getLeft().setMaxWidth(500);
                });
        splitPane.disable(model.getBusy());
        return splitPane.prefHeight(2000).build();
    }
}
