import clsx from "clsx";
import type { ReactNode } from "react";
import React, { useCallback, useState } from "react";
import { FiClipboard } from "react-icons/fi";
import ReactMarkdown from "react-markdown";
import rehypeHighlight from "rehype-highlight";
import remarkGfm from "remark-gfm";
import "highlight.js/styles/default.css";

interface MarkdownRendererProps {
  children: string;
  className?: string;
}

const MarkdownRenderer = ({ children, className }: MarkdownRendererProps) => {
  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
      rehypePlugins={[() => rehypeHighlight({ ignoreMissing: true })]}
      components={{
        pre: CustomPre,
        code: CustomCodeBlock,
        h1: (props) => <h1 className="text-md mb-2 font-black sm:text-xl">{props.children}</h1>,
        h2: (props) => <h1 className="sm:text-md mb-2 text-sm font-bold">{props.children}</h1>,
        a: (props) => CustomLink({ children: props.children, href: props.href }),
        p: (props) => <p className="mb-4">{props.children}</p>,
        ul: (props) => (
          <ul className={clsx("mb-4 list-disc marker:text-neutral-400", className)}>
            {props.children}
          </ul>
        ),
        ol: (props) => (
          <ol className="mb-4 ml-8 list-decimal marker:text-neutral-400">{props.children}</ol>
        ),
        li: (props) => <li className="mb-1 ml-8">{props.children}</li>,
      }}
    >
      {children}
    </ReactMarkdown>
  );
};

const CustomPre = ({ children }: { children: ReactNode }) => {
  const [isCopied, setIsCopied] = useState(false);

  const code = React.Children.toArray(children).find(isValidCustomCodeBlock);

  const language: string =
    code && code.props.className
      ? extractLanguageName(code.props.className.replace("hljs ", ""))
      : "";

  const handleCopyClick = useCallback(() => {
    if (code && React.isValidElement(code)) {
      const codeString = extractTextFromNode(code.props.children);
      void navigator.clipboard.writeText(codeString);
      setIsCopied(true);
      setTimeout(() => {
        setIsCopied(false);
      }, 2000);
    }
  }, [code]);

  return (
    <div className="mb-4 flex flex-col ">
      <div className="flex w-full items-center justify-between rounded-t-lg bg-slate-10 p-1 px-4 text-white">
        <div>{language.charAt(0).toUpperCase() + language.slice(1)}</div>
        <button
          onClick={handleCopyClick}
          className="flex items-center gap-2 rounded px-2 py-1 hover:bg-slate-9 focus:outline-none"
        >
          <FiClipboard />
          {isCopied ? "Copied!" : "Copy Code"}
        </button>
      </div>
      <pre className="rounded-t-[0]">{children}</pre>
    </div>
  );
};

interface CustomCodeBlockProps {
  inline?: boolean;
  className?: string;
  children: ReactNode;
}

const CustomCodeBlock = ({ inline, className, children }: CustomCodeBlockProps) => {
  // Inline code blocks will be placed directly within a paragraph
  if (inline) {
    return <code className="rounded bg-slate-2 px-1 py-[1px] text-black">{children}</code>;
  }

  const language = className ? className.replace("language-", "") : "plaintext";

  return <code className={`hljs ${language}`}>{children}</code>;
};

const CustomLink = ({ children, href }) => {
  return (
    <a
      className={clsx(
        "mx-0.5 rounded-full bg-sky-600 px-1.5 py-0.5 align-top text-[0.6rem] text-white",
        "transition-colors duration-300 hover:bg-sky-500 hover:text-white"
      )}
      href={href as string}
      target="_blank"
      rel="noopener noreferrer"
    >
      {children}
    </a>
  );
};

const isValidCustomCodeBlock = (
  element: ReactNode
): element is React.ReactElement<CustomCodeBlockProps> =>
  React.isValidElement(element) && element.type === CustomCodeBlock;

const extractLanguageName = (languageString: string): string => {
  // The provided language will be "language-{PROGRAMMING_LANGUAGE}"
  const parts = languageString.split("-");
  if (parts.length > 1) {
    return parts[1] || "";
  }
  return "";
};

const extractTextFromNode = (node: React.ReactNode): string => {
  if (typeof node === "string") {
    return node;
  }

  if (Array.isArray(node)) {
    return node.map(extractTextFromNode).join("");
  }

  if (React.isValidElement(node)) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
    return extractTextFromNode(node.props.children);
  }

  return "";
};

export default MarkdownRenderer;
