import Image from "@/components/Image"
import Link from "@/components/TempDesignSystem/Link"
import { insertResponseToImageVaultAsset } from "@/utils/imageVault"

import ImageContainer from "../ImageContainer"
import Divider from "../TempDesignSystem/Divider"
import BiroScript from "../TempDesignSystem/Text/BiroScript"
import Body from "../TempDesignSystem/Text/Body"
import Caption from "../TempDesignSystem/Text/Caption"
import Footnote from "../TempDesignSystem/Text/Footnote"
import Subtitle from "../TempDesignSystem/Text/Subtitle"
import Title from "../TempDesignSystem/Text/Title"
import { hasAvailableParagraphFormat, hasAvailableULFormat } from "./utils"

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

import type { EmbedByUid } from "@/types/components/jsontohtml"
import { EmbedEnum } from "@/types/requests/utils/embeds"
import type { Attributes, RTEImageVaultAttrs } from "@/types/rte/attrs"
import {
  AvailableParagraphFormatEnum,
  RTEItemTypeEnum,
  RTETypeEnum,
} from "@/types/rte/enums"
import type {
  RTEDefaultNode,
  RTEImageNode,
  RTENext,
  RTENode,
  RTERegularNode,
  RTETextNode,
} from "@/types/rte/node"
import { RTEMarkType } from "@/types/rte/node"
import type { RenderOptions } from "@/types/rte/option"

function extractPossibleAttributes(attrs: Attributes | undefined) {
  if (!attrs) return {}
  const props: Record<string, any> = {}
  if (attrs.id) {
    props.id = attrs.id
  }

  if (attrs.class) {
    props.className = attrs.class
  } else if (attrs["class-name"]) {
    props.className = attrs["class-name"]
  } else if (attrs.classname) {
    props.className = attrs.classname
  } else if (attrs?.style?.["text-align"]) {
    props.style = {
      textAlign: attrs?.style?.["text-align"],
    }
  }

  return props
}

export const renderOptions: RenderOptions = {
  [RTETypeEnum.a]: (
    node: RTERegularNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    if (node.attrs.url) {
      const props = extractPossibleAttributes(node.attrs)
      return (
        <Link
          {...props}
          href={node.attrs.url}
          key={node.uid}
          target={node.attrs.target ?? "_blank"}
          variant="underscored"
          color="burgundy"
        >
          {next(node.children, embeds, fullRenderOptions)}
        </Link>
      )
    }
    return null
  },

  [RTETypeEnum.blockquote]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <BiroScript key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </BiroScript>
    )
  },

  [RTETypeEnum.code]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <code key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </code>
    )
  },

  [RTETypeEnum.embed]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    if (node.attrs.src) {
      props.src = node.attrs.src
    }
    if (node.attrs.url) {
      props.src = node.attrs.url
    }
    if (!props.src) {
      return null
    }
    return (
      <iframe key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </iframe>
    )
  },

  [RTETypeEnum.h1]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Title key={node.uid} {...props} level="h1">
        {next(node.children, embeds, fullRenderOptions)}
      </Title>
    )
  },

  [RTETypeEnum.h2]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Title key={node.uid} {...props} level="h2">
        {next(node.children, embeds, fullRenderOptions)}
      </Title>
    )
  },

  [RTETypeEnum.h3]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Title key={node.uid} {...props} level="h3">
        {next(node.children, embeds, fullRenderOptions)}
      </Title>
    )
  },

  [RTETypeEnum.h4]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Title key={node.uid} {...props} level="h4">
        {next(node.children, embeds, fullRenderOptions)}
      </Title>
    )
  },

  [RTETypeEnum.h5]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Title key={node.uid} {...props} level="h5">
        {next(node.children, embeds, fullRenderOptions)}
      </Title>
    )
  },

  [RTETypeEnum.hr]: () => {
    return <Divider />
  },

  [RTETypeEnum.li]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <li key={node.uid} {...props} className={styles.li}>
        {next(node.children, embeds, fullRenderOptions)}
      </li>
    )
  },

  [RTETypeEnum.ol]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)

    // Set the number of rows dynamically to create even rows for each column. We want the li:s
    // to flow with the column, so therefore this is needed.
    let numberOfRows: number | undefined
    if (node.children.length > 4) {
      const half = node.children.length / 2
      numberOfRows = Math.ceil(half)
    }

    return (
      <ol
        key={node.uid}
        {...props}
        className={styles.ol}
        style={
          numberOfRows
            ? { gridTemplateRows: `repeat(${numberOfRows}, auto)` }
            : {}
        }
      >
        {next(node.children, embeds, fullRenderOptions)}
      </ol>
    )
  },

  [RTETypeEnum.p]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)

    const hasFormat = node.children.some((item) =>
      hasAvailableParagraphFormat((item as RTETextNode)?.classname)
    )

    // If a child node has an available format as className, we wrap it in a
    // span and render the children with the correct component
    if (hasFormat) {
      return next(node.children, embeds, fullRenderOptions)
    }

    return (
      <Body {...props} key={node.uid}>
        {next(node.children, embeds, fullRenderOptions)}
      </Body>
    )
  },

  [RTETypeEnum.reference]: (
    node: RTENode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    if ("attrs" in node) {
      const type = node.attrs.type
      if (type === RTEItemTypeEnum.asset) {
        const image = embeds?.[node?.attrs?.["asset-uid"]]
        if (image?.node.__typename === EmbedEnum.SysAsset) {
          const alt = image?.node?.title ?? node.attrs.alt
          const props = extractPossibleAttributes(node.attrs)
          props.className = styles.image
          return (
            <Image
              key={node.uid}
              alt={alt}
              height={image.node.dimension.height}
              src={image?.node?.url}
              width={image.node.dimension.width}
              {...props}
            />
          )
        }
      } else if (type === RTEItemTypeEnum.entry) {
        const entry = embeds?.[node?.attrs?.["entry-uid"]]

        if (entry?.node.__typename === EmbedEnum.ImageContainer) {
          const leftImage = insertResponseToImageVaultAsset(
            entry.node.image_left
          )
          const rightImage = insertResponseToImageVaultAsset(
            entry.node.image_right
          )
          return (
            <ImageContainer leftImage={leftImage} rightImage={rightImage} />
          )
        } else {
          // If entry is not an ImageContainer, it is a page and we return it as a link
          const props = extractPossibleAttributes(node.attrs)
          const href = node.attrs?.locale
            ? `/${node.attrs.locale}${node.attrs.href}`
            : node.attrs.href
          return (
            <Link
              {...props}
              href={href}
              key={node.uid}
              variant="underscored"
              color="burgundy"
            >
              {next(node.children, embeds, fullRenderOptions)}
            </Link>
          )
        }
      }
    }

    return null
  },

  [RTETypeEnum.ImageVault]: (node: RTEImageNode) => {
    if ("attrs" in node) {
      const type = node.type
      if (type === RTETypeEnum.ImageVault) {
        const attrs = node.attrs as RTEImageVaultAttrs
        const image = insertResponseToImageVaultAsset(attrs)
        const alt = image.meta.alt ?? image.title

        const width = parseInt(attrs.width.replaceAll("px", ""))
        const props = extractPossibleAttributes(attrs)
        return (
          <section key={node.uid}>
            <Image
              alt={alt}
              className={styles.image}
              height={365}
              src={image.url}
              width={width}
              {...props}
            />
            <Caption>{image.meta.caption}</Caption>
          </section>
        )
      }
    }

    return null
  },

  [RTETypeEnum.table]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <table key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </table>
    )
  },

  [RTETypeEnum.thead]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <thead key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </thead>
    )
  },

  [RTETypeEnum.tbody]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <tbody key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </tbody>
    )
  },

  [RTETypeEnum.tfoot]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <tfoot key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </tfoot>
    )
  },

  [RTETypeEnum.fragment]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    return <>{next(node.children, embeds, fullRenderOptions)}</>
  },

  [RTETypeEnum.tr]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <tr key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </tr>
    )
  },

  [RTETypeEnum.th]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <th key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </th>
    )
  },

  [RTETypeEnum.td]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <td key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </td>
    )
  },

  [RTETypeEnum.ul]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)

    // Set the number of rows dynamically to create even rows for each column. We want the li:s
    // to flow with the column, so therefore this is needed.
    let numberOfRows: number | undefined
    if (node.children.length > 4) {
      const half = node.children.length / 2
      numberOfRows = Math.ceil(half)
    }

    return (
      <ul
        key={node.uid}
        {...props}
        className={styles.ul}
        style={
          numberOfRows
            ? { gridTemplateRows: `repeat(${numberOfRows}, auto)` }
            : {}
        }
      >
        {next(node.children, embeds, fullRenderOptions)}
      </ul>
    )
  },

  /** TextNode wrappers */
  [RTEMarkType.bold]: (children: React.ReactNode) => {
    return <strong>{children}</strong>
  },

  [RTEMarkType.italic]: (children: React.ReactNode) => {
    return <em>{children}</em>
  },

  [RTEMarkType.underline]: (children: React.ReactNode) => {
    return <u>{children}</u>
  },

  [RTEMarkType.strikethrough]: (children: React.ReactNode) => {
    return <s>{children}</s>
  },

  [RTEMarkType.inlineCode]: (children: React.ReactNode) => {
    return <span>{children}</span>
  },

  [RTEMarkType.subscript]: (children: React.ReactNode) => {
    return <sub>{children}</sub>
  },

  [RTEMarkType.superscript]: (children: React.ReactNode) => {
    return <sup>{children}</sup>
  },

  [RTEMarkType.break]: (children: React.ReactNode) => {
    return (
      <>
        <br />
        {children}
      </>
    )
  },

  [RTEMarkType.classnameOrId]: (
    children: React.ReactNode,
    className?: string,
    id?: string
  ) => {
    let props = {
      className,
      id,
    }
    if (!className) {
      delete props.className
    }
    if (!id) {
      delete props.id
    }

    if (className) {
      if (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
        props.className = styles[className]
      }
    }

    if (className === AvailableParagraphFormatEnum.footnote) {
      return (
        <Footnote key={id} {...props}>
          {children}
        </Footnote>
      )
    }

    if (className === AvailableParagraphFormatEnum.caption) {
      return (
        <Caption key={id} {...props}>
          {children}
        </Caption>
      )
    }

    if (className === AvailableParagraphFormatEnum["script-1"]) {
      return (
        <BiroScript key={id} type="one" {...props}>
          {children}
        </BiroScript>
      )
    }

    if (className === AvailableParagraphFormatEnum["script-2"]) {
      return (
        <BiroScript key={id} type="two" {...props}>
          {children}
        </BiroScript>
      )
    }

    if (className === AvailableParagraphFormatEnum["subtitle-1"]) {
      return (
        <Subtitle key={id} {...props}>
          {children}
        </Subtitle>
      )
    }
    if (className === AvailableParagraphFormatEnum["subtitle-2"]) {
      return (
        <Subtitle key={id} {...props}>
          {children}
        </Subtitle>
      )
    }
    return (
      <span key={id} {...props}>
        {children}
      </span>
    )
  },

  /**
   * Contentstack can return something called `default` as seen here in their
   * own SDK (https://github.com/contentstack/contentstack-utils-javascript/blob/master/src/options/default-node-options.ts#L89)
   */
  default: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    return next(node.children, embeds, fullRenderOptions)
  },
}
