<?php
/**
 * Queued actor message execution for a founder/admin session.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Admin_Autonomy_Session_Actor_Message_Runner {
	/** @var object|null */
	private $control_plane;

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

	public function __construct( $control_plane = null, $callbacks = array() ) {
		$this->control_plane = $control_plane;
		$this->callbacks     = is_array( $callbacks ) ? $callbacks : array();
	}

	public function run( $session_id, $entry ) {
		$session_id = (string) $session_id;
		$entry      = is_array( $entry ) ? $entry : array();
		$message    = isset( $entry['message'] ) && is_array( $entry['message'] ) ? $entry['message'] : array();
		$chat_id    = isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '';
		$channel    = isset( $message['channel'] ) ? sanitize_key( (string) $message['channel'] ) : 'telegram';
		$uses_typing_feedback = '' === $channel || in_array( $channel, array( 'telegram', 'whatsapp' ), true );
		$keep_typing_for_continuation = false;
		if ( '' === $chat_id && 'admin_ui' !== $channel ) {
			return array(
				'success' => true,
				'ignored' => true,
			);
		}

		$admin_ui_delivery = $this->call( 'recover_admin_ui_delivery', array( $session_id, $entry ), array( 'handled' => false ) );
		if ( ! empty( $admin_ui_delivery['handled'] ) ) {
			if ( ! empty( $admin_ui_delivery['retry'] ) ) {
				throw new RuntimeException( (string) ( $admin_ui_delivery['error'] ?? 'admin_ui_delivery_retry_required' ) );
			}
			if ( ! empty( $admin_ui_delivery['deferred'] ) && empty( $admin_ui_delivery['defer_seconds'] ) ) {
				$admin_ui_delivery['defer_seconds'] = 5;
			}
			return $admin_ui_delivery;
		}

		$this->debug(
			'actor_message_run_start',
			array(
				'session_id'   => $session_id,
				'actor_id'     => isset( $entry['id'] ) ? (string) $entry['id'] : '',
				'channel'      => $channel,
				'chat_id'      => $chat_id,
				'message_id'   => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
				'update_id'    => isset( $message['update_id'] ) ? intval( $message['update_id'] ) : 0,
				'user_role'    => isset( $message['user_role'] ) ? (string) $message['user_role'] : '',
				'text_preview' => isset( $message['text'] ) ? (string) $message['text'] : '',
				'entry_keys'   => array_keys( $entry ),
			)
		);

		if ( $uses_typing_feedback ) {
			$this->call( 'begin_typing_feedback', array( $chat_id ) );
		}

		try {
			$prepared = $this->call( 'prepare_actor_message_for_execution', array( $message ), array() );
			if ( ! empty( $prepared['handled'] ) ) {
				return $prepared;
			}

			$message = isset( $prepared['message'] ) && is_array( $prepared['message'] ) ? $prepared['message'] : $message;
			$chat_id = isset( $message['chat_id'] ) ? (string) $message['chat_id'] : $chat_id;
			$channel = isset( $message['channel'] ) ? sanitize_key( (string) $message['channel'] ) : $channel;
			$context_payload = isset( $entry['context_payload'] ) && is_array( $entry['context_payload'] ) ? $entry['context_payload'] : array();
			if ( 'admin_ui' === $channel && ! empty( $context_payload['session_id'] ) ) {
				$message['session_id'] = trim( (string) $context_payload['session_id'] );
			}
			if ( 'admin_ui' === $channel ) {
				foreach ( array( 'request_id', 'admin_ui_job_id', 'admin_ui_sequence' ) as $correlation_key ) {
					if ( ! empty( $context_payload[ $correlation_key ] ) ) {
						$message[ $correlation_key ] = (string) $context_payload[ $correlation_key ];
					}
				}
			}
			$context = $this->call( 'build_message_context', array( $message ), array() );
			$context = is_array( $context ) ? $context : array();

			if ( ! empty( $context_payload ) ) {
				$context = array_merge( $context, $context_payload );
			}
			$context['channel']    = '' !== $channel ? $channel : ( isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram' );
			$context['chat_id']    = $chat_id;
			$context['message_id'] = isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0;
			$context['update_id']  = isset( $message['update_id'] ) ? intval( $message['update_id'] ) : 0;
			$context['source']     = isset( $message['source'] ) ? sanitize_key( (string) $message['source'] ) : '';
			$context['message_date'] = isset( $message['date'] ) ? intval( $message['date'] ) : 0;
			if ( ! empty( $entry['id'] ) ) {
				$delivery_actor_id = 'admin_ui' === $channel && ! empty( $entry['continuation_root_actor_message_id'] )
					? (string) $entry['continuation_root_actor_message_id']
					: (string) $entry['id'];
				$context['actor_message_id'] = sanitize_text_field( $delivery_actor_id );
			}
			if ( ! empty( $entry['enqueued_at'] ) ) {
				$context['actor_enqueued_at'] = intval( $entry['enqueued_at'] );
			}
			if ( ! empty( $message['scheduled_job'] ) && is_array( $message['scheduled_job'] ) ) {
				$scheduled_job = array();
				foreach ( array( 'id', 'job_uuid', 'title' ) as $scheduled_key ) {
					if ( isset( $message['scheduled_job'][ $scheduled_key ] ) ) {
						$scheduled_job[ $scheduled_key ] = is_scalar( $message['scheduled_job'][ $scheduled_key ] )
							? sanitize_text_field( (string) $message['scheduled_job'][ $scheduled_key ] )
							: '';
					}
				}
				$context['scheduled_job'] = $scheduled_job;
			}
			foreach ( array( 'raw_message_id', 'from_id', 'remote_jid', 'phone_jid', 'chat_jid', 'sender_jid', 'whatsapp_jid', 'gateway_chat_jid' ) as $key ) {
				if ( isset( $message[ $key ] ) && '' !== trim( (string) $message[ $key ] ) ) {
					$context[ $key ] = (string) $message[ $key ];
				}
			}
			if ( isset( $message['user_role'] ) ) {
				$context['user_role'] = (string) $message['user_role'];
			}

			if ( ! empty( $entry['continuation_context'] ) && is_array( $entry['continuation_context'] ) ) {
				$context['continuation_context'] = $entry['continuation_context'];
			}

			if ( ! empty( $this->call( 'is_media_only_message', array( $message ), false ) ) ) {
				$message = $this->call( 'prepare_media_only_message', array( $message, $context ), $message );
			}

			$context['_original_context_payload'] = ! empty( $context_payload )
				? $context_payload
				: $this->call( 'compact_queued_context', array( $context ), array() );
			$context['soft_deadline'] = time()
				+ intval( $this->call( 'turn_worker_time_limit', array(), 360 ) )
				- intval( $this->call( 'turn_execution_soft_deadline_buffer', array(), 0 ) );
			$this->debug(
				'actor_message_context_built',
				array(
					'session_id'      => $session_id,
					'actor_id'        => isset( $entry['id'] ) ? (string) $entry['id'] : '',
					'channel'         => isset( $context['channel'] ) ? (string) $context['channel'] : '',
					'chat_id'         => $chat_id,
					'message_id'      => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
					'update_id'       => isset( $context['update_id'] ) ? intval( $context['update_id'] ) : 0,
					'context_keys'    => array_keys( $context ),
					'context_payload' => $context_payload,
					'soft_deadline'   => $context['soft_deadline'],
				)
			);

			if ( ! empty( $entry['id'] ) && $this->control_plane && method_exists( $this->control_plane, 'is_actor_message_superseded' ) ) {
				$active_message_id       = (string) $entry['id'];
				$cancel_generation       = intval( isset( $entry['cancel_generation'] ) ? $entry['cancel_generation'] : 0 );
				$context['cancel_probe'] = function () use ( $session_id, $active_message_id, $cancel_generation ) {
					if ( ! $this->control_plane ) {
						return false;
					}

					if ( method_exists( $this->control_plane, 'is_actor_message_cancelled' ) ) {
						return ! empty( $this->control_plane->is_actor_message_cancelled( $session_id, $active_message_id, $cancel_generation ) );
					}

					return method_exists( $this->control_plane, 'is_actor_message_superseded' )
						&& ! empty( $this->control_plane->is_actor_message_superseded( $session_id, $active_message_id ) );
				};
			}

			$admin_ui_job_id  = isset( $context_payload['admin_ui_job_id'] ) ? sanitize_key( (string) $context_payload['admin_ui_job_id'] ) : '';
			$admin_ui_actor_id = ! empty( $entry['continuation_root_actor_message_id'] )
				? sanitize_text_field( (string) $entry['continuation_root_actor_message_id'] )
				: ( isset( $entry['id'] ) ? sanitize_text_field( (string) $entry['id'] ) : '' );
			if ( 'admin_ui' === $channel && '' !== $admin_ui_job_id && '' !== $admin_ui_actor_id ) {
				$start_fenced = (bool) $this->call( 'refresh_admin_ui_job', array( $admin_ui_job_id, $session_id, $admin_ui_actor_id ), false );
				if ( ! $start_fenced ) {
					return array(
						'success'  => false,
						'deferred' => true,
						'error'    => 'admin_ui_actor_start_fence_failed',
					);
				}
				$previous_progress_callback = isset( $context['turn_progress_callback'] ) && is_callable( $context['turn_progress_callback'] )
					? $context['turn_progress_callback']
					: null;
				$last_admin_ui_refresh_at = time();
				$context['turn_progress_callback'] = function ( $event = '', $payload = array() ) use ( $admin_ui_job_id, $session_id, $admin_ui_actor_id, $previous_progress_callback, &$last_admin_ui_refresh_at ) {
					$now = time();
					if ( $now - $last_admin_ui_refresh_at >= 30 ) {
						$last_admin_ui_refresh_at = $now;
						$this->call( 'refresh_admin_ui_job', array( $admin_ui_job_id, $session_id, $admin_ui_actor_id ), false );
					}
					if ( is_callable( $previous_progress_callback ) ) {
						call_user_func( $previous_progress_callback, $event, is_array( $payload ) ? $payload : array() );
					}
				};
			}

			if ( ! empty( $entry['id'] ) ) {
				$active_message_id = (string) $entry['id'];
				$this->call( 'refresh_actor_message_lease', array( $session_id, $active_message_id ) );
				$context['turn_progress_callback'] = $this->call(
					'wrap_actor_message_lease_callback',
					array(
						$session_id,
						$active_message_id,
						isset( $context['turn_progress_callback'] ) && is_callable( $context['turn_progress_callback'] )
							? $context['turn_progress_callback']
							: null,
					)
				);
			}

			$this->call( 'clear_last_soft_deadline_result' );
			$result = $this->call(
				'execute_founder_message',
				array(
					$message,
					$context,
					! empty( $entry['callback_acknowledged'] ),
				),
				array()
			);
			$result = is_array( $result ) ? $result : array();
			$soft_deadline_result = $this->call( 'get_last_soft_deadline_result', array(), null );
			if ( empty( $result['continuation'] ) && ! empty( $soft_deadline_result['context_snapshot'] ) ) {
				$result['continuation'] = $soft_deadline_result;
			}
			if ( ! empty( $result['continuation'] ) ) {
				$keep_typing_for_continuation = true;
			}
			$this->debug(
				'actor_message_execute_result',
				array(
					'session_id'       => $session_id,
					'actor_id'         => isset( $entry['id'] ) ? (string) $entry['id'] : '',
					'channel'          => isset( $context['channel'] ) ? (string) $context['channel'] : '',
					'chat_id'          => $chat_id,
					'message_id'       => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
					'success'          => ! empty( $result['success'] ),
					'reply_len'        => isset( $result['reply'] ) ? strlen( (string) $result['reply'] ) : 0,
					'error'            => isset( $result['error'] ) ? (string) $result['error'] : '',
					'has_continuation' => ! empty( $result['continuation'] ),
				)
			);

			return $result;
		} finally {
			if ( $uses_typing_feedback ) {
				if ( $keep_typing_for_continuation ) {
					$this->call( 'keep_typing_feedback', array( $chat_id ) );
				} else {
					$this->call( 'end_typing_feedback', array( $chat_id ) );
				}
			}
		}
	}

	private function call( $name, $args = array(), $default = null ) {
		return Niwa_AI_Admin_Autonomy_Callbacks::call( $this->callbacks, $name, $args, $default );
	}

	private function debug( $event, $context = array() ) {
		$this->call( 'debug_logger', array( $event, $context ) );
	}
}
