<?php

declare(strict_types=1);

namespace Drupal\flowdrop\Constants;

/**
 * Constants for reserved names in the FlowDrop system.
 *
 * These names are reserved for system functionality and cannot be used
 * by plugins for custom parameters or outputs. They are organized into
 * two groups:
 *
 * - Config keys: Reserved properties injected into configSchema by the
 *   API layer (NodesController). Plugins must not use these as regular
 *   parameter names unless implementing the corresponding feature.
 *
 * - Port names: Reserved port identifiers used by the orchestrator and
 *   the workflow editor for system-level ports.
 *
 * @see docs/development/reserved-names.md
 *
 * @api
 */
final class ReservedName {

  // --- Reserved config keys ---
  // These are intercepted by NodesController::deriveSchemas() and routed
  // to configSchema regardless of entity configuration.

  /**
   * Dynamic input port definitions config key.
   */
  public const DYNAMIC_INPUTS = 'dynamicInputs';

  /**
   * Dynamic output port definitions config key.
   */
  public const DYNAMIC_OUTPUTS = 'dynamicOutputs';

  /**
   * Gateway branch definitions config key.
   */
  public const BRANCHES = 'branches';

  /**
   * Per-instance title override config key.
   */
  public const INSTANCE_TITLE = 'instanceTitle';

  /**
   * Per-instance description override config key.
   */
  public const INSTANCE_DESCRIPTION = 'instanceDescription';

  /**
   * Per-instance visual type selection config key.
   */
  public const NODE_TYPE = 'nodeType';

  /**
   * Per-instance retry budget config key.
   *
   * An injected reserved config property (like {@see INSTANCE_TITLE}) present
   * on every executable node: how many times the engine retries the node on a
   * retryable failure before giving up. Defaults to 0 (no retry). Read at job
   * generation into the job's `max_retries` field; only consumed when the
   * node processor throws a
   * {@see \Drupal\flowdrop\Exception\RetryableExceptionInterface}.
   */
  public const MAX_RETRIES = 'maxRetries';

  /**
   * Per-instance port order + exposure config key.
   *
   * An injected reserved config property (like {@see MAX_RETRIES}) present on
   * every executable node: a per-direction ordered list of the node's ports,
   * shaped `{inputs: [{id, exposed?}], outputs: [{id, exposed?}]}`. Array index
   * encodes display order relative to the metadata default; the optional
   * `exposed` flag overrides the port's metadata `exposedByDefault` and is
   * stored only when it diverges. A node the author never touched stores no
   * `ports` key at all (pure metadata default order + exposure).
   *
   * Order is cosmetic (the engine never reads it); exposure is semantic in v2 —
   * a not-exposed port is hidden on the canvas, not wireable, and not
   * runtime-overridable — so the engine reads exposure from `data.config`, not
   * from an editor-only `extensions.ui` blob.
   */
  public const PORTS = 'ports';

  // --- Reserved port names ---
  // These are used by the orchestrator and workflow editor.

  /**
   * Unified input port (accepts all connectable params as JSON).
   */
  public const PORT_INPUT = 'input';

  /**
   * Unified output port (provides all exposed outputs as JSON).
   */
  public const PORT_OUTPUT = 'output';

  /**
   * Trigger port for flow control signals.
   */
  public const PORT_TRIGGER = 'trigger';

  /**
   * Tool input port a tool-consuming node exposes to collect wired tools.
   *
   * A node that declares an input port named `tool` (data type `tool`, marked
   * connectable) receives the tools wired into it. The runtime classifies the
   * incoming edge by this port's handle ("{nodeId}-input-tool"), the same way
   * it classifies trigger/loopback ports.
   *
   * @see docs/development/tool-aware-node-processors.md
   */
  public const PORT_TOOL = 'tool';

  /**
   * Loopback input port that re-enters a node within an iteration/agent loop.
   *
   * The value is `loop_back` (not `loopback`) to match the runtime: ForEach's
   * loopback port, StateGraphOrchestrator's loop detection, and the editor all
   * use `loop_back`. The edge classifier must agree with them.
   */
  public const PORT_LOOPBACK = 'loop_back';

  /**
   * Reserved output port a node emits on when its execution fails.
   *
   * An edge drawn from this port ("{nodeId}-output-error") defines the node's
   * error path: when the node fails terminally (after any retries) the engine
   * routes the error payload down it instead of failing the run. A node with
   * no error edge keeps the default behaviour — the failure fails the run.
   */
  public const PORT_ERROR = 'error';

  /**
   * Gateway output indicating which branches are active.
   */
  public const ACTIVE_BRANCHES = 'active_branches';

  /**
   * Parameter names that bypass entity config and route to configSchema.
   *
   * Used by NodesController::deriveSchemas() to detect parameters that
   * should always appear in the config form, never as input ports,
   * regardless of entity-level connectable/configurable flags.
   *
   * @return array<int, string>
   *   Parameter names that are always routed to configSchema.
   */
  public static function configSchemaOnlyParams(): array {
    return [
      self::DYNAMIC_INPUTS,
      self::DYNAMIC_OUTPUTS,
      self::BRANCHES,
    ];
  }

  /**
   * Names that are forbidden for use as dynamic port names.
   *
   * Used by DynamicPortTrait::validatePortNames() to reject user-defined
   * port names that would collide with system functionality.
   *
   * @return array<int, string>
   *   Names that cannot be used for dynamic ports.
   */
  public static function forbiddenPortNames(): array {
    return [
      self::PORT_INPUT,
      self::PORT_OUTPUT,
      self::PORT_TRIGGER,
      self::PORT_TOOL,
      self::PORT_LOOPBACK,
      self::PORT_ERROR,
      self::ACTIVE_BRANCHES,
      self::BRANCHES,
      self::NODE_TYPE,
      self::INSTANCE_TITLE,
      self::INSTANCE_DESCRIPTION,
      self::MAX_RETRIES,
      self::PORTS,
      self::DYNAMIC_INPUTS,
      self::DYNAMIC_OUTPUTS,
    ];
  }

}
