<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow\Diagnostic;

use Drupal\flowdrop\Utility\NodeAnchor;
use Drupal\flowdrop_workflow\WorkflowDefinitionInterface;

/**
 * The read-only working set a diagnostic inspects.
 *
 * Wraps the stored workflow and the small derived indexes diagnostics share
 * (nodes-by-id, edges-by-id). Diagnostics read everything they need through
 * here and never touch storage — describing fixes as mutation descriptors that
 * a single {@see \Drupal\flowdrop_workflow\Service\WorkflowMutatorInterface}
 * applies.
 */
final class DiagnosticContext {

  /**
   * Memoised nodes indexed by id.
   *
   * @var array<string, array<string, mixed>>|null
   */
  private ?array $nodesById = NULL;

  /**
   * Memoised edges indexed by id.
   *
   * @var array<string, array<string, mixed>>|null
   */
  private ?array $edgesById = NULL;

  public function __construct(
    private readonly WorkflowDefinitionInterface $workflow,
  ) {}

  /**
   * The workflow under diagnosis.
   *
   * @return \Drupal\flowdrop_workflow\WorkflowDefinitionInterface
   *   The stored workflow entity.
   */
  public function workflow(): WorkflowDefinitionInterface {
    return $this->workflow;
  }

  /**
   * The workflow nodes.
   *
   * @return array<int, array<string, mixed>>
   *   The node list as stored.
   */
  public function nodes(): array {
    return $this->workflow->getNodes();
  }

  /**
   * The workflow edges.
   *
   * @return array<int, array<string, mixed>>
   *   The edge list as stored.
   */
  public function edges(): array {
    return $this->workflow->getEdges();
  }

  /**
   * Nodes indexed by their id (nodes without an id are skipped).
   *
   * @return array<string, array<string, mixed>>
   *   Nodes keyed by id.
   */
  public function nodesById(): array {
    if ($this->nodesById === NULL) {
      $this->nodesById = [];
      foreach ($this->nodes() as $node) {
        $id = is_array($node) ? (string) ($node['id'] ?? '') : '';
        if ($id !== '') {
          $this->nodesById[$id] = $node;
        }
      }
    }
    return $this->nodesById;
  }

  /**
   * Edges indexed by their id (edges without an id are skipped).
   *
   * @return array<string, array<string, mixed>>
   *   Edges keyed by id.
   */
  public function edgesById(): array {
    if ($this->edgesById === NULL) {
      $this->edgesById = [];
      foreach ($this->edges() as $edge) {
        $id = is_array($edge) ? (string) ($edge['id'] ?? '') : '';
        if ($id !== '') {
          $this->edgesById[$id] = $edge;
        }
      }
    }
    return $this->edgesById;
  }

  /**
   * Resolves the node-type anchor of a node by its id.
   *
   * @param string $nodeId
   *   The node id.
   *
   * @return string
   *   The node-type id, or '' when the node is unknown or anchor-less.
   */
  public function nodeTypeId(string $nodeId): string {
    $node = $this->nodesById()[$nodeId] ?? NULL;
    return is_array($node) ? NodeAnchor::read($node) : '';
  }

  /**
   * The user config of a node by its id.
   *
   * @param string $nodeId
   *   The node id.
   *
   * @return array<string, mixed>
   *   The `data.config` array, or [] when absent/non-array.
   */
  public function nodeConfig(string $nodeId): array {
    $node = $this->nodesById()[$nodeId] ?? NULL;
    $config = is_array($node) ? ($node['data']['config'] ?? []) : [];
    return is_array($config) ? $config : [];
  }

}
