const statusEl = document.getElementById("status");
const outputEl = document.getElementById("output");
const healthBtn = document.getElementById("healthBtn");
const offerBtn = document.getElementById("offerBtn");
const resetBtn = document.getElementById("resetBtn");
const sendBtn = document.getElementById("sendBtn");
const chatInput = document.getElementById("chatInput");
const remoteVideo = document.getElementById("remoteVideo");
remoteVideo.muted = false;
remoteVideo.volume = 1.0;
const messagesEl = document.getElementById("messages");
const connBadge = document.getElementById("connBadge");
const mPeers = document.getElementById("mPeers");
const mBusy = document.getElementById("mBusy");
const mLatency = document.getElementById("mLatency");
const mAsrLatency = document.getElementById("mAsrLatency");
const mLlmLatency = document.getElementById("mLlmLatency");
const mLlmSource = document.getElementById("mLlmSource");
const mLlmHistory = document.getElementById("mLlmHistory");
const mTtsLatency = document.getElementById("mTtsLatency");
const mTtsFirst = document.getElementById("mTtsFirst");
const mTts = document.getElementById("mTts");
const mBargeIn = document.getElementById("mBargeIn");
const mInputMode = document.getElementById("mInputMode");
const mVadCount = document.getElementById("mVadCount");
const dConn = document.getElementById("dConn");
const dVad = document.getElementById("dVad");
const dAsr = document.getElementById("dAsr");
const dLlm = document.getElementById("dLlm");
const dTts = document.getElementById("dTts");

let pc = null;
let evt = null;
let subtitleWs = null;
let localMicStream = null;
let lastSeenRun = 0;
let lastBusy = false;
let aiStreamingEl = null;
const AUTO_RECONNECT_KEY = "visual_chat_auto_reconnect";

const stateMap = {
  "SessionState.IDLE": "空闲",
  "SessionState.USER_SPEAKING": "用户说话",
  "SessionState.THINKING": "思考中",
  "SessionState.AVATAR_SPEAKING": "数字人说话",
  idle: "空闲",
  user_speaking: "用户说话",
  thinking: "思考中",
  avatar_speaking: "数字人说话",
};

function fmtTime() {
  return new Date().toLocaleTimeString("zh-CN", { hour12: false });
}

function toCnState(raw) {
  return stateMap[raw] || raw;
}

function addMessage(role, text) {
  const row = document.createElement("div");
  row.className = "msg-row";
  const div = document.createElement("div");
  div.className = `msg ${role}`;
  div.textContent = text;
  const time = document.createElement("div");
  time.className = `msg-time ${role}`;
  time.textContent = fmtTime();
  row.appendChild(div);
  row.appendChild(time);
  messagesEl.appendChild(row);
  messagesEl.scrollTop = messagesEl.scrollHeight;
  return div;
}

function setConnBadge(label, kind = "neutral") {
  connBadge.textContent = label;
  connBadge.className = `badge ${kind}`;
}

function setDot(el, kind) {
  el.className = `dot ${kind}`;
}

function updateMetrics(data) {
  mPeers.textContent = String(data.peers ?? 0);
  mBusy.textContent = data.pipeline_busy ? "是" : "否";
  mLatency.textContent = `${data.last_latency_ms ?? 0} ms`;
  mAsrLatency.textContent = `${data.last_asr_latency_ms ?? 0} ms`;
  mLlmLatency.textContent = `${data.last_llm_latency_ms ?? 0} ms`;
  mLlmSource.textContent = data.last_llm_source === "online" ? "在线" : "兜底";
  mLlmHistory.textContent = String(data?.llm?.history_items ?? 0);
  mTtsLatency.textContent = `${data.last_tts_latency_ms ?? 0} ms`;
  mTtsFirst.textContent = `${data.last_tts_first_chunk_ms ?? 0} ms`;
  mTts.textContent = data?.tts?.ready ? "就绪" : "未就绪";
  mBargeIn.textContent = `${data.last_barge_in_ms ?? 0} ms (${data.barge_in_count ?? 0})`;
  mInputMode.textContent = data.last_input_mode === "voice" ? "语音" : data.last_input_mode === "text" ? "文本" : "none";
  mVadCount.textContent = `${data.vad_start_count ?? 0}/${data.vad_end_count ?? 0}`;

  setDot(dConn, (data.peers ?? 0) > 0 ? "ok" : "warn");
  setDot(dVad, (data.vad_start_count ?? 0) > 0 ? "ok" : "warn");
  setDot(dAsr, data?.asr?.ready ? "ok" : "warn");
  setDot(dLlm, data?.llm?.ready ? "ok" : "bad");
  setDot(dTts, data?.tts?.ready ? "ok" : "warn");
}

async function checkHealth() {
  const res = await fetch("/health");
  const data = await res.json();
  statusEl.textContent = `状态：${toCnState(data.state)}`;
  outputEl.textContent = JSON.stringify(data, null, 2);
  sendBtn.disabled = !!data.pipeline_busy;
  updateMetrics(data);
}

function subscribeEvents() {
  if (evt) return;
  evt = new EventSource("/events");
  evt.onmessage = (event) => {
    try {
      const data = JSON.parse(event.data);
      statusEl.textContent = `状态：${toCnState(data.state)}`;
      outputEl.textContent = JSON.stringify(data, null, 2);
      sendBtn.disabled = !!data.pipeline_busy;
      updateMetrics(data);
      lastBusy = !!data.pipeline_busy;
      if (typeof data.pipeline_runs === "number" && data.pipeline_runs > lastSeenRun) lastSeenRun = data.pipeline_runs;
    } catch (_) {
      // no-op
    }
  };
}

function subscribeSubtitles() {
  if (subtitleWs && (subtitleWs.readyState === WebSocket.OPEN || subtitleWs.readyState === WebSocket.CONNECTING)) return;
  const scheme = location.protocol === "https:" ? "wss" : "ws";
  subtitleWs = new WebSocket(`${scheme}://${location.host}/ws/subtitles`);
  subtitleWs.onmessage = (event) => {
    try {
      const data = JSON.parse(event.data);
      if (data.role === "user") {
        aiStreamingEl = null;
        addMessage(data.role, data.text || "");
      } else if (data.role === "ai") {
        if (data.partial) {
          if (!aiStreamingEl) aiStreamingEl = addMessage("ai", "");
          aiStreamingEl.textContent = `${aiStreamingEl.textContent}${data.text || ""}`;
          if (data.final) aiStreamingEl = null;
        } else {
          aiStreamingEl = null;
          addMessage("ai", data.text || "");
        }
      }
    } catch (_) {
      // no-op
    }
  };
  subtitleWs.onclose = () => {
    setTimeout(subscribeSubtitles, 1000);
  };
}

async function connectWebRTC() {
  if (pc) {
    outputEl.textContent = "WebRTC 已连接或正在连接";
    return;
  }
  setConnBadge("连接中...", "warn");
  offerBtn.disabled = true;

  const metaRes = await fetch("/meta");
  const meta = await metaRes.json();
  pc = new RTCPeerConnection({
    iceServers: [{ urls: [meta.stun_url || "stun:stun.l.google.com:19302"] }],
  });
  const currentPc = pc;
  let seenConnected = false;
  currentPc.onconnectionstatechange = () => {
    const st = currentPc.connectionState;
    if (st === "connected") {
      seenConnected = true;
      setConnBadge("已连接", "ok");
      localStorage.setItem(AUTO_RECONNECT_KEY, "1");
    }
    else if (st === "connecting") setConnBadge("连接中...", "warn");
    else if (st === "failed" || st === "disconnected" || st === "closed") {
      setConnBadge(`连接${st}`, "warn");
      if (seenConnected && (st === "disconnected" || st === "failed")) {
        addMessage("system", "连接已断开，可能被新的客户端连接接管。");
      }
      if (localMicStream) {
        localMicStream.getTracks().forEach((t) => t.stop());
        localMicStream = null;
      }
      if (pc === currentPc) {
        pc = null;
      }
      offerBtn.disabled = false;
    }
  };
  // Explicitly request remote media m-lines so server can attach tracks.
  pc.addTransceiver("video", { direction: "recvonly" });
  pc.ontrack = (event) => {
    if (event.streams && event.streams[0]) {
      remoteVideo.srcObject = event.streams[0];
      remoteVideo.play().catch(() => {
        addMessage("system", "远端音视频已到达，如未自动播放请点击视频区域后重试。");
      });
    }
  };

  let stream;
  try {
    stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
    localMicStream = stream;
  } catch (err) {
    statusEl.textContent = "状态：麦克风权限失败";
    outputEl.textContent = `getUserMedia 失败：${err?.message || err}\n提示：跨机器访问请使用 HTTPS 或浏览器安全例外。`;
    setConnBadge("麦克风失败", "warn");
    offerBtn.disabled = false;
    throw err;
  }
  stream.getTracks().forEach((track) => pc.addTrack(track, stream));
  const sendAudioTrack = stream.getAudioTracks()[0];
  if (sendAudioTrack) {
    sendAudioTrack.enabled = true;
  }

  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);

  const res = await fetch("/webrtc/offer", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      sdp: pc.localDescription.sdp,
      type: pc.localDescription.type,
    }),
  });
  const answer = await res.json();
  await pc.setRemoteDescription(answer);
  statusEl.textContent = "状态：WebRTC 已连接";
  outputEl.textContent = JSON.stringify(
    { connected: true, iceConnectionState: pc.iceConnectionState },
    null,
    2
  );
  setConnBadge("已连接", "ok");
  addMessage("system", "WebRTC 已连接，可开始对话。");
  if (answer.replaced_previous) {
    addMessage("system", "检测到已有旧连接，已自动断开旧连接并由当前页面接管。");
  }

  subscribeEvents();
}

async function sendText() {
  const text = (chatInput.value || "").trim();
  if (!text) return;
  if (sendBtn.disabled) return;
  sendBtn.disabled = true;
  const res = await fetch("/chat/text", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ text }),
  });
  const data = await res.json();
  outputEl.textContent = JSON.stringify(data, null, 2);
  if (data.ok) {
    chatInput.value = "";
  } else if (data.message) {
    addMessage("system", `发送失败：${data.message}`);
  }
  sendBtn.disabled = false;
}

async function resetChat() {
  await fetch("/chat/reset", { method: "POST" });
  messagesEl.innerHTML = "";
  addMessage("system", "会话已重置。");
  await checkHealth();
}

function tryAutoReconnect() {
  if (localStorage.getItem(AUTO_RECONNECT_KEY) !== "1") return;
  setTimeout(() => {
    if (!pc) connectWebRTC().catch(() => {
      // no-op: user can reconnect manually if permission/policy blocks auto flow
    });
  }, 300);
}

healthBtn.addEventListener("click", checkHealth);
offerBtn.addEventListener("click", connectWebRTC);
resetBtn.addEventListener("click", resetChat);
sendBtn.addEventListener("click", sendText);
chatInput.addEventListener("keydown", (e) => {
  if (e.key === "Enter") {
    e.preventDefault();
    sendText();
  }
});
checkHealth();
subscribeEvents();
subscribeSubtitles();
setConnBadge("未连接", "neutral");
tryAutoReconnect();
