<?php

namespace Fotogenic_AI;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Immutable value object for one server-authoritative workflow definition.
 *
 * Array properties are returned by value and no mutator is exposed. Callers can
 * therefore change their local copy without changing the registered contract.
 */
final class Workflow_Definition implements Workflow_Definition_Interface {
	private string $id;

	/** @var array<string, array> */
	private array $sources;

	private array $recipe_schema;

	private array $output_cardinality;

	/**
	 * @param string               $id                 Canonical workflow identifier.
	 * @param array<string, array> $sources            Source and scope matrix.
	 * @param array                $recipe_schema      Strict browser recipe metadata.
	 * @param array                $output_cardinality Per-item output limits.
	 */
	public function __construct( string $id, array $sources, array $recipe_schema, array $output_cardinality ) {
		$this->assert_valid_definition( $id, $sources, $recipe_schema, $output_cardinality );

		$this->id                 = $id;
		$this->sources            = $sources;
		$this->recipe_schema       = $recipe_schema;
		$this->output_cardinality = $output_cardinality;
	}

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

	public function get_sources(): array {
		return $this->sources;
	}

	public function get_recipe_schema(): array {
		return $this->recipe_schema;
	}

	public function get_output_cardinality(): array {
		return $this->output_cardinality;
	}

	public function supports( string $source_type, string $scope ): bool {
		$source_type = strtolower( trim( $source_type ) );
		$scope       = strtolower( trim( $scope ) );
		$source      = $this->sources[ $source_type ] ?? null;

		return is_array( $source )
			&& in_array( $scope, $source['scopes'] ?? [], true );
	}

	public function get_browser_projection(): array {
		$sources = [];
		foreach ( $this->sources as $source_id => $source ) {
			$sources[] = [
				'id'                  => $source_id,
				'scopes'              => array_values( $source['scopes'] ),
				'requiresWooCommerce' => $source['requiresWooCommerce'],
				'sourcePolicy'        => $source['sourcePolicy'],
				'selectionCardinality' => $source['selectionCardinality'],
				'inputCardinality'    => $source['inputCardinality'],
			];
		}

		return [
			'id'                => $this->id,
			'sources'           => $sources,
			'recipe'            => $this->recipe_schema,
			'outputCardinality' => $this->output_cardinality,
		];
	}

	/**
	 * Fail during development if a built-in definition loses a frozen invariant.
	 *
	 * @param string               $id                 Workflow identifier.
	 * @param array<string, array> $sources            Source matrix.
	 * @param array                $recipe_schema      Recipe metadata.
	 * @param array                $output_cardinality Output limits.
	 */
	private function assert_valid_definition( string $id, array $sources, array $recipe_schema, array $output_cardinality ): void {
		if ( ! in_array( $id, [ 'product_images', 'campaign_set', 'increase_resolution' ], true ) ) {
			throw new \InvalidArgumentException( 'Unsupported Fotogenic workflow definition.' );
		}
		if ( empty( $sources ) ) {
			throw new \InvalidArgumentException( 'A workflow definition requires at least one source.' );
		}

		foreach ( $sources as $source_id => $source ) {
			$scopes = is_array( $source['scopes'] ?? null ) ? $source['scopes'] : [];
			if (
				! in_array( $source_id, [ 'product', 'media', 'post', 'page' ], true )
				|| ! is_array( $source )
				|| empty( $scopes )
				|| ! fotogenic_ai_array_is_list( $scopes )
				|| count( $scopes ) !== count( array_unique( $scopes ) )
				|| ! empty( array_diff( $scopes, [ 'single', 'bulk' ] ) )
				|| 'single' !== $scopes[0]
				|| ! is_bool( $source['requiresWooCommerce'] ?? null )
				|| ! is_array( $source['sourcePolicy'] ?? null )
				|| ! is_array( $source['sourcePolicy']['bulkModes'] ?? null )
				|| ! is_array( $source['selectionCardinality'] ?? null )
				|| ! is_array( $source['inputCardinality'] ?? null )
			) {
				throw new \InvalidArgumentException( 'Invalid Fotogenic workflow source definition.' );
			}
			$expected_input_scopes = array_map(
				static fn( string $scope ): string => 'bulk' === $scope ? 'bulkItem' : $scope,
				$scopes
			);
			if (
				$scopes !== array_keys( $source['selectionCardinality'] )
				|| $expected_input_scopes !== array_keys( $source['inputCardinality'] )
			) {
				throw new \InvalidArgumentException( 'Unsupported scope cardinality was declared.' );
			}
			$bulk_modes = $source['sourcePolicy']['bulkModes'];
			if (
				count( $bulk_modes ) !== count( array_unique( $bulk_modes ) )
				|| ! empty( array_diff( $bulk_modes, [ 'featured', 'featured_gallery', 'gallery_only' ] ) )
				|| ( 'product' !== $source_id && ! empty( $bulk_modes ) )
			) {
				throw new \InvalidArgumentException( 'Invalid Fotogenic source policy definition.' );
			}
			foreach ( $scopes as $scope ) {
				$cardinality = $source['selectionCardinality'][ $scope ] ?? null;
				if (
					! is_array( $cardinality )
					|| ! is_int( $cardinality['minimum'] ?? null )
					|| ! is_int( $cardinality['maximum'] ?? null )
					|| $cardinality['minimum'] < 1
					|| $cardinality['maximum'] < $cardinality['minimum']
				) {
					throw new \InvalidArgumentException( 'Invalid Fotogenic workflow selection cardinality.' );
				}
			}
		}

		if (
			'object' !== ( $recipe_schema['type'] ?? null )
			|| false !== ( $recipe_schema['additionalProperties'] ?? null )
			|| ! is_array( $recipe_schema['fields'] ?? null )
		) {
			throw new \InvalidArgumentException( 'Invalid Fotogenic workflow recipe definition.' );
		}

		$minimum = $output_cardinality['minimum'] ?? null;
		$maximum = $output_cardinality['maximum'] ?? null;
		if ( ! is_int( $minimum ) || ! is_int( $maximum ) || $minimum < 1 || $maximum < $minimum ) {
			throw new \InvalidArgumentException( 'Invalid Fotogenic workflow output cardinality.' );
		}
	}
}
