import { relative, resolve } from "path"
import { promises as fs } from "fs"
import type { Plugin } from "esbuild"
import postcss from "postcss"
// @ts-expect-error
import postcssSass from "@csstools/postcss-sass"
import cssModules from "postcss-modules"

type Options = Parameters<typeof cssModules>[0]

const PLUGIN_NAME = "esbuild-css-modules"

async function buildCSSModule(cssFullPath: string, options: Options) {
  options = {
    localsConvention: "camelCaseOnly",
    ...options,
  }
  const source = await fs.readFile(cssFullPath)

  let classNames = {}
  const { css } = await postcss([
    postcssSass(),
    cssModules({
      getJSON(_, json) {
        classNames = json
        return classNames
      },
      ...options,
    }),
  ]).process(source, {
    from: cssFullPath,
    map: false,
  })

  return {
    css,
    classNames,
  }
}

const srcDir = resolve(__dirname, "../../src")

const plugin = (options: Options = {}): Plugin => ({
  name: PLUGIN_NAME,
  async setup(build) {
    const memfs = new Map<string, string>()
    const FS_NAMESPACE = PLUGIN_NAME + "-fs"

    build.onResolve({ filter: /\.modules?\.s?css$/, namespace: "file" }, async args => {
      const res = await build.resolve(args.path, {
        kind: "import-statement",
        resolveDir: args.resolveDir,
      })

      // This is just the unique ID for this CSS module. We use a relative path to make it easier to debug.
      const path = relative(srcDir, res.path)

      return {
        path,
        namespace: PLUGIN_NAME,
        pluginData: {
          realPath: res.path,
        },
      }
    })

    build.onResolve({ filter: /^@css-modules\/.*/ }, ({ path }) => ({
      path,
      namespace: FS_NAMESPACE,
    }))

    build.onLoad({ filter: /./, namespace: FS_NAMESPACE }, ({ path }) => ({
      contents: memfs.get(path)!,
      loader: "css",
    }))

    build.onLoad({ filter: /./, namespace: PLUGIN_NAME }, async args => {
      const tmpFilePath = "@css-modules/" + args.path
      const { classNames, css } = await buildCSSModule(args.pluginData.realPath, options)
      memfs.set(tmpFilePath, css)

      return {
        contents:
          `import ${JSON.stringify(tmpFilePath)};\n` +
          `module.exports = ${JSON.stringify(classNames)};`,
        loader: "js",
      }
    })
  },
})

export { plugin as cssModules }
