<?php
/**
 * Pinterest tracking main class.
 *
 * @package Pinterest_For_WooCommerce/Classes/
 * @version 1.0.0
 */

namespace Automattic\WooCommerce\Pinterest;

use Automattic\WooCommerce\Pinterest\Tracking\Data;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Category;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Checkout;
use Automattic\WooCommerce\Pinterest\Tracking\Data\None;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Product;
use Automattic\WooCommerce\Pinterest\Tracking\Data\Search;
use Automattic\WooCommerce\Pinterest\Tracking\Tag;
use Automattic\WooCommerce\Pinterest\Tracking\Tracker;
use Throwable;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Tracker responsible for hooking into system events.
 */
class Tracking {

	const EVENT_CHECKOUT      = 'Checkout';

	const EVENT_ADD_TO_CART   = 'AddToCart';

	const EVENT_PAGE_VISIT    = 'PageVisit';

	const EVENT_SEARCH        = 'Search';

	const EVENT_VIEW_CATEGORY = 'ViewCategory';

	/**
	 * @var Tracker[] $trackers A list of available trackers.
	 */
	private $trackers = array();

	/**
	 * Attaches all the required tracking events to corresponding WP/WC hooks.
	 *
	 * @since 1.4.0
	 *
	 * @param array $trackers A list of trackers to track events with.
	 */
	public function __construct( array $trackers = array() ) {
		$this->trackers = $trackers;

		// Tracks page visit events.
		add_action( 'wp_footer', array( $this, 'handle_page_visit' ) );

		// Tracks category visit events.
		add_action( 'wp_footer', array( $this, 'handle_view_category' ) );

		// Tracks search events.
		add_action( 'wp_footer', array( $this, 'handle_search' ) );

		// Tracks add to cart events.
		add_action( 'woocommerce_add_to_cart', array( $this, 'handle_add_to_cart' ), 10, 6 );

		// Tracks checkout events.
		add_action( 'woocommerce_before_thankyou', array( $this, 'handle_checkout' ), 10, 2 );

		array_walk(
			$this->trackers,
			fn ( $tracker ) => call_user_func( array( $tracker, 'init_hooks' ) )
		);
	}

	/**
	 * Used as a callback for the wp_footer hook.
	 *
	 * @since 1.4.0
	 * @since 1.4.8 Added check for product page.
	 *
	 * @return void
	 */
	public function handle_page_visit() {
		if ( is_404() ) {
			// Do not track 404 pages.
			return;
		}

		// Dumy data for when we can't get product data.
		$data = new None( uniqid( 'page' ) );

		// Not a product page.
		if ( ! is_product() ) {
			$this->track_event( static::EVENT_PAGE_VISIT, $data );
			return;
		}

		$product = wc_get_product();
		if ( ! $product instanceof \WC_Product ) {
			$this->track_event( static::EVENT_PAGE_VISIT, $data );
			return;
		}

		$data = new Product(
			uniqid( 'page' ),
			$product->get_id(),
			$product->get_name(),
			wc_get_product_category_list( $product->get_id() ),
			'brand',
			wc_get_price_to_display( $product ),
			get_woocommerce_currency(),
			1
		);
		$this->track_event( static::EVENT_PAGE_VISIT, $data );
	}

	/**
	 * Used as a callback for the wp_footer hook.
	 *
	 * @since 1.4.0
	 *
	 * @return void
	 */
	public function handle_view_category() {
		if ( ! is_product_category() ) {
			return;
		}
		$queried_object = get_queried_object();
		$data           = new Category(
			uniqid( 'category' ),
			$queried_object->term_id,
			$queried_object->name
		);
		$this->track_event( static::EVENT_VIEW_CATEGORY, $data );
	}

	/**
	 * Used as a callback for the woocommerce_add_to_cart hook.
	 *
	 * @since 1.4.0
	 *
	 * @param string $cart_item_key - WooCommerce cart item key.
	 * @param string $product_id           - WooCommerce product id.
	 * @param string $quantity             - Number of products.
	 * @param string $variation_id         - Product variation id if any.
	 *
	 * @return void
	 */
	public function handle_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id ) {
		$object_id = empty( $variation_id ) ? $product_id : $variation_id;
		$product   = wc_get_product( $object_id );
		$data      = new Product(
			uniqid( 'cart' ),
			$product->get_id(),
			$product->get_name(),
			wc_get_product_category_list( $product->get_id() ),
			'brand',
			wc_get_price_to_display( $product ),
			get_woocommerce_currency(),
			$quantity
		);
		$this->track_event( static::EVENT_ADD_TO_CART, $data );
	}

	/**
	 * Used as a callback for the woocommerce_checkout_order_created hook.
	 *
	 * @since 1.4.0
	 *
	 * @param string $order_id WooCommerce order id.
	 *
	 * @return void
	 */
	public function handle_checkout( $order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}

		$items          = array();
		$total_quantity = 0;
		foreach ( $order->get_items() as $order_item ) {
			if ( ! method_exists( $order_item, 'get_product' ) ) {
				continue;
			}

			$product = $order_item->get_product();

			$items[] = new Product(
				uniqid( 'product' ),
				$product->get_id(),
				$order_item->get_name(),
				wc_get_product_category_list( $product->get_id() ),
				'brand',
				wc_get_price_to_display( $product ),
				get_woocommerce_currency(),
				$order_item->get_quantity()
			);

			$total_quantity += $order_item->get_quantity();
		}

		$data = new Checkout(
			uniqid( 'checkout' ),
			(string) $order->get_id(),
			$order->get_total(),
			$total_quantity,
			$order->get_currency(),
			$items
		);
		$this->track_event( static::EVENT_CHECKOUT, $data );
	}

	/**
	 * Search event handler.
	 *
	 * @since 1.4.0
	 *
	 * @return void
	 */
	public function handle_search() {
		if ( ! is_search() ) {
			return;
		}

		$data = new Search(
			uniqid( 'pinterest-for-woocommerce-tag-and-conversions-event-id' ),
			get_search_query()
		);
		$this->track_event( static::EVENT_SEARCH, $data );
	}

	/**
	 * Method which iterates over all the attached trackers and delegates the event to them.
	 *
	 * @since 1.4.0
	 *
	 * @param string $event_name Tracking event name.
	 * @param Data   $data       Event Data object.
	 *
	 * @return void
	 */
	public function track_event( string $event_name, Data $data ) {
		foreach ( $this->get_trackers() as $tracker ) {
			// Skip Pinterest tag tracking if tag is not active.
			if ( $tracker instanceof Tag && ! Tag::get_active_tag() ) {
				continue;
			}

			try {
				$tracker->track_event( $event_name, $data );
			} catch ( Throwable $e ) {
				/* translators: %1$s - event name, %2$s - tracker class name, %3$s - error message */
				$message = sprintf(
					'Error while tracking event %1$s with tracker %2$s. Error: %3$s',
					$event_name,
					get_class( $tracker ),
					$e->getMessage()
				);
				Logger::log( $message, 'error' );
			}
		}
	}

	/**
	 * Returns an array of registered trackers.
	 *
	 * @since 1.4.0
	 *
	 * @return Tracker[]
	 */
	public function get_trackers() {
		return $this->trackers;
	}

	/**
	 * Adds a tracker to the array of trackers.
	 *
	 * @since 1.4.0
	 *
	 * @param Tracker $tracker - One of objects implementing Tracker interface.
	 *
	 * @return void
	 */
	public function add_tracker( Tracker $tracker ) {
		$tracker->init_hooks();
		$this->trackers[ get_class( $tracker ) ] = $tracker;
	}

	/**
	 * Removes a tracker.
	 *
	 * @since 1.4.0
	 *
	 * @param string $tracker Tracker class name to be removed. e.g. Tag::class, Conversions::class.
	 *
	 * @return void
	 */
	public function remove_tracker( string $tracker ) {
		$this->trackers = array_filter(
			$this->trackers,
			function ( $item ) use ( $tracker ) {
				if ( get_class( $item ) === $tracker ) {
					$item->disable_hooks();
					return false;
				}
				return true;
			}
		);
	}
}
