defmodule ExDoc.Markdown do
  @moduledoc """
  Adapter behaviour and conveniences for converting Markdown to HTML.

  ExDoc is compatible with any markdown processor that implements the
  functions defined in this module. The markdown processor can be changed
  via the `:markdown_processor` option in your `mix.exs`.

  ExDoc supports the following Markdown parsers out of the box:

    * [EarmarkParser](https://github.com/robertdober/earmark_parser)

  ExDoc uses EarmarkParser by default.
  """

  @doc """
  Converts markdown into HTML.
  """
  @callback to_ast(String.t(), Keyword.t()) :: term()

  @doc """
  Returns true if all dependencies necessary are available.
  """
  @callback available?() :: boolean()

  @markdown_processors [
    ExDoc.Markdown.Earmark
  ]

  @markdown_processor_key :markdown_processor

  @doc """
  Converts the given markdown document to HTML AST.
  """
  def to_ast(text, opts \\ []) when is_binary(text) do
    {processor, options} = get_markdown_processor()
    processor.to_ast(text, options |> Keyword.merge(opts))
  end

  @doc """
  Gets the current markdown processor set globally.
  """
  def get_markdown_processor do
    case Application.fetch_env(:ex_doc, @markdown_processor_key) do
      {:ok, {processor, options}} ->
        {processor, options}

      :error ->
        processor = find_markdown_processor() || raise_no_markdown_processor()
        put_markdown_processor({processor, []})
        {processor, []}
    end
  end

  @doc """
  Changes the markdown processor globally.
  """
  def put_markdown_processor(processor) when is_atom(processor) do
    put_markdown_processor({processor, []})
  end

  def put_markdown_processor({processor, options}) do
    Application.put_env(:ex_doc, @markdown_processor_key, {processor, options})
  end

  defp find_markdown_processor do
    Enum.find(@markdown_processors, fn module ->
      Code.ensure_loaded?(module) && module.available?()
    end)
  end

  defp raise_no_markdown_processor do
    raise """
    Could not find a markdown processor to be used by ex_doc.
    You can either:

    * Add {:earmark, ">= 0.0.0"} to your mix.exs deps
      to use an Elixir-based markdown processor
    """
  end
end
