pragma Singleton

import ZShell.Services
import ZShell
import Quickshell
import Quickshell.Services.Pipewire
import QtQuick
import ZShell.Config

Singleton {
	id: root

	readonly property alias beatTracker: beatTracker
	readonly property alias cava: cava
	readonly property bool muted: !!sink?.audio?.muted
	readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => {
		if (!node.isStream) {
			if (node.isSink)
				acc.sinks.push(node);
			else if (node.audio)
				acc.sources.push(node);
		} else if (node.isStream && node.audio) {
			// Application streams (output streams)
			acc.streams.push(node);
		}
		return acc;
	}, {
		sources: [],
		sinks: [],
		streams: []
	})
	property string previousSinkName: ""
	property string previousSourceName: ""
	readonly property PwNode sink: Pipewire.defaultAudioSink
	readonly property list<PwNode> sinks: nodes.sinks
	readonly property PwNode source: Pipewire.defaultAudioSource
	readonly property bool sourceMuted: !!source?.audio?.muted
	readonly property real sourceVolume: source?.audio?.volume ?? 0
	readonly property list<PwNode> sources: nodes.sources
	readonly property list<PwNode> streams: nodes.streams
	readonly property real volume: sink?.audio?.volume ?? 0

	function decrementSourceVolume(amount: real): void {
		setSourceVolume(sourceVolume - (amount || Config.services.audioIncrement));
	}

	function decrementVolume(amount: real): void {
		setVolume(volume - (amount || Config.services.audioIncrement));
	}

	function getStreamMuted(stream: PwNode): bool {
		return !!stream?.audio?.muted;
	}

	function getStreamName(stream: PwNode): string {
		if (!stream)
			return qsTr("Unknown");
		// Try application name first, then description, then name
		return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
	}

	function getStreamVolume(stream: PwNode): real {
		return stream?.audio?.volume ?? 0;
	}

	function incrementSourceVolume(amount: real): void {
		setSourceVolume(sourceVolume + (amount || Config.services.audioIncrement));
	}

	function incrementVolume(amount: real): void {
		setVolume(volume + (amount || Config.services.audioIncrement));
	}

	function setAudioSink(newSink: PwNode): void {
		Pipewire.preferredDefaultAudioSink = newSink;
	}

	function setAudioSource(newSource: PwNode): void {
		Pipewire.preferredDefaultAudioSource = newSource;
	}

	function setSourceVolume(newVolume: real): void {
		if (source?.ready && source?.audio) {
			source.audio.muted = false;
			source.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
		}
	}

	function setStreamMuted(stream: PwNode, muted: bool): void {
		if (stream?.ready && stream?.audio) {
			stream.audio.muted = muted;
		}
	}

	function setStreamVolume(stream: PwNode, newVolume: real): void {
		if (stream?.ready && stream?.audio) {
			stream.audio.muted = false;
			stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
		}
	}

	function setVolume(newVolume: real): void {
		if (sink?.ready && sink?.audio) {
			sink.audio.muted = false;
			sink.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
		}
	}

	Component.onCompleted: {
		previousSinkName = sink?.description || sink?.name || qsTr("Unknown Device");
		previousSourceName = source?.description || source?.name || qsTr("Unknown Device");
	}
	onSinkChanged: {
		if (!sink?.ready)
			return;

		const newSinkName = sink.description || sink.name || qsTr("Unknown Device");

		if (previousSinkName && previousSinkName !== newSinkName && Config.utilities.toasts.audioOutputChanged)
			Toaster.toast(qsTr("Audio output changed"), qsTr("Now using: %1").arg(newSinkName), "volume_up");

		previousSinkName = newSinkName;
	}
	onSourceChanged: {
		if (!source?.ready)
			return;

		const newSourceName = source.description || source.name || qsTr("Unknown Device");

		if (previousSourceName && previousSourceName !== newSourceName && Config.utilities.toasts.audioInputChanged)
			Toaster.toast(qsTr("Audio input changed"), qsTr("Now using: %1").arg(newSourceName), "mic");

		previousSourceName = newSourceName;
	}

	CavaProvider {
		id: cava

		bars: Config.services.visualizerBars
	}

	BeatTracker {
		id: beatTracker

	}

	PwObjectTracker {
		objects: [...root.sinks, ...root.sources, ...root.streams]
	}
}
