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

import ImageContainer from "../ImageContainer"
import Divider from "../TempDesignSystem/Divider"
import Table from "../TempDesignSystem/Table"
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/deprecatedjsontohtml"
import type { ImageVaultAsset } from "@/types/components/imageVault"
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,
  type RTEImageNode,
  RTEMarkType,
  type RTENext,
  type RTENode,
  type RTERegularNode,
  type RTETextNode,
} 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"}
          textDecoration="underline"
        >
          {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) {
          if (entry.node.image_left && entry.node.image_right) {
            if ("dimensions" in entry.node.image_left) {
              // Only temp until all ImageVaultAssets are
              // handled in schema.transform()
              return (
                <ImageContainer
                  leftImage={
                    entry.node.image_left as unknown as ImageVaultAsset
                  }
                  rightImage={
                    entry.node.image_right as unknown as ImageVaultAsset
                  }
                />
              )
            }
            const leftImage = insertResponseToImageVaultAsset(
              entry.node.image_left
            )
            const rightImage = insertResponseToImageVaultAsset(
              entry.node.image_right
            )
            return (
              <ImageContainer leftImage={leftImage} rightImage={rightImage} />
            )
          }
          return null
        } else if (
          entry?.node.__typename === EmbedEnum.LoyaltyPage ||
          entry?.node.__typename === EmbedEnum.ContentPage ||
          entry?.node.__typename === EmbedEnum.AccountPage
        ) {
          // If entry is not an ImageContainer, it is a page and we return it as a link
          const props = extractPossibleAttributes(node.attrs)
          let href = ""
          if (entry?.node.__typename === EmbedEnum.AccountPage) {
            href = removeMultipleSlashes(
              `/${entry.node.system.locale}${entry.node.url}`
            )
          } else {
            href =
              entry.node?.web?.original_url ||
              removeMultipleSlashes(
                `/${entry.node.system.locale}${entry.node.url}`
              )
          }

          return (
            <Link
              {...props}
              href={href}
              key={node.uid}
              textDecoration="underline"
            >
              {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
        let image = undefined
        if ("dimensions" in attrs) {
          image = attrs
        } else {
          image = insertResponseToImageVaultAsset(attrs)
        }
        const alt = image.meta.alt ?? image.title

        const width = attrs.width
          ? parseInt(attrs.width.replaceAll("px", ""))
          : image.dimensions.width
        const props = extractPossibleAttributes(attrs)
        return (
          <section key={node.uid}>
            <Image
              alt={alt}
              className={styles.image}
              height={365}
              src={image.url}
              width={width}
              focalPoint={image.focalPoint}
              {...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 (
      <div className={styles.tableContainer}>
        <Table key={node.uid} {...props}>
          {next(node.children, embeds, fullRenderOptions)}
        </Table>
      </div>
    )
  },

  [RTETypeEnum.thead]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    // Override the styling of p tags inside the thead tag
    const theadChildPRenderOptions = {
      ...fullRenderOptions,
      [RTETypeEnum.p]: (
        node: RTEDefaultNode,
        embeds: EmbedByUid,
        next: RTENext,
        fullRenderOptions: RenderOptions
      ) => (
        <Body color={"burgundy"} textTransform={"bold"}>
          {next(node.children, embeds, fullRenderOptions)}
        </Body>
      ),
    }
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Table.THead key={node.uid} {...props}>
        {next(node.children, embeds, theadChildPRenderOptions)}
      </Table.THead>
    )
  },

  [RTETypeEnum.tbody]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Table.TBody key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </Table.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 (
      <Table.TR key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </Table.TR>
    )
  },

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

  [RTETypeEnum.td]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const props = extractPossibleAttributes(node.attrs)
    return (
      <Table.TD key={node.uid} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </Table.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} type="one">
          {children}
        </Subtitle>
      )
    }
    if (className === AvailableParagraphFormatEnum["subtitle-2"]) {
      return (
        <Subtitle key={id} {...props} type="two">
          {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)
  },
}
