"use client";
// This hook manages the WebSocket connection to the chatbot server, including authentication, message handling, and reconnection logic.
import { useState, useRef, useEffect, useCallback } from "react";
import type { Message, ChatServerEvent } from "@/types/chatbot";
import { createChatSocket } from "@/lib/chatSocket";
import Cookies from "js-cookie";
import { jwtDecode } from "jwt-decode";

const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL || "ws://localhost:8001";
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8001";

const MIN_TYPING_TIME = 300;

function stripHtml(html: string): string {
  const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
  // Decode HTML entities so TTS doesn't say "ampersand semicolon" etc.
  try {
    const doc = new DOMParser().parseFromString(text, "text/html");
    return doc.documentElement.textContent ?? text;
  } catch {
    return text
      .replace(/&amp;/g, "&")
      .replace(/&lt;/g, "<")
      .replace(/&gt;/g, ">")
      .replace(/&quot;/g, '"')
      .replace(/&#39;/g, "'")
      .replace(/&apos;/g, "'")
      .replace(/&nbsp;/g, " ");
  }
}

export function useChatSocket() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [typing, setTyping] = useState(true);
  const [roomId, setRoomId] = useState<string | null>(null);
  const socketRef = useRef<WebSocket | null>(null);
  const typingStartRef = useRef<number | null>(null);
  const reconnectAttemptsRef = useRef(0);
  const maxReconnectAttempts = 3;

  const isMounted = useRef(false);
  const isConnecting = useRef(false);
  const connectGenRef = useRef(0);

  // Registered by ChatbotWindow when voice mode is active.
  // Called with plain text for bot responses.
  const onVoiceOutputRef = useRef<((text: string) => void) | null>(null);
  // Greeting audio (single chunk, plays then starts listening).
  const onVoiceGreetingRef = useRef<((audioB64: string, format: string) => void) | null>(null);
  // Response audio chunks (sentence-level, queued by useVoiceRecording).
  const onVoiceChunkRef = useRef<((audioB64: string, format: string) => void) | null>(null);
  // Called when backend signals no more response chunks.
  const onVoiceDoneRef = useRef<(() => void) | null>(null);
  // Race-condition fix: greeting may arrive before TTS config is committed to React state.
  const pendingGreetingRef   = useRef<{ audio: string; format: string } | null>(null);
  const voiceTtsConfigRefInt = useRef<{
    rate: number; pitch: number; volume: number; voiceURI: string; voiceName: string;
    ignoreBackgroundNoise?: boolean; allowInterrupt?: boolean;
  } | null>(null);

  const registerVoiceOutput = useCallback(
    (cb: ((text: string) => void) | null) => {
      onVoiceOutputRef.current = cb;
      if (voiceTtsConfigRefInt.current && pendingGreetingRef.current && onVoiceGreetingRef.current) {
        const queued = pendingGreetingRef.current;
        pendingGreetingRef.current = null;
        setTimeout(() => onVoiceGreetingRef.current?.(queued.audio, queued.format), 200);
      }
    },
    []
  );

  const registerVoiceGreeting = useCallback(
    (cb: ((audioB64: string, format: string) => void) | null) => {
      onVoiceGreetingRef.current = cb;
    },
    []
  );

  const registerVoiceResponseHandlers = useCallback(
    (
      onChunk: ((audioB64: string, format: string) => void) | null,
      onDone: (() => void) | null,
    ) => {
      onVoiceChunkRef.current = onChunk;
      onVoiceDoneRef.current  = onDone;
    },
    []
  );

  // TTS config pushed by the backend on voice_start
  const [voiceTtsConfig, setVoiceTtsConfig] = useState<{
    rate: number; pitch: number; volume: number; voiceURI: string; voiceName: string;
    ignoreBackgroundNoise?: boolean; allowInterrupt?: boolean;
  } | null>(null);

  // Recording constants config — all values controlled from admin console
  const [voiceRecordingConfig, setVoiceRecordingConfig] = useState<{
    minRecordMs?: number; silenceDuration?: number; maxRecordMs?: number;
    calibrationMs?: number; noiseMarginDefault?: number; noiseMarginHigh?: number;
    bargeInFrames?: number; levelFps?: number;
    bargeInNoiseMargin?: number;
  } | null>(null);

  // If typing stays true for 20s with no message, clear it.
  useEffect(() => {
    if (!typing) return;
    const t = setTimeout(() => {
      if (isMounted.current) setTyping(false);
    }, 20000);
    return () => clearTimeout(t);
  }, [typing]);

  // Track mount state and cancel any in-flight connect() on unmount.
  useEffect(() => {
    isMounted.current = true;
    return () => {
      isMounted.current = false;
      connectGenRef.current++;
      isConnecting.current = false;
      if (socketRef.current) {
        const ws = socketRef.current;
        socketRef.current = null;
        if (ws.readyState === WebSocket.CONNECTING) {
          ws.addEventListener("open", () => ws.close());
        } else {
          ws.close();
        }
      }
    };
  }, []);

  const addMessage = (sender: "user" | "bot", html: string, links: any[] = []) => {
    setMessages((prev) => {
      const lastMsg = prev[prev.length - 1];
      if (lastMsg && lastMsg.sender === sender && lastMsg.html === html) {
        return prev;
      }
      return [...prev, { sender, html, links }];
    });
  };

  const refreshTokens = async () => {
    const refresh = Cookies.get("refreshToken");
    if (!refresh) return null;
    try {
      const baseUrl = API_URL.endsWith("/api/v1") ? API_URL : `${API_URL}/api/v1`;
      const res = await fetch(`${baseUrl}/auth/refresh`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refresh_token: refresh }),
      });
      if (res.ok) {
        const data = await res.json();
        Cookies.set("accessToken", data.access_token, { expires: 30 / 1440 });
        Cookies.set("refreshToken", data.refresh_token, { expires: 120 / 1440 });
        return data.access_token;
      }
    } catch (e) {
      console.error("Refresh failed", e);
    }
    Cookies.remove("accessToken");
    Cookies.remove("refreshToken");
    return null;
  };

  const connect = useCallback(async () => {
    if (!isMounted.current) return;
    if (socketRef.current?.readyState === WebSocket.OPEN) return;
    if (isConnecting.current) return;

    isConnecting.current = true;
    const gen = connectGenRef.current;

    // Resolve the best available token.
    let token = Cookies.get("accessToken");

    if (token) {
      try {
        const decoded: any = jwtDecode(token);
        const expired = decoded.exp * 1000 < Date.now();
        if (expired) token = await refreshTokens();
      } catch {
        token = await refreshTokens();
      }
    } else {
      token = await refreshTokens();
    }

    if (connectGenRef.current !== gen || !isMounted.current) {
      isConnecting.current = false;
      return;
    }

    if (process.env.NODE_ENV === "development") {
      await new Promise((resolve) => setTimeout(resolve, 50));
      if (connectGenRef.current !== gen || !isMounted.current) {
        isConnecting.current = false;
        return;
      }
    }

    try {
      const ws = createChatSocket(`${SOCKET_URL}/socket/chat`, {
        onOpen: () => {
          if (!isMounted.current) {
            ws.close();
            return;
          }

          reconnectAttemptsRef.current = 0;

          if (ws.readyState !== WebSocket.OPEN) return;

          typingStartRef.current = Date.now();
          setTyping(true);

          try {
            const kSessionId = localStorage.getItem("k_session_id");
            let chatUserData = null;
            try {
              const rawUser = localStorage.getItem("chat_user");
              if (rawUser) chatUserData = JSON.parse(rawUser);
            } catch {}

            const payload = {
              event: "socket_connect",
              token: token || null,
              k_session_id: kSessionId || null,
              user_info: chatUserData || null,
            };
            ws.send(JSON.stringify(payload));
          } catch {}
        },

        onTextMessage: async (text: string) => {
          let data: any;
          try {
            data = JSON.parse(text);
          } catch {
            return;
          }

          const stopTyping = () => {
            setTyping(false);
            typingStartRef.current = null;
          };

          if (data.type === "room") {
            setRoomId(data.room_id);
            return;
          }

          if (data.type === "auth_update") {
            Cookies.set("accessToken", data.access_token, { expires: 30 / 1440 });
            Cookies.set("refreshToken", data.refresh_token, { expires: 120 / 1440 });
            return;
          }

          // ── Voice TTS config — store for use by the voice hook ───────────
          if (data.type === "voice_tts_config") {
            if (data.config) {
              setVoiceTtsConfig(data.config);
              voiceTtsConfigRefInt.current = data.config;
              // Flush queued greeting only when both TTS config AND greeting handler are ready
              if (pendingGreetingRef.current && onVoiceGreetingRef.current) {
                const queued = pendingGreetingRef.current;
                pendingGreetingRef.current = null;
                setTimeout(() => onVoiceGreetingRef.current?.(queued.audio, queued.format), 200);
              }
            }
            return;
          }

          // ── Voice recording config — recording constants from admin console ─
          if (data.type === "voice_recording_config") {
            if (data.config) setVoiceRecordingConfig(data.config);
            return;
          }

          // ── Voice greeting audio ─────────────────────────────────────────
          if (data.type === "voice_greeting_audio") {
            if (data.audio) {
              const fmt = (data.format as string) ?? "wav";
              onVoiceGreetingRef.current?.(data.audio as string, fmt);
            }
            return;
          }

          // ── Voice response audio chunk (sentence-level TTS) ───────────────
          if (data.type === "voice_response_audio") {
            if (data.audio) {
              const fmt = (data.format as string) ?? "wav";
              onVoiceChunkRef.current?.(data.audio as string, fmt);
            }
            return;
          }

          // ── Backend signals no more response chunks ───────────────────────
          if (data.type === "voice_response_done") {
            onVoiceDoneRef.current?.();
            return;
          }

          // ── Voice transcript — show as user bubble ───────────────────────
          if (data.type === "voice_transcript") {
            if (data.transcript) {
              setMessages(prev => {
                const last = prev[prev.length - 1];
                if (last?.sender === "user") {
                  return [...prev.slice(0, -1), { sender: "user", html: data.transcript as string, links: [] }];
                }
                return [...prev, { sender: "user", html: data.transcript as string, links: [] }];
              });
            }
            return;
          }

          // ── Regular chat message ──────────────────────────────────────────
          const now = Date.now();
          const startedAt = typingStartRef.current ?? now;
          const elapsed = now - startedAt;
          if (elapsed < MIN_TYPING_TIME) {
            setTimeout(stopTyping, MIN_TYPING_TIME - elapsed);
          } else {
            stopTyping();
          }

          if (!data.html) return;

          const rawSender = data.sender?.toLowerCase().trim();
          const sender = rawSender === "user" || rawSender === "bot" ? rawSender : "bot";
          addMessage(sender, data.html, data.links || []);

        },

        onJsonMessage: (data: ChatServerEvent) => {
          if (data.type === "room") setRoomId(data.room_id);
        },

        onError: () => {
          setTyping(false);
        },

        onClose: () => {
          isConnecting.current = false;
          socketRef.current = null;

          if (isMounted.current && reconnectAttemptsRef.current < maxReconnectAttempts) {
            const timeout = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 5000);
            reconnectAttemptsRef.current += 1;
            setTimeout(() => {
              if (isMounted.current) connect();
            }, timeout);
          }
        },
      });

      if (!isMounted.current) {
        ws.close();
        isConnecting.current = false;
        return;
      }

      socketRef.current = ws;
      isConnecting.current = false;
    } catch {
      isConnecting.current = false;
    }
  }, []);

  const sendMessage = (text: string) => {
    const trimmed = text.trim();
    if (!trimmed) return;

    addMessage("user", trimmed);
    typingStartRef.current = Date.now();
    setTyping(true);

    if (socketRef.current?.readyState === WebSocket.OPEN) {
      socketRef.current.send(trimmed);
    } else {
      if (reconnectAttemptsRef.current < maxReconnectAttempts) {
        if (!isConnecting.current) connect();
      } else {
        addMessage("bot", "Connection lost. Please refresh.");
        setTyping(false);
      }
    }
  };

  const sendVoiceStart = () => {
    if (socketRef.current?.readyState === WebSocket.OPEN) {
      socketRef.current.send(JSON.stringify({ event: "voice_start" }));
    }
  };

  const saveVoiceMessages = useCallback(async (msgs: Array<{ sender: string; text: string }>) => {
    if (!msgs.length) return;
    let token = Cookies.get("accessToken");
    if (!token) return;
    try {
      const baseUrl = API_URL.endsWith("/api/v1") ? API_URL : `${API_URL}/api/v1`;
      await fetch(`${baseUrl}/voice/messages`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify(msgs),
      });
    } catch (e) {
      console.error("[voice] Failed to save messages to DB:", e);
    }
  }, []);

  const sendVoiceMessage = useCallback((audioB64: string) => {
    typingStartRef.current = Date.now();
    setTyping(true);
    if (socketRef.current?.readyState === WebSocket.OPEN) {
      socketRef.current.send(JSON.stringify({ event: "voice_message", audio: audioB64 }));
    } else {
      // Socket not ready — reconnect then send
      if (!isConnecting.current) connect();
      const waited = { attempts: 0 };
      const retry = setInterval(() => {
        waited.attempts++;
        if (socketRef.current?.readyState === WebSocket.OPEN) {
          clearInterval(retry);
          socketRef.current.send(JSON.stringify({ event: "voice_message", audio: audioB64 }));
        } else if (waited.attempts >= 10) {
          clearInterval(retry);
          setTyping(false);
        }
      }, 500);
    }
  }, [connect]);

  return { messages, typing, roomId, connect, sendMessage, sendVoiceMessage, sendVoiceStart, saveVoiceMessages, registerVoiceOutput, registerVoiceGreeting, registerVoiceResponseHandlers, voiceTtsConfig, voiceRecordingConfig, addMessage, setTyping };
}