<?php
/**
 * Post-reply companion memory queue worker.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Admin_Autonomy_Post_Reply_Memory_Queue {
	const RETRY_HOOK = 'niwa_ai_admin_autonomy_retry_post_reply_memory_enqueue';
	/** @var object|null */
	private $chat_agent;

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

	/** @var string */
	private $option_name;

	/** @var int */
	private $max_queue;

	/** @var int */
	private $max_age;

	/** @var string */
	private $mutation_lock_name;

	/** @var string */
	private $completed_option_name;

	public function __construct( $chat_agent = null, $callbacks = array(), $options = array() ) {
		$this->chat_agent         = $chat_agent;
		$this->callbacks          = is_array( $callbacks ) ? $callbacks : array();
		$options                  = is_array( $options ) ? $options : array();
		$this->option_name        = isset( $options['option_name'] ) ? (string) $options['option_name'] : 'niwa_ai_admin_autonomy_post_reply_memory_queue';
		$this->max_queue          = max( 1, intval( isset( $options['max_queue'] ) ? $options['max_queue'] : 30 ) );
		$this->max_age            = max( 1, intval( isset( $options['max_age'] ) ? $options['max_age'] : DAY_IN_SECONDS ) );
		$this->mutation_lock_name = isset( $options['mutation_lock_name'] ) ? (string) $options['mutation_lock_name'] : 'post_reply_memory_queue_mutation';
		$this->completed_option_name = isset( $options['completed_option_name'] )
			? (string) $options['completed_option_name']
			: $this->option_name . '_completed';
	}

	public function enqueue( $session_id, $user_text, $reply, $context = array() ) {
		$session_id = trim( (string) $session_id );
		$user_text  = trim( (string) $user_text );
		$reply      = trim( (string) $reply );

		if ( '' === $session_id || ( '' === $user_text && '' === $reply ) ) {
			return false;
		}
		$context     = is_array( $context ) ? $context : array();
		$admin_job_id = trim( (string) ( $context['admin_ui_job_id'] ?? '' ) );

		$entry = array(
			'id'         => '' !== $admin_job_id
				? 'admin_ui_memory_' . md5( $session_id . '|' . $admin_job_id )
				: ( function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : md5( uniqid( 'post_reply_memory_', true ) ) ),
			'session_id' => $session_id,
			'user_text'  => $user_text,
			'reply'      => $reply,
			'context'    => $this->call( 'compact_queued_context', array( $context ), array() ),
			'created_at' => time(),
		);

		return $this->enqueue_entry( $entry );
	}

	public function enqueue_entry( $entry ) {
		$entry = is_array( $entry ) ? $entry : array();
		if ( empty( $entry['id'] ) || empty( $entry['session_id'] ) ) {
			return false;
		}

		if ( ! $this->acquire_mutation_lock() ) {
			$this->debug(
				'post_reply_memory_enqueue_lock_busy',
				array(
					'session_id' => (string) $entry['session_id'],
				)
			);
			if ( function_exists( 'wp_schedule_single_event' ) ) {
				wp_schedule_single_event( time() + 5, self::RETRY_HOOK, array( $entry ) );
			}
			return false;
		}

		$queue_size = 0;
		try {
			$queue = get_option( $this->option_name, array() );
			if ( ! is_array( $queue ) ) {
				$queue = array();
			}
			$completed = $this->get_completed_ledger();
			if ( isset( $completed[ (string) $entry['id'] ] ) ) {
				return true;
			}
			foreach ( $queue as $queued ) {
				if ( is_array( $queued ) && hash_equals( (string) $entry['id'], (string) ( $queued['id'] ?? '' ) ) ) {
					return true;
				}
			}

			$queue[]    = $entry;
			$queue      = array_slice( $queue, -$this->max_queue );
			$queue_size = count( $queue );
			$queue      = array_values( $queue );
			$saved      = update_option( $this->option_name, $queue, false ) || get_option( $this->option_name, array() ) === $queue;
			if ( ! $saved ) {
				if ( function_exists( 'wp_schedule_single_event' ) ) {
					wp_schedule_single_event( time() + 5, self::RETRY_HOOK, array( $entry ) );
				}
				return false;
			}
		} finally {
			$this->release_mutation_lock();
		}

		$this->debug(
			'post_reply_memory_queued',
			array(
				'session_id' => (string) $entry['session_id'],
				'queue_size' => $queue_size,
			)
		);
		return true;
	}

	public function process( $limit = 3 ) {
		$limit = max( 1, min( 10, intval( $limit ) ) );
		if ( ! $this->acquire_lock() ) {
			return array(
				'success'   => true,
				'processed' => 0,
				'remaining' => 0,
				'note'      => 'locked',
			);
		}

		$remaining     = 0;
		$processed     = 0;
		$skipped_stale = 0;
		$errors        = array();

		try {
			if ( ! $this->acquire_mutation_lock() ) {
				return array(
					'success'   => true,
					'processed' => 0,
					'remaining' => 0,
					'note'      => 'mutation_locked',
				);
			}

			$batch = array();
			try {
				$queue = get_option( $this->option_name, array() );
				if ( ! is_array( $queue ) || empty( $queue ) ) {
					delete_option( $this->option_name );
					return array(
						'success'   => true,
						'processed' => 0,
						'remaining' => 0,
						'errors'    => array(),
					);
				}

				foreach ( array_values( $queue ) as $entry ) {
					if ( count( $batch ) >= $limit ) {
						continue;
					}
					$batch[] = $entry;
				}

			} finally {
				$this->release_mutation_lock();
			}

			$completed_ids = array();
			$completed_ledger = $this->get_completed_ledger();
			foreach ( $batch as $entry ) {
				try {
					$entry = is_array( $entry ) ? $entry : array();
					$entry_id = (string) ( $entry['id'] ?? '' );
					if ( '' !== $entry_id && isset( $completed_ledger[ $entry_id ] ) ) {
						$completed_ids[ $entry_id ] = true;
						$processed++;
						continue;
					}
					if ( $this->is_stale( $entry ) ) {
						$skipped_stale++;
					} else {
						$this->run_capture( $entry );
					}
					if ( ! empty( $entry['id'] ) ) {
						$completed_ids[ (string) $entry['id'] ] = true;
					}
					$processed++;
				} catch ( Throwable $e ) {
					$errors[] = $e->getMessage();
					$processed++;
				}
			}

			if ( $this->acquire_mutation_lock() ) {
				try {
					$queue = get_option( $this->option_name, array() );
					$queue = is_array( $queue ) ? array_values( $queue ) : array();
					$original_queue = $queue;
					$queue = array_values(
						array_filter(
							$queue,
							function ( $entry ) use ( $completed_ids ) {
								$id = is_array( $entry ) && isset( $entry['id'] ) ? (string) $entry['id'] : '';
								return '' === $id || ! isset( $completed_ids[ $id ] );
							}
						)
					);
					$completed = $this->get_completed_ledger();
					foreach ( array_keys( $completed_ids ) as $completed_id ) {
						$completed[ (string) $completed_id ] = time();
					}
					$completed = array_slice( $completed, -max( 20, $this->max_queue * 4 ), null, true );
					$ledger_saved = true;
					if ( ! empty( $completed_ids ) ) {
						$ledger_saved = update_option( $this->completed_option_name, $completed, false )
							|| get_option( $this->completed_option_name, array() ) === $completed;
					}
					if ( ! $ledger_saved ) {
						$errors[]  = 'Post-reply memory completion ledger could not be persisted.';
						$queue     = $original_queue;
					}
					$remaining = count( $queue );
					if ( empty( $queue ) ) {
						$queue_saved = delete_option( $this->option_name ) || array() === get_option( $this->option_name, array() );
					} else {
						$queue_saved = update_option( $this->option_name, $queue, false ) || get_option( $this->option_name, array() ) === $queue;
					}
					if ( ! $queue_saved ) {
						$errors[]  = 'Post-reply memory queue acknowledgement could not be persisted.';
						$remaining = count( $original_queue );
					}
				} finally {
					$this->release_mutation_lock();
				}
			} else {
				$remaining = count( (array) get_option( $this->option_name, array() ) );
			}
		} finally {
			$this->release_lock();
		}

		$this->debug(
			'post_reply_memory_processed',
			array(
				'processed'     => $processed,
				'remaining'     => $remaining,
				'skipped_stale' => $skipped_stale,
				'errors'        => array_slice( array_values( array_filter( $errors ) ), 0, 3 ),
			)
		);

		return array(
			'success'       => empty( $errors ),
			'processed'     => $processed,
			'remaining'     => $remaining,
			'skipped_stale' => $skipped_stale,
			'errors'        => $errors,
		);
	}

	public function is_stale( $entry ) {
		$created_at = isset( $entry['created_at'] ) ? intval( $entry['created_at'] ) : 0;
		if ( $created_at <= 0 ) {
			return false;
		}

		return $created_at < ( time() - $this->max_age );
	}

	public function run_capture( $entry ) {
		$session_id = isset( $entry['session_id'] ) ? trim( (string) $entry['session_id'] ) : '';
		$user_text  = isset( $entry['user_text'] ) ? trim( (string) $entry['user_text'] ) : '';
		$reply      = isset( $entry['reply'] ) ? trim( (string) $entry['reply'] ) : '';
		$context    = isset( $entry['context'] ) && is_array( $entry['context'] ) ? $entry['context'] : array();

		if ( '' === $session_id || ( '' === $user_text && '' === $reply ) ) {
			return;
		}

		if ( $this->chat_agent && method_exists( $this->chat_agent, 'capture_companion_memory_exchange' ) ) {
			$result = $this->chat_agent->capture_companion_memory_exchange( $session_id, $user_text, $reply, $context );
			if ( is_array( $result ) && array_key_exists( 'success', $result ) && empty( $result['success'] ) ) {
				throw new RuntimeException( (string) ( $result['error'] ?? 'post_reply_memory_capture_failed' ) );
			}
		}
	}

	private function get_completed_ledger() {
		$ledger = get_option( $this->completed_option_name, array() );
		$ledger = is_array( $ledger ) ? $ledger : array();
		$cutoff = time() - $this->max_age;
		return array_filter(
			$ledger,
			static function ( $completed_at ) use ( $cutoff ) {
				return intval( $completed_at ) >= $cutoff;
			}
		);
	}

	private function acquire_lock() {
		return ! empty( $this->call( 'acquire_lock', array( 'post_reply_memory', 180 ), false ) );
	}

	private function release_lock() {
		$this->call( 'release_lock', array( 'post_reply_memory' ) );
	}

	private function acquire_mutation_lock() {
		return ! empty( $this->call( 'acquire_lock', array( $this->mutation_lock_name, 15 ), false ) );
	}

	private function release_mutation_lock() {
		$this->call( 'release_lock', array( $this->mutation_lock_name ) );
	}

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