defmodule ExcellentMigrations.AstParserFullDetections do
  @moduledoc false

  @index_functions [:create, :create_if_not_exists, :drop, :drop_if_exists]
  @index_types [:index, :unique_index]

  def parse(ast) do
    full_ast_detections(ast)
  end

  defp full_ast_detections(ast) do
    [
      &detect_invalid_index_concurrently/1
    ]
    |> Enum.map(&apply(&1, [ast]))
    |> Enum.concat()
  end

  defp detect_invalid_index_concurrently(ast) do
    default = %{
      line: nil,
      is_index_concurrently?: false,
      ddl_transaction_disabled?: false,
      lock_disabled?: false
    }

    ast
    |> Macro.postwalk(default, &detect_invalid_index_concurrently_inner/2)
    |> case do
      {_, %{is_index_concurrently?: false}} ->
        []

      {_, %{lock_disabled?: true, ddl_transaction_disabled?: true}} ->
        []

      {_, %{lock_disabled?: true, ddl_transaction_disabled?: false, line: line}} ->
        [{:index_concurrently_without_disable_ddl_transaction, line}]

      {_, %{lock_disabled?: false, ddl_transaction_disabled?: true, line: line}} ->
        [{:index_concurrently_without_disable_migration_lock, line}]

      {_, %{line: line}} ->
        [
          {:index_concurrently_without_disable_migration_lock, line},
          {:index_concurrently_without_disable_ddl_transaction, line}
        ]
    end
  end

  defp detect_invalid_index_concurrently_inner(
         {fun_name, location, [{operation, _, [_, _, options]}]} = ast_part,
         acc
       )
       when fun_name in @index_functions and operation in @index_types do
    is_concurrently? = Keyword.get(options, :concurrently, false) || acc.is_index_concurrently?
    line = if is_concurrently?, do: Keyword.get(location, :line), else: acc.line

    {ast_part, %{acc | is_index_concurrently?: is_concurrently?, line: line}}
  end

  defp detect_invalid_index_concurrently_inner(
         {:disable_ddl_transaction, _, [true]} = ast_part,
         acc
       ) do
    {ast_part, %{acc | ddl_transaction_disabled?: true}}
  end

  defp detect_invalid_index_concurrently_inner(
         {:disable_migration_lock, _, [true]} = ast_part,
         acc
       ) do
    {ast_part, %{acc | lock_disabled?: true}}
  end

  defp detect_invalid_index_concurrently_inner(ast_part, acc) do
    {ast_part, acc}
  end
end
