TODO: Refactor this ASAP

defmodule Guaxinim do
  alias Guaxinim.Internal.SourceProcessor
  alias Guaxinim.Internal.SourceFormatter
  alias Guaxinim.BeamInspector
  alias Guaxinim.Config
  import Guaxinim.Utils.Path, only: [path: 1]
  alias Guaxinim.Config
  require EEx
     
@doc

Prepare the destination directory

  def prepare_dst(config) do
       

Clean the already existing files (oportunity for caching)

    if File.exists?(config.dst) and File.dir?(config.dst) do
      File.rm_rf!(config.dst)
    end
       

Create the destination directory

    File.mkdir_p!(config.dst)
       

Copy the static assets

    dst_static = Path.join(config.dst, "_static")
    static_src =
      :guaxinim
      |> :code.priv_dir
      |> Path.join("static")

    File.cp_r!(static_src, dst_static)
    :ok
  end

  def all_relative_files(root) do
    paths = Path.wildcard(root <> "/**/*.ex") ++ Path.wildcard(root <> "/*.ex")
    Enum.map(paths, fn path -> Path.relative_to(path, root) end)
  end

  def all_relative_dirs(root) do
    child_paths =
      Path.wildcard(root <> "/**")
      |> Enum.filter(fn path -> File.dir?(path) end)
      |> Enum.map(fn path -> Path.relative_to(path, root) end)

    ["" | child_paths]
  end

  def run() do
    config = Config.from_mix()
    BeamInspector.gather_data_from_modules(config)
    prepare_dst(config)

    config.src
    |> all_relative_files
    |> Enum.map(fn path -> process_file(path, config) end)

    config.src
    |> all_relative_dirs
    |> Enum.map(fn path -> create_index_file(path, config) end)

    :ok
  end


  def create_index_file(directory, config) do
    abs_directory = Path.join(config.src, directory)

    {sibling_directories, sibling_files} =
      child_directories_and_files(abs_directory)

    file = Path.join(directory, "index.html")

    assigns = %{
      project_title: config.project_title,
      sibling_directories: sibling_directories,
      sibling_files: sibling_files,
      config: config,
      file: file,
      content: ""
    }

    html = render_code_page(assigns)
    output_file = Path.join(config.dst, file)
    write_with_mkdir(output_file, html)
  end

  def process_file(file, config) do
    abs_file = Path.join(config.src, file)
    root_dirname = Path.dirname(abs_file)

    {sibling_directories, sibling_files} =
      child_directories_and_files(root_dirname)

    source = File.read!(abs_file)

    data = SourceProcessor.process_source(file, source)
    content = SourceFormatter.blocks_to_html(config, data)

    assigns = %{
      project_title: config.project_title,
      sibling_directories: sibling_directories,
      sibling_files: sibling_files,
      config: config,
      file: file,
      content: content
    }

    html = render_code_page(assigns)
    output_file = Path.join(config.dst, file <> ".html")
    write_with_mkdir(output_file, html)
  end

  def write_with_mkdir(output_file, content) do
    dirname = Path.dirname(output_file)
       

Ensure the directory exists. To do this, we try to create the directory and ignore both the :ok result (which means we’ve succeeded) and the {:error, :eexist} result (which means the directory already existed) Raise an error in case we get any other {:error, _} value.

    case File.mkdir_p(dirname) do
      :ok -> nil
      {:error, :eexist} -> nil
    end
    File.write!(output_file, content)
  end

  def child_directories_and_files(parent_dir) do
    children =
      parent_dir
      |> File.ls!
      |> Enum.reject(fn path -> path == "index.md" end)

    child_directories =
      children
      |> Enum.filter(fn path -> parent_dir |> Path.join(path) |> File.dir? end)
      |> Enum.sort

    child_files =
      children
      |> Enum.filter(fn path ->
        full_path = Path.join(parent_dir, path)
        not File.dir?(full_path) and Path.extname(path) == ".ex"
      end)
      |> Enum.sort

    {child_directories, child_files}
  end
     

Convenience functions to render the templates

  @external_resource "lib/guaxinim/templates/layout/master.html.eex"
  EEx.function_from_file(:defp, :render_code_page,
    "lib/guaxinim/templates/layout/master.html.eex",
    [:assigns])

  @external_resource "lib/guaxinim/templates/layout/sidebar.html.eex"
  EEx.function_from_file(:defp, :render_sidebar,
    "lib/guaxinim/templates/layout/sidebar.html.eex",
    [:assigns])
end