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