<script lang="ts">
	import * as Tone from 'tone';
	import { onMount, onDestroy } from 'svelte';
	import AudioVisualization from '$lib/components/AudioVisualization.svelte';
	import { createPadSynth } from '$lib/audio/instruments/padSynth';
	import { createArpSynth } from '$lib/audio/instruments/arpSynth';
	import { createBassSynth } from '$lib/audio/instruments/bassSynth';
	import { createPingSynth } from '$lib/audio/instruments/pingSynth';
	import {
		createReverb,
		createDelay,
		createFilter,
		createGain,
		createAnalyser
	} from '$lib/audio/audio-effects';
	import { selectChordProgression, calculateComfortScore } from '$lib/audio/weather-mood';
	import type { ChordProgression } from '$lib/audio/chord-progressions';

	// Component props with default values
	let {
		temperature2m = 20,
		relativeHumidity2m = 50,
		cloudCover = 30,
		precipitation = 0,
		volume = -10,
		windSpeed10m = 0,
		isDay
	} = $props();

	// Component state using runes
	let isPlaying = $state(false);
	let isInitialized = $state(false);

	// Detect mobile for visualization sizing
	const isMobile = typeof window !== 'undefined' &&
		(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
		window.innerWidth < 768);
	const vizSize = isMobile ? 280 : 400;

	// Screen Wake Lock to prevent screen from sleeping during playback
	let wakeLock: WakeLockSentinel | null = null;

	// Audio components
	let synth: Tone.PolySynth | null = null;
	let arpSynth: Tone.Synth | null = null;
	let pingSynth: Tone.Synth | null = null;
	let bassSynth: Tone.Synth | null = null;
	let arpSequence: Tone.Sequence | null = null;
	let pingSequence: Tone.Sequence | null = null;
	let bassSequence: Tone.Sequence | null = null;
	let reverb: Tone.Reverb | null = null;
	let delay: Tone.FeedbackDelay | null = null;
	let filter: Tone.Filter | null = null;
	let sequence: Tone.Sequence | null = null;
	let gain: Tone.Gain | null = null;
	let analyser: Tone.Analyser | null = null;

	// Select chord progression based on weather mood
	const currentProgression: ChordProgression = $derived.by(() => {
		return selectChordProgression({
			temperature2m,
			relativeHumidity2m,
			cloudCover,
			windSpeed10m,
			precipitation,
			isDay
		});
	});

	// Derived reactive values using runes with safe fallbacks
	const bpm = $derived.by(() => {
		const temp = temperature2m ?? 20;
		// BPM: 5 at 0°C or below, 30 at 30°C or above
		const normalizedTemp = Math.max(0, Math.min(30, temp));
		return 5 + (normalizedTemp / 30) * 25;
	});

	const reverbWet = $derived.by(() => {
		const humidity = relativeHumidity2m ?? 50;
		// Ensure minimum 0.3 wet signal so reverb is always audible
		return Math.max(0.3, Math.min(1, humidity / 100));
	});

	// Delay time: 8th note for calm, quarter note for windy
	const delayTime = $derived.by(() => {
		const speed = windSpeed10m ?? 0;
		return speed > 5 ? '4n' : '8n';
	});

	// Delay feedback: stronger with more wind
	const delayFeedback = $derived.by(() => {
		const speed = windSpeed10m ?? 0;
		// Map 0-20 m/s wind to 0.2-0.7 feedback range
		return Math.max(0.2, Math.min(0.7, (speed / 20) * 0.5 + 0.2));
	});

	// Filter cutoff: colder = darker/lower frequency, warmer = brighter/higher frequency
	const filterCutoff = $derived.by(() => {
		const temp = temperature2m ?? 20;
		// Map temperature: 0°C = 400Hz (dark), 30°C = 8000Hz (bright)
		const normalizedTemp = Math.max(0, Math.min(30, temp));
		return 400 + (normalizedTemp / 30) * 7600;
	});

	// Filter resonance: more wind = more resonant
	const filterResonance = $derived.by(() => {
		const speed = windSpeed10m ?? 0;
		// Map 0-30 m/s wind to 1-18 resonance (Q factor)
		return Math.max(1, Math.min(18, 1 + (speed / 30) * 17));
	});

	// Arpeggio interval: slower in cold, faster in heat
	const arpInterval = $derived.by(() => {
		const temp = temperature2m ?? 20;
		// Map temperature: <0°C = 1n (whole note), 30°C+ = 8n (eighth note)
		if (temp < 0) return '1n';
		if (temp < 10) return '2n';
		if (temp < 20) return '4n';
		return '8n';
	});

	// Arpeggio volume: quieter in cold, louder in heat
	const arpVolume = $derived.by(() => {
		const temp = temperature2m ?? 20;
		// Map temperature: <0°C = -22dB, 30°C+ = -10dB
		const normalizedTemp = Math.max(0, Math.min(30, temp));
		return -22 + (normalizedTemp / 30) * 12;
	});

	// Weather extremity: 0 = pleasant, 1 = extreme conditions
	const weatherExtremity = $derived.by(() => {
		const comfortScore = calculateComfortScore({
			temperature2m,
			relativeHumidity2m,
			cloudCover,
			windSpeed10m,
			precipitation,
			isDay
		});
		return 1 - comfortScore; // Invert: higher = more extreme
	});

	// Ping volume: quieter in pleasant weather, louder in extreme weather
	const pingVolume = $derived.by(() => {
		// Map extremity: 0 (pleasant) = -22dB (present), 1 (extreme) = -8dB (prominent)
		return -22 + weatherExtremity * 14;
	});

	// Ping interval: slower in cold, faster in heat
	const pingInterval = $derived.by(() => {
		const temp = temperature2m ?? 20;
		// Map temperature: 0°C = 1n (whole note, slow), 30°C = 16n (16th note, fast)
		if (temp <= 0) return '1n';
		if (temp <= 10) return '2n';
		if (temp <= 20) return '4n';
		if (temp <= 25) return '8n';
		return '16n';
	});

	// Request wake lock to prevent screen from sleeping
	const requestWakeLock = async (): Promise<void> => {
		try {
			if ('wakeLock' in navigator) {
				wakeLock = await navigator.wakeLock.request('screen');
				console.log('Wake Lock is active');

				// Handle wake lock release (e.g., when tab is hidden)
				wakeLock.addEventListener('release', () => {
					console.log('Wake Lock was released');
				});
			}
		} catch (err) {
			console.error('Failed to request wake lock:', err);
		}
	};

	// Release wake lock
	const releaseWakeLock = async (): Promise<void> => {
		if (wakeLock !== null) {
			try {
				await wakeLock.release();
				wakeLock = null;
				console.log('Wake Lock released');
			} catch (err) {
				console.error('Failed to release wake lock:', err);
			}
		}
	};

	// Initialize audio components
	const initializeAudio = async (): Promise<void> => {
		try {
			// Optimize audio scheduling for better stability
			Tone.getContext().lookAhead = 0.1; // Keep default 100ms lookahead

			// Create instruments
			synth = createPadSynth(isDay);
			arpSynth = createArpSynth(arpVolume);
			pingSynth = createPingSynth(pingVolume);
			bassSynth = createBassSynth();

			// Create effects
			reverb = createReverb(reverbWet);
			delay = createDelay(delayTime, delayFeedback);
			filter = createFilter(filterCutoff, filterResonance);
			gain = createGain(volume);
			analyser = createAnalyser();

			// Connect audio chain using .chain() for clarity
			// Synth gets filtered based on temperature
			synth.chain(filter, delay, reverb, gain, analyser, Tone.Destination);
			// Arpeggios bypass filter - only delay and reverb
			arpSynth.chain(delay, reverb, gain);
			pingSynth.chain(delay, reverb, gain);
			bassSynth.chain(delay, reverb, gain);

			// Generate reverb impulse
			await reverb.generate();

			isInitialized = true;
		} catch (error) {
			console.error('Failed to initialize audio:', error);
		}
	};

	// Start the chord sequence
	const startSequence = async (): Promise<void> => {
		try {
			if (!isInitialized) {
				await initializeAudio();
			}

			await Tone.start();

			if (sequence) {
				sequence.dispose();
				sequence = null;
			}

			// Set all audio parameters once (static until page refresh)
			Tone.getTransport().bpm.value = bpm;

			if (reverb) {
				reverb.wet.value = reverbWet;
			}

			if (delay) {
				const delayTimeSeconds = Tone.Time(delayTime).toSeconds();
				delay.delayTime.value = delayTimeSeconds;
				delay.feedback.value = delayFeedback;
			}

			if (filter) {
				filter.frequency.value = filterCutoff;
				filter.Q.value = filterResonance;
			}

			if (gain) {
				gain.gain.value = Tone.dbToGain(volume);
			}

			if (arpSynth) {
				arpSynth.volume.value = arpVolume;
			}

			if (pingSynth) {
				pingSynth.volume.value = pingVolume;
			}

			sequence = new Tone.Sequence(
				(time: number, chord) => {
					if (synth && chord) {
						synth!.triggerAttackRelease(chord.notes, '4n', time);
					}
				},
				currentProgression,
				'4n'
			);

			sequence.start(0);

			// Create arpeggio sequence
			if (arpSequence) {
				arpSequence.dispose();
				arpSequence = null;
			}

			arpSequence = new Tone.Sequence(
				(time: number, chord) => {
					if (arpSynth && chord && chord.notes) {
						// Capture synth reference for TypeScript
						const synth = arpSynth;
						// Calculate time between notes based on 16th notes
						const sixteenthNote = Tone.Time('16n').toSeconds();
						// Play arpeggio pattern through the chord notes
						chord.notes.forEach((note: string, index: number) => {
							synth.triggerAttackRelease(note, '16n', time + index * sixteenthNote);
						});
					}
				},
				currentProgression,
				arpInterval
			);

			arpSequence.start(0);

			// Create ping sequence (reverse arpeggio - evenly spaced through chord duration)
			if (pingSequence) {
				pingSequence.dispose();
				pingSequence = null;
			}

			// Build a flat array of notes: 4 notes per chord, in reverse order, transposed up 2 octaves
			const pingNotes: string[] = [];
			currentProgression.forEach((chord) => {
				if (chord && chord.notes) {
					// Get 4 notes in reverse order (last to first, cycling if needed)
					const reversedNotes = [...chord.notes].reverse();
					for (let i = 0; i < 4; i++) {
						const note = reversedNotes[i % reversedNotes.length];
						const noteName = note.slice(0, -1);
						const octave = parseInt(note.slice(-1));
						const highNote = noteName + (octave + 2);
						pingNotes.push(highNote);
					}
				}
			});

			pingSequence = new Tone.Sequence(
				(time: number, note: string) => {
					if (pingSynth && note) {
						pingSynth.triggerAttackRelease(note, '32n', time);
					}
				},
				pingNotes,
				pingInterval // Temperature-reactive: 1n (cold) to 16n (hot)
			);

			pingSequence.start(0);

			// Create bass sequence
			if (bassSequence) {
				bassSequence.dispose();
				bassSequence = null;
			}

			bassSequence = new Tone.Sequence(
				(time: number, chord) => {
					if (bassSynth && chord && chord.notes && chord.notes.length > 0) {
						// Get root note (first note of chord) and transpose down 2 octaves
						const rootNote = chord.notes[0];
						const noteName = rootNote.slice(0, -1); // e.g., 'C' from 'C4'
						const octave = parseInt(rootNote.slice(-1)); // e.g., 4 from 'C4'
						const bassNote = noteName + (octave - 2); // e.g., 'C2'

						// Randomize note duration: half to full chord duration (2n to 4n)
						const randomDuration = 0.5 + Math.random() * 0.5;
						const noteDuration = Tone.Time('4n').toSeconds() * randomDuration;

						// Trigger bass note at the scheduled time
						bassSynth.triggerAttackRelease(bassNote, noteDuration, time);
					}
				},
				currentProgression,
				'4n'
			);

			bassSequence.start(0);

			Tone.getTransport().start();

			// Request wake lock to prevent screen sleep
			await requestWakeLock();

			isPlaying = true;
		} catch (error) {
			console.error('Error starting sequence:', error);
		}
	};

	// Stop the sequence
	const stopSequence = async (): Promise<void> => {
		if (sequence) {
			sequence.stop();
			sequence.dispose();
			sequence = null;
		}
		if (arpSequence) {
			arpSequence.stop();
			arpSequence.dispose();
			arpSequence = null;
		}
		if (pingSequence) {
			pingSequence.stop();
			pingSequence.dispose();
			pingSequence = null;
		}
		if (bassSequence) {
			bassSequence.stop();
			bassSequence.dispose();
			bassSequence = null;
		}
		Tone.getTransport().stop();
		Tone.getTransport().cancel();
		isPlaying = false;

		// Release wake lock when stopping
		await releaseWakeLock();
	};

	// Toggle playback
	const togglePlayback = async (): Promise<void> => {
		if (isPlaying) {
			stopSequence();
		} else {
			await startSequence();
		}
	};

	// Audio parameters are set once on playback start and remain static

	// Lifecycle
	onMount(() => {
		initializeAudio();

		// Handle page visibility changes (screen sleep/wake, tab switching)
		const handleVisibilityChange = async () => {
			if (!document.hidden && isPlaying) {
				// Page is visible again - resume audio context if suspended
				if (Tone.getContext().state === 'suspended') {
					await Tone.getContext().resume();
				}
				// Restart transport if it stopped
				if (Tone.getTransport().state !== 'started') {
					Tone.getTransport().start();
				}
				// Re-request wake lock if it was released
				if (wakeLock === null) {
					await requestWakeLock();
				}
			}
		};

		document.addEventListener('visibilitychange', handleVisibilityChange);

		return () => {
			document.removeEventListener('visibilitychange', handleVisibilityChange);
		};
	});

	onDestroy(async () => {
		// Release wake lock on component destroy
		await releaseWakeLock();

		if (sequence) {
			sequence.dispose();
		}
		if (arpSequence) {
			arpSequence.dispose();
		}
		if (pingSequence) {
			pingSequence.dispose();
		}
		if (bassSequence) {
			bassSequence.dispose();
		}
		if (synth) {
			synth.dispose();
		}
		if (arpSynth) {
			arpSynth.dispose();
		}
		if (pingSynth) {
			pingSynth.dispose();
		}
		if (bassSynth) {
			bassSynth.dispose();
		}
		if (reverb) {
			reverb.dispose();
		}
		if (delay) {
			delay.dispose();
		}
		if (filter) {
			filter.dispose();
		}
		if (gain) {
			gain.dispose();
		}
		if (analyser) {
			analyser.dispose();
		}
	});
</script>

<div class="mx-auto grid max-w-6xl grid-cols-1 gap-8 p-4 md:grid-cols-[300px_1fr]">
	<div class="flex flex-col gap-4">
		<button
			class="cursor-crosshair rounded-md border border-white/20 px-6 py-3 text-base transition-all duration-300 hover:border-white/40 disabled:cursor-not-allowed disabled:opacity-50 {isPlaying
				? 'bg-white text-black'
				: 'bg-transparent text-white'}"
			onclick={togglePlayback}
			disabled={!isInitialized}
		>
			{isPlaying ? 'Stop' : 'Play'} Weather Ambient
		</button>

		{#if isPlaying}
			<div class="flex flex-col gap-2 text-sm opacity-80">
				<p class="m-0">Temperature: {temperature2m.toFixed(1)}°C</p>
				<p class="m-0">Humidity: {relativeHumidity2m}%</p>
				<p class="m-0">Cloud Cover: {cloudCover}%</p>
				<p class="m-0">Wind Speed: {windSpeed10m.toFixed(1)} m/s</p>
				<p class="my-1 opacity-40">---</p>
				<p class="m-0">BPM: {bpm.toFixed(2)}</p>
				<p class="m-0">Weather Extremity: {(weatherExtremity * 100).toFixed(0)}%</p>
				<p class="m-0">Reverb: {reverbWet.toFixed(2)}</p>
				<p class="m-0">Delay: {delayTime} @ {delayFeedback.toFixed(2)} feedback</p>
				<p class="m-0">Filter: {Math.round(filterCutoff)}Hz Q:{filterResonance.toFixed(1)}</p>
			</div>
		{/if}
	</div>

	<div class="flex items-center justify-center">
		<AudioVisualization {isPlaying} {analyser} width={vizSize} height={vizSize} />
	</div>
</div>
