"use client"

import { useMemo, useState } from "react"
import type { ComponentProps, ComponentType, ReactNode } from "react"
import { LexicalComposer } from "@lexical/react/LexicalComposer"
import { TextNode } from "lexical"
import { I18nProvider, useI18nProvider } from "@workspace/i18n"
import { cn } from "@workspace/ui/lib/utils"

import { LexicalTextNode } from "./nodes/text-node"
import { lexicalTheme } from "./theme"
import { LexicalEmbedProvider } from "./embed"
import { LexicalHtmlPlugin } from "./plugins/html-value-plugin"
import {
  compileLexicalActions,
  findLexicalActions,
} from "./action-declarations"
import { LexicalActionsProvider } from "./actions-context"
import { messages as englishMessages } from "./locales/en"

const pluginKeys = new WeakMap<ComponentType, string>()
let nextPluginKey = 0

function getPluginKey(plugin: ComponentType) {
  const existingKey = pluginKeys.get(plugin)
  if (existingKey) return existingKey

  const key = `${plugin.displayName || plugin.name || "plugin"}:${nextPluginKey}`
  nextPluginKey += 1
  pluginKeys.set(plugin, key)
  return key
}

export interface LexicalRootProps extends Omit<
  ComponentProps<"div">,
  "onChange"
> {
  namespace?: string
  onChange: (html: string) => void
  value?: string
}

function LexicalI18nBoundary({ children }: { children: ReactNode }) {
  const hasI18nProvider = useI18nProvider()

  if (hasI18nProvider) return children

  return (
    <I18nProvider catalogs={{ en: englishMessages }} locale="en">
      {children}
    </I18nProvider>
  )
}

export function LexicalRoot({
  className,
  children,
  namespace = "lexical-editor",
  onChange,
  value,
  ...props
}: LexicalRootProps) {
  const [compiledActions] = useState(() =>
    compileLexicalActions(findLexicalActions(children))
  )
  const initialConfig = useMemo(
    () => ({
      namespace,
      nodes: [
        LexicalTextNode,
        {
          replace: TextNode,
          with: (node: TextNode) => new LexicalTextNode(node.getTextContent()),
          withKlass: LexicalTextNode,
        },
        ...compiledActions.nodes,
      ],
      onError: (error: Error) => {
        throw error
      },
      theme: lexicalTheme,
    }),
    [compiledActions.nodes, namespace]
  )

  return (
    <LexicalI18nBoundary>
      <LexicalComposer initialConfig={initialConfig}>
        <LexicalActionsProvider actions={compiledActions.actions}>
          <LexicalEmbedProvider definitions={compiledActions.embeds}>
            <div
              className={cn(
                "overflow-hidden rounded-lg border bg-background",
                className
              )}
              {...props}
            >
              {children}
            </div>
            {compiledActions.plugins.map((Plugin) => (
              <Plugin key={getPluginKey(Plugin)} />
            ))}
            <LexicalHtmlPlugin value={value} onChange={onChange} />
          </LexicalEmbedProvider>
        </LexicalActionsProvider>
      </LexicalComposer>
    </LexicalI18nBoundary>
  )
}
