// ============ BACKEND CLIENT ============
// This module talks to the djanything.com backend for YouTube -> audio extraction.
// If no backend is configured (window.DJANYTHING_API_URL/window.MIXFM_API_URL
// unset), it falls back to a
// mock that generates a synthetic track so the UI still demos end-to-end.
//
// Real backend contract lives in BACKEND_CONTRACT.md.

const BackendClient = (() => {
  const RAW_BASE = window.DJANYTHING_API_URL || window.MIXFM_API_URL || '';
  const BASE = String(RAW_BASE).replace(/\/+$/, ''); // e.g. "https://api.djanything.com"
  const MOCK = !BASE;

  function ytIdFromUrl(url) {
    if (!url) return null;
    const s = url.trim();
    if (/^[A-Za-z0-9_-]{11}$/.test(s)) return s;
    const m = s.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([A-Za-z0-9_-]{11})/);
    return m ? m[1] : null;
  }

  function playlistIdFromUrl(url) {
    if (!url) return null;
    const m = url.match(/[?&]list=([A-Za-z0-9_-]+)/);
    return m ? m[1] : null;
  }

  function normalizeAnalysis(meta = {}) {
    const bpm = Number(meta.bpm);
    const firstBeat = Number(meta.firstBeat);
    const confidence = Number(meta.analyzeConfidence ?? meta.confidence);
    const beatGrid = Array.isArray(meta.beatGrid)
      ? meta.beatGrid.map(Number).filter(Number.isFinite).slice(0, 512)
      : null;
    const tempoMap = Array.isArray(meta.tempoMap)
      ? meta.tempoMap
          .map(p => ({
            time: Number(p.time),
            bpm: Number(p.bpm),
            beat: p.beat == null ? undefined : Number(p.beat),
          }))
          .filter(p => Number.isFinite(p.time) && Number.isFinite(p.bpm))
          .slice(0, 1024)
      : null;

    const out = {};
    if (Number.isFinite(bpm) && bpm > 0) out.bpm = bpm;
    if (Number.isFinite(firstBeat) && firstBeat >= 0) out.firstBeat = firstBeat;
    if (beatGrid && beatGrid.length) out.beatGrid = beatGrid;
    if (Number.isFinite(confidence)) out.analyzeConfidence = Math.max(0, Math.min(1, confidence));
    if (tempoMap && tempoMap.length) out.tempoMap = tempoMap;
    return out;
  }

  // --- MOCK MODE ---
  async function mockFetchTrack(url, onProgress) {
    const id = ytIdFromUrl(url) || 'mock' + Date.now();
    const bpm = 100 + (id.charCodeAt(0) % 40);
    const key = ['8A','9A','7A','8B','9B','10A'][id.charCodeAt(1) % 6];
    const title = `YouTube · ${id.slice(0,6)}`;
    const artist = 'Mock download';
    // simulate progressive download
    const steps = 20;
    for (let i = 1; i <= steps; i++) {
      await new Promise(r => setTimeout(r, 120 + Math.random() * 80));
      onProgress?.(i / steps);
    }
    // Generate a synthetic audio buffer via the engine
    const ctx = AudioEngine.ensureCtx();
    const hue = id.charCodeAt(2) % 12;
    const seconds = 150 + (id.charCodeAt(3) % 90);
    const buffer = synthesizeMockBuffer(ctx, seconds, bpm, hue);
    return { ytId: id, title, artist, bpm, key, duration: seconds, thumb: null, buffer };
  }

  // --- YouTube search (mock + real) ---
  async function mockSearchYouTube(query, limit = 5) {
    await new Promise(r => setTimeout(r, 250 + Math.random() * 250));
    const q = (query || '').trim();
    const out = [];
    for (let i = 0; i < limit; i++) {
      const id = btoa(q + i).replace(/[+/=]/g, '').slice(0, 11);
      const labels = ['(Official Audio)', '(Official Video)', '(Lyrics)', '(Live)', '(Visualizer)'];
      out.push({
        ytId: id,
        url: `https://youtu.be/${id}`,
        title: `${q} ${labels[i] || ''}`.trim(),
        channel: 'Official Channel',
        duration: 180 + Math.floor(Math.random() * 120),
        thumb: `https://i.ytimg.com/vi/${id}/mqdefault.jpg`,
        views: Math.floor(Math.random() * 10_000_000),
      });
    }
    return out;
  }

  async function realSearchYouTube(query, limit = 5) {
    const params = new URLSearchParams({ q: query, limit: String(limit) });
    const resp = await fetch(`${BASE}/api/search?${params}`);
    if (!resp.ok) throw new Error(`Search failed: ${resp.status}`);
    return resp.json();
  }

  async function mockFetchPlaylist(url, onItemStart, onItemProgress, onItemComplete) {
    const count = 4 + Math.floor(Math.random() * 3);
    const items = [];
    for (let i = 0; i < count; i++) {
      const fakeUrl = 'https://youtube.com/watch?v=mock' + i + Date.now();
      onItemStart?.({ index: i, total: count, ytId: 'mock' + i, title: `Playlist Track ${i + 1}`, artist: 'Mock' });
      const t = await mockFetchTrack(fakeUrl, (p) => onItemProgress?.({ index: i, progress: p }));
      onItemComplete?.({ index: i, track: t });
      items.push(t);
    }
    return items;
  }

  function synthesizeMockBuffer(ctx, seconds, bpm, hue) {
    const sr = ctx.sampleRate;
    const len = Math.floor(sr * seconds);
    const buffer = ctx.createBuffer(2, len, sr);
    const beatSec = 60 / bpm;
    for (let ch = 0; ch < 2; ch++) {
      const data = buffer.getChannelData(ch);
      for (let i = 0; i < len; i++) {
        const t = i / sr;
        const beatT = t % beatSec;
        const kick = beatT < 0.15 ? Math.sin(2*Math.PI*55*beatT) * Math.exp(-beatT*18) * 0.9 : 0;
        const hiT = (t + beatSec/2) % beatSec;
        const hat = hiT < 0.05 ? (Math.random()*2-1) * Math.exp(-hiT*80) * 0.15 : 0;
        const freq = 55 * Math.pow(2, hue/12);
        const bass = Math.sin(2*Math.PI*freq*t) * 0.18;
        data[i] = kick + hat + bass;
      }
    }
    return buffer;
  }

  // --- REAL BACKEND ---
  async function realFetchTrack(url, onProgress) {
    // 1) Request extraction job
    const resp = await fetch(`${BASE}/api/fetch`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ url }),
    });
    if (!resp.ok) throw new Error(`Fetch failed: ${resp.status}`);
    const initial = await resp.json();
    const { jobId, title, artist, ytId, key, duration, thumb } = initial;

    // 2) Poll job status until ready, reporting progress
    let audioUrl = null;
    let finalStatus = {};
    while (!audioUrl) {
      await new Promise(r => setTimeout(r, 500));
      const st = await fetch(`${BASE}/api/jobs/${jobId}`).then(r => r.json());
      finalStatus = st || {};
      onProgress?.(st.progress ?? 0);
      if (st.status === 'ready') audioUrl = st.audioUrl;
      else if (st.status === 'error') throw new Error(st.error || 'Extraction failed');
    }

    // 3) Download + decode audio
    const audioResp = await fetch(audioUrl);
    const arr = await audioResp.arrayBuffer();
    const ctx = AudioEngine.ensureCtx();
    const buffer = await ctx.decodeAudioData(arr);
    const analysis = normalizeAnalysis({ ...initial, ...finalStatus });
    return {
      ytId, title, artist, key,
      duration: duration || finalStatus.duration || buffer.duration,
      thumb, buffer,
      ...analysis,
    };
  }

  async function realFetchPlaylist(url, onItemStart, onItemProgress, onItemComplete) {
    const resp = await fetch(`${BASE}/api/playlist`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ url }),
    });
    if (!resp.ok) throw new Error(`Playlist fetch failed: ${resp.status}`);
    const { items } = await resp.json(); // [{ ytId, title, artist }, ...]
    const results = [];
    for (let i = 0; i < items.length; i++) {
      const it = items[i];
      onItemStart?.({ index: i, total: items.length, ...it });
      const track = await realFetchTrack(`https://youtu.be/${it.ytId}`, (p) => onItemProgress?.({ index: i, progress: p }));
      onItemComplete?.({ index: i, track });
      results.push(track);
    }
    return results;
  }

  return {
    MOCK,
    ytIdFromUrl, playlistIdFromUrl,
    fetchTrack: MOCK ? mockFetchTrack : realFetchTrack,
    fetchPlaylist: MOCK ? mockFetchPlaylist : realFetchPlaylist,
    searchYouTube: MOCK ? mockSearchYouTube : realSearchYouTube,
  };
})();

window.BackendClient = BackendClient;
