
import {
  AppstoreOutlined,
  CloseOutlined,
  EllipsisOutlined,
  FullscreenExitOutlined,
  FullscreenOutlined,
  MinusOutlined,
} from '@ant-design/icons';
import { Button, Typography } from 'antd';
import type { PointerEvent as ReactPointerEvent } from 'react';
import { useEffect, useRef, useState } from 'react';
import type { WindowFrame, WindowUIProps } from './types';
import './WindowUI.css';

const { Text } = Typography;

const HEADER_HEIGHT = 48;
const MOBILE_BREAKPOINT = 768;
const MINIMIZED_SIZE = 52;
const DRAG_CLICK_THRESHOLD = 4;
const DEFAULT_FRAME: WindowFrame = {
  x: 32,
  y: 32,
  width: 520,
  height: 340,
};

type DragState = {
  pointerId: number;
  startX: number;
  startY: number;
  frame: WindowFrame;
  moved: boolean;
};

type ResizeDirection = 'n' | 'e' | 's' | 'w' | 'ne' | 'nw' | 'se' | 'sw';

type ResizeState = DragState & {
  direction: ResizeDirection;
  moved: boolean;
};

type ResizeTapRecord = {
  direction: ResizeDirection;
  at: number;
  x: number;
  y: number;
};

const DOUBLE_TAP_DELAY = 320;
const DOUBLE_TAP_MOVE_TOLERANCE = 18;
const RESIZE_MOVE_THRESHOLD = 4;

function clamp(value: number, min: number, max: number) {
  return Math.min(Math.max(value, min), max);
}

function mergeFrame(defaultFrame?: Partial<WindowFrame>): WindowFrame {
  return {
    x: defaultFrame?.x ?? DEFAULT_FRAME.x,
    y: defaultFrame?.y ?? DEFAULT_FRAME.y,
    width: defaultFrame?.width ?? DEFAULT_FRAME.width,
    height: defaultFrame?.height ?? DEFAULT_FRAME.height,
  };
}

function getParentRect(element: HTMLDivElement | null) {
  return element?.parentElement?.getBoundingClientRect() ?? null;
}

function getVisibleParentBounds(element: HTMLDivElement | null) {
  const parentRect = getParentRect(element);

  if (!parentRect) {
    return null;
  }

  if (typeof window === 'undefined') {
    return {
      x: 0,
      y: 0,
      width: parentRect.width,
      height: parentRect.height,
    };
  }

  const viewportWidth = window.visualViewport?.width ?? window.innerWidth;
  const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
  const visibleLeft = Math.max(parentRect.left, 0);
  const visibleTop = Math.max(parentRect.top, 0);
  const visibleRight = Math.min(parentRect.right, viewportWidth);
  const visibleBottom = Math.min(parentRect.bottom, viewportHeight);
  const width = Math.max(0, visibleRight - visibleLeft);
  const height = Math.max(0, visibleBottom - visibleTop);

  if (width <= 0 || height <= 0) {
    return null;
  }

  return {
    x: visibleLeft - parentRect.left,
    y: visibleTop - parentRect.top,
    width,
    height,
  };
}

export function WindowUI({
  title,
  subtitle,
  children,
  bodyClassName,
  defaultFrame,
  frame: controlledFrame,
  onFrameChange,
  onOpenLayoutControls,
  zIndex,
  onActivate,
  minWidth = 360,
  minHeight = 220,
  className,
  onClose,
  preserveChildrenOnMinimize = false,
  showMaximizeControl = true,
  compactHeader = false,
}: WindowUIProps) {
  const rootRef = useRef<HTMLDivElement>(null);
  const [frame, setFrame] = useState<WindowFrame>(() => mergeFrame(defaultFrame));
  const [restoreFrame, setRestoreFrame] = useState<WindowFrame | null>(null);
  const [isMinimized, setIsMinimized] = useState(false);
  const [isMaximized, setIsMaximized] = useState(false);
  const [isMobileViewport, setIsMobileViewport] = useState(false);
  const [isDragging, setIsDragging] = useState(false);
  const [isResizing, setIsResizing] = useState(false);
  const [isMinimizeMenuOpen, setIsMinimizeMenuOpen] = useState(false);
  const dragStateRef = useRef<DragState | null>(null);
  const resizeStateRef = useRef<ResizeState | null>(null);
  const resizeTapRef = useRef<ResizeTapRecord | null>(null);
  const frameRef = useRef(frame);

  useEffect(() => {
    frameRef.current = frame;
  }, [frame]);

  useEffect(() => {
    if (controlledFrame) {
      setFrame(controlledFrame);
    }
  }, [controlledFrame]);

  const updateFrame = (nextFrame: WindowFrame | ((previousFrame: WindowFrame) => WindowFrame)) => {
    setFrame((previousFrame) => {
      const resolvedFrame =
        typeof nextFrame === 'function' ? nextFrame(previousFrame) : nextFrame;

      onFrameChange?.(resolvedFrame);
      return resolvedFrame;
    });
  };

  useEffect(() => {
    const root = rootRef.current;
    const parentElement = root?.parentElement;

    if (!parentElement) {
      return;
    }

    const resizeObserver = new ResizeObserver(() => {
      const parentRect = getParentRect(rootRef.current);

      if (!parentRect) {
        return;
      }

      updateFrame((previousFrame) => {
        setIsMobileViewport(parentRect.width <= MOBILE_BREAKPOINT);

        if (isMaximized) {
          return {
            x: 0,
            y: 0,
            width: parentRect.width,
            height: parentRect.height,
          };
        }

        const nextWidth = Math.min(previousFrame.width, parentRect.width);
        const nextHeight = Math.min(previousFrame.height, parentRect.height);

        return {
          ...previousFrame,
          width: nextWidth,
          height: nextHeight,
          x: clamp(previousFrame.x, 0, Math.max(0, parentRect.width - nextWidth)),
          y: clamp(
            previousFrame.y,
            0,
            Math.max(0, parentRect.height - (isMinimized ? HEADER_HEIGHT : nextHeight)),
          ),
        };
      });
    });

    resizeObserver.observe(parentElement);

    return () => {
      resizeObserver.disconnect();
    };
  }, [isMaximized, isMinimized]);

  useEffect(() => {
    const parentRect = getParentRect(rootRef.current);

    if (!parentRect) {
      return;
    }

    const mobile = parentRect.width <= MOBILE_BREAKPOINT;
    setIsMobileViewport(mobile);

    updateFrame((previousFrame) => ({
      ...previousFrame,
      width: mobile ? Math.min(parentRect.width, previousFrame.width) : previousFrame.width,
      x: mobile ? clamp(previousFrame.x, 0, Math.max(0, parentRect.width - previousFrame.width)) : previousFrame.x,
      y: clamp(
        previousFrame.y,
        0,
        Math.max(0, parentRect.height - (isMinimized ? HEADER_HEIGHT : previousFrame.height)),
      ),
    }));
  }, [isMinimized]);

  const toggleMaximize = () => {
    const parentRect = getParentRect(rootRef.current);

    if (!parentRect) {
      return;
    }

    if (isMaximized) {
      updateFrame(restoreFrame ?? mergeFrame(defaultFrame));
      setIsMaximized(false);
      return;
    }

    setRestoreFrame(frameRef.current);
    setIsMinimized(false);
    setIsMaximized(true);
    updateFrame({
      x: 0,
      y: 0,
      width: parentRect.width,
      height: parentRect.height,
    });
  };

  const fillFrameToDirection = (direction: ResizeDirection) => {
    const parentRect = getParentRect(rootRef.current);
    const visibleBounds = getVisibleParentBounds(rootRef.current);

    if (!parentRect || !visibleBounds) {
      return;
    }

    const currentFrame = frameRef.current;
    const visibleRight = visibleBounds.x + visibleBounds.width;
    const visibleBottom = visibleBounds.y + visibleBounds.height;
    let nextX = currentFrame.x;
    let nextY = currentFrame.y;
    let nextWidth = currentFrame.width;
    let nextHeight = currentFrame.height;

    if (direction.includes('w')) {
      nextWidth += nextX - visibleBounds.x;
      nextX = visibleBounds.x;
    }

    if (direction.includes('n')) {
      nextHeight += nextY - visibleBounds.y;
      nextY = visibleBounds.y;
    }

    if (direction.includes('e')) {
      nextWidth = visibleRight - nextX;
    }

    if (direction.includes('s')) {
      nextHeight = visibleBottom - nextY;
    }

    updateFrame({
      x: clamp(nextX, 0, Math.max(0, parentRect.width - minWidth)),
      y: clamp(nextY, 0, Math.max(0, parentRect.height - minHeight)),
      width: clamp(nextWidth, minWidth, parentRect.width - nextX),
      height: clamp(nextHeight, minHeight, parentRect.height - nextY),
    });
  };

  const onHeaderPointerDown = (event: ReactPointerEvent<HTMLElement>) => {
    onActivate?.();

    if (isMaximized) {
      return;
    }

    const root = rootRef.current;

    if (!root) {
      return;
    }

    dragStateRef.current = {
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      frame: frameRef.current,
      moved: false,
    };

    setIsMinimizeMenuOpen(false);
    setIsDragging(true);
    root.setPointerCapture(event.pointerId);
  };

  const onResizePointerDown =
    (direction: ResizeDirection) => (event: ReactPointerEvent<HTMLDivElement>) => {
      if (isMaximized || isMinimized) {
        return;
      }

      const root = rootRef.current;

      if (!root) {
        return;
      }

      event.stopPropagation();
      onActivate?.();

      resizeStateRef.current = {
        direction,
        pointerId: event.pointerId,
        startX: event.clientX,
        startY: event.clientY,
        frame: frameRef.current,
        moved: false,
      };

      setIsResizing(true);
      root.setPointerCapture(event.pointerId);
    };

  const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    const parentRect = getParentRect(rootRef.current);

    if (!parentRect) {
      return;
    }

    if (dragStateRef.current && dragStateRef.current.pointerId === event.pointerId) {
      const deltaX = event.clientX - dragStateRef.current.startX;
      const deltaY = event.clientY - dragStateRef.current.startY;
      const nextWidth = isMinimized ? MINIMIZED_SIZE : frameRef.current.width;
      const nextHeight = isMinimized ? MINIMIZED_SIZE : frameRef.current.height;

      if (
        !dragStateRef.current.moved &&
        (Math.abs(deltaX) > DRAG_CLICK_THRESHOLD || Math.abs(deltaY) > DRAG_CLICK_THRESHOLD)
      ) {
        dragStateRef.current.moved = true;
      }

      updateFrame({
        ...frameRef.current,
        x: isMobileViewport
          ? 0
          : clamp(dragStateRef.current.frame.x + deltaX, 0, Math.max(0, parentRect.width - nextWidth)),
        y: clamp(
          dragStateRef.current.frame.y + deltaY,
          0,
          Math.max(0, parentRect.height - nextHeight),
        ),
      });
    }

    if (resizeStateRef.current && resizeStateRef.current.pointerId === event.pointerId) {
      const { direction, frame: initialFrame, startX, startY } = resizeStateRef.current;
      const deltaX = event.clientX - startX;
      const deltaY = event.clientY - startY;

      if (
        !resizeStateRef.current.moved &&
        (Math.abs(deltaX) > RESIZE_MOVE_THRESHOLD || Math.abs(deltaY) > RESIZE_MOVE_THRESHOLD)
      ) {
        resizeStateRef.current.moved = true;
      }

      let nextX = initialFrame.x;
      let nextY = initialFrame.y;
      let nextWidth = initialFrame.width;
      let nextHeight = initialFrame.height;

      if (direction.includes('e')) {
        nextWidth = clamp(initialFrame.width + deltaX, minWidth, parentRect.width - initialFrame.x);
      }

      if (direction.includes('s')) {
        nextHeight = clamp(
          initialFrame.height + deltaY,
          minHeight,
          parentRect.height - initialFrame.y,
        );
      }

      if (direction.includes('w')) {
        const maxX = initialFrame.x + initialFrame.width - minWidth;
        nextX = clamp(initialFrame.x + deltaX, 0, maxX);
        nextWidth = initialFrame.width - (nextX - initialFrame.x);
      }

      if (direction.includes('n')) {
        const maxY = initialFrame.y + initialFrame.height - minHeight;
        nextY = clamp(initialFrame.y + deltaY, 0, maxY);
        nextHeight = initialFrame.height - (nextY - initialFrame.y);
      }

      nextWidth = Math.min(nextWidth, parentRect.width - nextX);
      nextHeight = Math.min(nextHeight, parentRect.height - nextY);

      updateFrame({
        x: nextX,
        y: nextY,
        width: nextWidth,
        height: nextHeight,
      });
    }
  };

  const onPointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (dragStateRef.current?.pointerId === event.pointerId) {
      const wasMinimizedDrag = isMinimized && !dragStateRef.current.moved;
      dragStateRef.current = null;
      setIsDragging(false);

      if (wasMinimizedDrag) {
        setIsMinimizeMenuOpen((previous) => !previous);
      }
    }

    if (resizeStateRef.current?.pointerId === event.pointerId) {
      const resizeState = resizeStateRef.current;
      resizeStateRef.current = null;
      setIsResizing(false);

      if (!resizeState.moved) {
        const now = Date.now();
        const previousTap = resizeTapRef.current;
        const isDoubleTap =
          previousTap &&
          previousTap.direction === resizeState.direction &&
          now - previousTap.at <= DOUBLE_TAP_DELAY &&
          Math.abs(previousTap.x - event.clientX) <= DOUBLE_TAP_MOVE_TOLERANCE &&
          Math.abs(previousTap.y - event.clientY) <= DOUBLE_TAP_MOVE_TOLERANCE;

        if (isDoubleTap) {
          resizeTapRef.current = null;
          fillFrameToDirection(resizeState.direction);
        } else {
          resizeTapRef.current = {
            direction: resizeState.direction,
            at: now,
            x: event.clientX,
            y: event.clientY,
          };
        }
      }
    }

    rootRef.current?.releasePointerCapture(event.pointerId);
  };

  const renderControlButtons = () => (
    <>
      {onOpenLayoutControls ? (
        <Button
          type="text"
          aria-label="윈도우 배치 컨트롤"
          className="window-ui__control window-ui__control--layout"
          icon={<AppstoreOutlined />}
          onPointerDown={(event) => {
            event.stopPropagation();
          }}
          onClick={onOpenLayoutControls}
        />
      ) : null}
      {onClose ? (
        <Button
          type="text"
          aria-label="창 닫기"
          className="window-ui__control window-ui__control--close"
          icon={<CloseOutlined />}
          onPointerDown={(event) => {
            event.stopPropagation();
          }}
          onClick={onClose}
        />
      ) : null}
      <Button
        type="text"
        aria-label={isMinimized ? '창 복원' : '창 최소화'}
        className="window-ui__control window-ui__control--minimize"
        icon={isMinimized ? <EllipsisOutlined /> : <MinusOutlined />}
        onPointerDown={(event) => {
          event.stopPropagation();
        }}
        onClick={() => {
          setIsMinimizeMenuOpen(false);
          setIsMinimized((previous) => !previous);
        }}
      />
      {showMaximizeControl ? (
        <Button
          type="text"
          aria-label={isMaximized ? '창 복원' : '창 최대화'}
          className="window-ui__control window-ui__control--maximize"
          icon={isMaximized ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
          onPointerDown={(event) => {
            event.stopPropagation();
          }}
          onClick={toggleMaximize}
        />
      ) : null}
    </>
  );

  const minimizedLabel =
    typeof title === 'string' && title.trim().length > 0 ? title.trim().slice(0, 1).toUpperCase() : 'W';

  const minimizedTitle = typeof title === 'string' ? title : minimizedLabel;

  return (
    <div
      ref={rootRef}
      className={[
        'window-ui',
        isMaximized ? 'window-ui--maximized' : '',
        isMobileViewport ? 'window-ui--mobile' : '',
        isMinimized ? 'window-ui--minimized' : '',
        isDragging ? 'window-ui--dragging' : '',
        isResizing ? 'window-ui--resizing' : '',
        compactHeader ? 'window-ui--compact' : '',
        className ?? '',
      ]
        .filter(Boolean)
        .join(' ')}
      style={{
        left: frame.x,
        top: frame.y,
        width: isMinimized
          ? MINIMIZED_SIZE
          : isMobileViewport && !isMaximized
            ? Math.min(frame.width, getParentRect(rootRef.current)?.width ?? frame.width)
            : frame.width,
        height: isMinimized ? MINIMIZED_SIZE : frame.height,
        zIndex,
      }}
      onPointerDown={() => {
        onActivate?.();
      }}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerCancel={onPointerUp}
    >
      {isMinimized ? (
        <div className="window-ui__minimized-shell">
          <button
            type="button"
            className="window-ui__minimized-bubble"
            aria-label={`${title} 최소화 아이콘`}
            onPointerDown={(event) => {
              event.stopPropagation();
              onHeaderPointerDown(event);
            }}
            onDoubleClick={(event) => {
              event.stopPropagation();
              setIsMinimizeMenuOpen(false);
              setIsMinimized(false);
            }}
          >
            <span className="window-ui__minimized-label">{minimizedLabel}</span>
          </button>

          {isMinimizeMenuOpen ? (
            <div
              className="window-ui__minimized-menu"
              onPointerDown={(event) => {
                event.stopPropagation();
              }}
            >
              <div className="window-ui__minimized-header">
                <div className="window-ui__minimized-meta">
                  <Text strong className="window-ui__title-text">
                    {minimizedTitle}
                  </Text>
                  {subtitle ? <div className="window-ui__subtitle">{subtitle}</div> : null}
                </div>
                <div className="window-ui__controls window-ui__controls--minimized">
                  {renderControlButtons()}
                </div>
              </div>
            </div>
          ) : null}
        </div>
      ) : (
        <>
          <div
            className="window-ui__header"
            onPointerDown={onHeaderPointerDown}
            onDoubleClick={toggleMaximize}
          >
            <div className="window-ui__title">
              <div className="window-ui__title-copy">
                <Text strong className="window-ui__title-text">
                  {title}
                </Text>
                {subtitle ? <div className="window-ui__subtitle">{subtitle}</div> : null}
              </div>
            </div>

            <div className="window-ui__controls">{renderControlButtons()}</div>
          </div>

          <div className={['window-ui__body', bodyClassName ?? ''].filter(Boolean).join(' ')}>
            {children}
          </div>
        </>
      )}

      {isMinimized && preserveChildrenOnMinimize ? (
        <div
          className={['window-ui__body', 'window-ui__body--preserved', bodyClassName ?? ''].filter(Boolean).join(' ')}
          aria-hidden="true"
        >
          {children}
        </div>
      ) : null}

      {isMaximized || isMinimized
        ? null
        : ['n', 'e', 's', 'w', 'ne', 'nw', 'se', 'sw'].map((direction) => (
            <div
              key={direction}
              className={`window-ui__resize-handle window-ui__resize-handle--${direction}`}
              onPointerDown={onResizePointerDown(direction as ResizeDirection)}
            >
              <div className="window-ui__resize-grip" aria-hidden="true" />
            </div>
          ))}
    </div>
  );
}
