<?php
/**
 * ApiUtil class file.
 */

namespace Automattic\WooCommerce\Internal;

/**
 * Helper methos for the REST API.
 *
 * Class ApiUtil
 *
 * @package Automattic\WooCommerce\Internal
 */
class RestApiUtil {

	/**
	 * Converts a create refund request from the public API format:
	 *
	 * [
	 *   "reason" => "",
	 *   "api_refund" => "x",
	 *   "line_items" => [
	 *     "id" => "111",
	 *     "quantity" => 222,
	 *     "refund_total" => 333,
	 *     "refund_tax" => [
	 *       [
	 *          "id": "444",
	 *          "refund_total": 555
	 *       ],...
	 *   ],...
	 * ]
	 *
	 * ...to the internally used format:
	 *
	 * [
	 *   "reason" => null,     (if it's missing or any empty value, set as null)
	 *   "api_refund" => true, (if it's missing or non-bool, set as "true")
	 *   "line_items" => [     (convert sequential array to associative based on "id")
	 *     "111" => [
	 *       "qty" => 222,     (rename "quantity" to "qty")
	 *       "refund_total" => 333,
	 *       "refund_tax" => [ (convert sequential array to associative based on "id" and "refund_total)
	 *         "444" => 555,...
	 *       ],...
	 *   ]
	 * ]
	 *
	 * It also calculates the amount if missing and whenever possible, see maybe_calculate_refund_amount_from_line_items.
	 *
	 * The conversion is done in a way that if the request is already in the internal format,
	 * then nothing is changed for compatibility. For example, if the line items array
	 * is already an associative array or any of its elements
	 * is missing the "id" key, then the entire array is left unchanged.
	 * Same for the "refund_tax" array inside each line item.
	 *
	 * @param \WP_REST_Request $request The request to adjust.
	 */
	public static function adjust_create_refund_request_parameters( \WP_REST_Request &$request ) {
		if ( empty( $request['reason'] ) ) {
			$request['reason'] = null;
		}

		if ( ! is_bool( $request['api_refund'] ) ) {
			$request['api_refund'] = true;
		}

		if ( empty( $request['line_items'] ) ) {
			$request['line_items'] = array();
		} else {
			$request['line_items'] = self::adjust_line_items_for_create_refund_request( $request['line_items'] );
		}

		if ( ! isset( $request['amount'] ) ) {
			$amount = self::calculate_refund_amount_from_line_items( $request );
			if ( null !== $amount ) {
				$request['amount'] = strval( $amount );
			}
		}
	}

	/**
	 * Calculate the "amount" parameter for the request based on the amounts found in line items.
	 * This will ONLY be possible if ALL of the following is true:
	 *
	 * - "line_items" in the request is a non-empty array.
	 * - All line items have a "refund_total" field with a numeric value.
	 * - All values inside "refund_tax" in all line items are a numeric value.
	 *
	 * The request is assumed to be in internal format already.
	 *
	 * @param \WP_REST_Request $request The request to maybe calculate the total amount for.
	 * @return number|null The calculated amount, or null if it can't be calculated.
	 */
	private static function calculate_refund_amount_from_line_items( $request ) {
		$line_items = $request['line_items'];

		if ( ! is_array( $line_items ) || empty( $line_items ) ) {
			return null;
		}

		$amount = 0;

		foreach ( $line_items as $item ) {
			if ( ! isset( $item['refund_total'] ) || ! is_numeric( $item['refund_total'] ) ) {
				return null;
			}

			$amount += $item['refund_total'];

			if ( ! isset( $item['refund_tax'] ) ) {
				continue;
			}

			foreach ( $item['refund_tax'] as $tax ) {
				if ( ! is_numeric( $tax ) ) {
					return null;
				}
				$amount += $tax;
			}
		}

		return $amount;
	}

	/**
	 * Convert the line items of a refund request to internal format (see adjust_create_refund_request_parameters).
	 *
	 * @param array $line_items The line items to convert.
	 * @return array The converted line items.
	 */
	private static function adjust_line_items_for_create_refund_request( $line_items ) {
		if ( ! is_array( $line_items ) || empty( $line_items ) || self::is_associative( $line_items ) ) {
			return $line_items;
		}

		$new_array = array();
		foreach ( $line_items as $item ) {
			if ( ! isset( $item['id'] ) ) {
				return $line_items;
			}

			if ( isset( $item['quantity'] ) && ! isset( $item['qty'] ) ) {
				$item['qty'] = $item['quantity'];
			}
			unset( $item['quantity'] );

			if ( isset( $item['refund_tax'] ) ) {
				$item['refund_tax'] = self::adjust_taxes_for_create_refund_request_line_item( $item['refund_tax'] );
			}

			$id               = $item['id'];
			$new_array[ $id ] = $item;

			unset( $new_array[ $id ]['id'] );
		}

		return $new_array;
	}

	/**
	 * Adjust the taxes array from a line item in a refund request, see adjust_create_refund_parameters.
	 *
	 * @param array $taxes_array The array to adjust.
	 * @return array The adjusted array.
	 */
	private static function adjust_taxes_for_create_refund_request_line_item( $taxes_array ) {
		if ( ! is_array( $taxes_array ) || empty( $taxes_array ) || self::is_associative( $taxes_array ) ) {
			return $taxes_array;
		}

		$new_array = array();
		foreach ( $taxes_array as $item ) {
			if ( ! isset( $item['id'] ) || ! isset( $item['refund_total'] ) ) {
				return $taxes_array;
			}

			$id               = $item['id'];
			$refund_total     = $item['refund_total'];
			$new_array[ $id ] = $refund_total;
		}

		return $new_array;
	}

	/**
	 * Is an array sequential or associative?
	 *
	 * @param array $array The array to check.
	 * @return bool True if the array is associative, false if it's sequential.
	 */
	private static function is_associative( array $array ) {
		return array_keys( $array ) !== range( 0, count( $array ) - 1 );
	}
}
