import fs from 'node:fs/promises'
import path from 'node:path'

import {type Plugin} from 'vite'

import {generateWebManifest} from '../webManifest'

const mimeTypes: Record<string, string | undefined> = {
  '.ico': 'image/x-icon',
  '.svg': 'image/svg+xml',
  '.png': 'image/png',
}

/**
 * Fallback favicons plugin for Sanity.
 *
 * If a favicon is not found in the static folder, this plugin will serve the default
 * Sanity favicons from the npm bundle. If a custom `favicon.ico` is found in the static
 * folder, it will also be served for a root `/favicon.ico` request.
 *
 * @param options - Options for the plugin
 * @returns A Vite plugin
 * @internal
 */
export function sanityFaviconsPlugin({
  defaultFaviconsPath,
  customFaviconsPath,
  staticUrlPath,
}: {
  defaultFaviconsPath: string
  customFaviconsPath: string
  staticUrlPath: string
}): Plugin {
  const cache: {favicons?: string[]} = {}

  async function getFavicons(): Promise<string[]> {
    if (cache.favicons) {
      return cache.favicons
    }

    cache.favicons = await fs.readdir(defaultFaviconsPath)
    return cache.favicons
  }

  async function hasCustomFavicon(fileName: string): Promise<boolean> {
    try {
      await fs.access(path.join(customFaviconsPath, fileName))
      return true
    } catch (err) {
      return false
    }
  }

  return {
    name: 'sanity/server/sanity-favicons',
    apply: 'serve',
    configureServer(viteDevServer) {
      const webManifest = JSON.stringify(generateWebManifest(staticUrlPath), null, 2)
      const webManifestPath = `${staticUrlPath}/manifest.webmanifest`

      viteDevServer.middlewares.use(async (req, res, next) => {
        if (req.url?.endsWith(webManifestPath)) {
          res.writeHead(200, 'OK', {'content-type': 'application/manifest+json'})
          res.write(webManifest)
          res.end()
          return
        }

        const parsedUrl =
          ((req as any)._parsedUrl as URL) || new URL(req.url || '/', 'http://localhost:3333')

        const pathName = parsedUrl.pathname || ''
        const fileName = path.basename(pathName || '')
        const icons = await getFavicons()
        const isIconRequest =
          pathName.startsWith('/favicon.ico') ||
          (icons.includes(fileName) && pathName.includes(staticUrlPath))

        if (!isIconRequest) {
          next()
          return
        }

        const faviconPath = (await hasCustomFavicon(fileName))
          ? path.join(customFaviconsPath, fileName)
          : path.join(defaultFaviconsPath, fileName)

        const mimeType = mimeTypes[path.extname(fileName)] || 'application/octet-stream'
        res.writeHead(200, 'OK', {'content-type': mimeType})
        res.write(await fs.readFile(faviconPath))
        res.end()
      })
    },
  }
}
