import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { MessageSocket, type Message } from '@insignia/iios-kernel-client';

interface MessageContextValue {
  socket: MessageSocket;
}

const MessageContext = createContext<MessageContextValue | null>(null);

/** Wraps a MessageSocket and provides it to the message hooks. */
export function MessageProvider({
  serviceUrl,
  token,
  children,
}: {
  serviceUrl: string;
  token: string;
  children: React.ReactNode;
}): React.ReactElement {
  const socket = useMemo(() => new MessageSocket({ serviceUrl, token }), [serviceUrl, token]);
  useEffect(() => () => socket.disconnect(), [socket]);
  return <MessageContext.Provider value={{ socket }}>{children}</MessageContext.Provider>;
}

function useSocket(): MessageSocket {
  const ctx = useContext(MessageContext);
  if (!ctx) throw new Error('useThread/useMessages must be used within <MessageProvider>');
  return ctx.socket;
}

/** Open or create a thread; returns the resolved thread id + status. */
export function useThread(): {
  threadId: string | null;
  status: string;
  open: (id?: string) => Promise<string>;
} {
  const socket = useSocket();
  const [threadId, setThreadId] = useState<string | null>(null);
  const [status, setStatus] = useState('OPEN');

  const open = async (id?: string): Promise<string> => {
    const r = await socket.openThread(id);
    setThreadId(r.threadId);
    setStatus(r.status);
    return r.threadId;
  };

  return { threadId, status, open };
}

/** Live messages for a thread + send/read/typing actions. */
export function useMessages(threadId: string | null): {
  messages: Message[];
  send: (content: string, opts?: { contentRef?: string }) => Promise<void>;
  markRead: (interactionId: string) => Promise<void>;
  typing: () => void;
  typingUsers: string[];
  reads: string[];
} {
  const socket = useSocket();
  const [messages, setMessages] = useState<Message[]>([]);
  const [typingUsers, setTypingUsers] = useState<string[]>([]);
  const [reads, setReads] = useState<string[]>([]);
  const seen = useRef<Set<string>>(new Set());

  useEffect(() => {
    if (!threadId) return;
    seen.current = new Set();

    void socket.openThread(threadId).then((r) => {
      seen.current = new Set(r.history.map((m) => m.id));
      setMessages(r.history);
    });

    const offMessage = socket.on('message', (m) => {
      if (m.threadId !== threadId || seen.current.has(m.id)) return;
      seen.current.add(m.id);
      setMessages((prev) => [...prev, m]);
    });
    const offTyping = socket.on('typing', (e) => {
      if (e.threadId !== threadId) return;
      setTypingUsers((u) => (u.includes(e.userId) ? u : [...u, e.userId]));
      setTimeout(() => setTypingUsers((u) => u.filter((x) => x !== e.userId)), 2500);
    });
    const offReceipt = socket.on('receipt', (e) => {
      if (e.kind === 'READ') setReads((r) => (r.includes(e.interactionId) ? r : [...r, e.interactionId]));
    });

    return () => {
      offMessage();
      offTyping();
      offReceipt();
    };
  }, [socket, threadId]);

  const send = async (content: string, opts?: { contentRef?: string }): Promise<void> => {
    if (threadId) await socket.sendMessage(threadId, content, opts);
  };
  const markRead = async (interactionId: string): Promise<void> => {
    if (threadId) await socket.markRead(threadId, interactionId);
  };
  const typing = (): void => {
    if (threadId) socket.typing(threadId);
  };

  return { messages, send, markRead, typing, typingUsers, reads };
}
