<?php

declare(strict_types=1);

namespace App\Framework\EventSourcing\Sagas;

use App\Framework\Core\ValueObjects\Timestamp;

/**
 * Saga State Value Object
 *
 * Tracks the current state and progress of a saga
 */
final readonly class SagaState
{
    public function __construct(
        public SagaId $sagaId,
        public string $sagaName,
        public SagaStatus $status,
        public array $data,
        public Timestamp $startedAt,
        public ?Timestamp $completedAt = null,
        public ?string $errorMessage = null,
        public int $stepCount = 0,
        public int $completedSteps = 0
    ) {
    }

    /**
     * Create initial saga state
     */
    public static function start(
        SagaId $sagaId,
        string $sagaName,
        array $initialData = []
    ): self {
        return new self(
            sagaId: $sagaId,
            sagaName: $sagaName,
            status: SagaStatus::RUNNING,
            data: $initialData,
            startedAt: Timestamp::now(),
            stepCount: 0,
            completedSteps: 0
        );
    }

    /**
     * Update saga data
     */
    public function withData(array $data): self
    {
        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: $this->status,
            data: array_merge($this->data, $data),
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            errorMessage: $this->errorMessage,
            stepCount: $this->stepCount,
            completedSteps: $this->completedSteps
        );
    }

    /**
     * Mark step as completed
     */
    public function withStepCompleted(): self
    {
        $completedSteps = $this->completedSteps + 1;
        $status = $completedSteps >= $this->stepCount
            ? SagaStatus::COMPLETED
            : SagaStatus::RUNNING;

        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: $status,
            data: $this->data,
            startedAt: $this->startedAt,
            completedAt: $status === SagaStatus::COMPLETED ? Timestamp::now() : null,
            errorMessage: $this->errorMessage,
            stepCount: $this->stepCount,
            completedSteps: $completedSteps
        );
    }

    /**
     * Mark saga as completed
     */
    public function withCompleted(): self
    {
        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: SagaStatus::COMPLETED,
            data: $this->data,
            startedAt: $this->startedAt,
            completedAt: Timestamp::now(),
            errorMessage: $this->errorMessage,
            stepCount: $this->stepCount,
            completedSteps: $this->stepCount
        );
    }

    /**
     * Mark saga as failed
     */
    public function withFailed(string $errorMessage): self
    {
        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: SagaStatus::FAILED,
            data: $this->data,
            startedAt: $this->startedAt,
            completedAt: Timestamp::now(),
            errorMessage: $errorMessage,
            stepCount: $this->stepCount,
            completedSteps: $this->completedSteps
        );
    }

    /**
     * Mark saga as compensating (rolling back)
     */
    public function withCompensating(): self
    {
        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: SagaStatus::COMPENSATING,
            data: $this->data,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            errorMessage: $this->errorMessage,
            stepCount: $this->stepCount,
            completedSteps: $this->completedSteps
        );
    }

    /**
     * Mark saga as compensated (rollback complete)
     */
    public function withCompensated(): self
    {
        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: SagaStatus::COMPENSATED,
            data: $this->data,
            startedAt: $this->startedAt,
            completedAt: Timestamp::now(),
            errorMessage: $this->errorMessage,
            stepCount: $this->stepCount,
            completedSteps: $this->completedSteps
        );
    }

    /**
     * Set total step count
     */
    public function withStepCount(int $stepCount): self
    {
        return new self(
            sagaId: $this->sagaId,
            sagaName: $this->sagaName,
            status: $this->status,
            data: $this->data,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            errorMessage: $this->errorMessage,
            stepCount: $stepCount,
            completedSteps: $this->completedSteps
        );
    }

    /**
     * Get progress percentage
     */
    public function getProgress(): float
    {
        if ($this->stepCount === 0) {
            return 0.0;
        }

        return ($this->completedSteps / $this->stepCount) * 100;
    }

    /**
     * Check if saga is terminal (completed, failed, or compensated)
     */
    public function isTerminal(): bool
    {
        return in_array($this->status, [
            SagaStatus::COMPLETED,
            SagaStatus::FAILED,
            SagaStatus::COMPENSATED,
        ], true);
    }
}
