/* RÉGIE — logique front */
'use strict';

const $  = (s, r = document) => r.querySelector(s);
const $$ = (s, r = document) => [...r.querySelectorAll(s)];
const API = 'api.php';

const state = {
  voiceId: null,
  music: null,        // { token, name, duration }
  config: {},
  quota: null,        // { used, limit, remaining }
};

// Modèles à demi-tarif (0,5 crédit / caractère)
const HALF_MODELS = ['eleven_turbo_v2_5', 'eleven_flash_v2_5', 'eleven_turbo_v2', 'eleven_flash_v2'];
function creditPerChar() {
  return HALF_MODELS.includes(state.config.model_id) ? 0.5 : 1;
}
function nfmt(n) { return Math.round(n).toLocaleString('fr-FR'); }

/* ----------------------------------------------------------- helpers */
function toast(msg, isErr = false) {
  const t = $('#toast');
  t.textContent = msg;
  t.className = 'toast' + (isErr ? ' err' : '');
  t.hidden = false;
  requestAnimationFrame(() => t.classList.add('show'));
  clearTimeout(toast._t);
  toast._t = setTimeout(() => {
    t.classList.remove('show');
    setTimeout(() => (t.hidden = true), 250);
  }, isErr ? 5500 : 3200);
}

function fmtDur(s) {
  s = Math.round(s || 0);
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
}

async function post(action, data, raw = false) {
  const fd = data instanceof FormData ? data : new FormData();
  if (!(data instanceof FormData)) {
    for (const k in data) fd.append(k, data[k]);
  }
  const r = await fetch(`${API}?action=${action}`, { method: 'POST', body: fd });
  if (raw) {
    if (!r.ok) throw new Error((await r.json().catch(() => ({}))).error || `HTTP ${r.status}`);
    return r.blob();
  }
  const j = await r.json().catch(() => ({ ok: false, error: 'Réponse invalide du serveur.' }));
  if (!j.ok) throw new Error(j.error || 'Erreur inconnue.');
  return j;
}

/* ----------------------------------------------------------- config */
async function loadConfig() {
  try {
    const c = await (await fetch(`${API}?action=get_config`)).json();
    state.config = c;
    $('#statusLed').classList.toggle('ok', !!c.has_key);
    $('#statusLed').title = c.has_key ? 'Clé API configurée' : 'Clé API manquante';
    $('#cfgKey').placeholder = c.has_key ? c.key_masked : 'xi-api-key…';
    $('#cfgModel').value = c.model_id;
    setFader('#cfgStab', '#oStab', c.stability, v => v.toFixed(2));
    setFader('#cfgSim',  '#oSim',  c.similarity, v => v.toFixed(2));
    setFader('#cfgStyle','#oStyle',c.style,      v => v.toFixed(2));
    $('#cfgBoost').checked = !!c.speaker_boost;
    setFader('#fVol',  '#oVol',  c.music_volume, v => Math.round(v * 100) + '%');
    setFader('#fIntro','#oIntro',c.intro,        v => v.toFixed(1) + ' s');
    setFader('#fTail', '#oTail', c.tail,         v => v.toFixed(1) + ' s');
    $('#cDuck').checked = !!c.ducking;
    setCookieState(!!c.has_cookies);
    updateCost();
    if (c.has_key) { loadVoices(); loadQuota(); }
  } catch (e) { /* page peut-être pas encore prête */ }
}

function setFader(sel, out, val, fmt) {
  const el = $(sel); if (!el) return;
  el.value = val;
  $(out).textContent = fmt(parseFloat(val));
}

async function saveConfig() {
  const btn = $('#saveConfig');
  btn.disabled = true;
  try {
    await post('save_config', {
      api_key: $('#cfgKey').value,
      model_id: $('#cfgModel').value,
      stability: $('#cfgStab').value,
      similarity: $('#cfgSim').value,
      style: $('#cfgStyle').value,
      speaker_boost: $('#cfgBoost').checked ? '1' : 'false',
      music_volume: $('#fVol').value,
      intro: $('#fIntro').value,
      tail: $('#fTail').value,
      ducking: $('#cDuck').checked ? '1' : 'false',
      app_password: $('#cfgPw').value,
    });
    $('#cfgKey').value = '';
    $('#cfgPw').value = '';
    toast('Configuration enregistrée.');
    closeConfig();
    loadConfig();
  } catch (e) { toast(e.message, true); }
  finally { btn.disabled = false; }
}

/* ----------------------------------------------------------- voix */
async function loadVoices() {
  const list = $('#voiceList');
  $('#voiceHint') && ($('#voiceHint').textContent = 'Chargement des voix…');
  try {
    const { voices } = await post('voices', {});
    if (!voices.length) { list.innerHTML = '<p class="hint">Aucune voix dans ce compte ElevenLabs.</p>'; return; }
    list.innerHTML = '';
    voices.forEach(v => {
      const meta = [v.gender, v.accent, v.use_case].filter(Boolean).join(' · ') || v.category || '—';
      const card = document.createElement('div');
      card.className = 'voice-card';
      card.dataset.id = v.voice_id;
      card.tabIndex = 0;
      card.innerHTML = `<span class="check">●</span>
        <p class="vname">${esc(v.name)}</p>
        <p class="vmeta">${esc(meta)}</p>
        ${v.preview_url ? `<button class="vplay" type="button" title="Écouter la démo (gratuit, sans consommer)">♪ démo gratuite</button>` : ''}`;
      card.addEventListener('click', () => selectVoice(v.voice_id, card));
      card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectVoice(v.voice_id, card); } });
      const pb = card.querySelector('.vplay');
      if (pb) pb.addEventListener('click', e => { e.stopPropagation(); playPreview(v.preview_url, pb); });
      list.appendChild(card);
    });
    // sélectionne la première par défaut
    selectVoice(voices[0].voice_id, list.firstElementChild);
  } catch (e) {
    list.innerHTML = `<p class="hint">${esc(e.message)}</p>`;
    toast(e.message, true);
  }
}

function selectVoice(id, card) {
  state.voiceId = id;
  $$('.voice-card').forEach(c => c.classList.toggle('sel', c === card));
  $('#btnTest').disabled = false;
}

function esc(s) { return String(s).replace(/[&<>"]/g, m => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;' }[m])); }

/* écoute de la démo pré-générée — gratuit, ne consomme aucun caractère */
let previewAudio = null, previewBtn = null;
function playPreview(url, btn) {
  if (previewAudio && !previewAudio.paused && previewBtn === btn) {
    previewAudio.pause(); btn.classList.remove('playing'); return;
  }
  if (previewAudio) previewAudio.pause();
  $$('.vplay.playing').forEach(b => b.classList.remove('playing'));
  previewAudio = new Audio(url); previewBtn = btn;
  btn.classList.add('playing');
  previewAudio.onended = () => btn.classList.remove('playing');
  previewAudio.play().catch(() => { btn.classList.remove('playing'); toast('Démo indisponible pour cette voix.', true); });
}

async function testVoice() {
  if (!state.voiceId) return toast('Choisis une voix d\'abord.', true);
  const btn = $('#btnTest'); const old = btn.textContent;
  btn.disabled = true; btn.textContent = '⏳ Synthèse…';
  try {
    const fd = new FormData();
    fd.append('voice_id', state.voiceId);
    fd.append('text', $('#testText').value);
    fd.append('stability', $('#cfgStab').value);
    fd.append('similarity', $('#cfgSim').value);
    fd.append('style', $('#cfgStyle').value);
    fd.append('speaker_boost', $('#cfgBoost').checked ? '1' : 'false');
    const r = await fetch(`${API}?action=test_voice`, { method: 'POST', body: fd });
    if (!r.ok) { throw new Error((await r.json().catch(() => ({}))).error || `HTTP ${r.status}`); }
    const cached = r.headers.get('X-Cache') === 'hit';
    const blob = await r.blob();
    const p = $('#testPlayer');
    p.src = URL.createObjectURL(blob);
    p.hidden = false;
    p.play();
    toast(cached ? 'Réutilisé depuis le cache — 0 caractère consommé.' : 'Voix générée.');
    if (!cached) loadQuota();
  } catch (e) { toast(e.message, true); }
  finally { btn.disabled = false; btn.textContent = old; }
}

/* ----------------------------------------------------------- musique */
function setMusic(m) {
  state.music = m;
  if (!m) { $('#musicStatus').hidden = true; return; }
  $('#musicName').textContent = m.name;
  $('#musicDur').textContent = fmtDur(m.duration);
  $('#musicStatus').hidden = false;
}

async function uploadMusic(file) {
  if (!file) return;
  const fd = new FormData(); fd.append('music', file);
  toast('Import du fichier…');
  try { const j = await post('upload_music', fd); setMusic(j); toast('Musique importée.'); }
  catch (e) { toast(e.message, true); }
}

async function importYoutube() {
  const url = $('#ytUrl').value.trim();
  if (!url) return toast('Colle un lien YouTube.', true);
  const btn = $('#btnYt'); const old = btn.textContent;
  btn.disabled = true; btn.textContent = '⏳ Extraction…';
  try {
    const j = await post('youtube', { url });
    setMusic({ token: j.token, name: j.name || 'Audio YouTube', duration: j.duration });
    toast('Audio YouTube importé.');
  } catch (e) { toast(e.message, true); }
  finally { btn.disabled = false; btn.textContent = old; }
}

/* ----------------------------------------------------------- solde / coût */
async function loadQuota() {
  try {
    const q = await post('quota', {});
    state.quota = q;
    const bal = $('#balance'); bal.hidden = false;
    $('#balVal').innerHTML = `<b>${nfmt(q.remaining)}</b> / ${nfmt(q.limit)}`;
    const pct = q.limit ? Math.max(2, (q.remaining / q.limit) * 100) : 0;
    const bar = $('#balBar');
    bar.style.width = pct + '%';
    bar.className = pct < 10 ? 'low' : pct < 25 ? 'warn' : '';
    bal.title = `Palier : ${q.tier || '—'} — cliquer pour rafraîchir`;
    updateCost();
  } catch (e) {
    // On affiche la raison au lieu de masquer en silence
    state.quota = null;
    const bal = $('#balance'); bal.hidden = false;
    const missing = /permission|user_read/i.test(e.message);
    $('#balVal').innerHTML = missing ? '<b>permission requise</b>' : 'indispo.';
    $('#balBar').style.width = '0';
    $('#balBar').className = 'low';
    bal.title = missing
      ? 'Active la permission « User → Read » sur ta clé API ElevenLabs (dashboard → API Keys), puis clique ici.'
      : e.message;
    if (!loadQuota._warned) {
      loadQuota._warned = true;
      toast(missing
        ? 'Solde indisponible : ta clé API ElevenLabs n\'a pas la permission « User → Read ».'
        : e.message, true);
    }
    updateCost();
  }
}

/* estimation du coût du message courant */
function updateCost() {
  const el = $('#costEst'); if (!el) return;
  const chars = $('#msgText').value.length;
  const cost = Math.ceil(chars * creditPerChar());
  if (!chars) { el.textContent = ''; el.classList.remove('low'); return; }
  let txt = `≈ ${nfmt(cost)} crédit${cost > 1 ? 's' : ''}`;
  let low = false;
  if (state.quota) {
    const left = state.quota.remaining - cost;
    txt += ` · reste ${nfmt(Math.max(0, left))} après`;
    low = left < 0;
    if (left < 0) txt = `≈ ${nfmt(cost)} crédits · solde insuffisant !`;
  }
  el.textContent = txt;
  el.classList.toggle('low', low);
}

/* ----------------------------------------------------------- cookies YouTube */
function setCookieState(has) {
  const s = $('#ckState'); if (!s) return;
  s.textContent = has ? 'cookies configurés ✓' : 'cookies non configurés';
  s.classList.toggle('ok', has);
  $('#delCookies').hidden = !has;
}

async function uploadCookies(file) {
  if (!file) return;
  const fd = new FormData(); fd.append('cookies', file);
  try { await post('upload_cookies', fd); setCookieState(true); toast('Cookies YouTube enregistrés.'); }
  catch (e) { toast(e.message, true); }
}

async function deleteCookies() {
  try { await post('delete_cookies', {}); setCookieState(false); toast('Cookies supprimés.'); }
  catch (e) { toast(e.message, true); }
}

/* ----------------------------------------------------------- génération */
async function generate() {
  if (!state.voiceId) { toast('Choisis une voix (voie 01).', true); return; }
  const text = $('#msgText').value.trim();
  if (!text) { toast('Le texte du message est vide (voie 02).', true); return; }

  const btn = $('#btnGenerate'); const old = btn.textContent;
  btn.classList.add('busy'); btn.textContent = '⏳ Production en cours…';
  $('#statusLed').classList.add('ok');
  const meter = $('.master-meter'); meter.classList.add('live');

  try {
    const data = {
      voice_id: state.voiceId,
      text,
      stability: $('#cfgStab').value,
      similarity: $('#cfgSim').value,
      style: $('#cfgStyle').value,
      speaker_boost: $('#cfgBoost').checked ? '1' : 'false',
      music_volume: $('#fVol').value,
      intro: $('#fIntro').value,
      tail: $('#fTail').value,
      ducking: $('#cDuck').checked ? '1' : 'false',
    };
    if (state.music) data.music_token = state.music.token;

    const j = await post('generate', data);
    const url = j.url + '?t=' + Date.now();
    $('#outPlayer').src = url;
    $('#dlLink').href = url;
    $('#dlLink').setAttribute('download', j.file);
    $('#outDur').textContent = 'Durée ' + fmtDur(j.duration);
    $('#result').hidden = false;
    $('#result').scrollIntoView({ behavior: 'smooth', block: 'center' });
    toast(j.cached ? 'Message produit ✓ — voix réutilisée, 0 caractère consommé.' : 'Message produit ✓');
    if (!j.cached) loadQuota();
  } catch (e) { toast(e.message, true); }
  finally {
    btn.classList.remove('busy'); btn.textContent = old;
    meter.classList.remove('live');
  }
}

/* ----------------------------------------------------------- modale */
function openConfig()  { $('#configModal').hidden = false; }
function closeConfig() { $('#configModal').hidden = true; }

/* ----------------------------------------------------------- init */
document.addEventListener('DOMContentLoaded', () => {
  loadConfig();

  $('#loadVoices').addEventListener('click', loadVoices);
  $('#btnTest').addEventListener('click', testVoice);
  $('#btnGenerate').addEventListener('click', generate);

  $('#openConfig').addEventListener('click', openConfig);
  $('#closeConfig').addEventListener('click', closeConfig);
  $('#saveConfig').addEventListener('click', saveConfig);
  $('#configModal').addEventListener('click', e => { if (e.target.id === 'configModal') closeConfig(); });

  // onglets musique
  $$('.tab').forEach(tab => tab.addEventListener('click', () => {
    $$('.tab').forEach(t => t.classList.toggle('active', t === tab));
    $$('.src-panel').forEach(p => p.hidden = p.dataset.panel !== tab.dataset.src);
    if (tab.dataset.src === 'none') setMusic(null);
  }));
  $('#pickFile').addEventListener('click', () => $('#musicFile').click());
  $('#musicFile').addEventListener('change', e => uploadMusic(e.target.files[0]));
  $('#btnYt').addEventListener('click', importYoutube);
  $('#clearMusic').addEventListener('click', () => setMusic(null));
  $('#pickCookies').addEventListener('click', () => $('#cookiesFile').click());
  $('#cookiesFile').addEventListener('change', e => uploadCookies(e.target.files[0]));
  $('#delCookies').addEventListener('click', deleteCookies);

  // faders → output live
  const fmtPct = v => Math.round(v * 100) + '%';
  const fmtSec = v => parseFloat(v).toFixed(1) + ' s';
  const fmt2   = v => parseFloat(v).toFixed(2);
  bindFader('#fVol', '#oVol', fmtPct);
  bindFader('#fIntro', '#oIntro', fmtSec);
  bindFader('#fTail', '#oTail', fmtSec);
  bindFader('#cfgStab', '#oStab', fmt2);
  bindFader('#cfgSim', '#oSim', fmt2);
  bindFader('#cfgStyle', '#oStyle', fmt2);

  // compteur de caractères + estimation de coût
  const ta = $('#msgText'), cc = $('#charCount');
  const upd = () => {
    cc.textContent = ta.value.length + ' caractère' + (ta.value.length > 1 ? 's' : '');
    updateCost();
  };
  ta.addEventListener('input', upd); upd();

  // rafraîchir le solde au clic
  $('#balance').addEventListener('click', loadQuota);
});

function bindFader(sel, out, fmt) {
  const el = $(sel); if (!el) return;
  el.addEventListener('input', () => $(out).textContent = fmt(el.value));
}
