import fetch from 'node-fetch'
import path from 'path'
import { URL } from 'url'
import { fileCache } from './files.js'
import { config } from './config.js'
import { hash } from './util.js'

export const loadViteManifests = (() => {
  let cachePromise
  let lastCacheTime
  let lastHash

  async function reload () {
    await config.load()
    // vite manifests contains a set of objects with the vite-manifests
    // from the corresponding registered services
    const viteManifests = await Promise.all(config.urls.map(async url => {
      const { origin } = new URL(url)
      // fetch the manifests
      const result = await fetch(url)
      if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`)
      try {
        const manifest = await result.json()
        for (const file in manifest) {
          manifest[file].meta = manifest[file].meta || {}
          manifest[file].meta.baseUrl = origin
        }
        return manifest
      } catch (err) {
        throw new Error(`Failed to load manifest for url ${result.url}: ${err}`)
      }
    }))

    // combine all manifests by keys. With duplicates, last wins
    return viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
  }

  return function loadViteManifests ({ useCache = true } = {}) {
    const CACHE_TTL = parseInt(process.env.CACHE_TTL)

    if (!cachePromise || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) {
      cachePromise = reload()
      cachePromise.then(manifests => {
        // update cache promise
        const newHash = hash(manifests)
        if (newHash !== lastHash) {
          if (lastHash) {
            const deps = viteManifestToDeps(manifests)
            // asynchronously rewarm the cache
            fileCache.warmUp(manifests, deps)
          }
          lastHash = newHash
          Object.defineProperty(manifests, '__hash__', {
            enumerable: false,
            writable: true
          })
          manifests.__hash__ = newHash
        }
      })
      lastCacheTime = +new Date()
    }
    return cachePromise
  }
})()

export function viteToOxManifest (viteManifests) {
  const deps = viteManifestToDeps(viteManifests)
  return Object.values(viteManifests)
    .filter(manifest => Array.isArray(manifest?.meta?.manifests))
    .map(manifest =>
      manifest.meta.manifests.map(oxManifest => {
        const dependencies = deps[manifest.file]
        const data = {
          ...oxManifest,
          path: manifest.file.slice(0, -path.parse(manifest.file).ext.length)
        }
        if (dependencies?.length > 0) data.dependencies = dependencies
        return data
      })
    )
    .flat()
}

export const getOxManifests = (() => {
  let prevViteManifest
  let oxManifestCache
  return async function getOxManifests () {
    const viteManifest = await loadViteManifests()
    if (viteManifest !== prevViteManifest) {
      oxManifestCache = viteToOxManifest(viteManifest)
      prevViteManifest = viteManifest
    }
    return oxManifestCache
  }
})()

export function viteManifestToDeps (viteManifest) {
  const deps = {}
  for (const [codePoint, { isEntry, file, imports, css, assets }] of Object.entries(viteManifest)) {
    if (isEntry && codePoint.endsWith('.html')) deps[codePoint] = [file]
    if (Array.isArray(assets)) assets.forEach(asset => { deps[asset] = [] })
    if (Array.isArray(css)) css.forEach(css => { deps[css] = [] })
    deps[file] = []
      .concat(imports?.map(path => viteManifest[path].file))
      .concat(css)
      .concat(assets)
      .filter(Boolean)
  }
  return deps
}

export const getDependencies = (() => {
  let prevViteManifest
  let depCache
  return async function getDependencies () {
    const viteManifest = await loadViteManifests()
    if (viteManifest !== prevViteManifest) {
      depCache = viteManifestToDeps(viteManifest)
      prevViteManifest = viteManifest
    }
    return depCache
  }
})()

export async function getCSSDependenciesFor (file) {
  const allDependencies = await getDependencies()
  const dependencies = allDependencies[file] || []
  return dependencies.filter(dep => /\.css/i.test(dep))
}

export const getVersion = (() => {
  let prevViteManifest
  let version
  let versionString
  return async function getVersion () {
    const viteManifest = await loadViteManifests()
    if (viteManifest !== prevViteManifest) {
      const newVersion = hash(viteManifest)
      if (newVersion !== version) {
        versionString = `${+new Date()}.${newVersion}`
        version = newVersion
      }
    }
    return versionString
  }
})()