<?php

namespace RadiusTheme\RadiusBooking\Controllers;

use RadiusTheme\RadiusBooking\Core\Api\ApiResponse;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\AuthMiddleware;
use RadiusTheme\RadiusBooking\Core\Api\Middleware\PermissionMiddleware;
use RadiusTheme\RadiusBooking\Core\Container\Container;
use RadiusTheme\RadiusBooking\Helpers\SettingsHelper;
use RadiusTheme\RadiusBooking\Models\Appointment;
use RadiusTheme\RadiusBooking\Models\EmployeeService;
use RadiusTheme\RadiusBooking\Models\Service;
use RadiusTheme\RadiusBooking\Models\User;
use RadiusTheme\RadiusBooking\Models\UserProfile;
use RadiusTheme\RadiusBooking\Repositories\UserProfileRepository;
use RadiusTheme\RadiusBooking\Repositories\ServiceRepository;
use RadiusTheme\RadiusBooking\Repositories\UserRepository;
use RadiusTheme\RadiusBooking\Resources\UserResource;
use RadiusTheme\RadiusBooking\Services\AppointmentService;
use RadiusTheme\RadiusBooking\Services\EmployeePanelService;
use RadiusTheme\RadiusBooking\Services\UserService;
use RadiusTheme\RadiusBooking\Services\EmployeeServices\UserWeekdayDataService;
use WP_REST_Request;
use RadiusTheme\RadiusBooking\Abstracts\BaseController;

/**
 * Api/Controllers/EmployeePanelController.php
 * EmployeePanel API controller
 */
class EmployeePanelController extends BaseController {

	public function __construct() {
		parent::__construct( new UserRepository() );
		$this->middleware[] = new AuthMiddleware();
		$this->middleware[] = new PermissionMiddleware( 'radius_booking_employee' );
	}

	protected function transformItem( $item ): array {
		return ( new UserResource() )->transform( $item );
	}

	protected function transformCollection( array $items ): array {
		return UserResource::collection( $items );
	}

	/**
	 * Validation rules for create and update.
	 *
	 * @param string $context 'create' or 'update'
	 *
	 * @return array
	 */
	protected function getValidationRules( $request, string $context = 'create' ): array {
		$rules = [
			// Add your fields below
			// 'field_name' => [ new RequiredRule(), new IntegerRule() ],
		];

		if ( $context === 'update' ) {
			// Make some fields optional
			// unset( $rules['field_name'] );
		}

		return $rules;
	}

	public function employeeDashboard( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$employee_service = Container::resolve( EmployeePanelService::class );
			$dashboard_data   = $employee_service->getDashboardData( $employee_id );

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

	public function employeeSchedule( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$all_params       = $request->get_params();
			$employee_service = Container::resolve( EmployeePanelService::class );
			$schedule_data    = $employee_service->getScheduleData( $employee_id, $all_params );

			return ApiResponse::success( $schedule_data, 'Schedule info fetch successfully' )->send();
		} );
	}

	public function employeePayment( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$page             = $request->get_param( 'page' );
			$per_page         = $request->get_param( 'per_page' );
			$date_filter      = $request->get_param( 'date_filter' );
			$status           = $request->get_param( 'status' );
			$search           = $request->get_param( 'search' );
			$employee_service = Container::resolve( EmployeePanelService::class );
			$payment_data     = $employee_service->getPaymentData( $employee_id, $date_filter, $page, $per_page, $status, $search );

			return ApiResponse::success( $payment_data, 'Payment info fetch successfully' )->send();
		} );
	}

	public function employeeCustomer( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$page             = $request->get_param( 'page' );
			$per_page         = $request->get_param( 'per_page' );
			$employee_service = Container::resolve( EmployeePanelService::class );
			$customer_data    = $employee_service->getClients( $employee_id, $page, $per_page );

			return ApiResponse::success( $customer_data, 'Payment info fetch successfully' )->send();
		} );
	}

	public function updateAppointmentStatus( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$appointment_id = $request->get_param( 'appointment_id' );
			$employee_id    = $request->get_param( 'employee_id' );
			$user           = $this->repository->find( $employee_id );
			$is_employee    = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$status           = $request->get_param( 'status' );
			$employee_service = Container::resolve( EmployeePanelService::class );
			$service          = Container::resolve( AppointmentService::class );

			$result = $employee_service->updateAppointmentStatus( $appointment_id, $service, $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 = Appointment::find( $appointment_id );
			$service->fireStatusAction( $appointment );
			$data = [
				'status' => $status,
				'id'     => $appointment_id,
			];

			return ApiResponse::success( $data, 'Appointment status updated successfully' )->send();
		} );
	}

	public function employeeProfile( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$employee_service = Container::resolve( EmployeePanelService::class );
			$profile_data     = $employee_service->getProfileData( $employee_id );
			return ApiResponse::success( $profile_data, 'Profile info fetch successfully' )->send();
		});
	}

	public function employeeUpdateProfile( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$employee_service = Container::resolve( EmployeePanelService::class );
			$profile_data     = $employee_service->updateProfileData( $request );
			return ApiResponse::success( $profile_data, 'Profile info updated successfully' )->send();
		});
	}

	public function employeeChangePassword( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			$employee_id = $request->get_param( 'employee_id' );
			$user        = $this->repository->find( $employee_id );
			$is_employee = $user->is_employee();
			if ( ! $is_employee ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}
			$employee_service = Container::resolve( EmployeePanelService::class );
			$profile_data     = $employee_service->changePassword( $request );
			if( $profile_data['success'] === false ) {
				return ApiResponse::error( $profile_data['message'] ?? 'Password change failed' )->send();
			}
			return ApiResponse::success( $profile_data, 'Password changed successfully' )->send();
		});
	}

	// ─── Staff Permission Helpers ─────────────────────────────────────

	/**
	 * Get staff permissions from settings.
	 *
	 * @return array
	 */
	private function getStaffPermissions() {
		return SettingsHelper::get_setting( 'staffPermissions' );
	}

	/**
	 * Meta key for the per-parent override controlling which permissions
	 * a parent staff grants to all of their sub-staff. Stored on the parent's
	 * WP user as an associative array (e.g. { canEditWorkSchedule: bool }).
	 */
	const PARENT_CHILD_PERMS_META = '_rtrb_child_staff_perms';

	/**
	 * Default per-parent overrides for sub-staff.
	 *
	 * @return array
	 */
	private function defaultChildStaffPerms(): array {
		return array(
			'canEditWorkSchedule' => true,
		);
	}

	/**
	 * Read the per-parent overrides from the parent's WP user meta.
	 *
	 * @param int $parent_wp_user_id Parent's WordPress user ID.
	 *
	 * @return array
	 */
	private function getParentChildPerms( int $parent_wp_user_id ): array {
		if ( ! $parent_wp_user_id ) {
			return $this->defaultChildStaffPerms();
		}
		$raw = get_user_meta( $parent_wp_user_id, self::PARENT_CHILD_PERMS_META, true );
		if ( ! is_array( $raw ) ) {
			return $this->defaultChildStaffPerms();
		}
		return array_merge( $this->defaultChildStaffPerms(), $raw );
	}

	/**
	 * Resolve the WP user ID of the current sub-staff's parent (creator).
	 *
	 * @return int Parent WP user ID, or 0 if not a sub-staff or unresolved.
	 */
	private function getCurrentParentWpUserId(): int {
		$wp_user_id = get_current_user_id();
		if ( ! $wp_user_id ) {
			return 0;
		}
		$profile_repo = Container::resolve( UserProfileRepository::class );
		$profile      = $profile_repo->findProfileByWPId( $wp_user_id );
		if ( ! $profile || empty( $profile->user_id ) ) {
			return 0;
		}
		$user = User::find( $profile->user_id );
		if ( ! $user || empty( $user->created_by ) ) {
			return 0;
		}
		$parent_profile = $profile_repo->findBy( array( 'user_id' => (int) $user->created_by ) );
		return $parent_profile ? (int) $parent_profile->wp_user_id : 0;
	}

	/**
	 * Check if a permission is allowed (and pro-gated where needed).
	 *
	 * @param string $permission Permission key.
	 *
	 * @return bool
	 */
	private function hasPermission( $permission ) {
		$perms = $this->getStaffPermissions();

		if ( empty( $perms[ $permission ] ) ) {
			return false;
		}

		// Pro-gated permissions.
		$pro_only = array( 'canCreateService', 'canDeleteService', 'canManageStaff' );
		if ( in_array( $permission, $pro_only, true ) && ! rtrb_pro_active() ) {
			return false;
		}

		// Sub-staff cannot manage staff or services.
		$sub_staff_blocked = array( 'canManageStaff', 'canEditService', 'canCreateService', 'canDeleteService' );
		if ( in_array( $permission, $sub_staff_blocked, true ) && $this->isSubStaff() ) {
			return false;
		}

		// Parent-controlled overrides for sub-staff (e.g. canEditWorkSchedule).
		if ( 'canEditWorkSchedule' === $permission && $this->isSubStaff() ) {
			$parent_perms = $this->getParentChildPerms( $this->getCurrentParentWpUserId() );
			if ( empty( $parent_perms['canEditWorkSchedule'] ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Check if the current logged-in user is a sub-staff (created by another employee).
	 *
	 * @return bool
	 */
	private function isSubStaff() {
		$wp_user_id = get_current_user_id();
		if ( ! $wp_user_id ) {
			return false;
		}

		$profile_repo = Container::resolve( UserProfileRepository::class );
		$profile      = $profile_repo->findProfileByWPId( $wp_user_id );
		if ( ! $profile || empty( $profile->user_id ) ) {
			return false;
		}

		$user = User::find( $profile->user_id );

		return $user && ! empty( $user->created_by );
	}

	/**
	 * Verify the requesting user is the employee and return them, or send error.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \RadiusTheme\RadiusBooking\Models\User|null
	 */
	private function verifyEmployee( WP_REST_Request $request ) {
		$employee_id = $request->get_param( 'employee_id' );
		$user        = $this->repository->find( $employee_id );

		if ( ! $user || ! $user->is_employee() ) {
			return null;
		}

		return $user;
	}

	// ─── Employee Service CRUD ────────────────────────────────────────

	/**
	 * Get a single service for editing.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function getService( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$service_id = $request->get_param( 'service_id' );
			$service    = Service::find( $service_id );

			if ( ! $service ) {
				return ApiResponse::error( 'Service not found', 404 )->send();
			}

			// Verify any of this WP user's related employee identities is
			// assigned to the service. CL flows split one WP user across
			// multiple radius_booking users (per-listing in staff mode, plus
			// the author identity in service mode), so checking only the
			// panel's employee_id rejects services owned by a sibling.
			$employee_id = (int) $request->get_param( 'employee_id' );
			$assigned    = $this->findEmployeeServiceForRelated( $employee_id, (int) $service_id );

			if ( ! $assigned ) {
				return ApiResponse::error( 'You are not assigned to this service', 403 )->send();
			}

			$service_repo = Container::resolve( ServiceRepository::class );
			$data         = $service_repo->find( $service_id );

			$payload = $data ? $data->toArray() : array();

			// Include the IDs of this employee's staff (children) who are
			// assigned to this service, so the form can pre-check them.
			$payload['assigned_staff_ids'] = $this->getAssignedStaffIds( (int) $employee_id, (int) $service_id );

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

	/**
	 * Look up an EmployeeService row matching the given service for any of
	 * the WP user's related employee identities (panel id + sibling profiles
	 * sharing the same wp_user_id). One WP user can be split across multiple
	 * radius_booking users by the Classified Listing flows (per-listing in
	 * staff mode plus the author identity in service mode), so the assignment
	 * check must consider all of them, not just the panel id.
	 *
	 * @param int $employee_id Panel-level employee id from the request.
	 * @param int $service_id  Service being acted on.
	 *
	 * @return EmployeeService|null
	 */
	private function findEmployeeServiceForRelated( int $employee_id, int $service_id ) {
		$panel_service = Container::resolve( EmployeePanelService::class );
		$ids           = $panel_service->getRelatedEmployeeIds( $employee_id );
		if ( empty( $ids ) ) {
			$ids = array( $employee_id );
		}
		return EmployeeService::where( 'service_id', $service_id )
			->whereIn( 'user_id', $ids )
			->first();
	}

	/**
	 * Return the IDs of the parent employee's child staff who are assigned to
	 * the given service. Used to pre-populate the "Assign to staff" picker on
	 * the employee service form.
	 *
	 * @param int $parent_employee_id The parent employee user ID.
	 * @param int $service_id         The service ID.
	 *
	 * @return int[]
	 */
	private function getAssignedStaffIds( int $parent_employee_id, int $service_id ): array {
		$child_ids = User::where( 'created_by', $parent_employee_id )
			->where( 'user_type', 'employee' )
			->get();

		if ( empty( $child_ids ) ) {
			return array();
		}

		$ids = array_map( fn( $u ) => (int) $u->id, $child_ids );

		$assigned = EmployeeService::where( 'service_id', $service_id )
			->whereIn( 'user_id', $ids )
			->get();

		return array_values( array_map( fn( $row ) => (int) $row->user_id, $assigned ) );
	}

	/**
	 * Sync the `employees_services` rows for a service against the desired set
	 * of child staff IDs. Only operates on staff created by the parent — never
	 * touches other employees' assignments. Removes rows for unchecked staff
	 * and inserts rows for newly checked staff (with the service's base price).
	 *
	 * @param int   $parent_employee_id The parent employee user ID.
	 * @param int   $service_id         The service ID.
	 * @param array $desired_staff_ids  IDs the form submitted as checked.
	 * @param float $service_price      Default price for new assignments.
	 *
	 * @return void
	 */
	private function syncAssignedStaff( int $parent_employee_id, int $service_id, array $desired_staff_ids, float $service_price,$param ): void {
		$children = User::where( 'created_by', $parent_employee_id )
			->where( 'user_type', 'employee' )
			->get();

		if ( empty( $children ) ) {
			return;
		}

		$child_ids   = array_map( fn( $u ) => (int) $u->id, $children );
		$desired_set = array_map( 'intval', $desired_staff_ids );
		// Restrict to children only — defensive against a tampered payload
		// trying to assign other employees through this endpoint.
		$desired_set = array_values( array_intersect( $desired_set, $child_ids ) );

		$existing_rows = EmployeeService::where( 'service_id', $service_id )
			->whereIn( 'user_id', $child_ids )
			->get();
		$existing_ids = array_map( fn( $r ) => (int) $r->user_id, $existing_rows );

		$to_add    = array_diff( $desired_set, $existing_ids );
		$to_remove = array_diff( $existing_ids, $desired_set );
		foreach ( $to_add as $staff_id ) {
			EmployeeService::create( array(
				'user_id'    => $staff_id,
				'service_id' => $service_id,
				'price'      => $service_price,
				'min_capacity' => $param['min_capacity'] ?? 1,
				'max_capacity' => $param['max_capacity'] ?? 1
			) );

		}

		foreach ( $to_remove as $staff_id ) {
			EmployeeService::where( 'service_id', $service_id )
				->where( 'user_id', $staff_id )
				->delete();
		}
	}

	/**
	 * Update an assigned service (requires canEditService permission).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function updateService( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canEditService' ) ) {
				return ApiResponse::error( 'You do not have permission to edit services', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = (int) $request->get_param( 'employee_id' );
			$service_id  = (int) $request->get_param( 'service_id' );

			$assigned = $this->findEmployeeServiceForRelated( $employee_id, $service_id );

			if ( ! $assigned ) {
				return ApiResponse::error( 'You are not assigned to this service', 403 )->send();
			}

			$service_repo = Container::resolve( ServiceRepository::class );
			$params       = $request->get_params();

			// Employees can update these fields.
			$allowed = array(
				'name', 'description', 'price', 'duration', 'category_id', 'color',
				'picture_full_path', 'picture_thumb_path',
				'time_before', 'time_after', 'is_visible',
			);

			// Pro-only fields — only allow when Pro is active.
			if ( rtrb_pro_active() ) {
				$pro_fields = array(
					'min_capacity', 'max_capacity', 'bringing_anyone', 'aggregated_price', 'max_extra_people',
					'recurring_cycle', 'recurring_sub', 'recurring_payment',
					'deposit_payment', 'deposit_type', 'deposit', 'deposit_per_person', 'full_payment',
					'settings',
				);
				$allowed = array_merge( $allowed, $pro_fields );
			}

			$data = array_intersect_key( $params, array_flip( $allowed ) );

			// Capture the pre-update price so we can shift each employee's
			// override by the same delta — keeps custom prices relative to
			// the new service price instead of leaving them stale.
			$existing  = $service_repo->find( $service_id );
			$old_price = $existing ? (float) $existing->price : null;

			$service_repo->update( $service_id, $data );

			$updated = $service_repo->find( $service_id );

			if ( $updated && null !== $old_price && array_key_exists( 'price', $data ) ) {
				$delta = (float) $updated->price - $old_price;
				if ( 0.0 !== $delta ) {
					$service_repo->applyPriceDeltaToEmployees( (int) $updated->id, $delta );
				}
			}
			// Sync child staff assignments if the form sent a list. We use
			// array_key_exists so an explicit empty array clears all assignments;
			// omitting the key entirely leaves them untouched.
			if ( $updated && array_key_exists( 'assigned_staff_ids', $params ) ) {
				$desired = is_array( $params['assigned_staff_ids'] ) ? $params['assigned_staff_ids'] : array();
				$this->syncAssignedStaff(
					(int) $employee_id,
					(int) $updated->id,
					$desired,
					(float) ( $updated->price ?? 0 ),
					$params,
				);
			}

			return ApiResponse::success(
				$updated ? $updated->toArray() : array(),
				'Service updated successfully'
			)->send();
		} );
	}

	/**
	 * Create a new service (requires canCreateService permission — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function createService( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canCreateService' ) ) {
				return ApiResponse::error( 'You do not have permission to create services', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = $request->get_param( 'employee_id' );
			$params      = $request->get_params();

			// Validate required fields.
			if ( empty( $params['name'] ) || empty( $params['duration'] ) ) {
				return ApiResponse::error( 'Name and duration are required', 422 )->send();
			}

			$service_repo = Container::resolve( ServiceRepository::class );
			$service      = $service_repo->create( $params );

			// Auto-assign the creating employee to this service.
			if ( $service ) {
				EmployeeService::create( array(
					'user_id'    => $employee_id,
					'service_id' => $service->id,
					'price'      => $params['price'] ?? 0,
				) );
				// Optionally assign the parent's child staff in the same step.
				if ( ! empty( $params['assigned_staff_ids'] ) && is_array( $params['assigned_staff_ids'] ) ) {
					$this->syncAssignedStaff(
						(int) $employee_id,
						(int) $service->id,
						$params['assigned_staff_ids'],
						(float) ( $params['price'] ?? 0 ),
						$params
					);
				}
			}

			$created = $service_repo->find( $service->id );
			return ApiResponse::success(
				$created ? $created->toArray() : array(),
				'Service created successfully'
			)->send();
		} );
	}

	/**
	 * Delete a service (requires canDeleteService permission — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function deleteService( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canDeleteService' ) ) {
				return ApiResponse::error( 'You do not have permission to delete services', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = (int) $request->get_param( 'employee_id' );
			$service_id  = (int) $request->get_param( 'service_id' );

			$assigned = $this->findEmployeeServiceForRelated( $employee_id, $service_id );

			if ( ! $assigned ) {
				return ApiResponse::error( 'You are not assigned to this service', 403 )->send();
			}

			$service_repo = Container::resolve( ServiceRepository::class );
			$service      = Service::find( $service_id );

			// If this service is linked to a Classified Listing, remove booking meta from the listing.
			if ( $service && ! empty( $service->listing_id ) ) {
				$listing_id = (int) $service->listing_id;
				delete_post_meta( $listing_id, '_rtrb_booking_enabled' );
				delete_post_meta( $listing_id, '_rtrb_booking_duration' );
				delete_post_meta( $listing_id, '_rtrb_booking_category_id' );
			}

			$service_repo->delete( $service_id );

			return ApiResponse::success( null, 'Service deleted successfully' )->send();
		} );
	}

	// ─── Employee Staff Management ────────────────────────────────────

	/**
	 * List all staff members (requires canManageStaff — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function listStaff( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = (int) $request->get_param( 'employee_id' );

			// The Manage Staff list is the union of two relationships:
			//
			//   1. CL listing-as-staff siblings — every listing-tied UserProfile
			//      that shares the calling employee's wp_user_id. Each listing IS
			//      a staff member, so they all belong here regardless of which
			//      profile findProfileByWPId() happened to return first on the
			//      dashboard (fresh installs were getting an empty list when a
			//      non-listing profile won that race).
			//   2. Manually-created sub-staff — rows in `users` whose
			//      `created_by` points at the calling employee (createStaff sets
			//      this). Required even when the user owns CL listings, so they
			//      can still mix in non-listing employees they create by hand.
			$primary_profile = UserProfile::where( 'user_id', $employee_id )->first();
			$wp_user_id      = $primary_profile ? (int) $primary_profile->wp_user_id : 0;

			$staff_user_ids = array();

			// Path 1: CL listing-tied siblings (only when the user has CL
			// listings — keeps the legacy non-CL flow unchanged).
			if ( $wp_user_id ) {
				$listing_profiles = UserProfile::where( 'wp_user_id', $wp_user_id )
					->whereNotNull( 'listing_id' )
					->get();
				foreach ( $listing_profiles as $sp ) {
					$uid = (int) $sp->user_id;
					if ( $uid ) {
						$staff_user_ids[ $uid ] = true;
					}
				}
			}

			// Path 2: legacy created_by hierarchy (manually-created sub-staff).
			$created_by_users = User::where( 'created_by', $employee_id )
				->where( 'user_type', 'employee' )
				->get();
			foreach ( $created_by_users as $u ) {
				$uid = (int) $u->id;
				if ( $uid ) {
					$staff_user_ids[ $uid ] = true;
				}
			}

			$staff_user_ids = array_keys( $staff_user_ids );
			$users          = ! empty( $staff_user_ids )
				? User::whereIn( 'id', $staff_user_ids )->where( 'user_type', 'employee' )->get()
				: array();

			$user_profile_repo = Container::resolve( UserProfileRepository::class );
			$staff_list        = array();

			foreach ( $users as $user ) {
				$profile = $user_profile_repo->findBy( array( 'user_id' => (int) $user->id ) );

				$assigned       = EmployeeService::where( 'user_id', $user->id )->get();
				$service_ids    = array();
				$service_prices = array();
				foreach ( $assigned as $es ) {
					$sid                    = (int) $es->service_id;
					$service_ids[]          = $sid;
					$service_prices[ $sid ] = (float) $es->price;
				}

				$image_url = '';
				if ( $profile && ! empty( $profile->image_id ) ) {
					$image_url = wp_get_attachment_image_url( $profile->image_id, 'thumbnail' ) ?: '';
				}

				$staff_list[] = array(
					'id'             => (int) $user->id,
					'email'          => $user->email,
					'first_name'     => $profile ? $profile->first_name : '',
					'last_name'      => $profile ? $profile->last_name : '',
					'phone'          => $profile ? $profile->phone : '',
					'status'         => $user->status ?? 'active',
					'service_ids'    => $service_ids,
					'service_prices' => (object) $service_prices,
					'note'           => $profile ? ( $profile->note ?? '' ) : '',
					'time_zone'      => $profile ? ( $profile->time_zone ?? '' ) : '',
					'image_id'       => $profile ? ( $profile->image_id ?? 0 ) : 0,
					'image_url'      => $image_url,
				);
			}

			return ApiResponse::success( $staff_list, 'Staff list fetched successfully' )->send();
		} );
	}

	/**
	 * Get the parent's child-staff permission overrides.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function getStaffSettings( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = (int) $request->get_param( 'employee_id' );
			$profile     = Container::resolve( UserProfileRepository::class )
				->findBy( array( 'user_id' => $employee_id ) );
			$wp_user_id  = $profile ? (int) $profile->wp_user_id : 0;
			if ( ! $wp_user_id ) {
				return ApiResponse::error( 'Profile not found', 404 )->send();
			}

			return ApiResponse::success(
				$this->getParentChildPerms( $wp_user_id ),
				'Staff settings fetched successfully'
			)->send();
		} );
	}

	/**
	 * Update the parent's child-staff permission overrides.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function updateStaffSettings( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = (int) $request->get_param( 'employee_id' );
			$profile     = Container::resolve( UserProfileRepository::class )
				->findBy( array( 'user_id' => $employee_id ) );
			$wp_user_id  = $profile ? (int) $profile->wp_user_id : 0;
			if ( ! $wp_user_id ) {
				return ApiResponse::error( 'Profile not found', 404 )->send();
			}

			$current = $this->getParentChildPerms( $wp_user_id );
			$can     = $request->get_param( 'canEditWorkSchedule' );
			if ( null !== $can ) {
				$current['canEditWorkSchedule'] = filter_var( $can, FILTER_VALIDATE_BOOLEAN );
			}

			update_user_meta( $wp_user_id, self::PARENT_CHILD_PERMS_META, $current );

			return ApiResponse::success( $current, 'Staff settings updated successfully' )->send();
		} );
	}

	/**
	 * Get a single staff member for editing (requires canManageStaff — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function getStaff( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$staff_id = $request->get_param( 'staff_id' );
			$staff    = $this->repository->find( $staff_id );

			if ( ! $staff || ! $staff->is_employee() ) {
				return ApiResponse::error( 'Staff member not found', 404 )->send();
			}

			return ApiResponse::success( $this->transformItem( $staff ), 'Staff fetched successfully' )->send();
		} );
	}

	/**
	 * Create a new staff member (requires canManageStaff — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function createStaff( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id  = $request->get_param( 'employee_id' );
			$params       = $request->get_params();
			$params['created_by'] = (int) $employee_id;

			$user_service = Container::resolve( UserService::class );
			$result       = $user_service->createEmployee( $params );

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

			// Assign services from the main staff to the new sub-staff.
			$service_ids    = $request->get_param( 'service_ids' );
			$service_prices = $request->get_param( 'service_prices' );
			if ( ! empty( $service_ids ) && is_array( $service_ids ) ) {
				$main_service_ids = array_map(
					function ( $es ) {
						return (int) $es->service_id;
					},
					EmployeeService::where( 'user_id', $employee_id )->get()
				);

				foreach ( $service_ids as $sid ) {
					$sid = (int) $sid;
					if ( ! in_array( $sid, $main_service_ids, true ) ) {
						continue;
					}

					// Use custom price if sent, else main staff's price.
					$custom_price = null;
					if ( is_array( $service_prices ) && isset( $service_prices[ $sid ] ) ) {
						$custom_price = (float) $service_prices[ $sid ];
					} elseif ( is_object( $service_prices ) && isset( $service_prices->$sid ) ) {
						$custom_price = (float) $service_prices->$sid;
					}

					$existing = EmployeeService::where( 'user_id', $result['id'] )
						->where( 'service_id', $sid )
						->first();
					if ( ! $existing ) {
						if ( null === $custom_price ) {
							$main_es      = EmployeeService::where( 'user_id', $employee_id )
								->where( 'service_id', $sid )
								->first();
							$custom_price = $main_es ? $main_es->price : 0;
						}
						$es = new EmployeeService();
						$es->fill(
							array(
								'user_id'    => $result['id'],
								'service_id' => $sid,
								'price'      => $custom_price,
								'min_capacity' => 1,
								'max_capacity' => 1,
							)
						);
						$es->save();
					}
				}
				$result['service_ids'] = array_values( array_map( 'intval', $service_ids ) );
			}

			// Sub-staff inherit the creator's schedule live via
			// UserWeekdayDataService::getEffectiveWeekdays(), so we no longer
			// seed a copy at creation time. They keep using the main staff's
			// hours until they set their own custom schedule.

			return ApiResponse::success( $result, 'Staff member created successfully' )->send();
		} );
	}

	/**
	 * Update a staff member (requires canManageStaff — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function updateStaff( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$staff_id = $request->get_param( 'staff_id' );
			$staff    = $this->repository->find( $staff_id );

			if ( ! $staff || ! $staff->is_employee() ) {
				return ApiResponse::error( 'Staff member not found', 404 )->send();
			}

			$user_service = Container::resolve( UserService::class );
			$result       = $user_service->updateEmployee( $staff_id, $request->get_params() );

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

			// Sync service assignments if provided.
			$service_ids    = $request->get_param( 'service_ids' );
			$service_prices = $request->get_param( 'service_prices' );
			if ( is_array( $service_ids ) ) {
				$employee_id      = $request->get_param( 'employee_id' );
				$main_service_ids = array_map(
					function ( $es ) {
						return (int) $es->service_id;
					},
					EmployeeService::where( 'user_id', $employee_id )->get()
				);

				// Remove services no longer selected.
				$current = EmployeeService::where( 'user_id', $staff_id )->get();
				foreach ( $current as $es ) {
					if ( ! in_array( (int) $es->service_id, array_map( 'intval', $service_ids ), true ) ) {
						EmployeeService::where( 'id', $es->id )->delete();
					}
				}

				// Add or update selected services.
				foreach ( $service_ids as $sid ) {
					$sid = (int) $sid;
					if ( ! in_array( $sid, $main_service_ids, true ) ) {
						continue;
					}

					// Determine price: use custom price if sent, else main staff's price.
					$custom_price = null;
					if ( is_array( $service_prices ) && isset( $service_prices[ $sid ] ) ) {
						$custom_price = (float) $service_prices[ $sid ];
					} elseif ( is_object( $service_prices ) && isset( $service_prices->$sid ) ) {
						$custom_price = (float) $service_prices->$sid;
					}

					$existing_raw = EmployeeService::where( 'user_id', $staff_id )
						->where( 'service_id', $sid )
						->first();

					if ( $existing_raw ) {
						// Update price if custom price was sent.
						if ( null !== $custom_price ) {
							$existing_model = EmployeeService::hydrate( $existing_raw );
							$existing_model->fill( array( 'price' => $custom_price,'min_capacity' => 1,
							                              'max_capacity' => 1, ) );
							$existing_model->save();
						}
					} else {
						// Create new assignment.
						if ( null === $custom_price ) {
							$main_es_raw  = EmployeeService::where( 'user_id', $employee_id )
								->where( 'service_id', $sid )
								->first();
							$custom_price = $main_es_raw ? $main_es_raw->price : 0;
						}
						$es = new EmployeeService();
						$es->fill(
							array(
								'user_id'    => $staff_id,
								'service_id' => $sid,
								'price'      => $custom_price,
								'min_capacity' => 1,
								'max_capacity' => 1,
							)
						);
						$es->save();
					}
				}
				$result['service_ids'] = array_values( array_map( 'intval', $service_ids ) );
			}

			return ApiResponse::success( $result, 'Staff member updated successfully' )->send();
		} );
	}

	/**
	 * Delete a staff member (requires canManageStaff — Pro).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function deleteStaff( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->hasPermission( 'canManageStaff' ) ) {
				return ApiResponse::error( 'You do not have permission to manage staff', 403 )->send();
			}

			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$staff_id    = $request->get_param( 'staff_id' );
			$employee_id = $request->get_param( 'employee_id' );

			// Cannot delete yourself.
			if ( (int) $staff_id === (int) $employee_id ) {
				return ApiResponse::error( 'You cannot delete your own account', 400 )->send();
			}

			$staff = $this->repository->find( $staff_id );
			if ( ! $staff || ! $staff->is_employee() ) {
				return ApiResponse::error( 'Staff member not found', 404 )->send();
			}

			$this->repository->delete( $staff_id );

			return ApiResponse::success( array(), 'Staff member deleted successfully' )->send();
		} );
	}

	// ─── Employee Schedule Management ────────────────────────────────

	/**
	 * Get the employee's schedule settings (weekdays, days off, special days).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function getScheduleSettings( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id      = (int) $request->get_param( 'employee_id' );
			$user_repo        = new UserRepository();
			$weekday_service  = new UserWeekdayDataService();

			// Employee custom data (DB-only).
			$custom_weekdays     = $user_repo->getUserWeekdays( $employee_id );
			$custom_days_off     = $user_repo->getUserDaysOff( $employee_id );
			$custom_special_days = $user_repo->getUserSpecialDays( $employee_id );

			// Default ("Use Custom Schedule" OFF) view. For sub-staff this is
			// the creator's effective schedule, so the UI shows what the
			// employee actually inherits — not the unrelated global schedule.
			$employee     = User::find( $employee_id );
			$creator_id   = ( $employee && ! empty( $employee->created_by ) )
				? (int) $employee->created_by
				: 0;

			if ( $creator_id && $creator_id !== $employee_id ) {
				$global_weekdays     = UserWeekdayDataService::getEffectiveWeekdays( $creator_id );
				$global_days_off     = UserWeekdayDataService::getEffectiveDaysOff( $creator_id );
				$global_special_days = UserWeekdayDataService::getEffectiveSpecialDays( $creator_id );
			} else {
				$global_weekdays     = $weekday_service->getGlobalScheduleAsWeekdays( $employee_id );
				$global_days_off     = $weekday_service->getGlobalHolidaysAsDaysOff();
				$global_special_days = $weekday_service->getGlobalCustomSchedulesAsSpecialDays();
			}

			$data = array(
				'weekdays'              => $custom_weekdays,
				'days_off'              => $custom_days_off,
				'special_days'          => $custom_special_days,
				'global_weekdays'       => $global_weekdays,
				'global_days_off'       => $global_days_off,
				'global_special_days'   => $global_special_days,
				'has_custom_weekdays'   => ! empty( $custom_weekdays ),
				'has_custom_days_off'   => ! empty( $custom_days_off ),
				'has_custom_special_days' => ! empty( $custom_special_days ),
				'is_pro'                => rtrb_pro_active(),
			);

			return ApiResponse::success( $data, 'Schedule settings fetched successfully' )->send();
		} );
	}

	/**
	 * Update the employee's weekly work schedule.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function updateWeekdays( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = $request->get_param( 'employee_id' );
			$weekdays    = $request->get_param( 'weekdays' );

			if ( ! is_array( $weekdays ) ) {
				return ApiResponse::error( 'Invalid weekdays data', 400 )->send();
			}

			$user_service = Container::resolve( UserService::class );
			$user_service->createOrUpdateUserWeekdays( $employee_id, $weekdays );

			$user_repo = new UserRepository();

			return ApiResponse::success(
				$user_repo->getUserWeekdays( $employee_id ),
				'Work schedule updated successfully'
			)->send();
		} );
	}

	/**
	 * Update the employee's days off.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function updateDaysOff( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = $request->get_param( 'employee_id' );
			$days_off    = $request->get_param( 'days_off' );

			if ( ! is_array( $days_off ) ) {
				return ApiResponse::error( 'Invalid days off data', 400 )->send();
			}

			$user_service = Container::resolve( UserService::class );
			$user_service->createOrUpdateUserDaysOff( $employee_id, $days_off );

			$user_repo = new UserRepository();

			return ApiResponse::success(
				$user_repo->getUserDaysOff( $employee_id ),
				'Days off updated successfully'
			)->send();
		} );
	}

	/**
	 * Update the employee's special days schedule.
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function updateSpecialDays( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$employee_id = $request->get_param( 'employee_id' );
			$special_days = $request->get_param( 'special_days' );

			if ( ! is_array( $special_days ) ) {
				return ApiResponse::error( 'Invalid special days data', 400 )->send();
			}

			$user_service = Container::resolve( UserService::class );
			$user_service->createOrUpdateUserSpecialDays( $employee_id, $special_days );

			$user_repo = new UserRepository();

			return ApiResponse::success(
				$user_repo->getUserSpecialDays( $employee_id ),
				'Special days updated successfully'
			)->send();
		} );
	}

	/**
	 * Retrieves the resource type for this controller.
	 *
	 * @return string The resource type, which is 'user' in this case.
	 */
	/**
	 * Get all booking categories (read-only for employees).
	 *
	 * @param WP_REST_Request $request The request.
	 *
	 * @return \WP_REST_Response
	 */
	public function getCategories( WP_REST_Request $request ) {
		return $this->applyMiddleware( $request, function ( $request ) {
			if ( ! $this->verifyEmployee( $request ) ) {
				return ApiResponse::error( 'Invalid User ID', 400 )->send();
			}

			$categories = \RadiusTheme\RadiusBooking\Models\Category::all();
			$list       = array();
			foreach ( $categories as $cat ) {
				$list[] = array(
					'id'   => (int) $cat->id,
					'name' => $cat->name,
				);
			}

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

	protected function getResourceType(): string {
		return "employee_panel";
	}
}
