<?php
/**
 * Turns a WooCommerce order into a flat, predictable array.
 *
 * @package Ordsync
 */

defined( 'ABSPATH' ) || exit;

/**
 * One shape of data for every destination. Change it here, it changes everywhere.
 */
class Ordsync_Payload {

	/**
	 * Order object.
	 *
	 * @var WC_Order
	 */
	private $order;

	/**
	 * Constructor.
	 *
	 * @param WC_Order $order Order.
	 */
	public function __construct( $order ) {
		$this->order = $order;
	}

	/**
	 * Build the payload.
	 *
	 * @return array
	 */
	public function to_array() {
		$order   = $this->order;
		$created = $order->get_date_created();

		$payload = array(
			'order_id'     => $order->get_id(),
			'order_number' => $order->get_order_number(),
			'status'       => $order->get_status(),
			'date'         => $created ? $created->date( 'Y-m-d H:i:s' ) : '',
			'currency'     => $order->get_currency(),
			'subtotal'     => wc_format_decimal( $order->get_subtotal(), 2 ),
			'shipping'     => wc_format_decimal( $order->get_shipping_total(), 2 ),
			'tax'          => wc_format_decimal( $order->get_total_tax(), 2 ),
			'total'        => wc_format_decimal( $order->get_total(), 2 ),
			'payment'      => $order->get_payment_method_title(),
			/*
			 * 'shipping' above is the shipping COST and existing webhook
			 * receivers already read it, so the method name and address go
			 * under new keys rather than changing what that one means.
			 */
			'shipping_method' => $order->get_shipping_method(),
			'transaction_id'  => $order->get_transaction_id(),
			'coupons'         => $this->coupons(),
			'coupons_text'    => implode( ', ', $this->coupons() ),
			'shipping_address' => array(
				'first_name' => $order->get_shipping_first_name(),
				'last_name'  => $order->get_shipping_last_name(),
				'company'    => $order->get_shipping_company(),
				'address_1'  => $order->get_shipping_address_1(),
				'address_2'  => $order->get_shipping_address_2(),
				'city'       => $order->get_shipping_city(),
				'postcode'   => $order->get_shipping_postcode(),
				'state'      => $order->get_shipping_state(),
				'country'    => $order->get_shipping_country(),
			),
			'customer'     => array(
				'id'         => $order->get_customer_id(),
				'first_name' => $order->get_billing_first_name(),
				'last_name'  => $order->get_billing_last_name(),
				'email'      => $order->get_billing_email(),
				'phone'      => $order->get_billing_phone(),
				'company'    => $order->get_billing_company(),
				'address_1'  => $order->get_billing_address_1(),
				'address_2'  => $order->get_billing_address_2(),
				'city'       => $order->get_billing_city(),
				'postcode'   => $order->get_billing_postcode(),
				'state'      => $order->get_billing_state(),
				'country'    => $order->get_billing_country(),
			),
			'items'        => $this->items(),
			'note'         => self::decode( $order->get_customer_note() ),
			'store'        => array(
				// 'raw', not the default 'display' filter — this is JSON, not HTML,
				// so the site name must not come back with &amp; / &#039; entities.
				'name' => self::decode( get_bloginfo( 'name', 'raw' ) ),
				'url'  => home_url(),
			),
		);

		/**
		 * Filter the outgoing order payload.
		 *
		 * @param array    $payload Payload.
		 * @param WC_Order $order   Order.
		 */
		return apply_filters( 'ordsync_payload', $payload, $order );
	}

	/**
	 * Turn stored HTML entities back into real characters.
	 *
	 * WordPress keeps titles encoded, so the site name comes out of the
	 * database as "Lowe&#039;s Coupons &amp; Home Depot". That is correct for
	 * a web page and wrong for everything this plugin does: JSON, a
	 * spreadsheet cell and a PDF all want the actual apostrophe.
	 *
	 * get_bloginfo( 'name', 'raw' ) does NOT solve this. "raw" means
	 * unfiltered, and the entities are in the stored value itself.
	 *
	 * @param string $text Possibly encoded text.
	 * @return string
	 */
	public static function decode( $text ) {
		return html_entity_decode( (string) $text, ENT_QUOTES, 'UTF-8' );
	}

	/**
	 * Coupon codes used on this order.
	 *
	 * get_coupon_codes() only exists from WooCommerce 3.7, and the plugin
	 * declares support back to 8.3, but the fallback costs one line and
	 * saves a fatal on an unexpectedly old install.
	 *
	 * @return string[]
	 */
	private function coupons() {

		if ( method_exists( $this->order, 'get_coupon_codes' ) ) {
			return array_values( (array) $this->order->get_coupon_codes() );
		}

		$codes = array();

		foreach ( $this->order->get_items( 'coupon' ) as $coupon ) {
			$codes[] = $coupon->get_code();
		}

		return $codes;
	}

	/**
	 * Line items.
	 *
	 * @return array
	 */
	private function items() {
		$items = array();

		foreach ( $this->order->get_items() as $item ) {
			$product = $item->get_product();

			$items[] = array(
				'product_id' => $item->get_product_id(),
				'name'       => self::decode( $item->get_name() ),
				'sku'        => $product ? $product->get_sku() : '',
				'quantity'   => $item->get_quantity(),
				'total'      => wc_format_decimal( $item->get_total(), 2 ),
			);
		}

		return $items;
	}

	/**
	 * A fake order, used by the [Send Test Order] button.
	 *
	 * @return array
	 */
	public static function sample() {
		return array(
			'order_id'     => 0,
			'order_number' => 'TEST-1001',
			'status'       => 'processing',
			'date'         => current_time( 'Y-m-d H:i:s' ),
			'currency'     => get_woocommerce_currency(),
			'subtotal'     => '99.00',
			'shipping'     => '0.00',
			'tax'          => '0.00',
			'total'        => '99.00',
			'payment'      => 'Test Payment',
			'shipping_method'  => 'Flat rate',
			'transaction_id'   => 'TEST-TXN-1',
			'coupons'          => array( 'WELCOME10' ),
			'coupons_text'     => 'WELCOME10',
			'shipping_address' => array(
				'first_name' => 'John',
				'last_name'  => 'Doe',
				'company'    => '',
				'address_1'  => 'Teststrasse 1',
				'address_2'  => '',
				'city'       => 'Berlin',
				'postcode'   => '10115',
				'state'      => '',
				'country'    => 'DE',
			),
			'customer'     => array(
				'id'         => 0,
				'first_name' => 'John',
				'last_name'  => 'Doe',
				'email'      => 'john@example.com',
				'phone'      => '+49 000 000000',
				'company'    => '',
				'address_1'  => 'Teststrasse 1',
				'address_2'  => '',
				'city'       => 'Berlin',
				'postcode'   => '10115',
				'state'      => '',
				'country'    => 'DE',
			),
			'items'        => array(
				array(
					'product_id' => 0,
					'name'       => 'Test Product',
					'sku'        => 'TEST-SKU',
					'quantity'   => 2,
					'total'      => '99.00',
				),
			),
			'note'         => 'This is a test order from OrderCarry.',
			'store'        => array(
				'name' => self::decode( get_bloginfo( 'name', 'raw' ) ),
				'url'  => home_url(),
			),
			'is_test'      => true,
		);
	}
}
