<?php

namespace Fotogenic_AI;

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

/**
 * Builds the final Campaign Set multipart boundary from normalized data.
 */
final class Campaign_Set_Multipart_Builder {
	private const MIN_OUTPUTS = 2;
	private const MAX_OUTPUTS = 6;
	private const MIN_PRIMARY = 1;
	private const MAX_PRIMARY = 10;

	private const ALLOWED_INPUTS = [
		'recipe',
		'product_images',
		'subject_image',
		'environment_image',
	];

	private const ALLOWED_RECIPE_FIELDS = [
		'version',
		'count',
		'quality',
		'customInstructions',
		'style',
		'aspectRatio',
		'subjectAttachmentId',
		'environmentAttachmentId',
	];

	/**
	 * @param array $command Already-normalized recipe data and resolved file descriptors.
	 * @return array{fields: array, files: array}|\WP_Error
	 */
	public function build( array $command ) {
		$command = Workflow_Contract_Guard::finalize( $command );
		if ( is_wp_error( $command ) ) {
			return $command;
		}

		$unknown = array_values( array_diff( array_keys( $command ), self::ALLOWED_INPUTS ) );
		if ( ! empty( $unknown ) ) {
			return $this->invalid_payload( $unknown );
		}

		$recipe = $command['recipe'] ?? null;
		if ( ! is_array( $recipe ) || fotogenic_ai_array_is_list( $recipe ) ) {
			return $this->invalid_payload( [ 'recipe' ] );
		}
		$unknown_recipe = array_values( array_diff( array_keys( $recipe ), self::ALLOWED_RECIPE_FIELDS ) );
		if ( ! empty( $unknown_recipe ) ) {
			return $this->invalid_payload( $unknown_recipe );
		}

		$count = $recipe['count'] ?? null;
		if ( ! is_int( $count ) || $count < self::MIN_OUTPUTS || $count > self::MAX_OUTPUTS ) {
			return $this->invalid_payload( [ 'count' ] );
		}
		$quality = $recipe['quality'] ?? null;
		if ( ! is_string( $quality ) || ! Workflow_Quality_Catalog::supports( 'campaign_set', $quality ) ) {
			return $this->invalid_payload( [ 'quality' ] );
		}

		$product_images = $command['product_images'] ?? null;
		if ( ! $this->is_file_list( $product_images, self::MIN_PRIMARY, self::MAX_PRIMARY ) ) {
			return $this->invalid_inputs();
		}

		$subject_image = $this->optional_file( $command['subject_image'] ?? null );
		if ( is_wp_error( $subject_image ) ) {
			return $subject_image;
		}
		$environment_image = $this->optional_file( $command['environment_image'] ?? null );
		if ( is_wp_error( $environment_image ) ) {
			return $environment_image;
		}

		$fields = [
			'count'   => (string) $count,
			'quality' => $quality,
		];
		foreach ( [ 'customInstructions', 'style', 'aspectRatio' ] as $key ) {
			if ( ! array_key_exists( $key, $recipe ) ) {
				continue;
			}
			if ( ! is_string( $recipe[ $key ] ) ) {
				return $this->invalid_payload( [ $key ] );
			}
			$value = trim( $recipe[ $key ] );
			if ( '' !== $value ) {
				$fields[ $key ] = $value;
			}
		}
		$files = [ 'productImages' => $product_images ];
		if ( null !== $subject_image ) {
			$files['subjectImage'] = $subject_image;
		}
		if ( null !== $environment_image ) {
			$files['environmentImage'] = $environment_image;
		}

		return [
			'fields' => $fields,
			'files'  => $files,
		];
	}

	/**
	 * @param mixed $value Candidate file list.
	 */
	private function is_file_list( $value, int $minimum, int $maximum ): bool {
		if ( ! is_array( $value ) || ! fotogenic_ai_array_is_list( $value ) ) {
			return false;
		}
		$count = count( $value );
		if ( $count < $minimum || $count > $maximum ) {
			return false;
		}

		foreach ( $value as $descriptor ) {
			if ( ! $this->is_file_descriptor( $descriptor ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * @param mixed $value Optional single file descriptor.
	 * @return array|null|\WP_Error
	 */
	private function optional_file( $value ) {
		if ( null === $value ) {
			return null;
		}
		if ( ! $this->is_file_descriptor( $value ) ) {
			return $this->invalid_inputs();
		}

		return $value;
	}

	/**
	 * @param mixed $value Resolved file descriptor.
	 */
	private function is_file_descriptor( $value ): bool {
		return is_array( $value )
			&& isset( $value['path'], $value['filename'], $value['mime'], $value['bytes'] );
	}

	/**
	 * @param string[] $fields Invalid internal field names.
	 */
	private function invalid_payload( array $fields = [] ): \WP_Error {
		return new \WP_Error(
			'fotogenic_ai_invalid_campaign_multipart',
			__( 'The Campaign Set request could not be prepared safely.', 'fotogenic-ai' ),
			[
				'status' => 400,
				'fields' => $fields,
			]
		);
	}

	private function invalid_inputs(): \WP_Error {
		return new \WP_Error(
			'fotogenic_ai_invalid_campaign_images',
			__( 'Campaign Set requires one to ten resolved primary images.', 'fotogenic-ai' ),
			[ 'status' => 400 ]
		);
	}
}
