import React, {
  memo,
  useEffect,
  useLayoutEffect,
  forwardRef,
  useImperativeHandle,
} from 'react'
import { cn } from '@/utils/cn'
import { useInteractions } from './use-interactions'
import { useGestures } from './use-gestures'
import {
  useDominoStore,
  useDominoStoreInstance,
  useDominoDOMContext,
  DominoRenderHooksContext,
} from './domino-hooks'
import { ElementRenderer } from './element-renderer'
import { SelectionOverlay } from './selection-overlay'
import { useDominoScrollIntoView } from './use-domino-scroll-into-view'
import {
  type Viewport,
  type DominoCanvasProps,
  type ScrollIntoViewOptions,
} from './domino'

export interface DominoCanvasRef {
  scrollIntoView: (elementId: string, options?: ScrollIntoViewOptions) => void
}

/**
 * 拖拽框选框渲染器：独立订阅避免主画布重绘
 */
const MarqueeOverlay = memo(() => {
  const selectionBox = useDominoStore(state => state.selectionBox)
  if (!selectionBox) return null

  return (
    <div
      className='domino-selection-box absolute pointer-events-none z-[1000]'
      style={{
        left: selectionBox.x,
        top: selectionBox.y,
        width: selectionBox.width,
        height: selectionBox.height,
        borderWidth: 'calc(1px / var(--domino-scale))',
        borderStyle: 'solid',
        borderColor: 'color-mix(in srgb, var(--editor-accent) 55%, transparent)',
        backgroundColor: 'color-mix(in srgb, var(--editor-accent) 8%, transparent)',
      }}
    />
  )
})
MarqueeOverlay.displayName = 'MarqueeOverlay'

export const DominoCanvas = memo(
  forwardRef<DominoCanvasRef, DominoCanvasProps>(
    function DominoCanvas(props, ref) {
      const {
        className,
        style,
        minScale = 0.1,
        maxScale = 20,
        padding,
        readOnly = false,
        onClick,
        onSelect,
        onElementClick,
        onTransformEnd,
        onViewportChange,
        onContextMenu,
        onHoverChange,
        onElementsRemove,
        elements: externalElements,
        elementOrder: externalElementOrder,
        renderElement,
        renderElementMetadata,
      } = props
      const store = useDominoStoreInstance()
      const elementOrder = useDominoStore(state => state.elementOrder)

      const setViewport = useDominoStore(state => state.setViewport)
      const setPadding = useDominoStore(state => state.setPadding)
      const setReadOnly = useDominoStore(state => state.setReadOnly)
      const setElementsData = useDominoStore(state => state.setElementsData)

      const hostRef = useDominoDOMContext()
      const localRef = React.useRef<HTMLDivElement>(null)
      const containerRef = (hostRef ||
        localRef) as React.RefObject<HTMLDivElement>

      const worldRef = React.useRef<HTMLDivElement>(null)
      const scrollIntoView = useDominoScrollIntoView(containerRef)

      useImperativeHandle(ref, () => ({
        scrollIntoView,
      }))

      // Sync controlled data
      useLayoutEffect(() => {
        if (externalElements && externalElementOrder) {
          setElementsData(externalElements, externalElementOrder)
        }
      }, [externalElements, externalElementOrder, setElementsData])

      useLayoutEffect(() => {
        if (padding) {
          setPadding(padding)
        }
      }, [padding, setPadding])

      useLayoutEffect(() => {
        setReadOnly(readOnly)
      }, [readOnly, setReadOnly])

      useEffect(() => {
        if (props.initViewport !== undefined) {
          setViewport(props.initViewport)
        }
      }, [])

      // Fast-path for viewport updates: bypass React renders
      useEffect(() => {
        const updateTransform = (v: Viewport) => {
          if (worldRef.current) {
            const x = Math.round(v.x * 100) / 100
            const y = Math.round(v.y * 100) / 100
            const s = Math.round(v.scale * 10000) / 10000
            worldRef.current.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${s})`
          }

          containerRef.current?.style.setProperty(
            '--domino-scale',
            (Math.round(v.scale * 10000) / 10000).toString(),
          )
        }

        // Set initial state
        updateTransform(store.getState().viewport)

        // Subscribe to changes
        let lastViewport = store.getState().viewport
        const unsubscribe = store.subscribe(state => {
          const v = state.viewport
          if (v === lastViewport) return
          lastViewport = v
          updateTransform(v)
          onViewportChange?.(v)
        })
        return unsubscribe
      }, [store, onViewportChange])

      useEffect(() => {
        let lastSelectedIds = store.getState().selectedIds
        return store.subscribe(state => {
          const ids = state.selectedIds
          if (ids === lastSelectedIds) return
          lastSelectedIds = ids
          onSelect?.(ids)
        })
      }, [store, onSelect])

      // Subscribe to removals
      useEffect(() => {
        let lastOrder = store.getState().elementOrder
        return store.subscribe(state => {
          const newOrder = state.elementOrder
          if (newOrder === lastOrder) return
          const removedIds = lastOrder.filter(id => !newOrder.includes(id))
          lastOrder = newOrder
          if (removedIds.length > 0) {
            onElementsRemove?.(removedIds)
          }
        })
      }, [store, onElementsRemove])

      // Subscribe to hover changes
      useEffect(() => {
        let lastHoveredId = store.getState().hoveredElementId
        return store.subscribe(state => {
          const id = state.hoveredElementId
          if (id === lastHoveredId) return
          lastHoveredId = id
          onHoverChange?.(id)
        })
      }, [store, onHoverChange])

      // Force zero scroll to prevent coordinate misalignment
      useEffect(() => {
        const container = containerRef.current
        if (!container) return

        const resetScroll = () => {
          if (container.scrollTop !== 0) container.scrollTop = 0
          if (container.scrollLeft !== 0) container.scrollLeft = 0
        }

        container.addEventListener('scroll', resetScroll)
        // Initial reset
        resetScroll()

        return () => {
          container.removeEventListener('scroll', resetScroll)
        }
      }, [])

      useGestures(containerRef, { minScale, maxScale })

      const interactionOptions = React.useMemo(
        () => ({
          minScale,
          maxScale,
          onTransformEnd,
          onClick,
          onElementClick,
        }),
        [minScale, maxScale, onTransformEnd, onClick, onElementClick],
      )

      const {
        onDoubleClickCapture,
        onPointerDown,
        onPointerMove,
        onPointerUp,
        onPointerCancel,
      } = useInteractions(containerRef, interactionOptions)
      const renderHooksValue = React.useMemo(
        () => ({
          renderElement,
          renderElementMetadata,
        }),
        [renderElement, renderElementMetadata],
      )
      const mode = useDominoStore(state => state.mode)

      return (
        <DominoRenderHooksContext.Provider value={renderHooksValue}>
          <div
            ref={containerRef}
            className={cn(
              'domino-canvas-root domino-canvas relative w-full h-full overflow-hidden bg-muted outline-none select-none touch-none',
              `${mode}-mode`,
              className,
            )}
            style={style}
            tabIndex={-1}
            onContextMenu={e => {
              e.preventDefault()
              const target = e.target as HTMLElement
              const elementEl = target.closest('.domino-element')
              onContextMenu?.(e, elementEl?.id)
            }}
            onPointerDown={onPointerDown}
            onDoubleClick={onDoubleClickCapture}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onPointerCancel={onPointerCancel}
          >
            <div
              ref={worldRef}
              className='absolute origin-top-left pointer-events-none'
            >
              {elementOrder.map(id => (
                <ElementRenderer key={id} id={id} />
              ))}

              <SelectionOverlay />

              <MarqueeOverlay />
            </div>

            {/* Overlay UI Layer - Separated to prevent event bubbling to interaction layer */}
            <div className='absolute inset-0 pointer-events-none overflow-hidden'>
              {props.children}
            </div>
          </div>
        </DominoRenderHooksContext.Provider>
      )
    },
  ),
)
