<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Service\Storage;

use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\flowdrop_job\Constants\JobStatus;
use Drupal\flowdrop_job\FlowDropJobInterface;
use Drupal\user\UserInterface;

/**
 * In-memory implementation of FlowDropJobInterface.
 *
 * This value object provides a fast, ephemeral job implementation
 * that doesn't persist to the database. Used by SynchronousOrchestrator
 * for lightweight workflow execution.
 *
 * @internal This class is not part of the public API and may change without notice.
 *
 * @phpstan-ignore-next-line
 */
class InMemoryJob implements FlowDropJobInterface {

  /**
   * Auto-increment counter for IDs.
   */
  protected static int $idCounter = 0;

  /**
   * The job ID.
   */
  protected string $id;

  /**
   * The job label.
   */
  protected string $label = '';

  /**
   * The node ID.
   */
  protected string $nodeId = '';

  /**
   * The pipeline ID.
   */
  protected string $pipelineId = '';

  /**
   * The job status.
   */
  protected string $status = JobStatus::IDLE;

  /**
   * The job priority.
   */
  protected int $priority = 0;

  /**
   * The metadata.
   */
  protected array $metadata = [];

  /**
   * The input data.
   */
  protected array $inputData = [];

  /**
   * The output data.
   */
  protected array $outputData = [];

  /**
   * The error message.
   */
  protected string $errorMessage = '';

  /**
   * The started timestamp.
   */
  protected int $started = 0;

  /**
   * The completed timestamp.
   */
  protected int $completed = 0;

  /**
   * The retry count.
   */
  protected int $retryCount = 0;

  /**
   * The max retries.
   *
   * Defaults to 0: retry is opt-in, set explicitly per node at job generation.
   */
  protected int $maxRetries = 0;

  /**
   * The executor plugin ID.
   */
  protected string $executorPluginId = '';

  /**
   * The dependent job IDs.
   *
   * @var string[]
   */
  protected array $dependentJobIds = [];

  /**
   * Reference to dependent jobs (for in-memory lookups).
   *
   * @var \Drupal\flowdrop_job\FlowDropJobInterface[]
   */
  protected array $dependentJobs = [];

  /**
   * The created timestamp.
   */
  protected int $created;

  /**
   * The changed timestamp.
   */
  protected int $changed;

  /**
   * The owner ID.
   */
  protected int $ownerId = 0;

  /**
   * Constructs a new InMemoryJob.
   *
   * @param array $data
   *   Initial data for the job.
   */
  public function __construct(array $data = []) {
    $this->id = 'inmem_job_' . (++self::$idCounter);
    $this->created = time();
    $this->changed = time();

    if (isset($data['label'])) {
      $this->label = $data['label'];
    }
    if (isset($data['node_id'])) {
      $this->nodeId = $data['node_id'];
    }
    if (isset($data['pipeline_id'])) {
      $this->pipelineId = (string) $data['pipeline_id'];
    }
    if (isset($data['status'])) {
      $this->status = $data['status'];
    }
    if (isset($data['priority'])) {
      $this->priority = (int) $data['priority'];
    }
    if (isset($data['metadata'])) {
      $this->metadata = is_string($data['metadata'])
        ? json_decode($data['metadata'], TRUE) ?? []
        : $data['metadata'];
    }
    if (isset($data['max_retries'])) {
      $this->maxRetries = (int) $data['max_retries'];
    }
    if (isset($data['executor_plugin_id'])) {
      $this->executorPluginId = $data['executor_plugin_id'];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function id(): string|int|null {
    return $this->id;
  }

  /**
   * {@inheritdoc}
   */
  public function label(): ?string {
    return $this->label;
  }

  /**
   * {@inheritdoc}
   */
  public function getMetadata(): array {
    return $this->metadata;
  }

  /**
   * {@inheritdoc}
   */
  public function setMetadata(array $metadata): static {
    $this->metadata = $metadata;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getMetadataValue(string $key, mixed $default = NULL): mixed {
    return $this->metadata[$key] ?? $default;
  }

  /**
   * {@inheritdoc}
   */
  public function setMetadataValue(string $key, mixed $value): static {
    $this->metadata[$key] = $value;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getNodeId(): string {
    return $this->nodeId;
  }

  /**
   * {@inheritdoc}
   */
  public function setNodeId(string $node_id): static {
    $this->nodeId = $node_id;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatus(): string {
    return $this->status;
  }

  /**
   * {@inheritdoc}
   */
  public function setStatus(string $status): static {
    $this->status = $status;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getPriority(): int {
    return $this->priority;
  }

  /**
   * {@inheritdoc}
   */
  public function setPriority(int $priority): static {
    $this->priority = $priority;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getInputData(): array {
    return $this->inputData;
  }

  /**
   * {@inheritdoc}
   */
  public function setInputData(array $input_data): static {
    $this->inputData = $input_data;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputData(): array {
    return $this->outputData;
  }

  /**
   * {@inheritdoc}
   */
  public function setOutputData(array $output_data): static {
    $this->outputData = $output_data;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getErrorMessage(): string {
    return $this->errorMessage;
  }

  /**
   * {@inheritdoc}
   */
  public function setErrorMessage(string $error_message): static {
    $this->errorMessage = $error_message;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getStarted(): int {
    return $this->started;
  }

  /**
   * {@inheritdoc}
   */
  public function setStarted(int $started): static {
    $this->started = $started;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getCompleted(): int {
    return $this->completed;
  }

  /**
   * {@inheritdoc}
   */
  public function setCompleted(int $completed): static {
    $this->completed = $completed;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getRetryCount(): int {
    return $this->retryCount;
  }

  /**
   * {@inheritdoc}
   */
  public function setRetryCount(int $retry_count): static {
    $this->retryCount = $retry_count;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getMaxRetries(): int {
    return $this->maxRetries;
  }

  /**
   * {@inheritdoc}
   */
  public function setMaxRetries(int $max_retries): static {
    $this->maxRetries = $max_retries;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getDependentJobs(): array {
    return $this->dependentJobs;
  }

  /**
   * {@inheritdoc}
   */
  public function addDependentJob(FlowDropJobInterface $job): static {
    $jobId = $job->id();
    $this->dependentJobIds[$jobId] = $jobId;
    $this->dependentJobs[$jobId] = $job;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function removeDependentJob(FlowDropJobInterface $job): static {
    $jobId = $job->id();
    unset($this->dependentJobIds[$jobId]);
    unset($this->dependentJobs[$jobId]);
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function clearDependentJobs(): static {
    $this->dependentJobIds = [];
    $this->dependentJobs = [];
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getExecutorPluginId(): string {
    return $this->executorPluginId;
  }

  /**
   * {@inheritdoc}
   */
  public function setExecutorPluginId(string $executor_plugin_id): static {
    $this->executorPluginId = $executor_plugin_id;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isReadyToRun(array $completed_jobs): bool {
    if (empty($this->dependentJobIds)) {
      return TRUE;
    }

    // Normalize completed_jobs to IDs.
    $completedIds = [];
    foreach ($completed_jobs as $item) {
      if ($item instanceof FlowDropJobInterface) {
        $completedIds[] = $item->id();
      }
      else {
        $completedIds[] = (string) $item;
      }
    }

    // Check if all dependencies are completed.
    foreach ($this->dependentJobIds as $dependentId) {
      if (!in_array($dependentId, $completedIds, TRUE)) {
        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function canRetry(): bool {
    return $this->retryCount < $this->maxRetries;
  }

  /**
   * {@inheritdoc}
   */
  public function incrementRetryCount(): static {
    $this->retryCount++;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsStarted(): static {
    $this->status = JobStatus::RUNNING;
    $this->started = time();
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsCompleted(array $output_data = []): static {
    $this->status = JobStatus::COMPLETED;
    $this->outputData = $output_data;
    $this->completed = time();
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsFailed(string $error_message = ''): static {
    $this->status = JobStatus::FAILED;
    $this->errorMessage = $error_message;
    $this->completed = time();
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsCancelled(): static {
    $this->status = JobStatus::CANCELLED;
    $this->completed = time();
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsIdle(): static {
    $this->status = JobStatus::IDLE;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsPending(): static {
    $this->status = JobStatus::PENDING;
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsSkipped(): static {
    $this->status = JobStatus::SKIPPED;
    $this->completed = time();
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function markAsInterrupted(string $interrupt_id, string $message = ""): static {
    $this->status = JobStatus::INTERRUPTED;
    $this->metadata['interrupt_id'] = $interrupt_id;
    if ($message) {
      $this->metadata['interrupt_message'] = $message;
    }
    $this->changed = time();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function save(): int {
    // In-memory - no actual save, return SAVED_UPDATED.
    $this->changed = time();
    return 2;
  }

  /**
   * {@inheritdoc}
   */
  public function delete(): void {
    // No-op for in-memory storage.
  }

  // =========================================================================
  // EntityOwnerInterface methods
  // =========================================================================

  /**
   * {@inheritdoc}
   */
  public function getOwner(): ?UserInterface {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setOwner(UserInterface $account): static {
    $this->ownerId = (int) $account->id();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getOwnerId(): ?int {
    return $this->ownerId ?: NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setOwnerId($uid): static {
    $this->ownerId = (int) $uid;
    return $this;
  }

  // =========================================================================
  // EntityChangedInterface methods
  // =========================================================================

  /**
   * {@inheritdoc}
   */
  public function getChangedTime(): int {
    return $this->changed;
  }

  /**
   * {@inheritdoc}
   */
  public function setChangedTime($timestamp): static {
    $this->changed = (int) $timestamp;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getChangedTimeAcrossTranslations(): int {
    return $this->changed;
  }

  // =========================================================================
  // ContentEntityInterface methods (stubbed)
  // =========================================================================

  /**
   * {@inheritdoc}
   */
  public function hasField($field_name): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function get($field_name): FieldItemListInterface {
    throw new \RuntimeException("InMemoryJob does not support field access via get()");
  }

  /**
   * {@inheritdoc}
   */
  public function set($field_name, $value, $notify = TRUE): static {
    throw new \RuntimeException("InMemoryJob does not support field access via set()");
  }

  /**
   * {@inheritdoc}
   */
  public function getFields($include_computed = TRUE): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getTranslatableFields($include_computed = TRUE): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldDefinition($name): ?FieldDefinitionInterface {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldDefinitions(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function toArray(): array {
    return [
      'id' => $this->id,
      'label' => $this->label,
      'node_id' => $this->nodeId,
      'pipeline_id' => $this->pipelineId,
      'status' => $this->status,
      'priority' => $this->priority,
      'metadata' => $this->metadata,
      'input_data' => $this->inputData,
      'output_data' => $this->outputData,
      'error_message' => $this->errorMessage,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function onChange($name): void {
    $this->changed = time();
  }

  /**
   * {@inheritdoc}
   */
  public function validate(): object {
    throw new \RuntimeException("InMemoryJob does not support validation");
  }

  /**
   * {@inheritdoc}
   */
  public function isValidationRequired(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function setValidationRequired($required): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function uuid(): ?string {
    return $this->id;
  }

  /**
   * {@inheritdoc}
   */
  public function bundle(): string {
    return 'default';
  }

  /**
   * {@inheritdoc}
   */
  public function isNew(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function enforceIsNew($value = TRUE): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityTypeId(): string {
    return 'flowdrop_job';
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityType(): EntityTypeInterface {
    throw new \RuntimeException("InMemoryJob does not support getEntityType()");
  }

  /**
   * {@inheritdoc}
   */
  public function language(): LanguageInterface {
    throw new \RuntimeException("InMemoryJob does not support language()");
  }

  /**
   * {@inheritdoc}
   */
  public function isNewTranslation(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isNewRevision(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function setNewRevision($value = TRUE): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public function isDefaultRevision($new_value = NULL): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function wasDefaultRevision(): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function isLatestRevision(): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function isLatestTranslationAffectedRevision(): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getRevisionId(): ?int {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getLoadedRevisionId(): ?int {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function updateLoadedRevisionId(): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isTranslatable(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isDefaultTranslation(): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getTranslationLanguages($include_default = TRUE): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getTranslation($langcode): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getUntranslated(): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function hasTranslation($langcode): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function hasTranslationChanges(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function addTranslation($langcode, array $values = []): static {
    throw new \RuntimeException("InMemoryJob does not support translations");
  }

  /**
   * {@inheritdoc}
   */
  public function removeTranslation($langcode): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public function isRevisionTranslationAffected(): ?bool {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setRevisionTranslationAffected($affected): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isRevisionTranslationAffectedEnforced(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function setRevisionTranslationAffectedEnforced($enforced): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE): AccessResultInterface|bool {
    return $return_as_object ? new AccessResultAllowed() : TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge(): int {
    return 0;
  }

  /**
   * {@inheritdoc}
   */
  public function toLink($text = NULL, $rel = 'canonical', array $options = []): object {
    throw new \RuntimeException("InMemoryJob does not support toLink()");
  }

  /**
   * {@inheritdoc}
   */
  public function toUrl($rel = 'canonical', array $options = []): object {
    throw new \RuntimeException("InMemoryJob does not support toUrl()");
  }

  /**
   * {@inheritdoc}
   */
  public function hasLinkTemplate($key): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function uriRelationships(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public static function load($id): ?static {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public static function loadMultiple(?array $ids = NULL): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public static function create(array $values = []): static {
    return new static($values);
  }

  /**
   * {@inheritdoc}
   */
  public function createDuplicate(): static {
    $duplicate = clone $this;
    $duplicate->id = 'inmem_job_' . (++self::$idCounter);
    return $duplicate;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigDependencyKey(): string {
    return 'content';
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigDependencyName(): string {
    return $this->getEntityTypeId() . ':' . $this->id();
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigTarget(): string {
    return $this->getEntityTypeId() . ':' . $this->uuid();
  }

  /**
   * {@inheritdoc}
   */
  public function getOriginalId(): ?string {
    return $this->id;
  }

  /**
   * {@inheritdoc}
   */
  public function setOriginalId($id): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getTypedData(): TypedDataInterface {
    throw new \RuntimeException("InMemoryJob does not support getTypedData()");
  }

  /**
   * {@inheritdoc}
   */
  public function preSave(EntityStorageInterface $storage): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public function postCreate(EntityStorageInterface $storage): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageInterface $storage, $update = TRUE): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public static function preCreate(EntityStorageInterface $storage, array &$values): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public static function preDelete(EntityStorageInterface $storage, array $entities): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public static function postDelete(EntityStorageInterface $storage, array $entities): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public static function postLoad(EntityStorageInterface $storage, array &$entities): void {
    // No-op.
  }

  /**
   * {@inheritdoc}
   */
  public function referencedEntities(): array {
    return [];
  }

  // =========================================================================
  // Additional interface methods
  // =========================================================================

  /**
   * {@inheritdoc}
   */
  public function addCacheContexts(array $cache_contexts): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function addCacheTags(array $cache_tags): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function addCacheableDependency($other_object): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function mergeCacheMaxAge($max_age): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getBundleEntity(): ?EntityInterface {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTagsToInvalidate(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getOriginal(): ?static {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setOriginal(?object $entity): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isDefaultTranslationAffectedOnly(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isSyncing(): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function setSyncing($syncing): static {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record): void {
    // No-op.
  }

}
