import React, { forwardRef, useState, useRef, useEffect, cloneElement, HTMLAttributes } from 'react';
import { clsx } from 'clsx';

export interface TooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
  content: React.ReactNode;
  placement?: 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end';
  trigger?: 'hover' | 'click' | 'focus' | 'manual';
  delay?: number;
  hideDelay?: number;
  disabled?: boolean;
  arrow?: boolean;
  size?: 'sm' | 'md' | 'lg';
  variant?: 'default' | 'dark' | 'light' | 'error' | 'warning' | 'success' | 'info';
  maxWidth?: string | number;
  offset?: number;
  zIndex?: number;
  portalId?: string;
  followCursor?: boolean;
  interactive?: boolean;
  onVisibleChange?: (visible: boolean) => void;
}

const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(({
  content,
  placement = 'top',
  trigger = 'hover',
  delay = 100,
  hideDelay = 100,
  disabled = false,
  arrow = true,
  size = 'md',
  variant = 'default',
  maxWidth = 320,
  offset = 8,
  zIndex = 1000,
  followCursor = false,
  interactive = false,
  onVisibleChange,
  className,
  children,
  ...props
}, ref) => {
  const [isVisible, setIsVisible] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const [actualPlacement, setActualPlacement] = useState(placement);
  
  const triggerRef = useRef<HTMLElement>(null);
  const tooltipRef = useRef<HTMLDivElement>(null);
  const showTimer = useRef<NodeJS.Timeout>();
  const hideTimer = useRef<NodeJS.Timeout>();

  // Clear timers on unmount
  useEffect(() => {
    return () => {
      if (showTimer.current) clearTimeout(showTimer.current);
      if (hideTimer.current) clearTimeout(hideTimer.current);
    };
  }, []);

  // Calculate tooltip position
  const calculatePosition = (cursorX?: number, cursorY?: number) => {
    if (!triggerRef.current || !tooltipRef.current) return;

    const triggerRect = triggerRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();
    const viewport = {
      width: window.innerWidth,
      height: window.innerHeight,
    };

    let top = 0;
    let left = 0;
    let finalPlacement = placement;

    // Use cursor position if followCursor is enabled
    if (followCursor && cursorX !== undefined && cursorY !== undefined) {
      top = cursorY + offset;
      left = cursorX + offset;
    } else {
      // Calculate position based on placement
      switch (placement) {
        case 'top':
        case 'top-start':
        case 'top-end':
          top = triggerRect.top - tooltipRect.height - offset;
          left = placement === 'top-start' 
            ? triggerRect.left
            : placement === 'top-end'
            ? triggerRect.right - tooltipRect.width
            : triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
          break;
        case 'bottom':
        case 'bottom-start':
        case 'bottom-end':
          top = triggerRect.bottom + offset;
          left = placement === 'bottom-start'
            ? triggerRect.left
            : placement === 'bottom-end'
            ? triggerRect.right - tooltipRect.width
            : triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
          break;
        case 'left':
        case 'left-start':
        case 'left-end':
          top = placement === 'left-start'
            ? triggerRect.top
            : placement === 'left-end'
            ? triggerRect.bottom - tooltipRect.height
            : triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
          left = triggerRect.left - tooltipRect.width - offset;
          break;
        case 'right':
        case 'right-start':
        case 'right-end':
          top = placement === 'right-start'
            ? triggerRect.top
            : placement === 'right-end'
            ? triggerRect.bottom - tooltipRect.height
            : triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
          left = triggerRect.right + offset;
          break;
      }
    }

    // Flip if tooltip would go outside viewport
    const willOverflowTop = top < 0;
    const willOverflowBottom = top + tooltipRect.height > viewport.height;
    const willOverflowLeft = left < 0;
    const willOverflowRight = left + tooltipRect.width > viewport.width;

    // Adjust for viewport constraints
    if (willOverflowTop && (placement.startsWith('top') || placement.includes('left') || placement.includes('right'))) {
      if (placement.startsWith('top')) {
        finalPlacement = placement.replace('top', 'bottom') as typeof placement;
        top = triggerRect.bottom + offset;
      } else {
        top = Math.max(8, top);
      }
    }

    if (willOverflowBottom && placement.startsWith('bottom')) {
      finalPlacement = placement.replace('bottom', 'top') as typeof placement;
      top = triggerRect.top - tooltipRect.height - offset;
    }

    if (willOverflowLeft) {
      if (placement.startsWith('left')) {
        finalPlacement = placement.replace('left', 'right') as typeof placement;
        left = triggerRect.right + offset;
      } else {
        left = Math.max(8, left);
      }
    }

    if (willOverflowRight) {
      if (placement.startsWith('right')) {
        finalPlacement = placement.replace('right', 'left') as typeof placement;
        left = triggerRect.left - tooltipRect.width - offset;
      } else {
        left = Math.min(viewport.width - tooltipRect.width - 8, left);
      }
    }

    setPosition({ top, left });
    setActualPlacement(finalPlacement);
  };

  const showTooltip = (cursorX?: number, cursorY?: number) => {
    if (disabled || !content) return;

    if (hideTimer.current) {
      clearTimeout(hideTimer.current);
      hideTimer.current = undefined;
    }

    if (!isVisible) {
      showTimer.current = setTimeout(() => {
        setIsVisible(true);
        onVisibleChange?.(true);
        // Calculate position after showing to get accurate measurements
        setTimeout(() => calculatePosition(cursorX, cursorY), 0);
      }, delay);
    }
  };

  const hideTooltip = () => {
    if (showTimer.current) {
      clearTimeout(showTimer.current);
      showTimer.current = undefined;
    }

    if (isVisible) {
      hideTimer.current = setTimeout(() => {
        setIsVisible(false);
        onVisibleChange?.(false);
      }, hideDelay);
    }
  };

  const handleMouseEnter = (e: React.MouseEvent) => {
    if (trigger === 'hover') {
      showTooltip(followCursor ? e.clientX : undefined, followCursor ? e.clientY : undefined);
    }
  };

  const handleMouseLeave = () => {
    if (trigger === 'hover') {
      hideTooltip();
    }
  };

  const handleMouseMove = (e: React.MouseEvent) => {
    if (trigger === 'hover' && followCursor && isVisible) {
      calculatePosition(e.clientX, e.clientY);
    }
  };

  const handleClick = () => {
    if (trigger === 'click') {
      if (isVisible) {
        hideTooltip();
      } else {
        showTooltip();
      }
    }
  };

  const handleFocus = () => {
    if (trigger === 'focus') {
      showTooltip();
    }
  };

  const handleBlur = () => {
    if (trigger === 'focus') {
      hideTooltip();
    }
  };

  const handleTooltipMouseEnter = () => {
    if (interactive && trigger === 'hover') {
      if (hideTimer.current) {
        clearTimeout(hideTimer.current);
        hideTimer.current = undefined;
      }
    }
  };

  const handleTooltipMouseLeave = () => {
    if (interactive && trigger === 'hover') {
      hideTooltip();
    }
  };

  // Recalculate position on window resize
  useEffect(() => {
    const handleResize = () => {
      if (isVisible) {
        calculatePosition();
      }
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [isVisible]);

  // Size classes
  const sizeClasses = {
    sm: 'px-2 py-1 text-xs',
    md: 'px-3 py-2 text-sm',
    lg: 'px-4 py-3 text-base',
  };

  // Variant classes
  const variantClasses = {
    default: 'bg-text-primary text-text-inverse',
    dark: 'bg-gray-900 text-white',
    light: 'bg-white text-text-primary border border-border-primary shadow-lg',
    error: 'bg-color-error text-text-inverse',
    warning: 'bg-color-warning text-text-inverse',
    success: 'bg-color-success text-text-inverse',
    info: 'bg-color-info text-text-inverse',
  };

  // Arrow classes based on placement
  const getArrowClasses = () => {
    const baseArrow = 'absolute w-2 h-2 rotate-45';
    const arrowColor = variant === 'light' 
      ? 'bg-white border border-border-primary' 
      : variantClasses[variant].split(' ')[0];
    
    switch (actualPlacement.split('-')[0]) {
      case 'top':
        return `${baseArrow} ${arrowColor} -bottom-1 left-1/2 -translate-x-1/2`;
      case 'bottom':
        return `${baseArrow} ${arrowColor} -top-1 left-1/2 -translate-x-1/2`;
      case 'left':
        return `${baseArrow} ${arrowColor} -right-1 top-1/2 -translate-y-1/2`;
      case 'right':
        return `${baseArrow} ${arrowColor} -left-1 top-1/2 -translate-y-1/2`;
      default:
        return `${baseArrow} ${arrowColor} -bottom-1 left-1/2 -translate-x-1/2`;
    }
  };

  const tooltipClasses = clsx(
    'absolute rounded-lg font-medium leading-tight',
    'transition-all duration-200 ease-out',
    'pointer-events-auto',
    sizeClasses[size],
    variantClasses[variant],
    {
      'opacity-0 scale-95 pointer-events-none': !isVisible,
      'opacity-100 scale-100': isVisible,
    },
    className
  );

  const tooltipStyle: React.CSSProperties = {
    top: position.top,
    left: position.left,
    maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth,
    zIndex,
    ...props.style,
  };

  // Clone the trigger element and add event handlers
  const triggerElement = React.isValidElement(children) 
    ? cloneElement(children as React.ReactElement, {
        ref: (node: HTMLElement) => {
          triggerRef.current = node;
          // Preserve original ref if it exists
          const originalRef = (children as any).ref;
          if (typeof originalRef === 'function') {
            originalRef(node);
          } else if (originalRef) {
            originalRef.current = node;
          }
        },
        onMouseEnter: (e: React.MouseEvent) => {
          handleMouseEnter(e);
          // Call original handler if it exists
          (children as any).props?.onMouseEnter?.(e);
        },
        onMouseLeave: (e: React.MouseEvent) => {
          handleMouseLeave();
          (children as any).props?.onMouseLeave?.(e);
        },
        onMouseMove: (e: React.MouseEvent) => {
          handleMouseMove(e);
          (children as any).props?.onMouseMove?.(e);
        },
        onClick: (e: React.MouseEvent) => {
          handleClick();
          (children as any).props?.onClick?.(e);
        },
        onFocus: (e: React.FocusEvent) => {
          handleFocus();
          (children as any).props?.onFocus?.(e);
        },
        onBlur: (e: React.FocusEvent) => {
          handleBlur();
          (children as any).props?.onBlur?.(e);
        },
      })
    : children;

  return (
    <>
      {triggerElement}
      {content && (
        <div
          ref={ref || tooltipRef}
          className={tooltipClasses}
          style={tooltipStyle}
          role="tooltip"
          onMouseEnter={handleTooltipMouseEnter}
          onMouseLeave={handleTooltipMouseLeave}
          {...props}
        >
          {content}
          {arrow && <div className={getArrowClasses()} />}
        </div>
      )}
    </>
  );
});

Tooltip.displayName = 'Tooltip';

export default Tooltip;