<?php
/**
 * Canonical operation factory.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Domain\Operation;

use ScheduledChanges\Domain\Launch\ConflictPolicy;
use ScheduledChanges\Domain\Shared\Uuid;
use ScheduledChanges\Field\FieldDefinition;

/**
 * Converts field changes into complete replayable operations.
 */
final class OperationFactory {
	/**
	 * @param Uuid[] $dependencies Explicit operation dependencies.
	 * @param mixed  $before
	 * @param mixed  $after
	 */
	public function fieldChange(
		TargetDefinition $target,
		FieldDefinition $field,
		$before,
		$after,
		array $dependencies = array(),
		?Uuid $uuid = null
	): Operation {
		if ( ! $field->writable() || ! $field->schedulable() ) {
			throw new \InvalidArgumentException( 'Field changes require a writable schedulable definition.' );
		}

		$type     = $this->fieldOperationType( $field, $after );
		$conflict = \ScheduledChanges\Support\EnumValue::tryFrom( \ScheduledChanges\Domain\Launch\ConflictPolicy::class, $field->conflictStrategy() ) ?? ConflictPolicy::FAIL;

		return Operation::create(
			$type,
			$target,
			$field->id(),
			$field->provider(),
			$before,
			$after,
			$field->dataType(),
			$field->toArray(),
			array(
				'capability'      => $field->capability(),
				'scope'           => $field->scope(),
				'target_subtypes' => $field->subtypes(),
				'side_effects'    => $field->sideEffects(),
			),
			$dependencies,
			$field->reversible()
				? Reversibility::REVERSIBLE
				: Reversibility::IRREVERSIBLE,
			$conflict,
			array( 'sensitive' => $field->sensitive() ),
			'provider.write',
			$field->reversible() ? 'provider.rollback' : null,
			$this->fieldSummary( $field, $target, $after ),
			$uuid
		);
	}

	/**
	 * @param mixed $after
	 */
	private function fieldOperationType( FieldDefinition $field, $after ): string {
		if ( null === $after ) {
			return OperationType::UNSET_FIELD;
		}

		if ( strpos( $field->id(), '.media.' ) !== false ) {
			return OperationType::SET_MEDIA;
		}

		return OperationType::SET_FIELD;
	}

	/**
	 * @param mixed $after
	 */
	private function fieldSummary(
		FieldDefinition $field,
		TargetDefinition $target,
		$after
	): string {
		$verb = null === $after ? 'Unset' : 'Set';

		return sprintf( '%1$s %2$s on %3$s.', $verb, $field->label(), $target->reference() );
	}
}
