///|
/// A Builder for constructing finite state machines declaratively.
pub(all) struct Builder[S, E, Ctx] {
  config : EngineConfig[S, E, Ctx]
}

///|
/// Creates a new FSM Builder.
pub fn[S : Hash + Eq, E, Ctx] Builder::new() -> Builder[S, E, Ctx] {
  { config: { transitions: [], on_enter: Map([]), on_exit: Map([]) } }
}

///|
/// Adds a transition from one state to another triggered by an event.
pub fn[S, E, Ctx] Builder::transition(
  self : Builder[S, E, Ctx],
  from : S,
  event : E,
  to : S,
) -> Builder[S, E, Ctx] {
  self.config.transitions.push({
    from,
    to,
    event,
    guard_cond: None,
    action: None,
  })
  self
}

///|
/// Adds a transition with a guard condition.
pub fn[S, E, Ctx] Builder::transition_if(
  self : Builder[S, E, Ctx],
  from : S,
  event : E,
  to : S,
  guard_cond : Guard[S, E, Ctx],
) -> Builder[S, E, Ctx] {
  self.config.transitions.push({
    from,
    to,
    event,
    guard_cond: Some(guard_cond),
    action: None,
  })
  self
}

///|
/// Adds a transition with a context-updating action.
pub fn[S, E, Ctx] Builder::transition_do(
  self : Builder[S, E, Ctx],
  from : S,
  event : E,
  to : S,
  action : TransitionAction[S, E, Ctx],
) -> Builder[S, E, Ctx] {
  self.config.transitions.push({
    from,
    to,
    event,
    guard_cond: None,
    action: Some(action),
  })
  self
}

///|
/// Adds a guarded transition with a context-updating action.
pub fn[S, E, Ctx] Builder::transition_if_do(
  self : Builder[S, E, Ctx],
  from : S,
  event : E,
  to : S,
  guard_cond : Guard[S, E, Ctx],
  action : TransitionAction[S, E, Ctx],
) -> Builder[S, E, Ctx] {
  self.config.transitions.push({
    from,
    to,
    event,
    guard_cond: Some(guard_cond),
    action: Some(action),
  })
  self
}

///|
/// Registers a callback to be executed when entering a specific state.
pub fn[S : Hash + Eq, E, Ctx] Builder::on_enter(
  self : Builder[S, E, Ctx],
  state : S,
  callback : Callback[S, E, Ctx],
) -> Builder[S, E, Ctx] {
  self.config.on_enter.set(state, callback)
  self
}

///|
/// Registers a callback to be executed when exiting a specific state.
pub fn[S : Hash + Eq, E, Ctx] Builder::on_exit(
  self : Builder[S, E, Ctx],
  state : S,
  callback : Callback[S, E, Ctx],
) -> Builder[S, E, Ctx] {
  self.config.on_exit.set(state, callback)
  self
}

///|
fn[S : Hash + Eq, V] ensure_state_map(
  store : Map[S, V],
  key : S,
  default_value : V,
) -> V {
  match store.get(key) {
    Some(existing) => existing
    None => {
      store.set(key, default_value)
      default_value
    }
  }
}

///|
/// Builds the FSM runtime engine starting at the given state.
pub fn[S : Hash + Eq, E : Hash + Eq, Ctx] Builder::build(
  self : Builder[S, E, Ctx],
  initial_state : S,
  initial_context : Ctx,
) -> Engine[S, E, Ctx] {
  let transitions = Map([])
  let guards = Map([])
  let actions = Map([])
  let duplicate_transitions = duplicate_transition_entries(self)

  for t in self.config.transitions {
    let state_map = ensure_state_map(transitions, t.from, Map([]))
    if state_map.contains(t.event) {
      continue
    }

    state_map.set(t.event, t.to)

    match t.guard_cond {
      Some(guard_cond) => {
        let state_guards = ensure_state_map(guards, t.from, Map([]))
        state_guards.set(t.event, guard_cond)
      }
      None => ()
    }

    match t.action {
      Some(action) => {
        let state_actions = ensure_state_map(actions, t.from, Map([]))
        state_actions.set(t.event, action)
      }
      None => ()
    }
  }

  {
    transitions,
    guards,
    actions,
    on_enter: self.config.on_enter,
    on_exit: self.config.on_exit,
    history_entries: [],
    audit_entries: [],
    metrics_value: {
      attempted: 0,
      successful: 0,
      rejected: 0,
      guard_rejected: 0,
      unknown_event: 0,
      no_outgoing_state: 0,
      duplicate_configuration: 0,
      invalid_configuration: 0,
      lifecycle_hooks: 0,
    },
    current_state: initial_state,
    context: initial_context,
    last_error_value: None,
    build_error: if duplicate_transitions.length() > 0 {
      Some(DuplicateTransitionDefinition)
    } else {
      None
    },
  }
}