pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import qs.Components
import qs.Services

ListView {
	id: root

	property bool first
	property alias values: valuesModel.values

	signal itemMoved(from: int, to: int)
	signal itemRemoved(index: int)
	signal itemToggled(index: int, checked: bool)

	function dampOvershoot(overshoot: real, maxOvershoot: real): real {
		return overshoot / (1 + overshoot / maxOvershoot);
	}

	function labelFor(item: var): string {
		return item.label;
	}

	function toggledFor(item: var): bool {
		return false;
	}

	Layout.fillWidth: true
	implicitHeight: contentHeight
	interactive: false
	spacing: Tokens.spacing.extraSmall / 2

	add: Transition {
		Anim {
			from: 0
			properties: "opacity"
			to: 1
			type: Anim.DefaultEffects
		}
	}
	displaced: Transition {
		Anim {
			properties: "opacity"
			to: 1
			type: Anim.DefaultEffects
		}

		Anim {
			properties: "y"
		}
	}
	Behavior on implicitHeight {
		Anim {
		}
	}
	model: DelegateModel {
		id: visualModel

		delegate: ListRow {
		}
		model: ScriptModel {
			id: valuesModel
		}
	}
	move: Transition {
		Anim {
			properties: "opacity"
			to: 1
			type: Anim.DefaultEffects
		}

		Anim {
			properties: "y"
		}
	}
	remove: Transition {
		Anim {
			properties: "opacity"
			to: 0
			type: Anim.DefaultEffects
		}
	}

	component ListRow: Item {
		id: item

		property bool held
		property real lastMoveY
		required property var modelData
		property int pressIndex
		property point pressPos
		property real radiusLerpProg
		property real topRadius: root?.first && DelegateModel.itemsIndex === 0 ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall

		function lerpRadius(a: real, b: real): real {
			return a + (b - a) * radiusLerpProg;
		}

		anchors.left: root?.contentItem.left
		anchors.right: root?.contentItem.right
		implicitHeight: row.implicitHeight + row.anchors.margins * 2
		state: held ? "held" : ""
		z: held || returnAnim.running ? 1 : 0

		states: State {
			name: "held"

			PropertyChanges {
				dragIcon.color: Colors.palette.m3onPrimaryContainer
				elevation.opacity: 1
				item.radiusLerpProg: 1
				itemBg.color: Colors.palette.m3primaryContainer
				label.color: Colors.palette.m3onPrimaryContainer
				placeholder.opacity: 0.1
			}
		}
		Behavior on topRadius {
			Anim {
				type: Anim.DefaultEffects
			}
		}
		transitions: Transition {
			Anim {
				properties: "opacity,radiusLerpProg"
				type: Anim.SlowEffects
			}

			PropertyAction {
				properties: "color,inactiveOnColor"
			}
		}

		CustomRect {
			id: placeholder

			anchors.fill: parent
			color: Colors.palette.m3primary
			opacity: 0
			radius: Tokens.rounding.extraSmall
			topLeftRadius: item.topRadius
			topRightRadius: item.topRadius
		}

		MouseArea {
			id: mouse

			anchors.fill: parent
			preventStealing: true

			onPositionChanged: e => {
				item.lastMoveY = item.y;
				const absY = item.mapToItem(root.contentItem, 0, e.y).y;

				const swap = root.itemAt(0, absY);
				if (!swap || swap === item)
					return;

				const idx = item.DelegateModel.itemsIndex;
				const swapIdx = swap.DelegateModel.itemsIndex;
				visualModel.items.move(idx, swapIdx);
			}
			onPressed: e => {
				returnAnim.stop();
				item.pressPos = Qt.point(e.x, e.y);
				item.pressIndex = item.DelegateModel.itemsIndex;
				item.lastMoveY = item.y;
				item.held = true;

				itemContent.x = Qt.binding(() => {
					if (!root) {
						return 0;
					}

					const maxOvershoot = Tokens.padding.extraLargeIncreased;
					const x = mouse.mouseX - item.pressPos.x;
					return root.dampOvershoot(Math.abs(x), maxOvershoot) * Math.sign(x);
				});
				itemContent.y = Qt.binding(() => {
					if (!root)
						return 0;

					const maxOvershoot = Tokens.padding.extraLarge;
					const yDiff = item.y - item.lastMoveY; // Extra offset if the y of the item changed between mouseY updates
					const y = mouse.mouseY - item.pressPos.y - yDiff;
					const absY = item.mapToItem(root.contentItem, 0, y).y;
					const maxY = root.implicitHeight - item.implicitHeight;
					if (absY < 0)
						return y - absY - root.dampOvershoot(-absY, maxOvershoot);
					if (absY > maxY)
						return y - absY + maxY + root.dampOvershoot(absY - maxY, maxOvershoot);
					return y;
				});
			}
			onReleased: e => {
				// Break bindings
				itemContent.x = itemContent.x;
				itemContent.y = itemContent.y;
				returnAnim.start();
				item.held = false;

				const idx = item.DelegateModel.itemsIndex;
				if (idx !== item.pressIndex)
					root.itemMoved(item.pressIndex, idx);
			}
		}

		Anim {
			id: returnAnim

			properties: "x,y"
			target: itemContent
			to: 0
		}

		Item {
			id: itemContent

			implicitHeight: item.implicitHeight
			implicitWidth: item.width

			Elevation {
				id: elevation

				anchors.fill: parent
				level: 3
				opacity: 0
				radius: itemBg.radius
				topLeftRadius: itemBg.topLeftRadius
				topRightRadius: itemBg.topRightRadius
			}

			CustomRect {
				id: itemBg

				anchors.fill: parent
				color: Colors.tPalette.m3surfaceContainer
				radius: item.lerpRadius(Tokens.rounding.extraSmall, Tokens.rounding.largeIncreased)
				topLeftRadius: item.lerpRadius(item.topRadius, Tokens.rounding.largeIncreased)
				topRightRadius: item.lerpRadius(item.topRadius, Tokens.rounding.largeIncreased)
			}

			MouseArea {
				id: stateLayer

				acceptedButtons: Qt.NoButton
				anchors.fill: parent
				cursorShape: mouse.pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
				hoverEnabled: true

				CustomRect {
					anchors.fill: parent
					color: Colors.palette.m3onSurface
					opacity: parent.containsMouse && !item.held ? 0.08 : 0
					radius: itemBg.radius
					topLeftRadius: itemBg.topLeftRadius
					topRightRadius: itemBg.topRightRadius

					Behavior on opacity {
						Anim {
							type: Anim.DefaultEffects
						}
					}
				}
			}

			RowLayout {
				id: row

				anchors.fill: parent
				anchors.leftMargin: Tokens.padding.largeIncreased
				anchors.margins: Tokens.padding.small
				anchors.rightMargin: Tokens.padding.large
				spacing: Tokens.spacing.medium

				MaterialIcon {
					id: dragIcon

					color: Qt.alpha(Colors.palette.m3onSurfaceVariant, enabledSwitch.checked ? 1 : 0.5)
					font.pointSize: Tokens.font.size.normal
					text: "drag_indicator"
				}

				CustomText {
					id: label

					Layout.fillWidth: true
					color: Qt.alpha(Colors.palette.m3onSurface, enabledSwitch.checked ? 1 : 0.5)
					elide: Text.ElideRight
					font.pointSize: Tokens.font.size.smaller
					text: root.labelFor(item.modelData)
				}

				CustomSwitch {
					id: enabledSwitch

					checked: root.toggledFor(item.modelData)
					font.pointSize: Tokens.font.size.normal

					onToggled: root.itemToggled(item.DelegateModel.itemsIndex, checked)
				}

				IconButton {
					font.pointSize: Tokens.font.size.large
					icon: "delete"
					inactiveOnColor: Colors.palette.m3error
					isRound: true
					label.fill: 0
					type: IconButton.Text

					onClicked: root.itemRemoved(item.DelegateModel.itemsIndex)
				}
			}
		}
	}
}
