export interface CompressionPreview {
  originalSize: number
  compressedSize: number
  savedBytes: number
  savedRatio: number
}

export interface CompressionResult {
  file: File
  usedCompressed: boolean
  preview: CompressionPreview | null
}

interface ProcessImageOptions {
  quality: number
  maxWidth: number
  maxHeight: number
  preferredFormats: string[]
  coverWidth?: number
  coverHeight?: number
}

function formatBytes(value: number) {
  if (!Number.isFinite(value) || value <= 0) {
    return '0 B'
  }

  const units = ['B', 'KB', 'MB', 'GB']
  let size = value
  let unit = 0

  while (size >= 1024 && unit < units.length - 1) {
    size /= 1024
    unit += 1
  }

  return `${size >= 10 || unit === 0 ? size.toFixed(0) : size.toFixed(1)} ${units[unit]}`
}

function canTransformWithCanvas(file: File) {
  return file.type.startsWith('image/') && file.type !== 'image/svg+xml' && file.type !== 'image/gif'
}

async function canvasToBlob(
  canvas: HTMLCanvasElement,
  preferredFormats: string[],
  quality: number,
) {
  for (const format of preferredFormats) {
    const blob = await new Promise<Blob | null>((resolve) => {
      canvas.toBlob(resolve, format, quality)
    })

    if (blob && blob.type === format) {
      return blob
    }
  }

  return new Promise<Blob | null>((resolve) => {
    canvas.toBlob(resolve, 'image/png')
  })
}

function extensionForMimeType(mimeType: string) {
  switch (mimeType) {
    case 'image/avif':
      return '.avif'
    case 'image/webp':
      return '.webp'
    case 'image/png':
      return '.png'
    default:
      return '.jpg'
  }
}

function deriveFileName(file: File, mimeType: string) {
  const extension = extensionForMimeType(mimeType)
  if (/\.[A-Za-z0-9]+$/.test(file.name)) {
    return file.name.replace(/\.[A-Za-z0-9]+$/, extension)
  }

  return `processed${extension}`
}

async function processImage(file: File, options: ProcessImageOptions): Promise<File> {
  if (!canTransformWithCanvas(file)) {
    return file
  }

  const bitmap = await createImageBitmap(file)
  const canvas = document.createElement('canvas')

  if (options.coverWidth && options.coverHeight) {
    canvas.width = options.coverWidth
    canvas.height = options.coverHeight
  } else {
    const scale = Math.min(
      options.maxWidth / bitmap.width,
      options.maxHeight / bitmap.height,
      1,
    )

    canvas.width = Math.max(1, Math.round(bitmap.width * scale))
    canvas.height = Math.max(1, Math.round(bitmap.height * scale))
  }

  const ctx = canvas.getContext('2d')
  if (!ctx) {
    return file
  }

  if (options.coverWidth && options.coverHeight) {
    const scale = Math.max(
      options.coverWidth / bitmap.width,
      options.coverHeight / bitmap.height,
    )
    const drawWidth = bitmap.width * scale
    const drawHeight = bitmap.height * scale
    const offsetX = (options.coverWidth - drawWidth) / 2
    const offsetY = (options.coverHeight - drawHeight) / 2

    ctx.drawImage(bitmap, offsetX, offsetY, drawWidth, drawHeight)
  } else {
    ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height)
  }

  const blob = await canvasToBlob(canvas, options.preferredFormats, options.quality)
  if (!blob) {
    return file
  }

  return new File([blob], deriveFileName(file, blob.type), {
    type: blob.type,
    lastModified: Date.now(),
  })
}

async function maybeProcessImageWithPrompt(
  file: File,
  options?: {
    quality?: number
    ask?: boolean
    minSavingsRatio?: number
    contextLabel?: string
    maxWidth?: number
    maxHeight?: number
    preferredFormats?: string[]
    coverWidth?: number
    coverHeight?: number
    forceProcessed?: boolean
  },
): Promise<CompressionResult> {
  if (!canTransformWithCanvas(file)) {
    return { file, usedCompressed: false, preview: null }
  }

  const quality = Math.min(Math.max(options?.quality ?? 0.82, 0.45), 0.95)
  const minSavingsRatio = Math.min(Math.max(options?.minSavingsRatio ?? 0.03, 0), 0.9)
  const ask = options?.ask ?? true
  const contextLabel = options?.contextLabel ?? '图片上传'
  const forceProcessed = options?.forceProcessed ?? false

  let processed: File
  try {
    processed = await processImage(file, {
      quality,
      maxWidth: Math.max(options?.maxWidth ?? 2200, 320),
      maxHeight: Math.max(options?.maxHeight ?? 2200, 320),
      preferredFormats:
        options?.preferredFormats && options.preferredFormats.length
          ? options.preferredFormats
          : file.type === 'image/png'
            ? ['image/png', 'image/webp', 'image/jpeg']
            : ['image/webp', 'image/avif', 'image/jpeg'],
      coverWidth: options?.coverWidth,
      coverHeight: options?.coverHeight,
    })
  } catch {
    return { file, usedCompressed: false, preview: null }
  }

  const savedBytes = file.size - processed.size
  const savedRatio = file.size > 0 ? savedBytes / file.size : 0
  const preview: CompressionPreview = {
    originalSize: file.size,
    compressedSize: processed.size,
    savedBytes,
    savedRatio,
  }

  if (!forceProcessed && processed.size >= file.size) {
    return { file, usedCompressed: false, preview }
  }

  if (!forceProcessed && savedRatio < minSavingsRatio) {
    return { file, usedCompressed: false, preview }
  }

  if (!ask) {
    return { file: processed, usedCompressed: true, preview }
  }

  const deltaText =
    savedBytes >= 0
      ? `节省: ${formatBytes(savedBytes)} (${(savedRatio * 100).toFixed(1)}%)`
      : `体积增加: ${formatBytes(Math.abs(savedBytes))} (${Math.abs(savedRatio * 100).toFixed(1)}%)`

  const intro = forceProcessed
    ? `${contextLabel}: 已生成规范化版本。`
    : `${contextLabel}: 检测到可压缩空间。`

  const useProcessed = window.confirm(
    [
      intro,
      `原始: ${formatBytes(file.size)}`,
      `处理后: ${formatBytes(processed.size)}`,
      deltaText,
      '',
      forceProcessed ? '是否使用规范化版本上传？' : '是否使用压缩版本上传？',
    ].join('\n'),
  )

  return {
    file: useProcessed ? processed : file,
    usedCompressed: useProcessed,
    preview,
  }
}

export async function maybeCompressImageWithPrompt(
  file: File,
  options?: {
    quality?: number
    ask?: boolean
    minSavingsRatio?: number
    contextLabel?: string
  },
): Promise<CompressionResult> {
  return maybeProcessImageWithPrompt(file, options)
}

export async function normalizeCoverImageWithPrompt(
  file: File,
  options?: {
    quality?: number
    ask?: boolean
    contextLabel?: string
    width?: number
    height?: number
  },
): Promise<CompressionResult> {
  return maybeProcessImageWithPrompt(file, {
    quality: options?.quality ?? 0.82,
    ask: options?.ask ?? true,
    contextLabel: options?.contextLabel ?? '封面图规范化',
    preferredFormats: ['image/avif', 'image/webp', 'image/jpeg'],
    coverWidth: Math.max(options?.width ?? 1600, 640),
    coverHeight: Math.max(options?.height ?? 900, 360),
    forceProcessed: true,
    minSavingsRatio: 0,
  })
}

export function formatCompressionPreview(preview: CompressionPreview | null) {
  if (!preview) {
    return ''
  }

  if (preview.savedBytes >= 0) {
    return `原始 ${formatBytes(preview.originalSize)} → 处理后 ${formatBytes(
      preview.compressedSize,
    )}，节省 ${(preview.savedRatio * 100).toFixed(1)}%`
  }

  return `原始 ${formatBytes(preview.originalSize)} → 处理后 ${formatBytes(
    preview.compressedSize,
  )}，体积增加 ${Math.abs(preview.savedRatio * 100).toFixed(1)}%`
}
