defmodule MDEx.Document.Traversal do
  @moduledoc false
  # traverse_and_update/{2,3} based on https://github.com/philss/floki/blob/96955f925d62989b6f0bfaf09ce6505e67e04fbb/lib/floki/traversal.ex

  def traverse_and_update(%{nodes: nodes} = doc, fun) do
    fun.(%{doc | nodes: do_traverse_and_update(nodes, fun)})
  end

  def traverse_and_update(doc, fun) do
    fun.(doc)
  end

  defp do_traverse_and_update([], _fun), do: []

  defp do_traverse_and_update([node | rest], fun) do
    case do_traverse_and_update(node, fun) do
      :pop ->
        do_traverse_and_update(rest, fun)

      nil ->
        do_traverse_and_update(rest, fun)

      mapped_node ->
        [mapped_node | do_traverse_and_update(rest, fun)]
    end
  end

  defp do_traverse_and_update(%{nodes: nodes} = node, fun) do
    fun.(%{node | nodes: do_traverse_and_update(nodes, fun)})
  end

  defp do_traverse_and_update(%{} = node, fun) do
    fun.(node)
  end

  def traverse_and_update(%{nodes: nodes} = doc, acc, fun) do
    {mapped_nodes, new_acc} = do_traverse_and_update(nodes, acc, fun)
    fun.(%{doc | nodes: mapped_nodes}, new_acc)
  end

  def traverse_and_update(doc, acc, fun) do
    {node, new_acc} = do_traverse_and_update(doc, acc, fun)
    fun.(node, new_acc)
  end

  defp do_traverse_and_update([], acc, _fun), do: {[], acc}

  defp do_traverse_and_update([node | rest], acc, fun) do
    case do_traverse_and_update(node, acc, fun) do
      {:pop, new_acc} ->
        do_traverse_and_update(rest, new_acc, fun)

      {nil, new_acc} ->
        do_traverse_and_update(rest, new_acc, fun)

      {mapped_node, new_acc} ->
        {mapped_rest, new_acc_rest} = do_traverse_and_update(rest, new_acc, fun)
        {[mapped_node | mapped_rest], new_acc_rest}
    end
  end

  defp do_traverse_and_update(%{nodes: nodes} = node, acc, fun) do
    {mapped_nodes, new_acc} = do_traverse_and_update(nodes, acc, fun)
    fun.(%{node | nodes: mapped_nodes}, new_acc)
  end

  defp do_traverse_and_update(%{} = node, acc, fun) do
    fun.(node, acc)
  end
end
