<template>
	<NcContent app-name="glxradio">
		<NcAppNavigation>
			<template #list>
				<button
					class="glxradio-sidebar-btn glxradio-sidebar-btn-nav"
					:class="{ 'glxradio-sidebar-btn-active': currentView === 'radio' }"
					@click="goToStationList">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M3.24 6.15C2.51 6.43 2 7.17 2 8v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2H8.3l8.26-3.34L15.88 3 3.24 6.15zM7 20c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm13-8h-2V9h2v3z"/></svg>
					Radio
				</button>

				<button
					v-for="folder in settings.folders"
					:key="folder"
					class="glxradio-sidebar-btn glxradio-sidebar-btn-nav glxradio-sidebar-btn-folder"
					:class="{ 'glxradio-sidebar-btn-active': currentView === 'folder' && activeFolder === folder }"
					@click="goToFolder(folder)">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
					{{ folder }}
				</button>

				<div class="glxradio-sidebar-search">
					<div class="glxradio-search-input-wrap">
						<input
							v-model="query"
							type="text"
							placeholder="Sendername oder Genre suchen..."
							@keyup.enter="search">
						<button v-if="query" class="glxradio-search-clear" title="Suche leeren" @click="clearSearchInput">✕</button>
					</div>
					<button class="glxradio-icon-btn-round" title="Suchen" @click="search">
						<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 5L20.49 19zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z"/></svg>
					</button>
				</div>

				<button v-if="hasSearched" class="glxradio-sidebar-btn" @click="goToStationList">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/></svg>
					Senderliste
				</button>

				<button
					class="glxradio-sidebar-btn glxradio-sidebar-btn-nav"
					:class="{ 'glxradio-sidebar-btn-active': currentView === 'discover' }"
					@click="openDiscover">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.9 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2H4.26zm.81 2h2.95c.32 1.25.78 2.45 1.38 3.56A7.99 7.99 0 0 1 5.07 16zm2.95-8H5.07a7.987 7.987 0 0 1 4.33-3.56C8.8 5.55 8.34 6.75 8.02 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2s.07-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/></svg>
					Externe Radiosender
				</button>

				<button class="glxradio-sidebar-btn" @click="showAddForm = !showAddForm">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z"/></svg>
					{{ showAddForm ? 'Abbrechen' : 'Eigener Sender' }}
				</button>
				<div v-if="showAddForm" class="glxradio-add-form glxradio-add-form-sidebar">
					<input v-model="newStation.name" type="text" placeholder="Name des Senders">
					<input v-model="newStation.url_resolved" type="text" placeholder="Stream-Link (.mp3, .m3u, .m3u8, ...)">
					<div class="glxradio-favicon-row">
						<input v-model="newStation.favicon" type="text" placeholder="Logo-Link (optional)">
						<button class="glxradio-btn glxradio-btn-secondary" title="Logo aus Nextcloud wählen" @click="openLogoPicker('new')">📁</button>
					</div>
					<select v-model="newStation.folder" class="glxradio-folder-select">
						<option value="">Kein Ordner</option>
						<option v-for="folder in settings.folders" :key="folder" :value="folder">{{ folder }}</option>
					</select>
					<button class="glxradio-btn" @click="addCustomStation">Als Favorit speichern</button>
				</div>

				<button class="glxradio-sidebar-btn" @click="exportFavorites">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2z"/></svg>
					Exportieren
				</button>
				<button class="glxradio-sidebar-btn" @click="triggerImport">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M9 16h6v-6h4l-7-7-7 7h4zM5 18h14v2H5z"/></svg>
					Importieren
				</button>
				<input ref="importInput" type="file" accept="application/json" style="display:none;" @change="importFavorites">

				<button
					class="glxradio-sidebar-btn glxradio-sidebar-btn-nav"
					:class="{ 'glxradio-sidebar-btn-active': currentView === 'usage' }"
					@click="currentView = 'usage'">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg>
					Benutzung
				</button>

				<button
					class="glxradio-sidebar-btn glxradio-sidebar-btn-nav glxradio-sidebar-btn-settings"
					:class="{ 'glxradio-sidebar-btn-active': currentView === 'settings' }"
					@click="currentView = 'settings'">
					<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.5.5 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.44.17-.48.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.63c-.11.2-.06.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.5.5 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.25.41.48.41h3.84c.24 0 .44-.17.48-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32a.5.5 0 0 0-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
					Einstellungen
				</button>
			</template>
		</NcAppNavigation>

		<NcAppContent>
			<div class="glxradio-layout">
				<div class="glxradio-main">
					<div class="glxradio-player">
					<template v-if="current">
						<div class="glxradio-tile" :style="showTileImage ? null : { background: monogramColor }">
							<img
								v-if="showTileImage"
								:src="current.favicon"
								class="glxradio-tile-img"
								alt=""
								@error="faviconFailed = true">
							<span v-else class="glxradio-tile-letter">{{ monogramLetter }}</span>
						</div>

						<button
							class="glxradio-transport-btn glxradio-transport-btn-main"
							title="Vorheriger Favorit"
							:disabled="!favorites.length"
							@click="playPrevious">
							<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"><path fill="currentColor" d="M6 6h2v12H6zm3.5 6 8.5 6V6z"/></svg>
						</button>
						<button
							class="glxradio-transport-btn glxradio-transport-btn-main"
							:aria-label="isPlaying ? 'Pause' : 'Abspielen'"
							@click="togglePlay">
							<svg v-if="!isPlaying" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"><path fill="currentColor" d="M8 5v14l11-7z"/></svg>
							<svg v-else viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"><path fill="currentColor" d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
						</button>
						<button
							class="glxradio-transport-btn glxradio-transport-btn-main"
							title="Nächster Favorit"
							:disabled="!favorites.length"
							@click="playNext">
							<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"><path fill="currentColor" d="M16 6h2v12h-2zM6 18l8.5-6L6 6z"/></svg>
						</button>

						<div class="glxradio-info">
							<div class="glxradio-info-top">
								<strong class="glxradio-station-name">{{ current.name }}</strong>
								<span class="glxradio-live" :class="{ 'glxradio-live-buffering': playState === 'buffering' || playState === 'connecting' }">
									<span class="glxradio-live-dot"></span>{{ playState === 'buffering' || playState === 'connecting' ? 'VERBINDET' : 'LIVE' }}
								</span>
							</div>
							<div class="glxradio-info-sub glxradio-player-meta">
								<span v-if="current.tags">{{ shortTags(current.tags) }}</span>
								<span v-if="current.bitrate">{{ current.tags ? '· ' : '' }}{{ current.bitrate }} kbps</span>
							</div>
							<div v-if="nowPlayingTitle" class="glxradio-now-playing-title">🎵 {{ nowPlayingTitle }}</div>
						</div>

						<div class="glxradio-player-fill">
							<div class="glxradio-progress-track">
								<div class="glxradio-progress-fill" :class="{ 'glxradio-progress-fill-active': playState === 'playing' }"></div>
							</div>
							<div class="glxradio-status" :class="'glxradio-status-' + playState">
								{{ statusText }}
							</div>
						</div>

						<div class="glxradio-controls">
							<button class="glxradio-transport-btn glxradio-transport-btn-main" :title="isMuted ? 'Ton an' : 'Ton aus'" @click="toggleMute">
								<svg v-if="!isMuted" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"><path fill="currentColor" d="M3 10v4h4l5 5V5L7 10H3zm13.5 2A4.5 4.5 0 0 0 14 7.97v8.05A4.5 4.5 0 0 0 16.5 12z"/></svg>
								<svg v-else viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"><path fill="currentColor" d="M16.5 12A4.5 4.5 0 0 0 14 7.97v2.21l2.45 2.45c.03-.2.05-.42.05-.63zM19 12c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3 3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06a8.99 8.99 0 0 0 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4 9.91 6.09 12 8.18V4z"/></svg>
							</button>
							<input
								type="range"
								class="glxradio-volume"
								min="0"
								max="1"
								step="0.01"
								v-model.number="volume"
								@input="onVolumeInput"
								aria-label="Lautstärke">
						</div>

						<audio
							ref="audioEl"
							:key="current.stationuuid"
							:src="playUrl"
							autoplay
							class="glxradio-audio-hidden"
							@error="onPlayError"
							@loadstart="onLoadStart"
							@waiting="onWaiting"
							@stalled="onStalled"
							@playing="onPlaying"
							@play="isPlaying = true"
							@pause="isPlaying = false" />

						<div v-if="playError" class="glxradio-play-error">
							⚠️ Dieser Sender lässt sich gerade nicht abspielen - vermutlich ist der Stream selbst offline
							oder überlastet. Bitte einen anderen Sender probieren oder es in ein paar Minuten nochmal versuchen.
						</div>
					</template>

					<template v-else>
						<div class="glxradio-tile glxradio-tile-empty">
							<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M3 9v6h4l5 5V4L7 9zm13.5 3A4.5 4.5 0 0 0 14 7.97v8.05A4.5 4.5 0 0 0 16.5 12zM14 3.23v2.06a7 7 0 0 1 0 13.42v2.06A9 9 0 0 0 14 3.23z"/></svg>
						</div>
						<div class="glxradio-info">
							<span class="glxradio-station-name">Kein Sender ausgewählt</span>
							<div class="glxradio-info-sub">Wähle einen Favoriten oder ein Suchergebnis zum Abspielen.</div>
						</div>
					</template>
				</div>

				<div v-if="currentView === 'radio'" class="glxradio-list">
					<div class="glxradio-list-header">
						<h2><svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 2l2.9 6.6 7.1.6-5.4 4.7 1.6 7-6.2-3.8L5.8 21l1.6-7L2 9.2l7.1-.6z"/></svg>{{ hasSearched ? 'Suchergebnisse' : 'Favoriten' }}</h2>
						<button v-if="hasSearched" class="glxradio-btn glxradio-btn-secondary" @click="clearSearch">← Zurück zu Favoriten</button>
					</div>

					<div v-if="hasSearched">
						<div v-if="loading" class="glxradio-empty">Suche läuft...</div>
						<div v-else-if="!results.length" class="glxradio-empty">Keine Sender gefunden.</div>
						<div
							v-for="station in results"
							:key="station.stationuuid"
							class="glxradio-item">
							<img v-if="settings.showLogos && station.favicon" :src="station.favicon" class="glxradio-favicon" alt="" @error="onImgError">
							<span class="glxradio-name" @click="play(station)">{{ station.name }}</span>
							<button
								class="glxradio-icon-btn"
								:title="isFavorite(station) ? 'Favorit entfernen' : 'Als Favorit speichern'"
								@click="toggleFavorite(station)">
								{{ isFavorite(station) ? '★' : '☆' }}
							</button>
						</div>
					</div>

					<div v-else>
						<div v-if="!favorites.length" class="glxradio-empty">Noch keine Favoriten gespeichert.</div>
						<div v-for="(station, index) in favorites" :key="station.stationuuid">
							<div v-if="editingId !== station.stationuuid" class="glxradio-item">
								<span class="glxradio-pos-num">{{ index + 1 }}.</span>
								<img v-if="settings.showLogos && station.favicon" :src="station.favicon" class="glxradio-favicon" alt="" @error="onImgError">
								<span class="glxradio-name" @click="play(station)">{{ station.name }}</span>
								<span v-if="station.folder" class="glxradio-folder-badge">📁 {{ station.folder }}</span>
								<button class="glxradio-icon-btn" title="Einstellungen des Senders" @click="startEdit(station)"><svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/><circle cx="12" cy="12" r="3"/></g></svg></button>
							</div>
							<div v-else class="glxradio-add-form glxradio-edit-form">
								<input v-model="editStation.name" type="text" placeholder="Name">
								<input v-model="editStation.url_resolved" type="text" placeholder="Stream-Link">
								<div class="glxradio-favicon-row">
									<input v-model="editStation.favicon" type="text" placeholder="Logo-Link (optional)">
									<button class="glxradio-btn glxradio-btn-secondary" title="Logo aus Nextcloud wählen" @click="openLogoPicker('edit')">📁</button>
								</div>
								<select v-model="editStation.folder" class="glxradio-folder-select">
									<option value="">Kein Ordner</option>
									<option v-for="folder in settings.folders" :key="folder" :value="folder">{{ folder }}</option>
								</select>
								<div class="glxradio-pos-row">
									<label>Platz in der Liste</label>
									<input v-model="editStation.position" type="number" min="1">
									<span class="glxradio-pos-hint">von {{ positionCount }}</span>
								</div>
								<div class="glxradio-edit-actions">
									<button class="glxradio-btn glxradio-btn-danger" @click="requestDeleteFavorite(editingStation)">Sender löschen</button>
									<button class="glxradio-btn" @click="saveEdit">Speichern</button>
									<button class="glxradio-btn glxradio-btn-secondary" @click="editingId = null">Abbrechen</button>
								</div>
							</div>
						</div>
					</div>
				</div>

				<div v-else-if="currentView === 'folder'" class="glxradio-list">
					<div class="glxradio-list-header">
						<h2><svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>{{ activeFolder }}</h2>
					</div>
					<div v-if="!folderStations.length" class="glxradio-empty">Diesem Ordner sind noch keine Sender zugewiesen. Über das Zahnrad-Symbol eines Favoriten kannst du ihn per Dropdown zuweisen.</div>
					<div v-for="(station, index) in folderStations" :key="station.stationuuid">
						<div v-if="editingId !== station.stationuuid" class="glxradio-item">
							<span class="glxradio-pos-num">{{ index + 1 }}.</span>
							<img v-if="settings.showLogos && station.favicon" :src="station.favicon" class="glxradio-favicon" alt="" @error="onImgError">
							<span class="glxradio-name" @click="play(station)">{{ station.name }}</span>
							<button class="glxradio-icon-btn" title="Einstellungen des Senders" @click="startEdit(station)"><svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/><circle cx="12" cy="12" r="3"/></g></svg></button>
						</div>
						<div v-else class="glxradio-add-form glxradio-edit-form">
							<input v-model="editStation.name" type="text" placeholder="Name">
							<input v-model="editStation.url_resolved" type="text" placeholder="Stream-Link">
							<div class="glxradio-favicon-row">
								<input v-model="editStation.favicon" type="text" placeholder="Logo-Link (optional)">
								<button class="glxradio-btn glxradio-btn-secondary" title="Logo aus Nextcloud wählen" @click="openLogoPicker('edit')">📁</button>
							</div>
							<select v-model="editStation.folder" class="glxradio-folder-select">
								<option value="">Kein Ordner</option>
								<option v-for="folder in settings.folders" :key="folder" :value="folder">{{ folder }}</option>
							</select>
							<div class="glxradio-pos-row">
								<label>Platz in der Liste</label>
								<input v-model="editStation.position" type="number" min="1">
								<span class="glxradio-pos-hint">von {{ positionCount }}</span>
							</div>
							<div class="glxradio-edit-actions">
								<button class="glxradio-btn glxradio-btn-danger" @click="requestDeleteFavorite(editingStation)">Sender löschen</button>
								<button class="glxradio-btn" @click="saveEdit">Speichern</button>
								<button class="glxradio-btn glxradio-btn-secondary" @click="editingId = null">Abbrechen</button>
							</div>
						</div>
					</div>
				</div>

				<div v-else-if="currentView === 'settings'" class="glxradio-settings">
					<h2>Einstellungen</h2>

					<div class="glxradio-settings-block">
						<h3>Sender-Logos</h3>
						<label class="glxradio-settings-toggle">
							<input type="checkbox" v-model="settings.showLogos" @change="saveSettings">
							Sender-Logos in Listen und Player anzeigen
						</label>
					</div>

					<div class="glxradio-settings-block">
						<h3>Logo-Ordner</h3>
						<p class="glxradio-info-sub">
							Ordner in deinen eigenen Nextcloud-Dateien, aus dem sich der 📁-Button beim Sender-Logo
							bedient. Leg dort Bilddateien ab, die du als Sender-Icons nutzen möchtest.
						</p>
						<div class="glxradio-favicon-row">
							<input v-model="settings.logoFolder" type="text" placeholder="/RadioLogos">
							<button class="glxradio-btn glxradio-btn-secondary" title="Ordner auf dem Server auswählen" @click="openFolderPicker">📁 Durchsuchen</button>
							<button class="glxradio-btn" @click="saveSettings">Speichern</button>
						</div>
						<div v-if="settingsSaved" class="glxradio-settings-saved">✓ Gespeichert</div>
					</div>

					<div class="glxradio-settings-block">
						<h3>Sender-Ordner</h3>
						<p class="glxradio-info-sub">
							Eigene Ordner zum Sortieren deiner Favoriten - erscheinen links im Seitenmenü.
							Sender werden über ihr Zahnrad-Symbol per Dropdown einem Ordner zugewiesen.
							Die Gesamtliste "Favoriten" zeigt weiterhin alle Sender, egal welchem Ordner sie zugewiesen sind.
						</p>
						<div class="glxradio-favicon-row">
							<input v-model="newFolderName" type="text" placeholder="Name des neuen Ordners" @keyup.enter="createFolder">
							<button class="glxradio-btn" @click="createFolder">Anlegen</button>
						</div>
						<div v-if="!settings.folders.length" class="glxradio-empty">Noch keine Ordner angelegt.</div>
						<div v-for="folder in settings.folders" :key="folder" class="glxradio-folder-row">
							<span>{{ folder }}</span>
							<button class="glxradio-icon-btn" title="Ordner löschen" @click="requestDeleteFolder(folder)">✕</button>
						</div>
					</div>

					<div class="glxradio-app-version">GLX Radio Version {{ appVersion }}</div>
				</div>

				<div v-else-if="currentView === 'usage'" class="glxradio-settings glxradio-help">
					<h2>Benutzung</h2>
					<p class="glxradio-info-sub">Kurzanleitung zu allen Funktionen von GLX-Radio.</p>

				<section class="glxradio-help-section">
					<h3 class="glxradio-help-heading">
						<svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M8 5v14l11-7z"/></svg>
						Sender hören
					</h3>
					<dl class="glxradio-help-dl">
						<dt>Sender abspielen</dt>
						<dd>Auf den Namen eines Senders klicken &ndash; egal ob in &bdquo;Favoriten&ldquo;, in Suchergebnissen, in &bdquo;Externe Radiosender&ldquo; oder unter &bdquo;Meist gespielt&ldquo;. Die Wiedergabe startet sofort in der Kopfzeile, die immer sichtbar bleibt &ndash; auch beim Scrollen oder beim Wechsel zwischen den Ansichten.</dd>
						<dt>Player-Steuerung</dt>
						<dd>Play/Pause, Vor/Zur&uuml;ck bl&auml;ttert durch deine Favoriten, daneben Ton-aus und Lautst&auml;rkeregler.</dd>
					</dl>
				</section>

				<section class="glxradio-help-section">
					<h3 class="glxradio-help-heading">
						<svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
						Favoriten &amp; eigene Sender
					</h3>
					<dl class="glxradio-help-dl">
						<dt>Sender favorisieren</dt>
						<dd>Bei Suchergebnissen bzw. &bdquo;Externe Radiosender&ldquo; auf das &#9734; neben dem Sendernamen klicken &ndash; er erscheint danach dauerhaft unter &bdquo;Favoriten&ldquo; (Stern wird ausgef&uuml;llt &#9733;). Nochmal klicken entfernt ihn wieder.</dd>
						<dt>Eigenen Sender hinzuf&uuml;gen</dt>
						<dd>&bdquo;Eigener Sender&ldquo; im Seitenmen&uuml; anklicken, Name und Stream-Link eintragen (z.&nbsp;B. eine .mp3-, .m3u- oder .m3u8-Adresse), optional Logo und Ordner w&auml;hlen, dann &bdquo;Als Favorit speichern&ldquo;.</dd>
						<dt>Das Zahnrad rechts &ndash; Einstellungen eines Senders</dt>
						<dd>Jeder Favorit hat rechts ein Zahnrad-Symbol. Dahinter liegt alles, was zu diesem einen Sender geh&ouml;rt: <strong>Name</strong>, <strong>Stream-Link</strong>, <strong>Logo</strong> (als Link oder per Ordner-Button aus deinen Nextcloud-Dateien), <strong>Ordner</strong> zum Einsortieren, der <strong>Platz in der Liste</strong> und ganz links der Knopf <strong>Sender l&ouml;schen</strong>. Vor dem endg&uuml;ltigen L&ouml;schen fragt die App per Ja/Nein-Popup nach. Die Zeile selbst bleibt dadurch frei von Symbolen &ndash; der Sendername hat den ganzen Platz.</dd>
						<dt>Reihenfolge selbst festlegen</dt>
						<dd>Vor jedem Sender steht seine laufende Nummer. Um einen Sender zu verschieben, &ouml;ffnest du sein Zahnrad und tr&auml;gst unter <strong>Platz in der Liste</strong> die gew&uuml;nschte Nummer ein. Nach dem Speichern sitzt er dort, alle dazwischen rutschen eine Position weiter. Eine zu gro&szlig;e Zahl setzt ihn ans Ende. In einer Ordner-Ansicht z&auml;hlt der Platz innerhalb des Ordners, in &bdquo;Favoriten&ldquo; der Platz in der Gesamtliste.</dd>
						<dt>Warum die Reihenfolge z&auml;hlt</dt>
						<dd>Die Vor/Zur&uuml;ck-Tasten im Player bl&auml;ttern genau in dieser Reihenfolge durch deine Favoriten. Du legst damit also auch fest, in welcher Folge du dich im Auto oder unterwegs durchschaltest.</dd>
						<dt>Sender-Logo setzen</dt>
						<dd>Beim Bearbeiten/Hinzuf&uuml;gen entweder einen Bild-Link eintragen oder &uuml;ber den Ordner-Button ein Bild direkt aus deinen Nextcloud-Dateien w&auml;hlen. Welcher Ordner das ist, stellst du in den Einstellungen ein (Standard: /RadioLogos). Logos lassen sich dort auch komplett ausschalten.</dd>
					</dl>
				</section>

				<section class="glxradio-help-section">
					<h3 class="glxradio-help-heading">
						<svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z"/></svg>
						Ordnung &amp; Sicherung
					</h3>
					<dl class="glxradio-help-dl">
						<dt>Sender-Ordner</dt>
						<dd>In den Einstellungen unter &bdquo;Sender-Ordner&ldquo; eigene Ordner anlegen (z.&nbsp;B. &bdquo;Nachrichten&ldquo;, &bdquo;Musik zum Arbeiten&ldquo;). Sie erscheinen als eigene Punkte im Seitenmen&uuml; zwischen &bdquo;Radio&ldquo; und der Suche. Die Zuordnung erfolgt beim Bearbeiten eines Senders per Dropdown, &bdquo;Kein Ordner&ldquo; hebt sie wieder auf.</dd>
						<dt>Favoriten bleiben vollst&auml;ndig</dt>
						<dd>&bdquo;Favoriten&ldquo; zeigt immer alle Sender, unabh&auml;ngig vom Ordner. Ordner-Ansichten zeigen nur ihre zugewiesenen Sender. L&ouml;schst du einen Ordner, bleiben die Sender erhalten und verlieren nur die Zuordnung.</dd>
						<dt>Export &amp; Import</dt>
						<dd>&bdquo;Exportieren&ldquo; sichert alle Favoriten inklusive Ordner-Zuordnung und Ordner-Liste als Datei. &bdquo;Importieren&ldquo; spielt eine solche Datei wieder ein und legt fehlende Ordner automatisch neu an.</dd>
					</dl>
				</section>

				<section class="glxradio-help-section">
					<h3 class="glxradio-help-heading">
						<svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>
						Entdecken &amp; Infos
					</h3>
					<dl class="glxradio-help-dl">
						<dt>Externe Radiosender</dt>
						<dd>Zeigt die aktuell beliebtesten deutschen Sender live von radio-browser.info &ndash; keine feste Liste. Mit eigener Suche nach Sendernamen und &bdquo;Mehr laden&ldquo; f&uuml;r weitere Ergebnisse.</dd>
						<dt>Sender-Infos</dt>
						<dd>Rechts oben zeigt eine eigene Box Genre, Bitrate, Codec, Land, Sprache und Verbindungsqualit&auml;t des gerade laufenden Senders &ndash; sofern radio-browser.info diese Infos kennt.</dd>
						<dt>Meist gespielt</dt>
						<dd>Darunter stehen deine am h&auml;ufigsten geh&ouml;rten Sender mit Z&auml;hler.</dd>
					</dl>
				</section>

				<section class="glxradio-help-section">
					<h3 class="glxradio-help-heading">
						<svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M17 1H7c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 18H7V5h10z"/></svg>
						iPhone, Apple Watch &amp; CarPlay
					</h3>
					<dl class="glxradio-help-dl">
						<dt>Was funktioniert</dt>
						<dd>GLX-Radio meldet die Wiedergabe an iOS an. Sperrbildschirm, Apple Watch und CarPlay zeigen das Sender-Logo als Cover, den Sendernamen und &ndash; sofern der Stream die Info mitliefert &ndash; den laufenden Titel. Play/Pause und Lautst&auml;rke lassen sich dort ebenfalls bedienen.</dd>
						<dt>Vor/Zur&uuml;ck bl&auml;ttert dort nicht</dt>
						<dd>Auf Apple Watch und in CarPlay zeigt iOS bei Live-Streams zwingend die 10-Sekunden-Spultasten statt der Titelsprung-Tasten. Das l&auml;sst sich von einer Web-App aus nicht &auml;ndern. Im Browser am iPhone selbst funktioniert das Sender-Bl&auml;ttern normal.</dd>
						<dt>CarPlay-Vollbild bleibt leer</dt>
						<dd>Die Kachel &bdquo;Aktuelle Wiedergabe&ldquo; zeigt Cover und Titel korrekt, die gro&szlig;e Player-Ansicht dahinter bleibt der leere Standard-Player. Eine eigene CarPlay-Oberfl&auml;che ist ausschlie&szlig;lich nativen iOS-Apps vorbehalten.</dd>
					</dl>
				</section>

				<section class="glxradio-help-section">
					<h3 class="glxradio-help-heading">
						<svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2zm2.07-7.75-.9.92c-.72.73-1.17 1.33-1.17 2.83h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26A1.96 1.96 0 0 0 14 9a2 2 0 1 0-4 0H8a4 4 0 1 1 8 0c0 .88-.36 1.68-.93 2.25z"/></svg>
						Wenn etwas nicht geht
					</h3>
					<dl class="glxradio-help-dl">
						<dt>Sender l&auml;sst sich nicht abspielen</dt>
						<dd>Der Stream selbst ist meist gerade offline oder &uuml;berlastet &ndash; einfach einen anderen probieren.</dd>
						<dt>Kein Logo sichtbar</dt>
						<dd>Nicht jeder Sender hinterlegt bei radio-browser.info ein Logo. Du kannst beim Bearbeiten jederzeit ein eigenes Bild setzen.</dd>
					</dl>
				</section>
				</div>

				<div v-else class="glxradio-settings">
					<div class="glxradio-list-header">
						<h2>🌐 Externe Radiosender (Deutschland)</h2>
					</div>
					<p class="glxradio-info-sub">Live von radio-browser.info, sortiert nach Beliebtheit.</p>

					<div class="glxradio-sidebar-search" style="padding:0; margin-bottom:16px;">
						<div class="glxradio-search-input-wrap">
							<input
								v-model="discoverQuery"
								type="text"
								placeholder="Sendername in dieser Liste suchen..."
								@keyup.enter="searchDiscover">
							<button v-if="discoverQuery" class="glxradio-search-clear" title="Suche leeren" @click="discoverQuery = ''; searchDiscover()">✕</button>
						</div>
						<button class="glxradio-icon-btn-round" title="Suchen" @click="searchDiscover">
							<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 5L20.49 19zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z"/></svg>
						</button>
					</div>

					<div v-if="discoverLoading && !discoverResults.length" class="glxradio-empty">Lädt...</div>
					<div v-else-if="!discoverLoading && !discoverResults.length" class="glxradio-empty">Keine Sender gefunden.</div>
					<div v-for="station in discoverResults" :key="station.stationuuid" class="glxradio-item">
						<img v-if="settings.showLogos && station.favicon" :src="station.favicon" class="glxradio-favicon" alt="" @error="onImgError">
						<span class="glxradio-name" @click="play(station)">{{ station.name }}</span>
						<span class="glxradio-item-meta" style="margin-right:8px; max-width:220px; display:inline-block; flex-shrink:0;">{{ shortTags(station.tags, 3) }}</span>
						<button
							class="glxradio-icon-btn"
							:title="isFavorite(station) ? 'Favorit entfernen' : 'Als Favorit speichern'"
							@click="toggleFavorite(station)">
							{{ isFavorite(station) ? '★' : '☆' }}
						</button>
					</div>
					<button v-if="!discoverLoading" class="glxradio-btn glxradio-btn-secondary" style="margin-top:12px;" @click="loadDiscoverStations(true)">Mehr laden</button>
					<div v-if="discoverLoading && discoverResults.length" class="glxradio-empty">Lädt weitere...</div>
				</div>
			</div>

			<aside class="glxradio-filler">
				<div v-if="current" class="glxradio-station-info-box">
					<h2><svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>Sender-Infos</h2>
					<dl class="glxradio-station-info-list">
						<template v-if="currentStationInfo.tags">
							<dt>Genre</dt><dd>{{ shortTags(currentStationInfo.tags) }}</dd>
						</template>
						<template v-if="currentStationInfo.bitrate">
							<dt>Bitrate</dt><dd>{{ currentStationInfo.bitrate }} kbps</dd>
						</template>
						<template v-if="currentStationInfo.codec">
							<dt>Codec</dt><dd>{{ currentStationInfo.codec }}</dd>
						</template>
						<template v-if="currentStationInfo.country">
							<dt>Land</dt><dd>{{ currentStationInfo.country }}</dd>
						</template>
						<template v-if="currentStationInfo.language">
							<dt>Sprache</dt><dd>{{ currentStationInfo.language }}</dd>
						</template>
						<dt>Verbindung</dt><dd>{{ connectionQualityText }}</dd>
					</dl>
					<div v-if="currentStationInfoEmpty" class="glxradio-empty">
						Für diesen Sender liegen (noch) keine Zusatzinfos von radio-browser.info vor.
					</div>
				</div>

				<h2><svg class="glxradio-heading-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M16 6l2.29 2.29-4.88 4.88-4-4L2 16.59 3.41 18l6-6 4 4 6.3-6.29L22 12V6z"/></svg>Meist gespielt</h2>
				<div v-if="!mostPlayed.length" class="glxradio-empty">Noch keine Hörstatistik.</div>
				<div v-for="entry in mostPlayed" :key="entry.stationuuid" class="glxradio-item glxradio-item-compact">
					<img v-if="settings.showLogos && entry.favicon" :src="entry.favicon" class="glxradio-favicon" alt="" @error="onImgError">
					<div class="glxradio-item-text">
						<span class="glxradio-name" @click="play(entry)">{{ entry.name }}</span>
						<div class="glxradio-item-meta">
							<span v-if="entry.tags">{{ shortTags(entry.tags) }}</span>
							<span v-if="entry.bitrate">{{ entry.tags ? '· ' : '' }}{{ entry.bitrate }} kbps</span>
							<span v-if="entry.codec">{{ entry.tags || entry.bitrate ? '· ' : '' }}{{ entry.codec }}</span>
							<span v-if="entry.country">{{ entry.tags || entry.bitrate || entry.codec ? '· ' : '' }}{{ entry.country }}</span>
							<span v-if="entry.language">{{ entry.tags || entry.bitrate || entry.codec || entry.country ? '· ' : '' }}{{ entry.language }}</span>
							<span v-if="entry.enrichChecked && !entry.tags && !entry.bitrate && !entry.codec && !entry.country && !entry.language">Keine Zusatzinfos verfügbar</span>
						</div>
					</div>
					<span class="glxradio-play-count">{{ entry.count }}×</span>
				</div>
			</aside>
		</div>
	</NcAppContent>

		<NcModal v-if="logoPickerOpen" size="normal" @close="logoPickerOpen = false">
			<div class="glxradio-logo-picker">
				<h2>Logo aus Nextcloud wählen</h2>
				<p class="glxradio-info-sub">
					Zeigt den Inhalt des Ordners <strong>{{ settings.logoFolder }}</strong> in deinen eigenen Dateien.
					Leg dort Bilddateien ab, die du als Sender-Icons nutzen möchtest.
				</p>
				<div v-if="logoPickerLoading" class="glxradio-empty">Lädt...</div>
				<div v-else-if="logoPickerError" class="glxradio-empty">{{ logoPickerError }}</div>
				<div v-else-if="!logoPickerEntries.length" class="glxradio-empty">
					Der Ordner "{{ settings.logoFolder }}" ist leer, existiert noch nicht, oder enthält keine
					unterstützten Bilder (png, jpg, jpeg, gif, svg, webp). Gefundene Objekte im Ordner: {{ logoPickerRawCount }}.
				</div>
				<div class="glxradio-logo-grid">
					<button
						v-for="entry in logoPickerEntries"
						:key="entry.href"
						class="glxradio-logo-grid-item"
						:title="entry.name"
						@click="selectLogo(entry)">
						<img :src="entry.previewUrl" :alt="entry.name">
						<span>{{ entry.name }}</span>
					</button>
				</div>
			</div>
		</NcModal>

		<NcModal v-if="folderPickerOpen" size="normal" @close="folderPickerOpen = false">
			<div class="glxradio-logo-picker">
				<h2>Ordner auswählen</h2>
				<div class="glxradio-folder-path">
					<button class="glxradio-btn glxradio-btn-secondary" :disabled="folderPickerPath === '/'" @click="folderPickerGoUp">↑ Nach oben</button>
					<code>{{ folderPickerPath }}</code>
				</div>
				<div v-if="folderPickerLoading" class="glxradio-empty">Lädt...</div>
				<div v-else-if="folderPickerError" class="glxradio-empty">{{ folderPickerError }}</div>
				<div v-else-if="!folderPickerEntries.length" class="glxradio-empty">Keine Unterordner vorhanden.</div>
				<div v-else class="glxradio-folder-list">
					<button
						v-for="entry in folderPickerEntries"
						:key="entry.href"
						class="glxradio-folder-list-item"
						@click="openFolderPickerFolder(entry.name)">
						<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
						{{ entry.name }}
					</button>
				</div>
				<div class="glxradio-confirm-actions">
					<button class="glxradio-btn" @click="chooseCurrentFolder">Diesen Ordner verwenden</button>
					<button class="glxradio-btn glxradio-btn-secondary" @click="folderPickerOpen = false">Abbrechen</button>
				</div>
			</div>
		</NcModal>

		<NcModal v-if="deleteConfirmStation" size="small" @close="cancelDelete">
			<div class="glxradio-confirm">
				<h2>Sender löschen?</h2>
				<p>Soll "{{ deleteConfirmStation.name }}" wirklich aus den Favoriten entfernt werden?</p>
				<div class="glxradio-confirm-actions">
					<button class="glxradio-btn" @click="confirmDelete">Ja</button>
					<button class="glxradio-btn glxradio-btn-secondary" @click="cancelDelete">Nein</button>
				</div>
			</div>
		</NcModal>

		<NcModal v-if="deleteConfirmFolder" size="small" @close="deleteConfirmFolder = null">
			<div class="glxradio-confirm">
				<h2>Ordner löschen?</h2>
				<p>Soll der Ordner "{{ deleteConfirmFolder }}" wirklich gelöscht werden? Die zugewiesenen Sender bleiben als Favoriten erhalten, verlieren aber die Ordner-Zuweisung.</p>
				<div class="glxradio-confirm-actions">
					<button class="glxradio-btn" @click="confirmDeleteFolder">Ja</button>
					<button class="glxradio-btn glxradio-btn-secondary" @click="deleteConfirmFolder = null">Nein</button>
				</div>
			</div>
		</NcModal>
	</NcContent>
</template>

<script>
import axios from '@nextcloud/axios'
import { generateUrl, generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { NcContent, NcAppNavigation, NcAppContent, NcModal } from '@nextcloud/vue'

// Ordner in den eigenen Nextcloud-Dateien, in dem Sender-Logos abgelegt
// werden koennen - lokal statt ueber einen externen Bild-Link.
const LOGO_FOLDER = '/RadioLogos'

// Oeffentliche, kostenlose Radiosender-Datenbank - wird direkt aus dem Browser
// angesprochen (unterstuetzt CORS), kein eigener Server-Proxy noetig.
const STATION_API = 'https://de1.api.radio-browser.info/json/stations/search'
// Muss synchron mit <version> in appinfo/info.xml gepflegt werden.
const APP_VERSION = '1.6.0'

// Fuer externe Anfragen an radio-browser.info nutzen wir bewusst das native
// fetch() statt des @nextcloud/axios-Imports - Nextclouds axios-Wrapper haengt
// automatisch einen internen CSRF-"requesttoken"-Header an jede Anfrage, den
// radio-browser.info nicht kennt und der dadurch die CORS-Preflight-Anfrage
// zum Scheitern bringt.
async function fetchRadioBrowser(params) {
	const url = STATION_API + '?' + new URLSearchParams(params).toString()
	const response = await fetch(url, { headers: { Accept: 'application/json' } })
	if (!response.ok) {
		throw new Error('HTTP ' + response.status)
	}
	return response.json()
}

export default {
	name: 'GlxRadioApp',

	components: {
		NcContent,
		NcAppNavigation,
		NcAppContent,
		NcModal,
	},

	data() {
		return {
			query: '',
			loading: false,
			hasSearched: false,
			currentView: 'radio', // 'radio' | 'settings'
			results: [],
			favorites: [],
			current: null,
			playError: false,
			playState: 'idle', // idle | connecting | playing | buffering
			loadStartTime: null,
			startupDurationMs: null,
			stallCount: 0,
			isPlaying: true,
			connectWatchdog: null,
			connectRetried: false,
			suppressAudioError: false,
			volume: 1,
			isMuted: false,
			faviconFailed: false,
			playCounts: {},
			nowPlayingTitle: null,
			nowPlayingPoll: null,
			logoPickerOpen: false,
			logoPickerLoading: false,
			logoPickerError: null,
			logoPickerRawCount: 0,
			logoPickerEntries: [],
			logoPickerTarget: null,
			settings: { logoFolder: LOGO_FOLDER, showLogos: true, folders: [] },
			settingsSaved: false,
			discoverResults: [],
			discoverLoading: false,
			discoverOffset: 0,
			discoverQuery: '',
			deleteConfirmStation: null,
			activeFolder: '',
			newFolderName: '',
			deleteConfirmFolder: null,
			folderPickerOpen: false,
			folderPickerPath: '/',
			folderPickerEntries: [],
			folderPickerLoading: false,
			folderPickerError: null,
			showAddForm: false,
			newStation: { name: '', url_resolved: '', favicon: '', folder: '' },
			editingId: null,
			editStation: { name: '', url_resolved: '', favicon: '', folder: '' },
		}
	},

	watch: {
		query(newVal) {
			if (!newVal.trim() && this.hasSearched) {
				this.clearSearch()
			}
		},
		current() {
			this.updateMediaSession()
			this.updateDocumentTitle()
		},
		nowPlayingTitle() {
			this.updateMediaSession()
			this.updateDocumentTitle()
		},
		isPlaying() {
			this.updateMediaSession()
		},
	},

	computed: {
		playUrl() {
			if (!this.current) return ''
			// Alles ueber den eigenen Server-Durchleiter abspielen - das umgeht
			// Mixed-Content-Blockaden (egal ob der Sender http oder https ist)
			// und loest .asx/.pls-Wiedergabelisten automatisch mit auf.
			return generateUrl('/apps/glxradio/api/proxy') + '?url=' + encodeURIComponent(this.current.url_resolved)
		},

		statusText() {
			if (!this.current) return ''
			if (this.playState === 'connecting') return '⏳ Verbindet...'
			if (this.playState === 'buffering') return `⏳ Puffert erneut... (${this.stallCount}. Aussetzer)`
			if (this.playState === 'playing') {
				if (this.startupDurationMs !== null) {
					return `✓ Läuft (Start nach ${(this.startupDurationMs / 1000).toFixed(1)} s)`
				}
				return '✓ Läuft'
			}
			return ''
		},

		showTileImage() {
			return this.settings.showLogos && !!(this.current && this.current.favicon) && !this.faviconFailed
		},

		monogramLetter() {
			if (!this.current || !this.current.name) return '?'
			return this.current.name.trim().charAt(0).toUpperCase()
		},

		monogramColor() {
			if (!this.current || !this.current.name) return 'hsl(210, 60%, 45%)'
			// Deterministischer, aber optisch abwechslungsreicher Farbton je
			// Sendername - so wie GNOME Music/Lollypop es bei fehlendem Cover
			// machen. Saettigung/Helligkeit bleiben fix, damit die Schrift
			// (weiss) immer lesbar bleibt, egal welcher Hue rauskommt.
			let hash = 0
			for (let i = 0; i < this.current.name.length; i++) {
				hash = this.current.name.charCodeAt(i) + ((hash << 5) - hash)
			}
			const hue = Math.abs(hash) % 360
			return `hsl(${hue}, 60%, 45%)`
		},

		mostPlayed() {
			return Object.values(this.playCounts)
				.sort((a, b) => b.count - a.count)
				.slice(0, 5)
		},

		appVersion() {
			return APP_VERSION
		},

		// Fuehrt die Daten des aktuell laufenden Senders selbst (current,
		// z.B. wenn direkt aus der Suche/Externe-Radiosender-Liste gestartet)
		// mit den nachgeladenen Metadaten aus der Hoerstatistik zusammen
		// (playCounts, z.B. bei eigenen "custom-"-Sendern) - je nachdem, wo
		// die Info tatsaechlich vorliegt.
		currentStationInfo() {
			if (!this.current) return {}
			const fromCounts = this.playCounts[this.current.stationuuid] || {}
			return {
				tags: this.current.tags || fromCounts.tags || '',
				bitrate: this.current.bitrate || fromCounts.bitrate || null,
				codec: this.current.codec || fromCounts.codec || '',
				country: this.current.country || fromCounts.country || '',
				language: this.current.language || fromCounts.language || '',
			}
		},

		currentStationInfoEmpty() {
			const info = this.currentStationInfo
			return !info.tags && !info.bitrate && !info.codec && !info.country && !info.language
		},

		connectionQualityText() {
			if (this.playState === 'connecting') return 'Verbindet...'
			if (this.playState === 'buffering') return `Puffert (${this.stallCount}. Aussetzer)`
			if (this.playState === 'playing') {
				return this.stallCount > 0 ? `Läuft (${this.stallCount}× gepuffert)` : 'Stabil, keine Aussetzer'
			}
			return 'Unbekannt'
		},

		// Sender, der gerade im Formular bearbeitet wird - fuer den Loeschen-Button.
		editingStation() {
			return this.favorites.find((f) => f.stationuuid === this.editingId) || null
		},

		// Wie viele Plaetze zur Auswahl stehen: in einer Ordner-Ansicht nur die
		// Sender des Ordners, sonst alle Favoriten.
		positionCount() {
			return this.currentView === 'folder' ? this.folderStations.length : this.favorites.length
		},

		folderStations() {
			return this.favorites.filter((f) => f.folder === this.activeFolder)
		},
	},

	mounted() {
		this.loadFavorites()
		this.loadSettings()
		try {
			const stored = window.localStorage.getItem('glxradio-play-counts')
			if (stored) {
				this.playCounts = JSON.parse(stored)
			}
		} catch (e) {
			// localStorage nicht verfuegbar oder Daten kaputt - einfach bei leerer Statistik starten
		}
		this.setupMediaSessionHandlers()
		this.updateDocumentTitle()
	},

	beforeUnmount() {
		if (this.nowPlayingPoll) {
			clearInterval(this.nowPlayingPoll)
		}
		this.clearConnectWatchdog()
		if ('mediaSession' in navigator) {
			navigator.mediaSession.metadata = null
			navigator.mediaSession.playbackState = 'none'
		}
		document.title = 'GLX-Radio'
	},

	methods: {
		onPlayError(event) {
			// Fehler waehrend eines gewollten Abbruchs (Umschalten) ignorieren
			if (this.suppressAudioError) return
			// Nur Fehler beachten, die wirklich vom aktuell laufenden Element
			// kommen - nicht vom gerade weggeraeumten alten (das loest beim
			// Kappen selbst ein Fehler-Signal aus, das nicht echt ist)
			if (event && event.target && event.target !== this.$refs.audioEl) return
			this.handleConnectFailure()
		},

		openDiscover() {
			this.currentView = 'discover'
			if (!this.discoverResults.length) {
				this.discoverOffset = 0
				this.loadDiscoverStations(false)
			}
		},

		searchDiscover() {
			this.discoverOffset = 0
			this.loadDiscoverStations(false)
		},

		async loadDiscoverStations(append) {
			this.discoverLoading = true
			try {
				const params = {
					country: 'Germany',
					limit: 30,
					offset: append ? this.discoverOffset : 0,
					hidebroken: true,
					order: 'clickcount',
					reverse: true,
				}
				if (this.discoverQuery.trim()) {
					params.name = this.discoverQuery.trim()
				}
				const data = await fetchRadioBrowser(params)
				if (append) {
					const existingUuids = new Set(this.discoverResults.map((s) => s.stationuuid))
					this.discoverResults = [...this.discoverResults, ...data.filter((s) => !existingUuids.has(s.stationuuid))]
				} else {
					this.discoverResults = data
				}
				this.discoverOffset += 30
			} catch (e) {
				// Bei Fehler einfach die bisherige Liste stehen lassen
			} finally {
				this.discoverLoading = false
			}
		},

		async search() {
			if (!this.query.trim()) return
			this.loading = true
			this.hasSearched = true
			const q = this.query.trim().toLowerCase()
			const localMatches = this.favorites.filter((f) =>
				f.name.toLowerCase().includes(q) || (f.tags || '').toLowerCase().includes(q))
			try {
				const data = await fetchRadioBrowser({ name: this.query, limit: 30, hidebroken: true, order: 'clickcount', reverse: true })
				const localUuids = new Set(localMatches.map((f) => f.stationuuid))
				this.results = [...localMatches, ...data.filter((r) => !localUuids.has(r.stationuuid))]
			} catch (e) {
				// radio-browser.info nicht erreichbar - wenigstens die eigenen Treffer zeigen
				this.results = localMatches
			} finally {
				this.loading = false
			}
		},

		clearSearch() {
			this.hasSearched = false
			this.results = []
			this.query = ''
		},

		goToStationList() {
			this.currentView = 'radio'
			this.clearSearch()
		},

		goToFolder(folder) {
			this.currentView = 'folder'
			this.activeFolder = folder
		},

		async createFolder() {
			const name = this.newFolderName.trim()
			if (!name) return
			try {
				const response = await axios.post(generateUrl('/apps/glxradio/api/folders'), { name })
				this.settings.folders = response.data.folders || this.settings.folders
				this.newFolderName = ''
			} catch (e) {
				// bei Fehler bleibt die Eingabe stehen, damit man's nochmal versuchen kann
			}
		},

		requestDeleteFolder(folder) {
			this.deleteConfirmFolder = folder
		},

		async confirmDeleteFolder() {
			const folder = this.deleteConfirmFolder
			this.deleteConfirmFolder = null
			if (!folder) return
			try {
				const response = await axios.delete(generateUrl('/apps/glxradio/api/folders'), { params: { name: folder } })
				this.settings.folders = response.data.folders || this.settings.folders.filter((f) => f !== folder)
			} catch (e) {
				return
			}
			// Sender, die diesem Ordner zugewiesen waren, verlieren die
			// Zuweisung (bleiben aber als Favoriten erhalten).
			let changed = false
			this.favorites = this.favorites.map((f) => {
				if (f.folder === folder) {
					changed = true
					return { ...f, folder: '' }
				}
				return f
			})
			if (this.activeFolder === folder) {
				this.goToStationList()
			}
			if (changed) {
				this.saveFavorites()
			}
		},

		clearSearchInput() {
			this.query = ''
			if (this.hasSearched) {
				this.clearSearch()
			}
		},

		play(station) {
			// Alte Verbindung hart kappen, BEVOR wir umschalten. Sonst bleibt
			// sie in Safari als halb offene Verbindung haengen; nach ein paar
			// Senderwechseln ist der Verbindungsvorrat zum Server erschoepft
			// und kein Sender baut mehr auf ("ewiges Verbindet"). Erst das
			// komplette Neuladen der Seite hat das bisher geloest.
			this.teardownAudio()
			this.clearConnectWatchdog()
			this.playError = false
			this.current = station
			this.playState = 'connecting'
			this.loadStartTime = performance.now()
			this.startupDurationMs = null
			this.stallCount = 0
			this.isPlaying = true
			this.faviconFailed = false
			this.isMuted = false
			this.connectRetried = false
			this.recordPlayCount(station)
			this.startNowPlayingPoll(station)
			// Wenn der Sender nicht binnen 15s wirklich spielt, greift der
			// Waechter ein (ein Neuversuch, dann sauber aufgeben) - statt
			// endlos auf "Verbindet..." stehenzubleiben.
			this.startConnectWatchdog()

			// Safari erlaubt das Abspielen nur im direkten Zusammenhang mit dem
			// Klick. Das autoplay-Attribut allein reicht nicht, weil das
			// audio-Element durch den Schluesselwechsel erst im naechsten
			// Render-Durchlauf neu entsteht - also nach dem Klick. Deshalb
			// stossen wir die Wiedergabe zusaetzlich selbst an.
			this.$nextTick(() => {
				this.startPlayback()
			})
		},

		// Manche Sender melden bei radio-browser.info dutzende Schlagworte.
		// Ungekuerzt sprengt das auf dem Handy die halbe Anzeige, deshalb
		// zeigen wir nur die ersten paar.
		shortTags(value, max = 5) {
			if (!value) return ''
			const parts = String(value).split(',').map((t) => t.trim()).filter(Boolean)
			if (parts.length <= max) return parts.join(', ')
			return parts.slice(0, max).join(', ') + ' \u2026'
		},

		startPlayback() {
			const el = this.$refs.audioEl
			if (!el) return
			const attempt = el.play()
			if (attempt && typeof attempt.catch === 'function') {
				attempt.catch(() => {
					// Zweiter Anlauf, sobald das Element seine Quelle kennt.
					window.setTimeout(() => {
						const again = this.$refs.audioEl
						if (!again) return
						const retry = again.play()
						if (retry && typeof retry.catch === 'function') {
							retry.catch(() => {})
						}
					}, 150)
				})
			}
		},

		// Stoppt das laufende Audio-Element hart und bricht seine Netzwerk-
		// verbindung sofort ab (src leeren + load()), damit Safari die
		// Verbindung zum Server freigibt.
		teardownAudio() {
			const el = this.$refs.audioEl
			if (!el) return
			this.suppressAudioError = true
			try {
				el.pause()
				el.removeAttribute('src')
				el.load()
			} catch (e) {
				// nicht kritisch
			}
			// Echte Fehler nach einem kurzen Fenster wieder zulassen (lang genug,
			// dass das beim Kappen selbst ausgeloeste Fehler-Signal noch reinfaellt)
			setTimeout(() => { this.suppressAudioError = false }, 120)
		},

		startConnectWatchdog() {
			this.clearConnectWatchdog()
			this.connectWatchdog = setTimeout(() => {
				this.handleConnectFailure()
			}, 15000)
		},

		clearConnectWatchdog() {
			if (this.connectWatchdog) {
				clearTimeout(this.connectWatchdog)
				this.connectWatchdog = null
			}
		},

		// Gemeinsame Fehlerbehandlung fuer "haengt beim Verbinden" (Waechter)
		// und echte Audio-Fehler: erst ein automatischer Neuversuch, beim
		// zweiten Fehlschlag sauber aufgeben und die Meldung zeigen.
		handleConnectFailure() {
			// Laeuft schon oder haben wir bereits sauber aufgegeben? Dann nichts
			// tun - verhindert u.a., dass sich der Aufgeben-Pfad ueber das beim
			// Kappen ausgeloeste Fehler-Signal selbst erneut aufruft.
			if (this.playState === 'playing' || this.playState === 'idle') return
			this.clearConnectWatchdog()
			if (!this.connectRetried) {
				this.connectRetried = true
				this.playError = false
				this.playState = 'connecting'
				this.loadStartTime = performance.now()
				const el = this.$refs.audioEl
				if (el) {
					try {
						el.load()
						const p = el.play()
						if (p && p.catch) p.catch(() => {})
					} catch (e) {
						// nicht kritisch
					}
				}
				this.startConnectWatchdog()
			} else {
				this.teardownAudio()
				this.playError = true
				this.playState = 'idle'
				this.isPlaying = false
			}
		},

		startNowPlayingPoll(station) {
			if (this.nowPlayingPoll) {
				clearInterval(this.nowPlayingPoll)
			}
			this.nowPlayingTitle = null
			// Metadaten brauchen nach Verbindungsaufbau kurz, bis sie im
			// Stream ankommen - erster Abruf daher leicht verzoegert.
			setTimeout(() => this.fetchNowPlaying(station), 4000)
			this.nowPlayingPoll = setInterval(() => this.fetchNowPlaying(station), 15000)
		},

		async fetchNowPlaying(station) {
			if (!this.current || this.current.stationuuid !== station.stationuuid) {
				return
			}
			try {
				const response = await axios.get(generateUrl('/apps/glxradio/api/nowplaying'), {
					params: { url: station.url_resolved },
				})
				this.nowPlayingTitle = response.data.title || null
			} catch (e) {
				// Metadaten optional - bei Fehler einfach nichts anzeigen
			}
		},

		// Tab-Titel: zeigt den laufenden Song, sonst den Sendernamen,
		// im Leerzustand nur den App-Namen.
		updateDocumentTitle() {
			if (this.current) {
				document.title = (this.nowPlayingTitle || this.current.name) + ' · GLX-Radio'
			} else {
				document.title = 'GLX-Radio'
			}
		},

		// Fuettert die Media Session API - das ist die Schnittstelle, ueber die
		// CarPlay, die Apple Watch und der Sperrbildschirm anzeigen, was laeuft
		// (Titel, Sender, Sender-Logo) und ihre Play/Pause/Weiter-Tasten steuern.
		updateMediaSession() {
			if (!('mediaSession' in navigator)) {
				return
			}
			if (!this.current) {
				navigator.mediaSession.metadata = null
				navigator.mediaSession.playbackState = 'none'
				return
			}
			const song = this.nowPlayingTitle
			const stationName = this.current.name || 'GLX-Radio'
			// Song verfuegbar -> Song gross, Sender als Interpret darunter.
			// Kein Song -> Sendername gross, keine zweite Zeile.
			const title = song || stationName
			const artist = song ? stationName : ''
			const artwork = []
			if (this.settings.showLogos && this.current.favicon && !this.faviconFailed) {
				let art = this.current.favicon
				if (art.startsWith('/')) {
					art = window.location.origin + art
				}
				let type = ''
				const lower = art.toLowerCase()
				if (lower.endsWith('.png')) type = 'image/png'
				else if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) type = 'image/jpeg'
				else if (lower.endsWith('.svg')) type = 'image/svg+xml'
				else if (lower.endsWith('.webp')) type = 'image/webp'
				else if (lower.endsWith('.gif')) type = 'image/gif'
				// Dieselbe URL in mehreren Groessen anmelden, damit iOS/CarPlay
				// sie sicher als Cover auswaehlt (sonst faellt es aufs App-Icon zurueck).
				for (const s of ['96x96', '128x128', '192x192', '256x256', '384x384', '512x512']) {
					artwork.push({ src: art, sizes: s, type })
				}
			}
			try {
				navigator.mediaSession.metadata = new window.MediaMetadata({
					title,
					artist,
					album: 'GLX-Radio',
					artwork,
				})
				navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
				// Live-Radio hat keine Zeitleiste. iOS/CarPlay/Watch zeigen die
				// 10-Sekunden-Spul-Tasten vor allem dann, wenn sie eine (endliche)
				// Dauer/Position annehmen. Wir melden explizit eine unendliche Dauer
				// (= Live), damit die Spul-Steuerung wegfällt und stattdessen die
				// Track-Tasten (Sender vor/zurück) erscheinen.
				try {
					if (typeof navigator.mediaSession.setPositionState === 'function') {
						navigator.mediaSession.setPositionState({
							duration: Infinity,
							playbackRate: 1,
							position: 0,
						})
					}
				} catch (e) {
					// manche Browser lehnen Infinity ab - unkritisch
				}
			} catch (e) {
				// MediaMetadata nicht verfuegbar - unkritisch, App laeuft normal weiter
			}
		},

		setupMediaSessionHandlers() {
			if (!('mediaSession' in navigator)) {
				return
			}
			const set = (action, handler) => {
				try {
					navigator.mediaSession.setActionHandler(action, handler)
				} catch (e) {
					// Nicht jeder Browser kennt jede Action - einzeln wegfangen
				}
			}
			set('play', () => {
				const el = this.$refs.audioEl
				if (el) el.play()
			})
			set('pause', () => {
				const el = this.$refs.audioEl
				if (el) el.pause()
			})
			set('previoustrack', () => this.playPrevious())
			set('nexttrack', () => this.playNext())
			// Bei Live-Radio ergibt Spulen keinen Sinn. Die 10-Sekunden-Sprünge
			// abschalten (auf null), damit CarPlay/Apple Watch stattdessen die
			// Vor/Zurück-Tasten zeigen = nächster/vorheriger Sender aus der Liste.
			set('seekbackward', null)
			set('seekforward', null)
			set('seekto', null)
		},

		openLogoPicker(target) {
			this.logoPickerTarget = target
			this.logoPickerOpen = true
			this.loadLogoFolder()
		},

		openFolderPicker() {
			this.folderPickerOpen = true
			this.loadFolderPicker(this.settings.logoFolder || '/')
		},

		async loadFolderPicker(path) {
			this.folderPickerLoading = true
			this.folderPickerError = null
			try {
				const entries = await this.webdavPropfind(path)
				this.folderPickerEntries = entries
					.filter((e) => e.isCollection && e.name)
					.sort((a, b) => a.name.localeCompare(b.name))
				this.folderPickerPath = path
			} catch (e) {
				this.folderPickerError = `Ordner "${path}" konnte nicht gelesen werden.`
			} finally {
				this.folderPickerLoading = false
			}
		},

		openFolderPickerFolder(name) {
			const base = this.folderPickerPath === '/' ? '' : this.folderPickerPath
			this.loadFolderPicker(base + '/' + name)
		},

		folderPickerGoUp() {
			if (this.folderPickerPath === '/') return
			const parts = this.folderPickerPath.split('/').filter(Boolean)
			parts.pop()
			this.loadFolderPicker(parts.length ? '/' + parts.join('/') : '/')
		},

		chooseCurrentFolder() {
			this.settings.logoFolder = this.folderPickerPath
			this.folderPickerOpen = false
			this.saveSettings()
		},

		async webdavPropfind(path) {
			const user = getCurrentUser()
			if (!user || !user.uid) {
				throw new Error('Benutzer konnte nicht ermittelt werden.')
			}
			const davUrl = generateRemoteUrl('dav') + '/files/' + encodeURIComponent(user.uid) + path
			// Pfad des angefragten Ordners selbst (ohne Host/Query), damit wir
			// dessen Eintrag in der Antwort zuverlaessig per Pfadvergleich
			// ausschliessen koennen - nicht per Position, denn die Reihenfolge
			// der PROPFIND-Antwort ist nicht garantiert (bei nur einer Datei
			// im Ordner wuerde sonst faelschlich die Datei statt des Ordners
			// selbst entfernt).
			const selfPath = new URL(davUrl, window.location.origin).pathname.replace(/\/$/, '')
			const response = await axios.request({
				method: 'PROPFIND',
				url: davUrl,
				headers: { Depth: '1' },
				data: '<?xml version="1.0"?><d:propfind xmlns:d="DAV:"><d:prop><d:resourcetype/><d:getcontenttype/></d:prop></d:propfind>',
			})
			const xml = new DOMParser().parseFromString(response.data, 'text/xml')
			const nodes = Array.from(xml.getElementsByTagNameNS('DAV:', 'response'))
			const entries = []
			for (const node of nodes) {
				const hrefEl = node.getElementsByTagNameNS('DAV:', 'href')[0]
				if (!hrefEl) continue
				const href = decodeURIComponent(hrefEl.textContent || '')
				if (href.replace(/\/$/, '') === selfPath) continue
				const isCollection = node.getElementsByTagNameNS('DAV:', 'collection').length > 0
				const contentTypeEl = node.getElementsByTagNameNS('DAV:', 'getcontenttype')[0]
				const contentType = contentTypeEl ? contentTypeEl.textContent || '' : ''
				const name = decodeURIComponent(href.replace(/\/$/, '').split('/').pop() || '')
				entries.push({ href, name, isCollection, contentType })
			}
			return entries
		},

		async loadLogoFolder() {
			this.logoPickerLoading = true
			this.logoPickerError = null
			this.logoPickerEntries = []
			const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp']
			try {
				const entries = await this.webdavPropfind(this.settings.logoFolder)
				this.logoPickerRawCount = entries.length
				this.logoPickerEntries = entries
					.filter((e) => {
						if (e.isCollection || !e.name) return false
						const ext = e.name.split('.').pop().toLowerCase()
						return imageExtensions.includes(ext)
					})
					.map((e) => ({ href: e.href, name: e.name, previewUrl: window.location.origin + e.href }))
			} catch (e) {
				this.logoPickerError = `Ordner "${this.settings.logoFolder}" konnte nicht gelesen werden. Leg ihn in deinen Nextcloud-Dateien an, falls er noch nicht existiert.`
			} finally {
				this.logoPickerLoading = false
			}
		},

		selectLogo(entry) {
			if (this.logoPickerTarget === 'new') {
				this.newStation.favicon = entry.previewUrl
			} else if (this.logoPickerTarget === 'edit') {
				this.editStation.favicon = entry.previewUrl
			}
			this.logoPickerOpen = false
		},

		recordPlayCount(station) {
			const existing = this.playCounts[station.stationuuid]
			this.playCounts = {
				...this.playCounts,
				[station.stationuuid]: {
					stationuuid: station.stationuuid,
					name: station.name,
					favicon: station.favicon,
					// Bereits nachgeladene Metadaten (z.B. per enrichPlayCount)
					// nicht mit leeren Werten aus dem Favoriten-Objekt
					// ueberschreiben - sonst geht die Anreicherung bei jedem
					// erneuten Abspielen wieder verloren.
					tags: station.tags || existing?.tags || '',
					bitrate: station.bitrate || existing?.bitrate || null,
					codec: station.codec || existing?.codec || '',
					country: station.country || existing?.country || '',
					language: station.language || existing?.language || '',
					enrichChecked: existing?.enrichChecked || false,
					count: existing ? existing.count + 1 : 1,
				},
			}
			this.persistPlayCounts()

			// Aeltere Favoriten wurden ohne Bitrate/Codec/Land gespeichert -
			// die fehlenden Infos einmalig von radio-browser.info nachladen,
			// damit "Meist gespielt" auch fuer die schon vorhandenen Favoriten
			// mehr anzeigt, nicht nur fuer neu hinzugefuegte. Bei manuell
			// hinzugefuegten Sendern ("custom-..."-ID) gibt es keine echte
			// radio-browser.info-UUID zum Nachschlagen - dort stattdessen nach
			// dem Sendernamen suchen und die Daten des besten Treffers nehmen.
			if (String(station.stationuuid).startsWith('custom-')) {
				this.enrichPlayCountByName(station.stationuuid, station.name)
			} else {
				this.enrichPlayCount(station.stationuuid)
			}
		},

		async enrichPlayCountByName(stationuuid, name) {
			if (!name) return
			try {
				const data = await fetchRadioBrowser({ name, limit: 1, hidebroken: true, order: 'clickcount', reverse: true })
				const info = data && data[0]
				if (!this.playCounts[stationuuid]) return
				this.playCounts = {
					...this.playCounts,
					[stationuuid]: {
						...this.playCounts[stationuuid],
						tags: this.playCounts[stationuuid].tags || info?.tags || '',
						bitrate: this.playCounts[stationuuid].bitrate || info?.bitrate || null,
						codec: this.playCounts[stationuuid].codec || info?.codec || '',
						country: this.playCounts[stationuuid].country || info?.country || '',
						language: this.playCounts[stationuuid].language || info?.language || '',
						enrichChecked: true,
					},
				}
				this.persistPlayCounts()
			} catch (e) {
				console.error('[GLX Radio] Fehler beim Nachladen per Name:', e)
			}
		},

		async enrichPlayCount(stationuuid) {
			try {
				const fetchResponse = await fetch(`https://de1.api.radio-browser.info/json/stations/byuuid/${stationuuid}`, { headers: { Accept: 'application/json' } })
				const data = fetchResponse.ok ? await fetchResponse.json() : []
				const info = data && data[0]
				if (!this.playCounts[stationuuid]) return
				this.playCounts = {
					...this.playCounts,
					[stationuuid]: {
						...this.playCounts[stationuuid],
						tags: this.playCounts[stationuuid].tags || info?.tags || '',
						bitrate: this.playCounts[stationuuid].bitrate || info?.bitrate || null,
						codec: this.playCounts[stationuuid].codec || info?.codec || '',
						country: this.playCounts[stationuuid].country || info?.country || '',
						language: this.playCounts[stationuuid].language || info?.language || '',
						enrichChecked: true,
					},
				}
				this.persistPlayCounts()
			} catch (e) {
				// Nachladen ist nur eine Zusatzinfo - bei Fehlschlag einfach ignorieren
			}
		},

		persistPlayCounts() {
			try {
				window.localStorage.setItem('glxradio-play-counts', JSON.stringify(this.playCounts))
			} catch (e) {
				// localStorage nicht verfuegbar - Statistik bleibt dann nur fuer diese Sitzung erhalten
			}
		},

		togglePlay() {
			const el = this.$refs.audioEl
			if (!el) return
			if (el.paused) {
				el.play()
			} else {
				el.pause()
			}
		},

		toggleMute() {
			const el = this.$refs.audioEl
			if (!el) return
			el.muted = !el.muted
			this.isMuted = el.muted
		},

		playNext() {
			if (!this.favorites.length) return
			const idx = this.current ? this.favorites.findIndex((f) => f.stationuuid === this.current.stationuuid) : -1
			const next = this.favorites[(idx + 1 + this.favorites.length) % this.favorites.length]
			this.play(next)
		},

		playPrevious() {
			if (!this.favorites.length) return
			const idx = this.current ? this.favorites.findIndex((f) => f.stationuuid === this.current.stationuuid) : -1
			const prev = this.favorites[(idx - 1 + this.favorites.length) % this.favorites.length]
			this.play(prev)
		},

		onVolumeInput() {
			const el = this.$refs.audioEl
			if (el) {
				el.volume = this.volume
			}
		},

		onLoadStart() {
			if (this.playState !== 'playing') {
				this.playState = 'connecting'
			}
			const el = this.$refs.audioEl
			if (el) {
				el.volume = this.volume
			}
		},

		onWaiting() {
			if (this.playState === 'playing') {
				this.stallCount++
				this.playState = 'buffering'
			} else if (this.playState === 'idle') {
				this.playState = 'connecting'
			}
		},

		onStalled() {
			if (this.playState === 'playing') {
				this.stallCount++
				this.playState = 'buffering'
			}
		},

		onPlaying() {
			if (this.startupDurationMs === null && this.loadStartTime !== null) {
				this.startupDurationMs = performance.now() - this.loadStartTime
			}
			this.playState = 'playing'
			// Sender spielt wirklich -> evtl. sichtbare Fehlermeldung wegnehmen,
			// Waechter abschalten, Neuversuch-Merker zuruecksetzen
			this.playError = false
			this.clearConnectWatchdog()
			this.connectRetried = false
		},

		onImgError(event) {
			// Kaputte/fehlende Logo-Links einfach ausblenden statt Platzhalter-Symbol zu zeigen
			event.target.style.display = 'none'
		},

		isFavorite(station) {
			return this.favorites.some((f) => f.stationuuid === station.stationuuid)
		},

		requestDeleteFavorite(station) {
			this.deleteConfirmStation = station
		},

		confirmDelete() {
			if (this.deleteConfirmStation) {
				this.toggleFavorite(this.deleteConfirmStation)
				this.removeFromPlayCounts(this.deleteConfirmStation.stationuuid)
			}
			this.deleteConfirmStation = null
		},

		removeFromPlayCounts(stationuuid) {
			if (!this.playCounts[stationuuid]) return
			const updated = { ...this.playCounts }
			delete updated[stationuuid]
			this.playCounts = updated
			this.persistPlayCounts()
		},

		cancelDelete() {
			this.deleteConfirmStation = null
		},

		toggleFavorite(station) {
			if (this.isFavorite(station)) {
				this.favorites = this.favorites.filter((f) => f.stationuuid !== station.stationuuid)
			} else {
				this.favorites.push({
					stationuuid: station.stationuuid,
					name: station.name,
					url_resolved: station.url_resolved,
					tags: station.tags || '',
					favicon: station.favicon,
					bitrate: station.bitrate || null,
					codec: station.codec || '',
					country: station.country || '',
					homepage: station.homepage || '',
					folder: '',
				})
			}
			this.saveFavorites()
		},

		addCustomStation() {
			if (!this.newStation.name.trim() || !this.newStation.url_resolved.trim()) return
			this.favorites.push({
				stationuuid: 'custom-' + Date.now(),
				name: this.newStation.name.trim(),
				url_resolved: this.newStation.url_resolved.trim(),
				tags: '',
				favicon: this.newStation.favicon.trim(),
				folder: this.newStation.folder || '',
			})
			this.saveFavorites()
			this.newStation = { name: '', url_resolved: '', favicon: '', folder: '' }
			this.showAddForm = false
		},

		startEdit(station) {
			this.editingId = station.stationuuid
			const list = this.currentView === 'folder' ? this.folderStations : this.favorites
			const pos = list.findIndex((f) => f.stationuuid === station.stationuuid)
			this.editStation = {
				name: station.name,
				url_resolved: station.url_resolved,
				favicon: station.favicon || '',
				folder: station.folder || '',
				position: pos === -1 ? '' : String(pos + 1),
				positionWas: pos === -1 ? '' : String(pos + 1),
			}
		},

		saveEdit() {
			const idx = this.favorites.findIndex((f) => f.stationuuid === this.editingId)
			if (idx !== -1) {
				const wasInFolder = this.currentView === 'folder'
				const updated = {
					...this.favorites[idx],
					name: this.editStation.name.trim(),
					url_resolved: this.editStation.url_resolved.trim(),
					favicon: this.editStation.favicon.trim(),
					folder: this.editStation.folder || '',
				}
				this.favorites[idx] = updated

				// Platz nur anfassen, wenn die Zahl wirklich geaendert wurde.
				const target = parseInt(this.editStation.position, 10)
				const before = parseInt(this.editStation.positionWas, 10)
				if (target && target !== before) {
					if (wasInFolder) {
						this.moveWithinFolder(updated, target)
					} else {
						this.moveWithinFavorites(updated, target)
					}
				}

				this.saveFavorites()
			}
			this.editingId = null
		},

		exportFavorites() {
			const blob = new Blob([JSON.stringify({
				favorites: this.favorites,
				folders: this.settings.folders,
			}, null, 2)], { type: 'application/json' })
			const url = URL.createObjectURL(blob)
			const a = document.createElement('a')
			a.href = url
			a.download = 'glxradio-favoriten.json'
			document.body.appendChild(a)
			a.click()
			a.remove()
			URL.revokeObjectURL(url)
		},

		triggerImport() {
			this.$refs.importInput.click()
		},

		importFavorites(event) {
			const file = event.target.files && event.target.files[0]
			event.target.value = ''
			if (!file) return
			const reader = new FileReader()
			reader.onload = async () => {
				try {
					const data = JSON.parse(reader.result)
					if (Array.isArray(data.favorites)) {
						this.favorites = data.favorites
						this.saveFavorites()
					}
					// Falls die importierte Datei Ordner-Zuweisungen enthaelt,
					// aber die passenden Ordner hier noch nicht existieren,
					// legen wir sie mit an - sonst waeren die Sender zwar
					// zugewiesen, der Ordner selbst taucht aber nirgends im
					// Seitenmenue auf.
					const foldersFromFile = Array.isArray(data.folders) ? data.folders : []
					const foldersFromStations = this.favorites.map((f) => f.folder).filter(Boolean)
					const missing = [...new Set([...foldersFromFile, ...foldersFromStations])]
						.filter((name) => !this.settings.folders.includes(name))
					for (const name of missing) {
						await this.createFolderByName(name)
					}
				} catch (e) {
					// ungueltige Datei - einfach ignorieren
				}
			}
			reader.readAsText(file)
		},

		async createFolderByName(name) {
			try {
				const response = await axios.post(generateUrl('/apps/glxradio/api/folders'), { name })
				this.settings.folders = response.data.folders || this.settings.folders
			} catch (e) {
				// bei Fehler bleibt der Ordner einfach ungelistet, die Zuweisung
				// am Sender selbst geht dadurch aber nicht verloren
			}
		},

		async loadFavorites() {
			try {
				const response = await axios.get(generateUrl('/apps/glxradio/api/favorites'))
				this.favorites = response.data.favorites || []
			} catch (e) {
				this.favorites = []
			}
		},


		moveWithinFavorites(station, target) {
			const list = this.favorites.slice()
			const from = list.findIndex((f) => f.stationuuid === station.stationuuid)
			if (from === -1) return
			const to = Math.max(0, Math.min(list.length - 1, target - 1))
			if (to === from) return
			const moved = list.splice(from, 1)[0]
			list.splice(to, 0, moved)
			this.favorites = list
		},

		// In einer Ordner-Ansicht zaehlt nur die Reihenfolge innerhalb des Ordners.
		// Die Sender behalten deshalb ihre Plaetze in der Gesamtliste, nur ihre
		// Reihenfolge untereinander wird neu gesetzt.
		moveWithinFolder(station, target) {
			const slots = []
			this.favorites.forEach((f, i) => {
				if (f.folder === this.activeFolder) slots.push(i)
			})
			const items = slots.map((i) => this.favorites[i])
			const from = items.findIndex((f) => f.stationuuid === station.stationuuid)
			if (from === -1) return
			const to = Math.max(0, Math.min(items.length - 1, target - 1))
			if (to === from) return
			const moved = items.splice(from, 1)[0]
			items.splice(to, 0, moved)
			const list = this.favorites.slice()
			slots.forEach((globalIndex, k) => {
				list[globalIndex] = items[k]
			})
			this.favorites = list
		},

		async saveFavorites() {
			try {
				await axios.post(generateUrl('/apps/glxradio/api/favorites'), { favorites: this.favorites })
			} catch (e) {
				// still lokal behalten, auch wenn Speichern fehlschlaegt
			}
		},

		async loadSettings() {
			try {
				const response = await axios.get(generateUrl('/apps/glxradio/api/settings'))
				this.settings.logoFolder = response.data.logoFolder || LOGO_FOLDER
				this.settings.showLogos = response.data.showLogos !== false
				this.settings.folders = Array.isArray(response.data.folders) ? response.data.folders : []
			} catch (e) {
				// Standardwerte bleiben bestehen, falls das Laden fehlschlaegt
			}
		},

		async saveSettings() {
			try {
				await axios.post(generateUrl('/apps/glxradio/api/settings'), {
					logoFolder: this.settings.logoFolder,
					showLogos: this.settings.showLogos,
				})
				this.settingsSaved = true
				setTimeout(() => { this.settingsSaved = false }, 2000)
			} catch (e) {
				// still nur lokal wirksam, falls Speichern fehlschlaegt
			}
		},
	},
}
</script>

<style scoped>
.glxradio-layout {
	display: flex;
	gap: 24px;
	align-items: flex-start;
	flex-wrap: nowrap;
	padding: 20px;
}
.glxradio-sidebar-search {
	display: flex;
	gap: 6px;
	margin-bottom: 8px;
	padding: 8px 8px 0;
}
.glxradio-search-input-wrap {
	position: relative;
	flex: 1;
	min-width: 0;
}
.glxradio-search-input-wrap input {
	width: 100%;
	padding: 8px 28px 8px 8px;
	box-sizing: border-box;
}
.glxradio-search-clear {
	position: absolute;
	right: 4px;
	top: 50%;
	transform: translateY(-50%);
	border: none;
	background: none;
	cursor: pointer;
	color: var(--color-text-maxcontrast, #767676);
	font-size: 13px;
	line-height: 1;
	padding: 4px;
}
.glxradio-search-clear:hover {
	color: var(--color-main-text, #222);
}
.glxradio-icon-btn-round {
	width: 36px;
	height: 36px;
	flex-shrink: 0;
	border: none;
	border-radius: 50%;
	cursor: pointer;
	background: var(--color-primary-element, #1b6ca8);
	color: var(--color-primary-element-text, #fff);
	display: flex;
	align-items: center;
	justify-content: center;
}
.glxradio-sidebar-btn {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 9px 12px;
	margin: 0 8px;
	border: none;
	border-radius: 8px;
	cursor: pointer;
	background: var(--color-background-hover, #eee);
	color: inherit;
	font-size: 14px;
	text-align: left;
	width: calc(100% - 16px);
}
.glxradio-sidebar-btn:hover {
	background: var(--color-background-dark, #e0e0e0);
}
.glxradio-sidebar-btn-nav {
	font-weight: 600;
}
.glxradio-sidebar-btn-active {
	background: var(--color-primary-element-light, rgba(27, 108, 168, 0.15));
	color: var(--color-primary-element, #1b6ca8);
}
.glxradio-sidebar-btn-settings {
	margin-top: auto;
}
.glxradio-add-form-sidebar {
	flex-direction: column;
	margin: -2px 8px 6px;
}
.glxradio-main {
	flex: 1 1 auto;
	min-width: 320px;
}
.glxradio-filler {
	flex: 0 0 260px;
	width: 260px;
	min-width: 220px;
	position: sticky;
	top: 0;
	align-self: flex-start;
	max-height: 100vh;
	overflow-y: auto;
}
.glxradio-filler h2 {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 16px;
}
.glxradio-station-info-box {
	margin-bottom: 24px;
	padding-bottom: 16px;
	border-bottom: 1px solid var(--color-border, #eee);
}
.glxradio-station-info-list {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 4px 10px;
	font-size: 13px;
	margin: 8px 0 0;
}
.glxradio-station-info-list dt {
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-station-info-list dd {
	margin: 0;
	font-weight: 600;
}
.glxradio-item-compact {
	padding: 6px 4px;
	align-items: flex-start;
}
.glxradio-item-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}
.glxradio-item-meta {
	font-size: 11px;
	color: var(--color-text-maxcontrast, #767676);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.glxradio-settings {
	padding: 20px;
	max-width: 600px;
}
.glxradio-settings-block {
	margin-bottom: 28px;
}
.glxradio-player-meta {
	display: block;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 100%;
}
.glxradio-pos-num {
	flex-shrink: 0;
	min-width: 24px;
	text-align: right;
	font-size: 13px;
	font-variant-numeric: tabular-nums;
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-pos-row {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-top: 4px;
	font-size: 13px;
}
.glxradio-pos-row input {
	width: 70px;
	text-align: center;
}
.glxradio-pos-hint {
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-btn-danger {
	margin-right: auto;
	color: var(--color-error, #c33);
}

.glxradio-help-list {
	padding-left: 20px;
	font-size: 13px;
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-help-list li {
	margin-bottom: 8px;
}
.glxradio-help-list strong {
	color: var(--color-main-text, #222);
}
.glxradio-help {
	max-width: 760px;
}
.glxradio-help-section {
	margin-top: 24px;
	padding: 14px 16px 4px;
	border-radius: var(--border-radius-large, 12px);
	background: var(--color-background-hover, rgba(127, 127, 127, .08));
}
.glxradio-help-heading {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0 0 10px;
	font-size: 15px;
	font-weight: 600;
	color: var(--color-main-text, #222);
}
.glxradio-help-dl {
	margin: 0;
}
.glxradio-help-dl dt {
	font-size: 13px;
	font-weight: 600;
	color: var(--color-main-text, #222);
}
.glxradio-help-dl dd {
	margin: 2px 0 12px;
	font-size: 13px;
	line-height: 1.5;
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-settings-block h3 {
	margin-bottom: 8px;
}
.glxradio-settings-toggle {
	display: flex;
	align-items: center;
	gap: 8px;
	cursor: pointer;
}
.glxradio-settings-saved {
	margin-top: 8px;
	color: var(--color-success-text, #46ba61);
	font-size: 13px;
}
.glxradio-app-version {
	margin-top: 32px;
	font-size: 11px;
	color: var(--color-text-maxcontrast, #767676);
	text-align: center;
}
.glxradio-confirm {
	padding: 20px;
}
.glxradio-confirm-actions {
	display: flex;
	justify-content: flex-end;
	gap: 10px;
	margin-top: 16px;
}
.glxradio-folder-path {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 12px 0;
}
.glxradio-folder-path code {
	background: var(--color-background-hover, #eee);
	padding: 4px 8px;
	border-radius: 4px;
	font-size: 13px;
}
.glxradio-folder-list {
	max-height: 320px;
	overflow-y: auto;
	border: 1px solid var(--color-border, #eee);
	border-radius: 8px;
}
.glxradio-folder-list-item {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	padding: 10px 12px;
	border: none;
	background: none;
	color: inherit;
	cursor: pointer;
	text-align: left;
}
.glxradio-folder-list-item:hover {
	background: var(--color-background-hover, #eee);
}
.glxradio-play-count {
	font-size: 12px;
	color: var(--color-text-maxcontrast, #767676);
	flex-shrink: 0;
}
.glxradio-btn {
	padding: 8px 16px;
	cursor: pointer;
	white-space: nowrap;
}
.glxradio-btn-secondary {
	background: var(--color-background-hover, #eee);
}
.glxradio-add-form {
	display: flex;
	gap: 8px;
	margin-bottom: 16px;
	flex-wrap: wrap;
}
.glxradio-add-form input {
	flex: 1;
	min-width: 160px;
	padding: 8px;
}
.glxradio-favicon-row {
	display: flex;
	gap: 6px;
	width: 100%;
}
.glxradio-edit-actions {
	display: flex;
	justify-content: flex-end;
	gap: 8px;
	width: 100%;
}
.glxradio-folder-select {
	width: 100%;
	padding: 8px;
	background: var(--color-main-background, #fff);
	color: var(--color-main-text, #222);
	border: 1px solid var(--color-border, #ddd);
	border-radius: 4px;
}
.glxradio-folder-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 8px 4px;
	border-bottom: 1px solid var(--color-border, #eee);
}
.glxradio-sidebar-btn-folder {
	padding-left: 30px;
}
.glxradio-folder-badge {
	width: 160px;
	flex-shrink: 0;
	text-align: left;
	font-size: 12px;
	color: var(--color-text-maxcontrast, #767676);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.glxradio-favicon-row input {
	flex: 1;
}
.glxradio-favicon-row button {
	flex-shrink: 0;
	padding: 8px 10px;
}
.glxradio-logo-picker {
	padding: 20px;
	max-width: 640px;
}
.glxradio-logo-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
	gap: 12px;
	margin-top: 12px;
}
.glxradio-logo-grid-item {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	border: none;
	background: none;
	cursor: pointer;
	padding: 8px;
	border-radius: 8px;
	color: inherit;
}
.glxradio-logo-grid-item:hover {
	background: var(--color-background-hover, #eee);
}
.glxradio-logo-grid-item img {
	width: 64px;
	height: 64px;
	object-fit: cover;
	border-radius: 8px;
}
.glxradio-logo-grid-item span {
	font-size: 12px;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.glxradio-player {
	margin: 0 0 20px;
	padding: 12px 16px;
	background: var(--color-background-hover, #f2f2f2);
	border-radius: 10px;
	display: flex;
	align-items: center;
	gap: 14px;
	flex-wrap: wrap;
	position: sticky;
	top: 0;
	z-index: 5;
	box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
}
.glxradio-tile {
	width: 56px;
	height: 56px;
	border-radius: 10px;
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
}
.glxradio-tile-empty {
	background: var(--color-background-darker, #e0e0e0);
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-tile-img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.glxradio-tile-letter {
	color: #fff;
	font-weight: 700;
	font-size: 22px;
}
.glxradio-info {
	min-width: 120px;
}
.glxradio-player-fill {
	flex: 1;
	min-width: 80px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 5px;
}
.glxradio-progress-track {
	height: 4px;
	border-radius: 2px;
	background: var(--color-border, rgba(127, 127, 127, 0.3));
	overflow: hidden;
}
.glxradio-progress-fill {
	height: 100%;
	width: 0%;
	border-radius: 2px;
	background: var(--color-primary-element, #1b6ca8);
	transition: width 0.4s ease;
}
.glxradio-progress-fill-active {
	width: 100%;
}
.glxradio-info-top {
	display: flex;
	align-items: center;
	gap: 10px;
}
.glxradio-station-name {
	font-size: 15px;
	display: block;
	max-width: 260px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.glxradio-info-sub {
	font-size: 13px;
	color: var(--color-text-maxcontrast, #767676);
	margin-top: 2px;
}
.glxradio-now-playing-title {
	font-size: 13px;
	color: var(--color-main-text, #222);
	margin-top: 2px;
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 340px;
}
.glxradio-live {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.04em;
	color: var(--color-error-text, #e9322d);
	white-space: nowrap;
}
.glxradio-live-dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: currentColor;
	animation: glxradio-pulse 1.6s ease-in-out infinite;
}
.glxradio-live-buffering {
	color: rgb(204, 85, 0);
}
@keyframes glxradio-pulse {
	0%, 100% { opacity: 1; }
	50% { opacity: 0.25; }
}
.glxradio-controls {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-shrink: 0;
}
.glxradio-transport-btn {
	width: 32px;
	height: 32px;
	border: none;
	border-radius: 50%;
	cursor: pointer;
	background: none;
	color: var(--color-main-text, #222);
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
}
.glxradio-transport-btn:hover:not(:disabled) {
	background: var(--color-background-dark, rgba(127, 127, 127, 0.15));
}
.glxradio-transport-btn:disabled {
	opacity: 0.3;
	cursor: default;
}
.glxradio-transport-btn-main {
	width: 44px;
	height: 44px;
}
.glxradio-volume-icon {
	color: var(--color-text-maxcontrast, #767676);
	flex-shrink: 0;
}
.glxradio-volume {
	width: 90px;
	accent-color: var(--color-primary-element, #1b6ca8);
}
.glxradio-audio-hidden {
	display: none;
}
.glxradio-play-error {
	flex-basis: 100%;
	margin-top: 4px;
	padding: 8px;
	background: rgba(204, 85, 0, 0.15);
	border-radius: 6px;
	font-size: 13px;
}
.glxradio-bitrate {
	color: var(--color-text-maxcontrast, #767676);
	font-size: 13px;
}
.glxradio-status {
	margin-top: 8px;
	font-size: 13px;
	color: var(--color-text-maxcontrast, #767676);
}
.glxradio-status-buffering {
	color: rgb(204, 85, 0);
}
.glxradio-status-playing {
	color: var(--color-success-text, #46ba61);
}
.glxradio-list {
	width: 100%;
}
.glxradio-list-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	flex-wrap: wrap;
}
.glxradio-item {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px 10px;
	border-bottom: 1px solid var(--color-border, #eee);
}
.glxradio-favicon {
	width: 24px;
	height: 24px;
	object-fit: contain;
	border-radius: 4px;
	flex-shrink: 0;
}
.glxradio-name {
	cursor: pointer;
	flex: 1;
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.glxradio-name:hover {
	text-decoration: underline;
}
.glxradio-icon-btn {
	background: none;
	border: none;
	cursor: pointer;
	font-size: 16px;
}
.glxradio-list-header h2 {
	display: flex;
	align-items: center;
	gap: 8px;
}
.glxradio-heading-icon {
	color: var(--color-primary-element, #1b6ca8);
	flex-shrink: 0;
}
.glxradio-empty {
	color: var(--color-text-maxcontrast, #767676);
	padding: 8px 0;
}
@media (max-width: 900px) {
	.glxradio-layout {
		flex-wrap: wrap;
	}
	.glxradio-main {
		flex-basis: 100%;
		max-width: 100%;
	}
	.glxradio-filler {
		flex-basis: 100%;
	}
}
</style>
