<?php
/**
 * Typed recording workspace hook context.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Recording;

use ScheduledChanges\Domain\Shared\Uuid;

/**
 * Validates and exposes the stable recording workspace context contract.
 *
 * @phpstan-import-type RecordingContext from RecordingWorkspaceAdapter
 */
final class RecordingWorkspaceContext {
	/**
	 * @var array<string,mixed>
	 * @phpstan-var RecordingContext
	 */
	private array $context;
	private string $session_uuid;
	private string $source_provider_id;
	private string $source_type;
	private string $source_reference;
	private int $source_id;
	private ?int $workspace_id;
	private int $owner_id;
	private string $phase;

	/**
	 * @param array<string,mixed> $context Adapter context.
	 * @phpstan-param RecordingContext $context
	 */
	private function __construct( array $context ) {
		$this->session_uuid       = self::requiredUuid( $context, 'session_uuid' );
		$this->source_provider_id = self::requiredString( $context, 'source_provider_id' );
		$this->source_type        = self::requiredString( $context, 'source_type' );
		$this->source_reference   = self::requiredString( $context, 'source_reference' );
		$this->source_id          = self::requiredPositiveInt( $context, 'source_id' );
		$this->workspace_id       = self::optionalPositiveInt( $context, 'workspace_id' );
		$this->owner_id           = self::requiredPositiveInt( $context, 'owner_id' );
		$this->phase              = self::requiredString( $context, 'phase' );
		$this->context            = array_merge(
			$context,
			array(
				'session_uuid'       => $this->session_uuid,
				'source_provider_id' => $this->source_provider_id,
				'source_type'        => $this->source_type,
				'source_reference'   => $this->source_reference,
				'source_id'          => $this->source_id,
				'workspace_id'       => $this->workspace_id,
				'owner_id'           => $this->owner_id,
				'phase'              => $this->phase,
			)
		);
	}

	/**
	 * @param array<string,mixed> $context Adapter context.
	 */
	public static function fromArray( array $context ): self {
		return new self( $context );
	}

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

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

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

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

	public function sourceId(): int {
		return $this->source_id;
	}

	public function workspaceId(): ?int {
		return $this->workspace_id;
	}

	public function ownerId(): int {
		return $this->owner_id;
	}

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

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

	/**
	 * Verifies adapter context still describes the service-owned recording request.
	 */
	public function assertMatches( Uuid $uuid, int $source_id, ?int $workspace_id, int $owner_id, string $phase ): void {
		if ( $this->session_uuid !== $uuid->value() ) {
			throw new \RuntimeException( 'Recording workspace context does not match session_uuid.' );
		}

		if ( $this->source_id !== $source_id ) {
			throw new \RuntimeException( 'Recording workspace context does not match source_id.' );
		}

		if ( $this->workspace_id !== $workspace_id ) {
			throw new \RuntimeException( 'Recording workspace context does not match workspace_id.' );
		}

		if ( $this->owner_id !== $owner_id ) {
			throw new \RuntimeException( 'Recording workspace context does not match owner_id.' );
		}

		if ( $this->phase !== $phase ) {
			throw new \RuntimeException( 'Recording workspace context does not match phase.' );
		}
	}

	/**
	 * @param array<string,mixed> $context Adapter context.
	 */
	private static function requiredUuid( array $context, string $key ): string {
		$value = self::requiredString( $context, $key );

		return ( new Uuid( $value ) )->value();
	}

	/**
	 * @param array<string,mixed> $context Adapter context.
	 */
	private static function requiredString( array $context, string $key ): string {
		$value = trim( (string) ( $context[ $key ] ?? '' ) );

		if ( '' === $value ) {
			throw new \RuntimeException( 'Recording workspace context is missing ' . esc_html( $key ) . '.' );
		}

		return $value;
	}

	/**
	 * @param array<string,mixed> $context Adapter context.
	 */
	private static function requiredPositiveInt( array $context, string $key ): int {
		$value = (int) ( $context[ $key ] ?? 0 );

		if ( $value <= 0 ) {
			throw new \RuntimeException( 'Recording workspace context is missing ' . esc_html( $key ) . '.' );
		}

		return $value;
	}

	/**
	 * @param array<string,mixed> $context Adapter context.
	 */
	private static function optionalPositiveInt( array $context, string $key ): ?int {
		if ( ! array_key_exists( $key, $context ) || null === $context[ $key ] || '' === $context[ $key ] ) {
			return null;
		}

		return self::requiredPositiveInt( $context, $key );
	}
}
