package io.xpipe.app.hub.comp;

import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.SimpleRegionBuilder;
import io.xpipe.app.comp.augment.ContextMenuAugment;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.platform.ClipboardHelper;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.MenuHelper;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.OsType;

import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.kordamp.ikonli.javafx.FontIcon;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

@EqualsAndHashCode(callSuper = true)
@Value
public class StoreCategoryComp extends SimpleRegionBuilder {

    private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
    private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");

    StoreCategoryWrapper category;

    @Override
    protected Region createSimple() {
        var prop = new SimpleStringProperty();
        category.getName().subscribe(prop::setValue);
        AppPrefs.get().censorMode().subscribe(aBoolean -> {
            var n = category.getName().getValue();
            prop.setValue(aBoolean ? "*".repeat(n.length()) : n);
        });
        prop.addListener((observable, oldValue, newValue) -> {
            if (!AppPrefs.get().censorMode().get()) {
                category.getName().setValue(newValue);
            }
        });
        var name = new LazyTextFieldComp(prop)
                .style("name")
                .applyStructure(struc -> {
                    category.getRenameTrigger().onFire(() -> {
                        struc.get().requestFocus();
                        struc.getTextField().selectAll();
                    });
                })
                .build();
        var showing = new SimpleBooleanProperty();

        var expandIcon = Bindings.createObjectBinding(
                () -> {
                    if (category.getChildren().getList().size() == 0) {
                        return new LabelGraphic.IconGraphic("mdal-keyboard_arrow_right");
                    }

                    var exp = category.getExpanded().get();
                    return new LabelGraphic.IconGraphic(
                            exp ? "mdal-keyboard_arrow_down" : "mdi2c-chevron-double-right");
                },
                category.getExpanded(),
                category.getChildren().getList());
        var expandButton = new IconButtonComp(expandIcon, () -> {
                    category.toggleExpanded();
                })
                .apply(struc -> {
                    struc.setAlignment(Pos.CENTER);
                    if (OsType.ofLocal() == OsType.WINDOWS) {
                        HBox.setMargin(struc, new Insets(0, 0, 2.3, 0));
                    } else if (OsType.ofLocal() == OsType.MACOS) {
                        HBox.setMargin(struc, new Insets(0, 0, 1.8, 0));
                    }
                })
                .disable(Bindings.isEmpty(category.getChildren().getList()))
                .style("expand-button")
                .describe(d -> d.nameKey("expand")
                        .shortcut(new KeyCodeCombination(KeyCode.SPACE))
                        .showTooltips(false));

        var focus = new SimpleBooleanProperty();
        var hover = new SimpleBooleanProperty();
        var statusIcon = Bindings.createObjectBinding(
                () -> {
                    if (hover.get() || focus.get()) {
                        return new LabelGraphic.IconGraphic("mdomz-settings");
                    }

                    if (!DataStorage.get().supportsSync()
                            || (!category.getCategory().canShare())) {
                        return new LabelGraphic.IconGraphic("mdi2g-git");
                    }

                    return new LabelGraphic.IconGraphic(category.getSync().getValue() ? "mdi2g-git" : "mdi2c-cancel");
                },
                category.getSync(),
                hover,
                focus);
        var statusButton = new IconButtonComp(statusIcon)
                .apply(struc -> AppFontSizes.xs(struc))
                .apply(struc -> {
                    struc.setAlignment(Pos.CENTER);
                    struc.setPadding(new Insets(0, 0, 0, 0));
                })
                .apply(new ContextMenuAugment<>(
                        mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, null, () -> {
                            var cm = createContextMenu();
                            showing.bind(cm.showingProperty());
                            return cm;
                        }))
                .describe(d -> d.nameKey("configuration"))
                .style("status-button");

        var count = new CountComp(
                category.getShownContainedEntriesCount(),
                category.getAllContainedEntriesCount(),
                string -> "[" + string + "]");
        count.style("count");
        count.hide(Bindings.equal(0, category.getShownContainedEntriesCount()));
        count.minWidth(Region.USE_PREF_SIZE);

        var iconButton = new StoreCategoryIconComp(category, 16);

        var showStatus = hover.or(new SimpleBooleanProperty(DataStorage.get().supportsSync()))
                .or(showing)
                .or(focus);
        var h = new HorizontalComp(List.of(
                expandButton,
                RegionBuilder.hspacer(2),
                iconButton,
                RegionBuilder.hspacer(4),
                RegionBuilder.of(() -> name).hgrow(),
                RegionBuilder.hspacer(2),
                count,
                RegionBuilder.hspacer(9),
                statusButton.hide(showStatus.not())));
        h.padding(new Insets(0, 10, 0, (category.getDepth() * 8)));

        var categoryButton = new ButtonComp(
                        null, new SimpleObjectProperty<>(new LabelGraphic.CompGraphic(h)), category::select)
                .describe(d -> d.name(prop).shortcut(new KeyCodeCombination(KeyCode.SPACE)))
                .style("category-button")
                .apply(struc -> hover.bind(struc.hoverProperty()))
                .apply(struc -> focus.bind(struc.focusWithinProperty()))
                .maxWidth(2000);
        categoryButton.apply(new ContextMenuAugment<>(
                mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY,
                keyEvent -> keyEvent.getCode() == KeyCode.SPACE,
                () -> createContextMenu()));
        categoryButton.apply(struc -> {
            struc.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
                if (event.getCode() == KeyCode.SPACE) {
                    category.toggleExpanded();
                    event.consume();
                }
            });
        });

        var l = category.getChildren()
                .getList()
                .sorted(Comparator.comparing(storeCategoryWrapper ->
                        storeCategoryWrapper.nameProperty().getValue().toLowerCase(Locale.ROOT)));
        var children =
                new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper), false);
        children.style("children");
        children.minHeight(0);
        children.setVisibilityControl(true);

        var hide = Bindings.createBooleanBinding(
                () -> {
                    return !category.getExpanded().get()
                            || category.getChildren().getList().isEmpty();
                },
                category.getChildren().getList(),
                category.getExpanded());
        var v = new VerticalComp(List.of(categoryButton, children.hide(hide)));
        v.style("category");
        v.apply(struc -> {
            struc.pseudoClassStateChanged(ROOT, category.getCategory().getParentCategory() == null);
            StoreViewState.get().getActiveCategory().subscribe(val -> {
                struc.pseudoClassStateChanged(SELECTED, val.equals(category));
            });

            category.getColor().subscribe((c) -> {
                DataStoreColor.applyStyleClasses(c, struc);
            });
        });

        return v.build();
    }

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

        if (AppPrefs.get().enableHttpApi().get()) {
            var copyId = new MenuItem(AppI18n.get("copyId"), new FontIcon("mdi2c-content-copy"));
            copyId.setOnAction(event ->
                    ClipboardHelper.copyText(category.getCategory().getUuid().toString()));
            contextMenu.getItems().add(copyId);
        }

        if (AppPrefs.get().developerMode().getValue()) {
            var browse = new MenuItem(AppI18n.get("browseInternalStorage"), new FontIcon("mdi2f-folder-open-outline"));
            browse.setOnAction(
                    event -> DesktopHelper.browseFile(category.getCategory().getDirectory()));
            contextMenu.getItems().add(browse);
        }

        var newCategory = new MenuItem(AppI18n.get("createNewCategory"), new FontIcon("mdi2p-plus-thick"));
        newCategory.setOnAction(event -> {
            StoreViewState.get().createNewCategory(category);
        });
        newCategory.setDisable(!DataStorage.get().canCreateStoreCategoryWithin(category.getCategory()));
        contextMenu.getItems().add(newCategory);

        contextMenu.getItems().add(new SeparatorMenuItem());

        var configure = new MenuItem(AppI18n.get("configure"), new FontIcon("mdi2w-wrench-outline"));
        configure.setOnAction(event -> {
            StoreCategoryConfigComp.show(category);
        });
        contextMenu.getItems().add(configure);

        var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdal-edit"));
        rename.setOnAction(event -> {
            category.getRenameTrigger().fire(null);
            event.consume();
        });
        contextMenu.getItems().add(rename);

        var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
        var none = new MenuItem();
        none.textProperty().bind(AppI18n.observable("none"));
        none.setOnAction(event -> {
            category.getCategory().setConfig(category.getCategory().getConfig().withColor(null));
            event.consume();
        });
        none.setGraphic(DataStoreColor.createDisplayGraphic(null));
        color.getItems().add(none);
        Arrays.stream(DataStoreColor.values()).forEach(dataStoreColor -> {
            MenuItem m = new MenuItem();
            m.textProperty().bind(AppI18n.observable(dataStoreColor.getId()));
            m.setOnAction(event -> {
                category.getCategory()
                        .setConfig(category.getCategory().getConfig().withColor(dataStoreColor));
                event.consume();
            });
            m.setGraphic(DataStoreColor.createDisplayGraphic(dataStoreColor));
            color.getItems().add(m);
        });
        contextMenu.getItems().add(color);

        contextMenu.getItems().add(new SeparatorMenuItem());

        if (category.canMove()) {
            var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
            StoreViewState.get().getSortedCategories(getCategory().getRoot(), true).getList().stream()
                    .filter(w -> {
                        var isSource =
                                DataStorage.get()
                                        .getCategoryParentHierarchy(
                                                getCategory().getCategory())
                                        .stream()
                                        .anyMatch(h -> h.getUuid().equals(DataStorage.SCRIPT_SOURCES_CATEGORY_UUID));
                        if (isSource) {
                            return DataStorage.get().getCategoryParentHierarchy(w.getCategory()).stream()
                                    .anyMatch(h -> h.getUuid().equals(DataStorage.SCRIPT_SOURCES_CATEGORY_UUID));
                        }

                        var isScript =
                                DataStorage.get()
                                        .getCategoryParentHierarchy(
                                                getCategory().getCategory())
                                        .stream()
                                        .anyMatch(h -> h.getUuid().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID));
                        if (isScript) {
                            return DataStorage.get().getCategoryParentHierarchy(w.getCategory()).stream()
                                    .noneMatch(h -> h.getUuid().equals(DataStorage.SCRIPT_SOURCES_CATEGORY_UUID));
                        }

                        return true;
                    })
                    .forEach(storeCategoryWrapper -> {
                        var m = new CustomMenuItem();

                        var l = new Label();
                        l.setGraphic(PrettyImageHelper.ofFixedSizeSquare(
                                        storeCategoryWrapper.getIconFile().getValue(), 16)
                                .padding(new Insets(0, 0, 1, 0))
                                .build());
                        l.setText(storeCategoryWrapper.getName().getValue());
                        l.setPadding(new Insets(0, 1, 1, storeCategoryWrapper.getDepth() * 10));
                        m.setContent(l);
                        l.prefWidthProperty().bind(contextMenu.widthProperty().subtract(40));

                        m.setOnAction(event -> {
                            category.moveToParent(storeCategoryWrapper.getCategory());
                            event.consume();
                        });
                        if (storeCategoryWrapper.getParent() == null
                                || storeCategoryWrapper.equals(category)
                                || storeCategoryWrapper.equals(category.getParent())) {
                            m.setDisable(true);
                        }

                        move.getItems().add(m);
                    });
            contextMenu.getItems().add(move);
        }

        var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
        del.setOnAction(event -> {
            category.delete();
        });
        del.setDisable(!DataStorage.get().canDeleteStoreCategory(category.getCategory()));
        contextMenu.getItems().add(del);

        return contextMenu;
    }
}
