defmodule Mobus.Stepwise.History do @moduledoc """ Structured access to runtime history and trace data. The engine maintains two audit trails in the runtime: * `:history` — state transition log (`from`, `to`, `event`, `at`) * `:trace` — fine-grained execution trace (actions, steps, breakpoints) These helpers provide stable read access and controlled append without depending on internal list structure. """ @doc "Returns the full history list from the runtime." @spec events(map()) :: [map()] def events(runtime), do: Map.get(runtime, :history, []) @doc "Returns the full trace list from the runtime." @spec trace(map()) :: [map()] def trace(runtime), do: Map.get(runtime, :trace, []) @doc "Appends a custom event to the history." @spec append(map(), map()) :: map() def append(runtime, %{} = entry) do entry = Map.put_new(entry, :at, DateTime.utc_now()) Map.update(runtime, :history, [entry], &(&1 ++ [entry])) end @doc "Appends a custom entry to the trace." @spec append_trace(map(), map()) :: map() def append_trace(runtime, %{} = entry) do Map.update(runtime, :trace, [entry], &(&1 ++ [entry])) end @doc "Returns history entries filtered by event name." @spec events_by(map(), atom() | String.t()) :: [map()] def events_by(runtime, event_name) do runtime |> events() |> Enum.filter(fn entry -> Map.get(entry, :event) == event_name or to_string(Map.get(entry, :event)) == to_string(event_name) end) end @doc "Returns trace entries filtered by kind." @spec trace_by_kind(map(), atom() | String.t()) :: [map()] def trace_by_kind(runtime, kind) do runtime |> trace() |> Enum.filter(fn entry -> Map.get(entry, :kind) == kind or to_string(Map.get(entry, :kind)) == to_string(kind) end) end @doc "Returns the last history entry, or `nil`." @spec last_event(map()) :: map() | nil def last_event(runtime), do: runtime |> events() |> List.last() @doc "Returns the number of state transitions recorded." @spec transition_count(map()) :: non_neg_integer() def transition_count(runtime), do: runtime |> events() |> length() end