import { buildMentionHtml, type Node } from '@/lib/mention-ast';
import type { MentionSuggestion, MentionType, RoomAiConfig } from '@/lib/mention-types';
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Skeleton } from '@/components/ui/skeleton';
import {
  Bot, ChevronRight, Database, SearchX, Sparkles, User, Check,
} from 'lucide-react';
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';

// ─── Types ───────────────────────────────────────────────────────────────────

interface PopoverPosition { top: number; left: number }

interface MentionPopoverProps {
  members: Array<{ user: string; user_info?: { username?: string; avatar_url?: string }; role?: string }>;
  repos: Array<{ uid: string; repo_name: string }> | null;
  aiConfigs: RoomAiConfig[];
  reposLoading: boolean;
  aiConfigsLoading: boolean;
  containerRef: React.RefObject<HTMLDivElement | null>;
  /** Plain text value of the input */
  inputValue: string;
  /** Current caret offset in the text */
  cursorPosition: number;
  onSelect: (newValue: string, newCursorPos: number) => void;
  onOpenChange: (open: boolean) => void;
  onCategoryEnter: (category: string) => void;
  suggestions: MentionSuggestion[];
  selectedIndex: number;
  setSelectedIndex: (index: number) => void;
}

// ─── Category Configs ────────────────────────────────────────────────────────

const CATEGORY_CONFIG: Record<string, { icon: JSX.Element; color: string; bgColor: string; borderColor: string; gradient: string }> = {
  repository: {
    icon: <Database className="h-3.5 w-3.5" />, color: 'text-violet-600 dark:text-violet-400',
    bgColor: 'bg-violet-500/10 dark:bg-violet-500/15', borderColor: 'border-violet-500/20',
    gradient: 'from-violet-500/5 to-transparent',
  },
  user: {
    icon: <User className="h-3.5 w-3.5" />, color: 'text-sky-600 dark:text-sky-400',
    bgColor: 'bg-sky-500/10 dark:bg-sky-500/15', borderColor: 'border-sky-500/20',
    gradient: 'from-sky-500/5 to-transparent',
  },
  ai: {
    icon: <Sparkles className="h-3.5 w-3.5" />, color: 'text-emerald-600 dark:text-emerald-400',
    bgColor: 'bg-emerald-500/10 dark:bg-emerald-500/15', borderColor: 'border-emerald-500/20',
    gradient: 'from-emerald-500/5 to-transparent',
  },
};

const CATEGORIES: MentionSuggestion[] = [
  { type: 'category', category: 'repository', label: 'Repository' },
  { type: 'category', category: 'user', label: 'User' },
  { type: 'category', category: 'ai', label: 'AI' },
];

// ─── Utilities ───────────────────────────────────────────────────────────────

function escapeRegExp(s: string): string {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// ─── Sub-components ──────────────────────────────────────────────────────────

function HighlightMatch({ text, match }: { text: string; match: string }) {
  if (!match) return <>{text}</>;
  const parts = text.split(new RegExp(`(${escapeRegExp(match)})`, 'gi'));
  return (
    <>
      {parts.map((part, i) =>
        /[@a-z]/i.test(part) && new RegExp(escapeRegExp(match), 'gi').test(part) ? (
          <span key={i} className="bg-yellow-500/20 text-yellow-700 dark:text-yellow-300 font-semibold rounded px-0.5">
            {part}
          </span>
        ) : (
          <span key={i}>{part}</span>
        ),
      )}
    </>
  );
}

function CategoryHeader({ suggestion, isSelected }: { suggestion: MentionSuggestion; isSelected: boolean }) {
  const config = suggestion.category ? CATEGORY_CONFIG[suggestion.category] : null;
  return (
    <div className={cn(
      'flex items-center gap-2 px-3 py-2.5 text-xs font-medium text-muted-foreground transition-colors duration-150',
      isSelected && 'text-foreground bg-muted/50',
    )}>
      <div className={cn('flex h-5 w-5 items-center justify-center rounded-md border', config?.bgColor, config?.borderColor, config?.color)}>
        {config?.icon ?? <ChevronRight className="h-3 w-3" />}
      </div>
      <span className="flex-1">{suggestion.label}</span>
      <ChevronRight className="h-3.5 w-3.5 opacity-50" />
    </div>
  );
}

function SuggestionItem({ suggestion, isSelected, onSelect, onMouseEnter, searchTerm }: {
  suggestion: MentionSuggestion; isSelected: boolean;
  onSelect: () => void; onMouseEnter: () => void; searchTerm: string;
}) {
  const config = suggestion.category ? CATEGORY_CONFIG[suggestion.category] : null;

  return (
    <div className={cn(
      'group relative flex items-center gap-3 px-3 py-2.5 cursor-pointer transition-all duration-150 ease-out border-l-2 border-transparent',
      isSelected && ['bg-gradient-to-r', config?.gradient ?? 'from-muted to-transparent', 'border-l-current', config?.color ?? 'text-foreground'],
      !isSelected && 'hover:bg-muted/40',
    )}
      onPointerDown={(e) => e.preventDefault()}
      onClick={onSelect} onMouseEnter={onMouseEnter}
    >
      {/* Icon */}
      <div className="relative shrink-0">
        {suggestion.category === 'ai' ? (
          <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500/20 to-emerald-600/10 border border-emerald-500/20 shadow-sm">
            <Bot className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
          </div>
        ) : suggestion.category === 'repository' ? (
          <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500/20 to-violet-600/10 border border-violet-500/20 shadow-sm">
            <Database className="h-4 w-4 text-violet-600 dark:text-violet-400" />
          </div>
        ) : suggestion.avatar ? (
          <Avatar size="sm" className="h-8 w-8 ring-2 ring-background">
            <AvatarImage src={suggestion.avatar} alt={suggestion.label} />
            <AvatarFallback className="bg-sky-500/10 text-sky-600 text-xs font-medium">
              {suggestion.label.slice(0, 2).toUpperCase()}
            </AvatarFallback>
          </Avatar>
        ) : (
          <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-sky-500/20 to-sky-600/10 border border-sky-500/20 shadow-sm">
            <span className="text-xs font-semibold text-sky-600 dark:text-sky-400">
              {suggestion.label[0]?.toUpperCase()}
            </span>
          </div>
        )}
        {/* Selection dot */}
        <div className={cn(
          'absolute -right-0.5 -bottom-0.5 h-2.5 w-2.5 rounded-full bg-primary border-2 border-background transition-transform duration-150',
          isSelected ? 'scale-100' : 'scale-0',
        )} />
      </div>

      {/* Text */}
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2">
          <span className="block truncate text-sm font-medium">
            <HighlightMatch text={suggestion.label} match={searchTerm} />
          </span>
          {suggestion.category && (
            <span className={cn('text-[10px] px-1.5 py-0.5 rounded-full font-medium shrink-0 border', config?.bgColor, config?.color, config?.borderColor)}>
              {suggestion.category}
            </span>
          )}
        </div>
        {suggestion.sublabel && (
          <span className="block truncate text-xs text-muted-foreground mt-0.5">{suggestion.sublabel}</span>
        )}
      </div>

      {/* Check mark */}
      <div className={cn(
        'flex items-center justify-center w-5 h-5 rounded-full transition-all duration-150',
        isSelected ? 'bg-primary/10 text-primary' : 'opacity-0 group-hover:opacity-30',
      )}>
        <Check className="h-3.5 w-3.5" />
      </div>
    </div>
  );
}

function LoadingSkeleton() {
  return (
    <div className="px-3 py-3 space-y-2">
      {[1, 2, 3].map(i => (
        <div key={i} className="flex items-center gap-3 py-1">
          <Skeleton className="h-8 w-8 rounded-lg shrink-0" />
          <div className="flex-1 space-y-1.5"><Skeleton className="h-3.5 w-24" /><Skeleton className="h-3 w-16" /></div>
        </div>
      ))}
    </div>
  );
}

function EmptyState({ loading }: { loading?: boolean }) {
  return (
    <div className="flex flex-col items-center justify-center px-4 py-8 text-center">
      <div className={cn('flex h-12 w-12 items-center justify-center rounded-xl mb-3 bg-muted/80')}>
        {loading ? (
          <div className="h-5 w-5 rounded-full border-2 border-primary/30 border-t-primary animate-spin" />
        ) : (
          <SearchX className="h-5 w-5 text-muted-foreground" />
        )}
      </div>
      <p className="text-sm font-medium text-foreground">{loading ? 'Loading...' : 'No matches found'}</p>
      <p className="text-xs text-muted-foreground mt-1">{loading ? 'Please wait' : 'Try a different search term'}</p>
    </div>
  );
}

// ─── Main Component ──────────────────────────────────────────────────────────

export function MentionPopover({
  members, repos, aiConfigs, reposLoading, aiConfigsLoading,
  containerRef, inputValue, cursorPosition, onSelect, onOpenChange, onCategoryEnter,
  suggestions, selectedIndex, setSelectedIndex,
}: MentionPopoverProps) {
  const popoverRef = useRef<HTMLDivElement>(null);
  const itemsRef = useRef<(HTMLDivElement | null)[]>([]);

  // Stable refs for event listener closure
  const mentionStateRef = useRef<{ category: string; item: string; hasColon: boolean } | null>(null);
  const visibleSuggestionsRef = useRef<MentionSuggestion[]>([]);
  const selectedIndexRef = useRef(selectedIndex);
  const handleSelectRef = useRef<() => void>(() => {});
  const closePopoverRef = useRef<() => void>(() => {});
  const onCategoryEnterRef = useRef(onCategoryEnter);
  onCategoryEnterRef.current = onCategoryEnter;

  // Parse mention state
  const mentionState = useMemo(() => {
    const before = inputValue.slice(0, cursorPosition);
    const match = before.match(/@([^:@\s<]*)(:([^\s<]*))?$/);
    if (!match) return null;
    return { category: match[1].toLowerCase(), item: (match[3] ?? '').toLowerCase(), hasColon: match[2] !== undefined };
  }, [inputValue, cursorPosition]);
  mentionStateRef.current = mentionState;

  visibleSuggestionsRef.current = suggestions;
  selectedIndexRef.current = selectedIndex;

  // Auto-select first item
  useEffect(() => { setSelectedIndex(0); }, [suggestions.length]);

  // Scroll selected into view
  useLayoutEffect(() => {
    const el = itemsRef.current[selectedIndex];
    if (el) el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
  }, [selectedIndex]);

  const closePopover = useCallback(() => onOpenChange(false), [onOpenChange]);
  closePopoverRef.current = closePopover;

  // ─── Insertion Logic ───────────────────────────────────────────────────────

  const doInsert = useCallback((suggestion: MentionSuggestion) => {
    const textarea = containerRef.current;
    if (!textarea || suggestion.type !== 'item') return;

    // Get current DOM text and cursor position
    const textBeforeCursor = (() => {
      const sel = window.getSelection();
      if (!sel || sel.rangeCount === 0) return inputValue.slice(0, cursorPosition);
      const range = sel.getRangeAt(0);
      const pre = range.cloneRange();
      pre.selectNodeContents(textarea);
      pre.setEnd(range.startContainer, range.startOffset);
      let count = 0;
      const walker = document.createTreeWalker(textarea, NodeFilter.SHOW_TEXT);
      while (walker.nextNode()) {
        const node = walker.currentNode as Text;
        if (node === range.startContainer) { count += range.startOffset; break; }
        count += node.length;
      }
      return inputValue.slice(0, count);
    })();

    const atMatch = textBeforeCursor.match(/@([^:@\s<]*)(:([^\s<]*))?$/);
    if (!atMatch) return;

    const [fullPattern] = atMatch;
    const startPos = textBeforeCursor.length - fullPattern.length;
    const before = textBeforeCursor.substring(0, startPos);
    // Reconstruct after: from DOM text after cursor
    const domText = (() => {
      const sel = window.getSelection();
      if (!sel || sel.rangeCount === 0) return inputValue.substring(cursorPosition);
      const range = sel.getRangeAt(0);
      const post = range.cloneRange();
      post.setStart(range.endContainer, range.endOffset);
      let count = 0;
      const tree = document.createTreeWalker(textarea, NodeFilter.SHOW_TEXT);
      while (tree.nextNode()) count += (tree.currentNode as Text).length;
      return inputValue.substring(cursorPosition);
    })();

    const html = buildMentionHtml(
      suggestion.category!, suggestion.mentionId!, suggestion.label,
    );
    const spacer = ' ';
    const newValue = before + html + spacer + domText;
    const newCursorPos = startPos + html.length + spacer.length;

    onSelect(newValue, newCursorPos);
    closePopover();
  }, [containerRef, inputValue, cursorPosition, onSelect, closePopover]);

  handleSelectRef.current = doInsert;

  // ─── Positioning ───────────────────────────────────────────────────────────

  const [position, setPosition] = useState<PopoverPosition>({ top: 0, left: 0 });

  useLayoutEffect(() => {
    if (!containerRef.current) return;
    if (!mentionState) return;

    const textarea = containerRef.current;
    const styles = window.getComputedStyle(textarea);

    // Measure @pattern position using hidden clone div
    const tempDiv = document.createElement('span');
    tempDiv.style.cssText = getComputedStyleStyles(styles);
    tempDiv.style.position = 'absolute';
    tempDiv.style.visibility = 'hidden';
    tempDiv.style.whiteSpace = 'pre-wrap';
    tempDiv.style.pointerEvents = 'none';
    document.body.appendChild(tempDiv);

    const pattern = mentionState.hasColon ? `@${mentionState.category}:${mentionState.item}` : `@${mentionState.category}`;
    tempDiv.textContent = inputValue.slice(0, cursorPosition - pattern.length + (inputValue.slice(0, cursorPosition).lastIndexOf(pattern)));

    const span = document.createElement('span');
    span.textContent = '|';
    tempDiv.appendChild(span);

    const rect = textarea.getBoundingClientRect();
    const borderTop = parseFloat(styles.borderTopWidth) || 0;
    const borderLeft = parseFloat(styles.borderLeftWidth) || 0;
    const scrollTop = textarea.scrollTop;
    const scrollLeft = textarea.scrollLeft;

    const lineHeight = parseFloat(styles.lineHeight) || 20;
    const contentTop = rect.top + borderTop + span.offsetTop - scrollTop;
    const contentLeft = rect.left + borderLeft + span.offsetLeft - scrollLeft;

    // Append clone for measurement
    const clone = textarea.cloneNode(true) as HTMLDivElement;
    clone.style.cssText = getComputedStyleStyles(styles);
    clone.style.position = 'fixed';
    clone.style.top = '-9999px';
    clone.style.left = '-9999px';
    clone.style.width = styles.width;
    clone.style.maxWidth = styles.maxWidth;
    clone.innerHTML = '';
    const textSpan = document.createElement('span');
    textSpan.style.whiteSpace = 'pre';
    textSpan.textContent = inputValue.slice(0, cursorPosition);
    clone.appendChild(textSpan);
    const cursorSpan = document.createElement('span');
    cursorSpan.textContent = '|';
    clone.appendChild(cursorSpan);
    document.body.appendChild(clone);
    document.body.removeChild(clone);

    const popoverWidth = 320;
    const popoverHeight = Math.min(360, suggestions.length * 56 + 100);
    const vp = 16;

    const left = Math.max(Math.max(contentLeft, vp), window.innerWidth - popoverWidth - vp);
    const shouldBelow = contentTop + lineHeight + 8 + popoverHeight < window.innerHeight - vp;
    const top = shouldBelow
      ? contentTop + lineHeight + 8
      : Math.max(vp, contentTop - popoverHeight - 8);

    setPosition({ top, left: contentLeft });

    document.body.removeChild(tempDiv);

    // Simple approach: just use the textarea bounds + estimated offset
    // This is sufficient for most cases
  }, [inputValue, cursorPosition, containerRef, mentionState, suggestions.length]);

  // ─── Keyboard Navigation ───────────────────────────────────────────────────

  useEffect(() => {
    const textarea = containerRef.current;
    if (!textarea) return;

    const onKeyDown = (e: KeyboardEvent) => {
      const vis = visibleSuggestionsRef.current;
      if (vis.length === 0) return;

      if (e.key === 'ArrowDown') {
        e.preventDefault();
        const next = (selectedIndexRef.current + 1) % Math.max(vis.length, 1);
        setSelectedIndex(next);
        selectedIndexRef.current = next;
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        const prev = (selectedIndexRef.current - 1 + vis.length) % Math.max(vis.length, 1);
        setSelectedIndex(prev);
        selectedIndexRef.current = prev;
      } else if (e.key === 'Enter' || e.key === 'Tab') {
        const item = vis[selectedIndexRef.current];
        if (!item) return;
        e.preventDefault();
        if (item.type === 'category') onCategoryEnterRef.current(item.category!);
        else handleSelectRef.current();
      } else if (e.key === 'Escape') {
        e.preventDefault();
        closePopoverRef.current();
      }
    };

    textarea.addEventListener('keydown', onKeyDown);
    return () => textarea.removeEventListener('keydown', onKeyDown);
  }, [containerRef, setSelectedIndex]);

  // ─── Close on Outside Click ────────────────────────────────────────────────

  useEffect(() => {
    const handler = (e: PointerEvent) => {
      const t = e.target as Node;
      if (popoverRef.current?.contains(t)) return;
      if (containerRef.current?.contains(t)) return;
      closePopover();
    };
    document.addEventListener('pointerdown', handler);
    return () => document.removeEventListener('pointerdown', handler);
  }, [closePopover, containerRef]);

  // Hide when mention state is gone
  useEffect(() => {
    if (!mentionState) closePopover();
  }, [inputValue, cursorPosition, closePopover, mentionState]);

  // Don't render if no valid mention context
  if (!mentionState) return null;

  const isLoading = (mentionState.category === 'repository' && reposLoading) ||
    (mentionState.category === 'ai' && aiConfigsLoading);

  const currentCategory = mentionState.hasColon ? mentionState.category : null;
  const catConfig = currentCategory ? CATEGORY_CONFIG[currentCategory] : null;

  return (
    <div ref={popoverRef} className="animate-in fade-in zoom-in-95 duration-150 ease-out fixed z-50 w-80 overflow-hidden rounded-xl border border-border/50 bg-popover/95 backdrop-blur-xl shadow-2xl shadow-black/10 dark:shadow-black/20 ring-1 ring-black/5 dark:ring-white/10" style={{ top: position.top, left: position.left }}>
      {/* Header */}
      <div className={cn('flex items-center gap-2 px-3 py-2.5 border-b border-border/50 bg-gradient-to-r from-muted/50 to-transparent', catConfig?.gradient)}>
        <div className={cn('flex h-6 w-6 items-center justify-center rounded-md bg-muted border border-border/50', catConfig?.bgColor, catConfig?.borderColor)}>
          <span className="text-xs font-semibold text-muted-foreground">@</span>
        </div>
        <div className="flex-1 flex items-center gap-1.5 min-w-0">
          {mentionState.hasColon ? (
            <>
              <span className={cn('text-xs font-medium px-1.5 py-0.5 rounded-md', catConfig?.bgColor, catConfig?.color)}>{mentionState.category}</span>
              {mentionState.item && (<>
                <span className="text-muted-foreground">/</span>
                <span className="text-xs text-foreground font-medium truncate">{mentionState.item}</span>
              </>)}
            </>
          ) : (
            <span className="text-xs text-muted-foreground">Type to filter</span>
          )}
        </div>
        <div className="flex items-center gap-1 text-[10px] text-muted-foreground">
          <kbd className="px-1.5 py-0.5 rounded bg-muted border border-border/50 font-mono">↑↓</kbd>
          <span>navigate</span>
          <span className="text-muted-foreground/50">|</span>
          <kbd className="px-1.5 py-0.5 rounded bg-muted border border-border/50 font-mono">↵</kbd>
          <span>select</span>
        </div>
      </div>

      {/* Content */}
      {suggestions.length > 0 ? (
        <ScrollArea className="max-h-80">
          <div className="py-1">
            {suggestions.map((s, i) => s.type === 'category' ? (
              <CategoryHeader key={s.label + i} suggestion={s} isSelected={i === selectedIndex} />
            ) : (
              <div ref={el => { itemsRef.current[i] = el; }}>
                <SuggestionItem key={s.mentionId || s.label + i} suggestion={s} isSelected={i === selectedIndex}
                  onSelect={() => doInsert(s)}
                  onMouseEnter={() => { setSelectedIndex(i); selectedIndexRef.current = i; }}
                  searchTerm={mentionState.item} />
              </div>
            ))}
          </div>
        </ScrollArea>
      ) : isLoading ? <LoadingSkeleton /> : <EmptyState loading={isLoading} />}

      {/* Footer */}
      {suggestions[selectedIndex]?.type === 'item' && (
        <div className="flex items-center justify-between px-3 py-2 border-t border-border/50 bg-muted/30">
          <div className="flex items-center gap-2 text-[11px] text-muted-foreground">
            <span className={cn('px-1.5 py-0.5 rounded-md font-medium', catConfig?.bgColor, catConfig?.color)}>
              {suggestions[selectedIndex]?.type === 'item' ? suggestions[selectedIndex].category : ''}
            </span>
            <span className="truncate max-w-[140px]">{suggestions[selectedIndex]?.type === 'item' ? suggestions[selectedIndex].label : ''}</span>
          </div>
        </div>
      )}
    </div>
  );
}

/** Extract relevant CSS properties from computed styles for measurement clone */
function getComputedStyleStyles(styles: CSSStyleDeclaration): string {
  const props = ['fontFamily', 'fontSize', 'fontWeight', 'lineHeight', 'paddingLeft', 'paddingRight',
    'paddingTop', 'paddingBottom', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth', 'borderBottomWidth',
    'boxSizing', 'width', 'maxWidth', 'letterSpacing', 'tabSize'];
  return props.map(p => `${p.replace(/([A-Z])/g, '-$1').toLowerCase()}:${styles.getPropertyValue(p)}`).join(';');
}
