"use client"

import React, { useCallback } from "react"
import ReactMarkdown, { Components } from "react-markdown"
import remarkGfm from "remark-gfm"
import katex from "katex"
import "katex/dist/katex.min.css"
import rehypeRaw from "rehype-raw"
import { TableRowCard } from "../cards/table-row-card"
import { Accordion } from "./accordion"
import Prism from "prismjs"
import "prismjs/themes/prism-tomorrow.css"
import "prismjs/components/prism-javascript"
import "prismjs/components/prism-typescript"
import "prismjs/components/prism-jsx"
import "prismjs/components/prism-tsx"
import "prismjs/components/prism-json"
import "prismjs/components/prism-bash"
import "prismjs/components/prism-css"
import "prismjs/components/prism-markdown"
import "prismjs/components/prism-yaml"
import "prismjs/components/prism-python"
import "prismjs/components/prism-rust"
import "prismjs/components/prism-solidity"

const SCROLL_OFFSET = 150

const scrollToElementWithOffset = (target: HTMLElement) => {
  const rect = target.getBoundingClientRect()
  const targetPosition = rect.top + window.pageYOffset - SCROLL_OFFSET

  // Set margin before scrolling
  target.style.scrollMarginTop = `${SCROLL_OFFSET}px`

  requestAnimationFrame(() => {
    window.scrollTo({
      top: targetPosition,
      behavior: "smooth",
    })
  })
}

interface CustomComponents extends Components {
  "table-row-card": React.ComponentType<{
    node: any
    children: string
  }>
  accordion: React.ComponentType<{
    node: any
    children: string
  }>
  "footnote-ref": React.ComponentType<{
    identifier: string
    label: string
  }>
  "footnote-definition": React.ComponentType<{
    identifier: string
    label: string
    children: React.ReactNode
  }>
  footnotes: React.ComponentType<{
    children: React.ReactNode
  }>
}

const generateSectionId = (text: string) => {
  return text
    .toLowerCase()
    .replace(/[']/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
}

export const createMarkdownElement = (
  tag: keyof JSX.IntrinsicElements,
  props: any
) =>
  React.createElement(tag, {
    ...props,
    ref: (node: HTMLElement | null) => {
      if (node && node.textContent) {
        node.setAttribute(
          "data-section-id",
          generateSectionId(node.textContent)
        )
      }
    },
  })

const Table = (props: any) => {
  return (
    <div className="w-full overflow-x-auto border rounded-lg border-tuatara-300">
      <table className="min-w-full !bg-background" data-component="table">
        {props.children}
      </table>
    </div>
  )
}

const TableRow = (props: any) => {
  return <tr data-component="table-row">{props.children}</tr>
}

const TableHead = (props: any) => {
  if (!props.children || props.children.length === 0) {
    return null
  }

  const isEmpty = React.Children.toArray(props.children).every((child: any) => {
    if (!child.props || !child.props.children) return true

    if (child.props.children) {
      const thChildren = React.Children.toArray(child.props.children)
      if (thChildren.length === 0) return true

      return thChildren.every((thChild: any) => {
        if (!thChild) return true
        if (typeof thChild === "string") return thChild.trim() === ""
        if (!thChild.props || !thChild.props.children) return true
        if (typeof thChild.props.children === "string")
          return thChild.props.children.trim() === ""
        return false
      })
    }
    return true
  })

  if (isEmpty) {
    return null
  }

  return <thead>{props.children}</thead>
}

const TableBody = (props: any) => {
  if (!props.children || props.children.length === 0) {
    return null
  }

  return <tbody>{props.children}</tbody>
}

const remarkCustomNewlines = () => {
  return (tree: any) => {
    const visit = (node: any) => {
      if (node.type === "text" && typeof node.value === "string") {
        if (node.value.includes("/n")) {
          const parts = node.value.split("/n")
          const newChildren: any[] = []

          parts.forEach((part: string, index: number) => {
            newChildren.push({ type: "text", value: part })

            if (index < parts.length - 1) {
              newChildren.push({ type: "break" })
            }
          })

          return newChildren
        }
      }

      if (node.children) {
        const newChildren: any[] = []
        for (const child of node.children) {
          const result = visit(child)
          if (Array.isArray(result)) {
            newChildren.push(...result)
          } else if (result) {
            newChildren.push(result)
          } else {
            newChildren.push(child)
          }
        }
        node.children = newChildren
      }

      return node
    }

    return visit(tree)
  }
}

// Custom math components
const KaTeXBlock = ({ math }: { math: string }) => {
  const mathRef = React.useRef<HTMLDivElement>(null)

  React.useEffect(() => {
    if (mathRef.current) {
      try {
        const processedMath = preprocessMathContent(math.trim())
        katex.render(processedMath, mathRef.current, {
          displayMode: true,
          throwOnError: false,
        })
      } catch (error) {
        console.error("KaTeX block render error:", error)
        if (mathRef.current) {
          mathRef.current.textContent = math
        }
      }
    }
  }, [math])

  return <div ref={mathRef} className="my-4 flex justify-center" />
}

const KaTeXInline = ({ math }: { math: string }) => {
  const mathRef = React.useRef<HTMLSpanElement>(null)

  React.useEffect(() => {
    if (mathRef.current) {
      try {
        // Pre-process the math content to replace "mod" with "\pmod"
        const processedMath = preprocessMathContent(math.trim())
        katex.render(processedMath, mathRef.current, {
          displayMode: false,
          throwOnError: false,
        })
      } catch (error) {
        console.error("KaTeX inline render error:", error)
        if (mathRef.current) {
          mathRef.current.textContent = math
        }
      }
    }
  }, [math])

  return <span ref={mathRef} className="inline-block align-middle katex-math" />
}

// Preprocess math content to improve rendering
const preprocessMathContent = (mathContent: string): string => {
  // Replace pattern "x ; mod ; q" or "x \mod q" with "\pmod{q}"
  const modRegex = /([^\\])(;?\s*mod\s*;?\s*)([^{])/g
  const processedMath = mathContent.replace(
    modRegex,
    (match, before, mod, after) => {
      return `${before}\\pmod{${after}}`
    }
  )

  // Also handle cases like "\mod q" or "\mod{q}"
  const modCommandRegex = /\\mod\s+([^{])/g
  return processedMath.replace(modCommandRegex, (match, after) => {
    return `\\pmod{${after}}`
  })
}

// Define a simple regex to extract math content from our markdown
const extractMathBlocks = (content: string) => {
  const mathBlocks: {
    start: number
    end: number
    content: string
    isBlock: boolean
  }[] = []

  const blockMathRegex = /\$\$([\s\S]*?)\$\$/g
  let match: RegExpExecArray | null

  while ((match = blockMathRegex.exec(content)) !== null) {
    mathBlocks.push({
      start: match.index,
      end: match.index + match[0].length,
      content: match[1],
      isBlock: true,
    })
  }

  const inlineMathRegex = /\$(.*?)\$/g
  let inlineMatch: RegExpExecArray | null
  while ((inlineMatch = inlineMathRegex.exec(content)) !== null) {
    // Make sure this isn't already part of a block math section
    const isInsideBlockMath = mathBlocks.some(
      (block) =>
        inlineMatch!.index > block.start && inlineMatch!.index < block.end
    )

    if (!isInsideBlockMath) {
      mathBlocks.push({
        start: inlineMatch.index,
        end: inlineMatch.index + inlineMatch[0].length,
        content: inlineMatch[1],
        isBlock: false,
      })
    }
  }

  mathBlocks.sort((a, b) => a.start - b.start)

  return mathBlocks
}

// Helper to check if text contains unescaped math delimiters
const containsMath = (text: string): boolean => {
  // Skip markdown links
  if (text.match(/\[.*?\]\(.*?\)/)) {
    return false
  }

  if (!text.includes("$")) return false

  // Check for currency pattern first
  const currencyPattern = /\$\s*\d+(?:,\d{3})*(?:\.\d{2})?(?!\^|\{|\}|\d)/g
  if (text.match(currencyPattern) && !text.match(/\$.*[\^_{}].*\$/)) {
    return false
  }

  const blockMathRegex = /\$\$([\s\S]*?)\$\$/g
  const inlineMathRegex =
    /(?<![\\$])\$(?![\s\d,]*\d(?:\.\d{2})?(?!\^|\{|\}|\d))((?:[^$\\]|\\$|\\[^$])+?)\$/g

  return blockMathRegex.test(text) || inlineMathRegex.test(text)
}

const MathText = ({ text }: { text: string }) => {
  const parts: React.ReactNode[] = []
  let lastIndex = 0

  try {
    // First handle block math ($$...$$)
    const blockMathRegex = /\$\$([\s\S]*?)\$\$/g
    let match: RegExpExecArray | null

    while ((match = blockMathRegex.exec(text))) {
      if (match.index > lastIndex) {
        parts.push(text.slice(lastIndex, match.index))
      }

      const mathContent = match[1].trim()
      parts.push(
        <KaTeXBlock key={`block-math-${match.index}`} math={mathContent} />
      )

      lastIndex = match.index + match[0].length
    }

    // Then handle remaining text for inline math ($...$)
    const remainingText = text.slice(lastIndex)
    if (remainingText) {
      const inlineParts: React.ReactNode[] = []
      let inlineLastIndex = 0
      const inlineMathRegex = /(?<![\\$])\$(?![$])([^$]+)\$/g
      let inlineMatch: RegExpExecArray | null

      while ((inlineMatch = inlineMathRegex.exec(remainingText))) {
        if (inlineMatch.index > inlineLastIndex) {
          inlineParts.push(
            remainingText.slice(inlineLastIndex, inlineMatch.index)
          )
        }

        const mathContent = inlineMatch[1].trim()
        inlineParts.push(
          <KaTeXInline
            key={`inline-math-${inlineMatch.index}`}
            math={mathContent}
          />
        )

        inlineLastIndex = inlineMatch.index + inlineMatch[0].length
      }

      if (inlineLastIndex < remainingText.length) {
        inlineParts.push(remainingText.slice(inlineLastIndex))
      }

      parts.push(...inlineParts)
    }

    return <>{parts}</>
  } catch (error) {
    console.error("Error processing text with math:", error)
    return <>{text}</>
  }
}

const rehypeProcessBrTags = () => {
  return (tree: any) => {
    const visit = (node: any) => {
      if (
        node.type === "element" &&
        (node.tagName === "td" || node.tagName === "th")
      ) {
        // Look for text nodes that contain \n or <br> and convert them
        if (node.children) {
          for (let i = 0; i < node.children.length; i++) {
            const child = node.children[i]
            if (child.type === "text" && child.value) {
              if (child.value.includes("\n")) {
                const parts = child.value.split("\n")
                const newChildren: any[] = []

                parts.forEach((part: string, index: number) => {
                  newChildren.push({ type: "text", value: part })

                  // Add <br> element except after the last part
                  if (index < parts.length - 1) {
                    newChildren.push({
                      type: "element",
                      tagName: "br",
                      properties: {},
                      children: [],
                    })
                  }
                })

                node.children.splice(i, 1, ...newChildren)
                // Adjust i based on the new length of children we've inserted
                i += newChildren.length - 1
              }
            }
          }
        }
      }

      if (node.children) {
        node.children.forEach(visit)
      }

      return node
    }

    return visit(tree)
  }
}

const CodeBlock = ({
  className,
  children,
}: {
  className?: string
  children: string
}) => {
  const language = className ? className.replace(/language-/, "") : "text"
  const codeRef = React.useRef<HTMLElement>(null)

  React.useEffect(() => {
    if (codeRef.current) {
      Prism.highlightElement(codeRef.current)
    }
  }, [children])

  return (
    <pre className="relative rounded-lg bg-tuatara-950 overflow-hidden">
      <code ref={codeRef} className={`bg-tuatara-950 language-${language}`}>
        {children}
      </code>
    </pre>
  )
}

const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({
  a: ({ href, children }) => {
    if (href?.startsWith("#")) {
      return (
        <a
          href={href}
          data-anchor="with-scroll-margin"
          onClick={(e) => {
            e.preventDefault()
            const targetId = href.slice(1)
            const target = document.getElementById(targetId)
            if (target) {
              scrollToElementWithOffset(target)
            }
          }}
          className="text-anakiwa-500 hover:text-orange duration-200"
        >
          {children}
        </a>
      )
    }

    return (
      <a
        href={href}
        target="_blank"
        rel="noopener noreferrer"
        className="text-anakiwa-500 hover:text-orange duration-200"
      >
        {children}
      </a>
    )
  },
  h1: ({ ...props }) =>
    createMarkdownElement("h1", {
      className: "text-primary text-4xl md:text-5xl font-bold",
      ...props,
    }),
  h2: ({ ...props }) =>
    createMarkdownElement("h2", {
      className: "text-primary text-4xl",
      ...props,
    }),
  h3: ({ ...props }) =>
    createMarkdownElement("h3", {
      className: "text-primary text-3xl",
      ...props,
    }),
  h4: ({ ...props }) =>
    createMarkdownElement("h4", {
      className: "text-primary text-xl",
      ...props,
    }),
  h5: ({ ...props }) =>
    createMarkdownElement("h5", {
      className: "text-primary text-lg font-bold",
      ...props,
    }),
  h6: ({ ...props }) =>
    createMarkdownElement("h6", {
      className: "text-primary text-md font-bold",
      ...props,
    }),
  code: ({ node, inline, className, children, ...props }) => {
    const match = /language-(\w+)/.exec(className || "")
    return !inline ? (
      <CodeBlock className={className} {...props}>
        {String(children).replace(/\n$/, "")}
      </CodeBlock>
    ) : (
      <code
        className="bg-tuatara-950 px-1.5 py-0.5 rounded-md text-white"
        {...props}
      >
        {children}
      </code>
    )
  },
  p: ({ node, children }: { node: any; children: React.ReactNode }) => {
    const childArray = React.Children.toArray(children)
    const isOnlyLink =
      childArray.length === 1 &&
      React.isValidElement(childArray[0]) &&
      childArray[0].type === "a"

    if (isOnlyLink) {
      return <>{children}</>
    }

    // For other paragraphs, continue with normal processing
    const text = childArray
      .map((child) => {
        if (typeof child === "string") return child
        if (React.isValidElement(child) && child.props?.children) {
          return child.props.children
        }
        return ""
      })
      .join("")

    let isMathOnly = false
    if (text.trim().startsWith("$$") && text.trim().endsWith("$$")) {
      const innerContent = text.trim().slice(2, -2)
      if (!innerContent.includes("$$")) {
        isMathOnly = true
      }
    }

    if (containsMath(text)) {
      return (
        <p
          className={`${darkMode ? "text-white" : "text-tuatara-600"} font-sans text-lg font-normal ${isMathOnly ? "math-only" : ""}`}
        >
          <MathText text={text} />
        </p>
      )
    }

    return (
      <p
        className={`${darkMode ? "text-white" : "text-tuatara-600"} font-sans text-lg font-normal`}
      >
        {children}
      </p>
    )
  },
  // Handle math in list items
  li: ({ node, children, ...props }) => {
    const text = React.Children.toArray(children)
      .map((child) => {
        if (typeof child === "string") return child
        // @ts-expect-error - children props vary
        if (child?.props?.children) return child.props.children
        return ""
      })
      .join("")

    if (containsMath(text)) {
      return (
        <li
          className="text-tuatara-600 font-sans text-lg font-normal"
          {...props}
        >
          <MathText text={text} />
        </li>
      )
    }

    return (
      <li
        className="text-tuatara-600  font-sans text-lg font-normal"
        {...props}
      >
        {children}
      </li>
    )
  },
  ul: ({ ordered, ...props }) =>
    createMarkdownElement(ordered ? "ol" : "ul", {
      className:
        "ml-6 list-disc text-tuatara-600  font-sans text-lg font-normal",
      ...props,
    }),
  ol: ({ ordered, ...props }) =>
    createMarkdownElement(ordered ? "ol" : "ul", {
      className:
        "list-decimal text-tuatara-600 font-sans text-lg font-normal mt-3",
      ...props,
    }),
  table: Table,
  tr: TableRow,
  thead: TableHead,
  tbody: TableBody,
  "table-row-card": ({ node, children }: { node: any; children: string }) => {
    try {
      const content = JSON.parse(children)
      return <TableRowCard items={content} />
    } catch (error) {
      console.error("Error parsing table-row-card content:", error)
      return <div>Error rendering table row card</div>
    }
  },
  accordion: ({ node, children }: { node: any; children: string }) => {
    try {
      const content = JSON.parse(children)
      return (
        <Accordion
          type={content.type || "multiple"}
          size={content.size || "xs"}
          defaultValue={content.defaultValue}
          items={content.items}
          iconOnHover={content.iconOnHover}
          id={content.id}
        />
      )
    } catch (error) {
      console.error("Error parsing accordion content:", error)
      return <div>Error rendering accordion</div>
    }
  },
  td: (props) => {
    const { node, children, ...rest } = props

    // Convert children to text to check for math
    const text = React.Children.toArray(children)
      .map((child) => {
        if (typeof child === "string") return child
        // @ts-expect-error - children props vary
        if (child?.props?.children) return child.props.children
        return ""
      })
      .join("")

    // Handle line breaks in table cells by replacing <br> with actual line breaks
    const hasBrTags = typeof text === "string" && text.includes("<br>")

    // Check if there's HTML content with style attribute in the cell
    const hasHtmlContent =
      typeof text === "string" && (text.includes("<div style=") || hasBrTags)

    // Check if there's math content
    if (containsMath(text)) {
      return (
        <td className="p-4 text-left" {...rest}>
          <MathText text={text} />
        </td>
      )
    }

    if (hasHtmlContent) {
      const processedText = hasBrTags ? text.replace(/<br>/g, "<br/>") : text
      return (
        <td
          className="p-4 text-left"
          {...rest}
          dangerouslySetInnerHTML={{ __html: processedText }}
        />
      )
    }

    return (
      <td className="p-4 text-left" {...rest}>
        {children}
      </td>
    )
  },
  th: (props) => {
    const { node, children, ...rest } = props
    if (!children || (Array.isArray(children) && children.length === 0)) {
      return null
    }

    // Convert children to text to check for math
    const text = React.Children.toArray(children)
      .map((child) => {
        if (typeof child === "string") return child
        // @ts-expect-error - children props vary
        if (child?.props?.children) return child.props.children
        return ""
      })
      .join("")

    // Handle line breaks in table headers by replacing <br> with actual line breaks
    const hasBrTags = typeof text === "string" && text.includes("<br>")

    // Check if there's HTML content with style attribute in the cell
    const hasHtmlContent =
      typeof text === "string" && (text.includes("<div style=") || hasBrTags)

    if (containsMath(text)) {
      return (
        <th className="p-4 text-left font-medium" {...rest}>
          <MathText text={text} />
        </th>
      )
    }

    if (hasHtmlContent) {
      const processedText = hasBrTags ? text.replace(/<br>/g, "<br/>") : text
      return (
        <th
          className="p-4 text-left font-medium"
          {...rest}
          dangerouslySetInnerHTML={{ __html: processedText }}
        />
      )
    }

    return (
      <th className="p-4 text-left font-medium" {...rest}>
        {children}
      </th>
    )
  },
  img: ({ ...props }) =>
    createMarkdownElement("img", {
      className:
        "w-auto w-auto mx-auto rounded-lg object-cover dark:bg-white dark:p-3",
      ...props,
    }),
  "footnote-ref": ({ identifier, label }) => {
    const handleClick = useCallback(
      (e: React.MouseEvent) => {
        e.preventDefault()
        e.stopPropagation()

        const target = document.getElementById(`fn-${identifier}`)
        if (target) {
          scrollToElementWithOffset(target)
        }
      },
      [identifier]
    )

    React.useEffect(() => {
      console.log("Footnote ref mounted:", identifier)
    }, [identifier])

    return (
      <sup>
        <button
          type="button"
          id={`fnref-${identifier}`}
          className="text-anakiwa-500 hover:text-orange duration-200"
          onClick={handleClick}
        >
          [{label}]
        </button>
      </sup>
    )
  },
  "footnote-definition": ({ identifier, label, children }) => {
    const handleBackClick = useCallback(
      (e: React.MouseEvent) => {
        e.preventDefault()
        e.stopPropagation()

        const target = document.getElementById(`fnref-${identifier}`)
        if (target) {
          scrollToElementWithOffset(target)
        }
      },
      [identifier]
    )

    React.useEffect(() => {
      console.log("Footnote definition mounted:", identifier)
    }, [identifier])

    return (
      <div
        id={`fn-${identifier}`}
        className="flex gap-2 text-sm text-tuatara-600 mb-2"
      >
        <div className="flex-none">[{label}]</div>
        <div className="flex-1">
          {children}
          <button
            type="button"
            className="text-anakiwa-500 hover:text-orange duration-200 ml-1"
            onClick={handleBackClick}
          >
            ↩
          </button>
        </div>
      </div>
    )
  },
  footnotes: ({ children }) => {
    return (
      <div className="mt-8 pt-8 border-t border-tuatara-200">
        <h2 className="text-xl font-bold mb-4">Footnotes</h2>
        {children}
      </div>
    )
  },
})

interface MarkdownProps {
  children: string
  components?: Components // components overrides the default components
  darkMode?: boolean
}

export const Markdown = ({
  children,
  components,
  darkMode = false,
}: MarkdownProps) => {
  const [content, setContent] = React.useState<React.ReactNode[]>([])

  React.useEffect(() => {
    if (!children) {
      setContent([])
      return
    }

    try {
      const blockMathRegex = /\$\$([\s\S]*?)\$\$/g
      const blockParts = children.split(blockMathRegex)

      const mathComponents = {
        ...REACT_MARKDOWN_CONFIG(darkMode),
        ...components,
      }

      const rehypePlugins = [rehypeRaw as any, rehypeProcessBrTags as any]

      if (blockParts.length === 1) {
        setContent([
          <ReactMarkdown
            key="markdown"
            skipHtml={false}
            rehypePlugins={rehypePlugins}
            components={mathComponents}
            remarkPlugins={[remarkGfm, remarkCustomNewlines]}
          >
            {children}
          </ReactMarkdown>,
        ])
        return
      }

      const parts: React.ReactNode[] = []

      blockParts.forEach((part, index) => {
        if (index % 2 === 0) {
          if (part.trim()) {
            parts.push(
              <ReactMarkdown
                key={`text-${index}`}
                skipHtml={false}
                rehypePlugins={rehypePlugins}
                components={mathComponents}
                remarkPlugins={[remarkGfm, remarkCustomNewlines]}
              >
                {part}
              </ReactMarkdown>
            )
          }
        } else {
          parts.push(<KaTeXBlock key={`block-math-${index}`} math={part} />)
        }
      })

      setContent(parts)
    } catch (error) {
      console.error("Error processing markdown with math:", error)
      setContent([
        <ReactMarkdown
          key="fallback"
          skipHtml={false}
          rehypePlugins={[rehypeRaw as any, rehypeProcessBrTags as any]}
          components={{
            ...REACT_MARKDOWN_CONFIG(darkMode),
            ...components,
          }}
          remarkPlugins={[remarkGfm, remarkCustomNewlines]}
        >
          {children}
        </ReactMarkdown>,
      ])
    }
  }, [children, darkMode, components])

  return <>{content}</>
}
