import { readFileSync } from 'node:fs'
import { mkdir, rm, writeFile } from 'node:fs/promises'

import { relative, resolve } from 'pathe'
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
import escapeRE from 'escape-string-regexp'
import { normalizeViteManifest } from 'vue-bundle-renderer'
import type { Manifest as RendererManifest } from 'vue-bundle-renderer'
import type { Manifest as ViteClientManifest } from 'vite'
import type { ViteBuildContext } from './vite'

export async function writeManifest (ctx: ViteBuildContext, css: string[] = []) {
  // Write client manifest for use in vue-bundle-renderer
  const clientDist = resolve(ctx.nuxt.options.buildDir, 'dist/client')
  const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server')

  const devClientManifest: RendererManifest = {
    '@vite/client': {
      isEntry: true,
      file: '@vite/client',
      css,
      module: true,
      resourceType: 'script',
    },
    [ctx.entry]: {
      isEntry: true,
      file: ctx.entry,
      module: true,
      resourceType: 'script',
    },
  }

  const manifestFile = resolve(clientDist, 'manifest.json')
  const clientManifest = ctx.nuxt.options.dev
    ? devClientManifest
    : JSON.parse(readFileSync(manifestFile, 'utf-8')) as ViteClientManifest

  const manifestEntries = Object.values(clientManifest)

  const buildAssetsDir = withTrailingSlash(withoutLeadingSlash(ctx.nuxt.options.app.buildAssetsDir))
  const BASE_RE = new RegExp(`^${escapeRE(buildAssetsDir)}`)

  for (const entry of manifestEntries) {
    if (entry.file) {
      entry.file = entry.file.replace(BASE_RE, '')
    }
    for (const item of ['css', 'assets'] as const) {
      if (entry[item]) {
        entry[item] = entry[item].map((i: string) => i.replace(BASE_RE, ''))
      }
    }
  }

  await mkdir(serverDist, { recursive: true })

  if (ctx.config.build?.cssCodeSplit === false) {
    for (const entry of manifestEntries) {
      if (entry.file?.endsWith('.css')) {
        const key = relative(ctx.config.root!, ctx.entry)
        clientManifest[key]!.css ||= []
        ;(clientManifest[key]!.css as string[]).push(entry.file)
        break
      }
    }
  }

  const manifest = normalizeViteManifest(clientManifest)
  await ctx.nuxt.callHook('build:manifest', manifest)
  const stringifiedManifest = JSON.stringify(manifest, null, 2)
  await writeFile(resolve(serverDist, 'client.manifest.json'), stringifiedManifest, 'utf8')
  await writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default ' + stringifiedManifest, 'utf8')

  if (!ctx.nuxt.options.dev) {
    await rm(manifestFile, { force: true })
  }
}
