import { pipeline } from '@huggingface/transformers';
import { pagesRegistry } from '../pages/pagesRegistry.js';

// ── Modelo (singleton entre sesiones) ─────────────────────────────────────────
// Xenova/whisper-tiny + transformers.js v3 = WASM puro, sin WebGPU, todos los navegadores
let transcriberPromise = null;

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

// ── Comandos de voz (generados desde pagesRegistry) ──────────────────────────
const COMMANDS = pagesRegistry
  .filter(p => p.voicePatterns?.length)
  .map(p => ({
    label: p.name,
    hint: `"${p.voicePatterns.slice(0, 2).join('" · "')}"`,
    icon: p.icon,
    patterns: p.voicePatterns,
    action: () => { window.location.hash = '#' + p.route; },
  }));

function matchCommand(text) {
  const norm = text.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim();
  for (const cmd of COMMANDS) {
    for (const pattern of cmd.patterns) {
      const normP = pattern.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
      if (norm.includes(normP)) return cmd;
    }
  }
  return null;
}

// ── Grabación con detección de silencio ───────────────────────────────────────
async function startRecording(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;   // RMS 0-128 (sala silenciosa ≈ 2-5)
  const SILENCE_DELAY_MS = 1800; // ms de silencio para auto-parar
  const MIN_RECORD_MS = 700;  // no parar antes de este tiempo

  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();
  if (arrayBuffer.byteLength === 0) throw new Error('Blob de audio vacío');

  // 1) Decodificar al sample rate nativo del archivo (p.ej. 44100 Hz)
  const decodeCtx = new AudioContext();
  let nativeBuffer;
  try {
    nativeBuffer = await decodeCtx.decodeAudioData(arrayBuffer);
  } finally {
    await decodeCtx.close();
  }

  const srcRate = nativeBuffer.sampleRate;
  const srcData = nativeBuffer.getChannelData(0); // mono

  // 2) Si ya está a 16 kHz no hace falta resampling
  if (srcRate === 16000) return srcData;

  // 3) Resampling a 16 kHz con OfflineAudioContext
  const targetLength = Math.ceil(srcData.length * (16000 / srcRate));
  const offCtx = new OfflineAudioContext(1, targetLength, 16000);

  const srcBuf = offCtx.createBuffer(1, srcData.length, srcRate);
  srcBuf.getChannelData(0).set(srcData);

  const source = offCtx.createBufferSource();
  source.buffer = srcBuf;
  source.connect(offCtx.destination);
  source.start(0);

  const resampled = await offCtx.startRendering();
  return resampled.getChannelData(0);
}

// ── Componente ────────────────────────────────────────────────────────────────
let assistantEl = null;

export function mountVoiceAssistant() {
  if (assistantEl) { assistantEl.style.display = ''; return; }
  if (!navigator.mediaDevices?.getUserMedia || !window.MediaRecorder) return;

  // ── DOM ────────────────────────────────────────────────────────────────────
  assistantEl = document.createElement('div');
  assistantEl.className = 'va-root';
  assistantEl.innerHTML = /*html*/`
    <div class="va-panel" id="va-panel" hidden>
      <div class="va-panel-header">
        <span class="va-panel-title">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 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>
          Asistente de voz
        </span>
        <button class="va-close-btn" id="va-close" aria-label="Cerrar">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
        </button>
      </div>

      <div class="va-status-area" id="va-status-area">
        <div class="va-status-icon" id="va-status-icon">
          <svg width="22" height="22" 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>
        </div>
        <p class="va-status-text" id="va-status-text">Di un comando…</p>
        <p class="va-status-sub" id="va-status-sub"></p>
      </div>

      <div class="va-result" id="va-result" hidden>
        <span class="va-result-label">Escuché:</span>
        <span class="va-result-text" id="va-result-text"></span>
        <span class="va-result-action" id="va-result-action"></span>
      </div>

      <div class="va-commands">
        <p class="va-commands-title">Comandos disponibles</p>
        <ul class="va-commands-list" id="va-commands-list"></ul>
      </div>
    </div>

    <button class="va-fab" id="va-fab" aria-label="Asistente de voz">
      <svg class="va-fab-mic" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 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>
      <svg class="va-fab-stop" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none" style="display:none"><rect x="4" y="4" width="16" height="16" rx="3"/></svg>
    </button>
  `;

  document.body.appendChild(assistantEl);

  // ── Poblar lista de comandos ────────────────────────────────────────────────
  const cmdList = assistantEl.querySelector('#va-commands-list');
  COMMANDS.forEach(cmd => {
    const li = document.createElement('li');
    li.className = 'va-cmd-item';
    li.innerHTML = /*html*/`
      <span class="va-cmd-icon">${cmd.icon}</span>
      <span class="va-cmd-info">
        <span class="va-cmd-label">${cmd.label}</span>
        <span class="va-cmd-hint">${cmd.hint}</span>
      </span>
    `;
    li.addEventListener('click', () => { cmd.action(); closePanel(); });
    cmdList.appendChild(li);
  });

  // ── Referencias ────────────────────────────────────────────────────────────
  const fab = assistantEl.querySelector('#va-fab');
  const panel = assistantEl.querySelector('#va-panel');
  const closeBtn = assistantEl.querySelector('#va-close');
  const statusIcon = assistantEl.querySelector('#va-status-icon');
  const statusText = assistantEl.querySelector('#va-status-text');
  const statusSub = assistantEl.querySelector('#va-status-sub');
  const resultBox = assistantEl.querySelector('#va-result');
  const resultText = assistantEl.querySelector('#va-result-text');
  const resultAction = assistantEl.querySelector('#va-result-action');
  const fabMic = fab.querySelector('.va-fab-mic');
  const fabStop = fab.querySelector('.va-fab-stop');

  // ── Estado ─────────────────────────────────────────────────────────────────
  let state = 'idle';   // idle | recording | processing
  let recorder = null;
  let panelOpen = false;

  function setStatus(s, text, sub = '') {
    state = s;
    statusText.textContent = text;
    statusSub.textContent = sub;
    statusIcon.className = `va-status-icon va-status-icon--${s}`;
    fab.className = `va-fab va-fab--${s}`;
    fabMic.style.display = (s === 'recording') ? 'none' : '';
    fabStop.style.display = (s === 'recording') ? '' : 'none';
  }

  function showResult(heard, actionLabel) {
    resultBox.hidden = false;
    resultText.textContent = `"${heard}"`;
    resultAction.textContent = actionLabel ? `→ ${actionLabel}` : '→ Comando no reconocido';
    resultAction.className = 'va-result-action' + (actionLabel ? '' : ' va-result-action--unknown');
  }

  function openPanel() {
    panel.hidden = false;
    panelOpen = true;
    resultBox.hidden = true;
    fab.setAttribute('aria-label', 'Hablar');
  }

  function closePanel() {
    panel.hidden = true;
    panelOpen = false;
    if (state === 'recording' && recorder) {
      recorder.stop().catch(() => { });
      recorder = null;
    }
    setStatus('idle', 'Di un comando…');
  }

  // ── Lógica principal ───────────────────────────────────────────────────────
  fab.addEventListener('click', async () => {
    if (!panelOpen) { openPanel(); return; }

    if (state === 'recording') {
      // Detener grabación manualmente
      setStatus('processing', 'Procesando audio…');
      const blob = await recorder.stop();
      recorder = null;
      await transcribe(blob);
      return;
    }

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

    // Iniciar grabación con auto-stop por silencio
    try {
      recorder = await startRecording(async () => {
        if (state !== 'recording') return;
        setStatus('processing', 'Procesando audio…');
        const blob = await recorder.stop();
        recorder = null;
        await transcribe(blob);
      });
      setStatus('recording', 'Escuchando…', 'Para solo al detectar silencio');
      resultBox.hidden = true;
      // Pre-carga el modelo mientras graba (sin bloquear)
      if (!transcriberPromise) loadModel();
    } catch (e) {
      console.error('[VA] Micrófono:', e);
      setStatus('idle', 'Sin acceso al micrófono');
    }
  });

  closeBtn.addEventListener('click', closePanel);

  async function transcribe(blob) {
    try {
      // Carga el modelo si aún no está listo
      setStatus('processing', 'Cargando modelo…', 'Solo la primera vez (~140 MB)');
      const transcriber = await loadModel(prog => {
        if (prog.status === 'progress') {
          const pct = Math.round(prog.progress ?? 0);
          statusSub.textContent = `Descargando: ${pct}%`;
        }
      });

      setStatus('processing', 'Transcribiendo…', '');

      let audioData;
      try {
        audioData = await blobToFloat32(blob);
      } catch (e) {
        console.error('[VA] Error decodificando audio:', e);
        setStatus('idle', 'Error al leer el audio');
        return;
      }

      if (audioData.length < 3200) { // < 0.2s de audio
        setStatus('idle', 'Muy corto, inténtalo de nuevo');
        return;
      }

      const result = await transcriber(audioData, { language: 'spanish', task: 'transcribe' });
      const text = (result?.text ?? '').trim();

      if (!text) {
        setStatus('idle', 'No se detectó voz');
        return;
      }

      const cmd = matchCommand(text);
      showResult(text, cmd?.label ?? '');
      setStatus('idle', 'Di un comando…');

      if (cmd) {
        setTimeout(() => { cmd.action(); }, 600);
      }
    } catch (e) {
      console.error('[VA] Error en transcripción:', e?.message ?? e);
      const msg = e?.message?.includes('vacío') ? 'Audio vacío, graba más tiempo'
        : e?.message?.includes('session') ? 'Error cargando el modelo'
          : e?.message?.includes('decodeAudioData') ? 'Formato de audio no soportado'
            : 'Error al procesar';
      setStatus('idle', msg);
    }
  }
}

export function hideVoiceAssistant() {
  if (assistantEl) assistantEl.style.display = 'none';
}

export function showVoiceAssistant() {
  if (assistantEl) assistantEl.style.display = '';
  else mountVoiceAssistant();
}
