defmodule Statifier.Validator.Utils do @moduledoc """ Utility functions shared across validator modules. Provides common operations like result manipulation, state collection, and document traversal helpers. """ @doc """ Add an error to the validation result. """ @spec add_error(Statifier.Validator.validation_result(), String.t()) :: Statifier.Validator.validation_result() def add_error(%Statifier.Validator{} = result, error) do %{result | errors: [error | result.errors]} end @doc """ Add a warning to the validation result. """ @spec add_warning(Statifier.Validator.validation_result(), String.t()) :: Statifier.Validator.validation_result() def add_warning(%Statifier.Validator{} = result, warning) do %{result | warnings: [warning | result.warnings]} end @doc """ Check if a state with the given ID exists in the document. """ @spec state_exists?(String.t(), Statifier.Document.t()) :: boolean() def state_exists?(state_id, %Statifier.Document{} = document) do collect_all_states(document) |> Enum.any?(&(&1.id == state_id)) end @doc """ Collect all states from a document, including nested states. """ @spec collect_all_states(Statifier.Document.t()) :: [Statifier.State.t()] def collect_all_states(%Statifier.Document{states: states}) do collect_states_recursive(states) end @doc """ Recursively collect states from a state list. """ @spec collect_states_recursive([Statifier.State.t()]) :: [Statifier.State.t()] def collect_states_recursive(states) do Enum.flat_map(states, fn state -> [state | collect_states_recursive(state.states)] end) end @doc """ Collect all transitions from a list of states. """ @spec collect_all_transitions([Statifier.State.t()]) :: [Statifier.Transition.t()] def collect_all_transitions(states) do Enum.flat_map(states, fn state -> state.transitions end) end @doc """ Get the initial state ID from a document. Returns the explicit initial state if set, otherwise the first state's ID, or nil if the document has no states. """ @spec get_initial_state(Statifier.Document.t()) :: String.t() | nil def get_initial_state(%Statifier.Document{initial: initial}) when is_binary(initial), do: initial def get_initial_state(%Statifier.Document{states: [first_state | _rest]}), do: first_state.id def get_initial_state(%Statifier.Document{states: []}), do: nil @doc """ Linear search for a state by ID during validation (before lookup maps are built). """ @spec find_state_by_id_linear(String.t(), Statifier.Document.t()) :: Statifier.State.t() | nil def find_state_by_id_linear(state_id, %Statifier.Document{} = document) do collect_all_states(document) |> Enum.find(&(&1.id == state_id)) end end