<?php
/**
 * Operation execution result.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Execution;

use ScheduledChanges\Domain\Run\FailureClassification;
use ScheduledChanges\Domain\Run\OperationRunStatus;

/**
 * Normalized verified result returned by every operation handler.
 */
final class OperationExecutionResult {
	private string $status;
	private string $code;
	private string $message;
	private bool $changed;
	private bool $verified;
	private bool $retryable;
	private ?string $classification;
	/**
	 * @var array<string, mixed>
	 */
	private array $details;
	/**
	 * @param array<string,mixed> $details Redacted structured details.
	 */
	private function __construct(
		string $status,
		string $code,
		string $message,
		bool $changed,
		bool $verified,
		bool $retryable,
		?string $classification,
		array $details
	) {
		$this->status         = $status;
		$this->code           = $code;
		$this->message        = $message;
		$this->changed        = $changed;
		$this->verified       = $verified;
		$this->retryable      = $retryable;
		$this->classification = $classification;
		$this->details        = $details;
		if ( '' === trim( $this->code ) ) {
			throw new \InvalidArgumentException( 'Operation execution result codes are required.' );
		}
	}

	/**
	 * @param array<string,mixed> $details Result details.
	 */
	public static function success(
		string $message,
		bool $changed,
		bool $verified = true,
		array $details = array()
	): self {
		return new self(
			OperationRunStatus::SUCCESSFUL,
			'success',
			$message,
			$changed,
			$verified,
			false,
			null,
			$details
		);
	}

	/**
	 * @param array<string,mixed> $details Result details.
	 */
	public static function skipped( string $code, string $message, array $details = array() ): self {
		return new self(
			OperationRunStatus::SKIPPED,
			$code,
			$message,
			false,
			true,
			false,
			null,
			$details
		);
	}

	/**
	 * @param array<string,mixed> $details Result details.
	 */
	public static function failure(
		string $code,
		string $message,
		string $classification,
		bool $retryable,
		array $details = array()
	): self {
		return new self(
			OperationRunStatus::FAILED,
			$code,
			$message,
			false,
			false,
			$retryable,
			$classification,
			$details
		);
	}

	public function status(): string {
		return $this->status;
	}

	public function code(): string {
		return $this->code;
	}

	public function message(): string {
		return $this->message;
	}

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

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

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

	public function classification(): ?string {
		return $this->classification;
	}

	/**
	 * @return array<string,mixed>
	 */
	public function details(): array {
		return $this->details;
	}

	/**
	 * Returns whether the handler reported non-blocking execution warnings.
	 */
	public function hasWarnings(): bool {
		return true === ( $this->details['warning'] ?? false )
			|| ( is_array( $this->details['warnings'] ?? null ) && array() !== $this->details['warnings'] );
	}

	/**
	 * @return array<string,mixed>
	 */
	public function toArray(): array {
		return array(
			'status'                 => $this->status,
			'code'                   => $this->code,
			'message'                => $this->message,
			'changed'                => $this->changed,
			'verified'               => $this->verified,
			'retryable'              => $this->retryable,
			'failure_classification' => $this->classification,
			'details'                => $this->details,
		);
	}
}
