/* ========================================================================
   voice-client.jsx, useWonderfulCall hook
   Wraps WonderfulVoice.Client (telephony WS, mic, playback) and a second
   WS for the live transcript stream.
   ======================================================================== */

function fmtMMSS(seconds) {
  const s = Math.max(0, Math.floor(seconds || 0));
  const m = Math.floor(s / 60);
  const r = s % 60;
  return `${String(m).padStart(2, "0")}:${String(r).padStart(2, "0")}`;
}

function mapSpeaker(raw) {
  const s = String(raw || "").toLowerCase();
  if (s === "agent" || s === "assistant" || s === "ai" || s === "bot") return "hala";
  if (s === "user" || s === "human" || s === "customer" || s === "human_agent") return "sarah";
  return "system";
}

function summarizeServerEvent(msg) {
  const name = msg?.event || msg?.data?.event || "event";
  const d = msg?.data || {};
  const parts = [];
  if (d.instruction) parts.push(d.instruction);
  else {
    for (const [k, v] of Object.entries(d)) {
      if (k === "event" || k === "type") continue;
      if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
        parts.push(`${k}: ${v}`);
      }
    }
  }
  return `${name}${parts.length ? " · " + parts.join(" · ") : ""}`;
}

// Server events we never want to surface in the UI. Matches by event name
// regardless of whether the event arrives as a top-level server.event message
// or embedded as JSON text inside a system transcript row.
const HIDDEN_SERVER_EVENTS = new Set(["gender_classified"]);

function isHiddenEventPayload(value) {
  if (!value || typeof value !== "object") return false;
  const name = value.event || value?.data?.event;
  return !!(name && HIDDEN_SERVER_EVENTS.has(name));
}

function textLooksHidden(text) {
  if (typeof text !== "string") return false;
  const trimmed = text.trim();
  if (!trimmed || (trimmed[0] !== "{" && trimmed[0] !== "[")) return false;
  try { return isHiddenEventPayload(JSON.parse(trimmed)); }
  catch { return false; }
}

function extractEntry(msg) {
  // Server events render as system rows, deduped by data.id when present.
  if (msg?.type === "server.event" || msg?.event) {
    if (isHiddenEventPayload(msg)) return null;
    return {
      id: msg?.data?.id || null,
      who: "system",
      text: summarizeServerEvent(msg),
    };
  }

  const data = msg?.data || {};
  const speakerRaw = msg?.speaker || data?.speaker || msg?.role || data?.role;
  if (!speakerRaw) return null;

  const text =
    data?.text ||
    data?.transcript ||
    msg?.text ||
    msg?.transcript ||
    (Array.isArray(data?.content)
      ? data.content.map((c) => c.text || c.transcript).filter(Boolean).join(" ")
      : "");
  if (!text || typeof text !== "string") return null;

  // Some backends pipe server events through the transcript stream as a
  // system row whose text is the stringified event. Drop those too.
  if (mapSpeaker(speakerRaw) === "system" && textLooksHidden(text)) return null;

  return {
    id: data?.id || null,
    who: mapSpeaker(speakerRaw),
    text: text.trim(),
    tool: data?.tool_details || null,
  };
}

function useWonderfulCall({ active }) {
  const [status, setStatus] = React.useState("idle");
  const [elapsed, setElapsed] = React.useState(0);
  const [turns, setTurns] = React.useState([]);
  const [error, setError] = React.useState(null);
  const [muted, setMuted] = React.useState(false);

  const clientRef = React.useRef(null);
  const transcriptWsRef = React.useRef(null);
  const elapsedRef = React.useRef(0);
  const tickRef = React.useRef(null);

  const cleanup = React.useCallback(() => {
    if (transcriptWsRef.current) {
      try { transcriptWsRef.current.close(); } catch {}
      transcriptWsRef.current = null;
    }
    if (clientRef.current) {
      try { clientRef.current.disconnect(); } catch {}
      try { clientRef.current.destroy(); } catch {}
      clientRef.current = null;
    }
    if (tickRef.current) {
      clearInterval(tickRef.current);
      tickRef.current = null;
    }
  }, []);

  const openTranscriptWs = React.useCallback((communicationId) => {
    const cfg = window.WONDERFUL_CONFIG;
    if (!cfg || !communicationId) return;
    const url = `wss://${cfg.host}/api/v1/communications/${communicationId}/live`;
    let ws;
    try {
      ws = new WebSocket(url, ["apikey", cfg.apiKey]);
    } catch (e) {
      console.warn("[voice] transcript ws ctor failed", e);
      return;
    }
    transcriptWsRef.current = ws;
    ws.onopen = () => console.log("[voice] transcript ws open", communicationId);
    ws.onerror = (e) => console.warn("[voice] transcript ws error", e);
    ws.onclose = (e) => console.log("[voice] transcript ws close", e.code, e.reason);
    ws.onmessage = (ev) => {
      let msg;
      try { msg = typeof ev.data === "string" ? JSON.parse(ev.data) : ev.data; }
      catch { console.log("[voice] transcript non-json", ev.data); return; }
      console.log("[voice] transcript msg", msg);
      const entry = extractEntry(msg);
      if (!entry) return;
      setTurns((prev) => {
        if (entry.id) {
          const idx = prev.findIndex((t) => t.id === entry.id);
          if (idx >= 0) {
            const next = prev.slice();
            next[idx] = { ...next[idx], text: entry.text, who: entry.who, tool: entry.tool || next[idx].tool };
            return next;
          }
        }
        const last = prev[prev.length - 1];
        if (last && last.who === entry.who && last.text === entry.text && !entry.tool) return prev;
        return [
          ...prev,
          { id: entry.id, who: entry.who, t: fmtMMSS(elapsedRef.current), text: entry.text, tool: entry.tool || null },
        ];
      });
    };
  }, []);

  const start = React.useCallback(async () => {
    const cfg = window.WONDERFUL_CONFIG;
    if (!cfg) { setError("Missing WONDERFUL_CONFIG"); return; }
    if (!window.WonderfulVoice?.Client) { setError("WonderfulVoice client not loaded"); return; }
    if (clientRef.current) return;

    setError(null);
    setTurns([]);
    elapsedRef.current = 0;
    setElapsed(0);

    let client;
    try {
      client = new window.WonderfulVoice.Client({
        host: cfg.host,
        agentId: cfg.agentId,
        apiKey: cfg.apiKey,
      });
    } catch (e) {
      setError(e?.message || "Failed to create client");
      return;
    }
    clientRef.current = client;

    client.on("statusChange", (s) => {
      setStatus(s);
      if (s === "connected" && !tickRef.current) {
        const t0 = Date.now();
        tickRef.current = setInterval(() => {
          const v = Math.floor((Date.now() - t0) / 1000);
          elapsedRef.current = v;
          setElapsed(v);
        }, 250);
      }
      if (s === "disconnected" || s === "idle") {
        if (tickRef.current) { clearInterval(tickRef.current); tickRef.current = null; }
      }
    });
    client.on("communicationId", (id) => {
      console.log("[voice] communicationId", id);
      openTranscriptWs(id);
    });
    client.on("error", (m) => setError(m));

    try {
      await client.connect();
    } catch (e) {
      setError(e?.message || "Failed to connect");
      cleanup();
    }
  }, [openTranscriptWs, cleanup]);

  const end = React.useCallback(() => {
    cleanup();
    setStatus("disconnected");
    setMuted(false);
  }, [cleanup]);

  const toggleMute = React.useCallback(() => {
    const c = clientRef.current;
    if (!c) return;
    if (c.isMuted) { c.unmute(); setMuted(false); }
    else { c.mute(); setMuted(true); }
  }, []);

  // Auto-end if user navigates away from the scene mid-call.
  React.useEffect(() => {
    if (!active && clientRef.current) {
      cleanup();
      setStatus("idle");
      setElapsed(0);
      elapsedRef.current = 0;
    }
  }, [active, cleanup]);

  React.useEffect(() => () => cleanup(), [cleanup]);

  return { status, elapsed, turns, start, end, error, muted, toggleMute };
}

window.useWonderfulCall = useWonderfulCall;
