import { readFile, stat } from 'node:fs/promises'
import * as path from 'node:path'
import type { OnLoadResult, Plugin } from 'esbuild'
import { babelProcessCode } from './babel.js'
import {
  debugLogger,
  generateID,
  getAllFiles,
  isCached,
  makeContents,
  setCache,
} from './shared.js'

export const metaPlugin = (
  options: Based.UI.Extractor.PluginOptions,
): Plugin => {
  const {
    storiesDir,
    virtualModuleId = 'virtual:@based/ui/stories',
    packageName = '',
    cacheSize = 1000,
    debug = false,
  } = options

  const namespace = 'based-ui-stories'
  const absoluteStoriesDir = path.isAbsolute(storiesDir)
    ? storiesDir
    : path.resolve(process.cwd(), storiesDir)

  const cache: Based.UI.Extractor.Cache = new Map()

  const logger = debugLogger(debug)

  const metaData = {} as Based.UI.Extractor.MetaResult
  const blocksData = {} as Based.UI.Extractor.BlocksResult

  return {
    name: 'collect-meta-plugin',
    setup(build) {
      build.onResolve({ filter: /^virtual:@based\/ui\/stories$/ }, () => ({
        path: virtualModuleId,
        namespace,
      }))

      build.onLoad(
        { filter: /.*/, namespace },
        async (): Promise<OnLoadResult> => {
          const storyFiles = await getAllFiles(
            absoluteStoriesDir,
            '.stories.tsx',
          )

          const stats = await Promise.all(
            storyFiles.map((filePath) => stat(filePath)),
          )

          const filesToProcess: string[] = []

          for (let i = 0; i < storyFiles.length; i++) {
            const filePath = storyFiles[i]
            const mtimeMs = stats[i].mtimeMs

            if (isCached(filePath, mtimeMs, cache)) {
              const cachedEntry = cache.get(filePath)

              if (cachedEntry?.meta && cachedEntry?.blocks) {
                metaData[generateID(cachedEntry.meta.name)] = {
                  file: path.relative(absoluteStoriesDir, filePath),
                  meta: {
                    ...cachedEntry.meta,
                    packageName,
                  },
                }

                blocksData[generateID(cachedEntry.meta.name)] = {
                  file: path.relative(absoluteStoriesDir, filePath),
                  blocks: cachedEntry.blocks,
                }
              }
            } else {
              filesToProcess.push(filePath)
            }
          }

          if (!filesToProcess.length) {
            return {
              contents: makeContents(metaData, blocksData),
              loader: 'js',
            }
          }

          logger('│', true)
          logger(
            `Processing ${filesToProcess.length} files in parallel... This may take a while...`,
          )

          const startTime = performance.now()
          let processedFiles: number = 0

          await Promise.all(
            filesToProcess.map(async (filePath) => {
              const file = path.relative(absoluteStoriesDir, filePath)

              const fileContent = await readFile(filePath, 'utf-8')
              const { metaObject, blocksArray } = await babelProcessCode(
                filePath,
                fileContent,
              )

              if (metaObject && blocksArray) {
                metaData[generateID(metaObject.name)] = {
                  file,
                  meta: {
                    ...metaObject,
                    packageName,
                  },
                }

                blocksData[generateID(metaObject.name)] = {
                  file,
                  blocks: blocksArray,
                }

                const mtimeMs = (await stat(filePath)).mtimeMs

                setCache(
                  filePath,
                  { mtimeMs, meta: metaObject, blocks: blocksArray },
                  cache,
                  cacheSize,
                )

                processedFiles++
              }
            }),
          )

          const endTime = performance.now()
          const processedTime = (endTime - startTime) / 1000

          logger('│', true)
          logger(
            `${processedFiles}/${filesToProcess.length} files processed and cached. Total time spent: ${processedTime.toFixed(2)} | Average time per file: ${(processedTime / filesToProcess.length).toFixed(2)}`,
          )

          return {
            contents: makeContents(metaData, blocksData),
            loader: 'js',
          }
        },
      )

      // build.onEnd(async () => {
      //   const outDir = 'dist'
      //   await mkdir(outDir, { recursive: true })

      //   const metaPath = path.join(outDir, 'metaData.json')
      //   const blocksPath = path.join(outDir, 'blocksData.json')

      //   await writeFile(metaPath, JSON.stringify(metaData, null, 2), 'utf8')
      //   await writeFile(blocksPath, JSON.stringify(blocksData, null, 2), 'utf8')
      // })
    },
  }
}
