import devalue from '@nuxt/devalue'
import { getRequestURL, setResponseHeader } from 'h3'
import { renderToWebStream } from 'vue/server-renderer'
import { renderSSRHead } from '@unhead/ssr'
import { minify } from 'html-minifier-terser'

import type { VueroServerRender, VueroSSRContext } from '/@server/types'
import { createApp } from '/@src/app'

const placeholderAppRe = /<div id="app"([\s\w\-"'=[\]]*)><\/div>/
const placeholderStream = '<!--vueroplaceholder-->'

export const render: VueroServerRender = async ({
  event,
  manifest,
  template,
}) => {
  const url = getRequestURL(event)

  event.context.initialState = {}

  const { app, router, pinia, head } = await createApp(event)

  // set the router to the desired URL before rendering
  router.push(url.pathname)
  await router.isReady()

  // passing SSR context object which will be available via useSSRContext()
  // @vitejs/plugin-vue injects code into a component's setup() that registers
  // itself on ctx.modules. After the render, ctx.modules would contain all the
  // components that have been instantiated during this render call.
  const ctx: VueroSSRContext = {
    event,
  }

  const stream = renderToWebStream(app, ctx)
  const {
    headTags,
    htmlAttrs,
    bodyAttrs,
    bodyTags,
    bodyTagsOpen,
  } = await renderSSRHead(head)

  event.context.initialState.pinia = pinia?.state.value

  // the SSR manifest generated by Vite contains module -> chunk/asset mapping
  // which we can then use to determine what files need to be preloaded for this
  // request.
  const preloadLinks = renderPreloadLinks(ctx.modules, manifest)

  let wrapper = template
    .replace(`<html>`, `<html${htmlAttrs}>`)
    .replace(`<head>`, `<head>${headTags}`)
    .replace(`</head>`, `${preloadLinks}</head>`)
    .replace(`<body>`, `<body${bodyAttrs}>${bodyTagsOpen}`)
    .replace(`</body>`, `${bodyTags}</body>`)

  // minify app wrapper
  wrapper = await minify(wrapper, {
    collapseWhitespace: true,
    collapseInlineTagWhitespace: true,
    removeComments: true,
    minifyJS: true,
  })

  wrapper = wrapper.replace(
    placeholderAppRe,
    `<div id="app" data-server-rendered="true"$1>${placeholderStream}</div><script>window.__vuero__=${devalue(
      event.context.initialState,
    )}</script>`,
  )

  setResponseHeader(event, 'Content-Type', 'text/html')

  return stream.pipeThrough(wrapTemplate(wrapper))
}

function wrapTemplate(html: string) {
  const [pre, post] = html.split(placeholderStream) as [string, string]

  return new TransformStream<Uint8Array, Uint8Array>({
    start(controller) {
      controller.enqueue(new TextEncoder().encode(pre))
    },
    flush(controller) {
      controller.enqueue(new TextEncoder().encode(post))
    },
  })
}

function renderPreloadLinks(modules?: any, manifest?: any) {
  let links = ''
  if (!modules) return links
  if (!manifest) return links

  const seen = new Set()
  modules?.forEach((id: string) => {
    const files = manifest[id]
    if (files) {
      files.forEach((file: any) => {
        if (!seen.has(file)) {
          seen.add(file)
          links += renderPreloadLink(file)
        }
      })
    }
  })
  return links
}

function renderPreloadLink(file: string) {
  if (file.endsWith('.js') || file.endsWith('.mjs')) {
    return `<link rel="modulepreload" crossorigin href="${file}">`
  }
  else if (file.endsWith('.css')) {
    return `<link rel="stylesheet" href="${file}">`
  }
  else if (file.endsWith('.woff')) {
    return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
  }
  else if (file.endsWith('.woff2')) {
    return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
  }
  else if (file.endsWith('.gif')) {
    return `<link rel="preload" href="${file}" as="image" type="image/gif">`
  }
  else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
    return `<link rel="preload" href="${file}" as="image" type="image/jpeg">`
  }
  else if (file.endsWith('.png')) {
    return `<link rel="preload" href="${file}" as="image" type="image/png">`
  }
  else if (file.endsWith('.webp')) {
    return `<link rel="preload" href="${file}" as="image" type="image/webp">`
  }
  else if (file.endsWith('.svg')) {
    return `<link rel="prefetch" href="${file}" as="image" type="image/svg+xml"/>`
  }
  else {
    console.log('missing preload link for', file)
    return ''
  }
}
