import {
  useEffect,
  useId,
  useRef,
  useState,
  type HTMLAttributes,
  type KeyboardEvent,
  type PointerEvent,
  type ReactNode,
  type RefObject,
} from "react";
import { cn } from "./cn.js";
import { Icon } from "./Icon.js";

export interface WorkspaceWindowRect {
  x: number;
  y: number;
  width: number;
  height: number;
}

export interface WorkspaceWindowProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onResize"> {
  boundsRef: RefObject<HTMLElement | null>;
  rect: WorkspaceWindowRect;
  onRectChange: (rect: WorkspaceWindowRect) => void;
  maximized: boolean;
  onMaximizedChange: (maximized: boolean) => void;
  onActivate?: () => void;
  onClose: () => void;
  title: ReactNode;
  subtitle?: ReactNode;
  status?: ReactNode;
  footer?: ReactNode;
  children: ReactNode;
  minWidth?: number;
  minHeight?: number;
  resizable?: boolean;
  autoHeight?: boolean;
  active?: boolean;
  zIndex?: number;
  closeLabel?: string;
  maximizeLabel?: string;
  restoreLabel?: string;
  moveLabel?: string;
  resizeLabel?: string;
}

type WorkspaceBounds = { width: number; height: number };

type WorkspaceInteraction = {
  kind: "move" | "resize";
  pointerId: number;
  startClientX: number;
  startClientY: number;
  startRect: WorkspaceWindowRect;
};

const rectEquals = (left: WorkspaceWindowRect, right: WorkspaceWindowRect) => (
  left.x === right.x
  && left.y === right.y
  && left.width === right.width
  && left.height === right.height
);

const clamp = (value: number, minimum: number, maximum: number) => (
  Math.min(Math.max(value, minimum), maximum)
);

const clampDimension = (value: number, minimum: number, available: number) => {
  const limit = Math.max(0, available);
  const effectiveMinimum = Math.min(Math.max(0, minimum), limit);
  return clamp(value, effectiveMinimum, limit);
};

const normalizeRect = (
  rect: WorkspaceWindowRect,
  bounds: WorkspaceBounds,
  minWidth: number,
  minHeight: number,
): WorkspaceWindowRect => {
  const width = clampDimension(rect.width, minWidth, bounds.width);
  const height = clampDimension(rect.height, minHeight, bounds.height);
  return {
    x: clamp(rect.x, 0, Math.max(0, bounds.width - width)),
    y: clamp(rect.y, 0, Math.max(0, bounds.height - height)),
    width,
    height,
  };
};

const resizeRect = (
  rect: WorkspaceWindowRect,
  deltaX: number,
  deltaY: number,
  bounds: WorkspaceBounds,
  minWidth: number,
  minHeight: number,
): WorkspaceWindowRect => {
  const normalized = normalizeRect(rect, bounds, minWidth, minHeight);
  return {
    ...normalized,
    width: clampDimension(normalized.width + deltaX, minWidth, bounds.width - normalized.x),
    height: clampDimension(normalized.height + deltaY, minHeight, bounds.height - normalized.y),
  };
};

const keyboardStep = (event: KeyboardEvent<HTMLElement>) => event.shiftKey ? 10 : 1;

export function WorkspaceWindow({
  boundsRef,
  rect,
  onRectChange,
  maximized,
  onMaximizedChange,
  onActivate,
  onClose,
  title,
  subtitle,
  status,
  footer,
  children,
  minWidth = 260,
  minHeight = 180,
  resizable = true,
  autoHeight = false,
  active = false,
  zIndex,
  closeLabel = "Закрыть окно",
  maximizeLabel = "Развернуть окно",
  restoreLabel = "Восстановить окно",
  moveLabel = "Переместить окно",
  resizeLabel = "Изменить размер окна",
  className,
  style,
  onPointerDownCapture,
  onFocusCapture,
  ...props
}: WorkspaceWindowProps) {
  const titleId = useId();
  const descriptionId = useId();
  const interactionRef = useRef<WorkspaceInteraction | null>(null);
  const rectRef = useRef(rect);
  const onRectChangeRef = useRef(onRectChange);
  const headRef = useRef<HTMLElement>(null);
  const bodyRef = useRef<HTMLDivElement>(null);
  const bodyContentRef = useRef<HTMLDivElement>(null);
  const footerRef = useRef<HTMLElement>(null);
  const [interactionKind, setInteractionKind] = useState<WorkspaceInteraction["kind"] | null>(null);

  rectRef.current = rect;
  onRectChangeRef.current = onRectChange;

  const readBounds = (): WorkspaceBounds | null => {
    const bounds = boundsRef.current;
    if (!bounds) return null;
    return { width: bounds.clientWidth, height: bounds.clientHeight };
  };

  const emitRect = (nextRect: WorkspaceWindowRect) => {
    if (rectEquals(rectRef.current, nextRect)) return;
    rectRef.current = nextRect;
    onRectChangeRef.current(nextRect);
  };

  useEffect(() => {
    const bounds = boundsRef.current;
    if (!bounds || maximized) return;

    const clampToBounds = () => {
      const nextBounds = readBounds();
      if (nextBounds) emitRect(normalizeRect(rectRef.current, nextBounds, minWidth, minHeight));
    };

    clampToBounds();
    const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(clampToBounds);
    observer?.observe(bounds);
    window.addEventListener("resize", clampToBounds);
    return () => {
      observer?.disconnect();
      window.removeEventListener("resize", clampToBounds);
    };
  }, [boundsRef, maximized, minHeight, minWidth, rect.height, rect.width, rect.x, rect.y]);

  useEffect(() => {
    if (!autoHeight || maximized) return;
    const bounds = boundsRef.current;
    const head = headRef.current;
    const body = bodyRef.current;
    const content = bodyContentRef.current;
    if (!bounds || !head || !body || !content) return;
    let frame = 0;

    const fitContent = () => {
      frame = 0;
      const nextBounds = readBounds();
      if (!nextBounds) return;
      const normalized = normalizeRect(rectRef.current, nextBounds, minWidth, minHeight);
      const bodyStyle = window.getComputedStyle(body);
      const bodyPadding = Number.parseFloat(bodyStyle.paddingTop || "0")
        + Number.parseFloat(bodyStyle.paddingBottom || "0");
      const desiredHeight = Math.ceil(
        head.offsetHeight
        + content.scrollHeight
        + bodyPadding
        + (footerRef.current?.offsetHeight ?? 0),
      );
      const availableHeight = Math.max(0, nextBounds.height - normalized.y);
      emitRect({
        ...normalized,
        height: clampDimension(desiredHeight, minHeight, availableHeight),
      });
    };

    const scheduleFit = () => {
      if (frame) cancelAnimationFrame(frame);
      frame = requestAnimationFrame(fitContent);
    };

    scheduleFit();
    const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(scheduleFit);
    observer?.observe(content);
    observer?.observe(head);
    if (footerRef.current) observer?.observe(footerRef.current);
    return () => {
      if (frame) cancelAnimationFrame(frame);
      observer?.disconnect();
    };
  }, [autoHeight, boundsRef, maximized, minHeight, minWidth, rect.width]);

  useEffect(() => {
    const handlePointerMove = (event: globalThis.PointerEvent) => {
      const interaction = interactionRef.current;
      if (!interaction || event.pointerId !== interaction.pointerId) return;
      const bounds = readBounds();
      if (!bounds) return;

      const deltaX = event.clientX - interaction.startClientX;
      const deltaY = event.clientY - interaction.startClientY;
      const nextRect = interaction.kind === "move"
        ? normalizeRect({
            ...interaction.startRect,
            x: interaction.startRect.x + deltaX,
            y: interaction.startRect.y + deltaY,
          }, bounds, minWidth, minHeight)
        : resizeRect(interaction.startRect, deltaX, deltaY, bounds, minWidth, minHeight);
      emitRect(nextRect);
      event.preventDefault();
    };

    const endInteraction = (event: globalThis.PointerEvent) => {
      if (interactionRef.current?.pointerId !== event.pointerId) return;
      interactionRef.current = null;
      setInteractionKind(null);
    };

    window.addEventListener("pointermove", handlePointerMove, { passive: false });
    window.addEventListener("pointerup", endInteraction);
    window.addEventListener("pointercancel", endInteraction);
    return () => {
      window.removeEventListener("pointermove", handlePointerMove);
      window.removeEventListener("pointerup", endInteraction);
      window.removeEventListener("pointercancel", endInteraction);
    };
  }, [boundsRef, minHeight, minWidth]);

  useEffect(() => {
    if ((!resizable && interactionRef.current?.kind === "resize") || maximized) {
      interactionRef.current = null;
      setInteractionKind(null);
    }
  }, [maximized, resizable]);

  const beginInteraction = (kind: WorkspaceInteraction["kind"], event: PointerEvent<HTMLElement>) => {
    if (maximized || event.button !== 0 || (kind === "resize" && !resizable)) return;
    if (kind === "move" && (event.target as HTMLElement).closest("button, input, select, textarea, a")) return;
    const bounds = readBounds();
    if (!bounds) return;
    const startRect = normalizeRect(rectRef.current, bounds, minWidth, minHeight);
    emitRect(startRect);
    interactionRef.current = {
      kind,
      pointerId: event.pointerId,
      startClientX: event.clientX,
      startClientY: event.clientY,
      startRect,
    };
    setInteractionKind(kind);
    event.currentTarget.focus();
    event.preventDefault();
  };

  const handleMoveKeyDown = (event: KeyboardEvent<HTMLElement>) => {
    if (maximized || !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return;
    const bounds = readBounds();
    if (!bounds) return;
    const step = keyboardStep(event);
    const next = { ...rectRef.current };
    if (event.key === "ArrowLeft") next.x -= step;
    if (event.key === "ArrowRight") next.x += step;
    if (event.key === "ArrowUp") next.y -= step;
    if (event.key === "ArrowDown") next.y += step;
    emitRect(normalizeRect(next, bounds, minWidth, minHeight));
    event.preventDefault();
  };

  const handleResizeKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
    if (maximized || !resizable || !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return;
    const bounds = readBounds();
    if (!bounds) return;
    const step = keyboardStep(event);
    const deltaX = event.key === "ArrowLeft" ? -step : event.key === "ArrowRight" ? step : 0;
    const deltaY = event.key === "ArrowUp" ? -step : event.key === "ArrowDown" ? step : 0;
    emitRect(resizeRect(rectRef.current, deltaX, deltaY, bounds, minWidth, minHeight));
    event.preventDefault();
  };

  const geometryStyle = maximized
    ? { left: 0, top: 0, width: "100%", height: "100%" }
    : { left: rect.x, top: rect.y, width: rect.width, height: rect.height };

  return (
    <div
      {...props}
      className={cn("nodedc-workspace-window nodedc-glass-material nodedc-material-rim nodedc-ui-root", className)}
      data-active={active ? "true" : undefined}
      data-maximized={maximized ? "true" : undefined}
      data-resizable={resizable ? "true" : undefined}
      data-auto-height={autoHeight ? "true" : undefined}
      data-interaction={interactionKind ?? undefined}
      role="dialog"
      aria-modal="false"
      aria-labelledby={titleId}
      aria-describedby={subtitle ? descriptionId : undefined}
      style={{ ...style, ...geometryStyle, zIndex: zIndex ?? style?.zIndex }}
      onPointerDownCapture={(event) => {
        onActivate?.();
        onPointerDownCapture?.(event);
      }}
      onFocusCapture={(event) => {
        onActivate?.();
        onFocusCapture?.(event);
      }}
    >
      <header
        ref={headRef}
        className="nodedc-workspace-window__head"
        tabIndex={maximized ? -1 : 0}
        role="group"
        aria-roledescription="Перемещаемая область окна"
        aria-label={moveLabel}
        aria-keyshortcuts="ArrowUp ArrowDown ArrowLeft ArrowRight"
        onPointerDown={(event) => beginInteraction("move", event)}
        onKeyDown={handleMoveKeyDown}
      >
        <div className="nodedc-workspace-window__titles">
          <div className="nodedc-workspace-window__title-row">
            <h2 id={titleId} className="nodedc-workspace-window__title">{title}</h2>
            {status ? <span className="nodedc-workspace-window__status">{status}</span> : null}
          </div>
          {subtitle ? <p id={descriptionId} className="nodedc-workspace-window__subtitle">{subtitle}</p> : null}
        </div>
        <div className="nodedc-workspace-window__actions">
          <button
            type="button"
            className="nodedc-workspace-window__action"
            aria-label={maximized ? restoreLabel : maximizeLabel}
            title={maximized ? restoreLabel : maximizeLabel}
            onClick={() => onMaximizedChange(!maximized)}
          >
            <Icon name={maximized ? "minimize" : "expand"} size={15} strokeWidth={1.6} />
          </button>
          <button
            type="button"
            className="nodedc-workspace-window__action"
            aria-label={closeLabel}
            title={closeLabel}
            onClick={onClose}
          >
            <Icon name="close" size={15} strokeWidth={1.6} />
          </button>
        </div>
      </header>
      <div ref={bodyRef} className="nodedc-workspace-window__body">
        {autoHeight ? <div ref={bodyContentRef} className="nodedc-workspace-window__body-content">{children}</div> : children}
      </div>
      {footer ? <footer ref={footerRef} className="nodedc-workspace-window__footer">{footer}</footer> : null}
      {resizable && !maximized ? (
        <button
          type="button"
          className="nodedc-workspace-window__resize"
          aria-label={resizeLabel}
          title={resizeLabel}
          onPointerDown={(event) => beginInteraction("resize", event)}
          onKeyDown={handleResizeKeyDown}
        />
      ) : null}
    </div>
  );
}
