<?php

namespace Fotogenic_AI;

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

final class Service_Generate_Poll {
    private const FINALIZE_META_TERMINAL_ERRORS_KEY = '__terminal_errors';
	private const FINALIZE_META_TERMINAL_OUTPUT_COUNTS_KEY = '__terminal_output_counts';
    private const FINALIZE_CONCURRENCY = 1;
    private const FINALIZE_LOCK_TTL = 600;
    private const WORKFLOWS = [ 'product_images', 'campaign_set', 'increase_resolution' ];
    private const TARGETS = [ 'product', 'media', 'post', 'page' ];

	public function poll( int $user_id, array $command ) {
        $job_manager = Job_Manager::instance();
		$requested_job_ids   = $this->normalize_job_ids( $command['jobIds'] ?? null );
		$requested_workflow  = sanitize_key( (string) ( $command['workflow'] ?? '' ) );
		$requested_request_id = strtolower( trim( (string) ( $command['requestId'] ?? '' ) ) );
		$bulk_run_uuid       = (string) ( $command['bulkRunUuid'] ?? '' );
		$bulk_item_id        = (int) ( $command['bulkItemId'] ?? 0 );
        $bulk_job_key = ( $bulk_run_uuid !== '' && $bulk_item_id > 0 ) ? 'bulk:' . $bulk_run_uuid . ':' . $bulk_item_id : '';
        if ( $bulk_job_key !== '' ) {
            $active_job = $this->build_bulk_active_job( $bulk_run_uuid, $bulk_item_id, $user_id );
            if ( ! $active_job ) {
                $active_job = $job_manager->get_active_job_by_key( $user_id, $bulk_job_key );
            }
        } else {
            $active_job = ! empty( $requested_job_ids )
                ? $job_manager->get_active_job_by_job_ids( $user_id, $requested_job_ids )
                : null;
            if ( ! $active_job ) {
				$target     = sanitize_key( (string) ( $command['sourceType'] ?? '' ) );
				$subject_id = (int) ( $command['subjectId'] ?? 0 );
                if ( in_array( $requested_workflow, [ 'campaign_set', 'increase_resolution' ], true ) ) {
                    $active_job = $job_manager->get_active_workflow_job(
                        $user_id,
                        $requested_workflow,
                        $target,
                        $subject_id,
                        $requested_request_id
                    );
                } else {
                    $subject_job_key = $job_manager->get_subject_job_key( $target, $subject_id );
                    if ( '' !== $subject_job_key ) {
                        $active_job = $job_manager->get_active_job_by_key( $user_id, $subject_job_key );
                    }
                }
            }
        }
        $logger = Logger::instance();
        $can_log = $logger instanceof Logger;

        try {
            if ( ! $active_job ) {
				$recovered_response = $this->recover_run_response( $command, $user_id, $requested_job_ids );
                if ( $recovered_response instanceof \WP_REST_Response ) {
                    return $recovered_response;
                }
				return $this->format_poll_error_response( $command, 'invalid_request', 403 );
            }

            if ( $bulk_job_key !== '' ) {
                $tracked_active_job = $job_manager->get_active_job_by_key( $user_id, $bulk_job_key );
                if ( is_array( $tracked_active_job ) ) {
                    $active_job = array_merge( $active_job, $tracked_active_job );
                } else {
                    $bulk_job_ids = $this->normalize_job_ids( $active_job['job_ids'] ?? [] );
                    if ( ! empty( $bulk_job_ids ) ) {
                        $job_manager->set_active_job( $user_id, (string) $bulk_job_ids[0], $active_job, $bulk_job_key );
                    }
                }
            }

			if ( ! $this->active_job_context_is_owned( $command, $active_job, $user_id, $requested_job_ids ) ) {
				return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
            }

            $active_job_key = $bulk_job_key;
            $active_job_ids = $this->normalize_job_ids( $active_job['job_ids'] ?? [ (string) ( $active_job['job_id'] ?? '' ) ] );
            if ( empty( $active_job_ids ) ) {
				return $this->format_poll_error_response( $command, 'invalid_request', 403 );
            }

            if ( empty( $requested_job_ids ) ) {
                $requested_job_ids = $active_job_ids;
            }
            foreach ( $requested_job_ids as $job_id ) {
                if ( ! in_array( $job_id, $active_job_ids, true ) ) {
					return $this->format_poll_error_response( $command, 'invalid_request', 403 );
                }
            }

            $run_id = (string) ( $active_job['run_id'] ?? '' );
            $workflow_response = array_merge(
                $this->get_workflow_response_context( $active_job ),
                $this->get_owned_result_projection( $run_id, $user_id )
            );
            $poll_started = ! empty( $active_job['poll_started'] );
            $poll_count = (int) ( $active_job['poll_count'] ?? 0 ) + 1;
            if ( $can_log && $logger->enabled() && ! $poll_started ) {
                $logger->info( 'poll_start', [
                    'job_ids' => $requested_job_ids,
                    'run_id' => $run_id,
                ] );
                $job_manager->update_active_job( $user_id, [ 'poll_started' => true ], $active_job_key, $requested_job_ids );
            }

            $job_response = Api_Client::instance()->request(
                'POST',
                'jobs_batch',
                [ 'jobIds' => $requested_job_ids ],
                [ 'run_id' => $run_id ]
            );
            if ( is_wp_error( $job_response ) ) {
                if ( $can_log && $logger->enabled() ) {
                    $logger->error( 'poll_failed', [
                        'job_ids'    => $requested_job_ids,
                        'error_code' => sanitize_key( (string) $job_response->get_error_code() ),
                        'run_id'     => $run_id,
                    ] );
                }
				return $this->format_poll_error_response( $command, 'request_failed', 500, $requested_job_ids, 'REQUEST_FAILED' );
            }

            $elapsed = time() - (int) ( $active_job['started_at'] ?? time() );
			$batch = Generate_Batch_Response_Parser::parse( $job_response, $requested_job_ids );
			if ( is_wp_error( $batch ) ) {
                if ( $can_log && $logger->enabled() ) {
                    $logger->warn( 'poll_invalid_response', [
                        'job_ids' => $requested_job_ids,
                        'run_id'  => $run_id,
                    ] );
                }
				return $this->format_poll_error_response( $command, 'invalid_response', 500, $requested_job_ids, 'INVALID_RESPONSE' );
			}
			$job_statuses = $batch['statuses'];
			$job_errors   = $batch['errors'];
            $status_signature = implode( '|', array_values( $job_statuses ) );
            $last_status = (string) ( $active_job['last_status'] ?? '' );
            if ( $can_log && $logger->enabled() && ( $status_signature !== $last_status || 0 === ( $poll_count % 3 ) ) ) {
                $logger->info( 'poll_status', [
                    'job_ids' => $requested_job_ids,
                    'status'  => $job_statuses,
                    'elapsed' => $elapsed,
                    'run_id'  => $run_id,
                    'count'   => $poll_count,
                ] );
            }
            $job_manager->update_active_job( $user_id, [
                'poll_count' => $poll_count,
                'last_status'=> $status_signature,
            ], $active_job_key, $requested_job_ids );

            $has_processing = false;
            foreach ( $job_statuses as $status ) {
				if ( ! in_array( $status, [ 'completed', 'failed', 'not_found' ], true ) ) {
                    $has_processing = true;
                    break;
                }
            }

            $timeout_seconds = $logger->get_generation_timeout_seconds( (string) ( $active_job['workflow'] ?? '' ) );
            if ( $has_processing && $elapsed > $timeout_seconds ) {
                $timeout_lock_key = $this->get_run_lock_key( $run_id );
                $timeout_lock_token = Background_Lock::instance()->acquire( $timeout_lock_key, self::FINALIZE_LOCK_TTL );
                if ( false === $timeout_lock_token ) {
                    $timeout_map = is_array( $active_job['attachments_map'] ?? null )
						? $this->filter_attachment_map_for_jobs( $active_job['attachments_map'], $requested_job_ids, (string) ( $active_job['workflow'] ?? '' ) )
                        : [];
                    return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $timeout_map, [], '', $run_id, $active_job, $workflow_response );
                }
                $timeout_committed = '' === $run_id;
                try {
					$terminal_response = $this->recover_terminal_run_response( $command, $user_id, $requested_job_ids, $run_id, $timeout_lock_token );
                    if ( $terminal_response instanceof \WP_REST_Response ) {
                        return $terminal_response;
                    }
                    if ( $can_log && $logger->enabled() ) {
                        $logger->warn( 'poll_timeout', [
                            'job_ids' => $requested_job_ids,
                            'elapsed' => $elapsed,
                            'run_id'  => $run_id,
                        ] );
                    }
                    if ( '' !== $run_id && Background_Lock::instance()->renew( $timeout_lock_key, $timeout_lock_token, self::FINALIZE_LOCK_TTL ) ) {
						$timeout_committed = false !== Logger::instance()->finalize_run( $run_id, 'timeout', [
                            'error_code'    => 'timeout',
                            'error_message' => __( 'Generation timed out.', 'fotogenic-ai' ),
                        ] );
                    }
                } finally {
                    Background_Lock::instance()->release( $timeout_lock_key, $timeout_lock_token );
                }
                if ( ! $timeout_committed ) {
                    $timeout_map = is_array( $active_job['attachments_map'] ?? null )
						? $this->filter_attachment_map_for_jobs( $active_job['attachments_map'], $requested_job_ids, (string) ( $active_job['workflow'] ?? '' ) )
                        : [];
                    return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $timeout_map, [], '', $run_id, $active_job, $workflow_response );
                }
                return rest_ensure_response( array_merge( [
                    'status'      => 'timeout',
                    'jobIds'      => $requested_job_ids,
					'finalizationPending' => true,
                    'errorCode'   => 'timeout',
                    'message'     => __( 'Generation timed out.', 'fotogenic-ai' ),
                    'runId'       => $run_id,
                    'sourceType'  => (string) ( $active_job['run_target'] ?? '' ),
                    'subjectId'   => (int) ( $active_job['subject_id'] ?? 0 ),
                ], $workflow_response ) );
            }

            $run_lock_key = $this->get_run_lock_key( $run_id );
            $run_lock_token = Background_Lock::instance()->acquire( $run_lock_key, self::FINALIZE_LOCK_TTL );
            if ( false === $run_lock_token ) {
                $locked_map = is_array( $active_job['attachments_map'] ?? null )
					? $this->filter_attachment_map_for_jobs( $active_job['attachments_map'], $requested_job_ids, (string) ( $active_job['workflow'] ?? '' ) )
                    : [];
                return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $locked_map, [], '', $run_id, $active_job, $workflow_response );
            }

            try {
				$terminal_response = $this->recover_terminal_run_response( $command, $user_id, $requested_job_ids, $run_id, $run_lock_token );
                if ( $terminal_response instanceof \WP_REST_Response ) {
                    return $terminal_response;
                }
                $completed_job_ids = array_values(
                    array_intersect(
                        $this->normalize_job_ids( $active_job['completed_job_ids'] ?? [] ),
                        $active_job_ids
                    )
                );
                $attachment_ids = [];
                $attachments_map = is_array( $active_job['attachments_map'] ?? null ) ? $active_job['attachments_map'] : [];
                $asset_fetch_attempts = is_array( $active_job['asset_fetch_attempts'] ?? null ) ? $active_job['asset_fetch_attempts'] : [];
                $asset_fetch_first = is_array( $active_job['asset_fetch_first'] ?? null ) ? $active_job['asset_fetch_first'] : [];
                $failed_job_ids = [];
                $failed_error_code = '';
                $terminal_errors = [];
				$terminal_output_counts = [];
				$campaign_output_errors = [];
				$campaign_output_errors_observed = false;
                $pending_asset_fetch = false;
                $processed_one = false;
                $job_finalizer = new Generate_Job_Finalizer();
                $finalizer_context = array_merge(
                    $active_job,
                    [
                        'run_id'  => $run_id,
                        'user_id' => $user_id,
                    ]
                );
                $expected_output_count = max( 1, (int) ( $active_job['expected_output_count'] ?? count( $requested_job_ids ) ) );
				$attachments_map = $this->filter_attachment_map_for_jobs( $attachments_map, $active_job_ids, (string) ( $active_job['workflow'] ?? '' ) );
				$workflow = sanitize_key( (string) ( $active_job['workflow'] ?? '' ) );

                foreach ( $job_statuses as $job_id => $status ) {
					if ( in_array( $status, [ 'failed', 'not_found' ], true ) && ! ( 'campaign_set' === $workflow && 'failed' === $status ) ) {
                        $failed_job_ids[] = $job_id;
						$terminal_error_code = substr( sanitize_key( (string) ( $job_errors[ $job_id ]['errorCode'] ?? $status ) ), 0, 64 );
                        if ( '' === $terminal_error_code ) {
                            $terminal_error_code = 'job_failed';
                        }
                        $terminal_errors[ $job_id ] = $terminal_error_code;
                        if ( '' === $failed_error_code ) {
                            $failed_error_code = $terminal_error_code;
                        }
                        continue;
                    }
					if ( 'completed' !== $status && ! ( 'campaign_set' === $workflow && 'failed' === $status ) ) {
                        continue;
                    }
					if ( in_array( $job_id, $completed_job_ids, true ) && Generate_Output_Map::has_job_outputs( $attachments_map, $job_id ) ) {
						continue;
					}
                    if ( $processed_one ) {
						if ( 'campaign_set' === $workflow ) {
							$pending_asset_fetch = true;
							continue;
						}
						$existing_outputs = $job_finalizer->find_attachments_for_job( $job_id, $finalizer_context );
						if ( ! empty( $existing_outputs ) ) {
							foreach ( Generate_Output_Map::attachment_ids( $existing_outputs ) as $existing_attachment_id ) {
								if ( ! $this->attachment_is_owned_for_workflow( $existing_attachment_id, $user_id ) ) {
								return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
								}
                            }
							$attachments_map = Generate_Output_Map::replace_job_slice( $attachments_map, $job_id, $existing_outputs, $active_job_ids, $workflow );
							if ( Generate_Output_Map::has_job_outputs( $attachments_map, $job_id ) ) {
								$completed_job_ids[] = $job_id;
							} else {
								$pending_asset_fetch = true;
							}
						} else {
							$pending_asset_fetch = true;
                        }
                        continue;
                    }
					if ( 'campaign_set' !== $workflow && in_array( $job_id, $completed_job_ids, true ) ) {
						$existing_outputs = $job_finalizer->find_attachments_for_job( $job_id, $finalizer_context );
						if ( ! empty( $existing_outputs ) ) {
							foreach ( Generate_Output_Map::attachment_ids( $existing_outputs ) as $existing_attachment_id ) {
								if ( ! $this->attachment_is_owned_for_workflow( $existing_attachment_id, $user_id ) ) {
								return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
								}
                            }
							$attachments_map = Generate_Output_Map::replace_job_slice( $attachments_map, $job_id, $existing_outputs, $active_job_ids, $workflow );
							if ( Generate_Output_Map::has_job_outputs( $attachments_map, $job_id ) ) {
								continue;
							}
							$completed_job_ids = array_values( array_diff( $completed_job_ids, [ $job_id ] ) );
                        }
                    }
					$existing_outputs = 'campaign_set' === $workflow ? [] : $job_finalizer->find_attachments_for_job( $job_id, $finalizer_context );
					if ( ! empty( $existing_outputs ) ) {
						foreach ( Generate_Output_Map::attachment_ids( $existing_outputs ) as $existing_attachment_id ) {
							if ( ! $this->attachment_is_owned_for_workflow( $existing_attachment_id, $user_id ) ) {
							return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
							}
                        }
						$attachments_map = Generate_Output_Map::replace_job_slice( $attachments_map, $job_id, $existing_outputs, $active_job_ids, $workflow );
						if ( Generate_Output_Map::has_job_outputs( $attachments_map, $job_id ) ) {
							$completed_job_ids[] = $job_id;
							continue;
						}
                    }
                    $job_lock_token = $this->claim_job_processing( $job_id );
                    if ( false === $job_lock_token ) {
                        $pending_asset_fetch = true;
                        continue;
                    }
                    $bulk_finalize_token = false;
                    if ( $bulk_run_uuid !== '' ) {
                        $bulk_finalize_token = $this->claim_bulk_finalize_slot( $bulk_run_uuid );
                        if ( false === $bulk_finalize_token ) {
                            $this->release_job_processing( $job_id, $job_lock_token );
                            $pending_asset_fetch = true;
                            continue;
                        }
                    }
                    if ( ! Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL ) ) {
                        $this->release_job_processing( $job_id, $job_lock_token );
                        if ( false !== $bulk_finalize_token ) {
                            $this->release_bulk_finalize_slot( $bulk_run_uuid, $bulk_finalize_token );
                        }
                        return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                    }
                    $leases_valid = false;
                    try {
                        $result = $job_finalizer->finalize_job( $job_id, $finalizer_context );
                        $leases_valid = Background_Lock::instance()->renew( $this->get_job_lock_key( $job_id ), $job_lock_token, self::FINALIZE_LOCK_TTL )
                            && ( false === $bulk_finalize_token || Background_Lock::instance()->renew( $this->get_bulk_lock_key( $bulk_run_uuid ), $bulk_finalize_token, self::FINALIZE_LOCK_TTL ) )
                            && Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL );
                    } finally {
                        $this->release_job_processing( $job_id, $job_lock_token );
                        if ( false !== $bulk_finalize_token ) {
                            $this->release_bulk_finalize_slot( $bulk_run_uuid, $bulk_finalize_token );
                        }
                    }
                    if ( ! $leases_valid ) {
                        return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                    }
                    $processed_one = true;
                    $status_result = (string) ( $result['status'] ?? '' );
					$counts = $this->normalize_terminal_output_counts( $result['terminal_output_counts'] ?? [] );
					if ( ! empty( $counts ) ) {
						$terminal_output_counts[ $job_id ] = $counts;
					}
					if ( array_key_exists( 'terminal_output_errors', $result ) ) {
						$campaign_output_errors = is_array( $result['terminal_output_errors'] )
							? $result['terminal_output_errors']
							: [];
						$campaign_output_errors_observed = true;
						$output_error_code = substr( sanitize_key( (string) ( $campaign_output_errors[0]['error_code'] ?? '' ) ), 0, 64 );
						if ( '' === $failed_error_code && '' !== $output_error_code ) {
							$failed_error_code = $output_error_code;
						}
					}
					$result_map = Generate_Output_Map::normalize( $result['attachments_map'] ?? [], $active_job_ids, $workflow );
					if ( ! empty( $result_map ) ) {
						$attachments_map = Generate_Output_Map::replace_job_slice( $attachments_map, $job_id, $result_map, $active_job_ids, $workflow );
					}
                    if ( 'completed' === $status_result && Generate_Output_Map::has_job_outputs( $attachments_map, $job_id ) ) {
                        $completed_job_ids[] = $job_id;
                        continue;
                    }
                    if ( 'not_ready' === $status_result ) {
                        $attempts = (int) ( $asset_fetch_attempts[ $job_id ] ?? 0 ) + 1;
                        $asset_fetch_attempts[ $job_id ] = $attempts;
						$retry_error_code = strtoupper( substr( sanitize_key( (string) ( $result['error_code'] ?? '' ) ), 0, 64 ) );
                        if ( empty( $asset_fetch_first[ $job_id ] ) ) {
                            $asset_fetch_first[ $job_id ] = time();
                        }
                        if ( $attempts >= $job_finalizer->get_max_asset_attempts( (string) ( $active_job['workflow'] ?? '' ) ) ) {
                            if ( $can_log && $logger->enabled() ) {
                                $logger->warn( 'job_missing_image', [
                                    'job_id'  => $job_id,
                                    'run_id'  => $run_id,
                                ] );
                            }
                            $failed_job_ids[] = $job_id;
                            $terminal_error_code = in_array( $retry_error_code, [ '', 'JOB_NOT_READY', 'MISSING_IMAGE_URL' ], true )
                                ? 'asset_not_ready_timeout'
								: substr( sanitize_key( $retry_error_code ), 0, 64 );
                            $terminal_errors[ $job_id ] = $terminal_error_code;
                            if ( '' === $failed_error_code ) {
                                $failed_error_code = $terminal_error_code;
                            }
                        } else {
                            $pending_asset_fetch = true;
                        }
                        continue;
                    }
                    if ( $can_log && $logger->enabled() ) {
                        $logger->error( 'job_save_failed', [
                            'job_id'    => $job_id,
                            'error_code'=> sanitize_key( (string) ( $result['error_code'] ?? 'failed' ) ),
                            'run_id'    => $run_id,
                        ] );
                    }
                    $failed_job_ids[] = $job_id;
					$terminal_error_code = substr( sanitize_key( (string) ( $result['error_code'] ?? 'job_finalize_failed' ) ), 0, 64 );
                    $terminal_errors[ $job_id ] = '' !== $terminal_error_code ? $terminal_error_code : 'job_finalize_failed';
                    if ( '' === $failed_error_code ) {
                        $failed_error_code = $terminal_errors[ $job_id ];
                    }
                }

                $latest_active = $active_job_key !== ''
                    ? $job_manager->get_active_job_by_key( $user_id, $active_job_key )
                    : $job_manager->get_active_job_by_job_ids( $user_id, $requested_job_ids );
                if ( is_array( $latest_active ) ) {
                    $latest_completed = $this->normalize_job_ids( $latest_active['completed_job_ids'] ?? [] );
                    if ( ! empty( $latest_completed ) ) {
                        $completed_job_ids = array_values(
                            array_unique( array_merge( $completed_job_ids, $latest_completed ) )
                        );
                    }
					$latest_map = is_array( $latest_active['attachments_map'] ?? null )
						? $this->filter_attachment_map_for_jobs( $latest_active['attachments_map'], $active_job_ids, $workflow )
						: [];
					if ( ! empty( $latest_map ) ) {
						$attachments_map = Generate_Output_Map::normalize(
							array_merge( $latest_map, $attachments_map ),
							$active_job_ids,
							$workflow
						);
					}
                }

                $ordered_attachments = $this->build_ordered_attachments( $requested_job_ids, $attachments_map );
                $attachment_ids = array_values(
                    array_unique(
                        array_filter(
                            array_map(
                                static fn( array $attachment ): int => absint( $attachment['attachmentId'] ?? 0 ),
                                $ordered_attachments
                            )
                        )
                    )
                );

                if ( $pending_asset_fetch ) {
                    $has_processing = true;
                }
                if ( ! Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL ) ) {
                    return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                }
                if ( '' !== $run_id ) {
					if ( ! $this->persist_terminal_errors( $run_id, $active_job_ids, $attachments_map, $terminal_errors, $completed_job_ids, $terminal_output_counts, $campaign_output_errors, $campaign_output_errors_observed ) ) {
						return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
					}
                }
				$updated_active_job = $job_manager->update_active_job( $user_id, [
                    'completed_job_ids' => array_values( array_unique( $completed_job_ids ) ),
                    'attachment_ids'    => array_values( array_unique( $attachment_ids ) ),
                    'failed_job_ids'    => array_values( array_unique( $failed_job_ids ) ),
                    'failed_error_code' => $failed_error_code,
                    'attachments_map'   => $attachments_map,
                    'asset_fetch_attempts' => $asset_fetch_attempts,
                    'asset_fetch_first'    => $asset_fetch_first,
                    'final_status'      => $has_processing
                        ? 'processing'
                        : ( ! empty( $failed_job_ids ) || count( $attachment_ids ) !== $expected_output_count ? 'partial' : 'completed' ),
                ], $active_job_key, $requested_job_ids );
				if ( null === $updated_active_job ) {
					return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
				}
                if ( '' !== $run_id ) {
                    if ( ! Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL ) ) {
                        return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                    }
					if ( false === Logger::instance()->update_run( $run_id, [
                        'attachments_map' => wp_json_encode( $attachments_map ),
                        'job_ids' => wp_json_encode( $active_job_ids ),
					] ) ) {
						return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
					}
                }

                if ( ! $has_processing ) {
                    $resolved_job_ids = array_filter(
                        $requested_job_ids,
						function ( string $job_id ) use ( $completed_job_ids, $failed_job_ids ): bool {
							return in_array( $job_id, $completed_job_ids, true )
                                || in_array( $job_id, $failed_job_ids, true );
                        }
                    );
                    if ( count( $resolved_job_ids ) < count( $requested_job_ids ) ) {
                        $has_processing = true;
                    }
                }
                if ( $has_processing ) {
                    if ( ! empty( $failed_job_ids ) && $can_log && $logger->enabled() ) {
                        $logger->warn( 'poll_partial_failure', [
                            'job_ids'        => $requested_job_ids,
                            'failed_job_ids' => $failed_job_ids,
                            'error_code'     => $failed_error_code,
                            'run_id'         => $run_id,
                        ] );
                    }
                    return rest_ensure_response( array_merge( [
                        'status'         => 'processing',
                        'jobIds'         => $requested_job_ids,
                        'jobStatuses'   => $this->format_job_statuses( $job_statuses, $job_errors ),
                        'attachmentsMap' => $attachments_map,
                        'attachments'    => $ordered_attachments,
                        'failedJobIds'   => array_values( array_unique( $failed_job_ids ) ),
                        'errorCode'      => $failed_error_code,
                        'message'        => ! empty( $failed_job_ids ) ? Messages::t( $this->map_api_error_code_to_message_key( $failed_error_code ) ) : '',
                        'runId'          => $run_id,
                        'sourceType'     => (string) ( $active_job['run_target'] ?? '' ),
                        'subjectId'      => (int) ( $active_job['subject_id'] ?? 0 ),
                        'partial'        => ! empty( $failed_job_ids ),
                    ], $workflow_response ) );
                }

                $has_completed = ! empty( $completed_job_ids );
                if ( ! $has_completed ) {
					$finalization_pending = false;
                    if ( $can_log && $logger->enabled() ) {
                        $logger->warn( 'poll_failed_status', [
                            'job_ids' => $requested_job_ids,
                            'status'  => $job_statuses,
                            'elapsed' => $elapsed,
                            'run_id'  => $run_id,
                            'error_code' => $failed_error_code,
                        ] );
                    }
                    if ( '' !== $run_id ) {
                        if ( ! Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL ) ) {
                            return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                        }
                        $message_key = $this->map_api_error_code_to_message_key( $failed_error_code );
						$finalized = Logger::instance()->finalize_run( $run_id, 'failed', [
                            'error_code'    => $failed_error_code ?: 'failed',
                            'error_message' => Messages::t( $message_key ),
                        ] );
						if ( false === $finalized ) {
							return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
						}
						$finalization_pending = ( new Generate_Terminal_Reconciler() )->can_reconcile(
                            Logger::instance()->get_run_row( $run_id ),
                            $requested_job_ids
                        );
                    }
                    if ( '' !== $run_id ) {
                        $job_manager->clear_active_jobs_by_run( $user_id, $run_id );
                    } elseif ( $active_job_key !== '' ) {
                        $job_manager->clear_active_job( $user_id, $active_job_key );
                    } else {
                        $job_manager->clear_active_job_by_job_ids( $user_id, $requested_job_ids );
                    }
                    $message_key = $this->map_api_error_code_to_message_key( $failed_error_code );
	                    return rest_ensure_response(
						array_merge(
							[
								'status'              => 'failed',
								'jobIds'              => $requested_job_ids,
								'jobStatuses'         => $this->format_job_statuses( $job_statuses, $job_errors ),
								'attachmentsMap'      => $attachments_map,
								'attachments'         => $ordered_attachments,
								'failedJobIds'        => array_values( array_unique( $failed_job_ids ) ),
								'errorCode'           => $failed_error_code,
								'message'             => Messages::t( $message_key ),
								'runId'               => $run_id,
								'sourceType'          => (string) ( $active_job['run_target'] ?? '' ),
								'subjectId'           => (int) ( $active_job['subject_id'] ?? 0 ),
								'partial'             => false,
								'finalizationPending' => $finalization_pending,
							],
							$workflow_response,
							$this->get_owned_result_projection( $run_id, $user_id )
						)
	                    );
                }

                $is_partial = ! empty( $failed_job_ids ) || count( $attachment_ids ) !== $expected_output_count;
				$finalization_pending = false;
                if ( $can_log && $logger->enabled() ) {
                    $logger->info( 'poll_completed', [
                        'job_ids'    => $requested_job_ids,
                        'elapsed'    => $elapsed,
                        'run_id'     => $run_id,
                        'jobs_total' => count( $requested_job_ids ),
                        'attachments_unique_count' => count( $attachment_ids ),
                        'failed_job_ids' => $failed_job_ids,
                    ] );
                }
                if ( '' !== $run_id ) {
                    if ( ! Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL ) ) {
                        return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                    }
					if ( false === $logger->update_run( $run_id, [
                        'images_count' => count( $attachment_ids ),
                        'quality'      => (string) ( $active_job['quality'] ?? '' ),
					] ) ) {
						return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
					}
                    $final_status = $is_partial ? 'partial' : 'completed';
                    if ( ! Background_Lock::instance()->renew( $run_lock_key, $run_lock_token, self::FINALIZE_LOCK_TTL ) ) {
                        return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
                    }
					$finalized = $logger->finalize_run( $run_id, $final_status, [
                        'attachment_id' => (int) ( $attachment_ids[0] ?? 0 ),
                        'error_code'    => $is_partial ? ( $failed_error_code ?: 'partial' ) : '',
                    ] );
					if ( false === $finalized ) {
						return $this->build_processing_response( $requested_job_ids, $job_statuses, $job_errors, $attachments_map, $failed_job_ids, $failed_error_code, $run_id, $active_job, $workflow_response );
					}
					$finalization_pending = ( new Generate_Terminal_Reconciler() )->can_reconcile(
                        $logger->get_run_row( $run_id ),
                        $requested_job_ids
                    );
                }

                if ( '' !== $run_id ) {
                    $job_manager->clear_active_jobs_by_run( $user_id, $run_id );
                } elseif ( $active_job_key !== '' ) {
                    $job_manager->clear_active_job( $user_id, $active_job_key );
                } else {
                    $job_manager->clear_active_job_by_job_ids( $user_id, $requested_job_ids );
                }
                return rest_ensure_response(
                    array_merge(
                        [
                            'status'         => $is_partial ? 'partial' : 'completed',
                            'jobIds'         => $requested_job_ids,
                            'jobStatuses'    => $this->format_job_statuses( $job_statuses, $job_errors ),
                            'attachmentsMap' => $attachments_map,
                            'attachments'    => $ordered_attachments,
                            'failedJobIds'   => array_values( array_unique( $failed_job_ids ) ),
                            'errorCode'      => $failed_error_code,
                            'message'        => '',
                            'runId'          => $run_id,
                            'sourceType'     => (string) ( $active_job['run_target'] ?? '' ),
                            'subjectId'      => (int) ( $active_job['subject_id'] ?? 0 ),
                            'partial'        => $is_partial,
							'finalizationPending' => $finalization_pending,
                        ],
                        $workflow_response,
                        $this->get_owned_result_projection( $run_id, $user_id )
                    )
                );
            } finally {
                Background_Lock::instance()->release( $run_lock_key, $run_lock_token );
            }
        } catch ( \Throwable $error ) {
            if ( $can_log && $logger->enabled() ) {
                $logger->error( 'poll_exception', [
                    'job_ids'    => $requested_job_ids,
                    'error_code' => 'internal_error',
                    'run_id'     => (string) ( $active_job['run_id'] ?? '' ),
                ] );
            }
			return $this->format_poll_error_response( $command, 'request_failed', 500, $requested_job_ids, 'INTERNAL_ERROR' );
        }
    }

    private function build_processing_response(
        array $job_ids,
        array $job_statuses,
        array $job_errors,
        array $attachments_map,
        array $failed_job_ids,
        string $error_code,
        string $run_id,
        array $active_job,
        array $workflow_response
    ): \WP_REST_Response {
        return rest_ensure_response(
            array_merge(
                [
                    'status'         => 'processing',
                    'jobIds'         => $job_ids,
                    'jobStatuses'    => $this->format_job_statuses( $job_statuses, $job_errors ),
                    'attachmentsMap' => $attachments_map,
                    'attachments'    => $this->build_ordered_attachments( $job_ids, $attachments_map ),
                    'failedJobIds'   => array_values( array_unique( $failed_job_ids ) ),
                    'errorCode'      => $error_code,
                    'message'        => '',
                    'runId'          => $run_id,
                    'sourceType'     => (string) ( $active_job['run_target'] ?? '' ),
                    'subjectId'      => (int) ( $active_job['subject_id'] ?? 0 ),
                    'partial'        => ! empty( $failed_job_ids ),
                ],
                $workflow_response
            )
        );
    }

    private function format_poll_error_response( array $command, string $message_key, int $status = 400, array $job_ids = [], string $error_code = '' ): \WP_REST_Response {
		$job_ids = $job_ids ?: $this->normalize_job_ids( $command['jobIds'] ?? null );
        $response = rest_ensure_response( [
            'status'        => 'failed',
            'jobIds'        => $job_ids,
            'jobStatuses'   => [],
            'attachmentsMap'=> [],
            'attachments'   => [],
            'failedJobIds'  => [],
            'errorCode'     => '' !== $error_code ? $error_code : strtoupper( $message_key ),
            'message'       => Messages::t( $message_key ),
        ] );
        if ( $response instanceof \WP_REST_Response ) {
            $response->set_status( $status );
        }
        return $response;
    }

    private function active_job_context_is_owned( array $command, array $active_job, int $user_id, array $requested_job_ids ): bool {
        if ( $user_id <= 0 ) {
            return false;
        }
        $workflow = sanitize_key( (string) ( $active_job['workflow'] ?? '' ) );
        if ( ! in_array( $workflow, self::WORKFLOWS, true ) ) {
            return false;
        }
        $active_user_id = (int) ( $active_job['user_id'] ?? 0 );
        if ( $active_user_id > 0 && $active_user_id !== $user_id ) {
            return false;
        }
        if ( 'campaign_set' === $workflow && $active_user_id !== $user_id ) {
            return false;
        }
		if ( ! $this->poll_request_matches_context( $command, $active_job ) ) {
            return false;
        }

        $run_id = trim( (string) ( $active_job['run_id'] ?? '' ) );
        if ( '' === $run_id ) {
            return false;
        }
        $row = Logger::instance()->get_run_row( $run_id );
        if (
            empty( $row )
            || (int) ( $row['user_id'] ?? 0 ) !== $user_id
            || $workflow !== sanitize_key( (string) ( $row['workflow'] ?? '' ) )
            || (string) ( $active_job['run_target'] ?? '' ) !== (string) ( $row['run_target'] ?? '' )
            || (int) ( $active_job['subject_id'] ?? 0 ) !== (int) ( $row['subject_id'] ?? 0 )
			|| ! $this->poll_request_matches_context( $command, $row )
        ) {
            return false;
        }
        if (
            'campaign_set' === $workflow
            && (
                '' === (string) ( $active_job['request_id'] ?? '' )
                || ! hash_equals( (string) ( $row['request_id'] ?? '' ), (string) $active_job['request_id'] )
            )
        ) {
            return false;
        }

        $active_job_ids = $this->normalize_job_ids( $active_job['job_ids'] ?? [ (string) ( $active_job['job_id'] ?? '' ) ] );
        $run_job_ids = $this->normalize_job_ids( $row['job_ids'] ?? [] );
        return ! empty( $active_job_ids )
            && empty( array_diff( $active_job_ids, $run_job_ids ) )
            && empty( array_diff( $requested_job_ids, $run_job_ids ) );
    }

    private function poll_request_matches_context( array $command, array $context ): bool {
		$requested_workflow = sanitize_key( (string) ( $command['workflow'] ?? '' ) );
        $context_workflow = sanitize_key( (string) ( $context['workflow'] ?? '' ) );
        if ( ! in_array( $requested_workflow, self::WORKFLOWS, true ) || $requested_workflow !== $context_workflow ) {
            return false;
        }

		$requested_target = sanitize_key( (string) ( $command['sourceType'] ?? '' ) );
        if ( ! in_array( $requested_target, self::TARGETS, true ) || $requested_target !== (string) ( $context['run_target'] ?? '' ) ) {
            return false;
        }
		if ( (int) ( $command['subjectId'] ?? 0 ) <= 0 || (int) $command['subjectId'] !== (int) ( $context['subject_id'] ?? 0 ) ) {
            return false;
        }

		$requested_run_id = trim( (string) ( $command['runId'] ?? '' ) );
        if ( '' === $requested_run_id || ! hash_equals( $requested_run_id, (string) ( $context['run_id'] ?? '' ) ) ) {
            return false;
        }
		$requested_request_id = strtolower( trim( (string) ( $command['requestId'] ?? '' ) ) );
        if ( ! wp_is_uuid( $requested_request_id ) || ! hash_equals( $requested_request_id, (string) ( $context['request_id'] ?? '' ) ) ) {
            return false;
        }
        return true;
    }

    private function attachment_is_owned_for_workflow( int $attachment_id, int $user_id ): bool {
        $owner_id = (int) get_post_meta( $attachment_id, '_fotogenic_ai_user_id', true );
        return $user_id > 0 && $owner_id === $user_id;
    }

    private function get_workflow_response_context( array $active_job ): array {
        $workflow = sanitize_key( (string) ( $active_job['workflow'] ?? '' ) );
        $source_attachment_ids = $active_job['source_attachment_ids'] ?? [];
        if ( ! is_array( $source_attachment_ids ) ) {
            $source_attachment_ids = [];
        }
        $source_attachment_ids = array_values( array_unique( array_filter( array_map( 'absint', $source_attachment_ids ) ) ) );
        return [
            'workflow'             => $workflow,
            'requestId'            => (string) ( $active_job['request_id'] ?? '' ),
            'sourceAttachmentId'   => (int) ( $active_job['source_attachment_id'] ?? 0 ),
            'sourceAttachmentIds'  => $source_attachment_ids,
            'expectedOutputCount'  => (int) ( $active_job['expected_output_count'] ?? count( $this->normalize_job_ids( $active_job['job_ids'] ?? [] ) ) ),
            'estimatedCost'        => (float) ( $active_job['estimated_cost'] ?? 0 ),
        ];
    }

    /**
     * Return the durable terminal response after the run lock is acquired.
     *
     * Polling performs its upstream status request before claiming the run lock.
     * A background worker may complete the run during that request, so the poll
     * path must re-read the durable row before making any state commit.
     */
    private function recover_terminal_run_response(
		array $command,
        int $user_id,
        array $requested_job_ids,
        string $run_id,
        string $run_lock_token = ''
    ): ?\WP_REST_Response {
        if ( '' === $run_id ) {
            return null;
        }

        $row = Logger::instance()->get_run_row( $run_id );
        $final_status = sanitize_key( (string) ( $row['final_status'] ?? '' ) );
        if ( ! in_array( $final_status, [ 'completed', 'partial', 'failed', 'timeout', 'stale' ], true ) ) {
            return null;
        }

		return $this->recover_run_response( $command, $user_id, $requested_job_ids, $run_id, $run_lock_token );
    }

    private function recover_run_response(
		array $command,
        int $user_id,
        array $requested_job_ids,
        string $run_id_override = '',
        string $run_lock_token = ''
    ): ?\WP_REST_Response {
        $run_id = '' !== $run_id_override
            ? sanitize_text_field( $run_id_override )
			: sanitize_text_field( (string) ( $command['runId'] ?? '' ) );
        if ( '' === $run_id ) {
            return null;
        }
        $row = Logger::instance()->get_run_row( $run_id );
        if ( empty( $row ) ) {
            return null;
        }
        if ( (int) ( $row['user_id'] ?? 0 ) !== $user_id ) {
			return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
        }
		if ( ! $this->poll_request_matches_context( $command, $row ) ) {
			return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
        }

        $run_job_ids = $this->normalize_job_ids( $row['job_ids'] ?? [] );
        if ( empty( $requested_job_ids ) ) {
            $requested_job_ids = $run_job_ids;
        }
        foreach ( $requested_job_ids as $job_id ) {
            if ( ! in_array( $job_id, $run_job_ids, true ) ) {
				return $this->format_poll_error_response( $command, 'invalid_request', 403, $requested_job_ids );
            }
        }

        $row = $this->reconcile_terminal_run_if_needed(
			$command,
            $row,
            $user_id,
            $requested_job_ids,
            $run_lock_token
        );
        if ( empty( $row ) ) {
            return null;
        }

        $attachments_map = [];
        if ( ! empty( $row['attachments_map'] ) ) {
            $decoded = json_decode( (string) $row['attachments_map'], true );
            if ( is_array( $decoded ) ) {
				$attachments_map = $this->filter_attachment_map_for_jobs( $decoded, $requested_job_ids, (string) ( $row['workflow'] ?? '' ) );
            }
        }
        $ordered_attachments = $this->build_ordered_attachments( $requested_job_ids, $attachments_map );
        $attachment_ids = array_values(
            array_unique( array_filter( array_map( 'absint', array_values( $attachments_map ) ) ) )
        );
        $expected_output_count = max( 1, (int) ( $row['expected_output_count'] ?? count( $requested_job_ids ) ) );
        $final_status = sanitize_key( (string) ( $row['final_status'] ?? '' ) );
		$error_code = substr( sanitize_key( (string) ( $row['error_code'] ?? '' ) ), 0, 64 );
        $is_partial = ! empty( $attachment_ids ) && (
            count( $attachment_ids ) !== $expected_output_count
            || 'partial' === $error_code
        );

        $status = $this->get_recovered_run_status( $final_status, $attachment_ids, $is_partial );

        $terminal_reconciler = new Generate_Terminal_Reconciler();
		$finalization_pending = $terminal_reconciler->can_reconcile( $row, $requested_job_ids );
        $failed_job_ids = $terminal_reconciler->get_failed_job_ids( $row, $requested_job_ids );
        return rest_ensure_response(
            array_merge(
                [
                    'status'         => $status,
                    'jobIds'         => $requested_job_ids,
                    'jobStatuses'    => [],
                    'attachmentsMap' => $attachments_map,
                    'attachments'    => $ordered_attachments,
                    'failedJobIds'   => $failed_job_ids,
                    'errorCode'      => $error_code,
                    'message'        => sanitize_text_field( (string) ( $row['error_message'] ?? '' ) ),
                    'runId'          => $run_id,
                    'sourceType'     => sanitize_key( (string) ( $row['run_target'] ?? '' ) ),
                    'subjectId'      => (int) ( $row['subject_id'] ?? 0 ),
                    'partial'        => $is_partial,
					'finalizationPending' => $finalization_pending,
                ],
                $this->get_run_workflow_response_context( $row, $expected_output_count ),
                ( new Workflow_Response_Factory() )->present_owned_run( $row, $user_id )
            )
        );
    }

    /**
     * Attempt one idempotent CDN fetch/save for a terminal run without ever
     * entering a billable start path.
     *
     * @return array<string, mixed>
     */
    private function reconcile_terminal_run_if_needed(
		array $command,
        array $row,
        int $user_id,
        array $requested_job_ids,
        string $run_lock_token
    ): array {
        $reconciler = new Generate_Terminal_Reconciler();
        if ( ! $reconciler->can_reconcile( $row, $requested_job_ids ) ) {
            return $row;
        }

        $run_id = trim( (string) ( $row['run_id'] ?? '' ) );
        if ( '' === $run_id ) {
            return $row;
        }
        $owns_lock = '' === $run_lock_token;
        if ( $owns_lock ) {
            $claimed_token = Background_Lock::instance()->acquire( $this->get_run_lock_key( $run_id ), self::FINALIZE_LOCK_TTL );
            if ( false === $claimed_token ) {
                return $row;
            }
            $run_lock_token = $claimed_token;
        }

        try {
            $fresh = Logger::instance()->get_run_row( $run_id );
            if (
                empty( $fresh )
                || (int) ( $fresh['user_id'] ?? 0 ) !== $user_id
				|| ! $this->poll_request_matches_context( $command, $fresh )
            ) {
                return $fresh;
            }
            return $reconciler->reconcile( $fresh, $user_id, $requested_job_ids, $run_lock_token );
        } finally {
            if ( $owns_lock ) {
                Background_Lock::instance()->release( $this->get_run_lock_key( $run_id ), $run_lock_token );
            }
        }
    }

    private function get_recovered_run_status( string $final_status, array $attachment_ids, bool $is_partial ): string {
        if ( in_array( $final_status, [ 'starting', 'processing' ], true ) ) {
            return 'processing';
        }
        if ( 'timeout' === $final_status ) {
            return 'timeout';
        }
        if ( in_array( $final_status, [ 'completed', 'partial' ], true ) ) {
            if ( empty( $attachment_ids ) ) {
                return 'failed';
            }
            return $is_partial || 'partial' === $final_status ? 'partial' : 'completed';
        }
        if ( in_array( $final_status, [ '', 'started' ], true ) ) {
            if ( empty( $attachment_ids ) ) {
                return 'processing';
            }
            return $is_partial ? 'partial' : 'completed';
        }
        return 'failed';
    }

    private function get_run_workflow_response_context( array $row, int $expected_output_count ): array {
        $source_attachment_ids = [];
        if ( ! empty( $row['source_attachment_ids'] ) ) {
            $decoded = json_decode( (string) $row['source_attachment_ids'], true );
            if ( is_array( $decoded ) ) {
                $source_attachment_ids = array_values(
                    array_unique( array_filter( array_map( 'absint', $decoded ) ) )
                );
            }
        }
        return [
            'workflow'            => sanitize_key( (string) ( $row['workflow'] ?? '' ) ),
            'requestId'           => (string) ( $row['request_id'] ?? '' ),
            'sourceAttachmentId'  => (int) ( $row['source_attachment_id'] ?? 0 ),
            'sourceAttachmentIds' => $source_attachment_ids,
            'expectedOutputCount' => $expected_output_count,
            'estimatedCost'       => max( 0, (float) ( $row['estimated_cost'] ?? 0 ) ),
        ];
    }

    /**
     * Preserve exact per-job terminal evidence without discarding background
     * finalizer attempt counters or errors for other jobs.
     */
    private function persist_terminal_errors(
        string $run_id,
        array $run_job_ids,
        array $attachments_map,
        array $terminal_errors,
		array $completed_job_ids,
		array $terminal_output_counts,
		array $campaign_output_errors,
		bool $campaign_output_errors_observed
    ): bool {
        if ( '' === $run_id ) {
            return false;
        }

        $run_job_ids = $this->normalize_job_ids( $run_job_ids );
        if ( empty( $run_job_ids ) ) {
            return false;
        }
        $row = Logger::instance()->get_run_row( $run_id );
        if ( empty( $row ) ) {
            return false;
        }

        $finalize_meta = [];
        if ( ! empty( $row['finalize_meta'] ) ) {
            $decoded = json_decode( (string) $row['finalize_meta'], true );
            if ( is_array( $decoded ) ) {
                $finalize_meta = $decoded;
            }
        }
        $stored_errors = $finalize_meta[ self::FINALIZE_META_TERMINAL_ERRORS_KEY ] ?? [];
        $stored_errors = is_array( $stored_errors ) ? $stored_errors : [];
        $filtered_errors = [];
        foreach ( $run_job_ids as $job_id ) {
			if ( in_array( $job_id, $completed_job_ids, true ) ) {
                continue;
            }
			$error_code = substr( sanitize_key( (string) ( $terminal_errors[ $job_id ] ?? $stored_errors[ $job_id ] ?? '' ) ), 0, 64 );
            if ( '' !== $error_code ) {
                $filtered_errors[ $job_id ] = $error_code;
            }
        }

		if ( empty( $filtered_errors ) ) {
            unset( $finalize_meta[ self::FINALIZE_META_TERMINAL_ERRORS_KEY ] );
        } else {
            $finalize_meta[ self::FINALIZE_META_TERMINAL_ERRORS_KEY ] = $filtered_errors;
		}
		$stored_counts = $finalize_meta[ self::FINALIZE_META_TERMINAL_OUTPUT_COUNTS_KEY ] ?? [];
		$stored_counts = is_array( $stored_counts ) ? $stored_counts : [];
		$filtered_counts = [];
		foreach ( $run_job_ids as $job_id ) {
			$counts = $this->normalize_terminal_output_counts( $terminal_output_counts[ $job_id ] ?? $stored_counts[ $job_id ] ?? [] );
			if ( ! empty( $counts ) ) {
				$filtered_counts[ $job_id ] = $counts;
			}
		}
		if ( empty( $filtered_counts ) ) {
			unset( $finalize_meta[ self::FINALIZE_META_TERMINAL_OUTPUT_COUNTS_KEY ] );
		} else {
			$finalize_meta[ self::FINALIZE_META_TERMINAL_OUTPUT_COUNTS_KEY ] = $filtered_counts;
		}
		$finalize_meta = Generate_Output_Map::with_completed_job_ids( $finalize_meta, $completed_job_ids, $run_job_ids );
		$finalize_meta = Generate_Campaign_Admission::carry_forward( $finalize_meta, $row['finalize_meta'] ?? null );
		if ( ! $campaign_output_errors_observed ) {
			$campaign_output_errors = Generate_Campaign_Admission::output_errors_from_finalize_meta( $finalize_meta );
		}
		$finalize_meta = Generate_Campaign_Admission::with_output_errors( $finalize_meta, $campaign_output_errors );
		return Logger::instance()->update_run( $run_id, [ 'finalize_meta' => wp_json_encode( $finalize_meta ) ] );
    }

	/** @return array{requested:int,completed:int,failed:int} */
	private function normalize_terminal_output_counts( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}
		$requested = $value['requested'] ?? null;
		$completed = $value['completed'] ?? null;
		$failed    = $value['failed'] ?? null;
		if (
			! is_int( $requested )
			|| ! is_int( $completed )
			|| ! is_int( $failed )
			|| $requested < 1
			|| $completed < 0
			|| $failed < 0
			|| $completed + $failed !== $requested
		) {
			return [];
		}
		return [ 'requested' => $requested, 'completed' => $completed, 'failed' => $failed ];
	}

    private function filter_attachment_map_for_jobs( array $attachments_map, array $job_ids, string $workflow ): array {
		return Generate_Output_Map::normalize( $attachments_map, $job_ids, sanitize_key( $workflow ) );
    }

    /** @return array<string, mixed> */
    private function get_owned_result_projection( string $run_id, int $user_id ): array {
        if ( '' === $run_id || $user_id <= 0 ) {
            return [];
        }
        return ( new Workflow_Response_Factory() )->present_owned_run( Logger::instance()->get_run_row( $run_id ), $user_id );
    }

    private function build_ordered_attachments( array $job_ids, array $attachments_map ): array {
        $ordered = [];
        foreach ( $job_ids as $job_id ) {
			foreach ( Generate_Output_Map::for_job( $attachments_map, $job_id ) as $attachment_id ) {
				$edit_url = get_edit_post_link( $attachment_id, 'raw' );
				$attachment_meta = wp_get_attachment_metadata( $attachment_id );
				$ordered[] = [
					'jobId'        => (string) $job_id,
					'attachmentId' => $attachment_id,
					'fullUrl'      => Media_Saver::get_attachment_view_url( $attachment_id, 'full' ),
					'editUrl'      => is_string( $edit_url ) ? $edit_url : '',
					'width'        => is_array( $attachment_meta ) ? absint( $attachment_meta['width'] ?? 0 ) : 0,
					'height'       => is_array( $attachment_meta ) ? absint( $attachment_meta['height'] ?? 0 ) : 0,
				];
			}
        }
        return $ordered;
    }

    private function build_bulk_active_job( string $run_uuid, int $item_id, int $user_id ): ?array {
        if ( '' === $run_uuid || $item_id <= 0 || $user_id <= 0 ) {
            return null;
        }
        $repo = Bulk_Repo::instance();
        $run = $repo->get_run_by_uuid( $run_uuid, $user_id );
        if ( empty( $run ) ) {
            return null;
        }
        $item = $repo->get_item_by_id( $item_id, $user_id );
        if ( empty( $item ) || (int) ( $item['run_id'] ?? 0 ) !== (int) ( $run['id'] ?? 0 ) ) {
            return null;
        }
        $job_ids = $this->normalize_job_ids( $item['job_ids'] ?? '' );
        if ( empty( $job_ids ) ) {
            return null;
        }
        $subject_type = sanitize_key( (string) ( $item['subject_type'] ?? '' ) );
        $subject_id = (int) ( $item['subject_id'] ?? 0 );
        if ( ! in_array( $subject_type, self::TARGETS, true ) || $subject_id <= 0 ) {
            return null;
        }
        $product_id = 'product' === $subject_type ? $subject_id : 0;
        $subject_title = '';
        if ( $product_id > 0 ) {
            $product_query_adapter = Plugin::instance()->compat()->get_product_query_adapter();
            $summary = $product_query_adapter->get_product_summary( $product_id );
            if ( is_array( $summary ) ) {
				$subject_title = (string) ( $summary['label'] ?? '' );
            }
        } else {
			$subject_post  = get_post( $subject_id );
			$subject_title = $subject_post instanceof \WP_Post ? (string) $subject_post->post_title : '';
        }
        $workflow = sanitize_key( (string) ( $run['workflow'] ?? '' ) );
        if ( ! in_array( $workflow, self::WORKFLOWS, true ) ) {
            return null;
        }
        $run_id = trim( (string) ( $item['run_id_single'] ?? '' ) );
        if ( '' === $run_id ) {
            return null;
        }
        $run_row = Logger::instance()->get_run_row( $run_id );
        $run_job_ids = $this->normalize_job_ids( $run_row['job_ids'] ?? [] );
        $item_request_id = (string) ( $item['request_id'] ?? '' );
        if (
            empty( $run_row )
            || (int) ( $run_row['user_id'] ?? 0 ) !== $user_id
            || $workflow !== sanitize_key( (string) ( $run_row['workflow'] ?? '' ) )
            || $subject_type !== (string) ( $run_row['run_target'] ?? '' )
            || $subject_id !== (int) ( $run_row['subject_id'] ?? 0 )
            || ! empty( array_diff( $job_ids, $run_job_ids ) )
            || (
                '' !== (string) ( $item['command_snapshot'] ?? '' )
                && ( '' === $item_request_id || ! hash_equals( (string) ( $run_row['request_id'] ?? '' ), $item_request_id ) )
            )
        ) {
            return null;
        }
        $attachments_map = [];
        if ( ! empty( $run_row['attachments_map'] ) && is_string( $run_row['attachments_map'] ) ) {
            $decoded = json_decode( $run_row['attachments_map'], true );
            if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
				$attachments_map = $this->filter_attachment_map_for_jobs( $decoded, $job_ids, $workflow );
            }
        }
        $item_meta = [];
        if ( ! empty( $item['meta'] ) && is_string( $item['meta'] ) ) {
            $decoded_meta = json_decode( $item['meta'], true );
            if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded_meta ) ) {
                $item_meta = $decoded_meta;
            }
        }
        $expected_output_count = max(
            1,
            (int) ( $item_meta['expected_output_count'] ?? $run_row['expected_output_count'] ?? count( $job_ids ) )
        );
        $source_attachment_ids = [];
        if ( ! empty( $run_row['source_attachment_ids'] ) && is_string( $run_row['source_attachment_ids'] ) ) {
            $decoded_sources = json_decode( $run_row['source_attachment_ids'], true );
            if ( is_array( $decoded_sources ) ) {
                $source_attachment_ids = array_values( array_unique( array_filter( array_map( 'absint', $decoded_sources ) ) ) );
            }
        }
        if ( empty( $source_attachment_ids ) && is_array( $item_meta['source_attachment_ids'] ?? null ) ) {
            $source_attachment_ids = array_values(
                array_unique( array_filter( array_map( 'absint', $item_meta['source_attachment_ids'] ) ) )
            );
        }
		$completed_job_ids = Generate_Output_Map::completed_job_ids_from_meta( $run_row['finalize_meta'] ?? [], $job_ids );
        $started_at_raw = $item['generation_started_at'] ?? ( $item['started_at'] ?? '' );
        $started_at = $started_at_raw ? (int) strtotime( (string) $started_at_raw . ' UTC' ) : time();
        return [
            'job_id'          => (string) ( $job_ids[0] ?? '' ),
            'job_ids'         => $job_ids,
            'run_id'          => $run_id,
            'run_target'      => $subject_type,
            'subject_id'      => $subject_id,
            'product_title'   => $subject_title,
            'workflow'        => $workflow,
            'user_id'         => $user_id,
            'request_id'      => (string) ( $item['request_id'] ?? '' ),
            'quality'         => (string) ( $run_row['quality'] ?? '' ),
            'scene_description' => '',
            'images_count'    => $expected_output_count,
            'expected_output_count' => $expected_output_count,
            'estimated_cost'  => max( 0, (float) ( $run_row['estimated_cost'] ?? 0 ) ),
            'source_attachment_id' => (int) ( $run_row['source_attachment_id'] ?? $source_attachment_ids[0] ?? 0 ),
            'source_attachment_ids' => $source_attachment_ids,
            'completed_job_ids' => $completed_job_ids,
            'attachments_map' => $attachments_map,
			'attachment_ids'  => Generate_Output_Map::attachment_ids( $attachments_map ),
            'started_at'      => $started_at,
        ];
    }

    /**
     * @return string|false
     */
    private function claim_bulk_finalize_slot( string $run_uuid ) {
        if ( '' === $run_uuid || self::FINALIZE_CONCURRENCY < 1 ) {
            return false;
        }
        return Background_Lock::instance()->acquire( $this->get_bulk_lock_key( $run_uuid ), self::FINALIZE_LOCK_TTL );
    }

    private function release_bulk_finalize_slot( string $run_uuid, string $token ): void {
        if ( '' === $run_uuid || '' === $token ) {
            return;
        }
        Background_Lock::instance()->release( $this->get_bulk_lock_key( $run_uuid ), $token );
    }

    private function normalize_job_ids( $job_ids ): array {
        if ( is_string( $job_ids ) ) {
            $trimmed = trim( $job_ids );
            if ( '' !== $trimmed && ( fotogenic_ai_str_starts_with( $trimmed, '[' ) || fotogenic_ai_str_starts_with( $trimmed, '{' ) ) ) {
                $decoded = json_decode( $trimmed, true );
                if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
                    $job_ids = $decoded;
                } else {
                    $job_ids = [ $job_ids ];
                }
            } else {
                $job_ids = [ $job_ids ];
            }
        }
        if ( ! is_array( $job_ids ) ) {
            return [];
        }
		$normalized = [];
		foreach ( $job_ids as $job_id ) {
			$job_id = Upstream_Identifier::normalize( $job_id );
			if ( '' !== $job_id ) {
                $normalized[] = $job_id;
            }
        }
        return array_values( array_unique( $normalized ) );
    }

    private function format_job_statuses( array $statuses, array $errors ): array {
        $formatted = [];
        foreach ( $statuses as $job_id => $status ) {
            $error = $errors[ $job_id ] ?? [];
            $formatted[] = [
                'jobId'     => (string) $job_id,
                'status'    => (string) $status,
                'errorCode' => isset( $error['errorCode'] ) ? (string) $error['errorCode'] : '',
                'error'     => isset( $error['error'] ) ? (string) $error['error'] : '',
            ];
        }
        return $formatted;
    }

    private function map_api_error_code_to_message_key( string $error_code ): string {
        $code = strtoupper( trim( $error_code ) );
		$message_keys = [
			'INSUFFICIENT_CREDITS'     => 'insufficient_credits',
			'PROHIBITED_CONTENT'       => 'unprocessable',
			'MODEL_OVERLOADED'         => 'service_unavailable',
			'RATE_LIMIT_EXCEEDED'      => 'service_unavailable',
			'ASSET_NOT_READY_TIMEOUT'  => 'request_failed',
			'INVALID_REQUEST'          => 'invalid_request',
			'AUTH_ERROR'               => 'unauthorized',
		];

		return $message_keys[ $code ] ?? 'request_failed';
    }

    /**
     * @return string|false
     */
    private function claim_job_processing( string $job_id ) {
        if ( '' === $job_id ) {
            return false;
        }
        return Background_Lock::instance()->acquire( $this->get_job_lock_key( $job_id ), self::FINALIZE_LOCK_TTL );
    }

    private function release_job_processing( string $job_id, string $token ): void {
        if ( '' === $job_id || '' === $token ) {
            return;
        }
        Background_Lock::instance()->release( $this->get_job_lock_key( $job_id ), $token );
    }

    private function get_run_lock_key( string $run_id ): string {
        return 'run-finalization:' . $run_id;
    }

    private function get_job_lock_key( string $job_id ): string {
        return 'job-finalization:' . $job_id;
    }

    private function get_bulk_lock_key( string $run_uuid ): string {
        return 'bulk-finalization:' . $run_uuid;
    }
}
