import { useState, useMemo, useEffect, useCallback } from "react";
import { Bookmark, Download, FileJson, X } from "lucide-react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useChatPanel } from "../context/ChatPanelContext";
import { useCompany } from "../context/CompanyContext";
import { useToast } from "../context/ToastContext";
import { ChatInput } from "./ChatInput";
import { ChatConversationList } from "./ChatConversationList";
import { ChatMessageList } from "./ChatMessageList";
import { ChatAgentSelector } from "./ChatAgentSelector";
import { ChatStopButton } from "./ChatStopButton";
import { ChatSearchDialog } from "./ChatSearchDialog";
import { ChatBranchSelector } from "./ChatBranchSelector";
import { ChatBookmarkList } from "./ChatBookmarkList";
import { MobileChatView } from "./MobileChatView";
import { InstallPromptBanner } from "./InstallPromptBanner";
import { OfflineBanner } from "./OfflineBanner";
import { NotificationPermissionPrompt } from "./NotificationPermissionPrompt";
import { Button } from "@/components/ui/button";
import { chatApi } from "../api/chat";
import { agentsApi } from "../api/agents";
import { useChatMessages } from "../hooks/useChatMessages";
import { useStreamingChat } from "../hooks/useStreamingChat";
import { useBrainstormerDefault } from "../hooks/useBrainstormerDefault";
import { useChatBookmarks, useToggleBookmark } from "../hooks/useChatBookmarks";
import { useChatFileUpload } from "../hooks/useChatFileUpload";
import { useMediaQuery } from "../hooks/useMediaQuery";
import { useOfflineQueue } from "../hooks/useOfflineQueue";
import { useOnlineStatus } from "../hooks/useOnlineStatus";
import { resolveAgentFromContent } from "../lib/slash-commands";
import type { AgentRole } from "@paperclipai/shared";

export function ChatPanel() {
  const isDesktop = useMediaQuery("(min-width: 768px)");
  const { chatOpen, setChatOpen, activeConversationId, setActiveConversationId, setScrollToMessageId } = useChatPanel();
  const { selectedCompanyId } = useCompany();
  const queryClient = useQueryClient();
  const { pushToast } = useToast();
  const [isSending, setIsSending] = useState(false);
  const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
  const [searchOpen, setSearchOpen] = useState(false);
  const [bookmarksOpen, setBookmarksOpen] = useState(false);

  const { messages } = useChatMessages(activeConversationId);
  const { streamingContent, isStreaming, startStream, stop } = useStreamingChat(activeConversationId);
  const { pendingFiles, addFile, removeFile, clearCompleted, completedFileIds } = useChatFileUpload(activeConversationId);
  const { enqueue, queuedCount } = useOfflineQueue();
  const isOnline = useOnlineStatus();

  const brainstormerDefaultId = useBrainstormerDefault();

  // Count assistant messages for the notification engagement gate
  const agentResponseCount = useMemo(
    () => messages.filter((m) => m.role === "assistant").length,
    [messages],
  );

  // Listen for nexus:open-chat-search custom event (dispatched by CommandPalette)
  useEffect(() => {
    const handler = () => setSearchOpen(true);
    window.addEventListener("nexus:open-chat-search", handler);
    return () => window.removeEventListener("nexus:open-chat-search", handler);
  }, []);

  // Auto-select brainstormer (general agent) for new conversations
  useEffect(() => {
    if (activeAgentId === null && brainstormerDefaultId !== null && !activeConversationId) {
      setActiveAgentId(brainstormerDefaultId);
    }
  }, [activeAgentId, brainstormerDefaultId, activeConversationId]);

  // Handoff callback: call handoff API and invalidate messages cache
  const handleHandoff = useCallback(async (spec: { what: string; why: string; constraints: string; success: string }) => {
    if (!activeConversationId) return;
    try {
      await chatApi.handoffSpec(activeConversationId, spec, "pm");
      queryClient.invalidateQueries({ queryKey: ["chat", "messages", activeConversationId] });
    } catch {
      pushToast({ title: "Could not send to PM. Try again.", tone: "error" });
    }
  }, [activeConversationId, queryClient, pushToast]);

  // Load agents for routing and identity
  const { data: agents = [], isLoading: agentsLoading } = useQuery({
    queryKey: ["agents", selectedCompanyId],
    queryFn: () => agentsApi.list(selectedCompanyId!),
    enabled: !!selectedCompanyId,
  });

  // Build agent map for message identity bars
  const agentMap = useMemo(() => {
    const map = new Map<string, { name: string; icon: string | null; role: AgentRole | null }>();
    for (const a of agents) {
      map.set(a.id, { name: a.name, icon: a.icon, role: (a.role as AgentRole) ?? null });
    }
    return map;
  }, [agents]);

  // Resolve streaming agent identity
  const streamingAgent = activeAgentId ? agentMap.get(activeAgentId) : undefined;

  // Fetch branches for active conversation
  const { data: branchesData } = useQuery({
    queryKey: ["chat", "branches", activeConversationId],
    queryFn: () => chatApi.listBranches(activeConversationId!),
    enabled: !!activeConversationId,
  });
  const branches = branchesData?.items ?? [];

  // Bookmark state for active conversation
  const { data: bookmarkData } = useChatBookmarks(selectedCompanyId, activeConversationId ?? undefined);
  const { toggleBookmark } = useToggleBookmark();
  const bookmarkedMessageIds = useMemo(() => {
    const ids = new Set<string>();
    for (const b of bookmarkData?.items ?? []) {
      ids.add(b.message.id);
    }
    return ids;
  }, [bookmarkData]);

  // Search navigation handler
  const handleSearchNavigate = useCallback((conversationId: string, messageId: string) => {
    setActiveConversationId(conversationId);
    setScrollToMessageId(messageId);
    setSearchOpen(false);
  }, [setActiveConversationId, setScrollToMessageId]);

  // Bookmark navigation handler
  const handleBookmarkNavigate = useCallback((conversationId: string, messageId: string) => {
    setActiveConversationId(conversationId);
    setScrollToMessageId(messageId);
    setBookmarksOpen(false);
  }, [setActiveConversationId, setScrollToMessageId]);

  // Bookmark toggle handler
  const handleBookmark = useCallback((messageId: string) => {
    if (!activeConversationId) return;
    toggleBookmark({ conversationId: activeConversationId, messageId });
  }, [activeConversationId, toggleBookmark]);

  // Export handler
  const handleExport = useCallback((format: "markdown" | "json") => {
    if (!activeConversationId) return;
    window.location.href = chatApi.exportConversation(activeConversationId, format);
  }, [activeConversationId]);

  const handleSend = async (content: string) => {
    if (!selectedCompanyId) return;

    // If offline, enqueue the message and show a toast
    if (!isOnline) {
      if (activeConversationId) {
        await enqueue(activeConversationId, content);
        pushToast({ title: "Message queued — will send when you're back online", tone: "info" });
      }
      return;
    }

    // Resolve agent from slash command or @mention
    const resolvedAgentId = resolveAgentFromContent(content, agents, activeAgentId);

    // Capture file IDs before clearing
    const fileIdsToAttach = [...completedFileIds];

    setIsSending(true);
    try {
      if (!activeConversationId) {
        // Path 1: No active conversation -- create one, post user message, then stream
        const newConvo = await chatApi.createConversation(selectedCompanyId, {
          agentId: resolvedAgentId ?? undefined,
        });
        setActiveConversationId(newConvo.id);
        const message = await chatApi.postMessage(newConvo.id, { role: "user", content });
        // Attach any uploaded files to the message
        if (fileIdsToAttach.length > 0) {
          await chatApi.attachFilesToMessage(fileIdsToAttach, message.id);
          clearCompleted();
          queryClient.invalidateQueries({ queryKey: ["chat", "messages", newConvo.id] });
        }
        queryClient.invalidateQueries({ queryKey: ["chat"] });
        startStream(content, resolvedAgentId ?? undefined);
      } else {
        // Path 2: Active conversation -- post user message then stream
        const message = await chatApi.postMessage(activeConversationId, { role: "user", content });
        // Attach any uploaded files to the message
        if (fileIdsToAttach.length > 0) {
          await chatApi.attachFilesToMessage(fileIdsToAttach, message.id);
          clearCompleted();
        }
        queryClient.invalidateQueries({ queryKey: ["chat", "messages", activeConversationId] });
        startStream(content, resolvedAgentId ?? undefined);
      }
    } finally {
      setIsSending(false);
    }
  };

  // Edit handler: if editing a message that has subsequent responses, branch first
  const handleEdit = async (messageId: string, newContent: string) => {
    if (!activeConversationId) return;

    // Check if there are any messages after the edited message
    const editedIdx = messages.findIndex((m) => m.id === messageId);
    const hasSubsequentMessages = editedIdx >= 0 && editedIdx < messages.length - 1;

    if (hasSubsequentMessages) {
      // Branch the conversation from this message first (CHAT-14)
      try {
        const newConv = await chatApi.branchConversation(activeConversationId, messageId);
        setActiveConversationId(newConv.id);
        // Invalidate branches list so branch selector updates
        queryClient.invalidateQueries({ queryKey: ["chat", "branches"] });
        queryClient.invalidateQueries({ queryKey: ["chat"] });
        // Edit the message on the new branch
        await chatApi.editMessage(newConv.id, messageId, newContent);
        await chatApi.truncateMessagesAfter(newConv.id, messageId);
        queryClient.invalidateQueries({ queryKey: ["chat", "messages", newConv.id] });
        queryClient.invalidateQueries({ queryKey: ["chat", "search"] });
        startStream(newContent, activeAgentId ?? undefined);
      } catch {
        pushToast({ title: "Could not create branch. Try again.", tone: "error" });
      }
    } else {
      // No subsequent messages — edit in place
      await chatApi.editMessage(activeConversationId, messageId, newContent);
      await chatApi.truncateMessagesAfter(activeConversationId, messageId);
      queryClient.invalidateQueries({ queryKey: ["chat", "messages", activeConversationId] });
      queryClient.invalidateQueries({ queryKey: ["chat", "search"] });
      startStream(newContent, activeAgentId ?? undefined);
    }
  };

  // Retry handler: find the last user message before the assistant message,
  // delete the assistant message and everything after it, then re-stream
  // with the actual prior user message content (not hardcoded text).
  const handleRetry = async (assistantMessageId: string) => {
    if (!activeConversationId || !messages) return;

    // Find the assistant message index in the messages array
    const assistantIdx = messages.findIndex((m) => m.id === assistantMessageId);
    if (assistantIdx < 0) return;

    // Find the last user message before this assistant message
    let lastUserContent = "";
    for (let i = assistantIdx - 1; i >= 0; i--) {
      if (messages[i]!.role === "user") {
        lastUserContent = messages[i]!.content;
        break;
      }
    }
    if (!lastUserContent) return; // No prior user message found; nothing to retry

    // Truncate messages after the user message (this deletes the assistant msg + everything after)
    // First, find the user message to truncate after
    let userMessageId = "";
    for (let i = assistantIdx - 1; i >= 0; i--) {
      if (messages[i]!.role === "user") {
        userMessageId = messages[i]!.id;
        break;
      }
    }
    if (!userMessageId) return;

    // Delete everything after the user message (includes the assistant message itself)
    await chatApi.truncateMessagesAfter(activeConversationId, userMessageId);
    queryClient.invalidateQueries({ queryKey: ["chat", "messages", activeConversationId] });
    queryClient.invalidateQueries({ queryKey: ["chat", "search"] });

    // Re-stream using the actual user message content
    startStream(lastUserContent, activeAgentId ?? undefined);
  };

  // On mobile, render the full-screen MobileChatView instead of the desktop slide-in panel
  if (!isDesktop) {
    return <MobileChatView />;
  }

  return (
    <aside
      aria-label="Chat"
      className="hidden md:flex overflow-hidden transition-[width] duration-100 ease-out flex-shrink-0 border-l border-border flex-col bg-background"
      style={{ width: chatOpen ? 380 : 0 }}
    >
      {/* Offline status banner */}
      <OfflineBanner queuedCount={queuedCount} />

      {/* Header with agent selector */}
      <div className="flex items-center justify-between border-b border-border px-4 py-2 min-w-[380px]">
        <span className="text-sm font-medium">Chat</span>
        <div className="flex items-center gap-1">
          {selectedCompanyId && activeConversationId && (
            <>
              {/* Bookmarks toggle */}
              <Button
                variant={bookmarksOpen ? "secondary" : "ghost"}
                size="icon"
                className="h-6 w-6"
                onClick={() => setBookmarksOpen((v) => !v)}
                aria-label="Bookmarks"
                title="Bookmarks"
              >
                <Bookmark className="h-4 w-4" />
              </Button>
              {/* Export buttons */}
              <Button
                variant="ghost"
                size="icon"
                className="h-6 w-6"
                onClick={() => handleExport("markdown")}
                aria-label="Export as Markdown"
                title="Export as Markdown"
              >
                <Download className="h-4 w-4" />
              </Button>
              <Button
                variant="ghost"
                size="icon"
                className="h-6 w-6"
                onClick={() => handleExport("json")}
                aria-label="Export as JSON"
                title="Export as JSON"
              >
                <FileJson className="h-4 w-4" />
              </Button>
            </>
          )}
          {selectedCompanyId && (
            <ChatAgentSelector
              companyId={selectedCompanyId}
              conversationId={activeConversationId}
              agentId={activeAgentId}
              onAgentChange={setActiveAgentId}
            />
          )}
          <Button
            variant="ghost"
            size="icon"
            className="h-6 w-6"
            onClick={() => setChatOpen(false)}
            aria-label="Close chat"
          >
            <X className="h-4 w-4" />
          </Button>
        </div>
      </div>

      {/* Two-column layout */}
      <div className="flex flex-1 min-h-0 min-w-[380px]">
        {/* Left column: conversation list */}
        <div className="w-[160px] flex-shrink-0 border-r border-border bg-card overflow-hidden">
          {selectedCompanyId ? (
            <ChatConversationList companyId={selectedCompanyId} />
          ) : (
            <div className="p-3 text-center text-xs text-muted-foreground">
              No workspace selected
            </div>
          )}
        </div>

        {/* Right column: message thread + stop button + input */}
        <div className="flex flex-1 flex-col min-w-0">
          {/* Bookmarks panel (shown when toggled) */}
          {bookmarksOpen && selectedCompanyId && (
            <div className="border-b border-border" style={{ maxHeight: 200, overflow: "hidden" }}>
              <ChatBookmarkList
                companyId={selectedCompanyId}
                onNavigate={handleBookmarkNavigate}
              />
            </div>
          )}

          {/* Branch selector */}
          {activeConversationId && branches.length > 0 && (
            <ChatBranchSelector
              conversationId={activeConversationId}
              branches={branches}
              activeBranchId={activeConversationId}
              onSelectBranch={setActiveConversationId}
            />
          )}

          {/* Message area */}
          <div className="flex-1 overflow-hidden">
            {activeConversationId ? (
              <ChatMessageList
                conversationId={activeConversationId}
                streamingContent={streamingContent}
                isStreaming={isStreaming}
                streamingAgentName={streamingAgent?.name ?? null}
                streamingAgentIcon={streamingAgent?.icon ?? null}
                streamingAgentRole={streamingAgent?.role ?? null}
                onEdit={handleEdit}
                onRetry={handleRetry}
                onHandoff={handleHandoff}
                agentMap={agentMap}
                onBookmark={handleBookmark}
                bookmarkedMessageIds={bookmarkedMessageIds}
              />
            ) : (
              <div className="flex items-center justify-center h-full p-3">
                <p className="text-sm text-muted-foreground text-center">
                  Send a message to start this conversation.
                </p>
              </div>
            )}
          </div>

          {/* Stop button (shown during streaming) */}
          {isStreaming && <ChatStopButton onStop={stop} />}

          {/* Input area */}
          <div className="border-t border-border px-3 py-2">
            <ChatInput
              onSend={handleSend}
              isSubmitting={isSending}
              disabled={isStreaming}
              placeholder={isStreaming ? "Waiting for response..." : "Message your agent..."}
              agents={agents}
              agentsLoading={agentsLoading}
              pendingFiles={pendingFiles}
              onRemoveFile={removeFile}
              onFilesPicked={(files) => files.forEach(addFile)}
              enableVoiceInput={true}
            />
          </div>
        </div>
      </div>

      {/* Search dialog */}
      <ChatSearchDialog
        open={searchOpen}
        onOpenChange={setSearchOpen}
        companyId={selectedCompanyId}
        onNavigate={handleSearchNavigate}
      />

      {/* PWA install prompt banner (self-contained show/hide logic) */}
      <InstallPromptBanner />

      {/* Push notification permission prompt (shows after 3 agent responses) */}
      <NotificationPermissionPrompt agentResponseCount={agentResponseCount} />
    </aside>
  );
}
