@extends('layouts.overlay')

@section('title', 'NC-MUSIC-BOX')

@section('styles')
<style>
  body.overlay-body {
    display: grid;
    grid-template-rows: auto 1fr auto;
    gap: 22px;
    font-family: var(--mono);
  }
  .tui {
    display: grid;
    grid-template-columns: 1fr 36ch;
    gap: 24px;
    min-height: 0;
  }
  .pane {
    position: relative;
    background: var(--term-bg);
    border: 2px solid var(--term-edge);
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    box-shadow:
      0 0 60px rgba(80, 220, 100, 0.10),
      inset 0 0 90px rgba(0, 30, 0, 0.65);
  }
  .pane::after {
    content: '';
    position: absolute; inset: 0;
    pointer-events: none;
    background: repeating-linear-gradient(
      to bottom,
      rgba(0,0,0,0)    0px,
      rgba(0,0,0,0)    2px,
      rgba(0,0,0,0.28) 3px,
      rgba(0,0,0,0.28) 4px
    );
    opacity: 0.55;
    mix-blend-mode: multiply;
  }
  .pane > .pane-tab {
    position: absolute;
    top: -14px; left: 28px;
    padding: 2px 14px;
    background: var(--bg);
    color: var(--term-fg-bright);
    font-size: 16px;
    letter-spacing: 0.32em;
    text-shadow: 0 0 6px var(--term-glow);
    z-index: 1;
  }
  .pane.terminal {
    /* Override hud.css `.terminal { overflow: hidden }` so the `┤ TERMINAL ├`
       pane-tab can stick out 14px above the pane border. The scrolling
       clip still happens inside #termOutput. */
    overflow: visible;
    padding: 28px 36px;
    font-size: 24px;
    line-height: 1.55;
    color: var(--term-fg);
    text-shadow: 0 0 6px var(--term-glow);
  }
  .pane.np-pane { overflow: visible; }
  .np-pane {
    padding: 24px 22px 20px;
    font-size: 18px;
    color: var(--term-fg);
    text-shadow: 0 0 6px var(--term-glow);
    display: grid;
    grid-template-rows: auto auto auto auto 1fr auto auto;
    gap: 18px;
  }
  .np-art {
    position: relative;
    aspect-ratio: 1 / 1;
    background: rgba(0, 30, 0, 0.45);
    border: 1px solid var(--term-edge-soft);
    overflow: hidden;
  }
  .np-art img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 320ms ease;
  }
  .np-art img.on { opacity: 1; }
  .np-art .placeholder {
    position: absolute; inset: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 10px; color: var(--term-fg-dim); text-align: center;
    transition: opacity 320ms ease;
  }
  .np-art .placeholder.off { opacity: 0; }
  .np-art .placeholder svg { opacity: 0.5; }
  .np-art .placeholder .ph-text {
    font-size: 14px;
    letter-spacing: 0.42em;
    color: rgba(95, 220, 98, 0.65);
  }
  .np-meta {
    display: grid; grid-template-columns: 6.5em 1fr;
    row-gap: 6px; column-gap: 12px;
    font-size: 17px; align-content: start;
  }
  .np-meta .key { color: var(--term-fg-dim); letter-spacing: 0.18em; }
  .np-meta .val {
    color: var(--term-fg-bright);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    min-width: 0;
  }
  .np-meta .val.empty { color: var(--term-fg-dim); }
  .np-stats {
    display: flex; flex-direction: column; gap: 8px;
    padding-top: 6px;
    border-top: 1px dashed var(--term-edge-soft);
  }
  .np-stats .stats-label {
    font-size: 14px; letter-spacing: 0.32em;
    color: var(--term-fg-dim);
    text-shadow: 0 0 4px var(--term-glow);
  }
  .np-stats .stats-grid {
    display: grid; grid-template-columns: 6.5em 1fr;
    row-gap: 4px; column-gap: 12px;
    font-size: 16px;
  }
  .np-stats .stats-grid .key { color: var(--term-fg-dim); letter-spacing: 0.16em; }
  .np-stats .stats-grid .val {
    color: var(--term-fg);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .np-stats .stats-grid .val.empty { color: var(--term-fg-dim); }
  .np-stats .stats-grid .val.lossless {
    color: var(--term-fg-bright);
    text-shadow: 0 0 8px var(--term-glow);
  }
  .np-spectrum { min-height: 64px; }
  .np-progress { display: grid; grid-template-rows: auto auto; gap: 6px; }
  .np-progress .bar {
    position: relative; height: 6px;
    background: rgba(95, 220, 98, 0.14);
    border: 1px solid var(--term-edge-soft);
  }
  .np-progress .bar .fill {
    position: absolute; inset: 0 auto 0 0;
    width: 0%;
    background: var(--term-fg);
    box-shadow: 0 0 8px var(--term-glow);
    transition: width 350ms linear;
  }
  .np-progress .time {
    font-size: 15px; color: var(--term-fg-dim);
    letter-spacing: 0.10em;
    display: flex; justify-content: space-between;
  }
  .np-progress .time .now { color: var(--term-fg-bright); }
</style>
@endsection

@push('data-scripts')
<script>window.__TRACK_COUNT = @json($trackCount);</script>
<script src="{{ asset('data/playlist.js') }}"></script>
@endpush

@section('content')
  @include('partials.hud-strip', [
    'variant' => 'onair',
    'label'   => 'ON AIR',
    'idText'  => 'OPHI-118 // MUSIC',
    'signalGlyph' => '▮▮▮▮▯',
    'signalProfile' => 'strong',
  ])

  <main class="tui">
    <section class="pane terminal">
      <span class="pane-tab">┤ TERMINAL ├</span>
      <div id="termOutput"><div id="pinnedBlock"><div class="pin-spacer"></div><div class="term-line next-line"><span class="next-arrow">↳</span><span class="next-label">next:</span><span class="next-title empty" data-slot="0">— end of queue —</span></div><div class="term-line next-line"><span class="next-arrow">↳</span><span class="next-label">then:</span><span class="next-title empty" data-slot="1">—</span></div><div class="term-line next-line"><span class="next-arrow">↳</span><span class="next-label">then:</span><span class="next-title empty" data-slot="2">—</span></div><div class="pin-spacer"></div><div class="term-line np-line"><span class="np-arrow">▶</span><span class="np-title">connecting to daemon…</span><span class="np-time">[--:-- / --:--]</span></div></div></div>
    </section>

    <aside class="pane np-pane">
      <span class="pane-tab">┤ NOW PLAYING ├</span>
      <div class="np-art">
        <img id="cover" alt="">
        <div class="placeholder" id="placeholder">
          <svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
            <rect x="5" y="5" width="46" height="46" rx="2" stroke="#5fdc62" stroke-width="2"/>
            <circle cx="28" cy="28" r="12" stroke="#5fdc62" stroke-width="2"/>
            <circle cx="28" cy="28" r="3"  fill="#5fdc62"/>
          </svg>
          <span class="ph-text">— NO ART —</span>
        </div>
      </div>
      <div class="np-meta">
        <span class="key">ARTIST</span><span class="val empty" id="metaArtist">—</span>
        <span class="key">TITLE</span> <span class="val empty" id="metaTitle">—</span>
        <span class="key">ALBUM</span> <span class="val empty" id="metaAlbum">—</span>
        <span class="key">YEAR</span>  <span class="val empty" id="metaYear">—</span>
        <span class="key">GENRE</span> <span class="val empty" id="metaGenre">—</span>
      </div>
      <div class="np-stats">
        <div class="stats-label">─ FILE ─</div>
        <div class="stats-grid">
          <span class="key">FORMAT</span> <span class="val empty" id="fileFormat">—</span>
          <span class="key">QUALITY</span><span class="val empty" id="fileQuality">—</span>
          <span class="key">BITRATE</span><span class="val empty" id="fileBitrate">—</span>
        </div>
      </div>
      <div class="np-stats">
        <div class="stats-label">─ LIBRARY ─</div>
        <div class="stats-grid">
          <span class="key">ARTISTS</span> <span class="val empty" id="statArtists">—</span>
          <span class="key">ALBUMS</span>  <span class="val empty" id="statAlbums">—</span>
          <span class="key">SONGS</span>   <span class="val empty" id="statSongs">—</span>
          <span class="key">PLAYTIME</span><span class="val empty" id="statPlaytime">—</span>
          <span class="key">QUEUE</span>   <span class="val empty" id="statQueue">—</span>
        </div>
      </div>
      <div aria-hidden="true"></div>
      <div class="np-spectrum" id="spectrumSlot"></div>
      <div class="np-progress">
        <div class="bar"><div class="fill" id="progFill"></div></div>
        <div class="time">
          <span class="now" id="progElapsed">00:00</span>
          <span id="progTotal">00:00</span>
        </div>
      </div>
    </aside>
  </main>

  <section class="status-strip">
    <span class="label">CHAT</span>
    <span class="cmd-group">
      <span><span class="cmd">!skip</span><span class="desc">next track</span></span>
      <span><span class="cmd">!queue</span><span class="desc">peek next 3</span></span>
      <span><span class="cmd">!info</span><span class="desc">full metadata</span></span>
    </span>
  </section>
@endsection

@section('scripts')
<script>
  'use strict';
  const pad = HUD.pad;
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const fmtClock = s => (!isFinite(s) || s < 0)
    ? '--:--' : `${pad(Math.floor(s / 60))}:${pad(Math.floor(s % 60))}`;

  const playlistData = window.__PLAYLIST || { tracks: [] };
  const trackCount = (playlistData.tracks || []).length || (window.__TRACK_COUNT || 0);

  const term         = document.getElementById('termOutput');
  const pinnedBlock  = document.getElementById('pinnedBlock');
  const npTitle      = pinnedBlock.querySelector('.np-title');
  const npTime       = pinnedBlock.querySelector('.np-time');
  const nextSlots    = [0, 1, 2].map(i => pinnedBlock.querySelector(`[data-slot="${i}"]`));
  const nextRows     = nextSlots.map(slot => slot.closest('.next-line'));
  const PROMPT       = 'OPHI-118://> ';
  const MAX_TERM_LINES = 200;
  const MODE_KEYS    = ['repeat', 'random', 'single'];
  let bootStarted = false;
  let bootFinished = false;
  let lastPlaybackModes = null;
  let pendingState = null;

  function pruneTerminal() {
    while (term.children.length > MAX_TERM_LINES) {
      const first = term.firstElementChild;
      if (!first || first === pinnedBlock) break;
      term.removeChild(first);
    }
    while (term.children.length > 1) {
      let total = 0;
      for (const c of term.children) total += c.getBoundingClientRect().height;
      if (total <= term.clientHeight) break;
      const first = term.firstElementChild;
      if (!first || first === pinnedBlock) break;
      term.removeChild(first);
    }
  }
  function logBeforePins(text, cls = 'term-out', prompt = '> ') {
    const div = document.createElement('div');
    div.className = 'term-line';
    if (prompt) {
      const p = document.createElement('span');
      p.className = 'term-prompt';
      p.textContent = prompt;
      div.appendChild(p);
    }
    const t = document.createElement('span');
    if (cls) t.className = cls;
    t.textContent = text;
    div.appendChild(t);
    term.insertBefore(div, pinnedBlock);
    pruneTerminal();
  }
  async function typeCommand(text) {
    const div = document.createElement('div');
    div.className = 'term-line';
    const p = document.createElement('span');
    p.className = 'term-prompt';
    p.textContent = PROMPT;
    div.appendChild(p);
    const body = document.createElement('span');
    body.className = 'term-out';
    div.appendChild(body);
    term.insertBefore(div, pinnedBlock);
    for (const c of text) {
      body.textContent += c;
      await sleep(38);
    }
    await sleep(280);
    return body;
  }
  function normalizePlayback(playback) {
    const src = playback && typeof playback === 'object' ? playback : {};
    const normalized = {};
    for (const key of MODE_KEYS) normalized[key] = !!src[key];
    normalized.consume = !!src.consume;
    normalized.volume = Number.isFinite(src.volume) ? src.volume : null;
    return normalized;
  }
  function formatBootCommand(playback) {
    const p = normalizePlayback(playback);
    const parts = ['music', '--boot'];
    if (p.volume !== null) parts.push(`--volume=${p.volume}%`);
    MODE_KEYS.forEach(key => { if (p[key]) parts.push(`--${key}`); });
    if (p.consume) parts.push('--consume');
    return parts.join(' ');
  }
  function nextPreviewConfig(playback) {
    const p = normalizePlayback(playback);
    if (p.single && p.repeat) return { count: 1, empty: '— repeat single armed —' };
    if (p.single) return { count: 0, empty: '— single mode —' };
    if (p.random) return { count: 1, empty: '— shuffle active —' };
    return { count: 3, empty: '— end of queue —' };
  }
  async function logModeChange(mode, enabled) {
    await typeCommand(`music --${mode}=${enabled ? 'true' : 'false'}`);
    logBeforePins(`[audio] ${mode} mode ${enabled ? 'enabled' : 'disabled'}`, 'term-out', null);
  }
  let modeLogChain = Promise.resolve();
  function trackModeChanges(playback) {
    const current = normalizePlayback(playback);
    if (!lastPlaybackModes) { lastPlaybackModes = current; return; }
    MODE_KEYS.forEach(key => {
      if (current[key] !== lastPlaybackModes[key]) {
        modeLogChain = modeLogChain.then(() => logModeChange(key, current[key]));
      }
    });
    lastPlaybackModes = current;
  }

  const coverImg    = document.getElementById('cover');
  const placeholder = document.getElementById('placeholder');
  const metaArtist  = document.getElementById('metaArtist');
  const metaTitle   = document.getElementById('metaTitle');
  const metaAlbum   = document.getElementById('metaAlbum');
  const metaYear    = document.getElementById('metaYear');
  const metaGenre   = document.getElementById('metaGenre');
  const progFill    = document.getElementById('progFill');
  const progElapsed = document.getElementById('progElapsed');
  const progTotal   = document.getElementById('progTotal');
  const statArtists  = document.getElementById('statArtists');
  const statAlbums   = document.getElementById('statAlbums');
  const statSongs    = document.getElementById('statSongs');
  const statPlaytime = document.getElementById('statPlaytime');
  const statQueue    = document.getElementById('statQueue');
  const fileFormat   = document.getElementById('fileFormat');
  const fileQuality  = document.getElementById('fileQuality');
  const fileBitrate  = document.getElementById('fileBitrate');

  const LOSSLESS_FORMATS = new Set(['FLAC', 'ALAC', 'WAV', 'WAVPACK', 'APE']);
  function fmtSampleRate(hz) {
    if (!hz || !isFinite(hz) || hz <= 0) return null;
    const khz = hz / 1000;
    const s = (khz % 1 === 0) ? khz.toFixed(0) : khz.toFixed(1);
    return `${s} kHz`;
  }
  function fmtQuality(audio) {
    const sr = fmtSampleRate(audio.samplerate);
    const bits = (typeof audio.bits === 'number' && audio.bits > 0)
      ? `${audio.bits}-bit` : null;
    if (sr && bits) return `${sr} · ${bits}`;
    return sr || bits || null;
  }
  const fmtCount = n => (typeof n === 'number' && isFinite(n))
    ? n.toLocaleString('en-US') : null;
  function setMeta(el, value) {
    if (value && String(value).trim()) {
      el.textContent = value;
      el.classList.remove('empty');
    } else {
      el.textContent = '—';
      el.classList.add('empty');
    }
  }
  let lastCoverHash = undefined;
  function showArt(_path, hash) {
    if (hash === lastCoverHash) return;
    lastCoverHash = hash;
    if (!hash) {
      coverImg.classList.remove('on');
      placeholder.classList.remove('off');
      coverImg.removeAttribute('src');
      return;
    }
    coverImg.onload  = () => { coverImg.classList.add('on'); placeholder.classList.add('off'); };
    coverImg.onerror = () => { coverImg.classList.remove('on'); placeholder.classList.remove('off'); };
    coverImg.src = `/cover.jpg?v=${hash}`;
  }

  let lastTrackFile = null;
  function renderState(s) {
    if (lastTrackFile !== null && s.file && s.file !== lastTrackFile && s.title) {
      logBeforePins(`[audio] now: ${s.title}`);
    }
    if (s.file) lastTrackFile = s.file;

    npTitle.textContent = s.title || '—';
    npTime.textContent  = `[${fmtClock(s.currentTime)} / ${fmtClock(s.duration)}]`;
    const titles = Array.isArray(s.nextTitles) ? s.nextTitles : [];
    const preview = nextPreviewConfig(s.playback);
    nextSlots.forEach((slot, i) => {
      const v = i < preview.count ? titles[i] : null;
      if (v) {
        slot.textContent = v;
        slot.classList.remove('empty');
        nextRows[i].style.display = '';
      } else {
        if (i === 0) {
          slot.textContent = preview.empty;
          slot.classList.add('empty');
          nextRows[i].style.display = '';
        } else {
          slot.textContent = '';
          slot.classList.add('empty');
          nextRows[i].style.display = 'none';
        }
      }
    });

    showArt(s.coverPath, s.coverHash);
    setMeta(metaArtist, s.artist);
    setMeta(metaTitle,  s.trackTitle);
    setMeta(metaAlbum,  s.album);
    setMeta(metaYear,   s.year);
    setMeta(metaGenre,  s.genre);

    const dur = Number(s.duration) || 0;
    const cur = Number(s.currentTime) || 0;
    const pct = dur > 0 ? Math.max(0, Math.min(100, (cur / dur) * 100)) : 0;
    progFill.style.width = `${pct}%`;
    progElapsed.textContent = fmtClock(cur);
    progTotal.textContent   = fmtClock(dur);

    const audio = s.audio || {};
    setMeta(fileFormat, audio.format);
    fileFormat.classList.toggle('lossless', LOSSLESS_FORMATS.has(audio.format));
    setMeta(fileQuality, fmtQuality(audio));
    setMeta(fileBitrate, audio.bitrate ? `${fmtCount(audio.bitrate)} kbps` : null);

    const stats = s.stats;
    if (stats) {
      setMeta(statArtists,  fmtCount(stats.artists));
      setMeta(statAlbums,   fmtCount(stats.albums));
      setMeta(statSongs,    fmtCount(stats.songs));
      setMeta(statPlaytime, stats.dbPlaytime);
    }
    const total = Number(s.total) || 0;
    const idx   = Number.isInteger(s.index) ? s.index : -1;
    setMeta(statQueue, total > 0 && idx >= 0 ? `${idx + 1} / ${total}` : null);

    pruneTerminal();
  }
  async function bootFromState(s) {
    if (bootStarted) return;
    bootStarted = true;
    lastPlaybackModes = normalizePlayback(s.playback);
    await typeCommand(formatBootCommand(s.playback));
    logBeforePins('[audio] subscribing to mpd:state events', 'term-out');
    await sleep(220);
    if (trackCount > 0) {
      logBeforePins(`[audio] library: ${trackCount} tracks`, 'term-out');
    } else {
      logBeforePins('[audio] library manifest empty', 'term-dim');
    }
    await sleep(180);
    logBeforePins('[audio] standby for transmission ✓', 'term-out');
    bootFinished = true;
    if (pendingState) renderState(pendingState);
  }
  function applyState(s) {
    pendingState = s;
    if (!bootStarted) { bootFromState(s); return; }
    if (!bootFinished) return;
    renderState(s);
    trackModeChanges(s.playback);
  }
  function setOffline(msg) {
    npTitle.textContent = msg;
    npTime.textContent  = '[--:-- / --:--]';
  }

  (async () => {
    await sleep(450);
    logBeforePins('[audio] waiting for mpd state', 'term-dim', null);
    await sleep(280);
    OBSWSBootstrap.onState(applyState, {
      onClose: () => {
        logBeforePins('[audio] daemon connection lost — retrying', 'term-dim');
        setOffline('daemon disconnected');
      },
    });
  })();
</script>
@endsection
