<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow_executor\DTO;

use Drupal\flowdrop_workflow_executor\Enum\WorkflowExecutionStatus;

/**
 * Data transfer object for workflow execution results.
 *
 * This class encapsulates the result of a workflow execution,
 * including status, session information, outputs, and timing data.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class WorkflowExecutionResult {

  /**
   * Constructs a WorkflowExecutionResult object.
   *
   * @param bool $success
   *   Whether the execution was successful.
   * @param \Drupal\flowdrop_workflow_executor\Enum\WorkflowExecutionStatus $status
   *   The execution status.
   * @param string $sessionId
   *   The playground session ID.
   * @param string $jobId
   *   The job ID if available.
   * @param array<string, mixed> $outputs
   *   The workflow outputs when completed.
   * @param string $errorMessage
   *   Error message if execution failed.
   * @param int $executionTimeMs
   *   Execution time in milliseconds.
   * @param string $pipelineId
   *   The owning pipeline ID, if any.
   */
  public function __construct(
    private readonly bool $success,
    private readonly WorkflowExecutionStatus $status,
    private readonly string $sessionId = "",
    private readonly string $jobId = "",
    private readonly array $outputs = [],
    private readonly string $errorMessage = "",
    private readonly int $executionTimeMs = 0,
    private readonly string $pipelineId = "",
  ) {}

  /**
   * Creates a successful result.
   *
   * @param string $sessionId
   *   The session ID.
   * @param array<string, mixed> $outputs
   *   The workflow outputs.
   * @param int $executionTimeMs
   *   Execution time in milliseconds.
   * @param string $jobId
   *   The job ID if available.
   * @param string $pipelineId
   *   The owning pipeline ID, if any.
   *
   * @return self
   *   A new successful result instance.
   */
  public static function success(
    string $sessionId,
    array $outputs = [],
    int $executionTimeMs = 0,
    string $jobId = "",
    string $pipelineId = "",
  ): self {
    return new self(
      success: TRUE,
      status: WorkflowExecutionStatus::Completed,
      sessionId: $sessionId,
      jobId: $jobId,
      outputs: $outputs,
      executionTimeMs: $executionTimeMs,
      pipelineId: $pipelineId,
    );
  }

  /**
   * Creates a pending/async result.
   *
   * @param string $sessionId
   *   The session ID.
   * @param string $jobId
   *   The job ID if available.
   *
   * @return self
   *   A new pending result instance.
   */
  public static function pending(string $sessionId, string $jobId = ""): self {
    return new self(
      success: TRUE,
      status: WorkflowExecutionStatus::Pending,
      sessionId: $sessionId,
      jobId: $jobId,
    );
  }

  /**
   * Creates a running result.
   *
   * @param string $sessionId
   *   The session ID.
   * @param string $jobId
   *   The job ID if available.
   *
   * @return self
   *   A new running result instance.
   */
  public static function running(string $sessionId, string $jobId = ""): self {
    return new self(
      success: TRUE,
      status: WorkflowExecutionStatus::Running,
      sessionId: $sessionId,
      jobId: $jobId,
    );
  }

  /**
   * Creates a paused result for a sub-workflow that can't complete yet.
   *
   * The sub-pipeline has stopped mid-flight in a non-terminal state —
   * today that's a HITL interrupt (session `awaiting_input`), but the
   * status is intentionally generic so other non-terminal pauses (e.g.
   * max iterations, scheduled waits) can use the same signal. The
   * caller (typically WorkflowNode) is expected to convert this into a
   * linked interrupt on the parent pipeline rather than block.
   */
  public static function paused(string $sessionId, string $pipelineId = ""): self {
    return new self(
      success: TRUE,
      status: WorkflowExecutionStatus::Paused,
      sessionId: $sessionId,
      pipelineId: $pipelineId,
    );
  }

  /**
   * Creates a failed result.
   *
   * @param string $errorMessage
   *   The error message.
   * @param string $sessionId
   *   The session ID if available.
   *
   * @return self
   *   A new failed result instance.
   */
  public static function failure(string $errorMessage, string $sessionId = ""): self {
    return new self(
      success: FALSE,
      status: WorkflowExecutionStatus::Failed,
      sessionId: $sessionId,
      errorMessage: $errorMessage,
    );
  }

  /**
   * Creates a timeout result.
   *
   * @param string $sessionId
   *   The session ID.
   * @param int $executionTimeMs
   *   How long we waited before timing out.
   *
   * @return self
   *   A new timeout result instance.
   */
  public static function timeout(string $sessionId, int $executionTimeMs = 0): self {
    return new self(
      success: FALSE,
      status: WorkflowExecutionStatus::Timeout,
      sessionId: $sessionId,
      errorMessage: "Workflow execution timed out",
      executionTimeMs: $executionTimeMs,
    );
  }

  /**
   * Gets whether the execution was successful.
   *
   * @return bool
   *   TRUE if successful, FALSE otherwise.
   */
  public function isSuccess(): bool {
    return $this->success;
  }

  /**
   * Gets the execution status.
   *
   * @return \Drupal\flowdrop_workflow_executor\Enum\WorkflowExecutionStatus
   *   The execution status.
   */
  public function getStatus(): WorkflowExecutionStatus {
    return $this->status;
  }

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

  /**
   * Gets the sub-pipeline ID for this execution.
   *
   * Populated when the workflow ran (or paused) as a sub-pipeline within
   * a parent's playground session — callers use this to gate the sentinel
   * on the sub-pipeline's PipelineCompletedEvent rather than the
   * session's. Empty string for top-level executions that don't have a
   * distinct sub-pipeline identity.
   *
   * @return string
   *   The pipeline ID, or empty string if not applicable.
   */
  public function getPipelineId(): string {
    return $this->pipelineId;
  }

  /**
   * Gets the job ID.
   *
   * @return string
   *   The job ID.
   */
  public function getJobId(): string {
    return $this->jobId;
  }

  /**
   * Gets the workflow outputs.
   *
   * @return array<string, mixed>
   *   The outputs array.
   */
  public function getOutputs(): array {
    return $this->outputs;
  }

  /**
   * Gets the error message.
   *
   * @return string
   *   The error message.
   */
  public function getErrorMessage(): string {
    return $this->errorMessage;
  }

  /**
   * Gets the execution time in milliseconds.
   *
   * @return int
   *   The execution time.
   */
  public function getExecutionTimeMs(): int {
    return $this->executionTimeMs;
  }

  /**
   * Converts the result to an array.
   *
   * @return array<string, mixed>
   *   The result as an array.
   */
  public function toArray(): array {
    return [
      "success" => $this->success,
      "status" => $this->status->value,
      "session_id" => $this->sessionId,
      "job_id" => $this->jobId,
      "outputs" => $this->outputs,
      "error_message" => $this->errorMessage,
      "execution_time_ms" => $this->executionTimeMs,
    ];
  }

}
