import {
  createContext,
  useContext,
  type ReactNode,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { useNavigate } from 'react-router-dom';
import { useWsEvent, useWsStatus, getWsClient, useRoomSubscription } from '@/ws';
import { roomGet, participantList, pinAdd, pinRemove, pinList, threadList } from '@/client/api';
import type { AxiosResponse } from 'axios';
import type {
  ApiResponseRoomResponse,
  ApiResponseRoomParticipantListResponse,
  ApiResponseVecRoomPinResponse,
  ApiResponseVecRoomThreadResponse,
} from '@/client/generated';
import { useCurrentUserQuery } from '@/hooks/useAuth';
import { db } from '@/lib/db';
import { MessageRepository } from '@/lib/db/repository';
import type {
  Message,
  Member,
  ThreadState,
  PinnedMessage,
  ActiveAiStream,
  ReactionGroup,
} from './types';
import { useRoomMessages } from './use-room-messages';
import { useAiStreaming } from './use-ai-streaming';
import { mapParticipantToMember } from './utils';
import type { RoomId } from '@/ws/types/core';

function safeGetClient() {
  try { return getWsClient(); } catch { return null; }
}

export interface RoomContextValue {
  wsStatus: ReturnType<typeof useWsStatus>;

  /** Room metadata */
  currentRoom: { id: string; room_name: string; topic?: string; public: boolean } | null;
  members: Member[];
  pinnedMessages: PinnedMessage[];
  threads: ThreadState[];

  /** Messages */
  messages: Message[];
  isHistoryLoaded: boolean;
  isLoadingMore: boolean;
  isTransitioningRoom: boolean;
  nextCursor: number | null;
  loadHistory: (cursor?: number | null) => Promise<void>;
  clearMessages: () => void;
  mergePendingMessages: () => void;

  /** Optimistic operations */
  sendMessage: (content: string, opts?: { contentType?: string; thread?: string; inReplyTo?: string; attachmentIds?: string[] }) => Promise<void>;
  editMessage: (messageId: string, content: string) => Promise<void>;
  revokeMessage: (messageId: string) => Promise<void>;

  /** Typing users: userId -> { username, avatar_url } */
  typingUsers: Map<string, { user_id: string; username: string; avatar_url: string | null }>;

  /** AI streaming */
  streamingChunks: Map<RoomId, Array<{ type: string; content: string; seq?: number }>>;
  activeAiStream: ActiveAiStream | null;
  cancelAiStream: () => void;
  getAiList: () => Promise<unknown>;
  upsertAi: (config: Record<string, unknown>) => Promise<unknown>;
  deleteAi: (agentId: string) => void;

  /** Pin management */
  addPin: (messageId: string) => Promise<void>;
  removePin: (messageId: string) => Promise<void>;

  /** Member state */
  updateDoNotDisturb: (dnd: boolean) => Promise<void>;
  updateCustomStatus: (emoji: string, text: string, expires_at?: string) => Promise<void>;

  /** Voice/Video */
  voiceJoin: () => void;
  voiceLeave: () => void;

  /** General */
  searchMessages: (query: string, opts?: Record<string, unknown>) => Promise<unknown>;
  createInvite: (opts?: Record<string, unknown>) => Promise<void>;
  updatePresence: (status: string) => void;
  grantAccess: (targetUserId: string, role: string) => void;
  banUser: (userId: string, reason?: string) => void;
  createThread: (parentSeq: number) => Promise<unknown> | undefined;

  /** UI state */
  setCurrentRoom: (room: { id: string; room_name: string; topic?: string; public: boolean } | null) => void;
  setMembers: React.Dispatch<React.SetStateAction<Member[]>>;
  setPinnedMessages: React.Dispatch<React.SetStateAction<PinnedMessage[]>>;
  setThreads: React.Dispatch<React.SetStateAction<ThreadState[]>>;
}

const RoomContext = createContext<RoomContextValue | null>(null);

interface RoomProviderProps {
  roomId: RoomId | null;
  projectName?: string;
  children: ReactNode;
}

export function RoomProvider({ roomId, projectName, children }: RoomProviderProps) {
  const navigate = useNavigate();
  const wsStatus = useWsStatus();
  useRoomSubscription(roomId);
  const { data: user } = useCurrentUserQuery();
  const currentUserId = user?.uid ?? null;

  // ── Messages ──

  const {
    messages,
    isHistoryLoaded,
    isLoadingMore,
    isTransitioningRoom,
    nextCursor,
    clearMessages,
    loadHistory,
    triggerCatchupSync,
    sendMessage: optimisticSend,
    editMessage: optimisticEdit,
    revokeMessage: optimisticRevoke,
    handleNewMessage,
    handleEditedMessage,
    handleRevokedMessage,
    handleReactionUpdate,
    mergePendingMessages,
  } = useRoomMessages(roomId);

  // ── AI streaming ──

  const {
    streamingChunks,
    activeAiStream,
    setActiveAiStream,
    insertChunk,
    finalizeStream,
    cancelStream,
    cleanup: cleanupStream,
    startTimer,
    clearTimer,
  } = useAiStreaming();

  // ── Sync Read Receipts ──

  const lastReadSeqRef = useRef<number>(0);
  const syncReadReceipt = useCallback(() => {
    if (!roomId || messages.length === 0) return;
    const latestSeq = Math.max(...messages.map(m => m.seq));
    if (latestSeq > lastReadSeqRef.current && document.visibilityState === 'visible') {
      const client = safeGetClient();
      if (client) {
        client.sendReadReceipt(roomId, latestSeq);
        lastReadSeqRef.current = latestSeq;
      }
    }
  }, [roomId, messages]);

  useEffect(() => {
    syncReadReceipt();
    const onVisibilityChange = () => syncReadReceipt();
    document.addEventListener('visibilitychange', onVisibilityChange);
    return () => document.removeEventListener('visibilitychange', onVisibilityChange);
  }, [syncReadReceipt]);

  // ── Room metadata ──

  const [currentRoom, setCurrentRoom] = useState<{ id: string; room_name: string; topic?: string; public: boolean } | null>(null);
  const [members, setMembers] = useState<Member[]>([]);
  const [pinnedMessages, setPinnedMessages] = useState<PinnedMessage[]>([]);
  const [threads, setThreads] = useState<ThreadState[]>([]);
  const [typingUsers, setTypingUsers] = useState<Map<string, { user_id: string; username: string; avatar_url: string | null }>>(new Map());
  const typingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());

  // ── Exposed actions ──

   
  const getAiList = useCallback(async () => {
    const client = safeGetClient();
    if (client && roomId) return client.getAiList(roomId);
    return null;
  }, [roomId]);
  const upsertAi = useCallback(async (config: Record<string, unknown>) => {
    const client = safeGetClient();
    if (client && roomId) return client.upsertAi(roomId, config);
    return null;
  }, [roomId]);
  const deleteAi = useCallback((agentId: string) => {
    const client = safeGetClient();
    if (client && roomId) client.deleteAi(roomId, agentId);
  }, [roomId]);

  const addPin = useCallback(async (messageId: string) => {
    if (!roomId) return;
    const res = await pinAdd(roomId, messageId);
    const pin = res.data?.data;
    if (!pin) return;
    setPinnedMessages((prev) => (prev.some((p) => p.message === pin.message) ? prev : [...prev, pin]));
  }, [roomId]);

  const removePin = useCallback(async (messageId: string) => {
    if (!roomId) return;
    await pinRemove(roomId, messageId);
    setPinnedMessages((prev) => prev.filter((p) => p.message !== messageId));
  }, [roomId]);

  const updateDoNotDisturb = useCallback(async (dnd: boolean) => {
    const client = safeGetClient();
    if (client && roomId) {
      client.emitRaw('state_update_dnd', { room: roomId, do_not_disturb: dnd });
      setMembers(prev => prev.map(m => m.uid === currentUserId ? { ...m, do_not_disturb: dnd } : m));
    }
  }, [roomId, currentUserId]);

  const updateCustomStatus = useCallback(async (emoji: string, text: string, expires_at?: string) => {
    const client = safeGetClient();
    if (client) client.emitRaw('custom_status_update', { emoji, text, expires_at: expires_at ?? null });
  }, []);

  const searchMessages = useCallback(async (query: string, opts?: Record<string, unknown>) => {
    const client = safeGetClient();
    if (client) return client.search(query, { room: roomId, ...opts });
    return null;
  }, [roomId]);

  const voiceJoin = useCallback(() => {
    const client = safeGetClient();
    if (client && roomId) client.emitRaw('voice_join', { room: roomId });
  }, [roomId]);

  const voiceLeave = useCallback(() => {
    const client = safeGetClient();
    if (client && roomId) client.emitRaw('voice_leave', { room: roomId });
  }, [roomId]);

  const createInvite = useCallback(async (options: Record<string, unknown>) => {
    const client = safeGetClient();
    if (client && roomId) client.emitRaw('invite_create', { room: roomId, ...options });
  }, [roomId]);


  const updatePresence = useCallback((status: string) => {
    const client = safeGetClient();
    if (client) client.updatePresence(status);
  }, []);
  const grantAccess = useCallback((targetUserId: string, role: string) => {
    const client = safeGetClient();
    if (client && roomId) client.grantAccess(roomId, targetUserId, role);
  }, [roomId]);
  const banUser = useCallback((userId: string, reason?: string) => {
    const client = safeGetClient();
    if (client && roomId) client.banUser(roomId, userId, reason);
  }, [roomId]);
  const createThread = useCallback((parentSeq: number) => {
    const client = safeGetClient();
    if (client && roomId) return client.createThread(roomId, parentSeq);
  }, [roomId]);

  // ── Room switch cleanup ──

  // Reset state during render when roomId changes
  const [prevRoomId, setPrevRoomId] = useState(roomId);
  if (roomId !== prevRoomId) {
    setPrevRoomId(roomId);
    setCurrentRoom(null);
    setMembers([]);
    setPinnedMessages([]);
    setThreads([]);
    setTypingUsers(new Map());
  }

  // Imperative cleanup when roomId changes
  useEffect(() => {
    if (!roomId) return;
    clearMessages();
    cleanupStream();
    mergePendingMessages();
  }, [roomId, clearMessages, cleanupStream, mergePendingMessages]);

  // ── Load room info ──

  useEffect(() => {
    if (!roomId) return;
    const load = async () => {
      try {
        const [roomRes, membersRes, pinsRes, threadsRes] = await Promise.allSettled([
          roomGet(roomId),
          participantList(roomId),
          pinList(roomId),
          threadList(roomId),
        ]);

        if (roomRes.status === 'fulfilled') {
          const data = (roomRes.value as AxiosResponse<ApiResponseRoomResponse>).data?.data;
          if (data) {
            setCurrentRoom({ id: data.id, room_name: data.room_name, public: data.public });
            // Update presence to online when joining a room
            updatePresence('online');
          }
        }
        if (membersRes.status === 'fulfilled') {
          const membersData = (membersRes.value as AxiosResponse<ApiResponseRoomParticipantListResponse>).data?.data;
          const participants = membersData?.participants ?? [];
          setMembers(participants.map((p) => mapParticipantToMember(p, 'online')));
        }
        if (pinsRes.status === 'fulfilled') {
          setPinnedMessages((pinsRes.value as AxiosResponse<ApiResponseVecRoomPinResponse>).data?.data ?? []);
        }
        if (threadsRes.status === 'fulfilled') {
          const threadData = (threadsRes.value as AxiosResponse<ApiResponseVecRoomThreadResponse>).data?.data ?? [];
          const mapped: ThreadState[] = threadData.map((t) => ({
            ...t,
            messages: [],
            isOpen: false,
          }));
          setThreads(mapped);
        }
      } catch (err) {
        console.error('[RoomProvider] failed to load room info:', err);
      }
    };
    load();
    loadHistory().then(() => triggerCatchupSync());
  }, [roomId, loadHistory, triggerCatchupSync, updatePresence]);

  // ── WS: message events ──

  useWsEvent('message_new', (event) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    handleNewMessage(event as any);
  });

  useWsEvent('message_edited', (event) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    handleEditedMessage(event as any);
  });

  useWsEvent('message_revoked', (event) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    handleRevokedMessage(event as any);
  });

  // ── WS: AI streaming ──

  useWsEvent('message_stream_start', async (event) => {
    if (event.room_id !== roomId) return;
    const { message_id, display_name, sse_url } = event.data;

    const existing = await db.messages.get(message_id);
    if (existing) return;

    const streamMsg: Message = {
      id: message_id, seq: Date.now(), room: roomId, sender_type: 'ai', sender_id: null,
      display_name, content: '', content_type: 'text/markdown', thinking_content: null,
      send_at: new Date().toISOString(), thread: null, in_reply_to: null,
      is_streaming: true, _localReactions: [],
    };
    await MessageRepository.saveMessages(roomId, [streamMsg]);

    // Open EventSource for SSE chunk delivery.
    const client = getWsClient();
    if (client && sse_url) {
      client.connectEventSource(message_id, sse_url);
    }
  });

  useWsEvent('message_stream_chunk', async (event) => {
    if (event.room_id !== roomId) return;
    const { message_id, content, seq, chunk_type } = event.data;
    const isToolCall = chunk_type === 'tool_call' || chunk_type === 'tool_result';
    insertChunk(message_id, chunk_type ?? undefined, content, seq);
    clearTimer(message_id);
    startTimer(message_id, async () => {
      finalizeStream(message_id, content || '[Stream timed out]', null);
      await db.messages.update(message_id, { is_streaming: false, content: content || '[Stream timed out]' });
    });
    if (!isToolCall && event.data.display_name) {
      setActiveAiStream({ message_id, display_name: event.data.display_name });
    }
    
    const msg = await db.messages.get(message_id);
    if (msg) {
        await db.messages.update(message_id, { content: (msg.content || '') + content });
    }
  });

  useWsEvent('message_stream_done', async (event) => {
    if (event.room_id !== roomId) return;
    const { message_id, content, thinking_content } = event.data;
    const finalized = finalizeStream(message_id, content, thinking_content);
    await db.messages.update(message_id, {
        content: finalized.content,
        thinking_content: finalized.thinking_content,
        is_streaming: false
    });
    const client = getWsClient?.();
    if (client) client.closeEventSource(message_id);
  });

  // ── WS: Reaction ──

  useWsEvent('reaction_batch_updated', (event) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    handleReactionUpdate(event as any);
  });

  useWsEvent('reaction_added', async (event) => {
    if (event.room_id !== roomId) return;
    const { message: messageId, emoji, user: userId } = event.data;
    
    const m = await db.messages.get(messageId);
    if (!m) return;

    const reactions = [...(m._localReactions || [])];
    const existing = reactions.find((r) => r.emoji === emoji);
    if (existing && !existing.users.includes(userId)) {
      existing.count++;
      existing.users.push(userId);
    } else if (!existing) {
      reactions.push({ emoji, count: 1, reacted_by_me: currentUserId === userId, users: [userId] });
    }
    await db.messages.update(messageId, { _localReactions: reactions });
  });

  useWsEvent('reaction_removed', async (event) => {
    if (event.room_id !== roomId) return;
    const { message: messageId, emoji, user: userId } = event.data;
    
    const m = await db.messages.get(messageId);
    if (!m) return;

    const reactions = (m._localReactions || [])
      .map((r: ReactionGroup) => r.emoji !== emoji ? r : { ...r, count: r.count - 1, users: r.users.filter((u: string) => u !== userId) })
      .filter((r: ReactionGroup) => r.count > 0);
    
    await db.messages.update(messageId, { _localReactions: reactions });
  });

  // ── WS: Member events ──

  useWsEvent('member_joined', (event) => {
    if (event.room_id !== roomId) return;
    const { user, username } = event.data;
    setMembers((prev: Member[]) => {
      if (prev.some((m) => m.uid === user)) return prev;
      return [...prev, { uid: user, username, avatar_url: null, project_role: 'member', is_room_owner: false, last_read_seq: null, do_not_disturb: false, presence: 'online' }];
    });
  });

  useWsEvent('member_removed', (event) => {
    if (event.room_id !== roomId) return;
    setMembers((prev: Member[]) => prev.filter((m) => m.uid !== event.data.user));
  });

  useWsEvent('presence_changed', (event) => {
    const { user, status } = event.data;
    setMembers((prev: Member[]) => prev.map((m) => (m.uid === user ? { ...m, presence: status } : m)));
  });

  // ── WS: Pin events ──

  useWsEvent('pin_added', (event) => {
    if (event.room_id !== roomId) return;
    const { room, message, pinned_by, pinned_at } = event.data;
    setPinnedMessages((prev: PinnedMessage[]) => (
      prev.some((p) => p.message === message)
        ? prev
        : [...prev, { room, message, pinned_by, pinned_at }]
    ));
  });

  useWsEvent('pin_removed', (event) => {
    if (event.room_id !== roomId) return;
    setPinnedMessages((prev: PinnedMessage[]) => prev.filter((p) => p.message !== event.data.message));
  });

  // ── WS: Room events ──

  useWsEvent('room_topic_updated', (event) => {
    if (event.room_id !== roomId) return;
    setCurrentRoom((prev) => prev ? { ...prev, topic: event.data.new_topic ?? prev.topic } : prev);
  });

  useWsEvent('room_renamed', (event) => {
    if (event.room_id !== roomId) return;
    setCurrentRoom((prev) => prev ? { ...prev, room_name: event.data.new_name } : prev);
  });

  useWsEvent('room_deleted', (event) => {
    if (event.room_id !== roomId) return;
    navigate(`/${projectName}/channel`, { replace: true });
  });

  useWsEvent('room_settings_updated', (event) => {
    if (event.room_id !== roomId) return;
    setCurrentRoom((prev) => prev ? { ...prev, public: (event.data as { public_status?: boolean }).public_status ?? prev.public } : prev);
  });

  // ── WS: Thread events ──

  useWsEvent('thread_created', (event) => {
    if (event.room_id !== roomId) return;
    setThreads((prev) => {
      if (prev.some((t) => t.id === event.data.id)) return prev;
      return [...prev, { ...event.data, messages: [], isOpen: false, last_message_at: event.data.created_at, last_message_preview: null } as ThreadState];
    });
  });

  useWsEvent('thread_updated', (event) => {
    if (event.room_id !== roomId) return;
    setThreads((prev) => prev.map((t) => (t.id === event.data.id ? { ...t, ...event.data } : t)));
  });

  useWsEvent('thread_resolved', (event) => {
    if (event.room_id !== roomId) return;
    setThreads((prev) => prev.filter((t) => t.id !== event.data.id));
  });

  useWsEvent('thread_archived', (event) => {
    if (event.room_id !== roomId) return;
    setThreads((prev) => prev.filter((t) => t.id !== event.data.id));
  });

  // ── WS: Draft events ──

  useWsEvent('draft_saved', (event) => {
    if (event.room_id !== roomId) return;
    // Local input state is handled by ChannelPage, but we could sync across tabs here if needed
  });

  useWsEvent('draft_cleared', (event) => {
    if (event.room_id !== roomId) return;
  });

  // ── WS: Other Full Coverage Listeners (No-op or Logging) ──
  useWsEvent('thread_participant_joined', (e) => console.debug('thread_participant_joined', e));
  useWsEvent('thread_participant_left', (e) => console.debug('thread_participant_left', e));
  
  useWsEvent('category_created', (e) => console.debug('category_created', e));
  useWsEvent('category_updated', (e) => console.debug('category_updated', e));
  useWsEvent('category_deleted', (e) => console.debug('category_deleted', e));
  
  useWsEvent('project_room_created', (e) => console.debug('project_room_created', e));
  useWsEvent('project_room_deleted', (e) => console.debug('project_room_deleted', e));
  useWsEvent('project_room_renamed', (e) => console.debug('project_room_renamed', e));
  useWsEvent('project_room_moved', (e) => console.debug('project_room_moved', e));
  useWsEvent('room_moved', (e) => console.debug('room_moved', e));

  useWsEvent('invite_created', (e) => console.debug('invite_created', e));
  useWsEvent('invite_accepted', (e) => console.debug('invite_accepted', e));
  useWsEvent('invite_rejected', (e) => console.debug('invite_rejected', e));
  useWsEvent('invite_revoked', (e) => console.debug('invite_revoked', e));

  useWsEvent('attachment_uploaded', (e) => console.debug('attachment_uploaded', e));
  useWsEvent('attachment_thumbnail_generated', (e) => console.debug('attachment_thumbnail_generated', e));
  useWsEvent('attachment_deleted', (e) => console.debug('attachment_deleted', e));

  useWsEvent('user_banned', (e) => console.debug('user_banned', e));
  useWsEvent('user_unbanned', (e) => console.debug('user_unbanned', e));

  useWsEvent('ai_agent_joined', (e) => console.debug('ai_agent_joined', e));
  useWsEvent('ai_agent_left', (e) => console.debug('ai_agent_left', e));
  useWsEvent('ai_agent_status_changed', (e) => console.debug('ai_agent_status_changed', e));

  useWsEvent('voice_channel_joined', (e) => console.debug('voice_channel_joined', e));
  useWsEvent('voice_channel_left', (e) => console.debug('voice_channel_left', e));
  useWsEvent('voice_mute_updated', (e) => console.debug('voice_mute_updated', e));
  useWsEvent('voice_deaf_updated', (e) => console.debug('voice_deaf_updated', e));
  useWsEvent('screen_share_started', (e) => console.debug('screen_share_started', e));
  useWsEvent('screen_share_stopped', (e) => console.debug('screen_share_stopped', e));
  useWsEvent('speaking_started', (e) => console.debug('speaking_started', e));
  useWsEvent('speaking_stopped', (e) => console.debug('speaking_stopped', e));
  useWsEvent('custom_status_updated', (e) => console.debug('custom_status_updated', e));
  useWsEvent('search_result', (e) => console.debug('search_result', e));

  // ── WS: Typing indicator ──

  useWsEvent('typing_start', (event) => {
    if (event.room_id !== roomId) return;
    const { user, username, avatar_url } = event.data;
    if (currentUserId && user === currentUserId) return;
    const existing = typingTimersRef.current.get(user);
    if (existing) clearTimeout(existing);
    setTypingUsers((prev) => {
      const next = new Map(prev);
      next.set(user, { user_id: user, username, avatar_url: avatar_url ?? null });
      return next;
    });
    typingTimersRef.current.set(user, setTimeout(() => {
      setTypingUsers((prev) => {
        const next = new Map(prev);
        next.delete(user);
        return next;
      });
      typingTimersRef.current.delete(user);
    }, 4000));
  });

  useWsEvent('typing_stop', (event) => {
    if (event.room_id !== roomId) return;
    const timer = typingTimersRef.current.get(event.data.user);
    if (timer) { clearTimeout(timer); typingTimersRef.current.delete(event.data.user); }
    setTypingUsers((prev) => {
      const next = new Map(prev);
      next.delete(event.data.user);
      return next;
    });
  });

  // ── Cancel AI stream ──

  const cancelAiStream = useCallback(() => {
    const client = safeGetClient();
    if (client && roomId) {
      client.emitRaw('ai_stop', { room: roomId });
    }
    cancelStream();
  }, [roomId, cancelStream]);

  // ── Context value ──

  const value = useMemo<RoomContextValue>(() => ({
    wsStatus,
    currentRoom,
    members,
    pinnedMessages,
    threads,
    messages,
    isHistoryLoaded,
    isLoadingMore,
    isTransitioningRoom,
    nextCursor,
    loadHistory,
    clearMessages,
    mergePendingMessages,
    sendMessage: optimisticSend,
    editMessage: optimisticEdit,
    revokeMessage: optimisticRevoke,
    typingUsers,
    streamingChunks,
    activeAiStream,
    cancelAiStream,
    getAiList,
    upsertAi,
    deleteAi,
    addPin,
    removePin,
    updateDoNotDisturb,
    updateCustomStatus,
    searchMessages,
    voiceJoin,
    voiceLeave,
    createInvite,
    updatePresence,
    grantAccess,
    banUser,
    createThread,
    setCurrentRoom,
    setMembers,
    setPinnedMessages,
    setThreads,
  }), [
    wsStatus,
    currentRoom,
    members,
    pinnedMessages,
    threads,
    messages,
    isHistoryLoaded,
    isLoadingMore,
    isTransitioningRoom,
    nextCursor,
    loadHistory,
    clearMessages,
    mergePendingMessages,
    optimisticSend,
    optimisticEdit,
    optimisticRevoke,
    typingUsers,
    streamingChunks,
    activeAiStream,
    cancelAiStream,
    getAiList,
    upsertAi,
    deleteAi,
    addPin,
    removePin,
    updateDoNotDisturb,
    updateCustomStatus,
    searchMessages,
    voiceJoin,
    voiceLeave,
    createInvite,
    updatePresence,
    grantAccess,
    banUser,
    createThread,
  ]);

  return <RoomContext.Provider value={value}>{children}</RoomContext.Provider>;
}

export function useRoom(): RoomContextValue {
  const ctx = useContext(RoomContext);
  if (!ctx) throw new Error('useRoom must be used within RoomProvider');
  return ctx;
}

export function useOptionalRoom(): RoomContextValue | null {
  return useContext(RoomContext);
}
