'use client'

import { useEffect, useRef, useState } from 'react'
import type { MutableRefObject } from 'react'
import type { Canvas, FabricImage, FabricObject, Textbox } from 'fabric'
import type { CSSProperties } from 'react'

export type CanvasElement = {
  id: string
  kind: 'text' | 'square' | 'circle' | 'star' | 'image'
  x: number
  y: number
  width: number
  height: number
  color: string
  opacity: number
  blur: number
  shadowBlur: number
  shadowOpacity: number
  rotation: number
  text?: string
  fontFamily?: string
  fontSize?: number
  align?: 'left' | 'center' | 'right'
  bold?: boolean
  italic?: boolean
  uppercase?: boolean
  src?: string
  name?: string
  layerName?: string
  groupId?: string
  highlight?: boolean
  highlightColor?: string
  outlineColor?: string
  outlineWidth?: number
  flipX?: boolean
  flipY?: boolean
}

export type ElementUpdate = Partial<CanvasElement>
export type CanvasTool = 'select' | 'eyedropper' | 'bucket'
type PresetId = 'feed' | 'tiktok' | 'reels' | 'shorts' | 'youtube'
type LayerObject = FabricObject & { layerId: string; layerKind: CanvasElement['kind']; imageSrc?: string; filterKey?: string }
type FabricCanvasSurfaceProps = {
  elements: CanvasElement[]
  preset: { id: PresetId; width: number; height: number; label: string }
  backgroundColor: string
  checkerboard: boolean
  guides: Set<'thirds' | 'phi' | 'horizon' | 'triangles' | 'center' | 'safe'>
  selectedIds: Set<string>
  tool: CanvasTool
  zoom: number
  canvasRef: MutableRefObject<Canvas | null>
  onSelectionChange: (ids: Set<string>, selectedId: string | null) => void
  onObjectUpdates: (updates: Array<{ id: string; updates: ElementUpdate }>) => void
  onBackgroundSelect: () => void
  onPickColor: (color: string) => void
  onPaint: (targetId?: string) => void
  onZoomChange: (zoom: number) => void
}

const WORKSPACE_MARGIN = 0.18
const MIN_ZOOM = 0.5
const MAX_ZOOM = 2.5

function cssColorFromPixel(data: Uint8ClampedArray) {
  return `#${[data[0], data[1], data[2]].map((value) => value.toString(16).padStart(2, '0')).join('').toUpperCase()}`
}

function textWidth(element: CanvasElement) {
  const fontSize = element.fontSize ?? 48
  const lines = (element.text ?? '').split('\n')
  return Math.max(fontSize, ...lines.map((line) => line.length * fontSize * 0.7)) + 12
}

function getLayerId(object: FabricObject) {
  return (object as unknown as LayerObject).layerId
}

export function FabricCanvasSurface({
  elements,
  preset,
  backgroundColor,
  checkerboard,
  guides,
  selectedIds,
  tool,
  zoom,
  canvasRef,
  onSelectionChange,
  onObjectUpdates,
  onBackgroundSelect,
  onPickColor,
  onPaint,
  onZoomChange,
}: FabricCanvasSurfaceProps) {
  const elementRef = useRef<HTMLCanvasElement>(null)
  const fabricRef = useRef<Canvas | null>(null)
  const objectsRef = useRef(new Map<string, LayerObject>())
  const syncingSelectionRef = useRef(false)
  const [ready, setReady] = useState(false)
  const marginX = Math.round(preset.width * WORKSPACE_MARGIN)
  const marginY = Math.round(preset.height * WORKSPACE_MARGIN)
  const workspaceWidth = preset.width + marginX * 2
  const workspaceHeight = preset.height + marginY * 2
  const propsRef = useRef({ elements, tool, backgroundColor, onSelectionChange, onBackgroundSelect, onPickColor, onPaint, onObjectUpdates })
  propsRef.current = { elements, tool, backgroundColor, onSelectionChange, onBackgroundSelect, onPickColor, onPaint, onObjectUpdates }
  const artboardStyle = {
    left: `${marginX / workspaceWidth * 100}%`,
    top: `${marginY / workspaceHeight * 100}%`,
    width: `${preset.width / workspaceWidth * 100}%`,
    height: `${preset.height / workspaceHeight * 100}%`,
    backgroundColor: checkerboard ? undefined : backgroundColor,
    aspectRatio: `${preset.width} / ${preset.height}`,
  } as CSSProperties

  useEffect(() => {
    let cancelled = false
    let instance: Canvas | null = null
    const start = async () => {
      const fabric = await import('fabric')
      if (cancelled || !elementRef.current) return
      instance = new fabric.Canvas(elementRef.current, {
        width: workspaceWidth,
        height: workspaceHeight,
        backgroundColor: 'transparent',
        selection: true,
        selectionKey: ['shiftKey', 'ctrlKey'],
        altSelectionKey: 'altKey',
        selectionColor: 'rgba(210, 218, 225, 0.13)',
        selectionBorderColor: '#e0e5e9',
        selectionLineWidth: 1,
        selectionDashArray: [5, 4],
        preserveObjectStacking: true,
        allowTouchScrolling: false,
      })
      fabricRef.current = instance
      canvasRef.current = instance
      setReady(true)

      const reportSelection = () => {
        if (syncingSelectionRef.current) return
        const current = propsRef.current
        const selectedObjects = instance?.getActiveObjects() ?? []
        const selectedLayerIds = selectedObjects.map(getLayerId).filter(Boolean)
        const selectedGroups = new Set(current.elements.filter((item) => selectedLayerIds.includes(item.id)).map((item) => item.groupId).filter(Boolean))
        const expanded = current.elements.filter((item) => selectedLayerIds.includes(item.id) || (item.groupId && selectedGroups.has(item.groupId))).map((item) => item.id)
        current.onSelectionChange(new Set(expanded), expanded.at(-1) ?? null)
      }
      instance.on('selection:created', reportSelection)
      instance.on('selection:updated', reportSelection)
      instance.on('selection:cleared', () => {
        if (!syncingSelectionRef.current && propsRef.current.tool === 'select') propsRef.current.onBackgroundSelect()
      })
      instance.on('mouse:down', ({ target, viewportPoint }) => {
        const current = propsRef.current
        if (current.tool === 'eyedropper' && viewportPoint) {
          const context = instance?.lowerCanvasEl.getContext('2d', { willReadFrequently: true })
          if (context) {
            try {
              const ratio = instance?.getRetinaScaling() ?? 1
              const pixel = context.getImageData(Math.round(viewportPoint.x * ratio), Math.round(viewportPoint.y * ratio), 1, 1).data
              if (pixel[3] > 0) current.onPickColor(cssColorFromPixel(pixel))
              else {
                const layer = target ? current.elements.find((item) => item.id === getLayerId(target)) : undefined
                current.onPickColor(layer?.color ?? current.backgroundColor)
              }
            } catch {
              const layer = target ? current.elements.find((item) => item.id === getLayerId(target)) : undefined
              current.onPickColor(layer?.color ?? current.backgroundColor)
            }
          }
          instance?.discardActiveObject()
          return
        }
        if (current.tool === 'bucket' && target) {
          current.onPaint(getLayerId(target))
          return
        }
        if (!target && current.tool === 'select') current.onBackgroundSelect()
      })
      const reportTransform = () => {
        if (!instance) return
        const updates = instance.getObjects().flatMap((object) => {
          const layerId = getLayerId(object)
          if (!layerId) return []
          const center = object.getCenterPoint()
          const scaleX = object.scaleX ?? 1
          const scaleY = object.scaleY ?? 1
          const kind = (object as unknown as LayerObject).layerKind
          const width = kind === 'text' ? undefined : Math.max(12, (object.width ?? 0) * scaleX)
          const height = kind === 'text' ? undefined : Math.max(12, (object.height ?? 0) * scaleY)
          const fontSize = kind === 'text' ? Math.max(8, ((object as Textbox).fontSize ?? 48) * scaleY) : undefined
          return [{
            id: layerId,
            updates: {
              x: center.x - (object.width ?? 0) * scaleX / 2 - marginX,
              y: center.y - (object.height ?? 0) * scaleY / 2 - marginY,
              ...(width === undefined ? {} : { width }),
              ...(height === undefined ? {} : { height }),
              ...(fontSize === undefined ? {} : { fontSize }),
              rotation: object.angle ?? 0,
              flipX: object.flipX,
              flipY: object.flipY,
            },
          }]
        })
        if (updates.length) propsRef.current.onObjectUpdates(updates)
      }
      instance.on('object:modified', reportTransform)
      instance.on('object:scaling', () => instance?.requestRenderAll())
      instance.on('object:moving', () => instance?.requestRenderAll())
      instance.on('object:rotating', () => instance?.requestRenderAll())
    }
    void start()
    return () => {
      cancelled = true
      if (instance) {
        instance.dispose()
        if (fabricRef.current === instance) fabricRef.current = null
        if (canvasRef.current === instance) canvasRef.current = null
      }
      objectsRef.current.clear()
    }
  }, [canvasRef, marginX, marginY, workspaceHeight, workspaceWidth])

  useEffect(() => {
    const canvas = fabricRef.current
    if (!canvas || !ready) return
    let cancelled = false
    const sync = async () => {
      const fabric = await import('fabric')
      if (cancelled || fabricRef.current !== canvas) return
      canvas.backgroundColor = 'transparent'
      canvas.requestRenderAll()
      const ids = new Set(elements.map((element) => element.id))
      for (const [id, object] of objectsRef.current) {
        if (!ids.has(id)) {
          canvas.remove(object)
          objectsRef.current.delete(id)
        }
      }
      for (const element of elements) {
        const existing = objectsRef.current.get(element.id)
        if (existing && existing.layerKind === element.kind && (element.kind !== 'image' || existing.imageSrc === element.src)) continue
        if (existing) {
          canvas.remove(existing)
          objectsRef.current.delete(element.id)
        }
        let object: LayerObject | null = null
        const common = {
          left: marginX + element.x,
          top: marginY + element.y,
          angle: element.rotation,
          opacity: element.opacity / 100,
          scaleX: 1,
          scaleY: 1,
          flipX: element.flipX ?? false,
          flipY: element.flipY ?? false,
          originX: 'left' as const,
          originY: 'top' as const,
          selectable: true,
          evented: true,
          hasControls: true,
          hasBorders: true,
          borderColor: '#edf1f4',
          cornerColor: '#f2f4f6',
          cornerStrokeColor: '#282c30',
          cornerStyle: 'rect' as const,
          transparentCorners: false,
          cornerSize: 10,
          padding: 5,
          rotatingPointOffset: 28,
          strokeUniform: true,
          shadow: element.shadowBlur > 0 && element.shadowOpacity > 0 ? new fabric.Shadow({ color: `rgba(0, 0, 0, ${element.shadowOpacity / 100})`, blur: element.shadowBlur }) : undefined,
        }
        if (element.kind === 'text') {
          object = new fabric.Textbox(element.uppercase ? (element.text ?? '').toUpperCase() : element.text ?? '', {
            ...common,
            width: textWidth(element),
            fontFamily: element.fontFamily === 'Akira Expanded' ? 'Impact' : element.fontFamily === 'Yamatha' ? 'cursive' : element.fontFamily ?? 'Arial',
            fontSize: element.fontSize ?? 48,
            fontWeight: element.bold ? 'bold' : 'normal',
            fontStyle: element.italic ? 'italic' : 'normal',
            textAlign: element.align ?? 'left',
            lineHeight: 0.98,
            fill: element.color,
            backgroundColor: element.highlight ? element.highlightColor ?? '#f0f2f3' : '',
            stroke: element.outlineWidth ? element.outlineColor ?? '#111111' : undefined,
            strokeWidth: element.outlineWidth ?? 0,
            paintFirst: 'stroke',
          }) as unknown as LayerObject
        } else if (element.kind === 'image' && element.src) {
          const image = await fabric.FabricImage.fromURL(element.src, { crossOrigin: 'anonymous' })
          if (cancelled) return
          image.set({ ...common, width: element.width, height: element.height, scaleX: 1, scaleY: 1 })
          object = image as unknown as LayerObject
          object.imageSrc = element.src
        } else if (element.kind === 'circle') {
          object = new fabric.Ellipse({ ...common, rx: element.width / 2, ry: element.height / 2, fill: element.color }) as unknown as LayerObject
        } else if (element.kind === 'star') {
          const points = Array.from({ length: 10 }, (_, index) => {
            const angle = -Math.PI / 2 + index * Math.PI / 5
            const radius = index % 2 === 0 ? 0.5 : 0.2
            return { x: element.width / 2 + Math.cos(angle) * element.width * radius, y: element.height / 2 + Math.sin(angle) * element.height * radius }
          })
          object = new fabric.Polygon(points, { ...common, fill: element.color }) as unknown as LayerObject
        } else {
          object = new fabric.Rect({ ...common, width: element.width, height: element.height, fill: element.color, rx: 2, ry: 2 }) as unknown as LayerObject
        }
        if (!object) continue
        object.layerId = element.id
        object.layerKind = element.kind
        if (element.kind === 'image') object.imageSrc = element.src
        canvas.add(object)
        objectsRef.current.set(element.id, object)
      }

      for (const element of elements) {
        const object = objectsRef.current.get(element.id)
        if (!object) continue
        const imageSourceChanged = element.kind === 'image' && (object as unknown as LayerObject).imageSrc !== element.src
        if (imageSourceChanged) continue
        const left = marginX + element.x
        const top = marginY + element.y
        const common = {
          left,
          top,
          angle: element.rotation,
          opacity: element.opacity / 100,
          scaleX: 1,
          scaleY: 1,
          flipX: element.flipX ?? false,
          flipY: element.flipY ?? false,
          shadow: element.shadowBlur > 0 && element.shadowOpacity > 0 ? new fabric.Shadow({ color: `rgba(0, 0, 0, ${element.shadowOpacity / 100})`, blur: element.shadowBlur }) : undefined,
        }
        if (element.kind === 'text') {
          object.set({
            ...common,
            text: element.uppercase ? (element.text ?? '').toUpperCase() : element.text ?? '',
            width: textWidth(element),
            fontFamily: element.fontFamily === 'Akira Expanded' ? 'Impact' : element.fontFamily === 'Yamatha' ? 'cursive' : element.fontFamily ?? 'Arial',
            fontSize: element.fontSize ?? 48,
            fontWeight: element.bold ? 'bold' : 'normal',
            fontStyle: element.italic ? 'italic' : 'normal',
            textAlign: element.align ?? 'left',
            fill: element.color,
            backgroundColor: element.highlight ? element.highlightColor ?? '#f0f2f3' : '',
            stroke: element.outlineWidth ? element.outlineColor ?? '#111111' : undefined,
            strokeWidth: element.outlineWidth ?? 0,
          })
        } else {
          const sizeProps = element.kind === 'circle' ? { rx: element.width / 2, ry: element.height / 2 } : { width: element.width, height: element.height }
          object.set({ ...common, ...sizeProps, fill: element.kind === 'image' ? undefined : element.color })
          if (element.kind === 'image') {
            const imageObject = object as unknown as FabricImage & { filterKey?: string }
            const filterKey = `${element.color.toUpperCase()}:${element.blur}`
            if (imageObject.filterKey !== filterKey) {
              imageObject.filters = element.color.toUpperCase() === '#FFFFFF' ? [] : [new fabric.filters.BlendColor({ color: element.color, mode: 'tint', alpha: 1 })]
              if (element.blur > 0) imageObject.filters.push(new fabric.filters.Blur({ blur: Math.min(1, element.blur / 100) }))
              imageObject.applyFilters()
              imageObject.filterKey = filterKey
            }
          }
        }
      }
      const desiredIds = Array.from(selectedIds).filter((id) => objectsRef.current.has(id))
      const activeIds = canvas.getActiveObjects().map(getLayerId).filter(Boolean).sort()
      const sortedDesiredIds = [...desiredIds].sort()
      if (activeIds.join('|') !== sortedDesiredIds.join('|')) {
        syncingSelectionRef.current = true
        canvas.discardActiveObject()
        const activeObjects = desiredIds.map((id) => objectsRef.current.get(id)).filter((object): object is LayerObject => Boolean(object))
        if (activeObjects.length === 1) canvas.setActiveObject(activeObjects[0])
        else if (activeObjects.length > 1) canvas.setActiveObject(new fabric.ActiveSelection(activeObjects, { canvas }))
        syncingSelectionRef.current = false
      }
      elements.forEach((element, index) => {
        const object = objectsRef.current.get(element.id)
        if (object) canvas.moveObjectTo(object, index)
      })
      canvas.requestRenderAll()
    }
    void sync()
    return () => { cancelled = true }
  }, [backgroundColor, checkerboard, elements, marginX, marginY, ready, selectedIds])

  useEffect(() => {
    const canvas = fabricRef.current
    if (!canvas) return
    const centerX = marginX + preset.width / 2
    const centerY = marginY + preset.height / 2
    const translationX = centerX * (1 - zoom)
    const translationY = centerY * (1 - zoom)
    canvas.setViewportTransform([zoom, 0, 0, zoom, translationX, translationY])
    canvas.requestRenderAll()
  }, [marginX, marginY, preset.height, preset.width, zoom, ready])

  useEffect(() => {
    const canvas = fabricRef.current
    if (!canvas) return
    const container = canvas.wrapperEl
    const wheel = (event: WheelEvent) => {
      event.preventDefault()
      const next = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom * (event.deltaY < 0 ? 1.1 : 0.9)))
      onZoomChange(Math.round(next * 100) / 100)
    }
    container.addEventListener('wheel', wheel, { passive: false })
    return () => container.removeEventListener('wheel', wheel)
  }, [onZoomChange, ready, zoom])

  const workspaceRatio = workspaceWidth / workspaceHeight
  return (
    <div
      className={`fabric-viewport${checkerboard ? ' is-checkerboard' : ''}`}
      style={{ aspectRatio: `${workspaceWidth} / ${workspaceHeight}`, '--workspace-ratio': workspaceRatio } as CSSProperties}
      data-tool={tool}
      aria-label={`${preset.label} canvas workspace`}
    >
      <div className="fabric-artboard" style={artboardStyle}>
        <div className="fabric-artboard-guides"><CompositionGuides guides={guides} preset={preset.id} /></div>
      </div>
      <canvas ref={elementRef} className="fabric-drawing-canvas" aria-label={`${preset.label} composition canvas`} />
      <div className="fabric-selection-outlines" aria-hidden="true">
        {elements.filter((element) => selectedIds.has(element.id)).map((element) => {
          const fontSize = element.fontSize ?? 48
          const lines = (element.text ?? '').split('\n')
          const width = element.kind === 'text' ? textWidth(element) : element.width
          const height = element.kind === 'text' ? fontSize * Math.max(1, lines.length) : element.height
          const centerX = marginX + preset.width / 2
          const centerY = marginY + preset.height / 2
          const tx = centerX * (1 - zoom)
          const ty = centerY * (1 - zoom)
          const left = (zoom * (marginX + element.x) + tx) / workspaceWidth * 100
          const top = (zoom * (marginY + element.y) + ty) / workspaceHeight * 100
          return <span key={element.id} className="fabric-selection-outline" style={{ left: `${left}%`, top: `${top}%`, width: `${width * zoom / workspaceWidth * 100}%`, height: `${height * zoom / workspaceHeight * 100}%`, transform: `rotate(${element.rotation}deg)` }} />
        })}
      </div>
      <div className="fabric-workspace-label" aria-hidden="true">WORKSPACE · {preset.width} × {preset.height} ARTBOARD</div>
    </div>
  )
}

function CompositionGuides({ guides, preset }: { guides: FabricCanvasSurfaceProps['guides']; preset: PresetId }) {
  const safe = preset === 'tiktok' || preset === 'reels' || preset === 'shorts' ? { x: 8, y: 14, width: 84, height: 64 } : preset === 'youtube' ? { x: 5, y: 7, width: 90, height: 86 } : { x: 7, y: 7, width: 86, height: 86 }
  return (
    <div className="canvas-guides" aria-hidden="true">
      {guides.has('thirds') && <svg className="guide-svg thirds-guide" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M33.333 0V100 M66.667 0V100 M0 33.333H100 M0 66.667H100" /></svg>}
      {guides.has('phi') && <svg className="guide-svg phi-guide" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M38.2 0V100 M61.8 0V100 M0 38.2H100 M0 61.8H100" /></svg>}
      {guides.has('horizon') && <svg className="guide-svg horizon-guide" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M0 50H100 M45 50H55 M50 45V55" /><circle cx="50" cy="50" r="2.2" /></svg>}
      {guides.has('triangles') && <svg className="guide-svg triangles-guide" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M0 0L100 100 M0 100L100 0 M0 0L100 0L0 100 M100 100L0 100L100 0" /></svg>}
      {guides.has('center') && <svg className="guide-svg center-guide" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M50 0V100 M0 50H100 M47 50H53 M50 47V53" /><circle cx="50" cy="50" r="1.2" /></svg>}
      {guides.has('safe') && <svg className="guide-svg safe-guide" viewBox="0 0 100 100" preserveAspectRatio="none"><rect x={safe.x} y={safe.y} width={safe.width} height={safe.height} /><path d={`M${safe.x} ${safe.y + safe.height}H${safe.x + safe.width}`} /></svg>}
    </div>
  )
}

export function exportCanvasImage(canvas: Canvas, options: { left: number; top: number; width: number; height: number; format: 'png' | 'jpeg'; quality?: number }) {
  return canvas.toDataURL({ ...options, multiplier: 1 })
}

export { MIN_ZOOM, MAX_ZOOM }
