import type { RoomMemberResponse } from '@/client';
import { cn } from '@/lib/utils';
import { Check, CornerDownLeft, Keyboard, SearchX } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { SetStateAction } from 'react';
import { ModelIcon } from './icon-match';

interface MentionSuggestion {
  type: 'category' | 'item';
  category?: 'repository' | 'user' | 'ai';
  label: string;
  value: string;
  avatar?: string | null;
}

interface MentionState {
  startPos: number;
  category: string;
  item: string;
  hasColon: boolean;
}

interface PopoverPosition {
  top: number;
  left: number;
  placement: 'top' | 'bottom';
}

interface MentionPopoverProps {
  /** Available members for user/ai mention suggestions */
  members: RoomMemberResponse[];
  /** Repository names available for @repository: mention */
  repos?: string[];
  inputValue: string;
  cursorPosition: number;
  onSelect: (newValue: string, newCursorPosition: number) => void;
  textareaRef: React.RefObject<HTMLTextAreaElement | null>;
  onOpenChange: (open: boolean) => void;
}

export function MentionPopover({
  members,
  repos = [],
  inputValue,
  cursorPosition,
  onSelect,
  textareaRef,
  onOpenChange,
}: MentionPopoverProps) {
  const [position, setPosition] = useState<PopoverPosition>({ top: 0, left: 0, placement: 'top' });
  const popoverRef = useRef<HTMLDivElement>(null);

  const mentionState = useMemo<MentionState | null>(() => {
    const textBeforeCursor = inputValue.slice(0, cursorPosition);
    const atMatch = textBeforeCursor.match(/@([^:@\s]*)(:([^\s]*))?$/);

    if (!atMatch) return null;

    const [fullMatch, category, , item] = atMatch;
    const startPos = cursorPosition - fullMatch.length;

    return {
      startPos,
      category: category.toLowerCase(),
      item: item?.toLowerCase() ?? '',
      hasColon: fullMatch.includes(':'),
    };
  }, [inputValue, cursorPosition]);

  const mentionKey = useMemo(() => {
    if (!mentionState) return '';
    return [mentionState.startPos, mentionState.category, mentionState.item, mentionState.hasColon].join(':');
  }, [mentionState]);

  const [selectionState, setSelectionState] = useState<{ key: string; index: number }>({
    key: '',
    index: 0,
  });
  const selectedIndex = selectionState.key === mentionKey ? selectionState.index : 0;
  const setSelectedIndex = useCallback(
    (action: SetStateAction<number>) => {
      setSelectionState((previous) => {
        const baseIndex = previous.key === mentionKey ? previous.index : 0;
        const nextIndex = typeof action === 'function' ? action(baseIndex) : action;
        return { key: mentionKey, index: nextIndex };
      });
    },
    [mentionKey],
  );

  const suggestions = useMemo((): MentionSuggestion[] => {
    if (!mentionState) return [];

    const { category, item, hasColon } = mentionState;
    const categories: MentionSuggestion[] = [
      { type: 'category', category: 'repository', label: 'Repository', value: '@repository:' },
      { type: 'category', category: 'user', label: 'User', value: '@user:' },
      { type: 'category', category: 'ai', label: 'AI', value: '@ai:' },
    ];

    if (!hasColon) {
      if (!category) return categories;
      return categories.filter((c) => c.category?.toLowerCase().startsWith(category));
    }

    if (!['repository', 'user', 'ai'].includes(category)) {
      return categories.filter((c) => c.category?.toLowerCase().startsWith(category));
    }

    if (category === 'repository') {
      return repos
        .filter((repo) => !item || repo.toLowerCase().includes(item))
        .map((repo) => ({
          type: 'item' as const,
          category: 'repository' as const,
          label: repo,
          value: `@repository:${repo}`,
          avatar: null,
        }));
    }

    if (category === 'user') {
      return members
        .filter((m) => m.role !== 'ai')
        .filter((m) => {
          const username = m.user_info?.username ?? m.user;
          return !item || username.toLowerCase().includes(item);
        })
        .map((m) => {
          const username = m.user_info?.username ?? m.user;
          return {
            type: 'item' as const,
            category: 'user' as const,
            label: username,
            value: `@user:${m.user}`,
            avatar: m.user_info?.avatar_url ?? null,
          };
        });
    }

    if (category === 'ai') {
      return members
        .filter((m) => m.role === 'ai')
        .filter((m) => {
          const username = m.user_info?.username ?? m.user;
          return !item || username.toLowerCase().includes(item);
        })
        .map((m) => {
          const username = m.user_info?.username ?? m.user;
          return {
            type: 'item' as const,
            category: 'ai' as const,
            label: username,
            value: `@ai:${m.user}`,
            avatar: m.user_info?.avatar_url ?? null,
          };
        });
    }

    return [];
  }, [mentionState, members, repos]);

  const visibleSuggestions = useMemo(() => suggestions.slice(0, 8), [suggestions]);
  const validSelectedIndex = selectedIndex >= visibleSuggestions.length ? 0 : selectedIndex;

  const closePopover = useCallback(() => {
    onOpenChange(false);
  }, [onOpenChange]);

  const handleSelect = useMemo(() => {
    return (suggestion: MentionSuggestion) => {
      if (!mentionState) return;

      const before = inputValue.slice(0, mentionState.startPos);
      const after = inputValue.slice(cursorPosition);
      const spacer = suggestion.type === 'item' ? ' ' : '';
      const newValue = before + suggestion.value + spacer + after;
      const newCursorPos = mentionState.startPos + suggestion.value.length + spacer.length;

      onSelect(newValue, newCursorPos);

      if (suggestion.type === 'item') {
        closePopover();
      } else {
        onOpenChange(true);
      }
    };
  }, [mentionState, inputValue, cursorPosition, onSelect, closePopover, onOpenChange]);

  // Refs for keyboard navigation to avoid stale closure
  const visibleSuggestionsRef = useRef(visibleSuggestions);
  const validSelectedIndexRef = useRef(validSelectedIndex);
  const handleSelectRef = useRef(handleSelect);
  const closePopoverRef = useRef(closePopover);
  visibleSuggestionsRef.current = visibleSuggestions;
  validSelectedIndexRef.current = validSelectedIndex;
  handleSelectRef.current = handleSelect;
  closePopoverRef.current = closePopover;

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

    const handleKeyDown = (e: KeyboardEvent) => {
      if (!mentionState) return;
      const currentVisible = visibleSuggestionsRef.current;
      const currentSelected = validSelectedIndexRef.current;

      if (e.key === 'ArrowDown') {
        e.preventDefault();
        if (currentVisible.length > 0) {
          setSelectedIndex((prev) => (prev + 1) % currentVisible.length);
        }
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        if (currentVisible.length > 0) {
          setSelectedIndex((prev) => (prev - 1 + currentVisible.length) % currentVisible.length);
        }
      } else if (e.key === 'Enter' || e.key === 'Tab') {
        if (currentVisible[currentSelected]) {
          e.preventDefault();
          handleSelectRef.current(currentVisible[currentSelected]);
        }
      } else if (e.key === 'Escape') {
        e.preventDefault();
        closePopoverRef.current();
      }
    };

    textarea.addEventListener('keydown', handleKeyDown);
    return () => textarea.removeEventListener('keydown', handleKeyDown);
  }, [mentionState, setSelectedIndex, textareaRef]);

  useEffect(() => {
    const handleOutsideClick = (e: MouseEvent) => {
      const target = e.target as Node;
      if (popoverRef.current?.contains(target)) return;
      if (textareaRef.current?.contains(target)) return;
      closePopover();
    };

    document.addEventListener('mousedown', handleOutsideClick);
    return () => document.removeEventListener('mousedown', handleOutsideClick);
  }, [closePopover, textareaRef]);

  useEffect(() => {
    if (!mentionState) closePopover();
  }, [mentionState, closePopover]);

  useEffect(() => {
    if (!textareaRef.current || !mentionState) return;

    const textarea = textareaRef.current;
    const caretPos = cursorPosition;
    const tempDiv = document.createElement('div');
    const styles = window.getComputedStyle(textarea);

    const styleProps = [
      'font-family', 'font-size', 'font-weight', 'font-style', 'line-height',
      'letter-spacing', 'text-transform', 'word-spacing', 'text-indent', 'white-space',
      'word-wrap', 'word-break', 'overflow-wrap', 'padding-left', 'padding-right',
      'padding-top', 'padding-bottom', 'border-left-width', 'border-right-width',
      'border-top-width', 'border-bottom-width', 'box-sizing',
    ];

    tempDiv.style.position = 'absolute';
    tempDiv.style.visibility = 'hidden';
    tempDiv.style.top = '-9999px';
    tempDiv.style.left = '-9999px';
    tempDiv.style.width = styles.width;

    styleProps.forEach((prop) => {
      tempDiv.style.setProperty(prop, styles.getPropertyValue(prop));
    });

    document.body.appendChild(tempDiv);
    tempDiv.textContent = inputValue.slice(0, caretPos);

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

    const spanLeft = span.offsetLeft;
    const spanTop = span.offsetTop;
    const rect = textarea.getBoundingClientRect();
    const borderLeft = Number.parseFloat(styles.borderLeftWidth) || 0;
    const borderTop = Number.parseFloat(styles.borderTopWidth) || 0;
    const scrollTop = textarea.scrollTop;
    const scrollLeft = textarea.scrollLeft;

    const lineHeight = Number.parseFloat(styles.lineHeight) || 20;
    const contentTop = rect.top + borderTop + spanTop - scrollTop;
    const contentLeft = rect.left + borderLeft + spanLeft - scrollLeft;
    const popoverWidth = 288;
    const popoverHeight = Math.min(360, Math.max(72, visibleSuggestions.length * 40 + 40));
    const viewportPadding = 8;

    const left = Math.min(
      Math.max(contentLeft, viewportPadding),
      window.innerWidth - popoverWidth - viewportPadding,
    );

    const bottomTop = contentTop + lineHeight + 8;
    const shouldPlaceBottom = bottomTop + popoverHeight < window.innerHeight - viewportPadding;
    const top = shouldPlaceBottom ? bottomTop : Math.max(viewportPadding, contentTop - popoverHeight - 8);

    document.body.removeChild(tempDiv);

    setPosition({ top, left, placement: shouldPlaceBottom ? 'bottom' : 'top' });
  }, [mentionState, inputValue, textareaRef, cursorPosition, visibleSuggestions.length]);

  if (!mentionState) return null;

  return (
    <div
      ref={popoverRef}
      className="fixed z-50 w-72 overflow-hidden rounded-xl border border-border/90 bg-popover shadow-2xl"
      style={{ top: position.top, left: position.left }}
    >
      <div className="flex items-center justify-between border-b border-border/70 px-3 py-2 text-xs text-muted-foreground">
        <span className="font-medium">Mention Suggestions</span>
        <span className="inline-flex items-center gap-1">
          <Keyboard className="h-3.5 w-3.5" />↑↓
        </span>
      </div>

      {visibleSuggestions.length > 0 ? (
        <div className="max-h-72 overflow-y-auto p-1.5">
          {visibleSuggestions.map((suggestion, index) => (
            <div
              key={suggestion.value}
              className={cn(
                'flex items-center gap-2 rounded-md px-3 py-2 transition-colors',
                index === validSelectedIndex
                  ? 'bg-accent text-accent-foreground'
                  : 'cursor-pointer hover:bg-accent/50',
              )}
              onMouseDown={(e) => e.preventDefault()}
              onClick={() => handleSelect(suggestion)}
              onMouseEnter={() => setSelectedIndex(index)}
            >
              <div
                className={cn(
                  'flex h-6 w-6 items-center justify-center rounded-full text-xs font-semibold',
                  suggestion.category === 'repository' && 'bg-purple-500/10 text-purple-600',
                  suggestion.category === 'user' && 'bg-blue-500/10 text-blue-600',
                  suggestion.category === 'ai' && 'bg-green-500/10 text-green-600',
                  suggestion.type === 'category' && 'bg-muted text-muted-foreground',
                )}
              >
                {suggestion.type === 'category' ? (
                  '>'
                ) : suggestion.category === 'ai' ? (
                  <ModelIcon modelId={suggestion.value.replace(/^@ai:/, '')} className="h-4 w-4" />
                ) : (
                  suggestion.label[0]?.toUpperCase()
                )}
              </div>
              <span className="flex-1 truncate text-sm">{suggestion.label}</span>
              {index === validSelectedIndex && <Check className="h-4 w-4 text-muted-foreground" />}
            </div>
          ))}
        </div>
      ) : (
        <div className="flex items-center gap-2 px-3 py-4 text-sm text-muted-foreground">
          <SearchX className="h-4 w-4" />
          No matching mentions
        </div>
      )}

      <div className="flex items-center justify-end gap-3 border-t border-border/70 px-3 py-2 text-[11px] text-muted-foreground">
        <span className="inline-flex items-center gap-1">
          <CornerDownLeft className="h-3 w-3" />
          Enter
        </span>
        <span className="inline-flex items-center gap-1">Tab</span>
        <span>Esc</span>
      </div>
    </div>
  );
}
