<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration\DTO\Snapshot;

/**
 * Tracks iterator state for array processing loops.
 *
 * This DTO maintains all state needed to iterate over arrays in
 * a state-managed loop, including:
 * - The source array being iterated.
 * - Current position in the array.
 * - Whether more items remain.
 * - Accumulated results from processing.
 *
 * Example usage:
 * @code
 * $iterator = IteratorState::fromItems(["a", "b", "c"]);
 * // currentIndex: 0, currentItem: "a", hasMore: true
 *
 * $iterator = $iterator->advance();
 * // currentIndex: 1, currentItem: "b", hasMore: true
 *
 * $iterator = $iterator->addResult(["processed" => "a"]);
 * @endcode
 *
 * @internal
 */
final class IteratorState implements \JsonSerializable {

  /**
   * Constructs a new IteratorState.
   *
   * @param array<mixed> $items
   *   The source array to iterate over.
   * @param int $currentIndex
   *   Current position in the array.
   * @param mixed $currentItem
   *   The current item being processed.
   * @param bool $hasMore
   *   Whether more items remain to process.
   * @param array<mixed> $results
   *   Accumulated results from processing.
   */
  public function __construct(
    public readonly array $items = [],
    public readonly int $currentIndex = 0,
    public readonly mixed $currentItem = NULL,
    public readonly bool $hasMore = TRUE,
    public readonly array $results = [],
  ) {}

  /**
   * Creates an IteratorState from an array of items.
   *
   * @param array<mixed> $items
   *   The items to iterate over.
   *
   * @return self
   *   A new IteratorState initialized at position 0.
   */
  public static function fromItems(array $items): self {
    $items = array_values($items);

    if (empty($items)) {
      return new self(
        items: [],
        currentIndex: 0,
        currentItem: NULL,
        hasMore: FALSE,
        results: [],
      );
    }

    return new self(
      items: $items,
      currentIndex: 0,
      currentItem: $items[0],
      hasMore: count($items) > 1,
      results: [],
    );
  }

  /**
   * Advances the iterator to the next item.
   *
   * @return self
   *   A new IteratorState at the next position.
   */
  public function advance(): self {
    $nextIndex = $this->currentIndex + 1;
    $hasMore = $nextIndex < count($this->items) - 1;
    $currentItem = $this->items[$nextIndex] ?? NULL;

    // If we've gone past the end, mark as exhausted.
    if ($nextIndex >= count($this->items)) {
      return new self(
        items: $this->items,
        currentIndex: $nextIndex,
        currentItem: NULL,
        hasMore: FALSE,
        results: $this->results,
      );
    }

    return new self(
      items: $this->items,
      currentIndex: $nextIndex,
      currentItem: $currentItem,
      hasMore: $hasMore,
      results: $this->results,
    );
  }

  /**
   * Adds a result to the accumulated results.
   *
   * @param mixed $result
   *   The result to add.
   *
   * @return self
   *   A new IteratorState with the result added.
   */
  public function addResult(mixed $result): self {
    $newResults = $this->results;
    $newResults[] = $result;

    return new self(
      items: $this->items,
      currentIndex: $this->currentIndex,
      currentItem: $this->currentItem,
      hasMore: $this->hasMore,
      results: $newResults,
    );
  }

  /**
   * Checks if the iterator is exhausted (no more items).
   *
   * @return bool
   *   TRUE if no more items to process.
   */
  public function isExhausted(): bool {
    return $this->currentIndex >= count($this->items);
  }

  /**
   * Gets the total number of items.
   *
   * @return int
   *   The total item count.
   */
  public function getTotalCount(): int {
    return count($this->items);
  }

  /**
   * Gets the number of remaining items.
   *
   * @return int
   *   The remaining item count.
   */
  public function getRemainingCount(): int {
    $remaining = count($this->items) - $this->currentIndex - 1;
    return max(0, $remaining);
  }

  /**
   * Creates an IteratorState from an array.
   *
   * @param array<string, mixed> $data
   *   The iterator state data.
   *
   * @return self
   *   A new IteratorState instance.
   */
  public static function fromArray(array $data): self {
    return new self(
      items: $data["items"] ?? [],
      currentIndex: (int) ($data["currentIndex"] ?? 0),
      currentItem: $data["currentItem"] ?? NULL,
      hasMore: (bool) ($data["hasMore"] ?? TRUE),
      results: $data["results"] ?? [],
    );
  }

  /**
   * Converts the iterator state to an array.
   *
   * @return array<string, mixed>
   *   The iterator state as an array.
   */
  public function toArray(): array {
    return [
      "items" => $this->items,
      "currentIndex" => $this->currentIndex,
      "currentItem" => $this->currentItem,
      "hasMore" => $this->hasMore,
      "results" => $this->results,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function jsonSerialize(): mixed {
    return $this->toArray();
  }

}
