// api-mock.jsx — Adapter mirroring the cifra-club-api Spanish-canonical contract.
// Source of truth for the contract: cifra-club-api/.specs/project/PROJECT.md
//
// Function ↔ endpoint mapping (Swift port replaces each body with URLSession):
//   buscarCanciones(nombre)               → GET /canciones?nombre=
//   obtenerAcordes(artista, cancion, fmt) → GET /acordes/:artista/:cancion?formato=
//   obtenerArtista(artista, completo)     → GET /artistas/:artista?completo=
//
// ─── DEV FLAG: USE_REAL_API ───────────────────────────────────────────────
// Auto-detect: real backend only when running on localhost (dev). In production
// (https://app.lavidvalencia.com) the backend isn't deployed yet AND mixed-content
// would block HTTP→HTTPS anyway, so we fall back to SONG_DB / mock acordes so the
// prototype stays visually functional. Flip the override below to force one mode.
const _isLocalhost = typeof window !== 'undefined'
  && /^(localhost|127\.|0\.0\.0\.0|192\.168\.|10\.)/.test(window.location.hostname || '');
const USE_REAL_API = _isLocalhost;
const REAL_API_BASE = 'http://localhost:8082';
//
// Latencies simulate observed backend behaviour (Solr fast, Vagalume parallel
// fan-out, CifraClub HTML scrape with parse). Real backend caps Vagalume at 2s
// per future via tokio::time::timeout + futures::future::join_all.

const API_MOCK_LATENCY = {
  solr:    380,
  enrich:  720,
  acordes: 580,
  artista: 420,
  cacheHit: 60,    // tiny delay so UI can flash "cache" indicator
};

const sleep = (ms) => new Promise(r => setTimeout(r, ms));

// ─── Cache layer ──────────────────────────────────────────────────────────
//
// Two-tier model (mirrors what the real backend will do):
//
//   GLOBAL cache  — `_cache.canciones`, `_cache.acordes`
//     Same response for every user. In production: Postgres + Redis at API
//     boundary. Searches keyed by normalized `nombre`. Acordes keyed by
//     `artist/song/formato` (TTL ~7d, refresh on miss).
//
//   PER-USER cache (history) — localStorage key `lavid:history`
//     Per-user list of recently opened songs. In production: Postgres
//     `user_history` table (user_id, song_id, accessed_at) + browser cache
//     for instant UX. Capped at 50 entries.
//
const _cache = {
  canciones: new Map(),  // normalizedQuery → song[]
  acordes:   new Map(),  // 'artistSlug/songSlug/formato' → acordes
  _hits: 0, _misses: 0,
};

const HISTORY_KEY = 'lavid:history';
const HISTORY_MAX = 50;

function loadHistory() {
  try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); }
  catch { return []; }
}
function persistHistory(arr) {
  try { localStorage.setItem(HISTORY_KEY, JSON.stringify(arr.slice(0, HISTORY_MAX))); } catch {}
}

function addToHistory(song) {
  const id = `${song.artist.slug}/${song.slug}`;
  const now = Date.now();
  // dedupe — move to top if already present
  const prev = loadHistory().filter(e => e.id !== id);
  const next = [{ id, song, accessedAt: now }, ...prev];
  persistHistory(next);
  return next;
}

function getHistory() {
  return loadHistory();
}

function clearHistory() {
  persistHistory([]);
}

function getCacheStats() {
  return { hits: _cache._hits, misses: _cache._misses,
           canciones: _cache.canciones.size, acordes: _cache.acordes.size };
}

// ─── Mock dataset (real impl: HTTP responses, not these arrays) ───────────

const SONG_DB = [
  {
    name: 'Reckless Love',
    slug: 'reckless-love',
    artist: { name: 'Cory Asbury', slug: 'cory-asbury' },
    genre: 'Worship',
    cover: 'linear-gradient(135deg, #F59E0B, #B45309)',
    match: 96.42,
    source: 'cifraclub',
    duration: '5:38', bpm: 70,
  },
  {
    name: 'Reckless Love (Live)',
    slug: 'reckless-love-live',
    artist: { name: 'Bethel Music', slug: 'bethel-music' },
    genre: 'Worship',
    cover: 'linear-gradient(135deg, #6366F1, #312E81)',
    match: 88.10,
    source: 'cifraclub',
    duration: '6:12', bpm: 72,
  },
  {
    name: 'Amor Increíble',
    slug: 'amor-increible',
    artist: { name: 'Casa Worship', slug: 'casa-worship' },
    genre: 'Adoración',
    cover: 'linear-gradient(135deg, #EC4899, #831843)',
    match: 82.00,
    source: 'cifraclub',
    duration: '5:54', bpm: 70,
  },
  {
    name: 'Tu Mirada',
    slug: 'tu-mirada',
    artist: { name: 'Marcos Witt', slug: 'marcos-witt' },
    genre: 'Gospel/Religioso',
    cover: 'linear-gradient(135deg, #10B981, #064E3B)',
    match: 91.50,
    source: 'cifraclub',
    duration: '4:18', bpm: 76,
  },
  {
    name: 'Tu Amor Por Mí',
    slug: 'tu-amor-por-mi',
    artist: { name: 'Marcos Vidal', slug: 'marcos-vidal' },
    genre: 'Gospel/Religioso',
    cover: 'linear-gradient(135deg, #06B6D4, #155E75)',
    match: 84.20,
    source: 'cifraclub',
    duration: '4:41', bpm: 72,
  },
  {
    name: 'Build My Life',
    slug: 'build-my-life',
    artist: { name: 'Pat Barrett', slug: 'pat-barrett' },
    genre: 'Worship',
    cover: 'linear-gradient(135deg, #8B5CF6, #581C87)',
    match: 79.30,
    source: 'cifraclub',
    duration: '4:52', bpm: 68,
  },
];

const ACORDES_DB = {
  'cory-asbury/reckless-love': {
    snippet: 'Antes que yo hablara, antes que pensara, Tú me amaste',
    key: 'C',
    chords_used: ['C', 'G', 'Am', 'F', 'Em', 'Dm'],
    html: '<pre>(intro/verso completo en producción — viene del scrape de CifraClub)</pre>',
  },
  'marcos-witt/tu-mirada': {
    snippet: 'Tus ojos revelan que yo soy aceptado',
    key: 'G',
    chords_used: ['G', 'Em7', 'C', 'D', 'C7M', 'Am', 'Am7', 'C/D', 'D/F#', 'G/B'],
    html: '<pre>(intro/verso completo en producción)</pre>',
  },
  'marcos-vidal/tu-amor-por-mi': {
    snippet: 'Tu amor por mí no tiene fin, no tiene principio ni final',
    key: 'D',
    chords_used: ['D', 'A', 'Bm', 'G', 'F#m', 'Em'],
    html: '<pre>(intro/verso completo en producción)</pre>',
  },
  'casa-worship/amor-increible': {
    snippet: 'Antes que yo hablara, Tú cantabas sobre mí',
    key: 'D',
    chords_used: ['D', 'A', 'Bm', 'G'],
    html: '<pre>(intro/verso completo en producción)</pre>',
  },
  __default: {
    snippet: 'Letra disponible al editar',
    key: 'C',
    chords_used: ['C', 'F', 'G', 'Am'],
    html: '<pre>(letra no en mock — vendrá del backend)</pre>',
  },
};

// ─── Public API (1:1 with backend endpoints) ──────────────────────────────

// GET /canciones?nombre=<query>
// Backend internally: Solr search → fan-out paralelo a Vagalume (genre).
// onProgress reports each backend phase. When a cache hit happens, only one
// 'cache' phase fires (instant) so the UI can show "Resultado en caché".
async function buscarCanciones(nombre, { onProgress } = {}) {
  const q = (nombre || '').trim().toLowerCase();
  if (q.length < 2) return [];

  if (_cache.canciones.has(q)) {
    _cache._hits++;
    onProgress?.({ phase: 'cache', label: 'Resultado en caché', done: true, fromCache: true });
    await sleep(API_MOCK_LATENCY.cacheHit);
    return _cache.canciones.get(q);
  }
  _cache._misses++;

  let matches;
  if (USE_REAL_API) {
    // Real backend (legacy Express). Single combined HTTP that internally does
    // Solr query + parallel Vagalume enrichment. We split it into two visual
    // phases so the UI keeps the same multi-step feedback.
    onProgress?.({ phase: 'solr', label: 'Buscando coincidencias en CifraClub' });
    const url = `${REAL_API_BASE}/songs?name=${encodeURIComponent(q)}`;
    const res = await fetch(url);
    if (!res.ok) throw new Error(`backend ${res.status}`);
    const raw = await res.json();
    onProgress?.({ phase: 'solr', label: `${raw.length} coincidencias`, done: true });

    onProgress?.({ phase: 'enrich', label: 'Recopilando datos de artistas' });
    // backend already enriched genre — small visual delay only
    await sleep(180);
    onProgress?.({ phase: 'enrich', label: 'Géneros agregados', done: true });

    // Backend returns { name, slug, artist:{name,slug}, genre? }.
    // Synthesize prototype-only display fields so the UI doesn't break.
    const palette = [
      'linear-gradient(135deg, #F59E0B, #B45309)',
      'linear-gradient(135deg, #6366F1, #312E81)',
      'linear-gradient(135deg, #EC4899, #831843)',
      'linear-gradient(135deg, #10B981, #064E3B)',
      'linear-gradient(135deg, #06B6D4, #155E75)',
      'linear-gradient(135deg, #8B5CF6, #581C87)',
    ];
    matches = raw.map((s, i) => ({
      ...s,
      cover: palette[i % palette.length],
      match: 100 - i * 4,           // synthesized rank (real backend will return Solr score)
      source: 'cifraclub',
      // duration / bpm: not in backend — would come from MusicKit/Spotify in iOS
    }));
  } else {
    onProgress?.({ phase: 'solr',   label: 'Buscando coincidencias en CifraClub' });
    await sleep(API_MOCK_LATENCY.solr);
    matches = SONG_DB
      .filter(s => s.name.toLowerCase().includes(q) || s.artist.name.toLowerCase().includes(q))
      .sort((a, b) => b.match - a.match);
    onProgress?.({ phase: 'solr', label: `${matches.length} coincidencias`, done: true });

    onProgress?.({ phase: 'enrich', label: 'Recopilando datos de artistas' });
    await sleep(API_MOCK_LATENCY.enrich);
    onProgress?.({ phase: 'enrich', label: 'Géneros agregados', done: true });
  }

  _cache.canciones.set(q, matches);
  return matches;
}

// ─── CifraClub HTML parser (TEMP — Rust port has parsers/lyrics.rs) ───────
// Real backend `/chords/:artist/:song` returns a JSON-string of HTML scraped
// from CifraClub's print view. Format: alternating chord lines (`<b>X</b>`
// tokens) and lyric lines, with `[Section]` markers between blocks.

const CHORD_RE = /^\(?[A-G](#|b)?(m|sus|dim|aug|maj|M)?\d?\d?\/?[A-G]?(#|b)?\)?$/;

// Pure-regex entity decoder (numeric hex/dec + a few named). Avoids innerHTML.
function _decodeEntities(s) {
  return (s || '')
    .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)))
    .replace(/&#(\d+);/g,            (_, n) => String.fromCodePoint(parseInt(n, 10)))
    .replace(/&amp;/g,  '&')
    .replace(/&lt;/g,   '<')
    .replace(/&gt;/g,   '>')
    .replace(/&quot;/g, '"')
    .replace(/&apos;/g, "'")
    .replace(/&#39;/g,  "'")
    .replace(/&nbsp;/g, ' ');
}
function _isChordOnly(line) {
  const stripped = line.replace(/<[^>]+>/g, '').replace(/[()]/g, ' ').trim();
  if (!stripped) return true;
  const toks = stripped.split(/\s+/);
  return toks.length > 0 && toks.every(t => CHORD_RE.test(t));
}
function _extractChords(line) {
  return [...line.matchAll(/<b>([^<]+)<\/b>/g)].map(m => m[1]);
}
function _normalizeSection(label) {
  const l = label.trim().toLowerCase();
  if (l.startsWith('intro')) return 'Intro';
  if (l.match(/^pre[\s-]?coro|pre[\s-]?chorus/)) return 'Pre-coro';
  if (l.match(/coro|chorus|refr|estribill/)) return 'Coro';
  if (l.match(/puente|ponte|bridge/)) return 'Puente';
  if (l.match(/final|outro|fim|ending/)) return 'Final';
  if (l.match(/verso|verse|primeira|segunda|terceira|parte/)) {
    const n = l.match(/\d+/)?.[0];
    return n ? `Verso ${n}` : 'Verso';
  }
  return label.charAt(0).toUpperCase() + label.slice(1).toLowerCase();
}

function parseChordsHtml(rawHtml) {
  const decoded = _decodeEntities(rawHtml || '');
  const lines = decoded.split('\n');
  const sections = [];
  const chordsSet = new Set();
  let firstChord = null;
  let firstLyric = null;
  let current = null;
  let versoCount = 0;

  const flush = () => {
    if (current && current.lines.length > 0) sections.push(current);
    current = null;
  };
  const startSection = (rawType) => {
    flush();
    let type = _normalizeSection(rawType);
    if (type === 'Verso') { versoCount++; type = `Verso ${versoCount}`; }
    current = { id: `s${sections.length}`, type, lines: [] };
  };

  for (const line of lines) {
    // Section marker on the line — may be alone OR followed by a chord row.
    const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(.*)$/);
    if (sectionMatch) {
      startSection(sectionMatch[1]);
      const rest = sectionMatch[2];
      if (rest && rest.trim()) {
        _extractChords(rest).forEach(c => {
          chordsSet.add(c);
          if (!firstChord) firstChord = c;
        });
        // If the residual is chord-only, drop it. Else add as a lyric.
        if (!_isChordOnly(rest)) {
          const plain = rest.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
          if (plain) {
            if (!firstLyric) firstLyric = plain;
            current.lines.push(plain);
          }
        }
      }
      continue;
    }

    _extractChords(line).forEach(c => {
      chordsSet.add(c);
      if (!firstChord) firstChord = c;
    });

    if (_isChordOnly(line)) continue;

    const plain = line.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
    if (!plain) continue;
    if (!firstLyric) firstLyric = plain;

    // Lyrics never live inside Intro — auto-promote to a fresh Verso.
    if (!current || current.type === 'Intro') {
      if (current && current.type === 'Intro') flush();
      versoCount++;
      current = { id: `s${sections.length}`, type: `Verso ${versoCount}`, lines: [] };
    }
    current.lines.push(plain);
  }
  flush();

  return {
    sections,
    snippet: firstLyric || '',
    key: firstChord || '',
    chords_used: Array.from(chordsSet),
  };
}

// GET /acordes/:artista/:cancion?formato=letra|acordes|completo
// Cached per `artista/cancion/formato` triple (lyrics rarely change).
async function obtenerAcordes(artistaSlug, cancionSlug, formato = 'completo') {
  const k = `${artistaSlug}/${cancionSlug}/${formato}`;
  if (_cache.acordes.has(k)) {
    _cache._hits++;
    await sleep(API_MOCK_LATENCY.cacheHit);
    return _cache.acordes.get(k);
  }
  _cache._misses++;

  if (USE_REAL_API) {
    try {
      const url = `${REAL_API_BASE}/chords/${encodeURIComponent(artistaSlug)}/${encodeURIComponent(cancionSlug)}`;
      const res = await fetch(url);
      if (res.ok) {
        const rawHtml = await res.json();
        const parsed = parseChordsHtml(rawHtml);
        // htmlPlain: tags stripped + entities decoded, ready for direct render.
        // Filter applied based on `formato`:
        //   completo → keep section markers + chord lines + lyric lines
        //   letra    → drop chord-only lines, keep section markers + lyrics
        //   acordes  → keep only section markers + chord lines
        const decoded = _decodeEntities(rawHtml).replace(/<[^>]+>/g, '');
        const lines = decoded.split('\n');
        const filtered = lines.filter(line => {
          const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(.*)$/);
          if (sectionMatch) {
            // Section header line — keep, but if has trailing chord row, optionally drop
            if (formato === 'letra') {
              const rest = sectionMatch[2];
              return !rest || !_isChordOnly(rest);
            }
            return true;
          }
          if (!line.trim()) return true;  // preserve blank spacing
          if (_isChordOnly(line)) return formato !== 'letra';
          // it's a lyric line
          return formato !== 'acordes';
        });
        const htmlPlain = filtered.join('\n').replace(/\n{3,}/g, '\n\n').trim();
        const result = { formato, html: rawHtml, htmlPlain, ...parsed };
        _cache.acordes.set(k, result);
        return result;
      }
    } catch (e) { /* fall through to mock */ }
  }

  await sleep(API_MOCK_LATENCY.acordes);
  const baseKey = `${artistaSlug}/${cancionSlug}`;
  const base = ACORDES_DB[baseKey] || ACORDES_DB.__default;
  const result = { ...base, formato };
  _cache.acordes.set(k, result);
  return result;
}

// GET /artistas/:artista?completo=<bool>
async function obtenerArtista(artistaSlug, completo = false) {
  await sleep(API_MOCK_LATENCY.artista);
  const song = SONG_DB.find(s => s.artist.slug === artistaSlug);
  if (!song) throw new Error('artist_not_found');
  return {
    name: song.artist.name,
    imgUrl: null,
    genre: song.genre,
    ...(completo ? { topLyrics: [] } : {}),
  };
}

window.cifraClubAPI = {
  buscarCanciones, obtenerAcordes, obtenerArtista,
  // history (per-user)
  addToHistory, getHistory, clearHistory,
  // diagnostic
  getCacheStats,
};
window.MOCK_RESULTS = SONG_DB;  // backward-compat for screens that read it directly
