<?php

declare(strict_types=1);

namespace App\Framework\Queue\ValueObjects;

/**
 * Value Object representing a chain of jobs with their execution order
 */
final readonly class JobChain
{
    /** @var string[] */
    public array $jobIds;

    public function __construct(
        public string $chainId,
        public string $name,
        array $jobIds,
        public ChainExecutionMode $executionMode = ChainExecutionMode::SEQUENTIAL,
        public bool $stopOnFailure = true,
        public ?array $metadata = null
    ) {
        $this->jobIds = array_values(array_unique($jobIds));
        $this->validate();
    }

    public static function sequential(string $chainId, string $name, array $jobIds): self
    {
        return new self($chainId, $name, $jobIds, ChainExecutionMode::SEQUENTIAL);
    }

    public static function parallel(string $chainId, string $name, array $jobIds): self
    {
        return new self($chainId, $name, $jobIds, ChainExecutionMode::PARALLEL);
    }

    public static function conditional(string $chainId, string $name, array $jobIds): self
    {
        return new self($chainId, $name, $jobIds, ChainExecutionMode::CONDITIONAL);
    }

    public function isSequential(): bool
    {
        return $this->executionMode === ChainExecutionMode::SEQUENTIAL;
    }

    public function isParallel(): bool
    {
        return $this->executionMode === ChainExecutionMode::PARALLEL;
    }

    public function isConditional(): bool
    {
        return $this->executionMode === ChainExecutionMode::CONDITIONAL;
    }

    public function shouldStopOnFailure(): bool
    {
        return $this->stopOnFailure;
    }

    public function getJobCount(): int
    {
        return count($this->jobIds);
    }

    public function getFirstJob(): ?string
    {
        return $this->jobIds[0] ?? null;
    }

    public function getLastJob(): ?string
    {
        return end($this->jobIds) ?: null;
    }

    public function containsJob(string $jobId): bool
    {
        return in_array($jobId, $this->jobIds, true);
    }

    public function getJobPosition(string $jobId): ?int
    {
        $position = array_search($jobId, $this->jobIds, true);

        return $position !== false ? $position : null;
    }

    public function getNextJob(string $currentJobId): ?string
    {
        $position = $this->getJobPosition($currentJobId);
        if ($position === null) {
            return null;
        }

        return $this->jobIds[$position + 1] ?? null;
    }

    public function getPreviousJob(string $currentJobId): ?string
    {
        $position = $this->getJobPosition($currentJobId);
        if ($position === null || $position === 0) {
            return null;
        }

        return $this->jobIds[$position - 1];
    }

    public function withMetadata(array $metadata): self
    {
        return new self(
            chainId: $this->chainId,
            name: $this->name,
            jobIds: $this->jobIds,
            executionMode: $this->executionMode,
            stopOnFailure: $this->stopOnFailure,
            metadata: array_merge($this->metadata ?? [], $metadata)
        );
    }

    public function withStopOnFailure(bool $stopOnFailure): self
    {
        return new self(
            chainId: $this->chainId,
            name: $this->name,
            jobIds: $this->jobIds,
            executionMode: $this->executionMode,
            stopOnFailure: $stopOnFailure,
            metadata: $this->metadata
        );
    }

    public function toArray(): array
    {
        return [
            'chain_id' => $this->chainId,
            'name' => $this->name,
            'job_ids' => $this->jobIds,
            'execution_mode' => $this->executionMode->value,
            'stop_on_failure' => $this->stopOnFailure,
            'job_count' => $this->getJobCount(),
            'metadata' => $this->metadata,
        ];
    }

    private function validate(): void
    {
        if (empty(trim($this->chainId))) {
            throw new \InvalidArgumentException('Chain ID cannot be empty');
        }

        if (empty(trim($this->name))) {
            throw new \InvalidArgumentException('Chain name cannot be empty');
        }

        if (empty($this->jobIds)) {
            throw new \InvalidArgumentException('Job chain must contain at least one job');
        }

        foreach ($this->jobIds as $jobId) {
            if (empty(trim($jobId))) {
                throw new \InvalidArgumentException('Job ID cannot be empty');
            }
        }
    }
}
