import { memo, useRef } from "react"
import { NodeResizer, type NodeProps } from "@xyflow/react"
import { RotateCw, X } from "lucide-react"

import { AnnotationData } from "./types"

type AnnotationNodeData = AnnotationData & {
  onLabelChange?: (label: string) => void
  onRotationChange?: (rotation: number) => void
  onDelete?: () => void
}

// `className` supplies its own position utility (e.g. "absolute -top-2.5 -right-2.5" or
// "static") — not baked in here, so callers that already sit inside a positioned flex
// row (LineNode's rotate/delete pair) aren't fighting a hardcoded `absolute`.
function DeleteHandle({ onDelete, className }: { onDelete?: () => void; className?: string }) {
  return (
    <button
      type="button"
      aria-label="Delete"
      title="Delete"
      className={`nodrag flex size-5 items-center justify-center rounded-full bg-destructive text-white shadow-sm hover:bg-destructive/90 ${className ?? ""}`}
      onPointerDown={(e) => e.stopPropagation()}
      onClick={(e) => {
        e.stopPropagation()
        onDelete?.()
      }}
    >
      <X className="size-3" />
    </button>
  )
}

/**
 * Free-floating group/label box. Purely visual — no Handles, so it can never be an edge
 * endpoint, and it's excluded from every graph check (see StageNode for the real stage card).
 * Rendered behind stage nodes: the page prepends new boxes to the nodes array, and React
 * Flow paints later array entries on top.
 */
function BoxNode({ data, selected }: NodeProps & { data: AnnotationNodeData }) {
  return (
    <div className="relative size-full rounded-xl border-2 border-dashed border-muted-foreground/30 bg-muted/40">
      <NodeResizer isVisible={selected} minWidth={160} minHeight={100} lineClassName="!border-primary" handleClassName="!size-2.5 !border-primary !bg-card" />
      {selected && data.onDelete && <DeleteHandle onDelete={data.onDelete} className="absolute -top-2.5 -right-2.5" />}
      <input
        defaultValue={data.label}
        disabled={!data.onLabelChange}
        onChange={(e) => data.onLabelChange?.(e.target.value)}
        placeholder="Group label…"
        className="nodrag m-2 w-[calc(100%-1rem)] rounded-md bg-transparent px-1.5 py-1 text-sm font-semibold text-foreground outline-none placeholder:text-muted-foreground/60 focus:bg-card"
      />
    </div>
  )
}

/**
 * Thin resizable divider bar, optionally labeled (e.g. "Phase 1"), and rotatable by dragging
 * the small handle that appears above it once selected. The resize outline/handles and the
 * rotate handle itself rotate together with the bar — they all live in one rotated wrapper —
 * so the selection box always matches the bar's visual angle. Note: NodeResizer computes its
 * drag deltas in unrotated screen space, so resizing while significantly rotated will feel a
 * little off; acceptable here since this is a lightweight annotation, not precision CAD.
 * `wrapperRef` (the outer, unrotated element) is what the rotate math measures from, so the
 * center point stays stable regardless of the current angle.
 */
function LineNode({ data, selected }: NodeProps & { data: AnnotationNodeData }) {
  const wrapperRef = useRef<HTMLDivElement>(null)
  const rotation = data.rotation ?? 0

  return (
    <div ref={wrapperRef} className="relative size-full">
      <div className="relative size-full" style={{ transform: `rotate(${rotation}deg)` }}>
        <NodeResizer
          isVisible={selected}
          minWidth={80}
          minHeight={4}
          maxHeight={4}
          lineClassName="!border-primary"
          handleClassName="!size-2.5 !border-primary !bg-card"
        />

        {selected && (data.onRotationChange || data.onDelete) && (
          <div className="absolute -top-7 left-1/2 flex -translate-x-1/2 items-center gap-1.5">
            {data.onRotationChange && (
              <button
                type="button"
                aria-label="Rotate line"
                title="Drag to rotate"
                className="nodrag flex size-5 cursor-grab items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm active:cursor-grabbing"
                onPointerDown={(e) => {
                  e.stopPropagation()
                  const handle = e.currentTarget
                  handle.setPointerCapture(e.pointerId)

                  const onMove = (ev: PointerEvent) => {
                    const rect = wrapperRef.current?.getBoundingClientRect()
                    if (!rect) return
                    const cx = rect.left + rect.width / 2
                    const cy = rect.top + rect.height / 2
                    // atan2 is 0° pointing right; +90 so "handle straight up" reads as 0° rotation.
                    const angle = Math.atan2(ev.clientY - cy, ev.clientX - cx) * (180 / Math.PI) + 90
                    data.onRotationChange?.(Math.round(angle))
                  }
                  const onUp = () => {
                    handle.releasePointerCapture(e.pointerId)
                    window.removeEventListener("pointermove", onMove)
                    window.removeEventListener("pointerup", onUp)
                  }
                  window.addEventListener("pointermove", onMove)
                  window.addEventListener("pointerup", onUp)
                }}
              >
                <RotateCw className="size-3" />
              </button>
            )}
            {data.onDelete && <DeleteHandle onDelete={data.onDelete} className="static" />}
          </div>
        )}

        <div className="flex size-full flex-col items-center justify-center">
          <div className="h-0.5 w-full rounded-full bg-muted-foreground/40" />
        </div>
      </div>

      <input
        defaultValue={data.label}
        disabled={!data.onLabelChange}
        onChange={(e) => data.onLabelChange?.(e.target.value)}
        placeholder="Label (optional)"
        className="nodrag absolute -bottom-6 left-1/2 w-24 -translate-x-1/2 rounded-md bg-transparent px-1 text-center text-xs text-muted-foreground outline-none placeholder:text-muted-foreground/50 focus:bg-card"
      />
    </div>
  )
}

export default memo(BoxNode)
export const LineNodeComponent = memo(LineNode)
