import { cx } from "class-variance-authority"

import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"

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 Table from "../TempDesignSystem/Table"
import {
  hasAvailableParagraphFormat,
  hasAvailableULFormat,
  makeCssModuleCompatibleClassName,
} from "./utils"

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

import { ContentEnum } from "@/types/enums/content"
import type { EmbedByUid } from "@/types/transitionTypes/jsontohtml"
import type {
  Attributes,
  RTEImageVaultAttrs,
} from "@/types/transitionTypes/rte/attrs"
import {
  AvailableParagraphFormatEnum,
  RTEItemTypeEnum,
  RTETypeEnum,
} from "@/types/transitionTypes/rte/enums"
import {
  type RTEDefaultNode,
  type RTEImageNode,
  RTEMarkType,
  type RTENext,
  type RTENode,
  type RTERegularNode,
  type RTETextNode,
} from "@/types/transitionTypes/rte/node"
import type { RenderOptions } from "@/types/transitionTypes/rte/option"

function noNestedLinksOrReferences(node: RTENode) {
  if ("type" in node) {
    if (node.type === RTETypeEnum.reference) {
      return node.children
    } else if (node.type === RTETypeEnum.a) {
      return node.children
    }
  }
  return node
}

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
  }

  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 { className, ...props } = extractPossibleAttributes(node.attrs)
      return (
        <Link
          key={node.uid}
          className={cx(styles.link, className)}
          {...props}
          href={node.attrs.url}
          target={node.attrs.target ?? "_blank"}
          textDecoration="underline"
        >
          {next(
            // Sometimes editors happen to nest a reference inside a link and vice versa.
            // In that case use the outermost link, i.e. ignore nested links.
            node.children.flatMap(noNestedLinksOrReferences),
            embeds,
            fullRenderOptions
          )}
        </Link>
      )
    }
    return null
  },

  [RTETypeEnum.blockquote]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...props } = extractPossibleAttributes(node.attrs)
    return (
      <Typography key={node.uid} variant="Title/Decorative/lg">
        <blockquote className={cx(styles.blockquote, className)} {...props}>
          {next(node.children, embeds, fullRenderOptions)}
        </blockquote>
      </Typography>
    )
  },

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

  [RTETypeEnum.embed]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...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}
        className={cx(styles.iframe, className)}
        {...props}
      >
        {next(node.children, embeds, fullRenderOptions)}
      </iframe>
    )
  },

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

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

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

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

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

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

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

  [RTETypeEnum.ol]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...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 (
      <Typography key={node.uid} variant="Body/Paragraph/mdRegular">
        <ol
          className={cx(styles.ol, className)}
          {...props}
          style={
            numberOfRows
              ? { gridTemplateRows: `repeat(${numberOfRows}, auto)` }
              : {}
          }
        >
          {next(node.children, embeds, fullRenderOptions)}
        </ol>
      </Typography>
    )
  },

  [RTETypeEnum.p]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...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 (
      <Typography key={node.uid} variant="Body/Paragraph/mdRegular">
        <p className={cx(styles.p, className)} {...props}>
          {next(node.children, embeds, fullRenderOptions)}
        </p>
      </Typography>
    )
  },

  [RTETypeEnum.span]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...props } = extractPossibleAttributes(node.attrs)
    let propsClassName = className

    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
        propsClassName = styles[className]
      }
    }

    return (
      <span className={cx(styles.span, propsClassName)} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </span>
    )
  },
  [RTETypeEnum.div]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...props } = extractPossibleAttributes(node.attrs)
    let propsClassName = props.className

    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
        propsClassName = styles[className]
      }
    }

    return (
      <div className={cx(styles.div, propsClassName)} {...props}>
        {next(node.children, embeds, fullRenderOptions)}
      </div>
    )
  },

  [RTETypeEnum.reference]: (
    node: RTENode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    if ("attrs" in node) {
      const type = node.attrs.type
      if (type === RTEItemTypeEnum.asset) {
        const imageTypeRegex = /^image\//
        const isImage = imageTypeRegex.test(node.attrs["asset-type"])
        if (isImage) {
          const image = embeds?.[node?.attrs?.["asset-uid"]]
          if (image?.node.__typename === ContentEnum.blocks.SysAsset) {
            if (image.node.url) {
              const alt = image?.node?.title ?? node.attrs.alt
              const { className, ...props } = extractPossibleAttributes(
                node.attrs
              )
              return (
                <div key={node.uid} className={styles.imageWrapper}>
                  <Image
                    className={cx(styles.image, className)}
                    src={image.node.url}
                    alt={alt}
                    fill
                    sizes="(min-width: 1367px) 800px, (max-width: 1366px) and (min-width: 1200px) 1200px, 100vw"
                    {...props}
                  />
                </div>
              )
            }
          }
        } else if (node.attrs["display-type"] === "link" && node.attrs.href) {
          const { className, ...props } = extractPossibleAttributes(node.attrs)
          return (
            <Link
              key={node.uid}
              className={cx(styles.link, className)}
              href={node.attrs.href}
              textDecoration="underline"
              target="_blank"
              {...props}
            >
              {next(
                // Sometimes editors happen to nest a reference inside a link and vice versa.
                // In that case use the outermost link, i.e. ignore nested links.
                node.children.flatMap(noNestedLinksOrReferences),
                embeds,
                fullRenderOptions
              )}
              <MaterialIcon icon="open_in_new" size={20} color="CurrentColor" />
            </Link>
          )
        }
      } else if (type === RTEItemTypeEnum.entry) {
        const entry = embeds?.[node?.attrs?.["entry-uid"]]

        if (entry?.node.__typename === ContentEnum.blocks.ImageContainer) {
          if (entry.node.image_left && entry.node.image_right) {
            return (
              <ImageContainer
                key={node.uid}
                leftImage={entry.node.image_left}
                rightImage={entry.node.image_right}
              />
            )
          }
          return null
        } else if (
          entry?.node.__typename === ContentEnum.blocks.AccountPage ||
          entry?.node.__typename === ContentEnum.blocks.CollectionPage ||
          entry?.node.__typename === ContentEnum.blocks.ContentPage ||
          entry?.node.__typename === ContentEnum.blocks.DestinationCityPage ||
          entry?.node.__typename ===
            ContentEnum.blocks.DestinationCountryPage ||
          entry?.node.__typename ===
            ContentEnum.blocks.DestinationOverviewPage ||
          entry?.node.__typename === ContentEnum.blocks.HotelPage ||
          entry?.node.__typename === ContentEnum.blocks.LoyaltyPage ||
          entry?.node.__typename === ContentEnum.blocks.StartPage
        ) {
          // If entry is not an ImageContainer, it is a page and we return it as a link
          const { className, ...props } = extractPossibleAttributes(node.attrs)

          return (
            <Link
              key={node.uid}
              className={cx(styles.link, className)}
              {...props}
              href={entry.node.url}
              textDecoration="underline"
            >
              {next(
                // Sometimes editors happen to nest a reference inside a link and vice versa.
                // In that case use the outermost link, i.e. ignore nested links.
                node.children.flatMap(noNestedLinksOrReferences),
                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 caption = image.meta.caption
        const { className, ...props } = extractPossibleAttributes(attrs)
        return (
          <div key={node.uid} className={styles.imageWithCaption}>
            <div className={styles.imageWrapper}>
              <Image
                alt={alt}
                className={cx(styles.image, className)}
                src={image.url}
                fill
                sizes="(min-width: 1367px) 800px, (max-width: 1366px) and (min-width: 1200px) 1200px, 100vw"
                focalPoint={image.focalPoint}
                {...props}
              />
            </div>
            {caption ? (
              <Typography variant="Body/Supporting text (caption)/smRegular">
                <p>{image.meta.caption}</p>
              </Typography>
            ) : null}
          </div>
        )
      }
    }

    return null
  },

  [RTETypeEnum.table]: (
    node: RTEDefaultNode,
    embeds: EmbedByUid,
    next: RTENext,
    fullRenderOptions: RenderOptions
  ) => {
    const { className, ...props } = extractPossibleAttributes(node.attrs)
    return (
      <div key={node.uid} className={styles.tableContainer}>
        <Table className={cx(styles.table, className)} {...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
      ) => (
        <Typography variant="Body/Paragraph/mdBold">
          <span className={styles.theadContent}>
            {next(node.children, embeds, fullRenderOptions)}
          </span>
        </Typography>
      ),
    }
    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 { className, ...props } = extractPossibleAttributes(node.attrs)
    const compatibleClassName = makeCssModuleCompatibleClassName(
      className,
      "ul"
    )

    // 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 (
      <Typography key={node.uid} variant="Body/Paragraph/mdRegular">
        <ul
          className={cx(styles.ul, compatibleClassName)}
          {...props}
          style={
            numberOfRows
              ? {
                  gridTemplateRows: `repeat(${numberOfRows}, auto)`,
                }
              : {}
          }
        >
          {next(node.children, embeds, fullRenderOptions)}
        </ul>
      </Typography>
    )
  },

  /** TextNode wrappers */
  [RTEMarkType.bold]: (children: React.ReactNode) => {
    return (
      <Typography variant="Body/Paragraph/mdBold">
        <strong>{children}</strong>
      </Typography>
    )
  },

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

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

  [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
  ) => {
    const props = { id }
    let propsClassName = 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
        propsClassName = styles[className]
      }
    }

    if (className === AvailableParagraphFormatEnum.footnote) {
      return (
        <Typography key={id} variant="Label/xsRegular">
          <p className={cx(styles.p, propsClassName)} {...props}>
            {children}
          </p>
        </Typography>
      )
    }

    if (className === AvailableParagraphFormatEnum.caption) {
      return (
        <Typography key={id} variant="Body/Supporting text (caption)/smRegular">
          <p className={cx(styles.p, propsClassName)} {...props}>
            {children}
          </p>
        </Typography>
      )
    }

    if (className === AvailableParagraphFormatEnum["script-1"]) {
      return (
        <Typography key={id} variant="Title/Decorative/lg">
          <p className={cx(styles.p, propsClassName)} {...props}>
            {children}
          </p>
        </Typography>
      )
    }

    if (className === AvailableParagraphFormatEnum["script-2"]) {
      return (
        <Typography key={id} variant="Title/Decorative/md">
          <p className={cx(styles.p, propsClassName)} {...props}>
            {children}
          </p>
        </Typography>
      )
    }

    if (className === AvailableParagraphFormatEnum["subtitle-1"]) {
      return (
        <Typography key={id} variant="Title/Subtitle/lg">
          <p className={cx(styles.p, propsClassName)} {...props}>
            {children}
          </p>
        </Typography>
      )
    }
    if (className === AvailableParagraphFormatEnum["subtitle-2"]) {
      return (
        <Typography key={id} variant="Title/Subtitle/md">
          <p className={cx(styles.p, propsClassName)} {...props}>
            {children}
          </p>
        </Typography>
      )
    }
    return (
      <Typography key={id} variant="Body/Paragraph/mdRegular">
        <span className={cx(styles.span, propsClassName)} {...props}>
          {children}
        </span>
      </Typography>
    )
  },

  /**
   * 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)
  },
}
