// StellarCaptions — Offscreen document
// Always runs a single persistent WebRTC translation session. Language switching,
// dubbing toggle, and "Keep original" all happen over the existing data channel
// with zero reconnect.

// Shared capture state
let streamRef = null
let originalAudioEl = null

// WebRTC state (persistent for the whole session)
let pc = null
let dataChannel = null
let audioSender = null
let dubAudioEl = null
let dcOpen = false
let dcQueue = [] // queued messages for the data channel before it opens

// Audio level meter
let meterCtx = null
let meterSource = null
let meterAnalyser = null
let meterInterval = null

// Session state
let paused = false
let dubbingEnabled = false
let muteSource = false
let currentSource = null
let currentTargetLang = null
let tokenUrl = null

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.action === 'startCapture') {
    startCapture(msg)
    sendResponse({ ok: true })
  } else if (msg.action === 'stopCapture') {
    stopCapture()
    sendResponse({ ok: true })
  } else if (msg.action === 'pauseCapture') {
    pauseCapture()
    sendResponse({ ok: true })
  } else if (msg.action === 'resumeCapture') {
    resumeCapture()
    sendResponse({ ok: true })
  } else if (msg.action === 'muteOriginal') {
    setOriginalVolume(0)
    sendResponse({ ok: true })
  } else if (msg.action === 'unmuteOriginal') {
    setOriginalVolume(1)
    sendResponse({ ok: true })
  } else if (msg.action === 'updateDubbing') {
    dubbingEnabled = !!msg.dubbing
    applyDubMute()
    sendResponse({ ok: true })
  } else if (msg.action === 'updateOutputLang') {
    currentTargetLang = msg.targetLang || 'en'
    setOutputLanguage(currentTargetLang)
    sendResponse({ ok: true })
  }
  return true
})

async function startCapture(msg) {
  currentSource = msg.source
  currentTargetLang = msg.targetLang || 'en'
  dubbingEnabled = !!msg.dubbing
  muteSource = !!msg.muteSource
  tokenUrl = msg.tokenUrl || null
  paused = false

  try {
    await acquireStream(msg)
  } catch (e) {
    chrome.runtime.sendMessage({ action: 'proxyError', message: `Capture failed: ${e?.message || e}` })
    return
  }

  setupOriginalPlayback()
  startLevelMeter()
  await startWebRTC()
}

async function acquireStream({ source, streamId }) {
  if (source === 'tab' && streamId) {
    streamRef = await navigator.mediaDevices.getUserMedia({
      audio: { mandatory: { chromeMediaSource: 'tab', chromeMediaSourceId: streamId } },
    })
  } else if (source === 'mic') {
    streamRef = await navigator.mediaDevices.getUserMedia({ audio: true })
  } else if (source === 'display') {
    streamRef = await navigator.mediaDevices.getDisplayMedia({ audio: true, video: true })
    streamRef.getVideoTracks().forEach((t) => t.stop())
    if (!streamRef.getAudioTracks().length) {
      throw new Error('No audio track — enable "Share tab/system audio".')
    }
  } else {
    throw new Error(`Unknown source: ${source}`)
  }
}

function setupOriginalPlayback() {
  if (currentSource === 'mic') return
  originalAudioEl = new Audio()
  originalAudioEl.srcObject = streamRef
  originalAudioEl.autoplay = true
  originalAudioEl.volume = muteSource ? 0 : 1
  originalAudioEl.play?.().catch(() => {})
}

function setOriginalVolume(v) {
  muteSource = v === 0
  if (originalAudioEl) originalAudioEl.volume = v
}

// ---------- Audio level meter ----------
function startLevelMeter() {
  stopLevelMeter()
  if (!streamRef || !streamRef.getAudioTracks().length) return
  try {
    meterCtx = new AudioContext()
    if (meterCtx.state === 'suspended') meterCtx.resume().catch(() => {})
    meterSource = meterCtx.createMediaStreamSource(streamRef)
    meterAnalyser = meterCtx.createAnalyser()
    meterAnalyser.fftSize = 512
    meterSource.connect(meterAnalyser)
    const buf = new Uint8Array(meterAnalyser.fftSize)
    meterInterval = setInterval(() => {
      if (!meterAnalyser) return
      meterAnalyser.getByteTimeDomainData(buf)
      let sum = 0
      for (let i = 0; i < buf.length; i++) {
        const v = (buf[i] - 128) / 128
        sum += v * v
      }
      const rms = Math.sqrt(sum / buf.length)
      const level = paused ? 0 : Math.min(1, rms * 3)
      chrome.runtime.sendMessage({ action: 'audioLevel', level }).catch(() => {})
    }, 60)
  } catch {
    stopLevelMeter()
  }
}

function stopLevelMeter() {
  if (meterInterval) { clearInterval(meterInterval); meterInterval = null }
  if (meterSource) { try { meterSource.disconnect() } catch {} meterSource = null }
  meterAnalyser = null
  if (meterCtx) { try { meterCtx.close() } catch {} meterCtx = null }
}

// ---------- WebRTC (persistent session) ----------
async function startWebRTC() {
  let secret
  try {
    const res = await fetch(tokenUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ targetLang: currentTargetLang }),
    })
    const data = await res.json().catch(() => null)
    if (!res.ok || !data?.value) throw new Error(data?.error || `token ${res.status}`)
    secret = data.value
  } catch (e) {
    chrome.runtime.sendMessage({ action: 'proxyError', message: `Token error: ${e?.message || e}` })
    return
  }

  pc = new RTCPeerConnection()

  dubAudioEl = new Audio()
  dubAudioEl.autoplay = true
  pc.ontrack = (ev) => {
    dubAudioEl.srcObject = ev.streams[0]
    applyDubMute()
    dubAudioEl.play?.().catch(() => {})
  }

  dcOpen = false
  dcQueue = []
  dataChannel = pc.createDataChannel('oai-events')
  dataChannel.onopen = () => {
    dcOpen = true
    for (const queued of dcQueue) {
      try { dataChannel.send(queued) } catch {}
    }
    dcQueue = []
  }
  dataChannel.onclose = () => { dcOpen = false }
  dataChannel.onmessage = onOaiEvent

  const track = streamRef.getAudioTracks()[0]
  audioSender = pc.addTrack(track, streamRef)

  try {
    const offer = await pc.createOffer()
    await pc.setLocalDescription(offer)

    const sdpRes = await fetch('https://api.openai.com/v1/realtime/translations/calls', {
      method: 'POST',
      headers: { Authorization: `Bearer ${secret}`, 'Content-Type': 'application/sdp' },
      body: offer.sdp,
    })
    if (!sdpRes.ok) throw new Error(`SDP ${sdpRes.status}`)
    const answer = await sdpRes.text()
    await pc.setRemoteDescription({ type: 'answer', sdp: answer })

    chrome.runtime.sendMessage({ action: 'proxyReady' })
  } catch (e) {
    chrome.runtime.sendMessage({ action: 'proxyError', message: `WebRTC error: ${e?.message || e}` })
  }
}

function onOaiEvent(e) {
  let ev
  try { ev = JSON.parse(e.data) } catch { return }

  if (ev.type === 'session.output_transcript.delta') {
    sendCaption('translationUpdate', ev.delta, false)
  } else if (ev.type === 'session.output_transcript.done') {
    sendCaption('translationUpdate', ev.transcript || '', true)
  } else if (ev.type === 'session.input_transcript.delta') {
    sendCaption('transcriptUpdate', ev.delta, false)
  } else if (ev.type === 'session.input_transcript.done') {
    sendCaption('transcriptUpdate', ev.transcript || '', true)
  } else if (ev.type === 'error' || (ev.type || '').endsWith('.error')) {
    chrome.runtime.sendMessage({ action: 'proxyError', message: ev.error?.message || 'OpenAI error' })
  }
}

function sendCaption(action, text, isFinal) {
  if (!text) return
  chrome.runtime.sendMessage({ action, text, isFinal })
}

function applyDubMute() {
  if (!dubAudioEl) return
  dubAudioEl.muted = !dubbingEnabled || paused
}

// ---------- Live language switch (no reconnect) ----------
function setOutputLanguage(lang) {
  const payload = JSON.stringify({
    type: 'session.update',
    session: { audio: { output: { language: lang } } },
  })
  if (dcOpen && dataChannel) {
    try { dataChannel.send(payload) } catch {}
  } else {
    dcQueue.push(payload)
  }
}

// ---------- Pause / resume ----------
function pauseCapture() {
  paused = true
  if (audioSender) { try { audioSender.replaceTrack(null) } catch {} }
  applyDubMute()
}

function resumeCapture() {
  paused = false
  const track = streamRef?.getAudioTracks?.()[0]
  if (audioSender && track) { try { audioSender.replaceTrack(track) } catch {} }
  applyDubMute()
}

// ---------- Teardown ----------
function stopCapture() {
  stopLevelMeter()
  if (dataChannel) { try { dataChannel.close() } catch {} dataChannel = null }
  if (pc) { try { pc.close() } catch {} pc = null }
  audioSender = null
  dcOpen = false
  dcQueue = []
  if (dubAudioEl) { try { dubAudioEl.pause() } catch {} dubAudioEl.srcObject = null; dubAudioEl = null }
  if (originalAudioEl) { try { originalAudioEl.pause() } catch {} originalAudioEl.srcObject = null; originalAudioEl = null }
  if (streamRef) { streamRef.getTracks().forEach((t) => t.stop()); streamRef = null }
  paused = false
  dubbingEnabled = false
  currentTargetLang = null
}
