const DEFAULT_HOST = 'https://rspace.online';

// --- State ---
let state = 'idle'; // idle | recording | processing | done
let mediaRecorder = null;
let audioChunks = [];
let timerInterval = null;
let startTime = 0;
let audioBlob = null;
let audioUrl = null;
let transcript = '';
let liveTranscript = '';
let uploadedFileUrl = '';
let uploadedMimeType = '';
let uploadedFileSize = 0;
let duration = 0;

// Web Speech API
let recognition = null;
let speechSupported = !!(window.SpeechRecognition || window.webkitSpeechRecognition);

// --- DOM refs ---
const recBtn = document.getElementById('recBtn');
const timerEl = document.getElementById('timer');
const statusLabel = document.getElementById('statusLabel');
const transcriptArea = document.getElementById('transcriptArea');
const transcriptText = document.getElementById('transcriptText');
const liveIndicator = document.getElementById('liveIndicator');
const audioPreview = document.getElementById('audioPreview');
const audioPlayer = document.getElementById('audioPlayer');
const notebookSelect = document.getElementById('notebook');
const postActions = document.getElementById('postActions');
const saveBtn = document.getElementById('saveBtn');
const discardBtn = document.getElementById('discardBtn');
const copyBtn = document.getElementById('copyBtn');
const statusBar = document.getElementById('statusBar');
const authWarning = document.getElementById('authWarning');
const closeBtn = document.getElementById('closeBtn');

// --- Helpers ---

async function getSettings() {
  const result = await chrome.storage.sync.get(['rspaceHost', 'rspaceSlug']);
  return {
    host: result.rspaceHost || DEFAULT_HOST,
    slug: result.rspaceSlug || '',
  };
}

function apiBase(settings) {
  return settings.slug ? `${settings.host}/${settings.slug}/rnotes` : `${settings.host}/rnotes`;
}

async function getToken() {
  const result = await chrome.storage.local.get(['encryptid_token']);
  return result.encryptid_token || null;
}

function decodeToken(token) {
  try {
    const payload = JSON.parse(atob(token.split('.')[1]));
    if (payload.exp && payload.exp * 1000 < Date.now()) return null;
    return payload;
  } catch { return null; }
}

function formatTime(seconds) {
  const m = Math.floor(seconds / 60).toString().padStart(2, '0');
  const s = (seconds % 60).toString().padStart(2, '0');
  return `${m}:${s}`;
}

function setStatusLabel(text, cls) {
  statusLabel.textContent = text;
  statusLabel.className = `status-label ${cls}`;
}

function showStatusBar(message, type) {
  statusBar.textContent = message;
  statusBar.className = `status-bar visible ${type}`;
  if (type === 'success') {
    setTimeout(() => { statusBar.className = 'status-bar'; }, 3000);
  }
}

// --- Parakeet progress UI ---

const progressArea = document.getElementById('progressArea');
const progressLabel = document.getElementById('progressLabel');
const progressFill = document.getElementById('progressFill');

function showParakeetProgress(p) {
  if (!progressArea) return;
  progressArea.classList.add('visible');

  if (p.message) progressLabel.textContent = p.message;

  if (p.status === 'downloading' && p.progress !== undefined) {
    progressFill.style.width = `${p.progress}%`;
  } else if (p.status === 'transcribing') {
    progressFill.style.width = '100%';
  } else if (p.status === 'loading') {
    progressFill.style.width = '0%';
  }
}

function hideParakeetProgress() {
  if (progressArea) {
    progressArea.classList.remove('visible');
    progressFill.style.width = '0%';
  }
}

// --- Notebook loader ---

async function loadNotebooks() {
  const token = await getToken();
  if (!token) return;
  const settings = await getSettings();
  if (!settings.slug) return;

  try {
    const res = await fetch(`${apiBase(settings)}/api/notebooks`, {
      headers: { 'Authorization': `Bearer ${token}` },
    });
    if (!res.ok) return;
    const data = await res.json();
    const notebooks = data.notebooks || (Array.isArray(data) ? data : []);

    for (const nb of notebooks) {
      const opt = document.createElement('option');
      opt.value = nb.id;
      opt.textContent = nb.title;
      notebookSelect.appendChild(opt);
    }

    const { lastNotebookId } = await chrome.storage.local.get(['lastNotebookId']);
    if (lastNotebookId) notebookSelect.value = lastNotebookId;
  } catch (err) {
    console.error('Failed to load notebooks:', err);
  }
}

notebookSelect.addEventListener('change', (e) => {
  chrome.storage.local.set({ lastNotebookId: e.target.value });
});

// --- Live transcription (Web Speech API) ---

function startLiveTranscription() {
  if (!speechSupported) return;

  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  recognition = new SpeechRecognition();
  recognition.continuous = true;
  recognition.interimResults = true;
  recognition.lang = 'en-US';

  let finalizedText = '';

  recognition.onresult = (event) => {
    let interimText = '';
    finalizedText = '';
    for (let i = 0; i < event.results.length; i++) {
      const result = event.results[i];
      if (result.isFinal) {
        finalizedText += result[0].transcript.trim() + ' ';
      } else {
        interimText += result[0].transcript;
      }
    }

    liveTranscript = finalizedText.trim();
    updateLiveDisplay(finalizedText.trim(), interimText.trim());
  };

  recognition.onerror = (event) => {
    if (event.error !== 'aborted' && event.error !== 'no-speech') {
      console.warn('Speech recognition error:', event.error);
    }
  };

  recognition.onend = () => {
    if (state === 'recording' && recognition) {
      try { recognition.start(); } catch {}
    }
  };

  try {
    recognition.start();
    if (liveIndicator) liveIndicator.classList.add('visible');
  } catch (err) {
    console.warn('Could not start speech recognition:', err);
    speechSupported = false;
  }
}

function stopLiveTranscription() {
  if (recognition) {
    const ref = recognition;
    recognition = null;
    try { ref.stop(); } catch {}
  }
  if (liveIndicator) liveIndicator.classList.remove('visible');
}

function updateLiveDisplay(finalText, interimText) {
  if (state !== 'recording') return;

  transcriptArea.classList.add('visible');

  let html = '';
  if (finalText) html += `<span class="final-text">${escapeHtml(finalText)}</span>`;
  if (interimText) html += `<span class="interim-text">${escapeHtml(interimText)}</span>`;
  if (!finalText && !interimText) html = '<span class="placeholder">Listening...</span>';
  transcriptText.innerHTML = html;
  transcriptText.scrollTop = transcriptText.scrollHeight;
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// --- Recording ---

async function startRecording() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

    const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
      ? 'audio/webm;codecs=opus'
      : 'audio/webm';

    mediaRecorder = new MediaRecorder(stream, { mimeType });
    audioChunks = [];
    liveTranscript = '';

    mediaRecorder.ondataavailable = (e) => {
      if (e.data.size > 0) audioChunks.push(e.data);
    };

    mediaRecorder.start(1000);
    startTime = Date.now();
    state = 'recording';

    recBtn.classList.add('recording');
    timerEl.classList.add('recording');
    setStatusLabel('Recording', 'recording');
    postActions.style.display = 'none';
    audioPreview.classList.remove('visible');
    statusBar.className = 'status-bar';

    if (speechSupported) {
      transcriptArea.classList.add('visible');
      transcriptText.innerHTML = '<span class="placeholder">Listening...</span>';
    } else {
      transcriptArea.classList.remove('visible');
    }

    timerInterval = setInterval(() => {
      const elapsed = Math.floor((Date.now() - startTime) / 1000);
      timerEl.textContent = formatTime(elapsed);
    }, 1000);

    startLiveTranscription();
  } catch (err) {
    showStatusBar(err.message || 'Microphone access denied', 'error');
  }
}

async function stopRecording() {
  if (!mediaRecorder || mediaRecorder.state === 'inactive') return;

  clearInterval(timerInterval);
  timerInterval = null;
  duration = Math.floor((Date.now() - startTime) / 1000);

  const capturedLiveTranscript = liveTranscript;
  stopLiveTranscription();

  state = 'processing';
  recBtn.classList.remove('recording');
  timerEl.classList.remove('recording');
  setStatusLabel('Processing...', 'processing');

  audioBlob = await new Promise((resolve) => {
    mediaRecorder.onstop = () => {
      mediaRecorder.stream.getTracks().forEach(t => t.stop());
      resolve(new Blob(audioChunks, { type: mediaRecorder.mimeType }));
    };
    mediaRecorder.stop();
  });

  if (audioUrl) URL.revokeObjectURL(audioUrl);
  audioUrl = URL.createObjectURL(audioBlob);
  audioPlayer.src = audioUrl;
  audioPreview.classList.add('visible');

  transcriptArea.classList.add('visible');
  if (capturedLiveTranscript) {
    transcriptText.textContent = capturedLiveTranscript;
    showStatusBar('Improving transcript...', 'loading');
  } else {
    transcriptText.innerHTML = '<span class="placeholder">Transcribing...</span>';
    showStatusBar('Uploading & transcribing...', 'loading');
  }

  const token = await getToken();
  const settings = await getSettings();

  try {
    const uploadForm = new FormData();
    uploadForm.append('file', audioBlob, 'voice-note.webm');

    const uploadRes = await fetch(`${apiBase(settings)}/api/uploads`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${token}` },
      body: uploadForm,
    });

    if (!uploadRes.ok) throw new Error('Upload failed');

    const uploadResult = await uploadRes.json();
    uploadedFileUrl = uploadResult.url;
    uploadedMimeType = uploadResult.mimeType;
    uploadedFileSize = uploadResult.size;

    // --- Three-tier transcription cascade ---

    // Tier 1: Server (Whisper)
    let bestTranscript = '';
    try {
      showStatusBar('Transcribing via server...', 'loading');
      const transcribeForm = new FormData();
      transcribeForm.append('audio', audioBlob, 'voice-note.webm');

      const transcribeRes = await fetch(`${apiBase(settings)}/api/voice/transcribe`, {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${token}` },
        body: transcribeForm,
      });

      if (transcribeRes.ok) {
        const transcribeResult = await transcribeRes.json();
        bestTranscript = transcribeResult.text || '';
      }
    } catch {
      console.warn('Tier 1 (batch API) unavailable');
    }

    // Tier 2: Web Speech API (already captured)
    if (!bestTranscript && capturedLiveTranscript) {
      bestTranscript = capturedLiveTranscript;
    }

    // Tier 3: Offline Parakeet.js
    if (!bestTranscript && window.ParakeetOffline) {
      try {
        showStatusBar('Transcribing offline (Parakeet)...', 'loading');
        bestTranscript = await window.ParakeetOffline.transcribeOffline(audioBlob, (p) => {
          showParakeetProgress(p);
        });
        hideParakeetProgress();
      } catch (offlineErr) {
        console.warn('Tier 3 (Parakeet offline) failed:', offlineErr);
        hideParakeetProgress();
      }
    }

    transcript = bestTranscript;

    if (transcript) {
      transcriptText.textContent = transcript;
    } else {
      transcriptText.innerHTML = '<span class="placeholder">No transcript available - you can type one here</span>';
    }

    state = 'done';
    setStatusLabel('Done', 'done');
    postActions.style.display = 'flex';
    statusBar.className = 'status-bar';

  } catch (err) {
    let fallbackTranscript = capturedLiveTranscript || '';

    if (!fallbackTranscript && window.ParakeetOffline) {
      try {
        showStatusBar('Upload failed, transcribing offline...', 'loading');
        fallbackTranscript = await window.ParakeetOffline.transcribeOffline(audioBlob, (p) => {
          showParakeetProgress(p);
        });
        hideParakeetProgress();
      } catch {
        hideParakeetProgress();
      }
    }

    transcript = fallbackTranscript;
    if (transcript) transcriptText.textContent = transcript;

    showStatusBar(`Error: ${err.message}`, 'error');
    state = 'done';
    setStatusLabel('Error', 'idle');
    postActions.style.display = 'flex';
  }
}

function toggleRecording() {
  if (state === 'idle' || state === 'done') {
    startRecording();
  } else if (state === 'recording') {
    stopRecording();
  }
}

// --- Save to rSpace ---

async function saveToRSpace() {
  saveBtn.disabled = true;
  showStatusBar('Saving to rSpace...', 'loading');

  const token = await getToken();
  const settings = await getSettings();

  const editedTranscript = transcriptText.textContent.trim();
  const isPlaceholder = transcriptText.querySelector('.placeholder') !== null;
  const finalTranscript = isPlaceholder ? '' : editedTranscript;

  const now = new Date();
  const timeStr = now.toLocaleString('en-US', {
    month: 'short', day: 'numeric',
    hour: 'numeric', minute: '2-digit',
    hour12: true
  });

  const body = {
    title: `Voice note - ${timeStr}`,
    content: finalTranscript
      ? `<p>${finalTranscript.replace(/\n/g, '</p><p>')}</p>`
      : '<p><em>Voice recording (no transcript)</em></p>',
    type: 'AUDIO',
    mimeType: uploadedMimeType || 'audio/webm',
    fileUrl: uploadedFileUrl,
    fileSize: uploadedFileSize,
    duration: duration,
    tags: ['voice'],
  };

  const notebookId = notebookSelect.value;
  if (notebookId) body.notebook_id = notebookId;

  try {
    const res = await fetch(`${apiBase(settings)}/api/notes`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
      },
      body: JSON.stringify(body),
    });

    if (!res.ok) {
      const text = await res.text();
      throw new Error(`${res.status}: ${text}`);
    }

    showStatusBar('Saved to rSpace!', 'success');

    chrome.runtime.sendMessage({
      type: 'notify',
      title: 'Voice Note Saved',
      message: `${formatTime(duration)} recording saved to rSpace`,
    });

    setTimeout(resetState, 1500);
  } catch (err) {
    showStatusBar(`Save failed: ${err.message}`, 'error');
  } finally {
    saveBtn.disabled = false;
  }
}

// --- Copy to clipboard ---

async function copyTranscript() {
  const text = transcriptText.textContent.trim();
  if (!text || transcriptText.querySelector('.placeholder')) {
    showStatusBar('No transcript to copy', 'error');
    return;
  }
  try {
    await navigator.clipboard.writeText(text);
    showStatusBar('Copied to clipboard', 'success');
  } catch {
    showStatusBar('Copy failed', 'error');
  }
}

// --- Discard ---

function resetState() {
  state = 'idle';
  mediaRecorder = null;
  audioChunks = [];
  audioBlob = null;
  transcript = '';
  liveTranscript = '';
  uploadedFileUrl = '';
  uploadedMimeType = '';
  uploadedFileSize = 0;
  duration = 0;

  stopLiveTranscription();

  if (audioUrl) {
    URL.revokeObjectURL(audioUrl);
    audioUrl = null;
  }

  timerEl.textContent = '00:00';
  timerEl.classList.remove('recording');
  recBtn.classList.remove('recording');
  setStatusLabel('Ready', 'idle');
  postActions.style.display = 'none';
  audioPreview.classList.remove('visible');
  transcriptArea.classList.remove('visible');
  hideParakeetProgress();
  statusBar.className = 'status-bar';
}

// --- Keyboard shortcuts ---

document.addEventListener('keydown', (e) => {
  if (e.code === 'Space' && document.activeElement !== transcriptText) {
    e.preventDefault();
    toggleRecording();
  }
  if (e.code === 'Escape') {
    window.close();
  }
  if ((e.ctrlKey || e.metaKey) && e.code === 'Enter' && state === 'done') {
    e.preventDefault();
    saveToRSpace();
  }
});

transcriptText.addEventListener('focus', () => {
  const ph = transcriptText.querySelector('.placeholder');
  if (ph) transcriptText.textContent = '';
});

// --- Event listeners ---

recBtn.addEventListener('click', toggleRecording);
saveBtn.addEventListener('click', saveToRSpace);
discardBtn.addEventListener('click', resetState);
copyBtn.addEventListener('click', copyTranscript);
closeBtn.addEventListener('click', () => window.close());

// --- Init ---

async function init() {
  const token = await getToken();
  const claims = token ? decodeToken(token) : null;

  if (!claims) {
    authWarning.style.display = 'block';
    recBtn.style.opacity = '0.3';
    recBtn.style.pointerEvents = 'none';
    return;
  }

  authWarning.style.display = 'none';
  await loadNotebooks();
}

document.addEventListener('DOMContentLoaded', init);
