<?php
/**
 * Per-turn structured log for the agent harness.
 *
 * Stores the last N turns in a wp_option ring buffer so we don't add another
 * custom table for diagnostic data that's only ever read by support / debug.
 *
 * Outcome lifecycle:
 *   - Default state on insert is `pending`.
 *   - `save_post_*` listeners (in AMBLD_Antimanual_Builder) flip the most-recent
 *     `pending` turn for the same post to `accepted` when the user saves an
 *     AI-produced page without further AI work, and bump helpfulness on each
 *     of that turn's retrieved sources.
 *   - `chat_stream()` flips the prior `pending` turn for a post to
 *     `regenerated` when a new turn fires for that post, and bumps helpfulness
 *     downward.
 *
 * @package Antimanual_Builder
 * @since   1.6.0
 */

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

class AMBLD_Agent_Turn_Logger {

	const OPTION_KEY = 'ambld_agent_turn_log';
	const MAX_TURNS  = 50;

	private $turn = array();

	public function __construct() {
		$this->turn = array(
			'started_at'   => microtime( true ),
			'provider'     => '',
			'model'        => '',
			'post_id'      => 0,
			'sources'      => array(),
			'tool_calls'   => array(),
			'guardrails'   => array(),
			'finish'       => '',
			'outcome'      => 'pending',
			'elapsed_ms'   => 0,
		);
	}

	public function set_provider( $provider, $model ) {
		$this->turn['provider'] = (string) $provider;
		$this->turn['model']    = (string) $model;
	}

	public function set_post_id( $post_id ) {
		$this->turn['post_id'] = (int) $post_id;
	}

	public function add_sources( array $sources ) {
		foreach ( $sources as $s ) {
			$this->turn['sources'][] = $s;
		}
	}

	public function add_tool_call( $name, $summary = '' ) {
		$this->turn['tool_calls'][] = array( 'name' => (string) $name, 'summary' => (string) $summary );
	}

	public function add_guardrail_result( array $result ) {
		$this->turn['guardrails'][] = $result;
	}

	public function finish( $reason ) {
		$this->turn['finish']     = (string) $reason;
		$this->turn['elapsed_ms'] = (int) round( ( microtime( true ) - (float) $this->turn['started_at'] ) * 1000 );
		self::append( $this->turn );
	}

	private static function append( array $turn ) {
		$log = get_option( self::OPTION_KEY, array() );
		if ( ! is_array( $log ) ) {
			$log = array();
		}
		$log[] = $turn;
		if ( count( $log ) > self::MAX_TURNS ) {
			$log = array_slice( $log, -self::MAX_TURNS );
		}
		update_option( self::OPTION_KEY, $log, false );
	}

	public static function recent( $limit = 20 ) {
		$log = get_option( self::OPTION_KEY, array() );
		if ( ! is_array( $log ) ) {
			return array();
		}
		return array_slice( $log, -max( 1, (int) $limit ) );
	}

	/**
	 * Find the most recent turn whose outcome is still `pending` for a given
	 * post id, mutate it in place, and write the log back.
	 *
	 * Used by save_post_* listeners (outcome = 'accepted') and by chat_stream
	 * on a new-turn-for-same-post (outcome = 'regenerated').
	 *
	 * @param int    $post_id Post that owns the turn.
	 * @param string $outcome 'accepted' | 'regenerated'.
	 * @return array|null The mutated turn record, or null if nothing matched.
	 */
	public static function mark_last_turn_outcome( $post_id, $outcome ) {
		$post_id = (int) $post_id;
		if ( $post_id <= 0 ) {
			return null;
		}
		$outcome = in_array( $outcome, array( 'accepted', 'regenerated' ), true ) ? $outcome : 'pending';
		$log     = get_option( self::OPTION_KEY, array() );
		if ( ! is_array( $log ) || empty( $log ) ) {
			return null;
		}
		// Walk from the tail — most recent first.
		for ( $i = count( $log ) - 1; $i >= 0; $i-- ) {
			$entry = $log[ $i ];
			if ( ! is_array( $entry ) ) {
				continue;
			}
			if ( (int) ( $entry['post_id'] ?? 0 ) !== $post_id ) {
				continue;
			}
			if ( ( $entry['outcome'] ?? 'pending' ) !== 'pending' ) {
				return null; // The most-recent turn for this post has already been resolved.
			}
			$log[ $i ]['outcome'] = $outcome;
			update_option( self::OPTION_KEY, $log, false );
			return $log[ $i ];
		}
		return null;
	}
}
