import React, {
  useEffect,
  useId,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  memo,
  useCallback,
} from "react";
import { createPortal } from "react-dom";
import {
  isFocusVisibleTarget,
  shouldHandleHoverPointer,
} from "../utils/pointer";

interface TooltipProps {
  text?: string;
  children: React.ReactElement<any>;
  enabled?: boolean;
  position?: "top";
  followAnchor?: boolean;
}

const Tooltip: React.FC<TooltipProps> = ({
  text,
  children,
  enabled = true,
  followAnchor = false,
}) => {
  const [visible, setVisible] = useState(false);
  const [pos, setPos] = useState<{ left: number; top: number } | null>(null);
  const anchorRef = useRef<HTMLElement | null>(null);
  const tooltipId = useId();
  const followRafRef = useRef<number | null>(null);

  const safeText = useMemo(() => (text ?? "").toString(), [text]);
  const active = enabled && safeText.trim().length > 0;

  if (!React.isValidElement(children)) {
    return <>{children}</>;
  }

  const updatePosition = useCallback(() => {
    const el = anchorRef.current;
    if (!el) return;

    const rect = el.getBoundingClientRect();
    setPos({ left: rect.left + rect.width / 2, top: rect.top });
  }, []);

  useLayoutEffect(() => {
    if (!active || !visible) return;
    updatePosition();

    const onUpdate = () => updatePosition();
    window.addEventListener("scroll", onUpdate, true);
    window.addEventListener("resize", onUpdate);
    return () => {
      window.removeEventListener("scroll", onUpdate, true);
      window.removeEventListener("resize", onUpdate);
    };
  }, [active, visible, safeText, updatePosition]);

  useEffect(() => {
    if (!followAnchor || !active || !visible) return;

    const tick = () => {
      updatePosition();
      followRafRef.current = requestAnimationFrame(tick);
    };

    followRafRef.current = requestAnimationFrame(tick);

    return () => {
      if (followRafRef.current) {
        cancelAnimationFrame(followRafRef.current);
        followRafRef.current = null;
      }
    };
  }, [followAnchor, active, visible, updatePosition]);

  const transform = useMemo(() => {
    return "translate(-50%, calc(-100% - 8px))";
  }, []);

  const background = "#1f2937";
  const color = "#ffffff";

  const arrowStyle = useMemo<React.CSSProperties>(() => {
    const base: React.CSSProperties = {
      position: "absolute",
      width: 0,
      height: 0,
      borderStyle: "solid",
      borderWidth: 6,
      borderColor: "transparent",
    };

    return {
      ...base,
      left: "50%",
      top: "100%",
      transform: "translateX(-50%)",
      borderTopColor: background,
    };
  }, []);

  const chain = useCallback(
    (a?: (...args: any[]) => void, b?: (...args: any[]) => void) => {
      return (...args: any[]) => {
        a?.(...args);
        b?.(...args);
      };
    },
    [],
  );

  const mergeRefs = useCallback(
    (
      a: React.Ref<HTMLElement> | undefined,
      b: (node: HTMLElement | null) => void,
    ) => {
      return (node: HTMLElement | null) => {
        if (typeof a === "function") {
          a(node);
        } else if (a && typeof a === "object" && "current" in (a as object)) {
          (a as React.MutableRefObject<HTMLElement | null>).current = node;
        }

        b(node);
      };
    },
    [],
  );

  const child = children as React.ReactElement<any> & {
    ref?: React.Ref<HTMLElement>;
  };
  const childProps = (child.props ?? {}) as Record<string, unknown>;
  const describedBy =
    (childProps["aria-describedby"] as string | undefined) ?? undefined;

  const clonedChild = React.cloneElement(child, {
    onPointerEnter: active
      ? chain(
          (child.props as any)?.onPointerEnter,
          (event: React.PointerEvent<HTMLElement>) => {
            if (shouldHandleHoverPointer(event.pointerType)) {
              setVisible(true);
            }
          },
        )
      : (child.props as any)?.onPointerEnter,
    onPointerLeave: active
      ? chain(
          (child.props as any)?.onPointerLeave,
          (event: React.PointerEvent<HTMLElement>) => {
            if (shouldHandleHoverPointer(event.pointerType)) {
              setVisible(false);
            }
          },
        )
      : (child.props as any)?.onPointerLeave,
    onPointerDown: active
      ? chain((child.props as any)?.onPointerDown, () => setVisible(false))
      : (child.props as any)?.onPointerDown,
    onFocus: active
      ? chain(
          (child.props as any)?.onFocus,
          (event: React.FocusEvent<HTMLElement>) => {
            setVisible(isFocusVisibleTarget(event.currentTarget));
          },
        )
      : (child.props as any)?.onFocus,
    onBlur: active
      ? chain((child.props as any)?.onBlur, () => setVisible(false))
      : (child.props as any)?.onBlur,
    ref: mergeRefs(child.ref, (node) => {
      anchorRef.current = node;
    }),
    "aria-describedby": active && visible ? tooltipId : describedBy,
  } as any);

  return (
    <>
      {clonedChild}
      {active && visible && pos && typeof document !== "undefined"
        ? createPortal(
            <div
              className="b3-wvs-pointer-events-none b3-wvs-fixed b3-wvs-left-0 b3-wvs-top-0 b3-wvs-z-50"
              style={{
                left: pos.left,
                top: pos.top,
                transform,
              }}
            >
              <div
                id={tooltipId}
                role="tooltip"
                className="b3-wvs-whitespace-nowrap b3-wvs-text-xs b3-wvs-rounded b3-wvs-px-2 b3-wvs-py-1 b3-wvs-tooltip"
                style={{
                  backgroundColor: background,
                  color,
                }}
              >
                {safeText}
                <div aria-hidden="true" style={arrowStyle} />
              </div>
            </div>,
            document.body,
          )
        : null}
    </>
  );
};

export default memo(Tooltip);
