<?php
/**
 * WC_Bookings_CLI_Runner class file.
 *
 * @package WooCommerce\Bookings\CLI
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * WC Bookings API to WP-CLI Bridge.
 *
 * Hooks into the REST API, figures out which endpoints come from WC Bookings,
 * and registers them as CLI commands.
 *
 * Based on WooCommerce's WC_CLI_Runner which is forked from wp-cli/restful
 * (by Daniel Bachhuber, released under the MIT license https://opensource.org/licenses/MIT).
 *
 * @since 3.8.0
 *
 * @package WooCommerce\Bookings
 */
class WC_Bookings_CLI_Runner {

	/**
	 * Schema titles of the endpoints to expose as CLI commands.
	 *
	 * @var array
	 */
	private static $enabled_endpoints = array(
		'wc_booking',
	);

	/**
	 * The REST API namespace to target for generating commands.
	 *
	 * @var string
	 */
	private static $target_namespace = 'wc-bookings/v2';

	/**
	 * Map schema titles to friendlier CLI command names.
	 *
	 * @var array
	 */
	private static $title_map = array(
		'wc_booking' => 'booking',
	);

	/**
	 * Registers all Bookings REST endpoints as CLI commands once WP has loaded.
	 */
	public static function after_wp_load() {
		global $wp_rest_server;
		$wp_rest_server = new WP_REST_Server();
		/**
		 * This action is documented in wp-includes/rest-api.php
		 *
		 * @since 2.0.0
		 */
		do_action( 'rest_api_init', $wp_rest_server );

		$request = new WP_REST_Request( 'GET', '/' );
		$request->set_param( 'context', 'help' );
		$response      = $wp_rest_server->dispatch( $request );
		$response_data = $response->get_data();
		if ( empty( $response_data ) ) {
			return;
		}

		$namespace_prefix = '/' . self::$target_namespace;

		foreach ( $response_data['routes'] as $route => $route_data ) {
			// Only register endpoints under our target namespace.
			if ( substr( $route, 0, strlen( $namespace_prefix ) ) !== $namespace_prefix ) {
				continue;
			}

			// Only register endpoints with schemas.
			if ( empty( $route_data['schema']['title'] ) ) {
				WP_CLI::debug( sprintf( 'No schema title found for %s, skipping REST command registration.', $route ), 'wc-bookings' );
				continue;
			}

			// Only register allow-listed endpoints. This also skips batch endpoints.
			if ( ! in_array( $route_data['schema']['title'], self::$enabled_endpoints, true ) ) {
				continue;
			}

			self::register_route_commands( new WC_Bookings_CLI_REST_Command( $route_data['schema']['title'], $route, $route_data['schema'] ), $route, $route_data );
		}
	}

	/**
	 * Generates command information and tells WP-CLI about all
	 * commands available from a route.
	 *
	 * @param WC_Bookings_CLI_REST_Command $rest_command Bookings CLI command.
	 * @param string                       $route        Path to route endpoint.
	 * @param array                        $route_data   Command data.
	 * @param array                        $command_args WP-CLI command arguments.
	 */
	private static function register_route_commands( $rest_command, $route, $route_data, $command_args = array() ) {
		$supported_ids = array(
			'booking_id'  => __( 'Booking ID.', 'woocommerce-bookings' ),
			'resource_id' => __( 'Resource ID.', 'woocommerce-bookings' ),
			'id'          => __( 'The ID for the resource.', 'woocommerce-bookings' ),
		);
		$rest_command->set_supported_ids( $supported_ids );
		$positional_args = array_keys( $supported_ids );

		// Map schema title to a friendlier CLI name.
		$title = $route_data['schema']['title'];
		if ( isset( self::$title_map[ $title ] ) ) {
			$title = self::$title_map[ $title ];
		}

		$parent             = "wc {$title}";
		$supported_commands = array();

		// Get a list of supported commands for each route.
		foreach ( $route_data['endpoints'] as $endpoint ) {
			preg_match_all( '#\([^\)]+\)#', $route, $matches );
			$resource_id   = ! empty( $matches[0] ) ? array_pop( $matches[0] ) : null;
			$trimmed_route = rtrim( $route );
			$is_singular   = substr( $trimmed_route, - strlen( $resource_id ?? '' ) ) === $resource_id;

			// List a collection.
			if ( array( 'GET' ) === $endpoint['methods'] && ! $is_singular ) {
				$supported_commands['list'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
			}
			// Create a specific resource.
			if ( array( 'POST' ) === $endpoint['methods'] && ! $is_singular ) {
				$supported_commands['create'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
			}
			// Get a specific resource.
			if ( array( 'GET' ) === $endpoint['methods'] && $is_singular ) {
				$supported_commands['get'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
			}
			// Update a specific resource.
			if ( in_array( 'POST', $endpoint['methods'], true ) && $is_singular ) {
				$supported_commands['update'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
			}
			// Delete a specific resource.
			if ( array( 'DELETE' ) === $endpoint['methods'] && $is_singular ) {
				$supported_commands['delete'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
			}
		}

		foreach ( $supported_commands as $command => $endpoint_args ) {
			$synopsis = array();
			$arg_regs = array();
			$ids      = array();

			foreach ( $supported_ids as $id_name => $id_desc ) {
				if ( strpos( $route, '<' . $id_name . '>' ) !== false ) {
					$synopsis[] = array(
						'name'        => $id_name,
						'type'        => 'positional',
						'description' => $id_desc,
						'optional'    => false,
					);
					$ids[]      = $id_name;
				}
			}

			foreach ( $endpoint_args as $name => $args ) {
				if ( ! in_array( $name, $positional_args, true ) || strpos( $route, '<' . $name . '>' ) === false ) {
					$arg_regs[] = array(
						'name'        => $name,
						'type'        => 'assoc',
						'description' => ! empty( $args['description'] ) ? $args['description'] : '',
						'optional'    => empty( $args['required'] ),
					);
				}
			}

			foreach ( $arg_regs as $arg_reg ) {
				$synopsis[] = $arg_reg;
			}

			if ( in_array( $command, array( 'list', 'get' ), true ) ) {
				$synopsis[] = array(
					'name'        => 'fields',
					'type'        => 'assoc',
					'description' => __( 'Limit response to specific fields. Defaults to all fields.', 'woocommerce-bookings' ),
					'optional'    => true,
				);
				$synopsis[] = array(
					'name'        => 'field',
					'type'        => 'assoc',
					'description' => __( 'Get the value of an individual field.', 'woocommerce-bookings' ),
					'optional'    => true,
				);
				$synopsis[] = array(
					'name'        => 'format',
					'type'        => 'assoc',
					'description' => __( 'Render response in a particular format.', 'woocommerce-bookings' ),
					'optional'    => true,
					'default'     => 'table',
					'options'     => array(
						'table',
						'json',
						'csv',
						'ids',
						'yaml',
						'count',
						'headers',
						'body',
						'envelope',
					),
				);
			}

			if ( in_array( $command, array( 'create', 'update', 'delete' ), true ) ) {
				$synopsis[] = array(
					'name'        => 'porcelain',
					'type'        => 'flag',
					'description' => __( 'Output just the id when the operation is successful.', 'woocommerce-bookings' ),
					'optional'    => true,
				);
			}

			$methods = array(
				'list'   => 'list_items',
				'create' => 'create_item',
				'delete' => 'delete_item',
				'get'    => 'get_item',
				'update' => 'update_item',
			);

			$before_invoke = null;
			if ( empty( $command_args['when'] ) && \WP_CLI::get_config( 'debug' ) ) {
				$before_invoke = function () {
					if ( ! defined( 'SAVEQUERIES' ) ) {
						define( 'SAVEQUERIES', true );
					}
				};
			}

			WP_CLI::add_command(
				"{$parent} {$command}",
				array( $rest_command, $methods[ $command ] ),
				array(
					'synopsis'      => $synopsis,
					'when'          => ! empty( $command_args['when'] ) ? $command_args['when'] : '',
					'before_invoke' => $before_invoke,
				)
			);
		}
	}
}
