defmodule Kalevala.World.Item do
  @moduledoc """
  Item struct

  Common data that all items will have
  """

  alias Kalevala.Verb

  defstruct [:id, :name, :description, :callback_module, meta: %{}, verbs: []]

  defimpl Jason.Encoder do
    alias Kalevala.Meta

    def encode(item, opts) do
      json = Map.take(item, [:id, :name, :description])
      json = Map.merge(json, %{meta: Meta.trim(item.meta)})
      Jason.Encode.map(json, opts)
    end
  end

  @type t() :: %__MODULE__{}

  @doc """
  Match a string against the item
  """
  @callback matches?(item :: t(), keyword :: String.t()) :: boolean()

  defmacro __using__(_opts) do
    quote do
      @behaviour unquote(__MODULE__)

      @impl true
      def matches?(item, keyword) do
        String.downcase(item.name) == String.downcase(keyword)
      end

      defoverridable matches?: 2
    end
  end

  @doc """
  Filter the item's verbs based on the current context
  """
  def context_verbs(item, context) do
    Enum.filter(item.verbs, fn action ->
      Verb.matches?(action, context)
    end)
  end
end

defmodule Kalevala.World.Item.ItemNotLoaded do
  @moduledoc """
  An empty struct for item instances to include by default

  To know that the item is _not_ loaded and you should load it
  """

  @derive Jason.Encoder
  defstruct []
end

defmodule Kalevala.World.Item.Instance do
  @moduledoc """
  Item instance struct

  A specific instance of an item in the world
  """

  alias Kalevala.World.Item.ItemNotLoaded

  @derive {Jason.Encoder, only: [:id, :item_id, :item, :created_at]}
  defstruct [:id, :item_id, :created_at, item: %ItemNotLoaded{}, meta: %{}]

  @type t() :: %__MODULE__{}

  @doc """
  Generate a random instance ID
  """
  def generate_id() do
    bytes =
      Enum.reduce(1..4, <<>>, fn _, bytes ->
        bytes <> <<Enum.random(0..255)>>
      end)

    Base.encode16(bytes, case: :lower)
  end
end
