<?php
/**
 * Versioned Action Scheduler payload.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Scheduler;

use ScheduledChanges\Domain\Run\TriggerSource;
use ScheduledChanges\Support\EnumValue;

/**
 * Canonical queue identity with a strict legacy positional reader.
 */
final class QueueActionPayload {
	public const SCHEMA_VERSION = 2;

	private int $version_id;
	private string $occurrence_at_utc;
	private string $trigger_source;
	private int $attempt_number;
	private int $execution_actor_id;

	private function __construct( int $version_id, string $occurrence_at_utc, string $trigger_source, int $attempt_number, int $execution_actor_id ) {
		$trigger_source = self::triggerSourceValue( $trigger_source );

		if ( $version_id <= 0 || $attempt_number <= 0 || $execution_actor_id <= 0 ) {
			throw new \UnexpectedValueException( 'Queued launch identity contains invalid numeric values.' );
		}

		if ( '' === trim( $occurrence_at_utc ) || '' === trim( $trigger_source ) ) {
			throw new \UnexpectedValueException( 'Queued launch identity is incomplete.' );
		}

		$this->version_id         = $version_id;
		$this->occurrence_at_utc  = $occurrence_at_utc;
		$this->trigger_source     = $trigger_source;
		$this->attempt_number     = $attempt_number;
		$this->execution_actor_id = $execution_actor_id;
	}

	public static function create( int $version_id, string $occurrence_at_utc, string $trigger_source, int $attempt_number, int $execution_actor_id ): self {
		return new self( $version_id, $occurrence_at_utc, $trigger_source, $attempt_number, $execution_actor_id );
	}

	/**
	 * Reads canonical one-object arguments or an exact five-value legacy payload.
	 *
	 * @param array<int,mixed> $arguments Action Scheduler callback arguments.
	 */
	public static function fromActionArguments( array $arguments ): self {
		if ( 1 === count( $arguments ) && is_array( $arguments[0] ) ) {
			return self::fromArray( $arguments[0] );
		}

		if ( 5 !== count( $arguments ) || is_array( $arguments[0] ?? null ) ) {
			throw new \UnexpectedValueException( 'Queued launch action has an ambiguous legacy payload.' );
		}

		return new self(
			(int) $arguments[0],
			(string) $arguments[1],
			(string) $arguments[2],
			(int) $arguments[3],
			(int) $arguments[4]
		);
	}

	/**
	 * @param array<string,mixed> $payload Canonical queue payload.
	 * @phpstan-param array{attempt_number:mixed,execution_actor_id:mixed,launch_version_id:mixed,occurrence_at_utc:mixed,schema_version:mixed,trigger_source:mixed} $payload
	 */
	public static function fromArray( array $payload ): self {
		$expected = array(
			'attempt_number',
			'execution_actor_id',
			'launch_version_id',
			'occurrence_at_utc',
			'schema_version',
			'trigger_source',
		);
		$actual   = array_keys( $payload );
		sort( $actual );

		if ( $expected !== $actual || self::SCHEMA_VERSION !== (int) ( $payload['schema_version'] ?? 0 ) ) {
			throw new \UnexpectedValueException( 'Queued launch action has an unsupported payload schema.' );
		}

		return new self(
			(int) $payload['launch_version_id'],
			(string) $payload['occurrence_at_utc'],
			(string) $payload['trigger_source'],
			(int) $payload['attempt_number'],
			(int) $payload['execution_actor_id']
		);
	}

	/**
	 * @return array{
	 *     schema_version:int,
	 *     launch_version_id:int,
	 *     occurrence_at_utc:string,
	 *     trigger_source:string,
	 *     attempt_number:int,
	 *     execution_actor_id:int
	 * }
	 */
	public function toArray(): array {
		return array(
			'schema_version'     => self::SCHEMA_VERSION,
			'launch_version_id'  => $this->version_id,
			'occurrence_at_utc'  => $this->occurrence_at_utc,
			'trigger_source'     => $this->trigger_source,
			'attempt_number'     => $this->attempt_number,
			'execution_actor_id' => $this->execution_actor_id,
		);
	}

	public function versionId(): int {
		return $this->version_id;
	}

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

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

	public function attemptNumber(): int {
		return $this->attempt_number;
	}

	public function executionActorId(): int {
		return $this->execution_actor_id;
	}

	private static function triggerSourceValue( string $trigger_source ): string {
		try {
			return EnumValue::from( TriggerSource::class, $trigger_source );
		} catch ( \InvalidArgumentException $exception ) {
			unset( $exception );

			throw new \UnexpectedValueException( 'Queued launch trigger source is invalid.' );
		}
	}
}
