defmodule Web.Markdown do
  @moduledoc """
  Converts markdown to HTML using MDEx, with custom rendering to add Tailwind CSS classes.
  Uses iodata for efficient string generation.
  """

  alias MDEx.Document

  @doc """
  Converts markdown to HTML with syntax highlighting, additional extensions,
  and appropriate Tailwind CSS classes for styling.
  """
  def to_html(markdown) when is_binary(markdown) do
    # First parse the document to get the AST
    {:ok, document} =
      MDEx.parse_document(markdown,
        extension: [
          strikethrough: true,
          table: true,
          autolink: true,
          tasklist: true,
          footnotes: true
        ],
        features: [syntax_highlight_theme: "catppuccin_latte"]
      )

    # Render the document to HTML with Tailwind classes
    render_document(document)
  end

  def to_html(nil), do: ""

  # Core rendering function for the document
  defp render_document(%Document{} = document) do
    document.nodes
    |> Enum.map(&render_node(&1, nil))
  end

  # Heading (h1-h6) with anchor links
  defp render_node(%MDEx.Heading{level: level} = node, _parent) do
    tag = "h#{level}"

    class =
      case level do
        1 -> "text-3xl font-bold mt-6 mb-4 group"
        2 -> "text-2xl font-bold mt-5 mb-3 group"
        3 -> "text-xl font-bold mt-4 mb-2 group"
        4 -> "text-lg font-bold mt-3 mb-2 group"
        5 -> "text-base font-bold mt-3 mb-2 group"
        6 -> "text-sm font-bold mt-3 mb-2 group"
      end

    # Generate a slug from the text content for the ID
    content = Enum.map(node.nodes, &render_node(&1, node))
    content_text = extract_text_content(node)
    slug = Slug.slugify(content_text)

    [
      "<",
      tag,
      " id=\"",
      slug,
      "\" class=\"",
      class,
      " relative\">",
      "<a href=\"#",
      slug,
      "\" class=\"absolute -left-5 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity\">¶</a>",
      content,
      "</",
      tag,
      ">"
    ]
  end

  # Paragraph
  defp render_node(%MDEx.Paragraph{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<p>", content, "</p>"]
  end

  # Text
  defp render_node(%MDEx.Text{literal: literal}, _parent) do
    literal
  end

  # Strong (bold)
  defp render_node(%MDEx.Strong{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<strong>", content, "</strong>"]
  end

  # Emphasis (italic)
  defp render_node(%MDEx.Emph{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<em>", content, "</em>"]
  end

  # Strikethrough
  defp render_node(%MDEx.Strikethrough{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<del>", content, "</del>"]
  end

  # Link
  defp render_node(%MDEx.Link{url: url, title: title} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    title_attr = if title, do: [" title=\"", title, "\""], else: []

    [
      "<a href=\"",
      url,
      "\"",
      title_attr,
      " class=\"text-blue-600 hover:text-blue-800 hover:underline\">",
      content,
      "</a>"
    ]
  end

  # Image
  defp render_node(%MDEx.Image{url: url, title: title} = node, _parent) do
    alt = Enum.map(node.nodes, &render_node(&1, node))
    title_attr = if title, do: [" title=\"", title, "\""], else: []

    [
      "<img src=\"",
      url,
      "\" alt=\"",
      alt,
      "\"",
      title_attr,
      " class=\"max-w-full my-4 rounded\">"
    ]
  end

  # Code (inline)
  defp render_node(%MDEx.Code{literal: literal}, _parent) do
    ["<code class=\"bg-gray-100 rounded px-1 py-0.5 text-sm\">", literal, "</code>"]
  end

  # Code blocks - use MDEx's built-in HTML rendering to preserve syntax highlighting
  defp render_node(%MDEx.CodeBlock{} = node, _parent) do
    # Use MDEx's built-in HTML rendering for code blocks to preserve syntax highlighting
    {:ok, html} =
      MDEx.to_html(
        %MDEx.Document{nodes: [node]},
        extension: [],
        features: [syntax_highlight_theme: "catppuccin_latte"]
      )

    # Return the HTML as is since it's already properly formatted with syntax highlighting
    String.replace(html, ~s|class="autumn-hl"|, ~s|class="autumn-hl p-2 rounded"|)
  end

  # Lists - Bullet
  defp render_node(%MDEx.List{list_type: :bullet} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<ul class=\"list-disc pl-6 my-4\">", content, "</ul>"]
  end

  # Lists - Ordered
  defp render_node(%MDEx.List{list_type: :ordered} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<ol class=\"list-decimal pl-6 my-4\">", content, "</ol>"]
  end

  # List items
  defp render_node(%MDEx.ListItem{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<li class=\"mb-1\">", content, "</li>"]
  end

  # Task items (checkboxes)
  defp render_node(%MDEx.TaskItem{checked: checked} = node, _parent) do
    checkbox =
      if checked do
        "<input type=\"checkbox\" checked disabled class=\"rounded mr-1.5 text-blue-600\">"
      else
        "<input type=\"checkbox\" disabled class=\"rounded mr-1.5 text-blue-600\">"
      end

    content = Enum.map(node.nodes, &render_node(&1, node))

    [
      "<li class=\"mb-1 flex items-start\">",
      checkbox,
      "<div class=\"inline\">",
      content,
      "</div>",
      "</li>"
    ]
  end

  # Blockquote
  defp render_node(%MDEx.BlockQuote{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))

    [
      "<blockquote class=\"border-l-4 border-gray-300 pl-4 py-1 my-4 italic\">",
      content,
      "</blockquote>"
    ]
  end

  # Horizontal Rule
  defp render_node(%MDEx.ThematicBreak{}, _parent) do
    "<hr class=\"border-t border-gray-300 my-6\">"
  end

  # Table
  defp render_node(%MDEx.Table{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))

    [
      "<div class=\"overflow-x-auto my-4\"><table class=\"min-w-full divide-y divide-gray-200\">",
      content,
      "</table></div>"
    ]
  end

  # Table row
  defp render_node(%MDEx.TableRow{header: true} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<tr class=\"bg-gray-50 border-b border-gray-200\">", content, "</tr>"]
  end

  defp render_node(%MDEx.TableRow{header: false} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<tr class=\"border-b border-gray-200\">", content, "</tr>"]
  end

  # Table cell in header row
  defp render_node(%MDEx.TableCell{} = node, %MDEx.TableRow{header: true}) do
    content = Enum.map(node.nodes, &render_node(&1, node))

    [
      "<th class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">",
      content,
      "</th>"
    ]
  end

  # Table cell in normal row
  defp render_node(%MDEx.TableCell{} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))
    ["<td class=\"px-6 py-4 whitespace-nowrap text-sm\">", content, "</td>"]
  end

  # HTML blocks (pass through)
  defp render_node(%MDEx.HtmlBlock{literal: literal}, _parent) do
    literal
  end

  # HTML inline (pass through)
  defp render_node(%MDEx.HtmlInline{literal: literal}, _parent) do
    literal
  end

  # Soft break (line break that doesn't create new paragraph)
  defp render_node(%MDEx.SoftBreak{}, _parent) do
    "\n"
  end

  # Hard break (explicit line break with <br>)
  defp render_node(%MDEx.LineBreak{}, _parent) do
    "<br>"
  end

  # Footnote reference
  defp render_node(%MDEx.FootnoteReference{name: name}, _parent) do
    [
      "<sup class=\"footnote-ref\">",
      "<a href=\"#fn-",
      name,
      "\" id=\"fnref-",
      name,
      "\" class=\"text-blue-600 hover:text-blue-800\">",
      name,
      "</a>",
      "</sup>"
    ]
  end

  # Footnote definition
  defp render_node(%MDEx.FootnoteDefinition{name: name} = node, _parent) do
    content = Enum.map(node.nodes, &render_node(&1, node))

    [
      "<div class=\"footnote flex flex-row\" id=\"fn-",
      name,
      "\">",
      "<p class=\"my-1 flex\">",
      "<span class=\"mr-2 flex-shrink-0\"><sup>",
      name,
      ".</sup></span>",
      "<span class=\"inline\">",
      content,
      " <a href=\"#fnref-",
      name,
      "\" class=\"text-blue-600 hover:text-blue-800 text-sm\">↩</a>",
      "</span>",
      "</p>",
      "</div>"
    ]
  end

  # Extract plain text from a node tree (for generating slugs)
  defp extract_text_content(node) do
    cond do
      is_map(node) && Map.has_key?(node, :literal) && is_binary(node.literal) ->
        node.literal

      is_map(node) && Map.has_key?(node, :nodes) ->
        node.nodes
        |> Enum.map(&extract_text_content/1)
        |> Enum.join("")

      true ->
        ""
    end
  end
end