<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Service\Orchestrator\Trait;

use Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface;
use Drupal\flowdrop_runtime\Event\PipelineCreatedEvent;
use Drupal\flowdrop_runtime\Service\Storage\PipelineStorageInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Provides common pipeline creation logic for orchestrators.
 *
 * This trait extracts the createPipelineFromWorkflow() method that is
 * nearly identical across multiple orchestrators. It uses the storage
 * interface abstraction to allow different storage backends.
 *
 * Requirements for using this trait:
 * - The class must implement getPipelineStorage() returning
 *   PipelineStorageInterface
 * - The class must implement getPipelineCreationLogger() returning
 *   LoggerInterface
 * - The class must implement getPipelineCreationEventDispatcher()
 *   returning EventDispatcherInterface
 *
 * Hook methods for customization:
 * - getPipelineLabelSuffix(): Override to customize pipeline label suffix
 * - buildPipelineExecutionContext(): Override to add custom execution context
 *
 * @see \Drupal\flowdrop_runtime\Service\Orchestrator\InterruptHandlingTrait
 *
 * @internal This trait is not part of the public API and may change without notice.
 */
trait PipelineCreationTrait {

  /**
   * Creates a pipeline from a workflow definition.
   *
   * @param array $workflow
   *   The workflow definition array with keys:
   *   - id: (string) The workflow ID
   *   - label: (string) Optional human-readable label
   *   - nodes: (array) The workflow nodes
   *   - edges: (array) The workflow edges.
   * @param array $initialData
   *   The initial input data for the pipeline.
   * @param array $options
   *   Configuration options including:
   *   - max_concurrent_jobs: (int) Maximum concurrent jobs
   *   - job_priority_strategy: (string) Priority strategy
   *   - retry_strategy: (string) Retry strategy
   *   - playground: (array) Optional playground context with session_id.
   *
   * @return \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface|null
   *   The created pipeline, or NULL if creation failed.
   */
  protected function createPipelineFromWorkflow(
    array $workflow,
    array $initialData,
    array $options,
  ): ?FlowDropPipelineInterface {
    try {
      $workflowLabel = $workflow["label"] ?? $workflow["id"] ?? "Unknown Workflow";

      // Create the pipeline using the storage interface.
      $pipeline = $this->getPipelineStorage()->create([
        "label" => $workflowLabel . $this->getPipelineLabelSuffix(),
        "bundle" => "default",
        "workflow_id" => $workflow["id"],
        "input_data" => $initialData,
        "status" => "pending",
        "max_concurrent_jobs" => $options["max_concurrent_jobs"] ?? 5,
        "job_priority_strategy" => $options["job_priority_strategy"] ?? "dependency_order",
        "retry_strategy" => $options["retry_strategy"] ?? "individual",
      ]);

      // Set execution context (allows customization via hook method).
      $executionContext = $this->buildPipelineExecutionContext($options);
      if (!empty($executionContext)) {
        $pipeline->setExecutionContext($executionContext);
      }

      // Save the pipeline.
      $this->getPipelineStorage()->save($pipeline);

      // Dispatch pipeline created event.
      $this->getPipelineCreationEventDispatcher()->dispatch(
        new PipelineCreatedEvent(
          pipeline: $pipeline,
          workflowId: $workflow["id"],
          inputData: $initialData,
          config: $options,
        ),
        PipelineCreatedEvent::NAME
      );

      $this->getPipelineCreationLogger()->info("Pipeline @id created from workflow @workflow_id", [
        "@id" => $pipeline->id(),
        "@workflow_id" => $workflow["id"],
      ]);

      return $pipeline;
    }
    catch (\Exception $e) {
      $this->getPipelineCreationLogger()->error("Failed to create pipeline from workflow @id: @message", [
        "@id" => $workflow["id"] ?? "unknown",
        "@message" => $e->getMessage(),
      ]);
      return NULL;
    }
  }

  /**
   * Gets the pipeline label suffix.
   *
   * Override this method in implementing classes to customize the suffix.
   *
   * @return string
   *   The suffix to append to the pipeline label.
   */
  protected function getPipelineLabelSuffix(): string {
    return " - Pipeline " . date("Y-m-d H:i:s");
  }

  /**
   * Builds the execution context for a new pipeline.
   *
   * Override this method in implementing classes to add custom context.
   * The default implementation extracts session_id for interrupt handling.
   *
   * @param array $options
   *   The orchestration options.
   *
   * @return array
   *   The execution context to set on the pipeline.
   */
  protected function buildPipelineExecutionContext(array $options): array {
    $context = [];

    // Store session_id for interrupt handling.
    // This enables interrupt nodes to include session_id when creating
    // interrupts, allowing proper interrupt resolution in the playground.
    if (isset($options["playground"]["session_id"])) {
      $context["session_id"] = $options["playground"]["session_id"];
    }

    return $context;
  }

  /**
   * Gets the pipeline storage service.
   *
   * @return \Drupal\flowdrop_runtime\Service\Storage\PipelineStorageInterface
   *   The pipeline storage service.
   */
  abstract protected function getPipelineStorage(): PipelineStorageInterface;

  /**
   * Gets the logger for pipeline creation operations.
   *
   * @return \Psr\Log\LoggerInterface
   *   The logger service.
   */
  abstract protected function getPipelineCreationLogger(): LoggerInterface;

  /**
   * Gets the event dispatcher for pipeline creation events.
   *
   * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
   *   The event dispatcher service.
   */
  abstract protected function getPipelineCreationEventDispatcher(): EventDispatcherInterface;

}
