<?php

declare(strict_types=1);

namespace Drupal\flowdrop\DTO\JsApi;

/**
 * A workflow envelope exactly as it arrives from (and returns to) the editor.
 *
 * The transport shape is `{id, name, description, nodes, edges, metadata}`,
 * optionally wrapped in a `data` key (the editor sometimes posts the full API
 * response, sometimes just the inner data). This object absorbs that wrapper
 * variance and the `edges`/`connections` alias, exposing typed {@see Node} and
 * {@see Edge} collections. It is the sole reader of the camelCase workflow
 * envelope keys; the domain
 * {@see \Drupal\flowdrop_workflow\DTO\WorkflowDTO} built from it owns
 * normalisation (name fallbacks, edge indexing, repair-on-read).
 *
 * @internal
 *   Boundary normalisation, not a published contract. External code should
 *   consume the domain WorkflowDTO, not bind to this transport shape.
 */
final class Workflow {

  /**
   * Constructs a Workflow.
   *
   * @param string $id
   *   The workflow ID.
   * @param string|null $name
   *   The workflow name, or NULL when absent.
   * @param string|null $label
   *   The workflow label, or NULL when absent.
   * @param string $description
   *   The workflow description.
   * @param array<int, \Drupal\flowdrop\DTO\JsApi\Node> $nodes
   *   The transport nodes.
   * @param array<int, \Drupal\flowdrop\DTO\JsApi\Edge> $edges
   *   The transport edges.
   * @param array<string, mixed> $metadata
   *   The workflow metadata.
   * @param array<string, mixed> $rawData
   *   The original raw workflow array (envelope included).
   */
  public function __construct(
    private readonly string $id,
    private readonly ?string $name,
    private readonly ?string $label,
    private readonly string $description,
    private readonly array $nodes,
    private readonly array $edges,
    private readonly array $metadata,
    private readonly array $rawData,
  ) {}

  /**
   * Reads a camelCase transport workflow array into a Workflow.
   *
   * @param array<string, mixed> $workflow
   *   The raw workflow data. May be the full API response or just the inner
   *   data block.
   *
   * @return self
   *   The typed transport workflow.
   */
  public static function fromArray(array $workflow): self {
    // The editor posts either the full API response or just its data block.
    $data = (isset($workflow['data']) && is_array($workflow['data'])) ? $workflow['data'] : $workflow;

    $rawNodes = is_array($data['nodes'] ?? NULL) ? $data['nodes'] : [];
    $rawEdges = is_array($data['edges'] ?? NULL) ? $data['edges'] : (is_array($data['connections'] ?? NULL) ? $data['connections'] : []);

    $nodes = [];
    foreach ($rawNodes as $rawNode) {
      if (is_array($rawNode)) {
        $nodes[] = Node::fromArray($rawNode);
      }
    }

    $edges = [];
    foreach ($rawEdges as $rawEdge) {
      if (is_array($rawEdge)) {
        $edges[] = Edge::fromArray($rawEdge);
      }
    }

    return new self(
      id: (string) ($data['id'] ?? ''),
      name: isset($data['name']) ? (string) $data['name'] : NULL,
      label: isset($data['label']) ? (string) $data['label'] : NULL,
      description: (string) ($data['description'] ?? ''),
      nodes: $nodes,
      edges: $edges,
      metadata: is_array($data['metadata'] ?? NULL) ? $data['metadata'] : [],
      rawData: $workflow,
    );
  }

  /**
   * The workflow ID.
   */
  public function getId(): string {
    return $this->id;
  }

  /**
   * The workflow name, or NULL when absent.
   */
  public function getName(): ?string {
    return $this->name;
  }

  /**
   * The workflow label, or NULL when absent.
   */
  public function getLabel(): ?string {
    return $this->label;
  }

  /**
   * The workflow description.
   */
  public function getDescription(): string {
    return $this->description;
  }

  /**
   * The transport nodes.
   *
   * @return array<int, \Drupal\flowdrop\DTO\JsApi\Node>
   *   The nodes.
   */
  public function getNodes(): array {
    return $this->nodes;
  }

  /**
   * The transport edges.
   *
   * @return array<int, \Drupal\flowdrop\DTO\JsApi\Edge>
   *   The edges.
   */
  public function getEdges(): array {
    return $this->edges;
  }

  /**
   * The workflow metadata.
   *
   * @return array<string, mixed>
   *   The metadata.
   */
  public function getMetadata(): array {
    return $this->metadata;
  }

  /**
   * The original raw workflow array (envelope included).
   *
   * @return array<string, mixed>
   *   The raw data.
   */
  public function getRawData(): array {
    return $this->rawData;
  }

}
