<template>
  <div class="music-player">
    <div class="player-header">
      <h1>🎵 PMO Music Player</h1>
      <p class="subtitle">Lecteur générique basé sur l'API pmosource</p>
    </div>

    <!-- Error Message -->
    <div v-if="error" class="error-banner">
      ❌ {{ error }}
    </div>

    <!-- Source Selection -->
    <div class="sources-section">
      <h2>📻 Sources Musicales</h2>
      <div v-if="sourcesLoading" class="loading-message">
        Chargement des sources...
      </div>
      <div v-else-if="sources.length === 0" class="empty-message">
        Aucune source musicale disponible
      </div>
      <div v-else class="sources-grid">
        <div
          v-for="source in sources"
          :key="source.id"
          :class="['source-card', { active: selectedSource?.id === source.id }]"
          @click="selectSource(source)"
        >
          <div class="source-image">
            <img :src="getSourceImageUrl(source.id)" :alt="source.name" />
          </div>
          <div class="source-info">
            <div class="source-name">{{ source.name }}</div>
            <div class="source-capabilities">
              <span v-if="source.supports_fifo" class="capability-badge">FIFO</span>
              <span v-if="source.capabilities.supports_search" class="capability-badge">Search</span>
              <span v-if="source.capabilities.supports_favorites" class="capability-badge">Favorites</span>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- Browser Section (si une source est sélectionnée) -->
    <div v-if="selectedSource" class="browser-section">
      <div class="browser-header">
        <h2>📂 {{ selectedSource.name }}</h2>
        <button @click="refreshBrowser" :disabled="browserLoading" class="btn-refresh">
          <span v-if="browserLoading">⏳</span>
          <span v-else>🔄</span>
          Actualiser
        </button>
      </div>

      <!-- Breadcrumb Navigation -->
      <div class="breadcrumb">
        <button
          v-for="(crumb, index) in breadcrumbs"
          :key="crumb.id"
          @click="navigateToContainer(crumb)"
          :class="['breadcrumb-item', { active: index === breadcrumbs.length - 1 }]"
        >
          {{ crumb.title }}
        </button>
      </div>

      <!-- Browser Loading -->
      <div v-if="browserLoading" class="loading-message">
        Chargement...
      </div>

      <!-- Containers List -->
      <div v-if="browseResult && browseResult.containers.length > 0" class="containers-section">
        <h3>📁 Dossiers</h3>
        <div class="containers-list">
          <div
            v-for="container in browseResult.containers"
            :key="container.id"
            class="container-item"
            @click="navigateIntoContainer(container)"
          >
            <div class="container-icon">📁</div>
            <div class="container-info">
              <div class="container-title">{{ container.title }}</div>
              <div class="container-meta">
                <span v-if="container.child_count">{{ container.child_count }} éléments</span>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- Items List -->
      <div v-if="browseResult && browseResult.items.length > 0" class="items-section">
        <h3>🎵 Morceaux</h3>
        <div class="items-list">
          <div
            v-for="(item, index) in browseResult.items"
            :key="item.id"
            :class="['item-card', {
              active: currentTrack?.id === item.id,
              playing: isPlaying && currentTrack?.id === item.id
            }]"
          >
            <div class="item-number">{{ index + 1 }}</div>
            <div class="item-cover">
              <img
                v-if="item.album_art"
                :src="item.album_art"
                :alt="item.title"
                @error="handleImageError"
              />
              <div v-else class="item-cover-placeholder">🎵</div>
            </div>
            <div class="item-info">
              <div class="item-title">{{ item.title }}</div>
              <div class="item-artist">{{ item.artist || item.creator || 'Artiste inconnu' }}</div>
              <div class="item-album">{{ item.album || '' }}</div>
            </div>
            <div class="item-actions">
              <button
                @click="playTrack(item)"
                :class="['btn-play-item', { active: isPlaying && currentTrack?.id === item.id }]"
              >
                <span v-if="isPlaying && currentTrack?.id === item.id">⏸️</span>
                <span v-else>▶️</span>
              </button>
            </div>
          </div>
        </div>
      </div>

      <!-- Empty State -->
      <div v-if="browseResult && browseResult.total === 0" class="empty-message">
        Ce container est vide
      </div>
    </div>

    <!-- Now Playing Section -->
    <div v-if="currentTrack || audioError" class="now-playing-section">
      <div class="now-playing-header">
        <h2>🎧 Lecture en cours</h2>
        <button v-if="isPlaying" @click="stopPlayback" class="btn-stop">
          ⏹️ Arrêter
        </button>
      </div>

      <div v-if="audioError" class="audio-error">
        ❌ {{ audioError }}
      </div>

      <div v-if="currentTrack" class="now-playing-content">
        <div class="now-playing-cover">
          <img
            v-if="currentTrack.album_art"
            :src="currentTrack.album_art"
            :alt="currentTrack.title"
            @error="handleImageError"
          />
          <div v-else class="now-playing-cover-placeholder">🎵</div>
        </div>
        <div class="now-playing-info">
          <div class="now-playing-title">{{ currentTrack.title }}</div>
          <div class="now-playing-artist">{{ currentTrack.artist || currentTrack.creator || 'Artiste inconnu' }}</div>
          <div class="now-playing-album">{{ currentTrack.album || '' }}</div>
          <div class="now-playing-source">Source: {{ selectedSource?.name }}</div>
        </div>
      </div>

      <!-- Progress Bar -->
      <div v-if="currentTrack" class="progress-container">
        <div class="progress-time">{{ formatTime(currentTime) }}</div>
        <div class="progress-bar">
          <div
            class="progress-fill"
            :style="{ width: duration > 0 ? (currentTime / duration * 100) + '%' : '0%' }"
          ></div>
        </div>
        <div class="progress-time">
          {{ duration > 0 ? formatTime(duration) : formatTime(parseDuration(currentTrack.resources[0]?.duration)) }}
        </div>
      </div>

      <!-- Audio Player -->
      <div class="audio-player-container">
        <audio
          ref="audioPlayer"
          preload="auto"
          @ended="handleAudioEnded"
          @error="handleAudioError"
          @play="handleAudioPlay"
          @pause="handleAudioPause"
          @timeupdate="handleTimeUpdate"
          @loadedmetadata="handleLoadedMetadata"
          @durationchange="handleDurationChange"
          @canplay="handleCanPlay"
          @waiting="handleAudioWaiting"
          @stalled="handleAudioStalled"
          @progress="handleAudioProgress"
        ></audio>
      </div>
    </div>

    <!-- Debug Info -->
    <div v-if="currentTrack" class="debug-info">
      <details>
        <summary>🔧 Informations de debug</summary>
        <div class="debug-content">
          <div><strong>Source ID:</strong> {{ selectedSource?.id }}</div>
          <div><strong>Object ID:</strong> {{ currentTrack.id }}</div>
          <div><strong>Current URI:</strong> {{ currentUri }}</div>
          <div><strong>Track Class:</strong> {{ currentTrack.class }}</div>
        </div>
      </details>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import {
  listSources,
  browseSource,
  resolveUri,
  getSourceImageUrl,
  type SourceInfo,
  type BrowseResponse,
  type BrowseItem,
  type BrowseContainer,
} from '../services/pmosource'

// State
const sources = ref<SourceInfo[]>([])
const sourcesLoading = ref(false)
const selectedSource = ref<SourceInfo | null>(null)
const browseResult = ref<BrowseResponse | null>(null)
const browserLoading = ref(false)
const error = ref<string | null>(null)

// Breadcrumb navigation
interface Breadcrumb {
  id: string
  title: string
}

const breadcrumbs = ref<Breadcrumb[]>([])
const currentObjectId = ref<string | null>(null)

// Audio player state
const currentTrack = ref<BrowseItem | null>(null)
const currentUri = ref<string | null>(null)
const isPlaying = ref(false)
const audioError = ref<string | null>(null)
const audioPlayer = ref<HTMLAudioElement | null>(null)
const currentTime = ref(0)
const duration = ref(0)
const lastTimeUpdateTs = ref<number | null>(null)
const lastLoggedTimeupdate = ref<number>(0)
const lastReadyState = ref<number | null>(null)
const lastNetworkState = ref<number | null>(null)

// Retry timer cleanup (auto-reload désactivé pour les streams live)
let retryTimer: ReturnType<typeof setTimeout> | null = null
let stallMonitor: ReturnType<typeof setInterval> | null = null
let stateMonitor: ReturnType<typeof setInterval> | null = null

// Metadata refresh via SSE
let metadataEventSource: EventSource | null = null

// Load sources on mount
onMounted(async () => {
  await loadSources()
  // Monitor pour détecter une éventuelle stagnation du currentTime
  stallMonitor = setInterval(() => {
    const p = audioPlayer.value
    if (!p || !isPlaying.value) return
    if (!lastTimeUpdateTs.value) return
    const now = Date.now()
    // Si aucun timeupdate depuis >5s et lecture en cours, logguer l'état du buffer
    if (now - lastTimeUpdateTs.value > 5000) {
      const buffered: string[] = []
      for (let i = 0; i < p.buffered.length; i++) {
        buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
      }
      console.debug('[PlayerDebug] stall-detected', {
        ts: now,
        currentTime: p.currentTime.toFixed(3),
        duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
        readyState: p.readyState,
        networkState: p.networkState,
        paused: p.paused,
        buffered,
      })
      // Eviter le spam : reset le marqueur pour n'émettre qu'une fois par stagnation
      lastTimeUpdateTs.value = null
    }
  }, 2000)

  // Monitor périodique des changements de readyState/networkState
  stateMonitor = setInterval(() => {
    const p = audioPlayer.value
    if (!p) return
    const rs = p.readyState
    const ns = p.networkState
    if (rs !== lastReadyState.value || ns !== lastNetworkState.value) {
      const buffered: string[] = []
      for (let i = 0; i < p.buffered.length; i++) {
        buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
      }
      console.debug('[PlayerDebug] state-change', {
        ts: Date.now(),
        currentTime: p.currentTime.toFixed(3),
        readyState: rs,
        networkState: ns,
        paused: p.paused,
        buffered,
      })
      lastReadyState.value = rs
      lastNetworkState.value = ns
    }
  }, 1000)
})

// Cleanup on unmount
onUnmounted(() => {
  stopMetadataRefresh()
  clearRetryTimer()
  if (stallMonitor) {
    clearInterval(stallMonitor)
    stallMonitor = null
  }
  if (stateMonitor) {
    clearInterval(stateMonitor)
    stateMonitor = null
  }
})

/**
 * Charge la liste des sources disponibles
 */
async function loadSources() {
  sourcesLoading.value = true
  error.value = null

  try {
    const result = await listSources()
    sources.value = result.sources
  } catch (e: any) {
    error.value = `Erreur lors du chargement des sources: ${e.message}`
    console.error('Failed to load sources:', e)
  } finally {
    sourcesLoading.value = false
  }
}

/**
 * Sélectionne une source et charge son contenu racine
 */
async function selectSource(source: SourceInfo) {
  selectedSource.value = source
  currentObjectId.value = null
  breadcrumbs.value = [{ id: source.id, title: source.name }]
  await loadBrowseResult()
}

/**
 * Charge le contenu d'un container
 */
async function loadBrowseResult() {
  if (!selectedSource.value) return

  browserLoading.value = true
  error.value = null

  try {
    browseResult.value = await browseSource(
      selectedSource.value.id,
      currentObjectId.value || undefined
    )
  } catch (e: any) {
    error.value = `Erreur lors de la navigation: ${e.message}`
    console.error('Failed to browse:', e)
  } finally {
    browserLoading.value = false
  }
}

/**
 * Navigue dans un container enfant
 */
async function navigateIntoContainer(container: BrowseContainer) {
  currentObjectId.value = container.id
  breadcrumbs.value.push({
    id: container.id,
    title: container.title,
  })
  await loadBrowseResult()
}

/**
 * Navigue vers un container du breadcrumb
 */
async function navigateToContainer(crumb: Breadcrumb) {
  // Find the index of this crumb
  const index = breadcrumbs.value.findIndex((c) => c.id === crumb.id)
  if (index === -1) return

  // Truncate breadcrumbs to this point
  breadcrumbs.value = breadcrumbs.value.slice(0, index + 1)

  // Set current object ID (root if it's the first breadcrumb)
  if (index === 0) {
    currentObjectId.value = null
  } else {
    currentObjectId.value = crumb.id
  }

  await loadBrowseResult()
}

/**
 * Actualise le contenu courant
 */
async function refreshBrowser() {
  await loadBrowseResult()
}

/**
 * Démarre le stream de métadonnées en temps réel via SSE
 */
function startMetadataRefresh() {
  if (!selectedSource.value || !currentTrack.value) return

  // Stop any existing connection
  stopMetadataRefresh()

  // Build SSE endpoint URL
  const params = new URLSearchParams({ object_id: currentTrack.value.id })
  const sseUrl = `/api/sources/${selectedSource.value.id}/item/stream?${params.toString()}`

  console.log('Connecting to SSE:', sseUrl)

  // Create EventSource connection
  metadataEventSource = new EventSource(sseUrl)

  metadataEventSource.onmessage = (event) => {
    try {
      const previous = currentTrack.value
      const metadata = JSON.parse(event.data)
      if (currentTrack.value && metadata.id === currentTrack.value.id) {
        currentTrack.value = metadata
        console.log('Metadata updated via SSE:', metadata.title)

        // Si le morceau change (live stream), remettre la progression à zéro
        // IMPORTANT: Ne pas recharger l'URI car c'est un flux continu !
        if (hasTrackChanged(previous, metadata)) {
          console.log('Nouveau morceau dans le flux:', metadata.title)
          resetProgressForNewTrack(metadata)
        }

        // Mettre à jour la durée si elle a changé
        const newDuration = metadata.resources?.[0]?.duration
        if (newDuration) {
          const parsedDuration = parseDuration(newDuration)
          if (parsedDuration > 0) {
            duration.value = parsedDuration
          }
        }
      }
    } catch (e: any) {
      console.error('Failed to parse SSE metadata:', e)
    }
  }

  metadataEventSource.onerror = (error) => {
    console.error('SSE connection error:', error)
    // Don't show error to user, SSE will auto-reconnect
  }
}

/**
 * Arrête le stream de métadonnées
 */
function stopMetadataRefresh() {
  if (metadataEventSource) {
    metadataEventSource.close()
    metadataEventSource = null
    console.log('SSE connection closed')
  }
}

/**
 * Joue un morceau
 */
async function playTrack(item: BrowseItem) {
  if (!selectedSource.value) return

  // Si c'est le même morceau en cours de lecture, toggle play/pause
  if (currentTrack.value?.id === item.id && isPlaying.value) {
    audioPlayer.value?.pause()
    return
  }

  resetAudioRetries()
  audioError.value = null
  currentTrack.value = item
  const preferredUri = pickPreferredResourceUri(item)

  // Initialise la durée depuis les métadonnées du morceau si disponible
  currentTime.value = 0
  const trackDuration = item.resources[0]?.duration
  if (trackDuration) {
    duration.value = parseDuration(trackDuration)
  } else {
    duration.value = 0
  }

  try {
    // Résout l'URI du morceau via l'API
    const result = await resolveUri(selectedSource.value.id, item.id)
    let uri = result.uri

    // Si resolveUri n'a pas retourné d'URI, utiliser notre URI préférée (basée sur le support navigateur)
    if (!uri && preferredUri) {
      uri = preferredUri
      console.log('Utilisation de l\'URI préférée (auto-détectée):', uri)
    }

    // Si les deux sont disponibles, privilégier l'URI la mieux supportée
    if (uri && preferredUri && uri !== preferredUri) {
      // Vérifier si l'URI préférée est mieux supportée
      const resolvedFormat = getAudioFormat(uri)
      const preferredFormat = getAudioFormat(preferredUri)

      if (preferredFormat && canPlayAudioType(preferredFormat)) {
        if (!resolvedFormat || !canPlayAudioType(resolvedFormat)) {
          console.log(`Remplacement de ${uri} par ${preferredUri} (meilleur support navigateur)`)
          uri = preferredUri
        }
      }
    }

    if (!uri) {
      throw new Error('Aucune URI audio disponible')
    }

    await startPlaybackFromUri(uri, true)
  } catch (e: any) {
    audioError.value = `Erreur lors de la lecture: ${e.message}`
    console.error('Failed to play track:', e)
  }
}

/**
 * Arrête la lecture
 */
function stopPlayback() {
  stopMetadataRefresh()

  if (audioPlayer.value) {
    audioPlayer.value.pause()
    audioPlayer.value.currentTime = 0
    audioPlayer.value.src = ''
  }
  isPlaying.value = false
  currentTrack.value = null
  currentUri.value = null
  audioError.value = null
  currentTime.value = 0
  duration.value = 0
  resetAudioRetries()
}

/**
 * Gère la fin de la lecture
 */
function handleAudioEnded() {
  isPlaying.value = false
  currentTime.value = 0
  stopMetadataRefresh()
  // On peut implémenter ici une logique de lecture automatique du prochain morceau
  const p = audioPlayer.value
  if (p) {
    console.debug('[PlayerDebug] ended', {
      ts: Date.now(),
      currentTime: p.currentTime.toFixed(3),
      duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
      readyState: p.readyState,
      networkState: p.networkState,
    })
  } else {
    console.debug('[PlayerDebug] ended (no player)')
  }
}

/**
 * Gère les erreurs audio
 */
function handleAudioError() {
  stopMetadataRefresh()

  const audio = audioPlayer.value
  const errorCode = audio?.error?.code
  const baseMessage = errorCode
    ? `Erreur de lecture audio (code ${errorCode})`
    : 'Erreur de lecture audio inconnue'

  // Pour les streams live (Radio Paradise), ne pas recharger le flux : cela remet la progression à 0
  // et crée un lag perceptible. On se contente d'afficher l'erreur et on tente un play() direct si possible.
  audioError.value = baseMessage
  isPlaying.value = false

  // Tenter une reprise douce sans reload si le player a déjà des données bufferisées
  const player = audioPlayer.value
  if (player && !player.paused) {
    // Rien à faire, il lit encore ou se relancera tout seul
    return
  }
  if (player && player.readyState >= 2) {
    console.debug('[PlayerDebug] error', {
      ts: Date.now(),
      message: baseMessage,
      code: audio?.error?.code,
      currentTime: player.currentTime.toFixed(3),
      duration: isFinite(player.duration) ? player.duration.toFixed(3) : 'NaN',
      readyState: player.readyState,
      networkState: player.networkState,
      buffered: (() => {
        const arr: string[] = []
        for (let i = 0; i < player.buffered.length; i++) {
          arr.push(`${player.buffered.start(i).toFixed(3)}-${player.buffered.end(i).toFixed(3)}`)
        }
        return arr
      })(),
    })
    void player.play().catch(() => {
      // Si play échoue (user gesture requis ou decode error), on laisse l'erreur affichée
    })
  } else {
    console.debug('[PlayerDebug] error (no retry)', {
      ts: Date.now(),
      message: baseMessage,
      code: audio?.error?.code,
      readyState: player?.readyState,
      networkState: player?.networkState,
    })
  }
}

/**
 * Gère le démarrage de la lecture
 */
function handleAudioPlay() {
  isPlaying.value = true
  audioError.value = null
  startMetadataRefresh()
}

/**
 * Gère la pause
 */
function handleAudioPause() {
  isPlaying.value = false
  // Don't stop refresh on pause, user might resume
  const p = audioPlayer.value
  if (p) {
    const buffered: string[] = []
    for (let i = 0; i < p.buffered.length; i++) {
      buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
    }
    console.debug('[PlayerDebug] pause', {
      ts: Date.now(),
      currentTime: p.currentTime.toFixed(3),
      readyState: p.readyState,
      networkState: p.networkState,
      paused: p.paused,
      buffered,
    })
  }
}

/**
 * Gère la mise à jour du temps de lecture
 */
function handleTimeUpdate() {
  if (audioPlayer.value) {
    currentTime.value = audioPlayer.value.currentTime
    lastTimeUpdateTs.value = Date.now()

    // Log périodiquement l'état du buffer pour diagnostiquer les coupures
    const logEvery = 2 // secondes (plus serré pour capturer les transitions)
    if (Math.floor(currentTime.value) % logEvery === 0 && Math.floor(currentTime.value) !== lastLoggedTimeupdate.value) {
      const p = audioPlayer.value
      const buffered: string[] = []
      for (let i = 0; i < p.buffered.length; i++) {
        buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
      }
      console.debug('[PlayerDebug] timeupdate', {
        ts: Date.now(),
        currentTime: p.currentTime.toFixed(3),
        duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
        readyState: p.readyState,
        networkState: p.networkState,
        paused: p.paused,
        buffered,
      })
      lastLoggedTimeupdate.value = Math.floor(currentTime.value)
    }

    // Si le temps recule (changement de piste) logguer explicitement l'état
    if (currentTime.value < lastLoggedTimeupdate.value) {
      const p = audioPlayer.value
      const buffered: string[] = []
      for (let i = 0; i < p.buffered.length; i++) {
        buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
      }
      console.debug('[PlayerDebug] time-jump', {
        ts: Date.now(),
        currentTime: p.currentTime.toFixed(3),
        duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
        readyState: p.readyState,
        networkState: p.networkState,
        paused: p.paused,
        buffered,
      })
    }
  }
}

/**
 * Gère le chargement des métadonnées audio
 */
function handleLoadedMetadata() {
  if (audioPlayer.value && !isNaN(audioPlayer.value.duration)) {
    duration.value = audioPlayer.value.duration
  }
}

/**
 * Gère le changement de durée
 */
function handleDurationChange() {
  if (audioPlayer.value && !isNaN(audioPlayer.value.duration)) {
    duration.value = audioPlayer.value.duration
  }
}

/**
 * Gère l'événement canplay (audio prêt à être joué)
 */
function handleCanPlay() {
  console.log('Audio ready to play (canplay event)')
}

function handleAudioWaiting() {
  const p = audioPlayer.value
  if (!p) return
  const buffered: string[] = []
  for (let i = 0; i < p.buffered.length; i++) {
    buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
  }
  console.debug('[PlayerDebug] waiting', {
    ts: Date.now(),
    currentTime: p.currentTime.toFixed(3),
    duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
    readyState: p.readyState,
    networkState: p.networkState,
    paused: p.paused,
    buffered,
  })
}

function handleAudioStalled() {
  const p = audioPlayer.value
  if (!p) return
  const buffered: string[] = []
  for (let i = 0; i < p.buffered.length; i++) {
    buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
  }
  console.debug('[PlayerDebug] stalled', {
    ts: Date.now(),
    currentTime: p.currentTime.toFixed(3),
    duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
    readyState: p.readyState,
    networkState: p.networkState,
    paused: p.paused,
    buffered,
  })
}

function handleAudioProgress() {
  const p = audioPlayer.value
  if (!p) return
  const buffered: string[] = []
  for (let i = 0; i < p.buffered.length; i++) {
    buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
  }
  console.debug('[PlayerDebug] progress', {
    ts: Date.now(),
    currentTime: p.currentTime.toFixed(3),
    duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
    readyState: p.readyState,
    networkState: p.networkState,
    paused: p.paused,
    buffered,
  })
}

/**
 * Formatte le temps en MM:SS
 */
function formatTime(seconds: number): string {
  if (!isFinite(seconds)) return '--:--'
  const mins = Math.floor(seconds / 60)
  const secs = Math.floor(seconds % 60)
  return `${mins}:${secs.toString().padStart(2, '0')}`
}

/**
 * Parse une durée au format UPnP (H:MM:SS ou H:MM:SS.mmm) en secondes
 */
function parseDuration(durationStr: string | null | undefined): number {
  if (!durationStr) return 0
  const parts = durationStr.split(':')
  if (parts.length === 3) {
    const hours = parseInt(parts[0] || '0', 10) || 0
    const minutes = parseInt(parts[1] || '0', 10) || 0
    const seconds = parseFloat(parts[2] || '0') || 0
    return hours * 3600 + minutes * 60 + seconds
  }
  return 0
}

/**
 * Gère les erreurs de chargement d'image
 */
function handleImageError(event: Event) {
  const img = event.target as HTMLImageElement
  img.style.display = 'none'
}

/**
 * Détecte un changement de morceau (utile pour les streams live)
 */
function hasTrackChanged(
  previous: BrowseItem | null,
  next: BrowseItem | null
): boolean {
  if (!previous || !next) return false
  return (
    previous.title !== next.title ||
    previous.album !== next.album ||
    (previous.artist || previous.creator) !== (next.artist || next.creator) ||
    previous.album_art !== next.album_art
  )
}

/**
 * Remet la progression à zéro pour un nouveau morceau
 */
function resetProgressForNewTrack(track: BrowseItem | null) {
  currentTime.value = 0
  if (audioPlayer.value) {
    audioPlayer.value.currentTime = 0
  }
  const d = track?.resources?.[0]?.duration
  duration.value = d ? parseDuration(d) : 0
}

/**
 * Détecte si un type MIME audio est supporté par le navigateur
 */
function canPlayAudioType(mimeType: string): boolean {
  const audio = document.createElement('audio')
  const support = audio.canPlayType(mimeType)
  return support === 'probably' || support === 'maybe'
}

/**
 * Extrait le type MIME audio depuis une URL
 */
function getAudioFormat(url: string): string | null {
  const urlLower = url.toLowerCase()

  // Détection par extension ou contenu de l'URL
  if (urlLower.includes('flac') || urlLower.endsWith('.flac')) {
    return 'audio/flac'
  }
  if (urlLower.includes('ogg') || urlLower.endsWith('.ogg') || urlLower.includes('vorbis')) {
    return 'audio/ogg'
  }
  if (urlLower.includes('mp3') || urlLower.endsWith('.mp3') || urlLower.includes('mpeg')) {
    return 'audio/mpeg'
  }
  if (urlLower.includes('wav') || urlLower.endsWith('.wav')) {
    return 'audio/wav'
  }
  if (urlLower.includes('opus')) {
    return 'audio/ogg' // Opus est généralement dans un container OGG
  }

  return null
}

/**
 * Sélectionne l'URI préférée en tenant compte du support navigateur
 */
function pickPreferredResourceUri(item: BrowseItem): string | null {
  const resources = item.resources || []
  if (resources.length === 0) return null

  // Ordre de préférence des formats (du meilleur au plus compatible)
  const preferredFormats = [
    { mime: 'audio/flac', keywords: ['flac'] },
    { mime: 'audio/ogg', keywords: ['ogg', 'vorbis', 'opus'] },
    { mime: 'audio/mpeg', keywords: ['mp3', 'mpeg'] },
    { mime: 'audio/wav', keywords: ['wav'] },
    { mime: 'audio/x-wav', keywords: ['wav'] },
  ]

  // Tester chaque format dans l'ordre de préférence
  for (const format of preferredFormats) {
    // Vérifier d'abord si le navigateur supporte ce format
    if (!canPlayAudioType(format.mime)) {
      console.log(`Format ${format.mime} non supporté par ce navigateur`)
      continue
    }

    // Chercher une ressource correspondant à ce format
    const matchingRes = resources.find((res) => {
      if (!res.url || !res.protocol_info) return false
      const protocolLower = res.protocol_info.toLowerCase()
      return format.keywords.some((keyword) => protocolLower.includes(keyword))
    })

    if (matchingRes?.url) {
      console.log(`Format sélectionné: ${format.mime}`, matchingRes.url)
      return matchingRes.url
    }
  }

  // Fallback: prendre n'importe quelle URL disponible
  const anyRes = resources.find((res) => !!res.url)
  if (anyRes?.url) {
    console.log('Fallback: utilisation de la première URL disponible', anyRes.url)
  }
  return anyRes?.url || null
}

/**
 * Ajoute un cache-buster pour forcer un nouveau stream côté navigateur
 */
function addCacheBuster(uri: string): string {
  const separator = uri.includes('?') ? '&' : '?'
  return `${uri}${separator}ts=${Date.now()}`
}

/**
 * Nettoie le timer de retry
 */
function clearRetryTimer() {
  if (retryTimer) {
    clearTimeout(retryTimer)
    retryTimer = null
  }
}

// Réinitialise l'état de retry (timer uniquement)
function resetAudioRetries() {
  clearRetryTimer()
}

/**
 * (Ré)initialise la source audio et démarre la lecture
 */
async function startPlaybackFromUri(uri: string, waitForDom = false) {
  if (waitForDom) {
    await nextTick()
  }

  const player = audioPlayer.value
  if (!player) {
    throw new Error('Audio player not ready')
  }

  const finalUri = addCacheBuster(uri)
  currentUri.value = finalUri

  player.pause()
  player.src = finalUri
  player.load()

  // Attendre que le navigateur ait bufferisé suffisamment de données
  // pour éviter les gaps audio entre les morceaux
  await waitForSufficientBuffer(player)

  await player.play()

  isPlaying.value = true
  audioError.value = null
  resetAudioRetries()
  startMetadataRefresh()
}

/**
 * Attend que le player ait suffisamment bufferisé avant de lancer la lecture
 */
async function waitForSufficientBuffer(player: HTMLAudioElement): Promise<void> {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      cleanup()
      console.warn('Buffer timeout - starting playback anyway')
      resolve()
    }, 5000) // Timeout de 5 secondes max

    const cleanup = () => {
      clearTimeout(timeout)
      player.removeEventListener('canplay', onCanPlay)
      player.removeEventListener('error', onError)
    }

    const onCanPlay = () => {
      // Vérifier si on a au moins 1-2 secondes de buffer
      if (player.buffered.length > 0) {
        const bufferedEnd = player.buffered.end(0)
        const currentTime = player.currentTime
        const bufferedSeconds = bufferedEnd - currentTime

        console.log(`Buffer disponible: ${bufferedSeconds.toFixed(2)}s`)

        // Attendre au moins 0.5 secondes de buffer pour réduire les gaps
        if (bufferedSeconds >= 0.5) {
          cleanup()
          resolve()
        }
      }
    }

    const onError = () => {
      cleanup()
      reject(new Error('Buffer error'))
    }

    player.addEventListener('canplay', onCanPlay)
    player.addEventListener('error', onError)

    // Check immédiatement au cas où c'est déjà prêt
    if (player.readyState >= 3) {
      onCanPlay()
    }
  })
}

</script>

<style scoped>
.music-player {
  padding: 20px;
  max-width: 1400px;
  margin: 0 auto;
}

.player-header {
  text-align: center;
  margin-bottom: 40px;
}

.player-header h1 {
  margin: 0;
  color: #00d4ff;
  font-size: 2.5rem;
}

.subtitle {
  color: #9aa0a6;
  font-size: 1rem;
  margin-top: 8px;
}

.error-banner {
  background: rgba(255, 68, 68, 0.15);
  border: 1px solid rgba(255, 68, 68, 0.4);
  color: #ff6b6b;
  padding: 16px;
  border-radius: 8px;
  margin-bottom: 20px;
}

/* Sources Section */
.sources-section {
  background: #1a1a2e;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 30px;
  border: 1px solid rgba(0, 212, 255, 0.3);
}

.sources-section h2 {
  margin-top: 0;
  color: #00d4ff;
}

.loading-message {
  color: #9aa0a6;
  text-align: center;
  padding: 20px;
}

.empty-message {
  color: #9aa0a6;
  text-align: center;
  padding: 20px;
  border: 1px dashed #444;
  border-radius: 8px;
}

.sources-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}

.source-card {
  background: #2a2a3e;
  border-radius: 8px;
  padding: 16px;
  cursor: pointer;
  transition: all 0.3s;
  border: 2px solid transparent;
  display: flex;
  align-items: center;
  gap: 16px;
}

.source-card:hover {
  background: #333350;
  transform: translateY(-2px);
}

.source-card.active {
  border-color: #00d4ff;
  background: rgba(0, 212, 255, 0.1);
}

.source-image img {
  width: 80px;
  height: 80px;
  object-fit: cover;
  border-radius: 8px;
}

.source-info {
  flex: 1;
}

.source-name {
  font-weight: bold;
  color: #fff;
  font-size: 1.1rem;
  margin-bottom: 8px;
}

.source-capabilities {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.capability-badge {
  background: rgba(0, 212, 255, 0.2);
  color: #00d4ff;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
}

/* Browser Section */
.browser-section {
  background: #1a1a2e;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 30px;
  border: 1px solid rgba(0, 212, 255, 0.3);
}

.browser-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.browser-header h2 {
  margin: 0;
  color: #00d4ff;
}

.btn-refresh {
  background: rgba(0, 212, 255, 0.15);
  border: 1px solid rgba(0, 212, 255, 0.4);
  color: #00d4ff;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  gap: 8px;
}

.btn-refresh:hover:not(:disabled) {
  background: rgba(0, 212, 255, 0.25);
}

.btn-refresh:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Breadcrumb */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
  padding-bottom: 16px;
  border-bottom: 1px solid rgba(0, 212, 255, 0.2);
}

.breadcrumb-item {
  background: rgba(0, 212, 255, 0.1);
  border: 1px solid rgba(0, 212, 255, 0.3);
  color: #00d4ff;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
}

.breadcrumb-item:hover {
  background: rgba(0, 212, 255, 0.2);
}

.breadcrumb-item.active {
  background: rgba(0, 212, 255, 0.25);
  font-weight: bold;
}

.breadcrumb-item:not(:last-child)::after {
  content: '›';
  margin-left: 8px;
  color: #666;
}

/* Containers */
.containers-section {
  margin-bottom: 30px;
}

.containers-section h3 {
  color: #00d4ff;
  margin-bottom: 12px;
}

.containers-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}

.container-item {
  background: rgba(0, 212, 255, 0.05);
  border: 1px solid rgba(0, 212, 255, 0.2);
  border-radius: 8px;
  padding: 14px;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  gap: 12px;
}

.container-item:hover {
  background: rgba(0, 212, 255, 0.1);
  border-color: rgba(0, 212, 255, 0.4);
}

.container-icon {
  font-size: 2rem;
}

.container-info {
  flex: 1;
}

.container-title {
  font-weight: 600;
  color: #fff;
  margin-bottom: 4px;
}

.container-meta {
  font-size: 0.85rem;
  color: #9aa0a6;
}

/* Items */
.items-section {
  margin-bottom: 30px;
}

.items-section h3 {
  color: #00d4ff;
  margin-bottom: 12px;
}

.items-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.item-card {
  background: #2a2a3e;
  border-radius: 8px;
  padding: 12px;
  display: flex;
  align-items: center;
  gap: 12px;
  transition: all 0.2s;
  border: 2px solid transparent;
}

.item-card:hover {
  background: #333350;
}

.item-card.active {
  border-color: rgba(0, 212, 255, 0.5);
}

.item-card.playing {
  border-color: #00d4ff;
  background: rgba(0, 212, 255, 0.1);
}

.item-number {
  font-weight: bold;
  color: #666;
  min-width: 30px;
  text-align: center;
}

.item-cover {
  width: 60px;
  height: 60px;
  flex-shrink: 0;
}

.item-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 6px;
}

.item-cover-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 212, 255, 0.1);
  border-radius: 6px;
  font-size: 1.5rem;
}

.item-info {
  flex: 1;
  min-width: 0;
}

.item-title {
  font-weight: 600;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 4px;
}

.item-artist {
  color: #00d4ff;
  font-size: 0.9rem;
  margin-bottom: 2px;
}

.item-album {
  color: #9aa0a6;
  font-size: 0.85rem;
}

.item-actions {
  display: flex;
  gap: 8px;
}

.btn-play-item {
  background: rgba(46, 204, 113, 0.2);
  border: 1px solid rgba(46, 204, 113, 0.4);
  color: #2ecc71;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 1.1rem;
  transition: all 0.2s;
  min-width: 50px;
}

.btn-play-item:hover {
  background: rgba(46, 204, 113, 0.3);
}

.btn-play-item.active {
  background: rgba(46, 204, 113, 0.4);
  border-color: #2ecc71;
}

/* Now Playing */
.now-playing-section {
  background: linear-gradient(135deg, #1a1a2e 0%, #0f0f1e 100%);
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 30px;
  border: 2px solid rgba(0, 212, 255, 0.4);
  position: sticky;
  bottom: 20px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.now-playing-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.now-playing-header h2 {
  margin: 0;
  color: #00d4ff;
}

.btn-stop {
  background: rgba(231, 76, 60, 0.2);
  border: 1px solid rgba(231, 76, 60, 0.4);
  color: #e74c3c;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.2s;
}

.btn-stop:hover {
  background: rgba(231, 76, 60, 0.3);
}

.audio-error {
  background: rgba(255, 68, 68, 0.15);
  border: 1px solid rgba(255, 68, 68, 0.4);
  color: #ff6b6b;
  padding: 12px;
  border-radius: 6px;
  margin-bottom: 16px;
}

.now-playing-content {
  display: flex;
  gap: 20px;
  align-items: center;
  margin-bottom: 16px;
}

.now-playing-cover {
  width: 120px;
  height: 120px;
  flex-shrink: 0;
}

.now-playing-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
  border: 2px solid rgba(0, 212, 255, 0.3);
}

.now-playing-cover-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 212, 255, 0.1);
  border-radius: 8px;
  font-size: 3rem;
  border: 2px solid rgba(0, 212, 255, 0.3);
}

.now-playing-info {
  flex: 1;
}

.now-playing-title {
  font-size: 1.5rem;
  font-weight: bold;
  color: #fff;
  margin-bottom: 8px;
}

.now-playing-artist {
  font-size: 1.2rem;
  color: #00d4ff;
  margin-bottom: 4px;
}

.now-playing-album {
  font-size: 1rem;
  color: #9aa0a6;
  margin-bottom: 8px;
}

.now-playing-source {
  font-size: 0.85rem;
  color: #666;
}

/* Progress Bar */
.progress-container {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 0;
  width: 100%;
}

.progress-time {
  font-size: 0.85rem;
  color: #9aa0a6;
  font-variant-numeric: tabular-nums;
  min-width: 45px;
  text-align: center;
}

.progress-bar {
  flex: 1;
  height: 6px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #00d4ff, #00a8cc);
  border-radius: 3px;
  transition: width 0.1s linear;
  box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
}

.audio-player-container {
  display: none;
}

/* Debug Info */
.debug-info {
  background: rgba(0, 0, 0, 0.3);
  border-radius: 8px;
  padding: 16px;
  border: 1px solid #333;
}

.debug-info summary {
  cursor: pointer;
  color: #9aa0a6;
  font-weight: 600;
  margin-bottom: 12px;
}

.debug-content {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: 'Courier New', monospace;
  font-size: 0.85rem;
  color: #9aa0a6;
}

.debug-content strong {
  color: #00d4ff;
}

/* Responsive */
@media (max-width: 768px) {
  .sources-grid {
    grid-template-columns: 1fr;
  }

  .containers-list {
    grid-template-columns: 1fr;
  }

  .now-playing-content {
    flex-direction: column;
    text-align: center;
  }

  .now-playing-cover {
    width: 150px;
    height: 150px;
  }
}
</style>
