<?php

namespace Uncanny_Automator;

use Uncanny_Automator\Recipe\Log_Properties;

/**
 * Class ANON_WP_POST_PUBLISHED_IN_TAXONOMY
 *
 * @package Uncanny_Automator
 */
class ANON_WP_POST_PUBLISHED_IN_TAXONOMY {

	use Log_Properties;

	/**
	 * Integration code
	 *
	 * @var string
	 */
	public static $integration = 'WP';

	/**
	 * Property trigger_code
	 *
	 * @var string
	 */
	private $trigger_code;

	/**
	 * Property trigger_meta
	 *
	 * @var string
	 */
	private $trigger_meta;

	/**
	 * Property trigger_meta_log
	 *
	 * @var array
	 */
	private $trigger_meta_log;

	/**
	 * Property array
	 *
	 * @var array
	 */

	private $result;

	/**
	 * Property post
	 *
	 * @var \WP_Post
	 */
	private $post;

	/**
	 * Property terms_list
	 *
	 * @var array
	 */
	private $terms_list;

	/**
	 * Property taxonomy_list
	 *
	 * @var array
	 */
	private $taxonomy_list = array();

	/**
	 * Property match_recipes
	 *
	 * @var array
	 */
	private $matched_recipes = array();

	/**
	 *  Property child_term_log_properties
	 *
	 * @var array
	 */
	private $child_term_log_properties = array();

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->trigger_code = 'WP_POST_PUBLISHED_IN_TAXONOMY';
		$this->trigger_meta = 'WPPOSTTYPES';

		if ( Automator()->helpers->recipe->is_edit_page() ) {
			add_action(
				'wp_loaded',
				function () {
					$this->define_trigger();
				},
				99
			);

			return;
		}
		$this->define_trigger();
	}

	/**
	 * Define and register the trigger by pushing it into the Automator object
	 *
	 * @return void
	 * @throws \Exception
	 */
	public function define_trigger() {
		$trigger = array(
			'author'              => Automator()->get_author_name( $this->trigger_code ),
			'support_link'        => Automator()->get_author_support_link( $this->trigger_code, 'integration/wordpress-core/' ),
			'integration'         => self::$integration,
			'code'                => $this->trigger_code,
			'type'                => 'anonymous',
			'sentence'            => sprintf(
			/* translators: Logged-in trigger - WordPress */
				esc_attr_x( '{{A type of post:%1$s}} with {{a taxonomy term:%2$s}} in {{a taxonomy:%3$s}} is published', 'WordPress', 'uncanny-automator' ),
				$this->trigger_meta,
				'WPTAXONOMYTERM:' . $this->trigger_meta,
				'WPTAXONOMIES:' . $this->trigger_meta
			),
			/* translators: Logged-in trigger - WordPress */
			'select_option_name'  => esc_attr_x( 'A post in a taxonomy is published', 'WordPress', 'uncanny-automator' ),
			'action'              => 'wp_after_insert_post',
			'priority'            => 90,
			'accepted_args'       => 4,
			'validation_function' => array( $this, 'post_published' ),
			'options_callback'    => array( $this, 'load_options' ),
			'loopable_tokens'     => Wp_Helpers::common_trigger_loopable_tokens(),
		);

		Automator()->register->trigger( $trigger );
	}

	/**
	 * Method load_options.
	 *
	 * @return array
	 */
	public function load_options() {

		Automator()->helpers->recipe->wp->options->load_options = true;

		$all_post_types = Automator()->helpers->recipe->wp->options->all_post_types(
			null,
			'WPPOSTTYPES',
			array(
				'token'               => false,
				'is_ajax'             => true,
				'target_field'        => 'WPTAXONOMIES',
				'endpoint'            => 'select_post_type_taxonomies',
				'use_zero_as_default' => true,
			)
		);

		$options = Automator()->utilities->keep_order_of_options(
			array(
				'options_group' => array(
					$this->trigger_meta => array(
						$all_post_types,
						/* translators: Noun */
						Automator()->helpers->recipe->field->select_field_ajax(
							'WPTAXONOMIES',
							esc_attr_x( 'Taxonomy', 'WordPress', 'uncanny-automator' ),
							array(),
							'',
							'',
							false,
							true,
							array(
								'target_field' => 'WPTAXONOMYTERM',
								'endpoint'     => 'select_terms_for_selected_taxonomy',
							)
						),
						Automator()->helpers->recipe->field->select_field( 'WPTAXONOMYTERM', esc_attr_x( 'Term', 'WordPress', 'uncanny-automator' ) ),
						Automator()->helpers->recipe->wp->options->conditional_child_taxonomy_checkbox(),
					),
				),
			)
		);

		return $options;
	}

	/**
	 * Fires when a post is published with taxonomy terms.
	 *
	 * @param int      $post_id     Post ID.
	 * @param \WP_Post $post        Post object.
	 * @param bool     $update      Whether this is an existing post being updated.
	 * @param \WP_Post $post_before Previous post object before update.
	 *
	 * @return void
	 */
	public function post_published( $post_id, $post, $update, $post_before ) {

		// Only run when posts are published first time.
		if ( ! Automator()->utilities->is_wp_post_being_published( $post, $post_before ) ) {
			return;
		}

		// Deprecated since 7.0 - cron-based processing replaced by wp_after_insert_post.
		apply_filters_deprecated( 'automator_wp_user_creates_post_cron_enabled', array( true, $post_id, $post, $update, $post_before, $this ), '7.1' );

		// Deduplication check - prevent multiple firings from other plugins.
		$transient_key = 'automator_trigger_' . $this->trigger_code . '_' . $post_id;
		if ( false !== get_transient( $transient_key ) ) {
			return;
		}

		// Set transient to prevent duplicate processing for 10 seconds.
		set_transient( $transient_key, true, 10 );

		$this->post = $post;
		$user_id                   = absint( isset( $post->post_author ) ? $post->post_author : 0 );
		$recipes                   = Automator()->get->recipes_from_trigger_code( $this->trigger_code );
		$required_post_type        = Automator()->get->meta_from_recipes( $recipes, 'WPPOSTTYPES' );
		$required_post_taxonomy    = Automator()->get->meta_from_recipes( $recipes, 'WPTAXONOMIES' );
		$required_post_term        = Automator()->get->meta_from_recipes( $recipes, 'WPTAXONOMYTERM' );
		$include_taxonomy_children = Automator()->get->meta_from_recipes( $recipes, 'WPTAXONOMIES_CHILDREN' );
		$include_taxonomy_children = ! empty( $include_taxonomy_children ) ? $include_taxonomy_children : array();

		// no recipes found, bail
		if ( empty( $recipes ) ) {
			return;
		}

		// Trigger Post types no found
		if ( empty( $required_post_type ) ) {
			return;
		}

		// Trigger taxonomy not found
		if ( empty( $required_post_taxonomy ) ) {
			return;
		}

		// Term taxonomy not found
		if ( empty( $required_post_term ) ) {
			return;
		}

		$user_obj = get_user_by( 'ID', (int) $post->post_author );
		// Author doesn't exist anymore
		if ( ! $user_obj instanceof \WP_User ) {
			return;
		}

		// Match recipe types with current $post
		$post_type_recipes = $this->get_recipes_post_type_matches( $recipes, $required_post_type, $post );

		// No post type matched, bail
		if ( empty( $post_type_recipes ) ) {
			return;
		}

		// Match taxonomy types with current $post
		$taxonomy_recipes = $this->get_recipes_taxonomy_matches( $required_post_taxonomy, $post );

		// No taxonomies found, bail
		if ( empty( $taxonomy_recipes ) ) {
			return;
		}

		// Match terms with current $post
		$terms_recipe = $this->get_recipes_term_matches( $required_post_term, $required_post_taxonomy, $post, $include_taxonomy_children );

		// No terms found, bail
		if ( empty( $terms_recipe ) ) {
			return;
		}

		// Find common recipes between post type + taxonomies + terms
		$matched = array_intersect( $post_type_recipes, $taxonomy_recipes, $terms_recipe );
		// Empty, bail
		if ( empty( $matched ) ) {
			return;
		}
		// build matched recipes ids array
		$matched_recipe_ids = $this->get_matched_recipes( $matched );

		if ( empty( $matched_recipe_ids ) ) {
			return;
		}

		// Complete trigger
		$this->complete_trigger( $matched_recipe_ids, $user_id, $post );
	}

	/**
	 * @param $matched_recipe_ids
	 * @param $user_id
	 * @param $post
	 *
	 * @return void
	 */
	private function complete_trigger( $matched_recipe_ids, $user_id, $post ) {

		foreach ( $matched_recipe_ids as $recipe_trigger ) {

			foreach ( $recipe_trigger as $recipe_id => $trigger_id ) {

				$recipe_id  = absint( $recipe_id );
				$trigger_id = absint( $trigger_id );
				$pass_args  = array(
					'code'             => $this->trigger_code,
					'meta'             => $this->trigger_meta,
					'user_id'          => $user_id,
					'recipe_to_match'  => $recipe_id,
					'trigger_to_match' => $trigger_id,
					'ignore_post_id'   => true,
					'is_signed_in'     => true,
				);

				$args = Automator()->maybe_add_trigger_entry( $pass_args, false );
				if ( empty( $args ) ) {
					continue;
				}
				foreach ( $args as $result ) {
					if ( false === $result['result'] ) {
						continue;
					}

					if ( isset( $this->child_term_log_properties[ $recipe_id ] ) ) {
						if ( isset( $this->child_term_log_properties[ $recipe_id ][ $trigger_id ] ) ) {
							$this->set_trigger_log_properties( $this->child_term_log_properties[ $recipe_id ][ $trigger_id ] );
						}
					}

					$this->store_tokens( $result, $user_id, $post, $recipe_id, $trigger_id );

					do_action( 'automator_loopable_token_hydrate', $result['args'], func_get_args() );

					Automator()->maybe_trigger_complete( $result['args'] );
				}
			}
		}
	}

	/**
	 * @param $result
	 * @param $user_id
	 * @param $post
	 * @param $recipe_id
	 * @param $trigger_id
	 *
	 * @return void
	 */
	private function store_tokens( $result, $user_id, $post, $recipe_id, $trigger_id ) {
		$trigger_meta = array(
			'user_id'        => $user_id,
			'trigger_id'     => $result['args']['trigger_id'],
			'trigger_log_id' => $result['args']['trigger_log_id'],
			'run_number'     => $result['args']['run_number'],
		);

		// post_id Token
		Automator()->db->token->save( 'post_id', $post->ID, $trigger_meta );

		if ( isset( $this->terms_list[ $recipe_id ][ $trigger_id ] ) ) {
			$terms = implode( ', ', $this->terms_list[ $recipe_id ][ $trigger_id ] );
			Automator()->db->token->save( 'WPTAXONOMYTERM', $terms, $trigger_meta );
		}

		if ( isset( $this->taxonomy_list[ $recipe_id ][ $trigger_id ] ) ) {
			$taxonomies = implode( ', ', $this->taxonomy_list[ $recipe_id ][ $trigger_id ] );
			Automator()->db->token->save( 'WPTAXONOMIES', $taxonomies, $trigger_meta );
		}

		$this->trigger_meta_log = $trigger_meta;
		$this->result           = $result;
	}

	/**
	 * Identify recipes that match criteria based on post type
	 *
	 * @param $recipes
	 * @param $required_post_type
	 * @param $post
	 *
	 * @return array
	 */
	private function get_recipes_post_type_matches( $recipes, $required_post_type, $post ) {
		$matched = array();
		foreach ( $recipes as $recipe_id => $recipe ) {
			foreach ( $recipe['triggers'] as $trigger ) {
				$trigger_id = absint( $trigger['ID'] );
				// required post type
				if ( '0' === (string) $required_post_type[ $recipe_id ][ $trigger_id ] || (string) $post->post_type === (string) $required_post_type[ $recipe_id ][ $trigger_id ] ) {
					$matched[]                           = $recipe_id;
					$this->matched_recipes[ $recipe_id ] = $recipe;
				}
			}
		}

		return array_unique( $matched );
	}

	/**
	 * Identify recipes that match criteria based on taxonomy
	 *
	 * @param $required_post_taxonomy
	 * @param $post
	 *
	 * @return array
	 */
	private function get_recipes_taxonomy_matches( $required_post_taxonomy, $post ) {
		$matched = array();
		$post_id = $post->ID;

		foreach ( $this->matched_recipes as $recipe_id => $recipe ) {
			foreach ( $recipe['triggers'] as $trigger ) {
				$trigger_id = absint( $trigger['ID'] );
				if ( ! isset( $required_post_taxonomy[ $recipe_id ] ) ) {
					continue;
				}
				if ( ! isset( $required_post_taxonomy[ $recipe_id ][ $trigger_id ] ) ) {
					continue;
				}
				// if any taxonomy
				if ( '0' === (string) $required_post_taxonomy[ $recipe_id ][ $trigger_id ] ) {
					$post_terms = $this->get_all_post_tax( $post_id, $post->post_type, $recipe_id, $trigger_id );
					foreach ( $post_terms as $term ) {
						$this->terms_list[ $recipe_id ][ $trigger_id ][ $term->term_id ] = $term->name;
					}

					$matched[] = $recipe_id;
					continue;
				}
				// let's check if the post has any terms in the selected taxonomy
				$post_terms = $this->get_taxonomy( $post_id, $required_post_taxonomy[ $recipe_id ][ $trigger_id ] );

				if ( empty( $post_terms ) ) {
					continue;
				}
				$matched[] = $recipe_id;
				foreach ( $post_terms as $term ) {
					$this->terms_list[ $recipe_id ][ $trigger_id ][ $term->term_id ] = $term->name;

					$_taxonomy = get_taxonomy( $term->taxonomy );
					if ( ! empty( $_taxonomy ) ) {
						$this->taxonomy_list[ $recipe_id ][ $trigger_id ][ $_taxonomy->name ] = $_taxonomy->labels->singular_name;
					}
				}
			}
		}

		return array_unique( $matched );
	}

	/**
	 * Identify recipes that match criteria based on term
	 *
	 * @param $required_post_term
	 * @param $required_post_taxonomy
	 * @param $post
	 * @param $include_term_children
	 *
	 * @return array
	 */
	private function get_recipes_term_matches( $required_post_term, $required_post_taxonomy, $post, $include_term_children ) {
		$matched = array();
		$post_id = $post->ID;

		foreach ( $this->matched_recipes as $recipe_id => $recipe ) {
			foreach ( $recipe['triggers'] as $trigger ) {
				$trigger_id = absint( $trigger['ID'] );
				if ( ! isset( $required_post_term[ $recipe_id ] ) ) {
					continue;
				}
				if ( ! isset( $required_post_term[ $recipe_id ][ $trigger_id ] ) ) {
					continue;
				}
				// if any term
				if ( '0' === (string) $required_post_term[ $recipe_id ][ $trigger_id ] ) {
					$matched[] = $recipe_id;
					continue;
				}
				if ( ! isset( $required_post_taxonomy[ $recipe_id ] ) ) {
					continue;
				}
				if ( ! isset( $required_post_taxonomy[ $recipe_id ][ $trigger_id ] ) ) {
					continue;
				}

				// Check if we should be including Children of the selected term.
				$include_children = isset( $include_term_children[ $recipe_id ] ) ? $include_term_children[ $recipe_id ] : array();
				$include_children = isset( $include_children[ $trigger_id ] ) ? $include_children[ $trigger_id ] : false;
				$include_children = filter_var( strtolower( $include_children ), FILTER_VALIDATE_BOOLEAN );

				// if the term is specific then tax and post type are also specified
				$post_terms = $this->get_taxonomy( $post_id, $required_post_taxonomy[ $recipe_id ][ $trigger_id ] );

				if ( empty( $post_terms ) ) {
					continue;
				}

				// check if the post has the required term
				$post_term_ids = array_map( 'absint', array_column( $post_terms, 'term_id' ) );
				if ( ! in_array( absint( $required_post_term[ $recipe_id ][ $trigger_id ] ), $post_term_ids, true ) ) {
					$child_term = false;
					if ( $include_children ) {
						$child_term = Automator()->helpers->recipe->wp->options->get_term_child_of( $post_terms, $required_post_term[ $recipe_id ][ $trigger_id ], $required_post_taxonomy[ $recipe_id ][ $trigger_id ], $post_id );
					}

					// Term or Child Term not found.
					if ( empty( $child_term ) ) {
						continue;
					} else {
						$this->set_child_matched_log_properties( $recipe_id, $trigger_id, $child_term );
					}
				}
				$matched[] = $recipe_id;

				// Specific Term
				$term = get_term( $required_post_term[ $recipe_id ][ $trigger_id ] );
				if ( ! array_key_exists( $term->term_id, $this->terms_list[ $recipe_id ][ $trigger_id ] ) ) {
					$this->terms_list[ $recipe_id ][ $trigger_id ][ $term->term_id ] = $term->name;
				}
			}
		}

		return array_unique( $matched );
	}

	/**
	 * @param $post_id
	 * @param $tax
	 *
	 * @return array|\WP_Error
	 */
	private function get_taxonomy( $post_id, $tax = '' ) {
		return wp_get_post_terms( $post_id, $tax );
	}

	/**
	 * @param $matched
	 *
	 * @return array
	 */
	private function get_matched_recipes( $matched ) {
		if ( empty( $matched ) || empty( $this->matched_recipes ) ) {
			return array();
		}
		$matched_recipe_ids = array();
		foreach ( $this->matched_recipes as $recipe_id => $recipe ) {
			$recipe_id = absint( $recipe_id );
			if ( ! in_array( $recipe_id, $matched, true ) ) {
				continue;
			}
			foreach ( $recipe['triggers'] as $trigger ) {
				$trigger_id                         = absint( $trigger['ID'] );
				$matched_recipe_ids[][ $recipe_id ] = $trigger_id;
			}
		}

		return $matched_recipe_ids;
	}

	/**
	 * Used when Any taxonomy, Any terms are used
	 *
	 * @param $post_id
	 * @param $post_type
	 * @param $recipe_id
	 * @param $trigger_id
	 *
	 * @return array
	 */
	private function get_all_post_tax( $post_id, $post_type, $recipe_id, $trigger_id ) {
		$all_terms  = array();
		$post_type  = get_post_type_object( $post_type );
		$taxonomies = get_object_taxonomies( $post_type->name, 'object' );
		if ( empty( $taxonomies ) ) {
			return $all_terms;
		}
		foreach ( $taxonomies as $taxonomy ) {
			$post_terms = wp_get_post_terms( $post_id, $taxonomy->name );
			if ( empty( $post_terms ) ) {
				continue;
			}
			$this->taxonomy_list[ $recipe_id ][ $trigger_id ][ $taxonomy->name ] = $taxonomy->labels->singular_name;
			foreach ( $post_terms as $term ) {
				$all_terms[] = $term;
			}
		}

		return $all_terms;
	}

	/**
	 * Set matched child term info to log properties.
	 *
	 * @param $recipe_id
	 * @param $trigger_id
	 * @param $child_term
	 *
	 * @return void
	 */
	private function set_child_matched_log_properties( $recipe_id, $trigger_id, $child_term ) {
		if ( ! isset( $this->child_term_log_properties[ $recipe_id ] ) ) {
			$this->child_term_log_properties[ $recipe_id ] = array();
		}
		$this->child_term_log_properties[ $recipe_id ][ $trigger_id ] = array(
			'type'       => 'string',
			'label'      => _x( 'Matched Child Term', 'WordPress', 'uncanny-automator' ),
			'value'      => $child_term->term_id . '( ' . $child_term->name . ' )',
			'attributes' => array(),
		);
	}

}
