#!/usr/bin/env bash
#
# ==========================================
#   Volume Limiter Script (macOS & Linux)
# ==========================================
# Limits system volume to protect hearing.
# Works with: PipeWire, PulseAudio, and ALSA
#
# Options:
#   --headphones-only     Limit only when headphones are connected
#   --max-volume <int>    Maximum allowed volume (0–100)
#   --delay <seconds>     Interval between volume checks
#

set -euo pipefail

# ----------------------------
# Configuration
# ----------------------------
MAX_VOLUME=60 # 60% = ~80-85dB, safe for extended listening
ONLY_ON_HEADPHONES=false
DELAY=5 # Check every 5 seconds
DEBUG=false
LOCKFILE="/tmp/volumizer.lock"
STATE_FILE="/tmp/volumizer.state"

# ----------------------------
# Parse Arguments
# ----------------------------
while [[ $# -gt 0 ]]; do
	case "$1" in
	--headphones-only)
		ONLY_ON_HEADPHONES=true
		shift
		;;
	--max-volume)
		MAX_VOLUME="$2"
		shift 2
		;;
	--delay)
		DELAY="$2"
		shift 2
		;;
	--debug)
		DEBUG=true
		shift
		;;
	*)
		echo "Usage: $0 [--headphones-only] [--max-volume N] [--delay N] [--debug]"
		exit 1
		;;
	esac
done

# ----------------------------
# Debug Logging
# ----------------------------
if [ "$DEBUG" = true ]; then
	debug() {
		local func="${FUNCNAME[1]:-main}"
		local line="${BASH_LINENO[0]}"
		echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$func:$line] $*" >&2
	}
else
	debug() { :; }
fi

# ----------------------------
# Cleanup Function
# ----------------------------
cleanup() {
	# Remove traps to prevent double-execution
	trap - EXIT INT TERM

	debug "Cleaning up and exiting"

	# Kill all child processes in our process group
	local pgrp
	pgrp=$(ps -o pgid= -p $$ | tr -d ' ')
	if [ -n "$pgrp" ]; then
		pkill -TERM -g "$pgrp" 2>/dev/null || true
		sleep 0.5
		pkill -KILL -g "$pgrp" 2>/dev/null || true
	fi

	rm -f "$LOCKFILE"
	exit
}

# ----------------------------
# Single Instance Check
# ----------------------------
debug "Checking for existing instance..."
if [ -f "$LOCKFILE" ]; then
	LOCKPID=$(cat "$LOCKFILE" 2>/dev/null || echo "")
	if [ -n "$LOCKPID" ] && kill -0 "$LOCKPID" 2>/dev/null; then
		debug "Already running (PID: $LOCKPID), exiting"
		exit 0
	fi
	debug "Stale lockfile found, removing"
fi

echo $$ >"$LOCKFILE"
debug "Created lockfile with PID: $$"
trap cleanup EXIT INT TERM

# ----------------------------
# Platform Detection
# ----------------------------
case "$(uname)" in
Darwin)
	PLATFORM="macos"
	;;
Linux)
	PLATFORM="linux"
	# Detect Linux audio system
	if command -v wpctl >/dev/null 2>&1 && wpctl status >/dev/null 2>&1; then
		AUDIO_SYSTEM="pipewire"
		debug "Detected: PipeWire"
	elif command -v pactl >/dev/null 2>&1 && pactl info >/dev/null 2>&1; then
		AUDIO_SYSTEM="pulseaudio"
		debug "Detected: PulseAudio"
	elif command -v amixer >/dev/null 2>&1; then
		AUDIO_SYSTEM="alsa"
		debug "Detected: ALSA only"
	else
		echo "Error: No supported audio system found (PipeWire/PulseAudio/ALSA)"
		exit 1
	fi
	;;
*)
	echo "Unsupported platform: $(uname)"
	exit 1
	;;
esac

debug "Platform: $PLATFORM"

# ----------------------------
# Volume Functions
# ----------------------------
get_volume() {
	if [ "$PLATFORM" = "macos" ]; then
		osascript -e "output volume of (get volume settings)" 2>/dev/null
	else
		case "$AUDIO_SYSTEM" in
		pipewire)
			# wpctl returns volume as decimal (0.00 to 1.50+)
			local vol
			vol=$(wpctl get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null | awk '{print $2}')
			# Convert to percentage and round
			echo "$vol" | awk '{printf "%.0f", $1 * 100}'
			;;
		pulseaudio)
			# pactl get-sink-volume returns percentage
			pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -oP '\d+%' | head -1 | tr -d '%'
			;;
		alsa)
			amixer get Master 2>/dev/null | grep -oP '\d+%' | head -1 | tr -d '%'
			;;
		esac
	fi
}

set_volume() {
	local target=$1

	if [ "$PLATFORM" = "macos" ]; then
		osascript -e "set volume output volume $target" 2>/dev/null
	else
		case "$AUDIO_SYSTEM" in
		pipewire)
			# wpctl uses decimal values (0.6 = 60%)
			local decimal
			decimal=$(echo "$target" | awk '{printf "%.2f", $1 / 100}')
			wpctl set-volume @DEFAULT_AUDIO_SINK@ "$decimal" 2>/dev/null
			;;
		pulseaudio)
			pactl set-sink-volume @DEFAULT_SINK@ "${target}%" 2>/dev/null
			;;
		alsa)
			amixer -q set Master "${target}%" 2>/dev/null
			;;
		esac
	fi
}

# ----------------------------
# Audio Output Detection
# ----------------------------
is_using_headphones() {
	if [ "$PLATFORM" = "macos" ]; then
		local output
		local transport
		local detection_method

		if command -v SwitchAudioSource >/dev/null 2>&1; then
			output=$(SwitchAudioSource -c 2>/dev/null || echo "")
			detection_method="SwitchAudioSource"
			debug "Audio detection method: $detection_method"
			debug "Raw audio output: '$output'"

			if echo "$output" | grep -qi "Speaker\|Built-in Output"; then
				debug "Match found: Built-in speakers"
				return 1
			else
				debug "No speaker match: Assuming headphones/external"
				return 0
			fi
		else
			transport=$(system_profiler SPAudioDataType 2>/dev/null | awk '
				/Default Output Device: Yes/ {found=1}
				found && /Transport:/ {print $2; exit}
			')

			detection_method="system_profiler (Transport)"
			debug "Audio detection method: $detection_method"
			debug "Transport type: '$transport'"

			if [ "$transport" = "Built-in" ]; then
				debug "Built-in transport: Using speakers"
				return 1
			else
				debug "External transport ($transport): Using headphones"
				return 0
			fi
		fi
	else
		# Linux headphone detection
		case "$AUDIO_SYSTEM" in
		pipewire)
			# Check active port on default sink
			local port
			port=$(wpctl inspect @DEFAULT_AUDIO_SINK@ 2>/dev/null | grep -i "node.nick" | cut -d'"' -f2)
			debug "PipeWire active device: '$port'"

			# Check if it's headphones/external
			if echo "$port" | grep -iq "headphone\|bluetooth\|usb"; then
				debug "Detected: Headphones/External"
				return 0
			else
				debug "Detected: Speakers/Internal"
				return 1
			fi
			;;
		pulseaudio)
			# Check active port on default sink
			local active_port
			active_port=$(pactl list sinks 2>/dev/null | grep -A 50 "State: RUNNING" | grep "Active Port:" | head -1 | awk '{print $3}')
			debug "PulseAudio active port: '$active_port'"

			# Check port name patterns
			if echo "$active_port" | grep -iq "headphone\|bluetooth\|usb"; then
				debug "Detected: Headphones/External"
				return 0
			else
				debug "Detected: Speakers/Internal"
				return 1
			fi
			;;
		alsa)
			# ALSA doesn't reliably distinguish - assume headphones if volume control exists
			debug "ALSA: Cannot reliably detect headphones, assuming speakers"
			return 1
			;;
		esac
	fi
}

# ----------------------------
# Notification
# ----------------------------
notify() {
	local message="$1"

	debug "Sending notification: $message"

	if [ "$PLATFORM" = "macos" ]; then
		if command -v terminal-notifier >/dev/null 2>&1; then
			terminal-notifier -group "volumizer-status" -title "Volume Limiter" -message "$message" >/dev/null 2>&1
		else
			osascript -e "display notification \"$message\" with title \"Volume Limiter\"" 2>/dev/null
		fi
	else
		if command -v notify-send >/dev/null 2>&1; then
			notify-send -u normal -r 9999 "Volume Limiter" "$message" 2>/dev/null
		fi
	fi
}

# ----------------------------
# State Management
# ----------------------------
get_last_state() {
	cat "$STATE_FILE" 2>/dev/null || echo ""
}

save_state() {
	echo "$1" >"$STATE_FILE"
}

# ----------------------------
# Main Logic
# ----------------------------
debug "Starting main loop"

# Show startup notification
if [ "$ONLY_ON_HEADPHONES" = false ]; then
	notify "🔊 Limiting volume to $MAX_VOLUME%"
fi

# Main loop
if [ "$DEBUG" = true ]; then
	ITERATION=0
fi

while true; do
	if [ "$DEBUG" = true ]; then
		ITERATION=$((ITERATION + 1))
		debug "--- Iteration $ITERATION ---"
	fi

	CURRENT_VOLUME=$(get_volume)
	debug "Current volume: $CURRENT_VOLUME"

	if [ "$ONLY_ON_HEADPHONES" = true ]; then
		# Check headphone status
		if is_using_headphones; then
			CURRENT_STATE="headphones"
		else
			CURRENT_STATE="speakers"
		fi

		debug "Current state: $CURRENT_STATE"

		LAST_STATE=$(get_last_state)
		debug "Last state: $LAST_STATE"

		# Notify on state change
		if [ "$CURRENT_STATE" != "$LAST_STATE" ]; then
			debug "State changed from '$LAST_STATE' to '$CURRENT_STATE'"
			if [ "$CURRENT_STATE" = "headphones" ]; then
				notify "🎧 Limiting headphones to $MAX_VOLUME%"
			else
				notify "Volume limiter paused (speakers)"
			fi
			save_state "$CURRENT_STATE"
		fi

		# Only limit volume when using headphones
		if [ "$CURRENT_STATE" = "headphones" ] && [ "$CURRENT_VOLUME" -gt "$MAX_VOLUME" ]; then
			debug "Volume $CURRENT_VOLUME exceeds limit $MAX_VOLUME, reducing"
			set_volume "$MAX_VOLUME"
		else
			debug "No volume adjustment needed"
		fi
	else
		# Always limit volume
		if [ "$CURRENT_VOLUME" -gt "$MAX_VOLUME" ]; then
			debug "Volume $CURRENT_VOLUME exceeds limit $MAX_VOLUME, reducing"
			set_volume "$MAX_VOLUME"
		else
			debug "Volume within limit"
		fi
	fi

	debug "Sleeping for $DELAY seconds"
	sleep "$DELAY"
done
