<?php
/**
 * The orders functions for the Autoship plugin.
 *
 * @package Autoship
 * @since 1.0.0
 */

// Display Functions // My Account.

use Automattic\WooCommerce\Utilities\OrderUtil;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Autoship\Services\Logging\Logger;

/**
 * Adds Custom Autoship Column to the WooCommerce My Account > Orders Dash
 *
 * @param array $columns An array of column names.
 *
 * @return array The filtered columns
 */
function autoship_order_customer_dashboard_icon_column( $columns ) {

	// Find the location to insert our column(s).
	$location = '';
	if ( isset( $columns['order-status'] ) ) {
		$location = 'order-status';
	} elseif ( isset( $columns['order-date'] ) ) {
		$location = 'order-date';
	} elseif ( isset( $columns['order-number'] ) ) {
		$location = 'order-number';
	}

	$location = apply_filters( 'autoship_order_customer_dashboard_column_location', $location );

	$inserted = autoship_array_insert_after( $location, $columns, 'order-autoship-icon', '' );

	if ( ! empty( $location ) && ( ! empty( $inserted ) ) ) {
		$columns = $inserted;
	} else {
		$columns['order-autoship-icon'] = '';
	}

	return $columns;
}

/**
 * Populates the Custom Autoship Column on the WooCommerce My Account > Orders Dash
 *
 * @param WC_Order $order The current row's WC Order.
 */
function autoship_order_customer_dashboard_icon_column_content( $order ) {

	if ( ! $order ) {
		return;
	}

	$tooltip       = array();
	$external_data = array(
		'source'        => '',
		'class'         => '',
		'origin_id'     => '',
		'scheduled_ids' => '',
		'error'         => '',
	);

	// Get the Source flag.
	$external_data['source'] = autoship_get_scheduled_order_related_flag( $order );

	// Get the Scheduled order id if it exists.
	$external_data['origin_id'] = autoship_get_order_scheduled_order_origin_id( $order );

	// Get the Scheduled orders generated from this order if they exist.
	$external_data['scheduled_ids'] = autoship_get_order_created_scheduled_orders_key( $order );

	if ( empty( $external_data['origin_id'] ) && empty( $external_data['scheduled_ids'] ) && empty( $external_data['source'] ) ) {
		return;
	}

	$external_data['notices'] = array();

	$scheduled_order_label  = autoship_search_for_translate_text( 'Scheduled Order' );
	$scheduled_orders_label = autoship_search_for_translate_text( 'Scheduled Orders' );

	// If this is an Order generated from a Scheduled Order.
	if ( ! empty( $external_data['origin_id'] ) ) {

		$external_data['source'] = 'result';
		$external_data['class']  = 'created';
		$schedule                = $order->get_meta( 'Schedule', true );

		// translators: %1$s: Scheduled Order, %2$d: Origin ID.
		$external_data['notices'][] = wc_sanitize_tooltip( sprintf( __( '%1$s %2$d', 'autoship' ), $scheduled_order_label, $external_data['origin_id'] ) );
		// translators: %s: Schedule.
		$external_data['notices'][] = wc_sanitize_tooltip( sprintf( __( 'Scheduled for %s', 'autoship' ), $schedule ) );

		// If this is an order from which Scheduled Orders are Generated.
	} elseif ( ! empty( $external_data['scheduled_ids'] ) ) {

		$external_data['source'] = 'source';
		$external_data['class']  = 'scheduled';
		// translators: %1$s: Scheduled Order, %2$s: Scheduled Orders.
		$external_data['notices'][] = wc_sanitize_tooltip( sprintf( __( 'Created %1$s(s) %2$s', 'autoship' ), $scheduled_order_label, implode( ', ', $external_data['scheduled_ids'] ) ) );

		// Otherwise do we know if this is even a source or result?
	} elseif ( ! empty( $external_data['source'] ) ) {

		$external_data['class']     = 'source' === $external_data['source'] ? 'maybe-scheduled' : 'maybe-created';
		$external_data['notices'][] = 'source' === $external_data['source'] ?
			// translators: %s: Scheduled Orders.
			sprintf( __( 'This order contains Items with associated Schedules and may have produced %s.', 'autoship' ), $scheduled_orders_label ) :
			// translators: %s: Scheduled Order.
			sprintf( __( 'This order might have been generated by a %s.', 'autoship' ), $scheduled_order_label );

		do_action( 'autoship_order_customer_dashboard_icon_column_content_maybe_scheduled_order', $external_data, $order );

		return;
	}

	$scheduled_orders_url = autoship_get_scheduled_orders_url();

	$external_data['notices'] = apply_filters( 'autoship_populate_order_customer_dashboard_icon_tooltip_components', $external_data['notices'], $external_data, $order );
	printf( '<a href="%s"><mark class="order-origin-icon %s autoship-tips" data-tip="%s"><span></span></mark></a>', $scheduled_orders_url, $external_data['class'], apply_filters( 'autoship_populate_order_customer_dashboard_icon_tooltip', wp_kses_post( implode( '<br/>', $external_data['notices'] ) ), $external_data, $order ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Includes the Scheduled Order Attribution and Details in the View Order Header.
 *
 * @param int $order_id The WC Order ID currently being viewed.
 */
function autoship_customer_view_order_header_attribution( $order_id ) {
	$order = wc_get_order( $order_id );

	if ( ! $order ) {
		return;
	}

	// Get the Source flag.
	$external_data['source'] = autoship_get_scheduled_order_related_flag( $order );

	// Get the Scheduled order id if it exists.
	$external_data['origin_id'] = autoship_get_order_scheduled_order_origin_id( $order );

	// Get the Scheduled orders generated from this order if they exist.
	$external_data['scheduled_ids'] = autoship_get_order_created_scheduled_orders_key( $order );

	if ( empty( $external_data['origin_id'] ) && empty( $external_data['scheduled_ids'] ) && empty( $external_data['source'] ) ) {
		return;
	}

	$scheduled_order_label  = autoship_search_for_translate_text( 'Scheduled Order' );
	$scheduled_orders_label = autoship_search_for_translate_text( 'Scheduled Orders' );

	$scheduled_orders_url = autoship_get_scheduled_orders_url();

	// If this is an Order generated from a Scheduled Order.
	if ( ! empty( $external_data['origin_id'] ) ) {

		$external_data['source'] = 'result';
		$external_data['class']  = 'created';
		// translators: %1$s: Scheduled Order, %2$d: Origin ID, %3$d: Scheduled Orders URL.
		$external_data['notice'] = sprintf( __( 'Created by %1$s <a href="%2$s">%3$d</a>', 'autoship' ), $scheduled_order_label, $scheduled_orders_url, $external_data['origin_id'] );

		// If this is an order from which Scheduled Orders are Generated.
	} elseif ( ! empty( $external_data['scheduled_ids'] ) ) {

		$external_data['source'] = 'source';
		$external_data['class']  = 'scheduled';
		// translators: %1$s: Scheduled Order, %2$s: Scheduled Orders URL, %3$s: Scheduled Order IDs.
		$external_data['notice'] = sprintf( __( 'Created %1$s(s) <a href="%2$s">%3$s</a>', 'autoship' ), $scheduled_order_label, $scheduled_orders_url, implode( ', ', $external_data['scheduled_ids'] ) );

		// Otherwise do we know if this is even a source or result?
	} elseif ( ! empty( $external_data['source'] ) ) {
		$external_data['class']  = 'source' === $external_data['source'] ? 'maybe-scheduled' : 'maybe-created';
		$external_data['notice'] = 'source' === $external_data['source'] ?
			// translators: %1$s: Scheduled Orders.
			sprintf( __( 'This order contains Items with associated Schedules and may have produced %1$s.', 'autoship' ), $scheduled_orders_label ) :
			// translators: %1$s: Scheduled Order.
			sprintf( __( 'This order might have been generated by a %1$s.', 'autoship' ), $scheduled_order_label );

		do_action( 'autoship_customer_view_order_header_attribution_maybe_scheduled_order', $external_data, $order );

		return;
	}

	$external_data['notice'] = apply_filters( 'autoship_populate_customer_view_order_header_attribution', $external_data['notice'], $external_data, $order );

	echo apply_filters( 'autoship_populate_customer_view_order_header_attribution_html', sprintf( '<h3 class="autoship-order-attribution %s">%s</h3>', $external_data['class'], $external_data['notice'] ), $external_data, $order ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

// ==========================================================
// Display Functions
// ==========================================================

/**
 * Display Stored Notices on Orders screen
 */
function autoship_admin_display_wc_order_notices() {
	autoship_print_messages( 'autoship_wc_orders' );
}

/**
 * Add a custom Autoship actions to order actions select box on edit order page
 *
 * @param array $actions order actions array to display.
 *
 * @return array - updated actions
 */
function autoship_add_meta_box_actions( $actions ) {
	global $theorder;

	if ( ! empty( autoship_get_order_created_scheduled_orders_key( $theorder ) ) ) {
		$actions['autoship_clear_existing_order_keys'] = __( 'Remove Scheduled Order IDs', 'autoship' );
	}

	return $actions;
}

/**
 * Add the new MetaBox to the Shop Orders Screen.
 */
function autoship_register_edit_order_metaboxes() {
	$screen = wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ? wc_get_page_screen_id( 'shop-order' ) : 'shop_order';

	add_meta_box( 'autoship_assign_scheduled_orders', 'Autoship Link Scheduled Orders', 'autoship_add_scheduled_order_ids_metabox', $screen );
}

/**
 * Outputs the content of the new Assign Scheduled Order IDs Actions MetaBox to the Shop Orders Screen.
 *
 * @param WP_Post $post The current post object.
 */
function autoship_add_scheduled_order_ids_metabox( $post ) {
	$theorder = ( $post instanceof WP_Post ) ? wc_get_order( $post->ID ) : $post;

	$scheduled_order_ids = autoship_get_order_created_scheduled_orders_key( $theorder );
	$val                 = ! empty( $scheduled_order_ids ) ? implode( ', ', $scheduled_order_ids ) : array();

	$assigned_notice = empty( $val ) ?
		__( '<strong>No Scheduled Order IDs assigned.</strong>' ) : // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
		// translators: %s: Assigned Scheduled Order IDs.
		sprintf( __( 'Scheduled Order IDs <strong>%s</strong> assigned.' ), $val );

	?>

	<div id="autoship-scheduled-order-ids-options-main">

		<div id="scheduled-order-ids-options">

			<div class="scheduled-order-ids-options-form-action">

				<p><?php echo esc_html( __( 'The following Autoship Scheduled Order IDs are assigned to this WooCommerce Order. Click on the "Update Scheduled Order ID(s)" button to change the assigned ids.', 'autoship' ) ); ?></p>

				<p><?php echo wp_kses_post( $assigned_notice ); ?></p>

				<div class="action-button-bar">
					<div class="action-buttons">
						<a class="autoship_hide_show_toggler autoship-modal-trigger button button-primary"
							data-target=".scheduled-order-ids-options-form, .scheduled-order-ids-options-form-action"
							data-modal-toggle="#autoship_order_assigned_ids_warning">Update Scheduled Order ID(s)</a>
					</div>
				</div>

			</div>

			<div class="scheduled-order-ids-options-form" style="display:none">

				<p><?php echo esc_html( __( 'Use the Autoship Scheduled Order IDs field below to assign one or more Scheduled Order IDs from QPilot to this Order. Enter more than one id by adding ids separated by a comma.', 'autoship' ) ); ?></p>

				<p><?php echo wp_kses_post( $assigned_notice ); ?></p>

				<hr/>

				<div class="auto-flex-row">

					<div class="form-field form-field-wide autoship-scheduled-order-ids auto-flex-col">
						<label for="autoship_assigned_order_ids">
							<?php echo esc_html( __( 'Scheduled Order IDs', 'autoship' ) ); ?>
						</label>
						<input type="text" name="autoship_assigned_order_ids" value="" style="max-width: 500px;">
					</div>

				</div>

				<div class="action-button-bar">
					<div class="action-buttons">
						<button type="submit" id="apply-autoship-scheduled-orders-btn" class="button button-primary">
							Assign Scheduled Order IDs
						</button>
						
						<?php if ( ! empty( $val ) ) : ?>

							<button class="button button-secondary autoship-order-actions-btn"
									data-target-action="autoship_clear_existing_order_keys">Remove Assigned IDs
							</button>
						
						<?php endif; ?>

					</div>
				</div>

			</div>

		</div>

	</div>

	<?php
	autoship_generate_modal( 'autoship_order_assigned_ids_warning', __( '<h3>Autoship Cloud Warning!</h3><hr/><p><strong><span style=\"color:#11a0d2;\">Important!</span></strong> Changing the Scheduled Order IDs assigned to a WooCommerce Order can affect reporting.</p>', 'autoship' ), 'warning-modal' );
}

/**
 * Handle a custom autoship query var to get orders with custom meta.
 *
 * @param array $query - Args for WP_Query.
 * @param array $query_vars - Query vars from WC_Order_Query.
 *
 * @return array modified $query
 */
function autoship_handle_custom_order_query_vars( $query, $query_vars ) {

	if ( ! empty( $query_vars['_autoship_created_scheduled_orders_key'] ) ) {
		$query['meta_query'][] = array(
			'key'   => '_autoship_created_scheduled_orders_key',
			'value' => esc_attr( $query_vars['_autoship_created_scheduled_orders_key'] ),
		);
	}

	if ( ! empty( $query_vars['_autoship_created_scheduled_order_error'] ) ) {
		$query['meta_query'][] = array(
			'key'   => '_autoship_created_scheduled_order_error',
			'value' => esc_attr( $query_vars['_autoship_created_scheduled_order_error'] ),
		);
	}

	return $query;
}

/**
 * Adds Custom Autoship Columns to the WooCommerce Orders Dash
 *
 * @return array $new_columns The filtered columns
 */
function autoship_order_dashboard_columns() {
	/**
	 * The Default Autoship Custom Dashboard Columns
	 * Column ID => array {
	 *   'location' Column ID after which it should be inserted.
	 *   'label'    Column Label
	 *   'meta_key' The meta key name to sort by.
	 *   'orderby'  How the sort should work, value, number value etc.
	 *   'callback' A valid callback function to populate the column.
	 *              Takes the Post ID ( int ) as Parameter.
	 * }
	 */
	$columns = array(
		'order_autoship_origin' => array(
			'location' => 'order_status',
			'label'    => __( 'Origin', 'autoship' ),
			'meta_key' => '_qpilot_scheduled_order_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
			'orderby'  => 'meta_value',
			'callback' => 'autoship_populate_order_dashboard_origin',
		),
		'order_autoship_icon'   => array(
			'location' => 'order_status',
			'label'    => '',
			'meta_key' => '_autoship_scheduled_order_type', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
			'orderby'  => 'meta_value',
			'callback' => 'autoship_populate_order_dashboard_icon',
		),
	);

	if ( autoship_display_next_occurrence_offset_enabled() ) {

		$columns['order_autoship_next_occurrence_offset'] = array(
			'location' => 'order_date',
			'label'    => __( 'Ship By Date', 'autoship' ),
			'meta_key' => '_qpilot_next_occurrence_offset', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
			'orderby'  => 'meta_value',
			'callback' => 'autoship_populate_next_occurrence_offset',
		);
	}

	return apply_filters( 'autoship_order_dashboard_columns', $columns );
}

/**
 * Callback for Ship By Date / Next Occurrence Offset Order Dashboard Column
 *
 * @param int  $id The WC Order ID.
 * @param bool $evaluate True to return the value else echo.
 *
 * @return string The Ship BY Date
 */
function autoship_populate_next_occurrence_offset( $id, $evaluate = false ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

	$order = wc_get_order( $id );

	if ( ! $order ) {
		return;
	}

	// Get order items from the WC Order.
	$offset       = $order->get_meta( '_qpilot_next_occurrence_offset', true );
	$date_created = $order->get_date_created() ? $order->get_date_created() : '';

	// Check if the offset value is a legacy value or not.
	if ( false !== strpos( $offset, 'T' ) ) {
		$offset = autoship_get_formatted_local_date( $offset, 'Y-m-d H:i:s' );
	}

	if ( ! $date_created ) {
		echo apply_filters( 'autoship_populate_next_occurrence_offset_empty_created_date', '&ndash;', $offset, $order ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		return;
	} elseif ( empty( $offset ) ) {
		echo apply_filters( 'autoship_populate_next_occurrence_offset_empty', '&ndash;', $order ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		return;
	}

	$order_date = DateTime::createFromFormat( 'Y-m-d H:i:s', $offset );
	$show_date  = $order_date->format( apply_filters( 'woocommerce_admin_order_date_format', __( 'M j, Y', 'woocommerce' ) ) );

	$display = sprintf( '<time datetime="%1$s" title="%2$s">%3$s</time>', esc_attr( $order_date->format( 'c' ) ), esc_html( $order_date->format( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ), esc_html( $show_date ) );

	echo apply_filters( 'autoship_populate_next_occurrence_offset_column_val', $display, $offset, $order ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Callback for New Origin Order Dashboard Column
 *
 * @param int $id The WC Order ID.
 */
function autoship_populate_order_dashboard_origin( $id ) {

	$order = wc_get_order( $id );

	if ( ! $order ) {
		return;
	}

	// Get order items from the WC Order.
	$external_id = $order->get_meta( '_qpilot_scheduled_order_id', true );
	echo apply_filters( 'autoship_populate_order_dashboard_origin', $external_id, $order ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Callback for New Origin Order Dashboard Column
 *
 * @param int $id The WC Order ID.
 */
function autoship_populate_order_dashboard_icon( $id ) {

	$order = wc_get_order( $id );

	if ( ! $order ) {
		return;
	}

	$tooltip       = array();
	$external_data = array(
		'source'        => '',
		'class'         => '',
		'origin_id'     => '',
		'scheduled_ids' => '',
		'error'         => '',
	);

	// Get the Source flag.
	$external_data['source'] = autoship_get_scheduled_order_related_flag( $order );

	// Get the Scheduled order id if it exists.
	$external_data['origin_id'] = autoship_get_order_scheduled_order_origin_id( $order );

	// Get the Scheduled orders generated from this order if they exist.
	$external_data['scheduled_ids'] = autoship_get_order_created_scheduled_orders_key( $order );

	// Get any errors.
	$external_data['error'] = autoship_get_order_created_scheduled_orders_error( $order );

	if ( empty( $external_data['origin_id'] ) && empty( $external_data['scheduled_ids'] ) && empty( $external_data['error'] ) && empty( $external_data['source'] ) ) {
		return;
	}

	// If this is an Order generated from a Scheduled Order.
	if ( ! empty( $external_data['origin_id'] ) ) {
		$external_data['source'] = 'result';
		$external_data['class']  = 'created';
		$schedule                = $order->get_meta( 'Schedule', true );

		// translators: %d: Scheduled Order ID.
		$tooltip[] = wc_sanitize_tooltip( sprintf( __( 'Scheduled Order ID: %d', 'autoship' ), $external_data['origin_id'] ) );

		// translators: %s: Schedule.
		$tooltip[] = wc_sanitize_tooltip( sprintf( __( 'Schedule: %s', 'autoship' ), $schedule ) );

		// If this is an order from which Scheduled Orders are Generated.
	} elseif ( ! empty( $external_data['scheduled_ids'] ) ) {

		$external_data['source'] = 'source';
		$external_data['class']  = 'scheduled';

		// translators: %s: Scheduled Order IDs.
		$tooltip[] = wc_sanitize_tooltip( sprintf( __( 'Scheduled Order(s): %s', 'autoship' ), implode( ', ', $external_data['scheduled_ids'] ) ) );

		// Otherwise do we know if this is even a source or result?
	} elseif ( ! empty( $external_data['source'] ) ) {
		$external_data['class'] = 'source' === $external_data['source'] ? 'maybe-scheduled' : 'maybe-created';
		$tooltip[]              = wc_sanitize_tooltip( 'source' === $external_data['source'] ? __( 'This order contains Items with associated Schedules and may have produced scheduled orders.', 'autoship' ) : __( 'This order might have been generated by a Scheduled Order.', 'autoship' ) );
	}

	if ( ! empty( $external_data['error'] ) && is_wp_error( $external_data['error'] ) ) {
		$external_data['class'] = 'error';
		$tooltip[]              = wc_sanitize_tooltip( $external_data['error']->get_error_message() );
	}

	$tooltip = apply_filters( 'autoship_populate_order_dashboard_icon_tooltip_components', $tooltip, $external_data, $order );

	printf( '<mark class="order-origin-icon %s tips" data-tip="%s"><span></span></mark>', esc_attr( $external_data['class'] ), apply_filters( 'autoship_populate_order_dashboard_icon_tooltip', wp_kses_post( implode( "\n", $tooltip ) ), $tooltip, $external_data, $order ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Allow users to sort by the Custom Columns
 *
 * @param WP_Query $query The Current Query value.
 *
 * @return WP_Query The adjusted filter.
 */
function autoship_order_dashboard_custom_column_sort( $query ) {
	// if it is not admin area, exit the filter immediately.
	if ( ! is_admin() ) {
		return;
	}

	$columns = autoship_order_dashboard_columns();

	if ( empty( $_GET['orderby'] ) || empty( $_GET['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return;
	}

	if ( isset( $columns[ $_GET['orderby'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$query->set( 'meta_key', $columns[ $_GET['orderby'] ]['meta_key'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$query->set( 'orderby', $columns[ $_GET['orderby'] ]['orderby'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$query->set( 'order', $_GET['order'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
	}

	return $query;
}

/**
 * Makes the custom column headers sortable.
 *
 * @param array $columns An array of sortable columns.
 *
 * @return array The updated sortable columns array.
 */
function autoship_order_dashboard_custom_column_sortable( $columns ) {

	$new_columns = autoship_order_dashboard_columns();

	foreach ( $new_columns as $key => $value ) {
		$columns[ $key ] = $key;
	}

	return $columns;
}

/**
 * Makes the custom columns searchable.
 *
 * @param array $meta_keys An array of sortable columns.
 *
 * @return array The Searchable meta keys.
 */
function autoship_order_dashboard_custom_column_searchable( $meta_keys ) {

	$columns = autoship_order_dashboard_columns();

	foreach ( $columns as $key => $value ) {
		$meta_keys[] = $value['meta_key'];
	}

	return $meta_keys;
}

/**
 * Adds Custom Autoship Columns to the WooCommerce Orders Dash
 *
 * @param array $columns An array of current order dash column names.
 *
 * @return array $new_columns The filtered columns
 */
function autoship_order_dashboard_column_headers( $columns ) {

	$new_columns = autoship_order_dashboard_columns();

	foreach ( $new_columns as $key => $values ) {

		$columns = autoship_array_insert_after( $values['location'], $columns, $key, $values['label'] );

	}

	return $columns;
}

/**
 * Populates the Custom Autoship Columns in the WooCommerce Orders Dash
 *
 * @param string $column The current column being displayed.
 * @param int    $post_id The ID of the current Post being displayed.
 */
function autoship_order_populate_dashboard_columns( $column, $post_id ) {

	$autoship_columns = autoship_order_dashboard_columns();

	if ( isset( $autoship_columns[ $column ] ) ) {

		$callback = $autoship_columns[ $column ]['callback'];
		echo function_exists( $callback ) ? $callback( $post_id ) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}
}

// ==========================================================
// Get & Utility Functions
// ==========================================================

/**
 * Groups and Orders the supplied order or cart items by Autoship Frequency,
 * Autoship Type, and Autoship Next Occurrence.
 * Order items included in an upsert to QPilot should be grouped by Autoship
 *
 * @param array    $order_items .  The order or cart items to sort.
 * @param WC_Order $order Optional. The WC Order Object.
 *
 * @return array               The original order items sorted by Autoship values.
 *                             any non-autoship items are removed.
 */
function autoship_group_order_items( $order_items, $order = null ) {

	$order_items = apply_filters( 'autoship_group_order_items_data', $order_items, $order );

	$counter = 0;

	// Group frequencies.
	$frequencies_hash = array();
	foreach ( $order_items as $item_key => $item ) {

		$schedule_values = autoship_get_item_data_schedule_values( $item );

		if ( ! autoship_item_data_has_valid_schedule( $schedule_values ) ) {
			continue;
		}

		// Get the item's hash values & key
		// When the plugin generates the Scheduled Order data it looks for
		// non-prefixed metakeys hence the str place call.
		$item_hash_values = array();
		foreach ( $schedule_values as $key => $value ) {
			$item_hash_values[ str_replace( 'autoship_', '', $key ) ] = $value;
		}

		$item_hash_key = implode( ';', array_filter( $item_hash_values ) );

		// Check if the hash already exists
		// if not then create the initial values.
		if ( ! isset( $frequencies_hash[ $item_hash_key ] ) ) {
			$frequencies_hash[ $item_hash_key ] = $item_hash_values + array( 'items' => array() );
		}

		// Now add the item.
		$frequencies_hash[ $item_hash_key ]['items'][ $item_key ] = $item;
	}

	ksort( $frequencies_hash );

	return array_values( $frequencies_hash );
}

/**
 * Gets the total number of autoship items in an order.
 *
 * @param WC_Order|int $order A WC_Order object or order id.
 *
 * @return int         The number of Autoship items in an order.
 */
function autoship_order_total_scheduled_items( $order ) {

	$scheduled_items = 0;

	// Check if supplied order id or WC Order.
	if ( is_numeric( $order ) ) {

		// Get the WC Order from the order id.
		$order = wc_get_order( $order );
	}

	// If order is not a WC Order.
	if ( ! $order ) {
		return $scheduled_items;
	}

	// Get order items from the WC Order.
	$order_items = $order->get_items();

	// Check if order has scheduled items.
	foreach ( $order_items as $item ) {
		if ( ( isset( $item['autoship_frequency_type'] ) && ! empty( $item['autoship_frequency_type'] ) ) && ( isset( $item['autoship_frequency'] ) && ! empty( $item['autoship_frequency'] ) ) ) {
			// This order has scheduled items.
			++$scheduled_items;
		}
	}

	return $scheduled_items;
}

/**
 * Gathers the scheduled order product data
 *
 * @param int    $external_id A WC_Product id.
 * @param string $frequency_type The frequency type assigned to this product.
 * @param int    $frequency The actual frequency duration.
 *
 * @return array       An array containing the Autoship Scheduled
 *                     order data for the supplied WC Order
 */
function autoship_get_scheduled_order_product_data( $external_id, $frequency_type, $frequency ) {

	$product = wc_get_product( $external_id );

	if ( ! $product ) {
		return array();
	}

	// Get site settings.
	$site_id = autoship_get_site_id();

	// Item price.
	$item_price = floatval( $product->get_regular_price() );
	$item_price = apply_filters( 'autoship_create_scheduled_order_item_product_price', $item_price, $frequency_type, $frequency, $product );

	// Get the Autoship re-order Sale Price.
	$autoship_recurring_price = autoship_get_product_recurring_price( $external_id, $frequency_type, $frequency );
	$item_recurring_price     = isset( $autoship_recurring_price ) ? floatval( $autoship_recurring_price ) : null;

	$item_recurring_price = apply_filters( 'autoship_create_scheduled_order_item_product_sale_price', $item_recurring_price, $frequency_type, $frequency, $product );

	$scheduled_order_item_data = array(
		'productId' => $product->get_id(),
		'product'   => autoship_generate_product_upsert_data( $product ),
		'price'     => $item_price,
		'salePrice' => $item_recurring_price,
	);

	return apply_filters( 'autoship_get_scheduled_order_item_product_data', $scheduled_order_item_data, $frequency_type, $frequency );
}

/**
 * Retrieves and Creates the QPilot Payment Data.
 *
 * @param WC_Order|int $order A WC Order object or order id.
 *
 * @return array|NULL  An array containing the Autoship Scheduled
 *                     order payment data for the supplied WC Order
 */
function autoship_get_scheduled_order_payment_data( $order ) {
	Logger::trace( 'autoship_get_scheduled_order_payment_data', 'Getting Scheduled Order Payment Data' );

	// Get order.
	if ( is_numeric( $order ) ) {
		Logger::trace( 'autoship_get_scheduled_order_payment_data', 'The woo commerce order is numeric' );

		$order = wc_get_order( $order );
	}

	if ( ! $order ) {
		Logger::trace( 'autoship_get_scheduled_order_payment_data', 'The woo commerce order is not valid' );

		return null;
	}

	// Get payment data via the OrderPaymentDataService.
	$payment_data = \Autoship\Modules\Payments\Services\OrderPaymentDataService::get_for_order( $order->get_id() );

	return apply_filters(
		'autoship_get_scheduled_order_payment_data',
		isset( $payment_data ) ?
			autoship_generate_customer_billing_data(
				$order->get_user_id(),
				array(
					'customerId'         => $order->get_user_id(),
					'description'        => $payment_data->description,
					'type'               => $payment_data->type,
					'expiration'         => $payment_data->expiration,
					'lastFourDigits'     => $payment_data->last_four,
					'gatewayCustomerId'  => $payment_data->gateway_customer_id,
					'gatewayPaymentId'   => $payment_data->gateway_payment_id,
					'gatewayPaymentType' => $payment_data->gateway_payment_type,
				)
			) :
			null,
		$order,
		$payment_data
	);
}

/**
 * Adjusts the Payment Method Data included on Checkout Orders
 *
 * @param array    $billing_data Billing Data.
 * @param WC_Order $order The WC Orders.
 * @param array    $payment_data The current payment info.
 *
 * @return array THe adjusted data
 */
function autoship_adjust_sepa_order_payment_method_data( $billing_data, $order, $payment_data ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

	if ( isset( $billing_data['description'] ) && 'SEPA IBAN' === $billing_data['description'] ) {
		$billing_data['gatewayPaymentType'] = 21;
	}

	return $billing_data;
}

/**
 * Retrieves and Creates the QPilot customer if needed.
 *
 * @param WC_Order|int $order A WC Order object or order id.
 *
 * @return array       An array containing the Autoship Scheduled
 *                     order data for the supplied WC Order
 */
function autoship_get_scheduled_order_customer_data( $order ) {

	// Get order.
	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	if ( ! $order ) {
		return array();
	}

	// Get the customer data.
	$autoship_customer    = autoship_generate_customer_upsert_data( $order->get_user_id() );
	$scheduled_order_data = autoship_generate_customer_shipping_data(
		$order->get_user_id(),
		array(
			'customerId'  => $order->get_user_id(),
			'customer'    => $autoship_customer,
			'CurrencyIso' => $order->get_currency(),
			'note'        => $order->get_customer_note(),
			'phoneNumber' => $order->get_billing_phone(),
		)
	);

	return apply_filters( 'autoship_get_scheduled_order_customer_data', $scheduled_order_data, $order );
}

/**
 * Returns the Scheduled Order Status for Upsert
 *
 * Developers can modify the status value for each order using
 * the new {@see autoship_create_scheduled_order_default_status } filter.
 *
 * @param WC_Order|int $order A WC Order object or order id.
 *
 * @return string      The status.
 */
function autoship_get_scheduled_order_default_status( $order ) {
	return apply_filters( 'autoship_create_scheduled_order_default_status', 'Active', $order );
}

/**
 * Creates an Orders External ID.
 *
 * Developers can modify the originalExternalId value for each order using
 * the new {@see autoship_create_scheduled_order_external_id } filter.
 *
 * @param WC_Order|int $order A WC Order object or order id.
 * @param string       $extension Optional. An extension to add to the id. Default empty string.
 *
 * @return string      The Original External ID.
 */
function autoship_get_scheduled_order_external_id( $order, $extension = '' ) {
	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return apply_filters( 'autoship_create_scheduled_order_external_id', ! $order ? $extension : 'wc-' . $order->get_id() . $extension, $order, $extension );
}

/**
 * Creates and Gathers the Scheduled order Data. Uses the following functions.
 *
 * @param WC_Order|int $order A WC Order object or order id.
 * @param string       $creation_date Optional. The date the order should be
 *                     created based on should be 'Y-m-d H:i:s' format.
 *                     Default NULL.
 *
 * @return array       An array containing the Autoship Scheduled
 *                     order data for the supplied WC Order
 * @uses autoship_group_order_items()
 * @uses autoship_get_scheduled_order_customer_data()
 * @uses autoship_get_scheduled_order_product_data()
 *
 * Developers can modify the next_occurrence value for each order using
 * the new {@see autoship_create_scheduled_order_next_occurrence} filter.
 */
function autoship_get_scheduled_order_data( $order, $creation_date = null ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	if ( ! $order ) {
		return array();
	}

	// Get the creation date either from "now" or the supplied date.
	$creation_date = autoship_get_api_formatted_date( $creation_date, 'Y-m-d H:i:s' );

	// Get order items from the WC Order.
	$order_items = $order->get_items();

	// Process order items and remove non-autoship items.
	// If empty this order does not have scheduled items.
	$grouped_items = autoship_group_order_items( $order_items, $order );

	if ( empty( $grouped_items ) ) {
		return array();
	}

	// Get Global Schedule Order Customer Data for the order.
	$scheduled_order_data = autoship_get_scheduled_order_customer_data( $order );

	// Get the Scheduled Order Payment Data for the order.
	$scheduled_order_data['paymentMethod'] = autoship_get_scheduled_order_payment_data( $order );

	// Get the Currency used with the Order.
	$scheduled_order_data['currencyIso'] = $order->get_currency();

	// Get the Scheduled Order Status.
	$scheduled_order_data['status'] = autoship_get_scheduled_order_default_status( $order );

	// Add the Origin to the Order data - 'CustomerCheckout' if created via checkout else 'CustomerApi'.
	$orders_created_via             = apply_filters(
		'autoship_order_created_via',
		array(
			'checkout',
			'store-api',
		)
	);
	$scheduled_order_data['origin'] = in_array( $order->get_created_via(), $orders_created_via, true ) ? 'CustomerCheckout' : 'CustomerApi';

	// Init a Container for the Occurrence dates.
	$group_scheduled_order_data = array();
	$occurrences                = array();

	// Gather the remaining data for the order by frequency / frequency type and next occurrence.
	foreach ( $grouped_items as $frequency_group ) {

		// Grab the global data.
		$new_sheduled_order = $scheduled_order_data;

		// Reset the type and frequency.
		$new_sheduled_order['frequencyType'] = $frequency_group['frequency_type'];
		$new_sheduled_order['frequency']     = $frequency_group['frequency'];

		$next_occurrence = ! empty( $frequency_group['next_occurrence'] ) ? autoship_format_next_occurrence_for_save( $frequency_group['next_occurrence'] ) : null;

		$next_occurrence = apply_filters( 'autoship_create_scheduled_order_next_occurrence', $next_occurrence, $frequency_group['frequency_type'], $frequency_group['frequency'], $creation_date, $order );

		// Set the unique frequency key for use in the External ID and in the NextOccurrenceUtc Array.
		$frequency_key = $new_sheduled_order['frequency'] . '-' . $new_sheduled_order['frequencyType'];

		// Get the originalExternalId to be unique to this grouping.
		// Generate a new unique id based on frequency & frequency_type & next occurrence.
		$new_sheduled_order['originalExternalId'] = autoship_get_scheduled_order_external_id( $order, ! empty( $next_occurrence ) ? "-{$frequency_key}-{$next_occurrence}" : "-{$frequency_key}" );

		/**
		 * Gather and Assign the Next Occurrence Dates for each frequency and frequency Type.
		 * 1. If it's not set but already set in the array for the frequency and frequency Type use it.
		 * 2. If it's set use it and add to array
		 * 3. Since it's not yet calculated let QPilot deal with it on upsert.
		 */
		$new_sheduled_order['nextOccurrenceUtc'] = null;
		if ( empty( $next_occurrence ) && isset( $occurrences[ $frequency_key ] ) ) {

			// get it from the array since it exists.
			$new_sheduled_order['nextOccurrenceUtc'] = $occurrences[ $frequency_key ];

		} elseif ( ! empty( $next_occurrence ) ) {

			// get the manually assigned next occurrence and add to array.
			$new_sheduled_order['nextOccurrenceUtc'] = $next_occurrence;
			$occurrences[ $frequency_key ]           = $new_sheduled_order['nextOccurrenceUtc'];
		}

		$new_sheduled_order['utcOffset'] = apply_filters( 'autoship_create_scheduled_order_next_occurrence_offset', autoship_get_local_timezone_offset(), $new_sheduled_order['nextOccurrenceUtc'], $frequency_group['frequency_type'], $frequency_group['frequency'], $creation_date, $order );

		// Loop through items and add to 'scheduleOrderItems.
		foreach ( $frequency_group['items'] as $item ) {

			$external_id = ! empty( $item['variation_id'] ) ? $item['variation_id'] : $item['product_id'];

			// Get the Product specific line item data.
			$scheduled_order_item_data = array();
			$scheduled_order_item_data = autoship_get_scheduled_order_product_data( $external_id, $frequency_group['frequency_type'], $frequency_group['frequency'] );

			if ( empty( $scheduled_order_item_data ) ) {

				autoship_log_entry( __( 'Autoship Order Data', 'autoship' ), sprintf( 'Product %d could not be found and is invalid. This product has been skipped and will not be included in the Scheduled Order Data for Scheduled Order #%s generated from WC Order #%d', $external_id, $new_sheduled_order['originalExternalId'], $order->get_id() ) );

				continue;
			}

			// Tag on the order quantity.
			$scheduled_order_item_data['quantity']       = (int) $item['qty'];
			$scheduled_order_item_data                   = apply_filters( 'autoship_get_scheduled_order_data_full_item_data', $scheduled_order_item_data, $order->get_id(), $item, $frequency_group['frequency_type'], $frequency_group['frequency'] );
			$new_sheduled_order['scheduledOrderItems'][] = $scheduled_order_item_data;
		}

		$group_scheduled_order_data[] = $new_sheduled_order;
	}

	return $group_scheduled_order_data;
}

/**
 * Gets the associated QPilot Virtual Coupons assigned to order.
 *
 * @param int|WC_Order $order The WC Order id or Order Object.
 *
 * @return array An array of WC_Coupon objects.
 */
function autoship_get_order_associated_virtual_wc_coupons( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_qpilot_dynamic_coupons', true );
}

/**
 * Gets the Legacy Scheduled Order IDs created from the order at checkout.
 *
 * @param int|WC_Order $order The WC Order id or Order Object.
 *
 * @return array The Order IDs.
 */
function autoship_get_order_scheduled_order_legacy_ids( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_autoship_created_scheduled_order_id', true );
}

/**
 * Gets the Scheduled Order ID from which the supplied Order was created.
 *
 * @param int|WC_Order $order The WC Order id or Order Object.
 *
 * @return int The Order ID.
 */
function autoship_get_order_scheduled_order_origin_id( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	// Get order items from the WC Order.
	return $order->get_meta( '_qpilot_scheduled_order_id', true );
}

/**
 * Gets the Created Scheduled Orders Key value for the supplied Order ID.
 *
 * @param int|WC_Order $order The WC Order id or Order Object.
 * @param bool         $expanded True to return an array of ids else string is returned.
 *
 * @return array|string The current Key value.
 */
function autoship_get_order_created_scheduled_orders_key( $order, $expanded = true ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	$val = $order->get_meta( '_autoship_created_scheduled_orders_key', true );

	if ( empty( $val ) && $expanded ) {
		$val = array();
	}

	return $expanded && ! empty( $val ) ? array_filter( explode( '-', $val ) ) : $val;
}

/**
 * Gets the Created Scheduled Orders Error value for the supplied Order ID.
 *
 * @param int|WC_Order $order The WC Order id or Order Object.
 *
 * @return string The current value.
 */
function autoship_get_order_created_scheduled_orders_error( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_autoship_created_scheduled_orders_error', true );
}

/**
 * Gets an Orders QPilot Processing ID
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 *
 * @return string The id.
 */
function autoship_get_scheduled_order_processing_id( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_qpilot_scheduled_order_processing_id', true );
}

/**
 * Gets an Orders Autoship Related flag.
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 *
 * @return string The Flag value.
 */
function autoship_get_scheduled_order_related_flag( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_autoship_scheduled_order_type', true );
}

/**
 * Gets the original WC Order source for a WC Order generated from QPilot
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 *
 * @return int|bool The WC Order ID or FALSE if doesn't exist.
 */
function autoship_get_wc_order_source_id( $order ) {

	// Get the QPilot Associated Order ID.
	$origin_id = autoship_get_order_scheduled_order_origin_id( $order );

	return ! empty( $origin_id ) ? autoship_get_scheduled_order_wc_source_id( $origin_id ) : false;
}

/**
 * Gets the original WC Order source for a Scheduled Order
 *
 * @param int $scheduled_order_id The QPilot Order ID.
 *
 * @return int The WC Order ID.
 */
function autoship_get_scheduled_order_wc_source_id( $scheduled_order_id ) {
	global $wpdb;

	// Query for the Key.
	$scheduled_order_id = absint( $scheduled_order_id );
	// Check if HPOS in use.
	if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
		$wc_order_id = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT order_id FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key = %s AND meta_value LIKE '%-%d-%'", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery
				'_autoship_created_scheduled_orders_key',
				$scheduled_order_id
			)
		);
	} else {
		$wc_order_id = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value LIKE '%-%d-%'", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery
				'_autoship_created_scheduled_orders_key',
				$scheduled_order_id
			)
		);
	}

	return $wc_order_id;
}

/**
 * Gets the payment gateway metadata for the supplied order.
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 *
 * @return array The Gateway Response.
 */
function autoship_get_scheduled_order_payment_response( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_qpilot_payment_gateway_response', true );
}

/**
 * Gets the payment gateway method object for the supplied order.
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 *
 * @return array The payment Method Object.
 */
function autoship_get_scheduled_order_payment_method( $order ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	return $order->get_meta( '_qpilot_scheduled_order_payment_method', true );
}

// ==========================================================
// Set / Update Functions
// ==========================================================

/**
 * Gets an array of matching shipping tax rates for a given class.
 *
 * @param string   $tax_class Tax class to get rates for.
 * @param array    $location The customers country, state, city, and postcode.
 * @param WC_Order $order The WC Order object.
 *
 * @return mixed
 */
function autoship_get_shipping_tax_rates_from_order( $tax_class, $location, $order ) {

	if ( 4 !== count( $location ) ) {
		return false;
	}

	extract( $location ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract

	$matched_tax_rates = array();

	if ( 'inherit' !== $tax_class ) {

		// Get the Tax Rate.
		$matched_tax_rates = WC_Tax::find_shipping_rates(
			array(
				'country'   => $country,
				'state'     => $state,
				'postcode'  => $postcode,
				'city'      => $city,
				'tax_class' => $tax_class,
			)
		);
	} else {

		$order_classes = array();

		// This will be per order shipping - loop through the order and find the highest tax class rate.
		foreach ( $order->get_items() as $item_id => $item ) {

			// Get the product ID.
			$product_id = $item->get_product_id();
			$product    = wc_get_product( $product_id );

			// If shipping is taxable for this product add the tax class to the array.
			if ( $product->is_shipping_taxable() ) {
				$order_classes[] = $product->get_tax_class();
			}
		}

		$order_classes = array_unique( $order_classes );

		// No tax classes = no taxable items.
		if ( empty( $order_classes ) ) {
			return array();
		}

		// If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additional tax class' section.
		if ( count( $order_classes ) > 1 && ! in_array( '', $order_classes, true ) ) {

			$tax_classes = WC_Tax::get_tax_class_slugs();

			foreach ( $tax_classes as $tax_class ) {

				if ( in_array( $tax_class, $order_classes, true ) ) {

					$matched_tax_rates = WC_Tax::find_shipping_rates(
						array(
							'country'   => $country,
							'state'     => $state,
							'postcode'  => $postcode,
							'city'      => $city,
							'tax_class' => $tax_class,
						)
					);

					break;
				}
			}
		} elseif ( 1 === count( $order_classes ) ) {

			// If a single tax class is found, use it.
			$matched_tax_rates = WC_Tax::find_shipping_rates(
				array(
					'country'   => $country,
					'state'     => $state,
					'postcode'  => $postcode,
					'city'      => $city,
					'tax_class' => $order_classes[0],
				)
			);
		}
	}

	// Get standard rate if no taxes were found.
	if ( ! count( $matched_tax_rates ) ) {
		$matched_tax_rates = WC_Tax::find_shipping_rates(
			array(
				'country'  => $country,
				'state'    => $state,
				'postcode' => $postcode,
				'city'     => $city,
			)
		);
	}

	return $matched_tax_rates;
}

/**
 * Set Prices Inclusive of Tax on Rest Orders from QPilot by Finding the WC_Tax
 * tax rates and back calculates the Line Item Subtotal and Totals to exclude the tax.
 *
 * Additional Details
 * 1. Only applies to QPilot Orders ( i.e. via rest-api & _qpilot_scheduled_order_processing_id )
 * 2. Only applies if the WooCommerce > Settings > Tax > "Prices entered with tax" option is set to Yes
 *
 * @param bool     $and_taxes Calc taxes if true.
 * @param WC_Order $order The WC Order object.
 */
function autoship_qpilot_orders_update_via_rest_adjust_prices_for_taxes( $and_taxes, $order ) {

	// Check if a REST Order.
	if ( ! ( $order instanceof WC_Order ) || ( 'rest-api' !== $order->get_created_via() ) ) {
		return;
	}

	// Check if the Prices Inclusive of Tax is set and if not bail.
	if ( 'yes' !== get_option( 'woocommerce_prices_include_tax' ) ) {
		return;
	}

	// Finally double check this is a QPilot Order.
	$scheduled_order_id = $order->get_meta( '_qpilot_scheduled_order_processing_id', true );
	if ( empty( $scheduled_order_id ) ) {
		return;
	}

	// Retrieve the metavalue to see if this order has been adjusted already.
	$adjusted = $order->get_meta( '_autoship_adjusted_tax_prices', true );

	// Check if this order has already been adjusted.
	if ( apply_filters( 'autoship_qpilot_orders_update_via_rest_adjust_prices_for_taxes', ! empty( $adjusted ), $adjusted, $order ) ) {
		return;
	}

	// Figure out Tax Location.
	$tax_based_on = get_option( 'woocommerce_tax_based_on' );

	if ( 'billing' === $tax_based_on ) {
		$country  = $order->get_billing_country();
		$state    = $order->get_billing_state();
		$postcode = $order->get_billing_postcode();
		$city     = $order->get_billing_city();
	} else {
		$country  = $order->get_shipping_country();
		$state    = $order->get_shipping_state();
		$postcode = $order->get_shipping_postcode();
		$city     = $order->get_shipping_city();
	}

	// The loop to get the order items which are WC_Order_Item_Product objects since WC 3+.
	foreach ( $order->get_items() as $item_id => $item ) {

		// Get the product ID.
		$product_id = $item->get_product_id();
		$product    = wc_get_product( $product_id );

		// Get the Tax Class.
		$tax_class      = $product->get_tax_class();
		$base_tax_rates = WC_Tax::find_rates(
			array(
				'country'   => $country,
				'state'     => $state,
				'postcode'  => $postcode,
				'city'      => $city,
				'tax_class' => $tax_class,
			)
		);

		$product_quantity = (int) $item->get_quantity();

		// Back calculated price ( incl. Taxes ).
		$old_price = $item->get_total() / $product_quantity;

		// Work out a new base price without the shop's base tax.
		$taxes = WC_Tax::calc_tax( $old_price, $base_tax_rates, true );

		// Now we have a new item price (excluding TAX).
		$new_price = $old_price - array_sum( $taxes );

		// The new line item price.
		$new_line_item_price = $new_price * $product_quantity;

		// Set the new price.
		$item->set_subtotal( $new_line_item_price );
		$item->set_total( $new_line_item_price );

		$item->save();
	}

	// Loop through shipping items and recalc item cost.
	foreach ( $order->get_items( 'shipping' ) as $item_id => $item ) {

		// Get the Tax class for the shipping item.
		$shipping_tax_class = $item->get_tax_class();

		$shipping_tax_rates = autoship_get_shipping_tax_rates_from_order(
			$shipping_tax_class,
			array(
				'country'  => $country,
				'state'    => $state,
				'postcode' => $postcode,
				'city'     => $city,
			),
			$order
		);

		// Get the current item cost.
		$shipping = $item->get_total();

		// Work out a new tax based on the shipping total and tax rate.
		$taxes = WC_Tax::calc_tax( $shipping, $shipping_tax_rates, true );

		// Now we have a new shipping total (excluding TAX).
		$new_shipping = $shipping - array_sum( $taxes );

		// Set the new item cost.
		$item->set_total( $new_shipping );

		// Save the item.
		$item->save();
	}

	// Finally set the metadata key value so we know this order has been adjusted already.
	$order->update_meta_data( '_autoship_adjusted_tax_prices', current_time( 'mysql' ) );
	$order->save();
}

/**
 * Adjusts to skip tax included hooked function until after initial save.
 *
 * @param bool     $updated True if the order has been updated.
 * @param bool     $adjusted True if the order has been adjusted.
 * @param WC_Order $order The WC Order object.
 *
 * @return bool True if order has been updated or it's a new non-saved order.
 */
function autoship_check_for_post_rest( $updated, $adjusted, $order ) {

	return $updated || empty( $order->get_id() );
}

/**
 * Forces Orders to be recalculated after saved via the rest api to force tax adjustment
 *
 * @param int    $order The created/inserted order.
 * @param string $request The request.
 * @param int    $creating If the order is creating.
 */
function autoship_adjust_line_item_tax_after_rest_save( $order, $request, $creating ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

	// Check if a REST Order.
	if ( ! ( $order instanceof WC_Order ) || ( 'rest-api' !== $order->get_created_via() ) ) {
		return;
	}

	// Check if the Prices Inclusive of Tax is set and if not bail.
	if ( 'yes' !== get_option( 'woocommerce_prices_include_tax' ) ) {
		return;
	}

	// Finally double check this is a QPilot Order.
	$scheduled_order_id = $order->get_meta( '_qpilot_scheduled_order_processing_id', true );
	if ( empty( $scheduled_order_id ) ) {
		return;
	}

	// Finally re-calc totals to trigger our tax function.
	$order->calculate_totals();
	$order->save();
}

/**
 * Sets the Next Occurrence Offset for Orders that enter via the API.
 *
 * @param WP_REST_Request $request The current Request data.
 * @param WP_REST_Request $original_request The original Request data.
 *
 * @return WP_Error|WP_REST_Response
 */
function autoship_qpilot_orders_update_via_rest_set_next_occurrence_offset( $request, $original_request ) {

	// Check for Next Occurrence UTC Metadata.
	if ( isset( $request['meta_data'] ) && ! empty( $request['meta_data'] ) ) {
		foreach ( $request['meta_data'] as $key => $meta ) {
			if ( '_qpilot_next_occurrence_utc' === $meta['key'] ) {
				$next_occurrence_utc = $meta['value'];
				break;
			}
		}
	}

	// Allow devs to hook in so that the offset could be modified on the fly.
	$next_occurrence_offset = apply_filters( 'autoship_rest_generated_order_offset', ! empty( $next_occurrence_utc ) ? autoship_get_formatted_local_date( $next_occurrence_utc, 'Y-m-d H:i:s' ) : '', $next_occurrence_utc, $request, $original_request );

	// Grab the request metadata.
	$meta_data   = $request['meta_data'];
	$meta_data[] = array(
		'key'   => '_qpilot_next_occurrence_offset',
		'value' => $next_occurrence_offset,
	);

	// Re-attach the metadata.
	$request->set_param( 'meta_data', $meta_data );

	return $request;
}

/**
 * Updates the Created Scheduled Orders Key value for the supplied Order ID.
 *
 * @param int       $order_id The WC Order id.
 * @param array|int $vals The Scheduled Order ids to set.
 *
 * @return string The resulting Key value.
 */
function autoship_set_order_created_scheduled_orders_key( $order_id, $vals ) {

	if ( ! empty( $vals ) ) {
		$vals = is_numeric( $vals ) ? '-' . $vals . '-' : '-' . implode( '-', $vals ) . '-';
	}

	$order = wc_get_order( $order_id );
	$order->update_meta_data( '_autoship_created_scheduled_orders_key', $vals );
	$order->save();

	return $vals;
}

/**
 * Clears the Assigned Scheduled Orders Key
 *
 * @param WC_Order $wc_order The WooCommerce Order.
 */
function autoship_remove_order_keys( $wc_order ) {
	autoship_set_order_created_scheduled_orders_key( $wc_order->get_id(), '' );
}

/**
 * Updates the Created Scheduled Orders Error value for the supplied Order ID.
 *
 * @param int    $order_id The WC Order id.
 * @param string $notice The Error.
 */
function autoship_set_order_created_scheduled_orders_error( $order_id, $notice = '' ) {

	$order = wc_get_order( $order_id );
	$order->update_meta_data( '_autoship_created_scheduled_orders_error', $notice );
	$order->save();
}

/**
 * Generates the scheduled_orders_key from the WC Order.
 *
 * @param WC_Order $wc_order The WooCommerce Order.
 */
function autoship_generate_existing_order_keys( $wc_order ) {

	$legacy_ids = autoship_get_order_scheduled_order_legacy_ids( $wc_order );

	if ( ! empty( $legacy_ids ) ) {
		autoship_set_order_created_scheduled_orders_key( $wc_order->get_id(), $legacy_ids );
	}
}

/**
 * Stores the Number of expected scheduled orders for the supplied order.
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 * @param int          $count The expected number of orders.
 */
function autoship_set_scheduled_order_expected_count( $order, $count ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	if ( ! $order ) {
		return;
	}

	// set the count.
	$order->update_meta_data( '_autoship_scheduled_order_expected_count', $count );
	$order->save();
}

/**
 * Applies the posted autoship scheduled order ids to the order.
 *
 * @param WC_Order $order The WC Order being saved.
 */
function autoship_maybe_save_new_scheduled_order_ids( $order ) {

	// Prevent loop when $order->save() is called later in metadata update.
	remove_action( 'woocommerce_after_order_object_save', 'autoship_maybe_save_new_scheduled_order_ids', 10, 1 );

	// Only save the ids if not empty in the form else we re-assign legacy ids.
	if ( isset( $_POST['autoship_assigned_order_ids'] ) && ! empty( $_POST['autoship_assigned_order_ids'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
		autoship_save_assigned_scheduled_order_ids_metabox( $order, $_POST['autoship_assigned_order_ids'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
	} else {
		autoship_flag_if_scheduled_order_related( $order );
	}
}

/**
 * Applies the posted autoship scheduled order ids to the order.
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 * @param string       $val The ids in a string value.
 *
 * @return bool True if updated else false.
 */
function autoship_save_assigned_scheduled_order_ids_metabox( $order, $val ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	if ( ! $order ) {
		return false;
	}

	$ids = array_map( 'trim', explode( ',', $val ) );

	$existing_ids = autoship_get_order_created_scheduled_orders_key( $order );

	if ( empty( $existing_ids ) ) {
		$existing_ids = array();
	}

	// Add the new ids to the existing linked ids.
	foreach ( $ids as $key => $value ) {

		if ( ! is_numeric( $value ) ) {
			return;
		}

		if ( ! in_array( $value, $existing_ids, true ) ) {
			$existing_ids[] = $value;
		}
	}

	// Re-save the ids to the order.
	$results = autoship_set_order_created_scheduled_orders_key( $order->get_id(), $existing_ids );

	// Finally Clear any errors if they exist.
	autoship_set_order_created_scheduled_orders_error( $order->get_id() );

	autoship_add_message(
		empty( $ids ) ?
			__( 'Assigned Autoship Scheduled Order IDs Cleared.', 'autoship' ) :
			// translators: %s is the list of assigned scheduled order ids.
			sprintf( __( 'Autoship Scheduled Order ID(s) <strong>%s</strong> assigned Successfully.', 'autoship' ), implode( ', ', $ids ) ),
		'updated',
		'autoship_wc_order_updates'
	);

	return true;
}

/**
 * Checks if the Order is Autoship Related and sets the metadata flag.
 *
 * @param int|WC_Order $order The WooCommerce Order or Order ID.
 * @param string       $source The value to use.
 *
 * @return string|bool The Flag Value ( source or result ) else
 *                     false if orders doesn't exist or not Autoship.
 */
function autoship_flag_if_scheduled_order_related( $order, $source = '' ) {

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	if ( ! $order ) {
		return false;
	}

	// Use the value if set manually.
	if ( 'result' === $source || 'source' === $source ) {

		// set the flag.
		$order->update_meta_data( '_autoship_scheduled_order_type', $source );

		// If the order is a source then regenerate the order keys
		// if no keys exist.
		if ( 'source' === $source ) {

			$existing_ids = autoship_get_order_created_scheduled_orders_key( $order );

			if ( empty( $existing_ids ) ) {
				autoship_generate_existing_order_keys( $order );
			}
		}

		return $source;
	}

	// Check if the order has autoship items.
	if ( autoship_order_total_scheduled_items( $order ) ) {

		// Check source.
		$created_via = $order->get_created_via();
		$source      = 'rest-api' === $created_via ? 'result' : 'source';

		// set the flag.
		$order->update_meta_data( '_autoship_scheduled_order_type', $source );

		// If the order is a source then regenerate the order keys
		// if no keys exist.
		if ( 'source' === $source ) {

			$existing_ids = autoship_get_order_created_scheduled_orders_key( $order );

			if ( empty( $existing_ids ) ) {
				autoship_generate_existing_order_keys( $order );
			}
		}

		return $source;
	}

	return false;
}

// ==========================================================
// SCHEDULED ORDER DATA CREATION FUNCTIONS
// ==========================================================

/**
 * Retrieves the Schedule Order Item data
 *
 * @param int    $order_id The Autoship Order id.
 * @param int    $product_id The Autoship Product id.
 * @param int    $external_id The wc product id.
 * @param int    $qty The quantity for the order item.
 * @param string $frequency_type .The frequency type assigned to this product.
 * @param int    $frequency .        The actual frequency duration.
 * @param float  $price Optional. The Price. If not supplied uses the WC Product Price. Default NULL.
 * @param float  $item_recurring_price Optional. The Sale Price to use. If not supplied uses the WC Product Autoship Recurring Price. or sale price. Default NULL.
 *
 * @return array The new scheduled_order_item_data
 * @uses autoship_get_scheduled_order_product_data()
 */
function autoship_create_scheduled_order_item_data( $order_id, $product_id, $external_id, $qty, $frequency_type, $frequency, $price = null, $item_recurring_price = null ) {

	$prices = wp_parse_args(
		autoship_get_product_prices( $external_id, $frequency_type, $frequency ),
		array(
			'regular_price'            => $price,
			'autoship_recurring_price' => $item_recurring_price,
		)
	);

	// Item price & recurring sale price.
	$item_price = apply_filters( 'autoship_create_scheduled_order_item_price', floatval( $prices['regular_price'] ), $frequency_type, $frequency, $product_id, $external_id );

	$item_recurring_price = apply_filters( 'autoship_create_scheduled_order_item_sale_price', isset( $prices['autoship_recurring_price'] ) ? floatval( $prices['autoship_recurring_price'] ) : null, $frequency_type, $frequency, $product_id, $external_id );

	// Get the Product specific line item data.
	return apply_filters(
		'autoship_create_scheduled_order_item_data',
		array(
			'quantity'         => $qty,
			'scheduledOrderId' => $order_id,
			'productId'        => $product_id,
			'price'            => $item_price,
			'salePrice'        => $item_recurring_price,
		),
		$frequency_type,
		$frequency,
		$product_id,
		$external_id
	);
}

/**
 * Retrieves the Schedule Order Item Product data
 *
 * @param int    $order_id The Autoship Order id.
 * @param int    $product_id The Autoship Product id.
 * @param int    $external_id The wc product id.
 * @param string $frequency_type Optional. The frequency Type being used. Supplied for filters.
 * @param string $frequency Optional. The frequency being used. Supplied for filters.
 *
 * @return array The new scheduled_order_item_product_data
 * @uses autoship_get_scheduled_order_product_data()
 *
 * NOTE NOT IN USE
 */
function autoship_create_scheduled_order_item_product_data( $order_id, $product_id, $external_id, $frequency_type = '', $frequency = '' ) {

	// Get the WC Product Object.
	$product = wc_get_product( $external_id );

	// Get site id.
	$site_id = autoship_get_site_id();

	// Product Item price & sale price.
	$item_price = apply_filters( 'autoship_create_scheduled_order_item_product_price', floatval( $product->get_regular_price() ), $frequency_type, $frequency, $product );

	// Get the Autoship re-order Sale Price.
	$autoship_recurring_price = autoship_get_product_recurring_price( $external_id, $frequency_type, $frequency );

	// The product level sale price is either the recurring price or the wc sale price.
	$item_sale_price = apply_filters( 'autoship_create_scheduled_order_item_product_sale_price', $autoship_recurring_price, $frequency_type, $frequency, $product );

	// Get the Product specific line item data.
	$scheduled_order_item_product_data = array(
		'product' => autoship_generate_product_upsert_data(
			$product,
			array(
				'price'     => $item_price,
				'salePrice' => $item_sale_price,
			)
		),
	);

	return apply_filters( 'autoship_create_scheduled_order_item_product_data', $scheduled_order_item_product_data, $frequency_type, $frequency, $product_id, $external_id );
}

// ==========================================================
// API Related Functions
// ==========================================================

/**
 * Creates and Sends scheduled orders to QPilot
 * WC Orders are broken down and grouped by Frequency Type + Frequency + Next Occurrence
 *
 * @param WC_Order|int $order A WC Order object or order id.
 * @param string       $creation_date Optional. The date the order should be created based on should be 'Y-m-d H:i:s' format. Default NULL.
 *
 * @return array|WP_Error An array of ids created or WP_Error on failure.
 * @uses QPilotClient::upsert_order()
 *
 * Developers can modify QPilot Orders using the {@see autoship_create_scheduled_order_data}
 * filter
 *
 * @uses autoship_get_scheduled_order_data()
 */
function autoship_create_scheduled_orders( $order, $creation_date = null ) {

	$start_time = floor( microtime( true ) * 1000 );

	if ( is_numeric( $order ) ) {
		$order = wc_get_order( $order );
	}

	$order_id = $order->get_id();

	Logger::trace( 'Autoship Orders Creator', "Prepare Scheduled Orders Data for WooCommerce Order: $order_id" );
	$creation_start_time = floor( microtime( true ) * 1000 );

	// Create the scheduled order data to upsert. Group the data for the order by frequency, frequency type, next occurrence, and preferred shipping rate.
	$group_scheduled_order_data = autoship_get_scheduled_order_data( $order, $creation_date );

	$creation_end_time = floor( microtime( true ) * 1000 );
	$creation_elapsed  = $creation_end_time - $creation_start_time;
	Logger::trace( 'Autoship Orders Creator', "Prepared Scheduled Orders for WooCommerce Order: $order_id Elapsed: $creation_elapsed milliseconds" );

	if ( ! empty( $group_scheduled_order_data ) ) {

		// Set the Expected Scheduled Order count.
		autoship_set_scheduled_order_expected_count( $order, count( $group_scheduled_order_data ) );

		// Set the Source if not already set.
		autoship_flag_if_scheduled_order_related( $order, 'source' );
	}

	$error               = '';
	$scheduled_order_ids = array();
	$scheduled_orders    = array();

	// Loop through the order groups and upsert each.
	$index = 1;
	foreach ( $group_scheduled_order_data as $scheduled_order_data ) {

		Logger::trace( 'Autoship Orders Creator', "Invoking Scheduled Orders Data Filter for WooCommerce Order: $order_id and Scheduled Order Data Index: $index" );
		$filter_start_time = floor( microtime( true ) * 1000 );

		$scheduled_order_data = apply_filters( 'autoship_create_scheduled_order_data', $scheduled_order_data, $order_id );

		$filter_end_time = floor( microtime( true ) * 1000 );
		$filter_elapsed  = $filter_end_time - $filter_start_time;
		Logger::trace( 'Autoship Orders Creator', "Invoked Scheduled Orders Data Filter for WooCommerce Order: $order_id and Scheduled Order Data Index $index Elapsed: $filter_elapsed milliseconds" );

		Logger::trace( 'Autoship Orders Creator', "Sending Scheduled Order Creation to QPilot for WooCommerce Order: $order_id and Scheduled Order Data Index: $index" );
		$qpilot_start_time = floor( microtime( true ) * 1000 );

		// Create the order in QPilot.
		$scheduled_order = autoship_create_scheduled_order( $scheduled_order_data['customerId'], $scheduled_order_data );

		$qpilot_end_time = floor( microtime( true ) * 1000 );
		$qpilot_elapsed  = $qpilot_end_time - $qpilot_start_time;
		Logger::trace( 'Autoship Orders Creator', "Received Scheduled Order Creation Response from QPilot for WooCommerce Order: $order_id and Scheduled Order Data Index $index Elapsed: $qpilot_elapsed milliseconds" );

		if ( is_wp_error( $scheduled_order ) ) {
			$failure_start_time = floor( microtime( true ) * 1000 );

			// Attach the order keys, save the error and add an error note.
			// translators: %1$d is the order id, %2$s is the error message.
			$note  = sprintf( __( 'Creating Scheduled Orders from Order #%1$d Failed.  Additional Details: %2$s' ), $order_id, $scheduled_order->get_error_message() );
			$error = new WP_Error( 'Create Scheduled Orders Failed', $note );

			autoship_set_order_created_scheduled_orders_key( $order_id, $scheduled_order_ids );
			autoship_set_order_created_scheduled_orders_error( $order_id, $error );

			$order->add_order_note( $note );

			// Allow for hooking in after a single order has been Successfully created.
			do_action( 'autoship_create_scheduled_order_failure', $scheduled_order, $order_id );

			$failure_end_time = floor( microtime( true ) * 1000 );
			$failure_elapsed  = $failure_end_time - $failure_start_time;

			Logger::trace( 'Autoship Orders Creator', "Failed Creation of Scheduled Order in QPilot for WooCommerce Order: $order_id and Scheduled Order Data Index: $index Elapsed: $failure_elapsed milliseconds" );

		} else {
			$success_start_time = floor( microtime( true ) * 1000 );

			// Tag on the order ids to this order's metadata.
			$scheduled_order_ids[]                    = $scheduled_order->id;
			$scheduled_orders[ $scheduled_order->id ] = $scheduled_order;
			$order->update_meta_data( '_autoship_created_scheduled_order_id', $scheduled_order_ids );

			// Allow for hooking in after a single order has been Successfully created.
			do_action( 'autoship_create_scheduled_order_success', $scheduled_order, $order_id );

			$success_end_time = floor( microtime( true ) * 1000 );
			$success_elapsed  = $success_end_time - $success_start_time;

			Logger::trace( 'Autoship Orders Creator', "Successful Creation of Scheduled Order in QPilot for WooCommerce Order: $order_id and Scheduled Order Data Index: $index. Scheduled Order Id: $scheduled_order->id Elapsed: $success_elapsed milliseconds" );
		}

		++$index;
	}

	$storing_start_time = floor( microtime( true ) * 1000 );

	if ( ! empty( $group_scheduled_order_data ) ) {

		// Add any last IDs.
		autoship_set_order_created_scheduled_orders_key( $order_id, $scheduled_order_ids );

		// Only if no errors then add success note.
		if ( empty( $error ) ) {
			// translators: %s is the list of scheduled order ids.
			$note = sprintf( __( 'Autoship Scheduled Order Created Successfully: %s.' ), implode( ', ', $scheduled_order_ids ) );
			autoship_set_order_created_scheduled_orders_error( $order_id );
			$order->add_order_note( $note );
			do_action( 'autoship_create_scheduled_orders_success', $scheduled_order_ids, $order_id, $scheduled_orders );
		}
	}

	$order->save();

	if ( ! empty( $error ) ) {
		do_action( 'autoship_create_scheduled_orders_failure', $scheduled_order_ids, $order_id, $error );
	}

	$storing_end_time = floor( microtime( true ) * 1000 );
	$storing_elapsed  = $storing_end_time - $storing_start_time;

	Logger::trace( 'Autoship Orders Creator', "Finishing Persisting Scheduled Order Data in WooCommerce Order: $order_id Elapsed: $storing_elapsed milliseconds" );

	$end_time = floor( microtime( true ) * 1000 );
	$elapsed  = $end_time - $start_time;

	if ( Logger::is_logging_enabled() ) {
		$output_data = var_export( $scheduled_order_ids, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export

		Logger::trace( 'Autoship Orders Creator', "Finished Creating Scheduled Orders for WooCommerce Order: $order_id Elapsed time: $elapsed milliseconds. Scheduled Order Ids: $output_data" );
	}

	return $scheduled_order_ids;
}

/**
 * Checks for Missing Payment Info on Checkout Created Scheduled Orders
 *
 * @param array $scheduled_order_ids Array of Scheduled order IDs.
 * @param int   $order_id The WC Order ID.
 * @param array $scheduled_orders Array of generated Scheduled order objects.
 */
function autoship_verify_payment_method_on_generated_scheduled_orders( $scheduled_order_ids, $order_id, $scheduled_orders ) {

	foreach ( $scheduled_orders as $scheduled_order ) {

		if ( ! isset( $scheduled_order->paymentMethod ) || empty( $scheduled_order->paymentMethod ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

			// Attach the orders key, save the error and add an error note.
			$note  = __( ' Important! One or More Created Scheduled Orders is Missing Payment Information.' );
			$error = new WP_Error( 'Created Scheduled Orders Missing Payment Method', $note );
			autoship_set_order_created_scheduled_orders_error( $order_id, $error );

			$order = wc_get_order( $order_id );
			$order->add_order_note( $note );
			break;

		}
	}
}

add_action( 'autoship_create_scheduled_orders_success', 'autoship_verify_payment_method_on_generated_scheduled_orders', 10, 3 );

/**
 * Creates / Upserts a Scheduled Order for any WC Order generated at checkout but put on Hold.
 *
 * @param WC_Order|int $order_id A WC Order object or order id.
 *
 * @uses autoship_create_scheduled_orders()
 */
function autoship_create_scheduled_orders_on_hold( $order_id ) {
	if ( is_checkout() ) {
		autoship_create_scheduled_orders( $order_id );
	}
}

/**
 * Updates the order payment gateway meta data when updated via QPilot.
 *
 * @param WC_Order|int $order_id A WC Order object or order id.
 *
 * @uses autoship_create_scheduled_orders()
 */
function autoship_update_scheduled_orders_on_processing( $order_id ) {

	// Get the wc order.
	$order = wc_get_order( $order_id );

	$processing_id  = autoship_get_scheduled_order_processing_id( $order );
	$payment_meta   = autoship_get_scheduled_order_payment_response( $order );
	$payment_object = autoship_get_scheduled_order_payment_method( $order );

	// We only care for QPilot orders that have payment meta.
	if ( is_checkout() || empty( $processing_id ) || empty( $payment_meta ) ) {
		return;
	}

	// Check if there is a payment method associated with it and fire the action.
	$payment_method = strtolower( $order->get_payment_method() );
	$action         = empty( $payment_method ) ? 'general' : $payment_method;

	do_action( 'autoship_update_scheduled_orders_on_processing_' . $action . '_gateway', $payment_meta, $order, $payment_object );
}

// ==========================================================
// DEFAULT HOOKED ACTIONS
// ==========================================================

/**
 * Checks the WooCommerce Shop Tax Setting to see if Prices are inclusive of Tax
 * and if so adjusts the prices so that double-tax isn't applied.
 * Hooked functionality a) first checks if the order coming in on the REST API has been saved
 * if not don't do anything and b) if it has been saved and Prices inclusive of tax is true force calculate totals
 *
 * NOTE This is actually a bug in WooCommerce REST API in that it doesn't abide by the Shop Tax Settings.
 *
 * @see autoship_qpilot_orders_update_via_rest_adjust_prices_for_taxes()
 * @see autoship_check_for_post_rest()
 * @see autoship_adjust_line_item_tax_after_rest_save()
 */
add_action( 'woocommerce_order_before_calculate_totals', 'autoship_qpilot_orders_update_via_rest_adjust_prices_for_taxes', 9, 2 );
add_filter( 'autoship_qpilot_orders_update_via_rest_adjust_prices_for_taxes', 'autoship_check_for_post_rest', 10, 3 );
add_action( 'woocommerce_rest_insert_shop_order_object', 'autoship_adjust_line_item_tax_after_rest_save', 9, 3 );

/**
 * Add Formatted Offset UTC for Orders Table.
 *
 * @see autoship_qpilot_orders_update_via_rest_set_next_occurrence_offset()
 */
add_filter( 'autoship_qpilot_orders_via_rest', 'autoship_qpilot_orders_update_via_rest_set_next_occurrence_offset', 10, 2 );

/**
 * Shop Order Display Hooks and Filters
 *
 * @see autoship_order_dashboard_custom_column_sort()
 * @see autoship_order_populate_dashboard_columns()
 * @see autoship_handle_custom_order_query_vars()
 * @see autoship_order_dashboard_custom_column_sortable()
 * @see autoship_order_dashboard_custom_column_searchable()
 * @see autoship_order_dashboard_column_headers()
 */
add_action( 'pre_get_posts', 'autoship_order_dashboard_custom_column_sort' );
add_action( 'manage_shop_order_posts_custom_column', 'autoship_order_populate_dashboard_columns', 11, 2 );
add_action( 'woocommerce_shop_order_list_table_custom_column', 'autoship_order_populate_dashboard_columns', 11, 2 );
add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', 'autoship_handle_custom_order_query_vars', 10, 2 );
add_filter( 'manage_edit-shop_order_sortable_columns', 'autoship_order_dashboard_custom_column_sortable', 10, 1 );
add_filter( 'woocommerce_shop_order_list_table_sortable_columns', 'autoship_order_dashboard_custom_column_sortable', 10, 1 );
add_filter( 'woocommerce_shop_order_search_fields', 'autoship_order_dashboard_custom_column_searchable', 10, 1 );
add_filter( 'manage_edit-shop_order_columns', 'autoship_order_dashboard_column_headers', 20, 1 );
add_filter( 'woocommerce_shop_order_list_table_columns', 'autoship_order_dashboard_column_headers', 20, 1 );

/**
 * My Account Orders and Order Display Hooks and Filters
 *
 * @see autoship_order_customer_dashboard_icon_column()
 * @see autoship_order_customer_dashboard_icon_column_content()
 * @see autoship_customer_view_order_header_attribution()
 */
add_filter( 'woocommerce_my_account_my_orders_columns', 'autoship_order_customer_dashboard_icon_column', 10, 1 );
add_action( 'woocommerce_my_account_my_orders_column_order-autoship-icon', 'autoship_order_customer_dashboard_icon_column_content', 10, 1 );
add_action( 'woocommerce_view_order', 'autoship_customer_view_order_header_attribution', 9, 1 );

/**
 * Admin Notices
 *
 * @see autoship_admin_display_wc_order_notices()
 */
add_action( 'admin_notices', 'autoship_admin_display_wc_order_notices', 99 );

/**
 * Hooks to Add Option to WC Edit Order Page
 *
 * @see autoship_add_meta_box_actions()
 */
add_action( 'woocommerce_order_actions', 'autoship_add_meta_box_actions', 10 );

/**
 * Hooks to Add Metabox to WC Edit Order Page
 *
 * @see autoship_register_edit_order_metaboxes()
 */
add_action( 'add_meta_boxes', 'autoship_register_edit_order_metaboxes', 9 );

/**
 * Hooks to WooCommerce Order Action
 *
 * @see autoship_remove_order_keys()
 */
add_action( 'woocommerce_order_action_autoship_clear_existing_order_keys', 'autoship_remove_order_keys' );

/**
 * WooCommerce Order is saved
 *
 * @see autoship_maybe_save_new_scheduled_order_ids()
 */
add_action( 'woocommerce_after_order_object_save', 'autoship_maybe_save_new_scheduled_order_ids', 10, 1 );

/**
 * WooCommerce Order Status On Hold
 *
 * @see autoship_create_scheduled_orders_on_hold()
 */
add_action( 'woocommerce_order_status_on-hold', 'autoship_create_scheduled_orders_on_hold', 10, 1 );

/**
 * WooCommerce Order Status Processing
 *
 * @see autoship_update_scheduled_orders_on_processing()
 */
add_action( 'woocommerce_order_status_processing', 'autoship_update_scheduled_orders_on_processing', 10, 1 );

/**
 * Filter Scheduled order Payment Data generated at checkout
 *
 * @see autoship_get_scheduled_order_payment_data()
 */
add_filter( 'autoship_get_scheduled_order_payment_data', 'autoship_adjust_sepa_order_payment_method_data', 10, 3 );
