import { nextTick } from 'vue'
import { imageCacheManager } from '@/utils/image/imageCacheManager'
import { isLowEndDevice } from '@/utils/performanceMode.js'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'

const TRANSPARENT_PLACEHOLDER =
  'data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22 viewBox=%220 0 1 1%22%3E%3C/svg%3E'
const LEGACY_TRANSPARENT_PLACEHOLDER =
  'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='

export const smartImage = {
  beforeMount(el) {
    preventNativeBrokenImage(el)
  },

  async mounted(el, binding) {
    const skipFade = binding.modifiers.noFade || binding.arg === 'noFade' || isLowEndDevice()
    const keepVisible = binding.modifiers.noFade || binding.arg === 'noFade'

    el.decoding = 'async'

    const url = getValidUrl(binding.value, el)

    if (url) {
      prepareImageLoadingState(el, skipFade, keepVisible)
      await processImage(el, url)
      el._smartImageCurrentUrl = url
      el._smartImageOriginalBindingValue = binding.value
      return
    }

    showImage(el)
  },

  async updated(el, binding) {
    const skipFade = binding.modifiers.noFade || binding.arg === 'noFade' || isLowEndDevice()
    const keepVisible = binding.modifiers.noFade || binding.arg === 'noFade'
    const currentUrl = getValidUrl(binding.value, el)
    const bindingChanged = binding.value !== binding.oldValue
    const elementSrcChanged = currentUrl && currentUrl !== el._smartImageCurrentUrl

    if (!bindingChanged && !elementSrcChanged) {
      return
    }

    if (currentUrl && currentUrl !== el._smartImageCurrentUrl) {
      preventNativeBrokenImage(el)
      prepareImageLoadingState(el, skipFade, keepVisible)
      await processImage(el, currentUrl)
      el._smartImageCurrentUrl = currentUrl
      el._smartImageOriginalBindingValue = binding.value
    }
  },

  unmounted(el) {
    el.removeEventListener('load', el._smartImageLoadHandler)
    el.removeEventListener('error', el._smartImageErrorHandler)
    delete el._smartImageCurrentUrl
    delete el._smartImageOriginalBindingValue
    delete el._smartImageOriginalSrcBeforePlaceholder
    delete el._smartImageRequestToken
    delete el._smartImageLoadHandler
    delete el._smartImageErrorHandler
  },
}

function getValidUrl(bindingValue, el) {
  const candidates = [bindingValue, el._smartImageOriginalSrcBeforePlaceholder, el.getAttribute('src'), el.src]

  for (const candidate of candidates) {
    if (
      typeof candidate === 'string' &&
      candidate &&
      candidate !== 'null' &&
      candidate !== 'undefined' &&
      !isTransparentPlaceholder(candidate) &&
      !candidate.includes('blob:') &&
      !candidate.includes('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw')
    ) {
      return candidate
    }
  }

  return null
}

function preventNativeBrokenImage(el) {
  const rawSrc = el.getAttribute('src') || ''
  el.style.backgroundColor = 'transparent'

  if (!rawSrc || isTransparentPlaceholder(rawSrc)) {
    return
  }

  el._smartImageOriginalSrcBeforePlaceholder = rawSrc
  el.src = TRANSPARENT_PLACEHOLDER
}

function isTransparentPlaceholder(src) {
  return (
    src === TRANSPARENT_PLACEHOLDER ||
    src === LEGACY_TRANSPARENT_PLACEHOLDER ||
    src.includes('data:image/svg+xml;charset=utf-8,%3Csvg') ||
    src.includes('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw')
  )
}

function prepareImageLoadingState(el, skipFade, keepVisible = false) {
  el.style.backgroundColor = 'transparent'

  if (keepVisible) {
    el.style.opacity = '1'
    el.style.transition = 'none'
    return
  }
  // 低端机也要先隐藏待处理图片，避免缓存异步完成前出现浏览器失败占位。
  el.style.opacity = '0'
  el.style.transition = skipFade ? 'none' : 'opacity 0.3s ease-in-out'
}

function showImage(el) {
  el.style.opacity = '1'
}

async function processImage(el, url) {
  if (!url) {
    return
  }

  await nextTick()

  try {
    const resolvedUrl = resolveProtectedAssetUrl(url)
    const requestToken = Symbol('smart-image-request')
    el._smartImageRequestToken = requestToken
    preventNativeBrokenImage(el)
    const imageUrl = await imageCacheManager.scheduleRequest(resolvedUrl)

    if (el._smartImageRequestToken !== requestToken) {
      return
    }

    const handleLoad = () => {
      showImage(el)

      el.removeEventListener('load', handleLoad)
    }

    el.removeEventListener('load', el._smartImageLoadHandler)
    el.addEventListener('load', handleLoad)
    el._smartImageLoadHandler = handleLoad

    const handleError = (error) => {
      console.error('Image load failed:', resolvedUrl, error)
      showImage(el)

      el.removeEventListener('error', handleError)
    }

    el.removeEventListener('error', el._smartImageErrorHandler)
    el.addEventListener('error', handleError)
    el._smartImageErrorHandler = handleError
    el.src = imageUrl
  } catch (error) {
    const fallbackUrl = resolveProtectedAssetUrl(url)
    console.warn(`Image processing failed: ${fallbackUrl}`, error)
    const imageLoaded = await loadNativeImage(fallbackUrl)
    if (imageLoaded) {
      el.src = fallbackUrl
    }
    showImage(el)
  }
}

function loadNativeImage(url) {
  return new Promise((resolve) => {
    const img = new Image()
    img.onload = () => resolve(true)
    img.onerror = () => resolve(false)
    img.src = url
  })
}
