<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\DTO\Compiler;

/**
 * Represents a compiled workflow with dependency and execution graphs.
 *
 * Contains two graphs:
 * - DependencyGraph: Complete picture of all node connections (including tools)
 * - ExecutionGraph: Only nodes that should be executed by the orchestrator.
 *
 * The orchestrator uses ExecutionGraph for execution order and DependencyGraph
 * for data resolution at runtime.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class CompiledWorkflow {

  /**
   * Constructs a new CompiledWorkflow.
   *
   * @param string $workflowId
   *   The workflow identifier.
   * @param \Drupal\flowdrop_runtime\DTO\Compiler\DependencyGraph $dependencyGraph
   *   The complete dependency graph (all connections).
   * @param \Drupal\flowdrop_runtime\DTO\Compiler\ExecutionGraph $executionGraph
   *   The execution graph (orchestrator flow).
   * @param array<string, NodeMapping> $nodeMappings
   *   Node mappings for processor configuration.
   * @param array<string, mixed> $metadata
   *   Compilation metadata.
   */
  public function __construct(
    private readonly string $workflowId,
    private readonly DependencyGraph $dependencyGraph,
    private readonly ExecutionGraph $executionGraph,
    private readonly array $nodeMappings,
    private readonly array $metadata,
  ) {}

  /**
   * Gets the workflow ID.
   *
   * @return string
   *   The workflow ID.
   */
  public function getWorkflowId(): string {
    return $this->workflowId;
  }

  /**
   * Gets the dependency graph.
   *
   * The dependency graph contains ALL connections including:
   * - Data edges
   * - Trigger edges
   * - Tool availability edges
   * - Loopback edges
   * - Agent result edges.
   *
   * Use this for:
   * - Tool discovery by agents
   * - Data resolution at runtime
   * - Understanding complete workflow structure
   *
   * @return \Drupal\flowdrop_runtime\DTO\Compiler\DependencyGraph
   *   The dependency graph.
   */
  public function getDependencyGraph(): DependencyGraph {
    return $this->dependencyGraph;
  }

  /**
   * Gets the execution graph.
   *
   * The execution graph contains only nodes that should be executed
   * by the orchestrator. Tool-only nodes, orphans, and nodes connected
   * only via special edges are excluded.
   *
   * Use this for:
   * - Determining execution order
   * - Identifying root/leaf nodes
   * - Finding nodes requiring special handling (agent, iterator)
   *
   * @return \Drupal\flowdrop_runtime\DTO\Compiler\ExecutionGraph
   *   The execution graph.
   */
  public function getExecutionGraph(): ExecutionGraph {
    return $this->executionGraph;
  }

  /**
   * Gets the node mappings.
   *
   * Node mappings contain processor configuration for each node.
   *
   * @return array<string, NodeMapping>
   *   Array of NodeMapping objects keyed by node ID.
   */
  public function getNodeMappings(): array {
    return $this->nodeMappings;
  }

  /**
   * Gets a specific node mapping.
   *
   * @param string $nodeId
   *   The node ID.
   *
   * @return \Drupal\flowdrop_runtime\DTO\Compiler\NodeMapping|null
   *   The node mapping or NULL if not found.
   */
  public function getNodeMapping(string $nodeId): ?NodeMapping {
    return $this->nodeMappings[$nodeId] ?? NULL;
  }

  /**
   * Gets the compilation metadata.
   *
   * @return array<string, mixed>
   *   The 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 a node exists in the workflow.
   *
   * @param string $nodeId
   *   The node ID.
   *
   * @return bool
   *   TRUE if the node exists.
   */
  public function hasNode(string $nodeId): bool {
    return $this->dependencyGraph->hasNode($nodeId);
  }

  /**
   * Checks if a node is executable (in execution graph).
   *
   * @param string $nodeId
   *   The node ID.
   *
   * @return bool
   *   TRUE if the node is in the execution graph.
   */
  public function isExecutable(string $nodeId): bool {
    return $this->executionGraph->hasNode($nodeId);
  }

  /**
   * Checks if a node is excluded from execution.
   *
   * @param string $nodeId
   *   The node ID.
   *
   * @return bool
   *   TRUE if the node is excluded.
   */
  public function isExcluded(string $nodeId): bool {
    return $this->executionGraph->isExcluded($nodeId);
  }

  /**
   * Gets the total node count in the workflow.
   *
   * @return int
   *   The total number of nodes.
   */
  public function getTotalNodeCount(): int {
    return $this->dependencyGraph->getNodeCount();
  }

  /**
   * Gets the executable node count.
   *
   * @return int
   *   The number of nodes in the execution graph.
   */
  public function getExecutableNodeCount(): int {
    return $this->executionGraph->getNodeCount();
  }

  /**
   * Gets the excluded node count.
   *
   * @return int
   *   The number of excluded nodes.
   */
  public function getExcludedNodeCount(): int {
    return $this->executionGraph->getExcludedCount();
  }

  /**
   * Converts to array format for serialization.
   *
   * @return array<string, mixed>
   *   Array representation of the compiled workflow.
   */
  public function toArray(): array {
    $nodeMappingsArray = [];
    foreach ($this->nodeMappings as $nodeId => $mapping) {
      $nodeMappingsArray[$nodeId] = $mapping->toArray();
    }

    return [
      "workflowId" => $this->workflowId,
      "dependencyGraph" => $this->dependencyGraph->toArray(),
      "executionGraph" => $this->executionGraph->toArray(),
      "nodeMappings" => $nodeMappingsArray,
      "metadata" => $this->metadata,
    ];
  }

}
