import { ref, shallowRef, watch, computed, nextTick } from 'vue'
import type { Ref } from 'vue'
import { reject } from 'lodash-es'
import { createFakeDom, isDom } from '@/utils/common'
import TintImageWorker from '@/utils/image.worker?worker'

export type Point = [number, number]

export type RGBA = number[]

export interface GetPixPointParams {
  width: number
  height: number
}

/**
 * @description 相似度阀值
 */
export const DISTANCE = 45

/**
 * @description 黑色RGBA
 */
export const BLACK = [0, 0, 0, 255]

/**
 * @description 白色RGBA
 */
export const WHITE = [255, 255, 255, 255]

/**
 * @description 背景色取色点
 */
export const COLOR_POINT: Point[] = [
  [0, 0],
  [0.25, 0],
  [0.5, 0],
  [0.75, 0],
  [1, 0],
  [0, 0.25],
  [1, 0.25],
  [0, 0.5],
  [1, 0.5],
  [0, 0.75],
  [1, 0.75],
  [0, 1],
  [0.25, 1],
  [0.5, 1],
  [0.75, 1],
  [1, 1],
]

/**
 * @description 根据取色点获取图片像素点
 * @params { Object } width canvas宽度
 * @params { Object } height canvas高度
 */

export const getPixelPoint = ({ width, height }: GetPixPointParams, colorPoint: Point[] = COLOR_POINT) => {
  return colorPoint.map(([x, y]) => {
    const index = Math.max(Math.ceil(width * x), 0) + Math.max(Math.ceil(height * y) - 1, 0) * width
    return Math.max(index + Math.ceil(x) * -1, 0) * 4
  })
}

/**
 * @description 计算像素点相似度
 */
export const calcPixSim = (rgba: RGBA, base: RGBA = WHITE) => {
  const [r, g, b, a] = rgba
  const [r1, g1, b1, a1] = base
  return Math.sqrt((r - r1) ** 2 + (g - g1) ** 2 + (b - b1) ** 2 + (a - a1) ** 2)
}

/**
 * @description 判断两个颜色是否相似
 */
export const isSimilar = (color1: RGBA, color2: RGBA, distance: number = DISTANCE) => {
  return calcPixSim(color1, color2) <= distance
}

/**
 * @description 根据imageData起始下标获取rgba
 */
export const getColorFromIndex = (index: number, data: Uint8ClampedArray) => {
  return [0, 1, 2, 3].map((_) => {
    return data[index + _]
  })
}

/**
 * @description 根据imageData起始下标设置rgba
 */
export const setColorFromIndex = (index: number, data: Uint8ClampedArray, color: RGBA) => {
  return [0, 1, 2, 3].forEach((_) => {
    data[index + _] = color[_]
  })
}

/**
 * @description 获取各取色点中出现次数最多的颜色
 */
export const getMainColor = (colors: RGBA[]) => {
  let max = 1
  let index = 0
  let num = 0
  const total = colors
    .map((_) => calcPixSim(_))
    .reduce((r, n, i) => {
      r[n] ??= 0
      r[n]++
      if (r[n] > max) {
        max = r[n]
        num = n
        index = i
      }
      return r
    }, {} as Record<number, number>)
  return {
    max,
    index,
    num,
    total,
    mainColor: colors[index],
  }
}

/**
 * @description 从字符串中提取颜色值
 * @example "rgba(121,121,121,1)"
 */

export const convertColor = (color: string) => {
  const [r, g, b, a] = color
    .replace(/rgba\((.*)\)/g, '$1')
    .split(',')
    .map((s) => s.trim()) as unknown as RGBA
  return [+r, +g, +b, a * 255]
}

export interface SetImgBgOption {
  image?: HTMLImageElement | string | null
  canvas?: HTMLCanvasElement | null
  basePoint?: Point[]
  useNaturalSize?: boolean
  distance?: number
  markColor?: RGBA
  immediate?: boolean
  enableSharpen?: boolean
  rotate?: number
  scale?: number
}

function canvasRotate(inputCanvas: HTMLCanvasElement, rotate = 0) {
  let W = inputCanvas.width
  let H = inputCanvas.height
  const canvasWidth = Math.max(W, H)
  const canvas = createFakeDom('canvas')
  canvas.width = canvas.height = canvasWidth
  const ctx = canvas.getContext('2d')
  if (!ctx) {
    return inputCanvas
  }

  ctx.translate(canvasWidth / 2, canvasWidth / 2)
  ctx.rotate(rotate * (Math.PI / 180))

  let x = -canvasWidth / 2
  let y = -canvasWidth / 2

  rotate = rotate % 360

  console.log(rotate)

  if (rotate % 180 !== 0) {
    if (rotate === -90 || rotate === 270) {
      x = -W + canvasWidth / 2
    } else {
      y = canvasWidth / 2 - H
    }
    const c = W
    W = H
    H = c
  } else if (rotate) {
    x = canvasWidth / 2 - W
    y = canvasWidth / 2 - H
  }
  ctx.drawImage(inputCanvas, x, y)
  const canvas2 = createFakeDom('canvas')
  canvas2.width = W
  canvas2.height = H
  const ctx2 = canvas2.getContext('2d')
  if (!ctx2) {
    return canvas
  }
  ctx2.drawImage(canvas, 0, 0, W, H, 0, 0, W, H)
  return canvas2
}

const getImage = async (image?: string | HTMLImageElement | null) => {
  if (!image) {
    return console.warn(`return for img define ${image}`)
  }

  if (!isDom<HTMLImageElement>(image)) {
    image = await new Promise<HTMLImageElement>((resolve) => {
      const img = new Image()
      img.src = image as string
      img.onload = () => {
        resolve(img)
      }
      img.onerror = reject
    })
  }
  return image
}

interface GetImageDataOptions {
  image: HTMLImageElement
  canvas?: HTMLCanvasElement | null
  useNaturalSize?: boolean
  scale?: number
}

interface GetImageDataResult {
  imageData: ImageData | null
  canvas: HTMLCanvasElement | null
  context: CanvasRenderingContext2D | null
}

const getImageData = async ({
  image,
  canvas,
  useNaturalSize,
  scale = 1,
}: GetImageDataOptions): Promise<GetImageDataResult> => {
  const result: GetImageDataResult = {
    imageData: null,
    canvas: null,
    context: null,
  }

  const cvs = canvas || createFakeDom('canvas')
  const ctx = cvs.getContext('2d')

  if (!ctx) {
    console.warn(`return for canvas context define ${ctx}`)
    return result
  }

  const width: number = (cvs.width = (useNaturalSize ? image.naturalWidth : image.width) * scale)
  const height: number = (cvs.height = (useNaturalSize ? image.naturalHeight : image.height) * scale)

  if (width <= 0 || height <= 0) {
    console.warn(`return for img size define width: ${width}, height: ${height}`)
    return result
  }

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

  const imageData = ctx.getImageData(0, 0, width, height)

  return Object.assign(result, { imageData, canvas: cvs, context: ctx })
}

interface TintImageMarkOption {
  imageData: ImageData
  markColor: RGBA
  markPointIndex: number[]
}

const start = (arr: Uint8ClampedArray, func: (index: number) => void) => {
  let i = 0
  const run = () => {
    if (i < arr.length) {
      func(i++)
      requestAnimationFrame(run)
    }
  }
  requestAnimationFrame(run)
}

const tingImageMark = ({ imageData, markColor, markPointIndex }: TintImageMarkOption) => {
  start(imageData.data, (index) => {
    if (markColor && markPointIndex.includes(index)) {
      setColorFromIndex(index, imageData.data, markColor)
    }
  })
}

interface SharpenImageOption {
  imageData: ImageData
  distance: number
}

const sharpenImage = ({ imageData, distance }: SharpenImageOption) => {
  start(imageData.data, (index) => {
    if (isSimilar(getColorFromIndex(index, imageData.data), BLACK, distance)) {
      setColorFromIndex(index, imageData.data, BLACK)
    }
  })
}

interface GetMainColorOption {
  canvas: HTMLCanvasElement
  imageData: ImageData
  basePoint?: Point[]
}

const getMainColorWithPointes = ({ canvas, imageData, basePoint }: GetMainColorOption) => {
  const pixelPoints = getPixelPoint({ width: canvas.width, height: canvas.height }, basePoint)

  const pixelsInfo = pixelPoints.map((_) => getColorFromIndex(_, imageData.data))
  const { mainColor } = getMainColor(pixelsInfo)
  return {
    pixelPoints,
    pixelsInfo,
    mainColor,
  }
}

interface TintImageOption {
  imageData: ImageData
  mainColor: RGBA
  distance: number
}

const tintImageWorker = new TintImageWorker()

const tintImage = async ({ mainColor, imageData, distance }: TintImageOption) => {
  // tintImageWorker.postMessage({ distance, mainColor: [...mainColor], imageData: { ...imageData } })

  // return new Promise<ImageData>((resolve) => {
  //   tintImageWorker.addEventListener('message', (e) => {
  //     resolve(e.data)
  //   })
  // })
  const targetColor = [255, 255, 255, 0]
  if (isSimilar(targetColor, mainColor, distance)) {
    return imageData
  }
  start(imageData.data, (index) => {
    if (isSimilar(getColorFromIndex(index, imageData.data), mainColor, distance)) {
      setColorFromIndex(index, imageData.data, targetColor)
    }
  })
  return imageData
}

export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
  const drawing = ref<boolean>(false)
  const imageData = shallowRef<ImageData | null>(null)
  const rawCanvas = shallowRef<HTMLCanvasElement | null>(null)
  const rawContext = shallowRef<CanvasRenderingContext2D | null>(null)
  const rawPixelPoints = ref<number[]>([])
  const rawMainColor = ref<RGBA>()
  const imageElement = ref<HTMLImageElement>()
  const dataUrl = ref('')

  let task: number | null = null

  const updateImageData = async (image?: HTMLImageElement, scale?: number) => {
    try {
      dataUrl.value = ''
      imageData.value = null
      rawCanvas.value = null
      rawContext.value = null
      if (image) {
        const {
          imageData: outPutImageData,
          canvas,
          context,
        } = await getImageData({
          image,
          canvas: option.value.canvas,
          scale,
          useNaturalSize: option.value.useNaturalSize,
        })
        updateCanvas(option.value.rotate, true)
        imageData.value = outPutImageData
        rawCanvas.value = canvas
        rawContext.value = context
      }
    } catch (error) {
      console.error(error)
    }
  }

  watch(
    () => option.value.image,
    () => {
      getImage(option.value.image).then((res) => {
        imageElement.value = res || undefined
      })
    },
    { immediate: option.value.immediate }
  )

  /** 渲染图片 */
  watch([imageElement, () => option.value.scale], () => {
    updateImageData(imageElement.value, option.value.scale || 1)
  })

  /** 获取主色 */
  watch(rawContext, () => {
    rawPixelPoints.value = []
    rawMainColor.value = []
    if (imageData.value && rawCanvas.value) {
      const { pixelPoints, mainColor } = getMainColorWithPointes({
        imageData: imageData.value,
        canvas: rawCanvas.value,
        basePoint: option.value.basePoint,
      })
      rawPixelPoints.value = pixelPoints
      rawMainColor.value = mainColor
    }
  })

  /** 锐化 */
  watch([imageData, () => option.value.enableSharpen], () => {
    if (imageData.value && option.value.enableSharpen) {
      sharpenImage({ imageData: imageData.value, distance: option.value.distance || DISTANCE })
      updateCanvas()
    }
  })

  // /** 移除背景色 */
  watch(rawMainColor, () => {
    if (imageData.value && rawMainColor.value?.length) {
      tintImage({
        distance: option.value.distance || DISTANCE,
        imageData: imageData.value,
        mainColor: rawMainColor.value,
      }).then((data: ImageData) => {
        rawContext.value?.putImageData(data, 0, 0)
        updateCanvas()
      })
    }
  })

  watch(
    () => option.value.rotate,
    () => {
      updateCanvas(option.value.rotate)
    }
  )

  const updateCanvas = (rotate = option.value.rotate, immediate = false) => {
    if (task) {
      clearTimeout(task)
    }
    const update = () => {
      if (rawCanvas.value) {
        rawCanvas.value = canvasRotate(rawCanvas.value, rotate)
        dataUrl.value = rawCanvas.value.toDataURL()
      }
    }
    if (immediate) {
      update()
    } else {
      task = window.setTimeout(update, 10)
    }
  }

  return {
    drawing,
    imageData,
    pixelPoints: rawPixelPoints,
    dataUrl,
    updateCanvas,
  }
}
