import { useCallback, useEffect, useRef, useState } from 'react';
import type { DragEvent } from 'react';
import { PanelRight, MessageSquare, Terminal, Bot } from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api/client';
import { sessionEvents } from '@/hooks/sessionEvents';
import type { Chat, WorkspacePane } from '@/api/types';
import { ChatPane } from '@/components/panes/ChatPane';
import { ChatTabBar } from '@/components/ChatTabBar';
import { SessionLandingPage } from '@/components/SessionLandingPage';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils';

interface Props {
  sessionId: string;
  projectId: string;
}

const MAX_PANES = 5;
const STORAGE_KEY = 'boocode.workspace.panes';

function generateId(): string {
  return crypto.randomUUID();
}

function emptyPane(): WorkspacePane {
  return { id: generateId(), kind: 'empty', chatIds: [], activeChatIdx: -1 };
}

function chatPane(chatId: string): WorkspacePane {
  return { id: generateId(), kind: 'chat', chatId, chatIds: [chatId], activeChatIdx: 0 };
}

function loadPanes(sessionId: string): WorkspacePane[] | null {
  try {
    const raw = localStorage.getItem(`${STORAGE_KEY}.${sessionId}`);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as WorkspacePane[];
    if (!Array.isArray(parsed) || parsed.length === 0) return null;
    return parsed;
  } catch {
    return null;
  }
}

function savePanes(sessionId: string, panes: WorkspacePane[]): void {
  try {
    localStorage.setItem(`${STORAGE_KEY}.${sessionId}`, JSON.stringify(panes));
  } catch { /* quota or disabled */ }
}

export function Workspace({ sessionId, projectId }: Props) {
  const [panes, setPanes] = useState<WorkspacePane[]>(() => {
    return loadPanes(sessionId) ?? [emptyPane()];
  });
  const [activePaneIdx, setActivePaneIdx] = useState(0);
  const [chats, setChats] = useState<Chat[]>([]);
  const chatsRef = useRef<Chat[]>([]);
  chatsRef.current = chats;
  const draggingIdxRef = useRef<number | null>(null);
  const [dragOverIdx, setDragOverIdx] = useState<number | null>(null);

  useEffect(() => {
    let cancelled = false;
    api.chats.listForSession(sessionId).then((list) => {
      if (cancelled) return;
      setChats(list);
      const openChat = list.find((c) => c.status === 'open');
      if (openChat) {
        setPanes((prev) => {
          if (prev.length === 1 && prev[0]!.kind === 'empty') {
            return [chatPane(openChat.id)];
          }
          return prev;
        });
      }
    }).catch(() => {});
    return () => { cancelled = true; };
  }, [sessionId]);

  useEffect(() => {
    savePanes(sessionId, panes);
  }, [sessionId, panes]);

  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
        ));
        removeChatFromPanes(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));
        removeChatFromPanes(event.chat_id);
      }
    });
  }, [sessionId]);

  function removeChatFromPanes(chatId: string) {
    setPanes((prev) => prev.map((p) => {
      const idx = p.chatIds.indexOf(chatId);
      if (idx < 0) return p;
      const nextIds = p.chatIds.filter((id) => id !== chatId);
      if (nextIds.length === 0) {
        return { ...p, kind: 'empty' as const, chatId: undefined, chatIds: [], activeChatIdx: -1 };
      }
      const nextActiveIdx = Math.min(p.activeChatIdx, nextIds.length - 1);
      return {
        ...p,
        chatIds: nextIds,
        activeChatIdx: nextActiveIdx,
        chatId: nextIds[nextActiveIdx],
      };
    }));
  }

  const openChatInPane = useCallback((paneIdx: number, chatId: string) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      const existing = pane.chatIds.indexOf(chatId);
      if (existing >= 0) {
        next[paneIdx] = { ...pane, kind: 'chat', chatId, activeChatIdx: existing };
      } else {
        const newIds = [...pane.chatIds, chatId];
        next[paneIdx] = {
          ...pane,
          kind: 'chat',
          chatId,
          chatIds: newIds,
          activeChatIdx: newIds.length - 1,
        };
      }
      return next;
    });
    setActivePaneIdx(paneIdx);
  }, []);

  const switchTab = useCallback((paneIdx: number, tabIdx: number) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      const chatId = pane.chatIds[tabIdx];
      if (!chatId) return prev;
      next[paneIdx] = { ...pane, chatId, activeChatIdx: tabIdx };
      return next;
    });
  }, []);

  const removeTab = useCallback((paneIdx: number, chatId: string) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      const nextIds = pane.chatIds.filter((id) => id !== chatId);
      if (nextIds.length === 0) {
        next[paneIdx] = { ...pane, kind: 'empty', chatId: undefined, chatIds: [], activeChatIdx: -1 };
      } else {
        const nextActiveIdx = Math.min(pane.activeChatIdx, nextIds.length - 1);
        next[paneIdx] = {
          ...pane,
          chatIds: nextIds,
          activeChatIdx: nextActiveIdx,
          chatId: nextIds[nextActiveIdx],
        };
      }
      return next;
    });
  }, []);

  // Keep only the right-clicked tab open in this pane.
  const closeOtherTabs = useCallback((paneIdx: number, keepChatId: string) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      const keepIdx = pane.chatIds.indexOf(keepChatId);
      if (keepIdx < 0) return prev;
      next[paneIdx] = {
        ...pane,
        kind: 'chat',
        chatId: keepChatId,
        chatIds: [keepChatId],
        activeChatIdx: 0,
      };
      return next;
    });
  }, []);

  // Close every tab to the right of the right-clicked one.
  const closeTabsToRight = useCallback((paneIdx: number, pivotChatId: string) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      const pivotIdx = pane.chatIds.indexOf(pivotChatId);
      if (pivotIdx < 0 || pivotIdx === pane.chatIds.length - 1) return prev;
      const nextIds = pane.chatIds.slice(0, pivotIdx + 1);
      const nextActiveIdx = Math.min(pane.activeChatIdx, nextIds.length - 1);
      next[paneIdx] = {
        ...pane,
        chatIds: nextIds,
        activeChatIdx: nextActiveIdx,
        chatId: nextIds[nextActiveIdx],
      };
      return next;
    });
  }, []);

  // Close every tab in this pane; land on landing page.
  const closeAllTabs = useCallback((paneIdx: number) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      next[paneIdx] = { ...pane, kind: 'empty', chatId: undefined, chatIds: [], activeChatIdx: -1 };
      return next;
    });
  }, []);

  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];
      });
      openChatInPane(paneIdx, chat.id);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to create chat');
    }
  }, [sessionId, openChatInPane]);

  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));
      removeChatFromPanes(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 showLandingPage = useCallback((paneIdx: number) => {
    setPanes((prev) => {
      const next = [...prev];
      const pane = next[paneIdx]!;
      next[paneIdx] = { ...pane, kind: 'empty', chatId: undefined };
      return next;
    });
  }, []);

  const addSplitPane = useCallback((kind: 'chat' | 'terminal' | 'agent') => {
    if (kind === 'terminal') {
      toast('Terminal panes coming in BooTerm');
      return;
    }
    if (kind === 'agent') {
      toast('Agent panes coming in BooCoder');
      return;
    }
    setPanes((prev) => {
      if (prev.length >= MAX_PANES) {
        toast.error(`Maximum ${MAX_PANES} panes`);
        return prev;
      }
      const next = [...prev, emptyPane()];
      setActivePaneIdx(next.length - 1);
      return next;
    });
  }, []);

  const removePane = useCallback((idx: number) => {
    setPanes((prev) => {
      if (prev.length <= 1) return prev;
      const next = prev.filter((_, i) => i !== idx);
      setActivePaneIdx((ai) => Math.min(ai, next.length - 1));
      return next;
    });
  }, []);

  const handlePaneDragStart = useCallback(
    (idx: number) => (e: DragEvent<HTMLDivElement>) => {
      draggingIdxRef.current = idx;
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/plain', String(idx));
    },
    []
  );

  const handlePaneDragOver = useCallback(
    (idx: number) => (e: DragEvent<HTMLDivElement>) => {
      if (draggingIdxRef.current === null) return;
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
      if (dragOverIdx !== idx) setDragOverIdx(idx);
    },
    [dragOverIdx]
  );

  const handlePaneDragLeave = useCallback(() => {
    setDragOverIdx(null);
  }, []);

  const handlePaneDrop = useCallback(
    (targetIdx: number) => (e: DragEvent<HTMLDivElement>) => {
      e.preventDefault();
      const fromIdx = draggingIdxRef.current;
      draggingIdxRef.current = null;
      setDragOverIdx(null);
      if (fromIdx === null || fromIdx === targetIdx) return;
      setPanes((prev) => {
        const next = [...prev];
        const [moved] = next.splice(fromIdx, 1);
        if (!moved) return prev;
        next.splice(targetIdx, 0, moved);
        // Keep active selection on the same logical pane (the one being dragged).
        setActivePaneIdx(targetIdx);
        return next;
      });
    },
    []
  );

  const handlePaneDragEnd = useCallback(() => {
    draggingIdxRef.current = null;
    setDragOverIdx(null);
  }, []);

  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];
      });
      openChatInPane(paneIdx, chat.id);
      await api.messages.send(chat.id, content);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to send');
    }
  }, [sessionId, openChatInPane]);

  function chatsForPane(pane: WorkspacePane): Chat[] {
    return pane.chatIds
      .map((id) => chats.find((c) => c.id === id))
      .filter((c): c is Chat => c !== undefined);
  }

  return (
    <div className="flex flex-col h-full min-h-0">
      <div className="flex items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5 shrink-0">
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <button
              type="button"
              disabled={panes.length >= MAX_PANES}
              className={cn(
                'flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-muted',
                panes.length >= MAX_PANES && 'opacity-40 cursor-not-allowed hover:bg-transparent'
              )}
            >
              <PanelRight size={14} />
              Split
            </button>
          </DropdownMenuTrigger>
          <DropdownMenuContent>
            <DropdownMenuItem onSelect={() => addSplitPane('chat')}>
              <MessageSquare size={14} /> Chat
            </DropdownMenuItem>
            <DropdownMenuItem onSelect={() => addSplitPane('terminal')}>
              <Terminal size={14} /> Terminal
            </DropdownMenuItem>
            <DropdownMenuItem onSelect={() => addSplitPane('agent')}>
              <Bot size={14} /> Agent
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </div>

      <div
        className="flex-1 grid min-h-0"
        style={{
          gridTemplateColumns: `repeat(${panes.length}, minmax(0, 1fr))`,
        }}
      >
        {panes.map((pane, idx) => (
          <div
            key={pane.id}
            className={cn(
              'flex flex-col h-full min-h-0 border-r border-border last:border-r-0 relative',
              idx === activePaneIdx && 'ring-1 ring-inset ring-ring/20',
              dragOverIdx === idx && draggingIdxRef.current !== idx &&
                'before:absolute before:inset-y-0 before:left-0 before:w-0.5 before:bg-primary before:z-10'
            )}
            onClick={() => setActivePaneIdx(idx)}
            onDragOver={panes.length > 1 ? handlePaneDragOver(idx) : undefined}
            onDragLeave={panes.length > 1 ? handlePaneDragLeave : undefined}
            onDrop={panes.length > 1 ? handlePaneDrop(idx) : undefined}
          >
            <div
              draggable={panes.length > 1}
              onDragStart={panes.length > 1 ? handlePaneDragStart(idx) : undefined}
              onDragEnd={panes.length > 1 ? handlePaneDragEnd : undefined}
            >
              <ChatTabBar
                pane={pane}
                tabs={chatsForPane(pane)}
                onSwitchTab={(tabIdx) => switchTab(idx, tabIdx)}
                onRemoveTab={(chatId) => removeTab(idx, chatId)}
                onCloseOthers={(chatId) => closeOtherTabs(idx, chatId)}
                onCloseToRight={(chatId) => closeTabsToRight(idx, chatId)}
                onCloseAll={() => closeAllTabs(idx)}
                onNewChat={() => void createChat(idx)}
                onShowHistory={() => showLandingPage(idx)}
                onRename={renameChat}
                onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
              />
            </div>

            <div className="flex-1 min-h-0 overflow-hidden">
              {pane.kind === 'chat' && pane.chatId ? (
                <ChatPane sessionId={sessionId} chatId={pane.chatId} projectId={projectId} sessionChats={chats} />
              ) : (
                <SessionLandingPage
                  sessionId={sessionId}
                  projectId={projectId}
                  chats={chats}
                  onOpenChat={(chatId) => openChatInPane(idx, chatId)}
                  onSend={(content) => void handleLandingSend(idx, content)}
                  onReopenChat={async (chatId) => {
                    await unarchiveChat(chatId);
                    openChatInPane(idx, chatId);
                  }}
                  onArchiveChat={archiveChat}
                  onRenameChat={renameChat}
                  onDeleteChat={deleteChat}
                />
              )}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}
