<?php
/**
 * Timeout guard for long-running batch operations.
 *
 * Tracks elapsed time per item and predicts whether the next item
 * can finish before PHP's max_execution_time kills the process.
 *
 * @package CoffeeWP_Media_Cleanup
 */

namespace CWPM\Utils;

defined( 'ABSPATH' ) || exit;

class TimeoutGuard {

	/**
	 * Maximum execution time in seconds (from php.ini or override).
	 *
	 * @var float
	 */
	private $max_time;

	/**
	 * Safety buffer in seconds — stop this many seconds before the limit.
	 *
	 * @var float
	 */
	private $buffer;

	/**
	 * Timestamp when the guard was started (microtime).
	 *
	 * @var float
	 */
	private $start_time;

	/**
	 * Number of items processed so far.
	 *
	 * @var int
	 */
	private $items_processed = 0;

	/**
	 * Constructor.
	 *
	 * @param float|null $max_time  Max execution time in seconds. Null = read from ini.
	 * @param float      $buffer    Safety buffer in seconds (default 2.0).
	 * @param float|null $start_time Override start time for testing. Null = now.
	 */
	public function __construct( $max_time = null, $buffer = 2.0, $start_time = null ) {
		if ( null === $max_time ) {
			$max_time = (float) ini_get( 'max_execution_time' );
		}
		$this->max_time   = $max_time;
		$this->buffer     = $buffer;
		$this->start_time = null !== $start_time ? $start_time : microtime( true );
	}

	/**
	 * Record that one item has been processed.
	 */
	public function tick() {
		++$this->items_processed;
	}

	/**
	 * Get number of items processed so far.
	 *
	 * @return int
	 */
	public function get_items_processed() {
		return $this->items_processed;
	}

	/**
	 * Get elapsed time in seconds since the guard was started.
	 *
	 * @return float
	 */
	public function get_elapsed() {
		return microtime( true ) - $this->start_time;
	}

	/**
	 * Get average time per item in seconds.
	 *
	 * @return float 0.0 if no items processed yet.
	 */
	public function get_avg_item_time() {
		if ( $this->items_processed < 1 ) {
			return 0.0;
		}
		return $this->get_elapsed() / $this->items_processed;
	}

	/**
	 * Get remaining safe time (max_time - buffer - elapsed).
	 *
	 * @return float Seconds remaining. Negative means already over.
	 */
	public function get_remaining() {
		return $this->max_time - $this->buffer - $this->get_elapsed();
	}

	/**
	 * Check whether it's safe to process another item.
	 *
	 * Returns true if:
	 * - max_execution_time is 0 (unlimited), OR
	 * - remaining time > average item time (with buffer)
	 *
	 * @return bool
	 */
	public function can_continue() {

		if ( $this->max_time <= 0 ) {
			return true;
		}

		$remaining = $this->get_remaining();

		if ( $this->items_processed < 1 ) {
			return $remaining > 0;
		}

		return $remaining > $this->get_avg_item_time();
	}
}
