package p.studio.controls;

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TitledPane;

import java.util.Objects;

public abstract class WorkspaceDockPane extends TitledPane {
    private static final double DEFAULT_MAXIMUM_DOCK_SHARE = 0.50;
    private static final double DEFAULT_FALLBACK_HEIGHT = 520.0;

    private final double collapsedHeight;
    private final double minimumExpandedHeight;
    private final double defaultExpandedHeight;
    private final double maximumDockShare;
    private final ChangeListener<Number> dividerPositionListener = (ignored, previous, current) -> normalizeDivider();
    private boolean syncingDivider;
    private SplitPane dockSplit;
    private SplitPane.Divider observedDivider;
    private Double lastExpandedDividerPosition;

    protected WorkspaceDockPane(
            final ObservableValue<String> titleBinding,
            final double collapsedHeight,
            final double minimumExpandedHeight,
            final double defaultExpandedHeight,
            final boolean expandedByDefault,
            final String... styleClasses) {
        this(titleBinding, collapsedHeight, minimumExpandedHeight, defaultExpandedHeight, DEFAULT_MAXIMUM_DOCK_SHARE, expandedByDefault, styleClasses);
    }

    protected WorkspaceDockPane(
            final ObservableValue<String> titleBinding,
            final double collapsedHeight,
            final double minimumExpandedHeight,
            final double defaultExpandedHeight,
            final double maximumDockShare,
            final boolean expandedByDefault,
            final String... styleClasses) {
        this.collapsedHeight = collapsedHeight;
        this.minimumExpandedHeight = minimumExpandedHeight;
        this.defaultExpandedHeight = defaultExpandedHeight;
        this.maximumDockShare = maximumDockShare;

        textProperty().bind(Objects.requireNonNull(titleBinding, "titleBinding"));
        getStyleClass().add("workspace-dock-pane");
        if (styleClasses != null && styleClasses.length > 0) {
            getStyleClass().addAll(styleClasses);
        }
        setCollapsible(true);
        setExpanded(expandedByDefault);
        setAnimated(false);
        setMinHeight(expandedByDefault ? minimumExpandedHeight : collapsedHeight);
        setPrefHeight(defaultExpandedHeight);
        setMaxHeight(expandedByDefault ? Double.MAX_VALUE : collapsedHeight);
        expandedProperty().addListener((ignored, previous, expanded) -> {
            if (expanded) {
                expandDock();
            } else {
                collapseDock();
            }
        });
    }

    public final double collapsedHeight() {
        return collapsedHeight;
    }

    public final double minimumExpandedHeight() {
        return minimumExpandedHeight;
    }

    public final double defaultExpandedHeight() {
        return defaultExpandedHeight;
    }

    public final SplitPane createBottomDockLayout(final Node primaryContent, final String... splitStyleClasses) {
        final var split = new SplitPane(Objects.requireNonNull(primaryContent, "primaryContent"), this);
        split.setOrientation(Orientation.VERTICAL);
        split.getStyleClass().add("workspace-dock-split");
        if (splitStyleClasses != null && splitStyleClasses.length > 0) {
            split.getStyleClass().addAll(splitStyleClasses);
        }
        SplitPane.setResizableWithParent(primaryContent, true);
        SplitPane.setResizableWithParent(this, true);
        dockSplit = split;
        bindDividerPositionListener();
        dockSplit.heightProperty().addListener((ignored, previous, current) -> {
            if (isExpanded()) {
                normalizeDivider();
            } else {
                applyCollapsedDivider();
            }
        });
        dockSplit.getDividers().addListener((javafx.collections.ListChangeListener<? super SplitPane.Divider>) change -> {
            while (change.next()) {
                if (change.wasAdded() && !change.getAddedSubList().isEmpty()) {
                    bindDividerPositionListener();
                }
            }
        });
        if (isExpanded()) {
            expandDock();
        } else {
            collapseDock();
        }
        return split;
    }

    public final DockState captureDockState() {
        final double preferredExpandedDivider = lastExpandedDividerPosition != null
                ? lastExpandedDividerPosition
                : currentDivider();
        return new DockState(isExpanded(), preferredExpandedDivider);
    }

    public final void restoreDockState(final DockState dockState) {
        if (dockState == null) {
            return;
        }
        lastExpandedDividerPosition = dockState.dividerPosition();
        setExpanded(dockState.expanded());
        if (dockState.expanded()) {
            setDivider(clampExpandedDivider(dockState.dividerPosition()));
        } else if (dockSplit != null) {
            applyCollapsedDivider();
        }
    }

    protected final void setDockContent(final Node content) {
        setContent(Objects.requireNonNull(content, "content"));
    }

    private void expandDock() {
        setMinHeight(minimumExpandedHeight);
        setPrefHeight(defaultExpandedHeight);
        setMaxHeight(Double.MAX_VALUE);
        getStyleClass().remove("workspace-dock-pane-collapsed");
        if (dockSplit != null) {
            dockSplit.getStyleClass().remove("workspace-dock-split-collapsed");
            updateDividerInteractivity(false);
            final double restored = lastExpandedDividerPosition != null ? lastExpandedDividerPosition : defaultDividerPosition();
            setDivider(clampExpandedDivider(restored));
        }
    }

    private void collapseDock() {
        setMinHeight(collapsedHeight);
        setPrefHeight(collapsedHeight);
        setMaxHeight(collapsedHeight);
        if (!getStyleClass().contains("workspace-dock-pane-collapsed")) {
            getStyleClass().add("workspace-dock-pane-collapsed");
        }
        if (dockSplit != null && !dockSplit.getStyleClass().contains("workspace-dock-split-collapsed")) {
            dockSplit.getStyleClass().add("workspace-dock-split-collapsed");
        }
        if (dockSplit != null) {
            applyCollapsedDivider();
            updateDividerInteractivity(true);
        }
    }

    private void applyCollapsedDivider() {
        final double height = currentHeight();
        final double collapsedPosition = Math.max(0.0, 1.0 - (collapsedHeight / height));
        setDivider(Math.min(collapsedPosition, 0.985));
    }

    private void normalizeDivider() {
        if (syncingDivider || dockSplit == null || dockSplit.getDividers().isEmpty()) {
            return;
        }
        final double clamped = clampExpandedDivider(currentDivider());
        lastExpandedDividerPosition = clamped;
        if (Math.abs(clamped - currentDivider()) > 0.0001) {
            setDivider(clamped);
        }
    }

    private double clampExpandedDivider(final double position) {
        final double minDivider = 1.0 - maximumDockShare;
        final double maxDivider = Math.max(minDivider, 1.0 - (minimumExpandedHeight / currentHeight()));
        if (position < minDivider) {
            return minDivider;
        }
        if (position > maxDivider) {
            return maxDivider;
        }
        return position;
    }

    private double defaultDividerPosition() {
        final double preferred = 1.0 - (defaultExpandedHeight / currentHeight());
        return clampExpandedDivider(preferred);
    }

    private double currentHeight() {
        if (dockSplit == null || dockSplit.getHeight() <= 0) {
            return DEFAULT_FALLBACK_HEIGHT;
        }
        return dockSplit.getHeight();
    }

    private double currentDivider() {
        return dockSplit == null || dockSplit.getDividers().isEmpty() ? defaultDividerPosition() : dockSplit.getDividers().get(0).getPosition();
    }

    private void setDivider(final double position) {
        if (dockSplit == null) {
            return;
        }
        syncingDivider = true;
        try {
            dockSplit.setDividerPositions(position);
        } finally {
            syncingDivider = false;
        }
    }

    private void bindDividerPositionListener() {
        if (dockSplit == null || dockSplit.getDividers().isEmpty()) {
            return;
        }
        final var divider = dockSplit.getDividers().get(0);
        if (observedDivider == divider) {
            return;
        }
        if (observedDivider != null) {
            observedDivider.positionProperty().removeListener(dividerPositionListener);
        }
        observedDivider = divider;
        observedDivider.positionProperty().addListener(dividerPositionListener);
    }

    private void updateDividerInteractivity(final boolean disabled) {
        if (dockSplit == null) {
            return;
        }
        Platform.runLater(() -> dockSplit.lookupAll(".split-pane-divider").forEach(node -> {
            node.setMouseTransparent(disabled);
            node.setVisible(!disabled);
            node.setManaged(!disabled);
        }));
    }

    public record DockState(boolean expanded, double dividerPosition) {
    }
}
