import { Drawer } from '@astryxdesign/lab'
import { useEffect, useRef, useState, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent, type ReactNode } from 'react'
import { IconButton } from './IconButton'

/** Viewport pixels a keyboard nudge moves a draggable drawer. */
const NUDGE_PX = 16

const NUDGE: Record<string, { x: number; y: number }> = {
  ArrowLeft: { x: -NUDGE_PX, y: 0 },
  ArrowRight: { x: NUDGE_PX, y: 0 },
  ArrowUp: { x: 0, y: -NUDGE_PX },
  ArrowDown: { x: 0, y: NUDGE_PX },
}

function CloseIcon() {
  return (
    <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
      <path d="M18 6 6 18M6 6l12 12" />
    </svg>
  )
}

function GripIcon() {
  return (
    <svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor">
      <circle cx="9" cy="6" r="1.6" />
      <circle cx="15" cy="6" r="1.6" />
      <circle cx="9" cy="12" r="1.6" />
      <circle cx="15" cy="12" r="1.6" />
      <circle cx="9" cy="18" r="1.6" />
      <circle cx="15" cy="18" r="1.6" />
    </svg>
  )
}

export function DetailDrawer(props: {
  eyebrow: string
  title: ReactNode
  titleId: string
  onClose(): void
  children: ReactNode
  /** Adds a drag handle that moves the drawer within the viewport. */
  draggable?: boolean
  /** False drops the backdrop and the focus trap, leaving the page behind it usable. */
  modal?: boolean
  size?: 'default' | 'wide'
}) {
  const modal = props.modal !== false
  const width = (props.size ?? 'default') === 'wide' ? 'min(72rem, 100%)' : 'min(28rem, 100%)'
  const drawerRef = useRef<HTMLDialogElement>(null)
  const capturedFocusRef = useRef<Element | null>(null)
  const dragOffsetRef = useRef<{ x: number; y: number } | null>(null)
  const dragBaselineRef = useRef<{ left: number; top: number } | null>(null)
  const onCloseRef = useRef(props.onClose)
  const [position, setPosition] = useState<{ left: number; top: number } | null>(null)
  onCloseRef.current = props.onClose

  // The lab drawer restores focus on its own isOpen flip; the callers here
  // unmount the drawer on close instead, so restore focus on unmount. Capture
  // the previously focused element during ref attach — before the lab drawer
  // moves focus inside on open — not in an effect, which runs too late.
  function drawerRefCallback(dialog: HTMLDialogElement | null) {
    drawerRef.current = dialog
    if (dialog && !capturedFocusRef.current) {
      const active = document.activeElement
      capturedFocusRef.current =
        active instanceof HTMLElement && active !== document.body ? active : null
    }
  }

  useEffect(() => {
    return () => {
      const target = capturedFocusRef.current
      capturedFocusRef.current = null
      if (target instanceof HTMLElement && target.isConnected) target.focus()
    }
  }, [])

  function moveTo(left: number, top: number) {
    const bounds = drawerRef.current?.getBoundingClientRect()
    const width = bounds?.width ?? window.innerWidth
    const height = bounds?.height ?? window.innerHeight
    setPosition({
      left: Math.max(0, Math.min(window.innerWidth - width, left)),
      top: Math.max(0, Math.min(window.innerHeight - height, top)),
    })
  }

  function positionFrom(event: PointerEvent<HTMLButtonElement>) {
    const bounds = drawerRef.current?.getBoundingClientRect()
    const base = position ?? { left: bounds?.left ?? 0, top: bounds?.top ?? 0 }
    dragBaselineRef.current = base
    dragOffsetRef.current = {
      x: event.clientX - (bounds?.left ?? 0),
      y: event.clientY - (bounds?.top ?? 0),
    }
    return base
  }

  function onDragStart(event: PointerEvent<HTMLButtonElement>) {
    positionFrom(event)
    event.currentTarget.setPointerCapture?.(event.pointerId)
  }

  function onDrag(event: PointerEvent<HTMLButtonElement>) {
    const offset = dragOffsetRef.current
    if (!offset) return
    moveTo(event.clientX - offset.x, event.clientY - offset.y)
  }

  function onDragEnd(event: PointerEvent<HTMLButtonElement>) {
    dragOffsetRef.current = null
    if (event.currentTarget.hasPointerCapture?.(event.pointerId)) event.currentTarget.releasePointerCapture?.(event.pointerId)
  }

  function onDragKeyDown(event: ReactKeyboardEvent<HTMLButtonElement>) {
    const step = NUDGE[event.key]
    if (!step) return
    event.preventDefault()
    const bounds = drawerRef.current?.getBoundingClientRect()
    const base = dragBaselineRef.current ?? { left: bounds?.left ?? 0, top: bounds?.top ?? 0 }
    const from = position ?? { left: bounds?.left ?? 0, top: bounds?.top ?? 0 }
    if (!dragBaselineRef.current) dragBaselineRef.current = base
    moveTo(from.left + step.x, from.top + step.y)
  }

  const transform: CSSProperties | undefined = position && dragBaselineRef.current
    ? {
        transform: `translate(${position.left - dragBaselineRef.current.left}px, ${position.top - dragBaselineRef.current.top}px)`,
        // The lab drawer sizes itself with this style var; the drawer's own
        // inline style is replaced when we add the drag transform.
        ['--x-maxWidth' as string]: width,
      }
    : undefined

  return (
    <Drawer
      ref={drawerRefCallback}
      isOpen
      onClose={props.onClose}
      side="end"
      size={width}
      label={props.eyebrow}
      aria-labelledby={props.titleId}
      hasScrim={modal}
      hasCloseButton={false}
      data-testid="detail-drawer"
      style={transform}
    >
      <header className="flex items-start justify-between gap-4 border-b border-border pb-4">
        <div>
          <p className="text-xs font-semibold uppercase tracking-wider text-fg-subtle">{props.eyebrow}</p>
          <h2 id={props.titleId} className="mt-1 text-lg font-semibold">
            {props.title}
          </h2>
        </div>
        <div className="flex shrink-0 items-center gap-1">
          {props.draggable ? (
            <IconButton
              icon={<GripIcon />}
              label="Drag window"
              tooltip="Drag, or move with the arrow keys"
              dragHandle={{
                onPointerDown: onDragStart,
                onPointerMove: onDrag,
                onPointerUp: onDragEnd,
                onPointerCancel: onDragEnd,
                onKeyDown: onDragKeyDown,
              }}
            />
          ) : null}
          <IconButton icon={<CloseIcon />} label="Close" tooltip="Close" onClick={props.onClose} data-autofocus />
        </div>
      </header>
      {props.children}
    </Drawer>
  )
}