import type { Plugin, ViteDevServer } from 'vite'
import type { AutomockPluginOptions } from './automockPlugin'
import type { HoistMocksPluginOptions } from './hoistMocksPlugin'

import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { resolve } from 'pathe'
import { automockPlugin } from './automockPlugin'
import { dynamicImportPlugin } from './dynamicImportPlugin'
import { hoistMocksPlugin } from './hoistMocksPlugin'
import { interceptorPlugin } from './interceptorPlugin'
import { ServerMockResolver } from './resolver'

interface MockerPluginOptions extends AutomockPluginOptions {
  hoistMocks?: HoistMocksPluginOptions
}

// this is an implementation for public usage
// vitest doesn't use this plugin directly

export function mockerPlugin(options: MockerPluginOptions = {}): Plugin[] {
  let server: ViteDevServer
  const registerPath = resolve(fileURLToPath(new URL('./register.js', import.meta.url)))
  return [
    {
      name: 'vitest:mocker:ws-rpc',
      config(_, { command }) {
        if (command !== 'serve') {
          return
        }
        return {
          server: {
            // don't pre-transform request because they might be mocked at runtime
            preTransformRequests: false,
          },
          optimizeDeps: {
            exclude: ['@vitest/mocker/register', '@vitest/mocker/browser'],
          },
        }
      },
      configureServer(server_) {
        server = server_
        const mockResolver = new ServerMockResolver(server)
        server.ws.on('vitest:mocks:resolveId', async ({ id, importer }: { id: string; importer: string }) => {
          const resolved = await mockResolver.resolveId(id, importer)
          server.ws.send('vitest:mocks:resolvedId:result', resolved)
        })
        server.ws.on('vitest:mocks:resolveMock', async ({ id, importer, options }: { id: string; importer: string; options: any }) => {
          const resolved = await mockResolver.resolveMock(id, importer, options)
          server.ws.send('vitest:mocks:resolveMock:result', resolved)
        })
        server.ws.on('vitest:mocks:invalidate', async ({ ids }: { ids: string[] }) => {
          mockResolver.invalidate(ids)
          server.ws.send('vitest:mocks:invalidate:result')
        })
      },
      async load(id) {
        if (id !== registerPath) {
          return
        }

        if (!server) {
          // mocker doesn't work during build
          return 'export {}'
        }

        const content = await readFile(registerPath, 'utf-8')
        const result = content
          .replace(
            /__VITEST_GLOBAL_THIS_ACCESSOR__/g,
            options.globalThisAccessor ?? '"__vitest_mocker__"',
          )
          .replace(
            '__VITEST_MOCKER_ROOT__',
            JSON.stringify(server.config.root),
          )
        return result
      },
    },
    hoistMocksPlugin(options.hoistMocks),
    interceptorPlugin(options),
    automockPlugin(options),
    dynamicImportPlugin(options),
  ]
}
