<?php
/**
 * Standard implementation of a batch system using action scheduler.
 *
 * @package Ealab\Deployer
 */

namespace EaLab\WP_Deployer\vendor\Ealab\Deployer\Batch;

use EaLab\WP_Deployer\vendor\Dakwamine\Component\ComponentBasedObject;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\CommonDataStorage;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Uuid;
use Throwable;

/**
 * Standard implementation of a batch system using action scheduler.
 */
class BatchScheduler extends ComponentBasedObject {
	/**
	 * Action scheduler callback for the batch system.
	 */
	public static function registerBatchActionCallback(): void {
		add_action(
			'deploy_sync_content/process_batch_step',
			/**
			 * Tracks an item using the action scheduler.
			 *
			 * @param callable $batch_step_callable Batch step callable. Usually a static function/method.
			 * @param string   $step_id Batch step ID.
			 *
			 * @throws Throwable Unrecoverable error.
			 */
			static function ( callable $batch_step_callable, string $step_id ) {
				// Load the batch metadata.
				$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

				$encoded_meta = $common_data_storage->loadSingleValue( $step_id, 'deploy_sync_content/batch_step_meta' ) ?? '[]';

				$decoded_meta = json_decode( $encoded_meta, true ) ?? array();

				$batch_step_callable_args = $decoded_meta['batch_step_callable_args'] ?? array();

				$batch_step_callable( ...$batch_step_callable_args );
			},
			10,
			2
		);

		add_action(
			'action_scheduler_after_execute',
			static function ( int $action_id, \ActionScheduler_Action $action, string $context ) {
				if ( 'deploy_sync_content/process_batch_step' !== $action->get_hook() ) {
					// Not a hook we manage.
					return;
				}

				// Retrieve the metadata for this step.
				$args     = $action->get_args();
				$batch_id = $action->get_group();

				$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

				$step_id = $args[1] ?? null;

				if ( empty( $step_id ) || empty( $batch_id ) ) {
					// Misconfigured step.
					return;
				}

				$batch_step_meta = json_decode( $common_data_storage->loadSingleValue( $step_id, 'deploy_sync_content/batch_step_meta' ), true );

				$steps_to_enqueue_on_success = $batch_step_meta['steps_to_enqueue_on_success'] ?? null;

				if ( ! is_array( $steps_to_enqueue_on_success ) || empty( $steps_to_enqueue_on_success ) ) {
					// Nothing more to do.
					return;
				}

				// Enqueue new steps to the batch scheduler.
				$batch_scheduler = ComponentBasedObject::getRootComponentByClassName( BatchScheduler::class, true );

				foreach ( $steps_to_enqueue_on_success as $step_to_enqueue_on_success ) {
					// $step_to_enqueue_on_success uses the same signature as enqueueStepToBatch().
					$batch_scheduler->enqueueStepToBatch( ...$step_to_enqueue_on_success );
				}
			},
			10,
			3
		);
	}

	/**
	 * Enqueues a step to the action scheduler batch system.
	 *
	 * @param string     $batch_id ID which will group the batch steps together.
	 * @param callable   $batch_callable The batch callable to use. Must be a static class method or a function.
	 * @param array|null $batch_step_callable_args Batch callable args.
	 *     Must not contain any data specific to this PHP process.
	 * @param array|null $options Options:
	 *     - "callable_after_execution" (array<callable>): Callable to call.
	 *     - "delay" (int): In seconds, added to the time of execution if set. Default is 10.
	 *     - "steps_to_enqueue_on_success" (array): Arguments of enqueueStepToBatch for steps to enqueue on success.
	 *       E.g.:
	 *       array( array('batch-id', ['CallableClass', 'callableMethod'], array( 'arg1' => 123 ) ) ).
	 *
	 * @return int The action ID of the tracking action.
	 *
	 * @throws Throwable Unmanaged errors.
	 */
	public function enqueueStepToBatch( string $batch_id, callable $batch_callable, ?array $batch_step_callable_args = null, ?array $options = null ): int {
		require_once DEPLOY_SYNC_CONTENT__PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';
		! did_action( 'action_scheduler_init' ) && do_action( 'action_scheduler_init' );

		$step_id = Uuid::getInstance()->generateUuidV4();

		// Save the args in a batch metadata entry.
		// This is needed to work around the action scheduler length limit on the args column.
		$batch_step_meta = array(
			'batch_step_callable_args'    => $batch_step_callable_args,
			'batch_step_id'               => $step_id,
			'steps_to_enqueue_on_success' => $options['steps_to_enqueue_on_success'] ?? null,
		);

		$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

		$common_data_storage->store( $step_id, json_encode( $batch_step_meta ?? array() ), 'deploy_sync_content/batch_step_meta' );

		// Delay the tracking by 10 seconds by default to leave enough time for the plugin to be ready.
		return as_schedule_single_action(
			time() + ( $options['delay'] ?? 10 ),
			'deploy_sync_content/process_batch_step',
			array(
				$batch_callable,
				$step_id,
			),
			$batch_id,
			false
		);
	}
}
