defmodule Mobus.Stepwise.Components.FsmBreakpoint do @moduledoc """ Breakpoint component for stepwise workflows. Records breakpoint hits for debugging and inspection purposes. > **Naming note**: Despite the `FsmBreakpoint` module name, this component > operates exclusively within stepwise pipelines. The name is historical > (predating the profile extraction) and is retained to avoid breaking > internal pipeline references. """ alias Mobus.Stepwise.SpecHelpers @doc """ Records breakpoint hits for debugging and inspection. This is an ALF pipeline stage. Checks the spec's `:breakpoints` list against the current event and state. Matching breakpoints are appended to `runtime.breakpoint_hits` with a timestamp, and a trace entry is recorded. Supports breakpoint types: - `on: :event` — matches when the breakpoint value equals the event name - `on: :state_enter` — matches when the breakpoint value equals the current state Skipped when the event has an error status. ## Parameters * `event` — pipeline event map with `:spec`, `:runtime`, `:event` * `opts` — ALF stage options (unused) ## Returns * Updated event map with breakpoint hits and trace entries in runtime. """ @spec call(map(), map()) :: map() def call(%{status: :error} = event, _opts), do: event def call(%{spec: spec, runtime: runtime, event: event_name} = event, _opts) do breakpoints = Map.get(spec, :breakpoints) || Map.get(spec, "breakpoints") || [] current = Map.get(runtime, :current_state) hits = Enum.filter(breakpoints, fn bp -> on = Map.get(bp, :on) || Map.get(bp, "on") value = Map.get(bp, :value) || Map.get(bp, "value") case on do :event -> value == event_name or to_string(value) == to_string(event_name) "event" -> to_string(value) == to_string(event_name) :state_enter -> SpecHelpers.equivalent_state?(value, current) "state_enter" -> SpecHelpers.equivalent_state?(value, current) _ -> false end end) runtime = if hits == [] do runtime else runtime |> Map.update(:breakpoint_hits, [], fn existing -> existing ++ Enum.map(hits, &Map.put(&1, :hit_at, DateTime.utc_now())) end) |> Map.update(:trace, [], fn trace -> trace ++ [%{kind: :breakpoints, hits: length(hits), event: event_name, state: current}] end) end Map.put(event, :runtime, runtime) end def call(event, _opts), do: event end