defmodule Mobus.Stepwise.IR do
@moduledoc """
Normalization helpers for workflow specs compiled into IR.
Normalizes spec shapes to reduce downstream conditionals in the
stepwise engine and pipeline components.
"""
@type t :: map()
@doc """
Normalizes a raw workflow spec into the internal representation (IR).
Applies the following normalizations:
- Coerces `"stepwise"` string profile to `:stepwise` atom
- Extracts `:initial_state` from atom or string keys
- Ensures `:states` and `:transitions` are maps (converts keyword lists)
This reduces downstream conditionals in engine and pipeline components.
## Parameters
* `spec` — a raw workflow specification map
## Returns
* A normalized spec map (IR) with consistent atom keys.
## Examples
IR.normalize(%{"profile" => "stepwise", "initial_state" => :step_one, "states" => %{}})
#=> %{profile: :stepwise, initial_state: :step_one, states: %{}, transitions: %{}, ...}
"""
@spec normalize(map()) :: t()
def normalize(%{} = spec) do
spec
|> normalize_profile()
|> normalize_initial_state()
|> normalize_states()
|> normalize_transitions()
end
defp normalize_profile(spec) do
profile = Map.get(spec, :profile) || Map.get(spec, "profile")
profile =
case profile do
"stepwise" -> :stepwise
other -> other
end
Map.put(spec, :profile, profile)
end
defp normalize_initial_state(spec) do
initial = Map.get(spec, :initial_state) || Map.get(spec, "initial_state")
Map.put(spec, :initial_state, initial)
end
defp normalize_states(spec) do
states = Map.get(spec, :states) || Map.get(spec, "states") || %{}
Map.put(spec, :states, states)
end
defp normalize_transitions(spec) do
transitions = Map.get(spec, :transitions) || Map.get(spec, "transitions") || %{}
transitions =
case transitions do
%{} -> transitions
list when is_list(list) -> Map.new(list, fn {k, v} -> {k, v} end)
other -> other
end
Map.put(spec, :transitions, transitions)
end
end