import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';
import { api } from '@/api/client';
import type { Chat } from '@/api/types';
import { sessionEvents } from '@/hooks/sessionEvents';

export interface UseSessionChatsOpts {
  removeChatFromPanes: (chatId: string) => void;
  openChatInPane: (paneIdx: number, chatId: string) => void;
  // Thin wrapper around openChatInPane(activePaneIdxRef.current, chatId);
  // built by Workspace and passed in so this hook doesn't need to know
  // about pane indexing.
  openChatInActivePane: (chatId: string) => void;
  initializeFirstChatIfEmpty: (chatId: string) => void;
  validatePanes: (validChatIds: Set<string>) => void;
}

export interface UseSessionChatsResult {
  chats: Chat[];
  setChats: React.Dispatch<React.SetStateAction<Chat[]>>;
  createChat: (paneIdx: number) => Promise<void>;
  archiveChat: (chatId: string) => Promise<void>;
  unarchiveChat: (chatId: string) => Promise<void>;
  deleteChat: (chatId: string) => Promise<void>;
  renameChat: (chatId: string, name: string) => Promise<void>;
  handleLandingSend: (paneIdx: number, content: string) => Promise<void>;
  handleLandingSkill: (paneIdx: number, skillName: string, userMessage: string | null) => Promise<void>;
}

export function useSessionChats(
  sessionId: string,
  opts: UseSessionChatsOpts,
): UseSessionChatsResult {
  const [chats, setChats] = useState<Chat[]>([]);
  const chatsRef = useRef<Chat[]>([]);
  chatsRef.current = chats;

  // Stable refs to opts callbacks so the subscription effect — which only
  // re-runs on sessionId change — always sees the latest closures without
  // unsubscribe/resubscribe churn.
  const removeChatFromPanesRef = useRef(opts.removeChatFromPanes);
  removeChatFromPanesRef.current = opts.removeChatFromPanes;
  const openChatInPaneRef = useRef(opts.openChatInPane);
  openChatInPaneRef.current = opts.openChatInPane;
  const openChatInActivePaneRef = useRef(opts.openChatInActivePane);
  openChatInActivePaneRef.current = opts.openChatInActivePane;
  const initializeFirstChatIfEmptyRef = useRef(opts.initializeFirstChatIfEmpty);
  initializeFirstChatIfEmptyRef.current = opts.initializeFirstChatIfEmpty;
  const validatePanesRef = useRef(opts.validatePanes);
  validatePanesRef.current = opts.validatePanes;

  useEffect(() => {
    let cancelled = false;
    api.chats.listForSession(sessionId).then((list) => {
      if (cancelled) return;
      setChats(list);
      validatePanesRef.current(new Set(list.map((c) => c.id)));
      const openChat = list.find((c) => c.status === 'open');
      if (openChat) {
        initializeFirstChatIfEmptyRef.current(openChat.id);
      }
    }).catch(() => {});
    return () => { cancelled = true; };
  }, [sessionId]);

  useEffect(() => {
    return sessionEvents.subscribe((event) => {
      if (event.type === 'chat_created' && event.session_id === sessionId) {
        setChats((prev) => {
          if (prev.some((c) => c.id === event.chat.id)) return prev;
          return [event.chat, ...prev];
        });
      }
      if (event.type === 'chat_updated') {
        setChats((prev) => prev.map((c) =>
          c.id === event.chat_id ? { ...c, name: event.name, updated_at: event.updated_at } : c
        ));
      }
      if (event.type === 'chat_archived') {
        setChats((prev) => prev.map((c) =>
          c.id === event.chat_id ? { ...c, status: 'archived' as const } : c
        ));
        removeChatFromPanesRef.current(event.chat_id);
      }
      if (event.type === 'chat_unarchived') {
        setChats((prev) => {
          if (prev.some((c) => c.id === event.chat.id)) {
            return prev.map((c) => c.id === event.chat.id ? { ...c, status: 'open' as const } : c);
          }
          return [event.chat, ...prev];
        });
      }
      if (event.type === 'chat_deleted') {
        setChats((prev) => prev.filter((c) => c.id !== event.chat_id));
        removeChatFromPanesRef.current(event.chat_id);
      }
      if (event.type === 'open_chat_in_active_pane') {
        openChatInActivePaneRef.current(event.chat_id);
      }
    });
  }, [sessionId]);

  const createChat = useCallback(async (paneIdx: number) => {
    try {
      const chat = await api.chats.create(sessionId);
      // Optimistic local insert; the WS chat_created echo will be deduped by id.
      setChats((prev) => {
        if (prev.some((c) => c.id === chat.id)) return prev;
        return [chat, ...prev];
      });
      openChatInPaneRef.current(paneIdx, chat.id);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to create chat');
    }
  }, [sessionId]);

  const archiveChat = useCallback(async (chatId: string) => {
    try {
      await api.chats.archive(chatId);
      // Server publishes chat_archived; bus forwarder updates state.
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to archive chat');
    }
  }, []);

  const unarchiveChat = useCallback(async (chatId: string) => {
    try {
      await api.chats.unarchive(chatId);
      // Server publishes chat_unarchived.
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to restore chat');
    }
  }, []);

  const deleteChat = useCallback(async (chatId: string) => {
    try {
      await api.chats.remove(chatId);
      setChats((prev) => prev.filter((c) => c.id !== chatId));
      removeChatFromPanesRef.current(chatId);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to delete chat');
    }
  }, []);

  const renameChat = useCallback(async (chatId: string, name: string) => {
    try {
      await api.chats.update(chatId, { name });
      setChats((prev) => prev.map((c) =>
        c.id === chatId ? { ...c, name } : c
      ));
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to rename chat');
    }
  }, []);

  const handleLandingSend = useCallback(async (paneIdx: number, content: string) => {
    try {
      const chat = await api.chats.create(sessionId);
      setChats((prev) => {
        if (prev.some((c) => c.id === chat.id)) return prev;
        return [chat, ...prev];
      });
      openChatInPaneRef.current(paneIdx, chat.id);
      await api.messages.send(chat.id, content);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to send');
    }
  }, [sessionId]);

  // Slash-command equivalent of handleLandingSend: the initial (landing) chat
  // must create the chat AND assign it to the pane (openChatInPane) before
  // invoking the skill, so the pane transitions to ChatPane and subscribes to
  // the chat's stream. Skipping the assignment left the pane stuck on the
  // landing page while the skill ran invisibly (and could blank the pane).
  const handleLandingSkill = useCallback(
    async (paneIdx: number, skillName: string, userMessage: string | null) => {
      try {
        const chat = await api.chats.create(sessionId);
        setChats((prev) => (prev.some((c) => c.id === chat.id) ? prev : [chat, ...prev]));
        openChatInPaneRef.current(paneIdx, chat.id);
        await api.chats.skillInvoke(chat.id, skillName, userMessage);
      } catch (err) {
        toast.error(err instanceof Error ? err.message : `/${skillName} failed`);
      }
    },
    [sessionId],
  );

  return {
    chats,
    setChats,
    createChat,
    archiveChat,
    unarchiveChat,
    deleteChat,
    renameChat,
    handleLandingSend,
    handleLandingSkill,
  };
}
