import type { Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import isEqual from "lodash/isEqual";
import { type FC, memo } from "react";
import ReactMarkdown, { type Options } from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism";
import gfm from "remark-gfm";
import colors from "theme/tailwindColors";

interface MarkdownProps {
  /**
   * The Markdown text to parse and render
   */
  children: string;

  className?: string;

  /**
   * Can override the behavior of the generated elements
   */
  components?: Options["components"];
}

export const Markdown: FC<MarkdownProps> = (props) => {
  const { children, className, components = {} } = props;

  return (
    <ReactMarkdown
      css={markdownStyles}
      className={className}
      remarkPlugins={[gfm]}
      components={{
        a: ({ href, target, children }) => (
          <Link href={href} target={target}>
            {children}
          </Link>
        ),

        pre: ({ node, children }) => {
          if (!node || !node.children) {
            return <pre>{children}</pre>;
          }
          const firstChild = node.children[0];
          // When pre is wrapping a code, the SyntaxHighlighter is already going
          // to wrap it with a pre so we don't need it
          if (firstChild.type === "element" && firstChild.tagName === "code") {
            return <>{children}</>;
          }
          return <pre>{children}</pre>;
        },

        code: ({ node, className, children, style, ref, ...restProps }) => {
          const match = /language-(\w+)/.exec(className || "");

          return match ? (
            <SyntaxHighlighter
              style={dracula}
              language={match[1].toLowerCase() ?? "language-shell"}
              useInlineStyles={false}
              codeTagProps={{ style: {} }}
              {...restProps} // Exclude 'ref' from being passed here
            >
              {String(children)}
            </SyntaxHighlighter>
          ) : (
            <code
              css={(theme) => ({
                padding: "1px 4px",
                background: theme.palette.divider,
                borderRadius: 4,
                color: theme.palette.text.primary,
                fontSize: 14,
              })}
              {...props}
            >
              {children}
            </code>
          );
        },

        table: ({ children }) => {
          return (
            <TableContainer>
              <Table>{children}</Table>
            </TableContainer>
          );
        },

        tr: ({ children }) => {
          return <TableRow>{children}</TableRow>;
        },

        thead: ({ children }) => {
          return <TableHead>{children}</TableHead>;
        },

        tbody: ({ children }) => {
          return <TableBody>{children}</TableBody>;
        },

        td: ({ children }) => {
          return <TableCell>{children}</TableCell>;
        },

        th: ({ children }) => {
          return <TableCell>{children}</TableCell>;
        },

        ...components,
      }}
    >
      {children}
    </ReactMarkdown>
  );
};

interface InlineMarkdownProps {
  /**
   * The Markdown text to parse and render
   */
  children: string;

  /**
   * Additional element types to allow.
   * Allows italic, bold, links, and inline code snippets by default.
   * eg. `["ol", "ul", "li"]` to support lists.
   */
  allowedElements?: readonly string[];

  className?: string;

  /**
   * Can override the behavior of the generated elements
   */
  components?: Options["components"];
}

/**
 * Supports a strict subset of Markdown that behaves well as inline/confined content.
 */
export const InlineMarkdown: FC<InlineMarkdownProps> = (props) => {
  const { children, allowedElements = [], className, components = {} } = props;

  return (
    <ReactMarkdown
      className={className}
      allowedElements={[
        "p",
        "em",
        "strong",
        "a",
        "pre",
        "code",
        ...allowedElements,
      ]}
      unwrapDisallowed
      components={{
        p: ({ children }) => <>{children}</>,

        a: ({ href, target, children }) => (
          <Link href={href} target={target}>
            {children}
          </Link>
        ),

        code: ({ node, className, children, style, ...props }) => (
          <code
            css={(theme) => ({
              padding: "1px 4px",
              background: theme.palette.divider,
              borderRadius: 4,
              color: theme.palette.text.primary,
              fontSize: 14,
            })}
            {...props}
          >
            {children}
          </code>
        ),

        ...components,
      }}
    >
      {children}
    </ReactMarkdown>
  );
};

export const MemoizedMarkdown = memo(Markdown, isEqual);
export const MemoizedInlineMarkdown = memo(InlineMarkdown, isEqual);

const markdownStyles: Interpolation<Theme> = (theme: Theme) => ({
  fontSize: 16,
  lineHeight: "24px",

  "& h1, & h2, & h3, & h4, & h5, & h6": {
    marginTop: 32,
    marginBottom: 16,
    lineHeight: "1.25",
  },

  "& p": {
    marginTop: 0,
    marginBottom: 16,
  },

  "& p:only-child": {
    marginTop: 0,
    marginBottom: 0,
  },

  "& ul, & ol": {
    display: "flex",
    flexDirection: "column",
    gap: 8,
    marginBottom: 16,
  },

  "& li > ul, & li > ol": {
    marginTop: 16,
  },

  "& li > p": {
    marginBottom: 0,
  },

  "& .prismjs": {
    background: theme.palette.background.paper,
    borderRadius: 8,
    padding: "16px 24px",
    overflowX: "auto",

    "& code": {
      color: theme.palette.text.secondary,
    },

    "& .key, & .property, & .inserted, .keyword": {
      color: colors.teal[300],
    },

    "& .deleted": {
      color: theme.palette.error.light,
    },
  },
});
