/**
 * folk-video-player — Video library browser + player.
 *
 * Lists videos from the API, plays them with native <video>,
 * and provides HLS live stream viewing.
 */

import { TourEngine } from "../../../shared/tour-engine";
import { authFetch, requireAuth } from "../../../shared/auth-fetch";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';
import type { DocumentId } from "../../../shared/local-first/document";
import { tubeSchema, tubeDocId } from "../schemas";
import { ViewHistory } from "../../../shared/view-history.js";

class FolkVideoPlayer extends HTMLElement {
	private shadow: ShadowRoot;
	private space = "demo";
	private videos: Array<{ name: string; size: number; duration?: string; date?: string }> = [];
	private currentVideo: string | null = null;
	private mode: "library" | "live" | "360live" = "library";
	private _history = new ViewHistory<"library" | "live" | "360live">("library", "rtube");
	private streamKey = "";
	private searchTerm = "";
	private isDemo = false;
	private splitModalOpen = false;
	private splitJob: { jobId: string; status: string; progress: number; currentView: string; outputFiles: string[]; error: string } | null = null;
	private splitSettings = { numViews: 4, hFov: 90, vFov: 90, overlap: 0, outputRes: "" };
	private splitPollInterval: ReturnType<typeof setInterval> | null = null;
	private splitImporting = false;
	private splitImported: string[] | null = null;
	// 360 Live state
	private liveSplitSession: { sessionId: string; status: string; views: Array<{ index: number; label: string; yaw: number; hls_url: string; alive?: boolean }> } | null = null;
	private liveSplitStreamKey = "";
	private liveSplitSettings = { numViews: 4, hFov: 90, vFov: 90, overlap: 0, outputRes: "" };
	private liveSplitError = "";
	private liveSplitStarting = false;
	private hlsPlayers: any[] = [];
	private liveSplitStatusInterval: ReturnType<typeof setInterval> | null = null;
	private expandedView: number | null = null;
	private _stopPresence: (() => void) | null = null;
	private _offlineUnsub: (() => void) | null = null;
	private _subscribedDocIds: string[] = [];
	private _tour!: TourEngine;
	private static readonly TOUR_STEPS = [
		{ target: '[data-mode="library"]', title: "Video Library", message: "Browse your recorded videos — search, select, and play.", advanceOnClick: false },
		{ target: '[data-input="search"]', title: "Search Videos", message: "Filter videos by name to quickly find what you need.", advanceOnClick: false },
		{ target: '.player-area', title: "Video Player", message: "Select a video from the sidebar to start playback.", advanceOnClick: false },
	];

	constructor() {
		super();
		this.shadow = this.attachShadow({ mode: "open" });
		this._tour = new TourEngine(
			this.shadow,
			FolkVideoPlayer.TOUR_STEPS,
			"rtube_tour_done",
			() => this.shadow.host as HTMLElement,
		);
	}

	connectedCallback() {
		this.space = this.getAttribute("space") || "demo";
		if (this.space === "demo") { this.loadDemoData(); }
		else { this.loadVideos(); this.subscribeOffline(); }
		if (!localStorage.getItem("rtube_tour_done")) {
			setTimeout(() => this._tour.start(), 1200);
		}
		this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rtube', context: this.currentVideo || 'Video' }));
		window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
	}

	disconnectedCallback() {
		this._history.destroy();
		window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
		this._stopPresence?.();
		this._offlineUnsub?.(); this._offlineUnsub = null;
		if (this.splitPollInterval) { clearInterval(this.splitPollInterval); this.splitPollInterval = null; }
		if (this.liveSplitStatusInterval) { clearInterval(this.liveSplitStatusInterval); this.liveSplitStatusInterval = null; }
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (runtime) {
			for (const id of this._subscribedDocIds) runtime.unsubscribe(id);
		}
		this._subscribedDocIds = [];
	}

	private _onViewRestored = (e: CustomEvent) => {
		if (e.detail?.moduleId !== 'rtube') return;
		if (this.mode === "360live" && e.detail.view !== "360live") {
			this.destroyHlsPlayers();
		}
		this.mode = e.detail.view;
		this.render();
	};

	private async subscribeOffline() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;
		try {
			const docId = tubeDocId(this.space) as DocumentId;
			await runtime.subscribe(docId, tubeSchema);
			this._subscribedDocIds.push(docId);
		} catch { /* runtime unavailable */ }
	}

	private loadDemoData() {
		this.isDemo = true;
		this.videos = [
			{ name: "community-meeting-2026-02-15.mp4", size: 524288000, duration: "1:23:45", date: "Feb 15, 2026" },
			{ name: "rspace-demo-walkthrough.mp4", size: 157286400, duration: "18:32", date: "Feb 12, 2026" },
			{ name: "design-sprint-day1.webm", size: 892108800, duration: "2:45:10", date: "Feb 10, 2026" },
			{ name: "interview-cosmolocal-founders.mp4", size: 1073741824, duration: "52:18", date: "Feb 7, 2026" },
			{ name: "workshop-local-first-data.mp4", size: 734003200, duration: "1:35:42", date: "Feb 3, 2026" },
			{ name: "lightning-talks-feb2026.webm", size: 445644800, duration: "42:15", date: "Jan 28, 2026" },
		];
		this.currentVideo = this.videos[0].name;
		this.render();
	}

	private async loadVideos() {
		try {
			const base = window.location.pathname.replace(/\/$/, "");
			const resp = await fetch(`${base}/api/videos`);
			if (resp.ok) {
				const data = await resp.json();
				this.videos = data.videos || [];
			}
		} catch { /* ignore */ }
		this.render();
	}

	private formatSize(bytes: number): string {
		if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
		if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
		return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
	}

	private getExtension(name: string): string {
		return name.substring(name.lastIndexOf(".") + 1).toLowerCase();
	}

	private isPlayable(name: string): boolean {
		const ext = this.getExtension(name);
		return ["mp4", "webm", "m4v"].includes(ext);
	}

	private render() {
		this.shadow.innerHTML = `
			<style>
				:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: var(--rs-text-primary); }
				.container { max-width: 1200px; margin: 0 auto; }
				.rapp-nav { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; min-height: 36px; }
			.rapp-nav__title { font-size: 13px; font-weight: 500; color: var(--rs-text-muted); flex: 1; text-align: right; }
				.tab { padding: 0.5rem 1.25rem; border-radius: 8px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.875rem; }
				.tab.active { background: #ef4444; color: white; border-color: #ef4444; }
				.layout { display: grid; grid-template-columns: 300px 1fr; gap: 1.5rem; }
				.sidebar { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1rem; max-height: 70vh; overflow-y: auto; }
				.video-item { padding: 0.75rem; border-radius: 8px; cursor: pointer; border: 1px solid transparent; margin-bottom: 0.25rem; }
				.video-item:hover { background: var(--rs-bg-hover); }
				.video-item.active { background: rgba(239,68,68,0.1); border-color: #ef4444; }
				.video-name { font-size: 0.875rem; font-weight: 500; word-break: break-all; }
				.video-meta { font-size: 0.75rem; color: var(--rs-text-muted); margin-top: 0.25rem; }
				.player-area { background: #000; border-radius: 12px; overflow: hidden; aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center; }
				video { width: 100%; height: 100%; }
				.placeholder { text-align: center; color: var(--rs-text-muted); }
				.placeholder-icon { font-size: 3rem; margin-bottom: 1rem; }
				.info-bar { margin-top: 1rem; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem; }
				.info-name { font-weight: 500; }
				.actions { display: flex; gap: 0.5rem; }
				.btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-btn-secondary-bg); color: var(--rs-text-primary); cursor: pointer; font-size: 0.8rem; text-decoration: none; }
				.btn:hover { background: var(--rs-bg-surface-raised); }
				.live-section { max-width: 640px; margin: 0 auto; }
				.live-card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 2rem; }
				input { width: 100%; padding: 0.75rem; background: var(--rs-input-bg); border: 1px solid var(--rs-input-border); border-radius: 8px; color: var(--rs-input-text); font-size: 0.875rem; margin-bottom: 1rem; box-sizing: border-box; }
				.btn-primary { padding: 0.75rem 1.5rem; background: #ef4444; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; width: 100%; font-size: 0.875rem; }
				.btn-primary:disabled { background: var(--rs-bg-surface-raised); color: var(--rs-text-muted); cursor: not-allowed; }
				.live-badge { display: inline-flex; align-items: center; gap: 0.5rem; }
				.live-dot { width: 10px; height: 10px; background: #ef4444; border-radius: 50%; animation: pulse 2s infinite; }
				@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
				.empty { text-align: center; padding: 3rem; color: var(--rs-text-muted); }
				.setup { margin-top: 1.5rem; padding: 1.5rem; background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; }
				.setup h3 { font-size: 0.875rem; color: #ef4444; margin-bottom: 0.5rem; }
				.setup code { display: block; background: var(--rs-input-bg); padding: 0.5rem 0.75rem; border-radius: 6px; font-size: 0.75rem; color: var(--rs-text-secondary); margin-top: 0.5rem; overflow-x: auto; }
				.setup ol { padding-left: 1.25rem; color: var(--rs-text-secondary); font-size: 0.8rem; line-height: 1.8; }
				.split-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000; display: flex; align-items: center; justify-content: center; }
				.split-modal { background: var(--rs-bg-surface, #1a1a2e); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.5rem; width: 420px; max-width: 90vw; max-height: 85vh; overflow-y: auto; }
				.split-modal h3 { margin: 0 0 1rem; font-size: 1rem; }
				.split-modal label { display: block; font-size: 0.8rem; color: var(--rs-text-secondary); margin-bottom: 0.25rem; }
				.split-modal input[type="number"], .split-modal input[type="text"] { margin-bottom: 0.75rem; }
				.view-btns { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
				.view-btn { flex: 1; padding: 0.5rem; border-radius: 8px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.85rem; }
				.view-btn.active { background: #ef4444; color: white; border-color: #ef4444; }
				.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 0.75rem; }
				.split-progress { margin-top: 1rem; }
				.progress-bar { height: 8px; background: var(--rs-bg-hover, #333); border-radius: 4px; overflow: hidden; margin: 0.5rem 0; }
				.progress-fill { height: 100%; background: #ef4444; border-radius: 4px; transition: width 0.3s; }
				.split-status { font-size: 0.8rem; color: var(--rs-text-secondary); }
				.split-error { color: #ef4444; font-size: 0.8rem; margin-top: 0.5rem; }
				.split-results { margin-top: 1rem; }
				.split-results li { font-size: 0.8rem; color: var(--rs-text-secondary); padding: 0.25rem 0; list-style: none; }
				.split-results li::before { content: ""; display: inline-block; width: 6px; height: 6px; background: #22c55e; border-radius: 50%; margin-right: 0.5rem; vertical-align: middle; }
				.btn-success { padding: 0.75rem 1.5rem; background: #22c55e; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; width: 100%; font-size: 0.875rem; margin-top: 0.75rem; }
				.btn-success:disabled { opacity: 0.5; cursor: not-allowed; }
				.live360-section { max-width: 960px; margin: 0 auto; }
				.live360-grid { display: grid; gap: 4px; margin-top: 1rem; }
				.live360-grid.views-2 { grid-template-columns: 1fr 1fr; }
				.live360-grid.views-3 { grid-template-columns: 1fr 1fr 1fr; }
				.live360-grid.views-4 { grid-template-columns: 1fr 1fr; }
				.live360-grid.views-6 { grid-template-columns: 1fr 1fr 1fr; }
				.live360-cell { position: relative; background: #111; border-radius: 8px; overflow: hidden; aspect-ratio: 16/9; cursor: pointer; }
				.live360-cell.expanded { grid-column: 1 / -1; aspect-ratio: 21/9; }
				.live360-cell video { width: 100%; height: 100%; object-fit: cover; }
				.live360-label { position: absolute; top: 8px; left: 8px; background: rgba(0,0,0,0.7); color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 0.75rem; display: flex; align-items: center; gap: 6px; pointer-events: none; }
				.live360-label .live-dot-sm { width: 6px; height: 6px; background: #ef4444; border-radius: 50%; animation: pulse 2s infinite; }
				.live360-controls { display: flex; gap: 0.75rem; align-items: center; margin-top: 1rem; }
				.live360-status { font-size: 0.8rem; color: var(--rs-text-secondary); flex: 1; }
				@media (max-width: 768px) { .layout { grid-template-columns: 1fr; } .live360-grid.views-3, .live360-grid.views-6 { grid-template-columns: 1fr 1fr; } }
			</style>
			<div class="container">
				<div class="rapp-nav">
					<button class="tab ${this.mode === "library" ? "active" : ""}" data-mode="library">Video Library</button>
					${!this.isDemo ? `<button class="tab ${this.mode === "live" ? "active" : ""}" data-mode="live">Live Stream</button>` : ""}
					${!this.isDemo ? `<button class="tab ${this.mode === "360live" ? "active" : ""}" data-mode="360live">360 Live</button>` : ""}
					<span class="rapp-nav__title">${this.isDemo ? `${this.videos.length} recordings` : ""}</span>
					<button class="tab" id="btn-tour" style="font-size:0.78rem;padding:4px 10px;opacity:0.7">Tour</button>
				</div>
				${this.mode === "library" ? this.renderLibrary() : this.mode === "live" ? this.renderLive() : this.render360Live()}
			</div>
			${this.splitModalOpen ? this.renderSplitModal() : ""}
		`;
		this.bindEvents();
		this.bindSplitEvents();
		if (this.mode === "360live") this.bind360LiveEvents();
		this._tour.renderOverlay();
	}

	startTour() {
		this._tour.start();
	}

	private renderLibrary(): string {
		const filteredVideos = this.searchTerm
			? this.videos.filter(v => v.name.toLowerCase().includes(this.searchTerm.toLowerCase()))
			: this.videos;

		const searchInput = `<input type="text" placeholder="Search videos..." data-input="search" value="${this.searchTerm}" style="margin-bottom:0.75rem" />`;

		const videoList = filteredVideos.length === 0
			? `<div class="empty">${this.videos.length === 0 ? "No videos yet" : "No matches"}</div>`
			: filteredVideos.map((v) => `
				<div class="video-item ${this.currentVideo === v.name ? "active" : ""}" data-name="${v.name}" data-collab-id="video:${v.name}">
					<div class="video-name">${v.name}</div>
					<div class="video-meta">${this.getExtension(v.name).toUpperCase()} &middot; ${this.formatSize(v.size)}${v.duration ? ` &middot; ${v.duration}` : ""}${v.date ? `<br>${v.date}` : ""}</div>
				</div>
			`).join("");

		const sidebar = searchInput + videoList;

		let player: string;
		if (!this.currentVideo) {
			player = `<div class="placeholder"><div class="placeholder-icon">&#127916;</div><p>Select a video to play</p></div>`;
		} else if (this.isDemo) {
			const selectedVideo = this.videos.find(v => v.name === this.currentVideo);
			const sizeStr = selectedVideo ? this.formatSize(selectedVideo.size) : "";
			const durStr = selectedVideo?.duration || "";
			player = `<div class="placeholder" style="padding:2rem">
				<div style="width:80px;height:80px;border-radius:50%;background:rgba(239,68,68,0.15);display:flex;align-items:center;justify-content:center;margin:0 auto 1.5rem">
					<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
				</div>
				<p style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:var(--rs-text-primary)">${this.currentVideo}</p>
				<p style="font-size:0.85rem;color:var(--rs-text-secondary)">${this.getExtension(this.currentVideo).toUpperCase()} &middot; ${sizeStr}${durStr ? ` &middot; ${durStr}` : ""}</p>
				${selectedVideo?.date ? `<p style="font-size:0.8rem;color:var(--rs-text-muted);margin-top:0.5rem">Recorded ${selectedVideo.date}</p>` : ""}
				<p style="font-size:0.7rem;color:var(--rs-text-secondary);margin-top:1.5rem;opacity:0.7">Demo preview &mdash; connect rTube to stream real video</p>
			</div>`;
		} else if (!this.isPlayable(this.currentVideo)) {
			player = `<div class="placeholder"><div class="placeholder-icon">&#9888;&#65039;</div><p><strong>${this.getExtension(this.currentVideo).toUpperCase()}</strong> files cannot play in browsers</p><p style="font-size:0.8rem;color:var(--rs-text-secondary);margin-top:0.5rem">Download to play locally</p></div>`;
		} else {
			const base = window.location.pathname.replace(/\/$/, "");
			player = `<video controls autoplay><source src="${base}/api/v/${encodeURIComponent(this.currentVideo)}" type="${this.getExtension(this.currentVideo) === "webm" ? "video/webm" : "video/mp4"}"></video>`;
		}

		const infoBar = this.currentVideo ? `
			<div class="info-bar">
				<span class="info-name">${this.currentVideo}</span>
				${!this.isDemo ? `<div class="actions"><button class="btn" data-action="copy">Copy Link</button><button class="btn" data-action="split360">Split 360&deg;</button></div>` : ""}
			</div>
		` : "";

		return `
			<div class="layout">
				<div class="sidebar">${sidebar}</div>
				<div>
					<div class="player-area">${player}</div>
					${infoBar}
				</div>
			</div>
		`;
	}

	private renderLive(): string {
		return `
			<div class="live-section">
				<div class="live-card">
					<h3 style="font-size:1rem;margin:0 0 0.5rem;color:var(--rs-text-primary)">Watch a Live Stream</h3>
					<p style="font-size:0.85rem;color:var(--rs-text-secondary);margin-bottom:1.5rem">Enter the stream key to watch a live broadcast.</p>
					<input type="text" placeholder="Stream key (e.g. community-meeting)" data-input="streamkey" />
					<button class="btn-primary" data-action="watch">Watch Stream</button>
				</div>
				<div class="setup">
					<h3>Broadcaster Setup (OBS Studio)</h3>
					<ol>
						<li>Open Settings &rarr; Stream</li>
						<li>Set Service to <strong>Custom</strong></li>
						<li>Server: <code>rtmp://rtube.online:1936/live</code></li>
						<li>Stream Key: any key (e.g. <code>community-meeting</code>)</li>
						<li>Click <strong>Start Streaming</strong></li>
					</ol>
				</div>
			</div>
		`;
	}

	private render360Live(): string {
		if (this.liveSplitSession && this.liveSplitSession.status === "running") {
			return this.render360LiveRunning();
		}
		if (this.liveSplitError) {
			return `
				<div class="live-section">
					<div class="live-card">
						<h3 style="font-size:1rem;margin:0 0 0.5rem;color:#ef4444">Error</h3>
						<p style="font-size:0.85rem;color:var(--rs-text-secondary);margin-bottom:1rem">${this.liveSplitError}</p>
						<button class="btn-primary" data-action="360live-retry">Try Again</button>
					</div>
				</div>
			`;
		}
		// Setup form
		const s = this.liveSplitSettings;
		return `
			<div class="live-section">
				<div class="live-card">
					<h3 style="font-size:1rem;margin:0 0 0.5rem;color:var(--rs-text-primary)">360&deg; Live Stream Splitter</h3>
					<p style="font-size:0.85rem;color:var(--rs-text-secondary);margin-bottom:1.5rem">Split a live 360&deg; RTMP stream into multiple perspective views in real-time.</p>
					<label style="font-size:0.8rem;color:var(--rs-text-secondary)">Stream Key</label>
					<input type="text" placeholder="Stream key (e.g. test360)" data-input="live360-key" value="${this.liveSplitStreamKey}" />
					<label style="font-size:0.8rem;color:var(--rs-text-secondary)">Number of Views</label>
					<div class="view-btns" style="margin-bottom:1rem">
						${[2, 3, 4, 6].map(n => `<button class="view-btn ${s.numViews === n ? "active" : ""}" data-live360-views="${n}">${n}</button>`).join("")}
					</div>
					<div class="settings-grid">
						<div><label style="font-size:0.8rem;color:var(--rs-text-secondary)">H-FOV (&deg;)</label><input type="number" data-live360-input="hFov" value="${s.hFov}" min="30" max="360" /></div>
						<div><label style="font-size:0.8rem;color:var(--rs-text-secondary)">V-FOV (&deg;)</label><input type="number" data-live360-input="vFov" value="${s.vFov}" min="30" max="180" /></div>
						<div><label style="font-size:0.8rem;color:var(--rs-text-secondary)">Overlap (&deg;)</label><input type="number" data-live360-input="overlap" value="${s.overlap}" min="0" max="90" /></div>
						<div><label style="font-size:0.8rem;color:var(--rs-text-secondary)">Output Resolution</label><input type="text" data-live360-input="outputRes" value="${s.outputRes}" placeholder="e.g. 1280x720" /></div>
					</div>
					<button class="btn-primary" data-action="360live-start" ${this.liveSplitStarting ? "disabled" : ""}>${this.liveSplitStarting ? "Starting..." : "Start Live Split"}</button>
				</div>
				<div class="setup" style="margin-top:1.5rem">
					<h3>Broadcaster Setup</h3>
					<ol>
						<li>Stream 360&deg; video to <code>rtmp://rtube.online:1936/live/{key}</code></li>
						<li>Enter the same stream key above</li>
						<li>Choose number of views and click Start</li>
						<li>Each view shows a different perspective of the 360&deg; stream</li>
					</ol>
				</div>
			</div>
		`;
	}

	private render360LiveRunning(): string {
		const session = this.liveSplitSession!;
		const viewCount = session.views.length;
		const gridClass = `views-${viewCount}`;
		const cells = session.views.map(v => {
			const isExpanded = this.expandedView === v.index;
			return `
				<div class="live360-cell ${isExpanded ? "expanded" : ""}" data-view-index="${v.index}">
					<video id="live360-video-${v.index}" muted autoplay playsinline></video>
					<div class="live360-label">
						<span class="live-dot-sm"></span>
						${v.label} (${Math.round(v.yaw)}&deg;)
					</div>
				</div>
			`;
		}).join("");

		return `
			<div class="live360-section">
				<div class="live360-grid ${gridClass}">${cells}</div>
				<div class="live360-controls">
					<div class="live360-status">
						<span class="live-badge"><span class="live-dot"></span> Live</span>
						&nbsp; Session: ${session.sessionId} &middot; ${viewCount} views
					</div>
					<button class="btn" data-action="360live-stop">Stop</button>
				</div>
			</div>
		`;
	}

	private async loadHlsJs(): Promise<any> {
		if ((window as any).Hls) return (window as any).Hls;
		return new Promise((resolve, reject) => {
			const script = document.createElement("script");
			script.src = "https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js";
			script.onload = () => resolve((window as any).Hls);
			script.onerror = () => reject(new Error("Failed to load HLS.js"));
			document.head.appendChild(script);
		});
	}

	private async attachHlsPlayers() {
		if (!this.liveSplitSession) return;
		const Hls = await this.loadHlsJs();
		if (!Hls.isSupported()) {
			this.liveSplitError = "HLS.js is not supported in this browser";
			this.render();
			return;
		}

		this.destroyHlsPlayers();
		const base = window.location.pathname.replace(/\/$/, "");

		for (const view of this.liveSplitSession.views) {
			const video = this.shadow.getElementById(`live360-video-${view.index}`) as HTMLVideoElement;
			if (!video) continue;

			const hls = new Hls({
				liveSyncDurationCount: 3,
				liveMaxLatencyDurationCount: 6,
				enableWorker: true,
			});
			const hlsUrl = `${base}/api/live-split/hls/${this.liveSplitSession.sessionId}/${view.hls_url.replace(/^.*?view_/, "view_")}`;
			hls.loadSource(hlsUrl);
			hls.attachMedia(video);
			hls.on(Hls.Events.MANIFEST_PARSED, () => video.play().catch(() => {}));
			this.hlsPlayers.push(hls);
		}
	}

	private destroyHlsPlayers() {
		for (const hls of this.hlsPlayers) {
			try { hls.destroy(); } catch {}
		}
		this.hlsPlayers = [];
		if (this.liveSplitStatusInterval) {
			clearInterval(this.liveSplitStatusInterval);
			this.liveSplitStatusInterval = null;
		}
	}

	private async startLiveSplit() {
		if (!this.liveSplitStreamKey) return;
		const base = window.location.pathname.replace(/\/$/, "");
		const s = this.liveSplitSettings;

		this.liveSplitStarting = true;
		this.liveSplitError = "";
		this.render();

		try {
			const resp = await authFetch(`${base}/api/live-split`, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					streamKey: this.liveSplitStreamKey,
					numViews: s.numViews,
					hFov: s.hFov,
					vFov: s.vFov,
					overlap: s.overlap || undefined,
					outputRes: s.outputRes || undefined,
				}),
			});

			if (!resp.ok) {
				const data = await resp.json().catch(() => ({ error: "Request failed" }));
				throw new Error(data.error || `HTTP ${resp.status}`);
			}

			const data = await resp.json();
			this.liveSplitSession = {
				sessionId: data.session_id,
				status: data.status,
				views: data.views.map((v: any) => ({
					index: v.index,
					label: v.label,
					yaw: v.yaw,
					hls_url: v.hls_url,
				})),
			};
			this.liveSplitStarting = false;
			this.render();

			// Wait a moment for FFmpeg to produce initial segments
			setTimeout(() => this.attachHlsPlayers(), 4000);
			this.startLiveSplitStatusPoll();
		} catch (e: any) {
			this.liveSplitStarting = false;
			this.liveSplitError = e.message || "Failed to start live split";
			this.render();
		}
	}

	private startLiveSplitStatusPoll() {
		if (this.liveSplitStatusInterval) clearInterval(this.liveSplitStatusInterval);
		const base = window.location.pathname.replace(/\/$/, "");
		const sessionId = this.liveSplitSession?.sessionId;
		if (!sessionId) return;

		this.liveSplitStatusInterval = setInterval(async () => {
			try {
				const resp = await fetch(`${base}/api/live-split/status/${sessionId}`);
				if (!resp.ok) return;
				const data = await resp.json();
				if (data.status === "error") {
					this.liveSplitError = data.error || "Stream ended unexpectedly";
					this.liveSplitSession = null;
					this.destroyHlsPlayers();
					this.render();
				}
			} catch { /* retry */ }
		}, 5000);
	}

	private async stopLiveSplit() {
		if (!this.liveSplitSession) return;
		const base = window.location.pathname.replace(/\/$/, "");
		const sessionId = this.liveSplitSession.sessionId;

		this.destroyHlsPlayers();
		this.liveSplitSession = null;
		this.expandedView = null;
		this.render();

		try {
			await authFetch(`${base}/api/live-split/stop/${sessionId}`, { method: "POST" });
		} catch { /* best effort */ }
	}

	private bind360LiveEvents() {
		// Setup form inputs
		const keyInput = this.shadow.querySelector('[data-input="live360-key"]') as HTMLInputElement;
		if (keyInput) {
			keyInput.addEventListener("input", () => { this.liveSplitStreamKey = keyInput.value; });
		}

		this.shadow.querySelectorAll("[data-live360-views]").forEach(btn => {
			btn.addEventListener("click", () => {
				const n = parseInt((btn as HTMLElement).dataset.live360Views || "4");
				this.liveSplitSettings.numViews = n;
				this.liveSplitSettings.hFov = Math.round(360 / n);
				this.render();
			});
		});

		this.shadow.querySelectorAll("[data-live360-input]").forEach(input => {
			input.addEventListener("change", () => {
				const key = (input as HTMLElement).dataset.live360Input as keyof typeof this.liveSplitSettings;
				const val = (input as HTMLInputElement).value;
				if (key === "outputRes") {
					this.liveSplitSettings[key] = val;
				} else {
					(this.liveSplitSettings as any)[key] = parseFloat(val) || 0;
				}
			});
		});

		// Start button
		this.shadow.querySelector('[data-action="360live-start"]')?.addEventListener("click", () => {
			if (!requireAuth("start 360 live split")) return;
			this.startLiveSplit();
		});

		// Retry button
		this.shadow.querySelector('[data-action="360live-retry"]')?.addEventListener("click", () => {
			this.liveSplitError = "";
			this.render();
		});

		// Stop button
		this.shadow.querySelector('[data-action="360live-stop"]')?.addEventListener("click", () => {
			this.stopLiveSplit();
		});

		// Click view cell to expand/collapse
		this.shadow.querySelectorAll(".live360-cell").forEach(cell => {
			cell.addEventListener("click", () => {
				const idx = parseInt((cell as HTMLElement).dataset.viewIndex || "0");
				this.expandedView = this.expandedView === idx ? null : idx;
				// Re-render grid without destroying HLS players
				const grid = this.shadow.querySelector(".live360-grid");
				if (grid) {
					grid.querySelectorAll(".live360-cell").forEach(c => {
						const cIdx = parseInt((c as HTMLElement).dataset.viewIndex || "-1");
						c.classList.toggle("expanded", cIdx === this.expandedView);
					});
				}
			});
		});
	}

	private bindEvents() {
		this.shadow.querySelector("#btn-tour")?.addEventListener("click", () => this.startTour());

		this.shadow.querySelectorAll(".tab").forEach((btn) => {
			btn.addEventListener("click", () => {
				const newMode = (btn as HTMLElement).dataset.mode as "library" | "live" | "360live";
				if (!newMode) return;
				if (this.mode === "360live" && newMode !== "360live") {
					this.destroyHlsPlayers();
				}
				this._history.push(newMode);
				this.mode = newMode;
				this.render();
			});
		});

		const searchInput = this.shadow.querySelector('[data-input="search"]') as HTMLInputElement;
		if (searchInput) {
			searchInput.addEventListener("input", () => {
				this.searchTerm = searchInput.value;
				this.render();
				// Restore focus and cursor position after re-render
				const newInput = this.shadow.querySelector('[data-input="search"]') as HTMLInputElement;
				if (newInput) { newInput.focus(); newInput.selectionStart = newInput.selectionEnd = newInput.value.length; }
			});
		}

		this.shadow.querySelectorAll(".video-item").forEach((item) => {
			item.addEventListener("click", () => {
				this.currentVideo = (item as HTMLElement).dataset.name || null;
				this.render();
			});
		});

		const copyBtn = this.shadow.querySelector('[data-action="copy"]');
		if (copyBtn) {
			copyBtn.addEventListener("click", () => {
				if (this.currentVideo) {
					const base = window.location.pathname.replace(/\/$/, "");
					const url = `${window.location.origin}${base}/api/v/${encodeURIComponent(this.currentVideo)}`;
					navigator.clipboard.writeText(url);
				}
			});
		}

		const splitBtn = this.shadow.querySelector('[data-action="split360"]');
		if (splitBtn) {
			splitBtn.addEventListener("click", () => {
				if (!requireAuth("split 360° video")) return;
				this.splitJob = null;
				this.splitImported = null;
				this.splitImporting = false;
				this.splitModalOpen = true;
				this.render();
			});
		}
	}

	private renderSplitModal(): string {
		const s = this.splitSettings;
		const job = this.splitJob;
		const isProcessing = job && (job.status === "queued" || job.status === "processing");
		const isComplete = job?.status === "complete";
		const isError = job?.status === "error";

		let body: string;
		if (this.splitImported) {
			body = `
				<p style="color:#22c55e;font-weight:500;margin-bottom:0.5rem">Imported ${this.splitImported.length} files to library</p>
				<ul class="split-results" style="padding:0;margin:0">${this.splitImported.map(f => `<li>${f}</li>`).join("")}</ul>
				<button class="btn-primary" data-split="close" style="margin-top:1rem">Done</button>
			`;
		} else if (isComplete) {
			body = `
				<p style="color:#22c55e;font-weight:500;margin-bottom:0.5rem">Split complete!</p>
				<ul class="split-results" style="padding:0;margin:0">${(job!.outputFiles || []).map(f => `<li>${f}</li>`).join("")}</ul>
				<button class="btn-success" data-split="import" ${this.splitImporting ? "disabled" : ""}>${this.splitImporting ? "Importing..." : "Import All to Library"}</button>
			`;
		} else if (isProcessing) {
			body = `
				<div class="split-progress">
					<div class="split-status">Status: ${job!.status}${job!.currentView ? ` &mdash; ${job!.currentView}` : ""}</div>
					<div class="progress-bar"><div class="progress-fill" style="width:${job!.progress || 0}%"></div></div>
					<div class="split-status">${job!.progress || 0}% complete</div>
				</div>
			`;
		} else if (isError) {
			body = `
				<div class="split-error">${job!.error || "Unknown error"}</div>
				<button class="btn-primary" data-split="retry" style="margin-top:0.75rem">Retry</button>
			`;
		} else {
			body = `
				<label>Number of Views</label>
				<div class="view-btns">
					${[2, 3, 4, 6].map(n => `<button class="view-btn ${s.numViews === n ? "active" : ""}" data-views="${n}">${n}</button>`).join("")}
				</div>
				<div class="settings-grid">
					<div><label>H-FOV (&deg;)</label><input type="number" data-split-input="hFov" value="${s.hFov}" min="30" max="360" /></div>
					<div><label>V-FOV (&deg;)</label><input type="number" data-split-input="vFov" value="${s.vFov}" min="30" max="180" /></div>
					<div><label>Overlap (&deg;)</label><input type="number" data-split-input="overlap" value="${s.overlap}" min="0" max="90" /></div>
					<div><label>Output Resolution</label><input type="text" data-split-input="outputRes" value="${s.outputRes}" placeholder="e.g. 1920x1080" /></div>
				</div>
				<button class="btn-primary" data-split="start">Start Split</button>
			`;
		}

		return `
			<div class="split-overlay" data-split="overlay">
				<div class="split-modal">
					<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.75rem">
						<h3 style="margin:0">Split 360&deg; Video</h3>
						<button class="btn" data-split="close" style="padding:0.25rem 0.6rem;font-size:0.9rem">&times;</button>
					</div>
					<p style="font-size:0.8rem;color:var(--rs-text-secondary);margin-bottom:1rem">${this.currentVideo}</p>
					${body}
				</div>
			</div>
		`;
	}

	private bindSplitEvents() {
		if (!this.splitModalOpen) return;

		// Close modal
		this.shadow.querySelector('[data-split="overlay"]')?.addEventListener("click", (e) => {
			if ((e.target as HTMLElement).dataset.split === "overlay") this.closeSplitModal();
		});
		this.shadow.querySelectorAll('[data-split="close"]').forEach(el =>
			el.addEventListener("click", () => this.closeSplitModal())
		);

		// View count buttons
		this.shadow.querySelectorAll(".view-btn").forEach(btn => {
			btn.addEventListener("click", () => {
				const n = parseInt((btn as HTMLElement).dataset.views || "4");
				this.splitSettings.numViews = n;
				this.splitSettings.hFov = Math.round(360 / n);
				this.render();
			});
		});

		// Settings inputs
		this.shadow.querySelectorAll("[data-split-input]").forEach(input => {
			input.addEventListener("change", () => {
				const key = (input as HTMLElement).dataset.splitInput as keyof typeof this.splitSettings;
				const val = (input as HTMLInputElement).value;
				if (key === "outputRes") {
					this.splitSettings[key] = val;
				} else {
					(this.splitSettings as any)[key] = parseFloat(val) || 0;
				}
			});
		});

		// Start
		this.shadow.querySelector('[data-split="start"]')?.addEventListener("click", () => this.startSplitJob());

		// Retry
		this.shadow.querySelector('[data-split="retry"]')?.addEventListener("click", () => {
			this.splitJob = null;
			this.render();
		});

		// Import
		this.shadow.querySelector('[data-split="import"]')?.addEventListener("click", () => this.importSplitResults());
	}

	private closeSplitModal() {
		this.splitModalOpen = false;
		if (this.splitPollInterval) {
			clearInterval(this.splitPollInterval);
			this.splitPollInterval = null;
		}
		this.render();
	}

	private async startSplitJob() {
		if (!this.currentVideo) return;
		const base = window.location.pathname.replace(/\/$/, "");
		const s = this.splitSettings;

		this.splitJob = { jobId: "", status: "queued", progress: 0, currentView: "", outputFiles: [], error: "" };
		this.render();

		try {
			const resp = await authFetch(`${base}/api/360split`, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					videoName: this.currentVideo,
					numViews: s.numViews,
					hFov: s.hFov,
					vFov: s.vFov,
					overlap: s.overlap || undefined,
					outputRes: s.outputRes || undefined,
				}),
			});

			if (!resp.ok) {
				const data = await resp.json().catch(() => ({ error: "Request failed" }));
				this.splitJob = { jobId: "", status: "error", progress: 0, currentView: "", outputFiles: [], error: data.error || `HTTP ${resp.status}` };
				this.render();
				return;
			}

			const { jobId } = await resp.json();
			this.splitJob.jobId = jobId;
			this.splitJob.status = "queued";
			this.render();
			this.pollSplitStatus();
		} catch (e: any) {
			this.splitJob = { jobId: "", status: "error", progress: 0, currentView: "", outputFiles: [], error: e.message || "Network error" };
			this.render();
		}
	}

	private pollSplitStatus() {
		if (this.splitPollInterval) clearInterval(this.splitPollInterval);

		const base = window.location.pathname.replace(/\/$/, "");
		const jobId = this.splitJob?.jobId;
		if (!jobId) return;

		this.splitPollInterval = setInterval(async () => {
			try {
				const resp = await fetch(`${base}/api/360split/status/${jobId}`);
				if (!resp.ok) return;
				const data = await resp.json();

				this.splitJob = {
					jobId,
					status: data.status,
					progress: data.progress || 0,
					currentView: data.current_view || "",
					outputFiles: data.output_files || [],
					error: data.error || "",
				};
				this.render();

				if (data.status === "complete" || data.status === "error") {
					clearInterval(this.splitPollInterval!);
					this.splitPollInterval = null;
				}
			} catch { /* retry on next poll */ }
		}, 2000);
	}

	private async importSplitResults() {
		if (!this.splitJob?.jobId) return;
		this.splitImporting = true;
		this.render();

		const base = window.location.pathname.replace(/\/$/, "");
		try {
			const resp = await authFetch(`${base}/api/360split/import/${this.splitJob.jobId}`, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ originalName: this.currentVideo }),
			});

			if (!resp.ok) {
				const data = await resp.json().catch(() => ({ error: "Import failed" }));
				this.splitJob.status = "error";
				this.splitJob.error = data.error || "Import failed";
				this.splitImporting = false;
				this.render();
				return;
			}

			const { imported } = await resp.json();
			this.splitImported = imported;
			this.splitImporting = false;
			this.render();
			// Refresh video list to show imported files
			this.loadVideos();
		} catch (e: any) {
			this.splitJob!.status = "error";
			this.splitJob!.error = e.message || "Import failed";
			this.splitImporting = false;
			this.render();
		}
	}
}

customElements.define("folk-video-player", FolkVideoPlayer);
