<?php
/**
 * WooCommerce Square
 *
 * This source file is subject to the GNU General Public License v3.0
 * that is bundled with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@woocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade WooCommerce Square to newer
 * versions in the future. If you wish to customize WooCommerce Square for your
 * needs please refer to https://docs.woocommerce.com/document/woocommerce-square/
 *
 * @author    WooCommerce
 * @copyright Copyright: (c) 2019, Automattic, Inc.
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
 */

namespace WooCommerce\Square\Sync\Records;

defined( 'ABSPATH' ) || exit;

use WooCommerce\Square\Sync\Records;

/**
 * The sync record object.
 *
 * @since 2.0.0
 */
class Record {


	/** @var string unique identifier */
	private $id = '';

	/** @var string date in UTC */
	private $date = '';

	/** @var string the record status */
	private $type = '';

	/** @var string message (optional, may contain HTML) */
	private $message = '';

	/** @var int associated product ID (optional) */
	private $product_id = 0;

	/** @var bool whether the associated product was hidden when the record was created */
	private $product_hidden = false;


	/**
	 * Sync record constructor.
	 *
	 * @since 2.0.0
	 *
	 * @param array|int $data raw data or related product ID
	 */
	public function __construct( $data ) {

		foreach ( $this->parse_data( $data ) as $key => $value ) {
			$this->$key = $value;
		}
	}


	/**
	 * Parses data from the store into the object properties
	 *
	 * @since 2.0.0
	 *
	 * @param int|array $data associative array or product ID
	 * @return array
	 */
	private function parse_data( $data ) {

		if ( is_numeric( $data ) ) {
			$data = array(
				'type'       => 'alert',
				'product_id' => absint( $data ),
			);
		}

		$date = date( 'Y-m-d H:i:s', current_time( 'timestamp', true ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.RequestedUTC
		$data = wp_parse_args(
			(array) $data,
			array(
				'type'           => $this->get_default_type(),
				'date'           => $date,
				'message'        => '',
				'product_id'     => 0,
				'product_hidden' => false,
			)
		);

		if ( empty( $data['id'] ) ) {
			$data['id'] = uniqid( 'wc_square_sync_record_', false );
		}

		if ( ! strtotime( $data['date'] ) ) {
			$data['date'] = $date;
		}

		if ( ! is_string( $data['message'] ) ) {
			$data['message'] = '';
		}

		if ( ! is_numeric( $data['product_id'] ) ) {
			$data['product_id'] = $data['product_id'] instanceof \WC_Product ? $data['product_id']->get_id() : 0;
		}

		if ( 0 === $data['product_id'] ) {
			$data['product_hidden'] = false;
		}

		return $data;
	}


	/**
	 * Gets the record's raw data.
	 *
	 * @since 2.0.0
	 *
	 * @return array associative array
	 */
	public function get_data() {

		return array(
			'id'             => (string) $this->id,
			'type'           => (string) $this->type,
			'date'           => (string) $this->date,
			'message'        => (string) $this->message,
			'product_id'     => (int) $this->product_id,
			'product_hidden' => (bool) $this->product_hidden,
		);
	}


	/**
	 * Gets the record's ID.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_id() {

		return (string) $this->id;
	}


	/**
	 * Sets an ID for the record.
	 *
	 * @since 2.0.0
	 *
	 * @param null|string $id
	 * @return string set ID
	 */
	public function set_id( $id = null ) {

		if ( is_string( $id ) ) {
			$id = trim( $id );
		} else {
			$id = null;
		}

		if ( empty( $id ) ) {
			$id = uniqid( 'wc_square_sync_record_', false );
		}

		return (string) $this->id = $id;
	}


	/**
	 * Gets a record's default status.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	private function get_default_type() {

		return 'info';
	}


	/**
	 * Gets a record's possible statuses.
	 *
	 * @since 2.0.0
	 *
	 * @return array associative array of types and labels
	 */
	private function get_valid_types() {

		return array(
			'info'     => __( 'Info', 'woocommerce-square' ),
			'notice'   => __( 'Notice', 'woocommerce-square' ),
			'alert'    => __( 'Alert', 'woocommerce-square' ),
			'resolved' => __( 'Resolved', 'woocommerce-square' ),
			'failed'   => __( 'Failed', 'woocommerce-square' ),
		);
	}


	/**
	 * Gets the record's label.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_label() {

		$type  = $this->get_type();
		$types = $this->get_valid_types();

		return isset( $types[ $type ] ) ? $types[ $type ] : $types[ $this->get_default_type() ];
	}


	/**
	 * Gets the record status.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_type() {

		return array_key_exists( $this->type, $this->get_valid_types() ) ? $this->type : $this->get_default_type();
	}


	/**
	 * Set's the record status.
	 *
	 * @since 2.0.0
	 *
	 * @param string $type a known type
	 * @return string set type
	 */
	public function set_type( $type ) {

		if ( array_key_exists( $type, $this->get_valid_types() ) ) {
			$this->type = $type;
		} else {
			$this->type = $this->get_default_type();
		}

		return $this->type;
	}


	/**
	 * Checks if the record is of a given type.
	 *
	 * @since 2.0.0
	 *
	 * @param array|string $type one or more types to check
	 * @return bool
	 */
	public function is_type( $type ) {

		return is_array( $type ) ? in_array( $this->type, $type, true ) : $this->type === $type;
	}


	/**
	 * Checks whether the record is resolved.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function is_resolved() {

		return $this->is_type( 'resolved' );
	}


	/**
	 * Resolves the record.
	 *
	 * @since 2.0.0
	 */
	public function resolve() {

		$this->set_type( 'resolved' );
	}


	/**
	 * Gets the record's timestamp, in UTC.
	 *
	 * @since 2.0.0
	 *
	if (
	 * @return int
	 */
	public function get_timestamp() {

		return (int) strtotime( $this->date );
	}


	/**
	 * Gets the record's date, in UTC.
	 *
	 * @since 2.0.0
	 *
	 * @param string $format defaults to MySQL format
	 * @return string
	 */
	public function get_date( $format = 'Y-m-d H:i:s' ) {

		return date( (string) $format, $this->get_timestamp() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
	}


	/**
	 * Gets the record's date, in the local timezone.
	 *
	 * @since 2.0.0
	 *
	 * @param null|string $format optional PHP date format (defaults to the site date/time format)
	 * @return string
	 */
	public function get_local_date( $format = null ) {

		if ( ! is_string( $format ) ) {
			$format = wc_date_format() . ' ' . wc_time_format();
		}

		try {

			$date      = new \DateTime( date( (string) $format, $this->get_timestamp() ), new \DateTimeZone( 'UTC' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
			$timezone  = new \DateTimeZone( wc_timezone_string() );
			$offset    = $timezone->getOffset( $date );
			$timestamp = $date->getTimestamp() + $offset;

		} catch ( \Exception $e ) {

			$timestamp = $this->get_timestamp();
		}

		return date( (string) $format, $timestamp ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
	}


	/**
	 * Gets the record's message.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_message() {

		$message = trim( $this->message );

		if ( '' === $message && ( $product = $this->get_product() ) ) {

			if ( 'variation' === $product->get_type() ) {
				$message = sprintf(
					/* translators: Placeholder: %s - product name */
					esc_html__( '%s variation not found in Square.', 'woocommerce-square' ),
					'<a href="' . esc_url( get_edit_post_link( $product->get_parent_id() ) ) . '">' . $product->get_formatted_name() . '</a>'
				);
			} else {
				$message = sprintf(
					/* translators: Placeholder: %s - product name */
					esc_html__( '%s not found in Square.', 'woocommerce-square' ),
					'<a href="' . esc_url( get_edit_post_link( $product->get_id() ) ) . '">' . $product->get_formatted_name() . '</a>'
				);
			}
		}

		return $message;
	}


	/**
	 * Sets the record's message.
	 *
	 * @since 2.0.0
	 *
	 * @param string $message may contain HTML
	 * @return bool success
	 */
	public function set_message( $message ) {

		if ( is_string( $message ) ) {
			$this->message = trim( $message );
		} else {
			$this->message = '';
		}

		return ! empty( $this->message );
	}


	/**
	 * Removes the record's message.
	 *
	 * @since 2.0.0
	 */
	public function remove_message() {

		$this->message = '';
	}


	/**
	 * Sets the record's product.
	 *
	 * @since 2.0.0
	 *
	 * @param int|\WC_Product $product
	 * @return bool success
	 */
	public function set_product( $product ) {

		if ( $product instanceof \WC_Product ) {
			$product_id = $product->get_id();
		} else {
			$product_id = $product;
		}

		if ( is_numeric( $product_id ) ) {
			$this->product_id = $product_id;
		} else {
			$this->product_id = 0;
		}

		return $this->product_id > 0;
	}


	/**
	 * Removes a related product from the record.
	 *
	 * @since 2.0.0
	 */
	public function remove_product() {

		$this->product_id = 0;
	}


	/**
	 * Checks whether there is a valid product associated with the record.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function has_product() {

		return $this->get_product() instanceof \WC_Product;
	}


	/**
	 * Gets the product associated with the record.
	 *
	 * @since 2.0.0
	 *
	 * @return null|\WC_Product
	 */
	public function get_product() {

		$product = wc_get_product( $this->get_product_id() );

		return $product instanceof \WC_Product ? $product : null;
	}


	/**
	 * Gets the product ID associated with the record.
	 *
	 * @since 2.0.0
	 *
	 * @return int
	 */
	public function get_product_id() {

		return absint( $this->product_id );
	}


	/**
	 * Sets a flag whether when adding the record the product was contextually hidden from catalog.
	 *
	 * Note: this is for historical record purposes and may not correspond to the effective product visibility, nor does not affect the product visibility when called.
	 *
	 * @since 2.0.0
	 *
	 * @param bool $was_hidden whether product was hidden when creating the record
	 * @return bool success
	 */
	public function set_product_hidden( $was_hidden = true ) {

		$set = false;

		if ( is_bool( $was_hidden ) ) {
			$this->product_hidden = $was_hidden;
			$set                  = true;
		}

		return $set;
	}


	/**
	 * Checks whether a flag was set for hiding the product from catalog when the record was created.
	 *
	 * This may not reflect the actual product visibility status, it is only for historical purposes.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function was_product_hidden() {

		return true === $this->product_hidden;
	}


	/**
	 * Gets the record's actions.
	 *
	 * @since 2.0.0
	 *
	 * @return \stdClass[] associative array of action names and action properties as objects
	 */
	public function get_actions() {

		$actions = array();

		if ( ! $this->is_resolved() ) {

			$actions['delete'] = (object) array(
				'name'  => 'delete',
				'label' => __( 'Delete', 'woocommerce-square' ),
				'icon'  => '<span class="dashicons dashicons-trash"></span>',
			);

			if ( ! $this->is_type( 'info' ) ) {

				$actions['resolve'] = (object) array(
					'name'  => 'resolve',
					'label' => __( 'Ignore', 'woocommerce-square' ),
					'icon'  => '<span class="dashicons dashicons-hidden"></span>',
				);
			}

			if ( $this->has_product() ) {

				$actions['unsync'] = (object) array(
					'name'  => 'unsync',
					'label' => __( 'Unlink', 'woocommerce-square' ),
					'icon'  => '<span class="dashicons dashicons-editor-unlink"></span>',
				);
			}
		}

		/**
		 * Filters the sync record action.
		 *
		 * @since 2.0.0
		 *
		 * @param \stdClass[] array of action names and objects
		 * @param Record instance of the current record object
		 */
		return (array) apply_filters( 'wc_square_sync_record_actions', $actions, $this );
	}


	/**
	 * Saves the record to storage.
	 *
	 * @since 2.0.0
	 *
	 * @return bool success
	 */
	public function save() {

		return Records::set_record( $this );
	}


	/**
	 * Deletes the record from storage.
	 *
	 * @since 2.0.0
	 *
	 * @return bool success
	 */
	public function destroy() {

		return Records::delete_record( $this->get_id() );
	}


}
