<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow_executor\EventSubscriber;

use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Enum\InterruptType;
use Drupal\flowdrop_interrupt\Service\InterruptManagerInterface;
use Drupal\flowdrop_pipeline\Constants\PipelineStatus;
use Drupal\flowdrop_runtime\Event\PipelineCompletedEvent;
use Drupal\flowdrop_workflow_executor\Service\WorkflowExecutionServiceInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Resolves PipelineCompletion sentinels when their pipeline finishes.
 *
 * Phase 6 replacement for the retired WorkflowExecutionInterruptSubscriber.
 * The subscriber's job is narrow: when a pipeline completes, look up the
 * sentinel (if any) waiting on that pipeline's session and resolve it with
 * the run's outputs. The generic LinkedInterruptResolvedSubscriber then
 * cascades the resolution to any followers (the parent's
 * WorkflowExecution interrupt linked via `linked_interrupt_id`).
 *
 * The output-fetching logic is sub-workflow-specific, so this subscriber
 * lives in flowdrop_workflow_executor — but the parent-tracking logic
 * that used to live in the retired subscriber is now handled generically
 * by the cascade primitive in flowdrop_interrupt.
 *
 * @internal
 */
final class PipelineCompletionSubscriber implements EventSubscriberInterface {

  public function __construct(
    private readonly InterruptManagerInterface $interruptManager,
    private readonly WorkflowExecutionServiceInterface $executionService,
    private readonly LoggerChannelInterface $logger,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      PipelineCompletedEvent::NAME => 'onPipelineCompleted',
    ];
  }

  /**
   * Resolves the PipelineCompletion sentinel for the completed pipeline.
   */
  public function onPipelineCompleted(PipelineCompletedEvent $event): void {
    // Preferred match: the completed pipeline's id (carried in
    // executionId). Sync sub-pipelines record this in the sentinel.
    $sentinel = NULL;
    $subPipelineId = $event->getExecutionId();
    if ($subPipelineId !== '') {
      $sentinel = $this->findPendingSentinel($subPipelineId);
    }

    // Fallback for async dispatch: the sub-pipeline didn't exist when the
    // sentinel was created (queued for later worker), so the sentinel was
    // stamped with the sub-session id instead. Match by playground
    // session id so those runs still resolve.
    if ($sentinel === NULL) {
      $playground = $event->getPlaygroundContext();
      $subSessionId = $playground !== NULL ? $playground->getSessionId() : '';
      if ($subSessionId !== '') {
        $sentinel = $this->findPendingSentinel($subSessionId);
      }
    }

    if ($sentinel === NULL) {
      return;
    }

    // Extract outputs directly from the completed sub-pipeline. Using
    // getPipelineOutputs (instead of session-keyed getResults) is the
    // shared-session-safe path: when the sub-pipeline runs inside the
    // parent's session, the session status is still RUNNING (the parent
    // is mid-execution), so a session-status check would return no
    // outputs even though the sub's outputs are sitting on its pipeline
    // entity. The cascade resolver carries these outputs forward to the
    // parent's WorkflowExecution placeholder.
    $outputs = [];
    if ($event->getStatus() === PipelineStatus::COMPLETED && $subPipelineId !== '') {
      try {
        $outputs = $this->executionService->getPipelineOutputs($subPipelineId);
      }
      catch (\Throwable $e) {
        $this->logger->error('Failed to fetch sub-pipeline @pipeline outputs: @message', [
          '@pipeline' => $subPipelineId,
          '@message' => $e->getMessage(),
        ]);
      }
    }

    try {
      $this->interruptManager->resolveInterrupt(
        (string) ($sentinel->uuid() ?? ''),
        [
          'status' => $event->getStatus(),
          'outputs' => $outputs,
          'execution_time_us' => $event->getExecutionTimeUs(),
        ],
      );
    }
    catch (\Throwable $e) {
      $this->logger->error('Failed to resolve PipelineCompletion sentinel @id: @message', [
        '@id' => $sentinel->uuid(),
        '@message' => $e->getMessage(),
      ]);
    }
  }

  /**
   * Finds the pending PipelineCompletion sentinel for the given sub-pipeline.
   *
   * Sentinels are created by {@see \Drupal\flowdrop_workflow_executor\Plugin\FlowDropNodeProcessor\WorkflowNode::dispatchAsync()}
   * with the sub-pipeline ID stored as `pipeline_id` so the lookup is a
   * single indexed query.
   */
  private function findPendingSentinel(string $subPipelineId): ?FlowDropInterruptInterface {
    return $this->interruptManager->findPendingInterruptOnPipeline(
      InterruptType::PipelineCompletion,
      $subPipelineId,
    );
  }

}
