<?php
/**
 * TutorHelper Availability admin controller.
 *
 * @package TutorHelper_Bookings
 */

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

/**
 * Connects Availability forms and calendar rendering to the Booking Hub.
 */
final class TutorHelper_Availability_Admin {

	/**
	 * Save form action.
	 *
	 * @var string
	 */
	const SAVE_ACTION = 'tutorhelper_save_availability';

	/**
	 * Status form action.
	 *
	 * @var string
	 */
	const STATUS_ACTION = 'tutorhelper_change_availability_status';

	/**
	 * Remove Availability action.
	 *
	 * @var string
	 */
	const REMOVE_ACTION = 'tutorhelper_remove_availability';

	/**
	 * Cached occurrences grouped by date.
	 *
	 * @var array|null
	 */
	private static $occurrences_by_date = null;

	/**
	 * Register module hooks.
	 *
	 * @return void
	 */
	public static function init() {
		add_action(
			'admin_post_' . self::SAVE_ACTION,
			array( __CLASS__, 'handle_save' )
		);

		add_action(
			'admin_post_' . self::STATUS_ACTION,
			array( __CLASS__, 'handle_status_change' )
		);

		add_action(
			'admin_post_' . self::REMOVE_ACTION,
			array( __CLASS__, 'handle_remove' )
		);

		add_action(
			'tutorhelper_booking_hub_availability_panel',
			array( __CLASS__, 'render_panel' )
		);

		add_action(
			'tutorhelper_booking_hub_week_availability',
			array( __CLASS__, 'render_week_occurrences' ),
			10,
			2
		);

		add_action(
			'tutorhelper_booking_hub_month_day',
			array( __CLASS__, 'render_month_occurrences' ),
			10,
			2
		);
	}

	/**
	 * Process an Availability create or scoped update.
	 *
	 * New rules are created as complete recurring rules.
	 *
	 * Existing rules can be edited as:
	 * - one date;
	 * - a date range;
	 * - the complete recurring rule.
	 *
	 * @return void
	 */
	public static function handle_save() {
		self::verify_admin_request();

		check_admin_referer(
			self::SAVE_ACTION
		);

		$availability_id = isset( $_POST['availability_id'] )
			? absint( $_POST['availability_id'] )
			: 0;

		/*
		* Build the submitted data before attempting to create or update
		* anything.
		*/
		$data = array(
			'tutor_user_id' => get_current_user_id(),

			'title' => isset( $_POST['title'] )
				? sanitize_text_field(
					wp_unslash( $_POST['title'] )
				)
				: '',

			'start_date' => isset( $_POST['start_date'] )
				? sanitize_text_field(
					wp_unslash( $_POST['start_date'] )
				)
				: '',

			'end_date' => isset( $_POST['end_date'] )
				? sanitize_text_field(
					wp_unslash( $_POST['end_date'] )
				)
				: '',

			'weekdays' => isset( $_POST['weekdays'] )
				&& is_array( $_POST['weekdays'] )
					? array_map(
						'sanitize_key',
						wp_unslash( $_POST['weekdays'] )
					)
					: array(),

			'start_time' => isset( $_POST['start_time'] )
				? sanitize_text_field(
					wp_unslash( $_POST['start_time'] )
				)
				: '',

			'end_time' => isset( $_POST['end_time'] )
				? sanitize_text_field(
					wp_unslash( $_POST['end_time'] )
				)
				: '',

			'timezone' => wp_timezone_string(),

			'location_type' => isset( $_POST['location_type'] )
				? sanitize_key(
					wp_unslash( $_POST['location_type'] )
				)
				: 'online',

			'location_types' => isset( $_POST['location_types'] )
				&& is_array( $_POST['location_types'] )
					? array_map(
						'sanitize_key',
						wp_unslash( $_POST['location_types'] )
					)
					: array(),

			'location_details' => isset(
				$_POST['location_details']
			)
				? sanitize_textarea_field(
					wp_unslash(
						$_POST['location_details']
					)
				)
				: '',

			'allowed_booking_type_ids' => isset(
				$_POST['allowed_booking_type_ids']
			)
			&& is_array(
				$_POST['allowed_booking_type_ids']
			)
				? array_map(
					'absint',
					wp_unslash(
						$_POST['allowed_booking_type_ids']
					)
				)
				: array(),

			'is_active' => 1,
		);

		/*
		* Default values used later for logging and redirects.
		*/
		$edit_scope = 'new_rule';
		$notice     = 'availability_created';
		$result     = null;
		$saved_id   = 0;

		if ( $availability_id ) {
			$existing_rule =
				TutorHelper_Booking_Availability::get_by_id(
					$availability_id
				);

			/*
			* Prevent one tutor editing another tutor's Availability by
			* changing the submitted ID.
			*/
			if (
				! $existing_rule
				|| absint( $existing_rule->tutor_user_id )
					!== get_current_user_id()
			) {
				self::redirect(
					'availability_error',
					'tutorhelper_availability_not_found'
				);
			}

			$edit_scope = isset( $_POST['edit_scope'] )
				? sanitize_key(
					wp_unslash( $_POST['edit_scope'] )
				)
				: 'complete_rule';

			/*
			* Scoped occurrence edits must continue to inherit the recurrence
			* dates and weekdays from the parent rule.
			*
			* These fields may be disabled or hidden in the form and therefore
			* might not have been submitted.
			*/
			if ( 'complete_rule' !== $edit_scope ) {
				$data['start_date'] = $existing_rule->start_date;
				$data['end_date']   = $existing_rule->end_date;
				$data['weekdays']   = self::split_csv(
					$existing_rule->weekdays
				);
			}

			if ( 'single_date' === $edit_scope ) {
				$occurrence_date = isset(
					$_POST['occurrence_date']
				)
					? sanitize_text_field(
						wp_unslash(
							$_POST['occurrence_date']
						)
					)
					: '';

				
				$result =
					TutorHelper_Availability_Exceptions::create_override(
						$availability_id,
						$occurrence_date,
						$data
					);

				$notice = 'availability_date_updated';
			} elseif ( 'date_range' === $edit_scope ) {
				$range_start = isset(
					$_POST['edit_start_date']
				)
					? sanitize_text_field(
						wp_unslash(
							$_POST['edit_start_date']
						)
					)
					: '';

				$range_end = isset(
					$_POST['edit_end_date']
				)
					? sanitize_text_field(
						wp_unslash(
							$_POST['edit_end_date']
						)
					)
					: '';

				

				$result =
					TutorHelper_Availability_Exceptions::create_overrides_in_range(
						$availability_id,
						$range_start,
						$range_end,
						$data
					);

				if ( ! is_wp_error( $result ) ) {
					self::store_edit_result( $result );
				}

				$notice = 'availability_range_updated';
			} elseif ( 'complete_rule' === $edit_scope ) {
				$result =
					TutorHelper_Booking_Availability::update(
						$availability_id,
						$data
					);

				$notice = 'availability_updated';
			} else {
				$result = new WP_Error(
					'tutorhelper_availability_invalid_edit_scope',
					__(
						'The selected Availability edit scope is invalid.',
						'tutorhelper-bookings'
					)
				);
			}

			$saved_id = $availability_id;
		} else {
			/*
			* A new Availability submission always creates a new recurring
			* parent rule.
			*/
			$result = TutorHelper_Booking_Availability::create(
				$data
			);

			if ( ! is_wp_error( $result ) ) {
				$saved_id = absint( $result );
			}
		}

		/*
		* Handle errors before attempting to log a successful save.
		*/
		if ( is_wp_error( $result ) ) {
			TutorHelper_Logger::warning(
				'Availability admin save was rejected.',
				array(
					'availability_id' => $availability_id,
					'edit_scope'      => $edit_scope,
					'error_code'      => $result->get_error_code(),
				)
			);

			self::redirect(
				'availability_error',
				$result->get_error_code(),
				$availability_id
			);
		}

		/*
		* Log only technical identifiers and scope information.
		*/
		TutorHelper_Logger::success(
			$availability_id
				? 'Availability updated from the Booking Hub.'
				: 'Availability created from the Booking Hub.',
			array(
				'availability_id' => $saved_id,
				'edit_scope'      => $edit_scope,
			)
		);

		self::redirect( $notice );
	}




	/**
	 * Remove one date, a date range or a complete Availability rule.
	 *
	 * @return void
	 */
	public static function handle_remove() {
		self::verify_admin_request();

		check_admin_referer(
			self::REMOVE_ACTION
		);

		$availability_id = isset( $_POST['availability_id'] )
			? absint( $_POST['availability_id'] )
			: 0;

		$remove_scope = isset( $_POST['remove_scope'] )
			? sanitize_key(
				wp_unslash( $_POST['remove_scope'] )
			)
			: '';

		$rule = TutorHelper_Booking_Availability::get_by_id(
			$availability_id
		);

		if (
			! $rule
			|| absint( $rule->tutor_user_id )
				!== get_current_user_id()
		) {
			self::redirect(
				'availability_error',
				'tutorhelper_availability_not_found'
			);
		}

		if ( 'complete_rule' === $remove_scope ) {
			$result = TutorHelper_Booking_Availability::archive(
				$availability_id
			);

			if ( is_wp_error( $result ) ) {
				self::redirect(
					'availability_error',
					$result->get_error_code(),
					$availability_id
				);
			}

			TutorHelper_Logger::success(
				'Complete Availability rule removed.',
				array(
					'availability_id' => $availability_id,
					'remove_scope'    => $remove_scope,
				)
			);

			self::redirect(
				'availability_removed_rule'
			);
		}

		if ( 'single_date' === $remove_scope ) {
			$occurrence_date = isset( $_POST['occurrence_date'] )
				? sanitize_text_field(
					wp_unslash( $_POST['occurrence_date'] )
				)
				: '';

			$result =
				TutorHelper_Availability_Exceptions::remove_occurrence(
					$availability_id,
					$occurrence_date
				);
		} elseif ( 'date_range' === $remove_scope ) {
			$range_start = isset( $_POST['remove_start_date'] )
				? sanitize_text_field(
					wp_unslash( $_POST['remove_start_date'] )
				)
				: '';

			$range_end = isset( $_POST['remove_end_date'] )
				? sanitize_text_field(
					wp_unslash( $_POST['remove_end_date'] )
				)
				: '';

			$result =
				TutorHelper_Availability_Exceptions::remove_occurrences_in_range(
					$availability_id,
					$range_start,
					$range_end
				);
		} else {
			self::redirect(
				'availability_error',
				'tutorhelper_availability_invalid_remove_scope',
				$availability_id
			);
		}

		if ( is_wp_error( $result ) ) {
			TutorHelper_Logger::warning(
				'Availability occurrence removal was rejected.',
				array(
					'availability_id' => $availability_id,
					'remove_scope'    => $remove_scope,
					'error_code'      => $result->get_error_code(),
				)
			);

			self::redirect(
				'availability_error',
				$result->get_error_code(),
				$availability_id
			);
		}

		self::store_removal_result(
			$result
		);

		TutorHelper_Logger::success(
			'Availability occurrences removed.',
			array(
				'availability_id' => $availability_id,
				'remove_scope'    => $remove_scope,
				'removed_count'   => count(
					$result['removed_dates']
				),
				'skipped_count'   => count(
					$result['skipped_booked_dates']
				),
			)
		);

		self::redirect(
			'availability_removed_dates'
		);
	}


	
	


	/**
	 * Store an Availability-removal result for the current administrator.
	 *
	 * @param array $result Removal result.
	 *
	 * @return void
	 */
	private static function store_removal_result( $result ) {
		set_transient(
			'tutorhelper_availability_removal_'
				. get_current_user_id(),
			array(
				'removed_dates' => isset( $result['removed_dates'] )
					? array_values( $result['removed_dates'] )
					: array(),

				'already_removed_dates' => isset(
					$result['already_removed_dates']
				)
					? array_values(
						$result['already_removed_dates']
					)
					: array(),

				'skipped_booked_dates' => isset(
					$result['skipped_booked_dates']
				)
					? array_values(
						$result['skipped_booked_dates']
					)
					: array(),

				'ignored_dates' => isset( $result['ignored_dates'] )
					? array_values( $result['ignored_dates'] )
					: array(),
			),
			5 * MINUTE_IN_SECONDS
		);
	}

	/**
	 * Retrieve and remove the current administrator's removal result.
	 *
	 * @return array|null
	 */
	private static function get_removal_result() {
		$key = 'tutorhelper_availability_removal_'
			. get_current_user_id();

		$result = get_transient( $key );

		delete_transient( $key );

		return is_array( $result ) ? $result : null;
	}


	/**
	 * Store the result of a scoped Availability edit.
	 *
	 * @param array $result Range-edit result.
	 *
	 * @return void
	 */
	private static function store_edit_result( $result ) {
		set_transient(
			'tutorhelper_availability_edit_'
				. get_current_user_id(),
			array(
				'updated_dates' => isset( $result['updated_dates'] )
					? array_values( $result['updated_dates'] )
					: array(),

				'skipped_booked_dates' => isset(
					$result['skipped_booked_dates']
				)
					? array_values(
						$result['skipped_booked_dates']
					)
					: array(),

				'ignored_dates' => isset( $result['ignored_dates'] )
					? array_values( $result['ignored_dates'] )
					: array(),
			),
			5 * MINUTE_IN_SECONDS
		);
	}

	/**
	 * Retrieve and clear the current tutor's scoped edit result.
	 *
	 * @return array|null
	 */
	private static function get_edit_result() {
		$key = 'tutorhelper_availability_edit_'
			. get_current_user_id();

		$result = get_transient( $key );

		delete_transient( $key );

		return is_array( $result ) ? $result : null;
	}





	/**
	 * Archive or restore a complete Availability rule.
	 *
	 * @return void
	 */
	public static function handle_status_change() {
		self::verify_admin_request();

		check_admin_referer(
			self::STATUS_ACTION
		);

		$availability_id = isset( $_POST['availability_id'] )
			? absint( $_POST['availability_id'] )
			: 0;

		$rule =
			TutorHelper_Booking_Availability::
				get_by_id(
					$availability_id
				);

		if (
			! $rule
			|| absint(
				$rule->tutor_user_id
			)
			!== get_current_user_id()
		) {
			self::redirect(
				'availability_error',
				'tutorhelper_availability_not_found'
			);
		}

		$status_action = isset( $_POST['status_action'] )
			? sanitize_key(
				wp_unslash( $_POST['status_action'] )
			)
			: '';

		if ( 'archive' === $status_action ) {
			$result = TutorHelper_Booking_Availability::archive(
				$availability_id
			);

			$notice = 'availability_archived';
		} elseif ( 'restore' === $status_action ) {
			$result = TutorHelper_Booking_Availability::restore(
				$availability_id
			);

			$notice = 'availability_restored';
		} else {
			$result = new WP_Error(
				'tutorhelper_availability_invalid_action',
				__(
					'The requested Availability action is invalid.',
					'tutorhelper-bookings'
				)
			);
		}

		if ( is_wp_error( $result ) ) {
			self::redirect(
				'availability_error',
				$result->get_error_code()
			);
		}

		TutorHelper_Logger::success(
			'Availability status changed from the Booking Hub.',
			array(
				'availability_id' => $availability_id,
				'status_action'   => $status_action,
			)
		);

		self::redirect( $notice );
	}

	/**
	 * Render the complete Availability side panel.
	 *
	 * @return void
	 */
	public static function render_panel() {
		$editing_id = isset( $_GET['availability_id'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			? absint( $_GET['availability_id'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			: 0;

		$editing_rule = $editing_id
			? TutorHelper_Booking_Availability::get_by_id(
				$editing_id
			)
			: null;

		if (
			$editing_rule
			&& absint( $editing_rule->tutor_user_id )
				!== get_current_user_id()
		) {
			$editing_rule = null;
			$editing_id   = 0;
		}



		self::render_notice();
		self::render_form( $editing_rule );
	}

	/**
	 * Render Availability occurrences for one weekly day.
	 *
	 * @param string            $date_string Date in Y-m-d format.
	 * @param DateTimeImmutable $date        Date object.
	 *
	 * @return void
	 */
	public static function render_week_occurrences(
		$date_string,
		$date
	) {
		unset( $date );

		$occurrences = self::get_occurrences_for_date(
			$date_string
		);

		foreach ( $occurrences as $occurrence ) {
			self::render_week_occurrence( $occurrence );
		}
	}

	/**
	 * Render compact Availability information in a monthly cell.
	 *
	 * @param string            $date_string Date in Y-m-d format.
	 * @param DateTimeImmutable $date        Date object.
	 *
	 * @return void
	 */
	public static function render_month_occurrences(
		$date_string,
		$date
	) {
		unset( $date );

		$occurrences = self::get_occurrences_for_date(
			$date_string
		);

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

		$maximum_visible = 2;
		$visible         = array_slice(
			$occurrences,
			0,
			$maximum_visible
		);

		foreach ( $visible as $occurrence ) {
			?>
			<div class="tutorhelper-month-availability">
				<strong>
					<?php
					echo esc_html(
						self::format_time_range(
							$occurrence['start_time'],
							$occurrence['end_time']
						)
					);
					?>
				</strong>

				<span>
                    <a
                        href="<?php echo esc_url(
                            self::get_edit_url(
                                $occurrence['availability_id'],
                                $occurrence['date']
                            )
                        ); ?>"
                    >
					<?php echo esc_html( $occurrence['title'] ); ?>
                    </a>
				</span>
			</div>
			<?php
		}

		if ( count( $occurrences ) > $maximum_visible ) {
			?>
			<p class="tutorhelper-month-availability__more">
				<?php
				printf(
					/* translators: %d: Additional Availability windows. */
					esc_html__(
						'+%d more',
						'tutorhelper-bookings'
					),
					count( $occurrences ) - $maximum_visible
				);
				?>
			</p>
			<?php
		}
	}

	/**
	 * Render the create/edit form.
	 *
	 * @param object|null $rule Availability rule.
	 *
	 * @return void
	 */
	private static function render_form( $rule = null ) {
		$is_editing = is_object( $rule );

		$occurrence_date = isset( $_GET['occurrence_date'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			? sanitize_text_field(
				wp_unslash( $_GET['occurrence_date'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			)
			: '';

		$existing_override = null;

		if (
			$is_editing
			&& ! empty( $occurrence_date )
		) {
			$existing_override =
				TutorHelper_Availability_Exceptions::get_override(
					$rule->id,
					$occurrence_date
				);
		}


		$values = array(
			'id' => $is_editing ? absint( $rule->id ) : 0,

			'title' => $is_editing
				? (
					$existing_override
					&& null !== $existing_override->override_title
						? $existing_override->override_title
						: $rule->title
				)
				: __( 'Availability', 'tutorhelper-bookings' ),

			'start_date' => $is_editing
				? $rule->start_date
				: wp_date( 'Y-m-d' ),

			'end_date' => $is_editing
				? $rule->end_date
				: '',

			'weekdays' => $is_editing
				? self::split_csv( $rule->weekdays )
				: array(),

			'start_time' => $is_editing
				? substr(
					$existing_override
					&& null !== $existing_override->override_start_time
						? $existing_override->override_start_time
						: $rule->start_time,
					0,
					5
				)
				: '09:00',

			'end_time' => $is_editing
				? substr(
					$existing_override
					&& null !== $existing_override->override_end_time
						? $existing_override->override_end_time
						: $rule->end_time,
					0,
					5
				)
				: '17:00',

			'location_type' => $is_editing
				? (
					$existing_override
					&& null !== $existing_override->override_location_type
						? $existing_override->override_location_type
						: $rule->location_type
				)
				: 'online',

			'location_types' => $is_editing
				? self::split_csv(
					$existing_override
					&& null !== $existing_override->override_location_types
						? $existing_override->override_location_types
						: $rule->location_types
				)
				: array( 'online' ),

			'location_details' => $is_editing
				? (
					$existing_override
					&& null !== $existing_override->override_location_details
						? $existing_override->override_location_details
						: $rule->location_details
				)
				: '',

			'booking_type_ids' => $is_editing
				? array_map(
					'absint',
					self::split_csv(
						$existing_override
						&& null !==
							$existing_override->override_allowed_booking_type_ids
							? $existing_override
								->override_allowed_booking_type_ids
							: $rule->allowed_booking_type_ids
					)
				)
				: array(),
		);

		$booking_types = TutorHelper_Booking_Types::get_active();

		?>
		<form
			method="post"
			action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"
			class="tutorhelper-availability-form"
		>
			<input
				type="hidden"
				name="action"
				value="<?php echo esc_attr( self::SAVE_ACTION ); ?>"
			/>

			<input
				type="hidden"
				name="availability_id"
				value="<?php echo esc_attr( $values['id'] ); ?>"
			/>

			<?php self::render_return_fields(); ?>

			<?php
			wp_nonce_field(
				self::SAVE_ACTION
			);
			?>

			<h3>
				<?php
				echo esc_html(
					$is_editing
						? __(
							'Edit Availability Blocks',
							'tutorhelper-bookings'
						)
						: __(
							'Add Availability',
							'tutorhelper-bookings'
						)
				);
				?>
			</h3>

			<p class="description">
				<?php
				echo esc_html(
					$is_editing
						? __(
							'Choose whether these changes apply to this date, a date range or the complete Availability block.',
							'tutorhelper-bookings'
						)
						: __(
							'Create a recurring window in which students may book lessons.',
							'tutorhelper-bookings'
						)
				);
				?>
			</p>

			<?php if ( $is_editing ) : ?>
				<fieldset class="tutorhelper-availability-edit-scope">
					<legend>
						<strong>
							<?php
							echo esc_html__(
								'Apply these changes to',
								'tutorhelper-bookings'
							);
							?>
						</strong>
					</legend>

					<label class="tutorhelper-edit-scope-option">
						<input
							type="radio"
							name="edit_scope"
							value="single_date"
							checked
						/>

						<span>
							<strong>
								<?php
								echo esc_html__(
									'This date only',
									'tutorhelper-bookings'
								);
								?>
							</strong>

							<small>
								<?php
								echo esc_html(
									$occurrence_date
										? wp_date(
											'j F Y',
											strtotime( $occurrence_date )
										)
										: __(
											'The selected calendar occurrence',
											'tutorhelper-bookings'
										)
								);
								?>
							</small>
						</span>
					</label>

					<label class="tutorhelper-edit-scope-option">
						<input
							type="radio"
							name="edit_scope"
							value="date_range"
						/>

						<span>
							<strong>
								<?php
								echo esc_html__(
									'A date range',
									'tutorhelper-bookings'
								);
								?>
							</strong>

							<small>
								<?php
								echo esc_html__(
									'Update matching occurrences between two dates.',
									'tutorhelper-bookings'
								);
								?>
							</small>
						</span>
					</label>

					<div
						class="tutorhelper-edit-range"
						data-edit-range
						hidden
					>
						<p>
							<label for="tutorhelper-edit-start-date">
								<strong>
									<?php
									echo esc_html__(
										'From date',
										'tutorhelper-bookings'
									);
									?>
								</strong>
							</label>

							<input
								type="date"
								id="tutorhelper-edit-start-date"
								name="edit_start_date"
								value="<?php echo esc_attr(
									$occurrence_date
										? $occurrence_date
										: $values['start_date']
								); ?>"
							/>
						</p>

						<p>
							<label for="tutorhelper-edit-end-date">
								<strong>
									<?php
									echo esc_html__(
										'To date',
										'tutorhelper-bookings'
									);
									?>
								</strong>
							</label>

							<input
								type="date"
								id="tutorhelper-edit-end-date"
								name="edit_end_date"
								value="<?php echo esc_attr(
									$occurrence_date
										? $occurrence_date
										: $values['end_date']
								); ?>"
							/>
						</p>
					</div>

					<label class="tutorhelper-edit-scope-option">
						<input
							type="radio"
							name="edit_scope"
							value="complete_rule"
						/>

						<span>
							<strong>
								<?php
								echo esc_html__(
									'All Availability in this set',
									'tutorhelper-bookings'
								);
								?>
							</strong>

							<small>
								<?php
								echo esc_html__(
									'Update the complete recurring rule.',
									'tutorhelper-bookings'
								);
								?>
							</small>
						</span>
					</label>
				</fieldset>

				<input
					type="hidden"
					name="occurrence_date"
					value="<?php echo esc_attr( $occurrence_date ); ?>"
				/>
			<?php endif; ?>


			<p>
				<label for="tutorhelper-availability-title">
					<strong>
						<?php
						echo esc_html__(
							'Title',
							'tutorhelper-bookings'
						);
						?>
					</strong>
				</label>

				<input
					type="text"
					id="tutorhelper-availability-title"
					name="title"
					class="widefat"
					value="<?php echo esc_attr( $values['title'] ); ?>"
					required
				/>
			</p>

			<div
				class="tutorhelper-recurrence-fields"
				data-recurrence-fields
			>

			<div class="tutorhelper-availability-form__dates">
				<p>
					<label for="tutorhelper-availability-start-date">
						<strong>
							<?php
							echo esc_html__(
								'Start date',
								'tutorhelper-bookings'
							);
							?>
						</strong>
					</label>

					<input
						type="date"
						id="tutorhelper-availability-start-date"
						name="start_date"
						value="<?php echo esc_attr( $values['start_date'] ); ?>"
						required
					/>
				</p>

				<p>
					<label for="tutorhelper-availability-end-date">
						<strong>
							<?php
							echo esc_html__(
								'End date',
								'tutorhelper-bookings'
							);
							?>
						</strong>
					</label>

					<input
						type="date"
						id="tutorhelper-availability-end-date"
						name="end_date"
						value="<?php echo esc_attr( $values['end_date'] ); ?>"
					/>

					<span class="description">
						<?php
						echo esc_html__(
							'Leave blank for no fixed end date.',
							'tutorhelper-bookings'
						);
						?>
					</span>
				</p>
			</div>

			<fieldset>
				<legend>
					<strong>
						<?php
						echo esc_html__(
							'Weekdays',
							'tutorhelper-bookings'
						);
						?>
					</strong>
				</legend>

				<div class="tutorhelper-weekday-options">
					<?php
					foreach (
						self::get_weekday_labels()
						as $key => $label
					) {
						?>
						<label>
							<input
								type="checkbox"
								name="weekdays[]"
								value="<?php echo esc_attr( $key ); ?>"
								<?php
								checked(
									in_array(
										$key,
										$values['weekdays'],
										true
									)
								);
								?>
							/>

							<span><?php echo esc_html( $label ); ?></span>
						</label>
						<?php
					}
					?>
				</div>
			</fieldset>
			</div>

			<div class="tutorhelper-availability-form__times">
				<p>
					<label for="tutorhelper-availability-start-time">
						<strong>
							<?php
							echo esc_html__(
								'Start time',
								'tutorhelper-bookings'
							);
							?>
						</strong>
					</label>

					<input
						type="time"
						id="tutorhelper-availability-start-time"
						name="start_time"
						value="<?php echo esc_attr( $values['start_time'] ); ?>"
						required
					/>
				</p>

				<p>
					<label for="tutorhelper-availability-end-time">
						<strong>
							<?php
							echo esc_html__(
								'End time',
								'tutorhelper-bookings'
							);
							?>
						</strong>
					</label>

					<input
						type="time"
						id="tutorhelper-availability-end-time"
						name="end_time"
						value="<?php echo esc_attr( $values['end_time'] ); ?>"
						required
					/>
				</p>
			</div>

			<fieldset>
				<legend>
					<strong>
						<?php
						echo esc_html__(
							'Available locations',
							'tutorhelper-bookings'
						);
						?>
					</strong>
				</legend>

				<div class="tutorhelper-location-options">
					<?php
					foreach (
						self::get_location_labels()
						as $key => $label
					) {
						?>
						<label>
							<input
								type="checkbox"
								name="location_types[]"
								value="<?php echo esc_attr( $key ); ?>"
								<?php
								checked(
									in_array(
										$key,
										$values['location_types'],
										true
									)
								);
								?>
							/>

							<span><?php echo esc_html( $label ); ?></span>
						</label>
						<?php
					}
					?>
				</div>
			</fieldset>

			<p>
				<label for="tutorhelper-primary-location">
					<strong>
						<?php
						echo esc_html__(
							'Primary location',
							'tutorhelper-bookings'
						);
						?>
					</strong>
				</label>

				<select
					id="tutorhelper-primary-location"
					name="location_type"
					class="widefat"
				>
					<?php
					foreach (
						self::get_location_labels()
						as $key => $label
					) {
						?>
						<option
							value="<?php echo esc_attr( $key ); ?>"
							<?php
							selected(
								$values['location_type'],
								$key
							);
							?>
						>
							<?php echo esc_html( $label ); ?>
						</option>
						<?php
					}
					?>
				</select>
			</p>

			<p>
				<label for="tutorhelper-location-details">
					<strong>
						<?php
						echo esc_html__(
							'Location details',
							'tutorhelper-bookings'
						);
						?>
					</strong>
				</label>

				<textarea
					id="tutorhelper-location-details"
					name="location_details"
					rows="3"
					class="widefat"
				><?php echo esc_textarea( $values['location_details'] ); ?></textarea>
			</p>

			<p>
                <label for="tutorhelper-availability-booking-types">
                    <strong>
                        <?php
                        echo esc_html__(
                            'Permitted Booking Types',
                            'tutorhelper-bookings'
                        );
                        ?>
                    </strong>
                </label>

                <div
                    class="tutorhelper-booking-type-multiselect"
                    id="tutorhelper-allowed-booking-type-picker"
                >
                    <label>
                        <strong>
                            <?php
                            echo esc_html__(
                                'Permitted Booking Types',
                                'tutorhelper-bookings'
                            );
                            ?>
                        </strong>
                    </label>

                    <p class="description">
                        <?php
                        echo esc_html__(
                            'Choose which Booking Types can use this Availability. Leave every option unselected to allow all active Booking Types.',
                            'tutorhelper-bookings'
                        );
                        ?>
                    </p>

                    <button
                        type="button"
                        class="button tutorhelper-booking-type-multiselect__toggle"
                        id="tutorhelper-allowed-booking-type-toggle"
                        aria-expanded="false"
                        aria-controls="tutorhelper-allowed-booking-type-panel"
                    >
                        <?php
                        echo esc_html__(
                            'All Booking Types',
                            'tutorhelper-bookings'
                        );
                        ?>
                    </button>

                    <div
                        class="tutorhelper-booking-type-multiselect__panel"
                        id="tutorhelper-allowed-booking-type-panel"
                        hidden
                    >
                        <div class="tutorhelper-booking-type-multiselect__summary">
                            <strong>
                                <?php
                                echo esc_html__(
                                    'Selected:',
                                    'tutorhelper-bookings'
                                );
                                ?>
                            </strong>

                            <div
                                class="tutorhelper-booking-type-multiselect__chips"
                                id="tutorhelper-allowed-booking-type-chips"
                                aria-live="polite"
                            >
                                <span class="tutorhelper-booking-type-chip is-empty">
                                    <?php
                                    echo esc_html__(
                                        'All Booking Types',
                                        'tutorhelper-bookings'
                                    );
                                    ?>
                                </span>
                            </div>
                        </div>

                        <div class="tutorhelper-booking-type-multiselect__actions">
                            <button
                                type="button"
                                class="button button-small"
                                id="tutorhelper-clear-allowed-booking-types"
                            >
                                <?php
                                echo esc_html__(
                                    'Allow all Booking Types',
                                    'tutorhelper-bookings'
                                );
                                ?>
                            </button>
                        </div>

                        <div
                            class="tutorhelper-booking-type-multiselect__options"
                            id="tutorhelper-allowed-booking-type-options"
                        >
                            <?php if ( empty( $booking_types ) ) : ?>

                                <p>
                                    <?php
                                    echo esc_html__(
                                        'No active Booking Types are available.',
                                        'tutorhelper-bookings'
                                    );
                                    ?>
                                </p>

                            <?php else : ?>

                                <?php foreach ( $booking_types as $booking_type ) : ?>
                                    <?php
                                    $booking_type_id = absint(
                                        $booking_type->id
                                    );

                                    $is_selected = in_array(
                                        $booking_type_id,
                                        $values['booking_type_ids'],
                                        true
                                    );
                                    ?>

                                    <label
                                        class="tutorhelper-booking-type-option<?php echo $is_selected ? ' is-selected' : ''; ?>"
                                        data-booking-type-option
                                        data-booking-type-id="<?php echo esc_attr( $booking_type_id ); ?>"
                                        data-booking-type-title="<?php echo esc_attr( $booking_type->title ); ?>"
                                    >
                                        <input
                                            type="checkbox"
                                            name="allowed_booking_type_ids[]"
                                            value="<?php echo esc_attr( $booking_type_id ); ?>"
                                            <?php checked( $is_selected ); ?>
                                        />

                                        <span>
                                            <?php
                                            echo esc_html(
                                                $booking_type->title
                                            );
                                            ?>
                                        </span>
                                    </label>
                                <?php endforeach; ?>

                            <?php endif; ?>
                        </div>
                    </div>
                </div>

                <span class="description">
                    <?php
                    echo esc_html__(
                        'Hold Ctrl on Windows or Command on macOS to select several options. Leave all unselected to permit every active Booking Type.',
                        'tutorhelper-bookings'
                    );
                    ?>
                </span>
            </p>

			<?php
			submit_button(
				$is_editing
					? __(
						'Update Availability',
						'tutorhelper-bookings'
					)
					: __(
						'Add Availability',
						'tutorhelper-bookings'
					),
				'primary',
				'submit',
				false
			);
			?>

			<?php if ( $is_editing ) : ?>
				<a
					class="button"
					href="<?php echo esc_url( self::get_hub_url() ); ?>"
				>
					<?php
					echo esc_html__(
						'Cancel edit',
						'tutorhelper-bookings'
					);
					?>
				</a>
			<?php endif; ?>
		</form>
		<?php
		if ( $is_editing ) {
			self::render_removal_form(
				$rule,
				$occurrence_date
			);
		}
	}


	/**
	 * Render scoped Availability-removal controls.
	 *
	 * @param object $rule            Availability rule.
	 * @param string $occurrence_date Date clicked in the calendar.
	 *
	 * @return void
	 */
	private static function render_removal_form(
		$rule,
		$occurrence_date
	) {
		$default_date = ! empty( $occurrence_date )
			? $occurrence_date
			: $rule->start_date;

		?>
		<div class="tutorhelper-availability-removal">
			<h3>
				<?php
				echo esc_html__(
					'Remove Availability',
					'tutorhelper-bookings'
				);
				?>
			</h3>

			<p class="description">
				<?php
				echo esc_html__(
					'Dates containing existing bookings will be left unchanged.',
					'tutorhelper-bookings'
				);
				?>
			</p>

			<form
				method="post"
				action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"
			>
				<input
					type="hidden"
					name="action"
					value="<?php echo esc_attr( self::REMOVE_ACTION ); ?>"
				/>

				<input
					type="hidden"
					name="availability_id"
					value="<?php echo esc_attr( $rule->id ); ?>"
				/>

				<input
					type="hidden"
					name="occurrence_date"
					value="<?php echo esc_attr( $default_date ); ?>"
				/>

				<?php self::render_return_fields(); ?>

				<?php
				wp_nonce_field(
					self::REMOVE_ACTION
				);
				?>

				<fieldset>
					<legend class="screen-reader-text">
						<?php
						echo esc_html__(
							'Choose what to remove',
							'tutorhelper-bookings'
						);
						?>
					</legend>

					<label class="tutorhelper-removal-option">
						<input
							type="radio"
							name="remove_scope"
							value="single_date"
							checked
						/>

						<span>
							<strong>
								<?php
								echo esc_html__(
									'This date only',
									'tutorhelper-bookings'
								);
								?>
							</strong>

							<small>
								<?php
								echo esc_html(
									wp_date(
										'j F Y',
										strtotime( $default_date )
									)
								);
								?>
							</small>
						</span>
					</label>

					<label class="tutorhelper-removal-option">
						<input
							type="radio"
							name="remove_scope"
							value="date_range"
						/>

						<span>
							<strong>
								<?php
								echo esc_html__(
									'A date range',
									'tutorhelper-bookings'
								);
								?>
							</strong>

							<small>
								<?php
								echo esc_html__(
									'Remove matching occurrences between two dates.',
									'tutorhelper-bookings'
								);
								?>
							</small>
						</span>
					</label>

					<div
						class="tutorhelper-removal-range"
						data-removal-range
						hidden
					>
						<p>
							<label for="tutorhelper-remove-start-date">
								<strong>
									<?php
									echo esc_html__(
										'From date',
										'tutorhelper-bookings'
									);
									?>
								</strong>
							</label>

							<input
								type="date"
								id="tutorhelper-remove-start-date"
								name="remove_start_date"
								value="<?php echo esc_attr( $default_date ); ?>"
							/>
						</p>

						<p>
							<label for="tutorhelper-remove-end-date">
								<strong>
									<?php
									echo esc_html__(
										'To date',
										'tutorhelper-bookings'
									);
									?>
								</strong>
							</label>

							<input
								type="date"
								id="tutorhelper-remove-end-date"
								name="remove_end_date"
								value="<?php echo esc_attr( $default_date ); ?>"
							/>
						</p>
					</div>

					<label class="tutorhelper-removal-option is-destructive">
						<input
							type="radio"
							name="remove_scope"
							value="complete_rule"
						/>

						<span>
							<strong>
								<?php
								echo esc_html__(
									'The complete recurring rule',
									'tutorhelper-bookings'
								);
								?>
							</strong>

							<small>
								<?php
								echo esc_html__(
									'This removes every current and future occurrence generated by the rule.',
									'tutorhelper-bookings'
								);
								?>
							</small>
						</span>
					</label>
				</fieldset>

				<?php
				submit_button(
					__(
						'Remove Availability',
						'tutorhelper-bookings'
					),
					'delete',
					'submit',
					false
				);
				?>
			</form>
		</div>
		<?php
	}

	

	/**
	 * Render a weekly Availability block.
	 *
	 * @param array $occurrence Occurrence data.
	 *
	 * @return void
	 */
	private static function render_week_occurrence( $occurrence ) {
		?>
		<div class="tutorhelper-availability-block">
			<a
				class="tutorhelper-availability-block__edit"
				href="<?php echo esc_url( self::get_edit_url( $occurrence['availability_id'], $occurrence['date'] ) ); ?>"
			>
				<div class="tutorhelper-availability-block__time">
					<?php
					echo esc_html(
						self::format_time_range(
							$occurrence['start_time'],
							$occurrence['end_time']
						)
					);
					?>
				</div>

				<div class="tutorhelper-availability-block__title">
					<?php echo esc_html( $occurrence['title'] ); ?>
				</div>
			</a>

			<div class="tutorhelper-availability-block__meta">
				<?php
				echo esc_html(
					self::format_locations(
						$occurrence['location_types']
					)
				);
				?>
			</div>

			<div class="tutorhelper-availability-block__pill">
				<?php
				echo esc_html(
					self::format_booking_types(
						$occurrence['allowed_booking_type_ids']
					)
				);
				?>
			</div>
		</div>
		<?php
	}

	/**
	 * Return cached occurrences for one date.
	 *
	 * @param string $date_string Date in Y-m-d format.
	 *
	 * @return array
	 */
	private static function get_occurrences_for_date( $date_string ) {
		if ( null === self::$occurrences_by_date ) {
			self::load_occurrences();
		}

		return isset( self::$occurrences_by_date[ $date_string ] )
			? self::$occurrences_by_date[ $date_string ]
			: array();
	}

	/**
	 * Load the visible range once and group occurrences by date.
	 *
	 * @return void
	 */
	private static function load_occurrences() {
		self::$occurrences_by_date = array();

		$view          = TutorHelper_Booking_Hub::get_selected_view();
		$selected_date = TutorHelper_Booking_Hub::get_selected_date();

		if ( 'month' === $view ) {
			$weeks = TutorHelper_Booking_Hub::get_month_grid(
				$selected_date
			);

			$first_week  = reset( $weeks );
			$last_week   = end( $weeks );
			$range_start = reset( $first_week );
			$range_end   = end( $last_week );
		} else {
			$dates       = TutorHelper_Booking_Hub::get_week_dates(
				$selected_date
			);
			$range_start = reset( $dates );
			$range_end   = end( $dates );
		}

		$occurrences = TutorHelper_Booking_Availability::get_occurrences(
			get_current_user_id(),
			$range_start->format( 'Y-m-d' ),
			$range_end->format( 'Y-m-d' )
		);

		if ( is_wp_error( $occurrences ) ) {
			TutorHelper_Logger::warning(
				'Availability occurrences could not be generated.',
				array(
					'error_code' => $occurrences->get_error_code(),
				)
			);

			return;
		}

		foreach ( $occurrences as $occurrence ) {
			$date = $occurrence['date'];

			if ( ! isset( self::$occurrences_by_date[ $date ] ) ) {
				self::$occurrences_by_date[ $date ] = array();
			}

			self::$occurrences_by_date[ $date ][] = $occurrence;
		}
	}

	/**
	 * Render an archive or restore form.
	 *
	 * @param object $rule Availability rule.
	 *
	 * @return void
	 */
	private static function render_status_form( $rule ) {
		$is_active = 1 === absint( $rule->is_active );

		?>
		<form
			method="post"
			action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"
		>
			<input
				type="hidden"
				name="action"
				value="<?php echo esc_attr( self::STATUS_ACTION ); ?>"
			/>

			<input
				type="hidden"
				name="availability_id"
				value="<?php echo esc_attr( $rule->id ); ?>"
			/>

			<input
				type="hidden"
				name="status_action"
				value="<?php echo esc_attr( $is_active ? 'archive' : 'restore' ); ?>"
			/>

			<?php self::render_return_fields(); ?>

			<?php
			wp_nonce_field(
				self::STATUS_ACTION
			);
			?>

			<button type="submit" class="button button-small">
				<?php
				echo esc_html(
					$is_active
						? __(
							'Archive',
							'tutorhelper-bookings'
						)
						: __(
							'Restore',
							'tutorhelper-bookings'
						)
				);
				?>
			</button>
		</form>
		<?php
	}

	/**
	 * Render hidden calendar state fields.
	 *
	 * @return void
	 */
	private static function render_return_fields() {
		?>
		<input
			type="hidden"
			name="return_view"
			value="<?php echo esc_attr( TutorHelper_Booking_Hub::get_selected_view() ); ?>"
		/>

		<input
			type="hidden"
			name="return_date"
			value="<?php echo esc_attr( TutorHelper_Booking_Hub::get_selected_date()->format( 'Y-m-d' ) ); ?>"
		/>
		<?php
	}

	/**
	 * Verify a protected administrator request.
	 *
	 * @param string $nonce_action Nonce action.
	 *
	 * @return void
	 */
	private static function verify_admin_request() {
		if (
			! current_user_can(
				TutorHelper_Admin::CAPABILITY
			)
		) {
			wp_die(
				esc_html__(
					'You do not have permission to manage Availability.',
					'tutorhelper-bookings'
				),
				esc_html__(
					'Permission denied',
					'tutorhelper-bookings'
				),
				array(
					'response' => 403,
				)
			);
		}
	}

	/**
	 * Redirect to the Booking Hub.
	 *
	 * @param string $notice          Notice code.
	 * @param string $error_code      Error code.
	 * @param int    $availability_id Editing ID.
	 *
	 * @return void
	 */
	private static function redirect(
		$notice,
		$error_code = '',
		$availability_id = 0
	) {
		$view = isset( $_POST['return_view'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Request value is read only after the calling admin/AJAX action has completed capability and nonce verification; value is sanitized before use.
			? sanitize_key(
				wp_unslash( $_POST['return_view'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Request value is read only after the calling admin/AJAX action has completed capability and nonce verification; value is sanitized before use.
			)
			: 'week';

		$date = isset( $_POST['return_date'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Request value is read only after the calling admin/AJAX action has completed capability and nonce verification; value is sanitized before use.
			? sanitize_text_field(
				wp_unslash( $_POST['return_date'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Request value is read only after the calling admin/AJAX action has completed capability and nonce verification; value is sanitized before use.
			)
			: wp_date( 'Y-m-d' );

		$args = array(
			'page'               => TutorHelper_Booking_Hub::PAGE_SLUG,
			'tab'                => 'calendar',
			'view'               => in_array(
				$view,
				array( 'week', 'month' ),
				true
			)
				? $view
				: 'week',
			'date'               => $date,
			'tutorhelper_notice' => sanitize_key( $notice ),
		);

		if ( $error_code ) {
			$args['tutorhelper_error'] = sanitize_key(
				$error_code
			);
		}

		if ( $availability_id ) {
			$args['availability_id'] = absint(
				$availability_id
			);
		}

		wp_safe_redirect(
			add_query_arg(
				$args,
				admin_url( 'admin.php' )
			)
		);

		exit;
	}

	/**
	 * Render an Availability notice.
	 *
	 * @return void
	 */
	private static function render_notice() {
		if ( ! isset( $_GET['tutorhelper_notice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			return;
		}

		$notice = sanitize_key(
			wp_unslash( $_GET['tutorhelper_notice'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
		);

		$notices = array(
			'availability_created' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'Availability was created.',
					'tutorhelper-bookings'
				),
			),

			'availability_updated' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'All Availability in this set was updated.',
					'tutorhelper-bookings'
				),
			),

			'availability_date_updated' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'The selected Availability date was updated.',
					'tutorhelper-bookings'
				),
			),

			'availability_range_updated' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'The selected Availability range was processed.',
					'tutorhelper-bookings'
				),
			),

			'availability_archived' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'Availability was archived.',
					'tutorhelper-bookings'
				),
			),

			'availability_restored' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'Availability was restored.',
					'tutorhelper-bookings'
				),
			),

			'availability_removed_rule' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'The complete Availability set was removed.',
					'tutorhelper-bookings'
				),
			),

			'availability_removed_dates' => array(
				'class'   => 'notice notice-success inline',
				'message' => __(
					'The selected Availability dates were processed.',
					'tutorhelper-bookings'
				),
			),

			'availability_error' => array(
				'class'   => 'notice notice-error inline',
				'message' => self::get_error_message(),
			),
		);

		if ( ! isset( $notices[ $notice ] ) ) {
			return;
		}

		echo '<div class="';
		echo esc_attr( $notices[ $notice ]['class'] );
		echo '"><p>';
		echo esc_html( $notices[ $notice ]['message'] );
		echo '</p></div>';

		if ( 'availability_range_updated' === $notice ) {
			self::render_edit_result_notice();
		}

		if ( 'availability_removed_dates' === $notice ) {
			self::render_removal_result_notice();
		}
	}



	/**
	 * Render details from a scoped Availability range edit.
	 *
	 * @return void
	 */
	private static function render_edit_result_notice() {
		$result = self::get_edit_result();

		if ( ! $result ) {
			return;
		}

		$updated_dates = isset( $result['updated_dates'] )
			? $result['updated_dates']
			: array();

		$skipped_dates = isset( $result['skipped_booked_dates'] )
			? $result['skipped_booked_dates']
			: array();

		$ignored_dates = isset( $result['ignored_dates'] )
			? $result['ignored_dates']
			: array();

		$updated_count = count( $updated_dates );

		?>
		<div class="notice notice-info inline">
			<p>
				<?php
				printf(
					esc_html(
						/* translators: %d: Number of Availability dates updated. */
						_n(
							'%d Availability date was updated.',
							'%d Availability dates were updated.',
							$updated_count,
							'tutorhelper-bookings'
						)
					),
					absint( $updated_count )
				);
				?>
			</p>

			<?php if ( ! empty( $skipped_dates ) ) : ?>
				<p>
					<strong>
						<?php
						printf(
							esc_html(
								/* translators: %d: Number of booked dates skipped. */
								_n(
									'%d date was left unchanged because it contains an existing booking:',
									'%d dates were left unchanged because they contain existing bookings:',
									count( $skipped_dates ),
									'tutorhelper-bookings'
								)
							),
							count( $skipped_dates )
						);
						?>
					</strong>
				</p>

				<ul>
					<?php foreach ( $skipped_dates as $skipped_date ) : ?>
						<li>
							<?php
							echo esc_html(
								self::format_notice_date(
									$skipped_date
								)
							);
							?>
						</li>
					<?php endforeach; ?>
				</ul>
			<?php endif; ?>

			<?php if ( ! empty( $ignored_dates ) ) : ?>
				<p>
					<?php
					printf(
						esc_html(
							/* translators: %d: Number of dates that could not be updated. */
							_n(
								'%d date could not be updated. Check TutorHelper Diagnostics for details.',
								'%d dates could not be updated. Check TutorHelper Diagnostics for details.',
								count( $ignored_dates ),
								'tutorhelper-bookings'
							)
						),
						count( $ignored_dates )
					);
					?>
				</p>
			<?php endif; ?>
		</div>
		<?php
	}



	/**
	 * Render detailed results from a scoped Availability removal.
	 *
	 * @return void
	 */
	private static function render_removal_result_notice() {
		$result = self::get_removal_result();

		if ( ! $result ) {
			return;
		}

		$removed_count = count(
			$result['removed_dates']
		);

		$skipped_dates = $result['skipped_booked_dates'];

		?>
		<div class="notice notice-info inline">
			<p>
				<?php
				printf(
					esc_html(
						/* translators: %d: Number of removed dates. */
						_n(
							'%d Availability date was removed.',
							'%d Availability dates were removed.',
							$removed_count,
							'tutorhelper-bookings'
						)
					),
					absint( $removed_count )
				);
				?>
			</p>

			<?php if ( ! empty( $skipped_dates ) ) : ?>
				<p>
					<strong>
						<?php
						printf(
							esc_html(
								/* translators: %d: Number of protected dates. */
								_n(
									'%d date was left unchanged because it contains an existing booking:',
									'%d dates were left unchanged because they contain existing bookings:',
									count( $skipped_dates ),
									'tutorhelper-bookings'
								)
							),
							count( $skipped_dates )
						);
						?>
					</strong>
				</p>

				<ul>
					<?php foreach ( $skipped_dates as $skipped_date ) : ?>
						<li>
							<?php
							echo esc_html(
								wp_date(
									'j F Y',
									self::format_notice_date( $skipped_date )
								)
							);
							?>
						</li>
					<?php endforeach; ?>
				</ul>
			<?php endif; ?>
		</div>
		<?php
	}



	/**
	 * Return a safe Availability error message.
	 *
	 * @return string
	 */
	private static function get_error_message() {
		$error_code = isset( $_GET['tutorhelper_error'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			? sanitize_key(
				wp_unslash( $_GET['tutorhelper_error'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only request state used for navigation or feedback; value is sanitized and no state change occurs.
			)
			: '';

		$messages = array(
			'tutorhelper_availability_missing_start_date' => __(
				'Please choose a start date.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_invalid_start_date' => __(
				'The start date is invalid.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_invalid_end_date' => __(
				'The end date is invalid.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_end_before_start' => __(
				'The end date cannot be earlier than the start date.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_missing_weekdays' => __(
				'Please choose at least one weekday.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_missing_times' => __(
				'Please enter both a start and end time.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_invalid_time_range' => __(
				'The end time must be later than the start time.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_not_found' => __(
				'The Availability rule could not be found.',
				'tutorhelper-bookings'
			),

			'tutorhelper_availability_invalid_edit_scope' => __(
				'The selected Availability edit option is invalid.',
				'tutorhelper-bookings'
			),

			'tutorhelper_exception_invalid_range' => __(
				'The selected date range is invalid.',
				'tutorhelper-bookings'
			),

			'tutorhelper_exception_invalid_date' => __(
				'The selected Availability date is invalid.',
				'tutorhelper-bookings'
			),

			'tutorhelper_exception_date_not_in_rule' => __(
				'The selected date is not part of this Availability set.',
				'tutorhelper-bookings'
			),

			'tutorhelper_exception_override_failed' => __(
				'The Availability changes could not be saved.',
				'tutorhelper-bookings'
			),

			'tutorhelper_exception_availability_not_found' => __(
				'The Availability set could not be found.',
				'tutorhelper-bookings'
			),
		);

		return isset( $messages[ $error_code ] )
			? $messages[ $error_code ]
			: __(
				'Availability could not be saved. Check TutorHelper Diagnostics for more information.',
				'tutorhelper-bookings'
			);
	}

	/**
	 * Return the canonical Hub URL.
	 *
	 * @return string
	 */
	private static function get_hub_url() {
		return add_query_arg(
			array(
				'page' => TutorHelper_Booking_Hub::PAGE_SLUG,
				'tab'  => 'calendar',
				'view' => TutorHelper_Booking_Hub::get_selected_view(),
				'date' => TutorHelper_Booking_Hub::get_selected_date()->format(
					'Y-m-d'
				),
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Return an Availability edit URL.
	 *
	 * @param int $availability_id Availability ID.
	 *
	 * @return string
	 */
	private static function get_edit_url(
        $availability_id,
        $occurrence_date = ''
    ) {
        $args = array(
            'page'            => TutorHelper_Booking_Hub::PAGE_SLUG,
            'tab'             => 'calendar',
            'view'            => TutorHelper_Booking_Hub::get_selected_view(),
            'date'            => TutorHelper_Booking_Hub::get_selected_date()
                ->format( 'Y-m-d' ),
            'availability_id' => absint( $availability_id ),
        );

        if ( ! empty( $occurrence_date ) ) {
            $args['occurrence_date'] = sanitize_text_field(
                $occurrence_date
            );
        }

        return add_query_arg(
            $args,
            admin_url( 'admin.php' )
        );
    }

	/**
	 * Format weekdays.
	 *
	 * @param string $weekdays Stored weekdays.
	 *
	 * @return string
	 */
	private static function format_weekdays( $weekdays ) {
		$labels = self::get_weekday_labels();
		$output = array();

		foreach ( self::split_csv( $weekdays ) as $weekday ) {
			if ( isset( $labels[ $weekday ] ) ) {
				$output[] = $labels[ $weekday ];
			}
		}

		return implode( ', ', $output );
	}



	/**
	 * Format a stored date for an administrator notice.
	 *
	 * @param string $date_string Date in Y-m-d format.
	 *
	 * @return string
	 */
	private static function format_notice_date( $date_string ) {
		$date = DateTimeImmutable::createFromFormat(
			'!Y-m-d',
			(string) $date_string,
			wp_timezone()
		);

		if ( false === $date ) {
			return (string) $date_string;
		}

		return wp_date(
			'j F Y',
			$date->getTimestamp(),
			wp_timezone()
		);
	}



	/**
	 * Format a time range.
	 *
	 * @param string $start_time Start time.
	 * @param string $end_time   End time.
	 *
	 * @return string
	 */
	private static function format_time_range(
		$start_time,
		$end_time
	) {
		return substr( $start_time, 0, 5 )
			. '–'
			. substr( $end_time, 0, 5 );
	}

	/**
	 * Format locations.
	 *
	 * @param array $locations Location identifiers.
	 *
	 * @return string
	 */
	private static function format_locations( $locations ) {
		$labels = self::get_location_labels();
		$output = array();

		foreach ( $locations as $location ) {
			if ( isset( $labels[ $location ] ) ) {
				$output[] = $labels[ $location ];
			}
		}

		return implode( ', ', $output );
	}

	/**
	 * Format permitted Booking Types.
	 *
	 * @param array $booking_type_ids Booking Type IDs.
	 *
	 * @return string
	 */
	private static function format_booking_types(
		$booking_type_ids
	) {
		if ( empty( $booking_type_ids ) ) {
			return __(
				'All Booking Types',
				'tutorhelper-bookings'
			);
		}

		$titles = array();

		foreach ( $booking_type_ids as $booking_type_id ) {
			$booking_type = TutorHelper_Booking_Types::get_by_id(
				$booking_type_id
			);

			if ( $booking_type ) {
				$titles[] = $booking_type->title;
			}
		}

		return empty( $titles )
			? __( 'Selected Booking Types', 'tutorhelper-bookings' )
			: implode( ', ', $titles );
	}

	/**
	 * Return weekday labels.
	 *
	 * @return array
	 */
	private static function get_weekday_labels() {
		return array(
			'mon' => __( 'Monday', 'tutorhelper-bookings' ),
			'tue' => __( 'Tuesday', 'tutorhelper-bookings' ),
			'wed' => __( 'Wednesday', 'tutorhelper-bookings' ),
			'thu' => __( 'Thursday', 'tutorhelper-bookings' ),
			'fri' => __( 'Friday', 'tutorhelper-bookings' ),
			'sat' => __( 'Saturday', 'tutorhelper-bookings' ),
			'sun' => __( 'Sunday', 'tutorhelper-bookings' ),
		);
	}

	/**
	 * Return location labels.
	 *
	 * @return array
	 */
	private static function get_location_labels() {
		return array(
			'online' => __(
				'Online',
				'tutorhelper-bookings'
			),

			'phone' => __(
				'Telephone',
				'tutorhelper-bookings'
			),

			'in_person' => __(
				'In person',
				'tutorhelper-bookings'
			),

			'other' => __(
				'Other',
				'tutorhelper-bookings'
			),
		);
	}

	/**
	 * Split a stored comma-separated value.
	 *
	 * @param string|null $value Stored value.
	 *
	 * @return array
	 */
	private static function split_csv( $value ) {
		if ( empty( $value ) ) {
			return array();
		}

		return array_values(
			array_filter(
				array_map(
					'trim',
					explode( ',', (string) $value )
				)
			)
		);
	}
}
