import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'

import { defineConfig } from 'vite'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath, URL } from 'node:url'
import { VantResolver } from 'unplugin-vue-components/resolvers'

const PRODUCTION_LIKE_MODES = new Set(['production', 'production-atu'])
const STRIPPABLE_CONSOLE_METHODS = ['log', 'debug', 'info']
const LOCAL_OSS_MIME_TYPES = {
  '.gif': 'image/gif',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.png': 'image/png',
  '.svg': 'image/svg+xml',
  '.webp': 'image/webp',
}

function isEscaped(code, index) {
  let backslashCount = 0
  let cursor = index - 1

  while (cursor >= 0 && code[cursor] === '\\') {
    backslashCount += 1
    cursor -= 1
  }

  return backslashCount % 2 === 1
}

function findCallEnd(code, openParenIndex) {
  let depth = 0
  let quote = null
  let templateExpressionDepth = 0
  let inLineComment = false
  let inBlockComment = false

  for (let index = openParenIndex; index < code.length; index += 1) {
    const current = code[index]
    const next = code[index + 1]

    if (inLineComment) {
      if (current === '\n') {
        inLineComment = false
      }
      continue
    }

    if (inBlockComment) {
      if (current === '*' && next === '/') {
        inBlockComment = false
        index += 1
      }
      continue
    }

    if (quote) {
      if (quote === '`') {
        if (current === '$' && next === '{' && !isEscaped(code, index)) {
          templateExpressionDepth += 1
          index += 1
          continue
        }

        if (current === '}' && templateExpressionDepth > 0 && !isEscaped(code, index)) {
          templateExpressionDepth -= 1
          continue
        }

        if (current === '`' && templateExpressionDepth === 0 && !isEscaped(code, index)) {
          quote = null
        }
        continue
      }

      if (current === quote && !isEscaped(code, index)) {
        quote = null
      }
      continue
    }

    if (current === '/' && next === '/') {
      inLineComment = true
      index += 1
      continue
    }

    if (current === '/' && next === '*') {
      inBlockComment = true
      index += 1
      continue
    }

    if (current === '"' || current === "'" || current === '`') {
      quote = current
      continue
    }

    if (current === '(') {
      depth += 1
      continue
    }

    if (current === ')') {
      depth -= 1
      if (depth === 0) {
        let endIndex = index + 1

        while (endIndex < code.length && /\s/.test(code[endIndex])) {
          endIndex += 1
        }

        if (code[endIndex] === ';') {
          endIndex += 1
        }

        return endIndex
      }
    }
  }

  return -1
}

function findTokenOutsideLiterals(code, token, fromIndex = 0) {
  let quote = null
  let templateExpressionDepth = 0
  let inLineComment = false
  let inBlockComment = false

  for (let index = fromIndex; index < code.length; index += 1) {
    const current = code[index]
    const next = code[index + 1]

    if (inLineComment) {
      if (current === '\n') {
        inLineComment = false
      }
      continue
    }

    if (inBlockComment) {
      if (current === '*' && next === '/') {
        inBlockComment = false
        index += 1
      }
      continue
    }

    if (quote) {
      if (quote === '`') {
        if (current === '$' && next === '{' && !isEscaped(code, index)) {
          templateExpressionDepth += 1
          index += 1
          continue
        }

        if (current === '}' && templateExpressionDepth > 0 && !isEscaped(code, index)) {
          templateExpressionDepth -= 1
          continue
        }

        if (current === '`' && templateExpressionDepth === 0 && !isEscaped(code, index)) {
          quote = null
        }
        continue
      }

      if (current === quote && !isEscaped(code, index)) {
        quote = null
      }
      continue
    }

    if (current === '/' && next === '/') {
      inLineComment = true
      index += 1
      continue
    }

    if (current === '/' && next === '*') {
      inBlockComment = true
      index += 1
      continue
    }

    if (current === '"' || current === "'" || current === '`') {
      quote = current
      continue
    }

    if (code.startsWith(token, index)) {
      return index
    }
  }

  return -1
}

function stripSelectedConsoleCalls(code) {
  let output = code

  STRIPPABLE_CONSOLE_METHODS.forEach((method) => {
    let searchIndex = 0
    const token = `console.${method}`

    while (searchIndex < output.length) {
      const startIndex = findTokenOutsideLiterals(output, token, searchIndex)

      if (startIndex === -1) {
        break
      }

      let cursor = startIndex + token.length
      while (cursor < output.length && /\s/.test(output[cursor])) {
        cursor += 1
      }

      if (output[cursor] !== '(') {
        searchIndex = startIndex + token.length
        continue
      }

      const endIndex = findCallEnd(output, cursor)
      if (endIndex === -1) {
        searchIndex = cursor + 1
        continue
      }

      output = `${output.slice(0, startIndex)}void 0;${output.slice(endIndex)}`
      searchIndex = startIndex + 'void 0;'.length
    }
  })

  return output
}

function createBuildLogControlPlugin(stripDebugLogs) {
  return {
    name: 'likei-build-log-control',
    apply: 'build',
    enforce: 'post',
    transform(code, id) {
      if (!stripDebugLogs || id.includes('node_modules')) {
        return null
      }

      if (!/\.(vue|js|ts|jsx|tsx|mjs|cjs)(\?|$)/.test(id)) {
        return null
      }

      const transformedCode = stripSelectedConsoleCalls(code)

      if (transformedCode === code) {
        return null
      }

      return {
        code: transformedCode,
        map: null,
      }
    },
  }
}

function createLocalOssAssetPlugin() {
  const ossRoot = fileURLToPath(new URL('./public/oss', import.meta.url))

  return {
    name: 'likei-local-oss-assets',
    apply: 'serve',
    configureServer(server) {
      server.middlewares.use('/oss', (req, res, next) => {
        const rawPath = req.url?.split('?')[0] || '/'
        let decodedPath = ''

        try {
          decodedPath = decodeURIComponent(rawPath)
        } catch {
          next()
          return
        }

        const normalizedPath = path.normalize(decodedPath).replace(/^(\.\.[/\\])+/, '')
        const filePath = path.join(ossRoot, normalizedPath)
        const safeRoot = `${ossRoot}${path.sep}`

        if (!filePath.startsWith(safeRoot)) {
          next()
          return
        }

        if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
          next()
          return
        }

        const contentType = LOCAL_OSS_MIME_TYPES[path.extname(filePath).toLowerCase()]
        if (contentType) {
          res.setHeader('Content-Type', contentType)
        }

        fs.createReadStream(filePath).pipe(res)
      })
    },
  }
}

export default defineConfig(({ mode }) => {
  const keepBuildLogs = process.env.LIKEI_KEEP_BUILD_LOGS === 'true'
  const stripDebugLogs = PRODUCTION_LIKE_MODES.has(mode) && !keepBuildLogs

  return {
    plugins: [
      createLocalOssAssetPlugin(),
      vue(),
      Components({
        resolvers: [VantResolver()],
      }),
      createBuildLogControlPlugin(stripDebugLogs),
    ],
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url)),
      },
    },
    build: {
      minify: 'esbuild',
      sourcemap: false,
      cssMinify: 'esbuild',
      reportCompressedSize: false,
      esbuild: {
        drop: stripDebugLogs ? ['debugger'] : [],
        charset: 'ascii',
        legalComments: 'none',
        minifyIdentifiers: true,
        minifySyntax: true,
        minifyWhitespace: true,
        treeShaking: true,
      },
      rollupOptions: {
        output: {
          entryFileNames: 'js/[hash].js',
          chunkFileNames: 'js/[hash].js',
          assetFileNames: (assetInfo) => {
            const info = assetInfo.name.split('.')
            const ext = info[info.length - 1]

            if (/\.(css)$/.test(assetInfo.name)) {
              return 'css/[hash].[ext]'
            }

            return 'assets/[hash].[ext]'
          },
        },
      },
    },
    server: {
      host: '0.0.0.0',
      port: 5173,
      strictPort: true,
      open: false,
      cors: true,
      proxy: {
        '/oss': {
          target: 'https://asset.global-interaction.com',
          changeOrigin: true,
          secure: true,
          rewrite: (path) => path.replace(/^\/oss/, ''),
          configure: (proxy) => {
            proxy.on('proxyRes', (proxyRes) => {
              delete proxyRes.headers.etag
              delete proxyRes.headers['last-modified']
              delete proxyRes.headers['cache-control']
              delete proxyRes.headers.expires
            })
          },
        },
      },
    },
    preview: {
      host: '0.0.0.0',
      port: 4173,
      strictPort: true,
      open: false,
    },
  }
})
