import crypto from 'crypto'
import { createUnplugin } from 'unplugin'
import { parse } from 'acorn'
import MagicString from 'magic-string'
import { walk } from 'estree-walker'
import { parseQuery, parseURL } from 'ufo'

function createKey (
  source: string,
  method: crypto.BinaryToTextEncoding = 'base64'
) {
  const hash = crypto.createHash('md5')
  hash.update(source)
  return hash.digest(method).toString()
}

const keyedFunctions =
  /(useStatic|shallowSsrRef|ssrPromise|ssrRef|reqSsrRef|useAsync)/

export const KeyPlugin = createUnplugin(() => {
  return {
    name: 'nuxt-legacy-capi-key-transform',
    enforce: 'pre',
    transformInclude (id) {
      const { pathname, search } = parseURL(id)
      const query = parseQuery(search)

      if (id.includes('node_modules')) {
        return false
      }

      // vue files
      if (pathname.endsWith('.vue') && (query.type === 'script' || !search)) {
        return true
      }

      // js files
      if (pathname.match(/\.((c|m)?j|t)sx?/g)) {
        return true
      }
    },
    transform (code, id) {
      if (!keyedFunctions.test(code)) { return null }

      try {
        const { 0: script = code, index: codeIndex = 0 } =
          code.match(/(?<=<script[^>]*>)[\S\s.]*?(?=<\/script>)/) || []
        const ast = parse(script, { ecmaVersion: 2020, sourceType: 'module' })
        const s = new MagicString(code)

        walk(ast, {
          enter (node) {
            const { end } = node as unknown as {
              end: number
            }
            const { callee, arguments: args = [] } = node as {
              callee?: {
                type?: string
                name?: string
                property?: { type: string; name: string }
              }
              arguments?: any[]
            }
            if (
              callee?.type === 'Identifier' ||
              callee?.property?.type === 'Identifier'
            ) {
              let method: crypto.BinaryToTextEncoding = 'base64'

              switch (callee.name || callee.property?.name) {
                case 'useStatic':
                  if (args.length > 2) { return }
                  if (args.length === 2) {
                    s.prependLeft(codeIndex + end - 1, ', undefined')
                  }
                  method = 'hex'
                  break

                case 'shallowSsrRef':
                case 'ssrPromise':
                case 'ssrRef':
                case 'reqSsrRef':
                case 'useAsync':
                  if (args.length > 1) { return }
                  break

                default:
                  return
              }
              s.appendLeft(
                codeIndex + end - 1,
                ", '" + createKey(`${id}-${end}`, method) + "'"
              )
            }
          }
        })

        return s.toString()
      } catch { }
    }
  }
})
