import { type ComponentChildren, type FunctionalComponent } from 'preact';
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
import { createPortal } from 'preact/compat';
import { resolveOverlayPortalTarget } from '../../core/overlayMounts';

export type TooltipDirection = 'top' | 'right' | 'bottom' | 'left';

export interface TooltipProps {
  children: ComponentChildren;
  content?: ComponentChildren;
  direction?: TooltipDirection;
  offset?: number;
  delay?: number;
  disabled?: boolean;
  className?: string;
}

type TooltipPosition = {
  top: number;
  left: number;
};

const flattenText = (value: any): string => {
  if (value == null || typeof value === 'boolean') return '';
  if (typeof value === 'string' || typeof value === 'number') return String(value);
  if (Array.isArray(value)) return value.map(flattenText).join(' ').trim();
  if (value?.props?.children != null) return flattenText(value.props.children);
  return '';
};

const inferTooltipContent = (children: ComponentChildren, explicit?: ComponentChildren) => {
  if (explicit != null && explicit !== false) return explicit;
  const text = flattenText(children).replace(/\s+/g, ' ').trim();
  return text || null;
};

export const Tooltip: FunctionalComponent<TooltipProps> = ({
  children,
  content,
  direction = 'top',
  offset = 12,
  delay = 120,
  disabled = false,
  className,
}) => {
  const triggerRef = useRef<HTMLSpanElement | null>(null);
  const tooltipRef = useRef<HTMLDivElement | null>(null);
  const timeoutRef = useRef<number | null>(null);
  const [isOpen, setIsOpen] = useState(false);
  const [mounted, setMounted] = useState(false);
  const [position, setPosition] = useState<TooltipPosition>({ top: -9999, left: -9999 });

  const resolvedContent = useMemo(() => inferTooltipContent(children, content), [children, content]);
  const shouldRender = !disabled && resolvedContent != null;

  const clearScheduledOpen = () => {
    if (timeoutRef.current != null) {
      window.clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
  };

  const open = () => {
    if (!shouldRender) return;
    clearScheduledOpen();
    timeoutRef.current = window.setTimeout(() => {
      setMounted(true);
      setIsOpen(true);
    }, delay);
  };

  const close = () => {
    clearScheduledOpen();
    setIsOpen(false);
  };

  useEffect(() => () => clearScheduledOpen(), []);

  useEffect(() => {
    const root = triggerRef.current;
    if (!root || !shouldRender) return undefined;

    const titledNodes: HTMLElement[] = [];
    if (root.hasAttribute('title')) titledNodes.push(root);
    titledNodes.push(...Array.from(root.querySelectorAll<HTMLElement>('[title]')));

    titledNodes.forEach((node) => {
      if (node.dataset.askTooltipNativeTitle == null) {
        node.dataset.askTooltipNativeTitle = node.getAttribute('title') ?? '';
      }
      node.removeAttribute('title');
    });

    return () => {
      titledNodes.forEach((node) => {
        const originalTitle = node.dataset.askTooltipNativeTitle;
        if (originalTitle == null) return;
        if (originalTitle) node.setAttribute('title', originalTitle);
        delete node.dataset.askTooltipNativeTitle;
      });
    };
  }, [children, shouldRender]);

  useEffect(() => {
    if (!isOpen) {
      const id = window.setTimeout(() => setMounted(false), 120);
      return () => window.clearTimeout(id);
    }
    return undefined;
  }, [isOpen]);

  useLayoutEffect(() => {
    if (!mounted || !triggerRef.current || !tooltipRef.current) return;

    const updatePosition = () => {
      const triggerEl = triggerRef.current;
      const tooltipEl = tooltipRef.current;
      if (!triggerEl || !tooltipEl) return;

      const triggerRect = triggerEl.getBoundingClientRect();
      const tooltipRect = tooltipEl.getBoundingClientRect();
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;
      const margin = 10;

      let top = 0;
      let left = 0;

      switch (direction) {
        case 'bottom':
          top = triggerRect.bottom + offset;
          left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
          break;
        case 'left':
          top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;
          left = triggerRect.left - tooltipRect.width - offset;
          break;
        case 'right':
          top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;
          left = triggerRect.right + offset;
          break;
        case 'top':
        default:
          top = triggerRect.top - tooltipRect.height - offset;
          left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
          break;
      }

      top = Math.max(margin, Math.min(top, viewportHeight - tooltipRect.height - margin));
      left = Math.max(margin, Math.min(left, viewportWidth - tooltipRect.width - margin));
      setPosition({ top, left });
    };

    updatePosition();

    const opts = { passive: true } as AddEventListenerOptions;
    window.addEventListener('resize', updatePosition, opts);
    window.addEventListener('scroll', updatePosition, opts);
    return () => {
      window.removeEventListener('resize', updatePosition);
      window.removeEventListener('scroll', updatePosition);
    };
  }, [mounted, isOpen, direction, offset, resolvedContent]);

  const tooltipPortalTarget = resolveOverlayPortalTarget('tooltip');

  return (
    <>
      <span
        ref={triggerRef}
        class={['ask-tooltip-trigger', className ?? ''].filter(Boolean).join(' ')}
        onMouseEnter={open}
        onMouseLeave={close}
        onFocusIn={open}
        onFocusOut={close}
      >
        {children}
      </span>
      {mounted && shouldRender && typeof document !== 'undefined' && tooltipPortalTarget
        ? createPortal(
            <div
              ref={tooltipRef}
              class={[
                'ask-tooltip',
                `ask-tooltip--${direction}`,
                isOpen ? 'is-visible' : 'is-hidden',
              ].join(' ')}
              role="tooltip"
              style={{ top: `${position.top}px`, left: `${position.left}px` }}
              aria-hidden={!isOpen}
            >
              <div class="ask-tooltip__bubble">{resolvedContent}</div>
            </div>,
            tooltipPortalTarget,
          )
        : null}
    </>
  );
};

export default Tooltip;
