<?php
/**
 * Durable admin UI chat queue and worker.
 *
 * Keeps admin browser turns alive across wp-admin page navigation. The
 * browser submits a short enqueue request, then polls job status. The signed
 * HTTP worker only hands the turn to the durable actor runtime; it never runs
 * the LLM inside the request process.
 *
 * @package Niwa_AI
 */

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

trait Niwa_AI_Admin_Chat_Queue {
	private function find_or_enqueue_admin_ui_chat_job( $user_id, $message, $request_id = '' ) {
		$user_id    = max( 0, intval( $user_id ) );
		$message    = trim( (string) $message );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );
		$session_id = $this->get_admin_ui_chat_session_id( $user_id );
		$message_hash = hash( 'sha256', $message );

		if ( '' === $message || '' === $session_id ) {
			return array();
		}

		$terminal = $this->get_admin_ui_chat_request_result( $session_id, $request_id, $user_id );
		if ( ! empty( $terminal['id'] ) ) {
			if ( ! hash_equals( $this->admin_ui_chat_message_hash( $terminal ), $message_hash ) ) {
				return array( 'conflict' => true, 'code' => 'admin_ui_request_message_conflict' );
			}
			return $this->persist_admin_ui_chat_user_turn( $terminal, $message, $session_id, $request_id ) ? $terminal : array();
		}

		$existing = $this->find_admin_ui_chat_job( $session_id, $request_id, $user_id );
		if ( ! empty( $existing['id'] ) ) {
			if ( ! hash_equals( $this->admin_ui_chat_message_hash( $existing ), $message_hash ) ) {
				return array( 'conflict' => true, 'code' => 'admin_ui_request_message_conflict' );
			}
			return $this->persist_admin_ui_chat_user_turn( $existing, $message, $session_id, $request_id ) ? $existing : array();
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 3, 15 );
		if ( '' === $lock_token ) {
			return array();
		}

		try {
			$terminal = $this->get_admin_ui_chat_request_result( $session_id, $request_id, $user_id );
			if ( ! empty( $terminal['id'] ) ) {
				if ( ! hash_equals( $this->admin_ui_chat_message_hash( $terminal ), $message_hash ) ) {
					return array( 'conflict' => true, 'code' => 'admin_ui_request_message_conflict' );
				}
				return $this->persist_admin_ui_chat_user_turn( $terminal, $message, $session_id, $request_id ) ? $terminal : array();
			}

			$existing = $this->find_admin_ui_chat_job( $session_id, $request_id, $user_id );
			if ( ! empty( $existing['id'] ) ) {
				if ( ! hash_equals( $this->admin_ui_chat_message_hash( $existing ), $message_hash ) ) {
					return array( 'conflict' => true, 'code' => 'admin_ui_request_message_conflict' );
				}
				return $this->persist_admin_ui_chat_user_turn( $existing, $message, $session_id, $request_id ) ? $existing : array();
			}

			$queue  = $this->ensure_admin_ui_chat_queue_sequences( $this->get_admin_ui_chat_queue() );
			if ( $this->count_admin_ui_chat_session_jobs( $queue, $session_id, $user_id ) >= 50 ) {
				return array( 'queue_full' => true, 'code' => 'admin_ui_chat_queue_full' );
			}
			$sequence = $this->next_admin_ui_chat_sequence( $session_id, $queue );
			if ( $sequence < 1 ) {
				return array();
			}
			$job_id = 'achat_' . str_replace( '-', '', wp_generate_uuid4() );
			$job    = array(
				'id'         => $job_id,
				'user_id'    => $user_id,
				'session_id' => $session_id,
				'message'    => $message,
				'request_id' => $request_id,
				'status'     => 'pending',
				'attempts'   => 0,
				'created_at' => time(),
				'updated_at' => time(),
				'generation' => 0,
				'sequence'   => $sequence,
				'message_hash' => $message_hash,
				'claimable_since' => 0,
			);
			$queue[ $job_id ] = $job;
			if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
				return array();
			}
			if ( ! $this->persist_admin_ui_chat_user_turn( $job, $message, $session_id, $request_id ) ) {
				unset( $queue[ $job_id ] );
				$this->write_admin_ui_chat_queue( $queue );
				return array();
			}

			if ( ! $this->set_admin_ui_chat_job_result(
				$job_id,
				array(
					'status'     => 'pending',
					'job_id'     => $job_id,
					'user_id'    => $user_id,
					'request_id' => $request_id,
					'generation' => 0,
					'session'    => $session_id,
					'message'    => $message,
					'created_at' => $job['created_at'],
					'sequence'   => $sequence,
					'message_hash' => $message_hash,
				),
				15 * MINUTE_IN_SECONDS
			) ) {
				unset( $queue[ $job_id ] );
				$this->write_admin_ui_chat_queue( $queue );
				return array();
			}
			$this->schedule_admin_ui_chat_worker( 5 );

			return $job;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function persist_admin_ui_chat_user_turn( $job, $message, $session_id, $request_id ) {
		if ( ! isset( $this->conversation_store ) || ! is_object( $this->conversation_store ) || ! method_exists( $this->conversation_store, 'append' ) ) {
			return false;
		}

		$message    = trim( (string) $message );
		$session_id = trim( (string) $session_id );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );
		$job_id     = $this->normalize_admin_ui_chat_job_id( is_array( $job ) ? ( $job['id'] ?? ( $job['job_id'] ?? '' ) ) : '' );
		if ( '' === $message || '' === $session_id || '' === $request_id || '' === $job_id ) {
			return false;
		}

		return false !== $this->conversation_store->append(
			$session_id,
			'user',
			$message,
			array(),
			array(
				'channel'            => 'admin_ui',
				'source'             => 'admin_ui_enqueue',
				'request_id'         => $request_id,
				'admin_ui_job_id'    => $job_id,
				'admin_ui_accepted'  => true,
				'admin_ui_sequence'  => intval( $job['sequence'] ?? 0 ),
				'message_hash'       => (string) ( $job['message_hash'] ?? hash( 'sha256', $message ) ),
			)
		);
	}

	private function find_admin_ui_chat_job( $session_id, $request_id = '', $user_id = 0 ) {
		$session_id = trim( (string) $session_id );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );
		$user_id    = max( 0, intval( $user_id ) );
		if ( '' === $session_id ) {
			return array();
		}

		foreach ( $this->get_admin_ui_chat_queue() as $job ) {
			if ( ! is_array( $job ) || (string) ( $job['session_id'] ?? '' ) !== $session_id || ( $user_id > 0 && intval( $job['user_id'] ?? 0 ) !== $user_id ) ) {
				continue;
			}
			if ( ! in_array( (string) ( $job['status'] ?? 'pending' ), array( 'pending', 'processing', 'terminal_pending', 'delivery_pending' ), true ) ) {
				continue;
			}
			if ( '' !== $request_id && (string) ( $job['request_id'] ?? '' ) === $request_id ) {
				return $job;
			}
		}

		return array();
	}

	private function get_admin_ui_chat_job_status( $job_id = '', $user_id = 0, $request_id = '' ) {
		$user_id = max( 0, intval( $user_id ) );
		$job_id  = $this->normalize_admin_ui_chat_job_id( $job_id );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );

		if ( '' === $job_id ) {
			$terminal = '' !== $request_id
				? $this->get_admin_ui_chat_request_result( $this->get_admin_ui_chat_session_id( $user_id ), $request_id, $user_id )
				: array();
			if ( ! empty( $terminal['job_id'] ) ) {
				return $terminal;
			}
			$active = '' !== $request_id
				? $this->find_admin_ui_chat_job_for_request( $user_id, $request_id )
				: $this->get_latest_active_admin_ui_chat_job_for_user( $user_id );
			if ( empty( $active['id'] ) ) {
				return array( 'status' => 'idle' );
			}
			$job_id = (string) $active['id'];
		}

		$cached = $this->get_admin_ui_chat_job_result( $job_id );
		if ( ! empty( $cached ) && $user_id > 0 && intval( $cached['user_id'] ?? 0 ) !== $user_id ) {
			return array( 'status' => 'missing' );
		}
		if ( ! empty( $cached ) && '' !== $request_id && (string) ( $cached['request_id'] ?? '' ) !== $request_id ) {
			return array( 'status' => 'missing' );
		}
		if ( 'done' === (string) ( $cached['status'] ?? '' ) ) {
			$delivery_status = (string) ( $cached['delivery_status'] ?? '' );
			$cached_queue    = 'delivered' === $delivery_status ? $this->get_admin_ui_chat_queue() : array();
			if ( 'pending' === $delivery_status || ( 'delivered' === $delivery_status && isset( $cached_queue[ $job_id ] ) ) ) {
				$cached = $this->recover_pending_admin_ui_chat_delivery( $cached );
			}
		}
		if ( is_array( $cached ) && ! empty( $cached['status'] ) && ! in_array( (string) $cached['status'], array( 'pending', 'processing' ), true ) ) {
			return $cached;
		}

		$queue = $this->get_admin_ui_chat_queue();
		if ( ! empty( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ) {
			$job = $queue[ $job_id ];
			$recovery_pending = false;
			if ( $user_id > 0 && intval( $job['user_id'] ?? 0 ) !== $user_id ) {
				return array( 'status' => 'missing' );
			}
			if ( '' !== $request_id && (string) ( $job['request_id'] ?? '' ) !== $request_id ) {
				return array( 'status' => 'missing' );
			}

			$status = (string) ( $job['status'] ?? 'pending' );
			if ( in_array( $status, array( 'terminal_pending', 'delivery_pending' ), true ) && ! empty( $job['terminal_result'] ) && is_array( $job['terminal_result'] ) ) {
				$terminal = $job['terminal_result'];
				if ( 'delivery_pending' === $status && 'done' === (string) ( $terminal['status'] ?? '' ) ) {
					$payload  = $this->get_admin_ui_chat_delivery_payload( $job_id );
					$terminal = $this->recover_pending_admin_ui_chat_delivery( ! empty( $payload ) ? $payload : $terminal );
				}
				return $terminal;
			}
			if ( $this->is_admin_ui_chat_pending_job_expired( $job ) || $this->is_admin_ui_chat_processing_job_stale( $job ) ) {
				$terminal = $this->recover_expired_admin_ui_chat_job( $job_id );
				if ( ! empty( $terminal['status'] ) ) {
					return $terminal;
				}
				$this->schedule_admin_ui_chat_recovery( $job_id, 30 );
				$recovery_pending = true;
			}

			return array(
				'status'     => $status,
				'job_id'     => $job_id,
				'user_id'    => intval( $job['user_id'] ?? 0 ),
				'request_id' => (string) ( $job['request_id'] ?? '' ),
				'generation' => intval( $job['generation'] ?? 0 ),
				'session'    => (string) ( $job['session_id'] ?? '' ),
				'message'    => (string) ( $job['message'] ?? '' ),
				'created_at' => intval( $job['created_at'] ?? 0 ),
				'started_at' => intval( $job['started_at'] ?? 0 ),
				'updated_at' => intval( $job['updated_at'] ?? 0 ),
				'sequence'   => intval( $job['sequence'] ?? 0 ),
				'phase'      => $this->get_admin_ui_chat_job_phase( $job, $queue ),
				'continuation_pending' => ! empty( $job['continuation_pending'] ),
				'admin_ui_continuation_actor_id' => isset( $job['admin_ui_continuation_actor_id'] ) ? (string) $job['admin_ui_continuation_actor_id'] : '',
				'recovery_pending' => $recovery_pending,
			);
		}

		$terminal = '' !== $request_id
			? $this->get_admin_ui_chat_request_result( $this->get_admin_ui_chat_session_id( $user_id ), $request_id, $user_id )
			: array();
		if ( ! empty( $terminal['job_id'] ) && ( '' === $job_id || hash_equals( (string) $terminal['job_id'], $job_id ) ) ) {
			return $terminal;
		}

		return is_array( $cached ) && ! empty( $cached['status'] ) ? $cached : array( 'status' => 'missing' );
	}

	private function find_admin_ui_chat_job_for_request( $user_id, $request_id ) {
		$user_id    = max( 0, intval( $user_id ) );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );
		if ( $user_id < 1 || '' === $request_id ) {
			return array();
		}

		foreach ( $this->get_admin_ui_chat_queue() as $job ) {
			if ( is_array( $job ) && intval( $job['user_id'] ?? 0 ) === $user_id && (string) ( $job['request_id'] ?? '' ) === $request_id ) {
				return $job;
			}
		}

		return array();
	}

	private function get_latest_active_admin_ui_chat_job_for_user( $user_id = 0 ) {
		$user_id = max( 0, intval( $user_id ) );
		if ( $user_id < 1 && function_exists( 'get_current_user_id' ) ) {
			$user_id = intval( get_current_user_id() );
		}
		if ( $user_id < 1 ) {
			return array();
		}

		$latest = array();
		foreach ( $this->get_admin_ui_chat_queue() as $job ) {
			if ( ! is_array( $job ) || intval( $job['user_id'] ?? 0 ) !== $user_id ) {
				continue;
			}
			if ( ! in_array( (string) ( $job['status'] ?? 'pending' ), array( 'pending', 'processing' ), true ) ) {
				continue;
			}
			if ( $this->is_admin_ui_chat_pending_job_expired( $job ) || $this->is_admin_ui_chat_processing_job_stale( $job ) ) {
				continue;
			}
			if ( empty( $latest ) || intval( $job['created_at'] ?? 0 ) > intval( $latest['created_at'] ?? 0 ) ) {
				$latest = $job;
			}
		}

		return $latest;
	}

	private function has_active_admin_ui_chat_job_for_current_user() {
		return ! empty( $this->get_latest_active_admin_ui_chat_job_for_user( get_current_user_id() ) );
	}

	public function ajax_admin_autonomy_status() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array(
					'error'   => __( 'Nemate dozvolu za admin asistenta.', 'niwa-ai' ),
					'message' => __( 'Nemate dozvolu za admin asistenta.', 'niwa-ai' ),
				),
				403
			);
		}

		nocache_headers();
		$this->validate_admin_autonomy_ajax_nonce( array( 'nonce', '_ajax_nonce' ) );

		$job_id = isset( $_POST['job_id'] ) ? sanitize_text_field( wp_unslash( $_POST['job_id'] ) ) : '';
		$request_id = isset( $_POST['request_id'] ) ? sanitize_text_field( wp_unslash( $_POST['request_id'] ) ) : '';
		$status = $this->get_admin_ui_chat_job_status( $job_id, get_current_user_id(), $request_id );
		$requested_jobs = array();
		if ( isset( $_POST['jobs'] ) ) {
			$jobs_json = wp_unslash( (string) $_POST['jobs'] );
			if ( strlen( $jobs_json ) > 16384 ) {
				wp_send_json_error( array( 'error' => 'admin_ui_status_batch_too_large' ), 413 );
			}
			$decoded = json_decode( $jobs_json, true );
			foreach ( array_slice( is_array( $decoded ) ? $decoded : array(), 0, 50 ) as $requested ) {
				if ( ! is_array( $requested ) ) {
					continue;
				}
				$requested_jobs[] = $this->get_admin_ui_chat_job_status(
					$this->normalize_admin_ui_chat_job_id( $requested['job_id'] ?? '' ),
					get_current_user_id(),
					$this->normalize_admin_ui_chat_request_id( $requested['request_id'] ?? '' )
				);
			}
			$status = array( 'status' => 'batch', 'jobs' => $requested_jobs );
		}
		if ( ! empty( $_POST['include_history'] ) && $this->admin_autonomy && method_exists( $this->admin_autonomy, 'get_browser_display_history' ) ) {
			$since_ts = isset( $_POST['since_ts'] ) ? absint( wp_unslash( $_POST['since_ts'] ) ) : 0;
			$since_id = isset( $_POST['since_id'] ) ? sanitize_text_field( wp_unslash( $_POST['since_id'] ) ) : '';
			$history  = (array) $this->admin_autonomy->get_browser_display_history(
				get_current_user_id(),
				array(
					'limit'    => 31,
					'since_ts' => $since_ts,
					'since_id' => $since_id,
					'channel'  => 'admin_ui',
				)
			);
			$status['history'] = array_slice( $history, -30 );
		}

		wp_send_json_success( $status );
	}

	public function drain_admin_ui_chat_background_queue( $result = array() ) {
		return $this->has_unfinished_admin_ui_chat_jobs()
			? $this->process_admin_ui_chat_queue( 1 )
			: ( is_array( $result ) ? $result : array( 'success' => true, 'processed' => 0 ) );
	}

	public function process_admin_ui_chat_queue( $limit = 1 ) {
		$limit = max( 1, intval( $limit ) );
		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue_runner', 2, 30 );
		if ( '' === $lock_token ) {
			if ( $this->has_unfinished_admin_ui_chat_jobs() ) {
				$this->schedule_admin_ui_chat_worker( $this->get_admin_ui_chat_runner_retry_delay() );
			}
			return array( 'success' => true, 'processed' => 0, 'note' => 'runner_locked' );
		}

		$processed = 0;
		try {
			while ( $processed < $limit ) {
				$job = $this->claim_next_admin_ui_chat_job();
				if ( empty( $job['id'] ) ) {
					break;
				}

				$result = $this->run_admin_ui_chat_job( $job );
				$this->finalize_admin_ui_chat_job( $job, $result );
				$processed++;
			}

			return array( 'success' => true, 'processed' => $processed );
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue_runner', $lock_token );
			if ( $this->has_claimable_admin_ui_chat_jobs() ) {
				$this->schedule_admin_ui_chat_worker( 5 );
			}
		}
	}

	public function handle_admin_ui_chat_worker() {
		ignore_user_abort( true );

		if ( ! $this->verify_admin_ui_chat_worker_signature() ) {
			status_header( 403 );
			echo 'forbidden';
			exit;
		}

		$result = $this->process_admin_ui_chat_queue( 1 );
		if ( intval( $result['processed'] ?? 0 ) > 0 && $this->has_claimable_admin_ui_chat_jobs() ) {
			$this->dispatch_admin_ui_chat_worker( 'chain' );
		}

		status_header( empty( $result['success'] ) ? 500 : 200 );
		echo empty( $result['success'] ) ? 'error' : 'ok';
		exit;
	}

	private function run_admin_ui_chat_job( $job ) {
		$job = is_array( $job ) ? $job : array();
		if ( ! $this->admin_autonomy || ! method_exists( $this->admin_autonomy, 'enqueue_browser_message' ) ) {
			return array(
				'status'  => 'failed',
				'error'   => 'admin_autonomy_unavailable',
				'message' => __( 'Admin autonomy runtime nije dostupan.', 'niwa-ai' ),
			);
		}

		try {
			$result = (array) $this->admin_autonomy->enqueue_browser_message(
				isset( $job['message'] ) ? (string) $job['message'] : '',
				isset( $job['user_id'] ) ? intval( $job['user_id'] ) : 0,
				isset( $job['request_id'] ) ? (string) $job['request_id'] : '',
				isset( $job['id'] ) ? (string) $job['id'] : '',
				isset( $job['sequence'] ) ? intval( $job['sequence'] ) : 0
			);
		} catch ( Throwable $e ) {
			$result = array( 'success' => false, 'error' => $e->getMessage() );
		}

		if ( empty( $result['success'] ) ) {
			$error_code = sanitize_key( (string) ( $result['error'] ?? 'admin_ui_chat_failed' ) );
			$error_message = ! empty( $result['session_busy'] )
				? __( 'Admin chat sesija je trenutno zauzeta. Poruka ce biti ponovo pokusana.', 'niwa-ai' )
				: $this->get_admin_ui_chat_failure_message( $error_code );
			return array(
				'status'       => ! empty( $result['session_busy'] ) ? 'pending' : 'failed',
				'error'        => $error_message,
				'code'         => '' !== $error_code ? $error_code : 'admin_ui_chat_failed',
				'message'      => $error_message,
				'session_busy' => ! empty( $result['session_busy'] ),
				'session'      => isset( $result['session_id'] ) ? (string) $result['session_id'] : (string) ( $job['session_id'] ?? '' ),
			);
		}

		$answer = isset( $result['answer'] ) ? trim( (string) $result['answer'] ) : '';
		if ( '' === $answer && ! empty( $result['continuation_pending'] ) ) {
			return array(
				'status'                       => 'processing',
				'answer'                       => '',
				'session'                      => isset( $result['session_id'] ) ? (string) $result['session_id'] : (string) ( $job['session_id'] ?? '' ),
				'continuation_pending'         => true,
				'admin_ui_continuation_queued' => ! empty( $result['admin_ui_continuation_queued'] ),
				'admin_ui_continuation_actor_id' => isset( $result['admin_ui_continuation_actor_id'] ) ? (string) $result['admin_ui_continuation_actor_id'] : '',
				'updated_at'                   => time(),
			);
		}
		if ( '' === $answer ) {
			$message = __( 'Admin autonomy runtime je zavrsio bez teksta odgovora. Posalji poruku ponovo.', 'niwa-ai' );
			return array(
				'status'  => 'failed',
				'error'   => 'admin_ui_empty_answer',
				'message' => $message,
				'session' => isset( $result['session_id'] ) ? (string) $result['session_id'] : (string) ( $job['session_id'] ?? '' ),
			);
		}

		return array(
			'status'  => 'done',
			'answer'  => $answer,
			'session' => isset( $result['session_id'] ) ? (string) $result['session_id'] : (string) ( $job['session_id'] ?? '' ),
		);
	}

	private function get_admin_ui_chat_failure_message( $error_code ) {
		$error_code = sanitize_key( (string) $error_code );
		$messages   = array(
			'admin_ui_actor_missing_identity'          => 'Admin chat poruka nema ispravan job ili session identitet.',
			'actor_runtime_unavailable'                 => 'Admin chat actor runtime nije ucitan.',
			'admin_ui_actor_job_bind_failed'            => 'Admin chat job nije mogao da rezervise vezu sa actor porukom.',
			'admin_ui_actor_completed_without_terminal' => 'Postojeca actor poruka je zavrsena bez terminalnog odgovora.',
			'admin_ui_actor_enqueue_failed'             => 'Admin chat poruka nije mogla da se upise u actor queue.',
			'admin_ui_actor_job_confirm_failed'         => 'Admin chat job nije mogao da potvrdi vezu sa upisanom actor porukom.',
			'runner_dispatcher_unavailable'              => 'Admin chat runner dispatcher nije dostupan.',
			'runner_state_storage_unavailable'           => 'Admin chat runner state storage nije dostupan.',
			'actor_already_active'                       => 'Druga actor poruka je vec aktivna, ali novi wake zahtev nije bezbedno preuzet.',
			'runner_already_active'                      => 'Background runner je vec aktivan, ali novi wake zahtev nije bezbedno preuzet.',
			'chain_cooldown'                             => 'Background runner nije pokrenut zbog aktivnog chain cooldown prozora.',
			'local_cli_unavailable'                      => 'Lokalni PHP CLI runner nije dostupan ili je proc_open onemogucen.',
			'local_cli_script_missing'                   => 'PHP CLI runner skripta nedostaje ili nije citljiva.',
			'local_cli_shell_missing'                    => 'Server nema dostupan izvrsni /bin/sh potreban za CLI runner.',
			'local_cli_no_cli_binary'                    => 'Nije pronadjen ispravan PHP CLI binary.',
			'local_cli_proc_open_failed'                 => 'proc_open nije uspeo da pokrene PHP CLI runner proces.',
			'local_cli_shell_failed'                     => 'Shell komanda za PHP CLI runner zavrsila se greskom.',
			'local_cli_process_exited_early'             => 'PHP CLI runner proces se ugasio odmah nakon pokretanja.',
			'admin_ui_actor_dispatch_failed'             => 'Actor poruka je upisana, ali runner dispatch nije uspeo.',
		);

		$message = isset( $messages[ $error_code ] )
			? $messages[ $error_code ]
			: 'Admin chat je zaustavljen zbog nepoznate background runtime greske.';

		return sprintf(
			/* translators: 1: human-readable failure reason, 2: stable runtime error code. */
			__( '%1$s Kod: %2$s. Posalji poruku ponovo nakon sto se uzrok otkloni.', 'niwa-ai' ),
			$message,
			'' !== $error_code ? $error_code : 'admin_ui_chat_failed'
		);
	}

	private function handoff_admin_ui_chat_job( $job_id ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job_id );
		if ( '' === $job_id ) {
			return false;
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue_runner', 2, 30 );
		if ( '' === $lock_token ) {
			return false;
		}

		try {
			$job = $this->claim_next_admin_ui_chat_job( $job_id );
			if ( empty( $job['id'] ) ) {
				return false;
			}

			$this->finalize_admin_ui_chat_job( $job, $this->run_admin_ui_chat_job( $job ) );
			return true;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue_runner', $lock_token );
		}
	}

	private function claim_next_admin_ui_chat_job( $preferred_job_id = '' ) {
		$preferred_job_id = $this->normalize_admin_ui_chat_job_id( $preferred_job_id );
		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return array();
		}

		try {
			$stored_queue  = $this->get_admin_ui_chat_queue();
			$queue         = $this->ensure_admin_ui_chat_queue_sequences( $stored_queue );
			$queue_changed = $queue !== $stored_queue;
			$recover_jobs  = array();
			$delivery_jobs = array();

			foreach ( $queue as $job_id => $job ) {
				if ( ! is_array( $job ) ) {
					unset( $queue[ $job_id ] );
					$queue_changed = true;
					continue;
				}

				$status = (string) ( $job['status'] ?? 'pending' );
				if ( 'terminal_pending' === $status && $this->is_admin_ui_chat_job_deferred( $job ) ) {
					continue;
				}
				$durable_result = $this->get_admin_ui_chat_job_result( (string) $job_id );
				if ( ! empty( $durable_result['status'] ) && in_array( (string) $durable_result['status'], array( 'done', 'failed' ), true ) ) {
					if ( $this->set_admin_ui_chat_request_result( $job, $durable_result, 15 * MINUTE_IN_SECONDS ) ) {
						if ( 'done' === (string) $durable_result['status'] && 'delivered' !== (string) ( $durable_result['delivery_status'] ?? '' ) ) {
							$job['status']          = 'delivery_pending';
							$job['terminal_result'] = $durable_result;
							$job['retry_after']     = time() + 5;
							$queue[ $job_id ]       = $job;
							$delivery_jobs[]        = (string) $job_id;
						} else {
							unset( $queue[ $job_id ] );
						}
					} else {
						$job['status']          = 'terminal_pending';
						$job['terminal_result'] = $durable_result;
						$job['updated_at']      = time();
						$job['retry_after']     = time() + 30;
						$queue[ $job_id ]       = $job;
						$recover_jobs[]         = (string) $job_id;
					}
					$queue_changed = true;
					continue;
				}
				if ( 'terminal_pending' === $status && ! empty( $job['terminal_result'] ) && is_array( $job['terminal_result'] ) ) {
					if ( $this->persist_admin_ui_chat_terminal_result( (string) $job_id, $job, $job['terminal_result'], 15 * MINUTE_IN_SECONDS ) ) {
						if ( 'done' === (string) ( $job['terminal_result']['status'] ?? '' ) && 'delivered' !== (string) ( $job['terminal_result']['delivery_status'] ?? '' ) ) {
							$job['status']      = 'delivery_pending';
							$job['retry_after'] = time() + 5;
							$queue[ $job_id ]   = $job;
							$delivery_jobs[]    = (string) $job_id;
						} else {
							unset( $queue[ $job_id ] );
						}
						$queue_changed = true;
					} else {
						$job['retry_after'] = time() + 30;
						$queue[ $job_id ]   = $job;
						$queue_changed      = true;
						$recover_jobs[] = (string) $job_id;
					}
					continue;
				}
				if ( $this->is_admin_ui_chat_pending_job_expired( $job ) || $this->is_admin_ui_chat_processing_job_stale( $job ) ) {
					$terminal = $this->build_expired_admin_ui_chat_job_result( (string) $job_id, $job );
					if ( $this->persist_admin_ui_chat_terminal_result( (string) $job_id, $job, $terminal, 15 * MINUTE_IN_SECONDS ) ) {
						unset( $queue[ $job_id ] );
					} else {
						$job['status']          = 'terminal_pending';
						$job['terminal_result'] = $terminal;
						$job['updated_at']      = time();
						$job['retry_after']     = time() + 30;
						$queue[ $job_id ]       = $job;
						$recover_jobs[]         = (string) $job_id;
					}
					$queue_changed = true;
				}
			}

			if ( $queue_changed && ! $this->write_admin_ui_chat_queue( $queue ) ) {
				return array();
			}
			foreach ( array_unique( $recover_jobs ) as $recover_job_id ) {
				$this->schedule_admin_ui_chat_recovery( $recover_job_id, 30 );
			}
			foreach ( array_unique( $delivery_jobs ) as $delivery_job_id ) {
				$payload = $this->get_admin_ui_chat_delivery_payload( $delivery_job_id );
				$this->schedule_admin_ui_chat_delivery_retry(
					$delivery_job_id,
					(string) ( $payload['session'] ?? '' ),
					(string) ( $payload['answer'] ?? '' ),
					(string) ( $payload['actor_id'] ?? '' )
				);
			}

			foreach ( $queue as $job_id => $job ) {
				if ( '' !== $preferred_job_id && (string) $job_id !== $preferred_job_id ) {
					continue;
				}
				$status = (string) ( $job['status'] ?? 'pending' );
				if ( 'pending' !== $status ) {
					continue;
				}
				if ( $this->is_admin_ui_chat_job_deferred( $job ) ) {
					continue;
				}
				if ( $this->has_blocking_admin_ui_chat_job_for_session( $queue, (string) ( $job['session_id'] ?? '' ), (string) $job_id ) ) {
					continue;
				}
				if ( empty( $job['claimable_since'] ) ) {
					$job['claimable_since'] = time();
				}
				$attempts = intval( $job['attempts'] ?? 0 ) + 1;
				if ( $attempts > 3 ) {
					$failed_message = __( 'Odgovor je prekidan vise puta zaredom i obustavljen je. Posalji poruku ponovo.', 'niwa-ai' );
					$terminal = array(
						'status'     => 'failed',
						'job_id'     => (string) $job_id,
						'user_id'    => intval( $job['user_id'] ?? 0 ),
						'request_id' => (string) ( $job['request_id'] ?? '' ),
						'generation' => intval( $job['generation'] ?? 0 ),
						'session'    => (string) ( $job['session_id'] ?? '' ),
						'error'      => $failed_message,
						'message'    => $failed_message,
					);
					if ( $this->persist_admin_ui_chat_terminal_result(
						(string) $job_id,
						$job,
						$terminal,
						15 * MINUTE_IN_SECONDS
					) ) {
						unset( $queue[ $job_id ] );
					} else {
						$job['status']          = 'terminal_pending';
						$job['terminal_result'] = $terminal;
						$job['updated_at']      = time();
						$job['retry_after']     = time() + 30;
						$queue[ $job_id ]       = $job;
					}
					if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
						return array();
					}
					if ( isset( $queue[ $job_id ] ) ) {
						$this->schedule_admin_ui_chat_recovery( (string) $job_id, 30 );
					}
					continue;
				}

				$job['status']           = 'processing';
				$job['attempts']         = $attempts;
				$job['generation']       = intval( $job['generation'] ?? 0 ) + 1;
				$job['worker_token']     = wp_generate_password( 32, false, false );
				$job['started_at']       = empty( $job['started_at'] ) ? time() : intval( $job['started_at'] );
				$job['updated_at']       = time();
				$job['lease_expires_at'] = time() + 60;
				$job['retry_after']      = 0;
				$queue[ $job_id ]  = $job;
				if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
					return array();
				}
				$this->set_admin_ui_chat_job_result(
					(string) $job_id,
					array(
						'status'     => 'processing',
						'job_id'     => (string) $job_id,
						'user_id'    => intval( $job['user_id'] ?? 0 ),
						'request_id' => (string) ( $job['request_id'] ?? '' ),
						'generation' => intval( $job['generation'] ?? 0 ),
						'session'    => (string) ( $job['session_id'] ?? '' ),
						'message'    => (string) ( $job['message'] ?? '' ),
						'created_at' => intval( $job['created_at'] ?? 0 ),
						'started_at' => intval( $job['started_at'] ?? 0 ),
					),
					15 * MINUTE_IN_SECONDS
				);
				$this->schedule_admin_ui_chat_recovery( (string) $job_id, 65 );

				return $job;
			}

			return array();
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function finalize_admin_ui_chat_job( $job, $result ) {
		$job = is_array( $job ) ? $job : array( 'id' => $job );
		$job_id = $this->normalize_admin_ui_chat_job_id( $job['id'] ?? '' );
		$result = is_array( $result ) ? $result : array();
		if ( '' === $job_id || ! $this->is_current_admin_ui_chat_job_claim( $job ) ) {
			return;
		}

		if ( 'pending' === (string) ( $result['status'] ?? '' ) && ! empty( $result['session_busy'] ) ) {
			$this->requeue_admin_ui_chat_job( $job_id, 'pending', 'session_busy', $job );
			$this->set_admin_ui_chat_job_result( $job_id, array_merge( $result, array( 'job_id' => $job_id, 'user_id' => intval( $job['user_id'] ?? 0 ), 'request_id' => (string) ( $job['request_id'] ?? '' ), 'generation' => intval( $job['generation'] ?? 0 ) ) ), 5 * MINUTE_IN_SECONDS );
			return;
		}

		if ( 'processing' === (string) ( $result['status'] ?? '' ) && ! empty( $result['continuation_pending'] ) ) {
			$this->mark_admin_ui_chat_job_continuation_pending( $job_id, $result, $job );
			$this->set_admin_ui_chat_job_result( $job_id, array_merge( $result, array( 'job_id' => $job_id, 'user_id' => intval( $job['user_id'] ?? 0 ), 'request_id' => (string) ( $job['request_id'] ?? '' ), 'generation' => intval( $job['generation'] ?? 0 ) ) ), 15 * MINUTE_IN_SECONDS );
			return;
		}

		$result['job_id']     = $job_id;
		$result['user_id']    = intval( $job['user_id'] ?? 0 );
		$result['request_id'] = (string) ( $job['request_id'] ?? '' );
		$result['generation'] = intval( $job['generation'] ?? 0 );
		$result['sequence']    = intval( $job['sequence'] ?? 0 );
		$result['message_hash'] = (string) ( $job['message_hash'] ?? '' );
		if ( $this->persist_admin_ui_chat_terminal_result( $job_id, $job, $result, 15 * MINUTE_IN_SECONDS ) ) {
			$this->remove_admin_ui_chat_job( $job_id, $job );
			return;
		}

		$this->mark_admin_ui_chat_terminal_pending( $job, $result );
	}

	private function is_current_admin_ui_chat_job_claim( $job ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job['id'] ?? '' );
		$queue  = $this->get_admin_ui_chat_queue();
		$current = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();

		return ! empty( $current )
			&& intval( $current['generation'] ?? 0 ) === intval( $job['generation'] ?? 0 )
			&& hash_equals( (string) ( $current['worker_token'] ?? '' ), (string) ( $job['worker_token'] ?? '' ) );
	}

	private function mark_admin_ui_chat_terminal_pending( $job, $result ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job['id'] ?? '' );
		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $job_id || '' === $lock_token ) {
			return false;
		}

		try {
			$queue   = $this->get_admin_ui_chat_queue();
			$current = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			if (
				empty( $current )
				|| intval( $current['generation'] ?? 0 ) !== intval( $job['generation'] ?? 0 )
				|| ! hash_equals( (string) ( $current['worker_token'] ?? '' ), (string) ( $job['worker_token'] ?? '' ) )
			) {
				return false;
			}

			$current['status']          = 'terminal_pending';
			$current['terminal_result'] = $result;
			$current['updated_at']      = time();
			$current['retry_after']     = time() + 30;
			$queue[ $job_id ]           = $current;
			$saved = $this->write_admin_ui_chat_queue( $queue );
			if ( $saved ) {
				$this->schedule_admin_ui_chat_recovery( $job_id, 30 );
			}
			return $saved;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function mark_admin_ui_chat_job_continuation_pending( $job_id, $result, $claim = array() ) {
		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return;
		}

		try {
			$queue = $this->get_admin_ui_chat_queue();
			if ( ! empty( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ) {
				if ( ! empty( $claim ) && (
					intval( $queue[ $job_id ]['generation'] ?? 0 ) !== intval( $claim['generation'] ?? 0 )
					|| ! hash_equals( (string) ( $queue[ $job_id ]['worker_token'] ?? '' ), (string) ( $claim['worker_token'] ?? '' ) )
				) ) {
					return false;
				}
				$queue[ $job_id ]['status']                       = 'processing';
				$queue[ $job_id ]['updated_at']                   = time();
				$actor_started = ! empty( $queue[ $job_id ]['actor_started_at'] );
				$lease_seconds = $actor_started ? 30 * MINUTE_IN_SECONDS : 5 * MINUTE_IN_SECONDS;
				$queue[ $job_id ]['lease_expires_at']             = time() + $lease_seconds;
				$queue[ $job_id ]['last_note']                    = 'continuation_pending';
				$queue[ $job_id ]['continuation_pending']         = true;
				$queue[ $job_id ]['actor_reservation_pending']     = false;
				$queue[ $job_id ]['admin_ui_continuation_actor_id'] = isset( $result['admin_ui_continuation_actor_id'] ) ? (string) $result['admin_ui_continuation_actor_id'] : '';
				$this->write_admin_ui_chat_queue( $queue );
				$this->schedule_admin_ui_chat_recovery( (string) $job_id, $actor_started ? $lease_seconds + 5 : 30 );
			}
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	public function filter_admin_ui_chat_job_transition( $accepted, $transition ) {
		$transition = is_array( $transition ) ? $transition : array();
		$type       = sanitize_key( (string) ( $transition['type'] ?? '' ) );
		if ( 'reserve' === $type ) {
			return $this->bind_admin_ui_chat_continuation_job(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['request_id'] ?? '',
				$transition['actor_id'] ?? '',
				60,
				true
			);
		}
		if ( 'bind' === $type ) {
			return $this->bind_admin_ui_chat_continuation_job(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['request_id'] ?? '',
				$transition['actor_id'] ?? ''
			);
		}
		if ( 'progress' === $type ) {
			return $this->refresh_admin_ui_chat_continuation_job(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['actor_id'] ?? ''
			);
		}
		if ( 'complete' === $type ) {
			return $this->complete_admin_ui_chat_job(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['answer'] ?? '',
				$transition['actor_id'] ?? '',
				! empty( $transition['delivery_retry'] )
			);
		}
		if ( 'fail' === $type ) {
			return $this->fail_admin_ui_chat_job(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['actor_id'] ?? '',
				$transition['error'] ?? 'admin_ui_chat_failed',
				$transition['message'] ?? ''
			);
		}
		if ( 'actor_gate' === $type ) {
			return $this->get_admin_ui_chat_actor_gate(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['actor_id'] ?? ''
			);
		}
		if ( 'delivery_status' === $type ) {
			return $this->get_admin_ui_chat_delivery_status(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['actor_id'] ?? ''
			);
		}
		if ( 'delivery_payload' === $type ) {
			return $this->get_admin_ui_chat_delivery_payload( $transition['job_id'] ?? '' );
		}
		if ( 'fail_delivery' === $type ) {
			return $this->fail_admin_ui_chat_delivery( $transition['job_id'] ?? '' );
		}
		if ( 'delivered' === $type ) {
			return $this->mark_admin_ui_chat_delivery_complete(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['actor_id'] ?? ''
			);
		}
		if ( 'begin_delivery' === $type ) {
			return $this->acquire_named_option_lock( 'admin_ui_chat_delivery_' . md5( (string) ( $transition['job_id'] ?? '' ) ), 2, 30 );
		}
		if ( 'end_delivery' === $type ) {
			$this->release_named_option_lock(
				'admin_ui_chat_delivery_' . md5( (string) ( $transition['job_id'] ?? '' ) ),
				(string) ( $transition['lock_token'] ?? '' )
			);
			return true;
		}
		if ( 'retry_delivery' === $type ) {
			$this->schedule_admin_ui_chat_delivery_retry(
				$transition['job_id'] ?? '',
				$transition['session_id'] ?? '',
				$transition['answer'] ?? '',
				$transition['actor_id'] ?? ''
			);
			return true;
		}

		return false;
	}

	private function bind_admin_ui_chat_continuation_job( $job_id, $session_id, $request_id, $actor_id, $lease_seconds = 300, $reservation_pending = false ) {
		$job_id     = $this->normalize_admin_ui_chat_job_id( $job_id );
		$session_id = trim( (string) $session_id );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );
		$actor_id   = sanitize_text_field( (string) $actor_id );
		$lease_seconds = max( 60, intval( $lease_seconds ) );
		if ( '' === $job_id || '' === $session_id || '' === $actor_id ) {
			return false;
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return false;
		}

		try {
			$queue = $this->get_admin_ui_chat_queue();
			$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			$stored_actor = sanitize_text_field( (string) ( $job['admin_ui_continuation_actor_id'] ?? '' ) );
			if (
				empty( $job )
				|| 'processing' !== (string) ( $job['status'] ?? '' )
				|| $this->is_admin_ui_chat_processing_job_stale( $job )
				|| ! hash_equals( (string) ( $job['session_id'] ?? '' ), $session_id )
				|| ! hash_equals( (string) ( $job['request_id'] ?? '' ), $request_id )
				|| ( '' !== $stored_actor && ! hash_equals( $stored_actor, $actor_id ) )
			) {
				return false;
			}

			$job['continuation_pending']              = true;
			$job['admin_ui_continuation_actor_id']    = $actor_id;
			$job['actor_reservation_pending']          = ! empty( $reservation_pending );
			$job['last_note']                         = ! empty( $reservation_pending ) ? 'actor_reserved' : 'continuation_pending';
			$job['updated_at']                        = time();
			$job['lease_expires_at']                  = time() + $lease_seconds;
			$job['retry_after']                       = 0;
			$queue[ $job_id ]                         = $job;
			$saved = $this->write_admin_ui_chat_queue( $queue );
			if ( $saved ) {
				$this->schedule_admin_ui_chat_recovery( $job_id, ! empty( $reservation_pending ) ? $lease_seconds + 5 : 30 );
			}

			return $saved;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function refresh_admin_ui_chat_continuation_job( $job_id, $session_id, $actor_id ) {
		$job_id     = $this->normalize_admin_ui_chat_job_id( $job_id );
		$session_id = trim( (string) $session_id );
		$actor_id   = sanitize_text_field( (string) $actor_id );
		if ( '' === $job_id || '' === $session_id || '' === $actor_id ) {
			return false;
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 1, 15 );
		if ( '' === $lock_token ) {
			return false;
		}

		try {
			$queue = $this->get_admin_ui_chat_queue();
			$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			if (
				empty( $job )
				|| 'processing' !== (string) ( $job['status'] ?? '' )
				|| empty( $job['continuation_pending'] )
				|| ! empty( $job['actor_reservation_pending'] )
				|| ! hash_equals( (string) ( $job['session_id'] ?? '' ), $session_id )
				|| ! hash_equals( (string) ( $job['admin_ui_continuation_actor_id'] ?? '' ), $actor_id )
			) {
				return false;
			}

			$now = time();
			if ( intval( $job['updated_at'] ?? 0 ) >= $now - 10 && intval( $job['lease_expires_at'] ?? 0 ) >= $now + 29 * MINUTE_IN_SECONDS ) {
				return true;
			}

			$job['actor_started_at'] = intval( $job['actor_started_at'] ?? 0 ) ?: $now;
			$job['updated_at']       = $now;
			$job['lease_expires_at'] = $now + 30 * MINUTE_IN_SECONDS;
			$job['last_note']        = 'actor_progress';
			$queue[ $job_id ]        = $job;
			$saved = $this->write_admin_ui_chat_queue( $queue );
			if ( $saved ) {
				$this->schedule_admin_ui_chat_recovery( $job_id, 30 * MINUTE_IN_SECONDS + 5 );
			}
			return $saved;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function fail_admin_ui_chat_job( $job_id, $session_id, $actor_id, $error, $message = '' ) {
		$job_id     = $this->normalize_admin_ui_chat_job_id( $job_id );
		$session_id = trim( (string) $session_id );
		$actor_id   = sanitize_text_field( (string) $actor_id );
		$error      = sanitize_key( (string) $error );
		$message    = trim( (string) $message );
		if ( '' === $job_id || '' === $session_id || '' === $actor_id ) {
			return false;
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return false;
		}

		$wake_successor = false;
		try {
			$queue = $this->get_admin_ui_chat_queue();
			$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			if (
				empty( $job )
				|| 'processing' !== (string) ( $job['status'] ?? '' )
				|| ! hash_equals( (string) ( $job['session_id'] ?? '' ), $session_id )
				|| ! hash_equals( (string) ( $job['admin_ui_continuation_actor_id'] ?? '' ), $actor_id )
			) {
				return false;
			}

			$terminal_message = '' !== $message ? $message : __( 'Admin chat obrada nije bezbedno zavrsena. Neke trazene radnje su mozda izvrsene; prvo proveri trenutno stanje pre ponovnog slanja.', 'niwa-ai' );
			$terminal = array(
				'status'     => 'failed',
				'job_id'     => $job_id,
				'user_id'    => intval( $job['user_id'] ?? 0 ),
				'request_id' => (string) ( $job['request_id'] ?? '' ),
				'generation' => intval( $job['generation'] ?? 0 ),
				'session'    => $session_id,
				'actor_id'   => $actor_id,
				'error'      => $terminal_message,
				'code'       => '' !== $error ? $error : 'admin_ui_chat_failed',
				'message'    => $terminal_message,
				'updated_at' => time(),
			);
			if ( ! $this->persist_admin_ui_chat_terminal_result( $job_id, $job, $terminal, 15 * MINUTE_IN_SECONDS ) ) {
				$job['status']          = 'terminal_pending';
				$job['terminal_result'] = $terminal;
				$job['updated_at']      = time();
				$job['retry_after']     = time() + 30;
				$queue[ $job_id ]       = $job;
				return $this->write_admin_ui_chat_queue( $queue );
			}

			unset( $queue[ $job_id ] );
			if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
				return false;
			}
			$wake_successor = $this->has_claimable_admin_ui_chat_jobs( $queue );
			return true;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
			if ( $wake_successor ) {
				$this->dispatch_admin_ui_chat_worker( 'continuation-failed' );
			}
		}
	}

	private function requeue_admin_ui_chat_job( $job_id, $status = 'pending', $reason = '', $claim = array() ) {
		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return;
		}

		try {
			$queue = $this->get_admin_ui_chat_queue();
			if ( ! empty( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ) {
				if ( ! empty( $claim ) && (
					intval( $queue[ $job_id ]['generation'] ?? 0 ) !== intval( $claim['generation'] ?? 0 )
					|| ! hash_equals( (string) ( $queue[ $job_id ]['worker_token'] ?? '' ), (string) ( $claim['worker_token'] ?? '' ) )
				) ) {
					return false;
				}
				$queue[ $job_id ]['status']     = sanitize_key( $status );
				$queue[ $job_id ]['updated_at'] = time();
				$queue[ $job_id ]['last_note']  = sanitize_key( $reason );
				$queue[ $job_id ]['worker_token'] = '';
				$queue[ $job_id ]['lease_expires_at'] = 0;
				if ( 'session_busy' === $reason ) {
					// A busy session lane is a normal wait, not a failed run —
					// refund the claim attempt so it does not burn the retry cap.
					$queue[ $job_id ]['attempts'] = max( 0, intval( $queue[ $job_id ]['attempts'] ?? 0 ) - 1 );
					$queue[ $job_id ]['retry_after'] = time() + 30;
				}
				$saved = $this->write_admin_ui_chat_queue( $queue );
				if ( $saved && 'session_busy' === $reason ) {
					$this->schedule_admin_ui_chat_recovery( (string) $job_id, 30 );
				}
			}
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function remove_admin_ui_chat_job( $job_id, $claim = array() ) {
		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return;
		}

		try {
			$queue = $this->get_admin_ui_chat_queue();
			if ( isset( $queue[ $job_id ] ) ) {
				if ( ! empty( $claim ) && (
					intval( $queue[ $job_id ]['generation'] ?? 0 ) !== intval( $claim['generation'] ?? 0 )
					|| ! hash_equals( (string) ( $queue[ $job_id ]['worker_token'] ?? '' ), (string) ( $claim['worker_token'] ?? '' ) )
				) ) {
					return false;
				}
				unset( $queue[ $job_id ] );
				return $this->write_admin_ui_chat_queue( $queue );
			}
			return true;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	public function complete_admin_ui_chat_job( $job_id, $session_id, $answer, $actor_id = '', $delivery_retry = false ) {
		$job_id     = $this->normalize_admin_ui_chat_job_id( $job_id );
		$session_id = trim( (string) $session_id );
		$answer     = trim( (string) $answer );
		$actor_id   = sanitize_text_field( (string) $actor_id );
		if ( '' === $job_id || '' === $answer ) {
			return false;
		}
		$this->set_admin_ui_chat_delivery_payload(
			$job_id,
			array_merge(
				$this->get_admin_ui_chat_delivery_payload( $job_id ),
				array(
				'job_id'          => $job_id,
				'session'         => $session_id,
				'answer'          => $answer,
				'actor_id'        => $actor_id,
				'delivery_status' => 'pending',
				'updated_at'      => time(),
				)
			)
		);

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
			return false;
		}

		try {
			$queue          = $this->get_admin_ui_chat_queue();
			$job            = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			$current        = $this->get_admin_ui_chat_job_result( $job_id );
			$queued_terminal = in_array( (string) ( $job['status'] ?? '' ), array( 'terminal_pending', 'delivery_pending' ), true ) && is_array( $job['terminal_result'] ?? null )
				? $job['terminal_result']
				: array();
			$terminal       = in_array( (string) ( $current['status'] ?? '' ), array( 'done', 'failed' ), true ) ? $current : $queued_terminal;
			if ( ! empty( $terminal ) ) {
				if ( ! $this->admin_ui_chat_terminal_matches_delivery( $terminal, $job_id, $session_id, $answer, $actor_id ) ) {
					return false;
				}
				$terminal_job = ! empty( $job ) ? $job : $terminal;
				if ( ! $this->persist_admin_ui_chat_terminal_result( $job_id, $terminal_job, $terminal, 15 * MINUTE_IN_SECONDS ) ) {
					$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
					return false;
				}
				if ( ! empty( $job ) ) {
					$job['status']          = 'delivery_pending';
					$job['terminal_result'] = $terminal;
					$job['worker_token']    = '';
					$job['updated_at']      = time();
					$job['lease_expires_at'] = time() + 120;
					$job['retry_after']     = time() + 5;
					$queue[ $job_id ]       = $job;
					if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
						$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
						return false;
					}
				}
				if ( ! $delivery_retry ) {
					$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
				}
				return true;
			}

			$stored_actor   = sanitize_text_field( (string) ( $job['admin_ui_continuation_actor_id'] ?? '' ) );
			$stored_session = trim( (string) ( $job['session_id'] ?? '' ) );
			if (
				empty( $job )
				|| 'processing' !== (string) ( $job['status'] ?? '' )
				|| empty( $job['continuation_pending'] )
				|| '' === $stored_actor
				|| '' === $actor_id
				|| ! hash_equals( $stored_actor, $actor_id )
				|| '' === $stored_session
				|| ! hash_equals( $stored_session, $session_id )
				|| $this->is_admin_ui_chat_processing_job_stale( $job )
			) {
				return false;
			}

			$terminal = array(
				'status'     => 'done',
				'job_id'     => $job_id,
				'user_id'    => intval( $job['user_id'] ?? 0 ),
				'request_id' => (string) ( $job['request_id'] ?? '' ),
				'generation' => intval( $job['generation'] ?? 0 ),
				'session'    => $session_id,
				'message'    => (string) ( $job['message'] ?? '' ),
				'answer'     => $answer,
				'actor_id'   => $actor_id,
				'delivery_status' => 'pending',
				'updated_at' => time(),
			);
			$this->set_admin_ui_chat_delivery_payload( $job_id, $terminal );
			if ( ! $this->persist_admin_ui_chat_terminal_result(
				$job_id,
				$job,
				$terminal,
				15 * MINUTE_IN_SECONDS
			) ) {
				$current = $this->get_admin_ui_chat_job_result( $job_id );
				if ( 'failed' === (string) ( $current['status'] ?? '' ) ) {
					return false;
				}
				if ( 'done' === (string) ( $current['status'] ?? '' ) ) {
					$terminal = $current;
				}
				if ( ! empty( $job ) ) {
					$job['status']          = 'terminal_pending';
					$job['terminal_result'] = $terminal;
					$job['updated_at']      = time();
					$job['retry_after']     = time() + 30;
					$queue[ $job_id ]       = $job;
					$this->write_admin_ui_chat_queue( $queue );
					$this->schedule_admin_ui_chat_recovery( $job_id, 30 );
				}
				$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
				return false;
			}
			if ( isset( $queue[ $job_id ] ) ) {
				$job['status']           = 'delivery_pending';
				$job['terminal_result']  = $terminal;
				$job['worker_token']     = '';
				$job['updated_at']       = time();
				$job['lease_expires_at'] = time() + 120;
				$job['retry_after']      = time() + 5;
				$queue[ $job_id ]        = $job;
				if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
					$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
					return false;
				}
			}
			if ( ! $delivery_retry ) {
				$this->schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id );
			}
			return true;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
		}
	}

	private function schedule_admin_ui_chat_delivery_retry( $job_id, $session_id, $answer, $actor_id ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job_id );
		$args   = array( $job_id );
		if ( '' === $job_id || ! function_exists( 'wp_schedule_single_event' ) || false !== wp_next_scheduled( 'niwa_ai_retry_admin_ui_chat_delivery', $args ) ) {
			return;
		}

		$payload = $this->get_admin_ui_chat_delivery_payload( $job_id );
		if ( empty( $payload ) && '' !== trim( (string) $answer ) ) {
			$payload = array(
				'job_id'          => $job_id,
				'session'         => trim( (string) $session_id ),
				'answer'          => trim( (string) $answer ),
				'actor_id'        => sanitize_text_field( (string) $actor_id ),
				'delivery_status' => 'pending',
			);
		}
		if ( empty( $payload ) || 'delivered' === (string) ( $payload['delivery_status'] ?? '' ) ) {
			return;
		}

		$now                               = time();
		$payload['delivery_first_pending_at'] = intval( $payload['delivery_first_pending_at'] ?? 0 ) ?: $now;
		$payload['delivery_attempts']      = intval( $payload['delivery_attempts'] ?? 0 ) + 1;
		$payload['delivery_retry_after']   = $now + 5;
		$payload['updated_at']             = $now;
		if ( intval( $payload['delivery_attempts'] ) > 6 || intval( $payload['delivery_first_pending_at'] ) + 120 <= $now ) {
			$payload['delivery_failure_pending'] = true;
		}
		$this->set_admin_ui_chat_delivery_payload( $job_id, $payload );

		wp_schedule_single_event( $now + 5, 'niwa_ai_retry_admin_ui_chat_delivery', $args );
	}

	private function get_admin_ui_chat_delivery_payload( $job_id ) {
		$job_id  = $this->normalize_admin_ui_chat_job_id( $job_id );
		$terminal = $this->get_admin_ui_chat_job_result( $job_id );
		$payload = Niwa_AI_Durable_Cache::get( $this->admin_ui_chat_delivery_payload_key( $job_id ) );
		$payload = is_array( $payload ) && hash_equals( (string) ( $payload['job_id'] ?? '' ), $job_id ) ? $payload : array();
		if ( 'done' === (string) ( $terminal['status'] ?? '' ) ) {
			return array_merge( $payload, $terminal );
		}
		if ( ! empty( $payload ) ) {
			return $payload;
		}

		$queue = $this->get_admin_ui_chat_queue();
		$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
		return in_array( (string) ( $job['status'] ?? '' ), array( 'terminal_pending', 'delivery_pending' ), true ) && is_array( $job['terminal_result'] ?? null )
			? $job['terminal_result']
			: array();
	}

	private function recover_pending_admin_ui_chat_delivery( $terminal ) {
		$terminal = is_array( $terminal ) ? $terminal : array();
		$job_id   = $this->normalize_admin_ui_chat_job_id( $terminal['job_id'] ?? '' );
		if (
			'' === $job_id
			|| intval( $terminal['delivery_retry_after'] ?? 0 ) > time()
			|| ! $this->admin_autonomy
			|| ! method_exists( $this->admin_autonomy, 'retry_admin_ui_chat_delivery' )
		) {
			return $terminal;
		}

		$this->admin_autonomy->retry_admin_ui_chat_delivery(
			$job_id,
			(string) ( $terminal['session'] ?? ( $terminal['session_id'] ?? '' ) ),
			(string) ( $terminal['answer'] ?? '' ),
			(string) ( $terminal['actor_id'] ?? '' )
		);
		$refreshed = $this->get_admin_ui_chat_job_result( $job_id );
		return ! empty( $refreshed ) ? $refreshed : $terminal;
	}

	private function set_admin_ui_chat_delivery_payload( $job_id, $payload ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job_id );
		$payload = is_array( $payload ) ? $payload : array();
		return '' !== $job_id && Niwa_AI_Durable_Cache::set( $this->admin_ui_chat_delivery_payload_key( $job_id ), $payload, 15 * MINUTE_IN_SECONDS );
	}

	private function fail_admin_ui_chat_delivery( $job_id ) {
		$job_id       = $this->normalize_admin_ui_chat_job_id( $job_id );
		if ( '' === $job_id ) {
			return false;
		}
		$delivery_lock = $this->acquire_named_option_lock( 'admin_ui_chat_delivery_' . md5( $job_id ), 2, 30 );
		if ( '' === $delivery_lock ) {
			return false;
		}

		try {
			$terminal = $this->get_admin_ui_chat_job_result( $job_id );
			if ( 'delivered' === (string) ( $terminal['delivery_status'] ?? '' ) ) {
				return $this->remove_admin_ui_chat_terminal_blocker( $job_id, 'delivery-complete' );
			}
			$payload = $this->get_admin_ui_chat_delivery_payload( $job_id );
			$already_failed = 'failed' === (string) ( $terminal['status'] ?? '' )
				&& 'failed' === (string) ( $terminal['delivery_status'] ?? '' );
			if ( ! $already_failed && ( 'done' !== (string) ( $terminal['status'] ?? '' ) || empty( $payload['delivery_failure_pending'] ) ) ) {
				return false;
			}

			$message = __( 'Admin chat je napravio odgovor, ali trajni upis odgovora nije uspeo. Neke trazene radnje su mozda izvrsene; prvo proveri trenutno stanje pre ponovnog slanja.', 'niwa-ai' );
			$failed  = array_merge(
				is_array( $payload ) ? $payload : array(),
				is_array( $terminal ) ? $terminal : array(),
				array(
					'status'          => 'failed',
					'delivery_status' => 'failed',
					'code'            => 'admin_ui_chat_delivery_failed',
					'error'           => $message,
					'message'         => $message,
					'updated_at'      => time(),
				)
			);
			if (
				! Niwa_AI_Durable_Cache::set( $this->admin_ui_chat_job_result_key( $job_id ), $failed, 15 * MINUTE_IN_SECONDS )
				|| ! $this->set_admin_ui_chat_request_result_force( $failed, $failed, 15 * MINUTE_IN_SECONDS )
				|| ! $this->set_admin_ui_chat_delivery_payload( $job_id, $failed )
			) {
				return false;
			}

			return $this->remove_admin_ui_chat_terminal_blocker( $job_id, 'delivery-failed' );
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_delivery_' . md5( $job_id ), $delivery_lock );
		}
	}

	private function admin_ui_chat_terminal_matches_delivery( $terminal, $job_id, $session_id, $answer, $actor_id ) {
		return 'done' === (string) ( $terminal['status'] ?? '' )
			&& hash_equals( (string) ( $terminal['job_id'] ?? '' ), (string) $job_id )
			&& hash_equals( (string) ( $terminal['session'] ?? ( $terminal['session_id'] ?? '' ) ), (string) $session_id )
			&& hash_equals( (string) ( $terminal['answer'] ?? '' ), (string) $answer )
			&& hash_equals( (string) ( $terminal['actor_id'] ?? '' ), (string) $actor_id );
	}

	private function get_admin_ui_chat_actor_gate( $job_id, $session_id, $actor_id ) {
		$job_id     = $this->normalize_admin_ui_chat_job_id( $job_id );
		$session_id = trim( (string) $session_id );
		$actor_id   = sanitize_text_field( (string) $actor_id );
		$terminal   = $this->get_admin_ui_chat_job_result( $job_id );
		if ( 'done' === (string) ( $terminal['status'] ?? '' ) ) {
			return $this->admin_ui_chat_terminal_matches_delivery(
				$terminal,
				$job_id,
				$session_id,
				(string) ( $terminal['answer'] ?? '' ),
				$actor_id
			)
				? array( 'committed' => true, 'answer' => (string) $terminal['answer'] )
				: array( 'cancelled' => true );
		}

		$queue = $this->get_admin_ui_chat_queue();
		$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
		if ( empty( $job ) || 'processing' !== (string) ( $job['status'] ?? '' ) || $this->is_admin_ui_chat_processing_job_stale( $job ) ) {
			return array( 'cancelled' => true );
		}
		$stored_actor = sanitize_text_field( (string) ( $job['admin_ui_continuation_actor_id'] ?? '' ) );
		if ( '' === $stored_actor ) {
			return array( 'hold' => true );
		}
		if ( empty( $job['continuation_pending'] )
			|| ! hash_equals( (string) ( $job['session_id'] ?? '' ), $session_id )
			|| ! hash_equals( $stored_actor, $actor_id )
		) {
			return array( 'cancelled' => true );
		}
		if ( ! empty( $job['actor_reservation_pending'] ) ) {
			return array( 'hold' => true );
		}
		return array( 'allowed' => true );
	}

	private function get_admin_ui_chat_delivery_status( $job_id, $session_id, $actor_id ) {
		$terminal = $this->get_admin_ui_chat_job_result( $this->normalize_admin_ui_chat_job_id( $job_id ) );
		if ( ! $this->admin_ui_chat_terminal_matches_delivery( $terminal, $job_id, $session_id, (string) ( $terminal['answer'] ?? '' ), $actor_id ) ) {
			return array();
		}
		return $terminal;
	}

	private function mark_admin_ui_chat_delivery_complete( $job_id, $session_id, $actor_id ) {
		$job_id   = $this->normalize_admin_ui_chat_job_id( $job_id );
		$terminal = $this->get_admin_ui_chat_delivery_status( $job_id, $session_id, $actor_id );
		if ( empty( $terminal ) ) {
			return false;
		}

		$terminal['delivery_status'] = 'delivered';
		$terminal['delivered_at']    = intval( $terminal['delivered_at'] ?? 0 ) ?: time();
		if (
			! Niwa_AI_Durable_Cache::set( $this->admin_ui_chat_job_result_key( $job_id ), $terminal, 15 * MINUTE_IN_SECONDS )
			|| ! $this->set_admin_ui_chat_request_result_force( $terminal, $terminal, 15 * MINUTE_IN_SECONDS )
		) {
			return false;
		}
		if ( ! $this->remove_admin_ui_chat_terminal_blocker( $job_id, 'delivery-complete', $session_id, $actor_id ) ) {
			return false;
		}

		Niwa_AI_Durable_Cache::delete( $this->admin_ui_chat_delivery_payload_key( $job_id ) );
		return true;
	}

	private function remove_admin_ui_chat_terminal_blocker( $job_id, $wake_reason, $session_id = '', $actor_id = '' ) {
		$job_id   = $this->normalize_admin_ui_chat_job_id( $job_id );
		$session_id = trim( (string) $session_id );
		$actor_id = sanitize_text_field( (string) $actor_id );
		if ( '' === $job_id ) {
			return false;
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 2, 15 );
		if ( '' === $lock_token ) {
			return false;
		}

		$wake_successor = false;
		try {
			$queue = $this->get_admin_ui_chat_queue();
			$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			if ( empty( $job ) ) {
				return true;
			}
			if ( ! in_array( (string) ( $job['status'] ?? '' ), array( 'terminal_pending', 'delivery_pending' ), true ) ) {
				return false;
			}
			$stored_actor = sanitize_text_field( (string) ( $job['admin_ui_continuation_actor_id'] ?? '' ) );
			if (
				'' !== $actor_id
				&& ( '' === $stored_actor || ! hash_equals( $stored_actor, $actor_id ) || ! hash_equals( (string) ( $job['session_id'] ?? '' ), $session_id ) )
			) {
				return false;
			}

			unset( $queue[ $job_id ] );
			if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
				return false;
			}
			$wake_successor = $this->has_claimable_admin_ui_chat_jobs( $queue );
			return true;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
			if ( $wake_successor ) {
				$this->dispatch_admin_ui_chat_worker( sanitize_key( (string) $wake_reason ) );
			}
		}
	}

	private function has_claimable_admin_ui_chat_jobs( $queue = null ) {
		$queue = is_array( $queue ) ? $queue : $this->get_admin_ui_chat_queue();
		foreach ( $queue as $job_id => $job ) {
			if ( ! is_array( $job ) ) {
				continue;
			}

			$status = (string) ( $job['status'] ?? 'pending' );
			if ( $this->is_admin_ui_chat_pending_job_expired( $job ) || $this->is_admin_ui_chat_processing_job_stale( $job ) ) {
				return true;
			}
			if ( 'terminal_pending' === $status && ! $this->is_admin_ui_chat_job_deferred( $job ) ) {
				return true;
			}
			if ( 'pending' === $status && ! $this->is_admin_ui_chat_job_deferred( $job ) && ! $this->has_blocking_admin_ui_chat_job_for_session( $queue, (string) ( $job['session_id'] ?? '' ), (string) $job_id ) ) {
				return true;
			}
		}
		return false;
	}

	private function has_blocking_admin_ui_chat_job_for_session( $queue, $session_id, $candidate_job_id = '' ) {
		$session_id = trim( (string) $session_id );
		if ( '' === $session_id ) {
			return true;
		}

		$candidate = isset( $queue[ $candidate_job_id ] ) && is_array( $queue[ $candidate_job_id ] ) ? $queue[ $candidate_job_id ] : array();
		$candidate_sequence = intval( $candidate['sequence'] ?? PHP_INT_MAX );
		foreach ( (array) $queue as $job_id => $job ) {
			if ( (string) $job_id === (string) $candidate_job_id || ! is_array( $job ) || (string) ( $job['session_id'] ?? '' ) !== $session_id ) {
				continue;
			}

			$status = (string) ( $job['status'] ?? 'pending' );
			$sequence = intval( $job['sequence'] ?? 0 );
			if ( $sequence > 0 && $sequence < $candidate_sequence && in_array( $status, array( 'pending', 'processing', 'terminal_pending', 'delivery_pending' ), true ) ) {
				return true;
			}
		}

		return false;
	}

	private function is_admin_ui_chat_pending_job_expired( $job ) {
		if ( ! is_array( $job ) || 'pending' !== (string) ( $job['status'] ?? 'pending' ) ) {
			return false;
		}

		$created_at = intval( $job['created_at'] ?? 0 );
		$claimable_since = intval( $job['claimable_since'] ?? 0 );
		return $created_at <= 0
			|| $created_at + DAY_IN_SECONDS <= time()
			|| ( $claimable_since > 0 && $claimable_since + 5 * MINUTE_IN_SECONDS <= time() );
	}

	private function is_admin_ui_chat_job_deferred( $job ) {
		return is_array( $job )
			&& intval( $job['retry_after'] ?? 0 ) > time();
	}

	private function has_unfinished_admin_ui_chat_jobs() {
		foreach ( $this->get_admin_ui_chat_queue() as $job ) {
			if ( ! is_array( $job ) ) {
				continue;
			}

			$status = (string) ( $job['status'] ?? 'pending' );
			if ( 'processing' === $status || ( in_array( $status, array( 'pending', 'terminal_pending' ), true ) && ! $this->is_admin_ui_chat_job_deferred( $job ) ) ) {
				return true;
			}
		}

		return false;
	}

	private function build_expired_admin_ui_chat_job_result( $job_id, $job ) {
		$job           = is_array( $job ) ? $job : array();
		$was_processing = 'processing' === (string) ( $job['status'] ?? 'pending' );
		$message       = $was_processing
			? __( 'Obrada admin chat poruke je prekinuta i nije automatski ponovljena. Neke trazene radnje su mozda izvrsene; prvo proveri trenutno stanje pre ponovnog slanja.', 'niwa-ai' )
			: __( 'Admin chat job expired before processing started. Send the message again.', 'niwa-ai' );

		return array(
			'status'     => 'failed',
			'job_id'     => (string) $job_id,
			'user_id'    => intval( $job['user_id'] ?? 0 ),
			'request_id' => (string) ( $job['request_id'] ?? '' ),
			'generation' => intval( $job['generation'] ?? 0 ),
			'session'    => (string) ( $job['session_id'] ?? '' ),
			'error'      => $message,
			'message'    => $message,
			'code'       => $was_processing ? 'admin_ui_chat_processing_expired' : 'admin_ui_chat_pending_expired',
			'created_at' => intval( $job['created_at'] ?? 0 ),
			'started_at' => intval( $job['started_at'] ?? 0 ),
			'updated_at' => time(),
		);
	}

	private function recover_expired_admin_ui_chat_job( $job_id ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job_id );
		if ( '' === $job_id ) {
			return array();
		}

		$lock_token = $this->acquire_named_option_lock( 'admin_ui_chat_queue', 1, 15 );
		if ( '' === $lock_token ) {
			return array();
		}

		$wake_successor = false;
		try {
			$queue = $this->get_admin_ui_chat_queue();
			$job   = isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ? $queue[ $job_id ] : array();
			if (
				empty( $job )
				|| ( ! $this->is_admin_ui_chat_pending_job_expired( $job ) && ! $this->is_admin_ui_chat_processing_job_stale( $job ) )
			) {
				return array();
			}

			$terminal = $this->build_expired_admin_ui_chat_job_result( $job_id, $job );
			if ( $this->persist_admin_ui_chat_terminal_result( $job_id, $job, $terminal, 15 * MINUTE_IN_SECONDS ) ) {
				unset( $queue[ $job_id ] );
				$this->write_admin_ui_chat_queue( $queue );
				$wake_successor = $this->has_claimable_admin_ui_chat_jobs( $queue );
				$stored_terminal = $this->get_admin_ui_chat_job_result( $job_id );
				return ! empty( $stored_terminal ) ? $stored_terminal : $terminal;
			}

			$job['status']          = 'terminal_pending';
			$job['terminal_result'] = $terminal;
			$job['updated_at']      = time();
			$job['retry_after']     = time() + 30;
			$queue[ $job_id ]       = $job;
			if ( ! $this->write_admin_ui_chat_queue( $queue ) ) {
				return array();
			}
			$this->schedule_admin_ui_chat_recovery( $job_id, 30 );
			return $terminal;
		} finally {
			$this->release_named_option_lock( 'admin_ui_chat_queue', $lock_token );
			if ( $wake_successor ) {
				$this->schedule_admin_ui_chat_worker( 5 );
			}
		}
	}

	private function is_admin_ui_chat_processing_job_stale( $job ) {
		if ( ! is_array( $job ) || 'processing' !== (string) ( $job['status'] ?? 'pending' ) ) {
			return false;
		}

		$updated_at = intval( $job['updated_at'] ?? 0 );
		$confirmed_grace = ! empty( $job['actor_started_at'] ) ? 30 * MINUTE_IN_SECONDS : 5 * MINUTE_IN_SECONDS;
		if ( ! empty( $job['continuation_pending'] ) && empty( $job['actor_reservation_pending'] ) && $updated_at > 0 && time() - $updated_at <= $confirmed_grace ) {
			return false;
		}
		$lease_expires_at = intval( $job['lease_expires_at'] ?? 0 );
		return $lease_expires_at > 0
			? $lease_expires_at <= time()
			: ( $updated_at <= 0 || time() - $updated_at > 60 );
	}

	private function dispatch_admin_ui_chat_worker( $reason = 'queue' ) {
		$this->schedule_admin_ui_chat_worker( 5 );
		if ( ! function_exists( 'wp_remote_post' ) || ! function_exists( 'admin_url' ) || ! function_exists( 'add_query_arg' ) ) {
			return;
		}

		$ts    = time();
		$nonce = wp_generate_password( 32, false, false );
		set_transient( 'niwa_ai_admin_worker_nonce_' . md5( $nonce ), 1, 180 );
		$sig = hash_hmac( 'sha256', 'admin_ui_chat_worker|' . $ts . '|' . $nonce, $this->get_admin_ui_chat_worker_secret() );
		wp_remote_post(
			add_query_arg(
				array(
					'action' => 'niwa_ai_admin_ui_chat_worker',
					'reason' => sanitize_key( $reason ),
				),
				admin_url( 'admin-post.php' )
			),
			array(
				'timeout'   => 1,
				'blocking'  => false,
				'sslverify' => apply_filters( 'https_local_ssl_verify', true ),
				'body'      => array(
					'niwa_ts'  => $ts,
					'niwa_sig' => $sig,
					'niwa_nonce' => $nonce,
				),
			)
		);
	}

	private function schedule_admin_ui_chat_worker( $delay = 5 ) {
		$target = time() + max( 5, intval( $delay ) );
		$next   = wp_next_scheduled( 'niwa_ai_process_admin_ui_chat_queue' );
		if ( false !== $next && intval( $next ) > $target && function_exists( 'wp_unschedule_event' ) ) {
			$next = wp_unschedule_event( intval( $next ), 'niwa_ai_process_admin_ui_chat_queue' ) ? false : $next;
		}
		if ( false === $next ) {
			wp_schedule_single_event( $target, 'niwa_ai_process_admin_ui_chat_queue' );
		}
	}

	private function get_admin_ui_chat_runner_retry_delay() {
		$lock    = get_option( 'niwa_ai_named_lock_admin_ui_chat_queue_runner', '' );
		$parts   = is_string( $lock ) ? explode( ':', $lock, 2 ) : array();
		$expires = intval( $parts[0] ?? 0 );

		return $expires > time() ? max( 5, min( 35, $expires - time() + 5 ) ) : 5;
	}

	public function recover_admin_ui_chat_job( $job_id = '' ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job_id );
		$result = $this->process_admin_ui_chat_queue( 1 );
		if ( $this->has_claimable_admin_ui_chat_jobs() ) {
			$this->dispatch_admin_ui_chat_worker( 'recovery-chain' );
		}

		$queue = $this->get_admin_ui_chat_queue();
		if ( '' !== $job_id && isset( $queue[ $job_id ] ) && is_array( $queue[ $job_id ] ) ) {
			$job          = $queue[ $job_id ];
			$updated_at   = max( 0, intval( $job['updated_at'] ?? 0 ) );
			$is_confirmed = ! empty( $job['continuation_pending'] ) && empty( $job['actor_reservation_pending'] );
			$actor_started = $is_confirmed && ! empty( $job['actor_started_at'] );
			$stale_after  = $is_confirmed
				? ( $actor_started ? 30 * MINUTE_IN_SECONDS : 5 * MINUTE_IN_SECONDS )
				: 60;
			$lease_until  = intval( $job['lease_expires_at'] ?? 0 );
			$elapsed      = $updated_at > 0 ? max( 0, time() - $updated_at ) : $stale_after;
			if ( $is_confirmed && ! $actor_started && $lease_until > time() && $this->admin_autonomy && method_exists( $this->admin_autonomy, 'dispatch_admin_ui_actor_runner' ) ) {
				$dispatch = (array) $this->admin_autonomy->dispatch_admin_ui_actor_runner();
				if ( empty( $dispatch['success'] ) && empty( $dispatch['wake_pending'] ) ) {
					$this->fail_admin_ui_chat_job(
						$job_id,
						(string) ( $job['session_id'] ?? '' ),
						(string) ( $job['admin_ui_continuation_actor_id'] ?? '' ),
						'admin_ui_actor_dispatch_failed'
					);
					return $result;
				}
			}
			$retry_after  = $is_confirmed && ! $actor_started && $lease_until > time()
				? 30
				: ( $is_confirmed && $lease_until > time()
				? max( 30, $lease_until - time() + 5 )
				: max( 30, $stale_after - $elapsed + 5 ) );
			$this->schedule_admin_ui_chat_recovery( $job_id, $retry_after );
		}

		return $result;
	}

	private function schedule_admin_ui_chat_recovery( $job_id, $delay ) {
		$job_id = $this->normalize_admin_ui_chat_job_id( $job_id );
		$args   = array( $job_id );
		$target = time() + max( 30, intval( $delay ) );
		$next   = '' !== $job_id ? wp_next_scheduled( 'niwa_ai_recover_admin_ui_chat_job', $args ) : false;
		if ( false !== $next && intval( $next ) > $target && function_exists( 'wp_unschedule_event' ) ) {
			$next = wp_unschedule_event( intval( $next ), 'niwa_ai_recover_admin_ui_chat_job', $args ) ? false : $next;
		}
		if ( '' !== $job_id && false === $next ) {
			wp_schedule_single_event( $target, 'niwa_ai_recover_admin_ui_chat_job', $args );
		}
	}

	private function verify_admin_ui_chat_worker_signature() {
		$ts  = isset( $_POST['niwa_ts'] ) ? intval( $_POST['niwa_ts'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$sig = isset( $_POST['niwa_sig'] ) ? sanitize_text_field( wp_unslash( $_POST['niwa_sig'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
		$nonce = isset( $_POST['niwa_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['niwa_nonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
		if ( 0 === $ts || '' === $sig || '' === $nonce || abs( time() - $ts ) > 180 ) {
			return false;
		}

		$expected = hash_hmac( 'sha256', 'admin_ui_chat_worker|' . $ts . '|' . $nonce, $this->get_admin_ui_chat_worker_secret() );
		if ( ! hash_equals( $expected, $sig ) ) {
			return false;
		}

		$nonce_key = 'niwa_ai_admin_worker_nonce_' . md5( $nonce );
		if ( false === get_transient( $nonce_key ) ) {
			return false;
		}
		delete_transient( $nonce_key );
		return true;
	}

	private function get_admin_ui_chat_worker_secret() {
		$secret = get_option( Niwa_AI_Constants::OPT_ADMIN_UI_WORKER_SECRET, '' );
		if ( is_string( $secret ) && '' !== $secret ) {
			return $secret;
		}

		$secret = wp_generate_password( 48, false, false );
		update_option( Niwa_AI_Constants::OPT_ADMIN_UI_WORKER_SECRET, $secret, false );
		return $secret;
	}

	private function get_admin_ui_chat_session_id( $user_id ) {
		if ( $this->admin_autonomy && method_exists( $this->admin_autonomy, 'get_browser_session_id' ) ) {
			return (string) $this->admin_autonomy->get_browser_session_id( $user_id );
		}

		return 'admin-ui-' . max( 1, intval( $user_id ) );
	}

	private function get_admin_ui_chat_job_phase( $job, $queue = array() ) {
		$status = (string) ( $job['status'] ?? 'pending' );
		if ( 'pending' === $status && $this->has_blocking_admin_ui_chat_job_for_session( (array) $queue, (string) ( $job['session_id'] ?? '' ), (string) ( $job['id'] ?? '' ) ) ) {
			return 'waiting';
		}
		return 'processing' === $status ? 'processing' : $status;
	}

	private function ensure_admin_ui_chat_queue_sequences( $queue ) {
		$queue = is_array( $queue ) ? $queue : array();
		$max_by_session = array();
		foreach ( $queue as $job ) {
			if ( ! is_array( $job ) ) {
				continue;
			}
			$session_id = (string) ( $job['session_id'] ?? '' );
			$max_by_session[ $session_id ] = max( intval( $max_by_session[ $session_id ] ?? 0 ), intval( $job['sequence'] ?? 0 ) );
		}
		foreach ( $queue as $job_id => $job ) {
			if ( ! is_array( $job ) || intval( $job['sequence'] ?? 0 ) > 0 ) {
				continue;
			}
			$session_id = (string) ( $job['session_id'] ?? '' );
			$max_by_session[ $session_id ] = intval( $max_by_session[ $session_id ] ?? 0 ) + 1;
			$job['sequence'] = $max_by_session[ $session_id ];
			$job['message_hash'] = (string) ( $job['message_hash'] ?? hash( 'sha256', (string) ( $job['message'] ?? '' ) ) );
			$job['claimable_since'] = intval( $job['claimable_since'] ?? 0 );
			$queue[ $job_id ] = $job;
		}
		return $queue;
	}

	private function next_admin_ui_chat_sequence( $session_id, $queue ) {
		$session_id = trim( (string) $session_id );
		if ( '' === $session_id ) {
			return 0;
		}
		$option_name = 'niwa_ai_admin_ui_chat_sequence_' . md5( $session_id );
		$sequence = intval( get_option( $option_name, 0 ) );
		foreach ( (array) $queue as $job ) {
			if ( is_array( $job ) && hash_equals( (string) ( $job['session_id'] ?? '' ), $session_id ) ) {
				$sequence = max( $sequence, intval( $job['sequence'] ?? 0 ) );
			}
		}
		$sequence++;
		$updated = update_option( $option_name, $sequence, false );
		return $updated || intval( get_option( $option_name, 0 ) ) === $sequence ? $sequence : 0;
	}

	private function count_admin_ui_chat_session_jobs( $queue, $session_id, $user_id ) {
		$count = 0;
		foreach ( (array) $queue as $job ) {
			if ( is_array( $job )
				&& hash_equals( (string) ( $job['session_id'] ?? '' ), (string) $session_id )
				&& intval( $job['user_id'] ?? 0 ) === intval( $user_id )
				&& in_array( (string) ( $job['status'] ?? 'pending' ), array( 'pending', 'processing', 'terminal_pending', 'delivery_pending' ), true )
			) {
				$count++;
			}
		}
		return $count;
	}

	private function admin_ui_chat_message_hash( $record ) {
		$record = is_array( $record ) ? $record : array();
		$stored = trim( (string) ( $record['message_hash'] ?? '' ) );
		return '' !== $stored ? $stored : hash( 'sha256', (string) ( $record['message'] ?? '' ) );
	}

	private function get_admin_ui_chat_queue() {
		// Background runners live across many requests. Their process-local
		// object cache can outlive a queue update committed by wp-admin, cron,
		// or another runner and must never resurrect that stale snapshot.
		wp_cache_delete( Niwa_AI_Constants::OPT_ADMIN_UI_CHAT_QUEUE, 'options' );
		wp_cache_delete( 'alloptions', 'options' );
		$queue = get_option( Niwa_AI_Constants::OPT_ADMIN_UI_CHAT_QUEUE, array() );
		return is_array( $queue ) ? $queue : array();
	}

	private function write_admin_ui_chat_queue( $queue ) {
		$queue   = is_array( $queue ) ? $queue : array();
		$updated = update_option( Niwa_AI_Constants::OPT_ADMIN_UI_CHAT_QUEUE, $queue, false );
		wp_cache_delete( Niwa_AI_Constants::OPT_ADMIN_UI_CHAT_QUEUE, 'options' );
		return $updated || $this->get_admin_ui_chat_queue() === $queue;
	}

	private function get_admin_ui_chat_job_result( $job_id ) {
		$result = Niwa_AI_Durable_Cache::get( $this->admin_ui_chat_job_result_key( $job_id ) );
		return is_array( $result ) ? $result : array();
	}

	private function set_admin_ui_chat_job_result( $job_id, $result, $ttl ) {
		$result         = is_array( $result ) ? $result : array();
		$current        = $this->get_admin_ui_chat_job_result( $job_id );
		$current_status = (string) ( $current['status'] ?? '' );
		$next_status    = (string) ( $result['status'] ?? '' );
		$is_terminal    = in_array( $current_status, array( 'done', 'failed' ), true );
		if ( $is_terminal ) {
			return $current_status === $next_status;
		}

		if ( in_array( $next_status, array( 'done', 'failed' ), true ) ) {
			$ttl = max( intval( $ttl ), $this->get_admin_ui_chat_request_ledger_ttl() );
		}
		return Niwa_AI_Durable_Cache::set( $this->admin_ui_chat_job_result_key( $job_id ), $result, $ttl );
	}

	private function persist_admin_ui_chat_terminal_result( $job_id, $job, $result, $ttl ) {
		$ttl = max( intval( $ttl ), $this->get_admin_ui_chat_request_ledger_ttl() );
		$result = array_merge(
			is_array( $result ) ? $result : array(),
			array(
				'sequence'     => intval( is_array( $job ) ? ( $job['sequence'] ?? 0 ) : 0 ),
				'message_hash' => (string) ( is_array( $job ) ? ( $job['message_hash'] ?? '' ) : '' ),
			)
		);
		return $this->set_admin_ui_chat_job_result( $job_id, $result, $ttl )
			&& $this->set_admin_ui_chat_request_result( $job, $result, $ttl );
	}

	private function get_admin_ui_chat_request_result( $session_id, $request_id, $user_id = 0 ) {
		$session_id = trim( (string) $session_id );
		$request_id = $this->normalize_admin_ui_chat_request_id( $request_id );
		if ( '' === $session_id || '' === $request_id ) {
			return array();
		}

		$result = Niwa_AI_Durable_Cache::get( $this->admin_ui_chat_request_result_key( $session_id, $request_id, $user_id ) );
		if (
			! is_array( $result )
			|| ! in_array( (string) ( $result['status'] ?? '' ), array( 'done', 'failed' ), true )
			|| ! hash_equals( (string) ( $result['request_id'] ?? '' ), $request_id )
			|| ( intval( $user_id ) > 0 && intval( $result['user_id'] ?? 0 ) !== intval( $user_id ) )
		) {
			return array();
		}

		return $result;
	}

	private function set_admin_ui_chat_request_result( $job, $result, $ttl ) {
		$job        = is_array( $job ) ? $job : array();
		$result     = is_array( $result ) ? $result : array();
		$session_id = trim( (string) ( $job['session_id'] ?? ( $result['session'] ?? '' ) ) );
		$request_id = $this->normalize_admin_ui_chat_request_id( $job['request_id'] ?? ( $result['request_id'] ?? '' ) );
		if ( '' === $session_id || '' === $request_id ) {
			return true;
		}

		$user_id        = intval( $job['user_id'] ?? ( $result['user_id'] ?? 0 ) );
		$key            = $this->admin_ui_chat_request_result_key( $session_id, $request_id, $user_id );
		$current        = Niwa_AI_Durable_Cache::get( $key );
		$current_status = is_array( $current ) ? (string) ( $current['status'] ?? '' ) : '';
		$next_status    = (string) ( $result['status'] ?? '' );
		if ( in_array( $current_status, array( 'done', 'failed' ), true ) ) {
			return $current_status === $next_status;
		}

		$job_id = $this->normalize_admin_ui_chat_job_id( $job['id'] ?? ( $result['job_id'] ?? '' ) );
		$payload = array_merge(
			$result,
			array(
				'id'         => $job_id,
				'job_id'     => $job_id,
				'user_id'    => $user_id,
				'request_id' => $request_id,
				'session'    => $session_id,
				'session_id' => $session_id,
				'message'    => (string) ( $job['message'] ?? ( $result['message'] ?? '' ) ),
				'created_at' => intval( $job['created_at'] ?? ( $result['created_at'] ?? 0 ) ),
				'sequence'   => intval( $job['sequence'] ?? ( $result['sequence'] ?? 0 ) ),
				'message_hash' => (string) ( $job['message_hash'] ?? ( $result['message_hash'] ?? '' ) ),
			)
		);

		return '' !== $job_id && Niwa_AI_Durable_Cache::set( $key, $payload, max( intval( $ttl ), $this->get_admin_ui_chat_request_ledger_ttl() ) );
	}

	private function set_admin_ui_chat_request_result_force( $job, $result, $ttl ) {
		$job        = is_array( $job ) ? $job : array();
		$result     = is_array( $result ) ? $result : array();
		$session_id = trim( (string) ( $job['session_id'] ?? ( $job['session'] ?? ( $result['session_id'] ?? ( $result['session'] ?? '' ) ) ) ) );
		$request_id = $this->normalize_admin_ui_chat_request_id( $job['request_id'] ?? ( $result['request_id'] ?? '' ) );
		if ( '' === $session_id || '' === $request_id ) {
			return true;
		}

		$user_id = intval( $job['user_id'] ?? ( $result['user_id'] ?? 0 ) );
		$job_id  = $this->normalize_admin_ui_chat_job_id( $job['id'] ?? ( $job['job_id'] ?? ( $result['job_id'] ?? '' ) ) );
		$payload = array_merge(
			$result,
			array(
				'id'         => $job_id,
				'job_id'     => $job_id,
				'user_id'    => $user_id,
				'request_id' => $request_id,
				'session'    => $session_id,
				'session_id' => $session_id,
				'sequence'   => intval( $job['sequence'] ?? ( $result['sequence'] ?? 0 ) ),
				'message_hash' => (string) ( $job['message_hash'] ?? ( $result['message_hash'] ?? '' ) ),
			)
		);
		return '' !== $job_id && Niwa_AI_Durable_Cache::set(
			$this->admin_ui_chat_request_result_key( $session_id, $request_id, $user_id ),
			$payload,
			max( intval( $ttl ), $this->get_admin_ui_chat_request_ledger_ttl() )
		);
	}

	private function get_admin_ui_chat_request_ledger_ttl() {
		return 7 * DAY_IN_SECONDS;
	}

	private function admin_ui_chat_job_result_key( $job_id ) {
		return 'niwa_ai_admin_ui_chat_job_' . md5( (string) $job_id );
	}

	private function admin_ui_chat_delivery_payload_key( $job_id ) {
		return 'niwa_ai_admin_ui_chat_delivery_' . md5( (string) $job_id );
	}

	private function admin_ui_chat_request_result_key( $session_id, $request_id, $user_id = 0 ) {
		return 'niwa_ai_admin_ui_chat_request_' . md5( (string) $session_id . '|' . intval( $user_id ) . '|' . (string) $request_id );
	}

	private function normalize_admin_ui_chat_job_id( $job_id ) {
		return substr( preg_replace( '/[^a-zA-Z0-9_\-]/', '', trim( (string) $job_id ) ), 0, 80 );
	}

	private function normalize_admin_ui_chat_request_id( $request_id ) {
		return substr( preg_replace( '/[^a-zA-Z0-9_\-:.]/', '', trim( (string) $request_id ) ), 0, 96 );
	}
}
