defmodule Towerops.EctoTypes.JsonAny do
  @moduledoc """
  Custom Ecto type for JSONB columns that can store any JSON-encodable value.

  Unlike the built-in :map type which only accepts maps, this type accepts:
  - strings
  - numbers (integer, float)
  - booleans
  - nil
  - lists
  - maps

  All values are stored as JSONB in the database.
  """
  use Ecto.Type

  @type json_value :: String.t() | number() | boolean() | nil | list() | map()

  @impl Ecto.Type
  def type, do: :jsonb

  @impl Ecto.Type
  @spec cast(term()) :: {:ok, json_value()} | :error
  def cast(nil), do: {:ok, nil}
  def cast(value) when is_binary(value), do: {:ok, value}
  def cast(value) when is_number(value), do: {:ok, value}
  def cast(value) when is_boolean(value), do: {:ok, value}
  def cast(value) when is_list(value), do: {:ok, value}
  def cast(value) when is_map(value), do: {:ok, value}
  def cast(_), do: :error

  @impl Ecto.Type
  @spec load(term()) :: {:ok, json_value()} | :error
  def load(value), do: {:ok, value}

  @impl Ecto.Type
  @spec dump(term()) :: {:ok, json_value()} | :error
  def dump(nil), do: {:ok, nil}
  def dump(value) when is_binary(value), do: {:ok, value}
  def dump(value) when is_number(value), do: {:ok, value}
  def dump(value) when is_boolean(value), do: {:ok, value}
  def dump(value) when is_list(value), do: {:ok, value}
  def dump(value) when is_map(value), do: {:ok, value}
  def dump(_), do: :error
end
