"use client";

import { useState, useRef, useCallback, useEffect } from "react";
import {
  Room,
  RoomEvent,
  Track,
  type Participant,
  type RemoteTrack,
} from "livekit-client";

export type VoiceState = "idle" | "listening" | "thinking" | "speaking";

export type VoiceMessage = {
  sender: "user" | "bot";
  text: string;
  timestamp: number;
};

const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8001/api/v1";

async function fetchLiveKitToken(room: string, identity: string) {
  const res = await fetch(
    `${API_URL}/livekit/token?room=${encodeURIComponent(room)}&identity=${encodeURIComponent(identity)}`
  );
  if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`);
  return res.json() as Promise<{ token: string; url: string }>;
}

function randomId() {
  return Math.random().toString(36).slice(2, 10);
}

export function useLiveKitVoice(onVoiceCommand?: (command: string) => void) {
  const [voiceState, setVoiceState] = useState<VoiceState>("idle");
  const [audioLevel] = useState(0);
  const [voiceMessages, setVoiceMessages] = useState<VoiceMessage[]>([]);
  const [interimText, setInterimText] = useState<string>("");

  const roomRef  = useRef<Room | null>(null);
  const stateRef = useRef<VoiceState>("idle");

  const updateState = useCallback((s: VoiceState) => {
    stateRef.current = s;
    setVoiceState(s);
  }, []);

  const addMessage = useCallback((sender: "user" | "bot", text: string) => {
    setVoiceMessages((prev) => [...prev, { sender, text, timestamp: Date.now() }]);
  }, []);

  useEffect(() => {
    return () => { roomRef.current?.disconnect().catch(() => {}); };
  }, []);

  const startConversation = useCallback(async () => {
    if (roomRef.current) return;

    setVoiceMessages([]);

    const roomName = `karma-${randomId()}`;
    const identity = `user-${randomId()}`;

    let tokenData: { token: string; url: string };
    try {
      tokenData = await fetchLiveKitToken(roomName, identity);
    } catch (err) {
      console.error("[voice] Token fetch failed:", err);
      return;
    }

    const room = new Room({ adaptiveStream: true, dynacast: true });
    roomRef.current = room;

    // ── Voice activity state transitions ────────────────────────────────────
    room.on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {
      const localSid      = room.localParticipant.sid;
      // Any non-local speaker = the agent (only two participants in the room)
      const agentSpeaking = speakers.some((p) => p.sid !== localSid);
      const userSpeaking  = speakers.some((p) => p.sid === localSid);

      if (agentSpeaking) {
        updateState("speaking");
      } else if (userSpeaking) {
        updateState("listening");
      } else {
        const prev = stateRef.current;
        if (prev === "listening") updateState("thinking");
        else if (prev === "speaking") updateState("listening");
      }
    });

    // ── Data messages from agent (interim + transcripts + responses) ─────────
    room.on(RoomEvent.DataReceived, (payload: Uint8Array) => {
      try {
        const msg = JSON.parse(new TextDecoder().decode(payload)) as {
          type: "user_interim" | "user_transcript" | "agent_response" | "voice_command";
          text: string;
          command?: string;
        };
        if (msg.type === "user_interim") {
          setInterimText(msg.text);
        } else if (msg.type === "user_transcript" && msg.text) {
          setInterimText("");
          addMessage("user", msg.text);
        } else if (msg.type === "agent_response" && msg.text) {
          addMessage("bot", msg.text);
        } else if (msg.type === "voice_command" && msg.command) {
          // Navigation commands ("start_fresh", "continue") — handled by ChatbotWindow
          if (onVoiceCommand) onVoiceCommand(msg.command);
          if (msg.command === "start_fresh") {
            setVoiceMessages([]);
            setInterimText("");
          }
        }
      } catch {
        // ignore malformed messages
      }
    });

    // ── Auto-attach agent audio tracks ──────────────────────────────────────
    const attachedElements = new Map<RemoteTrack, HTMLAudioElement>();

    room.on(RoomEvent.TrackSubscribed, (track: RemoteTrack) => {
      if (track.kind === Track.Kind.Audio) {
        const el = track.attach() as HTMLAudioElement;
        el.autoplay = true;
        el.style.display = "none";
        document.body.appendChild(el);
        attachedElements.set(track, el);
      }
    });

    room.on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {
      const el = attachedElements.get(track);
      if (el) { el.remove(); attachedElements.delete(track); }
    });

    room.on(RoomEvent.Disconnected, () => {
      updateState("idle");
      roomRef.current = null;
    });

    try {
      await room.connect(tokenData.url, tokenData.token);
      // Echo cancellation removes the agent's speaker audio from the mic input.
      // Noise suppression + autoGainControl filter out background room noise.
      await room.localParticipant.setMicrophoneEnabled(true, {
        echoCancellation: true,
        noiseSuppression: true,
        autoGainControl: true,
      });
      updateState("listening");
    } catch (err) {
      console.error("[voice] Room connect failed:", err);
      await room.disconnect().catch(() => {});
      roomRef.current = null;
      updateState("idle");
    }
  }, [updateState, addMessage]);

  const stopConversation = useCallback(async () => {
    const room = roomRef.current;
    roomRef.current = null;
    updateState("idle");
    setInterimText("");
    if (room) await room.disconnect().catch(() => {});
  }, [updateState]);

  const interruptAndListen = useCallback(() => {
    if (stateRef.current === "speaking") updateState("listening");
  }, [updateState]);

  const stopAndSend = useCallback(() => {}, []);

  return {
    voiceState,
    audioLevel,
    voiceMessages,
    interimText,
    startConversation,
    stopConversation,
    interruptAndListen,
    stopAndSend,
  };
}
