import { Editor, Vec, createShapeId, AssetRecordType, getHashForString } from "tldraw"
import { WORKER_URL } from "../constants/workerUrl"

// URL patterns to detect multiple URLs in pasted text
const URL_REGEX = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/gi

// Image file extensions
const IMAGE_EXTENSIONS = /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)$/i

/**
 * Check if a URL points to an image
 */
function isImageUrl(url: string): boolean {
  try {
    const urlObj = new URL(url)
    return IMAGE_EXTENSIONS.test(urlObj.pathname)
  } catch {
    return false
  }
}

/**
 * Extract all URLs from a string of text
 */
function extractUrls(text: string): string[] {
  const matches = text.match(URL_REGEX)
  if (!matches) return []

  // Deduplicate URLs
  return [...new Set(matches)]
}

/**
 * Unfurl a bookmark URL to get metadata
 */
async function unfurlUrl(url: string): Promise<{
  title: string
  description: string
  image: string
  favicon: string
}> {
  try {
    const response = await fetch(
      `${WORKER_URL}/unfurl?url=${encodeURIComponent(url)}`
    )
    if (!response.ok) throw new Error('Failed to unfurl')
    return await response.json()
  } catch {
    return { title: '', description: '', image: '', favicon: '' }
  }
}

/**
 * Create a bookmark shape for a URL
 */
async function createBookmarkForUrl(
  editor: Editor,
  url: string,
  position: Vec
): Promise<void> {
  const assetId = AssetRecordType.createId(getHashForString(url))

  // Check if asset already exists
  const existingAsset = editor.getAsset(assetId)

  if (!existingAsset) {
    const metadata = await unfurlUrl(url)

    editor.createAssets([{
      id: assetId,
      typeName: 'asset',
      type: 'bookmark',
      meta: {},
      props: {
        src: url,
        title: metadata.title || url,
        description: metadata.description || '',
        image: metadata.image || '',
        favicon: metadata.favicon || '',
      },
    }])
  }

  editor.createShape({
    id: createShapeId(),
    type: 'bookmark',
    x: position.x,
    y: position.y,
    props: {
      assetId,
      url,
    },
  })
}

/**
 * Create an image shape for an image URL
 */
async function createImageForUrl(
  editor: Editor,
  url: string,
  position: Vec
): Promise<void> {
  const assetId = AssetRecordType.createId(getHashForString(url))

  // Check if asset already exists
  const existingAsset = editor.getAsset(assetId)

  if (!existingAsset) {
    // Try to get image dimensions
    let w = 300
    let h = 200

    try {
      const img = new Image()
      img.crossOrigin = 'anonymous'
      await new Promise<void>((resolve, reject) => {
        img.onload = () => {
          w = img.naturalWidth
          h = img.naturalHeight
          resolve()
        }
        img.onerror = () => reject()
        img.src = url
      })
    } catch {
      // Use default dimensions
    }

    editor.createAssets([{
      id: assetId,
      typeName: 'asset',
      type: 'image',
      meta: {},
      props: {
        src: url,
        w,
        h,
        mimeType: 'image/jpeg',
        name: url.split('/').pop() || 'image',
        isAnimated: url.endsWith('.gif'),
      },
    }])
  }

  const asset = editor.getAsset(assetId)
  const props = asset?.props as { w?: number; h?: number } | undefined

  editor.createShape({
    id: createShapeId(),
    type: 'image',
    x: position.x,
    y: position.y,
    props: {
      assetId,
      w: props?.w || 300,
      h: props?.h || 200,
    },
  })
}

/**
 * Create an image shape from a File
 */
async function createImageFromFile(
  editor: Editor,
  file: File,
  position: Vec
): Promise<void> {
  // Read file as data URL
  const dataUrl = await new Promise<string>((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(reader.result as string)
    reader.onerror = reject
    reader.readAsDataURL(file)
  })

  const assetId = AssetRecordType.createId(getHashForString(dataUrl.slice(0, 100) + file.name))

  // Get image dimensions
  let w = 300
  let h = 200

  try {
    const img = new Image()
    await new Promise<void>((resolve, reject) => {
      img.onload = () => {
        w = img.naturalWidth
        h = img.naturalHeight
        resolve()
      }
      img.onerror = reject
      img.src = dataUrl
    })
  } catch {
    // Use default dimensions
  }

  editor.createAssets([{
    id: assetId,
    typeName: 'asset',
    type: 'image',
    meta: {},
    props: {
      src: dataUrl,
      w,
      h,
      mimeType: file.type || 'image/jpeg',
      name: file.name,
      isAnimated: file.type === 'image/gif',
    },
  }])

  editor.createShape({
    id: createShapeId(),
    type: 'image',
    x: position.x,
    y: position.y,
    props: {
      assetId,
      w,
      h,
    },
  })
}

/**
 * Configuration for grid layout of pasted items
 */
const GRID_CONFIG = {
  spacing: 20,      // Space between items
  itemWidth: 320,   // Default width per item
  itemHeight: 240,  // Default height per item
  maxColumns: 5,    // Maximum items per row
}

/**
 * Calculate grid positions for multiple items
 */
function calculateGridPositions(
  startPosition: Vec,
  count: number
): Vec[] {
  const positions: Vec[] = []
  const columns = Math.min(count, GRID_CONFIG.maxColumns)

  for (let i = 0; i < count; i++) {
    const col = i % columns
    const row = Math.floor(i / columns)

    positions.push(new Vec(
      startPosition.x + col * (GRID_CONFIG.itemWidth + GRID_CONFIG.spacing),
      startPosition.y + row * (GRID_CONFIG.itemHeight + GRID_CONFIG.spacing)
    ))
  }

  return positions
}

/**
 * Main paste handler that supports multiple items
 */
export function setupMultiPasteHandler(editor: Editor): () => void {
  const handlePaste = async (e: ClipboardEvent) => {
    // Don't intercept if user is typing in an input
    const activeElement = document.activeElement
    if (
      activeElement instanceof HTMLInputElement ||
      activeElement instanceof HTMLTextAreaElement ||
      (activeElement instanceof HTMLElement && activeElement.isContentEditable)
    ) {
      return
    }

    const clipboardData = e.clipboardData
    if (!clipboardData) return

    // Get paste position (center of viewport)
    const viewportBounds = editor.getViewportPageBounds()
    const center = viewportBounds.center

    // Collect all items to paste
    const imageFiles: File[] = []
    const urls: string[] = []

    // Check for files (images)
    if (clipboardData.files.length > 0) {
      for (let i = 0; i < clipboardData.files.length; i++) {
        const file = clipboardData.files[i]
        if (file.type.startsWith('image/')) {
          imageFiles.push(file)
        }
      }
    }

    // Check for text content that might contain URLs
    const textData = clipboardData.getData('text/plain')
    if (textData) {
      const extractedUrls = extractUrls(textData)
      urls.push(...extractedUrls)
    }

    // Check for URL data type
    const urlData = clipboardData.getData('text/uri-list')
    if (urlData) {
      // URI list can contain multiple URLs separated by newlines
      const uriUrls = urlData.split('\n').filter(line => line.trim() && !line.startsWith('#'))
      for (const url of uriUrls) {
        if (!urls.includes(url.trim())) {
          urls.push(url.trim())
        }
      }
    }

    // If we have multiple items, handle them ourselves
    const totalItems = imageFiles.length + urls.length

    if (totalItems > 1) {
      // Prevent default tldraw handling
      e.preventDefault()
      e.stopPropagation()


      // Calculate grid positions
      const positions = calculateGridPositions(center, totalItems)
      let positionIndex = 0

      // Batch all shape creation in a single history entry
      editor.mark('multi-paste')

      // Process image files first
      for (const file of imageFiles) {
        const position = positions[positionIndex++]
        try {
          await createImageFromFile(editor, file, position)
        } catch (err) {
          console.error('Failed to create image from file:', err)
        }
      }

      // Process URLs
      for (const url of urls) {
        const position = positions[positionIndex++]
        try {
          if (isImageUrl(url)) {
            await createImageForUrl(editor, url, position)
          } else {
            await createBookmarkForUrl(editor, url, position)
          }
        } catch (err) {
          console.error('Failed to create shape for URL:', err)
        }
      }

      return
    }

    // For single items, let tldraw handle it normally
    // (don't prevent default)
  }

  // Add event listener
  document.addEventListener('paste', handlePaste, { capture: true })

  // Return cleanup function
  return () => {
    document.removeEventListener('paste', handlePaste, { capture: true })
  }
}
