import { useState, useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { cn } from '@/lib/utils';
import type { HoverCardProps, HoverCardPosition } from './types';

/**
 * Calculate the position of the hover card relative to the trigger element.
 * Flips to the opposite side when there isn't enough viewport space.
 */
function calculateCardPosition(
  triggerRect: DOMRect,
  cardRect: DOMRect,
  preferred: HoverCardPosition,
  gap = 12,
): { top: number; left: number; actual: HoverCardPosition } {
  const viewport = {
    width: window.innerWidth,
    height: window.innerHeight,
  };

  let actual = preferred;

  // Check if preferred position has enough space, otherwise flip
  switch (preferred) {
    case 'top':
      if (triggerRect.top - cardRect.height - gap < 0) actual = 'bottom';
      break;
    case 'bottom':
      if (triggerRect.bottom + cardRect.height + gap > viewport.height) actual = 'top';
      break;
    case 'left':
      if (triggerRect.left - cardRect.width - gap < 0) actual = 'right';
      break;
    case 'right':
      if (triggerRect.right + cardRect.width + gap > viewport.width) actual = 'left';
      break;
  }

  let top = 0;
  let left = 0;
  const padding = 8;

  switch (actual) {
    case 'top':
      top = triggerRect.top - cardRect.height - gap;
      left = triggerRect.left + triggerRect.width / 2 - cardRect.width / 2;
      break;
    case 'bottom':
      top = triggerRect.bottom + gap;
      left = triggerRect.left + triggerRect.width / 2 - cardRect.width / 2;
      break;
    case 'left':
      top = triggerRect.top + triggerRect.height / 2 - cardRect.height / 2;
      left = triggerRect.left - cardRect.width - gap;
      break;
    case 'right':
      top = triggerRect.top + triggerRect.height / 2 - cardRect.height / 2;
      left = triggerRect.right + gap;
      break;
  }

  // Clamp within viewport
  left = Math.max(padding, Math.min(left, viewport.width - cardRect.width - padding));
  top = Math.max(padding, Math.min(top, viewport.height - cardRect.height - padding));

  return { top, left, actual };
}

/**
 * Get the initial y-offset for the entrance animation based on position.
 */
function getInitialY(position: HoverCardPosition): number {
  switch (position) {
    case 'top':
      return 4;
    case 'bottom':
      return -4;
    default:
      return 0;
  }
}

/**
 * HoverCard - Discord-style rich preview card on hover
 *
 * Shows a rich content card when hovering over a trigger element.
 * Uses a portal for rendering and framer-motion for smooth animations.
 *
 * @example
 * ```tsx
 * <HoverCard
 *   content={<UserHoverContent name="John" username="johndoe" />}
 * >
 *   <span className="text-primary cursor-pointer">@johndoe</span>
 * </HoverCard>
 * ```
 */
export function HoverCard({
  content,
  children,
  position = 'bottom',
  openDelay = 400,
  closeDelay = 200,
  disabled = false,
  className,
  maxWidth,
}: HoverCardProps) {
  const [visible, setVisible] = useState(false);
  const [cardStyle, setCardStyle] = useState<React.CSSProperties>({});
  const [actualPosition, setActualPosition] = useState<HoverCardPosition>(position);

  const triggerRef = useRef<HTMLDivElement>(null);
  const cardRef = useRef<HTMLDivElement>(null);
  const openTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const clearTimers = useCallback(() => {
    if (openTimerRef.current) {
      clearTimeout(openTimerRef.current);
      openTimerRef.current = null;
    }
    if (closeTimerRef.current) {
      clearTimeout(closeTimerRef.current);
      closeTimerRef.current = null;
    }
  }, []);

  const open = useCallback(() => {
    if (closeTimerRef.current) {
      clearTimeout(closeTimerRef.current);
      closeTimerRef.current = null;
    }
    if (openTimerRef.current) return;

    openTimerRef.current = setTimeout(() => {
      setVisible(true);
      openTimerRef.current = null;
    }, openDelay);
  }, [openDelay]);

  const close = useCallback(() => {
    if (openTimerRef.current) {
      clearTimeout(openTimerRef.current);
      openTimerRef.current = null;
    }
    if (closeTimerRef.current) return;

    closeTimerRef.current = setTimeout(() => {
      setVisible(false);
      closeTimerRef.current = null;
    }, closeDelay);
  }, [closeDelay]);

  // Recalculate position when visible
  useEffect(() => {
    if (!visible || !triggerRef.current) return;

    const updatePosition = () => {
      if (!triggerRef.current || !cardRef.current) return;

      const triggerRect = triggerRef.current.getBoundingClientRect();
      const cardRect = cardRef.current.getBoundingClientRect();
      const result = calculateCardPosition(triggerRect, cardRect, position);

      setActualPosition(result.actual);
      setCardStyle({
        top: result.top,
        left: result.left,
      });
    };

    // Position on next frame after mount
    requestAnimationFrame(updatePosition);
  }, [visible, position]);

  // Close on scroll or resize
  useEffect(() => {
    if (!visible) return;

    const handleDismiss = () => {
      clearTimers();
      setVisible(false);
    };

    window.addEventListener('scroll', handleDismiss, true);
    window.addEventListener('resize', handleDismiss);

    return () => {
      window.removeEventListener('scroll', handleDismiss, true);
      window.removeEventListener('resize', handleDismiss);
    };
  }, [visible, clearTimers]);

  // Close on click outside
  useEffect(() => {
    if (!visible) return;

    const handleClickOutside = (e: MouseEvent) => {
      const target = e.target as Node;
      if (
        triggerRef.current &&
        !triggerRef.current.contains(target) &&
        cardRef.current &&
        !cardRef.current.contains(target)
      ) {
        clearTimers();
        setVisible(false);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [visible, clearTimers]);

  // Cleanup timers on unmount
  useEffect(() => {
    return clearTimers;
  }, [clearTimers]);

  if (disabled) {
    return <>{children}</>;
  }

  return (
    <>
      <div
        ref={triggerRef}
        className="inline-block"
        onMouseEnter={open}
        onMouseLeave={close}
      >
        {children}
      </div>

      {createPortal(
        <AnimatePresence>
          {visible && (
            <motion.div
              ref={cardRef}
              role="dialog"
              className={cn(
                'fixed z-50 p-4 max-w-xs',
                'bg-popover border border-border rounded-xl shadow-lg backdrop-blur-md',
                className,
              )}
              style={{
                ...cardStyle,
                ...(maxWidth != null && { maxWidth: `${maxWidth}px` }),
              }}
              initial={{ opacity: 0, scale: 0.96, y: getInitialY(actualPosition) }}
              animate={{ opacity: 1, scale: 1, y: 0 }}
              exit={{ opacity: 0, scale: 0.96 }}
              transition={{ duration: 0.15, ease: 'easeOut' }}
              onMouseEnter={open}
              onMouseLeave={close}
            >
              {content}
            </motion.div>
          )}
        </AnimatePresence>,
        document.body,
      )}
    </>
  );
}
