package p.studio.workspaces.editor;

import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.Node;
import p.studio.Container;
import p.studio.utilities.i18n.I18n;

import java.nio.file.Path;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

public final class EditorProjectNavigatorPanel extends BorderPane {
    private final Button refreshButton = new Button();
    private final TreeView<EditorProjectNode> treeView = new TreeView<>();
    private final Label emptyState = emptyLabel();
    private final StackPane content = new StackPane();
    private Runnable refreshAction = () -> { };
    private Consumer<EditorProjectNode> fileSelectionAction = node -> { };

    public EditorProjectNavigatorPanel() {
        getStyleClass().addAll("editor-workspace-panel", "editor-workspace-navigator-panel");
        setPadding(new Insets(14, 16, 14, 16));
        setTop(buildHeader());
        configureTreeView();
        content.getStyleClass().add("editor-workspace-panel-content");
        content.getChildren().addAll(treeView, emptyState);
        setCenter(content);
        showEmptyState(true);
    }

    private HBox buildHeader() {
        final var title = new Label();
        title.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_NAVIGATOR_TITLE));
        title.getStyleClass().add("editor-workspace-panel-title");

        refreshButton.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_NAVIGATOR_REFRESH));
        refreshButton.setFocusTraversable(false);
        refreshButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
        refreshButton.setOnAction(event -> refreshAction.run());

        final var spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);

        final var header = new HBox(8, title, spacer, refreshButton);
        header.getStyleClass().add("editor-workspace-panel-header");
        return header;
    }

    private void configureTreeView() {
        treeView.getStyleClass().add("editor-workspace-tree");
        treeView.setShowRoot(true);
        treeView.setFocusTraversable(false);
        treeView.setCellFactory(ignored -> new TreeCell<>() {
            @Override
            protected void updateItem(final EditorProjectNode item, final boolean empty) {
                super.updateItem(item, empty);
                getStyleClass().remove("editor-workspace-tree-cell-tagged");
                getStyleClass().remove("editor-workspace-tree-cell-root");
                if (empty || item == null) {
                    setText(null);
                    setGraphic(null);
                    return;
                }
                setText(labelFor(item, getTreeItem()));
                setGraphic(iconFor(item, getTreeItem()));
                if (isRoot(getTreeItem())) {
                    getStyleClass().add("editor-workspace-tree-cell-root");
                }
                if (item.taggedSourceRoot() || isInsideTaggedSourceRoot(getTreeItem())) {
                    getStyleClass().add("editor-workspace-tree-cell-tagged");
                }
            }
        });
        treeView.getSelectionModel().selectedItemProperty().addListener((ignored, previous, current) -> {
            if (current == null || current.getValue() == null || current.getValue().directory()) {
                return;
            }
            fileSelectionAction.accept(current.getValue());
        });
    }

    public void setRefreshAction(final Runnable refreshAction) {
        this.refreshAction = Objects.requireNonNull(refreshAction, "refreshAction");
    }

    public void setFileSelectionAction(final Consumer<EditorProjectNode> fileSelectionAction) {
        this.fileSelectionAction = Objects.requireNonNull(fileSelectionAction, "fileSelectionAction");
    }

    public void setSnapshot(final EditorProjectSnapshot snapshot) {
        final var currentSelection = selectedPath();
        final var expandedPaths = captureExpandedPaths(treeView.getRoot());
        final var rootItem = buildTreeItem(Objects.requireNonNull(snapshot, "snapshot").rootNode(), expandedPaths, true);
        treeView.setRoot(rootItem);
        restoreSelection(rootItem, currentSelection);
        showEmptyState(false);
    }

    private TreeItem<EditorProjectNode> buildTreeItem(
            final EditorProjectNode node,
            final Set<Path> expandedPaths,
            final boolean isRoot) {
        final var item = new TreeItem<>(node);
        item.setExpanded(isRoot || expandedPaths.contains(node.path()));
        node.children().stream()
                .map(child -> buildTreeItem(child, expandedPaths, false))
                .forEach(item.getChildren()::add);
        return item;
    }

    private Set<Path> captureExpandedPaths(final TreeItem<EditorProjectNode> item) {
        final var expandedPaths = new HashSet<Path>();
        if (item == null) {
            return expandedPaths;
        }
        collectExpandedPaths(item, expandedPaths);
        return expandedPaths;
    }

    private void collectExpandedPaths(final TreeItem<EditorProjectNode> item, final Set<Path> expandedPaths) {
        final var value = item.getValue();
        if (value != null && item.isExpanded()) {
            expandedPaths.add(value.path());
        }
        item.getChildren().forEach(child -> collectExpandedPaths(child, expandedPaths));
    }

    private Optional<Path> selectedPath() {
        return Optional.ofNullable(treeView.getSelectionModel().getSelectedItem())
                .map(TreeItem::getValue)
                .map(EditorProjectNode::path);
    }

    private void restoreSelection(final TreeItem<EditorProjectNode> rootItem, final Optional<Path> selectedPath) {
        treeView.getSelectionModel().clearSelection();
        if (selectedPath.isEmpty()) {
            return;
        }
        findTreeItem(rootItem, selectedPath.orElseThrow())
                .ifPresent(item -> treeView.getSelectionModel().select(item));
    }

    private Optional<TreeItem<EditorProjectNode>> findTreeItem(final TreeItem<EditorProjectNode> item, final Path path) {
        if (item == null || item.getValue() == null) {
            return Optional.empty();
        }
        if (item.getValue().path().equals(path)) {
            return Optional.of(item);
        }
        for (final var child : item.getChildren()) {
            final var match = findTreeItem(child, path);
            if (match.isPresent()) {
                return match;
            }
        }
        return Optional.empty();
    }

    private void showEmptyState(final boolean empty) {
        treeView.setVisible(!empty);
        treeView.setManaged(!empty);
        emptyState.setVisible(empty);
        emptyState.setManaged(empty);
    }

    private Node iconFor(final EditorProjectNode node, final TreeItem<EditorProjectNode> treeItem) {
        if (node.directory()) {
            if (isBuildTone(node)) {
                return EditorWorkspaceIcons.folderBuild();
            }
            if (node.taggedSourceRoot() || isInsideTaggedSourceRoot(treeItem)) {
                return EditorWorkspaceIcons.folderSource();
            }
            return EditorWorkspaceIcons.folderDefault();
        }
        if ("prometeu.json".equals(node.displayName())) {
            return EditorWorkspaceIcons.cog();
        }

        return EditorWorkspaceIcons.file();
    }

    private String labelFor(final EditorProjectNode node, final TreeItem<EditorProjectNode> treeItem) {
        if (!isRoot(treeItem)) {
            return node.displayName();
        }
        final var directoryName = Optional.ofNullable(node.path().getFileName())
                .map(Path::toString)
                .filter(name -> !name.isBlank())
                .orElse(node.displayName());
        return directoryName + " (" + node.displayName() + ")";
    }

    private boolean isInsideTaggedSourceRoot(final TreeItem<EditorProjectNode> item) {
        var current = item == null ? null : item.getParent();
        while (current != null && current.getValue() != null) {
            if (current.getValue().taggedSourceRoot()) {
                return true;
            }
            current = current.getParent();
        }
        return false;
    }

    private boolean isRoot(final TreeItem<EditorProjectNode> item) {
        return item != null && item.getParent() == null;
    }

    private boolean isBuildTone(final EditorProjectNode node) {
        return node.directory()
                && ("build".equals(node.displayName()) || "cartridge".equals(node.displayName()));
    }

    private Label emptyLabel() {
        final var placeholder = new Label();
        placeholder.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_NAVIGATOR_PLACEHOLDER));
        placeholder.getStyleClass().add("editor-workspace-placeholder");
        placeholder.setWrapText(true);
        return placeholder;
    }
}
