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

class OsInvoicesHelper {

	public static function is_enabled(): bool {
		return OsSettingsHelper::is_on( 'enable_invoices', LATEPOINT_VALUE_ON );
	}

	public static function get_invoices_for_select(): array {
		$invoices        = new OsInvoiceModel();
		$invoices        = $invoices->order_by( 'id desc' )->set_limit( 100 )->get_results_as_models();
		$invoice_options = [];
		foreach ( $invoices as $invoice ) {
			$name              = sprintf( esc_html__( '%s To %s', 'latepoint' ), OsMoneyHelper::format_price( $invoice->charge_amount, true, false ), $invoice->get_customer()->full_name ) . ' [#' . $invoice->get_invoice_number() . ' : ID: ' . $invoice->id . ']';
			$invoice_options[] = [
				'value' => $invoice->id,
				'label' => esc_html( $name ),
			];
		}

		return $invoice_options;
	}

	public static function replace_invoice_vars_in_template( string $text, array $vars, string $original_text ): string {
		if ( isset( $vars['invoice'] ) ) {
			$invoice      = $vars['invoice'];
			$needles      = [
				'{{invoice_status}}',
				'{{invoice_due_date}}',
				'{{invoice_amount}}',
				'{{invoice_number}}',
				'{{invoice_access_url}}',
				'{{invoice_pay_url}}',
				'{{invoice_receipt_url}}',
			];
			$replacements = [
				OsInvoicesHelper::readable_status( $invoice->status ),
				$invoice->get_readable_due_at(),
				OsMoneyHelper::format_price( $invoice->charge_amount, true, false ),
				$invoice->get_invoice_number(),
				$invoice->get_access_url(),
				$invoice->get_pay_url(),
				$invoice->get_receipt_url(),
			];
			$text         = str_replace( $needles, $replacements, $text );

		}
		return $text;
	}

	public static function handle_invoice_created( OsInvoiceModel $invoice ) {
		$objects   = [];
		$objects[] = [
			'model'       => 'invoice',
			'id'          => $invoice->id,
			'model_ready' => $invoice,
		];
		OsProcessJobsHelper::create_jobs_for_event( 'invoice_created', $objects );
	}


	public static function handle_invoice_updated( OsInvoiceModel $new_invoice, OsInvoiceModel $old_invoice ) {
		// remove previously scheduled jobs for this invoice because it's changed and might not need them anymore
		// remove only those that are in "scheduled" status, those that were already sent or errored should stay
		$jobs = new OsProcessJobModel();
		$jobs->delete_where(
			[
				'status'            => LATEPOINT_JOB_STATUS_SCHEDULED,
				'object_id'         => $new_invoice->id,
				'object_model_type' => 'invoice',
			]
		);

		$objects   = [];
		$objects[] = [
			'model'       => 'invoice',
			'id'          => $new_invoice->id,
			'model_ready' => $new_invoice,
		];
		$objects[] = [
			'model'       => 'old_invoice',
			'id'          => $old_invoice->id,
			'model_ready' => $old_invoice,
		];
		OsProcessJobsHelper::create_jobs_for_event( 'invoice_updated', $objects );
	}


	public static function log_invoice_created( OsInvoiceModel $invoice ) {
		$data                = [];
		$data['invoice_id']  = $invoice->id;
		$data['code']        = 'invoice_created';
		$data['description'] = wp_json_encode( [ 'invoice_data_vars' => $invoice->get_data_vars() ] );
		OsActivitiesHelper::create_activity( $data );
	}

	public static function log_invoice_updated( OsInvoiceModel $invoice, OsInvoiceModel $old_invoice ) {
		$data                = [];
		$data['invoice_id']  = $invoice->id;
		$data['code']        = 'invoice_updated';
		$data['description'] = wp_json_encode(
			[
				'invoice_data_vars' => [
					'new' => $invoice->get_data_vars(),
					'old' => $old_invoice->get_data_vars(),
				],
			]
		);
		OsActivitiesHelper::create_activity( $data );
	}

	public static function readable_status( string $status ): string {
		$statuses = self::list_of_statuses_for_select();

		return $statuses[ $status ] ?? __( 'n/a', 'latepoint' );
	}

	public static function get_invoice_by_key( string $key ): OsInvoiceModel {
		if ( empty( $key ) ) {
			return new OsInvoiceModel();
		}
		$invoice = new OsInvoiceModel();

		return $invoice->where( [ 'access_key' => $key ] )->set_limit( 1 )->get_results_as_models();
	}

	public static function get_invoice_logo_url(): string {
		$default_logo_url = LATEPOINT_IMAGES_URL . 'logo.svg';
		return OsImageHelper::get_image_url_by_id( OsSettingsHelper::get_settings_value( 'invoices_company_logo' ), 'thumbnail', $default_logo_url );
	}

	public static function invoice_document_html( OsInvoiceModel $invoice, bool $show_controls, ?OsTransactionModel $transaction = null ) {
		$invoice_data = json_decode( $invoice->data, true );
		?>
		<div class="invoice-document status-<?php echo esc_attr( $invoice->status ); ?>" data-invoice-id="<?php echo esc_attr( $invoice->id ); ?>" data-route="<?php echo OsRouterHelper::build_route_name( 'invoices', 'change_status' ); ?>">
			<?php if ( $show_controls ) { ?>
				<div class="invoice-controls">
					<div class="ic-block">
						<?php
						echo OsFormHelper::select_field( 'invoice[status]', __( 'Status', 'latepoint' ), OsInvoicesHelper::list_of_statuses_for_select(), $invoice->status, [ 'class' => 'invoice-change-status-selector' ] );
						// Add protection nonce for status change.
						wp_nonce_field( 'change_invoice_status_' . $invoice->id, '_wpnonce_status', false );
						?>
					</div>
					<div class="ic-block">
						<a target="_blank" href="<?php echo $invoice->get_access_url(); ?>" class="ic-external-link">
							<span><?php esc_html_e( 'Open', 'latepoint' ); ?></span>
							<i class="latepoint-icon latepoint-icon-external-link"></i>
						</a>
					</div>
					<div class="ic-block make-last">
						<button type="button" class="latepoint-btn latepoint-btn-sm latepoint-btn-outline"
								data-os-params="<?php echo esc_attr( http_build_query( [ 'invoice_id' => $invoice->id ] ) ); ?>"
							  data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'invoices', 'edit_data' ) ); ?>"
							  data-os-output-target="lightbox"
								 data-os-after-call="latepointInvoicesAdminFeature.init_invoice_data_form"
							  data-os-lightbox-classes="width-500">
							<i class="latepoint-icon latepoint-icon-edit-2"></i>
							<span><?php esc_html_e( 'Edit Data', 'latepoint' ); ?></span>
						</button>
						<button type="button" class="latepoint-btn latepoint-btn-sm latepoint-btn-outline"
								data-os-params="<?php echo esc_attr( http_build_query( [ 'invoice_id' => $invoice->id ] ) ); ?>"
							  data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'invoices', 'email_form' ) ); ?>"
							  data-os-output-target="lightbox"
								 data-os-after-call="latepointInvoicesAdminFeature.init_email_invoice_form"
							  data-os-lightbox-classes="width-500">
							<i class="latepoint-icon latepoint-icon-mail"></i>
							<span><?php esc_html_e( 'Email Invoice', 'latepoint' ); ?></span>
						</button>
					</div>
				</div>
			<?php } ?>
			<div class="invoice-document-i">
				<?php
				if ( empty( $transaction ) ) {
					switch ( $invoice->status ) {
						case LATEPOINT_INVOICE_STATUS_PAID:
							echo '<div class="invoice-status-paid-label">' . esc_html( self::readable_status( $invoice->status ) ) . '</div>';
							break;
						case LATEPOINT_INVOICE_STATUS_DRAFT:
							echo '<div class="invoice-status-draft-label">' . esc_html( self::readable_status( $invoice->status ) ) . '</div>';
							break;
						case LATEPOINT_INVOICE_STATUS_VOID:
							echo '<div class="invoice-status-voided-label">' . esc_html( self::readable_status( $invoice->status ) ) . '</div>';
							break;
					}
				}
				?>
				<div class="invoice-heading">
					<div class="invoice-info">
						<div class="invoice-title"><?php echo $transaction ? esc_html__( 'Receipt', 'latepoint' ) : esc_html__( 'Invoice', 'latepoint' ); ?></div>
						<div class="invoice-data">
							<div class="invoice-row">
								<div class="id-label"><?php esc_html_e( 'Invoice number', 'latepoint' ); ?></div>
								<div class="id-value"><?php echo esc_html( $invoice->get_invoice_number() ); ?></div>
							</div>
							<?php if ( $transaction ) { ?>
								<div class="invoice-row">
									<div class="id-label"><?php esc_html_e( 'Receipt number', 'latepoint' ); ?></div>
									<div class="id-value"><?php echo esc_html( $transaction->receipt_number ); ?></div>
								</div>
								<div class="invoice-row">
									<div class="id-label"><?php esc_html_e( 'Date paid', 'latepoint' ); ?></div>
									<div class="id-value"><?php echo esc_html( OsTimeHelper::get_readable_date( new OsWpDateTime( $transaction->created_at, new DateTimeZone( 'UTC' ) ) ) ); ?></div>
								</div>
							<?php } else { ?>
								<div class="invoice-row">
									<div class="id-label"><?php esc_html_e( 'Date of issue', 'latepoint' ); ?></div>
									<div class="id-value"><?php echo esc_html( OsTimeHelper::get_readable_date( new OsWpDateTime( $invoice->created_at, new DateTimeZone( 'UTC' ) ) ) ); ?></div>
								</div>
								<div class="invoice-row">
									<div class="id-label"><?php esc_html_e( 'Date due', 'latepoint' ); ?></div>
									<div class="id-value"><?php echo esc_html( OsTimeHelper::get_readable_date( new OsWpDateTime( $invoice->due_at, new DateTimeZone( 'UTC' ) ) ) ); ?></div>
								</div>
							<?php } ?>
							<?php if ( ! empty( $invoice_data['tax_id'] ) ) { ?>
							<div class="invoice-row">
								<div class="id-label"><?php esc_html_e( 'VAT Number', 'latepoint' ); ?></div>
								<div class="id-value"><?php echo esc_html( $invoice_data['tax_id'] ); ?></div>
							</div>
							<?php } ?>
						</div>
					</div>
					<div class="invoice-logo">
						<img src="<?php echo esc_attr( self::get_invoice_logo_url() ); ?>" width="50" height="50" alt="<?php esc_attr_e( 'LatePoint Dashboard', 'latepoint' ); ?>">
					</div>
				</div>
				<div class="invoice-to-from">
					<div class="invoice-from">
						<?php if ( ! empty( $invoice_data['company'] ) ) { ?>
						<div class="if-heading"><?php echo esc_html( $invoice_data['company'] ); ?></div>
						<?php } ?>
						<div class="if-data-block">
							<?php echo wp_kses( $invoice_data['from'], [ 'br' => [] ] ); ?>
						</div>
					</div>
					<div class="invoice-from">
						<div class="if-heading"><?php echo esc_html( __( 'Bill to', 'latepoint' ) ); ?></div>
						<div class="if-data-block">
							<?php echo wp_kses( $invoice_data['to'], [ 'br' => [] ] ); ?>
						</div>
					</div>
				</div>

				<?php do_action( 'latepoint_invoice_before_amount_info', $invoice, $transaction ); ?>

				<?php if ( empty( $transaction ) ) { ?>
					<div class="invoice-due-info">
						<div class="invoice-due-amount">
							<?php echo esc_html( sprintf( __( '%s due %s', 'latepoint' ), OsMoneyHelper::format_price( $invoice->charge_amount, true, false ), OsTimeHelper::get_readable_date_from_string( $invoice->due_at ) ) ); ?>
						</div>
						<?php if ( $invoice->status == LATEPOINT_INVOICE_STATUS_OPEN ) { ?>
							<div class="invoice-due-pay-link-w">
								<a href="<?php echo $invoice->get_pay_url(); ?>" target="_blank"><?php esc_html_e( 'Pay Online', 'latepoint' ); ?></a>
							</div>
						<?php } ?>
						<?php if ( $invoice->status == LATEPOINT_INVOICE_STATUS_PAID || $invoice->get_successful_payments() ) { ?>
							<div class="invoice-due-pay-link-w">
								<a target="_blank"
								   href="<?php echo OsOrdersHelper::generate_direct_manage_order_url( $invoice->get_order(), 'customer', 'list_payments' ) ?>"><?php esc_html_e( 'View Payments', 'latepoint' ); ?></a>
							</div>
						<?php } ?>
					</div>
				<?php } else { ?>
					<div class="invoice-due-info">
						<div class="invoice-due-amount">
							<?php echo esc_html( sprintf( __( '%s paid on %s', 'latepoint' ), OsMoneyHelper::format_price( $transaction->amount, true, false ), OsTimeHelper::get_readable_date_from_string( $transaction->created_at ) ) ); ?>
						</div>
					</div>

				<?php } ?>
				<div class="invoice-items">
					<div class="invoice-items-table-heading">
						<div class="it-column"><?php esc_html_e( 'Description', 'latepoint' ); ?></div>
						<div class="it-column"><?php esc_html_e( 'Amount', 'latepoint' ); ?></div>
					</div>
					<?php OsPriceBreakdownHelper::output_price_breakdown( $invoice_data['price_breakdown'] ); ?>
				</div>
				<div class="invoice-totals">
					<div class="it-row">
						<div class="it-column"><?php esc_html_e( 'Subtotal', 'latepoint' ); ?></div>
						<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $invoice_data['totals']['subtotal'], true, false ) ); ?></div>
					</div>
					<div class="it-row">
						<div class="it-column"><?php esc_html_e( 'Total', 'latepoint' ); ?></div>
						<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $invoice_data['totals']['total'], true, false ) ); ?></div>
					</div>
					<?php if ( empty( $transaction ) ) { ?>
						<?php if ( ! empty( $invoice_data['totals']['payments'] ) ) { ?>
							<div class="it-row it-row-positive">
								<div class="it-column"><?php esc_html_e( 'Payments & Credits', 'latepoint' ); ?></div>
								<div class="it-column"><?php echo '-' . esc_html( OsMoneyHelper::format_price( $invoice_data['totals']['payments'], true, false ) ); ?></div>
							</div>
						<?php } ?>
						<div class="it-row it-row-bold">
							<div class="it-column"><?php esc_html_e( 'Amount Due', 'latepoint' ); ?></div>
							<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $invoice->charge_amount, true, false ) ); ?></div>
						</div>
					<?php } else { ?>
						<div class="it-row it-row-bold">
							<div class="it-column"><?php esc_html_e( 'Amount Paid', 'latepoint' ); ?></div>
							<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $transaction->amount, true, false ) ); ?></div>
						</div>
					<?php } ?>
				</div>
				<?php if ( OsSettingsHelper::get_settings_value( 'invoice_terms', '' ) ) { ?>
					<div class="invoice-terms">
						<div class="terms-heading"><?php esc_html_e( 'Terms & Conditions', 'latepoint' ); ?></div>
						<div class="terms-content"><?php echo esc_html( OsSettingsHelper::get_settings_value( 'invoice_terms', '' ) ); ?></div>
					</div>
				<?php } ?>
			</div>
		</div>
		<?php
	}

	public static function list_of_statuses_for_select(): array {
		$statuses = [
			LATEPOINT_INVOICE_STATUS_OPEN           => __( 'Open', 'latepoint' ),
			LATEPOINT_INVOICE_STATUS_PAID           => __( 'Paid', 'latepoint' ),
			LATEPOINT_INVOICE_STATUS_PARTIALLY_PAID => __( 'Partially Paid', 'latepoint' ),
			LATEPOINT_INVOICE_STATUS_DRAFT          => __( 'Draft', 'latepoint' ),
			LATEPOINT_INVOICE_STATUS_VOID           => __( 'Void', 'latepoint' ),
			LATEPOINT_INVOICE_STATUS_UNCOLLECTIBLE  => __( 'Uncollectible', 'latepoint' ),
		];

		/**
		 * Get the list of invoice statuses
		 *
		 * @param {array} $statuses Array of invoice statuses
		 *
		 * @returns {array} Filtered array of invoice statuses
		 * @since 5.1.0
		 * @hook latepoint_invoices_statuses_for_select
		 *
		 */
		return apply_filters( 'latepoint_invoices_statuses_for_select', $statuses );
	}

	public static function list_of_payment_portions_for_select(): array {
		$payment_portions = [
			LATEPOINT_PAYMENT_PORTION_FULL      => __( 'Full', 'latepoint' ),
			LATEPOINT_PAYMENT_PORTION_REMAINING => __( 'Remaining', 'latepoint' ),
			LATEPOINT_PAYMENT_PORTION_DEPOSIT   => __( 'Deposit', 'latepoint' ),
			LATEPOINT_PAYMENT_PORTION_CUSTOM    => __( 'Custom', 'latepoint' ),
		];

		/**
		 * Get the list of invoice payment_portions
		 *
		 * @param {array} $payment_portions Array of invoice payment_portions
		 *
		 * @returns {array} Filtered array of invoice payment_portions
		 * @since 5.1.0
		 * @hook latepoint_invoices_payment_portions_for_select
		 *
		 */
		return apply_filters( 'latepoint_invoices_payment_portions_for_select', $payment_portions );
	}

	/**
	 * @param OsOrderModel $order
	 *
	 * @return void
	 * @throws Exception
	 */
	public static function list_invoices_for_order( OsOrderModel $order ) {
		if ( $order->is_new_record() ) {
			return;
		}
		$invoices = new OsInvoiceModel();
		$invoices = $invoices->where( [ 'order_id' => $order->id ] )->get_results_as_models();
		if ( OsRolesHelper::can_user( 'invoice__view' ) ) { ?>
			<div class="invoices-info-w">
				<div class="os-form-sub-header">
					<h3><?php esc_html_e( 'Invoices', 'latepoint' ); ?></h3>
				</div>
				<div class="list-of-invoices">
				<?php if ( $invoices ) {
					foreach ( $invoices as $invoice ) {
						echo OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice );
					}
				}
				?>
				</div>

				<?php if ( OsRolesHelper::can_user( 'invoice__create' ) ) { ?>
					<div class="quick-add-item-button"
						 data-os-params="<?php echo OsUtilHelper::build_os_params( [ 'order_id' => $order->id ] ); ?>"
						 data-os-after-call="latepointInvoicesAdminFeature.init_quick_invoice_settings_form"
						 data-os-output-target=".quick-invoice-settings-form-wrapper"
						 data-os-before-after="before"
						 data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'invoices', 'new_form' ) ); ?>">
						<i class="latepoint-icon latepoint-icon-plus2"></i>
						<span><?php esc_html_e( 'New Invoice', 'latepoint' ); ?></span>
					</div>
					<div class="quick-invoice-settings-form-wrapper"></div>
				<?php } ?>
			</div>
			<?php
		}
	}

	/**
	 * @param OsOrderModel $order
	 *
	 * @return array
	 */
	public static function generate_invoice_data_from_order( OsOrderModel $order ): array {
		$data = [
			'company'         => OsReplacerHelper::replace_business_vars( OsSettingsHelper::get_settings_value( 'invoices_company_name', '' ) ),
			'from'            => OsReplacerHelper::replace_all_vars(
				OsInvoicesHelper::get_invoice_data_bill_from(),
				[
					'order'    => $order,
					'customer' => $order->get_customer(),
				]
			),
			'to'              => OsReplacerHelper::replace_all_vars(
				OsInvoicesHelper::get_invoice_data_bill_to(),
				[
					'order'    => $order,
					'customer' => $order->get_customer(),
				]
			),
			'price_breakdown' => json_decode( $order->price_breakdown ),
			'tax_id'          => OsSettingsHelper::get_settings_value( 'invoices_tax_id', '' ),
			'totals'          => [
				'subtotal' => $order->get_subtotal(),
				'total'    => $order->get_total(),
			],
		];


		/**
		 * Invoice data generated from an order
		 *
		 * @since 5.1.0
		 * @hook latepoint_invoices_data_from_order
		 *
		 * @param {array} $data array of values for invoice
		 * @param {OsOrderModel} $order order model
		 * @returns {array} The filtered array of invoice data
		 */
		$data = apply_filters( 'latepoint_invoices_data_from_order', $data, $order );

		return $data;
	}

	/**
	 * @param OsOrderModel $order
	 * @param OsPaymentRequestModel|null $payment_request
	 *
	 * @return void
	 */
	public static function create_invoices_for_new_order( OsOrderModel $order, ?OsPaymentRequestModel $payment_request = null ) {
		if ( ! self::is_enabled() ) {
			return;
		}
		if ( $order->get_total() <= 0 && ! OsSettingsHelper::is_on( 'create_invoices_for_free_bookings', LATEPOINT_VALUE_OFF ) ) {
			return;
		}
		$existing_invoices     = new OsInvoiceModel();
		$existing_invoices     = $existing_invoices->where( [ 'order_id' => $order->id ] )->get_results_as_models();
		$total_invoiced_amount = 0;
		if ( $existing_invoices ) {
			foreach ( $existing_invoices as $invoice ) {
				$total_invoiced_amount += $invoice->charge_amount;
			}
		}
		$order_total           = $order->get_total();
		$total_invoiced_amount = OsMoneyHelper::pad_to_db_format( $total_invoiced_amount );

		if ( $total_invoiced_amount > 0 && $total_invoiced_amount == $order_total ) {
			return;
		}
		$invoice           = new OsInvoiceModel();
		$invoice->order_id = $order->id;
		$invoice->data     = json_encode( self::generate_invoice_data_from_order( $order ) );

		if ( $order->get_initial_payment_data_value( 'time' ) == LATEPOINT_PAYMENT_TIME_NOW ) {
			// need to pay now, portion will depend on what customer/booker selected
			$invoice->payment_portion = $order->get_initial_payment_data_value( 'portion' );
			$invoice->charge_amount   = $order->get_initial_payment_data_value( 'charge_amount' );
			if ( $order->get_initial_payment_data_value( 'portion' ) != LATEPOINT_PAYMENT_PORTION_FULL ) {
				// since we are not paying full balance, create invoice for the remaining amount
				$invoice_for_remaining_balance                  = clone $invoice;
				$invoice_for_remaining_balance->charge_amount   = $order->get_total() - $invoice->charge_amount;
				$invoice_for_remaining_balance->payment_portion = LATEPOINT_PAYMENT_PORTION_REMAINING;
			}
			if ( ! empty( $payment_request ) ) {
				$invoice->due_at = $payment_request->due_at;
			}
		} else {
			// will pay later, need to generate full price invoice
			$invoice->charge_amount   = $order->get_total();
			$invoice->payment_portion = LATEPOINT_PAYMENT_PORTION_FULL;
		}


		if ( $order->get_total_amount_paid_from_transactions() == $invoice->charge_amount ) {
			// since the order has just been created - any transactions that were made - are part of the time of creation, so should be on the invoice
			$invoice->status = LATEPOINT_INVOICE_STATUS_PAID;
		}


		if ( $invoice->save() ) {
			/**
			 * Invoice was created
			 *
			 * @param {OsInvoiceModel} $invoice instance of invoice model that was created
			 *
			 * @since 5.1.0
			 * @hook latepoint_invoice_created
			 *
			 */
			do_action( 'latepoint_invoice_created', $invoice );
			if ( ! empty( $payment_request ) ) {
				// if we have payment request, update it with created invoice
				$payment_request->invoice_id = $invoice->id;
				$payment_request->order_id   = $order->id;
				if ( $payment_request->save() ) {
					/**
					 * Invoice was created
					 *
					 * @param {OsInvoiceModel} $payment_request instance of payment request model that was created
					 *
					 * @since 5.1.0
					 * @hook latepoint_payment_request_created
					 *
					 */
					do_action( 'latepoint_payment_request_created', $payment_request );
				}
			}
			if ( isset( $invoice_for_remaining_balance ) && $invoice_for_remaining_balance->charge_amount > 0 ) {
				$order_items = $order->get_items();
				foreach ( $order_items as $item ) {
					if ( $item->is_booking() ) {
						$booking                               = $item->build_original_object_from_item_data();
						$invoice_for_remaining_balance->due_at = $booking->start_datetime_utc;
						break;
					}
				}
				$invoice_for_remaining_balance->status = LATEPOINT_INVOICE_STATUS_DRAFT;
				$invoice_for_remaining_balance->save();
				/**
				 * Invoice was created
				 *
				 * @param {OsInvoiceModel} $invoice instance of invoice model that was created
				 *
				 * @since 5.1.0
				 * @hook latepoint_invoice_created
				 *
				 */
				do_action( 'latepoint_invoice_created', $invoice_for_remaining_balance );
			}
		}
	}

	/**
	 *
	 * Tries to get a matching invoice for a transaction, this is only useful when a new order is created and a transaction needs to find an invoice to attach to
	 *
	 * @param OsTransactionModel $transaction
	 *
	 * @return OsInvoiceModel
	 */
	public static function get_matching_invoice_for_transaction( OsTransactionModel $transaction ): OsInvoiceModel {
		$invoice = new OsInvoiceModel();
		$invoice->where( [ 'order_id' => $transaction->order_id ] );
		$invoice->where( [ 'payment_portion' => $transaction->payment_portion ] );
		$invoice->where( [ 'charge_amount' => $transaction->amount ] );
		$invoice->where( [ 'status' => LATEPOINT_INVOICE_STATUS_OPEN ] );
		$invoice = $invoice->set_limit( 1 )->get_results_as_models();
		if ( empty( $invoice ) ) {
			$invoice = new OsInvoiceModel();
		}

		/**
		 * Try to get a matching invoice for a transaction
		 *
		 * @param {OsTransactionModel} $transaction transaction to match invoice to
		 *
		 * @returns {OsInvoiceModel} Filtered invoice model
		 * @since 5.1.0
		 * @hook latepoint_invoice_get_matching_invoice_for_transaction
		 *
		 */
		return apply_filters( 'latepoint_invoice_get_matching_invoice_for_transaction', $invoice, $transaction );
	}

	public static function create_invoice_from_transaction( OsTransactionModel $transaction_to_create_invoice ): bool {
		$order = new OsOrderModel( $transaction_to_create_invoice->order_id );
		if ( $order->is_new_record() ) {
			return false;
		}
		$invoice                  = new OsInvoiceModel();
		$invoice->charge_amount   = $transaction_to_create_invoice->amount;
		$invoice->data            = self::generate_invoice_data_from_order( $order );
		$invoice->order_id        = $order->id;
		$invoice->status          = LATEPOINT_INVOICE_STATUS_PAID;
		$invoice->payment_portion = $transaction_to_create_invoice->payment_portion;
		$invoice->due_at          = $transaction_to_create_invoice->created_at;
		if ( $invoice->save() ) {
			return $transaction_to_create_invoice->update_attributes( [ 'invoice_id' => $invoice->id ] );
		}

		return false;
	}

	public static function generate_invoice_tile_on_order_edit_form( $invoice ): string {
		$html  = '';
		$html .= '<div class="os-invoice-wrapper" data-reload-tile-route="' . esc_attr( OsRouterHelper::build_route_name( 'invoices', 'reload_invoice_tile' ) ) . '" data-route="' . esc_attr( OsRouterHelper::build_route_name( 'invoices', 'view' ) ) . '" data-invoice-id="' . esc_attr( $invoice->id ) . '">';
		$html .= '<div class="quick-invoice-head">
                <div class="quick-invoice-icon"><i class="latepoint-icon latepoint-icon-file-text"></i></div>
                <div class="quick-invoice-amount">' . OsMoneyHelper::format_price( $invoice->charge_amount, true, false ) . '</div>
                <div class="lp-invoice-status lp-invoice-status-' . $invoice->status . '">' . self::readable_status( $invoice->status ) . '</div>
              </div>
              <div class="quick-invoice-sub">
                <div class="lp-invoice-number"><span>' . esc_html__( 'Invoice Number:', 'latepoint' ) . '</span> <strong>' . esc_html( $invoice->get_invoice_number() ) . '</strong></div>
                <div class="lp-invoice-date">' . sprintf( esc_html__( 'Due: %s', 'latepoint' ), OsTimeHelper::get_readable_date( new OsWpDateTime( $invoice->due_at, new DateTimeZone( 'UTC' ) ) ) ) . '</div>
              </div>';
		$html .= '</div>';
		return $html;
	}


	public static function update_activity_after_invoice_job_run( OsActivityModel $activity, OsProcessJobModel $process_job, string $event_type ): OsActivityModel {
		switch ( $event_type ) {
			case 'invoice_created':
			case 'invoice_updated':
				$invoice               = new OsInvoiceModel( $process_job->object_id );
				$activity->customer_id = $invoice->get_customer()->id;
				$activity->order_id    = $invoice->order_id;
				break;
		}

		return $activity;
	}

	public static function add_activity_view_vars_for_invoice( array $vars, OsActivityModel $activity ): array {
		$data = json_decode( $activity->description, true );
		switch ( $activity->code ) {
			case 'invoice_created':
				$link_to_invoice      = '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', 'latepoint' ) . '</a>';
				$vars['name']         = __( 'Invoice Created', 'latepoint' );
				$vars['meta_html']    = '<div class="activity-preview-to"><span class="os-value">' . $link_to_invoice . '</span><span class="os-label">' . __( 'Created On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</div>';
				$vars['content_html'] = '<pre class="format-json">' . wp_json_encode( $data['invoice_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
				break;
			case 'invoice_updated':
				$link_to_invoice      = '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', 'latepoint' ) . '</a>';
				$vars['name']         = __( 'Invoice Updated', 'latepoint' );
				$vars['meta_html']    = '<div class="activity-preview-to"><span class="os-value">' . $link_to_invoice . '</span><span class="os-label">' . __( 'Updated On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</div>';
				$changes              = OsUtilHelper::compare_model_data_vars( $data['invoice_data_vars']['new'], $data['invoice_data_vars']['old'] );
				$vars['content_html'] = '<pre class="format-json">' . wp_json_encode( $changes, JSON_PRETTY_PRINT ) . '</pre>';
				break;
		}

		return $vars;
	}

	public static function add_invoice_activity_code( array $codes ): array {
		$codes['invoice_created'] = __( 'Invoice Created', 'latepoint' );
		$codes['invoice_updated'] = __( 'Invoice Updated', 'latepoint' );

		return $codes;
	}

	public static function add_invoice_templates_for_event_actions( array $templates, string $action_type, $wp_filesystem ) {

		switch ( $action_type ) {
			case 'send_email':
				$templates[] = [
					'id'           => 'invoice__created__to_customer',
					'to_user_type' => 'customer',
					'name'         => 'New Invoice',
					'to_email'     => '{{customer_full_name}} <{{customer_email}}>',
					'subject'      => 'New Invoice {{invoice_number}}',
					'content'      => OsEmailHelper::get_email_layout( $wp_filesystem->get_contents( LATEPOINT_ADDON_PRO_VIEWS_ABSPATH . 'mailers/customer/invoice_created.html' ) ),
				];
				$templates[] = [
					'id'           => 'invoice__paid__to_customer',
					'to_user_type' => 'customer',
					'name'         => 'Invoice Paid',
					'to_email'     => '{{customer_full_name}} <{{customer_email}}>',
					'subject'      => 'Invoice #{{invoice_number}} Paid',
					'content'      => OsEmailHelper::get_email_layout( $wp_filesystem->get_contents( LATEPOINT_ADDON_PRO_VIEWS_ABSPATH . 'mailers/customer/invoice_paid.html' ) ),
				];
		}

		return $templates;
	}

	public static function get_invoice_object_for_process( array $object_data, string $source, string $value, bool $include_model ): array {
		switch ( $source ) {
			case 'invoice_id':
				$object_data = [
					'model' => 'invoice',
					'id'    => $value,
				];
				if ( $include_model ) {
					$model                      = new OsInvoiceModel( $value );
					$object_data['model_ready'] = $model;
				}
				break;
		}

		return $object_data;
	}

	public static function prepare_replacement_vars_for_invoice( array $vars, array $data_objects, array $other_vars ): array {

		foreach ( $data_objects as $data_object ) {
			switch ( $data_object['model'] ) {
				case 'old_invoice':
					$old_invoice = $data_object['model_ready'] ?? new OsInvoiceModel( $data_object['id'] );
					$temp_vars   = self::generate_replacement_vars_from_invoice( $old_invoice );
					foreach ( $temp_vars as $key => $data ) {
						$vars[ 'old_' . $key ] = $data;
					}
					break;
				case 'invoice':
					$invoice = $data_object['model_ready'] ?? new OsInvoiceModel( $data_object['id'] );
					$vars    = array_merge( $vars, self::generate_replacement_vars_from_invoice( $invoice ) );
					break;
			}
		}

		return $vars;
	}

	/**
	 *
	 * Prepares an array of variables to be used in replacer method from a order object
	 *
	 * @param OsInvoiceModel $invoice
	 * @param array $other_vars
	 *
	 * @return array
	 */
	public static function generate_replacement_vars_from_invoice( OsInvoiceModel $invoice, array $other_vars = [] ): array {
		$vars             = [];
		$vars['invoice']  = $invoice;
		$vars['customer'] = $invoice->get_customer();
		$vars['order']    = $invoice->get_order();
		if ( ! empty( $other_vars ) ) {
			$vars = array_merge( $vars, $other_vars );
		}

		/**
		 * Returns an array of replacement variables, based on supplied <code>OsInvoiceModel</code> instance
		 *
		 * @param {array} $vars Current array of replacement variables
		 * @param {OsInvoiceModel} $invoice Instance of <code>OsInvoiceModel</code> to generate replacement variables for
		 * @param {array} $other_vars Array of additional (pre-prepared) replacement variables
		 *
		 * @returns {array} Filtered array of replacement variables
		 * @since 1.1.0
		 * @hook latepoint_prepare_replacement_vars_from_invoice
		 *
		 */
		return apply_filters( 'latepoint_prepare_replacement_vars_from_invoice', $vars, $invoice, $other_vars );
	}

	public static function add_data_source_for_invoice_events( array $data_sources, \LatePoint\Misc\ProcessEvent $event ): array {
		switch ( $event->type ) {
			case 'invoice_created':
				$invoices_for_select = \OsInvoicesHelper::get_invoices_for_select();
				$data_sources[]      = [
					'name'   => 'invoice_id',
					'values' => $invoices_for_select,
					'label'  => __( 'Choose an invoice for this test run:', 'latepoint' ),
					'model'  => 'invoice',
				];
				break;
			case 'invoice_updated':
				$invoices_for_select = \OsInvoicesHelper::get_invoices_for_select();
				$data_sources[]      = [
					'name'   => 'new_invoice_id',
					'values' => $invoices_for_select,
					'label'  => __( 'Choose old invoice to be used for this test run:', 'latepoint' ),
					'model'  => 'invoice',
				];
				$data_sources[]      = [
					'name'   => 'old_invoice_id',
					'values' => $invoices_for_select,
					'label'  => __( 'Choose new invoice to be used for this test run:', 'latepoint' ),
					'model'  => 'invoice',
				];
				break;
		}

		return $data_sources;
	}

	public static function prepare_data_for_run( \LatePoint\Misc\ProcessAction $action ): \LatePoint\Misc\ProcessAction {

		foreach ( $action->selected_data_objects as $data_object ) {
			switch ( $data_object['model'] ) {
				case 'invoice':
					$action->prepared_data_for_run['activity_data']['invoice_id'] = $data_object['id'];
					if ( ! empty( $data_object['model_ready'] ) ) {
						$action->prepared_data_for_run['activity_data']['order_id'] = $data_object['model_ready']->order_id;
					}
					break;
			}
		}

		return $action;
	}

	public static function set_object_model_type_for_invoice_processes( ?string $object_model_type, OsProcessModel $process, array $objects ): ?string {
		if ( in_array( $process->event_type, [ 'invoice_created', 'invoice_updated' ] ) ) {
			$object_model_type = 'invoice';
		}

		return $object_model_type;
	}


	public static function set_event_time_utc_for_invoice_processes( ?OsWpDateTime $event_time_utc, OsProcessModel $process, array $objects ): ?OsWpDateTime {

		if ( in_array( $process->event_type, [ 'invoice_created', 'invoice_updated' ] ) ) {
			try {
				switch ( $process->event_type ) {
					case 'invoice_created':
						$event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->created_at, new DateTimeZone( 'UTC' ) );
						break;
					case 'invoice_updated':
						$event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->updated_at, new DateTimeZone( 'UTC' ) );
						break;
				}
			} catch ( Exception $e ) {
				OsDebugHelper::log( 'Error creating jobs for workflow', 'process_jobs_error', print_r( $process->id, true ) . ' ' . print_r( $objects, true ) . ' ' . $e->getMessage() );

				return $event_time_utc;
			}
		}

		return $event_time_utc;
	}

	public static function add_invoice_to_process_event_models( OsModel $model, string $property_object_name ): OsModel {
		switch ( $property_object_name ) {
			case 'old_invoice':
			case 'invoice':
				$model = new OsInvoiceModel();
				break;
		}

		return $model;
	}

	public static function add_values_for_process_event_condition( array $values, string $property, string $property_object, string $property_attribute ): array {
		switch ( $property_object ) {
			case 'invoice':
			case 'old_invoice':
				switch ( $property_attribute ) {
					case 'status':
						$statuses = OsInvoicesHelper::list_of_statuses_for_select();
						foreach ( $statuses as $status_key => $status ) {
							$values[] = [
								'value' => $status_key,
								'label' => $status,
							];
						}
						break;
					case 'payment_portion':
						$statuses = OsInvoicesHelper::list_of_payment_portions_for_select();
						foreach ( $statuses as $status_key => $status ) {
							$values[] = [
								'value' => $status_key,
								'label' => $status,
							];
						}
						break;
				}
				break;

		}

		return $values;
	}

	public static function add_operators_to_conditions_for_invoice_events( array $operators, string $property, string $object_code, string $object_property ): array {
		switch ( $object_code ) {
			case 'old_invoice':
				// TODO time range operators instead of removing these opearators completely
				if ( $object_property != 'due_at' ) {
					$operators['equal']     = __( 'was equal to', 'latepoint' );
					$operators['not_equal'] = __( 'was not equal to', 'latepoint' );
				}
				$operators['changed']     = __( 'has changed', 'latepoint' );
				$operators['not_changed'] = __( 'has not changed', 'latepoint' );
				break;
			case 'invoice':
				// TODO time range operators instead of removing these opearators completely
				if ( $object_property != 'due_at' ) {
					$operators['equal']     = __( 'is equal to', 'latepoint' );
					$operators['not_equal'] = __( 'is not equal to', 'latepoint' );
				}
				break;
		}

		return $operators;
	}

	public static function add_conditions_for_invoice_events( array $objects, string $event_type ): array {
		switch ( $event_type ) {
			case 'invoice_created':
				$objects[] = [
					'code'       => 'invoice',
					'model'      => 'OsInvoiceModel',
					'label'      => __( 'Invoice', 'latepoint' ),
					'properties' => [],
				];
				break;
			case 'invoice_updated':
				$objects[] = [
					'code'       => 'old_invoice',
					'model'      => 'OsInvoiceModel',
					'label'      => __( 'Old Invoice', 'latepoint' ),
					'properties' => [],
				];
				$objects[] = [
					'code'       => 'invoice',
					'model'      => 'OsInvoiceModel',
					'label'      => __( 'New Invoice', 'latepoint' ),
					'properties' => [],
				];
				break;
		}

		return $objects;
	}

	public static function add_process_events_for_invoices( array $process_events ): array {
		$process_events[] = 'invoice_created';
		$process_events[] = 'invoice_updated';

		return $process_events;
	}

	public static function add_names_for_process_events_for_invoices( array $process_event_names ): array {
		$process_event_names['invoice_created'] = __( 'Invoice Created', 'latepoint' );
		$process_event_names['invoice_updated'] = __( 'Invoice Updated', 'latepoint' );

		return $process_event_names;
	}


	public static function get_subject_for_invoice_email(): string {
		$default_subject = 'Your Invoice for Order #{{order_confirmation_code}}';

		return OsSettingsHelper::get_settings_value( 'invoices_email_subject', $default_subject );
	}

	public static function get_invoice_data_bill_from(): string {
		$default = '{{business_address}}<br>{{business_phone}}';

		return OsSettingsHelper::get_settings_value( 'invoices_data_from', $default );
	}

	public static function get_invoice_data_bill_to(): string {
		$default = '{{customer_full_name}}<br>{{customer_email}}<br>{{customer_phone}}';

		return OsSettingsHelper::get_settings_value( 'invoices_data_to', $default );
	}

	public static function get_content_for_invoice_email(): string {
		$default_content = 'Hi {{customer_full_name}},<br><br>Thank you for choosing {{business_name}}.<br>You can view your invoice using <a href="{{invoice_access_url}}">this link</a><br><br>If you have any questions, feel free to contact us.';

		return OsSettingsHelper::get_settings_value( 'invoices_email_content', $default_content );
	}

	public static function output_invoice_vars() {
		?>
		<div class="available-vars-block">
			<h4><?php _e( 'Invoices', 'latepoint' ); ?></h4>
			<ul>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Status', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_status}}</span></li>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Due Date', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_due_date}}</span></li>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Amount', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_amount}}</span></li>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Number', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_number}}</span></li>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Access URL', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_access_url}}</span></li>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Pay URL', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_pay_url}}</span></li>
				<li><span class="var-label"><?php esc_html_e( 'Invoice Receipt URL', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_receipt_url}}</span></li>

			</ul>
		</div>
		<?php
	}

	public static function add_settings_sticky_menu_item( array $items ): array {
		$items[] = [
			'href'  => 'stickySectionInvoice',
			'label' => __( 'Invoices', 'latepoint' ),
		];
		return $items;
	}

	public static function output_invoice_settings() {
		?>
		<div class="white-box section-anchor" id="stickySectionInvoice">
			<div class="white-box-header">
				<div class="os-form-sub-header"><h3><?php esc_html_e( 'Invoices', 'latepoint' ); ?></h3></div>
			</div>
			<div class="white-box-content no-padding">
				<div class="sub-section-row">
					<div class="sub-section-label">
						<h3><?php esc_html_e( 'Invoices', 'latepoint' ); ?></h3>
					</div>
					<div class="sub-section-content">
						<?php echo OsFormHelper::toggler_field( 'settings[enable_invoices]', __( 'Enable Invoices', 'latepoint' ), self::is_enabled(), 'lp-invoice-settings', false, [ 'sub_label' => __( 'When disabled, no invoices will be created for new bookings and invoice-related features will be hidden', 'latepoint' ) ] ); ?>
					</div>
				</div>
				<div id="lp-invoice-settings" style="border-top: 1px solid #dcdad7; <?php echo self::is_enabled() ? '' : ' display: none;'; ?>">
				<?php

				echo '<div class="sub-section-row">';
					echo '<div class="sub-section-label">
                            <h3>' . __( 'Invoice Data', 'latepoint' ) . '</h3>
                          </div>';

					echo '<div class="sub-section-content">';
						echo '<div class="os-row">
							<div class="os-col-lg-12">' . OsFormHelper::media_uploader_field( 'settings[invoices_company_logo]', 0, __( 'Company Logo', 'latepoint' ), __( 'Remove Image', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_company_logo' ) ) . '</div>
						</div>';

						echo '<div class="os-row os-mb-2">';
							echo '<div class="os-col-4">';
								echo OsFormHelper::text_field( 'settings[invoices_company_name]', __( 'Company Name', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_company_name', '' ), [ 'theme' => 'simple' ] );
							echo '</div>';
							echo '<div class="os-col-4">';
								echo OsFormHelper::text_field( 'settings[invoices_tax_id]', __( 'VAT Number/Tax ID', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_tax_id', '' ), [ 'theme' => 'simple' ] );
							echo '</div>';
							echo '<div class="os-col-4">';
								echo OsFormHelper::text_field( 'settings[invoices_number_prefix]', __( 'Number Prefix', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_number_prefix', 'INV-' ), [ 'theme' => 'simple' ] );
							echo '</div>';
						echo '</div>';

						echo '<div class="os-mb-2">';
							echo OsFormHelper::textarea_field( 'settings[invoices_data_from]', __( 'Bill From', 'latepoint' ), self::get_invoice_data_bill_from(), [ 'theme' => 'simple' ] );
						echo '</div>';
						echo '<div class="os-mb-2">';
							echo OsFormHelper::textarea_field( 'settings[invoices_data_to]', __( 'Bill To', 'latepoint' ), self::get_invoice_data_bill_to(), [ 'theme' => 'simple' ] );
						echo '</div>';
					echo '</div>';

				echo '</div>';
				// echo '</div>
				// 	</div>';

				echo '<div class="sub-section-row">';

					echo '<div class="sub-section-label">
						<h3>' . __( 'Email Invoice', 'latepoint' ) . '</h3>
					</div>';

					echo '<div class="sub-section-content">';
						echo '<div class="latepoint-message latepoint-message-subtle">' . __( 'This subject and content will be used when invoice is being emailed. ', 'latepoint' ) . OsUtilHelper::template_variables_link_html() . '</div>';

						echo OsFormHelper::text_field( 'settings[invoices_email_subject]', __( 'Subject', 'latepoint' ), self::get_subject_for_invoice_email(), [ 'theme' => 'simple' ] );

						OsFormHelper::wp_editor_field( 'settings[invoices_email_content]', 'settingsInvoiceEmailContent', __( 'Email Content', 'latepoint' ), self::get_content_for_invoice_email() );
					echo '</div>';
				echo '</div>';

				echo '<div class="sub-section-row">';
					echo '<div class="sub-section-label"><h3>' . esc_html__( 'Free Bookings', 'latepoint' ) . '</h3></div>';
					echo '<div class="sub-section-content">';
						echo OsFormHelper::toggler_field(
							'settings[create_invoices_for_free_bookings]',
							__( 'Create invoices for free bookings', 'latepoint' ),
							OsSettingsHelper::is_on( 'create_invoices_for_free_bookings', LATEPOINT_VALUE_OFF ),
							'',
							false,
							[ 'sub_label' => __( 'When disabled, no invoice is created and no invoice email/notification is sent for bookings with a $0.00 total (e.g. free services or fully discounted by coupon).', 'latepoint' ) ]
						);
					echo '</div>';
				echo '</div>';
				?>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Get Invoice by transaction intent key
	 * @param string $intent_key
	 * @return OsInvoiceModel | false
	 */
	public static function get_invoice_by_transaction_intent_key( string $intent_key ) {
		$transaction_intent = OsTransactionIntentHelper::get_transaction_intent_by_intent_key( $intent_key );
		if ( ! $transaction_intent->is_new_record() ) {
			return new OsInvoiceModel( $transaction_intent->invoice_id );
		}
		return false;
	}
}
