# Copyright(c) 2015-2024 ACCESS CO., LTD. All rights reserved.

use Croma

defmodule Antikythera.EnumUtil do
  @moduledoc """
  Utility functions to work with enumerables.
  """

  @type context :: any
  @type element :: Enum.element()

  @not_found_error_msg "no matching element found"

  @doc """
  Updates items of an enumerable with the given function, depending on context.
  Context can be `any`.

  The function `fun` takes item and context as arguments, and must achieve 2 purposes:

  1. Update an item according to current context
  2. Produces new context for next item

  then, return both as tuple `{new_item, new_context}`.
  """
  defun map_with_context(
          e :: Enum.t(),
          c :: context,
          fun :: (element, context -> {element, context})
        ) :: [element] do
    Enum.map_reduce(e, c, fun) |> elem(0)
  end

  @doc """
  A variant of `Enum.find/2` that raises an exception when no matching element is found.
  """
  defun find!(e :: Enum.t(), fun :: (element -> any)) :: element do
    Enum.find(e, fun) || raise @not_found_error_msg
  end

  @doc """
  A variant of `Enum.find_value/2` that raises an exception when no matching element is found.
  """
  defun find_value!(e :: Enum.t(), fun :: (element -> any)) :: any do
    Enum.find_value(e, fun) || raise @not_found_error_msg
  end
end
