import { imageCacheManager } from '@/utils/image/imageCacheManager'

const DEFAULT_PRELOAD_CONCURRENCY = 3

function normalizeSources(sources) {
  let urls = []

  if (Array.isArray(sources)) {
    urls = sources
  } else if (sources && typeof sources === 'object') {
    urls = Object.values(sources)
  }

  return [...new Set(urls.filter((url) => typeof url === 'string' && url.trim()))]
}

function getSafeConcurrency(concurrency) {
  const value = Number(concurrency)

  if (!Number.isFinite(value) || value <= 0) {
    return DEFAULT_PRELOAD_CONCURRENCY
  }

  return Math.max(1, Math.min(Math.floor(value), 6))
}

async function runWithConcurrency(items, concurrency, worker) {
  const results = new Array(items.length)
  let cursor = 0

  async function runNext() {
    const currentIndex = cursor
    cursor += 1

    if (currentIndex >= items.length) {
      return
    }

    try {
      results[currentIndex] = {
        status: 'fulfilled',
        value: await worker(items[currentIndex], currentIndex),
      }
    } catch (error) {
      results[currentIndex] = {
        status: 'rejected',
        reason: error,
      }
    }

    await runNext()
  }

  const workers = Array.from({ length: Math.min(concurrency, items.length) }, runNext)
  await Promise.all(workers)

  return results
}

function waitForIdle(timeout = 1200) {
  if (typeof window === 'undefined') {
    return Promise.resolve()
  }

  if (typeof window.requestIdleCallback === 'function') {
    return new Promise((resolve) => {
      window.requestIdleCallback(resolve, { timeout })
    })
  }

  return new Promise((resolve) => {
    window.setTimeout(resolve, 16)
  })
}

export const preloadImages = async (sources, options = {}) => {
  const uniqueUrls = normalizeSources(sources)

  if (!uniqueUrls.length) {
    return []
  }

  if (options.idle) {
    await waitForIdle(options.idleTimeout)
  }

  const results = await runWithConcurrency(
    uniqueUrls,
    getSafeConcurrency(options.concurrency),
    (url) => imageCacheManager.scheduleRequest(url),
  )

  const failedResults = results.filter((result) => result?.status === 'rejected')

  if (failedResults.length && options.throwOnError) {
    throw failedResults[0].reason
  }

  return results
}

export const preloadImagesIdle = (sources, options = {}) => {
  return preloadImages(sources, {
    ...options,
    idle: true,
  })
}
