<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\DTO\Compiler;

use Drupal\flowdrop\Utility\NodeAnchor;

/**
 * Represents a node in the dependency graph.
 *
 * Contains all metadata about a node including its type, configuration,
 * inputs, outputs, and additional metadata.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class DependencyNode {

  /**
   * Constructs a new DependencyNode.
   *
   * @param string $id
   *   The unique node identifier.
   * @param string $typeId
   *   The node type identifier (processor ID).
   * @param string $label
   *   The human-readable node label.
   * @param array<string, mixed> $config
   *   The node configuration.
   * @param array<string, mixed> $inputs
   *   The node input port definitions.
   * @param array<string, mixed> $outputs
   *   The node output port definitions.
   * @param array<string, mixed> $metadata
   *   Additional node metadata.
   */
  public function __construct(
    private readonly string $id,
    private readonly string $typeId,
    private readonly string $label,
    private readonly array $config = [],
    private readonly array $inputs = [],
    private readonly array $outputs = [],
    private readonly array $metadata = [],
  ) {}

  /**
   * Creates a DependencyNode from a WorkflowNodeDTO.
   *
   * @param object $node
   *   The workflow node DTO.
   *
   * @return self
   *   A new DependencyNode instance.
   */
  public static function fromWorkflowNode(object $node): self {
    $metadata = [];
    if (method_exists($node, "getMetadata")) {
      $metadata = $node->getMetadata();
    }

    $inputs = [];
    if (method_exists($node, "getInputs")) {
      $inputs = $node->getInputs();
    }

    $outputs = [];
    if (method_exists($node, "getOutputs")) {
      $outputs = $node->getOutputs();
    }

    $config = [];
    if (method_exists($node, "getConfig")) {
      $config = $node->getConfig();
    }

    $label = "";
    if (method_exists($node, "getLabel")) {
      $label = $node->getLabel();
    }

    return new self(
      id: $node->getId(),
      typeId: $node->getTypeId(),
      label: $label,
      config: $config,
      inputs: $inputs,
      outputs: $outputs,
      metadata: $metadata,
    );
  }

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

  /**
   * Gets the node type ID (processor ID).
   *
   * @return string
   *   The node type ID.
   */
  public function getTypeId(): string {
    return $this->typeId;
  }

  /**
   * Gets the flowdrop_node_type config-entity ID.
   *
   * Unlike {@see getTypeId()} — which returns the executor plugin ID, colon-
   * separated for derivative or module-prefixed plugins — this returns the
   * `flowdrop_node_type` config entity ID (underscore form) as carried in node
   * metadata. It is the ID expected when loading a node type from storage.
   * Falls back to the type ID for stock node types whose entity ID equals the
   * executor plugin ID.
   *
   * @return string
   *   The node type config-entity ID.
   */
  public function getNodeTypeId(): string {
    $id = NodeAnchor::readMetadata($this->metadata);
    return $id !== "" ? $id : $this->typeId;
  }

  /**
   * Gets the node label.
   *
   * @return string
   *   The node label.
   */
  public function getLabel(): string {
    return $this->label;
  }

  /**
   * Gets the node configuration.
   *
   * @return array<string, mixed>
   *   The node configuration.
   */
  public function getConfig(): array {
    return $this->config;
  }

  /**
   * Gets a specific configuration value.
   *
   * @param string $key
   *   The configuration key.
   * @param mixed $default
   *   The default value if key doesn't exist.
   *
   * @return mixed
   *   The configuration value.
   */
  public function getConfigValue(string $key, mixed $default = NULL): mixed {
    return $this->config[$key] ?? $default;
  }

  /**
   * Gets the node input port definitions.
   *
   * @return array<string, mixed>
   *   The input port definitions.
   */
  public function getInputs(): array {
    return $this->inputs;
  }

  /**
   * Gets the node output port definitions.
   *
   * @return array<string, mixed>
   *   The output port definitions.
   */
  public function getOutputs(): array {
    return $this->outputs;
  }

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

  /**
   * Gets a specific metadata value.
   *
   * @param string $key
   *   The metadata key.
   * @param mixed $default
   *   The default value if key doesn't exist.
   *
   * @return mixed
   *   The metadata value.
   */
  public function getMetadataValue(string $key, mixed $default = NULL): mixed {
    return $this->metadata[$key] ?? $default;
  }

  /**
   * Checks if this node is an agent type.
   *
   * @return bool
   *   TRUE if this is an agent node.
   */
  public function isAgent(): bool {
    return $this->typeId === "agent";
  }

  /**
   * Checks if this node is an iterator type.
   *
   * @return bool
   *   TRUE if this is an iterator node.
   */
  public function isIterator(): bool {
    return $this->typeId === "iterator";
  }

  /**
   * Gets the special type if this node requires special handling.
   *
   * @return string|null
   *   The special type ("agent", "iterator") or NULL for regular nodes.
   */
  public function getSpecialType(): ?string {
    if ($this->isAgent()) {
      return "agent";
    }
    if ($this->isIterator()) {
      return "iterator";
    }
    return NULL;
  }

  /**
   * Converts to array format.
   *
   * @return array<string, mixed>
   *   Array representation of the node.
   */
  public function toArray(): array {
    return [
      "id" => $this->id,
      "typeId" => $this->typeId,
      "label" => $this->label,
      "config" => $this->config,
      "inputs" => $this->inputs,
      "outputs" => $this->outputs,
      "metadata" => $this->metadata,
    ];
  }

}
