pragma ComponentBehavior: Bound

import Quickshell
import Quickshell.Services.Mpris
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import QtQuick.Effects
import ZShell.Components
import ZShell.Services
import ZShell.Config
import qs.Modules.Dashboard.Dash.Media
import qs.Daemons
import qs.Components
import qs.Helpers
import qs.Services

Item {
	id: root
	readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
	property real playerProgress: {
		const active = Players.active;
		return active?.length ? (active.position % active.length) / active.length : 0;
	}
	property int rowHeight: Tokens.padding.large + Config.dashboard.sizes.mediaProgressThickness + Tokens.spacing.small

	anchors.left: parent.left
	anchors.right: parent.right
	implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
	function lengthStr(length: int): string {
		if (length < 0)
		return "-1:-1";

		const hours = Math.floor(length / 3600);
		const mins = Math.floor((length % 3600) / 60);
		const secs = Math.floor(length % 60).toString().padStart(2, "0");

		if (hours > 0)
		return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
		return `${mins}:${secs}`;
	}

	Behavior on playerProgress {
		Anim {
			duration: Tokens.anim.durations.large
		}
	}

	Timer {
		running: Players.active?.isPlaying ?? false
		interval: Config.dashboard.mediaUpdateInterval
		triggeredOnStart: true
		repeat: true
		onTriggered: Players.active?.positionChanged()
	}

	ServiceRef {
		service: Audio.cava
	}

	BackgroundShapes {
		anchors.fill: parent
	}

	Shape {
		id: visualizer

		readonly property int bars: Config.services.visualizerBars
		property color color: Qt.alpha(Colors.palette.m3primary, 0.5)
		property color fillColor: Qt.alpha(color, 0.1)

		anchors.fill: parent
		anchors.leftMargin: -(shape.strokeWidth / 2)
		layer.enabled: true
		asynchronous: true
		preferredRendererType: Shape.CurveRenderer

		ShapePath {
			id: shape
			strokeColor: visualizer.color
			fillColor: visualizer.fillColor
			strokeWidth: 2

			PathSvg {
				path: shape.curvePath
			}

			property string curvePath: {
				const values = Audio.cava.values;
				const n = values.length;

				if (n < 2)
				return "";

				const h = root.height + shape.strokeWidth / 2;
				const w = root.width + shape.strokeWidth;

				function x(i) {
					return i * w / (n - 1);
				}

				function y(i) {
					return h - (values[i] * Config.dashboard.sizes.mediaVisualizerSize) / 2;
				}

				let d = `M 0 ${h} `;
				d += `L ${x(0)} ${y(0)} `;

				for (let i = 0; i < n - 1; ++i) {
					const x0 = x(i);
					const y0 = y(i);

					const x1 = x(i + 1);
					const y1 = y(i + 1);

					const prev = Math.max(0, i - 1);
					const next = Math.min(n - 1, i + 2);

					const c1x = x0 + (x1 - x(prev)) / 6;
					const c1y = y0 + (y1 - y(prev)) / 6;

					const c2x = x1 - (x(next) - x0) / 6;
					const c2y = y1 - (y(next) - y0) / 6;

					d += `C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x1} ${y1} `;
				}

				d += `L ${w} ${h} `;
				d += `L 0 ${h} Z`;

				return d;
			}
		}
	}

	CustomRect {
		anchors.fill: visualizer

		color: Qt.alpha(Colors.palette.m3shadow, 0.2)
		layer.enabled: true
		layer.effect: MultiEffect {
			maskEnabled: true
			maskSource: visualizer
		}
	}

	RowLayout {
		id: layout

		anchors.fill: parent
		anchors.margins: Tokens.padding.large
		anchors.leftMargin: Tokens.padding.extraLarge

		CoverVisualizer {
			Layout.fillHeight: true
			implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
		}

		ColumnLayout {
			Layout.fillWidth: true
			Layout.leftMargin: Tokens.spacing.largeIncreased
			Layout.rightMargin: Tokens.spacing.largeIncreased

			spacing: Tokens.spacing.small

			MarqueeText {
				id: title

				Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
				color: Colors.palette.m3primary
				font.pointSize: Tokens.font.size.normal
				horizontalAlignment: Text.AlignHCenter
				pauseMs: 4000
				text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
				implicitWidth: parent.width - Tokens.padding.large * 4
			}

			MarqueeText {
				id: album

				Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
				color: Colors.palette.m3outline
				font.pointSize: Tokens.font.size.small
				horizontalAlignment: Text.AlignHCenter
				pauseMs: 4000
				text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
				implicitWidth: parent.width - Tokens.padding.large * 4
			}

			MarqueeText {
				id: artist

				Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
				color: Colors.palette.m3secondary
				horizontalAlignment: Text.AlignHCenter
				pauseMs: 4000
				text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
				implicitWidth: parent.width - Tokens.padding.large * 4
			}

			RowLayout {
				id: positionSliderLayout
				Layout.topMargin: Tokens.spacing.large
				Layout.fillWidth: true
				spacing: Tokens.spacing.small

				TextMetrics {
					id: timeMetrics

					text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
				}

				CustomText {
					id: positionLabel

					Layout.preferredWidth: timeMetrics.width
					text: root.lengthStr(Players.active?.position ?? -1)
					color: Colors.palette.m3onSurfaceVariant
					font: timeMetrics.font
					horizontalAlignment: Text.AlignHCenter
				}

				CustomSlider {
					id: positionSlider

					Layout.fillWidth: true
					value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
					enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
					wavy: true
					animateWave: Players.active?.isPlaying ?? false
					waveFrequency: 5
					waveDuration: 2000
					interactionOnMove: false
					onInteraction: value => {
						const active = Players.active;
						if (active?.canSeek && active?.positionSupported)
						active.position = value * active.length;
					}
					Binding {
						target: positionLabel
						property: "text"
						value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
						when: positionSlider.dragging
					}
				}

				CustomText {
					Layout.preferredWidth: timeMetrics.width
					text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
					color: Colors.palette.m3onSurfaceVariant
					font: timeMetrics.font
					horizontalAlignment: Text.AlignHCenter
				}
			}
			ButtonRow {
				Layout.topMargin: Tokens.spacing.largeIncreased
				Layout.fillWidth: true
				spacing: Tokens.spacing.extraSmall
				Layout.leftMargin: positionSliderLayout.implicitWidth / 4
				Layout.rightMargin: positionSliderLayout.implicitWidth / 4

				IconButton {
					type: IconButton.Tonal
					icon: "shuffle"
					isRound: true
					shapeMorph: true
					checked: Players.active?.shuffle ?? false
					enabled: Players.active?.shuffleSupported
					onClicked: Players.active.shuffle = !Players.active?.shuffle
				}

				IconButton {
					id: previousBtn

					type: IconButton.Tonal
					icon: "skip_previous"
					isRound: true
					shapeMorph: true
					enabled: Players.active?.canGoPrevious
					onClicked: Players.active?.previous()
					font.pointSize: Tokens.font.size.large
				}

				IconButton {
					id: playPauseBtn

					icon: Players.active?.isPlaying ? "pause" : "play_arrow"
					isRound: true
					shapeMorph: true
					fillWidth: true
					checked: Players.active?.isPlaying ?? false
					enabled: Players.active?.canTogglePlaying
					onClicked: Players.active?.togglePlaying()
					font.pointSize: Tokens.font.size.large
				}

				IconButton {
					id: nextBtn

					type: IconButton.Tonal
					icon: "skip_next"
					isRound: true
					shapeMorph: true
					enabled: Players.active?.canGoNext
					onClicked: Players.active?.next()
					font.pointSize: Tokens.font.size.large
				}

				IconButton {
					type: IconButton.Tonal
					icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
					isRound: true
					shapeMorph: true
					checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
					enabled: Players.active?.loopSupported
					onClicked: {
						const state = Players.active.loopState;
						if (state === MprisLoopState.None)
						Players.active.loopState = MprisLoopState.Track;
						else if (state === MprisLoopState.Track)
						Players.active.loopState = MprisLoopState.Playlist;
						else
						Players.active.loopState = MprisLoopState.None;
					}
				}
			}
		}
	}
}
