# Jason lives in main raxol; guarded with Code.ensure_loaded? in callers
defmodule Raxol.Core.Config do
  @compile {:no_warn_undefined, Jason}
  @moduledoc """
  Pure functional configuration management for Raxol.

  This module provides purely functional operations on configuration data.
  All functions take config as input and return new config as output - no processes,
  no side effects except where explicitly documented.

  ## Design

  Following Rich Hickey's principle: "State is not a service."

  Configuration is just data - maps with namespaced sections. There's no need
  to serialize read operations through a GenServer mailbox when the data is
  immutable between updates.

  ## Usage

      # Create default config
      config = Config.new()

      # Get/set values
      width = Config.get(config, :terminal, :width)
      config = Config.put(config, :terminal, :width, 120)

      # Merge configs (file load, runtime override)
      config = Config.merge(config, loaded_config)

      # Get entire namespace
      terminal_config = Config.get_namespace(config, :terminal)

  ## Runtime Storage

  For runtime access, use `Raxol.Core.Config.Store` which backs this data
  with ETS for fast concurrent reads. This module is the pure functional core.
  """

  @type namespace ::
          :terminal | :plugins | :performance | :security | :ui | :benchmark
  @type key :: atom()
  @type value :: any()
  @type t :: %{
          version: String.t(),
          terminal: map(),
          plugins: map(),
          performance: map(),
          security: map(),
          ui: map(),
          benchmark: map()
        }

  # ============================================================================
  # Constructors
  # ============================================================================

  @doc """
  Creates a new config with default values for all namespaces.

  ## Examples

      config = Config.new()
      config.terminal.width  # => 80
  """
  @spec new() :: t()
  def new do
    %{
      version: "1.5.4",
      terminal: default_terminal(),
      plugins: default_plugins(),
      performance: default_performance(),
      security: default_security(),
      ui: default_ui(),
      benchmark: default_benchmark()
    }
  end

  @doc """
  Creates a config from a map, filling in defaults for missing keys.
  """
  @spec from_map(map()) :: t()
  def from_map(map) when is_map(map) do
    new()
    |> merge(atomize_keys(map))
  end

  # ============================================================================
  # Accessors
  # ============================================================================

  @doc """
  Gets a value from config at namespace/key path.

  Returns default if namespace or key doesn't exist.

  ## Examples

      Config.get(config, :terminal, :width)
      Config.get(config, :terminal, :missing, 0)
  """
  @spec get(t(), namespace(), key(), value()) :: value()
  def get(config, namespace, key, default \\ nil) do
    config
    |> Map.get(namespace, %{})
    |> Map.get(key, default)
  end

  @doc """
  Gets an entire namespace from config.

  ## Examples

      terminal = Config.get_namespace(config, :terminal)
      # => %{width: 80, height: 24, ...}
  """
  @spec get_namespace(t(), namespace()) :: map()
  def get_namespace(config, namespace) do
    Map.get(config, namespace, %{})
  end

  # ============================================================================
  # Transformers
  # ============================================================================

  @doc """
  Puts a value into config at namespace/key path.

  Returns new config (original is unchanged).

  ## Examples

      config = Config.put(config, :terminal, :width, 120)
  """
  @spec put(t(), namespace(), key(), value()) :: t()
  def put(config, namespace, key, value) do
    namespace_config =
      config
      |> Map.get(namespace, %{})
      |> Map.put(key, value)

    Map.put(config, namespace, namespace_config)
  end

  @doc """
  Puts an entire namespace into config.

  ## Examples

      config = Config.put_namespace(config, :terminal, %{width: 120, height: 40})
  """
  @spec put_namespace(t(), namespace(), map()) :: t()
  def put_namespace(config, namespace, namespace_config)
      when is_map(namespace_config) do
    Map.put(config, namespace, namespace_config)
  end

  @doc """
  Resets a namespace to its default values.

  ## Examples

      config = Config.reset_namespace(config, :terminal)
  """
  @spec reset_namespace(t(), namespace()) :: t()
  def reset_namespace(config, namespace) do
    Map.put(config, namespace, default_for(namespace))
  end

  @doc """
  Deep merges override config into base config.

  Override values take precedence. Nested maps are merged recursively.

  ## Examples

      base = Config.new()
      override = %{terminal: %{width: 120}}
      config = Config.merge(base, override)
      # config.terminal.width => 120
      # config.terminal.height => 24 (preserved from base)
  """
  @spec merge(t(), map()) :: t()
  def merge(base, override) when is_map(base) and is_map(override) do
    Map.merge(base, override, fn
      _key, base_val, override_val
      when is_map(base_val) and is_map(override_val) ->
        Map.merge(base_val, override_val)

      _key, _base_val, override_val ->
        override_val
    end)
  end

  # ============================================================================
  # Validation
  # ============================================================================

  @doc """
  Validates config value for a given namespace and key.

  Returns `:ok` or `{:error, reason}`.

  ## Examples

      Config.validate_value(:terminal, :width, 80)   # => :ok
      Config.validate_value(:terminal, :width, -1)   # => {:error, "width must be positive"}
  """
  @spec validate_value(namespace(), key(), value()) ::
          :ok | {:error, String.t()}
  def validate_value(:terminal, :width, value)
      when is_integer(value) and value > 0,
      do: :ok

  def validate_value(:terminal, :width, _),
    do: {:error, "width must be a positive integer"}

  def validate_value(:terminal, :height, value)
      when is_integer(value) and value > 0,
      do: :ok

  def validate_value(:terminal, :height, _),
    do: {:error, "height must be a positive integer"}

  def validate_value(:performance, :memory_limit_mb, value)
      when is_integer(value) and value > 0,
      do: :ok

  def validate_value(:performance, :memory_limit_mb, _),
    do: {:error, "memory_limit_mb must be a positive integer"}

  def validate_value(_namespace, _key, _value), do: :ok

  @doc """
  Validates entire namespace config.

  Returns `:ok` or `{:error, [errors]}`.
  """
  @spec validate_namespace(namespace(), map()) :: :ok | {:error, [String.t()]}
  def validate_namespace(namespace, config) when is_map(config) do
    errors =
      config
      |> Enum.map(fn {key, value} -> validate_value(namespace, key, value) end)
      |> Enum.filter(&match?({:error, _}, &1))
      |> Enum.map(fn {:error, msg} -> msg end)

    case errors do
      [] -> :ok
      _ -> {:error, errors}
    end
  end

  # ============================================================================
  # Serialization
  # ============================================================================

  @doc """
  Encodes config to JSON string.

  ## Examples

      {:ok, json} = Config.to_json(config)
  """
  @spec to_json(t()) :: {:ok, String.t()} | {:error, any()}
  def to_json(config) do
    Jason.encode(config, pretty: true)
  end

  @doc """
  Decodes JSON string to config.

  ## Examples

      {:ok, config} = Config.from_json(json_string)
  """
  @spec from_json(String.t()) :: {:ok, t()} | {:error, any()}
  def from_json(json) when is_binary(json) do
    case Jason.decode(json) do
      {:ok, decoded} -> {:ok, from_map(decoded)}
      {:error, reason} -> {:error, reason}
    end
  end

  # ============================================================================
  # Defaults
  # ============================================================================

  @doc false
  def default_for(:terminal), do: default_terminal()
  def default_for(:plugins), do: default_plugins()
  def default_for(:performance), do: default_performance()
  def default_for(:security), do: default_security()
  def default_for(:ui), do: default_ui()
  def default_for(:benchmark), do: default_benchmark()
  def default_for(_), do: %{}

  defp default_terminal do
    %{
      width: 80,
      height: 24,
      colors: %{
        foreground: "#ffffff",
        background: "#000000",
        cursor: "#ffffff"
      },
      input: %{
        mouse_enabled: true,
        paste_mode: :bracketed
      },
      performance: %{
        render_fps: 60,
        buffer_size: 1000
      }
    }
  end

  defp default_plugins do
    %{
      enabled_plugins: [],
      plugin_configs: %{},
      auto_load: true,
      search_paths: ["~/.raxol/plugins", "./plugins"]
    }
  end

  defp default_performance do
    %{
      parser_cache_size: 1000,
      render_cache_enabled: true,
      memory_limit_mb: 100,
      gc_threshold: 0.8
    }
  end

  defp default_security do
    %{
      encryption_enabled: false,
      key_rotation_hours: 24,
      audit_logging: true,
      max_session_duration: 86_400
    }
  end

  defp default_ui do
    %{
      theme: "default",
      animations_enabled: true,
      transition_duration: 200,
      component_cache_size: 100
    }
  end

  defp default_benchmark do
    %{
      warmup_time: 1000,
      measurement_time: 5000,
      memory_time: 2000,
      parallel_jobs: 1
    }
  end

  # ============================================================================
  # Utilities
  # ============================================================================

  # Known config keys that are safe to atomize
  @known_keys ~w(
    version terminal plugins performance security ui benchmark
    width height colors foreground background cursor input
    mouse_enabled paste_mode render_fps buffer_size
    enabled_plugins plugin_configs auto_load search_paths
    parser_cache_size render_cache_enabled memory_limit_mb gc_threshold
    encryption_enabled key_rotation_hours audit_logging max_session_duration
    theme animations_enabled transition_duration component_cache_size
    warmup_time measurement_time memory_time parallel_jobs
  )a

  defp atomize_keys(map) when is_map(map) do
    Map.new(map, fn {key, value} ->
      atom_key = safe_to_atom(key)
      {atom_key, atomize_keys(value)}
    end)
  end

  defp atomize_keys(list) when is_list(list),
    do: Enum.map(list, &atomize_keys/1)

  defp atomize_keys(value), do: value

  defp safe_to_atom(key) when is_atom(key), do: key

  defp safe_to_atom(key) when is_binary(key) do
    atom = String.to_atom(key)

    if atom in @known_keys do
      atom
    else
      # Keep as string if not a known key (safety measure)
      key
    end
  end
end
