import { Buffer } from 'buffer'

import configureJimp from '@jimp/custom'
import jimpPng from '@jimp/png'

import { animals, pixel } from '../img/index.js'
import { cowColors } from '../enums.js'

import { COW_COLORS_HEX_MAP } from '../constants.js'

import { memoize } from './memoize.js'

import { convertStringToInteger } from './convertStringToInteger.js'

const Jimp = configureJimp({
  types: [jimpPng],
})

const colorizeCowTemplate = (() => {
  // TODO: Remove unused cowImageWidth, cowImageHeight, and
  // cowImageFactoryCanvas variables.
  // Instantiating a canvas directly in the module scope also poses runtime
  // risks in non-browser/SSR environments.
  const cowImageWidth = 48
  const cowImageHeight = 48
  const cowImageFactoryCanvas = document.createElement('canvas')

  cowImageFactoryCanvas.setAttribute('height', String(cowImageHeight))
  cowImageFactoryCanvas.setAttribute('width', String(cowImageWidth))

  const cachedCowImages: Record<string, string> = {}

  // https://stackoverflow.com/a/5624139
  const hexToRgb = memoize((hex: string) => {
    const [, r, g, b] = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(
      hex
    ) ?? ['', '0', '0', '0']

    return {
      r: parseInt(r, 16),
      g: parseInt(g, 16),
      b: parseInt(b, 16),
    }
  })

  /**
   * @param cowTemplate Base64 representation of an image
   * @param color
   * @returns Base64 representation of an image
   */
  return async (cowTemplate: string, color: farmhand.cowColors) => {
    if (color === cowColors.RAINBOW) return animals.cow.rainbow

    const imageKey = `${color}_${cowTemplate}`

    if (cachedCowImages[imageKey]) return cachedCowImages[imageKey]

    try {
      // `data:image/png;base64,` needs to be removed from the base64 string
      // before being provided to Buffer.
      // https://github.com/oliver-moran/jimp/issues/231#issuecomment-282167737
      const cowTemplateBuffer = Buffer.from(
        cowTemplate.split(',')[1] ?? '',
        'base64'
      )
      const image = await Jimp.read(cowTemplateBuffer)

      image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y) {
        const { r, g, b } = Jimp.intToRGBA(image.getPixelColor(x, y))

        // rgb(102, 102, 102) represents the color to replace in the template
        // source images (#666).
        if (r === 102 && g === 102 && b === 102) {
          const cowColorRgb = hexToRgb(COW_COLORS_HEX_MAP[color])
          const colorNumber = Jimp.rgbaToInt(
            cowColorRgb.r,
            cowColorRgb.g,
            cowColorRgb.b,
            255
          )

          image.setPixelColor(colorNumber, x, y)
        }
      })

      cachedCowImages[imageKey] = await image.getBase64Async(Jimp.MIME_PNG)

      return cachedCowImages[imageKey]
    } catch (e) {
      // Jimp.read() expectedly errors out when it receives an empty buffer,
      // which it will in some unit tests.
      if (import.meta.env?.MODE !== 'test') {
        console.error(e)
      }

      return pixel
    }
  }
})()

export const getCowImage = async (cow: farmhand.cow): Promise<string> => {
  const cowIdNumber = convertStringToInteger(cow.id)
  const { variations } = animals.cow
  const cowTemplate = variations[cowIdNumber % variations.length]

  return await colorizeCowTemplate(cowTemplate, cow.color as farmhand.cowColors)
}
