<?php
/**
 * Internal REST API Service
 *
 * Provides a unified interface for internal REST API operations across all resource types.
 * This service allows plugin code to use REST API controllers internally without HTTP overhead,
 * while maintaining all benefits: permission checks, data formatting, validation, caching, etc.
 *
 * **Key Features:**
 * - Direct controller method calls (no HTTP overhead)
 * - Supports all resource types: Appointments, Products, Staff, Slots, Availabilities, Index
 * - Automatic pagination for collection requests
 * - Comprehensive error handling and validation
 * - Thread-safe (request cloning prevents race conditions)
 * - Performance optimizations (caching, efficient operations)
 * - Works even if REST API HTTP endpoints are disabled
 *
 * **Usage:**
 * ```php
 * // Get appointments
 * $appointments = WC_Appointments_REST_API_Internal::appointments()->get( [ 'date_from' => '@1234567890' ] );
 *
 * // Get single appointment
 * $appointment = WC_Appointments_REST_API_Internal::appointments()->get_by_id( 123 );
 *
 * // Create appointment
 * $new = WC_Appointments_REST_API_Internal::appointments()->create( [ 'product_id' => 456 ] );
 *
 * // Get slots
 * $slots = WC_Appointments_REST_API_Internal::slots()->get( [ 'product_id' => 123 ] );
 * ```
 *
 * @package WooCommerce\Appointments\Rest\Internal
 * @since 5.0.0
 */

/**
 * Internal REST API Service Class
 *
 * @since 5.0.0
 */
class WC_Appointments_REST_API_Internal {

	/**
	 * API version to use by default.
	 *
	 * @var string
	 */
	public const DEFAULT_VERSION = 'v2';

	/**
	 * Static cache for controller instances (per-request).
	 *
	 * @var array<string, object>
	 */
	private static array $controller_instances = [];

	/**
	 * Static cache for namespace constants (per-request).
	 *
	 * @var array<string, string>
	 */
	private static array $cached_namespaces = [];

	/**
     * Static cache for REST API availability (per-request).
     */
    private static ?bool $cached_rest_api_available = null;

	/**
	 * Static cache for method existence checks (per-request).
	 *
	 * @var array<string, bool>
	 */
	private static array $method_cache = [];

	/**
	 * Resource type for this instance.
	 *
	 * @var string
	 */
	private string $resource_type;

	/**
	 * API version for this instance.
	 *
	 * @var string
	 */
	private string $version;

	/**
     * Get appointments resource handler.
     *
     * @since 5.0.0
     *
     * @param string $version Optional. API version ('v1' or 'v2'). Default 'v2'.
     */
    public static function appointments( $version = self::DEFAULT_VERSION ): self {
		return new self( 'appointments', $version );
	}

	/**
     * Get slots resource handler.
     *
     * @since 5.0.0
     *
     * @param string $version Optional. API version ('v1' or 'v2'). Default 'v2'.
     */
    public static function slots( $version = self::DEFAULT_VERSION ): self {
		return new self( 'slots', $version );
	}

	/**
     * Get availabilities resource handler.
     *
     * @since 5.0.0
     *
     * @param string $version Optional. API version ('v1' or 'v2'). Default 'v2'.
     */
    public static function availabilities( $version = self::DEFAULT_VERSION ): self {
		return new self( 'availabilities', $version );
	}

	/**
     * Get products resource handler.
     *
     * @since 5.0.0
     *
     * @param string $version Optional. API version ('v1' or 'v2'). Default 'v1' (v2 not available).
     */
    public static function products( $version = 'v1' ): self {
		return new self( 'products', $version );
	}

	/**
     * Get staff resource handler.
     *
     * @since 5.0.0
     *
     * @param string $version Optional. API version ('v1' or 'v2'). Default 'v1' (v2 not available).
     */
    public static function staff( $version = 'v1' ): self {
		return new self( 'staff', $version );
	}

	/**
     * Get index resource handler (v2 only).
     *
     * @since 5.0.0
     */
    public static function index(): self {
		return new self( 'index', 'v2' );
	}

	/**
	 * Get calendar resource handler (v2 only).
	 *
	 * Optimized endpoint for fetching appointment data for calendar display.
	 * Uses direct SQL with JOINs to avoid N+1 queries.
	 *
	 * @since 5.0.0
	 */
	public static function calendar(): self {
		return new self( 'calendar', 'v2' );
	}

	/**
	 * Constructor.
	 *
	 * @since 5.0.0
	 *
	 * @param string $resource_type Resource type: 'appointments', 'slots', 'availabilities', 'products', 'staff', or 'index'.
	 * @param string $version API version: 'v1' or 'v2'.
	 */
	private function __construct( $resource_type, $version = self::DEFAULT_VERSION ) {
		$this->resource_type = $resource_type;
		$this->version       = $version;
	}

	/**
	 * Get collection of items.
	 *
	 * @since 5.0.0
	 *
	 * @param array $params Optional. Query parameters.
	 * @param array $options Optional. Additional options:
	 *                      - 'auto_paginate' (bool): Automatically fetch all pages. Default true.
	 *                      - 'per_page' (int): Items per page. Default 100.
	 *                      - 'max_pages' (int): Maximum pages to fetch. Default 20.
	 *                      - 'skip_permission_check' (bool): Skip permission checks. Default false.
	 * @return array|WP_Error Array of items on success, WP_Error on failure.
	 */
	public function get( $params = [], array $options = [] ) {
		return $this->call( 'GET', '', $params, $options );
	}

	/**
	 * Get single item by ID.
	 *
	 * @since 5.0.0
	 *
	 * @param int   $id Resource ID.
	 * @param array $params Optional. Additional query parameters.
	 * @param array $options Optional. Additional options:
	 *                      - 'skip_permission_check' (bool): Skip permission checks. Default false.
	 * @return array|WP_Error Item data on success, WP_Error on failure.
	 */
	public function get_by_id( $id, $params = [], array $options = [] ) {
		$options['id'] = absint( $id );
		return $this->call( 'GET', '', $params, $options );
	}

	/**
	 * Create new item.
	 *
	 * @since 5.0.0
	 *
	 * @param array $data Item data.
	 * @param array $options Optional. Additional options:
	 *                      - 'skip_permission_check' (bool): Skip permission checks. Default false.
	 * @return array|WP_Error Created item data on success, WP_Error on failure.
	 */
	public function create( $data, array $options = [] ) {
		return $this->call( 'POST', '', $data, $options );
	}

	/**
	 * Update existing item.
	 *
	 * @since 5.0.0
	 *
	 * @param int   $id Resource ID.
	 * @param array $data Item data to update.
	 * @param array $options Optional. Additional options:
	 *                      - 'skip_permission_check' (bool): Skip permission checks. Default false.
	 * @return array|WP_Error Updated item data on success, WP_Error on failure.
	 */
	public function update( $id, $data, array $options = [] ) {
		$options['id'] = absint( $id );
		return $this->call( 'PATCH', '', $data, $options );
	}

	/**
	 * Delete item.
	 *
	 * @since 5.0.0
	 *
	 * @param int   $id Resource ID.
	 * @param array $params Optional. Additional parameters (e.g., 'force' => true).
	 * @param array $options Optional. Additional options:
	 *                      - 'skip_permission_check' (bool): Skip permission checks. Default false.
	 * @return array|WP_Error Deleted item data on success, WP_Error on failure.
	 */
	public function delete( $id, $params = [], array $options = [] ) {
		$options['id'] = absint( $id );
		return $this->call( 'DELETE', '', $params, $options );
	}

	/**
	 * Make a generic REST API call.
	 *
	 * @since 5.0.0
	 *
	 * @param string $method HTTP method: 'GET', 'POST', 'PATCH', 'PUT', or 'DELETE'.
	 * @param string $endpoint Optional. Endpoint path (usually empty for standard operations).
	 * @param array  $params Request parameters.
	 * @param array  $options Optional. Additional options.
	 * @return array|WP_Error Response data on success, WP_Error on failure.
	 */
	public function call( $method, $endpoint = '', $params = [], array $options = [] ) {
		// Check if REST API code is available
		if ( ! self::is_rest_api_enabled() ) {
			return new WP_Error(
			    'rest_api_unavailable',
			    'WordPress REST API code is not available. Internal REST API calls cannot be made.',
			    [ 'status' => 503 ],
			);
		}

		// Ensure REST API classes are loaded
		$this->ensure_rest_api_loaded();

		// Parse options with defaults
		$version = $options['version'] ?? $this->version;
		$resource_id = absint( $options['id'] ?? 0 );
		$auto_paginate = (bool) ( $options['auto_paginate'] ?? true );
		$per_page = absint( $options['per_page'] ?? 100 );
		$max_pages = absint( $options['max_pages'] ?? 20 );
		$skip_permission_check = (bool) ( $options['skip_permission_check'] ?? false );

		// Validate and normalize endpoint
		if ( empty( $endpoint ) ) {
			$endpoint = $this->resource_type;
		}

		// Normalize endpoint
		$endpoint = ltrim( $endpoint, '/' );
		$endpoint = preg_replace( '#^wc-appointments/v[12]/#', '', $endpoint );

		if ( empty( $endpoint ) ) {
			return new WP_Error(
			    'rest_invalid_endpoint',
			    'Endpoint cannot be empty after normalization.',
			    [ 'status' => 400 ],
			);
		}

		// Get controller instance
		$controller = $this->get_controller_instance($this->resource_type, $version);
		if ( is_wp_error( $controller ) ) {
			return $controller;
		}

		// Build full endpoint path
		$namespace = $this->get_namespace($version);
		if ( is_wp_error( $namespace ) ) {
			return $namespace;
		}

		$full_endpoint = '/' . $namespace . '/' . $endpoint;
		if ( 0 < $resource_id ) {
			$full_endpoint .= '/' . $resource_id;
		}

		// Create REST request
		try {
			$request = new WP_REST_Request( $method, $full_endpoint );
		} catch ( Exception $e ) {
			return new WP_Error(
			    'rest_request_creation_failed',
			    sprintf( 'Failed to create REST request: %s', $e->getMessage() ),
			    [ 'status' => 500 ],
			);
		}

		// Set parameters
		foreach ( $params as $key => $value ) {
			$request->set_param( $key, $value );
		}

		// Set user context for permission checks
		$request->set_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );

		// Handle different HTTP methods
		try {
			switch ( strtoupper( $method ) ) {
				case 'GET':
					return $this->handle_get_request( $controller, $request, $resource_id, $auto_paginate, $per_page, $max_pages, $skip_permission_check );

				case 'POST':
					return $this->handle_post_request( $controller, $request, $skip_permission_check );

				case 'PATCH':
				case 'PUT':
					return $this->handle_update_request( $controller, $request, $resource_id, $skip_permission_check );

				case 'DELETE':
					return $this->handle_delete_request( $controller, $request, $resource_id, $skip_permission_check );

				default:
					return new WP_Error(
					    'rest_invalid_method',
					    sprintf( 'HTTP method %s is not supported.', $method ),
					    [ 'status' => 400 ],
					);
			}
		} catch ( Exception $e ) {
			return new WP_Error(
			    'rest_controller_exception',
			    sprintf( 'Controller method threw an exception: %s', $e->getMessage() ),
			    [
					'status' => 500,
					'exception' => $e->getMessage(),
				],
			);
		}
	}

	/**
	 * Handle GET request.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param object $controller Controller instance.
	 * @param WP_REST_Request $request Request object.
	 * @param int $resource_id Resource ID.
	 * @param bool $auto_paginate Whether to auto-paginate.
	 * @param int $per_page Items per page.
	 * @param int $max_pages Maximum pages.
	 * @param bool $skip_permission_check Whether to skip permission check.
	 * @return array|WP_Error
	 */
	private function handle_get_request( $controller, $request, $resource_id, bool $auto_paginate, $per_page, $max_pages, bool $skip_permission_check ) {
		$controller_class = get_class( $controller );

		// Check permissions
		if ( ! $skip_permission_check ) {
			$permission_method = 0 < $resource_id ? 'get_item_permissions_check' : 'get_items_permissions_check';
			$cache_key = $controller_class . '::' . $permission_method;

			if ( ! isset( self::$method_cache[ $cache_key ] ) || ! class_exists( $controller_class ) ) {
				self::$method_cache[ $cache_key ] = method_exists( $controller, $permission_method );
			}

			if ( ! self::$method_cache[ $cache_key ] ) {
				return new WP_Error(
				    'rest_method_not_found',
				    sprintf( 'Permission check method %s not found on controller.', $permission_method ),
				    [ 'status' => 500 ],
				);
			}

			$permission_check = $controller->$permission_method( $request );
			if ( is_wp_error( $permission_check ) || ! $permission_check ) {
				return new WP_Error(
				    'rest_forbidden',
				    'You do not have permission to access this resource.',
				    [ 'status' => 403 ],
				);
			}
		}

		// Single item request
		if ( 0 < $resource_id ) {
			$cache_key = $controller_class . '::get_item';
			if ( ! isset( self::$method_cache[ $cache_key ] ) || ! class_exists( $controller_class ) ) {
				self::$method_cache[ $cache_key ] = method_exists( $controller, 'get_item' );
			}

			if ( ! self::$method_cache[ $cache_key ] ) {
				return new WP_Error(
				    'rest_method_not_found',
				    'get_item method not found on controller.',
				    [ 'status' => 500 ],
				);
			}

			$response = $controller->get_item( $request );
			if ( is_wp_error( $response ) ) {
				return $response;
			}

			if ( ! is_object( $response ) ) {
				return new WP_Error(
				    'rest_invalid_response',
				    'Controller returned a non-object response.',
				    [ 'status' => 500 ],
				);
			}

			$data = $response->get_data();
			return $data ?? [];
		}

		// Collection request with optional pagination
		$cache_key = $controller_class . '::get_items';
		if ( ! isset( self::$method_cache[ $cache_key ] ) || ! class_exists( $controller_class ) ) {
			self::$method_cache[ $cache_key ] = method_exists( $controller, 'get_items' );
		}

		if ( ! self::$method_cache[ $cache_key ] ) {
			return new WP_Error(
			    'rest_method_not_found',
			    'get_items method not found on controller.',
			    [ 'status' => 500 ],
			);
		}

		if ( $auto_paginate ) {
			return $this->get_paginated_items($controller, $request, $per_page, $max_pages);
		}
        $response = $controller->get_items( $request );
        if ( is_wp_error( $response ) ) {
				return $response;
			}
        if ( ! is_object( $response ) ) {
				return new WP_Error(
				    'rest_invalid_response',
				    'Controller returned a non-object response.',
				    [ 'status' => 500 ],
				);
			}
        $data = $response->get_data();
        return $data ?? [];
	}

	/**
	 * Handle POST request.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param object $controller Controller instance.
	 * @param WP_REST_Request $request Request object.
	 * @param bool $skip_permission_check Whether to skip permission check.
	 * @return array|WP_Error
	 */
	private function handle_post_request( $controller, $request, bool $skip_permission_check ) {
		if ( ! method_exists( $controller, 'create_item' ) ) {
			return new WP_Error(
			    'rest_method_not_found',
			    'create_item method not found on controller.',
			    [ 'status' => 500 ],
			);
		}

		if ( ! $skip_permission_check ) {
			if ( ! method_exists( $controller, 'create_item_permissions_check' ) ) {
				return new WP_Error(
				    'rest_method_not_found',
				    'create_item_permissions_check method not found on controller.',
				    [ 'status' => 500 ],
				);
			}

			$permission_check = $controller->create_item_permissions_check( $request );
			if ( is_wp_error( $permission_check ) || ! $permission_check ) {
				return new WP_Error(
				    'rest_forbidden',
				    'You do not have permission to create this resource.',
				    [ 'status' => 403 ],
				);
			}
		}

		$response = $controller->create_item( $request );
		return $this->extract_response_data( $response );
	}

	/**
	 * Handle UPDATE request (PATCH/PUT).
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param object $controller Controller instance.
	 * @param WP_REST_Request $request Request object.
	 * @param int $resource_id Resource ID.
	 * @param bool $skip_permission_check Whether to skip permission check.
	 * @return array|WP_Error
	 */
	private function handle_update_request( $controller, $request, $resource_id, bool $skip_permission_check ) {
		if ( 0 >= $resource_id ) {
			return new WP_Error(
			    'rest_invalid_request',
			    'Resource ID is required for update operations.',
			    [ 'status' => 400 ],
			);
		}

		if ( ! method_exists( $controller, 'update_item' ) ) {
			return new WP_Error(
			    'rest_method_not_found',
			    'update_item method not found on controller.',
			    [ 'status' => 500 ],
			);
		}

		if ( ! $skip_permission_check ) {
			if ( ! method_exists( $controller, 'update_item_permissions_check' ) ) {
				return new WP_Error(
				    'rest_method_not_found',
				    'update_item_permissions_check method not found on controller.',
				    [ 'status' => 500 ],
				);
			}

			$permission_check = $controller->update_item_permissions_check( $request );
			if ( is_wp_error( $permission_check ) || ! $permission_check ) {
				return new WP_Error(
				    'rest_forbidden',
				    'You do not have permission to update this resource.',
				    [ 'status' => 403 ],
				);
			}
		}

		$response = $controller->update_item( $request );
		return $this->extract_response_data( $response );
	}

	/**
	 * Handle DELETE request.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param object $controller Controller instance.
	 * @param WP_REST_Request $request Request object.
	 * @param int $resource_id Resource ID.
	 * @param bool $skip_permission_check Whether to skip permission check.
	 * @return array|WP_Error
	 */
	private function handle_delete_request( $controller, $request, $resource_id, bool $skip_permission_check ) {
		if ( 0 >= $resource_id ) {
			return new WP_Error(
			    'rest_invalid_request',
			    'Resource ID is required for delete operations.',
			    [ 'status' => 400 ],
			);
		}

		if ( ! method_exists( $controller, 'delete_item' ) ) {
			return new WP_Error(
			    'rest_method_not_found',
			    'delete_item method not found on controller.',
			    [ 'status' => 500 ],
			);
		}

		if ( ! $skip_permission_check ) {
			if ( ! method_exists( $controller, 'delete_item_permissions_check' ) ) {
				return new WP_Error(
				    'rest_method_not_found',
				    'delete_item_permissions_check method not found on controller.',
				    [ 'status' => 500 ],
				);
			}

			$permission_check = $controller->delete_item_permissions_check( $request );
			if ( is_wp_error( $permission_check ) || ! $permission_check ) {
				return new WP_Error(
				    'rest_forbidden',
				    'You do not have permission to delete this resource.',
				    [ 'status' => 403 ],
				);
			}
		}

		$response = $controller->delete_item( $request );
		return $this->extract_response_data( $response );
	}

	/**
	 * Extract data from response object.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param WP_REST_Response|WP_Error|mixed $response Response object.
	 * @return array|WP_Error
	 */
	private function extract_response_data( $response ) {
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		if ( ! is_object( $response ) ) {
			return new WP_Error(
			    'rest_invalid_response',
			    'Controller returned a non-object response.',
			    [ 'status' => 500 ],
			);
		}

		if ( method_exists( $response, 'get_data' ) ) {
			$data = $response->get_data();
			return $data ?? [];
		}

		return $response;
	}

	/**
	 * Get paginated results from a REST API controller.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param object $controller REST API controller instance.
	 * @param WP_REST_Request $request REST request object.
	 * @param int $per_page Items per page.
	 * @param int $max_pages Maximum pages to fetch.
	 * @return array|WP_Error Combined results from all pages.
	 */
	private function get_paginated_items( $controller, $request, $per_page = 100, $max_pages = 20 ) {
		if ( ! is_object( $controller ) ) {
			return new WP_Error(
			    'rest_invalid_controller',
			    'Invalid controller object for pagination.',
			    [ 'status' => 500 ],
			);
		}

		$controller_class = get_class( $controller );

		// Cache method_exists check
		$cache_key = $controller_class . '::get_items';
		if ( ! isset( self::$method_cache[ $cache_key ] ) || ! class_exists( $controller_class ) ) {
			self::$method_cache[ $cache_key ] = method_exists( $controller, 'get_items' );
		}

		if ( ! self::$method_cache[ $cache_key ] ) {
			return new WP_Error(
			    'rest_method_not_found',
			    'get_items method not found on controller for pagination.',
			    [ 'status' => 500 ],
			);
		}

		if ( ! ( $request instanceof WP_REST_Request ) ) {
			return new WP_Error(
			    'rest_invalid_request',
			    'Invalid request object for pagination.',
			    [ 'status' => 500 ],
			);
		}

		$all_items = [];
		$page = 1;

		try {
			do {
				// Clone request object to avoid race conditions
				$page_request = clone $request;

				// Set pagination parameters on the cloned request
				$page_request->set_param( 'page', $page );
				$page_request->set_param( 'per_page', $per_page );

				// Get items for this page using the cloned request
				$response = $controller->get_items( $page_request );
				if ( is_wp_error( $response ) ) {
					return $response;
				}

				if ( ! is_object( $response ) ) {
					return new WP_Error(
					    'rest_invalid_response',
					    'Controller returned a non-object response during pagination.',
					    [ 'status' => 500 ],
					);
				}

				$items = $response->get_data();

				// Handle non-array responses
				if ( ! is_array( $items ) ) {
					if ( 1 === $page ) {
						return [];
					}
					break;
				}

				// Break if no items
				if ( [] === $items ) {
					break;
				}

				// Add items to collection
				foreach ( $items as $item ) {
					$all_items[] = $item;
				}

				// Check if there are more pages
				$items_count = count( $items );
				$headers = $response->get_headers();

				if ( ! is_array( $headers ) ) {
					if ( $items_count < $per_page ) {
						break;
					}
				} else {
					$total_pages = (int) ( $headers['X-WP-TotalPages'] ?? 1 );
					if ( $page >= $total_pages || $items_count < $per_page ) {
						break;
					}
				}

				$page++;

				// Safety limit to prevent infinite loops
				if ( $page > $max_pages ) {
					break;
				}
			} while ( true );
		} catch ( Exception $e ) {
			// If pagination fails partway through, return what we have so far
			if ( function_exists( 'wc_get_logger' ) ) {
				wc_get_logger()->warning(
				    sprintf( 'Pagination error: %s', $e->getMessage() ),
				    [ 'source' => 'woocommerce-appointments' ],
				);
			}

			if ( [] !== $all_items ) {
				return $all_items;
			}

			return new WP_Error(
			    'rest_pagination_exception',
			    sprintf( 'Pagination failed: %s', $e->getMessage() ),
			    [ 'status' => 500 ],
			);
		}

		return $all_items;
	}

	/**
	 * Get a REST API controller instance.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param string $controller_type Type of controller.
	 * @param string $version API version 'v1' or 'v2'.
	 * @return object|WP_Error Controller instance or error.
	 */
	private function get_controller_instance( $controller_type, $version = 'v2' ) {
		$controller_type = strtolower( $controller_type );
		$version = strtolower( $version );

		// Check cache first
		$cache_key = $controller_type . '_' . $version;
		if ( isset( self::$controller_instances[ $cache_key ] ) ) {
			return self::$controller_instances[ $cache_key ];
		}

		// Map controller types to class names
		$v1_controllers = [
			'appointments'   => 'WC_Appointments_REST_Appointments_Controller',
			'slots'          => 'WC_Appointments_REST_Slots_Controller',
			'availabilities' => 'WC_Appointments_REST_Availabilities_Controller',
			'products'       => 'WC_Appointments_REST_Products_Controller',
			'staff'          => 'WC_Appointments_REST_Staff_Controller',
		];

		$v2_controllers = [
			'appointments'   => 'WC_Appointments_REST_V2_Appointments_Controller',
			'slots'          => 'WC_Appointments_REST_V2_Slots_Controller',
			'availabilities' => 'WC_Appointments_REST_V2_Availabilities_Controller',
			'index'          => 'WC_Appointments_REST_V2_Index_Controller',
			'calendar'       => 'WC_Appointments_REST_V2_Calendar_Controller',
		];

		$controllers = 'v2' === $version ? $v2_controllers : $v1_controllers;

		if ( ! isset( $controllers[ $controller_type ] ) ) {
			return new WP_Error(
			    'rest_invalid_controller',
			    sprintf( 'Invalid controller type: %s', $controller_type ),
			    [ 'status' => 400 ],
			);
		}

		$class_name = $controllers[ $controller_type ];

		if ( ! class_exists( $class_name ) ) {
			return new WP_Error(
			    'rest_controller_not_found',
			    sprintf( 'REST controller class %s not found.', $class_name ),
			    [ 'status' => 500 ],
			);
		}

		// Try to instantiate controller
		try {
			$controller = new $class_name();
			if ( ! is_object( $controller ) ) {
				return new WP_Error(
				    'rest_controller_instantiation_failed',
				    sprintf( 'Failed to instantiate REST controller class %s.', $class_name ),
				    [ 'status' => 500 ],
				);
			}

			// Cache instance
			self::$controller_instances[ $cache_key ] = $controller;
			return $controller;
		} catch ( Exception $e ) {
			return new WP_Error(
			    'rest_controller_exception',
			    sprintf( 'Exception while instantiating controller %s: %s', $class_name, $e->getMessage() ),
			    [
					'status' => 500,
					'exception' => $e->getMessage(),
				],
			);
		}
	}

	/**
	 * Get REST API namespace for version.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @param string $version API version 'v1' or 'v2'.
	 * @return string|WP_Error Namespace or error.
	 */
	private function get_namespace( string $version ) {
		$namespace_key = $version . '_namespace';

		// Check cache
		if ( isset( self::$cached_namespaces[ $namespace_key ] ) && class_exists( 'WC_Appointments_REST_API' ) ) {
			return self::$cached_namespaces[ $namespace_key ];
		}

		// Validate class exists
		if ( ! class_exists( 'WC_Appointments_REST_API' ) ) {
			return new WP_Error(
			    'rest_api_class_not_found',
			    'WC_Appointments_REST_API class not found.',
			    [ 'status' => 500 ],
			);
		}

		// Get namespace constant
		try {
			$namespace = 'v2' === $version
				? WC_Appointments_REST_API::V2_NAMESPACE
				: WC_Appointments_REST_API::V1_NAMESPACE;
		} catch ( Exception $e ) {
			return new WP_Error(
			    'rest_namespace_error',
			    sprintf( 'Error accessing REST API namespace: %s', $e->getMessage() ),
			    [ 'status' => 500 ],
			);
		}

		if ( '0' === $namespace ) {
			return new WP_Error(
			    'rest_namespace_empty',
			    'REST API namespace is empty.',
			    [ 'status' => 500 ],
			);
		}

		// Cache namespace
		self::$cached_namespaces[ $namespace_key ] = $namespace;
		return $namespace;
	}

	/**
	 * Check if WordPress REST API code is available.
	 *
	 * @since 5.0.0
	 * @access private
	 *
	 * @return bool True if available, false otherwise.
	 */
	private static function is_rest_api_enabled() {
		// Check cache
		if ( null !== self::$cached_rest_api_available ) {
			// Validate cache
			if ( self::$cached_rest_api_available && ( ! function_exists( 'rest_url' ) || ! function_exists( 'rest_ensure_response' ) ) ) {
				self::$cached_rest_api_available = false;
				return false;
			}
			return self::$cached_rest_api_available;
		}

		// Check if core REST API functions exist
		if ( ! function_exists( 'rest_url' ) || ! function_exists( 'rest_ensure_response' ) ) {
			self::$cached_rest_api_available = false;
			return false;
		}

		self::$cached_rest_api_available = true;
		return true;
	}

	/**
     * Ensure REST API classes are loaded.
     *
     * @since 5.0.0
     * @access private
     */
    private function ensure_rest_api_loaded(): void {
		// Check if REST API class exists
		if ( class_exists( 'WC_Appointments_REST_API' ) ) {
			$rest_api = new WC_Appointments_REST_API();
			$rest_api->rest_api_includes();
			return;
		}

		// Fallback: manually include necessary files
		$plugin_path = WC_APPOINTMENTS_ABSPATH;

		// Include base classes first
		if ( ! class_exists( 'WC_Appointments_REST_CRUD_Controller' ) ) {
			include_once $plugin_path . 'includes/api/trait-wc-appointments-rest-permission-check.php';
			include_once $plugin_path . 'includes/api/class-wc-appointments-rest-crud-controller.php';
		}

		// Include V1 controllers
		if ( ! class_exists( 'WC_Appointments_REST_Appointments_Controller' ) ) {
			include_once $plugin_path . 'includes/api/class-wc-appointments-rest-appointments-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_Slots_Controller' ) ) {
			include_once $plugin_path . 'includes/api/class-wc-appointments-rest-slots-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_Availabilities_Controller' ) ) {
			include_once $plugin_path . 'includes/api/class-wc-appointments-rest-availabilities-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_Products_Controller' ) ) {
			include_once $plugin_path . 'includes/api/class-wc-appointments-rest-products-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_Staff_Controller' ) ) {
			include_once $plugin_path . 'includes/api/class-wc-appointments-rest-staff-controller.php';
		}

		// Include V2 controllers
		if ( ! class_exists( 'WC_Appointments_REST_V2_Appointments_Controller' ) ) {
			include_once $plugin_path . 'includes/api/v2/class-wc-appointments-rest-v2-appointments-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_V2_Slots_Controller' ) ) {
			include_once $plugin_path . 'includes/api/v2/class-wc-appointments-rest-v2-slots-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_V2_Availabilities_Controller' ) ) {
			include_once $plugin_path . 'includes/api/v2/class-wc-appointments-rest-v2-availabilities-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_V2_Index_Controller' ) ) {
			include_once $plugin_path . 'includes/api/v2/class-wc-appointments-rest-v2-index-controller.php';
		}
		if ( ! class_exists( 'WC_Appointments_REST_V2_Calendar_Controller' ) ) {
			include_once $plugin_path . 'includes/api/v2/class-wc-appointments-rest-v2-calendar-controller.php';
		}
	}

	/**
	 * Get controller instance for this resource.
	 *
	 * @since 5.0.0
	 *
	 * @return object|WP_Error Controller instance or error.
	 */
	public function get_controller() {
		return $this->get_controller_instance($this->resource_type, $this->version);
	}

	/**
	 * Check if REST API code is available.
	 *
	 * @since 5.0.0
	 *
	 * @return bool True if available, false otherwise.
	 */
	public static function is_available() {
		return self::is_rest_api_enabled();
	}

	/**
     * Clear internal caches (useful for testing/debugging).
     *
     * @since 5.0.0
     */
    public static function clear_cache(): void {
		self::$controller_instances      = [];
		self::$cached_namespaces         = [];
		self::$cached_rest_api_available = null;
		self::$method_cache              = [];
	}
}
