import React, { cloneElement } from "react"

import { renderOptions } from "./renderOptions"

import styles from "./jsontohtml.module.css"

import type { Node } from "@/types/requests/utils/edges"
import type { EmbedByUid } from "@/types/transitionTypes/jsontohtml"
import {
  AvailableParagraphFormatEnum,
  AvailableULFormatEnum,
  RTETypeEnum,
} from "@/types/transitionTypes/rte/enums"
import {
  RTEMarkType,
  type RTENode,
  type RTERenderMark,
  type RTERenderOptionComponent,
  type RTETextNode,
} from "@/types/transitionTypes/rte/node"
import type { RenderOptions } from "@/types/transitionTypes/rte/option"
import type { Embeds } from "@/types/trpc/routers/utils/embeds"

export function groupEmbedsByUid(embedsArray: Node<Embeds>[]) {
  const embedsByUid = embedsArray.reduce<EmbedByUid>((acc, embed) => {
    if (embed.node.system?.uid) {
      acc[embed.node.system.uid] = embed
    }
    return acc
  }, {})

  return embedsByUid
}

export function nodeChildrenToHtml(
  nodes: RTENode[],
  embeds: EmbedByUid,
  fullRenderOptions: RenderOptions
): any {
  return nodes
    .map((node, i) => {
      // This function either returns a JSX element or null
      const element = nodeToHtml(node, embeds, fullRenderOptions)
      if (!element) {
        return null
      }

      return cloneElement(element, {
        key: `child-rte-${i}`,
      })
    })
    .filter(Boolean)
}

export function textNodeToHtml(
  node: RTETextNode,
  fullRenderOptions: RenderOptions
) {
  let text = <>{node.text}</>

  if (node.classname || node.id) {
    text = (fullRenderOptions[RTEMarkType.classnameOrId] as RTERenderMark)(
      text,
      node.classname,
      node.id
    )
  }
  if (node.break) {
    text = (fullRenderOptions[RTEMarkType.break] as RTERenderMark)(text)
  }
  if (node.superscript) {
    text = (fullRenderOptions[RTEMarkType.superscript] as RTERenderMark)(text)
  }
  if (node.subscript) {
    text = (fullRenderOptions[RTEMarkType.subscript] as RTERenderMark)(text)
  }
  if (node.inlineCode) {
    text = (fullRenderOptions[RTEMarkType.inlineCode] as RTERenderMark)(text)
  }
  if (node.strikethrough) {
    text = (fullRenderOptions[RTEMarkType.strikethrough] as RTERenderMark)(text)
  }
  if (node.underline) {
    text = (fullRenderOptions[RTEMarkType.underline] as RTERenderMark)(text)
  }
  if (node.italic) {
    text = (fullRenderOptions[RTEMarkType.italic] as RTERenderMark)(text)
  }
  if (node.bold) {
    text = (fullRenderOptions[RTEMarkType.bold] as RTERenderMark)(text)
  }
  return text
}

function next(
  nodes: RTENode[],
  embeds: EmbedByUid,
  fullRenderOptions: RenderOptions
) {
  return nodeChildrenToHtml(nodes, embeds, fullRenderOptions)
}

export function hasAvailableParagraphFormat(className?: string) {
  if (!className) {
    return false
  }
  return Object.keys(AvailableParagraphFormatEnum).includes(className)
}

export function hasAvailableULFormat(className?: string) {
  if (!className) {
    return false
  }
  return Object.keys(AvailableULFormatEnum).includes(className)
}

export function nodeToHtml(
  node: RTENode,
  embeds: EmbedByUid,
  fullRenderOptions: RenderOptions
) {
  if ("type" in node === false) {
    return textNodeToHtml(node, fullRenderOptions)
  } else {
    const renderer = fullRenderOptions[node.type] as RTERenderOptionComponent
    if (renderer) {
      if (node.type === RTETypeEnum.doc) {
        return null
      }

      return renderer(node, embeds, next, fullRenderOptions)
    } else {
      return next(node.children, embeds, fullRenderOptions)
    }
  }
}

function getUniqueId(node: RTENode) {
  if ("uid" in node) {
    return node.uid
  }
  return node.id
}

export function nodesToHtml(
  nodes: RTENode[],
  embedsArray: Node<Embeds>[],
  overrideRenderOptions: RenderOptions
) {
  const embeds = groupEmbedsByUid(embedsArray)
  const fullRenderOptions = { ...renderOptions, ...overrideRenderOptions }

  return nodes.map((node, index) => {
    const nodeHtml = nodeToHtml(node, embeds, fullRenderOptions)

    return (
      <React.Fragment key={getUniqueId(node) ?? `node-${index}`}>
        {nodeHtml}
      </React.Fragment>
    )
  })
}

export function makeCssModuleCompatibleClassName(
  className: string | undefined,
  formatType: "ul"
): string {
  if (!className) return ""

  if (formatType === "ul" && hasAvailableULFormat(className)) {
    // @ts-ignore: We want to set css modules classNames even if it does not correspond
    // to an existing class in the module style sheet. Due to our css modules plugin for
    // typescript, we cannot do this without the ts-ignore
    return styles[className] || className
  }

  return className
}
