defmodule Temple.Ast.Utils do
  @moduledoc false

  @attributes Application.compile_env(
                :temple,
                :attributes,
                {Phoenix.HTML, :attributes_escape}
              )

  def snake_to_kebab(stringable),
    do: stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-")

  def kebab_to_snake(stringable),
    do: stringable |> to_string() |> String.replace("-", "_")

  def compile_attrs([]), do: []

  def compile_attrs([attrs]) when is_list(attrs) do
    compile_attrs(attrs)
  end

  def compile_attrs(attrs) when is_list(attrs) do
    if Keyword.keyword?(attrs) do
      for {name, value} <- attrs, reduce: [] do
        acc ->
          name = snake_to_kebab(name)

          with false <- not is_binary(value) && Macro.quoted_literal?(value),
               false <- match?({_, _, _}, value),
               false <- is_list(value) do
            [{:text, " " <> name <> "=\"" <> to_string(value) <> "\""} | acc]
          else
            true ->
              nodes = Temple.Ast.Utils.build_attr(name, value)
              Enum.reverse(nodes) ++ acc
          end
      end
      |> Enum.reverse()
    else
      [
        {:expr,
         quote do
           unquote(__MODULE__).__attributes__(unquote(List.first(attrs)))
         end}
      ]
    end
  end

  def build_attr(name, true) do
    [{:text, " " <> name}]
  end

  def build_attr(_name, false) do
    []
  end

  def build_attr("rest!", values) when is_list(values) do
    Enum.flat_map(values, fn {name, value} ->
      build_attr(snake_to_kebab(name), value)
    end)
  end

  def build_attr("rest!", {_, _, _} = value) do
    expr =
      quote do
        unquote(__MODULE__).__attributes__(unquote(value))
      end

    [{:expr, expr}]
  end

  def build_attr(name, {_, _, _} = value) do
    expr =
      quote do
        unquote(__MODULE__).__attributes__([{unquote(name), unquote(value)}])
      end

    [{:expr, expr}]
  end

  def build_attr("class", classes) when is_list(classes) do
    value =
      quote do
        String.trim_leading(
          for value <- unquote(classes), into: "" do
            case value do
              {class, true} -> " #{class}"
              class when is_binary(class) -> " #{class}"
              _ -> ""
            end
          end
        )
      end

    [{:text, ~s' class="'}, {:expr, value}, {:text, ~s'"'}]
  end

  def build_attr(name, value) do
    [{:text, ~s' #{name}="' <> to_string(value) <> ~s'"'}]
  end

  def split_args(not_what_i_want) when is_nil(not_what_i_want) or is_atom(not_what_i_want),
    do: {[], []}

  def split_args(args) do
    {do_and_else, args} =
      args
      |> Enum.split_with(fn arg ->
        if Keyword.keyword?(arg) do
          arg
          |> Keyword.drop([:do, :else])
          |> Enum.empty?()
        else
          false
        end
      end)

    {List.flatten(do_and_else), args}
  end

  def consolidate_blocks(blocks, args) do
    case args do
      [args] when is_list(args) ->
        {do_value, args} = Keyword.pop(args, :do)

        {Keyword.put_new(blocks, :do, do_value), args}

      _ ->
        {blocks, args}
    end
  end

  def split_on_fn([{:fn, _, _} = func | rest], {args_before, _, args_after}) do
    split_on_fn(rest, {args_before, func, args_after})
  end

  def split_on_fn([arg | rest], {args_before, nil, args_after}) do
    split_on_fn(rest, {[arg | args_before], nil, args_after})
  end

  def split_on_fn([arg | rest], {args_before, func, args_after}) do
    split_on_fn(rest, {args_before, func, [arg | args_after]})
  end

  def split_on_fn([], {args_before, func, args_after}) do
    {Enum.reverse(args_before), func, Enum.reverse(args_after)}
  end

  def pop_compact?([]), do: {false, []}
  def pop_compact?([args]) when is_list(args), do: pop_compact?(args)

  def pop_compact?(args) do
    Keyword.pop(args, :compact, false)
  end

  def indent(nil) do
    ""
  end

  def indent(level) do
    String.duplicate(" ", level * 2)
  end

  def inspect_ast(ast) do
    ast
    |> Macro.to_string()
    |> IO.puts()

    ast
  end

  def __attributes__(attributes) do
    {mod, func} = @attributes

    apply(mod, func, [attributes])
  end
end
