"use client";

import { useCallback, useEffect, useRef, useState, type Dispatch, type SetStateAction } from "react";
import type { Message } from "./ChatPane";

const API_BASE =
  process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8000";

const WS_BASE = API_BASE.replace(/^http/, "ws");

type Props = {
  slug: string;
  messages: Message[];
  setMessages: Dispatch<SetStateAction<Message[]>>;
  sessionId: string | null;
  onSessionIdChange: (sessionId: string) => void;
};

export default function VoicePane({
  slug,
  messages,
  setMessages,
  sessionId,
  onSessionIdChange,
}: Props) {
  const [status, setStatus] = useState<
    "idle" | "connecting" | "ready" | "recording" | "thinking" | "speaking" | "error"
  >("idle");
  const [errorMsg, setErrorMsg] = useState("");

  const wsRef = useRef<WebSocket | null>(null);
  const audioCtxRef = useRef<AudioContext | null>(null);
  const workletRef = useRef<AudioWorkletNode | null>(null);
  const streamRef = useRef<MediaStream | null>(null);
  const playbackQueueRef = useRef<ArrayBuffer[]>([]);
  const playingRef = useRef(false);
  const holdingRef = useRef(false);
  const bottomRef = useRef<HTMLDivElement>(null);

  // 24000 Hz matches Kokoro output sample rate
  const TTS_SAMPLE_RATE = 24000;

  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  // ── PCM playback queue ─────────────────────────────────────────────────────

  // Store drain function in a ref to avoid closure/self-reference lint issues.
  const drainRef = useRef<() => void>(() => {});

  useEffect(() => {
    function drain() {
      const ctx = audioCtxRef.current;
      if (!ctx || playbackQueueRef.current.length === 0) {
        playingRef.current = false;
        return;
      }
      playingRef.current = true;
      const buf = playbackQueueRef.current.shift()!;
      const samples = new Int16Array(buf);
      const float32 = new Float32Array(samples.length);
      for (let i = 0; i < samples.length; i++) {
        float32[i] = samples[i] / 32768;
      }
      const audioBuf = ctx.createBuffer(1, float32.length, TTS_SAMPLE_RATE);
      audioBuf.copyToChannel(float32, 0);
      const src = ctx.createBufferSource();
      src.buffer = audioBuf;
      src.connect(ctx.destination);
      src.onended = () => drainRef.current();
      src.start();
    }
    drainRef.current = drain;
  }, [TTS_SAMPLE_RATE]);

  const enqueuePCM = useCallback((buf: ArrayBuffer) => {
    playbackQueueRef.current.push(buf);
    if (!playingRef.current) drainRef.current();
  }, []);

  // ── WebSocket setup ────────────────────────────────────────────────────────

  const connect = useCallback(async () => {
    if (wsRef.current) return;
    setStatus("connecting");

    try {
      const ctx = new AudioContext({ sampleRate: 16000 });
      audioCtxRef.current = ctx;
      await ctx.audioWorklet.addModule("/audio-capture-processor.js");

      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;

      const source = ctx.createMediaStreamSource(stream);
      const worklet = new AudioWorkletNode(ctx, "audio-capture-processor");
      workletRef.current = worklet;
      source.connect(worklet);
      worklet.connect(ctx.destination);

      worklet.port.onmessage = (e) => {
        if (e.data.type === "pcm" && wsRef.current?.readyState === WebSocket.OPEN) {
          wsRef.current.send(e.data.buffer);
        }
      };
    } catch {
      setStatus("error");
      setErrorMsg("Microphone access denied.");
      return;
    }

    const query = sessionId ? `?session_id=${encodeURIComponent(sessionId)}` : "";
    const ws = new WebSocket(`${WS_BASE}/api/voice/${slug}${query}`);
    ws.binaryType = "arraybuffer";
    wsRef.current = ws;

    ws.onopen = () => {};

    ws.onmessage = (e) => {
      if (e.data instanceof ArrayBuffer) {
        enqueuePCM(e.data);
        setStatus("speaking");
        return;
      }
      const msg = JSON.parse(e.data as string);
      if (msg.type === "session") {
        onSessionIdChange(msg.session_id);
        setStatus("ready");
      } else if (msg.type === "transcript") {
        if (msg.text) {
          setMessages((turns) => [
            ...turns,
            { role: "user", content: msg.text },
            { role: "assistant", content: "", streaming: true },
          ]);
          setStatus("thinking");
        }
      } else if (msg.type === "text") {
        setMessages((turns) => {
          const updated = [...turns];
          const last = updated[updated.length - 1];
          if (last?.role === "assistant") {
            updated[updated.length - 1] = { ...last, content: last.content + msg.text };
          }
          return updated;
        });
      } else if (msg.type === "done") {
        setMessages((turns) => {
          const updated = [...turns];
          const last = updated[updated.length - 1];
          if (last?.role === "assistant") {
            updated[updated.length - 1] = { ...last, streaming: false };
          }
          return updated;
        });
        setStatus("ready");
      } else if (msg.type === "error") {
        setStatus("error");
        setErrorMsg(msg.detail ?? "Unknown error");
      }
    };

    ws.onerror = () => {
      setStatus("error");
      setErrorMsg("WebSocket error — is the runtime running?");
    };

    ws.onclose = () => {
      wsRef.current = null;
      setStatus("idle");
    };
  }, [slug, enqueuePCM, onSessionIdChange, sessionId, setMessages]);

  const disconnect = useCallback(() => {
    workletRef.current?.port.postMessage({ type: "stop" });
    wsRef.current?.close();
    streamRef.current?.getTracks().forEach((t) => t.stop());
    audioCtxRef.current?.close();
    wsRef.current = null;
    streamRef.current = null;
    audioCtxRef.current = null;
    workletRef.current = null;
    playbackQueueRef.current = [];
    playingRef.current = false;
    setStatus("idle");
  }, []);

  // ── Hold-to-talk ───────────────────────────────────────────────────────────

  const startRecording = useCallback(() => {
    if (status !== "ready") return;
    holdingRef.current = true;
    workletRef.current?.port.postMessage({ type: "start" });
    wsRef.current?.send(JSON.stringify({ type: "start" }));
    setStatus("recording");
  }, [status]);

  const stopRecording = useCallback(() => {
    if (!holdingRef.current) return;
    holdingRef.current = false;
    workletRef.current?.port.postMessage({ type: "stop" });
    wsRef.current?.send(JSON.stringify({ type: "stop" }));
    setStatus("thinking");
  }, []);

  // Spacebar hold-to-talk
  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code === "Space" && !e.repeat) startRecording();
    };
    const onKeyUp = (e: KeyboardEvent) => {
      if (e.code === "Space") stopRecording();
    };
    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("keyup", onKeyUp);
    return () => {
      window.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("keyup", onKeyUp);
    };
  }, [startRecording, stopRecording]);

  // Scroll transcript assistant responses when audio plays
  useEffect(() => {
    if (status === "speaking") {
      bottomRef.current?.scrollIntoView({ behavior: "smooth" });
    }
  }, [status]);

  // ── Labels ─────────────────────────────────────────────────────────────────

  const statusLabel: Record<typeof status, string> = {
    idle: "Click mic to connect",
    connecting: "Connecting…",
    ready: "Hold SPACE or button to speak",
    recording: "● Recording…",
    thinking: "Thinking…",
    speaking: "Speaking…",
    error: errorMsg || "Error",
  };

  const isRecordable = status === "ready";

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", gap: 12 }}>
      {/* Transcript */}
      <div
        style={{
          flex: 1,
          overflowY: "auto",
          display: "flex",
          flexDirection: "column",
          gap: 8,
          padding: "0 4px",
        }}
      >
        {messages.length === 0 && (
          <p style={{ color: "var(--fg-muted)", fontSize: 13, margin: "auto 0" }}>
            Connect a mic to start a voice conversation.
          </p>
        )}
        {messages.map((turn, i) => (
          <div
            key={i}
            style={{
              alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
              background: turn.role === "user" ? "var(--accent)" : "var(--surface)",
              color: turn.role === "user" ? "#1a1209" : "var(--fg)",
              borderRadius: 10,
              padding: "8px 12px",
              maxWidth: "80%",
              fontSize: 14,
              lineHeight: 1.5,
            }}
          >
            {turn.content}
            {turn.streaming && <span> ▌</span>}
          </div>
        ))}
        <div ref={bottomRef} />
      </div>

      {/* Status bar */}
      <p
        style={{
          fontSize: 12,
          color: status === "error" ? "#e05252" : "var(--fg-muted)",
          textAlign: "center",
          margin: 0,
        }}
      >
        {statusLabel[status]}
      </p>

      {/* Controls */}
      <div style={{ display: "flex", gap: 8, justifyContent: "center", alignItems: "center" }}>
        {status === "idle" ? (
          <button className="send-btn" onClick={connect} style={{ padding: "10px 24px" }}>
            🎙 Connect mic
          </button>
        ) : (
          <>
            <button
              className="send-btn"
              onPointerDown={startRecording}
              onPointerUp={stopRecording}
              onPointerLeave={stopRecording}
              disabled={!isRecordable}
              style={{
                padding: "10px 32px",
                background: status === "recording" ? "#c23b3b" : undefined,
                opacity: isRecordable ? 1 : 0.5,
                userSelect: "none",
              }}
            >
              {status === "recording" ? "● Release to send" : "🎙 Hold to speak"}
            </button>
            <button
              onClick={disconnect}
              style={{
                background: "none",
                border: "1px solid var(--border)",
                borderRadius: 6,
                color: "var(--fg-muted)",
                padding: "8px 14px",
                cursor: "pointer",
                fontSize: 12,
              }}
            >
              Disconnect
            </button>
          </>
        )}
      </div>
    </div>
  );
}
