const chatLog = document.getElementById("chat-log");
const chatInput = document.getElementById("chat-text");
const chatSend = document.getElementById("chat-send");
const chatSessionLabel = document.getElementById("chat-session");
const chatStateLabel = document.getElementById("chat-state");
const chatScoreLabel = document.getElementById("chat-score");
const chatTurnsLabel = document.getElementById("chat-turns");
const chatStatus = document.getElementById("chat-status");

const voiceStatus = document.getElementById("voice-status");
const voiceWsLabel = document.getElementById("voice-ws");
const voiceAgentLabel = document.getElementById("voice-agent");
const voiceLog = document.getElementById("voice-log");
const bargeInToggle = document.getElementById("barge-in");

const apiKeyInput = document.getElementById("api-key");
const userIdInput = document.getElementById("user-id");
const orgIdInput = document.getElementById("org-id");
const planSelect = document.getElementById("plan");

const connectBtn = document.getElementById("voice-connect");
const micBtn = document.getElementById("voice-mic");
const stopBtn = document.getElementById("voice-stop");

let chatSessionId = null;
let accessToken = null;
let voiceSocket = null;
let micStream = null;
let micProcessor = null;
let micContext = null;
let micGain = null;
let playbackContext = null;
let nextPlayTime = 0;
let activeSources = [];
let agentState = "idle";
let interruptSent = false;
let vadActive = false;
let vadSilenceFrames = 0;
let micSuppressed = false;
let lastVadState = false;

const TARGET_SAMPLE_RATE = 16000;
const MIC_BUFFER_SIZE = 1024;
const VAD_THRESHOLD = 0.02;
const VAD_HOLD_FRAMES = 8;

function addChatItem(role, text) {
  const item = document.createElement("div");
  item.className = `chat-item ${role}`;
  const label = document.createElement("div");
  label.className = "label";
  label.textContent = role === "user" ? "You" : "Sales Brain";
  const body = document.createElement("div");
  body.textContent = text;
  item.appendChild(label);
  item.appendChild(body);
  chatLog.appendChild(item);
  chatLog.scrollTop = chatLog.scrollHeight;
}

function addVoiceItem(role, text) {
  const item = document.createElement("div");
  item.className = `chat-item ${role}`;
  const label = document.createElement("div");
  label.className = "label";
  label.textContent = role === "user" ? "Caller" : "Agent";
  const body = document.createElement("div");
  body.textContent = text;
  item.appendChild(label);
  item.appendChild(body);
  voiceLog.appendChild(item);
  voiceLog.scrollTop = voiceLog.scrollHeight;
}

async function sendChat() {
  const text = chatInput.value.trim();
  if (!text) return;
  addChatItem("user", text);
  chatInput.value = "";
  chatStatus.textContent = "thinking";
  try {
    const res = await fetch("/api/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ session_id: chatSessionId, message: text }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.detail || "Chat error");
    chatSessionId = data.session_id;
    chatSessionLabel.textContent = chatSessionId;
    chatStateLabel.textContent = data.state;
    chatScoreLabel.textContent = data.lead_score;
    chatTurnsLabel.textContent = data.turns ?? "0";
    addChatItem("agent", data.reply);
  } catch (err) {
    addChatItem("agent", `Error: ${err.message}`);
  } finally {
    chatStatus.textContent = "ready";
  }
}

chatSend.addEventListener("click", sendChat);
chatInput.addEventListener("keydown", (event) => {
  if (event.key === "Enter") {
    sendChat();
  }
});

async function fetchAccessToken() {
  if (accessToken) return accessToken;
  const apiKey = apiKeyInput.value.trim();
  if (!apiKey) throw new Error("Missing API key");
  const res = await fetch("/api/auth/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": apiKey,
    },
    body: JSON.stringify({
      user_id: userIdInput.value.trim() || "demo_user",
      org: orgIdInput.value.trim() || null,
      plan: planSelect.value,
    }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.detail || "Auth error");
  accessToken = data.access_token;
  return accessToken;
}

async function connectVoice() {
  if (voiceSocket && voiceSocket.readyState === WebSocket.OPEN) {
    return;
  }
  voiceStatus.textContent = "connecting";
  try {
    const token = await fetchAccessToken();
    const res = await fetch("/api/voice/session", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify({ format: "pcm16", sample_rate: TARGET_SAMPLE_RATE }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.detail || "Voice session error");

    const wsUrl = `${data.ws_url}?token=${encodeURIComponent(data.token)}`;
    voiceSocket = new WebSocket(wsUrl);
    voiceSocket.binaryType = "arraybuffer";

    voiceSocket.onopen = () => {
      voiceStatus.textContent = "connected";
      voiceWsLabel.textContent = "open";
      voiceSocket.send(
        JSON.stringify({
          type: "session.start",
          format: "pcm16",
          sample_rate: TARGET_SAMPLE_RATE,
          barge_in: bargeInToggle.checked,
        })
      );
    };

    voiceSocket.onmessage = (event) => {
      if (typeof event.data !== "string") return;
      const payload = JSON.parse(event.data);
      handleVoiceMessage(payload);
    };

    voiceSocket.onclose = () => {
      voiceStatus.textContent = "disconnected";
      voiceWsLabel.textContent = "closed";
      voiceAgentLabel.textContent = "idle";
    };

    voiceSocket.onerror = () => {
      voiceStatus.textContent = "error";
    };
  } catch (err) {
    voiceStatus.textContent = "error";
    addVoiceItem("agent", `Error: ${err.message}`);
  }
}

function handleVoiceMessage(payload) {
  if (!payload || !payload.type) return;
  if (payload.type === "agent.state") {
    agentState = payload.state;
    voiceAgentLabel.textContent = payload.state;
    interruptSent = false;
    micSuppressed = payload.state === "speaking" && !bargeInToggle.checked;
    return;
  }
  if (payload.type === "stt.final") {
    addVoiceItem("user", payload.text);
    return;
  }
  if (payload.type === "agent.text.final") {
    addVoiceItem("agent", payload.text);
    return;
  }
  if (payload.type === "tts.audio.chunk") {
    playPCM16(payload.audio);
    return;
  }
  if (payload.type === "tts.error") {
    addVoiceItem("agent", `TTS error: ${payload.detail || "no audio"}`);
    return;
  }
  if (payload.type === "agent.error") {
    addVoiceItem("agent", `Agent error: ${payload.detail || "LLM issue"}`);
    return;
  }
  if (payload.type === "error") {
    addVoiceItem("agent", `Error: ${payload.code || "unknown"}`);
  }
}

async function startMic() {
  if (!voiceSocket || voiceSocket.readyState !== WebSocket.OPEN) {
    await connectVoice();
  }
  if (micStream) return;

  micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
  micContext = new (window.AudioContext || window.webkitAudioContext)();
  await micContext.resume();
  const source = micContext.createMediaStreamSource(micStream);
  micProcessor = micContext.createScriptProcessor(MIC_BUFFER_SIZE, 1, 1);
  micGain = micContext.createGain();
  micGain.gain.value = 0;
  source.connect(micProcessor);
  micProcessor.connect(micGain);
  micGain.connect(micContext.destination);

  micProcessor.onaudioprocess = (event) => {
    if (!voiceSocket || voiceSocket.readyState !== WebSocket.OPEN) return;
    if (agentState === "speaking" && !bargeInToggle.checked) return;
    const input = event.inputBuffer.getChannelData(0);
    const level = rmsLevel(input);
    if (level > VAD_THRESHOLD) {
      vadActive = true;
      vadSilenceFrames = 0;
    } else if (vadActive) {
      vadSilenceFrames += 1;
      if (vadSilenceFrames > VAD_HOLD_FRAMES) {
        vadActive = false;
      }
    }
    if (vadActive !== lastVadState && voiceSocket && voiceSocket.readyState === WebSocket.OPEN) {
      voiceSocket.send(JSON.stringify({ type: vadActive ? "vad.start" : "vad.end" }));
      lastVadState = vadActive;
    }
    if (!vadActive) return;
    const downsampled = downsampleBuffer(input, micContext.sampleRate, TARGET_SAMPLE_RATE);
    const pcm16 = floatTo16BitPCM(downsampled);
    if (agentState === "speaking" && bargeInToggle.checked && !interruptSent) {
      interruptSent = true;
      stopPlayback();
      voiceSocket.send(JSON.stringify({ type: "control.interrupt" }));
    }
    voiceSocket.send(pcm16.buffer);
  };
}

function stopMic() {
  if (micProcessor) {
    micProcessor.disconnect();
    micProcessor.onaudioprocess = null;
    micProcessor = null;
  }
  if (micGain) {
    micGain.disconnect();
    micGain = null;
  }
  if (micStream) {
    micStream.getTracks().forEach((track) => track.stop());
    micStream = null;
  }
  if (micContext) {
    micContext.close();
    micContext = null;
  }
}

function closeVoice() {
  stopMic();
  if (voiceSocket && voiceSocket.readyState === WebSocket.OPEN) {
    voiceSocket.send(JSON.stringify({ type: "session.end" }));
    voiceSocket.close();
  }
  voiceSocket = null;
}

function downsampleBuffer(buffer, sampleRate, targetRate) {
  if (targetRate === sampleRate) return buffer;
  const ratio = sampleRate / targetRate;
  const newLength = Math.round(buffer.length / ratio);
  const result = new Float32Array(newLength);
  let offset = 0;
  for (let i = 0; i < newLength; i++) {
    const nextOffset = Math.round((i + 1) * ratio);
    let sum = 0;
    let count = 0;
    for (let j = offset; j < nextOffset && j < buffer.length; j++) {
      sum += buffer[j];
      count += 1;
    }
    result[i] = sum / count;
    offset = nextOffset;
  }
  return result;
}

function floatTo16BitPCM(float32Array) {
  const output = new Int16Array(float32Array.length);
  for (let i = 0; i < float32Array.length; i++) {
    const s = Math.max(-1, Math.min(1, float32Array[i]));
    output[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
  }
  return output;
}

function rmsLevel(float32Array) {
  let sum = 0;
  for (let i = 0; i < float32Array.length; i++) {
    sum += float32Array[i] * float32Array[i];
  }
  return Math.sqrt(sum / float32Array.length);
}

function ensurePlayback() {
  if (!playbackContext) {
    playbackContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: TARGET_SAMPLE_RATE });
  }
  playbackContext.resume();
}

function playPCM16(base64Audio) {
  if (!base64Audio) return;
  ensurePlayback();
  const raw = atob(base64Audio);
  const buffer = new ArrayBuffer(raw.length);
  const view = new Uint8Array(buffer);
  for (let i = 0; i < raw.length; i++) {
    view[i] = raw.charCodeAt(i);
  }
  const int16 = new Int16Array(buffer);
  const float32 = new Float32Array(int16.length);
  for (let i = 0; i < int16.length; i++) {
    float32[i] = int16[i] / 0x8000;
  }
  const audioBuffer = playbackContext.createBuffer(1, float32.length, TARGET_SAMPLE_RATE);
  audioBuffer.copyToChannel(float32, 0);
  const source = playbackContext.createBufferSource();
  source.buffer = audioBuffer;
  source.connect(playbackContext.destination);
  const startAt = Math.max(playbackContext.currentTime, nextPlayTime);
  source.start(startAt);
  nextPlayTime = startAt + audioBuffer.duration;
  activeSources.push(source);
  source.onended = () => {
    activeSources = activeSources.filter((s) => s !== source);
  };
}

function stopPlayback() {
  if (!playbackContext) return;
  activeSources.forEach((source) => {
    try {
      source.stop();
    } catch (err) {
      return;
    }
  });
  activeSources = [];
  nextPlayTime = playbackContext.currentTime;
}

connectBtn.addEventListener("click", connectVoice);
micBtn.addEventListener("click", startMic);
stopBtn.addEventListener("click", closeVoice);
