<?php
/**
 * Handles the update of the given appointment details, triggering the relevant booking services
 * to update associated data based on the changes.
 *
 * @param array $data The data payload with updated appointment information.
 * @param object $item The appointment object being processed for updates, containing its ID and other details.
 * @param WP_REST_Request $request The REST API request instance associated with the update operation.
 * @param object $controller The controller or handler managing this update operation.
 *
 * @return void
 */

namespace RadiusTheme\RadiusBooking\Controllers;

use Exception;
use RadiusTheme\RadiusBooking\Abstracts\BaseController;
use RadiusTheme\RadiusBooking\Core\Api\ApiResponse;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\AuthMiddleware;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\PermissionMiddleware;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\RateLimitMiddleware;
use RadiusTheme\RadiusBooking\Core\Api\Validation\Rules\IntegerRule;
use RadiusTheme\RadiusBooking\Core\Api\Validation\Rules\RequiredRule;
use RadiusTheme\RadiusBooking\Core\Container\Container;
use RadiusTheme\RadiusBooking\Helpers\SettingsHelper;
use RadiusTheme\RadiusBooking\Models\Appointment;
use RadiusTheme\RadiusBooking\Models\CustomerBooking;
use RadiusTheme\RadiusBooking\Models\Payment;
use RadiusTheme\RadiusBooking\Models\Service;
use RadiusTheme\RadiusBooking\Models\User;
use RadiusTheme\RadiusBooking\Models\UserProfile;
use RadiusTheme\RadiusBooking\Models\EmployeeService;

use RadiusTheme\RadiusBooking\Repositories\AppointmentRepository;

use RadiusTheme\RadiusBooking\Resources\AppointmentResource;
use RadiusTheme\RadiusBooking\Services\AppointmentService;
use RadiusTheme\RadiusBooking\Services\EmployeeServices\UserWeekdayDataService;
use WP_REST_Request;
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

/**
 * Handles the management of appointment-related operations.
 *
 * Extends the functionality of the BaseController for handling appointment-specific features,
 * such as validation, REST API endpoints, and appointment transformations.
 */
class AppointmentController extends BaseController {

	/**
	 * AppointmentController constructor.
	 *
	 * Initializes the controller with the AppointmentRepository.
	 *
	 * Middleware options (currently commented out):
	 * - AuthMiddleware: Enforces authentication for accessing appointment routes.
	 * - RateLimitMiddleware: Limits requests to 60 per hour.
	 */
	public function __construct() {
		parent::__construct( Container::resolve( AppointmentRepository::class ) );

		// Add middleware
		 $this->middleware[] = new PermissionMiddleware( 'manage_options' );

		add_action( 'radius_booking_item_created_appointment', array( $this, 'handle_appointment_create' ), 10, 4 );
		add_action(
			'radius_booking_item_updated_appointment_with_data',
			array(
				$this,
				'handle_appointment_update',
			),
			10,
			4
		);
		add_filter( 'radius_booking_index_query_condition_appointment', array( $this, 'setCondition' ), 10, 3 );
		add_filter( 'radius_booking_index_order_by_appointment', array( $this, 'setOrderBy' ), 10, 3 );
		add_filter(
			'radius_booking_store_validation_rules_appointment',
			array(
				$this,
				'createWithValidation',
			),
			10,
			3
		);
	}

	/**
	 * Prepare data for storage, resolving the default appointment status
	 * from the service model when not explicitly provided.
	 *
	 * Priority: per-service (Pro) > global statuses setting > 'confirmed'.
	 *
	 * @param WP_REST_Request $request The current request.
	 * @param string          $context The operation context ('create' or 'update').
	 *
	 * @return array
	 */
	protected function prepareDataForStorage( WP_REST_Request $request, string $context ): array {
		$data = parent::prepareDataForStorage( $request, $context );

		if ( 'create' === $context && ! empty( $data['service_id'] ) ) {
			if ( empty( $data['status'] ) || 'global' === $data['status'] ) {
				$service        = Service::find( $data['service_id'] );
				$data['status'] = $service ? $service->get_appointment_status() : 'confirmed';
			}

			// Validate and apply coupon if provided (Pro feature).
			if ( ! empty( $data['coupon_code'] ) ) {
				// Reconstruct the pre-discount, pre-tax subtotal the frontend
				// applied the coupon against. $data['total'] is post-discount and,
				// for exclusive tax mode, also includes the tax amount — so we add
				// the discount back and subtract the tax to recover the original
				// service subtotal. Without this, a 12% coupon on a 270 service
				// with 10% exclusive tax would be recalculated against 293.76
				// instead of 270, deducting too much.
				$tax_settings  = SettingsHelper::get_setting( 'tax' );
				$tax_inclusive = is_array( $tax_settings ) && ! empty( $tax_settings['inclusive'] );
				$tax_amount    = (float) ( $data['tax_amount'] ?? 0 );

				$pre_discount_amount = (float) ( $data['total'] ?? 0 )
					+ (float) ( $data['discount_amount'] ?? 0 )
					- ( $tax_inclusive ? 0 : $tax_amount );

				$coupon_result = apply_filters(
					'radius_booking_apply_coupon',
					null,
					$data['coupon_code'],
					$data['service_id'],
					$pre_discount_amount
				);

				if ( is_array( $coupon_result ) && ! empty( $coupon_result['valid'] ) ) {
					$data['coupon_id']       = $coupon_result['coupon_id'];
					$data['coupon_code']     = $coupon_result['code'];
					$data['discount_amount'] = $coupon_result['discount'];
				} else {
					// Invalid coupon or Pro not active - clear coupon data but don't block booking.
					unset( $data['coupon_id'], $data['coupon_code'], $data['discount_amount'] );
				}
			}
		}

		return $data;
	}

	/**
	 * Transform a single appointment item using the AppointmentResource.
	 *
	 * @param mixed $item The appointment item to transform.
	 *
	 * @return array The transformed appointment data.
	 */
	protected function transformItem( $item ): array {
		return ( new AppointmentResource() )->transform( $item );
	}

	/**
	 * Transform a collection of appointment items using the AppointmentResource.
	 *
	 * @param array $items The list of appointment items to transform.
	 *
	 * @return array The transformed collection of appointments.
	 */
	protected function transformCollection( array $items ): array {
		return AppointmentResource::collection( $items );
	}

	/**
	 * Get the validation rules for creating or updating an appointment.
	 *
	 * For 'create' context:
	 * - `user_id`, `service_id`, `appointment_date`, `start_time`, and `status` are required.
	 *
	 * For 'update' context:
	 * - `user_id` and `service_id` are omitted (assumed not updatable).
	 *
	 * @param string $request The validation context. Either 'create' or 'update'.
	 * @param string $context The validation context. Either 'create' or 'update'.
	 *
	 * @return array The array of validation rules for the given context.
	 */
	protected function getValidationRules( $request, string $context = 'create' ): array {
		$rules = array(
			'employee_id'      => array( new RequiredRule(), new IntegerRule() ),
			'service_id'       => array( new RequiredRule(), new IntegerRule() ),
			'appointment_date' => array( new RequiredRule() ),
			'start_time'       => array( new RequiredRule() ),
		);

		if ( 'update' === $context ) {
			// Make some fields optional for updates
			unset( $rules['user_id'], $rules['service_id'] );
		}

		return $rules;
	}

	/**
	 * Validates and creates data for an appointment request, ensuring service availability and
	 * handling errors if the service is not available.
	 *
	 * @param mixed $validationData The initial validation data, which may be modified based on the validation outcome.
	 * @param WP_REST_Request $request The REST request instance containing appointment details such as
	 *                                 'appointment_date', 'start_time', and 'end_time'.
	 * @param mixed $controller The controller handling the request, which may be used for additional operations.
	 *
	 * @return mixed The modified validation data, or an error response if the service is unavailable.
	 */
	public function createWithValidation( $validationData, $request, $controller ) {
		$data       = $request->get_params();
		if( empty( $data['service_id'] )){
			return $validationData;
		}
		$appService = Container::resolve( AppointmentService::class );
		$availability = $appService->isServiceAvailable( $data );

		if ( ! $availability['available'] ) {
			$appointmentDate = $data['appointment_date'] ?? 'N/A';
			$startTime       = $data['start_time'] ?? '';
			$endTime         = $data['end_time'] ?? '';

			// Use the specific error message from isServiceAvailable
			$formattedDate = 'N/A';
			$tsDate        = strtotime( $appointmentDate );
			if ( $tsDate ) {
				$formattedDate = gmdate( 'F j, Y', $tsDate );
			}

			$formattedStart = '';
			$tsStart        = strtotime( $startTime );
			if ( $tsStart ) {
				$formattedStart = gmdate( 'g:i A', $tsStart );
			}

			$formattedEnd = '';
			$tsEnd        = strtotime( $endTime );
			if ( $tsEnd ) {
				$formattedEnd = gmdate( 'g:i A', $tsEnd );
			}

			$message = sprintf(
				'%s (Date: %s, Time: %s - %s)',
				$availability['message'],
				$formattedDate,
				$formattedStart,
				$formattedEnd
			);

			$errors = array(
				'message' => $message,
				'reason'  => $availability['reason'],
			);

			$validationData = ApiResponse::error( 'Appointment failed', 422, $errors )->send();
		}

		return $validationData;
	}

	/**
	 * Handles the appointment creation logic by processing appointment bookings.
	 *
	 * @param array $data The data associated with the appointment that needs to be processed.
	 * @param mixed $item The Appointment object or item, which contains the details of the appointment.
	 * @param WP_REST_Request $request The REST request instance used during the creation process.
	 * @param WP_REST_Controller $controller The REST controller handling the request.
	 *
	 * @return void This method does not return any value.
	 */
	public function handle_appointment_create( $data, $item, $request, $controller ) {
		$appointment_id = $item->get_id();
		if ( ! $appointment_id ) {
			return;
		}
		$appointmentService = Container::resolve( AppointmentService::class );
		$appointmentService->processAppointmentBookings( $data, $appointment_id );
	}

	/**
	 * Handles the update of an appointment by processing its booking information.
	 *
	 * @param array $data The data payload containing updated appointment details.
	 * @param object $item The appointment item being updated, typically an object with an ID.
	 * @param WP_REST_Request $request The REST request instance associated with the update operation.
	 * @param object $controller The controller managing the request handling.
	 *
	 * @return void
	 */
	public function handle_appointment_update( $data, $item, $request, $controller ) {
		$appointment_id = $item->get_id();
		if ( ! $appointment_id ) {
			return;
		}
		$appointmentService = Container::resolve( AppointmentService::class );
		$appointmentService->processAppointmentBookings( $data, $appointment_id );
	}

	/**
	 * GET /appointments
	 *
	 * @param WP_REST_Request $request the current REST request.
	 *
	 * @return mixed
	 */
	public function upcoming( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$limit        = $request->get_param( 'limit' ) ?: 10; //phpcs:ignore
				$service      = Container::resolve( AppointmentService::class );
				$appointments = $service->getUpcomingAppointments( $limit );

				return ApiResponse::success( $this->transformCollection( $appointments ) )->send();
			}
		);
	}

	/**
	 * Retrieve all appointments for a specific user via REST API.
	 *
	 * @param WP_REST_Request $request The REST request containing:
	 *                                 - `user_id` (int): The ID of the user whose appointments should be retrieved.
	 *
	 * @return WP_REST_Response Response containing a collection of appointments for the user.
	 */
	public function userAppointments( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$userId       = $request->get_param( 'user_id' );
				$service      = Container::resolve( AppointmentService::class );
				$appointments = $service->getUserAppointments( $userId );

				return ApiResponse::success( $this->transformCollection( $appointments ) )->send();
			}
		);
	}

	/**
	 * Update the status of an appointment via REST API.
	 *
	 * Supported statuses:
	 * - `confirmed`: Marks the appointment as confirmed.
	 * - `completed`: Marks the appointment as completed.
	 * - `cancelled`: Cancels the appointment and optionally takes a reason.
	 *
	 * @param WP_REST_Request $request The REST request containing:
	 *                                 - `id` (int): ID of the appointment.
	 *                                 - `status` (string): New status to apply ('confirmed', 'completed', 'cancelled').
	 *                                 - `reason` (string, optional): Reason for cancellation if status is 'cancelled'.
	 *
	 * @return WP_REST_Response Response with success or error message.
	 */
	public function updateStatus( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$id     = $request->get_param( 'id' );
				$status = $request->get_param( 'status' );

				$service = Container::resolve( AppointmentService::class );

				try {

					$result = $service->executeStatusChange( $service, $id, $status, $request );

					if ( is_wp_error( $result ) ) {
						return ApiResponse::error( $result->get_error_message() )->send();
					}

					if ( ! $result ) {
						return ApiResponse::error( 'Failed to update appointment status' )->send();
					}

					$appointment = $this->repository->find( $id );
					$service->fireStatusAction( $appointment );
					return ApiResponse::success( $this->transformItem( $appointment ) )->send();

				} catch ( Exception $e ) {
					return ApiResponse::error( $e->getMessage() )->send();
				}
			}
		);
	}


	/**
	 * Bulk delete appointments by IDs.
	 *
	 * @param WP_REST_Request $request The REST request containing 'ids' parameter.
	 *
	 * @return mixed API response with deleted count or error.
	 */
	public function bulkDelete( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$ids = $request->get_param( 'ids' );
				if ( empty( $ids ) || ! is_array( $ids ) || ! array_filter( $ids, 'is_numeric' ) ) {
					return ApiResponse::notFound( 'Invalid IDs' )->send();
				}

				// Find related customer bookings.
				$bookings   = CustomerBooking::whereIn( 'appointment_id', $ids )->get();
				$bookingIds = array_map( fn( $booking ) => $booking->id, $bookings );

				if ( ! empty( $bookingIds ) ) {
					// Delete related payments first.
					$payments   = Payment::whereIn( 'booking_id', $bookingIds )->get();
					$paymentIds = array_map( fn( $payment ) => $payment->id, $payments );

					if ( ! empty( $paymentIds ) ) {
						Payment::deleteMany( $paymentIds );
					}

					// Delete related customer bookings.
					CustomerBooking::deleteMany( $bookingIds );
				}

				// Soft delete appointments.
				$deletedCount = Appointment::deleteMany( $ids );

				return ApiResponse::success( array( 'deleted_count' => $deletedCount ) )->send();
			}
		);
	}

	/**
	 * Retrieves the resource type for this controller.
	 *
	 * @return string The resource type, which is 'user' in this case.
	 */
	protected function getResourceType(): string {
		return 'appointment';
	}

	/**
	 * Processes the provided condition string and parses it into a structured array format
	 * based on predefined operators.
	 *
	 * @param string $where The condition string, typically URL-decoded, which contains
	 *                           column-value pairs separated by allowed operators (e.g., '=', '<', '>').
	 * @param mixed $request The request instance or object used, if applicable, to fetch additional data or context.
	 * @param mixed $controller The controller instance managing the request, if needed for access or validation.
	 *
	 * @return array An array of parsed condition data, where each entry contains a 'column', 'operator',
	 *               and 'value'. Returns an empty array if no valid conditions are provided.
	 */
	public function setCondition( $where, $request, $controller ) {
		if ( empty( $where ) ) {
			return array();
		}

		$where     = urldecode( $where );
		$wheres    = explode( '&', $where );
		$whereData = array();
		foreach ( $wheres as $whereItem ) {
			// Special: attendee_search resolves a name fragment to a set of
			// matching appointment IDs by LIKE-scanning the customer_info JSON
			// in customer_bookings. The appointments table has no name column
			// so the regular search_column path can't search by attendee name.
			if ( 0 === strpos( $whereItem, 'attendee_search=' ) ) {
				$term = trim( substr( $whereItem, strlen( 'attendee_search=' ) ) );
				if ( '' === $term ) {
					continue;
				}
				global $wpdb;
				$table  = $wpdb->prefix . 'radius_booking_customer_bookings';
				$like   = '%' . $wpdb->esc_like( $term ) . '%';
				$ids    = $wpdb->get_col(
					$wpdb->prepare(
						"SELECT DISTINCT appointment_id FROM `{$table}` WHERE customer_info LIKE %s AND appointment_id IS NOT NULL", // phpcs:ignore WordPress.DB
						$like
					)
				);
				// 0 sentinel when nothing matched so the IN clause is valid SQL
				// and the result set is empty (instead of erroring on `IN ()`).
				$idList = empty( $ids ) ? array( 0 ) : array_map( 'intval', $ids );
				// Emit a Closure so QueryBuilder::where() routes through
				// whereNested → whereIn, producing valid `id IN (%s, %s, ...)`
				// SQL. We can't use operator: 'IN' here because the basic
				// where() compiler builds `column IN %s` and binds the array
				// as one placeholder, which $wpdb->prepare casts to ''.
				$whereData[] = array(
					'column'   => function ( $query ) use ( $idList ) {
						$query->whereIn( 'id', $idList );
					},
					'operator' => '=',
					'value'    => null,
				);
				continue;
			}

			foreach ( $this->allowedOperators as $operator ) {
				if ( strpos( $whereItem, $operator ) !== false ) {
					[ $column, $value ] = explode( $operator, $whereItem, 2 );
					if ( 'all' === $value ) {
						continue;
					}
					if ( ! empty( $column ) && isset( $value ) ) {
						$value = trim( $value );

						// If value contains comma → use IN via a Closure so
						// QueryBuilder::where() routes through whereNested →
						// whereIn (same reasoning as the attendee_search path
						// above).
						if ( str_contains( $value, ',' ) ) {
							$values      = array_map( 'trim', explode( ',', $value ) );
							$col         = trim( $column );
							$whereData[] = array(
								'column'   => function ( $query ) use ( $col, $values ) {
									$query->whereIn( $col, $values );
								},
								'operator' => '=',
								'value'    => null,
							);
						} else {
							$whereData[] = array(
								'column'   => trim( $column ),
								'operator' => $operator,
								'value'    => $value,
							);
						}
					}
					break;
				}
			}
		}

		return $whereData;
	}

	/**
	 * Sets the order by parameter for a given request and controller.
	 *
	 * @param mixed $orderBy The value intended to specify the ordering logic. Can be a string, array, or other data type.
	 * @param mixed $request The request object or data that may be used for context or processing.
	 * @param mixed $controller The controller instance or handler managing the request.
	 *
	 * @return mixed Returns the $orderBy parameter if it is not empty, otherwise returns an empty array.
	 */
	public function setOrderBy( $orderBy, $request, $controller ) {
		if ( empty( $orderBy ) ) {
			return array();
		}

		return $orderBy;
	}

	/**
	 * Handles the retrieval of assigned services for a specific service ID,
	 * providing details about related employees and associated locations.
	 *
	 * @param WP_REST_Request $request The REST request instance containing parameters such as
	 *                                  'service_id' (required) to fetch the assigned services.
	 *
	 * @return mixed The response object containing either the assigned services data or an error message.
	 */
	public function getAssignServices( WP_REST_Request $request ) {
		// return $this->applyMiddleware(
		// $request,
		// function ( $request ) {
				$service_id = $request->get_param( 'service_id' );
		if ( ! $service_id ) {
			return ApiResponse::error( 'Service ID is required' )->send();
		}

				$service = Service::find( $service_id );
		if ( ! $service->get_id() ) {
			return ApiResponse::error( 'Service not found' )->send();
		}

				// Get all assign services for this service
				$assignedServices = $service->getAssignService()->get();

		if ( empty( $assignedServices ) ) {
			return ApiResponse::success(
				array(
					'employees' => array(),
					'locations' => array(),
				),
				'No employees assigned'
			)->send();
		}

				// Extract user IDs
				$userIds = array_map(
					fn( $emp ) => $emp->user_id,
					$assignedServices
				);

				// Fetch user profiles
				$employees = UserProfile::whereIn( 'user_id', $userIds )->get();

				// Group assigned services by user_id
				$assignGrouped = array();
		foreach ( $assignedServices as $assign ) {
			$assignGrouped[ $assign->user_id ][] = array(
				'id'             => $assign->id,
				'service_id'     => $assign->service_id,
				'price'          => $assign->price,
				'min_capacity'   => $assign->min_capacity,
				'max_capacity'   => $assign->max_capacity,
				'custom_pricing' => maybe_unserialize( $assign->custom_pricing ?? array() ),
			);
		}

				// Map employees + their multiple assign services
				$mappedEmployees = array_map(
					function ( $emp ) use ( $assignGrouped ) {
						return array(
							'id'          => $emp->id,
							'user_id'     => $emp->user_id,
							'first_name'  => $emp->first_name,
							'last_name'   => $emp->last_name,
							'phone'       => $emp->phone,
							'location_id' => $emp->location_id,
							'utc_offset'  => $emp->time_zone,
							'services'    => $assignGrouped[ $emp->user_id ] ?? array(),
						);
					},
					$employees
				);

				$weekdayService = new UserWeekdayDataService();

				$locationIds = array_unique(
					array_merge(
					// 1️⃣ From employee objects
						array_map(
							fn( $emp ) => $emp->location_id,
							$employees
						),
						// 2️⃣ From weekday service
						...array_map(
							function ( $emp ) use ( $weekdayService ) {
								$weekdays = UserWeekdayDataService::getEffectiveWeekdays( $emp->user_id );

								return $weekdayService->extractLocationIds( $weekdays );
							},
							$employees
						)
					)
				);
				// Get unique locations
				// $locationIds = array_unique( array_map(
				// fn( $emp ) => $emp->location_id,
				// $employees
				// ) );
				$locations = array();
		if ( ! empty( $locationIds ) ) {
			$locations = \RadiusTheme\RadiusBooking\Models\Location::whereIn(
				'id',
				$locationIds
			)->where( 'status', 'active' )->get();
		}

				$result = array(
					'employees' => $mappedEmployees,
					'locations' => $locations,
				);

				return ApiResponse::success( $result, 'Resource fetch successfully' )->send();
				// }
				// );
	}

	/**
	 * Retrieve every bookable employee together with the services each one
	 * provides and all of their available locations.
	 *
	 * This is the reverse of getAssignServices(): instead of "given a service,
	 * who can perform it", it answers "here are all staff and what they do".
	 * Used by the staff-first booking flow where the customer picks a staff
	 * member before choosing a service. The employee object shape mirrors
	 * getAssignServices() so the frontend can reuse the same model.
	 *
	 * @param WP_REST_Request $request The REST request instance.
	 *
	 * @return mixed The API response with employees + locations or an error.
	 */
	public function getEmployeeServices( WP_REST_Request $request ) {
		unset( $request );

		$assignedServices = EmployeeService::all();

		if ( empty( $assignedServices ) ) {
			return ApiResponse::success(
				array(
					'employees' => array(),
					'locations' => array(),
				),
				'No employees assigned'
			)->send();
		}

		// Unique user IDs across every service assignment.
		$userIds = array_values(
			array_unique(
				array_map(
					fn( $emp ) => $emp->user_id,
					$assignedServices
				)
			)
		);

		// Fetch user profiles.
		$employees = UserProfile::whereIn( 'user_id', $userIds )->get();

		// Group assigned services by user_id.
		$assignGrouped = array();
		foreach ( $assignedServices as $assign ) {
			$assignGrouped[ $assign->user_id ][] = array(
				'id'             => $assign->id,
				'service_id'     => $assign->service_id,
				'price'          => $assign->price,
				'min_capacity'   => $assign->min_capacity,
				'max_capacity'   => $assign->max_capacity,
				'custom_pricing' => maybe_unserialize( $assign->custom_pricing ?? array() ),
			);
		}

		// Map employees + their multiple assign services.
		$mappedEmployees = array_map(
			function ( $emp ) use ( $assignGrouped ) {
				return array(
					'id'          => $emp->id,
					'user_id'     => $emp->user_id,
					'first_name'  => $emp->first_name,
					'last_name'   => $emp->last_name,
					'phone'       => $emp->phone,
					'location_id' => $emp->location_id,
					'utc_offset'  => $emp->time_zone,
					'services'    => $assignGrouped[ $emp->user_id ] ?? array(),
				);
			},
			$employees
		);

		$weekdayService = new UserWeekdayDataService();

		$locationIds = array_unique(
			array_merge(
				array_map(
					fn( $emp ) => $emp->location_id,
					$employees
				),
				...array_map(
					function ( $emp ) use ( $weekdayService ) {
						$weekdays = UserWeekdayDataService::getEffectiveWeekdays( $emp->user_id );

						return $weekdayService->extractLocationIds( $weekdays );
					},
					$employees
				)
			)
		);

		$locations = array();
		if ( ! empty( $locationIds ) ) {
			$locations = \RadiusTheme\RadiusBooking\Models\Location::whereIn(
				'id',
				$locationIds
			)->where( 'status', 'active' )->get();
		}

		return ApiResponse::success(
			array(
				'employees' => $mappedEmployees,
				'locations' => $locations,
			),
			'Resource fetch successfully'
		)->send();
	}

	/**
	 * Handles the retrieval of an employee's available date and time for a specific service.
	 *
	 * @param WP_REST_Request $request The REST request instance containing parameters such as
	 *                                  'employee_id' (required) and 'service_id' (optional)
	 *                                  to process the availability.
	 *
	 * @return mixed The response object containing either the available date and time or an error message.
	 */
	public function getEmployeeAvailableDateTime( WP_REST_Request $request ) {
		return $this->applyBypass(
			$request,
			function ( $request ) {
				$employee_id = $request->get_param( 'employee_id' );
				$service_id  = $request->get_param( 'service_id' );
				if ( ! $employee_id ) {
					return ApiResponse::error( 'Staff ID is required' )->send();
				}
				$employee = User::find( $employee_id );
				if ( ! $employee->get_id() && $employee->get_type() !== 'employee' ) {
					return ApiResponse::error( 'Employee not found' )->send();
				}
				$service           = Container::resolve( AppointmentService::class );
				$availableDateTime = $service->getEmployeeAvailableDateTime( $employee_id, $service_id );
				return ApiResponse::success( $availableDateTime, 'Resource fetch successfully' )->send();
			}
		);
	}

	/**
	 * Retrieves the available time slots for a specific employee based on the provided parameters.
	 *
	 * @param WP_REST_Request $request The REST request object containing parameters:
	 *                                 - 'employee_id' (required): The ID of the employee.
	 *                                 - 'service_id' (optional): The ID of the service.
	 *                                 - 'date' (optional): The date to search for availability.
	 *                                 - 'location_id' (optional): The location ID for filtering availability.
	 *
	 * @return mixed The API response with the available time slots or an error message.
	 */
	public function getEmployeeAvailableTimeslot( WP_REST_Request $request ) {
		return $this->applyBypass(
			$request,
			function ( $request ) {
				$employee_id = $request->get_param( 'employee_id' );
				$service_id  = $request->get_param( 'service_id' );
				$date        = $request->get_param( 'date' );
				$location    = $request->get_param( 'location_id' );
				if ( ! $employee_id ) {
					return ApiResponse::error( 'Staff ID is required' )->send();
				}
				$employee = User::find( $employee_id );
				if ( ! $employee->get_id() && $employee->get_type() !== 'employee' ) {
					return ApiResponse::error( 'Staff not found' )->send();
				}
				$service           = Container::resolve( AppointmentService::class );
				$availableDateTime = $service->getEmployeeAvailableTimeSlot( $employee_id, $service_id, $date, $location );

				return ApiResponse::success( $availableDateTime, 'Resource fetch successfully' )->send();
			}
		);
	}


	/**
	 * Handles the updating of a payment for an appointment.
	 *
	 * This method processes the provided request, retrieves the necessary parameters,
	 * and attempts to update the payment details of an appointment using the
	 * AppointmentService. Returns an appropriate API response based on the success or
	 * failure of the operation.
	 *
	 * @param WP_REST_Request $request The HTTP request instance containing payment update details.
	 *        - id (string|int): The unique identifier of the appointment.
	 *        - status (string): The updated payment status.
	 *        - transaction_id (string): The transaction ID associated with the payment.
	 *        - amount (float): The payment amount.
	 *        - appointment (mixed): The appointment details related to the payment.
	 *
	 * @return mixed The API response indicating success or failure.
	 */
	public function updatePayment( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$id             = $request->get_param( 'id' );
				$payment_status = $request->get_param( 'status' );
				$transaction_id = $request->get_param( 'transaction_id' );
				$amount         = $request->get_param( 'amount' );
				$appointment    = $request->get_param( 'appointment' );
				$service        = Container::resolve( AppointmentService::class );

				try {
					$result = $service->updatePayment( $id, $payment_status, $transaction_id, $amount, $appointment );

					if ( $result ) {
						$appointment = $this->repository->find( $id );

						return ApiResponse::success( $this->transformItem( $appointment ) )->send();
					} else {
						return ApiResponse::error( 'Failed to update appointment payment' )->send();
					}
				} catch ( Exception $e ) {
					return ApiResponse::error( $e->getMessage() )->send();
				}
			}
		);
	}

	/**
	 * Displays the calendar data based on the provided filters and parameters.
	 *
	 * @param WP_REST_Request $request The REST API request object containing the parameters
	 *                                  such as 'serviceFilter', 'customerFilter',
	 *                                  'employeeFilter', 'date', and 'view'.
	 *
	 * @return mixed Returns the API response containing the calendar appointments data.
	 */
	public function calenderShow( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {

				$service_id  = $request->get_param( 'serviceFilter' );
				$customer_id = $request->get_param( 'customerFilter' );
				$employee_id = $request->get_param( 'employeeFilter' );
				$date        = $request->get_param( 'date' );
				$view        = $request->get_param( 'view' );
				$data_range  = $request->get_param( 'dateRange' );
				if ( empty( $view ) ) {
					$view = 'month';
				}
				$appointmentsService = Container::resolve( AppointmentService::class );
				$appointments        = $appointmentsService->getAppointmentForCalender( $service_id, $customer_id, $employee_id, $date,$data_range, $view );

				return ApiResponse::success(
					$this->transformCollection( $appointments ),
				)->send();
			}
		);
	}

	/**
	 * Retrieves the dashboard data based on the provided request.
	 * Applies middleware, processes the request, and returns a successful API response with the data.
	 *
	 * @param WP_REST_Request $request The REST request object containing parameters and context for the request.
	 *
	 * @return mixed The processed response after retrieving dashboard data.
	 */
	public function getDashboardData( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$booking_type        = sanitize_text_field( $request->get_param( 'booking_type' ) );
				$appointmentsService = Container::resolve( AppointmentService::class );
				$dashboardData       = $appointmentsService->getDashboardData( $booking_type ?: null );

				return ApiResponse::success(
					$dashboardData,
				)->send();
			}
		);
	}

	/**
	 * Handles the creation of a PayPal order based on the provided request.
	 *
	 * @param WP_REST_Request $request The request object containing the necessary parameters for creating a PayPal order.
	 *
	 * @return mixed The response after creating the PayPal order, either a success message with order data or an error response.
	 */
	public function getConfig( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function () {
				$service = Container::resolve( AppointmentService::class );
				$config  = $service->getConfig();

				return ApiResponse::success(
					$config,
					'Config fetched successfully'
				)->send();
			}
		);
	}

	/**
	 * Handles the creation of a PayPal order based on the provided request.
	 *
	 * @param WP_REST_Request $request The request object containing the necessary parameters for creating a PayPal order.
	 *
	 * @return mixed The response after creating the PayPal order, either a success message with order data or an error response.
	 */
	public function createPayPalOrder( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$service   = Container::resolve( AppointmentService::class );
				$orderData = $service->createPayPalOrder( $request->get_params() );

				if ( isset( $orderData['error'] ) && $orderData['error'] ) {
					return ApiResponse::error( $orderData['message'] ?? 'Failed to create PayPal order' )->send();
				}

				return ApiResponse::success(
					$orderData,
					'PayPal order created successfully'
				)->send();
			}
		);
	}

	/**
	 * Captures a PayPal order by processing the provided request.
	 *
	 * @param WP_REST_Request $request The REST API request containing the PayPal order ID.
	 *
	 * @return mixed The API response indicating the success or failure of the order capture process.
	 */
	public function capturePayPalOrder( WP_REST_Request $request ) {
		return $this->applyMiddleware(
			$request,
			function ( $request ) {
				$service = Container::resolve( AppointmentService::class );
				$params  = $request->get_params();
				if ( empty( $params['orderId'] ) ) {
					return ApiResponse::error( 'Order ID is required' )->send();
				}

				$captureResult = $service->capturePayPalOrder( $params['orderId'] );

				if ( isset( $captureResult['error'] ) && $captureResult['error'] ) {
					return ApiResponse::error( $captureResult['message'] ?? 'Payment capture failed' )->send();
				}

				return ApiResponse::success(
					$captureResult,
					'PayPal payment captured successfully'
				)->send();
			}
		);
	}
}
