package com.imyeyu.fx.ui.components.navigation;

import com.imyeyu.fx.TimiFX;
import com.imyeyu.fx.ui.TimiFXUI;
import com.imyeyu.fx.utils.scroll.SmoothScroll;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TitledPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.Getter;

import java.util.List;

/**
 * 纵向导航组件，可实现多级折叠导航
 *
 * @author 夜雨
 * @since 2026-06-16
 */
public class Navigation extends ScrollPane implements TimiFXUI {

	/** 默认缩进像素 */
	static final double DEFAULT_INDENT_SIZE = 16;

	/** 导航项原始标题属性 */
	private static final String RAW_ITEM_TEXT = "RAW_ITEM_TEXT";

	/** 导航项原始图标属性 */
	private static final String RAW_ITEM_GRAPHIC = "RAW_ITEM_GRAPHIC";

	/** 导航项图标同步锁属性 */
	private static final String GRAPHIC_SYNCING = "GRAPHIC_SYNCING";

	/** 导航项是否已初始化 */
	private static final String INITIALIZED = "INITIALIZED";

	/** 导航数据列表 */
	@Getter
	protected final ObservableList<Node> items;

	/** 已选中监听 */
	protected final ObjectProperty<ToggleButton> selectedItem;

	/** 缩进像素 */
	private final DoubleProperty indentSize;

	/** 根容器 */
	private final VBox contentBox;

	/** 选择组 */
	private final ToggleGroup toggleGroup;

	/**
	 * 默认构造器
	 */
	public Navigation() {
		items = FXCollections.observableArrayList();
		selectedItem = new SimpleObjectProperty<>();
		indentSize = new SimpleDoubleProperty(DEFAULT_INDENT_SIZE);
		contentBox = new VBox();
		toggleGroup = new ToggleGroup();

		initView();
		initSelection();
		initPropertyListener();
		initItemsListener();
	}

	@Override
	public String getUserAgentStylesheet() {
		return Navigation.class.getResource(RESOURCE + "style/components/navigation.css").toExternalForm();
	}

	/**
	 * 初始化视图
	 */
	private void initView() {
		getStyleClass().addAll("navigation", "sp-border");
		setMaxWidth(Double.MAX_VALUE);
		setVbarPolicy(ScrollBarPolicy.NEVER);
		setFitToWidth(true);
		setContent(contentBox);

		SmoothScroll.pane.install(this);
	}

	/**
	 * 初始化选中同步
	 */
	private void initSelection() {
		selectedItem.addListener((obs, o, newSelectedItem) -> {
			if (newSelectedItem == null) {
				toggleGroup.selectToggle(null);
				return;
			}
			if (toggleGroup.getToggles().contains(newSelectedItem)) {
				toggleGroup.selectToggle(newSelectedItem);
			}
		});
		toggleGroup.selectedToggleProperty().addListener((obs, o, toggle) -> {
			if (toggle instanceof ToggleButton button) {
				selectedItem.set(button);
				return;
			}
			selectedItem.set(null);
		});
	}

	/**
	 * 初始化属性监听
	 */
	private void initPropertyListener() {
		indentSize.addListener((obs, o, n) -> rebuild());
	}

	/**
	 * 初始化导航数据监听
	 */
	private void initItemsListener() {
		items.addListener(this::handleItemsChanged);
	}

	/**
	 * 处理导航数据变更
	 *
	 * @param change 变更内容
	 */
	private void handleItemsChanged(ListChangeListener.Change<? extends Node> change) {
		while (change.next()) {
			if (change.wasAdded()) {
				for (Node node : change.getAddedSubList()) {
					bindGroup(node);
				}
			}
			if (change.wasRemoved()) {
				for (Node node : change.getRemoved()) {
					unbindGroup(node);
				}
			}
		}
		rebuild();
	}

	/**
	 * 绑定导航组刷新回调
	 *
	 * @param node 子节点
	 */
	private void bindGroup(Node node) {
		if (node instanceof NavigationGroup group) {
			group.setStructureChangedAction(this::rebuild);
		}
	}

	/**
	 * 解绑导航组刷新回调
	 *
	 * @param node 子节点
	 */
	private void unbindGroup(Node node) {
		if (node instanceof NavigationGroup group) {
			group.setStructureChangedAction(null);
		}
	}

	/**
	 * 重建导航结构
	 */
	private void rebuild() {
		contentBox.getChildren().clear();
		toggleGroup.getToggles().clear();

		for (Node node : items) {
			Node attachedNode = buildNode(node, 0);
			if (attachedNode != null) {
				contentBox.getChildren().add(attachedNode);
			}
		}

		refreshStandaloneItemStyle();
		if (selectedItem.get() != null && !toggleGroup.getToggles().contains(selectedItem.get())) {
			selectedItem.set(null);
		}
		if (selectedItem.get() != null) {
			toggleGroup.selectToggle(selectedItem.get());
		}
	}

	/**
	 * 构建节点
	 *
	 * @param node  节点
	 * @param level 层级
	 * @return 可挂载节点
	 */
	private Node buildNode(Node node, int level) {
		if (node instanceof NavigationGroup group) {
			return buildGroup(group, level);
		}
		if (node instanceof ToggleButton button) {
			return buildItem(button, level);
		}
		return null;
	}

	/**
	 * 构建导航组
	 *
	 * @param group 导航组
	 * @param level 层级
	 * @return 导航组节点
	 */
	private NavigationGroup buildGroup(NavigationGroup group, int level) {
		int currentLevel = level + group.getLevelOffset();
		group.applyLevel(currentLevel, getIndentSize());
		if (!group.getStyleClass().contains("group-pane")) {
			group.getStyleClass().add("group-pane");
		}

		VBox groupContent = group.getContentBox();
		groupContent.getChildren().clear();
		for (Node child : group.getItems()) {
			Node childNode = buildNode(child, currentLevel + 1);
			if (childNode != null) {
				groupContent.getChildren().add(childNode);
			}
		}
		return group;
	}

	/**
	 * 构建导航项
	 *
	 * @param button 导航项
	 * @param level  层级
	 * @return 导航项节点
	 */
	private ToggleButton buildItem(ToggleButton button, int level) {
		configureButton(button, level);
		toggleGroup.getToggles().add(button);
		return button;
	}

	/**
	 * 刷新独立导航项样式
	 */
	private void refreshStandaloneItemStyle() {
		List<Node> children = contentBox.getChildren();
		for (int i = 0; i < children.size(); i++) {
			Node node = children.get(i);
			if (node instanceof ToggleButton button) {
				button.getStyleClass().remove("after-group");
				if (0 < i && children.get(i - 1) instanceof TitledPane) {
					button.getStyleClass().add("after-group");
				}
			}
		}
	}

	/**
	 * 配置导航项
	 *
	 * @param button 导航项
	 * @param level  层级
	 */
	private void configureButton(ToggleButton button, int level) {
		cacheItemState(button);
		button.setText(resolveItemText(button));
		button.setMaxWidth(Double.MAX_VALUE);
		button.setAlignment(Pos.CENTER_LEFT);
		applyButtonIndent(button, Math.max(0, level) * getIndentSize());
		button.getStyleClass().setAll(CSS.MINECRAFT, "navigation-button");

		if (!Boolean.TRUE.equals(button.getProperties().get(INITIALIZED))) {
			button.addEventFilter(MouseEvent.MOUSE_PRESSED, TimiFX.EVENT_CONSUME_TG_BTN);
			button.graphicProperty().addListener((obs, o, n) -> {
				if (!Boolean.TRUE.equals(button.getProperties().get(GRAPHIC_SYNCING))) {
					button.getProperties().put(RAW_ITEM_GRAPHIC, n);
				}
			});
			button.getProperties().put(INITIALIZED, true);
		}
	}

	/**
	 * 缓存导航项原始状态
	 *
	 * @param button 导航项
	 */
	private void cacheItemState(ToggleButton button) {
		if (!(button.getProperties().get(RAW_ITEM_TEXT) instanceof String)) {
			button.getProperties().put(RAW_ITEM_TEXT, button.getText());
		}
		if (!button.getProperties().containsKey(RAW_ITEM_GRAPHIC)) {
			button.getProperties().put(RAW_ITEM_GRAPHIC, button.getGraphic());
		}
	}

	/**
	 * 获取导航项原始标题
	 *
	 * @param button 导航项
	 * @return 原始标题
	 */
	private String resolveItemText(ToggleButton button) {
		cacheItemState(button);
		return (String) button.getProperties().get(RAW_ITEM_TEXT);
	}

	/**
	 * 应用导航项缩进
	 *
	 * @param button      导航项
	 * @param indentWidth 缩进宽度
	 */
	private void applyButtonIndent(ToggleButton button, double indentWidth) {
		Node rawGraphic = (Node) button.getProperties().get(RAW_ITEM_GRAPHIC);
		if (0 == indentWidth) {
			button.getProperties().put(GRAPHIC_SYNCING, true);
			button.setGraphic(rawGraphic);
			button.getProperties().put(GRAPHIC_SYNCING, false);
			return;
		}

		Region spacer = new Region();
		spacer.setMinWidth(indentWidth);
		spacer.setPrefWidth(indentWidth);
		spacer.setMaxWidth(indentWidth);

		HBox graphicBox = new HBox();
		graphicBox.getChildren().add(spacer);
		if (rawGraphic != null) {
			graphicBox.getChildren().add(rawGraphic);
		}

		button.getProperties().put(GRAPHIC_SYNCING, true);
		button.setGraphic(graphicBox);
		button.getProperties().put(GRAPHIC_SYNCING, false);
	}

	/**
	 * 添加导航按钮
	 *
	 * @param buttons 导航按钮
	 */
	public void add(ToggleButton... buttons) {
		getItems().addAll(buttons);
	}

	/**
	 * 添加导航组
	 *
	 * @param groups 导航组
	 */
	public void add(NavigationGroup... groups) {
		getItems().addAll(groups);
	}

	/**
	 * 添加导航节点
	 *
	 * @param nodes 导航节点
	 */
	public void add(Node... nodes) {
		getItems().addAll(nodes);
	}

	/**
	 * 添加默认没有展开的导航组
	 *
	 * @param title   标题
	 * @param buttons 导航项
	 * @return 构造的导航组
	 */
	public NavigationGroup addGroup(String title, ToggleButton... buttons) {
		return addGroup(title, 0, false, buttons);
	}

	/**
	 * 添加默认展开的导航组
	 *
	 * @param title   标题
	 * @param buttons 导航项
	 * @return 构造的导航组
	 */
	public NavigationGroup addExpandedGroup(String title, ToggleButton... buttons) {
		return addGroup(title, 0, true, buttons);
	}

	/**
	 * 添加默认没有展开的导航组
	 *
	 * @param title   标题
	 * @param level   缩进偏移量
	 * @param buttons 导航项
	 * @return 构造的导航组
	 */
	public NavigationGroup addGroup(String title, int level, ToggleButton... buttons) {
		return addGroup(title, level, false, buttons);
	}

	/**
	 * 添加默认展开的导航组
	 *
	 * @param title   标题
	 * @param level   缩进偏移量
	 * @param buttons 导航项
	 * @return 构造的导航组
	 */
	public NavigationGroup addExpandedGroup(String title, int level, ToggleButton... buttons) {
		return addGroup(title, level, true, buttons);
	}

	/**
	 * 添加导航组
	 *
	 * @param title      标题
	 * @param level      缩进偏移量
	 * @param isExpanded true 为默认展开
	 * @param buttons    导航项
	 * @return 构造的导航组
	 */
	public NavigationGroup addGroup(String title, int level, boolean isExpanded, ToggleButton... buttons) {
		NavigationGroup group = new NavigationGroup(title).levelOffset(level).expanded(isExpanded);
		group.add(buttons);
		add(group);
		return group;
	}

	/**
	 * 添加导航组
	 *
	 * @param group 导航组
	 * @return 原导航组
	 */
	public NavigationGroup addGroup(NavigationGroup group) {
		add(group);
		return group;
	}

	/**
	 * 添加导航组
	 *
	 * @param group      导航组
	 * @param isExpanded true 为默认展开
	 * @param buttons    导航项
	 * @return 原导航组
	 */
	public NavigationGroup addGroup(NavigationGroup group, boolean isExpanded, ToggleButton... buttons) {
		return addGroup(group, group.getLevelOffset(), isExpanded, buttons);
	}

	/**
	 * 添加导航组
	 *
	 * @param group      导航组
	 * @param level      缩进偏移量
	 * @param isExpanded true 为默认展开
	 * @param buttons    导航项
	 * @return 原导航组
	 */
	public NavigationGroup addGroup(NavigationGroup group, int level, boolean isExpanded, ToggleButton... buttons) {
		group.levelOffset(level).expanded(isExpanded).add(buttons);
		add(group);
		return group;
	}

	/**
	 * 获取该按钮所属组
	 *
	 * @param button 按钮
	 * @return 所属组，null 时为不属于任何组
	 */
	public NavigationGroup getGroup(ToggleButton button) {
		for (Node node : items) {
			NavigationGroup group = findGroup(node, button);
			if (group != null) {
				return group;
			}
		}
		return null;
	}

	/**
	 * 设置当前激活导航项
	 *
	 * @param button 导航项
	 */
	public void setSelectedItem(ToggleButton button) {
		selectedItem.set(button);
	}

	/**
	 * 获取当前激活的导航项
	 *
	 * @return 当前激活的导航项
	 */
	public ToggleButton getSelectedItem() {
		return selectedItem.get();
	}

	/**
	 * 获取激活导航项监听
	 *
	 * @return 激活导航项监听
	 */
	public ObjectProperty<ToggleButton> selectedItem() {
		return selectedItem;
	}

	/**
	 * 设置缩进像素
	 *
	 * @param indentSize 缩进像素
	 */
	public void setIndentSize(double indentSize) {
		this.indentSize.set(Math.max(0, indentSize));
	}

	/**
	 * 获取缩进像素
	 *
	 * @return 缩进像素
	 */
	public double getIndentSize() {
		return indentSize.get();
	}

	/**
	 * 获取缩进像素属性
	 *
	 * @return 缩进像素属性
	 */
	public DoubleProperty indentSize() {
		return indentSize;
	}

	/**
	 * 查找按钮所属导航组
	 *
	 * @param node   当前节点
	 * @param button 目标按钮
	 * @return 所属导航组
	 */
	private NavigationGroup findGroup(Node node, ToggleButton button) {
		if (!(node instanceof NavigationGroup group)) {
			return null;
		}
		for (Node child : group.getItems()) {
			if (child == button) {
				return group;
			}
			NavigationGroup nestedGroup = findGroup(child, button);
			if (nestedGroup != null) {
				return nestedGroup;
			}
		}
		return null;
	}
}
