<?php

namespace Fotogenic_AI;

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

/**
 * Immutable Campaign output authority captured from the admitted start.
 */
final class Generate_Campaign_Admission {
	public const FINALIZE_META_KEY = '__campaign_admission';
	public const FINALIZE_META_OUTPUT_ERRORS_KEY = '__campaign_output_errors';

	/**
	 * Normalize the exact server-owned admission shape.
	 *
	 * @param mixed $value Candidate admission value.
	 * @return array{accepted_output_count:int,outputs:array<int,array{output_id:string,output_index:int,job_id:string,status:string}>}
	 */
	public static function normalize( $value ): array {
		if (
			! is_array( $value )
			|| [ 'accepted_output_count', 'outputs' ] !== array_keys( $value )
			|| ! is_int( $value['accepted_output_count'] )
				|| $value['accepted_output_count'] < 2
			|| $value['accepted_output_count'] > 6
			|| ! is_array( $value['outputs'] )
			|| ! fotogenic_ai_array_is_list( $value['outputs'] )
			|| count( $value['outputs'] ) !== $value['accepted_output_count']
		) {
			return [];
		}

		$outputs      = [];
		$seen_ids     = [];
		$seen_indexes = [];
		foreach ( $value['outputs'] as $position => $output ) {
			if (
				! is_array( $output )
				|| [ 'output_id', 'output_index', 'job_id', 'status' ] !== array_keys( $output )
					|| '' === Upstream_Identifier::normalize( $output['output_id'] ?? null )
				|| ! is_int( $output['output_index'] )
				|| $position !== $output['output_index']
					|| '' === Upstream_Identifier::normalize( $output['job_id'] ?? null )
				|| 'processing' !== ( $output['status'] ?? null )
				|| isset( $seen_ids[ $output['output_id'] ] )
				|| isset( $seen_indexes[ $output['output_index'] ] )
			) {
				return [];
			}

			$seen_ids[ $output['output_id'] ]         = true;
			$seen_indexes[ $output['output_index'] ] = true;
			$outputs[] = [
				'output_id'    => $output['output_id'],
				'output_index' => $output['output_index'],
				'job_id'       => $output['job_id'],
				'status'       => 'processing',
			];
		}

		return [
			'accepted_output_count' => $value['accepted_output_count'],
			'outputs'                => $outputs,
		];
	}

	/**
	 * Read Campaign admission authority from durable finalization metadata.
	 *
	 * @param mixed $finalize_meta Encoded or decoded finalization metadata.
	 * @return array{accepted_output_count:int,outputs:array<int,array{output_id:string,output_index:int,job_id:string,status:string}>}
	 */
	public static function from_finalize_meta( $finalize_meta ): array {
		if ( is_string( $finalize_meta ) ) {
			$decoded       = json_decode( $finalize_meta, true );
			$finalize_meta = is_array( $decoded ) ? $decoded : [];
		}
		if ( ! is_array( $finalize_meta ) ) {
			return [];
		}

		return self::normalize( $finalize_meta[ self::FINALIZE_META_KEY ] ?? null );
	}

	/**
	 * Carry immutable admission evidence through a metadata projection write.
	 *
	 * @param mixed $source_finalize_meta Existing durable metadata.
	 */
	public static function carry_forward( array $target, $source_finalize_meta ): array {
		$admission = self::from_finalize_meta( $source_finalize_meta );
		if ( ! empty( $admission ) ) {
			$target[ self::FINALIZE_META_KEY ] = $admission;
		}

		return $target;
	}

	/**
	 * Normalize failed-output evidence only when every row is bound to the
	 * immutable Campaign admission identity.
	 *
	 * @param mixed $value     Candidate output-error rows.
	 * @param mixed $admission Immutable Campaign admission.
	 * @return array<int,array{output_id:string,output_index:int,job_id:string,error_code:string}>
	 */
	public static function normalize_output_errors( $value, $admission ): array {
		$admission = self::normalize( $admission );
		if ( empty( $admission ) || ! is_array( $value ) || ! fotogenic_ai_array_is_list( $value ) ) {
			return [];
		}

		$errors       = [];
		$seen_indexes = [];
		foreach ( $value as $error ) {
			if (
				! is_array( $error )
				|| [ 'output_id', 'output_index', 'job_id', 'error_code' ] !== array_keys( $error )
				|| ! is_int( $error['output_index'] )
				|| ! is_string( $error['error_code'] )
			) {
				return [];
			}
			$output_index = $error['output_index'];
			$admitted     = $admission['outputs'][ $output_index ] ?? null;
			$error_code   = substr( sanitize_key( $error['error_code'] ), 0, 64 );
			if (
				! is_array( $admitted )
				|| isset( $seen_indexes[ $output_index ] )
				|| ! is_string( $error['output_id'] )
				|| ! is_string( $error['job_id'] )
				|| ! hash_equals( (string) $admitted['output_id'], $error['output_id'] )
				|| ! hash_equals( (string) $admitted['job_id'], $error['job_id'] )
				|| '' === $error_code
			) {
				return [];
			}

			$seen_indexes[ $output_index ] = true;
			$errors[ $output_index ] = [
				'output_id'    => $error['output_id'],
				'output_index' => $output_index,
				'job_id'       => $error['job_id'],
				'error_code'   => $error_code,
			];
		}

		ksort( $errors, SORT_NUMERIC );
		return array_values( $errors );
	}

	/** @return array<int,array{output_id:string,output_index:int,job_id:string,error_code:string}> */
	public static function output_errors_from_finalize_meta( $finalize_meta ): array {
		if ( is_string( $finalize_meta ) ) {
			$decoded       = json_decode( $finalize_meta, true );
			$finalize_meta = is_array( $decoded ) ? $decoded : [];
		}
		if ( ! is_array( $finalize_meta ) ) {
			return [];
		}

		return self::normalize_output_errors(
			$finalize_meta[ self::FINALIZE_META_OUTPUT_ERRORS_KEY ] ?? null,
			$finalize_meta[ self::FINALIZE_META_KEY ] ?? null
		);
	}

	/**
	 * Persist output errors beside the same immutable admission that validates
	 * them. Invalid or empty evidence is removed rather than reinterpreted.
	 */
	public static function with_output_errors( array $target, $output_errors ): array {
		$normalized = self::normalize_output_errors(
			$output_errors,
			$target[ self::FINALIZE_META_KEY ] ?? null
		);
		if ( empty( $normalized ) ) {
			unset( $target[ self::FINALIZE_META_OUTPUT_ERRORS_KEY ] );
		} else {
			$target[ self::FINALIZE_META_OUTPUT_ERRORS_KEY ] = $normalized;
		}

		return $target;
	}

	/** @param array<int,array{output_id:string,output_index:int,job_id:string,error_code:string}> $output_errors */
	public static function primary_output_error_code( array $output_errors, $admission ): string {
		$normalized = self::normalize_output_errors( $output_errors, $admission );
		return (string) ( $normalized[0]['error_code'] ?? '' );
	}
}
