<?php
/*
 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}


if ( ! class_exists( 'OsOrdersController' ) ) :


	class OsOrdersController extends OsController {

		function __construct() {
			parent::__construct();

			$this->views_folder          = LATEPOINT_VIEWS_ABSPATH . 'orders/';
			$this->vars['page_header']   = OsMenuHelper::get_menu_items_by_id( 'orders' );
			$this->vars['breadcrumbs'][] = array(
				'label' => __( 'Orders', 'latepoint' ),
				'link'  => OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'orders', 'index' ) ),
			);

			$this->action_access['public'] = array_merge( $this->action_access['public'], [ 'continue_order_intent', 'continue_transaction_intent' ] );
		}


		public function view_order_log() {
			$order_id = absint( $this->params['order_id'] );
			$order    = ( new OsOrderModel() )->where( [ LATEPOINT_TABLE_ORDERS . '.id' => $order_id ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
			if ( ! $order ) {
				$this->access_not_allowed();
				return;
			}

			$activities = new OsActivityModel();
			$activities = $activities->where( [ 'order_id' => $order_id ] )->order_by( 'id desc' )->get_results_as_models();

			$this->vars['order']      = $order;
			$this->vars['activities'] = $activities;

			$this->format_render( __FUNCTION__ );
		}


		public function continue_order_intent() {
			$order_intent_key = $this->params['order_intent_key'];
			$order_intent     = OsOrderIntentHelper::get_order_intent_by_intent_key( $order_intent_key );

			if ( $order_intent->is_new_record() ) {
				http_response_code( 400 );
				OsDebugHelper::log( 'Order intent not found, id:' . $order_intent_key );
				exit();
			} else {

				$order_intent->convert_to_order();

				nocache_headers();
				// Redirect with the intent key in either outcome, front.js resumes the booking flow at the confirmation
				// step when convert_to_order succeeded, or at the payment step when it didn't (e.g. cancelled Mollie/PayPal
				// payment). The "double payment information" issue is fixed at the cart-model layer (see
				// OsCartModel::clear) without that fix, the resume lightbox showed every cart item twice.
				wp_redirect( $order_intent->get_page_url_with_intent(), 302 );
				exit;
			}
		}


		public function continue_transaction_intent() {
			$intent_key         = $this->params['transaction_intent_key'];
			$transaction_intent = OsTransactionIntentHelper::get_transaction_intent_by_intent_key( $intent_key );

			if ( $transaction_intent->is_new_record() ) {
				http_response_code( 400 );
				OsDebugHelper::log( 'Transaction intent not found, id:' . $intent_key );
				exit();
			} else {
				$transaction_intent->convert_to_transaction();

				if ( $transaction_intent ) {
					nocache_headers();
					wp_redirect( $transaction_intent->get_page_url_with_intent(), 302 );
				}
			}
		}

		/*
			Update order (used in admin on quick side form save)
		*/

		public function update() {
			$this->create_or_update();
		}


		/*
			Create order (used in admin on quick side form save)
		*/

		public function create() {
			$this->create_or_update();
		}


		// Create/Update order from quick form in admin
		public function create_or_update() {
			$validation_errors = [];

			if ( ! empty( $this->params['order']['id'] ) ) {
				$this->check_nonce( 'edit_order_' . $this->params['order']['id'] );
				$allowed_order = ( new OsOrderModel() )->where( [ LATEPOINT_TABLE_ORDERS . '.id' => absint( $this->params['order']['id'] ) ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
				if ( ! $allowed_order ) {
					$this->send_json(
						array(
							'status'  => LATEPOINT_STATUS_ERROR,
							'message' => __( 'Not Allowed', 'latepoint' ),
						)
					);
				}
			} else {
				$this->check_nonce( 'new_order' );
			}

			$order_params    = $this->params['order'];
			$customer_params = $this->params['customer'];

			$order_items_params = $this->params['order_items'] ?? [];

			$order = new OsOrderModel( $order_params['id'] );

			// if we are updating a order - save a copy by cloning old order
			$old_order = ( $order->is_new_record() ) ? [] : clone $order;
			$order->set_data( $order_params );

			// first validate & create a customer the customer
			$old_customer_data = [];
			if ( $order->customer_id ) {
				$customer          = new OsCustomerModel( $order->customer_id );
				$old_customer_data = $customer->get_data_vars();
				$is_new_customer   = false;
			} else {
				$customer        = new OsCustomerModel();
				$is_new_customer = true;
			}

			// The submitted customer[...] fields may overwrite an EXISTING customer record only when
			// (1) the current user is authorized to edit that customer
			// (2) the customer is is an allowed customer list.
			// Attaching any customer to an order is always permitted; new customers proceed normally.
			if ( $is_new_customer || $customer->is_new_record() ) {
				$can_modify_customer = true;
			} else {
				$wp_link_is_safe     = empty( $customer->wordpress_user_id )
					|| OsCustomerHelper::is_wp_user_safe_for_customer_link( (int) $customer->wordpress_user_id );
				$can_modify_customer = $wp_link_is_safe
					&& OsRolesHelper::can_user_make_action_on_model_record( $customer, 'edit' );
			}

			if ( $can_modify_customer ) {
				// Set customer data only if is allowed. Prevent mass assignment of wordpress_user_id.
				// Use admin scope for backend panel users (admin, agent, custom roles), otherwise restrict to public fields
				$customer->set_data( $customer_params, OsAuthHelper::get_current_user()->has_backend_access() ? LATEPOINT_PARAMS_SCOPE_ADMIN : LATEPOINT_PARAMS_SCOPE_PUBLIC );
				if ( $customer->save() ) {
					if ( $is_new_customer ) {
						do_action( 'latepoint_customer_created', $customer );
						$this->fields_to_update['order[customer_id]'] = $customer->id;
					} else {
						do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
					}

					$order->customer_id = $customer->id;
				} else {
					$this->send_json(
						[
							'status'  => LATEPOINT_STATUS_ERROR,
							// translators: %s is the description of an error
							'message' => sprintf( __( 'Error: %s', 'latepoint' ), implode( ', ', $customer->get_error_messages() ) ),
						]
					);
				}
			}

			// validate order items
			foreach ( $order_items_params as $order_item_id => $order_item ) {
				foreach ( $order_item['bookings'] as $booking_id => $booking_params ) {
					$booking              = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );
					$booking->customer_id = $order->customer_id;
					if ( ! $booking->validate( false, [ 'order_item_id' ] ) ) {
						$validation_errors = array_merge( $validation_errors, $booking->get_error_messages() );
					}
				}
			}

			// check if there are errors saving bookings
			if ( $validation_errors ) {
				// translators: %s is the description of an error
				$this->send_json(
					array(
						'status'  => LATEPOINT_STATUS_ERROR,
						'message' => sprintf( __( 'Error: %s', 'latepoint' ), implode( ', ', $validation_errors ) ),
					)
				);
			}

			if ( $old_order ) {
				// make sure old order items are still there, if not - remove them
				$order_items = $order->get_items();
				foreach ( $order_items as $order_item ) {
					if ( ! isset( $order_items_params[ $order_item->id ] ) ) {
						$order_item_id_to_delete = $order_item->id;
						/**
						 * Fires right before an order item is about to be deleted
						 *
						 * @param {integer} $order_item_id ID of the Order Item which will be deleted
						 *
						 * @since 5.0.0
						 * @hook latepoint_order_item_will_be_deleted
						 *
						 */
						do_action( 'latepoint_order_item_will_be_deleted', $order_item_id_to_delete );

						$order_item->delete();

						/**
						 * Fires right after an order item has been deleted
						 *
						 * @param {integer} $order_item_id ID of the Order Item that was deleted
						 *
						 * @since 5.0.0
						 * @hook latepoint_order_item_deleted
						 *
						 */
						do_action( 'latepoint_order_item_deleted', $order_item_id_to_delete );
						OsActivitiesHelper::log_order_item_deleted( $order_item );
					} else {
						// it's a bundle order item - search for bookings that are attached to this bundle and remove them if not found in passed params list
						if ( $order_item->is_bundle() ) {
							$old_bundle_bookings = OsOrdersHelper::get_bookings_for_order_item( $order_item->id );
							if ( $old_bundle_bookings ) {
								foreach ( $old_bundle_bookings as $old_bundle_booking ) {
									if ( empty( $order_items_params[ $order_item->id ]['bookings'][ $old_bundle_booking->id ] ) ) {

										if ( OsRolesHelper::can_user_make_action_on_model_record( $old_bundle_booking, 'delete' ) ) {
											$booking_id_to_delete = $old_bundle_booking->id;

											/**
											 * Fires right before a booking is about to be deleted
											 *
											 * @param {integer} $booking_id ID of the booking that will be deleted
											 *
											 * @since 5.0.0
											 * @hook latepoint_booking_will_be_deleted
											 *
											 */
											do_action( 'latepoint_booking_will_be_deleted', $booking_id_to_delete );

											$old_bundle_booking->delete();
											/**
											 * Fires right after a booking has been deleted
											 *
											 * @param {integer} $booking_id ID of the booking that was deleted
											 *
											 * @since 5.0.0
											 * @hook latepoint_booking_deleted
											 *
											 */
											do_action( 'latepoint_booking_deleted', $booking_id_to_delete );
											OsActivitiesHelper::log_booking_deleted( $old_bundle_booking );
										} else {
											OsDebugHelper::log( 'Not allowed: Deleting Booking', 'permissions_error' );
										}
									}
								}
							}
						}
					}
				}
			}

			// Because price is not in allowed_params to bulk set, check if it's passed in params and set it, OTHERWISE CALCULATE IT
			if ( isset( $order_params['total'] ) ) {
				$order->total = OsParamsHelper::sanitize_param( $order_params['total'], 'money' );
			}
			if ( isset( $order_params['subtotal'] ) ) {
				$order->subtotal = OsParamsHelper::sanitize_param( $order_params['subtotal'], 'money' );
			}

			// save price breakdown, we only need to save before and after subtotal, as total and subtotal values are stored on the Order record itself
			if ( ! empty( $this->params['price_breakdown'] ) ) {
				$order->price_breakdown = wp_json_encode( OsOrdersHelper::generate_price_breakdown_from_params( $this->params['price_breakdown'] ) );
			}

			// Check if we have to create a payment request
			$create_payment_request = ( sanitize_text_field( $this->params['create_payment_request'] ?? '' ) == LATEPOINT_VALUE_ON );
			if ( $create_payment_request ) {
				$payment_request_data                  = $this->params['payment_request'];
				$payment_request_data['portion']       = sanitize_text_field( $payment_request_data['portion'] );
				$payment_request_data['charge_amount'] = sanitize_text_field( $payment_request_data[ 'charge_amount_' . $payment_request_data['portion'] ] );
				$payment_request_data['due_at']        = OsTimeHelper::custom_datetime_utc_in_db_format( sanitize_text_field( $payment_request_data['due_at'] ) . ' 23:59:59' );
				$order->set_initial_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_NOW, false );
				$order->set_initial_payment_data_value( 'portion', $payment_request_data['portion'], false );
				$order->set_initial_payment_data_value( 'charge_amount', OsMoneyHelper::convert_amount_from_money_input_to_db_format( $payment_request_data['charge_amount'] ) );

				$payment_request = new OsPaymentRequestModel();

				$payment_request = $payment_request->set_data( $payment_request_data );

			} else {
				$order->set_initial_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_LATER );
				$payment_request = null;
			}

			if ( $order->save() ) {

				// save transactions
				if ( ! empty( $this->params['transactions'] ) ) {
					foreach ( $this->params['transactions'] as $transaction_params ) {
						if ( ! empty( $transaction_params['id'] ) && filter_var( $transaction_params['id'], FILTER_VALIDATE_INT ) ) {
							// update existing transaction
							$transaction        = new OsTransactionModel( $transaction_params['id'] );
							$is_new_transaction = false;
						} else {
							// new transaction
							$transaction        = new OsTransactionModel();
							$is_new_transaction = true;
						}
						unset( $transaction_params['id'] );
						$transaction_params['invoice_id'] = filter_var( $transaction_params['invoice_id'], FILTER_VALIDATE_INT ) ? $transaction_params['invoice_id'] : null;
						$transaction->set_data( $transaction_params );
						$transaction->order_id    = $order->id;
						$transaction->customer_id = $customer->id;
						$transaction->status      = LATEPOINT_TRANSACTION_STATUS_SUCCEEDED;
						$transaction->save();
						if ( $is_new_transaction ) {
							/**
							 * Transaction for order was created
							 *
							 * @param {OsTransactionModel} $transaction instance of transaction model that was created
							 *
							 * @since 5.0.0
							 * @hook latepoint_transaction_created
							 *
							 */
							do_action( 'latepoint_transaction_created', $transaction );
						}
					}
				}
				foreach ( $order_items_params as $order_item_id => $order_item ) {
					$order_item_model          = new OsOrderItemModel();
					$order_item_model->variant = $order_item['variant'];
					if ( strpos( $order_item_id, 'new_' ) === false ) {
						$order_item_model->load_by_id( $order_item_id );
					}
					if ( $order_item_model->is_bundle() ) {
						$order_item_model->item_data = base64_decode( $order_item['item_data'] );
						$order_item_model->recalculate_prices();
					}


					$order_item_model->order_id = $order->id;
					if ( $order_item_model->save() ) {
						foreach ( $order_item['bookings'] as $booking_id => $booking_params ) {
							$booking     = new OsBookingModel();
							$old_booking = false;
							if ( strpos( $order_item_id, 'new_' ) === false ) {
								$booking->load_by_id( $booking_id );
								if ( ! $booking->is_new_record() ) {
									$old_booking = clone $booking;
								}
							}

							$booking                = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );
							$booking->customer_id   = $order->customer_id;
							$booking->order_item_id = $order_item_model->id;
							$booking->form_id       = $booking_id;
							if ( $booking->save() ) {
								if ( $order_item_model->is_booking() ) {
									$order_item_model->item_data = $booking->generate_item_data();
									$order_item_model->recalculate_prices();
									$order_item_model->save();
								}
								if ( $old_booking ) {
									do_action( 'latepoint_booking_updated', $booking, $old_booking );
									if ( $old_booking->status != $booking->status ) {
										do_action( 'latepoint_booking_change_status', $booking, $old_booking );
										OsActivitiesHelper::log_booking_change_status( $booking, $old_booking );
									}
								} else {
									do_action( 'latepoint_booking_created', $booking );
								}
							} else {
								OsDebugHelper::log( 'Error saving booking (admin)', 'booking_save_error', $booking->get_error_messages() );
							}
						}
					} else {
						OsDebugHelper::log( 'Error saving order item (admin)', 'order_item_save_error', $order_item_model->get_error_messages() );
					}
				}



				if ( $old_order ) {
					/**
					 * Order was updated
					 *
					 * @param {OsOrderModel} $order instance of order model after it was updated
					 * @param {OsOrderModel} $old_order instance of order model before it was updated
					 *
					 * @since 5.0.0
					 * @hook latepoint_order_updated
					 *
					 */
					do_action( 'latepoint_order_updated', $order, $old_order );
				} else {
					OsInvoicesHelper::create_invoices_for_new_order( $order, $payment_request );
					/**
					 * Order was created
					 *
					 * @param {OsOrderModel} $order instance of order model that was created
					 *
					 * @since 5.0.0
					 * @hook latepoint_order_created
					 *
					 */
					do_action( 'latepoint_order_created', $order );
				}

				$status = LATEPOINT_STATUS_SUCCESS;
				// translators: %s is a link to the updated order
				$response_html = sprintf( ( ( $old_order ) ? __( 'Order Updated ID: %s', 'latepoint' ) : __( 'Order Created ID: %s', 'latepoint' ) ), '<span class="os-notification-link" ' . OsOrdersHelper::quick_order_btn_html( $order->id ) . '>' . $order->id . '</span>' );
			} else {
				OsDebugHelper::log( 'Error saving order (admin)', 'order_save_error', $order->get_error_messages() );
				$status = LATEPOINT_STATUS_ERROR;

				// translators: %s is an error message
				$response_html = sprintf( __( 'Error: %s', 'latepoint' ), implode( ', ', $order->get_error_messages() ) );
			}

			if ( $this->get_return_format() == 'json' ) {
				$this->send_json(
					array(
						'status'  => $status,
						'message' => $response_html,
					)
				);
			}
		}


		// reloads a section of a quick edit form that has a price breakdown
		public function reload_price_breakdown() {
			$order = new OsOrderModel();
			$order->set_data( $this->params['order'] );

			$order_items_params = $this->params['order_items'] ?? [];
			foreach ( $order_items_params as $order_items_param ) {
				$order->items[] = OsOrdersHelper::create_order_item_object( $order_items_param );
			}

			$order->subtotal = $order->recalculate_subtotal();
			$order->total    = $order->recalculate_total();

			/**
			 * Reloads price breakdown rows
			 *
			 * @since 5.0.0
			 * @hook latepoint_register_role
			 *
			 * @param {OsOrderModel} $order Order for which to reload price breakdown
			 * @returns {OsOrderModel} Filtered order with updated price breakdown rows
			 */
			$order = apply_filters( 'latepoint_order_reload_price_breakdown', $order );

			// we don't need to generate balance and payments info as it is printed in a separate block
			$this->vars['price_breakdown_rows'] = $order->generate_price_breakdown_rows( [ 'balance', 'payments' ], true );

			$this->vars['order'] = $order;
			$this->format_render( __FUNCTION__ );
		}

		function reload_balance_and_payments() {
			$order_params       = $this->params['order'];
			$order_items_params = $this->params['order_items'] ?? [];

			$order = new OsOrderModel();
			$order->set_data( $order_params );

			foreach ( $order_items_params as $order_items_param ) {
				$order->items[] = OsOrdersHelper::create_order_item_object( $order_items_param );
			}



			// Because price is not in allowed_params to bulk set, check if it's passed in params and set it, OTHERWISE CALCULATE IT
			if ( isset( $order_params['total'] ) ) {
				$order->total = OsParamsHelper::sanitize_param( $order_params['total'], 'money' );
			}
			if ( isset( $order_params['subtotal'] ) ) {
				$order->subtotal = OsParamsHelper::sanitize_param( $order_params['subtotal'], 'money' );
			}


			$this->vars['order'] = $order;
			$this->format_render( __FUNCTION__ );
		}

		function generate_bundle_order_item_block() {
			$bundle = new OsBundleModel( $this->params['bundle_id'] );

			$order_item_id  = OsUtilHelper::generate_form_id();
			$response_html  = '<div class="order-item order-item-variant-bundle" data-order-item-id="' . $order_item_id . '">';
			$response_html .= OsOrdersHelper::generate_order_item_pill_for_bundle( $bundle, $order_item_id );
			$response_html .= '</div>';

			if ( $this->get_return_format() == 'json' ) {
				$this->send_json(
					array(
						'status'  => LATEPOINT_STATUS_SUCCESS,
						'message' => $response_html,
					)
				);
			}
		}

		function generate_booking_order_item_block() {

			if ( ! empty( $this->params['order_item_variant'] ) && ( $this->params['order_item_variant'] == LATEPOINT_ITEM_VARIANT_BUNDLE ) ) {
				// booking for bundle, we don't need to wrap in order-item block because order item is a bundle
				$booking       = OsBookingHelper::build_booking_model_from_item_data( json_decode( base64_decode( $this->params['booking_item_data'] ), true ) );
				$response_html = OsOrdersHelper::booking_data_form_for_order_item_id( $this->params['order_item_id'], $booking, LATEPOINT_ITEM_VARIANT_BUNDLE, false );
			} else {
				// regular booking
				$booking        = OsBookingHelper::prepare_new_from_params( $this->params );
				$order_item_id  = OsUtilHelper::generate_form_id();
				$response_html  = '<div class="order-item order-item-variant-booking" data-order-item-id="' . $order_item_id . '">';
				$response_html .= OsOrdersHelper::booking_data_form_for_order_item_id( $order_item_id, $booking, LATEPOINT_ITEM_VARIANT_BOOKING, false );
				$response_html .= '</div>';
			}


			if ( $this->get_return_format() == 'json' ) {
				$this->send_json(
					array(
						'status'  => LATEPOINT_STATUS_SUCCESS,
						'message' => $response_html,
					)
				);
			}
		}

		function fold_booking_data_form() {
			// input fields are formatted in customer preferred format, we need to convert that to database format Y-m-d
			$order_item_id = $this->params['order_item_id'];
			$booking_id    = $this->params['booking_id'];

			$booking_params = $this->params['order_items'][ $order_item_id ]['bookings'][ $booking_id ];
			$booking        = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );

			if ( $this->params['order_items'][ $order_item_id ]['variant'] == LATEPOINT_ITEM_VARIANT_BUNDLE ) {
				$response_html = OsOrdersHelper::generate_order_item_pill_for_bundle_booking( $booking, $order_item_id );
			} else {
				$response_html = OsOrdersHelper::generate_order_item_pill_for_booking( $booking, $order_item_id );
			}
			$this->send_json(
				array(
					'status'  => LATEPOINT_STATUS_SUCCESS,
					'message' => $response_html,
				)
			);
		}

		function generate_order_item_booking_data_form() {
			$order_item          = new OsOrderItemModel();
			$order_item->variant = $this->params['order_item_variant'] ?? LATEPOINT_ITEM_VARIANT_BOOKING;
			if ( ! empty( $this->params['order_item_id'] ) ) {
				// existing order item
				if ( strpos( $this->params['order_item_id'], 'new_' ) !== false ) {
					$order_item->form_id = $this->params['order_item_id'];
				} else {
					$order_item->id = $this->params['order_item_id'];
				}
				$order_item->item_data = ! empty( $this->params['order_item_item_data'] ) ? base64_decode( $this->params['order_item_item_data'] ) : '';
				if ( $order_item->is_bundle() ) {
					$booking_item_data = ! empty( $this->params['booking_item_data'] ) ? base64_decode( $this->params['booking_item_data'] ) : '';
					$booking           = OsBookingHelper::build_booking_model_from_item_data( json_decode( $booking_item_data, true ) );
				} else {
					$booking = $order_item->build_original_object_from_item_data();
				}
			} else {
				// new order item
				$booking = OsBookingHelper::prepare_new_from_params( $this->params );
			}

			$response_html = OsOrdersHelper::booking_data_form_for_order_item_id( $order_item->get_form_id(), $booking, $order_item->variant );
			$this->send_json(
				array(
					'status'  => LATEPOINT_STATUS_SUCCESS,
					'message' => $response_html,
				)
			);
		}

		function quick_edit() {

			$customers = new OsCustomerModel();
			// only load customers that belong to logged in agent, if any
			if ( ! OsRolesHelper::are_all_records_allowed( 'agent' ) ) {
				$customers->select( LATEPOINT_TABLE_CUSTOMERS . '.*' )->join( LATEPOINT_TABLE_BOOKINGS, [ 'customer_id' => LATEPOINT_TABLE_CUSTOMERS . '.id' ] )->group_by( LATEPOINT_TABLE_CUSTOMERS . '.id' )->where( [ LATEPOINT_TABLE_BOOKINGS . '.agent_id' => OsRolesHelper::get_allowed_records( 'agent' ) ] );
			}


			$customers_arr           = $customers->order_by( 'first_name asc, last_name asc' )->set_limit( 20 )->get_results_as_models();
			$this->vars['customers'] = $customers_arr;

			// CREATING NEW ORDER
			$order    = new OsOrderModel();
			$order_id = $this->params['id'] ?? false;

			if ( ! empty( $this->params['booking_id'] ) ) {
				$preselected_booking    = new OsBookingModel( $this->params['booking_id'] );
				$preselected_order_item = new OsOrderItemModel( $preselected_booking->order_item_id );
				$order_id               = $preselected_order_item->order_id;
			} else {
				$preselected_booking    = false;
				$preselected_order_item = false;
			}

			if ( $order_id ) {
				// EDITING EXISTING ORDER
				$order = ( new OsOrderModel() )->where( [ LATEPOINT_TABLE_ORDERS . '.id' => absint( $order_id ) ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
				if ( ! $order ) {
					$this->access_not_allowed();
					return;
				}

				$transactions = $order->get_transactions();

			} else {
				// NEW ORDER

				// LOAD FROM PASSED PARAMS
				$order->status             = OsOrdersHelper::get_default_order_status();
				$order->fulfillment_status = $order->get_default_fulfillment_status();

				if ( ! empty( $this->params['customer_id'] ) ) {
					$order->customer_id = $this->params['customer_id'];
				}

				$new_booking                 = OsBookingHelper::prepare_new_from_params( $this->params );
				$new_booking                 = apply_filters( 'latepoint_prepare_booking_for_quick_view', $new_booking );
				$order_item_model            = new OsOrderItemModel();
				$order_item_model->variant   = LATEPOINT_ITEM_VARIANT_BOOKING;
				$order_item_model->item_data = $new_booking->generate_item_data();

				$order->items[] = $order_item_model;

				$order->total    = $order->recalculate_total();
				$order->subtotal = $order->recalculate_subtotal();

				$transactions = [];
			}

			$bundles               = new OsBundleModel();
			$bundles               = $bundles->should_be_active()->get_results_as_models();
			$this->vars['bundles'] = $bundles;


			$this->vars['price_breakdown_rows'] = $order->generate_price_breakdown_rows();

			$order = apply_filters( 'latepoint_prepare_order_for_quick_view', $order );

			$order_bookings = $order->get_bookings_from_order_items();
			$order_bundles  = $order->get_bundles_from_order_items();


			$this->vars['selected_customer']           = new OsCustomerModel( $order->customer_id );
			$this->vars['order']                       = $order;
			$this->vars['preselected_booking']         = $preselected_booking;
			$this->vars['preselected_order_item']      = $preselected_order_item;
			$this->vars['show_only_preselected_items'] = $preselected_booking && $preselected_order_item && ( ( count( $order_bookings ) > 1 ) || ( count( $order_bundles ) ) || ( $order_bundles && $order_bookings ) );

			$this->vars['order_bookings']              = $order_bookings;
			$this->vars['order_bundles']               = $order_bundles;
			$this->vars['transactions']                = $transactions;
			$this->vars['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
			$this->format_render( __FUNCTION__ );
		}

		/**
		 * Legacy method. Not used anymore.
		 * But kept for backward compatibility with old code that might be using it
		 */
		// public function edit_form() {
		// 	if ( empty( $this->params['id'] ) ) {
		// 		$order = new OsOrderModel();
		// 	} else {
		// 		$order = ( new OsOrderModel() )->where( [ LATEPOINT_TABLE_ORDERS . '.id' => absint( $this->params['id'] ) ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
		// 		if ( ! $order ) {
		// 			$this->send_json(
		// 				array(
		// 					'status'  => LATEPOINT_STATUS_ERROR,
		// 					'message' => __( 'Not Allowed', 'latepoint' ),
		// 				)
		// 			);
		// 			return;
		// 		}
		// 	}
		// 	// legacy fix for older orders that didn't have portion column, get it from connected order
		// 	if ( ! $order->is_new_record() && empty( $order->payment_portion ) && ! empty( $order->booking_id ) ) {
		// 		$booking = new OsBookingModel( $order->booking_id );
		// 		if ( ! empty( $booking->id ) ) {
		// 			$order->payment_portion = $booking->payment_portion;
		// 		}
		// 	}
		// 	$this->vars['real_or_rand_id'] = ( $order->is_new_record() ) ? 'new_order_' . OsUtilHelper::random_text( 'alnum', 5 ) : $order->id;
		// 	$this->vars['order']           = $order;

		// 	$this->format_render( __FUNCTION__ );
		// }

		public function destroy() {
			if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
				$this->check_nonce( 'destroy_order_' . $this->params['id'] );
				$order = ( new OsOrderModel() )->where( [ LATEPOINT_TABLE_ORDERS . '.id' => absint( $this->params['id'] ) ] )->filter_allowed_records()->set_limit( 1 )->get_results_as_models();
				if ( ! $order ) {
					$this->send_json(
						array(
							'status'  => LATEPOINT_STATUS_ERROR,
							'message' => __( 'Not Allowed', 'latepoint' ),
						)
					);
				}
				if ( $order->delete() ) {
					$status        = LATEPOINT_STATUS_SUCCESS;
					$response_html = __( 'Order Removed', 'latepoint' );
				} else {
					$status        = LATEPOINT_STATUS_ERROR;
					$response_html = __( 'Error Removing Order', 'latepoint' );
				}
			} else {
				$status        = LATEPOINT_STATUS_ERROR;
				$response_html = __( 'Error Removing Order', 'latepoint' );
			}
			if ( $this->get_return_format() == 'json' ) {
				$this->send_json(
					array(
						'status'  => $status,
						'message' => $response_html,
					)
				);
			}
		}

		/*
			Index of orders
		*/

		public function index() {

			$per_page    = OsSettingsHelper::get_number_of_records_per_page();
			$page_number = isset( $this->params['page_number'] ) ? $this->params['page_number'] : 1;

			$this->vars['page_header'] = false;

			$orders = new OsOrderModel();


			// TABLE SEARCH FILTERS
			$filter     = $this->params['filter'] ?? false;
			$query_args = [];
			if ( $filter ) {
				if ( ! empty( $filter['id'] ) ) {
					$query_args['id'] = $filter['id'];
				}
				if ( ! empty( $filter['total'] ) ) {
					$query_args['total'] = $filter['total'];
				}
				if ( ! empty( $filter['status'] ) ) {
					$query_args['status'] = $filter['status'];
				}
				if ( ! empty( $filter['payment_status'] ) ) {
					$query_args['payment_status'] = $filter['payment_status'];
				}
				if ( ! empty( $filter['fulfillment_status'] ) ) {
					$query_args['fulfillment_status'] = $filter['fulfillment_status'];
				}
				if ( ! empty( $filter['confirmation_code'] ) ) {
					$query_args['confirmation_code LIKE'] = '%' . $filter['confirmation_code'] . '%';
				}

				if ( ! empty( $filter['customer']['full_name'] ) ) {
					$orders->select( LATEPOINT_TABLE_ORDERS . '.*, ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name, ' . LATEPOINT_TABLE_CUSTOMERS . '.last_name' );
					$orders->join( LATEPOINT_TABLE_CUSTOMERS, [ 'id' => LATEPOINT_TABLE_ORDERS . '.customer_id' ] );

					$query_args[ 'concat_ws(" ", ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name,' . LATEPOINT_TABLE_CUSTOMERS . '.last_name) LIKE' ] = '%' . $filter['customer']['full_name'] . '%';
					$this->vars['customer_name_query'] = $filter['customer']['full_name'];

				}

				if ( ! empty( $filter['created_at_from'] ) && ! empty( $filter['created_at_to'] ) ) {
					$query_args['created_at >='] = $filter['created_at_from'] . ' 00:00:00';
					$query_args['created_at <='] = $filter['created_at_to'] . ' 23:59:59';
				}
			}


			// OUTPUT CSV IF REQUESTED
			if ( isset( $this->params['download'] ) && $this->params['download'] == 'csv' ) {
				$csv_filename = 'payments_' . OsUtilHelper::random_text() . '.csv';

				header( 'Content-Type: text/csv' );
				header( "Content-Disposition: attachment; filename={$csv_filename}" );

				$labels_row = [
					__( 'ID', 'latepoint' ),
					__( 'Token', 'latepoint' ),
					__( 'Booking ID', 'latepoint' ),
					__( 'Customer', 'latepoint' ),
					__( 'Processor', 'latepoint' ),
					__( 'Method', 'latepoint' ),
					__( 'Amount', 'latepoint' ),
					__( 'Status', 'latepoint' ),
					__( 'Type', 'latepoint' ),
					__( 'Date', 'latepoint' ),
				];


				$orders_data   = [];
				$orders_data[] = $labels_row;


				$orders_arr = $orders->where( $query_args )->filter_allowed_records()->get_results_as_models();

				if ( $orders_arr ) {
					foreach ( $orders_arr as $order ) {
						$values_row    = [
							$order->id,
							$order->token,
							$order->booking_id,
							( $order->customer_id ? $order->customer->full_name : 'n/a' ),
							$order->processor,
							$order->payment_method,
							OsMoneyHelper::format_price( $order->amount, true, false ),
							$order->status,
							$order->kind,
							$order->created_at,
						];
						$values_row    = apply_filters( 'latepoint_order_row_for_csv_export', $values_row, $order, $this->params );
						$orders_data[] = $values_row;
					}
				}

				$orders_data = apply_filters( 'latepoint_orders_data_for_csv_export', $orders_data, $this->params );
				OsCSVHelper::array_to_csv( $orders_data );

				return;
			}

			if ( $query_args ) {
				$orders->where( $query_args );
			}
			$orders->filter_allowed_records();


			$count_orders = clone $orders;
			$total_orders = $count_orders->count();

			$orders = $orders->order_by( LATEPOINT_TABLE_ORDERS . '.created_at desc' )->set_limit( $per_page );
			if ( $page_number > 1 ) {
				$orders = $orders->set_offset( ( $page_number - 1 ) * $per_page );
			}

			$this->vars['orders'] = $orders->get_results_as_models();

			$this->vars['total_orders']        = $total_orders;
			$this->vars['current_page_number'] = $page_number;
			$this->vars['per_page']            = $per_page;
			$total_pages                       = ceil( $total_orders / $per_page );
			$this->vars['total_pages']         = $total_pages;

			$this->vars['showing_from'] = ( ( $page_number - 1 ) * $per_page ) ? ( ( $page_number - 1 ) * $per_page ) : 1;
			$this->vars['showing_to']   = min( $page_number * $per_page, $total_orders );

			$this->format_render(
				[
					'json_view_name' => '_table_body',
					'html_view_name' => __FUNCTION__,
				],
				[],
				[
					'total_pages'   => $total_pages,
					'showing_from'  => $this->vars['showing_from'],
					'showing_to'    => $this->vars['showing_to'],
					'total_records' => $total_orders,
				]
			);
		}
	}


endif;
