<?php
/**
 * WP Cron Run Tool.
 *
 * Allows the agent to manually trigger WordPress cron events immediately
 * by invoking registered hook callbacks with their arguments.
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

// Direct access prevention
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * WP_Cron_Run_Tool class.
 */
class WP_Cron_Run_Tool extends Tool {

	/**
	 * Get the tool ID.
	 *
	 * @return string The tool ID.
	 * @since     1.0.0
	 */
	public function get_id() {
		return 'run_cron';
	}

	/**
	 * Get the tool name.
	 *
	 * @return string The tool name.
	 * @since     1.0.0
	 */
	public function get_name() {
		return __( 'Run Cron', 'agenistrator' );
	}

	/**
	 * Get the description of the tool.
	 *
	 * @return string The description of the tool.
	 * @since     1.0.0
	 */
	public function get_description() {
		return 'Runs WordPress cron events. Can run all due events or a specific hook.';
	}

	/**
	 * Get the parameters for this tool.
	 *
	 * @return array Parameters schema in JSON Schema format.
	 * @since     1.0.0
	 */
	public function get_parameters() {
		return array(
			'type'       => 'object',
			'properties' => array(
				'hook' => array(
					'type'        => 'string',
					'description' => 'Run a specific cron hook. If omitted, runs all due events.',
				),
				'args' => array(
					'type'        => 'array',
					'description' => 'Arguments to pass to the hook (if running a specific hook).',
					'items'       => array( 'type' => 'string' ),
				),
			),
			'required'   => array(),
		);
	}

	/**
	 * Check if the current user has permission to execute this tool.
	 *
	 * @return bool Whether the user has permission.
	 * @since     1.0.0
	 */
	public function current_user_can_execute() {
		return current_user_can( 'manage_options' );
	}

	/**
	 * Check if this tool is read-only.
	 *
	 * @return bool False - this tool executes cron events which may have side effects.
	 * @since     1.0.0
	 */
	public function is_read_only() {
		return false;
	}

	/**
	 * Execute the tool with the given arguments.
	 *
	 * @param array $args The arguments for the tool.
	 * @return array The result of executing the tool.
	 * @since     1.0.0
	 */
	protected function execute( $args ) {
		$hook       = $args['hook'] ?? null;
		$event_args = $args['args'] ?? array();

		if ( $hook ) {
			// Run a specific hook.
			$crons = _get_cron_array();
			$found = false;
			$ran   = 0;

			if ( ! empty( $crons ) ) {
				foreach ( $crons as $timestamp => $cron_hooks ) {
					if ( isset( $cron_hooks[ $hook ] ) ) {
						foreach ( $cron_hooks[ $hook ] as $event_data ) {
							// Check if args match (if specified).
							if ( ! empty( $event_args ) ) {
								$scheduled_args = $event_data['args'] ?? array();
								if ( $scheduled_args !== $event_args ) {
									continue;
								}
							}

							$found = true;

							// Execute the hook.
							// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- This tool's purpose is to run existing cron hooks; $hook comes from the WordPress cron schedule.
							do_action_ref_array( $hook, $event_data['args'] ?? array() );
							++$ran;

							// If it's a one-time event, unschedule it.
							if ( empty( $event_data['schedule'] ) ) {
								wp_unschedule_event( $timestamp, $hook, $event_data['args'] ?? array() );
							}
						}
					}
				}
			}

			if ( ! $found ) {
				return $this->error_response( "No cron event found with hook: {$hook}" );
			}

			return $this->success_response(
				"Executed {$ran} instance(s) of cron hook '{$hook}'",
				array(
					'hook'          => $hook,
					'instances_run' => $ran,
				)
			);
		} else {
			// Run all due events.
			$crons = _get_cron_array();
			$now   = time();
			$ran   = 0;

			if ( ! empty( $crons ) ) {
				foreach ( $crons as $timestamp => $cron_hooks ) {
					if ( $timestamp > $now ) {
						continue;
					}

					foreach ( $cron_hooks as $hook_name => $hook_events ) {
						foreach ( $hook_events as $event_data ) {
							// Execute the hook.
							// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- This tool's purpose is to run existing cron hooks; $hook_name comes from the WordPress cron schedule.
							do_action_ref_array( $hook_name, $event_data['args'] ?? array() );
							++$ran;

							// If it's a one-time event, unschedule it.
							if ( empty( $event_data['schedule'] ) ) {
								wp_unschedule_event( $timestamp, $hook_name, $event_data['args'] ?? array() );
							}
						}
					}
				}
			}

			return $this->success_response(
				"Executed {$ran} due cron event(s)",
				array(
					'events_run' => $ran,
				)
			);
		}
	}
}
