defmodule Raxol.Core.Utils.Validation do
  @moduledoc """
  Common validation utilities to reduce code duplication across the codebase.
  Provides standardized validation functions for dimensions, configs, and common patterns.
  """

  @doc """
  Validates that a dimension is a positive integer, returning default if invalid.
  """
  @spec validate_dimension(integer(), non_neg_integer()) :: non_neg_integer()
  def validate_dimension(dimension, _default)
      when is_integer(dimension) and dimension > 0 do
    dimension
  end

  def validate_dimension(_, default), do: default

  @doc """
  Validates that coordinates are valid non-negative integers.
  """
  @spec validate_coordinates(integer(), integer()) ::
          {:ok, {non_neg_integer(), non_neg_integer()}}
          | {:error, :invalid_coordinates}
  def validate_coordinates(x, y)
      when is_integer(x) and x >= 0 and is_integer(y) and y >= 0 do
    {:ok, {x, y}}
  end

  def validate_coordinates(_, _), do: {:error, :invalid_coordinates}

  @doc """
  Validates a configuration map against required keys.
  """
  @spec validate_config(map(), list(atom())) ::
          {:ok, map()} | {:error, {:missing_keys, list(atom())}}
  def validate_config(config, required_keys) when is_map(config) do
    missing_keys = Enum.reject(required_keys, &Map.has_key?(config, &1))

    case missing_keys do
      [] -> {:ok, config}
      keys -> {:error, {:missing_keys, keys}}
    end
  end

  def validate_config(_, _), do: {:error, :invalid_config}

  @doc """
  Validates that a value is within specified bounds.
  """
  @spec validate_bounds(number(), number(), number()) ::
          {:ok, number()} | {:error, :out_of_bounds}
  def validate_bounds(value, min, max)
      when is_number(value) and value >= min and value <= max do
    {:ok, value}
  end

  def validate_bounds(_, _, _), do: {:error, :out_of_bounds}

  @doc """
  Validates that a list contains only specific types.
  """
  @spec validate_list_types(list(), atom()) ::
          {:ok, list()} | {:error, :invalid_types}
  def validate_list_types(list, type) when is_list(list) do
    valid =
      Enum.all?(list, fn item ->
        case type do
          :atom -> is_atom(item)
          :string -> is_binary(item)
          :integer -> is_integer(item)
          :number -> is_number(item)
          :map -> is_map(item)
          _ -> false
        end
      end)

    case valid do
      true -> {:ok, list}
      false -> {:error, :invalid_types}
    end
  end

  def validate_list_types(_, _), do: {:error, :invalid_types}

  @doc """
  Validates that a string is not empty and optionally matches a pattern.
  """
  @spec validate_string(binary(), Regex.t() | nil) ::
          {:ok, binary()} | {:error, :invalid_string}
  def validate_string(str, pattern \\ nil)

  def validate_string(str, nil) when is_binary(str) and byte_size(str) > 0 do
    {:ok, str}
  end

  def validate_string(str, pattern)
      when is_binary(str) and byte_size(str) > 0 do
    case Regex.match?(pattern, str) do
      true -> {:ok, str}
      false -> {:error, :invalid_string}
    end
  end

  def validate_string(_, _), do: {:error, :invalid_string}

  @doc """
  Validates that a value is one of the allowed options.
  """
  @spec validate_enum(any(), list()) :: {:ok, any()} | {:error, :invalid_option}
  def validate_enum(value, allowed) when is_list(allowed) do
    case value in allowed do
      true -> {:ok, value}
      false -> {:error, :invalid_option}
    end
  end

  def validate_enum(_, _), do: {:error, :invalid_option}
end
