lib/parser.ex

defmodule Dsqlex.Parser do
  # Arithmetic operators (split by precedence group)
  @additive_ops [:plus, :minus]
  @multiplicative_ops [:multiply, :divide]
  # Comparison operators
  @comparison_ops [:eq, :neq, :lt, :gt, :lte, :gte]

  def parse(tokens) do
    case parse_select(tokens) do
      {:ok, ast, []} -> {:ok, ast}
      {:ok, _ast, remaining} -> {:error, "Unexpected tokens: #{inspect(remaining)}"}
      error -> error
    end
  end

  # SELECT is optional - if present, consume it; if not, just parse the expression
  defp parse_select([{:keyword, :select} | rest]) do
    case parse_expression(rest) do
      {:ok, expr, rest} -> {:ok, {:select, expr}, rest}
      error -> error
    end
  end
  defp parse_select(tokens) do
    # No SELECT keyword - parse expression directly
    case parse_expression(tokens) do
      {:ok, expr, rest} -> {:ok, {:select, expr}, rest}
      error -> error
    end
  end

  # Expression: starts at the lowest precedence (logical)
  defp parse_expression(tokens), do: parse_logical(tokens)

  # ============================================================
  # LEVEL 1: Logical AND/OR (lowest precedence)
  # - Operates on comparison expressions
  # - Same-op chaining allowed (a AND b AND c)
  # - Mixing AND/OR requires parentheses
  # ============================================================
  defp parse_logical(tokens) do
    with {:ok, left, rest} <- parse_comparison(tokens) do
      maybe_parse_logical_op(left, rest)
    end
  end

  defp maybe_parse_logical_op(left, [{:keyword, :and} | rest]) do
    with {:ok, right, rest} <- parse_comparison(rest) do
      parse_and_chain({:binary_op, :and, left, right}, rest)
    end
  end
  defp maybe_parse_logical_op(left, [{:keyword, :or} | rest]) do
    with {:ok, right, rest} <- parse_comparison(rest) do
      parse_or_chain({:binary_op, :or, left, right}, rest)
    end
  end
  defp maybe_parse_logical_op(left, rest), do: {:ok, left, rest}

  # Continue AND chain
  defp parse_and_chain(left, [{:keyword, :and} | rest]) do
    with {:ok, right, rest} <- parse_comparison(rest) do
      parse_and_chain({:binary_op, :and, left, right}, rest)
    end
  end
  defp parse_and_chain(_left, [{:keyword, :or} | _]) do
    {:error, "Ambiguous expression: mixing AND/OR requires parentheses"}
  end
  defp parse_and_chain(left, rest), do: {:ok, left, rest}

  # Continue OR chain
  defp parse_or_chain(left, [{:keyword, :or} | rest]) do
    with {:ok, right, rest} <- parse_comparison(rest) do
      parse_or_chain({:binary_op, :or, left, right}, rest)
    end
  end
  defp parse_or_chain(_left, [{:keyword, :and} | _]) do
    {:error, "Ambiguous expression: mixing AND/OR requires parentheses"}
  end
  defp parse_or_chain(left, rest), do: {:ok, left, rest}

  # ============================================================
  # LEVEL 2: Comparison operators
  # - Operates on arithmetic expressions
  # - Only ONE comparison allowed (no chaining)
  # ============================================================
  defp parse_comparison(tokens) do
    with {:ok, left, rest} <- parse_arithmetic(tokens) do
      maybe_parse_comparison_op(left, rest)
    end
  end

  defp maybe_parse_comparison_op(left, [{:operator, op} | rest]) when op in @comparison_ops do
    with {:ok, right, rest} <- parse_arithmetic(rest) do
      # Check for chained comparisons (not allowed)
      case rest do
        [{:operator, op2} | _] when op2 in @comparison_ops ->
          {:error, "Cannot chain comparison operators. Use parentheses."}
        _ ->
          {:ok, {:binary_op, op, left, right}, rest}
      end
    end
  end
  # expr IN (val1, val2, ...)
  defp maybe_parse_comparison_op(left, [{:keyword, :in}, {:lparen} | rest]) do
    with {:ok, items, rest} <- parse_in_list(rest) do
      case rest do
        [{:rparen} | rest] -> {:ok, {:in, left, items}, rest}
        _ -> {:error, "Expected closing parenthesis ')' after IN list"}
      end
    end
  end

  # expr NOT IN (val1, val2, ...)
  defp maybe_parse_comparison_op(left, [{:keyword, :not}, {:keyword, :in}, {:lparen} | rest]) do
    with {:ok, items, rest} <- parse_in_list(rest) do
      case rest do
        [{:rparen} | rest] -> {:ok, {:not_in, left, items}, rest}
        _ -> {:error, "Expected closing parenthesis ')' after NOT IN list"}
      end
    end
  end

  # expr IS NOT NULL / TRUE / FALSE
  defp maybe_parse_comparison_op(left, [{:keyword, :is}, {:keyword, :not}, {:keyword, kw} | rest])
       when kw in [:null, :true, :false] do
    literal = case kw do
      :null  -> {:null}
      :true  -> {:boolean, true}
      :false -> {:boolean, false}
    end
    {:ok, {:binary_op, :neq, left, literal}, rest}
  end

  # expr IS NULL / TRUE / FALSE
  defp maybe_parse_comparison_op(left, [{:keyword, :is}, {:keyword, kw} | rest])
       when kw in [:null, :true, :false] do
    literal = case kw do
      :null  -> {:null}
      :true  -> {:boolean, true}
      :false -> {:boolean, false}
    end
    {:ok, {:binary_op, :eq, left, literal}, rest}
  end

  # expr NOT LIKE pattern
  defp maybe_parse_comparison_op(left, [{:keyword, :not}, {:keyword, :like} | rest]) do
    with {:ok, pattern, rest} <- parse_primary(rest) do
      {:ok, {:not_like, left, pattern}, rest}
    end
  end

  # expr LIKE pattern
  defp maybe_parse_comparison_op(left, [{:keyword, :like} | rest]) do
    with {:ok, pattern, rest} <- parse_primary(rest) do
      {:ok, {:like, left, pattern}, rest}
    end
  end

  defp maybe_parse_comparison_op(left, rest), do: {:ok, left, rest}

  # ============================================================
  # LEVEL 3: Arithmetic operators
  # - Operates on primaries
  # - Same-group chaining allowed (a + b - c, a * b / c)
  # - Mixing additive (+/-) and multiplicative (*//) requires parentheses
  # ============================================================
  defp parse_arithmetic(tokens) do
    with {:ok, left, rest} <- parse_primary(tokens) do
      maybe_parse_arithmetic_op(left, rest)
    end
  end

  defp maybe_parse_arithmetic_op(left, [{:operator, op} | rest]) when op in @additive_ops do
    with {:ok, right, rest} <- parse_primary(rest) do
      parse_additive_chain({:binary_op, op, left, right}, rest)
    end
  end
  defp maybe_parse_arithmetic_op(left, [{:operator, op} | rest]) when op in @multiplicative_ops do
    with {:ok, right, rest} <- parse_primary(rest) do
      parse_multiplicative_chain({:binary_op, op, left, right}, rest)
    end
  end
  defp maybe_parse_arithmetic_op(left, rest), do: {:ok, left, rest}

  # Continue additive chain (+/-)
  defp parse_additive_chain(left, [{:operator, op} | rest]) when op in @additive_ops do
    with {:ok, right, rest} <- parse_primary(rest) do
      parse_additive_chain({:binary_op, op, left, right}, rest)
    end
  end
  defp parse_additive_chain(_left, [{:operator, op} | _]) when op in @multiplicative_ops do
    {:error, "Ambiguous expression: mixing +/- and *// requires parentheses"}
  end
  defp parse_additive_chain(left, rest), do: {:ok, left, rest}

  # Continue multiplicative chain (*//)
  defp parse_multiplicative_chain(left, [{:operator, op} | rest]) when op in @multiplicative_ops do
    with {:ok, right, rest} <- parse_primary(rest) do
      parse_multiplicative_chain({:binary_op, op, left, right}, rest)
    end
  end
  defp parse_multiplicative_chain(_left, [{:operator, op} | _]) when op in @additive_ops do
    {:error, "Ambiguous expression: mixing +/- and *// requires parentheses"}
  end
  defp parse_multiplicative_chain(left, rest), do: {:ok, left, rest}

  # ============================================================
  # LEVEL 4: Primary expressions (highest precedence)
  # ============================================================
  defp parse_primary([{:number, n} | rest]), do: {:ok, {:number, n}, rest}
  defp parse_primary([{:string, s} | rest]), do: {:ok, {:string, s}, rest}
  defp parse_primary([{:identifier, name} | rest]), do: {:ok, {:identifier, name}, rest}
  defp parse_primary([{:keyword, :null} | rest]), do: {:ok, {:null}, rest}
  defp parse_primary([{:keyword, :true} | rest]), do: {:ok, {:boolean, true}, rest}
  defp parse_primary([{:keyword, :false} | rest]), do: {:ok, {:boolean, false}, rest}

  # Parenthesized expression - resets to top level (logical)
  defp parse_primary([{:lparen} | rest]) do
    with {:ok, expr, rest} <- parse_expression(rest) do
      case rest do
        [{:rparen} | rest] -> {:ok, expr, rest}
        _ -> {:error, "Expected closing parenthesis ')'"}
      end
    end
  end

  # CASE WHEN ... THEN ... [WHEN ... THEN ...] [ELSE ...] END
  defp parse_primary([{:keyword, :case} | rest]) do
    with {:ok, when_clauses, rest} <- parse_when_clauses(rest),
         {:ok, else_clause, rest} <- parse_else_clause(rest),
         {:ok, rest} <- expect_keyword(:end, rest) do
      {:ok, {:case_expr, when_clauses, else_clause}, rest}
    end
  end

  # Function call: FUNCTION_NAME(arg1, arg2, ...)
  defp parse_primary([{:function, name}, {:lparen} | rest]) do
    with {:ok, args, rest} <- parse_function_args(rest) do
      case rest do
        [{:rparen} | rest] -> {:ok, {:call, name, args}, rest}
        _ -> {:error, "Expected closing parenthesis ')' after function arguments"}
      end
    end
  end

  defp parse_primary(tokens), do: {:error, "Unexpected token: #{inspect(tokens)}"}

  # ============================================================
  # IN list helpers
  # ============================================================

  # Parse comma-separated values inside IN (...)
  defp parse_in_list([{:rparen} | _] = tokens), do: {:ok, [], tokens}
  defp parse_in_list(tokens) do
    with {:ok, first, rest} <- parse_primary(tokens) do
      parse_more_in_items([first], rest)
    end
  end

  defp parse_more_in_items(items, [{:comma} | rest]) do
    with {:ok, item, rest} <- parse_primary(rest) do
      parse_more_in_items(items ++ [item], rest)
    end
  end
  defp parse_more_in_items(items, rest), do: {:ok, items, rest}

  # ============================================================
  # Function call helpers
  # ============================================================

  # Parse comma-separated arguments (can be empty)
  defp parse_function_args([{:rparen} | _] = tokens), do: {:ok, [], tokens}
  defp parse_function_args(tokens) do
    with {:ok, first_arg, rest} <- parse_expression(tokens) do
      parse_more_args([first_arg], rest)
    end
  end

  defp parse_more_args(args, [{:comma} | rest]) do
    with {:ok, arg, rest} <- parse_expression(rest) do
      parse_more_args(args ++ [arg], rest)
    end
  end
  defp parse_more_args(args, rest), do: {:ok, args, rest}

  # ============================================================
  # CASE/WHEN helpers
  # ============================================================

  # Parse one or more WHEN clauses
  defp parse_when_clauses(tokens) do
    case parse_when_clause(tokens) do
      {:ok, clause, rest} ->
        parse_more_when_clauses([clause], rest)
      {:error, _} = error ->
        error
    end
  end

  defp parse_more_when_clauses(clauses, [{:keyword, :when} | _] = tokens) do
    case parse_when_clause(tokens) do
      {:ok, clause, rest} ->
        parse_more_when_clauses(clauses ++ [clause], rest)
      {:error, _} = error ->
        error
    end
  end
  defp parse_more_when_clauses(clauses, rest), do: {:ok, clauses, rest}

  # Parse single: WHEN condition THEN result
  defp parse_when_clause([{:keyword, :when} | rest]) do
    with {:ok, condition, rest} <- parse_expression(rest),
         {:ok, rest} <- expect_keyword(:then, rest),
         {:ok, result, rest} <- parse_expression(rest) do
      {:ok, {:when, condition, result}, rest}
    end
  end
  defp parse_when_clause(_tokens), do: {:error, "Expected WHEN clause"}

  # Parse optional: ELSE result
  defp parse_else_clause([{:keyword, :else} | rest]) do
    parse_expression(rest)
  end
  defp parse_else_clause(rest), do: {:ok, nil, rest}

  # Helper to expect a specific keyword
  defp expect_keyword(expected, [{:keyword, actual} | rest]) when expected == actual do
    {:ok, rest}
  end
  defp expect_keyword(expected, tokens) do
    {:error, "Expected #{String.upcase(to_string(expected))}, got: #{inspect(Enum.take(tokens, 1))}"}
  end
end