<?php
/**
 * A WC_Bookings_Details_Meta_Box class file.
 *
 * @package WooCommerce Bookings
 */

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

/**
 * WC_Bookings_Details_Meta_Box.
 */
class WC_Bookings_Details_Meta_Box {

	/**
	 * Meta box ID.
	 *
	 * @var string
	 */
	public $id;

	/**
	 * Meta box title.
	 *
	 * @var string
	 */
	public $title;

	/**
	 * Meta box context.
	 *
	 * @var string
	 */
	public $context;

	/**
	 * Meta box priority.
	 *
	 * @var string
	 */
	public $priority;

	/**
	 * Meta box post types.
	 *
	 * @var array
	 */
	public $post_types;

	/**
	 * Are meta boxes saved?
	 *
	 * @var boolean
	 */
	private static $saved_meta_box = false;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->id         = 'woocommerce-booking-data';
		$this->title      = __( 'Booking Details', 'woocommerce-bookings' );
		$this->context    = 'normal';
		$this->priority   = 'high';
		$this->post_types = array( 'wc_booking' );
		add_action( 'save_post', array( $this, 'meta_box_save' ), 10, 2 );
	}

	/**
	 * Check data and output warnings.
	 *
	 * @param WC_Booking         $booking Booking object.
	 * @param WC_Product_Booking $product Product object.
	 */
	private function sanity_check_notices( $booking, $product ) {
		if ( $booking->get_start() && $booking->get_start() > strtotime( '+ 2 year', current_time( 'timestamp' ) ) ) {
			echo '<div class="notice notice-warning"><p>' . esc_html__( 'This booking is scheduled over 2 years into the future. Please ensure this is correct.', 'woocommerce-bookings' ) . '</p></div>';
		}

		if ( $product && is_callable( array( $product, 'get_max_date' ) ) ) {
			$max      = $product->get_max_date();
			$max_date = strtotime( "+{$max['value']} {$max['unit']}", current_time( 'timestamp' ) );
			if ( $booking->get_start() > $max_date || $booking->get_end() > $max_date ) {
				/* translators: 1: maximum bookable date */
				echo '<div class="notice notice-warning"><p>' . sprintf( esc_html__( 'This booking is scheduled over the product\'s allowed max booking date (%s). Please ensure this is correct.', 'woocommerce-bookings' ), esc_html( date_i18n( wc_bookings_date_format(), $max_date ) ) ) . '</p></div>';
			}
		}

		if ( ! $product || ( $booking->get_product_id() && ! wc_get_product( $booking->get_product_id() ) ) ) {
			echo '<div class="error"><p>' . esc_html__( 'It appears the booking product associated with this booking has been removed.', 'woocommerce-bookings' ) . '</p></div>';
			return;
		}

		if ( $product && is_callable( array( $product, 'is_skeleton' ) ) && $product->is_skeleton() ) {
			/* translators: 1: product type */
			echo '<div class="error"><p>' . esc_html( sprintf( __( 'This booking is missing a required add-on (product type: %s). Some information is shown below but might be incomplete. Please install the missing add-on through the plugins screen.', 'woocommerce-bookings' ), $product->get_type() ) ) . '</p></div>';
		}
	}

	/**
	 * Meta box content.
	 *
	 * @version 1.10.2
	 *
	 * @param WP_Post $post Post object.
	 */
	public function meta_box_inner( $post ) {
		global $booking;

		wp_nonce_field( 'wc_bookings_details_meta_box', 'wc_bookings_details_meta_box_nonce' );
		wp_enqueue_script( 'wc-enhanced-select' );
		wp_enqueue_script( 'jquery-ui-datepicker' );

		if ( ! $booking instanceof \WC_Booking || $booking->get_id() !== $post->ID ) {
			try {
				$booking = new WC_Booking( $post->ID );
			} catch ( Exception $e ) {
				wc_get_logger()->error( $e->getMessage() );
				return;
			}
		}

		$order = $booking->get_order();

		$product_id        = $booking->get_product_id( 'edit' );
		$resource_id       = $booking->get_resource_id( 'edit' );
		$product           = $booking->get_product( $product_id );
		$customer          = $booking->get_customer();
		$statuses          = array_unique( array_merge( get_wc_booking_statuses( null, true ), get_wc_booking_statuses( 'user', true ), get_wc_booking_statuses( 'cancel', true ) ) );
		$bookable_products = array( '' => __( 'N/A', 'woocommerce-bookings' ) );

		foreach ( WC_Bookings_Admin::get_booking_products() as $bookable_product ) {
			$bookable_products[ $bookable_product->get_id() ] = $bookable_product->get_name();

			$resources = $bookable_product->get_resources();

			foreach ( $resources as $resource ) {
				$bookable_products[ $bookable_product->get_id() . '=>' . $resource->get_id() ] = '&nbsp;&nbsp;&nbsp;' . $resource->get_name();
			}
		}

		$this->sanity_check_notices( $booking, $product );

		// Check if attendance status meta exists for old bookings.
		$attendance_status_meta_exists = metadata_exists( 'post', $booking->get_id(), '_booking_attendance_status' );

		// Check if booking is cancelled - hide attendance status if cancelled.
		$is_cancelled = 'cancelled' === $booking->get_status();

		// Hidden booking item meta.
		$hidden_booking_itemmeta = array( '_booking_id' );

		?>
		<style type="text/css">
			#post-body-content, #titlediv, #major-publishing-actions, #minor-publishing-actions, #visibility, #submitdiv { display:none }
		</style>
		<div class="panel-wrap woocommerce">
			<div id="booking_data" class="panel">
				<h2>
				<?php
				/* translators: 1: booking id */
				printf( esc_html__( 'Booking #%s details', 'woocommerce-bookings' ), esc_html( $post->ID ) );
				?>
				</h2>
				<p class="booking_number">
				<?php
				if ( $order ) {
					/* translators: 1: href to order id */
					printf( ' ' . esc_html__( 'Linked to order %s.', 'woocommerce-bookings' ), '<a href="' . esc_url( $order->get_edit_order_url() ) . '">#' . esc_html( $order->get_order_number() ) . '</a>' );
				}

				if ( $product && is_callable( array( $product, 'is_bookings_addon' ) ) && $product->is_bookings_addon() ) {
					/* translators: 1: bookings addon title */
					printf( ' ' . esc_html__( 'Booking type: %s', 'woocommerce-bookings' ), esc_html( $product->bookings_addon_title() ) );
				}
				?>
				</p>

				<div class="booking_data_column_container">
					<div class="booking_data_column">
						<h4><?php esc_html_e( 'General details', 'woocommerce-bookings' ); ?></h4>

						<p class="form-field form-field-wide">
							<label for="_booking_order_id"><?php esc_html_e( 'Order ID:', 'woocommerce-bookings' ); ?></label>
							<select name="_booking_order_id" id="_booking_order_id" data-placeholder="<?php esc_attr_e( 'N/A', 'woocommerce-bookings' ); ?>" data-allow_clear="true">
								<?php if ( $booking->get_order_id() && $order ) : ?>
									<option selected="selected" value="<?php echo esc_attr( $booking->get_order_id() ); ?>"><?php echo esc_html( $order->get_order_number() . ' &ndash; ' . date_i18n( wc_bookings_date_format(), strtotime( is_callable( array( $order, 'get_date_created' ) ) ? $order->get_date_created() : $order->post_date ) ) ); ?></option>
								<?php endif; ?>
							</select>
						</p>

						<p class="form-field form-field-wide"><label for="booking_date"><?php esc_html_e( 'Date created:', 'woocommerce-bookings' ); ?></label>
							<input type="text" class="date-picker-field" name="booking_date" id="booking_date" maxlength="10" value="<?php echo esc_attr( date_i18n( 'Y-m-d', $booking->get_date_created() ) ); ?>" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" /> @ <input type="number" class="hour" placeholder="<?php esc_attr_e( 'h', 'woocommerce-bookings' ); ?>" name="booking_date_hour" id="booking_date_hour" maxlength="2" size="2" value="<?php echo esc_attr( date_i18n( 'H', $booking->get_date_created() ) ); ?>" min="0" max="23" />:<input type="number" class="minute" placeholder="<?php esc_attr_e( 'm', 'woocommerce-bookings' ); ?>" name="booking_date_minute" id="booking_date_minute" maxlength="2" size="2" value="<?php echo esc_attr( date_i18n( 'i', $booking->get_date_created() ) ); ?>" min="0" max="59" />
						</p>

						<p class="form-field form-field-wide">
							<label for="_booking_status"><?php esc_attr_e( 'Booking status:', 'woocommerce-bookings' ); ?></label>
							<select id="_booking_status" name="_booking_status" class="wc-enhanced-select">
							<?php
							foreach ( $statuses as $key => $value ) {
								echo '<option value="' . esc_attr( $key ) . '" ' . selected( $key, $booking->get_status(), false ) . '>' . esc_html( $value ) . '</option>';
							}
							?>
							</select>
							<input type="hidden" name="post_status" value="<?php echo esc_attr( $booking->get_status() ); ?>">
						</p>

						<?php if ( ! $attendance_status_meta_exists ) : ?>
							<span class="form-field form-field-wide booking-attendance-status-prompt"<?php echo $is_cancelled ? ' style="display: none;"' : ''; ?>>
								<p>
									<strong><?php esc_html_e( 'Attendance Status not configured', 'woocommerce-bookings' ); ?></strong>
								</p>
								<p class="booking-attendance-status-description">
									<?php esc_html_e( 'This booking was created before the attendance status feature was added.', 'woocommerce-bookings' ); ?>
								</p>
								<p>
									<?php
									$tool_url = admin_url( 'admin.php?page=wc-status&tab=tools' );
									printf(
										/* translators: %s: Link to the tool */
										wp_kses_post( __( 'You can use the <a href="%s" target="_blank" rel="noopener noreferrer">WooCommerce > Status > Tools > Add missing attendance status</a> tool to add attendance status to all bookings at once.', 'woocommerce-bookings' ) ),
										esc_url( $tool_url )
									);
									?>
								</p>
								<button type="button" id="configure-attendance-status-btn" class="button button-secondary">
									<?php esc_html_e( 'Configure Attendance Status', 'woocommerce-bookings' ); ?>
								</button>
							</span>
							<p class="form-field form-field-wide booking-attendance-status-field" style="display: none;">
						<?php else : ?>
							<p class="form-field form-field-wide booking-attendance-status-field"<?php echo $is_cancelled ? ' style="display: none;"' : ''; ?>>
						<?php endif; ?>
							<label for="_booking_attendance_status">
								<?php esc_attr_e( 'Attendance status:', 'woocommerce-bookings' ); ?>
							</label>
							<select id="_booking_attendance_status" name="_booking_attendance_status" class="wc-enhanced-select">
							<?php
							echo '<option value="attended" ' . selected( 'attended', $booking->get_attendance_status(), false ) . '>' . esc_html__( 'Attended', 'woocommerce-bookings' ) . '</option>';
							echo '<option value="unattended" ' . selected( 'unattended', $booking->get_attendance_status(), false ) . '>' . esc_html__( 'Unattended', 'woocommerce-bookings' ) . '</option>';
							?>
							</select>
						</p>

						<p class="form-field form-field-wide">
							<label for="_booking_customer_id"><?php esc_html_e( 'Customer:', 'woocommerce-bookings' ); ?></label>
							<?php
							$name              = ! empty( $customer->name ) ? ' &ndash; ' . $customer->name : '';
							$guest_placeholder = __( 'Guest', 'woocommerce-bookings' );
							if ( 'Guest' === $name ) {
								/* translators: 1: guest name */
								$guest_placeholder = sprintf( _x( 'Guest (%s)', 'Admin booking guest placeholder', 'woocommerce-bookings' ), $name );
							}

							if ( $booking->get_customer_id() ) {
								$user            = get_user_by( 'id', $booking->get_customer_id() );
								$customer_string = sprintf(
									/* translators: 1: full name 2: user id 3: email */
									esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce-bookings' ),
									$user ? trim( $user->first_name . ' ' . $user->last_name ) : $customer->name,
									$booking->get_customer_id(),
									$user ? $user->user_email : $customer->email
								);
							} else {
								$customer_string = '';
							}
							?>
							<select name="_booking_customer_id" id="_booking_customer_id" class="wc-customer-search" data-placeholder="<?php echo esc_attr( $guest_placeholder ); ?>" data-allow_clear="true">
								<?php if ( $booking->get_customer_id() ) : ?>
									<option selected="selected" value="<?php echo esc_attr( $booking->get_customer_id() ); ?>"><?php echo esc_attr( $customer_string ); ?></option>
								<?php endif; ?>
							</select>
						</p>

						<p class="form-field form-field-wide">
							<?php
							try {
								$order_item_id = $booking->get_order_item_id();
								$order_item    = new WC_Order_Item_Product( $order_item_id );
								?>
								<label><?php esc_html_e( 'Metadata:', 'woocommerce-bookings' ); ?></label>
								<table cellspacing="0" class="wc_bookings_metadata_table">
								<?php
								foreach ( $order_item->get_formatted_meta_data() as $meta ) :
									if ( in_array( $meta->key, $hidden_booking_itemmeta, true ) ) {
										continue;
									}
									?>
									<tr>
										<th><?php echo wp_kses_post( $meta->display_key ); ?>:</th>
										<td><?php echo wp_kses_post( force_balance_tags( $meta->display_value ) ); ?></td>
									</tr>
								<?php endforeach; ?>
								</table>
								<?php
							} catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Ignore invalid order item ID errors, e.g., when an order item is deleted.
							}
							?>
						</p>
						<?php
						/**
						 * A woocommerce_admin_booking_data_after_booking_details action hook.
						 * Triggers when the booking edit page has finished loading the "General details" section.
						 *
						 * @param int $post->ID
						 *
						 * @since 1.15.79
						 */
						do_action( 'woocommerce_admin_booking_data_after_booking_details', $post->ID );
						?>

					</div>
					<div class="booking_data_column">
						<h4><?php esc_html_e( 'Booking specification', 'woocommerce-bookings' ); ?></h4>

						<?php
						woocommerce_wp_select(
							array(
								'id'            => 'product_or_resource_id',
								'class'         => 'wc-enhanced-select',
								'wrapper_class' => 'form-field form-field-wide',
								'label'         => __( 'Booked product:', 'woocommerce-bookings' ),
								'options'       => $bookable_products,
								'value'         => $resource_id ? $product_id . '=>' . $resource_id : $product_id,
							)
						);

						woocommerce_wp_text_input(
							array(
								'id'            => '_booking_parent_id',
								'label'         => __( 'Parent booking ID:', 'woocommerce-bookings' ),
								'wrapper_class' => 'form-field form-field-wide',
								'placeholder'   => 'N/A',
								'class'         => '',
								'value'         => $booking->get_parent_id() ? $booking->get_parent_id() : '',
							)
						);

						$person_counts = $booking->get_person_counts();

						echo '<br class="clear" />';
						echo '<h4>' . esc_html__( 'Person(s)', 'woocommerce-bookings' ) . '</h4>';

						$person_types = $product ? $product->get_person_types() : array();

						if ( count( $person_counts ) > 0 || count( $person_types ) > 0 ) {
							$needs_update = false;

							foreach ( $person_counts as $person_id => $person_count ) {
								$person_type = null;

								try {
									$person_type = new WC_Product_Booking_Person_Type( $person_id );
								} catch ( Exception $e ) {
									// This person type was deleted from the database.
									unset( $person_counts[ $person_id ] );
									$needs_update = true;
								}

								if ( $person_type ) {
									woocommerce_wp_text_input(
										array(
											'id'          => '_booking_person_' . $person_id,
											'label'       => $person_type->get_name(),
											'type'        => 'number',
											'placeholder' => '0',
											'value'       => $person_count,
											'custom_attributes' => array(
												'min'  => '0',
												'step' => '1',
											),
											'wrapper_class' => 'booking-person',
										)
									);
								}
							}

							if ( $needs_update ) {
								$booking->set_person_counts( $person_counts );
								$booking->save();
							}

							$product_booking_diff = array_diff( array_keys( $person_types ), array_keys( $person_counts ) );

							foreach ( $product_booking_diff as $id ) {
								$person_type = $person_types[ $id ];
								woocommerce_wp_text_input(
									array(
										'id'            => '_booking_person_' . $person_type->get_id(),
										'label'         => $person_type->get_name(),
										'type'          => 'number',
										'placeholder'   => '0',
										'value'         => '0',
										'custom_attributes' => array(
											'min'  => '0',
											'step' => '1',
										),
										'wrapper_class' => 'booking-person',
									)
								);
							}
						} else {
							$person_counts = $booking->get_person_counts();
							$person_type   = new WC_Product_Booking_Person_Type( 0 );

							woocommerce_wp_text_input(
								array(
									'id'                => '_booking_person_0',
									'label'             => $person_type->get_name(),
									'type'              => 'number',
									'placeholder'       => '0',
									'value'             => ! empty( $person_counts[0] ) ? $person_counts[0] : 0,
									'custom_attributes' => array(
										'min'  => '0',
										'step' => '1',
									),
									'wrapper_class'     => 'booking-person',
								)
							);
						}
						?>
					</div>
					<div class="booking_data_column">
						<h4><?php esc_html_e( 'Booking date &amp; time', 'woocommerce-bookings' ); ?></h4>
						<?php
							woocommerce_wp_text_input(
								array(
									'id'          => 'booking_start_date',
									'label'       => __( 'Start date:', 'woocommerce-bookings' ),
									'placeholder' => 'yyyy-mm-dd',
									'value'       => date( 'Y-m-d', $booking->get_start( 'edit' ) ),
									'class'       => 'date-picker-field',
								)
							);

							woocommerce_wp_text_input(
								array(
									'id'          => 'booking_end_date',
									'label'       => __( 'End date:', 'woocommerce-bookings' ),
									'placeholder' => 'yyyy-mm-dd',
									'value'       => date( 'Y-m-d', $booking->get_end( 'edit' ) ),
									'class'       => 'date-picker-field',
								)
							);

							woocommerce_wp_checkbox(
								array(
									'id'          => '_booking_all_day',
									'label'       => __( 'All day booking:', 'woocommerce-bookings' ),
									'description' => __( 'Check this box if the booking is for all day.', 'woocommerce-bookings' ),
									'value'       => $booking->get_all_day( 'edit' ) ? 'yes' : 'no',
								)
							);

							woocommerce_wp_text_input(
								array(
									'id'          => 'booking_start_time',
									'label'       => __( 'Start time:', 'woocommerce-bookings' ),
									'placeholder' => 'hh:mm',
									'value'       => date( 'H:i', $booking->get_start( 'edit' ) ),
									'type'        => 'time',
								)
							);

							woocommerce_wp_text_input(
								array(
									'id'          => 'booking_end_time',
									'label'       => __( 'End time:', 'woocommerce-bookings' ),
									'placeholder' => 'hh:mm',
									'value'       => date( 'H:i', $booking->get_end( 'edit' ) ),
									'type'        => 'time',
								)
							);

						if ( wc_should_convert_timezone( $booking ) ) {
							woocommerce_wp_text_input(
								array(
									'id'                => 'booking_start_time',
									'label'             => __( 'Start time (local timezone):', 'woocommerce-bookings' ),
									'placeholder'       => 'hh:mm',
									'value'             => date( 'H:i', $booking->get_start( 'edit', true ) ),
									'type'              => 'time',
									'custom_attributes' => array( 'disabled' => 'disabled' ),
								)
							);

							woocommerce_wp_text_input(
								array(
									'id'                => 'booking_end_time',
									'label'             => __( 'End time (local timezone):', 'woocommerce-bookings' ),
									'placeholder'       => 'hh:mm',
									'value'             => date( 'H:i', $booking->get_end( 'edit', true ) ),
									'type'              => 'time',
									'custom_attributes' => array( 'disabled' => 'disabled' ),
								)
							);
						}
						?>
					</div>
				</div>
			</div>
			<div class="clear"></div>
		</div>

		<?php
		// phpcs:disable Squiz.Strings.DoubleQuoteUsage.NotRequired
		$handle = 'wc-bookings-details-all-day';
		wp_register_script( $handle, '', array( 'jquery', 'jquery-ui-datepicker' ), false, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
		wp_enqueue_script( $handle );
		wp_add_inline_script(
			$handle,
			"
			( function( $ ) {
				$( function() {
					$( '#_booking_all_day' ).change( function () {
						if ( $( this ).is( ':checked' ) ) {
							$( '#booking_start_time, #booking_end_time' ).closest( 'p' ).hide();
						} else {
							$( '#booking_start_time, #booking_end_time' ).closest( 'p' ).show();
						}
					}).change();

					$( '.date-picker-field' ).datepicker({
						dateFormat: 'yy-mm-dd',
						firstDay: " . get_option( 'start_of_week' ) . ",
						numberOfMonths: 1,
						showButtonPanel: true,
						beforeShow: function( input, datePicker ) {
							datePicker.dpDiv.addClass( 'wc-bookings-ui-datepicker-widget' );
						},
						onClose: function( dateText, datePicker ) {
							datePicker.dpDiv.removeClass( 'wc-bookings-ui-datepicker-widget' );
						}
					});
				} );
			} )( jQuery );
			"
		);

		// Select2 handling.
		$handle = 'wc-bookings-details-select2';
		wp_register_script( $handle, '', array( 'jquery', 'selectWoo' ), false, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
		wp_enqueue_script( $handle );
		wp_add_inline_script(
			$handle,
			"
			( function( $ ) {
				$( function() {
					$( '#_booking_order_id' ).filter( ':not(.enhanced)' ).each( function() {
						var select2_args = {
							allowClear:  true,
							placeholder: $( this ).data( 'placeholder' ),
							minimumInputLength: 1,
							escapeMarkup: function( m ) {
								return m;
							},
							ajax: {
								url:         '" . admin_url( 'admin-ajax.php' ) . "',
								dataType:    'json',
								quietMillis: 250,
								data: function( params ) {
									return {
										term: params.term,
										action: 'wc_bookings_json_search_order',
										booking_id: '" . absint( $post->ID ) . "',
										security: '" . wp_create_nonce( "search-booking-order-$post->ID" ) . "'
									};
								},
								processResults: function( data ) {
									var terms = [];
									if ( data ) {
										$.each( data, function( id, text ) {
											terms.push({
												id: id,
												text: text
											});
										});
									}
									return {
										results: terms
									};
								},
								cache: true
							},
							multiple: false
						};
						$( this ).select2( select2_args ).addClass( 'enhanced' );
					});
				} );
			} )( jQuery );
			"
		);
		// phpcs:enable Squiz.Strings.DoubleQuoteUsage.NotRequired
	}

	/**
	 * Returns an array of labels (statuses wrapped in gettext)
	 *
	 * @param  array $statuses
	 * @deprecated since 1.9.13. $this->get_wc_booking_statuses now also comes with globalised strings.
	 * @return array
	 */
	public function get_labels_for_statuses( $statuses = array() ) {
		$labels = array();
		foreach ( $statuses as $status ) {
			$labels[ $status ] = $status;
		}
		return $labels;
	}

	/**
	 * Save handler.
	 *
	 * @version 1.10.2
	 *
	 * @param  int     $post_id Post ID.
	 * @param  WP_Post $post    Post object.
	 *
	 * @throws Exception If error occurs, show it.
	 */
	public function meta_box_save( $post_id, $post ) {
		if ( ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['wc_bookings_details_meta_box_nonce'] ?? '' ) ), 'wc_bookings_details_meta_box' ) ) {
			return $post_id;
		}

		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return $post_id;
		}

		// Check the post being saved == the $post_id to prevent triggering this call for other save_post events.
		if ( empty( $_POST['post_ID'] ) || intval( $_POST['post_ID'] ) !== $post_id ) {
			return $post_id;
		}

		if ( ! in_array( $post->post_type, $this->post_types, true ) ) {
			return $post_id;
		}

		if ( self::$saved_meta_box ) {
			return $post_id;
		}

		// Get booking object.
		try {
			$booking = new WC_Booking( $post_id );
		} catch ( Exception $e ) {
			wc_get_logger()->error( $e->getMessage() );
			return $post_id;
		}

		$product_id = isset( $_POST['product_or_resource_id'] ) ? wc_clean( wp_unslash( $_POST['product_or_resource_id'] ) ) : $booking->get_product_id();
		if ( ! $product_id ) {
			return $post_id;
		}

		// We need this save event to run once to avoid potential endless loops. This would have been perfect:
		// remove_action( current_filter(), __METHOD__ );
		// But cannot be used due to https://github.com/woocommerce/woocommerce/issues/6485
		// When that is patched in core we can use the above.
		self::$saved_meta_box = true;

		$start_date = isset( $_POST['booking_start_date'] ) ? wc_clean( wp_unslash( $_POST['booking_start_date'] ) ) : null;
		$end_date   = isset( $_POST['booking_end_date'] ) ? wc_clean( wp_unslash( $_POST['booking_end_date'] ) ) : null;

		if ( strtotime( $end_date ) < strtotime( $start_date ) ) {
			WC_Admin_Notices::add_custom_notice(
				'bookings_invalid_date_range',
				'<strong>' . esc_html__( 'WooCommerce Bookings:', 'woocommerce-bookings' ) . '</strong> ' . esc_html__( 'Start date cannot be greater than end date.', 'woocommerce-bookings' )
			);
			return $post_id;
		}

		/**
		 * Server-side validation for start and end dates to check if the format
		 * is yyyy-mm-dd in case client-side validation fails.
		 */
		$is_valid_start_date = DateTime::createFromFormat( 'Y-m-d', $start_date );
		$is_valid_end_date   = DateTime::createFromFormat( 'Y-m-d', $end_date );

		if ( false === $is_valid_start_date || false === $is_valid_end_date ) {
			WC_Admin_Notices::add_custom_notice(
				'bookings_invalid_date_format',
				'<strong>' . esc_html__( 'WooCommerce Bookings:', 'woocommerce-bookings' ) . '</strong> ' . esc_html__( 'Date should be of the format yyyy-mm-dd and cannot be empty.', 'woocommerce-bookings' )
			);

			return $post_id;
		}

		if ( WC_Admin_Notices::has_notice( 'bookings_invalid_date_format' ) ) {
			WC_Admin_Notices::remove_notice( 'bookings_invalid_date_format' );
		}

		if ( WC_Admin_Notices::has_notice( 'bookings_invalid_date_range' ) ) {
			WC_Admin_Notices::remove_notice( 'bookings_invalid_date_range' );
		}

		$booking_start_time = isset( $_POST['booking_start_time'] ) ? wc_clean( wp_unslash( $_POST['booking_start_time'] ) ) : null;
		$booking_end_time   = isset( $_POST['booking_end_time'] ) ? wc_clean( wp_unslash( $_POST['booking_end_time'] ) ) : null;

		if ( empty( $booking_start_time ) ) {
			$booking_start_time = '00:00';
		}

		if ( empty( $booking_end_time ) ) {
			$booking_end_time = '23:59';
		}

		$end_date   = explode( '-', $end_date );
		$start_date = explode( '-', $start_date );
		$start_time = explode( ':', $booking_start_time );
		$end_time   = explode( ':', $booking_end_time );
		$start      = mktime( $start_time[0], $start_time[1], 0, $start_date[1], $start_date[2], $start_date[0] );
		$end        = mktime( $end_time[0], $end_time[1], 0, $end_date[1], $end_date[2], $end_date[0] );

		if ( strstr( $product_id, '=>' ) ) {
			list( $product_id, $resource_id ) = explode( '=>', $product_id );
		} else {
			$resource_id = 0;
		}

		$person_counts     = $booking->get_person_counts( 'edit' );
		$product           = wc_get_product( $product_id );
		$booking_types_ids = array_keys( $booking->get_person_counts( 'edit' ) );
		$booking_order_id  = isset( $_POST['_booking_order_id'] ) ? absint( $_POST['_booking_order_id'] ) : '';
		$product_types_ids = $product ? array_keys( $product->get_person_types() ) : array();
		$booking_persons   = array();

		foreach ( array_unique( array_merge( $booking_types_ids, $product_types_ids ) ) as $person_id ) {
			$booking_persons[ $person_id ] = isset( $_POST[ '_booking_person_' . $person_id ] ) ? absint( $_POST[ '_booking_person_' . $person_id ] ) : 0;
		}

		$booking->set_props(
			array(
				'all_day'           => isset( $_POST['_booking_all_day'] ),
				'customer_id'       => isset( $_POST['_booking_customer_id'] ) ? absint( $_POST['_booking_customer_id'] ) : '',
				// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested,WordPress.Security.ValidatedSanitizedInput.InputNotValidated
				'date_created'      => empty( $_POST['booking_date'] ) ? current_time( 'timestamp' ) : strtotime( wc_clean( wp_unslash( $_POST['booking_date'] ) ) . ' ' . (int) $_POST['booking_date_hour'] . ':' . (int) $_POST['booking_date_minute'] . ':00' ),
				'end'               => $end,
				'order_id'          => $booking_order_id,
				'parent_id'         => isset( $_POST['_booking_parent_id'] ) ? absint( $_POST['_booking_parent_id'] ) : 0,
				'person_counts'     => $booking_persons,
				'product_id'        => absint( $product_id ),
				'resource_id'       => absint( $resource_id ),
				'start'             => $start,
				'status'            => isset( $_POST['_booking_status'] ) ? wc_clean( wp_unslash( $_POST['_booking_status'] ) ) : '',
				'attendance_status' => isset( $_POST['_booking_attendance_status'] ) ? wc_clean( wp_unslash( $_POST['_booking_attendance_status'] ) ) : '',
			)
		);

		/**
		 * A woocommerce_admin_process_booking_object action hook.
		 * Triggers before the booking is saved.
		 *
		 * @param WC_Booking $booking
		 *
		 * @since 1.15.79
		 */
		do_action( 'woocommerce_admin_process_booking_object', $booking );

		// Link booking with an order item.
		if ( ! empty( $booking_order_id ) ) {
			$order       = wc_get_order( $booking_order_id );
			$order_items = $order->get_items();

			foreach ( $order_items as $order_item ) {
				$order_item_id = $order_item->get_id();
				if ( ! $order_item_id ) {
					throw new Exception( esc_html__( 'Error: Could not create item', 'woocommerce-bookings' ) );
				}

				// Link only if the booking doesn't have an existing order item.
				if ( 0 === $booking->get_order_item_id() ) {
					$booking->set_order_item_id( $order_item_id );
				}
			}
		}

		$booking->save();

		/**
		 * A woocommerce_booking_process_meta action hook.
		 * Triggers after the booking is saved.
		 *
		 * @param int $post_id
		 *
		 * @since 1.15.79
		 */
		do_action( 'woocommerce_booking_process_meta', $post_id );
	}
}
