import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Image as KonvaImage, Layer, Rect, Stage, Text } from 'react-konva'
import { motion, useDragControls } from 'motion/react'
import { useStudio } from './StudioContext'
import {
  clamp,
  confidenceStroke,
  getCellKey,
  getPages,
  getTablesForPage,
  heatmapColor,
  isSameCell,
} from './studioUtils'

import API from '../../api'

function useLoadedImage(src, onError) {
  const [image, setImage] = useState(null)

  useEffect(() => {
    const nextImage = new window.Image()
    nextImage.crossOrigin = 'anonymous'
    nextImage.onload = () => setImage(nextImage)
    nextImage.onerror = () => {
      setImage(null)
      onError?.()
    }
    nextImage.src = src
    return () => {
      nextImage.onload = null
      nextImage.onerror = null
    }
  }, [onError, src])

  return image
}

function TableLabel({ table, bbox, scale }) {
  const label = `Table ${table.table_id + 1}`
  const x = bbox[0] * scale + 8
  const y = Math.max(12, bbox[1] * scale - 18)

  return (
    <Text
      x={x}
      y={y}
      text={label}
      fontSize={12}
      fontStyle="bold"
      fill="#1F2937"
    />
  )
}

function intersectionArea(a, b) {
  const x1 = Math.max(a[0], b[0])
  const y1 = Math.max(a[1], b[1])
  const x2 = Math.min(a[2], b[2])
  const y2 = Math.min(a[3], b[3])
  return Math.max(0, x2 - x1) * Math.max(0, y2 - y1)
}

function centerOfBox(box) {
  return [(box[0] + box[2]) / 2, (box[1] + box[3]) / 2]
}

function resolveOcrItemCell(table, item) {
  const itemBox = Array.isArray(item?.bbox) && item.bbox.length === 4 ? item.bbox : null
  const cells = Array.isArray(table?.cells) ? table.cells : []
  if (!itemBox || !cells.length) {
    return null
  }

  // Prefer explicit assignment if present in payload.
  if (Number.isFinite(item?.row) && Number.isFinite(item?.col)) {
    return { row: Number(item.row), col: Number(item.col) }
  }

  let bestOverlap = null
  let bestArea = 0
  for (const cell of cells) {
    const cellBox = Array.isArray(cell?.bbox) && cell.bbox.length === 4 ? cell.bbox : null
    if (!cellBox) {
      continue
    }
    const area = intersectionArea(itemBox, cellBox)
    if (area > bestArea) {
      bestArea = area
      bestOverlap = cell
    }
  }
  if (bestOverlap && bestArea > 0) {
    return { row: bestOverlap.row, col: bestOverlap.col }
  }

  // Fallback: nearest cell center when overlap is zero due to OCR/table bbox drift.
  const [cx, cy] = centerOfBox(itemBox)
  let nearest = null
  let nearestDistSq = Number.POSITIVE_INFINITY
  for (const cell of cells) {
    const cellBox = Array.isArray(cell?.bbox) && cell.bbox.length === 4 ? cell.bbox : null
    if (!cellBox) {
      continue
    }
    const [ccx, ccy] = centerOfBox(cellBox)
    const dx = ccx - cx
    const dy = ccy - cy
    const distSq = dx * dx + dy * dy
    if (distSq < nearestDistSq) {
      nearestDistSq = distSq
      nearest = cell
    }
  }
  return nearest ? { row: nearest.row, col: nearest.col } : null
}

function normalizeBbox(bbox, maxWidth, maxHeight, minSize = 12) {
  let [x1, y1, x2, y2] = bbox.map((value) => Number(value) || 0)
  x1 = clamp(x1, 0, maxWidth)
  x2 = clamp(x2, 0, maxWidth)
  y1 = clamp(y1, 0, maxHeight)
  y2 = clamp(y2, 0, maxHeight)

  let left = Math.min(x1, x2)
  let right = Math.max(x1, x2)
  let top = Math.min(y1, y2)
  let bottom = Math.max(y1, y2)

  if ((right - left) < minSize) {
    const expand = (minSize - (right - left)) / 2
    left = clamp(left - expand, 0, maxWidth)
    right = clamp(right + expand, 0, maxWidth)
    if ((right - left) < minSize) {
      if (left <= 0) right = clamp(minSize, 0, maxWidth)
      else left = clamp(maxWidth - minSize, 0, maxWidth)
    }
  }
  if ((bottom - top) < minSize) {
    const expand = (minSize - (bottom - top)) / 2
    top = clamp(top - expand, 0, maxHeight)
    bottom = clamp(bottom + expand, 0, maxHeight)
    if ((bottom - top) < minSize) {
      if (top <= 0) bottom = clamp(minSize, 0, maxHeight)
      else top = clamp(maxHeight - minSize, 0, maxHeight)
    }
  }

  return [left, top, right, bottom]
}

function expandBbox(bbox, padding, maxWidth, maxHeight) {
  const pad = Math.max(0, Number(padding) || 0)
  if (!pad) {
    return normalizeBbox(bbox, maxWidth, maxHeight)
  }
  const [x1, y1, x2, y2] = bbox
  return normalizeBbox([x1 - pad, y1 - pad, x2 + pad, y2 + pad], maxWidth, maxHeight)
}

function resizeBbox(startBbox, startPoint, currentPoint, handle, maxWidth, maxHeight) {
  const [sx1, sy1, sx2, sy2] = startBbox
  const dx = currentPoint.x - startPoint.x
  const dy = currentPoint.y - startPoint.y

  let x1 = sx1
  let y1 = sy1
  let x2 = sx2
  let y2 = sy2

  if (handle.includes('w')) x1 = sx1 + dx
  if (handle.includes('e')) x2 = sx2 + dx
  if (handle.includes('n')) y1 = sy1 + dy
  if (handle.includes('s')) y2 = sy2 + dy

  return normalizeBbox([x1, y1, x2, y2], maxWidth, maxHeight)
}

function StudioPageCanvas({
  page,
  tables,
  bboxOverrides,
  stageWidth,
  onImageError,
  onCopyText,
  hoveredCell,
  onHoverCellChange,
  onTableBBoxChange,
  onSelectionEnd,
}) {
  const { state, dispatch } = useStudio()
  const [hoveredOcrKey, setHoveredOcrKey] = useState(null)
  // Use a ref (not state) for resizeDrag so that Konva mouse-move/up handlers
  // always read the *current* value — state updates are async and would lag.
  const resizeDragRef = useRef(null)
  const [, forceUpdate] = useState(0)
  const setResizeDrag = useCallback((val) => {
    resizeDragRef.current = typeof val === 'function' ? val(resizeDragRef.current) : val
    forceUpdate((n) => n + 1)
  }, [])
  const imageUrl = `${API}/api/image/${page.jobId}?page=${page.page_index}`
  const image = useLoadedImage(imageUrl, onImageError)
  const [pageWidth, pageHeight] = page.image_size || [0, 0]
  const safeWidth = Math.max(1, pageWidth || image?.naturalWidth || stageWidth || 1)
  const safeHeight = Math.max(1, pageHeight || image?.naturalHeight || 1)
  const drawnWidth = stageWidth ? Math.min(safeWidth, Math.max(240, stageWidth)) : safeWidth
  const scale = drawnWidth / safeWidth
  const stageHeight = safeHeight * scale

  const tableBboxFor = useCallback((table) => {
    const override = bboxOverrides?.[table.table_id]
    if (Array.isArray(override) && override.length === 4) {
      return override
    }
    return table.bbox
  }, [bboxOverrides])

  const visibleTables = tables.filter((table) => (table.td_score ?? 0) >= state.tdThreshold)
  const isSelectionTool = state.activeTool === 'marquee' || state.activeTool === 'tableSelect'

  const selectionRect = state.selection && state.selection.pageIndex === page.page_index
    ? {
      left: Math.min(state.selection.start.x, state.selection.current.x),
      top: Math.min(state.selection.start.y, state.selection.current.y),
      right: Math.max(state.selection.start.x, state.selection.current.x),
      bottom: Math.max(state.selection.start.y, state.selection.current.y),
    }
    : null

  const handleMouseDown = (e) => {
    if (!isSelectionTool) return
    const stage = e.target.getStage()
    const pos = stage?.getPointerPosition()
    if (!pos) {
      return
    }
    dispatch({
      type: 'setSelection',
      selection: {
        pageIndex: page.page_index,
        start: { x: pos.x / scale, y: pos.y / scale },
        current: { x: pos.x / scale, y: pos.y / scale },
      }
    })
  }

  const handleMouseMove = (e) => {
    const drag = resizeDragRef.current
    if (drag) {
      const stage = e.target.getStage()
      const pos = stage?.getPointerPosition()
      if (!pos) {
        return
      }
      const nextBBox = resizeBbox(
        drag.startBBox,
        drag.startPoint,
        { x: pos.x / scale, y: pos.y / scale },
        drag.handle,
        safeWidth,
        safeHeight,
      )
      onTableBBoxChange?.(drag.tableId, nextBBox)
      return
    }
    if (!isSelectionTool || !state.selection || state.selection.pageIndex !== page.page_index) return
    const stage = e.target.getStage()
    const pos = stage?.getPointerPosition()
    if (!pos) {
      return
    }
    dispatch({
      type: 'setSelection',
      selection: {
        ...state.selection,
        current: { x: pos.x / scale, y: pos.y / scale },
      }
    })
  }

  const handleMouseUp = () => {
    if (resizeDragRef.current) {
      setResizeDrag(null)
      return
    }
    if (!isSelectionTool || !state.selection || state.selection.pageIndex !== page.page_index) return
    const { start, current } = state.selection
    const rawBbox = [
      Math.min(start.x, current.x),
      Math.min(start.y, current.y),
      Math.max(start.x, current.x),
      Math.max(start.y, current.y),
    ]
    // Ignore tiny / accidental drags
    if (Math.abs(rawBbox[2] - rawBbox[0]) <= 5 || Math.abs(rawBbox[3] - rawBbox[1]) <= 5) {
      dispatch({ type: 'setSelection', selection: null })
      return
    }

    const bbox = expandBbox(rawBbox, state.selectionPadding, safeWidth, safeHeight)
    // For marquee OCR, fire immediately on mouse release — no button needed.
    if (state.activeTool === 'marquee') {
      onSelectionEnd?.({
        bbox,
        clientX: ((bbox[0] + bbox[2]) / 2) * scale,
        clientY: (bbox[1] * scale) - 16,
      })
    }
    // For tableSelect we leave the selection visible so the user can confirm.
  }

  useEffect(() => {
    const handleGlobalPointerUp = () => {
      if (resizeDragRef.current) {
        setResizeDrag(null)
      }
    }
    window.addEventListener('mouseup', handleGlobalPointerUp)
    return () => window.removeEventListener('mouseup', handleGlobalPointerUp)
  }, [setResizeDrag])

  return (
    <div className="document-page-card" style={{ position: 'relative' }}>
      <Stage
        width={drawnWidth}
        height={stageHeight}
        className="document-stage"
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
        onMouseLeave={() => {
          onHoverCellChange?.(null)
          setResizeDrag(null)
        }}
      >
        <Layer>
          {image && (
            <KonvaImage
              image={image}
              width={drawnWidth}
              height={stageHeight}
            />
          )}

          {visibleTables.map((table) => {
            const isActiveTable = state.activeTable === table.table_id
            const [x1, y1, x2, y2] = tableBboxFor(table)
            const width = (x2 - x1) * scale
            const height = (y2 - y1) * scale
            const x = x1 * scale
            const y = y1 * scale
            return (
              <Rect
                key={`table-${table.table_id}`}
                x={x}
                y={y}
                width={width}
                height={height}
                stroke={isActiveTable ? '#DD6B20' : confidenceStroke(table.td_score)}
                strokeWidth={isActiveTable ? 3 : 2}
                dash={isActiveTable ? [] : [8, 6]}
                fill={state.showHeatmap ? heatmapColor(table.td_score, isActiveTable ? 0.18 : 0.09) : 'transparent'}
                onClick={() => dispatch({ type: 'setActiveTable', tableId: table.table_id })}
              />
            )
          })}

          {visibleTables.map((table) => (
            <TableLabel key={`label-${table.table_id}`} table={table} bbox={tableBboxFor(table)} scale={scale} />
          ))}



          {visibleTables.flatMap((table) => (table.cells || []).map((cell) => {
            const key = getCellKey(table.table_id, cell.row, cell.col)
            const selected = isSameCell(state.activeCell, {
              tableId: table.table_id,
              row: cell.row,
              col: cell.col,
            })
            const hovered = isSameCell(hoveredCell, {
              tableId: table.table_id,
              row: cell.row,
              col: cell.col,
            })
            const [x1, y1, x2, y2] = cell.bbox
            const x = x1 * scale
            const y = y1 * scale
            const width = (x2 - x1) * scale
            const height = (y2 - y1) * scale
            const visibleStroke = state.showTableGrid ? '#E5E7EB' : 'transparent'
            const visibleFill = hovered
              ? 'rgba(59, 130, 246, 0.2)'
              : selected
              ? 'rgba(221, 107, 32, 0.18)'
              : state.showHeatmap && state.showTableGrid
                ? heatmapColor(cell.ocr_score, 0.14)
                : 'transparent'
            return (
              <Rect
                key={`cell-${key}`}
                x={x}
                y={y}
                width={width}
                height={height}
                stroke={hovered ? '#2563EB' : selected ? '#DD6B20' : visibleStroke}
                strokeWidth={hovered || selected ? 2 : 1}
                fill={visibleFill}
                onMouseEnter={() => {
                  onHoverCellChange?.({
                    tableId: table.table_id,
                    row: cell.row,
                    col: cell.col,
                    pageIndex: page.page_index,
                  })
                  dispatch({ type: 'setActiveTable', tableId: table.table_id })
                  dispatch({ type: 'setActiveCell', cell: { tableId: table.table_id, row: cell.row, col: cell.col } })
                }}
                onMouseLeave={() => onHoverCellChange?.(null)}
                onClick={() => {
                  dispatch({ type: 'setActiveTable', tableId: table.table_id })
                  dispatch({ type: 'setActiveCell', cell: { tableId: table.table_id, row: cell.row, col: cell.col } })
                }}
                listening
                perfectDrawEnabled={false}
              />
            )
          }))}

          {state.showTextBoxes && visibleTables.flatMap((table) => (table.ocr_items || []).map((item, index) => {
            const key = `${table.table_id}:${index}`
            const [x1, y1, x2, y2] = item.bbox
            const x = x1 * scale
            const y = y1 * scale
            const width = Math.max(1, (x2 - x1) * scale)
            const height = Math.max(1, (y2 - y1) * scale)
            const hovered = hoveredOcrKey === key
            return (
              <Rect
                key={`ocr-${key}`}
                x={x}
                y={y}
                width={width}
                height={height}
                stroke={hovered ? '#DD6B20' : 'rgba(221, 107, 32, 0.35)'}
                strokeWidth={hovered ? 1.5 : 1}
                fill={hovered ? 'rgba(221, 107, 32, 0.15)' : 'transparent'}
                onMouseEnter={() => {
                  setHoveredOcrKey(key)
                  const targetCell = resolveOcrItemCell(table, item)
                  if (targetCell) {
                    dispatch({ type: 'setActiveTable', tableId: table.table_id })
                    onHoverCellChange?.({
                      tableId: table.table_id,
                      row: targetCell.row,
                      col: targetCell.col,
                      pageIndex: page.page_index,
                    })
                  } else {
                    onHoverCellChange?.(null)
                  }
                }}
                onMouseLeave={() => {
                  setHoveredOcrKey((current) => (current === key ? null : current))
                  onHoverCellChange?.(null)
                }}
                onClick={(event) => onCopyText(item.text || '', event.evt.clientX, event.evt.clientY)}
              />
            )
          }))}

          {/* Corners rendered LAST so they stay interactive on top of cells */}
          {state.activeTool === 'pointer' && visibleTables.map((table) => {
            if (state.activeTable !== table.table_id) {
              return null
            }
            const [x1, y1, x2, y2] = tableBboxFor(table)
            const corners = [
              { key: 'nw', x: x1, y: y1, cursor: 'nwse-resize' },
              { key: 'ne', x: x2, y: y1, cursor: 'nesw-resize' },
              { key: 'sw', x: x1, y: y2, cursor: 'nesw-resize' },
              { key: 'se', x: x2, y: y2, cursor: 'nwse-resize' },
            ]
            return corners.map((corner) => (
              <Rect
                key={`resize-${table.table_id}-${corner.key}`}
                x={(corner.x * scale) - 7}
                y={(corner.y * scale) - 7}
                width={14}
                height={14}
                fill="#2563EB"
                stroke="#FFFFFF"
                strokeWidth={2}
                cornerRadius={3}
                onMouseDown={(event) => {
                  event.cancelBubble = true
                  const stage = event.target.getStage()
                  const pos = stage?.getPointerPosition()
                  if (!pos) {
                    return
                  }
                  resizeDragRef.current = {
                    tableId: table.table_id,
                    handle: corner.key,
                    startPoint: { x: pos.x / scale, y: pos.y / scale },
                    startBBox: tableBboxFor(table),
                  }
                }}
                onMouseEnter={(event) => {
                  const container = event.target.getStage()?.container()
                  if (container) {
                    container.style.cursor = corner.cursor
                  }
                }}
                onMouseLeave={(event) => {
                  const container = event.target.getStage()?.container()
                  if (container) {
                    container.style.cursor = 'default'
                  }
                }}
              />
            ))
          })}

          {selectionRect && (
            <Rect
              x={selectionRect.left * scale}
              y={selectionRect.top * scale}
              width={(selectionRect.right - selectionRect.left) * scale}
              height={(selectionRect.bottom - selectionRect.top) * scale}
              fill="rgba(229, 115, 0, 0.1)"
              stroke="#E57300"
              strokeWidth={1}
              dash={[4, 2]}
            />
          )}
        </Layer>
      </Stage>
      
      {selectionRect && state.activeTool === 'tableSelect' && (
        <div 
          className="marquee-fab"
          style={{
            position: 'absolute',
            zIndex: 50,
            left: (selectionRect.right * scale) + 10,
            top: Math.max(6, (selectionRect.top * scale) - 8),
          }}
        >
          <button 
            type="button" 
            className="btn btn-sm btn-primary"
            onClick={() => {
              onSelectionEnd?.({
                bbox: [selectionRect.left, selectionRect.top, selectionRect.right, selectionRect.bottom],
                clientX: ((selectionRect.left + selectionRect.right) / 2) * scale,
                clientY: (selectionRect.top * scale) - 16,
              })
            }}
          >
            Run TSR + OCR here
          </button>
        </div>
      )}
    </div>
  )
}

export default function StudioDocumentPane({
  annotation,
  jobId,
  onCopyText,
  hoveredCell,
  onHoverCellChange,
  onReprocessTable,
  onDeleteTable,
  onAddCell,
  onDeleteCell,
  onSelectionEnd,
}) {
  const { state, dispatch } = useStudio()
  const scrollRef = useRef(null)
  const measureRef = useRef(null)
  const pageRefs = useRef([])
  const [stageWidth, setStageWidth] = useState(720)
  const [tableBboxOverrides, setTableBboxOverrides] = useState({})
  const [isReprocessingTable, setIsReprocessingTable] = useState(false)
  const [isDeletingTable, setIsDeletingTable] = useState(false)
  const [isAddingCell, setIsAddingCell] = useState(false)
  const [isDeletingCell, setIsDeletingCell] = useState(false)
  const [pendingDelete, setPendingDelete] = useState(false)

  // Palette State: Draggable & Resizable
  const [paletteSize, setPaletteSize] = useState({ width: 320, height: 420 })
  const paletteRef = useRef(null)
  const dragControls = useDragControls()

  const handleResizeStart = (e) => {
    e.preventDefault()
    e.stopPropagation()
    const startX = e.pageX
    const startY = e.pageY
    const startWidth = paletteSize.width
    const startHeight = paletteSize.height

    const onMove = (moveEvent) => {
      setPaletteSize({
        width: Math.max(240, startWidth + (moveEvent.pageX - startX)),
        height: Math.max(200, startHeight + (moveEvent.pageY - startY))
      })
    }

    const onUp = () => {
      window.removeEventListener('mousemove', onMove)
      window.removeEventListener('mouseup', onUp)
    }

    window.addEventListener('mousemove', onMove)
    window.addEventListener('mouseup', onUp)
  }

  const pages = useMemo(
    () => getPages(annotation).map((page) => ({ ...page, jobId })),
    [annotation, jobId],
  )
  const activePageTables = useMemo(
    () => getTablesForPage(annotation, state.activePage),
    [annotation, state.activePage],
  )
  const visibleActivePageTables = useMemo(
    () => activePageTables.filter((table) => (table.td_score ?? 0) >= state.tdThreshold),
    [activePageTables, state.tdThreshold],
  )
  const selectedVisibleTable = useMemo(
    () => visibleActivePageTables.find((table) => table.table_id === state.activeTable) || null,
    [visibleActivePageTables, state.activeTable],
  )
  const activeCellInSelectedTable = useMemo(() => {
    if (!selectedVisibleTable || !state.activeCell) {
      return null
    }
    if (state.activeCell.tableId !== selectedVisibleTable.table_id) {
      return null
    }
    return state.activeCell
  }, [selectedVisibleTable, state.activeCell])

  const getTableBbox = useCallback((table) => {
    const override = tableBboxOverrides[table.table_id]
    return Array.isArray(override) && override.length === 4 ? override : table.bbox
  }, [tableBboxOverrides])

  const clearTableBboxOverride = useCallback((tableId) => {
    setTableBboxOverrides((current) => {
      if (!(tableId in current)) {
        return current
      }
      const next = { ...current }
      delete next[tableId]
      return next
    })
  }, [])

  useEffect(() => {
    const node = measureRef.current
    if (!node) {
      return undefined
    }
    const observer = new ResizeObserver((entries) => {
      const entry = entries[0]
      const width = entry?.contentRect?.width || node.clientWidth
      setStageWidth(clamp(width - 48, 280, 1200))
    })
    observer.observe(node)
    return () => observer.disconnect()
  }, [])

  useEffect(() => {
    const root = scrollRef.current
    const targets = pageRefs.current.filter(Boolean)
    if (!root || targets.length === 0) {
      return undefined
    }

    const observer = new IntersectionObserver(
      (entries) => {
        const visibleEntries = entries.filter((entry) => entry.isIntersecting && entry.intersectionRatio >= 0.5)
        if (!visibleEntries.length) {
          return
        }
        const mostVisible = visibleEntries.reduce((best, entry) => (
          entry.intersectionRatio > best.intersectionRatio ? entry : best
        ))
        const pageIndex = Number(mostVisible.target.getAttribute('data-page-index'))
        if (!Number.isNaN(pageIndex) && pageIndex !== state.activePage) {
          dispatch({ type: 'setActivePage', page: pageIndex })
        }
      },
      {
        root,
        threshold: [0.5, 0.75],
      },
    )

    for (const target of targets) {
      observer.observe(target)
    }

    return () => observer.disconnect()
  }, [dispatch, state.activePage, pages.length])

  useEffect(() => {
    const validIds = new Set((annotation?.tables || []).map((table) => table.table_id))
    setTableBboxOverrides((current) => {
      const next = {}
      let changed = false
      for (const [tableId, bbox] of Object.entries(current)) {
        const numericId = Number(tableId)
        if (validIds.has(numericId)) {
          next[tableId] = bbox
        } else {
          changed = true
        }
      }
      return changed ? next : current
    })
  }, [annotation])

  const handleTableBBoxChange = useCallback((tableId, bbox) => {
    setTableBboxOverrides((current) => ({
      ...current,
      [tableId]: bbox,
    }))
  }, [])

  const handleReprocessSelectedTable = useCallback(async () => {
    if (!selectedVisibleTable || !onReprocessTable || isReprocessingTable) {
      return
    }
    setIsReprocessingTable(true)
    try {
      const ok = await onReprocessTable({
        tableId: selectedVisibleTable.table_id,
        pageIndex: state.activePage,
        bbox: getTableBbox(selectedVisibleTable),
      })
      if (ok) {
        clearTableBboxOverride(selectedVisibleTable.table_id)
      }
    } finally {
      setIsReprocessingTable(false)
    }
  }, [
    clearTableBboxOverride,
    getTableBbox,
    isReprocessingTable,
    onReprocessTable,
    selectedVisibleTable,
    state.activePage,
  ])

  const handleDeleteSelectedTable = useCallback(async () => {
    if (!selectedVisibleTable || !onDeleteTable || isDeletingTable) {
      return
    }
    // First click arms the delete — second click (Confirm) fires it.
    if (!pendingDelete) {
      setPendingDelete(true)
      return
    }
    setPendingDelete(false)
    setIsDeletingTable(true)
    try {
      const ok = await onDeleteTable({
        tableId: selectedVisibleTable.table_id,
      })
      if (ok) {
        clearTableBboxOverride(selectedVisibleTable.table_id)
      }
    } finally {
      setIsDeletingTable(false)
    }
  }, [clearTableBboxOverride, isDeletingTable, onDeleteTable, pendingDelete, selectedVisibleTable])

  const handleAddCellToSelectedTable = useCallback(async () => {
    if (!selectedVisibleTable || !onAddCell || isAddingCell || isDeletingCell || isDeletingTable || isReprocessingTable) {
      return
    }
    setIsAddingCell(true)
    try {
      await onAddCell({ tableId: selectedVisibleTable.table_id })
    } finally {
      setIsAddingCell(false)
    }
  }, [isAddingCell, isDeletingCell, isDeletingTable, isReprocessingTable, onAddCell, selectedVisibleTable])

  const handleDeleteSelectedCell = useCallback(async () => {
    if (!activeCellInSelectedTable || !onDeleteCell || isDeletingCell || isAddingCell || isDeletingTable || isReprocessingTable) {
      return
    }
    setIsDeletingCell(true)
    try {
      await onDeleteCell({
        tableId: activeCellInSelectedTable.tableId,
        row: activeCellInSelectedTable.row,
        col: activeCellInSelectedTable.col,
      })
    } finally {
      setIsDeletingCell(false)
    }
  }, [activeCellInSelectedTable, isAddingCell, isDeletingCell, isDeletingTable, isReprocessingTable, onDeleteCell])

  return (
    <section className="studio-panel studio-document-pane">
      <motion.div 
        drag
        dragMomentum={false}
        dragControls={dragControls}
        dragListener={false}
        style={{ 
          width: paletteSize.width, 
          height: paletteSize.height,
          top: 16,
          left: 16
        }}
        className="studio-palette"
        ref={paletteRef}
      >
        <div 
          className="palette-header" 
          onPointerDown={(e) => dragControls.start(e)}
        >
          <div className="palette-drag-dot" />
        </div>

        <div className="palette-content">
          <label className="studio-threshold-control">
            <span className="studio-threshold-label">Confidence sensitivity</span>
            <div className="studio-threshold-row">
              <input
                type="range"
                min="0"
                max="1"
                step="0.01"
                value={state.tdThreshold}
                onChange={(event) => dispatch({ type: 'setThreshold', value: Number(event.target.value) })}
              />
              <span className="studio-threshold-value">{Math.round(state.tdThreshold * 100)}%</span>
            </div>
          </label>

          <label className="studio-threshold-control">
            <span className="studio-threshold-label">Selection padding</span>
            <div className="studio-threshold-row">
              <input
                type="range"
                min="0"
                max="80"
                step="1"
                value={state.selectionPadding}
                onChange={(event) => dispatch({ type: 'setPadding', value: Number(event.target.value) })}
              />
              <span className="studio-threshold-value">{Math.round(state.selectionPadding)}px</span>
            </div>
          </label>

          <div className="studio-toolbar-toggles">
            <button
              type="button"
              className={`studio-toggle${state.showTableGrid ? ' is-active' : ''}`}
              onClick={() => dispatch({ type: 'toggleFlag', flag: 'showTableGrid' })}
            >
              Grid
            </button>
            <button
              type="button"
              className={`studio-toggle${state.showTextBoxes ? ' is-active' : ''}`}
              onClick={() => dispatch({ type: 'toggleFlag', flag: 'showTextBoxes' })}
            >
              Text
            </button>
            <button
              type="button"
              className={`studio-toggle${state.showHeatmap ? ' is-active' : ''}`}
              onClick={() => dispatch({ type: 'toggleFlag', flag: 'showHeatmap' })}
            >
              Heatmap
            </button>
          </div>

          <div className="studio-toolbar-toggles studio-tool-select">
            <button
              type="button"
              className={`studio-toggle${state.activeTool === 'pointer' ? ' is-active' : ''}`}
              onClick={() => dispatch({ type: 'setTool', tool: 'pointer' })}
            >
              Pointer
            </button>
            <button
              type="button"
              className={`studio-toggle${state.activeTool === 'marquee' ? ' is-active' : ''}`}
              onClick={() => dispatch({ type: 'setTool', tool: 'marquee' })}
            >
              Marquee
            </button>
            <button
              type="button"
              className={`studio-toggle${state.activeTool === 'tableSelect' ? ' is-active' : ''}`}
              onClick={() => dispatch({ type: 'setTool', tool: 'tableSelect' })}
            >
              Draw Table
            </button>
          </div>

          <div className="studio-toolbar-toggles">
            <button
              type="button"
              className="studio-toggle"
              disabled={!selectedVisibleTable || isReprocessingTable || isDeletingTable || isAddingCell || isDeletingCell}
              onClick={handleReprocessSelectedTable}
              title="Re-run TSR and OCR for selected table box"
            >
              {isReprocessingTable ? 'Running...' : 'Run TSR + OCR'}
            </button>
            <button
              type="button"
              className="studio-toggle"
              disabled={!selectedVisibleTable || isDeletingTable || isReprocessingTable || isAddingCell || isDeletingCell}
              onClick={handleDeleteSelectedTable}
              title="Delete selected table"
              style={{ color: pendingDelete ? 'var(--danger)' : 'inherit' }}
            >
              {isDeletingTable ? 'Deleting…' : pendingDelete ? 'Confirm' : 'Delete'}
            </button>
          </div>

          <div className="studio-toolbar-toggles">
            <button
              type="button"
              className="studio-toggle"
              disabled={!selectedVisibleTable || isAddingCell || isDeletingCell || isDeletingTable || isReprocessingTable}
              onClick={handleAddCellToSelectedTable}
              title="Add a blank cell into first available slot"
            >
              {isAddingCell ? 'Adding…' : 'Add Cell'}
            </button>
            <button
              type="button"
              className="studio-toggle"
              disabled={!activeCellInSelectedTable || isDeletingCell || isAddingCell || isDeletingTable || isReprocessingTable}
              onClick={handleDeleteSelectedCell}
              title="Delete currently selected cell"
            >
              {isDeletingCell ? 'Deleting…' : 'Delete Cell'}
            </button>
          </div>
        </div>

        <div className="palette-resize-handle" onMouseDown={handleResizeStart} />
      </motion.div>

      <div className="studio-document-scroll" ref={scrollRef}>
        <div className="studio-document-stack" ref={measureRef}>
          {pages.map((page, index) => (
            <section
              key={page.page_index}
              ref={(node) => { pageRefs.current[index] = node }}
              className="document-page"
              data-page-index={page.page_index}
            >
              <div className="document-page-meta">Page {page.page_index + 1}</div>
              <StudioPageCanvas
                page={page}
                tables={getTablesForPage(annotation, page.page_index)}
                bboxOverrides={tableBboxOverrides}
                stageWidth={stageWidth}
                onImageError={() => dispatch({ type: 'incrementErrors' })}
                onCopyText={onCopyText}
                hoveredCell={hoveredCell?.pageIndex === page.page_index ? hoveredCell : null}
                onHoverCellChange={onHoverCellChange}
                onTableBBoxChange={handleTableBBoxChange}
                onSelectionEnd={onSelectionEnd}
              />
            </section>
          ))}
        </div>
      </div>
    </section>
  )
}
