// Music: the in-game jukebox library. Owner only (gated in app.jsx nav and src/index.js). Tracks are
// OGG Vorbis files stored by the LAR API in R2; uploading or deleting one pushes the new catalog to
// running games live. Everything here talks to /api/music/*, which proxies the API's /admin/music.

const MUSIC_MAX_BYTES = 15 * 1024 * 1024;

function musicSize(bytes) {
  if (bytes == null) return "—";
  if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + " MB";
  return Math.max(1, Math.round(bytes / 1024)) + " KB";
}

// A safe track id: lowercase, only a-z 0-9 _ -, starts with a letter or digit, <= 64 chars.
function musicSlug(s) {
  const slug = String(s || "").toLowerCase().trim()
    .replace(/[^a-z0-9_-]+/g, "-")
    .replace(/^[-_]+/, "")
    .replace(/[-_]+$/, "")
    .slice(0, 64);
  return slug || "track";
}

const MUSIC_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;

// Raw OGG upload. Can't go through api()/larApi (they JSON-stringify the body), so this posts the
// file bytes directly with the metadata in the query string, mirroring api()'s 401 + error handling.
async function uploadTrack(id, title, artist, file) {
  const q = `title=${encodeURIComponent(title)}` + (artist ? `&artist=${encodeURIComponent(artist)}` : "");
  const res = await fetch(`/api/music/${encodeURIComponent(id)}?${q}`, {
    method: "PUT",
    headers: { "Content-Type": "audio/ogg" },
    body: file,
  });
  if (res.status === 401) { window.location.href = "/login.html"; throw new Error("Unauthorized"); }
  const json = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(json.error || `Upload failed (${res.status})`);
  return json;
}

function MusicUpload({ onUploaded }) {
  const [file, setFile] = React.useState(null);
  const [id, setId] = React.useState("");
  const [idTouched, setIdTouched] = React.useState(false);
  const [title, setTitle] = React.useState("");
  const [artist, setArtist] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const inputRef = React.useRef(null);

  const pick = (f) => {
    setError(null);
    if (!f) return;
    const okType = f.type ? f.type.toLowerCase().startsWith("audio/ogg") : /\.ogg$/i.test(f.name);
    if (!okType) { setError("Only OGG Vorbis (.ogg) files are accepted."); return; }
    if (f.size > MUSIC_MAX_BYTES) { setError(`That file is ${musicSize(f.size)}; the limit is 15 MB.`); return; }
    setFile(f);
    const base = f.name.replace(/\.[^.]+$/, "");
    if (!title.trim()) setTitle(base);
    if (!idTouched) setId(musicSlug(base));
  };

  const idValid = MUSIC_ID_RE.test(id);
  const ready = file && title.trim() && idValid && !busy;

  const submit = async () => {
    setBusy(true); setError(null);
    try {
      const res = await uploadTrack(id, title.trim(), artist.trim(), file);
      setFile(null); setId(""); setIdTouched(false); setTitle(""); setArtist("");
      if (inputRef.current) inputRef.current.value = "";
      onUploaded(res);
    } catch (e) { setError(e.message); }
    setBusy(false);
  };

  return (
    <div className="card card-pad">
      <div className="card-head"><div>
        <div className="card-title">Add a track</div>
        <div className="card-sub">OGG Vorbis only, up to 15 MB. Uploading updates the in-game library live.</div>
      </div></div>

      <div className="field">
        <label>File</label>
        <input ref={inputRef} className="input" type="file" accept=".ogg,audio/ogg"
          onChange={(e) => pick(e.target.files?.[0])}/>
        {file && <div className="tiny muted mt-4">{file.name} · {musicSize(file.size)}</div>}
      </div>

      <div className="field mt-8"><label>Title</label>
        <input className="input" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Lobby Theme"/></div>

      <div className="field mt-8"><label>Artist (optional)</label>
        <input className="input" value={artist} onChange={(e) => setArtist(e.target.value)} placeholder="SoreBones"/></div>

      <div className="field mt-8"><label>Track id</label>
        <input className="input mono" value={id} placeholder="lobby-theme"
          onChange={(e) => { setIdTouched(true); setId(e.target.value); }}/>
        <div className="tiny muted mt-4">
          {idTouched && id && !idValid
            ? <span style={{color:"var(--danger)"}}>Use a–z, 0–9, _ or -; must start with a letter or digit.</span>
            : "Permanent name in R2. Re-uploading the same id replaces that track's file."}
        </div>
      </div>

      <button className="btn btn-sm btn-accent mt-12" disabled={!ready} onClick={submit}>
        <Icon name="publish" size={12}/> {busy ? "Uploading…" : "Upload track"}
      </button>
      <LarError error={error}/>
    </div>
  );
}

function MusicRow({ track, onChanged }) {
  const [playing, setPlaying] = React.useState(false);
  const [busy, setBusy] = React.useState(false);

  const patch = async (body) => {
    setBusy(true);
    try { onChanged(await api(`/api/music/${track.id}`, { method: "PATCH", body })); }
    catch (e) { alert(e.message); }
    setBusy(false);
  };

  const rename = () => {
    const title = prompt("Track title", track.title);
    if (title == null) return;
    const artist = prompt("Artist (leave blank for none)", track.artist || "");
    if (artist == null) return;
    patch({ title: title.trim() || track.title, artist: artist.trim() });
  };

  const remove = async () => {
    if (!confirm(`Delete "${track.title}"? This removes the file and cannot be undone.`)) return;
    setBusy(true);
    try { onChanged(await api(`/api/music/${track.id}`, { method: "DELETE" })); }
    catch (e) { alert(e.message); setBusy(false); }
  };

  return (
    <>
      <tr>
        <td>
          <div style={{fontWeight:600, fontSize:12.5}}>{track.title}</div>
          <div className="tiny muted mono">{track.id}</div>
        </td>
        <td className="tiny">{track.artist || <span className="muted">—</span>}</td>
        <td className="mono tiny" style={{textAlign:"right"}}>{musicSize(track.sizeBytes)}</td>
        <td>{track.enabled
          ? <span className="tag tag-success">In rotation</span>
          : <span className="tag">Hidden</span>}</td>
        <td className="tiny muted">{timeSince(larIso(track.updatedAt))}</td>
        <td style={{textAlign:"right", whiteSpace:"nowrap"}}>
          <button className="btn btn-sm btn-ghost" title="Preview" onClick={() => setPlaying(p => !p)}><Icon name="play" size={12}/></button>
          <button className="btn btn-sm btn-ghost" disabled={busy} onClick={rename}><Icon name="edit" size={12}/></button>
          <button className="btn btn-sm btn-ghost" disabled={busy} onClick={() => patch({ enabled: !track.enabled })}>
            {track.enabled ? "Hide" : "Show"}
          </button>
          <button className="btn btn-sm btn-danger" disabled={busy} onClick={remove}><Icon name="trash" size={12}/></button>
        </td>
      </tr>
      {playing && (
        <tr><td colSpan={6} style={{paddingTop:0}}>
          <audio controls autoPlay src={`/api/music/${track.id}/file`} style={{width:"100%"}}
            onError={() => {}}/>
        </td></tr>
      )}
    </>
  );
}

function LarMusicPage() {
  const { data, error, loading, reload } = useLarLoad(() => api("/api/music"), []);
  const [notice, setNotice] = React.useState(null);

  const afterChange = (res) => { setNotice(res); reload(); };
  const tracks = data?.tracks || [];

  return (
    <div className="page">
      <div className="page-head"><div>
        <h1 className="page-title">Music</h1>
        <p className="page-subtitle">The cloud jukebox staff play to the room. Owner only. Changes reach running games within a second.</p>
      </div></div>

      <div className="row lar-row-1-2">
        <MusicUpload onUploaded={afterChange}/>

        <div className="card card-flush">
          <div className="toolbar">
            <span className="tiny muted">{data ? `${tracks.length} track${tracks.length === 1 ? "" : "s"} · version ${data.version}` : ""}</span>
            <div style={{flex:1}}/>
            <button className="btn btn-sm btn-ghost" onClick={reload}><Icon name="refund" size={12}/> Refresh</button>
          </div>
          {notice && <div style={{padding:"0 16px"}}><LarDelivered result={notice}/></div>}
          {error && <div className="tiny" style={{color:"var(--danger)", padding:"0 16px 12px"}}>{error}</div>}
          <table className="tbl">
            <thead><tr>
              <th>Track</th><th>Artist</th><th style={{textAlign:"right"}}>Size</th><th>Status</th><th>Updated</th><th></th>
            </tr></thead>
            <tbody>
              {tracks.map(t => <MusicRow key={t.id} track={t} onChanged={afterChange}/>)}
              {data && !tracks.length && <tr><td colSpan={6}><div className="empty"><h3>No music yet</h3><p>Upload an OGG track to start the library.</p></div></td></tr>}
              {loading && <tr><td colSpan={6}><div className="empty"><h3>Loading…</h3></div></td></tr>}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

window.LarMusicPage = LarMusicPage;
