import { useCallback, useEffect, useMemo, useRef } from 'react';
import { createPortal } from 'react-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { useNavigate, useRouterState } from '@tanstack/react-router';
import { X } from 'lucide-react';
import {
  useFloatingChat,
  computeDualAnchor,
  getChatWidth,
  CHAT_HEIGHT,
  PADDING,
} from '@/context/FloatingChatContext';
import { useAIChat, type UIChatContext, type FloatingDomainSignal } from '@/hooks/useAIChat';
import { ChatMarkdown } from '@/components/ai/AIChatPanel';
import { ChatInputBox, type ChatInputBoxHandle } from '@/components/ai/ChatInputBox';
import { Skeleton } from '@/components/ui/skeleton';

/** Map floating_domain signals to routes for background navigation */
const DOMAIN_ROUTES: Record<string, string> = {
  tasks: '/tasks',
  notes: '/notes',
  timelines: '/timeline',
  projects: '/projects',
};

const DOMAIN_SECTION_IDS: Partial<Record<'tasks' | 'notes' | 'timelines' | 'projects', string>> = {
  tasks: 'tasks-list',
  timelines: 'timeline-chart',
};

interface DomainNavigationTarget {
  route: '/tasks' | '/timeline' | '/projects' | '/notes/$noteId';
  sectionId?: string;
  projectId?: string;
  noteId?: string;
  nodeId?: string;
}

function normalizeDomainSignal(domain: FloatingDomainSignal): DomainNavigationTarget | null {
  if (typeof domain === 'string') {
    const route = DOMAIN_ROUTES[domain];
    if (!route) return null;
    return {
      route: route as DomainNavigationTarget['route'],
      sectionId: DOMAIN_SECTION_IDS[domain as keyof typeof DOMAIN_SECTION_IDS],
    };
  }

  switch (domain.type) {
    case 'task':
      return { route: '/tasks', sectionId: 'tasks-list' };
    case 'timeline':
      return { route: '/timeline', sectionId: 'timeline-chart' };
    case 'note':
      if (!domain.id) return { route: '/projects' };
      return { route: '/notes/$noteId', noteId: domain.id };
    case 'project': {
      if (domain.section === 'task') {
        return { route: '/projects', sectionId: 'project-tasks', projectId: domain.id ?? undefined };
      }
      if (domain.section === 'timeline') {
        return { route: '/projects', sectionId: 'project-timeline', projectId: domain.id ?? undefined };
      }
      if (domain.section === 'note') {
        return { route: '/projects', sectionId: 'project-notes', projectId: domain.id ?? undefined };
      }
      return { route: '/projects', projectId: domain.id ?? undefined };
    }
    case 'node':
      if (!domain.id) return null;
      return { route: '/projects', sectionId: domain.id, nodeId: domain.id };
    default:
      return null;
  }
}

function FloatingChatInner() {
  const { state, sections, close, updatePosition, setPendingSection, moveToSection } = useFloatingChat();
  const navigate = useNavigate();
  const routerState = useRouterState();
  const prevPathRef = useRef(routerState.location.pathname);
  const domainNavigationInFlightRef = useRef(false);

  // Active section lookup
  const activeSection = sections.get(state.activeSectionId ?? '');

  // Chat context — floating mode with scope derived from active section
  const chatContext = useMemo<UIChatContext>(() => {
    const scope = activeSection
      ? {
          type: (activeSection.label?.toLowerCase().includes('task')
            ? 'task'
            : activeSection.label?.toLowerCase().includes('note')
              ? 'note'
              : activeSection.label?.toLowerCase().includes('timeline')
                ? 'timeline'
                : 'project') as 'task' | 'project' | 'note' | 'timeline',
          id: activeSection.projectId,
        }
      : undefined;

    return {
      type: 'floating' as const,
      projectId: activeSection?.projectId,
      scope,
    };
  }, [activeSection?.projectId, activeSection?.label]);

  // Handle floating_domain signals — navigate in background
  const handleDomainSignal = useCallback(
    (domainSignal: FloatingDomainSignal) => {
      const target = normalizeDomainSignal(domainSignal);
      if (!target) return;

      // If backend points to a currently registered node/section, move there immediately.
      if (target.sectionId && sections.has(target.sectionId)) {
        moveToSection(target.sectionId);
        return;
      }

      const currentPath = routerState.location.pathname;
      const isCurrentRoute =
        (target.route === '/projects' && currentPath === '/projects') ||
        (target.route === '/tasks' && currentPath === '/tasks') ||
        (target.route === '/timeline' && currentPath === '/timeline') ||
        (target.route === '/notes/$noteId' && currentPath.startsWith('/notes/'));

      if (isCurrentRoute && target.sectionId) {
        setPendingSection({ sectionId: target.sectionId });
        return;
      }

      if (isCurrentRoute) return;

      domainNavigationInFlightRef.current = true;

      const pendingSectionId = target.sectionId;
      if (pendingSectionId) {
        setPendingSection({ sectionId: pendingSectionId });
      } else {
        setPendingSection(undefined);
      }

      if (target.route === '/projects') {
        void navigate({ to: '/projects', search: target.projectId ? { projectId: target.projectId } : {} });
      } else if (target.route === '/notes/$noteId' && target.noteId) {
        void navigate({ to: '/notes/$noteId', params: { noteId: target.noteId } });
      } else if (target.route === '/tasks') {
        void navigate({ to: '/tasks' });
      } else if (target.route === '/timeline') {
        void navigate({ to: '/timeline' });
      }
    },
    [routerState.location.pathname, navigate, setPendingSection, sections, moveToSection],
  );

  const {
    messages,
    isStreaming,
    streamingContent,
    handleSend,
    clearMessages,
    cacheKey,
  } = useAIChat(chatContext, { onDomainSignal: handleDomainSignal });

  const containerRef = useRef<HTMLDivElement>(null);

  // ---- Close on Escape ----

  useEffect(() => {
    if (!state.isOpen) return;
    const handler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.stopPropagation();
        close();
      }
    };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [state.isOpen, close]);

  // ---- Close on route change (unless cross-page navigation pending) ----

  // Tracks whether the most recent close was triggered by user navigation.
  // Used to decide whether to reset the session on close.
  const closeByNavigationRef = useRef(false);

  useEffect(() => {
    const currentPath = routerState.location.pathname;
    if (prevPathRef.current !== currentPath && state.isOpen) {
      // Keep floating chat alive when navigation is AI-domain driven.
      if (domainNavigationInFlightRef.current) {
        domainNavigationInFlightRef.current = false;
      } else if (!state.pendingSection) {
        closeByNavigationRef.current = true;
        close();
      }
    }
    prevPathRef.current = currentPath;
  }, [routerState.location.pathname, state.isOpen, state.pendingSection, close]);

  // ---- Clear messages on close ----

  const prevOpenRef = useRef(state.isOpen);
  useEffect(() => {
    if (prevOpenRef.current && !state.isOpen) {
      const resetSession = closeByNavigationRef.current;
      closeByNavigationRef.current = false;
      // Clear input draft first so the unmount flush writes '' to the cache.
      inputRef.current?.clear();
      clearMessages(resetSession);
    }
    prevOpenRef.current = state.isOpen;
  }, [state.isOpen, clearMessages]);

  // ---- Window resize: keep within bounds ----

  useEffect(() => {
    if (!state.isOpen) return;
    const handler = () => {
      // Re-anchor if the container would go offscreen
      const el = containerRef.current;
      if (el) {
        const rect = el.getBoundingClientRect();
        if (rect.right > window.innerWidth || rect.bottom > window.innerHeight) {
          el.style.left = `${Math.max(PADDING, Math.min(state.position.x, window.innerWidth - getChatWidth() - PADDING))}px`;
          el.style.top = `${Math.max(PADDING, Math.min(state.position.y, window.innerHeight - CHAT_HEIGHT - PADDING))}px`;
        }
      }
    };
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, [state.isOpen, state.position.x, state.position.y]);

  // ---- Scroll tracking: dual-anchor repositioning ----

  useEffect(() => {
    if (!state.isOpen || !state.activeSectionId) return;
    const section = sections.get(state.activeSectionId);
    if (!section || section.anchorMode === 'right-margin') return;

    const el = section.ref.current;
    if (!el) return;

    // Find scrollable ancestor
    let scrollParent: HTMLElement | null = el.parentElement;
    while (scrollParent) {
      const style = getComputedStyle(scrollParent);
      if (style.overflow === 'auto' || style.overflow === 'scroll' ||
          style.overflowY === 'auto' || style.overflowY === 'scroll') {
        break;
      }
      // Also check for Radix ScrollArea viewport
      if (scrollParent.hasAttribute('data-radix-scroll-area-viewport')) break;
      scrollParent = scrollParent.parentElement;
    }

    if (!scrollParent) return;

    let rafId: number | null = null;
    const handleScroll = () => {
      if (rafId !== null) return;
      rafId = requestAnimationFrame(() => {
        rafId = null;
        const newPos = computeDualAnchor(section);
        if (newPos) {
          updatePosition(newPos);
        }
        // null = fully off-screen → freeze (do nothing)
      });
    };

    scrollParent.addEventListener('scroll', handleScroll, { passive: true });
    return () => {
      scrollParent.removeEventListener('scroll', handleScroll);
      if (rafId !== null) cancelAnimationFrame(rafId);
    };
  }, [state.isOpen, state.activeSectionId, sections, updatePosition]);

  // ---- Auto-scroll messages ----

  const scrollRef = useRef<HTMLDivElement>(null);
  const scrollToBottom = useCallback(() => {
    const el = scrollRef.current;
    if (el) el.scrollTo({ top: el.scrollHeight });
  }, []);

  useEffect(() => {
    scrollToBottom();
  }, [messages, streamingContent, scrollToBottom]);

  // ---- Auto-focus input on open ----

  const inputRef = useRef<ChatInputBoxHandle>(null);
  useEffect(() => {
    if (state.isOpen) {
      const timer = setTimeout(() => inputRef.current?.focus(), 100);
      return () => clearTimeout(timer);
    }
  }, [state.isOpen]);

  const hasMessages = messages.length > 0 || isStreaming;

  // Expand the messages panel upward if there's enough space above the input bar,
  // otherwise expand downward. 320px = 300px max-h + 8px gap + 12px buffer.
  const expandUp = state.position.y >= 320;

  return (
    <AnimatePresence>
      {state.isOpen && (
        <motion.div
          ref={containerRef}
          key="floating-chat"
          layout
          layoutId={state.morphTargetId ?? undefined}
          initial={{ opacity: 0, scale: 0.95, y: 12 }}
          animate={{ opacity: 1, scale: 1, y: 0 }}
          exit={{ opacity: 0, scale: 0.95, y: 12 }}
          transition={{ type: 'spring', stiffness: 400, damping: 30 }}
          style={{
            position: 'fixed',
            left: state.position.x,
            top: state.position.y,
            width: state.position.width,
            zIndex: 9999,
          }}
          className="relative"
        >
          {/* ---- Messages panel — floats above or below the input bar ---- */}
          <AnimatePresence>
            {hasMessages && (
              <motion.div
                key="messages-panel"
                initial={{ opacity: 0, scale: 0.97, y: expandUp ? 8 : -8 }}
                animate={{ opacity: 1, scale: 1, y: 0 }}
                exit={{ opacity: 0, scale: 0.97, y: expandUp ? 8 : -8 }}
                transition={{ type: 'spring', stiffness: 400, damping: 30 }}
                style={{
                  position: 'absolute',
                  width: '100%',
                  ...(expandUp
                    ? { bottom: 'calc(100% + 8px)' }
                    : { top: 'calc(100% + 8px)' }),
                }}
                className="rounded-2xl overflow-hidden"
              >
                <div
                  ref={scrollRef}
                  className="max-h-[300px] overflow-y-auto rounded-2xl [&::-webkit-scrollbar]:w-2.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/40"
                >
                  <div className="flex flex-col gap-2.5 p-3">
                    {messages.map((msg) => {
                      if (msg.role === 'user') {
                        return (
                          <div key={msg.id} className="flex justify-end">
                            <div className="glass-surface-subtle max-w-[80%] rounded-2xl rounded-br-md px-3.5 py-2">
                              <p className="text-xs whitespace-pre-wrap leading-relaxed text-foreground">
                                {msg.content}
                              </p>
                            </div>
                          </div>
                        );
                      }

                      if (msg.error) {
                        return (
                          <div key={msg.id} className="flex justify-start">
                            <div className="glass-surface-subtle max-w-[80%] rounded-2xl rounded-bl-md px-3.5 py-2 !border-destructive/30">
                              <p className="text-xs text-destructive whitespace-pre-wrap leading-relaxed">
                                {msg.content}
                              </p>
                            </div>
                          </div>
                        );
                      }

                      return (
                        <div key={msg.id} className="flex justify-start">
                          <div className="glass-surface-subtle max-w-[80%] rounded-2xl rounded-bl-md px-3.5 py-2">
                            <div className="text-xs text-foreground">
                              <ChatMarkdown content={msg.content} />
                            </div>
                          </div>
                        </div>
                      );
                    })}

                    {/* Streaming */}
                    {isStreaming && (
                      <div className="flex justify-start">
                        <div className="glass-surface-subtle max-w-[80%] rounded-2xl rounded-bl-md px-3.5 py-2">
                          {streamingContent ? (
                            <div className="text-xs text-foreground">
                              <ChatMarkdown content={streamingContent} />
                            </div>
                          ) : (
                            <div className="space-y-1.5 py-0.5">
                              <Skeleton className="h-3 w-36" />
                              <Skeleton className="h-3 w-24" />
                            </div>
                          )}
                        </div>
                      </div>
                    )}
                  </div>
                </div>
              </motion.div>
            )}
          </AnimatePresence>

          {/* ---- Floating input bar ---- */}
          <div className="glass-surface relative rounded-2xl transition-shadow focus-within:shadow-[0_8px_60px_-8px_rgba(0,0,0,0.35)]">
            {/* Close button */}
            <button
              onClick={close}
              className="absolute -top-1.5 -right-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-muted/90 backdrop-blur-sm border border-border/50 shadow-sm hover:bg-muted transition-colors z-10"
            >
              <X size={10} />
            </button>

            <ChatInputBox
              ref={inputRef}
              variant="floating"
              cacheKey={cacheKey}
              isStreaming={isStreaming}
              onSend={handleSend}
              placeholder={`Ask about ${activeSection?.label ?? 'this section'}...`}
            />
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

export function FloatingChatPortal() {
  return createPortal(<FloatingChatInner />, document.body);
}
