import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import type { ReactNode } from "react";
import { createPortal } from "react-dom";
import { useLift } from "./LiftContext";

type LiftableProps = {
  id?: string;
  always?: boolean;
};

type Props = LiftableProps & {
  children: ReactNode;
};

export function useLiftable<T extends Record<string, unknown> = Record<string, never>>(
  render: (props: { isLifted: boolean } & T) => ReactNode,
  options?: LiftableProps & T,
): { id: string; element: ReactNode } {
  const autoId = useId();
  const {
    id: idProp,
    always,
    ...extraProps
  } = (options ?? {}) as LiftableProps & Record<string, unknown>;
  const id = idProp ?? autoId;
  const { liftedIds, alwaysLiftedIds, registerAlways, unregisterAlways } = useLift();

  useEffect(() => {
    if (always) {
      registerAlways(id);
      return () => unregisterAlways(id);
    }
  }, [always, id, registerAlways, unregisterAlways]);

  const isLifted = liftedIds.has(id) || alwaysLiftedIds.has(id);

  const element = (
    <Liftable id={id}>{render({ isLifted, ...extraProps } as { isLifted: boolean } & T)}</Liftable>
  );

  return { id, element };
}

export function Liftable({ id: idProp, always, children }: Props) {
  const autoId = useId();
  const id = idProp ?? autoId;
  const { liftedIds, alwaysLiftedIds, registerAlways, unregisterAlways, portalContainer } =
    useLift();
  const wrapperRef = useRef<HTMLDivElement>(null);
  const [rect, setRect] = useState<DOMRect | null>(null);

  useEffect(() => {
    if (always) {
      registerAlways(id);
      return () => unregisterAlways(id);
    }
  }, [always, id, registerAlways, unregisterAlways]);

  const isLifted = liftedIds.has(id) || alwaysLiftedIds.has(id);

  useLayoutEffect(() => {
    if (isLifted && wrapperRef.current) {
      setRect(wrapperRef.current.getBoundingClientRect());
    }
    if (!isLifted) {
      setRect(null);
    }
  }, [isLifted]);

  // Re-measure on resize while lifted
  useEffect(() => {
    if (!isLifted || !wrapperRef.current) return;

    const measure = () => {
      if (wrapperRef.current) {
        setRect(wrapperRef.current.getBoundingClientRect());
      }
    };

    const observer = new ResizeObserver(measure);
    observer.observe(wrapperRef.current);
    window.addEventListener("resize", measure);

    return () => {
      observer.disconnect();
      window.removeEventListener("resize", measure);
    };
  }, [isLifted]);

  // When lifted and we have measurements + portal target, render in portal
  if (isLifted && rect && portalContainer) {
    return (
      <>
        {/* Placeholder preserves layout space */}
        <div
          ref={wrapperRef}
          style={{ width: rect.width, height: rect.height, visibility: "hidden" }}
        />
        {/* Portal children above blur */}
        {createPortal(
          <div
            style={{
              position: "fixed",
              top: rect.top,
              left: rect.left,
              width: rect.width,
              height: rect.height,
              pointerEvents: "auto",
            }}
          >
            {children}
          </div>,
          portalContainer,
        )}
      </>
    );
  }

  // Normal inline rendering
  return <div ref={wrapperRef}>{children}</div>;
}
