<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Service\Orchestrator;

use Drupal\flowdrop_orchestration\DTO\Orchestrator\OrchestrationResponse;
use Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot;
use Drupal\flowdrop_orchestration\Exception\InterruptExceptionInterface;
use Drupal\flowdrop_runtime\Event\WorkflowInterruptedEvent;
use Drupal\flowdrop_runtime\Service\RealTime\RealTimeManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Provides common interrupt handling logic for orchestrators.
 *
 * This trait standardizes how orchestrators handle InterruptExceptionInterface
 * exceptions, ensuring consistent behavior across synchronous, asynchronous,
 * pipeline-based, and stategraph orchestrators.
 *
 * Requirements for using this trait:
 * - The class must implement OrchestratorInterface.
 * - The class must have access to RealTimeManager, EventDispatcher, and Logger.
 *
 * @see \Drupal\flowdrop_orchestration\Exception\InterruptExceptionInterface
 *
 * @internal This trait is not part of the public API and may change without notice.
 */
trait InterruptHandlingTrait {

  /**
   * Handles an interrupt exception and returns an appropriate response.
   *
   * This method:
   * - Updates the execution status to "interrupted".
   * - Logs the interrupt event.
   * - Dispatches the "flowdrop.workflow.interrupted" event.
   * - Returns an OrchestrationResponse with status "interrupted".
   *
   * @param \Drupal\flowdrop_orchestration\Exception\InterruptExceptionInterface $exception
   *   The interrupt exception that was thrown.
   * @param string $executionId
   *   The execution ID for this workflow run.
   * @param string $workflowId
   *   The workflow ID being executed.
   * @param float $startTime
   *   The microtime when execution started.
   * @param array<string, mixed> $results
   *   The results collected so far during execution.
   * @param array<string, mixed> $additionalMetadata
   *   Additional metadata to include in the response.
   *
   * @return \Drupal\flowdrop_orchestration\DTO\Orchestrator\OrchestrationResponse
   *   The orchestration response with "interrupted" status.
   */
  protected function handleInterruptException(
    InterruptExceptionInterface $exception,
    string $executionId,
    string $workflowId,
    float $startTime,
    array $results = [],
    array $additionalMetadata = [],
  ): OrchestrationResponse {
    $totalTime = microtime(TRUE) - $startTime;

    // Update execution status to interrupted.
    $this->getInterruptRealTimeManager()->updateExecutionStatus($executionId, WorkflowSnapshot::STATUS_INTERRUPTED, [
      "interrupt_id" => $exception->getInterruptId(),
      "interrupt_type" => $exception->getInterruptType(),
      "total_execution_time" => $totalTime,
      "nodes_executed" => count($results),
      "end_time" => time(),
    ]);

    // Log workflow interrupted.
    $this->getInterruptLogger()->info("Workflow @workflow interrupted at node for user input. Interrupt ID: @interrupt", [
      "@workflow" => $workflowId,
      "@interrupt" => $exception->getInterruptId(),
    ]);

    // Emit workflow interrupted event.
    $this->getInterruptEventDispatcher()->dispatch(
      new WorkflowInterruptedEvent(
        executionId: $executionId,
        workflowId: $workflowId,
        interrupt: $exception->toArray(),
        interruptContext: $exception->getExecutionContext(),
      ),
      WorkflowInterruptedEvent::NAME
    );

    // Build metadata for the response.
    $metadata = array_merge([
      "orchestrator_type" => $this->getType(),
      "nodes_executed" => count($results),
      "interrupt" => $exception->toArray(),
    ], $additionalMetadata);

    return new OrchestrationResponse(
      executionId: $executionId,
      status: WorkflowSnapshot::STATUS_INTERRUPTED,
      results: $results,
      executionTime: $totalTime,
      metadata: $metadata,
    );
  }

  /**
   * Gets the real-time manager for interrupt handling.
   *
   * Override this method if the property name differs in your class.
   *
   * @return \Drupal\flowdrop_runtime\Service\RealTime\RealTimeManagerInterface
   *   The real-time manager service.
   */
  protected function getInterruptRealTimeManager(): RealTimeManagerInterface {
    // @phpstan-ignore-next-line - Property exists in implementing class.
    return $this->realTimeManager;
  }

  /**
   * Gets the event dispatcher for interrupt handling.
   *
   * Override this method if the property name differs in your class.
   *
   * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
   *   The event dispatcher service.
   */
  protected function getInterruptEventDispatcher(): EventDispatcherInterface {
    // @phpstan-ignore-next-line - Property exists in implementing class.
    return $this->eventDispatcher;
  }

  /**
   * Gets the logger for interrupt handling.
   *
   * Override this method if the property name differs in your class.
   *
   * @return \Psr\Log\LoggerInterface
   *   The logger service.
   */
  protected function getInterruptLogger(): LoggerInterface {
    // @phpstan-ignore-next-line - Property exists in implementing class.
    return $this->logger;
  }

}
