"use client"

import type { ReactNode } from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import {
  DndContext,
  DragEndEvent,
  MouseSensor,
  PointerSensor,
  TouchSensor,
  closestCenter,
  useSensor,
  useSensors,
} from "@dnd-kit/core"
import { CSS } from "@dnd-kit/utilities"
import {
  SortableContext,
  arrayMove,
  useSortable,
} from "@dnd-kit/sortable"
import { cn } from "@/lib/utils"

type PackedLayoutItem = {
  i: string
  x: number
  y: number
  w: number
  h: number
  minW?: number
  minH?: number
  maxW?: number
  maxH?: number
  static?: boolean
}

type CanvasItem = {
  key: string
  element: ReactNode
  layout: PackedLayoutItem
  minW?: number
  minH?: number
  maxW?: number
  maxH?: number
  static?: boolean
}

type ReportCanvasProps = {
  items: CanvasItem[]
  editable?: boolean
  columns?: number
  rowHeight?: number
  gap?: number
  ready?: boolean
  onReorder?: (nextOrder: string[]) => void
  onResize?: (key: string, size: { w: number; h: number }, options?: { commit?: boolean }) => void
}

type InternalResizeState = {
  key: string
  originX: number
  originY: number
  initialWidth: number
  initialHeight: number
  minW: number
  minH: number
  maxW: number
  maxH: number
}

const DEFAULT_COLUMNS = 12
const DEFAULT_ROW_HEIGHT = 80
const MAX_ROWS = 24

export function ReportCanvas({
  items,
  editable = false,
  columns = DEFAULT_COLUMNS,
  rowHeight = DEFAULT_ROW_HEIGHT,
  gap = 16,
  ready = false,
  onReorder,
  onResize,
}: ReportCanvasProps) {
  const containerRef = useRef<HTMLDivElement | null>(null)
  const [containerWidth, setContainerWidth] = useState(0)
  const [resizing, setResizing] = useState<InternalResizeState | null>(null)
  const lastResizeSizeRef = useRef<{ w: number; h: number } | null>(null)

  useEffect(() => {
    const element = containerRef.current
    if (!element) return
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        if (entry.contentRect) {
          setContainerWidth(entry.contentRect.width)
        }
      }
    })
    observer.observe(element)
    return () => {
      observer.disconnect()
    }
  }, [])

  const columnWidth = useMemo(() => {
    if (containerWidth <= 0 || columns <= 0) return 0
    const totalGap = gap * (columns - 1)
    const effective = containerWidth - totalGap
    return effective > 0 ? effective / columns : 0
  }, [containerWidth, columns, gap])
  const unitColumnWidth = useMemo(() => {
    if (columnWidth <= 0) return 0
    return columnWidth + (columns > 1 ? gap : 0)
  }, [columnWidth, columns, gap])
  const unitRowHeight = useMemo(() => rowHeight + gap, [rowHeight, gap])

  useEffect(() => {
    if (!resizing) return
    function handlePointerMove(event: PointerEvent) {
      if (!resizing) return
      if (!unitColumnWidth || !unitRowHeight) return
      const deltaX = event.clientX - resizing.originX
      const deltaY = event.clientY - resizing.originY
      const deltaCols = Math.round(deltaX / unitColumnWidth)
      const deltaRows = Math.round(deltaY / unitRowHeight)
      let nextW = resizing.initialWidth + deltaCols
      let nextH = resizing.initialHeight + deltaRows
      nextW = Math.min(resizing.maxW, Math.max(resizing.minW, nextW))
      nextH = Math.min(resizing.maxH, Math.max(resizing.minH, nextH))
      const previous = lastResizeSizeRef.current
      if (!previous || previous.w !== nextW || previous.h !== nextH) {
        lastResizeSizeRef.current = { w: nextW, h: nextH }
        onResize?.(resizing.key, { w: nextW, h: nextH })
      }
    }
    function handlePointerUp() {
      if (resizing) {
        const finalSize = lastResizeSizeRef.current ?? {
          w: resizing.initialWidth,
          h: resizing.initialHeight,
        }
        const clampedFinal = {
          w: Math.min(resizing.maxW, Math.max(resizing.minW, finalSize.w)),
          h: Math.min(resizing.maxH, Math.max(resizing.minH, finalSize.h)),
        }
        onResize?.(resizing.key, clampedFinal, { commit: true })
      }
      setResizing(null)
      lastResizeSizeRef.current = null
      window.removeEventListener("pointermove", handlePointerMove)
      window.removeEventListener("pointerup", handlePointerUp)
    }
    window.addEventListener("pointermove", handlePointerMove)
    window.addEventListener("pointerup", handlePointerUp)
    return () => {
      window.removeEventListener("pointermove", handlePointerMove)
      window.removeEventListener("pointerup", handlePointerUp)
    }
  }, [columns, onResize, resizing, unitColumnWidth, unitRowHeight])

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
    useSensor(MouseSensor, { activationConstraint: { distance: 6 } }),
    useSensor(TouchSensor, { activationConstraint: { delay: 120, tolerance: 8 } }),
  )

  const sortableItems = useMemo(() => items.map((item) => item.key), [items])

  const handleDragEnd = (event: DragEndEvent) => {
    if (!editable) return
    const { active, over } = event
    if (!active?.id || !over?.id) return
    if (active.id === over.id) return
    const currentOrder = sortableItems
    const oldIndex = currentOrder.indexOf(String(active.id))
    const newIndex = currentOrder.indexOf(String(over.id))
    if (oldIndex === -1 || newIndex === -1) return
    const nextOrder = arrayMove(currentOrder, oldIndex, newIndex)
    onReorder?.(nextOrder)
  }

  const handleResizePointerDown = (
    event: React.PointerEvent<HTMLDivElement>,
    item: CanvasItem,
  ) => {
    if (!editable || item.static) return
    event.preventDefault()
    event.stopPropagation()
    const layout = item.layout
    const minW = item.minW ?? layout.minW ?? 2
    const minH = item.minH ?? layout.minH ?? 2
    const maxW = item.maxW ?? layout.maxW ?? columns
    const maxH = item.maxH ?? layout.maxH ?? MAX_ROWS
    setResizing({
      key: item.key,
      originX: event.clientX,
      originY: event.clientY,
      initialWidth: layout.w,
      initialHeight: layout.h,
      minW,
      minH,
      maxW,
      maxH,
    })
  }

  const content = (
    <div
      ref={containerRef}
      data-dashboard-ready={ready}
      className={cn(
        "grid transition-all duration-200",
        ready ? "opacity-100" : "opacity-70",
      )}
      style={{
        gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
        gridAutoRows: `${rowHeight}px`,
        gap,
      }}
    >
      {items.map((item) => {
        const layout = item.layout
        const gridStyle: React.CSSProperties = {
          gridColumn: `${layout.x + 1} / span ${layout.w}`,
          gridRow: `${layout.y + 1} / span ${layout.h}`,
        }
        return (
          <SortableTile
            key={item.key}
            id={item.key}
            style={gridStyle}
            className="relative h-full"
            disabled={!editable || Boolean(resizing)}
          >
            <div className="relative h-full">
              {item.element}
              {editable && !item.static ? (
                <div
                  role="presentation"
                  className="absolute bottom-2 right-2 size-4 cursor-se-resize rounded-sm border border-border/60 bg-background/80 shadow ring-1 ring-border/40"
                  onPointerDown={(event) => handleResizePointerDown(event, item)}
                />
              ) : null}
            </div>
          </SortableTile>
        )
      })}
    </div>
  )

  if (!editable) {
    return content
  }

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragEnd={handleDragEnd}
    >
      <SortableContext items={sortableItems}>{content}</SortableContext>
    </DndContext>
  )
}

type SortableTileProps = {
  id: string
  children: ReactNode
  style?: React.CSSProperties
  className?: string
  disabled?: boolean
}

function SortableTile({ id, children, style, className, disabled }: SortableTileProps) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id,
    disabled,
  })
  const combinedStyle: React.CSSProperties = {
    ...style,
    transform: transform ? CSS.Transform.toString(transform) : undefined,
    transition,
    zIndex: isDragging ? 5 : undefined,
  }

  return (
    <div
      ref={setNodeRef}
      style={combinedStyle}
      className={cn(
        "rounded-xl border border-transparent bg-transparent transition",
        isDragging ? "shadow-xl" : "",
        className,
      )}
      {...attributes}
      {...listeners}
      data-key={id}
    >
      {children}
    </div>
  )
}
