defmodule Raxol.Utils.MapUtils do
  @moduledoc """
  Common utility functions for map operations.

  This module consolidates frequently used map transformation functions
  to avoid code duplication across the codebase.
  """

  @doc """
  Recursively converts all map keys to strings.

  ## Examples

      iex> Raxol.Utils.MapUtils.stringify_keys(%{foo: "bar", nested: %{key: "value"}})
      %{"foo" => "bar", "nested" => %{"key" => "value"}}
      
      iex> Raxol.Utils.MapUtils.stringify_keys(%{:atom => [%{inner: "value"}]})
      %{"atom" => [%{"inner" => "value"}]}
  """
  @spec stringify_keys(any()) :: any()
  def stringify_keys(map) when is_map(map) do
    Enum.reduce(map, %{}, fn {key, value}, acc ->
      string_key = to_string(key)

      stringified_value =
        case value do
          v when is_map(v) -> stringify_keys(v)
          v when is_list(v) -> Enum.map(v, &stringify_keys/1)
          v -> v
        end

      Map.put(acc, string_key, stringified_value)
    end)
  end

  def stringify_keys(value), do: value

  @doc """
  Recursively converts all map keys to atoms.

  ## Examples

      iex> Raxol.Utils.MapUtils.atomize_keys(%{"foo" => "bar", "nested" => %{"key" => "value"}})
      %{foo: "bar", nested: %{key: "value"}}
      
      iex> Raxol.Utils.MapUtils.atomize_keys(%{"atom" => [%{"inner" => "value"}]})
      %{atom: [%{inner: "value"}]}
  """
  @spec atomize_keys(any()) :: any()
  def atomize_keys(map) when is_map(map) do
    Enum.reduce(map, %{}, fn {key, value}, acc ->
      atom_key =
        cond do
          is_atom(key) -> key
          is_binary(key) -> String.to_atom(key)
          true -> String.to_atom(to_string(key))
        end

      atomized_value =
        case value do
          v when is_map(v) -> atomize_keys(v)
          v when is_list(v) -> Enum.map(v, &atomize_keys/1)
          v -> v
        end

      Map.put(acc, atom_key, atomized_value)
    end)
  end

  def atomize_keys(value), do: value

  @doc """
  Safely atomizes keys, only converting strings that already exist as atoms.
  This prevents atom exhaustion attacks.

  ## Examples

      iex> Raxol.Utils.MapUtils.safe_atomize_keys(%{"foo" => "bar"})
      %{"foo" => "bar"}  # "foo" atom doesn't exist
      
      iex> _ = :existing_atom
      iex> Raxol.Utils.MapUtils.safe_atomize_keys(%{"existing_atom" => "value"})
      %{existing_atom: "value"}
  """
  @spec safe_atomize_keys(any()) :: any()
  def safe_atomize_keys(map) when is_map(map) do
    Enum.reduce(map, %{}, fn {key, value}, acc ->
      safe_key =
        cond do
          is_atom(key) -> key
          is_binary(key) -> String.to_existing_atom(key)
          true -> key
        end
        |> case do
          key when is_atom(key) -> key
          _ -> key
        end

      safe_value =
        case value do
          v when is_map(v) -> safe_atomize_keys(v)
          v when is_list(v) -> Enum.map(v, &safe_atomize_keys/1)
          v -> v
        end

      Map.put(acc, safe_key, safe_value)
    end)
  rescue
    ArgumentError -> map
  end

  def safe_atomize_keys(value), do: value
end
