import { useEffect, useRef, type PointerEvent as ReactPointerEvent, type RefObject } from 'react'
import { screenToWorld, snapLocalToBedGrid, worldToLocal, type Point } from '@/lib/geometry'
import { useUpdatePlanting } from '@/lib/objects'
import type { EditorPlanting } from '@/lib/plantings'
import { HANDLE_PX, MIN_RADIUS_CM, SELECT_COLOR, objectTransform } from './shared'
import { useEditorStore } from './store'
import type { EditorObject } from './types'

const MAX_RADIUS_CM = 10_000

/**
 * Move/resize handles for the selected plop, rendered inside its parent object's
 * translate+rotate group so the plop stays in the object's local frame. Drag the
 * body to move (clamped to the object's bounds); drag the edge handle to resize
 * the radius. Each gesture updates livePlanting for instant feedback and fires
 * one PATCH on release — the same contract as SelectionOverlay (#11).
 */
export function PlopOverlay({
  plop,
  object,
  gardenId,
  svgRef,
  snap,
  gridCm,
}: {
  plop: EditorPlanting
  object: EditorObject
  gardenId: number
  svgRef: RefObject<SVGSVGElement | null>
  // The parent bed's grid: when snap is true, moving the plop snaps it to the
  // bed grid (radius stays free either way).
  snap: boolean
  gridCm: number
}) {
  const setLivePlanting = useEditorStore((s) => s.setLivePlanting)
  const setObjectDragging = useEditorStore((s) => s.setObjectDragging)
  const scale = useEditorStore((s) => s.viewport.scale)
  const update = useUpdatePlanting(gardenId)

  const cleanupRef = useRef<(() => void) | null>(null)
  useEffect(
    () => () => {
      cleanupRef.current?.()
      cleanupRef.current = null
    },
    [],
  )

  const handleCm = HANDLE_PX / scale
  const halfW = object.widthCm / 2
  const halfH = object.heightCm / 2
  const center: Point = { x: object.xCm, y: object.yCm }
  const rot = object.rotationDeg

  // Pointer (client) → the object's local frame, snapshotting the svg rect once
  // per gesture (the object doesn't move during a plop drag).
  const makePointerLocal = () => {
    const rect = svgRef.current?.getBoundingClientRect()
    return (e: { clientX: number; clientY: number }): Point => {
      const vp = useEditorStore.getState().viewport
      const canvas = rect ? { x: e.clientX - rect.left, y: e.clientY - rect.top } : { x: e.clientX, y: e.clientY }
      return worldToLocal(screenToWorld(canvas, vp), center, rot)
    }
  }

  const begin = (
    e: ReactPointerEvent,
    onMove: (e: PointerEvent) => EditorPlanting,
    fields: (final: EditorPlanting) => Parameters<typeof update.mutate>[0],
  ) => {
    e.stopPropagation()
    e.preventDefault()
    setObjectDragging(true)
    const move = (ev: PointerEvent) => setLivePlanting(onMove(ev))
    const detach = () => {
      window.removeEventListener('pointermove', move)
      window.removeEventListener('pointerup', finish)
      window.removeEventListener('pointercancel', finish)
    }
    const finish = () => {
      detach()
      cleanupRef.current = null
      const final = useEditorStore.getState().livePlanting
      setObjectDragging(false)
      setLivePlanting(null)
      if (final) update.mutate(fields(final))
    }
    cleanupRef.current = () => {
      detach()
      setObjectDragging(false)
      setLivePlanting(null)
    }
    window.addEventListener('pointermove', move)
    window.addEventListener('pointerup', finish)
    window.addEventListener('pointercancel', finish)
  }

  const base = { ...plop }

  const startMove = (e: ReactPointerEvent) => {
    const ptr = makePointerLocal()
    const start = ptr(e.nativeEvent)
    begin(
      e,
      (ev) => {
        const p = ptr(ev)
        const moved: Point = { x: base.xCm + (p.x - start.x), y: base.yCm + (p.y - start.y) }
        // snapLocalToBedGrid clamps either way; step 0 (snapping off) makes it a
        // pure clamp, so both paths go through one helper.
        const next = snapLocalToBedGrid(moved, snap ? gridCm : 0, halfW, halfH)
        return { ...base, xCm: next.x, yCm: next.y }
      },
      (f) => ({ id: base.id, version: base.version, xCm: f.xCm, yCm: f.yCm }),
    )
  }

  const startResize = (e: ReactPointerEvent) => {
    const ptr = makePointerLocal()
    begin(
      e,
      (ev) => {
        const p = ptr(ev)
        const r = Math.max(MIN_RADIUS_CM, Math.min(MAX_RADIUS_CM, Math.hypot(p.x - base.xCm, p.y - base.yCm)))
        return { ...base, radiusCm: r }
      },
      (f) => ({ id: base.id, version: base.version, radiusCm: f.radiusCm }),
    )
  }

  const r = plop.radiusCm
  return (
    <g transform={objectTransform(object)}>
      {/* Transparent body: drag to move (at least handle-sized so tiny plops stay grabbable). */}
      <circle
        cx={plop.xCm}
        cy={plop.yCm}
        r={Math.max(r, handleCm)}
        fill="transparent"
        pointerEvents="all"
        style={{ cursor: 'move' }}
        onPointerDown={startMove}
      />
      {/* Selection ring. */}
      <circle
        cx={plop.xCm}
        cy={plop.yCm}
        r={r}
        fill="none"
        stroke={SELECT_COLOR}
        strokeWidth={1.5}
        vectorEffect="non-scaling-stroke"
        pointerEvents="none"
      />
      {/* Radius handle at the local +x edge. */}
      <circle
        cx={plop.xCm + r}
        cy={plop.yCm}
        r={handleCm * 0.6}
        fill="#ffffff"
        stroke={SELECT_COLOR}
        strokeWidth={1}
        vectorEffect="non-scaling-stroke"
        style={{ cursor: 'ew-resize' }}
        onPointerDown={startResize}
      />
    </g>
  )
}
