<?php
namespace JET_APB;

/**
 * Time slots generator class
 */
class Time_Slots {

	private static $starting_point = false;
	private static $timenow        = false;

	public static $current_timezone     = null;
	public static $current_timezone_raw = null;

	/**
	 * Returns current starting point
	 *
	 * @return int
	 */
	public static function get_starting_point() {

		if ( ! self::$starting_point ) {
			self::$starting_point = strtotime( 'today midnight' );
		}

		return self::$starting_point;
	}

	/**
	 * Returns current starting point
	 *
	 * @return int
	 */
	public static function set_starting_point( $timestamp ) {
		self::$starting_point = $timestamp;
	}

	public static function set_timezone( $tz_string ) {

		// fix DST offsets
		if ( true === apply_filters( 'jet-apb/time-slots/fix-dst', false ) ) {
			
			$fix_dst = array(
				'America/Mexico_City' => 'UTC-6',
				'America/Monterrey'   => 'UTC-6',
			);

			$now = time();

			if ( isset( $fix_dst[ $tz_string ] )
				&& strtotime( 'March, 26, 3:00AM' ) <= $now 
				&& $now <= strtotime( 'October 29, 4:00AM' ) 
			) {
				$tz_string = $fix_dst[ $tz_string ];
			}

		}

		$tz = $tz_string;

		if ( false !== strpos( $tz_string, 'UTC+' ) || false !== strpos( $tz_string, 'UTC-' ) ) {
			$tz      = str_replace( 'UTC', '', $tz );
			$offset  = (float) $tz;
			$hours   = (int) $offset;
			$minutes = ( $offset - $hours );

			$sign     = ( $offset < 0 ) ? '-' : '+';
			$abs_hour = abs( $hours );
			$abs_mins = abs( $minutes * 60 );
			$tz       = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
		}

		self::$current_timezone_raw = $tz_string;
		self::$current_timezone     = new \DateTimeZone( $tz );
	}

	/**
	 * Returns current starting point
	 *
	 * @return int
	 */
	public static function set_timenow( $timestamp ) {
		self::$timenow = $timestamp;
	}

	/**
	 * Get timezone offset for slot timestamp.
	 *
	 * Offset is cached by timezone + UTC day of slot timestamp.
	 *
	 * @param string|int|float|\DateTimeZone $timezone
	 * @param int                            $timestamp
	 * @param array                          $transitions_cache
	 * @param array                          $offsets_cache
	 *
	 * @return int
	 */
	public static function get_slot_offset( $timezone, $timestamp, array &$transitions_cache = [], array &$offsets_cache = [] ) {

		$timestamp = (int) $timestamp;

		// DateTimeZone object
		if ( $timezone instanceof \DateTimeZone ) {
			$tz      = $timezone;
			$tz_name = $tz->getName();
		}

		// Numeric GMT offset like 1, -1, 2.5
		elseif ( is_numeric( $timezone ) ) {
			return (int) ( (float) $timezone * HOUR_IN_SECONDS );
		}

		// String timezone
		else {
			$tz_name = (string) $timezone;

			// Numeric string like "1", "-1", "2.5"
			if ( is_numeric( $tz_name ) ) {
				return (int) ( (float) $tz_name * HOUR_IN_SECONDS );
			}

			$tz = new \DateTimeZone( $tz_name );
		}

		$cache_key = $tz_name . ':' . gmdate( 'Ymd', $timestamp );

		if ( isset( $offsets_cache[ $cache_key ] ) ) {
			return (int) $offsets_cache[ $cache_key ];
		}

		if ( ! isset( $transitions_cache[ $cache_key ] ) ) {
			$transitions_cache[ $cache_key ] = $tz->getTransitions(
				$timestamp,
				$timestamp + DAY_IN_SECONDS
			);
		}

		$transitions = $transitions_cache[ $cache_key ];

		if ( ! empty( $transitions ) && isset( $transitions[0]['offset'] ) ) {
			$offsets_cache[ $cache_key ] = (int) $transitions[0]['offset'];
		} else {
			$datetime = new \DateTimeImmutable( '@' . $timestamp );
			$offsets_cache[ $cache_key ] = (int) $tz->getOffset( $datetime );
		}

		return (int) $offsets_cache[ $cache_key ];
	}

	/**
	 * Generate time slots array
	 *
	 * @param  array  $args [description]
	 * @return [type]       [description]
	 */
	public static function generate_slots( $args = array() ) {

		$args = wp_parse_args( $args, array(
			'from'     => 0,
			'to'       => 0,
			'interval' => 30 * MINUTE_IN_SECONDS,
			'format'   => 'G:i',
			'from_now' => false,
		) );

		$starting_point = self::get_starting_point();
		$result         = array();
		$from           = ! empty( $args['from'] ) ? $starting_point + $args['from'] : $starting_point;
		$to             = ! empty( $args['to'] ) ? $starting_point + $args['to'] : $starting_point + DAY_IN_SECONDS;
		$timestamp      = $from;

		if ( $args['from_now'] && self::$timenow ) {
			$from = self::$timenow;
		}

		if ( ! is_integer( $timestamp ) ) {
			return $result;
		}

		while ( $timestamp <= $to ) {
			$result[]  = date( $args['format'], $timestamp );
			$timestamp += $args['interval'];
		}

		return $result;

	}

	/**
	 * Generate intervals
	 *
	 * @param  array  $args [description]
	 * @return [type]       [description]
	 */
	public static function generate_intervals( $args = array() ) {

		$args = wp_parse_args( $args, array(
			'from'          => '00:00',
			'to'            => '24:00',
			'duration'      => HOUR_IN_SECONDS,
			'buffer_before' => 0,
			'buffer_after'  => 0,
			'from_now'      => false, 
			'selected'      => [],
			'service'       => 0,
			'provider'      => 0,
		) );

		$result   = array();
		$from     = self::get_timestamp_from_time( $args['from'] );
		$to       = self::get_timestamp_from_time( $args['to'] );
		$i        = $from;
		$em_brake = 0;

		if ( $args['from_now'] && self::$timenow ) {
			while ( $i < self::$timenow ) {
				$i = $i + $args['buffer_before'] + $args['duration'] + $args['buffer_after'];
			}
		}

		if ( ! is_integer( $from ) || ! is_integer( $to ) ) {
			return $result;
		}

		if ( ! is_integer( $i ) || ! is_integer( $to ) ) {
			return $result;
		}

		$duration = ! empty( $args['duration'] ) ? absint( $args['duration'] ) : 0;

		if ( empty( $duration ) ) {
			return $result;
		}

		$buffer_before = ! empty( $args['buffer_before'] ) ? absint( $args['buffer_before'] ) : 0;
		$buffer_after  = ! empty( $args['buffer_after'] ) ? absint( $args['buffer_after'] ) : 0;

		while ( $i < $to ) {

			$start = $i + $buffer_before;
			$end   = $start + $duration;
			$i     = $end + $buffer_after;

			if ( $start >= $to ) {
				break;
			}

			if ( $end >= $to ) {
				$end = $to;
			}

			foreach( $args['selected'] as $selected ) {
				if ( $selected->slot === $start && Calendar::is_valid_excluded( $args['service'], $selected->service, $args['provider'], $selected->provider ) ) {
					$isSelected = true;
					break;
				} else {
					$isSelected = false;
				}
			}

			$result[ $start ] = array(
				'from'     => $start,
				'to'       => $end,
				'selected' => ! empty( $isSelected ) ? $isSelected : false,
			);

			$em_brake++;

			if ( self::get_max_slots_number() < $em_brake ) {
				break;
			}

		}

		return $result;
	}

	/**
	 * Returns maximum number of slots to generate per day to avoid inifinity loopings
	 * @return [type] [description]
	 */
	public static function get_max_slots_number() {
		return apply_filters( 'jet-apb/time-slots/max-slots', 250 );
	}

	/**
	 * Generate slots HTML markup
	 *
	 * @param  array  $slots  [description]
	 * @param  string $format [description]
	 * @return [type]         [description]
	 */
	public static function generate_slots_html( $slots = array(), $format = 'H:i', $dataset = array(), $date = '', $service = false,
	$provider = 0 ) {

		$offset = null;
		$timezone_transitions = [];
		$timezone_offsets     = [];

		if ( self::$current_timezone_raw ) {
			$dataset[] = 'data-timezone="' . self::$current_timezone_raw . '"';
		}

		/**
		 * Filter slots data list before processing
		 */
		$slots = apply_filters(
			'jet-apb/time-slots/slots-html/slots-list',
			$slots, $format, $dataset, $date, $service, $provider
		);

		$manage_capacity = Plugin::instance()->settings->get( 'manage_capacity' );
		$show_counter    = Plugin::instance()->settings->get( 'show_capacity_counter' );
		$date_translated = wp_date( get_option( 'date_format' ), $date, new \DateTimeZone( '+0000' ) );
		$check_date      = date( 'Ymd', $date );
		$date            = date( get_option( 'date_format' ), $date );

		/**
		 * Available values:
		 * %1$d - booked num
		 * %2$d - total num
		 * %3$d - available num
		 */
		$capacity_format = apply_filters(
			'jet-apb/time-slots/slots-html/capacity-format',
			'<small>(%3$d/%2$d)</small>'
		);

		if ( $manage_capacity && $service ) {
			$service_count = Plugin::instance()->tools->get_service_count( $service );
		}

		$prepared_slots = [];
		$slots_before   = [];
		$slots_after    = [];

		$starting_point  = self::$starting_point;
		$available_count = 0;
		
		foreach ( $slots as $timestamp => $slot ) {
			$class = $slot['selected'] ? 'jet-apb-slot--selected' : '' ;

			if ( $manage_capacity ) {

				$count           = ! empty( $slot['count'] ) ? $slot['count'] : 0;
				$available_count = $service_count - $count;

				$booked_count        = ! empty( $slot['booked_count'] ) ? $slot['booked_count'] : 0;
				$max_available_count = $service_count - $booked_count;

				if ( $show_counter ) {
					$capacity_html   = sprintf( $capacity_format, $count, $service_count, $available_count );
				} else {
					$capacity_html = '';
				}
			}
			

			$from_date_check = false;
			$to_date_check   = false;

			if ( self::$current_timezone ) {

				$timezone_string = get_option( 'timezone_string' ) ? get_option( 'timezone_string' ) : get_option( 'gmt_offset' );

				if ( null === $offset ) {
					$offset = self::get_slot_offset(
						$timezone_string,
						$slot['from'],
						$timezone_transitions,
						$timezone_offsets
					);
				}

				if ( null !== $offset ) {
					$from = $slot['from'] - $offset;
					$to   = $slot['to'] - $offset;
				} else {
					$from = $slot['from'];
					$to   = $slot['to'];
				}

				$from_time       = date_create( wp_date( 'Y-m-d H:i:s', $from ), wp_timezone() );
				$from_date_check = $from_time->setTimezone( self::$current_timezone )->format( 'Ymd' );
				$from_time       = $from_time->setTimezone( self::$current_timezone )->format( $format );
				$to_time         = date_create( wp_date( 'Y-m-d H:i:s', $to ), wp_timezone() );
				$to_date_check   = $to_time->setTimezone( self::$current_timezone )->format( 'Ymd' );
				$to_time         = $to_time->setTimezone( self::$current_timezone )->format( $format );
			} else {

				$use_calendar_timezone = Plugin::instance()->settings->get( 'use_calendar_timezone' );
				$calendar_timezone     = Plugin::instance()->settings->get( 'calendar_timezone' );

				$timezone = ( $use_calendar_timezone && ! empty( $calendar_timezone ) ) ? $calendar_timezone[0] : false;

				$custom_tz = apply_filters( 'jet-apb/custom-timezone', $timezone );

				if ( $custom_tz ) {

					self::set_timezone( $custom_tz );
					
					$timezone_string = get_option( 'timezone_string' );

					if ( $timezone_string ) {

						$offset = self::get_slot_offset(
							$timezone_string,
							$slot['from'],
							$timezone_transitions,
							$timezone_offsets
						);

						$from = $slot['from'] - $offset;
						$to   = $slot['to'] - $offset;

					} else {
						// Fallback to manual offset (e.g., UTC+2)
						$offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS;

						$from = $slot['from'] - $offset;
						$to   = $slot['to'] - $offset;

					}

					$timezone = new \DateTimeZone( $custom_tz );

					$from_time = wp_date( ltrim( $format ), $from, $timezone );
					$to_time   = wp_date( ltrim( $format ), $to, $timezone );
				} else {
					$from_time = date( ltrim( $format ), $slot['from'] );
					$to_time   = date( ltrim( $format ), $slot['to'] );
				}

			}

			if ( ! $manage_capacity || ! Plugin::instance()->settings->get( 'allow_manage_count' ) ) {
				$allowed_count = 0;
				$allowed_max_count = 0;
			} else {
				$allowed_count = $available_count;
				$allowed_max_count = $max_available_count;
			}

			$prepared_slot = [
				'from' => $slot['from'],
				'to' => $slot['to'],
				'starting_point' => $starting_point,
				'allowed_count' => $allowed_count,
				'from_time' => $from_time,
				'to_time' => $to_time,
				'capacity' => $capacity_html,
				'class' => $class,
				'date' => $date_translated,
				'gmt_from' => get_gmt_from_date( $date . ' ' . $from_time, 'Y-m-d H:i:s' ),
				'gmt_to' => get_gmt_from_date( $date . ' ' . $to_time, 'Y-m-d H:i:s' ),
				'max-allowed-count' => $allowed_max_count,
			];

			if ( ! $from_date_check || ! $to_date_check || ( $from_date_check == $check_date && $to_date_check == $check_date ) ) {
				$prepared_slots[] = $prepared_slot;
			} else {
				
				$from_date_check = absint( $from_date_check );
				$to_date_check   = absint( $to_date_check );
				$check_date      = absint( $check_date );

				if ( $from_date_check < $check_date || $to_date_check < $check_date ) {

					//$prepared_slot['date'] = date( get_option( 'date_format' ), strtotime( $date . ' + 1 day' ) );
					$prepared_slot['from'] = $prepared_slot['from'] + DAY_IN_SECONDS;
					$prepared_slot['to'] = $prepared_slot['to'] + DAY_IN_SECONDS;
					$prepared_slot['starting_point'] = $prepared_slot['starting_point'] + DAY_IN_SECONDS;
					$prepared_slot['gmt_from'] = get_gmt_from_date( $prepared_slot['date'] . ' ' . $from_time, 'Y-m-d H:i:s' );
					$prepared_slot['gmt_to'] = get_gmt_from_date( $prepared_slot['date'] . ' ' . $to_time, 'Y-m-d H:i:s' );

					$available_slots = Plugin::instance()->calendar->get_date_slots(
						absint( $service ),
						absint( $provider ),
						$prepared_slot['starting_point']
					);

					if ( isset( $available_slots[ $prepared_slot['from'] ] ) ) {
						$slots_after[] = $prepared_slot;
					}

				} elseif ( $from_date_check > $check_date || $to_date_check > $check_date ) {

					//$prepared_slot['date'] = date( get_option( 'date_format' ), strtotime( $date . ' - 1 day' ) );
					$prepared_slot['from'] = $prepared_slot['from'] - DAY_IN_SECONDS;
					$prepared_slot['to'] = $prepared_slot['to'] - DAY_IN_SECONDS;
					$prepared_slot['starting_point'] = $prepared_slot['starting_point'] - DAY_IN_SECONDS;
					$prepared_slot['gmt_from'] = get_gmt_from_date( $prepared_slot['date'] . ' ' . $from_time, 'Y-m-d H:i:s' );
					$prepared_slot['gmt_to'] = get_gmt_from_date( $prepared_slot['date'] . ' ' . $to_time, 'Y-m-d H:i:s' );

					$available_slots = Plugin::instance()->calendar->get_date_slots(
						absint( $service ),
						absint( $provider ),
						$prepared_slot['starting_point']
					);

					if ( isset( $available_slots[ $prepared_slot['from'] ] ) ) {
						$slots_before[] = $prepared_slot;
					}
				}

			}

		}

		$prepared_slots = apply_filters(
			'jet-apb/time-slots/prepared_slots',
			array_merge( $slots_before, $prepared_slots, $slots_after ),
			$slots,
			$service,
			$provider
		);

		/**
		 * %4$s - slot start time in selected format
		 * %5$s - slot end time in selected format
		 * @var string
		 */
		$slot_time_format = apply_filters( 'jet-apb/time-slots/slots-html/slot-time-format', '%4$s-%5$s', $slot );

		foreach ( $prepared_slots as $slot ) {

			/**
			 * Legacy part.
			 * Make sure $slot_time format is compatible with 'jet-apb/time-slots/slots-html/slot-time-format' filter
			 */
			$slot_time = sprintf( $slot_time_format, null, null, null, $slot['from_time'], $slot['to_time'] );

			$slot_time = apply_filters(	'jet-apb/time-slots/slot-time',	$slot_time,	$slot );

			$slot_dataset         = $dataset;
			$slot_data_attributes = [
				'data-slot' => $slot['from'],
				'data-slot-end' => $slot['to'],
				'data-date' => $slot['starting_point'],
				'data-friendly-time' => $slot_time,
				'data-friendly-date' => $slot['date'],
				'data-utc-start' => $slot['gmt_from'],
				'data-utc-end' => $slot['gmt_to'],
				'data-allowed-count' => $slot['allowed_count'],
				'data-service' => $service,
				'data-provider' => $provider,
				'data-max-allowed-count' => $slot['max-allowed-count'],
				'tabindex' => 0,
			];

			foreach ( $slot_data_attributes as $key => $value ) {
				$slot_dataset[] = sprintf( '%1$s="%2$s"', $key, $value );
			}
			// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
			printf(
				'<div class="jet-apb-slot %1$s" role="button" aria-label="%5$s: %3$s" %2$s>%3$s %4$s</div>',
				$slot['class'],
				implode( ' ', $slot_dataset ),
				$slot_time,
				$slot['capacity'],
				esc_attr__( 'Slot time', 'jet-appointments-booking' ),
			);
			// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped

		}

	}

	/**
	 * Returns timestamp from human readable time
	 *
	 * @return [type] [description]
	 */
	public static function get_timestamp_from_time( $time ) {

		$time  = explode( ':', $time );
		$hours = absint( $time[0] );
		$mins  = absint( $time[1] );

		return self::$starting_point + $hours * HOUR_IN_SECONDS + $mins * MINUTE_IN_SECONDS;

	}

	/**
	 * Generate time slots array
	 *
	 * @param  array  $args [description]
	 * @return [type]       [description]
	 */
	public static function prepare_slots_for_js( $slots = array(), $l_format = false, $plain = false, $diff = false ) {

		$result = array();

		foreach ( $slots as $slot ) {

			$value = $slot;
			$label = ! empty( $l_format ) ? date( $l_format, $slot ) : $slot;

			if ( $diff ) {
				$value = $slot - self::$starting_point;
			}

			if ( $plain ) {
				$result[ $value ] = $label;
			} else {
				$result[] = array(
					'value' => $value,
					'label' => $label,
				);
			}
		}

		return $result;

	}

}
