<script lang="ts">
	import { onMount, onDestroy } from 'svelte';
	import ContentDisplay from '$lib/components/ContentDisplay.svelte';
	import {
		initSocket,
		onContent,
		onLoading,
		onClear,
		onRoomStatus,
		onConnection,
		onVolume,
		onQrVisibility,
		onQueueUpdate,
		onQueueExhausted,
		onPlaybackState,
		remotePause,
		remotePlay,
		getRoomId,
		disconnect
	} from '$lib/socket';
	import { currentContent, isLoading, roomStatus, currentVolume } from '$lib/stores/content';
	import type { ContentPayload, RoomStatus, QueueItem } from '$lib/socket';
	import QRCode from 'qrcode';
	import * as THREE from 'three';

	// Queue state for TV display
	let queue = $state<QueueItem[]>([]);
	let queueIndex = $state(-1);

	let isFullscreen = $state(false);
	let isSocketConnected = $state(false);
	let needsInteraction = $state(true);
	let qrCodeDataUrl = $state<string | null>(null);
	let qrVisible = $state(true);
	let qrFadedOut = $state(false);
	let qrFadeTimer: ReturnType<typeof setTimeout> | null = null;

	// Time state
	let time = $state('');
	let date = $state('');

	// Weather state (currently unused - could be re-enabled with local weather API)
	let weather = $state<{ temp: number; condition: string; location?: string } | null>(null);

	// Idle return timers - return to idle screen after inactivity
	const IDLE_RETURN_DELAY = 60 * 1000; // 60 seconds after queue exhausts
	const PAUSE_IDLE_DELAY = 5 * 60 * 1000; // 5 minutes after pause (for native players)
	const CONTENT_IDLE_DELAY = 2 * 60 * 1000; // 2 minutes of same content (fallback for iframes like YouTube)
	let idleReturnTimer: ReturnType<typeof setTimeout> | null = null;
	let pauseIdleTimer: ReturnType<typeof setTimeout> | null = null;
	let contentIdleTimer: ReturnType<typeof setTimeout> | null = null;
	let isPaused = $state(false);

	// Idle overlay state - content stays loaded, just hidden behind overlay
	let showIdleOverlay = $state(false);

	function updateTime() {
		const now = new Date();

		time = now.toLocaleTimeString('en-US', {
			hour: 'numeric',
			minute: '2-digit',
			hour12: true
		}).replace(' ', '').toLowerCase();

		date = now.toLocaleDateString('en-US', {
			weekday: 'long',
			month: 'long',
			day: 'numeric'
		});
	}

	// ═══════════════════════════════════════════════════════════════
	// IDLE RETURN SYSTEM
	// ═══════════════════════════════════════════════════════════════

	/**
	 * Return to the idle screen by pausing and showing overlay
	 * Content stays loaded for instant resume
	 */
	function returnToIdle() {
		if (!$currentContent) return; // Already idle

		console.log('[Display] Showing idle overlay (content stays cached)');

		// Pause playback but keep content loaded
		remotePause();
		showIdleOverlay = true;

		// Reset gradient colors to default for idle screen
		currentColors = { ...defaultColors };
		applyColorScheme(defaultColors);
	}

	/**
	 * Resume playback from idle overlay - instant since content is still loaded
	 */
	function resumeFromIdle() {
		if (!showIdleOverlay || !$currentContent) {
			console.log('[Display] Nothing to resume');
			return;
		}

		console.log('[Display] Resuming from idle overlay');

		// Hide overlay and resume playback
		showIdleOverlay = false;
		remotePlay();

		// Restore colors from current content
		updateColorsFromContent($currentContent);

		// Restart the content idle timer
		startContentIdleTimer();
	}

	/**
	 * Start the idle return timer after queue exhausts
	 */
	function startIdleReturnTimer() {
		cancelIdleReturnTimer();
		console.log(`[Display] Queue exhausted - returning to idle in ${IDLE_RETURN_DELAY / 1000}s`);
		idleReturnTimer = setTimeout(() => {
			idleReturnTimer = null;
			returnToIdle();
		}, IDLE_RETURN_DELAY);
	}

	/**
	 * Cancel the idle return timer (e.g., when new content starts)
	 */
	function cancelIdleReturnTimer() {
		if (idleReturnTimer) {
			clearTimeout(idleReturnTimer);
			idleReturnTimer = null;
		}
	}

	/**
	 * Start the pause-based idle return timer
	 */
	function startPauseIdleTimer() {
		cancelPauseIdleTimer();
		console.log(`[Display] Paused - returning to idle in ${PAUSE_IDLE_DELAY / 1000}s if still paused`);
		pauseIdleTimer = setTimeout(() => {
			pauseIdleTimer = null;
			if (isPaused) {
				returnToIdle();
			}
		}, PAUSE_IDLE_DELAY);
	}

	/**
	 * Cancel the pause-based idle return timer
	 */
	function cancelPauseIdleTimer() {
		if (pauseIdleTimer) {
			clearTimeout(pauseIdleTimer);
			pauseIdleTimer = null;
		}
	}

	/**
	 * Start content-based idle timer (fallback for iframes like YouTube that don't report state)
	 * This ensures we eventually return to idle even if we can't detect playback state
	 */
	function startContentIdleTimer() {
		cancelContentIdleTimer();
		console.log(`[Display] Content started - will return to idle in ${CONTENT_IDLE_DELAY / 1000 / 60} minutes if unchanged`);
		contentIdleTimer = setTimeout(() => {
			contentIdleTimer = null;
			console.log('[Display] Content idle timeout reached');
			returnToIdle();
		}, CONTENT_IDLE_DELAY);
	}

	/**
	 * Cancel the content-based idle timer
	 */
	function cancelContentIdleTimer() {
		if (contentIdleTimer) {
			clearTimeout(contentIdleTimer);
			contentIdleTimer = null;
		}
	}

	/**
	 * Cancel all idle return timers
	 */
	function cancelAllIdleTimers() {
		cancelIdleReturnTimer();
		cancelPauseIdleTimer();
		cancelContentIdleTimer();
	}

	// ═══════════════════════════════════════════════════════════════
	// LIQUID GRADIENT SHADER SYSTEM
	// ═══════════════════════════════════════════════════════════════

	let gradientCanvas: HTMLCanvasElement;
	let renderer: THREE.WebGLRenderer | null = null;
	let camera: THREE.PerspectiveCamera | null = null;
	let scene: THREE.Scene | null = null;
	let gradientMesh: THREE.Mesh | null = null;
	let clock: THREE.Clock | null = null;
	let animationFrameId: number | null = null;
	let touchTexture: TouchTextureClass | null = null;

	// Album art color extraction - store as plain arrays to avoid $state proxy issues with THREE.Vector3
	interface ColorData {
		color1: [number, number, number];
		color2: [number, number, number];
		bgColor: number;
	}

	// Default color scheme (orange + navy like original)
	const defaultColors: ColorData = {
		color1: [0.95, 0.35, 0.13],   // Orange #F15A22
		color2: [0.04, 0.06, 0.15],   // Navy #0a0e27
		bgColor: 0x0a0e27
	};

	// Current colors (will be updated from album art)
	let currentColors = $state<ColorData>({ ...defaultColors });

	// Queue-aware color system
	const colorCache = new Map<string, ColorData>(); // Cache extracted colors by artwork URL
	let nextColors: ColorData | null = null; // Pre-extracted colors for next track
	const COLOR_BLEND_AMOUNT = 0.15; // How much to blend next track colors (0 = none, 1 = full)

	// Extract dominant colors from an image
	async function extractColorsFromImage(imageUrl: string): Promise<ColorData | null> {
		return new Promise((resolve) => {
			const img = new Image();
			img.crossOrigin = 'anonymous';

			img.onload = () => {
				try {
					const canvas = document.createElement('canvas');
					const ctx = canvas.getContext('2d');
					if (!ctx) {
						resolve(null);
						return;
					}

					// Sample at small size for performance
					const sampleSize = 50;
					canvas.width = sampleSize;
					canvas.height = sampleSize;
					ctx.drawImage(img, 0, 0, sampleSize, sampleSize);

					const imageData = ctx.getImageData(0, 0, sampleSize, sampleSize);
					const pixels = imageData.data;

					// Collect all colors with their positions
					const colors: { r: number; g: number; b: number; brightness: number; saturation: number }[] = [];

					for (let i = 0; i < pixels.length; i += 4) {
						const r = pixels[i] / 255;
						const g = pixels[i + 1] / 255;
						const b = pixels[i + 2] / 255;

						const max = Math.max(r, g, b);
						const min = Math.min(r, g, b);
						const brightness = (max + min) / 2;
						const saturation = max === min ? 0 : (max - min) / (1 - Math.abs(2 * brightness - 1));

						// Only include colors with some saturation (skip grays)
						if (saturation > 0.15) {
							colors.push({ r, g, b, brightness, saturation });
						}
					}

					if (colors.length < 2) {
						// Fallback: use any colors if not enough saturated ones
						for (let i = 0; i < pixels.length && colors.length < 10; i += 4) {
							colors.push({
								r: pixels[i] / 255,
								g: pixels[i + 1] / 255,
								b: pixels[i + 2] / 255,
								brightness: (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 765,
								saturation: 0.5
							});
						}
					}

					// Sort by saturation * brightness for vibrant colors
					colors.sort((a, b) => (b.saturation * (0.5 + b.brightness * 0.5)) - (a.saturation * (0.5 + a.brightness * 0.5)));

					// Pick the most vibrant color as color1
					const vibrant = colors[0] || { r: 0.95, g: 0.35, b: 0.13 };

					// Find a contrasting darker color for color2
					// Sort remaining by darkness
					const darkerColors = colors.filter(c => c.brightness < 0.5).sort((a, b) => a.brightness - b.brightness);
					const dark = darkerColors[0] || { r: vibrant.r * 0.2, g: vibrant.g * 0.2, b: vibrant.b * 0.2 };

					// Create darker background from the dark color
					const bgR = Math.floor(dark.r * 40);
					const bgG = Math.floor(dark.g * 40);
					const bgB = Math.floor(dark.b * 40);
					const bgColor = (bgR << 16) | (bgG << 8) | bgB;

					const colorData: ColorData = {
						color1: [vibrant.r, vibrant.g, vibrant.b],
						color2: [dark.r * 0.5, dark.g * 0.5, dark.b * 0.5],
						bgColor
					};

					// Save to localStorage for persistence
					try {
						localStorage.setItem('droppy-gradient-colors', JSON.stringify(colorData));
					} catch (e) {
						// Ignore localStorage errors
					}

					resolve(colorData);
				} catch (err) {
					console.log('[Display] Color extraction failed:', err);
					resolve(null);
				}
			};

			img.onerror = () => {
				resolve(null);
			};

			img.src = imageUrl;
		});
	}

	// Load saved colors from localStorage
	function loadSavedColors(): ColorData | null {
		try {
			const saved = localStorage.getItem('droppy-gradient-colors');
			if (saved) {
				return JSON.parse(saved) as ColorData;
			}
		} catch (e) {
			// Ignore errors
		}
		return null;
	}

	// Update gradient colors when content changes
	async function updateColorsFromContent(content: ContentPayload | null) {
		if (!content) return;

		// Try to get album art URL
		const artUrl = content.thumbnail || content.artworkUrl;
		if (!artUrl) return;

		// Check cache first for instant transition
		let colors = colorCache.get(artUrl);
		if (!colors) {
			colors = await extractColorsFromImage(artUrl);
			if (colors) {
				colorCache.set(artUrl, colors);
			}
		}

		if (colors) {
			// If we have next track colors, blend them slightly
			if (nextColors && COLOR_BLEND_AMOUNT > 0) {
				colors = blendColors(colors, nextColors, COLOR_BLEND_AMOUNT);
			}
			currentColors = colors;
			applyColorScheme(colors);
		}

		// Pre-extract colors for upcoming queue items
		preExtractQueueColors();
	}

	/**
	 * Blend two color schemes together
	 */
	function blendColors(primary: ColorData, secondary: ColorData, amount: number): ColorData {
		const blend = (a: number, b: number) => a * (1 - amount) + b * amount;
		return {
			color1: [
				blend(primary.color1[0], secondary.color1[0]),
				blend(primary.color1[1], secondary.color1[1]),
				blend(primary.color1[2], secondary.color1[2])
			],
			color2: [
				blend(primary.color2[0], secondary.color2[0]),
				blend(primary.color2[1], secondary.color2[1]),
				blend(primary.color2[2], secondary.color2[2])
			],
			bgColor: primary.bgColor // Keep primary background
		};
	}

	/**
	 * Pre-extract colors for upcoming queue items (next 3 tracks)
	 */
	async function preExtractQueueColors() {
		if (queueIndex < 0 || queue.length === 0) return;

		// Extract colors for next 3 tracks
		const upcoming = queue.slice(queueIndex + 1, queueIndex + 4);

		for (let i = 0; i < upcoming.length; i++) {
			const item = upcoming[i];
			const artUrl = item.thumbnail || item.artworkUrl;
			if (!artUrl || colorCache.has(artUrl)) continue;

			const colors = await extractColorsFromImage(artUrl);
			if (colors) {
				colorCache.set(artUrl, colors);
				// Store the immediate next track's colors
				if (i === 0) {
					nextColors = colors;
				}
			}
		}
	}

	/**
	 * Get artwork URL from a queue item
	 */
	function getArtworkUrl(item: QueueItem | null): string | null {
		if (!item) return null;
		return item.thumbnail || item.artworkUrl || null;
	}

	/**
	 * Update nextColors based on the next track in queue
	 */
	function updateNextColors() {
		if (queueIndex < 0 || queueIndex >= queue.length - 1) {
			nextColors = null;
			return;
		}

		const nextItem = queue[queueIndex + 1];
		const artUrl = getArtworkUrl(nextItem);
		if (artUrl && colorCache.has(artUrl)) {
			nextColors = colorCache.get(artUrl) || null;
		} else {
			nextColors = null;
		}
	}

	// Apply color scheme to the gradient
	function applyColorScheme(scheme: ColorData) {
		if (!scene || !uniforms) return;
		scene.background = new THREE.Color(scheme.bgColor);
		uniforms.uColor1.value.set(scheme.color1[0], scheme.color1[1], scheme.color1[2]);
		uniforms.uColor2.value.set(scheme.color2[0], scheme.color2[1], scheme.color2[2]);
	}

	// Touch texture for mouse interaction
	class TouchTextureClass {
		size = 64;
		maxAge = 64;
		radius = 16;
		speed = 1 / 64;
		trail: { x: number; y: number; age: number; force: number; vx: number; vy: number }[] = [];
		last: { x: number; y: number } | null = null;
		canvas: HTMLCanvasElement;
		ctx: CanvasRenderingContext2D;
		texture: THREE.Texture;

		constructor() {
			this.canvas = document.createElement('canvas');
			this.canvas.width = this.size;
			this.canvas.height = this.size;
			this.ctx = this.canvas.getContext('2d')!;
			this.ctx.fillStyle = 'black';
			this.ctx.fillRect(0, 0, this.size, this.size);
			this.texture = new THREE.Texture(this.canvas);
		}

		update() {
			this.ctx.fillStyle = 'black';
			this.ctx.fillRect(0, 0, this.size, this.size);

			for (let i = this.trail.length - 1; i >= 0; i--) {
				const point = this.trail[i];
				const f = point.force * this.speed * (1 - point.age / this.maxAge);
				point.x += point.vx * f;
				point.y += point.vy * f;
				point.age++;

				if (point.age > this.maxAge) {
					this.trail.splice(i, 1);
				} else {
					this.drawPoint(point);
				}
			}
			this.texture.needsUpdate = true;
		}

		addTouch(point: { x: number; y: number }) {
			let force = 0, vx = 0, vy = 0;
			if (this.last) {
				const dx = point.x - this.last.x;
				const dy = point.y - this.last.y;
				if (dx === 0 && dy === 0) return;
				const d = Math.sqrt(dx * dx + dy * dy);
				vx = dx / d;
				vy = dy / d;
				force = Math.min(d * d * 20000, 2.0);
			}
			this.last = { x: point.x, y: point.y };
			this.trail.push({ x: point.x, y: point.y, age: 0, force, vx, vy });
		}

		drawPoint(point: { x: number; y: number; age: number; force: number; vx: number; vy: number }) {
			const pos = { x: point.x * this.size, y: (1 - point.y) * this.size };
			let intensity = 1;
			if (point.age < this.maxAge * 0.3) {
				intensity = Math.sin((point.age / (this.maxAge * 0.3)) * (Math.PI / 2));
			} else {
				const t = 1 - (point.age - this.maxAge * 0.3) / (this.maxAge * 0.7);
				intensity = -t * (t - 2);
			}
			intensity *= point.force;

			const color = `${((point.vx + 1) / 2) * 255}, ${((point.vy + 1) / 2) * 255}, ${intensity * 255}`;
			const offset = this.size * 5;
			this.ctx.shadowOffsetX = offset;
			this.ctx.shadowOffsetY = offset;
			this.ctx.shadowBlur = this.radius;
			this.ctx.shadowColor = `rgba(${color},${0.2 * intensity})`;
			this.ctx.beginPath();
			this.ctx.fillStyle = 'rgba(255,0,0,1)';
			this.ctx.arc(pos.x - offset, pos.y - offset, this.radius, 0, Math.PI * 2);
			this.ctx.fill();
		}
	}

	const vertexShader = `
		varying vec2 vUv;
		void main() {
			gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
			vUv = uv;
		}
	`;

	const fragmentShader = `
		uniform float uTime;
		uniform vec2 uResolution;
		uniform vec3 uColor1;
		uniform vec3 uColor2;
		uniform sampler2D uTouchTexture;
		uniform float uSpeed;

		varying vec2 vUv;

		#define PI 3.14159265359

		float grain(vec2 uv, float time) {
			vec2 grainUv = uv * uResolution * 0.5;
			return fract(sin(dot(grainUv + time, vec2(12.9898, 78.233))) * 43758.5453) * 2.0 - 1.0;
		}

		vec3 getGradientColor(vec2 uv, float time) {
			float gradientRadius = 0.45;

			vec2 center1 = vec2(0.5 + sin(time * uSpeed * 0.4) * 0.4, 0.5 + cos(time * uSpeed * 0.5) * 0.4);
			vec2 center2 = vec2(0.5 + cos(time * uSpeed * 0.6) * 0.5, 0.5 + sin(time * uSpeed * 0.45) * 0.5);
			vec2 center3 = vec2(0.5 + sin(time * uSpeed * 0.35) * 0.45, 0.5 + cos(time * uSpeed * 0.55) * 0.45);
			vec2 center4 = vec2(0.5 + cos(time * uSpeed * 0.5) * 0.4, 0.5 + sin(time * uSpeed * 0.4) * 0.4);
			vec2 center5 = vec2(0.5 + sin(time * uSpeed * 0.7) * 0.35, 0.5 + cos(time * uSpeed * 0.6) * 0.35);
			vec2 center6 = vec2(0.5 + cos(time * uSpeed * 0.45) * 0.5, 0.5 + sin(time * uSpeed * 0.65) * 0.5);

			float influence1 = 1.0 - smoothstep(0.0, gradientRadius, length(uv - center1));
			float influence2 = 1.0 - smoothstep(0.0, gradientRadius, length(uv - center2));
			float influence3 = 1.0 - smoothstep(0.0, gradientRadius, length(uv - center3));
			float influence4 = 1.0 - smoothstep(0.0, gradientRadius, length(uv - center4));
			float influence5 = 1.0 - smoothstep(0.0, gradientRadius, length(uv - center5));
			float influence6 = 1.0 - smoothstep(0.0, gradientRadius, length(uv - center6));

			vec2 rotatedUv = uv - 0.5;
			float angle = time * uSpeed * 0.15;
			rotatedUv = vec2(rotatedUv.x * cos(angle) - rotatedUv.y * sin(angle), rotatedUv.x * sin(angle) + rotatedUv.y * cos(angle));
			rotatedUv += 0.5;
			float radialInfluence = 1.0 - smoothstep(0.0, 0.8, length(rotatedUv - 0.5));

			vec3 color = vec3(0.0);
			color += uColor1 * influence1 * (0.55 + 0.45 * sin(time * uSpeed)) * 0.5;
			color += uColor2 * influence2 * (0.55 + 0.45 * cos(time * uSpeed * 1.2)) * 1.8;
			color += uColor1 * influence3 * (0.55 + 0.45 * sin(time * uSpeed * 0.8)) * 0.5;
			color += uColor2 * influence4 * (0.55 + 0.45 * cos(time * uSpeed * 1.3)) * 1.8;
			color += uColor1 * influence5 * (0.55 + 0.45 * sin(time * uSpeed * 1.1)) * 0.5;
			color += uColor2 * influence6 * (0.55 + 0.45 * cos(time * uSpeed * 0.9)) * 1.8;

			color += mix(uColor1, uColor2, radialInfluence) * 0.4;
			color = clamp(color, vec3(0.0), vec3(1.0)) * 1.8;

			float luminance = dot(color, vec3(0.299, 0.587, 0.114));
			color = mix(vec3(luminance), color, 1.35);
			color = pow(color, vec3(0.92));

			float brightness = length(color);
			color = mix(uColor2 * 0.5, color, max(brightness * 1.2, 0.15));

			return clamp(color, vec3(0.0), vec3(1.0));
		}

		void main() {
			vec2 uv = vUv;

			vec4 touchTex = texture2D(uTouchTexture, uv);
			float vx = -(touchTex.r * 2.0 - 1.0);
			float vy = -(touchTex.g * 2.0 - 1.0);
			float intensity = touchTex.b;
			uv.x += vx * 0.8 * intensity;
			uv.y += vy * 0.8 * intensity;

			vec2 center = vec2(0.5);
			float dist = length(uv - center);
			float ripple = sin(dist * 20.0 - uTime * 3.0) * 0.04 * intensity;
			uv += vec2(ripple);

			vec3 color = getGradientColor(uv, uTime);
			color += grain(uv, uTime) * 0.08;

			float timeShift = uTime * 0.5;
			color.r += sin(timeShift) * 0.02;
			color.g += cos(timeShift * 1.4) * 0.02;
			color.b += sin(timeShift * 1.2) * 0.02;

			gl_FragColor = vec4(clamp(color, vec3(0.0), vec3(1.0)), 1.0);
		}
	`;

	let uniforms: {
		uTime: { value: number };
		uResolution: { value: THREE.Vector2 };
		uColor1: { value: THREE.Vector3 };
		uColor2: { value: THREE.Vector3 };
		uTouchTexture: { value: THREE.Texture | null };
		uSpeed: { value: number };
	};

	function getViewSize() {
		if (!camera) return { width: 1, height: 1 };
		const fovInRadians = (camera.fov * Math.PI) / 180;
		const height = Math.abs(camera.position.z * Math.tan(fovInRadians / 2) * 2);
		return { width: height * camera.aspect, height };
	}

	function initGradient() {
		if (!gradientCanvas) return;

		// Load saved colors from previous session, or use default
		const savedColors = loadSavedColors();
		if (savedColors) {
			currentColors = savedColors;
		}

		clock = new THREE.Clock();
		touchTexture = new TouchTextureClass();

		renderer = new THREE.WebGLRenderer({
			canvas: gradientCanvas,
			antialias: true,
			powerPreference: 'high-performance',
			alpha: false
		});
		renderer.setSize(window.innerWidth, window.innerHeight);
		renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

		camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
		camera.position.z = 50;

		scene = new THREE.Scene();
		scene.background = new THREE.Color(currentColors.bgColor);

		uniforms = {
			uTime: { value: 0 },
			uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
			uColor1: { value: new THREE.Vector3(currentColors.color1[0], currentColors.color1[1], currentColors.color1[2]) },
			uColor2: { value: new THREE.Vector3(currentColors.color2[0], currentColors.color2[1], currentColors.color2[2]) },
			uTouchTexture: { value: touchTexture.texture },
			uSpeed: { value: 1.5 }
		};

		const viewSize = getViewSize();
		const geometry = new THREE.PlaneGeometry(viewSize.width, viewSize.height, 1, 1);
		const material = new THREE.ShaderMaterial({
			uniforms,
			vertexShader,
			fragmentShader
		});

		gradientMesh = new THREE.Mesh(geometry, material);
		scene.add(gradientMesh);

		window.addEventListener('resize', handleResize);
		window.addEventListener('mousemove', handleMouseMove);
		window.addEventListener('touchmove', handleTouchMove);

		animate();
	}

	function handleResize() {
		if (!camera || !renderer || !gradientMesh) return;
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		renderer.setSize(window.innerWidth, window.innerHeight);

		const viewSize = getViewSize();
		gradientMesh.geometry.dispose();
		gradientMesh.geometry = new THREE.PlaneGeometry(viewSize.width, viewSize.height, 1, 1);
		uniforms.uResolution.value.set(window.innerWidth, window.innerHeight);
	}

	function handleMouseMove(ev: MouseEvent) {
		if (!touchTexture) return;
		touchTexture.addTouch({
			x: ev.clientX / window.innerWidth,
			y: 1 - ev.clientY / window.innerHeight
		});
	}

	function handleTouchMove(ev: TouchEvent) {
		if (!touchTexture || !ev.touches[0]) return;
		touchTexture.addTouch({
			x: ev.touches[0].clientX / window.innerWidth,
			y: 1 - ev.touches[0].clientY / window.innerHeight
		});
	}

	function animate() {
		animationFrameId = requestAnimationFrame(animate);

		if (!clock || !touchTexture || !renderer || !scene || !camera || !uniforms) return;

		const delta = Math.min(clock.getDelta(), 0.1);
		uniforms.uTime.value += delta;
		touchTexture.update();

		renderer.render(scene, camera);
	}

	function cleanupGradient() {
		if (animationFrameId) cancelAnimationFrame(animationFrameId);
		window.removeEventListener('resize', handleResize);
		window.removeEventListener('mousemove', handleMouseMove);
		window.removeEventListener('touchmove', handleTouchMove);

		if (gradientMesh) {
			gradientMesh.geometry.dispose();
			(gradientMesh.material as THREE.Material).dispose();
		}
		if (touchTexture) touchTexture.texture.dispose();
		renderer?.dispose();
	}

	onMount(async () => {
		if (sessionStorage.getItem('droppy-audio-enabled') === 'true') {
			needsInteraction = false;
		}

		initSocket('display');

		// Wait a frame for canvas to be bound
		await new Promise(resolve => requestAnimationFrame(resolve));
		initGradient();

		updateTime();
		const timeInterval = setInterval(updateTime, 1000);

		const roomId = getRoomId();
		if (roomId) {
			const uploaderUrl = `${window.location.origin}/?room=${roomId}`;
			try {
				qrCodeDataUrl = await QRCode.toDataURL(uploaderUrl, {
					width: 200,
					margin: 2,
					color: { dark: '#FFFFFF', light: '#00000000' }
				});
			} catch (err) {
				console.error('Failed to generate QR code:', err);
			}
		}

		const unsubContent = onContent((content: ContentPayload) => {
			currentContent.set(content);
			resetQrFade();

			// Hide idle overlay if showing (new content arrived)
			showIdleOverlay = false;

			// Cancel any pending idle return timers since new content is playing
			cancelAllIdleTimers();
			isPaused = false;

			// Start content-based idle timer (works for all content including YouTube iframes)
			startContentIdleTimer();

			// Extract colors from album art
			updateColorsFromContent(content);
		});

		const unsubLoading = onLoading((loading: boolean) => {
			isLoading.set(loading);
		});

		const unsubClear = onClear(() => {
			currentContent.set(null);
		});

		const unsubStatus = onRoomStatus((status: RoomStatus) => {
			roomStatus.set(status);
		});

		const unsubConnection = onConnection((connected: boolean) => {
			isSocketConnected = connected;
		});

		const unsubVolume = onVolume((volume: number) => {
			currentVolume.set(volume);
		});

		const unsubQr = onQrVisibility((visible: boolean) => {
			qrVisible = visible;
		});

		const unsubQueue = onQueueUpdate((state) => {
			queue = state.queue;
			queueIndex = state.currentIndex;
			// Pre-extract colors for upcoming tracks when queue updates
			preExtractQueueColors();
			// Update nextColors based on new queue state
			updateNextColors();
		});

		// Listen for queue exhausted event to start idle return timer
		const unsubQueueExhausted = onQueueExhausted(() => {
			startIdleReturnTimer();
		});

		// Listen for playback state to detect prolonged pause and handle overlay
		const unsubPlaybackState = onPlaybackState((state) => {
			const wasPlaying = !isPaused;
			isPaused = !state.playing;

			if (isPaused && wasPlaying) {
				// Just paused - start the pause idle timer
				startPauseIdleTimer();
			} else if (!isPaused && !wasPlaying) {
				// Resumed - cancel timers and hide overlay (user pressed play from uploader)
				cancelPauseIdleTimer();
				if (showIdleOverlay) {
					console.log('[Display] Playback resumed - hiding idle overlay');
					showIdleOverlay = false;
					// Restore colors and restart idle timer
					updateColorsFromContent($currentContent);
					startContentIdleTimer();
				}
			}
		});

		document.addEventListener('fullscreenchange', handleFullscreenChange);

		return () => {
			clearInterval(timeInterval);
			unsubContent();
			unsubLoading();
			unsubClear();
			unsubStatus();
			unsubConnection();
			unsubVolume();
			unsubQr();
			unsubQueue();
			unsubQueueExhausted();
			unsubPlaybackState();
			document.removeEventListener('fullscreenchange', handleFullscreenChange);
		};
	});

	onDestroy(() => {
		disconnect();
		if (qrFadeTimer) clearTimeout(qrFadeTimer);
		cancelAllIdleTimers();
		cleanupGradient();
	});

	function handleFullscreenChange() {
		isFullscreen = !!document.fullscreenElement;
	}

	function resetQrFade() {
		qrFadedOut = false;
		if (qrFadeTimer) clearTimeout(qrFadeTimer);
		qrFadeTimer = setTimeout(() => {
			qrFadedOut = true;
		}, 5000);
	}

	async function enableAudio() {
		needsInteraction = false;
		sessionStorage.setItem('droppy-audio-enabled', 'true');
		window.dispatchEvent(new CustomEvent('droppyAudioEnabled'));

		try {
			await document.documentElement.requestFullscreen();
		} catch (err) {
			// Fullscreen may fail
		}
	}

	function handleKeydown(event: KeyboardEvent) {
		if (needsInteraction) {
			enableAudio();
		}

		if (event.key === 'f' || event.key === 'F') {
			if (isFullscreen) {
				document.exitFullscreen().catch(() => {});
			} else {
				document.documentElement.requestFullscreen().catch(() => {});
			}
		}
	}

	function handleClick() {
		if (needsInteraction) {
			enableAudio();
		}
	}
</script>

<svelte:head>
	<title>Droppy Display</title>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
	<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400&family=Inter:wght@300;400;500&display=swap" rel="stylesheet">
	<style>
		html, body {
			margin: 0;
			padding: 0;
			overflow: hidden;
			background: #000;
		}
	</style>
</svelte:head>

<svelte:window onkeydown={handleKeydown} />

<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="display-page" onclick={handleClick}>
	<!-- Liquid gradient background -->
	<canvas bind:this={gradientCanvas} class="gradient-canvas"></canvas>

	<div class="content-wrapper">
		<ContentDisplay
			content={$currentContent}
			isLoading={$isLoading}
			hideIdle={!needsInteraction && !$currentContent && !$isLoading}
			blocked={needsInteraction}
			queuePosition={queueIndex}
			queueTotal={queue.length}
		/>
	</div>

	{#if needsInteraction}
		<button class="enable-audio-overlay" onclick={enableAudio} type="button">
			<div class="enable-content">
				<div class="enable-icon">
					<svg viewBox="0 0 24 24" fill="currentColor">
						<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
					</svg>
				</div>
				<h1>Click to Start</h1>
				<p>Click anywhere to enable audio playback</p>
			</div>
		</button>
	{/if}

	<!-- Ambient landing page (shows when no content OR as overlay when idle) -->
	{#if !needsInteraction && ((!$currentContent && !$isLoading) || showIdleOverlay)}
		<div class="ambient-landing" class:is-overlay={showIdleOverlay}>
			<div class="ambient-content">
				<div class="time-display">
					<span class="time-text">{time}</span>
				</div>
				<p class="date-text">{date}</p>
				{#if weather}
					<div class="weather-display">
						<span class="weather-temp">{weather.temp}°</span>
						<span class="weather-divider"></span>
						<span class="weather-condition">{weather.condition}</span>
						{#if weather.location}
							<span class="weather-divider"></span>
							<span class="weather-location">{weather.location}</span>
						{/if}
					</div>
				{/if}

				<!-- Paused indicator when content is cached behind overlay -->
				{#if showIdleOverlay && $currentContent}
					<div class="paused-indicator">
						<span class="paused-icon">❚❚</span>
						<span class="paused-text">Paused</span>
					</div>
				{/if}
			</div>

			<!-- QR code with contrast cycling for visibility -->
			{#if qrCodeDataUrl}
				<div class="qr-landing">
					<div class="qr-contrast-wrapper">
						<img src={qrCodeDataUrl} alt="Scan to add content" class="qr-image" />
					</div>
					<span class="qr-label">Scan to cast</span>
				</div>
			{/if}

			<!-- Branding -->
			<div class="ambient-brand">
				<svg viewBox="0 0 24 24" class="brand-icon">
					<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
				</svg>
				<span class="brand-name">droppy</span>
			</div>
		</div>
	{/if}

	<!-- Corner QR when content playing -->
	{#if qrVisible && qrCodeDataUrl && ($currentContent || $isLoading)}
		<div class="qr-corner" class:faded={qrFadedOut}>
			<img src={qrCodeDataUrl} alt="QR code" class="qr-code-small" />
		</div>
	{/if}

	<div class="status-dot" class:connected={isSocketConnected} class:has-uploaders={$roomStatus.uploaders > 0}></div>
</div>

<style>
	.display-page {
		position: fixed;
		inset: 0;
		background: #000;
		overflow: hidden;
		font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	}

	.gradient-canvas {
		position: fixed;
		inset: 0;
		width: 100%;
		height: 100%;
		z-index: 0;
	}

	.content-wrapper {
		position: absolute;
		inset: 0;
		z-index: 10;
	}

	/* ═══════════════════════════════════════════════════════════════
	   AUDIO UNLOCK OVERLAY
	   ═══════════════════════════════════════════════════════════════ */

	.enable-audio-overlay {
		position: fixed;
		inset: 0;
		border: none;
		cursor: pointer;
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 9999;
		pointer-events: auto;
		background: transparent;
	}

	.enable-content {
		position: relative;
		text-align: center;
		color: rgba(255, 255, 255, 0.9);
		z-index: 1;
	}

	.enable-icon {
		width: clamp(80px, 12vmin, 120px);
		height: clamp(80px, 12vmin, 120px);
		margin: 0 auto 2rem;
		background: rgba(255, 255, 255, 0.1);
		backdrop-filter: blur(20px);
		-webkit-backdrop-filter: blur(20px);
		border: 1px solid rgba(255, 255, 255, 0.2);
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
		box-shadow: 0 0 60px rgba(255, 255, 255, 0.1);
		animation: pulse-icon 3s ease-in-out infinite;
	}

	@keyframes pulse-icon {
		0%, 100% { transform: scale(1); opacity: 0.9; }
		50% { transform: scale(1.05); opacity: 1; }
	}

	.enable-icon svg {
		width: 45%;
		height: 45%;
		color: white;
	}

	.enable-content h1 {
		font-family: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
		font-size: clamp(2rem, 5vmin, 3.5rem);
		font-weight: 300;
		margin: 0 0 0.75rem;
		letter-spacing: 0.02em;
	}

	.enable-content p {
		font-size: clamp(0.9rem, 2vmin, 1.1rem);
		color: rgba(255, 255, 255, 0.5);
		margin: 0;
		font-weight: 400;
	}

	/* ═══════════════════════════════════════════════════════════════
	   AMBIENT LANDING PAGE
	   ═══════════════════════════════════════════════════════════════ */

	.ambient-landing {
		position: fixed;
		inset: 0;
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 5;
		pointer-events: none;
	}

	.ambient-content {
		position: relative;
		text-align: center;
		padding: 2rem;
	}

	.time-display {
		margin-bottom: 0.5rem;
	}

	.time-text {
		font-family: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
		font-size: clamp(7rem, 20vmin, 16rem);
		font-weight: 300;
		letter-spacing: -0.02em;
		line-height: 0.85;
		color: rgba(255, 255, 255, 0.92);
		text-shadow: 0 4px 60px rgba(0, 0, 0, 0.5);
	}

	.date-text {
		font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
		font-size: clamp(1rem, 2.5vmin, 1.6rem);
		font-weight: 400;
		color: rgba(255, 255, 255, 0.5);
		margin: clamp(2rem, 5vmin, 6rem) 0 1.5rem;
		letter-spacing: 0.1em;
		text-transform: uppercase;
	}

	.weather-display {
		display: inline-flex;
		align-items: center;
		gap: clamp(0.5rem, 1.5vmin, 1rem);
		font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
		font-size: clamp(1rem, 2.5vmin, 1.5rem);
		font-weight: 400;
		color: rgba(255, 255, 255, 0.5);
		background: rgba(0, 0, 0, 0.3);
		backdrop-filter: blur(20px);
		-webkit-backdrop-filter: blur(20px);
		padding: clamp(0.6rem, 1.5vmin, 1rem) clamp(1.2rem, 3vmin, 2rem);
		border-radius: 100px;
		border: 1px solid rgba(255, 255, 255, 0.1);
	}

	.weather-temp {
		font-weight: 500;
		color: rgba(255, 255, 255, 0.9);
	}

	.weather-divider {
		width: 1px;
		height: 1em;
		background: rgba(255, 255, 255, 0.3);
	}

	.weather-condition {
		opacity: 0.8;
	}

	.weather-location {
		opacity: 0.6;
		font-size: 0.9em;
	}

	/* Idle overlay mode (content still loaded behind) */
	.ambient-landing.is-overlay {
		background: rgba(0, 0, 0, 0.85);
		backdrop-filter: blur(30px);
		-webkit-backdrop-filter: blur(30px);
	}

	/* Paused indicator */
	.paused-indicator {
		display: flex;
		align-items: center;
		gap: clamp(0.75rem, 1.5vmin, 1rem);
		margin-top: clamp(2rem, 4vmin, 3rem);
		padding: clamp(0.75rem, 1.5vmin, 1rem) clamp(1.5rem, 3vmin, 2rem);
		background: rgba(255, 255, 255, 0.08);
		border: 1px solid rgba(255, 255, 255, 0.15);
		border-radius: 100px;
	}

	.paused-icon {
		font-size: clamp(1rem, 2vmin, 1.25rem);
		color: rgba(255, 255, 255, 0.5);
		letter-spacing: -0.1em;
	}

	.paused-text {
		font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
		font-size: clamp(0.9rem, 2vmin, 1.1rem);
		font-weight: 500;
		color: rgba(255, 255, 255, 0.5);
		text-transform: uppercase;
		letter-spacing: 0.1em;
	}

	/* ═══════════════════════════════════════════════════════════════
	   QR CODE & BRANDING
	   ═══════════════════════════════════════════════════════════════ */

	.qr-landing {
		position: fixed;
		bottom: clamp(2rem, 5vmin, 4rem);
		right: clamp(2rem, 5vmin, 4rem);
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 0.75rem;
		opacity: 0.5;
		transition: opacity 0.5s ease;
		animation: fade-in 2s ease-out 1s both;
		pointer-events: auto;
	}

	.qr-landing:hover {
		opacity: 0.85;
	}

	/* Contrast cycling wrapper - ensures QR is always scannable */
	.qr-contrast-wrapper {
		position: relative;
		padding: clamp(8px, 1.5vmin, 12px);
		border-radius: clamp(8px, 1.5vmin, 12px);
		animation: qr-bg-cycle 180s ease-in-out infinite;
	}

	@keyframes qr-bg-cycle {
		0%, 100% {
			background: rgba(0, 0, 0, 0.7);
		}
		50% {
			background: rgba(255, 255, 255, 0.9);
		}
	}

	.qr-image {
		width: clamp(80px, 12vmin, 120px);
		height: clamp(80px, 12vmin, 120px);
		display: block;
		animation: qr-invert-cycle 180s ease-in-out infinite;
	}

	@keyframes qr-invert-cycle {
		0%, 100% {
			filter: invert(0);
		}
		50% {
			filter: invert(1);
		}
	}

	.qr-label {
		font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
		font-size: clamp(0.65rem, 1.3vmin, 0.85rem);
		font-weight: 500;
		letter-spacing: 0.15em;
		text-transform: uppercase;
		color: rgba(255, 255, 255, 0.6);
		animation: qr-label-cycle 180s ease-in-out infinite;
	}

	@keyframes qr-label-cycle {
		0%, 100% {
			color: rgba(255, 255, 255, 0.6);
		}
		50% {
			color: rgba(0, 0, 0, 0.6);
		}
	}

	@keyframes fade-in {
		from { opacity: 0; transform: translateY(10px); }
		to { opacity: 0.35; transform: translateY(0); }
	}

	.ambient-brand {
		position: fixed;
		bottom: clamp(2rem, 5vmin, 4rem);
		left: clamp(2rem, 5vmin, 4rem);
		display: flex;
		align-items: center;
		gap: 0.6rem;
		opacity: 0.2;
		transition: opacity 0.5s ease;
		animation: fade-in-brand 2s ease-out 1.2s both;
		pointer-events: auto;
	}

	@keyframes fade-in-brand {
		from { opacity: 0; transform: translateY(10px); }
		to { opacity: 0.2; transform: translateY(0); }
	}

	.ambient-brand:hover {
		opacity: 0.5;
	}

	.brand-icon {
		width: clamp(18px, 2.5vmin, 24px);
		height: clamp(18px, 2.5vmin, 24px);
		stroke: rgba(255, 255, 255, 0.7);
		stroke-width: 1.5;
		fill: none;
	}

	.brand-name {
		font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
		font-size: clamp(0.85rem, 1.8vmin, 1.1rem);
		font-weight: 600;
		color: rgba(255, 255, 255, 0.9);
		letter-spacing: 0.1em;
	}

	/* Corner QR when content playing */
	.qr-corner {
		position: fixed;
		bottom: clamp(2rem, 5vmin, 3rem);
		right: clamp(2rem, 5vmin, 3rem);
		z-index: 50;
		opacity: 0.4;
		transition: opacity 0.8s ease;
	}

	.qr-corner.faded {
		opacity: 0.12;
	}

	.qr-code-small {
		width: clamp(48px, 6vmin, 64px);
		height: clamp(48px, 6vmin, 64px);
		display: block;
		filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.5));
	}

	/* Status indicator */
	.status-dot {
		position: fixed;
		top: clamp(1rem, 2vmin, 1.5rem);
		right: clamp(1rem, 2vmin, 1.5rem);
		width: 10px;
		height: 10px;
		border-radius: 50%;
		background: #ef4444;
		z-index: 100;
		opacity: 0.8;
		box-shadow: 0 0 15px rgba(239, 68, 68, 0.5);
		transition: all 0.4s ease;
	}

	.status-dot.connected {
		background: #fbbf24;
		opacity: 0.5;
		box-shadow: none;
	}

	.status-dot.connected.has-uploaders {
		background: #4ade80;
		opacity: 0.8;
		box-shadow: 0 0 15px rgba(74, 222, 128, 0.4);
	}
</style>
