<?php
namespace R2_Offload\Queue;

use R2_Offload\Logger;

defined( 'ABSPATH' ) || exit;

/**
 * Thin queue abstraction. Prefers Action Scheduler when present and healthy;
 * falls back to WP-Cron. Callers that need reliability (new uploads) should
 * also process on shutdown — see Offloader::on_metadata().
 */
final class Scheduler {

	private Logger $logger;

	/** @var bool|null Cached AS readiness. */
	private static $as_ready = null;

	public function __construct( Logger $logger ) {
		$this->logger = $logger;
	}

	public function register(): void {
		add_action( 'r2_offload_cron_tick', [ $this, 'cron_tick' ] );
	}

	/**
	 * True when Action Scheduler function exists AND its DB tables are present.
	 * On this site AS is loaded by PMPro but tables were never created — enqueue
	 * would silently fail and never fall back without this check.
	 */
	public function action_scheduler_ready(): bool {
		if ( self::$as_ready !== null ) {
			return self::$as_ready;
		}
		if ( ! function_exists( 'as_enqueue_async_action' ) ) {
			self::$as_ready = false;
			return false;
		}
		global $wpdb;
		$table = $wpdb->prefix . 'actionscheduler_actions';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
		self::$as_ready = ( $exists === $table );
		if ( ! self::$as_ready ) {
			$this->logger->warn(
				'Action Scheduler present but tables missing; using WP-Cron fallback.',
				'queue'
			);
		}
		return self::$as_ready;
	}

	public function enqueue_attachment( int $attachment_id, int $delay_seconds = 5 ): void {
		if ( $this->action_scheduler_ready() ) {
			try {
				$id = as_enqueue_async_action( 'r2_offload_process_attachment', [ $attachment_id ], 'r2-offload' );
				if ( $id ) {
					$this->logger->info( 'Queued via Action Scheduler.', 'queue', [ 'attachment' => $attachment_id, 'action_id' => $id ] );
					return;
				}
			} catch ( \Throwable $e ) {
				$this->logger->warn(
					'Action Scheduler enqueue failed; falling back to WP-Cron.',
					'queue',
					[ 'attachment' => $attachment_id, 'error' => $e->getMessage() ]
				);
				self::$as_ready = false;
			}
		}

		// Avoid duplicate cron events for the same attachment.
		if ( ! wp_next_scheduled( 'r2_offload_process_attachment', [ $attachment_id ] ) ) {
			wp_schedule_single_event( time() + max( 0, $delay_seconds ), 'r2_offload_process_attachment', [ $attachment_id ] );
		}
		if ( function_exists( 'spawn_cron' ) ) {
			spawn_cron();
		}
		$this->logger->info( 'Queued via WP-Cron.', 'queue', [ 'attachment' => $attachment_id, 'delay' => $delay_seconds ] );
	}

	public function cron_tick(): void {
		$this->logger->info( 'Cron tick.', 'queue' );
	}
}
