import {
  createAndSyncImage,
  createFileUploadRecord,
  getShortUrl,
  saveDomiBoardContent,
} from '../service/api'
import {
  type DominoStore,
  type ImageElement,
  type SceneElement,
  getDominoDOM,
} from '../components/canvas'
import { uploadFile } from './upload'
import { filename } from './filename'
import { blob2File } from './mark-image'

export function getCTMPoint(target: SVGSVGElement, e: React.PointerEvent) {
  const point = target.createSVGPoint()
  point.x = e.clientX
  point.y = e.clientY

  // 获取从 SVG 坐标系到屏幕坐标系的变换矩阵
  const ctm = target.getScreenCTM()

  if (!ctm) return { x: e.clientX, y: e.clientY }

  return point.matrixTransform(ctm.inverse())
}

export function flyParabola(
  label: string | number,
  from: { x: number; y: number },
  to: { x: number; y: number },
  onComplete?: () => void,
) {
  const dx = to.x - from.x
  const dy = to.y - from.y
  const distance = Math.sqrt(dx * dx + dy * dy)
  const height = Math.min(120, distance * 0.5)

  const duration = 200 + Math.min(distance * 0.5, 500)
  const step = 16.6 / duration
  let t = 0

  const el = document.createElement('div')
  el.className =
    'fixed left-0 top-0 z-[1000] flex h-[20px] w-[20px] items-center justify-center rounded-full text-[12px] pointer-events-none'
  Object.assign(el.style, {
    color: 'var(--primary-foreground)',
    background: 'var(--editor-accent)',
    boxShadow: '0 2px 8px var(--editor-shadow)',
    border: '1px solid rgba(255,255,255,0.85)',
  })
  el.innerText = label.toString()
  document.body.appendChild(el)

  function frame() {
    if (!el.isConnected) return

    t += step
    if (t > 1) t = 1

    const x = from.x + dx * t
    const y = from.y + dy * t - height * 4 * t * (1 - t)

    el.style.transform = `translate(${x}px, ${y}px)`
    if (t < 1) {
      requestAnimationFrame(frame)
    } else {
      el.remove()
      onComplete?.()
    }
  }

  requestAnimationFrame(frame)
}

export async function uploadImage(taskId: string, file: File) {
  const { name, extension } = filename(file.name)
  const fileName = `${name}-${Date.now().toString(36)}.${extension}`
  const filePath = `super_agent/user_upload_file/${fileName}`
  await uploadFile(file, filePath)
  const record = await createAndSyncImage({
    task_id: taskId,
    file_url: filePath,
    file_name: fileName,
    file_byte_size: file.size,
    file_type: file.type,
  })

  return {
    url: record.url,
    file_id: record.upload_record_id,
    file_path: record.sandbox_path,
    size: file.size,
  }
}

export async function uploadMarkImage(blob: Blob, name: string) {
  const uuid = Math.random().toString(36).substring(2, 15)
  const extension = 'png'
  const MAGIC_STRING = '_$_marked_$_'
  const file = blob2File(blob, `${name}-marked.${extension}`)
  const filePath = `capri/upload/mark/${MAGIC_STRING}-${uuid}.png`
  const result = await uploadFile(file, filePath)
  const randomID = Math.random().toString(36).substring(2, 5)
  const fileName = [MAGIC_STRING, name, randomID].join('-')
  const record = await createFileUploadRecord({
    file_url: filePath,
    file_name: [fileName, extension].join('.'),
    file_byte_size: file.size,
    file_type: file.type,
    conversation_id: uuid,
  })

  return {
    file_url: result.url,
    file_id: (record as any).file_upload_record_id,
    file_path: filePath,
    file_name: fileName,
    file_type: 'png',
    size: file.size,
  }
}

/**
 * 获取图片像素尺寸
 */
export async function getImageDimension(url: string, elementId?: string) {
  // 1. 尝试从 DOM 直接读取（最快）
  if (elementId) {
    const img = getImgElement(elementId)
    if (img && img.complete && img.naturalWidth > 0) {
      return { width: img.naturalWidth, height: img.naturalHeight }
    }
  }

  // 2. 兜底：通过 Image 对象加载
  return new Promise<{ width: number; height: number }>((resolve, reject) => {
    const img = new Image()
    img.onload = () => resolve({ width: img.width, height: img.height })
    img.onerror = reject
    img.src = url
  })
}

export async function getImageUrl(taskId: string, imagePath: string) {
  const [{ file_path, url }] = await getShortUrl(taskId, imagePath) as any
  return url || file_path
}

export async function manualPersistence(taskId: string, store: DominoStore) {
  const state = store.getState()
  const persistentElements: Record<string, SceneElement> = {}
  const persistentOrder: string[] = []

  Object.values(state.elements).forEach(el => {
    if (el.type !== 'placeholder') {
      persistentElements[el.id] = el
    }
  })
  state.elementOrder.forEach(id => {
    if (state.elements[id] && state.elements[id].type !== 'placeholder') {
      persistentOrder.push(id)
    }
  })

  await saveDomiBoardContent({
    task_id: taskId,
    data: {
      elements: persistentElements,
      elementOrder: persistentOrder,
      createdAt: state.metadata?.createdAt || Date.now(),
      updatedAt: Date.now(),
    },
  })
}

export function getImgElement(id: string) {
  return getDominoDOM(id)?.querySelector('img')
}

/**
 * 从 Performance API 获取资源大小（零开销，但需要同域或配置了 Timing-Allow-Origin）
 */
export function getImageFileSizeFromPerformance(url: string) {
  try {
    // 确保使用绝对路径进行查找，因为 Performance entries 存的是绝对 URL
    const absoluteUrl = new URL(url, window.location.origin).href
    const entries = performance.getEntriesByName(
      absoluteUrl,
      'resource',
    ) as PerformanceResourceTiming[]
    if (entries.length > 0) {
      const entry = entries[entries.length - 1]
      // 优先取 encodedBodySize (压缩后大小)，兜底取 transferSize
      return entry.encodedBodySize || entry.transferSize || 0
    }
  } catch (e) {
    console.warn('Failed to get file size via performance API', e)
  }
  return 0
}

/**
 * 获取图片文件比特大小（回归稳健的 fetch 方案）
 */
export async function getImageFileSizeFromUrl(url: string) {
  try {
    const response = await fetch(url)
    const blob = await response.blob()
    return blob.size
  } catch (e) {
    console.warn('Failed to get file size via fetch', e)
  }

  return 0
}

export async function getImageFileSize(element: ImageElement) {
  let fileSize = (element.data?.size as number) || 0

  if (!fileSize && element.src) {
    // 1. 尝试从 Performance API 获取（最快，零额外网络开销）
    fileSize = getImageFileSizeFromPerformance(element.src)

    // 2. 兜底尝试 fetch
    if (!fileSize) {
      fileSize = await getImageFileSizeFromUrl(element.src)
    }
  }

  return fileSize
}

export function isValidImageType(type: string) {
  return [
    'image/png',
    'image/jpeg',
    'image/webp',
    'image/heic',
    'image/avif',
  ].includes(type)
}

export function isValidImageFormat(fileName: string) {
  const name = fileName.split('.').pop() ?? ''
  return /(png|jpe?g|webp|avif)/.test(name.toLowerCase())
}

export function isValidImageFileSize(fileSize: number) {
  return fileSize > 0 && fileSize < 1024 * 1024 * 10
}

export function isValidImageSize(width: number, height: number) {
  const [min, max] = [width, height].sort((a, b) => a - b)
  return min > 0 && min < 2160 && max > 0 && max < 3840
}

export async function copyImageToClipboard(
  elementId: string,
): Promise<boolean> {
  const img = getImgElement(elementId)
  if (!img) {
    console.warn('Image element not found')
    return false
  }

  let imageBlob: Blob | null = null
  try {
    const canvas = document.createElement('canvas')
    canvas.width = img.naturalWidth
    canvas.height = img.naturalHeight
    const ctx = canvas.getContext('2d')
    if (ctx) {
      ctx.drawImage(img, 0, 0)
      imageBlob = await new Promise<Blob | null>(resolve =>
        canvas.toBlob(resolve, 'image/png'),
      )
    }
  } catch (e) {
    console.error(e)
  }

  const absoluteSrc = new URL(img.src, window.location.href).href

  if (!imageBlob) {
    try {
      const resp = await fetch(absoluteSrc)
      const fetchedBlob = await resp.blob()
      if (fetchedBlob.type === 'image/png') {
        imageBlob = fetchedBlob
      } else if (fetchedBlob.type.startsWith('image/')) {
        // 强制转换其它格式为 PNG 以满足 Clipboard API 限制
        const bitmap = await createImageBitmap(fetchedBlob)
        const canvas = document.createElement('canvas')
        canvas.width = bitmap.width
        canvas.height = bitmap.height
        canvas.getContext('2d')?.drawImage(bitmap, 0, 0)
        imageBlob = await new Promise<Blob | null>(resolve =>
          canvas.toBlob(resolve, 'image/png'),
        )
      }
    } catch (e) {
      console.error(e)
    }
  }

  if (!imageBlob) {
    try {
      imageBlob = await fetch(absoluteSrc).then(res => res.blob())
    } catch (e) {
      console.error(e)
    }
  }

  const htmlDoc = `
  <!DOCTYPE html>
  <html>
  <head><meta charset="utf-8"></head>
  <body>
    <img src="${absoluteSrc}" style="max-width: 100%;" />
  </body>
  </html>
`.trim()

  try {
    const dataItems: Record<string, Blob> = {
      'text/html': new Blob([htmlDoc], { type: 'text/html' }),
      'text/plain': new Blob([absoluteSrc], { type: 'text/plain' }),
    }

    if (imageBlob && imageBlob.type === 'image/png') {
      dataItems['image/png'] = imageBlob
    }

    await navigator.clipboard.write([new ClipboardItem(dataItems)])
    return true
  } catch (err) {
    console.warn('Modern clipboard write failed, using command fallback.', err)
    return false
  }
}

/**
 * 文本元素剪贴板数据结构
 */
export interface TextElementClipboardData {
  type: 'domino-text-element'
  content: string
  fontSize?: number
  fontWeight?: string | number
  color?: string
  fontFamily?: string
  lineHeight?: number
  textAlign?: 'left' | 'center' | 'right'
  fontStyle?: 'normal' | 'italic'
  width?: number
  height?: number
}

/**
 * 复制文本元素到剪贴板（包含完整样式信息）
 */
export async function copyTextElementToClipboard(textElement: {
  content: string
  fontSize?: number
  fontWeight?: string | number
  color?: string
  fontFamily?: string
  lineHeight?: number
  textAlign?: 'left' | 'center' | 'right'
  fontStyle?: 'normal' | 'italic'
  width?: number
  height?: number
}): Promise<boolean> {
  try {
    const textElementData = JSON.stringify({
      type: 'domino-text-element',
      content: textElement.content,
      fontSize: textElement.fontSize,
      fontWeight: textElement.fontWeight,
      color: textElement.color,
      fontFamily: textElement.fontFamily,
      lineHeight: textElement.lineHeight,
      textAlign: textElement.textAlign,
      fontStyle: textElement.fontStyle,
      width: textElement.width,
      height: textElement.height,
    })

    // 使用 text/html 作为载体，将数据嵌入到 HTML 注释中
    const htmlContent = `<!--DOMINO_TEXT_ELEMENT:${textElementData}--><span>${textElement.content}</span>`

    const clipboardItem = new ClipboardItem({
      'text/html': new Blob([htmlContent], { type: 'text/html' }),
      'text/plain': new Blob([textElement.content], { type: 'text/plain' }),
    })

    await navigator.clipboard.write([clipboardItem])
    return true
  } catch (err) {
    console.warn('Failed to copy text element to clipboard', err)
    return false
  }
}

/**
 * 从剪贴板解析文本元素数据
 * 返回 { type: 'styled', data } 表示带样式的文本元素
 * 返回 { type: 'plain', text } 表示纯文本
 * 返回 null 表示无有效数据
 */
export async function parseTextElementFromClipboard(): Promise<
  | { type: 'styled'; data: TextElementClipboardData }
  | { type: 'plain'; text: string }
  | null
> {
  try {
    const clipboardItems = await navigator.clipboard.read()

    for (const item of clipboardItems) {
      // 优先检查 text/html 中是否有 DOMINO_TEXT_ELEMENT 数据
      if (item.types.includes('text/html')) {
        try {
          const blob = await item.getType('text/html')
          const html = await blob.text()

          // 解析 HTML 注释中的元素数据
          const match = html.match(/<!--DOMINO_TEXT_ELEMENT:(.*?)-->/)
          if (match) {
            const textData = JSON.parse(match[1]) as TextElementClipboardData
            return { type: 'styled', data: textData }
          }
        } catch {
          // 解析失败，继续尝试纯文本
        }
      }

      // 回退到纯文本
      if (item.types.includes('text/plain')) {
        try {
          const blob = await item.getType('text/plain')
          const text = await blob.text()
          if (text.trim()) {
            return { type: 'plain', text }
          }
        } catch {
          // 忽略错误
        }
      }
    }
  } catch {
    // 剪贴板访问失败
  }

  return null
}

/**
 * 等待 DOM 元素渲染完成
 * 比 requestAnimationFrame 更可靠，通过轮询确保元素存在且有尺寸
 */
export function waitForElement(
  id: string,
  timeout = 500,
): Promise<HTMLElement | null> {
  return new Promise(resolve => {
    const startTime = Date.now()

    const check = () => {
      const el = getDominoDOM(id) as HTMLElement | null
      if (el && el.getBoundingClientRect().width > 0) {
        resolve(el)
      } else if (Date.now() - startTime < timeout) {
        requestAnimationFrame(check)
      } else {
        // 超时也 resolve，避免卡住流程
        resolve(el)
      }
    }

    requestAnimationFrame(check)
  })
}
