<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Event;

/**
 * Value object carrying playground-specific execution context from a pipeline.
 *
 * Extracted from the pipeline's execution context when a JobCompletedEvent is
 * dispatched. NULL from JobCompletedEvent::getPlaygroundContext() means the
 * pipeline is not running inside a playground session.
 *
 * @internal
 */
final class PlaygroundContext {

  /**
   * Constructs a new PlaygroundContext.
   *
   * @param string $sessionId
   *   The playground session ID the pipeline runs under.
   * @param array<int, array{id: string, label: string, icon?: string}> $hierarchy
   *   Ordered list of workflow → sub-workflow items the pipeline ran under.
   *   Display labels are pre-resolved at the point the workflow entity was
   *   loaded; downstream consumers never re-resolve.
   */
  public function __construct(
    private readonly string $sessionId,
    private readonly array $hierarchy = [],
  ) {}

  /**
   * Returns the playground session ID.
   */
  public function getSessionId(): string {
    return $this->sessionId;
  }

  /**
   * Returns the hierarchy trail attached to the running pipeline.
   *
   * @return array<int, array{id: string, label: string, icon?: string}>
   *   Empty when the pipeline did not stamp a hierarchy (older pipelines,
   *   non-playground runs, or top-level runs prior to seeding).
   */
  public function getHierarchy(): array {
    return $this->hierarchy;
  }

  /**
   * Creates a PlaygroundContext from a raw pipeline execution context array.
   *
   * @param array<string, mixed> $pipelineContext
   *   The value returned by
   *   FlowDropPipelineRuntimeInterface::getExecutionContext().
   *
   * @return static|null
   *   NULL when the context carries no playground session ID.
   */
  public static function fromPipelineContext(array $pipelineContext): ?static {
    $sessionId = $pipelineContext['playground']['session_id'] ?? NULL;
    if (!is_string($sessionId) || $sessionId === '') {
      return NULL;
    }
    $rawHierarchy = $pipelineContext['playground']['hierarchy'] ?? [];
    $hierarchy = is_array($rawHierarchy) ? self::sanitizeHierarchy($rawHierarchy) : [];
    return new static($sessionId, $hierarchy);
  }

  /**
   * Drops malformed entries; preserves only items with string id and label.
   *
   * @param array<mixed> $items
   *   Raw hierarchy items as decoded from the pipeline execution context.
   *
   * @return array<int, array{id: string, label: string, icon?: string}>
   *   Cleaned items in input order.
   */
  private static function sanitizeHierarchy(array $items): array {
    $cleaned = [];
    foreach ($items as $item) {
      if (!is_array($item)) {
        continue;
      }
      $id = $item['id'] ?? NULL;
      $label = $item['label'] ?? NULL;
      if (!is_string($id) || $id === '' || !is_string($label) || $label === '') {
        continue;
      }
      $entry = ['id' => $id, 'label' => $label];
      $icon = $item['icon'] ?? NULL;
      if (is_string($icon) && $icon !== '') {
        $entry['icon'] = $icon;
      }
      $cleaned[] = $entry;
    }
    return $cleaned;
  }

}
