import { pipeline } from '@huggingface/transformers';

// ── Iconos ────────────────────────────────────────────────────────────────────

const MIC_ICON = /*html*/`
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
    <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
    <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
    <line x1="12" y1="19" x2="12" y2="23"/>
    <line x1="8" y1="23" x2="16" y2="23"/>
  </svg>`;

const STOP_ICON = /*html*/`
  <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" stroke="none">
    <rect x="4" y="4" width="16" height="16" rx="2"/>
  </svg>`;

const SPIN_ICON = /*html*/`
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
    <path d="M12 2a10 10 0 0 1 10 10" class="voice-spin"/>
  </svg>`;

// ── Modelo (singleton) ────────────────────────────────────────────────────────

let transcriberPromise = null;

function loadModel(onProgress) {
  if (!transcriberPromise) {
    transcriberPromise = pipeline(
      'automatic-speech-recognition',
      'Xenova/whisper-base',
      { progress_callback: onProgress }
    ).catch(err => {
      transcriberPromise = null; // permitir reintento
      throw err;
    });
  }
  return transcriberPromise;
}

// ── Helpers ───────────────────────────────────────────────────────────────────

function showTip(btn, text, duration = 3000) {
  btn.querySelectorAll('.voice-tip').forEach(t => t.remove());
  const tip = document.createElement('span');
  tip.className = 'voice-tip';
  tip.textContent = text;
  btn.appendChild(tip);
  if (duration > 0) setTimeout(() => tip.remove(), duration);
  return tip;
}

async function startMediaRecorder(onSilence) {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
  const mimeType = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus']
    .find(t => MediaRecorder.isTypeSupported(t)) || '';

  const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : {});
  const chunks = [];
  recorder.ondataavailable = e => { if (e.data?.size > 0) chunks.push(e.data); };
  recorder.start(200);

  // Detección de silencio via Web Audio API
  const audioCtx = new AudioContext();
  const analyser = audioCtx.createAnalyser();
  analyser.fftSize = 512;
  const source = audioCtx.createMediaStreamSource(stream);
  source.connect(analyser);
  const dataArray = new Uint8Array(analyser.frequencyBinCount);

  const SILENCE_THRESHOLD = 12;
  const SILENCE_DELAY_MS  = 1800;
  const MIN_RECORD_MS     = 700;

  const startTime = Date.now();
  let silenceStart = null;
  let rafId = null;
  let stopped = false;

  function tick() {
    if (stopped) return;
    analyser.getByteTimeDomainData(dataArray);
    let sum = 0;
    for (let i = 0; i < dataArray.length; i++) {
      const v = dataArray[i] / 128.0 - 1.0;
      sum += v * v;
    }
    const rms = Math.sqrt(sum / dataArray.length) * 128;

    if (Date.now() - startTime >= MIN_RECORD_MS) {
      if (rms < SILENCE_THRESHOLD) {
        if (!silenceStart) silenceStart = Date.now();
        else if (Date.now() - silenceStart >= SILENCE_DELAY_MS) {
          stopped = true;
          audioCtx.close().catch(() => {});
          onSilence?.();
          return;
        }
      } else {
        silenceStart = null;
      }
    }
    rafId = requestAnimationFrame(tick);
  }
  rafId = requestAnimationFrame(tick);

  return {
    stop: () => new Promise(resolve => {
      stopped = true;
      if (rafId) cancelAnimationFrame(rafId);
      audioCtx.close().catch(() => {});
      recorder.onstop = () => {
        stream.getTracks().forEach(t => t.stop());
        resolve(new Blob(chunks, { type: mimeType || 'audio/webm' }));
      };
      if (recorder.state !== 'inactive') recorder.stop();
      else resolve(new Blob(chunks));
    })
  };
}

async function blobToFloat32(blob) {
  const arrayBuffer = await blob.arrayBuffer();
  const ctx = new AudioContext({ sampleRate: 16000 });
  const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
  await ctx.close();
  return audioBuffer.getChannelData(0); // mono Float32Array a 16kHz
}

// ── API pública ───────────────────────────────────────────────────────────────

/**
 * Adjunta un botón de voz (Whisper local) a un input.
 * Devuelve el botón, o null si el navegador no soporta MediaRecorder.
 */
export function attachVoiceInput(inputEl, { lang = 'es' } = {}) {
  if (!navigator.mediaDevices?.getUserMedia || !window.MediaRecorder) {
    console.warn('[VoiceInput] MediaRecorder no disponible en este navegador.');
    return null;
  }

  const btn = document.createElement('button');
  btn.type = 'button';
  btn.className = 'voice-btn';
  btn.setAttribute('aria-label', 'Buscar por voz');
  btn.title = 'Buscar por voz (Whisper local)';
  btn.innerHTML = MIC_ICON;

  let mediaRecorder = null; // { stop() }
  let state = 'idle'; // idle | recording | processing

  const setState = (s) => {
    state = s;
    btn.className = `voice-btn voice-btn--${s}`;
    if (s === 'idle')       { btn.innerHTML = MIC_ICON;  btn.disabled = false; }
    if (s === 'recording')  { btn.innerHTML = STOP_ICON; btn.disabled = false; }
    if (s === 'processing') { btn.innerHTML = SPIN_ICON; btn.disabled = true;  }
  };

  btn.addEventListener('click', async () => {
    // ── Detener grabación ──────────────────────────────────────────────────
    if (state === 'recording') {
      showTip(btn, 'Procesando…', 0);
      setState('processing');
      const blob = await mediaRecorder.stop();
      mediaRecorder = null;
      await transcribe(blob);
      return;
    }

    if (state !== 'idle') return;

    // ── Iniciar grabación ──────────────────────────────────────────────────
    try {
      mediaRecorder = await startMediaRecorder(async () => {
        if (state !== 'recording') return;
        showTip(btn, 'Procesando…', 0);
        setState('processing');
        const blob = await mediaRecorder.stop();
        mediaRecorder = null;
        await transcribe(blob);
      });
      setState('recording');
      showTip(btn, 'Hablando… para solo');

      // Pre-carga el modelo en paralelo mientras el usuario habla
      if (!transcriberPromise) loadModel();
    } catch (e) {
      console.error('[VoiceInput] Error al acceder al micrófono:', e);
      showTip(btn, 'Sin acceso al micrófono');
      setState('idle');
    }
  });

  async function transcribe(blob) {
    try {
      // Carga / espera el modelo (primera vez descarga ~40 MB, se cachea)
      const tipLoad = showTip(btn, 'Cargando modelo…', 0);
      const transcriber = await loadModel(progress => {
        if (progress.status === 'progress') {
          const pct = Math.round(progress.progress ?? 0);
          btn.title = `Cargando modelo: ${pct}% (~140 MB primera vez)`;
        }
      });
      tipLoad.remove();

      showTip(btn, 'Transcribiendo…', 0);
      const audioData = await blobToFloat32(blob);
      const result = await transcriber(audioData, { language: lang, task: 'transcribe' });
      const text = (result.text ?? '').trim();

      if (text) {
        inputEl.value = text;
        inputEl.dispatchEvent(new Event('input', { bubbles: true }));
        inputEl.focus();
        showTip(btn, `"${text}"`);
      } else {
        showTip(btn, 'No se detectó texto');
      }
    } catch (e) {
      console.error('[VoiceInput] Error en transcripción:', e);
      showTip(btn, 'Error al transcribir');
    } finally {
      btn.title = 'Buscar por voz';
      setState('idle');
    }
  }

  return btn;
}

/**
 * Envuelve el input en .search-wrapper y adjunta el botón de voz.
 */
export function wrapWithVoice(inputEl, options = {}) {
  const voiceBtn = attachVoiceInput(inputEl, options);
  if (!voiceBtn) return;

  const wrapper = document.createElement('div');
  wrapper.className = 'search-wrapper';
  inputEl.parentNode.insertBefore(wrapper, inputEl);
  wrapper.appendChild(inputEl);
  wrapper.appendChild(voiceBtn);
}
