export interface ImageMark {
  rect: {
    x: number
    y: number
    width: number
    height: number
  }
  number: number
}

export function loadImage(url: string) {
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const image = new Image()
    image.crossOrigin = 'anonymous'
    image.onload = () => resolve(image)
    image.onerror = reject
    image.src = url
  })
}

export function createCanvas(width: number, height: number) {
  if ('OffscreenCanvas' in window) {
    return new OffscreenCanvas(width, height)
  }
  const canvas = document.createElement('canvas')
  canvas.style.width = `${width}px`
  canvas.style.height = `${height}px`
  canvas.width = width
  canvas.height = height
  return canvas
}

export function blob2File(blob: Blob, fileName: string) {
  return new File([blob], fileName, { type: blob.type })
}

export interface ImageMarkParams {
  image: HTMLImageElement | HTMLCanvasElement | OffscreenCanvas
  marks: ImageMark[]
  blobFileType?: string
}

export async function canvas2Blob(
  canvas: OffscreenCanvas | HTMLCanvasElement,
  fileType?: string,
) {
  if ('convertToBlob' in canvas) {
    return await canvas.convertToBlob({ type: fileType })
  }

  return new Promise<Blob | null>(resolve => {
    canvas.toBlob(blob => {
      if (!blob) return resolve(null)
      canvas.remove()
      resolve(blob)
    }, fileType)
  })
}

export function image2Blob(image: HTMLImageElement) {
  const canvas = createCanvas(image.width, image.height)
  const ctx = canvas.getContext('2d') as
    | CanvasRenderingContext2D
    | OffscreenCanvasRenderingContext2D
  if (!ctx) return null

  ctx.drawImage(image, 0, 0, image.width, image.height)

  return canvas2Blob(canvas)
}

export async function markImage(params: ImageMarkParams): Promise<Blob | null> {
  const { image, marks, blobFileType: fileType = 'image/png' } = params

  try {
    const canvas = createCanvas(image.width, image.height)
    const ctx = canvas.getContext('2d') as
      | CanvasRenderingContext2D
      | OffscreenCanvasRenderingContext2D
    if (!ctx) return null

    ctx.drawImage(image, 0, 0, image.width, image.height)

    marks.forEach(mark => {
      const { rect, number: markNumber } = mark
      ctx.save()
      /**
       * 1. black-white dashed border
       */
      // 1.1 black border
      ctx.save()
      ctx.strokeStyle = 'black'
      ctx.lineWidth = 4
      ctx.lineJoin = 'round'
      ctx.beginPath()
      const round = 6
      if (ctx instanceof OffscreenCanvasRenderingContext2D) {
        ctx.roundRect(rect.x, rect.y, rect.width, rect.height, round)
      } else {
        ctx.rect(rect.x, rect.y, rect.width, rect.height)
      }
      ctx.stroke()
      ctx.closePath()
      // 1.2 white dashed border
      ctx.strokeStyle = 'white'
      ctx.lineWidth = 2
      ctx.setLineDash([8, 8])
      ctx.lineJoin = 'round'
      ctx.beginPath()
      if (ctx instanceof OffscreenCanvasRenderingContext2D) {
        ctx.roundRect(rect.x, rect.y, rect.width, rect.height, round)
      } else {
        ctx.rect(rect.x, rect.y, rect.width, rect.height)
      }
      ctx.stroke()
      ctx.closePath()
      ctx.restore()

      /**
       * 2. number mark
       */
      // 2.1 circle background
      const fontSize = 16
      const radius = 16
      let offsetX = 0
      let offsetY = 0

      if (
        rect.x + rect.width + radius > image.width ||
        rect.y + rect.height + radius > image.height
      ) {
        offsetX = -radius
        offsetY = -radius
      }

      ctx.beginPath()
      ctx.arc(
        rect.x + rect.width + offsetX,
        rect.y + rect.height + offsetY,
        radius,
        0,
        Math.PI * 2,
      )
      ctx.closePath()
      ctx.fillStyle = 'blue'
      ctx.fill()

      // 2.2 circle border
      ctx.strokeStyle = 'black'
      ctx.lineWidth = 3
      ctx.stroke()
      ctx.strokeStyle = 'white'
      ctx.lineWidth = 2
      ctx.stroke()

      // 2.3 number
      ctx.font = `${fontSize}px Arial`
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillStyle = 'white'
      ctx.fillText(
        markNumber.toString(),
        rect.x + rect.width + offsetX,
        rect.y + rect.height + offsetY + 2,
      )

      ctx.restore()
    })

    if ('convertToBlob' in canvas) {
      return await (canvas as OffscreenCanvas).convertToBlob({
        type: fileType,
        quality: 0.8,
      })
    }

    return new Promise<Blob | null>(resolve => {
      ;(canvas as HTMLCanvasElement).toBlob(blob => {
        if (!blob) return resolve(null)
        ;(canvas as HTMLCanvasElement).remove()
        resolve(blob)
      }, fileType)
    })
  } catch (error) {
    console.error('Failed to mark image:', error)
    return null
  }
}
