pragma ComponentBehavior: Bound

import Quickshell
import QtQuick
import QtQuick.Layouts
import QtQml.Models
import qs.Components
import qs.Config
import qs.Helpers

Item {
	id: root

	property bool dragActive: false
	property real dragHeight: 0
	property real dragStartX: 0
	property real dragStartY: 0
	property real dragX: 0
	property real dragY: 0
	property var draggedModelData: null
	property string draggedUid: ""
	property bool dropAnimating: false
	readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
	required property string name
	required property var object
	property var pendingCommitEntries: []
	required property string setting
	property int uidCounter: 0
	property var visualEntries: []

	function beginVisualDrag(uid, modelData, item) {
		const pos = item.mapToItem(root, 0, 0);

		root.draggedUid = uid;
		root.draggedModelData = modelData;
		root.dragHeight = item.height;
		root.dragStartX = pos.x;
		root.dragStartY = pos.y;
		root.dragX = pos.x;
		root.dragY = pos.y;
		root.dragActive = true;
		root.dropAnimating = false;
		root.pendingCommitEntries = [];
	}

	function commitVisualOrder(entries) {
		const list = [];

		for (let i = 0; i < entries.length; i++)
			list.push(entries[i].entry);

		root.object[root.setting] = list;
		Config.save();
		root.rebuildVisualEntries();
	}

	function endVisualDrag() {
		const entries = root.visualEntries.slice();
		const finalIndex = root.indexForUid(root.draggedUid);
		const finalItem = listView.itemAtIndex(finalIndex);

		root.dragActive = false;

		if (!finalItem) {
			root.pendingCommitEntries = entries;
			root.finishVisualDrag();
			return;
		}

		const pos = finalItem.mapToItem(root, 0, 0);

		root.pendingCommitEntries = entries;
		root.dropAnimating = true;
		settleX.to = pos.x;
		settleY.to = pos.y;
		settleAnim.start();
	}

	function ensureVisualEntries() {
		if (!root.dragActive && !root.dropAnimating)
			root.rebuildVisualEntries();
	}

	function finishVisualDrag() {
		const entries = root.pendingCommitEntries.slice();

		root.dragActive = false;
		root.dropAnimating = false;
		root.draggedUid = "";
		root.draggedModelData = null;
		root.pendingCommitEntries = [];
		root.dragHeight = 0;

		root.commitVisualOrder(entries);
	}

	function iconForId(id) {
		switch (id) {
		case "workspaces":
			return "dashboard";
		case "audio":
			return "volume_up";
		case "media":
			return "play_arrow";
		case "resources":
			return "monitoring";
		case "updates":
			return "system_update";
		case "dash":
			return "space_dashboard";
		case "spacer":
			return "horizontal_rule";
		case "activeWindow":
			return "web_asset";
		case "tray":
			return "widgets";
		case "upower":
			return "battery_full";
		case "network":
			return "wifi";
		case "clock":
			return "schedule";
		case "notifBell":
			return "notifications";
		default:
			return "drag_indicator";
		}
	}

	function indexForUid(uid) {
		for (let i = 0; i < root.visualEntries.length; i++) {
			if (root.visualEntries[i].uid === uid)
				return i;
		}

		return -1;
	}

	function labelForId(id) {
		switch (id) {
		case "workspaces":
			return qsTr("Workspaces");
		case "audio":
			return qsTr("Audio");
		case "media":
			return qsTr("Media");
		case "resources":
			return qsTr("Resources");
		case "updates":
			return qsTr("Updates");
		case "dash":
			return qsTr("Dash");
		case "spacer":
			return qsTr("Spacer");
		case "activeWindow":
			return qsTr("Active window");
		case "tray":
			return qsTr("Tray");
		case "upower":
			return qsTr("Power");
		case "network":
			return qsTr("Network");
		case "clock":
			return qsTr("Clock");
		case "notifBell":
			return qsTr("Notification bell");
		default:
			return id;
		}
	}

	function moveArrayItem(list, from, to) {
		const next = list.slice();
		const [item] = next.splice(from, 1);
		next.splice(to, 0, item);
		return next;
	}

	function previewVisualMove(from, hovered, before) {
		let to = hovered + (before ? 0 : 1);

		if (to > from)
			to -= 1;

		to = Math.max(0, Math.min(visualModel.items.count - 1, to));

		if (from === to)
			return;

		visualModel.items.move(from, to);
		root.visualEntries = root.moveArrayItem(root.visualEntries, from, to);
	}

	function rebuildVisualEntries() {
		const entries = root.object[root.setting] ?? [];
		const next = [];

		for (let i = 0; i < entries.length; i++) {
			const entry = entries[i];
			let existing = null;

			for (let j = 0; j < root.visualEntries.length; j++) {
				if (root.visualEntries[j].entry === entry) {
					existing = root.visualEntries[j];
					break;
				}
			}

			if (existing)
				next.push(existing);
			else
				next.push({
					uid: `entry-${root.uidCounter++}`,
					entry
				});
		}

		root.visualEntries = next;
	}

	function updateEntry(index, value) {
		const list = [...root.object[root.setting]];
		const entry = list[index];
		entry.enabled = value;
		list[index] = entry;
		root.object[root.setting] = list;
		Config.save();
		root.ensureVisualEntries();
	}

	Layout.fillWidth: true
	implicitHeight: layout.implicitHeight

	Component.onCompleted: root.rebuildVisualEntries()

	Rectangle {
		anchors.fill: parent
		anchors.margins: -Appearance.padding.smaller
		color: DynamicColors.palette.m3primaryContainer
		opacity: root.highlighted ? 0.5 : 0
		radius: Appearance.rounding.small
		z: -1

		Behavior on opacity {
			Anim {
				duration: Appearance.anim.durations.normal
			}
		}
	}

	ParallelAnimation {
		id: settleAnim

		onFinished: root.finishVisualDrag()

		Anim {
			id: settleX

			duration: Appearance.anim.durations.normal
			property: "dragX"
			target: root
		}

		Anim {
			id: settleY

			duration: Appearance.anim.durations.normal
			property: "dragY"
			target: root
		}
	}

	ColumnLayout {
		id: layout

		anchors.fill: parent
		spacing: Appearance.spacing.smaller

		CustomText {
			Layout.fillWidth: true
			font.pointSize: Appearance.font.size.larger
			text: root.name
		}

		DelegateModel {
			id: visualModel

			delegate: entryDelegate

			model: ScriptModel {
				objectProp: "uid"
				values: root.visualEntries
			}
		}

		ListView {
			id: listView

			Layout.fillWidth: true
			Layout.preferredHeight: contentHeight
			boundsBehavior: Flickable.StopAtBounds
			clip: false
			implicitHeight: contentHeight
			implicitWidth: width
			interactive: !(root.dragActive || root.dropAnimating)
			model: visualModel
			spacing: Appearance.spacing.small

			add: Transition {
				Anim {
					properties: "opacity,scale"
					to: 1
				}
			}
			addDisplaced: Transition {
				Anim {
					duration: Appearance.anim.durations.normal
					property: "y"
				}
			}
			displaced: Transition {
				Anim {
					duration: Appearance.anim.durations.normal
					property: "y"
				}
			}
			move: Transition {
				Anim {
					duration: Appearance.anim.durations.normal
					property: "y"
				}
			}
			removeDisplaced: Transition {
				Anim {
					duration: Appearance.anim.durations.normal
					property: "y"
				}
			}
		}
	}

	Loader {
		active: root.dragActive || root.dropAnimating
		asynchronous: false

		sourceComponent: Item {
			Drag.active: root.dragActive
			Drag.hotSpot.x: width / 2
			Drag.hotSpot.y: height / 2
			height: proxyRect.implicitHeight
			implicitHeight: proxyRect.implicitHeight
			implicitWidth: listView.width
			visible: root.draggedModelData !== null
			width: listView.width
			x: root.dragX
			y: root.dragY
			z: 100

			Drag.source: QtObject {
				property string uid: root.draggedUid
				property int visualIndex: root.indexForUid(root.draggedUid)
			}

			CustomRect {
				id: proxyRect

				color: DynamicColors.tPalette.m3surface
				implicitHeight: proxyRow.implicitHeight + Appearance.padding.small * 2
				implicitWidth: parent.width
				opacity: 0.95
				radius: Appearance.rounding.normal
				width: parent.width

				RowLayout {
					id: proxyRow

					anchors.fill: parent
					anchors.margins: Appearance.padding.small
					spacing: Appearance.spacing.normal

					CustomRect {
						color: Qt.alpha(DynamicColors.palette.m3onSurface, 0.12)
						implicitHeight: 32
						implicitWidth: implicitHeight
						radius: Appearance.rounding.small

						MaterialIcon {
							anchors.centerIn: parent
							color: DynamicColors.palette.m3onSurfaceVariant
							text: "drag_indicator"
						}
					}

					MaterialIcon {
						color: DynamicColors.palette.m3onSurfaceVariant
						text: root.iconForId(root.draggedModelData?.entry?.id ?? "")
					}

					CustomText {
						Layout.fillWidth: true
						font.pointSize: Appearance.font.size.larger
						text: root.labelForId(root.draggedModelData?.entry?.id ?? "")
					}

					CustomSwitch {
						checked: root.draggedModelData?.entry?.enabled ?? true
						enabled: false
					}
				}
			}
		}
	}

	Component {
		id: entryDelegate

		DropArea {
			id: slot

			readonly property var entryData: modelData.entry
			required property var modelData
			readonly property string uid: modelData.uid

			function previewReorder(drag) {
				const source = drag.source;
				if (!source || !source.uid || source.uid === slot.uid)
					return;

				const from = source.visualIndex;
				const hovered = slot.DelegateModel.itemsIndex;

				if (from < 0 || hovered < 0)
					return;

				root.previewVisualMove(from, hovered, drag.y < height / 2);
			}

			height: entryRow.implicitHeight
			implicitHeight: entryRow.implicitHeight
			implicitWidth: listView.width
			width: ListView.view ? ListView.view.width : listView.width

			onEntered: drag => previewReorder(drag)
			onPositionChanged: drag => previewReorder(drag)

			CustomRect {
				id: entryRow

				anchors.fill: parent
				color: DynamicColors.tPalette.m3surface
				implicitHeight: entryLayout.implicitHeight + Appearance.padding.small * 2
				implicitWidth: parent.width
				opacity: root.draggedUid === slot.uid ? 0 : 1
				radius: Appearance.rounding.full

				Behavior on opacity {
					Anim {
					}
				}

				RowLayout {
					id: entryLayout

					anchors.fill: parent
					anchors.margins: Appearance.padding.small
					spacing: Appearance.spacing.normal

					CustomRect {
						id: handle

						color: Qt.alpha(DynamicColors.palette.m3onSurface, handleDrag.active ? 0.12 : handleHover.hovered ? 0.09 : 0.06)
						implicitHeight: 32
						implicitWidth: implicitHeight
						radius: Appearance.rounding.full

						Behavior on color {
							CAnim {
							}
						}

						MaterialIcon {
							anchors.centerIn: parent
							color: DynamicColors.palette.m3onSurfaceVariant
							text: "drag_indicator"
						}

						HoverHandler {
							id: handleHover

							cursorShape: handleDrag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor
						}

						DragHandler {
							id: handleDrag

							enabled: true
							grabPermissions: PointerHandler.CanTakeOverFromAnything
							target: null
							xAxis.enabled: false
							yAxis.enabled: true

							onActiveChanged: {
								if (active) {
									root.beginVisualDrag(slot.uid, slot.modelData, entryRow);
								} else if (root.draggedUid === slot.uid) {
									root.endVisualDrag();
								}
							}
							onActiveTranslationChanged: {
								if (!active || root.draggedUid !== slot.uid)
									return;

								root.dragX = root.dragStartX;
								root.dragY = root.dragStartY + activeTranslation.y;
							}
						}
					}

					MaterialIcon {
						color: DynamicColors.palette.m3onSurfaceVariant
						text: root.iconForId(slot.entryData.id)
					}

					CustomText {
						Layout.fillWidth: true
						font.pointSize: Appearance.font.size.larger
						text: root.labelForId(slot.entryData.id)
					}

					CustomSwitch {
						Layout.rightMargin: Appearance.padding.small
						checked: slot.entryData.enabled ?? true

						onToggled: root.updateEntry(slot.DelegateModel.itemsIndex, checked)
					}
				}
			}
		}
	}
}
