import {
  useEffect,
  useId,
  useRef,
  useState,
  type HTMLAttributes,
  type PointerEvent,
  type ReactNode,
} from "react";
import { createPortal } from "react-dom";
import { InspectorSelectPolicyContext } from "./InspectorContext.js";
import { cn } from "./cn.js";
import { Icon } from "./Icon.js";

const focusableSelector = [
  "a[href]",
  "button:not([disabled])",
  "input:not([disabled])",
  "select:not([disabled])",
  "textarea:not([disabled])",
  "[tabindex]:not([tabindex='-1'])",
].join(",");

export type WindowSize = "sm" | "md" | "lg";
export type WindowPlacement = "center" | "end";

export interface WindowProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
  open: boolean;
  title: ReactNode;
  subtitle?: ReactNode;
  children: ReactNode;
  footer?: ReactNode;
  size?: WindowSize;
  placement?: WindowPlacement;
  closeLabel?: string;
  closeOnBackdrop?: boolean;
  closeOnEscape?: boolean;
  lockBodyScroll?: boolean;
  trapFocus?: boolean;
  draggable?: boolean;
  onClose: () => void;
}

export function Window({
  open,
  title,
  subtitle,
  children,
  footer,
  size = "md",
  placement = "center",
  closeLabel = "Закрыть",
  closeOnBackdrop,
  closeOnEscape = true,
  lockBodyScroll,
  trapFocus,
  draggable = false,
  onClose,
  className,
  style,
  ...props
}: WindowProps) {
  const titleId = useId();
  const descriptionId = useId();
  const dialogRef = useRef<HTMLDivElement>(null);
  const onCloseRef = useRef(onClose);
  const dragRef = useRef<{ pointerId: number; offsetX: number; offsetY: number } | null>(null);
  const [dragPosition, setDragPosition] = useState<{ left: number; top: number } | null>(null);
  const shouldLockBodyScroll = lockBodyScroll ?? placement === "center";
  const shouldTrapFocus = trapFocus ?? placement === "center";
  onCloseRef.current = onClose;

  useEffect(() => {
    if (!open || typeof document === "undefined") return;
    const previousActiveElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    const previousOverflow = document.body.style.overflow;
    if (shouldLockBodyScroll) document.body.style.overflow = "hidden";

    const frame = window.requestAnimationFrame(() => {
      const firstFocusable = dialogRef.current?.querySelector<HTMLElement>(focusableSelector);
      (firstFocusable ?? dialogRef.current)?.focus();
    });

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape" && closeOnEscape) {
        event.preventDefault();
        onCloseRef.current();
        return;
      }
      if (event.key !== "Tab" || !shouldTrapFocus || !dialogRef.current) return;
      const focusable = Array.from(dialogRef.current.querySelectorAll<HTMLElement>(focusableSelector));
      if (focusable.length === 0) {
        event.preventDefault();
        dialogRef.current.focus();
        return;
      }
      const first = focusable[0];
      const last = focusable[focusable.length - 1];
      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first.focus();
      }
    };

    document.addEventListener("keydown", handleKeyDown);
    return () => {
      window.cancelAnimationFrame(frame);
      document.removeEventListener("keydown", handleKeyDown);
      if (shouldLockBodyScroll) document.body.style.overflow = previousOverflow;
      previousActiveElement?.focus();
    };
  }, [closeOnEscape, open, shouldLockBodyScroll, shouldTrapFocus]);

  useEffect(() => {
    if (!open) setDragPosition(null);
  }, [open]);

  useEffect(() => {
    if (!draggable || !open) return;
    const handlePointerMove = (event: globalThis.PointerEvent) => {
      const drag = dragRef.current;
      const dialog = dialogRef.current;
      if (!drag || !dialog || event.pointerId !== drag.pointerId) return;
      const bounds = dialog.getBoundingClientRect();
      const left = Math.max(8, Math.min(window.innerWidth - bounds.width - 8, event.clientX - drag.offsetX));
      const top = Math.max(76, Math.min(window.innerHeight - bounds.height - 8, event.clientY - drag.offsetY));
      setDragPosition({ left, top });
    };
    const handlePointerUp = (event: globalThis.PointerEvent) => {
      if (dragRef.current?.pointerId === event.pointerId) dragRef.current = null;
    };
    window.addEventListener("pointermove", handlePointerMove);
    window.addEventListener("pointerup", handlePointerUp);
    window.addEventListener("pointercancel", handlePointerUp);
    return () => {
      window.removeEventListener("pointermove", handlePointerMove);
      window.removeEventListener("pointerup", handlePointerUp);
      window.removeEventListener("pointercancel", handlePointerUp);
    };
  }, [draggable, open]);

  if (!open || typeof document === "undefined") return null;

  const allowBackdropClose = closeOnBackdrop ?? placement === "center";

  const handleBackdropPointerDown = (event: PointerEvent<HTMLDivElement>) => {
    if (allowBackdropClose && event.target === event.currentTarget) onClose();
  };

  return createPortal(
    <InspectorSelectPolicyContext.Provider value={false}>
      <div className="nodedc-overlay nodedc-ui-root" data-placement={placement} onPointerDown={handleBackdropPointerDown}>
        <div
          ref={dialogRef}
          className={cn("nodedc-window nodedc-glass-material nodedc-material-rim", className)}
          data-material="glass-v4"
          data-size={size === "md" ? undefined : size}
          data-placement={placement}
          data-draggable={draggable ? "true" : undefined}
          role="dialog"
          aria-modal={placement === "center" ? "true" : undefined}
          aria-labelledby={titleId}
          aria-describedby={subtitle ? descriptionId : undefined}
          tabIndex={-1}
          style={dragPosition ? { ...style, position: "fixed", left: dragPosition.left, top: dragPosition.top, right: "auto", bottom: "auto" } : style}
          {...props}
        >
          <header
            className="nodedc-window__head"
            onPointerDown={(event) => {
              if (!draggable || event.button !== 0 || (event.target as HTMLElement).closest("button, input, select, textarea, a")) return;
              const bounds = dialogRef.current?.getBoundingClientRect();
              if (!bounds) return;
              dragRef.current = { pointerId: event.pointerId, offsetX: event.clientX - bounds.left, offsetY: event.clientY - bounds.top };
              event.preventDefault();
            }}
          >
            <div className="nodedc-window__titles">
              <h2 id={titleId} className="nodedc-window__title">{title}</h2>
              {subtitle ? <p id={descriptionId} className="nodedc-window__subtitle">{subtitle}</p> : null}
            </div>
            <button type="button" className="nodedc-window__close" aria-label={closeLabel} onClick={onClose}>
              <Icon name="close" size={16} strokeWidth={1.6} />
            </button>
          </header>
          <div className="nodedc-window__body">{children}</div>
          {footer ? <footer className="nodedc-window__footer">{footer}</footer> : null}
        </div>
      </div>
    </InspectorSelectPolicyContext.Provider>,
    document.body,
  );
}

export function WindowFooterActions({ children, className, ...props }: HTMLAttributes<HTMLDivElement>) {
  return <div className={cn("nodedc-window__footer-actions", className)} {...props}>{children}</div>;
}
