<?php

declare(strict_types=1);

namespace Drupal\flowdrop\DTO;

/**
 * A precomputed edge dependency record, as persisted into job metadata.
 *
 * This is a *different domain* from the transport edge ({@see
 * \Drupal\flowdrop\DTO\JsApi\Edge}): the transport edge is the editor's
 * camelCase wire format, whereas an ExecutionEdge is the denormalised,
 * snake_case dependency record that {@see
 * \Drupal\flowdrop_pipeline\Service\JobGenerationService} computes once and
 * stores under a job's `incoming_edges`/`outgoing_edges` metadata. The
 * orchestrators read it back at runtime to gate job readiness and route data.
 *
 * This class is the SOLE owner of the snake_case job-metadata edge keys: it is
 * the only place they are read or written. Keeping the two edge domains as
 * distinct types is what lets the generic {@see
 * \Drupal\flowdrop\Utility\EdgePortClassifier} operate on typed getters
 * instead of duck-typing two array shapes by their key casing.
 *
 * @internal
 *   The snake_case job-metadata key set this class owns is deliberately
 *   changeable in one place; external code should not construct or persist it
 *   directly.
 */
final class ExecutionEdge {

  /**
   * Constructs an ExecutionEdge.
   *
   * @param string $source
   *   The source node ID.
   * @param string $target
   *   The target node ID.
   * @param string $sourceHandle
   *   The source handle ("{nodeId}-output-{portName}").
   * @param string $targetHandle
   *   The target handle ("{nodeId}-input-{portName}").
   * @param bool $isTrigger
   *   Whether the edge lands on the target's trigger input port.
   * @param bool $isLoopback
   *   Whether the edge lands on the target's loopback input port.
   * @param bool $isTool
   *   Whether the edge supplies a tool to its target.
   * @param string $branchName
   *   The gateway branch name this edge carries ('' when not a branch).
   * @param string $edgeId
   *   The originating edge ID.
   */
  public function __construct(
    private readonly string $source,
    private readonly string $target,
    private readonly string $sourceHandle,
    private readonly string $targetHandle,
    private readonly bool $isTrigger,
    private readonly bool $isLoopback,
    private readonly bool $isTool,
    private readonly string $branchName,
    private readonly string $edgeId,
  ) {}

  /**
   * Reads a snake_case job-metadata edge array into an ExecutionEdge.
   *
   * @param array<string, mixed> $edge
   *   The stored job-metadata edge record.
   *
   * @return self
   *   The typed execution edge.
   */
  public static function fromMetadata(array $edge): self {
    return new self(
      source: (string) ($edge['source'] ?? ''),
      target: (string) ($edge['target'] ?? ''),
      sourceHandle: (string) ($edge['source_handle'] ?? ''),
      targetHandle: (string) ($edge['target_handle'] ?? ''),
      isTrigger: (bool) ($edge['is_trigger'] ?? FALSE),
      isLoopback: (bool) ($edge['is_loopback'] ?? FALSE),
      isTool: (bool) ($edge['is_tool'] ?? FALSE),
      branchName: (string) ($edge['branch_name'] ?? ''),
      edgeId: (string) ($edge['edge_id'] ?? ''),
    );
  }

  /**
   * The source node ID.
   */
  public function getSource(): string {
    return $this->source;
  }

  /**
   * The target node ID.
   */
  public function getTarget(): string {
    return $this->target;
  }

  /**
   * The source handle ("{nodeId}-output-{portName}").
   */
  public function getSourceHandle(): string {
    return $this->sourceHandle;
  }

  /**
   * The target handle ("{nodeId}-input-{portName}").
   */
  public function getTargetHandle(): string {
    return $this->targetHandle;
  }

  /**
   * Whether the edge lands on the target's trigger input port.
   */
  public function isTrigger(): bool {
    return $this->isTrigger;
  }

  /**
   * Whether the edge lands on the target's loopback input port.
   */
  public function isLoopback(): bool {
    return $this->isLoopback;
  }

  /**
   * Whether the edge supplies a tool to its target.
   */
  public function isTool(): bool {
    return $this->isTool;
  }

  /**
   * The gateway branch name this edge carries ('' when not a branch).
   */
  public function getBranchName(): string {
    return $this->branchName;
  }

  /**
   * The originating edge ID.
   */
  public function getEdgeId(): string {
    return $this->edgeId;
  }

  /**
   * Serialises back to the snake_case job-metadata array for persistence.
   *
   * @return array<string, mixed>
   *   The job-metadata edge record.
   */
  public function toMetadata(): array {
    return [
      'source' => $this->source,
      'target' => $this->target,
      'source_handle' => $this->sourceHandle,
      'target_handle' => $this->targetHandle,
      'is_trigger' => $this->isTrigger,
      'is_loopback' => $this->isLoopback,
      'is_tool' => $this->isTool,
      'branch_name' => $this->branchName,
      'edge_id' => $this->edgeId,
    ];
  }

}
