# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 DBVisor

defmodule SQL.Token do
  @moduledoc false

  @doc """
  Returns a SQL iodata for a given token.
  """
  @doc since: "0.3.0"
  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      @compile {:inline, to_iodata: 4, __to_iodata__: 4, indention: 3, indention: 4}

      def to_iodata(token, %{format: format, case: case}), do: to_iodata(token, format, case, [])

      defp to_iodata(token, format, case, acc), do: __to_iodata__(token, format, case, acc)

      defp indention(acc, format, [{_,{_,_,_,_,l,c}}|_]) do
        indention(acc, format, l, c)
      end
      defp indention(acc, format, [{_,{_,_,_,_,_,_,l,c}}|_]) do
        indention(acc, format, l, c)
      end
      defp indention([<<_::binary>>|_]=acc, :dynamic, 0, 0), do: [?\s|acc]
      defp indention(acc, _format, 0, 0), do: acc
      defp indention(acc, _format, 0, 1), do: [?\s|acc]
      defp indention(acc, _format, 1, 0), do: [?\n|acc]
      defp indention(acc, format, line, 0=column), do: indention([?\n|acc], format, line-1, column)
      defp indention(acc, format, line, column), do: indention([?\s|acc], format, line, column-1)

      {reserved, non_reserved, operators} = SQL.BNF.get_rules()
      for atom <- Enum.uniq(Enum.map(reserved++non_reserved++operators,&elem(&1, 0))) do
        defp __to_iodata__(unquote(atom), _format, :lower, acc) do
          [unquote("#{atom}")|acc]
        end
        defp __to_iodata__(unquote(atom), _format, :upper, acc) do
          [unquote(String.upcase("#{atom}"))|acc]
        end
      end
      defp __to_iodata__(:comma, _format, _case, acc) do
        [?,|acc]
      end
      defp __to_iodata__(:dot, _format, _case, acc) do
        [?.|acc]
      end
      defp __to_iodata__({:paren, [{_, {_,_,_,_,l,c,_,_}}|_]=m, []}, format, case, acc) do
        indention([?(,?)|acc], format, l, c)
      end
      defp __to_iodata__({:colon, m, values}, format, case, acc) do
        to_iodata(values, format, case, indention([?;|acc], format, m))
      end
      defp __to_iodata__({:comma, m, values}, format, case, acc) do
        indention([?,|to_iodata(values, format, case, acc)], format, m)
      end
      defp __to_iodata__({:binding, m, _}, format, _case, acc) do
        indention([??|acc], format, m)
      end
      defp __to_iodata__({:comment, m, value}, format, _case, acc) do
        indention([?-,?-,value|acc], format, m)
      end
      defp __to_iodata__({:comments, m, value}, format, _case, acc) do
        indention([?\\,?*,value,?*,?\\|acc], format, m)
      end
      defp __to_iodata__({:quote, m, value}, format, _case, acc) do
        indention([?',value,?'|acc], format, m)
      end
      defp __to_iodata__({:backtick, m, value}, format, _case, acc) do
        indention([?`,value,?`|acc], format, m)
      end
      defp __to_iodata__({tag, m, []}, format, case, acc) do
        indention(to_iodata(tag, format, case, acc), format, m)
      end
      defp __to_iodata__({:paren, [{_, {_,_,_,_,l,c,_,_}}|_]=m, values}, format, case, acc) do
        indention([?(|to_iodata(values, format, case, indention([?)|acc], format, m))], format, l, c)
      end
      defp __to_iodata__({:bracket, [{_, {_,_,_,_,l,c,_,_}}|_]=m, values}, format, case, acc) do
        indention([?[|to_iodata(values, format, case, indention([?]|acc], format, m))], format, l,c)
      end
      defp __to_iodata__({:brace, [{_, {_,_,_,_,l,c,_,_}}|_]=m, values}, format, case, acc) do
        indention([?{|to_iodata(values, format, case, indention([?}|acc], format, m))], format, l,c)
      end
      defp __to_iodata__({_, [], values}, format, case, acc) do
        to_iodata(values, format, case, acc)
      end
      defp __to_iodata__({:ident, [_,_,{:tag, tag}|_]=m, [{:paren, _, _}]=values}, format, case, acc) do
        indention(to_iodata(tag, format, case, to_iodata(values, format, case, acc)), format, m)
      end
      defp __to_iodata__({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {ll,cc,_,_,_,_}}|_], _}]=values}, format, case, acc) when l >= ll and c >= cc do
        to_iodata(values, format, case, indention(to_iodata(tag, format, case, acc), format, m))
      end
      defp __to_iodata__({tag, [_,{_, type}|_]=m, [left, right]}, format, case, acc) when type == :operator or tag in ~w[between cursor for to union except intersect]a do
        to_iodata(left, format, case, indention(to_iodata(tag, format, case, to_iodata(right, format, case, acc)), format, m))
      end
      defp __to_iodata__({:ident, m, value}=node, format, :upper, acc) do
        indention([:string.uppercase(value)|acc], format, m)
      end
      defp __to_iodata__({tag, m, [{_,_,_}|_]=values}, format, case, acc) do
        indention(to_iodata(tag, format, case, to_iodata(values, format, case, acc)), format, m)
      end
      defp __to_iodata__({tag, m, value}=node, format, _case, acc) when tag in ~w[ident numeric special]a do
        indention([value|acc], format, m)
      end
      defp __to_iodata__({:double_quote, m, value}=node, format, _case, acc) do
        indention([?", value, ?"|acc], format, m)
      end
      defp __to_iodata__([token|tokens], format, case, acc) do
        to_iodata(token, format, case, to_iodata(tokens, format, case, acc))
      end
      defp __to_iodata__([], _format, _case, acc) do
        acc
      end

      defoverridable to_iodata: 4
    end
  end
end
