# Finite State Machine

Deterministic state machine for bounded agent autonomy. Provides guard-based transitions, entry/exit actions, automatic transitions, event payloads, listeners, and visualization to Mermaid and Graphviz DOT.

## Quick Start

This example builds a simple review workflow with five states and guarded transitions. The machine starts in IDLE, moves through PROCESSING and REVIEW, and ends in either APPROVED or REJECTED based on a confidence score.

```java
State idle = State.initial("IDLE");
State processing = State.of("PROCESSING");
State review = State.of("REVIEW");
State approved = State.terminal("APPROVED");
State rejected = State.terminal("REJECTED");

StateMachine sm = StateMachine.builder("ReviewWorkflow")
    .states(idle, processing, review, approved, rejected)
    .transition(Transition.from(idle).to(processing).on("START"))
    .transition(Transition.from(processing).to(review).on("SUBMIT"))
    .transition(Transition.from(review).to(approved)
        .on("REVIEW_COMPLETE")
        .when(Guard.greaterThan("confidence", 0.9)))
    .transition(Transition.from(review).to(rejected)
        .on("REVIEW_COMPLETE")
        .when(Guard.lessThan("confidence", 0.5)))
    .maxTransitions(100)
    .build();

sm.start();
sm.fire(Event.of("START"));
sm.fire(Event.of("SUBMIT"));
sm.getContext().set("confidence", 0.95);
sm.fire(Event.of("REVIEW_COMPLETE"));

System.out.println(sm.getCurrentState().getName()); // "APPROVED"
System.out.println(sm.isInTerminalState());          // true
```

## State

States can be initial, terminal, or intermediate. Each supports entry/exit actions, timeouts, and metadata.

### Factory Methods

Use these to create states quickly. Every state machine needs exactly one initial state, and at least one terminal state where the machine stops.

| Method | Description |
|---|---|
| `State.initial(name)` | Entry point -- exactly one per machine |
| `State.of(name)` | Regular intermediate state |
| `State.terminal(name)` | End state -- machine completes here |

### Builder

For more control, the builder lets you attach entry/exit actions (code that runs when a state is entered or left), set timeouts, and store metadata on the state.

```java
State processing = State.builder("PROCESSING")
    .initial()                                         // Mark as initial
    .onEntry(ctx -> ctx.set("startTime", System.currentTimeMillis()))
    .onExit(ctx -> {
        long duration = System.currentTimeMillis() - (long) ctx.get("startTime");
        ctx.set("processingDuration", duration);
    })
    .timeout(30_000)                                   // 30 second timeout (ms)
    .metadata("retryable", true)
    .build();

processing.isInitial();        // true
processing.isTerminal();       // false
processing.hasTimeout();       // true
processing.getTimeoutMs();     // 30000
processing.getSpec("retryable"); // true
```

States are identified by name. Two states with the same name are considered equal.

## Transition

Transitions define movement between states, triggered by events with optional guards, actions, and priority.

```java
// Simple transition
Transition t1 = Transition.from(idle).to(processing).on("START").build();

// With guard and priority
Transition t2 = Transition.from(review).to(approved)
    .on("REVIEW_COMPLETE")
    .when(Guard.greaterThan("confidence", 0.9))
    .priority(10)
    .build();

// With transition action
Transition t3 = Transition.from(review).to(rejected)
    .on("REVIEW_COMPLETE")
    .when(Guard.lessThan("confidence", 0.5))
    .action((ctx, from, to) -> ctx.set("reason", "Low confidence"))
    .build();

// Automatic transition (no event required, fires when guard passes)
Transition auto = Transition.from(processing).to(review)
    .automatic()
    .when(Guard.equals("processed", true))
    .build();

// Internal (self-loop) transition
Transition internal = Transition.internal(processing)
    .on("RETRY")
    .action((ctx, from, to) -> {
        int count = ctx.getOrDefault("retries", 0);
        ctx.set("retries", count + 1);
    })
    .build();
```

When multiple transitions match the same event from the same state, the one with the highest `priority` wins.

## Event

Events are the signals that drive the state machine forward. When you fire an event, the machine looks for a matching transition from the current state. Events can carry key-value payload data that guards and actions can read.

```java
Event start = Event.of("START");

Event review = Event.of("REVIEW_COMPLETE")
    .withData("confidence", 0.95)
    .withData("reviewer", "agent-1");

double confidence = review.getData("confidence");    // 0.95
String reviewer = review.getDataOrDefault("reviewer", "unknown");
review.hasData("notes");                             // false

sm.fire(review);
```

## StateMachineContext

The context is a shared key-value store that guards, actions, and listeners can all read and write. Use it to pass data between states, track results, record errors, and review the full transition history.

```java
StateMachineContext ctx = new StateMachineContext(Map.of("userId", "u123"));

ctx.set("processed", true);
ctx.get("userId");                        // "u123"
ctx.getOrDefault("retries", 0);           // 0
ctx.has("processed");                     // true
ctx.remove("processed");

ctx.setResult("Task completed");
String result = ctx.getResult();
ctx.setError("Timeout exceeded");
ctx.hasError();                           // true

// Transition history
List<StateTransitionRecord> history = ctx.getHistory();
for (var record : history) {
    System.out.printf("%s -> %s via %s%n",
        record.fromState(), record.toState(), record.event());
}

// Start machine with pre-populated context
sm.start(new StateMachineContext(Map.of("threshold", 0.8)));
```

## Guard

Guards are conditions that must be true for a transition to fire. They check the context (shared state) and optionally the event payload. If a guard returns false, the transition is skipped even if the event matches. TnsAI provides a rich set of built-in guards plus composition operators so you can combine them.

### Built-in Guards

These factory methods cover the most common conditions. For anything more complex, use a lambda or compose built-in guards with `.and()`, `.or()`, and `.negate()`.

| Guard | Description |
|---|---|
| `Guard.always()` | Always true (default for transitions) |
| `Guard.never()` | Always false |
| `Guard.hasKey("k")` | True if key exists in context |
| `Guard.equals("k", val)` | True if context value equals expected |
| `Guard.greaterThan("k", 0.9)` | Numeric comparison (`>`) |
| `Guard.lessThan("k", 0.5)` | Numeric comparison (`<`) |
| `Guard.eventDataEquals("k", val)` | Check current event's payload |

### Custom and Composed Guards

You can write guards as lambdas or compose built-in guards using `.and()`, `.or()`, and `.negate()` for complex conditions.

```java
// Lambda guard
Guard isAuthenticated = ctx -> ctx.has("userId");

// Event data guard
Guard approvedStatus = ctx -> {
    Event event = ctx.getCurrentEvent();
    return "APPROVED".equals(event.getData("status"));
};

// AND composition
Guard safe = Guard.greaterThan("confidence", 0.8)
    .and(Guard.lessThan("risk", 0.3));

// OR composition
Guard acceptable = Guard.equals("role", "admin")
    .or(Guard.equals("role", "moderator"));

// Negation
Guard notBlocked = Guard.hasKey("blocked").negate();
```

## StateMachineListener

Listeners let you observe everything that happens in the state machine without modifying its behavior. Use them for logging, metrics, debugging, or triggering side effects when specific transitions occur.

```java
sm.addListener(new StateMachineListener() {
    @Override
    public void onStateEntered(StateMachine sm, State state, Event event) {
        System.out.println("Entered: " + state.getName());
    }

    @Override
    public void onTransition(StateMachine sm, State from, State to, Event event) {
        String trigger = event != null ? event.getName() : "auto";
        System.out.printf("%s -> %s [%s]%n", from.getName(), to.getName(), trigger);
    }

    @Override
    public void onNoTransition(StateMachine sm, State state, Event event) {
        System.out.println("Unhandled event: " + event.getName());
    }

    @Override
    public void onCompleted(StateMachine sm) {
        System.out.println("Completed in: " + sm.getCurrentState().getName());
    }
});

// Built-in logging listener (SLF4J)
sm.addListener(StateMachineListener.logging());
// Logs: [FSM:ReviewWorkflow] IDLE -> PROCESSING [START]
```

## StateMachineVisualizer

Export your state machine as a diagram for documentation, debugging, or sharing. Three output formats are supported: Mermaid (renders in GitHub, Notion, etc.), Graphviz DOT (for offline rendering), and plain text.

### Mermaid

Generates a Mermaid state diagram that renders natively on GitHub, GitLab, and many documentation tools.

```java
String mermaid = StateMachineVisualizer.toMermaid(sm);
// stateDiagram-v2
//     %% ReviewWorkflow
//     [*] --> IDLE
//     IDLE --> PROCESSING : START
//     PROCESSING --> REVIEW : SUBMIT
//     REVIEW --> APPROVED : REVIEW_COMPLETE [guarded]
//     REVIEW --> REJECTED : REVIEW_COMPLETE [guarded]
//     APPROVED --> [*]
//     REJECTED --> [*]

// With options
String mermaid = StateMachineVisualizer.toMermaid(sm,
    MermaidOptions.defaults()
        .includeTitle(true)
        .includeStateDescriptions(true)
        .showGuards(true)
        .showAutoTransitions(true)
        .highlightCurrentState(true));
```

### Graphviz DOT

Generates DOT format for use with Graphviz or compatible rendering tools, with configurable layout direction, colors, and fonts.

```java
String dot = StateMachineVisualizer.toDot(sm);

String dot = StateMachineVisualizer.toDot(sm,
    DotOptions.defaults()
        .direction("LR")               // LR, TB, RL, BT
        .nodeShape("ellipse")
        .fontName("Helvetica")
        .currentStateColor("#90EE90")
        .showGuards(true));
```

### Plain Text

A simple human-readable summary of the machine's states, transitions, and current status -- useful for logging and console output.

```java
String text = StateMachineVisualizer.toText(sm);
// State Machine: ReviewWorkflow
// Status: COMPLETED
// Current State: APPROVED
// Transitions: 3
//
// States:
//   - IDLE [initial]
//   - PROCESSING
//   - REVIEW
//   - APPROVED [terminal]
//   - REJECTED [terminal]
//
// Transitions:
//   - IDLE -> PROCESSING on START
//   - PROCESSING -> REVIEW on SUBMIT
//   - REVIEW -> APPROVED on REVIEW_COMPLETE
//   - REVIEW -> REJECTED on REVIEW_COMPLETE
```

## Status Lifecycle

A state machine moves through these statuses during its lifetime. You can query the current status at any time to decide whether to fire more events or handle completion.

| Status | Meaning |
|---|---|
| `NOT_STARTED` | Created but `start()` not called |
| `RUNNING` | Active, accepting events |
| `COMPLETED` | Reached a terminal state |
| `FAILED` | Max transitions exceeded or error |
| `TIMEOUT` | State timeout triggered |

```java
sm.getStatus();          // Status.RUNNING
sm.isRunning();          // true
sm.isInTerminalState();  // false
sm.getTransitionCount(); // 2
sm.getAvailableEvents(); // ["SUBMIT"]
sm.reset();              // Back to NOT_STARTED
```

## Comprehensive Example

This example models a real-world order processing workflow with automatic transitions, validation guards, cancellation handling, and logging. It demonstrates how all the FSM building blocks fit together.

```java
// Order processing workflow
State pending = State.builder("PENDING").initial()
    .onEntry(ctx -> log.info("Order received")).build();
State validating = State.builder("VALIDATING")
    .timeout(10_000).build();
State payment = State.of("PAYMENT");
State fulfilled = State.terminal("FULFILLED");
State cancelled = State.terminal("CANCELLED");

StateMachine orderFSM = StateMachine.builder("OrderProcess")
    .states(pending, validating, payment, fulfilled, cancelled)
    .transition(Transition.from(pending).to(validating).automatic()
        .when(Guard.hasKey("orderId")))
    .transition(Transition.from(validating).to(payment)
        .on("VALIDATED").when(Guard.equals("valid", true)))
    .transition(Transition.from(validating).to(cancelled)
        .on("VALIDATED").when(Guard.equals("valid", false))
        .action((ctx, from, to) -> ctx.set("reason", "Validation failed")))
    .transition(Transition.from(payment).to(fulfilled)
        .on("PAYMENT_RESULT").when(Guard.equals("paid", true)))
    .transition(Transition.from(payment).to(cancelled)
        .on("PAYMENT_RESULT").when(Guard.equals("paid", false)))
    .transition(Transition.from(validating).to(cancelled).on("CANCEL"))
    .transition(Transition.from(payment).to(cancelled).on("CANCEL"))
    .maxTransitions(50)
    .build();

orderFSM.addListener(StateMachineListener.logging());

StateMachineContext ctx = new StateMachineContext(Map.of("orderId", "ORD-123"));
orderFSM.start(ctx);                          // PENDING -> VALIDATING (auto)
ctx.set("valid", true);
orderFSM.fire(Event.of("VALIDATED"));          // -> PAYMENT
ctx.set("paid", true);
orderFSM.fire(Event.of("PAYMENT_RESULT"));     // -> FULFILLED

System.out.println(StateMachineVisualizer.toMermaid(orderFSM));
```

## Thread Safety

`StateMachine` is not thread-safe by design to keep the implementation simple and fast. If your application fires events from multiple threads, you need to synchronize access externally.

```java
synchronized (sm) {
    sm.fire(event);
}
```
