<?php

namespace CelerSearch\Queue;

defined( 'ABSPATH' ) || exit;

/**
 * Queue manager for sync operations
 *
 * Uses Action Scheduler to defer sync/delete operations, preventing
 * Meilisearch flooding during mass operations. Type-agnostic to support
 * any indexable content type (posts, products, custom tables, etc.)
 */
class Queue {

	/**
	 * Action Scheduler group name
	 */
	const GROUP = 'celersearch';

	/**
	 * Action name for sync operations
	 */
	const SYNC_ACTION = 'celersearch_queue_sync_item';

	/**
	 * Action name for delete operations
	 */
	const DELETE_ACTION = 'celersearch_queue_delete_item';

	/**
	 * Check if Action Scheduler is available
	 *
	 * @return bool
	 */
	public static function is_available(): bool {
		return function_exists( 'as_schedule_single_action' );
	}

	/**
	 * Register action handlers
	 *
	 * @return void
	 */
	public static function register_handlers(): void {
		if ( ! self::is_available() ) {
			return;
		}

		add_action( self::SYNC_ACTION, [ SyncHandler::class, 'handle_sync' ], 10, 3 );
		add_action( self::DELETE_ACTION, [ SyncHandler::class, 'handle_delete' ], 10, 3 );
	}

	/**
	 * Debounce delay in seconds for sync operations.
	 * Actions scheduled within this window will be replaced by the latest one.
	 */
	const DEBOUNCE_DELAY = 5;

	/**
	 * Schedule a sync operation with debouncing.
	 *
	 * Uses a debounce pattern: schedules sync with a small delay. If another
	 * sync is requested for the same item within the delay window, the previous
	 * is cancelled and a new one scheduled. This ensures only the final state
	 * is synced while minimizing cancelled entries.
	 *
	 * @param string $item_type Type identifier (e.g., 'post', 'product', 'custom_row').
	 * @param mixed  $item_id   The item's unique identifier.
	 * @param int    $index_id  The target index ID.
	 *
	 * @return void
	 */
	public static function schedule_sync( string $item_type, $item_id, int $index_id ): void {
		if ( ! self::is_available() ) {
			return;
		}

		$args = [
			'item_type' => $item_type,
			'item_id'   => $item_id,
			'index_id'  => $index_id,
		];

		// Cancel any existing pending sync for this item (debounce)
		self::cancel_pending( self::SYNC_ACTION, $item_type, $item_id, $index_id );

		// Schedule with delay to allow debouncing
		as_schedule_single_action(
			time() + self::DEBOUNCE_DELAY,
			self::SYNC_ACTION,
			$args,
			self::GROUP
		);
	}

	/**
	 * Schedule a delete operation.
	 *
	 * Cancels any pending sync for this item (no point syncing something being deleted),
	 * then schedules immediate delete if not already pending.
	 * Deletes are scheduled immediately (no debounce) since we want items
	 * removed from the search index as quickly as possible.
	 *
	 * @param string $item_type Type identifier (e.g., 'post', 'product', 'custom_row').
	 * @param mixed  $item_id   The item's unique identifier.
	 * @param int    $index_id  The target index ID.
	 *
	 * @return void
	 */
	public static function schedule_delete( string $item_type, $item_id, int $index_id ): void {
		if ( ! self::is_available() ) {
			return;
		}

		// Cancel any pending sync - no point syncing something being deleted
		self::cancel_pending( self::SYNC_ACTION, $item_type, $item_id, $index_id );

		// Skip if delete already scheduled for this specific item
		if ( self::is_scheduled( self::DELETE_ACTION, $item_type, $item_id, $index_id ) ) {
			return;
		}

		// Schedule immediately - deletes should happen ASAP
		as_enqueue_async_action(
			self::DELETE_ACTION,
			[
				'item_type' => $item_type,
				'item_id'   => $item_id,
				'index_id'  => $index_id,
			],
			self::GROUP
		);
	}

	/**
	 * Check if an action is already scheduled for this item
	 *
	 * @param string $action    The action name.
	 * @param string $item_type Type identifier.
	 * @param mixed  $item_id   The item's unique identifier.
	 * @param int    $index_id  The target index ID.
	 *
	 * @return bool
	 */
	public static function is_scheduled( string $action, string $item_type, $item_id, int $index_id ): bool {
		if ( ! self::is_available() ) {
			return false;
		}

		$args = [
			'item_type' => $item_type,
			'item_id'   => $item_id,
			'index_id'  => $index_id,
		];

		return as_next_scheduled_action( $action, $args, self::GROUP ) !== false;
	}

	/**
	 * Cancel pending actions for an item
	 *
	 * @param string $action    The action name.
	 * @param string $item_type Type identifier.
	 * @param mixed  $item_id   The item's unique identifier.
	 * @param int    $index_id  The target index ID.
	 *
	 * @return void
	 */
	public static function cancel_pending( string $action, string $item_type, $item_id, int $index_id ): void {
		if ( ! self::is_available() ) {
			return;
		}

		$args = [
			'item_type' => $item_type,
			'item_id'   => $item_id,
			'index_id'  => $index_id,
		];

		as_unschedule_all_actions( $action, $args, self::GROUP );
	}

	/**
	 * Cancel all pending actions for an item across all indices
	 *
	 * @param string $item_type Type identifier.
	 * @param mixed  $item_id   The item's unique identifier.
	 *
	 * @return void
	 */
	public static function cancel_all_for_item( string $item_type, $item_id ): void {
		if ( ! self::is_available() || ! function_exists( 'as_get_scheduled_actions' ) ) {
			return;
		}

		// Get all pending actions for this item
		$actions = as_get_scheduled_actions(
			[
				'hook'   => [ self::SYNC_ACTION, self::DELETE_ACTION ],
				'status' => \ActionScheduler_Store::STATUS_PENDING,
				'group'  => self::GROUP,
			]
		);

		foreach ( $actions as $action ) {
			$args = $action->get_args();
			if ( isset( $args['item_type'], $args['item_id'] )
			     && $args['item_type'] === $item_type
			     && $args['item_id'] === $item_id ) {
				as_unschedule_action( $action->get_hook(), $args, self::GROUP );
			}
		}
	}

	/**
	 * Get pending action count for the group
	 *
	 * @return int
	 */
	public static function get_pending_count(): int {
		if ( ! self::is_available() || ! function_exists( 'as_get_scheduled_actions' ) ) {
			return 0;
		}

		$actions = as_get_scheduled_actions(
			[
				'hook'   => [ self::SYNC_ACTION, self::DELETE_ACTION ],
				'status' => \ActionScheduler_Store::STATUS_PENDING,
				'group'  => self::GROUP,
			]
		);

		return count( $actions );
	}
}
