using System.Text.Json.Nodes;
using w4c_workflows.Models.Credentials;
using w4c_workflows.Models.Nodes;
namespace w4c_workflows.Services.Nodes;
///
/// Runs one node type. Implementations are registered in the node catalog and
/// resolved by the executor registry; the run engine feeds them resolved
/// parameters and input items and routes the returned outputs by port index.
///
public interface INodeExecutor
{
/// Blueprint type this executor implements, e.g. "core.set".
string Type { get; }
Task RunAsync(NodeExecutionContext context, CancellationToken ct);
}
///
/// Everything a node needs for one invocation. Parameters are already resolved
/// (interpolation applied) for the target item; the raw scope stays available so
/// a node can re-evaluate a parameter per item if it needs to.
///
public sealed record NodeExecutionContext
{
public required NodeBlueprint Blueprint { get; init; }
/// Resolved parameters keyed by parameter name.
public required JsonObject Parameters { get; init; }
/// Input items per input port index.
public required IReadOnlyList> Inputs { get; init; }
/// Decrypted credentials keyed by credential alias.
public IReadOnlyDictionary Credentials { get; init; } =
new Dictionary();
/// Workflow-level environment variables.
public JsonObject Environment { get; init; } = new();
public string TenantId { get; init; } = string.Empty;
public string RunId { get; init; } = string.Empty;
public string TaskId { get; init; } = string.Empty;
public string? NodeName { get; init; }
/// Index of the item this invocation targets (0 for all-items mode).
public int ItemIndex { get; init; }
/// Run iteration index for loops (0 on the first pass).
public int RunIndex { get; init; }
/// Directory a code node resolves its entry file from.
public string? WorkingDirectory { get; init; }
///
/// True when a loop-back edge targets this node, i.e. it is the pivot of a
/// loop and should advance its own iteration state on each invocation.
///
public bool LoopBackInput { get; init; }
/// Run-scoped mutable state, shared by nodes so loop nodes can keep a cursor.
public IDictionary State { get; init; } = new Dictionary();
///
/// Runs another workflow in-process (for core.executeWorkflow). Null
/// when the host does not support sub-workflows.
///
public ISubWorkflowInvoker? SubWorkflows { get; init; }
/// Nesting depth of this run: 0 top-level, +1 per sub-workflow call.
public int Depth { get; init; }
public IServiceProvider? Services { get; init; }
/// Items from the given input port (empty when the port is absent).
public IReadOnlyList Input(int portIndex = 0)
=> portIndex >= 0 && portIndex < Inputs.Count ? Inputs[portIndex] : Array.Empty();
}
///
/// Result of one node invocation. is indexed by output
/// port; a missing port means "no items on that port".
///
public sealed record NodeExecutionOutcome
{
public required IReadOnlyList> Outputs { get; init; }
/// Set when the node failed but the engine may route to the error port.
public NodeFailure? Failure { get; init; }
///
/// Set by a loop node on the invocation that emits its final ("done") output,
/// so the runner stops re-triggering it via the loop-back edge.
///
public bool LoopComplete { get; init; }
public bool Succeeded => Failure == null;
/// Single-output convenience wrapper.
public static NodeExecutionOutcome Single(IReadOnlyList items)
=> new() { Outputs = new[] { items } };
/// No items on any port (used by control nodes that consume input).
public static readonly NodeExecutionOutcome Empty = new()
{
Outputs = Array.Empty>(),
};
/// Failure on the error port.
public static NodeExecutionOutcome Failed(string message, string? code = null, string? description = null)
=> new()
{
Outputs = Array.Empty>(),
Failure = new NodeFailure(message, code, description),
};
}
/// A node-level failure, optionally mapped to a user-facing message.
public sealed record NodeFailure(
string Message,
string? Code = null,
string? Description = null,
int? HttpStatus = null);