<?php
/**
 * Telegram turn stream lifecycle orchestration.
 *
 * @package Niwa_AI
 */

defined( 'ABSPATH' ) || exit;

class Niwa_AI_Admin_Autonomy_Turn_Stream_Lifecycle {
	private $telegram;
	private $callbacks;

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

	public function begin( $context ) {
		$context = is_array( $context ) ? $context : array();
		if ( ! $this->telegram ) {
			return;
		}

		$chat_id    = isset( $context['chat_id'] ) ? trim( (string) $context['chat_id'] ) : '';
		$message_id = isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0;
		$user_role  = isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin';

		if ( '' === $chat_id || 'admin' !== $user_role || ! empty( $context['is_new_session'] ) ) {
			$this->debug(
				'turn_stream_skipped',
				array(
					'chat_id'        => $chat_id,
					'message_id'     => $message_id,
					'user_role'      => $user_role,
					'is_new_session' => ! empty( $context['is_new_session'] ),
				)
			);
			$this->call( 'clear_turn_stream_state', array( $chat_id, $message_id ) );
			return;
		}

		$now = time();
		$this->call(
			'update_turn_stream_state',
			array(
				$chat_id,
				$message_id,
				array(
					'chat_id'                 => $chat_id,
					'message_id'              => $message_id,
					'started_at'              => $now,
					'last_event_at'           => $now,
					'buffer'                  => '',
					'sent_text'               => '',
					'sent_chars'              => 0,
					'sent_any'                => false,
					'streaming_disabled'      => false,
					'assistant_stream_locked' => false,
					'quote_current_message'   => false,
					'tools_used'              => false,
					'last_tool_status_at'     => 0,
					'last_tool_status_key'    => '',
					'sent_tool_status_keys'   => array(),
					'tool_status_messages'    => array(),
				),
			)
		);
		$this->debug(
			'turn_stream_started',
			array(
				'job_id'     => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
				'chat_id'    => $chat_id,
				'message_id' => $message_id,
				'user_role'  => $user_role,
			)
		);
	}

	public function finalize( $context, $reply ) {
		$context    = is_array( $context ) ? $context : array();
		$chat_id    = isset( $context['chat_id'] ) ? trim( (string) $context['chat_id'] ) : '';
		$message_id = isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0;
		if ( '' === $chat_id ) {
			return (string) $reply;
		}

		$state = $this->call( 'get_turn_stream_state', array( $chat_id, $message_id ), array() );
		$state = is_array( $state ) ? $state : array();
		if ( empty( $state ) ) {
			$this->debug(
				'turn_stream_finalize_no_state',
				array(
					'chat_id'    => $chat_id,
					'message_id' => $message_id,
					'reply_len'  => $this->text_length( (string) $reply ),
				)
			);
			return (string) $reply;
		}

		if ( empty( $state['streaming_disabled'] ) ) {
			$state = $this->call( 'maybe_flush_turn_stream_buffer', array( $state, true ), $state );
			$state = is_array( $state ) ? $state : array();
		}

		$full_reply = (string) $reply;
		$sent_text  = isset( $state['sent_text'] ) ? (string) $state['sent_text'] : '';
		$sent_chars = intval( isset( $state['sent_chars'] ) ? $state['sent_chars'] : 0 );
		$dedupe    = $this->dedupe_final_reply_after_stream( $full_reply, $sent_text, $sent_chars );
		$send_reply = isset( $dedupe['reply'] ) ? (string) $dedupe['reply'] : $full_reply;

		$this->debug(
			'turn_stream_finalize',
			array(
				'job_id'             => isset( $context['turn_job_id'] ) ? (string) $context['turn_job_id'] : '',
				'chat_id'            => $chat_id,
				'message_id'         => $message_id,
				'streaming_disabled' => ! empty( $state['streaming_disabled'] ),
				'full_reply_len'     => $this->text_length( $full_reply ),
				'sent_text_len'      => $this->text_length( $sent_text ),
				'sent_chars'         => $sent_chars,
				'buffer_len'         => $this->text_length( isset( $state['buffer'] ) ? $state['buffer'] : '' ),
				'final_send_len'     => $this->text_length( $send_reply ),
				'dedupe_method'      => isset( $dedupe['method'] ) ? (string) $dedupe['method'] : '',
			)
		);

		$this->call( 'clear_turn_stream_state', array( $chat_id, $message_id ) );
		return $send_reply;
	}

	private function dedupe_final_reply_after_stream( $full_reply, $sent_text, $sent_chars ) {
		$full_reply = (string) $full_reply;
		$sent_text  = (string) $sent_text;
		$sent_chars = max( 0, intval( $sent_chars ) );

		if ( '' === $sent_text && $sent_chars <= 0 ) {
			return array( 'reply' => $full_reply, 'method' => 'none' );
		}

		if ( '' !== $sent_text && 0 === strpos( $full_reply, $sent_text ) ) {
			return $this->final_reply_result( $this->text_substr( $full_reply, $this->text_length( $sent_text ) ), $sent_text, 'exact_text' );
		}

		$trimmed_sent = trim( $sent_text );
		if ( '' !== $trimmed_sent && 0 === strpos( $full_reply, $trimmed_sent ) ) {
			return $this->final_reply_result( $this->text_substr( $full_reply, $this->text_length( $trimmed_sent ) ), $sent_text, 'trimmed_text' );
		}

		$normalized_end = $this->find_normalized_stream_prefix_end( $full_reply, $sent_text );
		if ( null !== $normalized_end ) {
			return $this->final_reply_result( substr( $full_reply, $normalized_end ), $sent_text, 'normalized_text' );
		}

		if ( $sent_chars > 0 ) {
			$full_length = $this->text_length( $full_reply );
			$reply       = $sent_chars >= $full_length ? '' : $this->text_substr( $full_reply, $sent_chars );
			return $this->final_reply_result( $reply, $sent_text, 'char_count' );
		}

		return array( 'reply' => $full_reply, 'method' => 'fallback_full' );
	}

	private function final_reply_result( $reply, $sent_text, $method ) {
		$reply = trim( (string) $reply );
		$reply = $this->remove_stream_echo_overlap( $reply, $sent_text );
		$reply = $this->suppress_tiny_stream_echo( $reply, $sent_text );

		return array(
			'reply'  => $reply,
			'method' => (string) $method,
		);
	}

	private function find_normalized_stream_prefix_end( $full_reply, $sent_text ) {
		$sent_norm = $this->normalize_stream_compare_text( $sent_text );
		if ( '' === $sent_norm ) {
			return null;
		}

		$chars = $this->unicode_chars_with_offsets( $full_reply );
		if ( empty( $chars ) ) {
			return null;
		}

		$norm = '';
		$map  = array();
		$pending_space = false;
		foreach ( $chars as $entry ) {
			$char = $entry['char'];
			if ( preg_match( '/\s/u', $char ) ) {
				if ( '' !== $norm ) {
					$pending_space = true;
				}
				continue;
			}

			if ( $pending_space ) {
				$norm .= ' ';
				$map[] = intval( $entry['end'] );
				$pending_space = false;
			}
			$norm .= $char;
			$map[] = intval( $entry['end'] );
		}

		if ( '' === $norm || 0 !== strpos( $norm, $sent_norm ) ) {
			return null;
		}

		$sent_norm_len = $this->unicode_char_count( $sent_norm );
		if ( $sent_norm_len <= 0 || ! isset( $map[ $sent_norm_len - 1 ] ) ) {
			return null;
		}

		return intval( $map[ $sent_norm_len - 1 ] );
	}

	private function remove_stream_echo_overlap( $reply, $sent_text ) {
		$reply = trim( (string) $reply );
		$sent  = trim( (string) $sent_text );
		if ( '' === $reply || '' === $sent ) {
			return $reply;
		}

		$reply_len = $this->text_length( $reply );
		$sent_len  = $this->text_length( $sent );
		$max       = min( 48, $reply_len, $sent_len );
		for ( $length = $max; $length >= 1; $length-- ) {
			$sent_tail  = $this->text_substr( $sent, $sent_len - $length, $length );
			$reply_head = $this->text_substr( $reply, 0, $length );
			if ( $sent_tail !== $reply_head ) {
				continue;
			}
			if ( $this->is_stream_echo_fragment( $sent_tail ) ) {
				return trim( $this->text_substr( $reply, $length ) );
			}
		}

		return $reply;
	}

	private function suppress_tiny_stream_echo( $reply, $sent_text ) {
		$reply = trim( (string) $reply );
		if ( '' === $reply ) {
			return '';
		}
		if ( preg_match( '/^[\p{P}\p{S}\s]+$/u', $reply ) ) {
			return '';
		}

		$reply_norm = $this->normalize_stream_compare_text( $reply );
		$sent_norm  = $this->normalize_stream_compare_text( $sent_text );
		if ( '' !== $reply_norm && $this->unicode_char_count( $reply_norm ) <= 24 && $this->string_ends_with( $sent_norm, $reply_norm ) && $this->is_stream_echo_fragment( $reply_norm ) ) {
			return '';
		}

		return $reply;
	}

	private function is_stream_echo_fragment( $text ) {
		$text = trim( (string) $text );
		if ( '' === $text ) {
			return false;
		}
		if ( preg_match( '/^[\p{P}\p{S}]+$/u', $text ) ) {
			return true;
		}

		$length = $this->unicode_char_count( $text );
		return $length >= 2 && $length <= 24 && ! preg_match( '/\s/u', $text );
	}

	private function normalize_stream_compare_text( $text ) {
		$text = trim( (string) $text );
		if ( '' === $text ) {
			return '';
		}

		return preg_replace( '/\s+/u', ' ', $text );
	}

	private function unicode_chars_with_offsets( $text ) {
		if ( ! preg_match_all( '/./us', (string) $text, $matches, PREG_OFFSET_CAPTURE ) ) {
			return array();
		}

		$chars = array();
		foreach ( $matches[0] as $match ) {
			$char    = (string) $match[0];
			$offset  = intval( $match[1] );
			$chars[] = array(
				'char' => $char,
				'end'  => $offset + strlen( $char ),
			);
		}

		return $chars;
	}

	private function unicode_char_count( $text ) {
		if ( '' === (string) $text ) {
			return 0;
		}
		if ( preg_match_all( '/./us', (string) $text, $matches ) ) {
			return count( $matches[0] );
		}

		return $this->text_length( $text );
	}

	private function string_ends_with( $haystack, $needle ) {
		$haystack = (string) $haystack;
		$needle   = (string) $needle;
		if ( '' === $needle ) {
			return true;
		}

		return strlen( $haystack ) >= strlen( $needle ) && substr( $haystack, -strlen( $needle ) ) === $needle;
	}

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

	private function text_substr( $text, $start, $length = null ) {
		return (string) $this->call( 'text_substr', array( $text, $start, $length ), null === $length ? substr( (string) $text, intval( $start ) ) : substr( (string) $text, intval( $start ), intval( $length ) ) );
	}

	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 );
	}
}
