<?php
/**
 * WhatsApp short text batching for gateway ingress.
 *
 * @package Niwa_AI
 */

defined( 'ABSPATH' ) || exit;

class Niwa_AI_Admin_Autonomy_WhatsApp_Text_Batcher {
	const QUIET_FLUSH_MS       = 5000;
	const TTL_SECONDS          = 900;
	const MAX_PARTS            = 12;
	const MAX_TOTAL_CHARS      = 50000;
	const MAX_SINGLE_TEXT_CHARS = 500;

	private $index_option;
	private $entry_prefix = 'niwa_ai_wa_text_batch_';

	public function __construct() {
		$this->index_option = Niwa_AI_Constants::OPT_AUTONOMY_WA_TEXT_BATCH_INDEX;
	}

	public function buffer_message( $payload ) {
		$payload = is_array( $payload ) ? $payload : array();
		if ( ! $this->is_bufferable( $payload ) ) {
			return false;
		}

		$chat_id = (string) $payload['chat_id'];
		$key     = $this->entry_key( $chat_id );
		$entry   = $this->read_entry( $key );
		$now_ms  = $this->now_ms();

		if ( ! is_array( $entry ) ) {
			$entry = array(
				'chat_id'              => $chat_id,
				'first_received_at_ms' => $now_ms,
				'last_received_at_ms'  => $now_ms,
				'parts'                => array(),
			);
			$this->add_to_index( $chat_id );
		}

		$current_total = $this->entry_text_length( $entry );
		$text          = (string) $payload['text_only'];
		if ( count( (array) $entry['parts'] ) >= self::MAX_PARTS || $current_total + strlen( $text ) > self::MAX_TOTAL_CHARS ) {
			return false;
		}

		$entry['last_received_at_ms'] = $now_ms;
		$entry['parts'][]            = $payload;
		$this->store_entry( $key, $entry );
		return true;
	}

	public function flush_ready_messages( $force = false ) {
		$now_ms         = $this->now_ms();
		$idle_threshold = $now_ms - self::QUIET_FLUSH_MS;
		$hard_threshold = $now_ms - ( self::TTL_SECONDS * 1000 );
		$index          = (array) get_option( $this->index_option, array() );
		$ready          = array();
		$remaining      = array();

		foreach ( $index as $chat_id ) {
			$chat_id = (string) $chat_id;
			$key     = $this->entry_key( $chat_id );
			$entry   = $this->read_entry( $key );
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$last_ms  = intval( $entry['last_received_at_ms'] ?? 0 );
			$first_ms = intval( $entry['first_received_at_ms'] ?? 0 );
			if ( $force || $last_ms <= $idle_threshold || $first_ms <= $hard_threshold ) {
				$combined = $this->combine_entry( $entry );
				if ( null !== $combined ) {
					$ready[] = $combined;
				}
				$this->delete_entry( $key );
			} else {
				$remaining[] = $chat_id;
			}
		}

		update_option( $this->index_option, array_values( array_unique( $remaining ) ), false );
		return $ready;
	}

	public function has_pending() {
		return ! empty( get_option( $this->index_option, array() ) );
	}

	public function next_flush_delay() {
		$index = (array) get_option( $this->index_option, array() );
		$now   = $this->now_ms();
		$delay = null;
		foreach ( $index as $chat_id ) {
			$entry = $this->read_entry( $this->entry_key( (string) $chat_id ) );
			if ( ! is_array( $entry ) ) {
				continue;
			}
			$last_ms   = intval( $entry['last_received_at_ms'] ?? 0 );
			$remaining = max( 1, (int) ceil( ( $last_ms + self::QUIET_FLUSH_MS - $now ) / 1000 ) );
			$delay     = null === $delay ? $remaining : min( $delay, $remaining );
		}

		return null === $delay ? 0 : $delay;
	}

	private function is_bufferable( $payload ) {
		if ( 'whatsapp' !== ( isset( $payload['channel'] ) ? sanitize_key( (string) $payload['channel'] ) : '' ) ) {
			return false;
		}
		if ( empty( $payload['success'] ) || empty( $payload['chat_id'] ) ) {
			return false;
		}
		if ( ! empty( $payload['media_refs'] ) || ! empty( $payload['callback_data'] ) || ! empty( $payload['callback_query_id'] ) ) {
			return false;
		}

		$text = isset( $payload['text_only'] ) ? (string) $payload['text_only'] : '';
		if ( '' === trim( $text ) || strlen( $text ) > self::MAX_SINGLE_TEXT_CHARS ) {
			return false;
		}

		return 0 !== strpos( ltrim( $text ), '/' );
	}

	private function combine_entry( $entry ) {
		$parts = isset( $entry['parts'] ) && is_array( $entry['parts'] ) ? $entry['parts'] : array();
		if ( empty( $parts ) ) {
			return null;
		}

		usort( $parts, function ( $a, $b ) {
			return intval( $a['message_id'] ?? 0 ) - intval( $b['message_id'] ?? 0 );
		} );

		$first           = $parts[0];
		$texts           = array();
		$message_ids     = array();
		$raw_message_ids = array();
		foreach ( $parts as $part ) {
			$texts[]       = (string) ( $part['text_only'] ?? '' );
			$message_ids[] = intval( $part['message_id'] ?? 0 );
			if ( ! empty( $part['raw_message_id'] ) ) {
				$raw_message_ids[] = (string) $part['raw_message_id'];
			}
		}

		$text = trim( implode( "\n", array_filter( $texts, 'strlen' ) ) );
		if ( '' === $text ) {
			return null;
		}

		$combined                     = $first;
		$combined['text']             = $text;
		$combined['text_only']        = $text;
		$combined['has_user_text']    = true;
		$combined['kind']             = 'text_batch';
		$combined['is_text_batch']    = true;
		$combined['batch_size']       = count( $parts );
		$combined['batch_message_ids'] = $message_ids;
		$combined['raw_message_ids']  = $raw_message_ids;

		return $combined;
	}

	private function entry_text_length( $entry ) {
		$total = 0;
		foreach ( (array) ( $entry['parts'] ?? array() ) as $part ) {
			$total += strlen( (string) ( $part['text_only'] ?? '' ) );
		}
		return $total;
	}

	private function now_ms() {
		return (int) round( microtime( true ) * 1000 );
	}

	private function entry_key( $chat_id ) {
		return $this->entry_prefix . sha1( (string) $chat_id );
	}

	private function add_to_index( $chat_id ) {
		$chat_id = (string) $chat_id;
		$index   = (array) get_option( $this->index_option, array() );
		if ( ! in_array( $chat_id, $index, true ) ) {
			$index[] = $chat_id;
			update_option( $this->index_option, $index, false );
		}
	}

	private function store_entry( $key, $entry ) {
		update_option(
			$key,
			array(
				'value'      => $entry,
				'expires_at' => time() + self::TTL_SECONDS,
			),
			false
		);
	}

	private function read_entry( $key ) {
		$wrapped = get_option( $key, false );
		if ( ! is_array( $wrapped ) || ! array_key_exists( 'value', $wrapped ) ) {
			return false;
		}

		$expires_at = intval( $wrapped['expires_at'] ?? 0 );
		if ( $expires_at > 0 && $expires_at < time() ) {
			delete_option( $key );
			return false;
		}

		return $wrapped['value'];
	}

	private function delete_entry( $key ) {
		delete_option( $key );
	}
}
