pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Services.Notifications
import ZShell.Components
import qs.Daemons
import qs.Helpers
import ZShell.Config
import qs.Components
import qs.Services

CustomRect {
	id: root

	property bool expanded: Config.notifs.openExpanded
	readonly property bool hasAppIcon: modelData.appIcon.length > 0
	readonly property bool hasImage: modelData.image.length > 0
	required property NotifServer.Notif modelData
	readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? Tokens.spacing.extraSmall * 2 + appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2

	color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondaryContainer : Colors.tPalette.m3surfaceContainer
	implicitHeight: inner.implicitHeight
	radius: Tokens.rounding.large - Tokens.padding.smaller
	x: implicitWidth

	Behavior on x {
		Anim {
			easing: Tokens.anim.emphasizedDecel
		}
	}

	Component.onCompleted: {
		x = 0;
		modelData.lock(this);
	}
	Component.onDestruction: modelData.unlock(this)

	MouseArea {
		property int startY

		acceptedButtons: Qt.LeftButton | Qt.MiddleButton
		anchors.fill: parent
		cursorShape: root.expanded && body.hoveredLink ? Qt.PointingHandCursor : pressed ? Qt.ClosedHandCursor : undefined
		drag.axis: Drag.XAxis
		drag.target: parent
		hoverEnabled: true
		preventStealing: true

		onClicked: event => {
			if (!Config.notifs.actionOnClick || event.button !== Qt.LeftButton)
				return;

			const actions = root.modelData.actions;
			if (actions.length === 1)
				actions[0].invoke();
		}
		onEntered: root.modelData.timer.stop()
		onExited: {
			if (!pressed)
				root.modelData.timer.start();
		}
		onPositionChanged: event => {
			if (pressed) {
				const diffY = event.y - startY;
				if (Math.abs(diffY) > Config.notifs.expandThreshold)
					root.expanded = diffY > 0;
			}
		}
		onPressed: event => {
			root.modelData.timer.stop();
			startY = event.y;
			if (event.button === Qt.MiddleButton)
				root.modelData.close();
		}
		onReleased: event => {
			if (!containsMouse)
				root.modelData.timer.start();

			if (Math.abs(root.x) < root.implicitWidth * Config.notifs.clearThreshold)
				root.x = 0;
			else
				root.modelData.popup = false;
		}

		Item {
			id: inner

			anchors.left: parent.left
			anchors.margins: Tokens.padding.small
			anchors.right: parent.right
			anchors.top: parent.top
			implicitHeight: root.nonAnimHeight

			Behavior on implicitHeight {
				Anim {
				}
			}

			Loader {
				id: image

				active: root.hasImage
				anchors.left: parent.left
				anchors.top: parent.top
				asynchronous: true
				height: Config.notifs.sizes.image
				visible: root.hasImage || root.hasAppIcon
				width: Config.notifs.sizes.image

				sourceComponent: CustomClippingRect {
					color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) : Colors.palette.m3secondaryContainer
					implicitHeight: Config.notifs.sizes.image
					implicitWidth: Config.notifs.sizes.image
					radius: Tokens.rounding.full

					Image {
						anchors.fill: parent
						asynchronous: true
						cache: false
						fillMode: Image.PreserveAspectCrop
						source: Qt.resolvedUrl(root.modelData.image)
						sourceSize: {
							const size = Config.notifs.sizes.image * ((QsWindow.window as QsWindow)?.devicePixelRatio ?? 1);
							return Qt.size(size, size);
						}
					}
				}
			}

			Loader {
				id: appIcon

				active: root.hasAppIcon || !root.hasImage
				anchors.bottom: root.hasImage ? image.bottom : undefined
				anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
				anchors.right: root.hasImage ? image.right : undefined
				anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
				asynchronous: true

				sourceComponent: CustomRect {
					color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) : Colors.palette.m3secondaryContainer
					implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
					implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
					radius: Tokens.rounding.full

					Loader {
						id: icon

						active: root.hasAppIcon
						anchors.centerIn: parent
						asynchronous: true
						height: Math.round(parent.width * 0.6)
						width: Math.round(parent.width * 0.6)

						sourceComponent: ColoredIcon {
							anchors.fill: parent
							color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colors.palette.m3onSurface : Colors.palette.m3onSecondaryContainer
							layer.enabled: root.modelData.appIcon.endsWith("symbolic")
							source: Quickshell.iconPath(root.modelData.appIcon)
						}
					}

					Loader {
						active: !root.hasAppIcon
						anchors.centerIn: parent
						anchors.verticalCenterOffset: 1
						asynchronous: true

						sourceComponent: MaterialIcon {
							color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colors.palette.m3onSurface : Colors.palette.m3onSecondaryContainer
							text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
						}
					}
				}
			}

			Shape {
				id: progressIndicator

				anchors.centerIn: appIcon
				height: appIcon.implicitHeight + progressShape.strokeWidth * 2
				preferredRendererType: Shape.CurveRenderer
				width: appIcon.implicitWidth + progressShape.strokeWidth * 2

				ShapePath {
					id: progressShape

					capStyle: ShapePath.RoundCap
					fillColor: "transparent"
					strokeColor: Colors.palette.m3primary
					strokeWidth: 2

					PathAngleArc {
						id: progressArc

						centerX: progressIndicator.width / 2
						centerY: progressIndicator.height / 2
						radiusX: progressIndicator.width / 2 - Tokens.padding.extraSmall / 2
						radiusY: progressIndicator.height / 2 - Tokens.padding.extraSmall / 2
						startAngle: -90
						sweepAngle: ((root.modelData.hints.value ?? 0) / 100) * 360

						Behavior on sweepAngle {
							Anim {
								easing: Tokens.anim.emphasizedDecel
							}
						}
					}
				}
			}

			CustomText {
				id: appName

				anchors.left: image.right
				anchors.leftMargin: Tokens.spacing.small
				anchors.top: parent.top
				animate: true
				color: Colors.palette.m3onSurfaceVariant
				maximumLineCount: 1
				opacity: root.expanded ? 1 : 0
				text: appNameMetrics.elidedText

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

			TextMetrics {
				id: appNameMetrics

				elide: Text.ElideRight
				elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Tokens.spacing.small * 3
				font: appName.font
				text: root.modelData.appName
			}

			CustomText {
				id: summary

				anchors.left: image.right
				anchors.leftMargin: Tokens.spacing.small
				anchors.top: parent.top
				animate: true
				font.pointSize: Tokens.font.size.small
				height: implicitHeight
				maximumLineCount: 1
				text: summaryMetrics.elidedText

				Behavior on height {
					Anim {
					}
				}
				states: State {
					name: "expanded"
					when: root.expanded

					PropertyChanges {
						body.anchors.topMargin: Tokens.spacing.extraSmall
						bodyPreview.anchors.topMargin: Tokens.spacing.extraSmall
						summary.anchors.topMargin: Tokens.spacing.extraSmall
						summary.maximumLineCount: undefined
					}

					AnchorChanges {
						anchors.top: appName.bottom
						target: summary
					}
				}
				transitions: Transition {
					PropertyAction {
						property: "maximumLineCount"
						target: summary
					}

					Anim {
						property: "topMargin"
					}

					AnchorAnim {
					}
				}
			}

			TextMetrics {
				id: summaryMetrics

				elide: Text.ElideRight
				elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Tokens.spacing.small * 3
				font: summary.font
				text: root.modelData.summary
			}

			CustomText {
				id: timeSep

				anchors.left: summary.right
				anchors.leftMargin: Tokens.spacing.small
				anchors.top: parent.top
				color: Colors.palette.m3onSurfaceVariant
				text: "•"

				states: State {
					name: "expanded"
					when: root.expanded

					AnchorChanges {
						anchors.left: appName.right
						target: timeSep
					}
				}
				transitions: Transition {
					AnchorAnim {
					}
				}
			}

			CustomText {
				id: time

				anchors.left: timeSep.right
				anchors.leftMargin: Tokens.spacing.small
				anchors.top: parent.top
				animate: true
				color: Colors.palette.m3onSurfaceVariant
				font.pointSize: Tokens.font.size.small
				horizontalAlignment: Text.AlignLeft
				text: root.modelData.timeStr
			}

			Item {
				id: expandBtn

				anchors.right: parent.right
				anchors.top: parent.top
				anchors.topMargin: -Tokens.padding.extraSmall
				implicitHeight: expandIcon.implicitHeight
				implicitWidth: expandIcon.implicitHeight

				StateLayer {
					color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onSurface
					radius: Tokens.rounding.full

					onClicked: root.expanded = !root.expanded
				}

				MaterialIcon {
					id: expandIcon

					anchors.centerIn: parent
					anchors.verticalCenterOffset: root.expanded ? -1 : 1
					rotation: root.expanded ? 180 : 0
					text: "expand_more"

					Behavior on anchors.verticalCenterOffset {
						Anim {
						}
					}
					Behavior on rotation {
						Anim {
						}
					}
				}
			}

			CustomText {
				id: bodyPreview

				anchors.left: summary.left
				anchors.right: expandBtn.left
				anchors.rightMargin: Tokens.spacing.small
				anchors.top: summary.bottom
				animate: true
				color: Colors.palette.m3onSurfaceVariant
				font.pointSize: Tokens.font.size.small
				opacity: root.expanded ? 0 : 1
				text: bodyPreviewMetrics.elidedText
				textFormat: Text.MarkdownText

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

			TextMetrics {
				id: bodyPreviewMetrics

				elide: Text.ElideRight
				elideWidth: bodyPreview.width
				font: bodyPreview.font
				text: root.modelData.body
			}

			CustomText {
				id: body

				anchors.left: summary.left
				anchors.right: expandBtn.left
				anchors.rightMargin: Tokens.spacing.small
				anchors.top: summary.bottom
				animate: true
				color: Colors.palette.m3onSurfaceVariant
				height: text ? implicitHeight : 0
				opacity: root.expanded ? 1 : 0
				text: root.modelData.body
				textFormat: Text.MarkdownText
				wrapMode: Text.WrapAtWordBoundaryOrAnywhere

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

				onLinkActivated: link => {
					if (!root.expanded)
						return;

					Quickshell.execDetached(["app2unit", "-O", "--", link]);
					root.modelData.popup = false;
				}
			}

			ButtonRow {
				id: actions

				anchors.left: parent.left
				anchors.right: parent.right
				anchors.top: body.bottom
				anchors.topMargin: Tokens.spacing.small
				opacity: root.expanded ? 1 : 0
				spacing: Tokens.spacing.extraSmall

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

				IconButton {
					enabled: root.expanded
					fillWidth: root.modelData.actions.length === 0
					icon: "close"
					inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondary : Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
					inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondary : Colors.palette.m3onSurfaceVariant
					isRound: true
					padding: Tokens.padding.extraSmall
					shapeMorph: true

					onClicked: root.modelData.close()
				}

				Repeater {
					model: root.modelData.actions

					TextButton {
						required property var modelData

						enabled: root.expanded
						fillWidth: true
						font.pointSize: Tokens.font.size.small
						implicitWidth: label.implicitWidth
						inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondary : Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
						inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondary : Colors.palette.m3onSurfaceVariant
						isRound: true
						label.anchors.centerIn: undefined
						label.anchors.left: left
						label.anchors.margins: Tokens.padding.small
						label.anchors.right: right
						label.anchors.verticalCenter: verticalCenter
						label.elide: Text.ElideRight
						label.horizontalAlignment: Text.AlignHCenter
						shapeMorph: true
						text: modelData.text

						onClicked: modelData.invoke()
					}
				}

				IconButton {
					enabled: root.expanded
					fillWidth: root.modelData.actions.length === 0
					icon: copyTimer.running ? "inventory" : "content_copy"
					inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondary : Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
					inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondary : Colors.palette.m3onSurfaceVariant
					isRound: true
					label.animate: true
					padding: Tokens.padding.extraSmall
					shapeMorph: true

					onClicked: {
						Quickshell.clipboardText = root.modelData.body;
						copyTimer.restart();
					}

					Timer {
						id: copyTimer

						interval: 3000
					}
				}
			}
		}
	}
}
