// Copyright (c) Microsoft. All rights reserved.
// How WorkflowGraphInfo maps to DurableEdgeMap at runtime.
// For a workflow like below:
//
// [A] ──► [B] ──► [C] ──► [E]
// │ ▲
// └──► [D] ──────┘
// (condition: x => x.NeedsReview)
//
// WorkflowGraphInfo DurableEdgeMap
// ┌──────────────────────────┐ ┌──────────────────────────────────────┐
// │ Successors: │ │ _routersBySource: │
// │ A → [B] │──constructs──►│ A → [DirectRouter(A→B)] │
// │ B → [C, D] │ │ B → [FanOutRouter([C, D])] │
// │ C → [E] │ │ C → [DirectRouter(C→E)] │
// │ D → [E] │ │ D → [DirectRouter(D→E)] │
// └──────────────────────────┘ │ │
// ┌──────────────────────────┐ │ _predecessorCounts: │
// │ Predecessors: │ │ A → 0 │
// │ E → [C, D] (fan-in!) │──constructs──►│ B → 1, C → 1, D → 1 │
// └──────────────────────────┘ │ E → 2 ◄── IsFanInExecutor = true │
// └──────────────────────────────────────┘
//
// Usage during superstep execution (continuing the example):
//
// 1. EnqueueInitialInput(msg) ──► MessageQueues["A"].Enqueue(envelope)
//
// 2. After B completes, RouteMessage("B", resultB) ──► _routersBySource["B"]
// │
// ▼
// FanOutRouter (B has 2 successors)
// ├─► DirectRouter(B→C) ──► no condition ──► enqueue to C
// └─► DirectRouter(B→D) ──► evaluate x => x.NeedsReview ──► enqueue to D (or skip)
//
// 3. Before superstep 4, IsFanInExecutor("E") returns true (count=2)
// → CollectExecutorInputs aggregates C and D results into ["resultC","resultD"]
using Microsoft.Extensions.Logging;
namespace Microsoft.Agents.AI.DurableTask.Workflows.EdgeRouters;
///
/// Manages message routing through workflow edges for durable orchestrations.
///
///
///
/// This is the durable equivalent of EdgeMap in the in-process runner.
/// It is constructed from (produced by )
/// and converts the static graph structure into an active routing layer used during superstep execution.
///
///
/// What it stores:
///
///
/// _routersBySource — For each source executor, a list of instances
/// that know how to deliver messages to successor executors. When a source has multiple successors, a single
/// wraps the individual instances.
/// _predecessorCounts — The number of predecessors for each executor, used to detect
/// fan-in points where multiple incoming messages should be aggregated before execution.
/// _startExecutorId — The entry-point executor that receives the initial workflow input.
///
///
/// How it is used during execution:
///
///
/// seeds the start executor's queue before the first superstep.
/// After each superstep, DurableWorkflowRunner.RouteOutputToSuccessors calls
/// which looks up the routers for the completed executor and forwards the
/// result to successor queues. Each router may evaluate an edge condition before enqueueing.
/// is checked during input collection to decide whether
/// to aggregate multiple queued messages into a single JSON array before dispatching.
///
///
internal sealed class DurableEdgeMap
{
private readonly Dictionary> _routersBySource = [];
private readonly Dictionary _predecessorCounts = [];
private readonly string _startExecutorId;
///
/// Initializes a new instance of from workflow graph info.
///
/// The workflow graph information containing routing structure.
internal DurableEdgeMap(WorkflowGraphInfo graphInfo)
{
ArgumentNullException.ThrowIfNull(graphInfo);
this._startExecutorId = graphInfo.StartExecutorId;
// Build edge routers for each source executor
foreach (KeyValuePair> entry in graphInfo.Successors)
{
string sourceId = entry.Key;
List successorIds = entry.Value;
if (successorIds.Count == 0)
{
continue;
}
graphInfo.ExecutorOutputTypes.TryGetValue(sourceId, out Type? sourceOutputType);
List routers = [];
foreach (string sinkId in successorIds)
{
graphInfo.EdgeConditions.TryGetValue((sourceId, sinkId), out Func