<?php

namespace AutomateWoo;

/**
 * @class Data_Layer
 */
class Data_Layer {

	/**
	 * When true, data from the order is used instead of the user data.
	 * E.g. order billing first name will be used instead of the first name in user meta.
	 * Also the language of the order overrides the customers language when this is true.
	 * This property also applies to subscriptions.
	 *
	 * In the case of a referral trigger the customer could be the advocate and the order will belong to the friend.
	 *
	 * @since 4.2
	 * @var bool
	 */
	public $order_belongs_to_customer = true;

	/**
	 * The data layer items keyed by type.
	 *
	 * @var array
	 */
	private $data = [];


	/**
	 * @param array $data
	 */
	public function __construct( $data = [] ) {
		if ( is_array( $data ) ) {
			$this->data = $data;

			// Add shop data item
			$this->data['shop'] = new ShopDataItem();
		}

		$this->init();
	}


	/**
	 * Initiate the data layer
	 */
	public function init() {
		$this->ensure_customer_object_compatibility();
		do_action( 'automatewoo/data_layer/init' );
	}


	/**
	 * Auto fill customer based on user and user based on customer for compatibility with legacy triggers, rules etc
	 */
	public function ensure_customer_object_compatibility() {
		if ( $this->get_customer() && ! $this->get_user() ) {

			if ( $this->get_customer()->is_registered() ) {
				$this->set_item( 'user', $this->get_customer()->get_user() );
			} else {
				// if the user is not registered at this point they may be a legacy order guest
				$order = $this->get_order();
				if ( $order ) {
					$this->set_item( 'user', AW()->order_helper->prepare_user_data_item( $order ) );
				} else {
					// IMPORTANT the customer might be a guest in which case remove the user completely
					unset( $this->data['user'] );
				}
			}
		}

		if ( $this->get_user() && ! $this->get_customer() ) {
			$this->set_item( 'customer', Customer_Factory::get_by_user_data_item( $this->get_user() ) );
		}

		// also inject customer objects for guests
		// but note that the guest data object hasn't been completely removed
		if ( $this->get_guest() && ! $this->get_customer() ) {
			$this->set_item( 'customer', Customer_Factory::get_by_guest_id( $this->get_guest()->get_id() ) );
		}
	}


	/**
	 * Clears the data layer.
	 */
	public function clear() {
		$this->data = [];
	}


	/**
	 * Returns unvalidated data layer
	 *
	 * @return array
	 */
	public function get_raw_data() {
		return $this->data;
	}


	/**
	 * @param string $type The data item type.
	 * @param mixed  $item The data item.
	 */
	public function set_item( $type, $item ) {
		$this->data[ $type ] = $item;
	}


	/**
	 * @param string $type
	 * @return mixed
	 */
	public function get_item( $type ) {

		if ( ! isset( $this->data[ $type ] ) ) {
			return false;
		}

		return aw_validate_data_item( $type, $this->data[ $type ] );
	}


	/**
	 * @return Customer|false
	 */
	public function get_customer() {
		return $this->get_item( 'customer' );
	}


	/**
	 * @return Cart|false
	 */
	public function get_cart() {
		return $this->get_item( 'cart' );
	}


	/**
	 * @return Guest|bool
	 */
	public function get_guest() {
		return $this->get_item( 'guest' );
	}


	/**
	 * @return \WP_User|Order_Guest|false
	 */
	public function get_user() {
		return $this->get_item( 'user' );
	}


	/**
	 * @return \WC_Order|false
	 */
	public function get_order() {
		return $this->get_item( 'order' );
	}


	/**
	 * @return \WC_Subscription|false
	 */
	public function get_subscription() {
		return $this->get_item( 'subscription' );
	}


	/**
	 * @return \WC_Booking|false
	 */
	public function get_booking() {
		return $this->get_item( 'booking' );
	}


	/**
	 * @return array|\WC_Order_Item_Product|false
	 */
	public function get_order_item() {
		return $this->get_item( 'order_item' );
	}


	/**
	 * @return \WC_Memberships_User_Membership|false
	 */
	public function get_membership() {
		return $this->get_item( 'membership' );
	}


	/**
	 * @return Wishlist|false
	 */
	public function get_wishlist() {
		return $this->get_item( 'wishlist' );
	}


	/**
	 * @return \WC_Product|false
	 */
	public function get_product() {
		return $this->get_item( 'product' );
	}


	/**
	 * @return Order_Note|false
	 */
	public function get_order_note() {
		return $this->get_item( 'order_note' );
	}


	/**
	 * @return \WP_Comment|false
	 */
	public function get_comment() {
		return $this->get_item( 'comment' );
	}


	/**
	 * @return Review|false
	 */
	public function get_review() {
		return $this->get_item( 'review' );
	}


	/**
	 * @return Workflow|false
	 */
	public function get_workflow() {
		return $this->get_item( 'workflow' );
	}


	/**
	 * @return \WP_Term|false
	 */
	public function get_category() {
		return $this->get_item( 'category' );
	}


	/**
	 * @return \WP_Term|false
	 */
	public function get_tag() {
		return $this->get_item( 'tag' );
	}

	/**
	 * @return \WP_Post|false
	 */
	public function get_course() {
		return $this->get_item( 'course' );
	}

	/**
	 * @return \WP_Post|false
	 */
	public function get_lesson() {
		return $this->get_item( 'lesson' );
	}

	/**
	 * @return \WP_Post|false
	 */
	public function get_quiz() {
		return $this->get_item( 'quiz' );
	}

	/**
	 * @return \WP_User|false
	 */
	public function get_teacher() {
		return $this->get_item( 'teacher' );
	}

	/**
	 * This should return the language of the customer in the data layer.
	 *
	 * @return string|bool
	 */
	public function get_language() {

		if ( ! Language::is_multilingual() ) {
			return false;
		}

		// only use the order language if the order belongs to the customer
		if ( $this->order_belongs_to_customer ) {
			$order = $this->get_order();
			if ( $order ) {
				$lang = Language::get_order_language( $order );
				if ( $lang ) {
					return $lang;
				}
			}
		}

		$customer = $this->get_customer();
		if ( $customer ) {
			$lang = $customer->get_language();
			if ( $lang ) {
				return $lang;
			}
		}

		$user = $this->get_user();
		if ( $user ) {
			$lang = Language::get_user_language( $user );
			if ( $lang ) {
				return $lang;
			}
		}

		$guest = $this->get_guest();
		if ( $guest ) {
			$lang = Language::get_guest_language( $guest );
			if ( $lang ) {
				return $lang;
			}
		}

		return false;
	}


	/**
	 * Alias for $this->get_language()
	 *
	 * @return bool|string
	 */
	public function get_customer_language() {
		return $this->get_language();
	}


	/**
	 * Gets the customer email based on the data layer.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_email() {
		$customer = $this->get_customer();

		if ( ! $customer ) {
			return '';
		}

		$billing_email = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$billing_email = Clean::email( $subscription->get_billing_email() );
			}

			if ( ! $billing_email ) {
				$order = $this->get_order();
				if ( $order ) {
					$billing_email = Clean::email( $order->get_billing_email() );
				}
			}
		}

		if ( $customer->is_registered() ) {
			if ( $billing_email && $this->should_use_order_billing_email_for_customer_email() ) {
				return $billing_email;
			}

			// If the customer has an account use the account email over an order billing email by default.
			// The reason for this is that a customer could change their account email and their
			// orders or subscriptions will not be updated.
			return $customer->get_email();
		} else {
			// If the customer is not registered use the order/subscription billing email over the
			// email stored in the guest account.
			if ( ! $billing_email ) {
				$billing_email = $customer->get_email();
			}

			return $billing_email;
		}
	}

	/**
	 * Should a registered customer's order billing email override their account email?
	 *
	 * @since 6.5.0
	 *
	 * @return bool
	 */
	protected function should_use_order_billing_email_for_customer_email() {
		$use_order_billing_email = defined( 'AUTOMATEWOO_USE_ORDER_BILLING_EMAIL_FOR_CUSTOMER_EMAIL' )
			&& \AUTOMATEWOO_USE_ORDER_BILLING_EMAIL_FOR_CUSTOMER_EMAIL;

		/**
		 * Filter whether a registered customer's order billing email should override their account email.
		 *
		 * @since 6.5.0
		 *
		 * @param bool       $use_order_billing_email True to prefer the order billing email.
		 * @param Data_Layer $data_layer              The current data layer instance.
		 */
		return (bool) apply_filters(
			'automatewoo/data_layer/use_order_billing_email_for_customer_email',
			$use_order_billing_email,
			$this
		);
	}


	/**
	 * Gets the customer first name based on the data layer.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_first_name() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_first_name();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_first_name();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_first_name();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer last name based on the data layer.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_last_name() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_last_name();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_last_name();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_last_name();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer full name based on the data layer.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_full_name() {
		/* translators: 1: User First name, 2: User Last name */
		return trim( sprintf( _x( '%1$s %2$s', 'full name', 'automatewoo' ), $this->get_customer_first_name(), $this->get_customer_last_name() ) );
	}


	/**
	 * Gets the customer billing phone.
	 * Doesn't parse or format.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_phone() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_phone();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_phone();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_phone();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing company.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_company() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_company();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_company();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_company();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer shipping country code.
	 *
	 * @since 6.5.0
	 * @return string
	 */
	public function get_customer_shipping_country() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_shipping_country();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_shipping_country();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				if ( $customer->is_registered() ) {
					$user = $customer->get_user();
					$prop = $user ? get_user_meta( $user->ID, 'shipping_country', true ) : '';
				}
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer shipping state.
	 *
	 * @since 6.5.0
	 * @return string
	 */
	public function get_customer_shipping_state() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_shipping_state();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_shipping_state();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				if ( $customer->is_registered() ) {
					$user = $customer->get_user();
					$prop = $user ? get_user_meta( $user->ID, 'shipping_state', true ) : '';
				}
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing country code.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_country() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_country();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_country();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_country();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing state.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_state() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_state();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_state();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_state();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing city.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_city() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_city();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_city();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_city();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing postcode.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_postcode() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_postcode();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_postcode();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_postcode();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing address 1.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_address_1() {
		$prop = '';

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			if ( $subscription ) {
				$prop = $subscription->get_billing_address_1();
			}

			if ( ! $prop ) {
				$order = $this->get_order();
				if ( $order ) {
					$prop = $order->get_billing_address_1();
				}
			}
		}

		if ( ! $prop ) {
			$customer = $this->get_customer();
			if ( $customer ) {
				$prop = $customer->get_billing_address_1();
			}
		}

		return $prop;
	}


	/**
	 * Gets the customer billing address 2.
	 *
	 * @since 4.2
	 * @return string
	 */
	public function get_customer_address_2() {
		$address_2 = '';
		$address_1 = '';
		$customer  = $this->get_customer();

		// since address 2 is often blank, only fall back if both address fields are blank

		if ( $this->order_belongs_to_customer ) {
			$subscription = $this->get_subscription();
			$order        = $this->get_order();

			if ( $subscription ) {
				$address_2 = $subscription->get_billing_address_2();
				$address_1 = $subscription->get_billing_address_1();
			}

			if ( ! $address_2 && ! $address_1 && $order ) {
				$address_2 = $order->get_billing_address_2();
				$address_1 = $order->get_billing_address_1();
			}
		}

		if ( ! $address_2 && ! $address_1 && $customer ) {
			$address_2 = $customer->get_billing_address_2();
		}

		return $address_2;
	}


	/**
	 * @since 4.2
	 * @param bool $include_name
	 * @return array
	 */
	public function get_customer_address_array( $include_name = true ) {
		$args = [];

		if ( $include_name ) {
			$args['first_name'] = $this->get_customer_first_name();
			$args['last_name']  = $this->get_customer_last_name();
		}

		$args['company']   = $this->get_customer_company();
		$args['address_1'] = $this->get_customer_address_1();
		$args['address_2'] = $this->get_customer_address_2();
		$args['city']      = $this->get_customer_city();
		$args['state']     = $this->get_customer_state();
		$args['postcode']  = $this->get_customer_postcode();
		$args['country']   = $this->get_customer_country();

		return $args;
	}


	/**
	 * Is the data layer missing data?
	 *
	 * Data can be missing if it has been deleted e.g. if an order has been trashed.
	 *
	 * @since 4.6
	 *
	 * @return bool
	 */
	public function is_missing_data() {
		return ! empty( $this->get_missing_data_types() );
	}


	/**
	 * Get the data types that could not be restored from the data layer.
	 *
	 * @since 6.5.0
	 *
	 * @return string[]
	 */
	public function get_missing_data_types() {
		$missing_data_types = [];
		$raw_data           = $this->get_raw_data();

		// Remove customer from the data to check
		unset( $raw_data['customer'] );

		foreach ( $raw_data as $data_type => $data_item ) {
			if ( ! $data_item ) {
				$missing_data_types[] = $data_type;
			}
		}

		return $missing_data_types;
	}
}
