<?php
/**
 * Founder message execution and reply delivery.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Admin_Autonomy_Founder_Message_Executor {
	/** @var object|null */
	private $telegram;

	/** @var object|null */
	private $conversation_store;

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

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

	public function execute( $message, $context, $callback_acknowledged = false ) {
		$message = is_array( $message ) ? $message : array();
		$context = is_array( $context ) ? $context : array();
		$this->debug(
			'execute_founder_message_start',
			array(
				'job_id'         => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
				'chat_id'        => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
				'message_id'     => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
				'user_role'      => isset( $context['user_role'] ) ? (string) $context['user_role'] : '',
				'is_new_session' => ! empty( $context['is_new_session'] ),
				'callback'       => ! empty( $message['callback_query_id'] ),
				'text_preview'   => isset( $message['text'] ) ? (string) $message['text'] : '',
			)
		);

		$result = $this->run_message_processor( $message, $context );
		$result = is_array( $result ) ? $result : array();
		$processor_success = ! array_key_exists( 'success', $result ) || ! empty( $result['success'] );
		$channel = sanitize_key( (string) ( $context['channel'] ?? '' ) );
		if (
			! $processor_success
			&& empty( $result['successful_write_execution'] )
			&& in_array( $channel, array( 'telegram', 'whatsapp' ), true )
		) {
			$result['reply'] = '';
		}
		if ( ! empty( $context['scheduled_self_wake_execution'] ) && ! empty( $result['scheduled_delivery_tool_successful'] ) ) {
			$result['reply']          = '';
			$result['suppress_intro'] = true;
		}

		$reply        = isset( $result['reply'] ) ? trim( (string) $result['reply'] ) : '';
		$reply_markup = isset( $result['reply_markup'] ) && is_array( $result['reply_markup'] ) ? $result['reply_markup'] : array();
		$is_admin     = ! isset( $context['user_role'] ) || 'admin' === (string) $context['user_role'];

		$reply = $this->maybe_prepend_intro( $reply, $message, $context, $result, $is_admin );
		$this->debug(
			'execute_founder_message_process_result',
			array(
				'job_id'            => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
				'chat_id'           => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
				'message_id'        => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
				'reply_len_raw'     => $this->text_length( $reply ),
				'has_reply_markup'  => ! empty( $reply_markup ),
				'remember_exchange' => ! array_key_exists( 'remember_exchange', $result ) || ! empty( $result['remember_exchange'] ),
			)
		);

		$reply      = (string) $this->call( 'normalize_outgoing_reply', array( $reply ), $reply );
		$full_reply = $reply;
		$this->debug(
			'execute_founder_message_normalized',
			array(
				'job_id'         => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
				'chat_id'        => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
				'message_id'     => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
				'normalized_len' => $this->text_length( $reply ),
			)
		);

		if ( $is_admin ) {
			$reply = (string) $this->call( 'finalize_turn_stream_feedback', array( $context, $reply ), $reply );
			$this->debug(
				'execute_founder_message_after_finalize',
				array(
					'job_id'         => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
					'chat_id'        => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
					'message_id'     => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
					'final_send_len' => $this->text_length( $reply ),
					'full_reply_len' => $this->text_length( $full_reply ),
				)
			);
		}

		$delivery_context = $context;
		if ( ! empty( $result['turn_id'] ) ) {
			$delivery_context['turn_id'] = (string) $result['turn_id'];
		}

		$this->answer_callback_if_needed( $message, $callback_acknowledged );
		$input_recorded = $this->record_admin_ui_input_before_delivery( $message, $delivery_context, $result );
		if ( ! $input_recorded ) {
			$terminalized = $this->call( 'fail_admin_ui_job', array( $delivery_context, 'admin_ui_input_write_failed' ), false );
			if ( ! $terminalized ) {
				throw new RuntimeException( 'admin_ui_input_terminalization_failed' );
			}
		}
		$send = $input_recorded
			? $this->send_final_reply( $message, $reply, $reply_markup, $delivery_context, $full_reply, $result )
			: array( 'reply' => $reply, 'delivery' => array( 'success' => false, 'error' => 'admin_ui_input_write_failed' ) );
		$send = is_array( $send ) ? $send : array(
			'reply'    => (string) $send,
			'delivery' => array(
				'success' => true,
				'legacy'  => true,
			),
		);
		$reply = isset( $send['reply'] ) ? (string) $send['reply'] : '';
		$this->clear_callback_markup_if_needed( $message, $result );
		$delivery         = isset( $send['delivery'] ) && is_array( $send['delivery'] ) ? $send['delivery'] : array();
		$delivery_success = ! empty( $delivery['success'] ) && empty( $delivery['skipped'] );
		$is_admin_ui      = 'admin_ui' === sanitize_key( (string) ( $delivery_context['channel'] ?? '' ) );
		$has_continuation = ! empty( $result['continuation'] ) && is_array( $result['continuation'] );
		if ( $is_admin_ui && ! $delivery_success && ! $has_continuation && in_array( (string) ( $delivery['reason'] ?? '' ), array( 'empty_reply', 'empty_after_quote_directive' ), true ) ) {
			$terminalized = $this->call( 'fail_admin_ui_job', array( $delivery_context, 'admin_ui_empty_reply' ), false );
			if ( ! $terminalized ) {
				throw new RuntimeException( 'admin_ui_empty_reply_terminalization_failed' );
			}
		}
		$this->remember_exchange_if_needed( $message, $delivery_context, $result, $full_reply, $is_admin );

		$successful_without_final_send = $has_continuation
			|| ! empty( $result['scheduled_delivery_tool_successful'] )
			|| ! empty( $result['scheduled_delivery_child_scheduled'] )
			|| ! empty( $result['subagent_dispatch_successful'] )
			|| in_array( (string) ( $delivery['reason'] ?? '' ), array( 'stream_already_delivered', 'scheduled_delivery_preparation' ), true );
		$response = array(
			'success'                    => $processor_success && ( $delivery_success || $successful_without_final_send ),
			'reply'                      => $reply,
			'delivery'                   => $delivery,
			'processor_success'          => $processor_success,
			'terminal_failure_delivered' => false,
		);
		if ( empty( $response['processor_success'] ) && ! empty( $result['error'] ) ) {
			$response['processor_error'] = (string) $result['error'];
		}
		$response['delivery_success'] = $delivery_success;
		if ( empty( $response['delivery_success'] ) && ! empty( $response['delivery']['error'] ) ) {
			$response['reply_delivery_failed'] = true;
			$response['delivery_error']        = (string) $response['delivery']['error'];
		}
		if ( empty( $response['success'] ) ) {
			$response['error'] = ! empty( $response['delivery_error'] )
				? (string) $response['delivery_error']
				: ( ! empty( $response['processor_error'] ) ? (string) $response['processor_error'] : 'founder_message_not_delivered' );
		}

		foreach ( array( 'task_completion_state', 'original_task_completed', 'extension_pending_approval', 'retry_original_task', 'executed_tools', 'successful_write_execution', 'context_snapshot', 'tool_evidence', 'verification_unavailable', 'execution_contract', 'turn_id', 'model', 'iterations', 'capability_gap', 'next_required_step', 'scheduled_delivery_tool_successful', 'scheduled_delivery_child_scheduled', 'subagent_dispatch_successful', 'pending_subagent_jobs' ) as $meta_key ) {
			if ( array_key_exists( $meta_key, $result ) ) {
				$response[ $meta_key ] = $result[ $meta_key ];
			}
		}

		if ( ! empty( $result['continuation'] ) && is_array( $result['continuation'] ) ) {
			$response['continuation'] = $result['continuation'];
		}

		return $response;
	}

	private function run_message_processor( $message, $context ) {
		try {
			$result = $this->call( 'maybe_handle_founder_onboarding_message', array( $message, $context ), array() );
			if ( empty( $result['handled'] ) ) {
				$result = $this->call( 'process_command', array( isset( $message['text'] ) ? $message['text'] : '', $context ), array() );
			}

			return $result;
		} catch ( Throwable $e ) {
			$this->debug(
				'execute_founder_message_process_exception',
				array(
					'job_id'     => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
					'chat_id'    => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
					'message_id' => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
					'error'      => $e->getMessage(),
				)
			);

			return array(
				'reply'        => $this->call( 'format_runtime_exception_message', array( $e ), '' ),
				'reply_markup' => $this->call( 'build_remove_keyboard', array(), array() ),
			);
		}
	}

	private function maybe_prepend_intro( $reply, $message, $context, $result, $is_admin ) {
		$normalized_command  = $this->call( 'normalize_command_text', array( isset( $message['text'] ) ? $message['text'] : '' ), '' );
		$onboarding_complete = ! empty( $this->call( 'is_founder_onboarding_complete', array(), true ) );
		if (
			$is_admin
			&& $onboarding_complete
			&& empty( $result['suppress_intro'] )
			&& empty( $result['continuation'] )
			&& 'soft_deadline' !== (string) ( $result['cancelled_reason'] ?? '' )
			&& ! empty( $context['is_new_session'] )
			&& empty( $message['callback_query_id'] )
			&& 'start' !== $normalized_command
		) {
			return (string) $this->call( 'prepend_founder_intro', array( $reply ), $reply );
		}

		return $reply;
	}

	private function answer_callback_if_needed( $message, $callback_acknowledged ) {
		if (
			! empty( $message['callback_query_id'] )
			&& ! $callback_acknowledged
			&& $this->telegram
			&& method_exists( $this->telegram, 'answer_callback_query' )
		) {
			$this->telegram->answer_callback_query( $message['callback_query_id'] );
		}
	}

	private function send_final_reply( $message, $reply, $reply_markup, $context, $full_reply, $result = array() ) {
		$channel = isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram';
		if ( 'admin_ui' === $channel && '' === trim( (string) $reply ) && '' !== trim( (string) $full_reply ) ) {
			$reply = trim( (string) $full_reply );
		}
		if ( '' === $reply ) {
			$this->debug(
				'execute_founder_message_no_final_send',
				array(
					'job_id'         => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
					'chat_id'        => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
					'message_id'     => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
					'full_reply_len' => $this->text_length( $full_reply ),
				)
			);
			if ( '' !== trim( (string) $full_reply ) ) {
				// The reply was non-empty but the live turn stream already sent
				// all of it (finalize dedupe left nothing for the final send).
				// That IS a successful delivery — report it as such so the
				// scheduler does not treat it as unconfirmed and re-send an
				// already-delivered scheduled reply on a 10-minute retry loop.
				return array(
					'reply'    => $reply,
					'delivery' => array(
						'success' => true,
						'method'  => 'turn_stream',
						'reason'  => 'stream_already_delivered',
					),
				);
			}
			return array(
				'reply'    => $reply,
				'delivery' => array(
					'success' => 'admin_ui' !== $channel,
					'skipped' => true,
					'reason'  => 'empty_reply',
				),
			);
		}

		if (
			! empty( $context['scheduled_research_delivery'] )
			&& isset( $context['scheduled_delivery_phase'] )
			&& 'preparation' === (string) $context['scheduled_delivery_phase']
			&& $this->should_hold_scheduled_preparation_reply( $result )
		) {
			$this->debug(
				'execute_founder_message_scheduled_delivery_prepared',
				array(
					'job_id'     => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
					'chat_id'    => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
					'message_id' => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
					'reply_len'  => $this->text_length( $reply ),
				)
			);

			return array(
				'reply'    => $reply,
				'delivery' => array(
					'success' => true,
					'skipped' => true,
					'reason'  => 'scheduled_delivery_preparation',
				),
			);
		}

		$quote_directive = 'telegram' === $channel
			? $this->call( 'consume_telegram_quote_current_directive', array( $reply ), array( 'text' => $reply ) )
			: array( 'text' => $reply );
		$reply           = isset( $quote_directive['text'] ) ? (string) $quote_directive['text'] : '';
		$reply_to_id     = ! empty( $quote_directive['quote_current'] ) ? intval( isset( $message['message_id'] ) ? $message['message_id'] : 0 ) : 0;
		if ( '' === trim( $reply ) ) {
			$send_result = array(
				'success' => true,
				'skipped' => true,
				'reason'  => 'empty_after_quote_directive',
			);
		} elseif ( 'telegram' !== $channel ) {
			$send_result = $this->call(
				'send_channel_message',
				array( $channel, isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '', $reply, $reply_to_id, $reply_markup, $context ),
				array( 'success' => false, 'error' => 'channel_transport_unavailable' )
			);
		} else {
			$send_result = $this->telegram && method_exists( $this->telegram, 'send_message' )
				? $this->telegram->send_message( $message['chat_id'], $reply, $reply_to_id, $reply_markup )
				: array( 'success' => false, 'error' => 'telegram_unavailable' );
		}
		$this->debug(
			'execute_founder_message_sent',
			array(
				'job_id'              => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
				'channel'             => $channel,
				'chat_id'             => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
				'message_id'          => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
				'reply_to_message_id' => $reply_to_id,
				'quote_current'       => ! empty( $quote_directive['quote_current'] ),
				'reply_len'           => $this->text_length( $reply ),
				'send_result'         => $send_result,
			)
		);

		return array(
			'reply'    => $reply,
			'delivery' => is_array( $send_result ) ? $send_result : array(
				'success' => false,
				'error'   => 'invalid_send_result',
			),
		);
	}

	private function should_hold_scheduled_preparation_reply( $result ) {
		$result = is_array( $result ) ? $result : array();
		foreach ( array( 'extension_pending_approval', 'retry_original_task', 'capability_gap', 'execution_incomplete' ) as $flag ) {
			if ( ! empty( $result[ $flag ] ) ) {
				return false;
			}
		}

		$state = isset( $result['task_completion_state'] ) ? sanitize_key( (string) $result['task_completion_state'] ) : '';
		if ( in_array( $state, array( 'pending_extension_approval', 'blocked_after_two_attempts' ), true ) ) {
			return false;
		}

		$budget_state = isset( $result['task_attempt_budget_status'] ) ? sanitize_key( (string) $result['task_attempt_budget_status'] ) : '';
		if ( 'blocked_after_two_attempts' === $budget_state ) {
			return false;
		}

		$next_required = isset( $result['next_required_step'] ) ? sanitize_key( (string) $result['next_required_step'] ) : '';
		if ( '' !== $next_required && false !== strpos( $next_required, 'approval' ) ) {
			return false;
		}

		if ( array_key_exists( 'success', $result ) && empty( $result['success'] ) ) {
			return false;
		}

		return true;
	}

	private function clear_callback_markup_if_needed( $message, $result ) {
		if (
			! empty( $message['callback_query_id'] )
			&& ! empty( $message['callback_origin_message_id'] )
			&& ! empty( $result['clear_callback_markup'] )
			&& $this->telegram
			&& method_exists( $this->telegram, 'edit_message_reply_markup' )
		) {
			$this->telegram->edit_message_reply_markup( $message['chat_id'], intval( $message['callback_origin_message_id'] ), array() );
		}
	}

	private function record_admin_ui_input_before_delivery( $message, $context, $result ) {
		if (
			'admin_ui' !== sanitize_key( (string) ( $context['channel'] ?? '' ) )
			|| '[continuation]' === trim( (string) ( $message['text'] ?? '' ) )
		) {
			return true;
		}
		if ( ! $this->conversation_store ) {
			return false;
		}

		$text = isset( $message['remember_as'] ) ? (string) $message['remember_as'] : (string) ( $message['text'] ?? '' );
		if ( '' === trim( $text ) ) {
			return true;
		}

		$session_id = trim( (string) ( $context['session_id'] ?? '' ) );
		if ( '' === $session_id ) {
			$session_id = $this->call( 'get_founder_session_id', array( $message['chat_id'] ?? '' ), '' );
		}
		if ( '' === $session_id ) {
			return false;
		}
		$meta = array( 'channel' => 'admin_ui' );
		foreach ( array( 'request_id', 'admin_ui_job_id', 'actor_message_id', 'source' ) as $key ) {
			if ( ! empty( $context[ $key ] ) ) {
				$meta[ $key ] = (string) $context[ $key ];
			}
		}
		if ( ! empty( $result['turn_id'] ) ) {
			$meta['turn_id'] = (string) $result['turn_id'];
		}
		return false !== $this->conversation_store->append( $session_id, 'user', $text, array(), $meta );
	}

	private function remember_exchange_if_needed( $message, $context, $result, $full_reply, $is_admin ) {
		$is_continuation_message  = '[continuation]' === trim( isset( $message['text'] ) ? (string) $message['text'] : '' );
		$should_remember_exchange = ! array_key_exists( 'remember_exchange', $result ) || ! empty( $result['remember_exchange'] );
		$session_id               = isset( $context['session_id'] ) ? trim( (string) $context['session_id'] ) : '';
		if ( '' === $session_id ) {
			$session_id = $this->call( 'get_founder_session_id', array( $message['chat_id'] ), '' );
		}
		if ( ! $should_remember_exchange ) {
			return;
		}
		$exchange_meta = array(
			'channel' => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram',
		);
		foreach ( array( 'request_id', 'admin_ui_job_id', 'actor_message_id', 'source' ) as $key ) {
			if ( ! empty( $context[ $key ] ) ) {
				$exchange_meta[ $key ] = (string) $context[ $key ];
			}
		}
		$exchange_meta['turn_id'] = ! empty( $result['turn_id'] )
			? (string) $result['turn_id']
			: ( isset( $context['turn_id'] ) ? (string) $context['turn_id'] : '' );
		$is_admin_ui = 'admin_ui' === (string) $exchange_meta['channel'];

		if ( ! $is_admin_ui && $is_continuation_message ) {
			if ( '' !== trim( (string) $full_reply ) && $this->conversation_store ) {
				$exchange_meta['continuation'] = true;
				$this->conversation_store->append( $session_id, 'bot', $full_reply, array(), $exchange_meta );
			}
		} elseif ( ! $is_admin_ui ) {
			$remember_text = isset( $message['remember_as'] ) ? (string) $message['remember_as'] : ( isset( $message['text'] ) ? (string) $message['text'] : '' );
			if ( $this->conversation_store ) {
				if ( '' !== trim( $remember_text ) ) {
					$this->conversation_store->append( $session_id, 'user', $remember_text, array(), $exchange_meta );
				}
				if ( '' !== trim( (string) $full_reply ) ) {
					$this->conversation_store->append( $session_id, 'bot', $full_reply, array(), $exchange_meta );
				}
			} else {
				$this->call( 'remember_exchange', array( isset( $message['chat_id'] ) ? $message['chat_id'] : '', $remember_text, $full_reply, $context ) );
			}
		}

		if ( $is_admin && ! $is_admin_ui && ( ! $is_continuation_message || '' !== trim( (string) $full_reply ) ) ) {
			// Continuation replies must be captured too — long turns often
			// deliver their real content (lists, reports) through the
			// continuation worker, and skipping them left those replies
			// invisible to session_recall.
			$this->call(
				'enqueue_post_reply_memory_capture',
				array(
					$session_id,
					$is_continuation_message ? '' : ( isset( $message['text'] ) ? (string) $message['text'] : '' ),
					$full_reply,
					$context,
				)
			);
		}
	}

	private function text_length( $text ) {
		return intval( $this->call( 'text_length', array( $text ), strlen( (string) $text ) ) );
	}

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

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