[Back]
<?php
/*
 * Copyright (c) 2022 LatePoint LLC. All rights reserved.
 */


class OsCalendarHelper {

    public static function generate_dates_and_times_picker(OsBookingModel $booking, OsWpDateTime $target_date, $auto_search = false, array $calendar_settings = []) : string{
        $is_recurring_supported = apply_filters('latepoint_is_feature_recurring_bookings_on', false);
        $can_service_be_recurring = $is_recurring_supported && ($booking->service->get_meta_by_key('allow_recurring_bookings') == 'on');
        ob_start(); ?>
        <div class="os-dates-and-times-w <?php echo $auto_search ? 'auto-search is-searching' : '' ; ?> calendar-style-<?php echo OsStepsHelper::get_calendar_style(); ?>" data-route="<?php echo esc_attr(OsRouterHelper::build_route_name('steps', 'load_datepicker_month')); ?>" data-allow-recurring="<?php echo $can_service_be_recurring ? 'yes' : 'no'; ?>">
            <div class="os-dates-w" data-time-pick-style="<?php echo esc_attr(OsStepsHelper::get_time_pick_style()); ?>">
                <?php if($auto_search){ ?>
                    <div class="os-calendar-searching-info"><?php echo sprintf(esc_html__( 'Searching %s for available dates', 'latepoint' ), '<span></span>'); ?></div>
                <?php } ?>
                <div class="os-calendar-while-searching-wrapper">
                    <?php OsCalendarHelper::generate_calendar_for_datepicker_step( \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking ), $target_date, $calendar_settings ); ?>
                </div>
            </div>

            <div class="time-selector-w <?php echo OsStepsHelper::hide_unavailable_slots() ? 'hide-not-available-slots' : ''; ?> <?php echo 'time-system-' . esc_attr(OsTimeHelper::get_time_system()); ?> <?php echo ( OsSettingsHelper::is_on( 'show_booking_end_time' ) ) ? 'with-end-time' : 'without-end-time'; ?> style-<?php echo esc_attr(OsStepsHelper::get_time_pick_style()); ?>">
                <div class="times-header">
                    <div class="th-line"></div>
                    <div class="times-header-label">
                        <?php esc_html_e( 'Pick a slot for', 'latepoint' ); ?> <span></span>
                        <?php do_action( 'latepoint_step_datepicker_appointment_time_header_label', $booking, $calendar_settings ); ?>
                    </div>
                    <div class="th-line"></div>
                </div>
                <div class="os-times-w">
                    <div class="timeslots"></div>
                </div>
            </div>
            <?php do_action( 'latepoint_dates_and_times_picker_after', $booking, $target_date, $calendar_settings ); ?>
        </div>
        <?php
        $html = ob_get_clean();
        return $html;
    }

	/**
	 * Get list of statuses which should not appear on calendar
	 *
	 * @return array
	 */
	public static function get_booking_statuses_hidden_from_calendar(): array {
		$statuses = explode( ',', OsSettingsHelper::get_settings_value( 'calendar_hidden_statuses', '' ) );

		/**
		 * Get list of statuses which bookings should not appear on calendar
		 *
		 * @param {array} $statuses array of status codes that will be hidden from calendar
		 * @returns {array} The filtered array of status codes
		 *
		 * @since 4.7.0
		 * @hook latepoint_get_booking_statuses_hidden_from_calendar
		 *
		 */
		return apply_filters( 'latepoint_get_booking_statuses_hidden_from_calendar', $statuses );
	}


	/**
	 * Returns an array of booking status codes to be displayed on calendar
	 *
	 * @return {array} The array of statuses
	 */
	public static function get_booking_statuses_to_display_on_calendar(): array {
		$hidden_statuses   = self::get_booking_statuses_hidden_from_calendar();
		$all_statuses      = OsBookingHelper::get_statuses_list();
		$eligible_statuses = [];
		foreach ( $all_statuses as $status_code => $status_label ) {
			if ( ! in_array( $status_code, $hidden_statuses ) ) {
				$eligible_statuses[] = $status_code;
			}
		}

		/**
		 * Returns an array of booking status codes to be displayed on calendar
		 *
		 * @param {array} array of statuses
		 *
		 * @returns {array} The array of statuses
		 *
		 * @since 4.7.0
		 * @hook latepoint_get_booking_statuses_to_display_on_calendar
		 *
		 */
		return apply_filters( 'latepoint_get_booking_statuses_to_display_on_calendar', $eligible_statuses );
	}

	public static function is_external_calendar_enabled( string $external_calendar_code ): bool {
		return OsSettingsHelper::is_on( 'enable_' . $external_calendar_code );
	}

	public static function get_list_of_external_calendars( $enabled_only = false ) {
		$external_calendars = [];

		/**
		 * Returns an array of external calendars
		 *
		 * @param {array} array of calendars
		 * @param {bool} filter to return only calendars that are enabled
		 *
		 * @returns {array} The array of external calendars
		 *
		 * @since 4.7.0
		 * @hook latepoint_list_of_external_calendars
		 *
		 */
		return apply_filters( 'latepoint_list_of_external_calendars', $external_calendars, $enabled_only );
	}


	/**
	 * @param \LatePoint\Misc\BookingRequest $booking_request
	 * @param DateTime $target_date
	 * @param array $settings
	 *
	 * @return void
	 */
	public static function generate_calendar_for_datepicker_step( \LatePoint\Misc\BookingRequest $booking_request, DateTime $target_date, array $settings = [] ) {
		$defaults = [
			'exclude_booking_ids'         => [],
			'number_of_months_to_preload' => 1,
			'timezone_name'               => false,
			'layout'                      => 'classic',
			'highlight_target_date'       => false,
			'consider_cart_items'         => false,
            'output_target_date_in_header' => false
		];

		$settings = OsUtilHelper::merge_default_atts( $defaults, $settings );

		$weekdays   = OsBookingHelper::get_weekdays_arr();
		$today_date = new OsWpDateTime( 'today' );


		?>
        <div class="os-current-month-label-w calendar-mobile-controls">
            <div class="os-current-month-label">
                <div class="current-month">
					<?php if ( $settings['highlight_target_date'] && $settings['output_target_date_in_header'] ) {
						echo esc_html( OsTimeHelper::get_nice_date_with_optional_year( $target_date->format( 'Y-m-d' ), false ) );
					} else {
						echo esc_html( OsUtilHelper::get_month_name_by_number( $target_date->format( 'n' ) ) );
					} ?>
                </div>
                <div class="current-year"><?php echo esc_html( $target_date->format( 'Y' ) ); ?></div>
            </div>
            <div class="os-month-control-buttons-w">
                <button type="button" class="os-month-prev-btn" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'steps', 'load_datepicker_month' ) ); ?>">
                    <i class="latepoint-icon latepoint-icon-arrow-left"></i></button>
				<?php if ( $settings['layout'] == 'horizontal' ) {
					echo '<button class="latepoint-btn latepoint-btn-outline os-month-today-btn" data-year="' . esc_attr( $today_date->format( 'Y' ) ) . '" data-month="' . esc_attr( $today_date->format( 'n' ) ) . '" data-date="' . esc_attr( $today_date->format( 'Y-m-d' ) ) . '">' . esc_html__( 'Today', 'latepoint' ) . '</button>';
				} ?>
                <button type="button" class="os-month-next-btn" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'steps', 'load_datepicker_month' ) ); ?>">
                    <i class="latepoint-icon latepoint-icon-arrow-right"></i></button>
            </div>
        </div>
		<?php if ( $settings['layout'] == 'classic' ) { ?>
            <div class="os-weekdays">
				<?php
				$start_of_week = OsSettingsHelper::get_start_of_week();

				// Output the divs for each weekday
				for ( $i = $start_of_week - 1; $i < $start_of_week - 1 + 7; $i ++ ) {
					// Calculate the index within the range of 0-6
					$index = $i % 7;

					// Output the div for the current weekday
					echo '<div class="weekday weekday-' . esc_attr( $index + 1 ) . '">' . esc_html( mb_substr($weekdays[ $index ], 0, 1) ) . '</div>';
				}
				?>
            </div>
		<?php } ?>
        <div class="os-months">
		<?php
		$month_settings = [
			'active'                => true,
			'timezone_name'         => $settings['timezone_name'],
			'highlight_target_date' => $settings['highlight_target_date'],
			'exclude_booking_ids'   => $settings['exclude_booking_ids'],
			'consider_cart_items'   => $settings['consider_cart_items']
		];

		// if it's not from admin - blackout dates that are not available to select due to date restrictions in settings
        $month_settings['earliest_possible_booking'] = OsSettingsHelper::get_earliest_possible_booking_restriction($booking_request->service_id);
        $month_settings['latest_possible_booking']   = OsSettingsHelper::get_latest_possible_booking_restriction($booking_request->service_id);

		OsCalendarHelper::generate_single_month( $booking_request, $target_date, $month_settings );
		for ( $i = 1; $i <= $settings['number_of_months_to_preload']; $i ++ ) {
			$next_month_target_date = clone $target_date;
			$next_month_target_date->modify( 'first day of next month' );
			$month_settings['active']                = false;
			$month_settings['highlight_target_date'] = false;
			OsCalendarHelper::generate_single_month( $booking_request, $next_month_target_date, $month_settings );
		}
		?>
        </div><?php
        /**
         * Fired after a datepicker calendar months are generated
         *
         * @param {BookingRequest} $booking_request instance of a booking request
         * @param {DateTime} $target_date target date that is being loaded
         * @param {array} $settings array of settings for the calendar
         *
         * @since 5.1.7
         * @hook latepoint_after_datepicker_months
         *
         */
        do_action('latepoint_after_datepicker_months', $booking_request, $target_date, $settings);
	}

	public static function generate_single_month( \LatePoint\Misc\BookingRequest $booking_request, DateTime $target_date, array $settings = [] ) {
		$defaults = [
			'accessed_from_backend'        => false,
			'active'                       => false,
			'layout'                       => 'classic',
			'highlight_target_date'        => false,
			'timezone_name'                => false,
			'earliest_possible_booking'    => false,
			'latest_possible_booking'      => false,
			'exclude_booking_ids'          => [],
			'consider_cart_items'          => false,
			'hide_slot_availability_count' => OsStepsHelper::hide_slot_availability_count()
		];
		$settings = OsUtilHelper::merge_default_atts( $defaults, $settings );


		// set service to the first available if not set
		// IMPORTANT, we have to have service in the booking request, otherwise we can't know duration and intervals
		$service = new OsServiceModel();
		$service = $service->where( [ 'id' => $booking_request->service_id ] )->set_limit( 1 )->get_results_as_models();
		if ( $service ) {
			if ( ! $booking_request->duration ) {
				$booking_request->duration = $service->duration;
			}
			$selectable_time_interval = $service->get_timeblock_interval();
		} else {
			echo '<div class="latepoint-message latepoint-message-error">' . esc_html__( 'In order to generate the calendar, a service must be selected.', 'latepoint' ) . '</div>';

			return;
		}


		# Get bounds for a month of a targetted day
		$calendar_start = clone $target_date;
		$calendar_start->modify( 'first day of this month' );
		$calendar_end = clone $target_date;
		$calendar_end->modify( 'last day of this month' );


		// if it's a classic layout - it means we need to load some days from previous and next month, to fill in blank spaces on the grid
		if ( $settings['layout'] == 'classic' ) {
			$weekday_for_first_day_of_month = intval( $calendar_start->format( 'N' ) );
			$weekday_for_last_day_of_month  = intval( $calendar_end->format( 'N' ) );

			$week_starts_on = OsSettingsHelper::get_start_of_week();
			$week_ends_on   = $week_starts_on > 1 ? $week_starts_on - 1 : 7;

			if ( $weekday_for_first_day_of_month != $week_starts_on ) {
				$days_to_subtract = ( $weekday_for_first_day_of_month - $week_starts_on + 7 ) % 7;
				if($days_to_subtract > 0){
                    $calendar_start->modify( '-' . $days_to_subtract . ' days' );
				}
			}

			if ( $weekday_for_last_day_of_month != $week_ends_on ) {
				$days_to_add = ( $weekday_for_last_day_of_month > $week_ends_on ) ? abs( 7 - $weekday_for_last_day_of_month + $week_ends_on ) : ( $week_ends_on - $weekday_for_last_day_of_month );
                if($days_to_add > 0){
                    $calendar_end->modify( '+' . $days_to_add . ' days' );
                }
			}
		}

		$now_datetime = OsTimeHelper::now_datetime_object();

		// figure out when the earliest and latest bookings can be placed
        try{
            $earliest_possible_booking = ( $settings['earliest_possible_booking'] ) ? new OsWpDateTime( $settings['earliest_possible_booking'] ) : clone $now_datetime;
            $latest_possible_booking   = ( $settings['latest_possible_booking'] ) ? new OsWpDateTime( $settings['latest_possible_booking'] ) : clone $calendar_end;
        }catch(Exception $e){

        }
		// make sure they are set correctly
		if ( empty($earliest_possible_booking) ) {
			$earliest_possible_booking = clone $now_datetime;
		}
		if ( empty($latest_possible_booking) ) {
			$latest_possible_booking = clone $calendar_end;
		}

		$date_range_start = ( $calendar_start->format( 'Y-m-d' ) > $earliest_possible_booking->format( 'Y-m-d' ) ) ? clone $calendar_start : clone $earliest_possible_booking;
		$date_range_end   = ( $calendar_end->format( 'Y-m-d' ) < $latest_possible_booking->format( 'Y-m-d' ) ) ? clone $calendar_end : clone $latest_possible_booking;

		// make sure date range is within the requested calendar range
		if ( ( $date_range_start->format( 'Y-m-d' ) >= $calendar_start->format( 'Y-m-d' ) )
		     && ( $date_range_end->format( 'Y-m-d' ) <= $calendar_end->format( 'Y-m-d' ) )
		     && ( $date_range_start->format( 'Y-m-d' ) <= $date_range_end->format( 'Y-m-d' ) ) ) {
			$daily_resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $date_range_start, $date_range_end, [
				'accessed_from_backend' => $settings['accessed_from_backend'],
				'exclude_booking_ids'   => $settings['exclude_booking_ids'],
				'consider_cart_items'   => $settings['consider_cart_items'],
                'timezone_name' => $settings['timezone_name'],
			] );
		} else {
			$daily_resources = [];
		}

		$active_class           = $settings['active'] ? 'active' : '';
		$hide_single_slot_class = OsStepsHelper::hide_timepicker_when_one_slot_available() ? 'hide-if-single-slot' : '';
		echo '<div class="os-monthly-calendar-days-w ' . esc_attr( $hide_single_slot_class . ' ' . $active_class ) . '" data-calendar-layout="' . esc_attr( $settings['layout'] ) . '" data-calendar-year="' . esc_attr( $target_date->format( 'Y' ) ) . '" data-calendar-month="' . esc_attr( $target_date->format( 'n' ) ) . '" data-calendar-month-label="' . esc_attr( OsUtilHelper::get_month_name_by_number( $target_date->format( 'n' ) ) ) . '">';
        echo '<div class="os-monthly-calendar-days">';
		// DAYS LOOP START
		for ( $day_date = clone $calendar_start; $day_date <= $calendar_end; $day_date->modify( '+1 day' ) ) {
			if ( ! isset( $daily_resources[ $day_date->format( 'Y-m-d' ) ] ) ) {
				$daily_resources[ $day_date->format( 'Y-m-d' ) ] = [];
			}

			$is_today              = ( $day_date->format( 'Y-m-d' ) == $now_datetime->format( 'Y-m-d' ) );
			$is_day_in_past        = ( $day_date->format( 'Y-m-d' ) < $now_datetime->format( 'Y-m-d' ) );
			$is_target_month       = ( $day_date->format( 'Ym' ) == $target_date->format( 'Ym' ) );
			$is_next_month         = ( $day_date->format( 'Ym' ) > $target_date->format( 'Ym' ) );
			$is_prev_month         = ( $day_date->format( 'Ym' ) < $target_date->format( 'Ym' ) );
			$not_in_allowed_period = false;

			if ( $day_date->format( 'Y-m-d' ) < $earliest_possible_booking->format( 'Y-m-d' ) ) {
				$not_in_allowed_period = true;
			}
			if ( $day_date->format( 'Y-m-d' ) > $latest_possible_booking->format( 'Y-m-d' ) ) {
				$not_in_allowed_period = true;
			}

			$work_minutes = [];

            if($settings['accessed_from_backend'] || !$not_in_allowed_period ){
                // only do this if is in allowed period or accessed from backend
                foreach ( $daily_resources[ $day_date->format( 'Y-m-d' ) ] as $resource ) {
                    if ( $is_day_in_past && $not_in_allowed_period ) {
                        continue;
                    }
                    $work_minutes = array_merge( $work_minutes, $resource->work_minutes );
                }
                $work_minutes = array_unique( $work_minutes, SORT_NUMERIC );
                sort( $work_minutes, SORT_NUMERIC );
            }



			$work_boundaries    = OsResourceHelper::get_work_boundaries_for_resources( $daily_resources[ $day_date->format( 'Y-m-d' ) ] );
			$total_work_minutes = $work_boundaries->end_time - $work_boundaries->start_time;

			$booking_slots = OsResourceHelper::get_ordered_booking_slots_from_resources( $daily_resources[ $day_date->format( 'Y-m-d' ) ] );

			$bookable_minutes = [];
            if($settings['accessed_from_backend'] || !$not_in_allowed_period ) {
	            // only do this if is in allowed period or accessed from backend
	            foreach ( $booking_slots as $booking_slot ) {
		            if ( $booking_slot->can_accomodate( $booking_request->total_attendees ) ) {
			            $bookable_minutes[ $booking_slot->start_time ] = isset( $bookable_minutes[ $booking_slot->start_time ] ) ? max( $booking_slot->available_capacity(), $bookable_minutes[ $booking_slot->start_time ] ) : $booking_slot->available_capacity();
		            }
	            }
	            ksort( $bookable_minutes );
            }
			$bookable_minutes_with_capacity_data = '';
			// this is a group service
			if ( $service->is_group_service() && ! $settings['hide_slot_availability_count'] ) {
				foreach ( $bookable_minutes as $minute => $available_capacity ) {
					$bookable_minutes_with_capacity_data .= $minute . ':' . $available_capacity . ',';
				}
			} else {
				foreach ( $bookable_minutes as $minute => $available_capacity ) {
					$bookable_minutes_with_capacity_data .= $minute . ',';
				}
			}
			$bookable_minutes_with_capacity_data = rtrim( $bookable_minutes_with_capacity_data, ',' );


			$bookable_slots_count = count( $bookable_minutes );
			// TODO use work minutes instead to calculate minimum gap
			$minimum_slot_gap = \LatePoint\Misc\BookingSlot::find_minimum_gap_between_slots( $booking_slots );
			$day_class = 'os-day os-day-current week-day-' . strtolower( $day_date->format( 'N' ) );
            $tabbable = true;
			if ( empty( $bookable_minutes ) ) {
				$day_class .= ' os-not-available';
                $tabbable = false;
			}
			if ( $is_today ) {
				$day_class .= ' os-today';
			}
			if ( $is_day_in_past ) {
				$day_class .= ' os-day-passed';
                $tabbable = false;
			}
			if ( $is_target_month ) {
				$day_class .= ' os-month-current';
			}
			if ( $is_next_month ) {
				$day_class .= ' os-month-next';
			}
			if ( $is_prev_month ) {
				$day_class .= ' os-month-prev';
			}
			if ( $not_in_allowed_period ) {
				$day_class .= ' os-not-in-allowed-period';
                $tabbable = false;
			}
			if ( count( $bookable_minutes ) == 1 && OsStepsHelper::hide_timepicker_when_one_slot_available() ) {
				$day_class .= ' os-one-slot-only';
			}
			if ( ( $day_date->format( 'Y-m-d' ) == $target_date->format( 'Y-m-d' ) ) && $settings['highlight_target_date'] ) {
				$day_class .= ' selected';
			}
			?>

            <div <?php if($tabbable) echo 'tabindex="0"'; ?> role="button" class="<?php echo esc_attr( $day_class ); ?>"
                 data-date="<?php echo esc_attr( $day_date->format( 'Y-m-d' ) ); ?>"
                 data-nice-date="<?php echo esc_attr( OsTimeHelper::get_nice_date_with_optional_year( $day_date->format( 'Y-m-d' ), false ) ); ?>"
                 data-service-duration="<?php echo esc_attr( $booking_request->duration ); ?>"
                 data-total-work-minutes="<?php echo esc_attr( $total_work_minutes ); ?>"
                 data-work-start-time="<?php echo esc_attr( $work_boundaries->start_time ); ?>"
                 data-work-end-time="<?php echo esc_attr( $work_boundaries->end_time ); ?>"
                 data-bookable-minutes="<?php echo esc_attr( $bookable_minutes_with_capacity_data ); ?>"
                 data-work-minutes="<?php echo esc_attr( implode( ',', $work_minutes ) ); ?>"
                 data-interval="<?php echo esc_attr( $selectable_time_interval ); ?>">
				<?php if ( $settings['layout'] == 'horizontal' ) { ?>
                    <div
                            class="os-day-weekday"><?php echo substr(esc_html( OsBookingHelper::get_weekday_name_by_number( $day_date->format( 'N' ) ) ), 0, 1); ?></div><?php } ?>
                <div class="os-day-box">
					<?php
					if ( $bookable_slots_count && ! $settings['hide_slot_availability_count'] ) {
                        // translators: %d is the number of slots available
                        echo '<div class="os-available-slots-tooltip">' . esc_html( sprintf( __( '%d Available', 'latepoint' ), $bookable_slots_count ) ) . '</div>';
					} ?>
                    <div class="os-day-number"><?php echo esc_html( $day_date->format( 'j' ) ); ?></div>
					<?php if ( ! $is_day_in_past && ! $not_in_allowed_period ) { ?>
                        <div class="os-day-status">
							<?php
							if ( $total_work_minutes > 0 && $bookable_slots_count ) {
								$available_blocks_count      = 0;
								$not_available_started_count = 0;
								$duration                    = $booking_request->duration;
								$end_time                    = $work_boundaries->end_time - $duration;
								$processed_count             = 0;
								$last_available_slot_time    = false;
								$bookable_ranges             = [];
								$loop_availability_status    = false;
								for ( $i = 0; $i < count( $booking_slots ); $i ++ ) {
									if ( $booking_slots[ $i ]->can_accomodate( $booking_request->total_attendees ) ) {
										// AVAILABLE SLOT
										if ( $loop_availability_status && $i > 0 && ( ( $booking_slots[ $i ]->start_time - $booking_slots[ $i - 1 ]->start_time ) > $minimum_slot_gap ) ) {
											// big gap between previous slot and this slot
											$bookable_ranges[] = $booking_slots[ $i - 1 ]->start_time + $minimum_slot_gap;
											$bookable_ranges[] = $booking_slots[ $i ]->start_time;
										}
										if ( ! $loop_availability_status ) {
											$bookable_ranges[] = $booking_slots[ $i ]->start_time;
										}
										$last_available_slot_time = $booking_slots[ $i ]->start_time;
										$loop_availability_status = true;
									} else {
										// NOT AVAILABLE
										// a different resource but with the same start time, so that if its available (checked in next loop iteration) - we don't block this slot
										if ( isset( $booking_slots[ $i + 1 ] ) && $booking_slots[ $i + 1 ]->start_time == $booking_slots[ $i ]->start_time ) {
											continue;
										}
										// check if last available slot had the same start time as current one, if so - we don't block this slot and move to the next one
										if ( $last_available_slot_time == $booking_slots[ $i ]->start_time && isset( $booking_slots[ $i - 1 ] ) && $booking_slots[ $i - 1 ]->start_time == $booking_slots[ $i ]->start_time ) {
											continue;
										}
										// if last available slot exists and previous slot was also available
										if ( $last_available_slot_time && $loop_availability_status ) {
											$bookable_ranges[] = $last_available_slot_time + $minimum_slot_gap;
										}
										$loop_availability_status = false;
									}
								}
								if ( $bookable_ranges ) {
									for ( $i = 0; $i < count( $bookable_ranges ); $i += 2 ) {
										$left  = ( $bookable_ranges[ $i ] - $work_boundaries->start_time ) / $total_work_minutes * 100;
										$width = isset( $bookable_ranges[ $i + 1 ] ) ? ( ( $bookable_ranges[ $i + 1 ] - $bookable_ranges[ $i ] ) / $total_work_minutes * 100 ) : ( ( $work_boundaries->end_time - $bookable_ranges[ $i ] ) / $total_work_minutes * 100 );
										echo '<div class="day-available" style="left:' . esc_attr( $left ) . '%;width:' . esc_attr( $width ) . '%;"></div>';
									}
								}
							}
							?>
                        </div>
					<?php } ?>
                </div>
            </div>

			<?php

			// DAYS LOOP END
		}
		echo '</div></div>';
	}

	// Used on holiday/custom schedule generator lightbox
	public static function generate_monthly_calendar_days_only( $target_date_string = 'today', $highlight_target_date = false, bool $is_active = false ) {
		$target_date    = new OsWpDateTime( $target_date_string );
		$calendar_start = clone $target_date;
		$calendar_start->modify( 'first day of this month' );
		$calendar_end = clone $target_date;
		$calendar_end->modify( 'last day of this month' );

		$weekday_for_first_day_of_month = $calendar_start->format( 'N' ) - 1;
		$weekday_for_last_day_of_month  = $calendar_end->format( 'N' ) - 1;


		if ( $weekday_for_first_day_of_month > 0 ) {
			$calendar_start->modify( '-' . $weekday_for_first_day_of_month . ' days' );
		}

		if ( $weekday_for_last_day_of_month < 6 ) {
			$days_to_add = 6 - $weekday_for_last_day_of_month;
            if($days_to_add > 0){
                $calendar_end->modify( '+' . $days_to_add . ' days' );
            }
		}

        $active_class = $is_active ? 'active' : '';

		echo '<div class="os-monthly-calendar-days-w '.$active_class.'" data-calendar-year="' . esc_attr( $target_date->format( 'Y' ) ) . '" data-calendar-month="' . esc_attr( $target_date->format( 'n' ) ) . '" data-calendar-month-label="' . esc_attr( OsUtilHelper::get_month_name_by_number( $target_date->format( 'n' ) ) ) . '">';
            echo '<div class="os-monthly-calendar-days">';
		for ( $day_date = clone $calendar_start; $day_date <= $calendar_end; $day_date->modify( '+1 day' ) ) {
			$is_today       = ( $day_date->format( 'Y-m-d' ) == OsTimeHelper::today_date() ) ? true : false;
			$is_day_in_past = ( $day_date->format( 'Y-m-d' ) < OsTimeHelper::today_date() ) ? true : false;
			$day_class      = 'os-day os-day-current week-day-' . strtolower( $day_date->format( 'N' ) );

			if ( $day_date->format( 'm' ) > $target_date->format( 'm' ) ) {
				$day_class .= ' os-month-next';
			}
			if ( $day_date->format( 'm' ) < $target_date->format( 'm' ) ) {
				$day_class .= ' os-month-prev';
			}

			if ( $is_today ) {
				$day_class .= ' os-today';
			}
			if ( $highlight_target_date && ( $day_date->format( 'Y-m-d' ) == $target_date->format( 'Y-m-d' ) ) ) {
				$day_class .= ' selected';
			}
			if ( $is_day_in_past ) {
				$day_class .= ' os-day-passed';
			} ?>
        <div class="<?php echo esc_attr( $day_class ); ?>" data-date="<?php echo esc_attr( $day_date->format( 'Y-m-d' ) ); ?>">
            <div class="os-day-box">
                <div class="os-day-number"><?php echo esc_html( $day_date->format( 'j' ) ); ?></div>
            </div>
            </div><?php
		}
		echo '</div></div>';
	}

	public static function generate_calendar_quick_actions_link( OsWpDateTime $day_date, array $settings = [] ) : string {
        $defaults = [
            'agent_id' => 0,
            'location_id' => 0,
            'service_id' => 0,
            'start_time' => 600
        ];

		$settings = OsUtilHelper::merge_default_atts( $defaults, $settings );

        return '<a href="#" data-os-after-call="latepoint_init_calendar_quick_actions" data-os-lightbox-classes="width-400" class="day-action-trigger" data-os-output-target="lightbox" data-os-params="'.OsUtilHelper::build_os_params(['target_date' => $day_date->format('Y-m-d'), 'start_time' => $settings['start_time'], 'agent_id' => $settings['agent_id'], 'location_id' => $settings['location_id'], 'service_id' => $settings['service_id']]).'" data-os-action="'.OsRouterHelper::build_route_name('calendars', 'quick_actions').'"></a>';
	}


}