import hash from "@emotion/hash"
import type { types } from "@babel/core"
import { kebabCase } from "lodash"
import type { BabelPlugin } from "./esbuild-babel"

export const inlineCSSVariables =
  (): BabelPlugin<{ styles: { id: string; light: string; dark: string }[] }> =>
  ({ types: t }) => ({
    name: "inline CSS variables",
    visitor: {
      Program: {
        enter(_, state) {
          state.styles = []
        },
        exit(path, { styles }) {
          if (!styles.length) return

          const css =
            `body.light {${styles.map(s => `--${s.id}:${s.light}`).join(";")}}\n` +
            `body.dark {${styles.map(s => `--${s.id}:${s.dark}`).join(";")}}`

          path.node.body.unshift(
            t.importDeclaration([], t.stringLiteral(`data:text/css,${encodeURI(css)}`)),
          )
        },
      },

      TaggedTemplateExpression(path, state) {
        function join(exp: types.Node): string[] | undefined {
          if (t.isIdentifier(exp)) return [exp.name]
          if (!t.isMemberExpression(exp) || !t.isIdentifier(exp.property)) return
          const prev = t.isIdentifier(exp.object) ? [exp.object.name] : join(exp.object)
          return prev ? [...prev, exp.property.name] : undefined
        }

        const { expressions: exps } = path.node.quasi
        for (const [i, exp] of exps.entries()) {
          if (t.isIdentifier(exp)) {
            if (exp.name === "DARK_MODE") {
              exps[i] = t.stringLiteral("body.dark &")
            } else if (exp.name === "LIGHT_MODE") {
              exps[i] = t.stringLiteral("body.light &")
            }
            continue
          }

          if (
            t.isCallExpression(exp) &&
            t.isIdentifier(exp.callee, { name: "color" }) &&
            exp.arguments.length === 2 &&
            t.isStringLiteral(exp.arguments[0]) &&
            t.isStringLiteral(exp.arguments[1])
          ) {
            const [light, dark] = (exp.arguments as babel.types.StringLiteral[]).map(
              arg => arg.value,
            )
            const id = hash(`${light}-${dark}`)
            state.styles.push({ id, light, dark })
            exps[i] = t.stringLiteral(`var(--${id})`)
            continue
          }

          let ids: string[] | undefined
          if (
            t.isMemberExpression(exp) &&
            t.isIdentifier(exp.property) &&
            (ids = join(exp))
          ) {
            const rest = ids.slice(1).join(".")
            if (ids[0] === "vars") {
              exps[i] = t.stringLiteral(`var(--${kebabCase(rest)})`)
            }
            if (ids[0] === "token") {
              exps[i] = t.stringLiteral(`var(--color-${kebabCase(rest)})`)
            }
          }
        }
      },
    },
  })
