<?php

class Brizy_Admin_Rules_Api extends Brizy_Admin_AbstractApi {

	const nonce = Brizy_Editor_API::nonce;
	const CREATE_RULES_ACTION = '_add_rules';
	const CREATE_RULE_ACTION = '_add_rule';
	const UPDATE_RULES_ACTION = '_update_rules';
	const DELETE_RULE_ACTION = '_delete_rule';
	const LIST_RULE_ACTION = '_list_rules';
	const VALIDATE_RULE = '_validate_rule';
	const RULE_GROUP_LIST = '_rule_group_list';
	const RULE_POSTS_GROUP_LIST = '_rule_posts_group_list';
	const RULE_ARCHIVE_GROUP_LIST = '_rule_archive_group_list';
	const RULE_TEMPLATE_GROUP_LIST = '_rule_template_group_list';


	/**
	 * @var Brizy_Admin_Rules_Manager
	 */
	private $manager;


	/**
	 * Brizy_Admin_Rules_Api constructor.
	 *
	 * @param Brizy_Admin_Rules_Manager $manager
	 */
	public function __construct( $manager ) {
		$this->manager = $manager;
		parent::__construct();
	}

	/**
	 * @return Brizy_Admin_Rules_Api
	 */
	public static function _init() {
		static $instance;
		if ( ! $instance ) {
			$instance = new self( new Brizy_Admin_Rules_Manager() );
		}

		return $instance;
	}

	protected function getRequestNonce() {
		return $this->param( 'hash' );
	}

	protected function initializeApiActions() {
		$pref = 'wp_ajax_' . Brizy_Editor::prefix();
		add_action( $pref . self::CREATE_RULE_ACTION, array( $this, 'actionCreateRule' ) );
		add_action( $pref . self::CREATE_RULES_ACTION, array( $this, 'actionCreateRules' ) );
		add_action( $pref . self::UPDATE_RULES_ACTION, array( $this, 'actionUpdateRules' ) );
		add_action( $pref . self::DELETE_RULE_ACTION, array( $this, 'actionDeleteRule' ) );
		add_action( $pref . self::LIST_RULE_ACTION, array( $this, 'actionGetRuleList' ) );
		add_action( $pref . self::VALIDATE_RULE, array( $this, 'actionValidateRule' ) );
		add_action( $pref . self::RULE_GROUP_LIST, array( $this, 'getGroupList' ) );
		add_action( $pref . self::RULE_POSTS_GROUP_LIST, array( $this, 'getPostsGroupsList' ) );
		add_action( $pref . self::RULE_ARCHIVE_GROUP_LIST, array( $this, 'getArchiveGroupsList' ) );
		add_action( $pref . self::RULE_TEMPLATE_GROUP_LIST, array( $this, 'getTemplateGroupsList' ) );
	}

	/**
	 * @return null|void
	 */
	public function actionGetRuleList() {

		$this->verifyAuthorization( self::nonce );
		$postId = (int) $this->param( 'post' );
		if ( ! $postId ) {
			wp_send_json_error( (object) array( 'message' => 'Invalid template' ), 400 );
		}
		try {
			$rules = $this->manager->getRules( $postId );
			$this->success( $rules );
		} catch ( Exception $e ) {
			Brizy_Logger::instance()->error( $e->getMessage(), [ $e ] );
			$this->error( 400, $e->getMessage() );
		}

		return null;
	}

	public function actionValidateRule() {
		$this->verifyAuthorization( self::nonce );
		$postId = (int) $this->param( 'post' );
		$postType = get_post_type( $postId );
		if ( ! $postId ) {
			$this->error( 400, "Validation" . 'Invalid post' );
		}
		$ruleData = file_get_contents( "php://input" );
		try {
			$rule          = $this->manager->createRuleFromJson( $ruleData, $postType );
			$ruleValidator = Brizy_Admin_Rules_ValidatorFactory::getValidator( $postId );
			if ( ! $ruleValidator ) {
				$this->error( 400, 'Unable to get the rule validator for this post type' );
			}
			$ruleValidator->validateRuleForPostId( $rule, $postId );
			wp_send_json_success( $rule, 200 );
		} catch ( Brizy_Admin_Rules_ValidationException $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage(), 'rule' => $e->getRuleId() ), 400 );
		} catch ( Exception $e ) {
			$this->error( 400, "Validation" . $e->getMessage() );
		}
	}

	public function actionCreateRule() {
		$this->verifyAuthorization( self::nonce );
		$postId            = (int) $this->param( 'post' );
		$ignoreDataVersion = (int) $this->param( 'ignoreDataVersion' );
		$dataVersion       = (int) $this->param( 'dataVersion' );
		$postType = get_post_type( $postId );
		if ( ! $postId ) {
			$this->error( 400, "Validation" . 'Invalid post' );
		}
		if ( ! $dataVersion && $ignoreDataVersion === 0 ) {
			$this->error( 400, "Validation" . 'Invalid data version' );
		}
		$ruleData = file_get_contents( "php://input" );
		try {
			$rule = $this->manager->createRuleFromJson( $ruleData, $postType );
		} catch ( Exception $e ) {
			$this->error( 400, "Validation" . $e->getMessage() );
		}
		try {
			// validate rule
			$ruleValidator = Brizy_Admin_Rules_ValidatorFactory::getValidator( $postId );
			if ( ! $ruleValidator ) {
				$this->error( 400, 'Unable to get the rule validator for this post type' );
			}
			$ruleValidator->validateRuleForPostId( $rule, $postId );
			if ( ! $ignoreDataVersion ) {
				$post = Brizy_Editor_Entity::get( $postId );
				$post->setDataVersion( $dataVersion );
				$post->save( 0 );
			}
			$this->manager->addRule( $postId, $rule );

		} catch ( Brizy_Editor_Exceptions_DataVersionMismatch $e ) {
			$this->error( 400, 'Invalid data version' );
		} catch ( Brizy_Admin_Rules_ValidationException $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage(), 'rule' => $e->getRuleId() ), 400 );
		} catch ( Exception $e ) {
			$this->error( 400, $e->getMessage() );
		}
		wp_send_json_success( $rule, 200 );
	}

	public function actionCreateRules() {
		$this->verifyAuthorization( self::nonce );
		$postId            = (int) $this->param( 'post' );
		$ignoreDataVersion = (int) $this->param( 'ignoreDataVersion' );
		$dataVersion       = (int) $this->param( 'dataVersion' );
		$postType          = get_post_type( $postId );
		if ( ! $postId ) {
			$this->error( 400, 'Invalid post' );
		}
		if ( ! $dataVersion && $ignoreDataVersion === 0 ) {
			$this->error( 400, "Validation" . 'Invalid data version' );
		}
		$rulesData = file_get_contents( "php://input" );
		try {
			$rules = $this->manager->createRulesFromJson( $rulesData, $postType );
			if ( count( $rules ) == 0 ) {
				$this->error( 400, "No rules found." );
			}

		} catch ( Exception $e ) {
			$this->error( 400, $e->getMessage() );
		}
		// validate rule
		$validator = Brizy_Admin_Rules_ValidatorFactory::getValidator( $postId );
		if ( ! $validator ) {
			$this->error( 400, 'Unable to get the rule validator for this post type' );
		}
		try {
			$validator->validateRulesForPostId( $rules, $postId );
			if ( ! $ignoreDataVersion ) {
				$post = Brizy_Editor_Entity::get( $postId );
				$post->setDataVersion( $dataVersion );
				$post->save( 0 );
			}
			foreach ( $rules as $newRule ) {
				$this->manager->addRule( $postId, $newRule );
			}

		} catch ( Brizy_Editor_Exceptions_DataVersionMismatch $e ) {
			$this->error( 400, 'Invalid data version' );
		} catch ( Brizy_Admin_Rules_ValidationException $e ) {
			wp_send_json_error( array(
				'rule'    => $e->getRuleId(),
				'message' => $e->getMessage(),
			), 400 );
		}
		$this->success( $rules );

		return null;
	}


	public function actionUpdateRules() {

		$this->verifyAuthorization( self::nonce );
		$postId            = (int) $this->param( 'post' );
		$ignoreDataVersion = (int) $this->param( 'ignoreDataVersion' );
		$dataVersion       = (int) $this->param( 'dataVersion' );
		$postType          = get_post_type( $postId );
		if ( ! $postId || ! in_array( $postType, [
				Brizy_Admin_Templates::CP_TEMPLATE,
				Brizy_Admin_Popups_Main::CP_POPUP,
			] ) ) {
			wp_send_json_error( (object) array( 'message' => 'Invalid template' ), 400 );
		}
		if ( ! $dataVersion && $ignoreDataVersion === 0 ) {
			$this->error( 400, "Validation" . 'Invalid data version' );
		}
		$rulesData = file_get_contents( "php://input" );
		try {
			$rules = $this->manager->createRulesFromJson( $rulesData, $postType );
		} catch ( Exception $e ) {
			Brizy_Logger::instance()->error( $e->getMessage(), [ $e ] );
			$this->error( 400, $e->getMessage() );
		}
//		$validator = Brizy_Admin_Rules_ValidatorFactory::getValidator( $postId );
//		if ( ! $validator ) {
//			$this->error( 400, 'Unable to get the rule validator for this post type' );
//		}
		try {
			if ( ! $ignoreDataVersion ) {
				$post = Brizy_Editor_Entity::get( $postId );
				$post->setDataVersion( $dataVersion );
				$post->save( 0 );
			}
			$this->manager->saveRules( $postId, $rules );
		} catch ( Brizy_Editor_Exceptions_DataVersionMismatch $e ) {
			$this->error( 400, 'Invalid data version' );
		} catch ( Exception $e ) {
			$this->error( 400, 'Unable to save rules' );
		}
		wp_send_json_success( $rules, 200 );

		return null;
	}

	public function actionDeleteRule() {

		$this->verifyAuthorization( self::nonce );
		$postId            = (int) $this->param( 'post' );
		$ignoreDataVersion = (int) $this->param( 'ignoreDataVersion' );
		$dataVersion       = (int) $this->param( 'dataVersion' );
		$ruleId            = $this->param( 'rule' );
		if ( ! $postId || ! $ruleId ) {
			$this->error( 400, 'Invalid request' );
		}
		if ( ! $dataVersion && $ignoreDataVersion === 0 ) {
			$this->error( 400, "Validation" . 'Invalid data version' );
		}
		try {

			if ( ! $ignoreDataVersion ) {
				$post = Brizy_Editor_Entity::get( $postId );
				$post->setDataVersion( $dataVersion );
				$post->save( 0 );
			}
			$this->manager->deleteRule( $postId, $ruleId );
		} catch ( Brizy_Editor_Exceptions_DataVersionMismatch $e ) {
			$this->error( 400, 'Invalid data version' );
		} catch ( Exception $e ) {
			$this->error( 400, 'Unable to delete rules' );
		}
		$this->success( null );
	}

	public function getGroupList() {

		$context      = $this->param( 'context' );
		$templateType = $this->param( 'templateType' );
		$closure = function ( $v ) {
			return array(
				'title'      => $v->label,
				'value'      => $v->name,
				'groupValue' => $v->groupValue,
			);
		};
		$groups = [];
		if ( $templateType == 'single' || $templateType == 'single_product' || $context == 'popup-rules' || $context == 'global-block-rules' ) {
			$groups[] = array(
				'title' => 'Main Content',
				'value' => Brizy_Admin_Rule::POSTS,
				'items' => array_map( $closure, $this->getCustomPostsList( Brizy_Admin_Rule::POSTS, $templateType, $context ) ),
			);
		}
		if ( $templateType == 'product_archive' ) {
			$wooPageItems = $this->getWooPageList( Brizy_Admin_Rule::ARCHIVE, $templateType );
			$groups[]     = $templateType == 'product_archive' && count( $wooPageItems ) && Brizy_Editor_Editor_Editor::get_woocomerce_plugin_info() ? array(
				'title' => 'Pages',
				'value' => Brizy_Admin_Rule::WOO_SHOP_PAGE,
				'items' => $wooPageItems,
			) : null;
		}
		if ( $templateType == 'archive' || $templateType == 'product_archive' || $context == 'popup-rules' || $context == 'global-block-rules' ) {

			$archiveItems  = array_map( $closure, $this->getArchivesList( Brizy_Admin_Rule::ARCHIVE, $templateType ) );
			$taxonomyItems = array_map( $closure, $this->getTaxonomyList( Brizy_Admin_Rule::TAXONOMY, $templateType ) );
			if ( $templateType === 'product_archive' ) {
				$archiveItems[] = array(
					'title'      => 'Search page',
					'value'      => 'search',
					'groupValue' => Brizy_Admin_Rule::TEMPLATE,
				);
			}
			$groups[] = count( $taxonomyItems ) ? array(
				'title' => 'Categories',
				'value' => Brizy_Admin_Rule::TAXONOMY,
				'items' => $taxonomyItems,
			) : null;
			$groups[] = count( $archiveItems ) ? array(
				'title' => 'Archives',
				'value' => Brizy_Admin_Rule::ARCHIVE,
				'items' => $archiveItems,
			) : null;
		}
		if ( $items = $this->geTemplateList( $context, $templateType ) ) {
			$groups[] = array(
				'title' => 'Others',
				'value' => Brizy_Admin_Rule::TEMPLATE,
				'items' => $items,
			);
		}
		$groups = array_values( array_filter( $groups, function ( $o ) {
			return ! is_null( $o );
		} ) );
		wp_send_json_success( $groups, 200 );
	}

	public function getPostsGroupsList() {

		global $wp_post_types;
		if ( ! ( $post_type = $this->param( 'postType' ) ) ) {
			wp_send_json_error( 'Invalid post type', 400 );
		}
		if ( ! isset( $wp_post_types[ $post_type ] ) ) {
			wp_send_json_error( 'Post type not found', 400 );
		}
		$postTypeName = $wp_post_types[ $post_type ]->labels->name;
		$taxonomies   = get_object_taxonomies( $post_type, 'objects' );
		if ( isset( $taxonomies['product_visibility'] ) ) {
			unset( $taxonomies['product_visibility'] );
		}
		$groups = array();
		$closureFromTerm  = function ( $v ) {
			return array(
				'title'      => $v->name,
				'value'      => "in|" . $v->taxonomy . "|" . $v->term_id,
				'groupValue' => $v->taxonomy,
			);
		};
		$closureChildTerm = function ( $v ) {
			return array(
				'title'      => $v->name,
				'value'      => "child|" . $v->taxonomy . "|" . $v->term_id,
				'groupValue' => $v->taxonomy,
			);
		};
		$closureAuthor = function ( $v ) use ( $postTypeName ) {
			return array(
				'title'      => ucfirst( $v->data->user_nicename ),
				'value'      => 'author|' . $v->ID,
				'groupValue' => 'author',
			);
		};
		$closurePost = function ( $v ) {
			return array(
				'title'      => $v->post_title,
				'value'      => $v->ID,
				'groupValue' => $v->post_type,
				'status'     => $v->post_status,
			);
		};
		// exclude woocomerce hidden tags
		$exclude = [ 'simple', 'variable', 'grouped', 'external' ];
		foreach ( $taxonomies as $tax ) {
			$groups[] = array(
				'title' => __( "From", 'brizy' ) . " " . $tax->labels->singular_name,
				'value' => Brizy_Admin_Rule::POSTS,
				'items' => array_merge( [
					[
						'title'      => 'All ' . $tax->labels->name,
						'value'      => "in|" . $tax->name,
						'groupValue' => $tax->name,
					],
				], array_map( $closureFromTerm, array_filter( get_terms( [
							'taxonomy'   => $tax->name,
							'hide_empty' => false,
						] ), function ( $term ) use ( $exclude ) {
							return in_array( $term->slug, $exclude ) ? false : true;
						} ) ) ),
			);
			if ( $tax->hierarchical ) {
				$groups[] = array(
					'title' => __( "From any child of", 'brizy' ) . " " . $tax->labels->singular_name,
					'value' => Brizy_Admin_Rule::POSTS,
					'items' => array_map( $closureChildTerm, get_terms( [
						'taxonomy'   => $tax->name,
						'hide_empty' => false,
					] ) ),
				);
			}
		}
		unset( $taxonomies );
		$groups[] = array(
			'title' => 'Specific ' . $postTypeName,
			'value' => Brizy_Admin_Rule::POSTS,
			'items' => array_map( $closurePost, Brizy_Editor_Post::getPostList( null, $post_type, null, 0, 100000 ) ),
		);
		$groups[] = array(
			'title' => 'Specific Author',
			'value' => Brizy_Admin_Rule::POSTS,
			'items' => array_merge( [
				[
					'title'      => __( 'All Authors', 'brizy' ),
					'value'      => 'author|',
					'groupValue' => 'author',
				],
			], array_map( $closureAuthor, get_users() ) ),
		);
		$groups = array_values( array_filter( $groups, function ( $o ) {
			return ! is_null( $o );
		} ) );
		wp_send_json_success( $groups, 200 );
	}

	public function getArchiveGroupsList() {

		if ( ! ( $taxonomy = $this->param( 'taxonomy' ) ) ) {
			wp_send_json_error( 'Invalid taxonomy', 400 );
		}
		$groups = [];
		$taxonomies = get_taxonomies( array( 'public' => true, 'show_ui' => true, 'name' => $taxonomy ), 'objects' );
		$closureSingleTerm = function ( $v ) {
			return array(
				'title'      => $v->name,
				'value'      => $v->term_id,
				'groupValue' => $v->taxonomy,
			);
		};
		$closureTerm = function ( $v ) {
			return array(
				'title'      => $v->name,
				'value'      => "child|" . $v->taxonomy . "|" . $v->term_id,
				'groupValue' => $v->taxonomy,
			);
		};
		foreach ( $taxonomies as $tax ) {
			$groups[] = array(
				'title' => __( "Specific", 'brizy' ) . " " . $tax->labels->singular_name,
				'value' => Brizy_Admin_Rule::TAXONOMY,
				'items' => array_merge( [
					[
						'title'      => 'All ' . $tax->labels->name,
						'value'      => "in|" . $tax->name,
						'groupValue' => $tax->name,
					],
				], array_map( $closureSingleTerm, get_terms( [
						'taxonomy'   => $tax->name,
						'hide_empty' => false,
					] ) ) ),
			);
			if ( $tax->hierarchical ) {
				$groups[] = array(
					'title' => __( "Any child of", 'brizy' ) . " " . $tax->labels->singular_name,
					'value' => Brizy_Admin_Rule::TAXONOMY,
					'items' => array_map( $closureTerm, get_terms( [
						'taxonomy'   => $tax->name,
						'hide_empty' => false,
					] ) ),
				);
			}
		}
		$groups = array_values( array_filter( $groups, function ( $o ) {
			return ! is_null( $o );
		} ) );
		wp_send_json_success( $groups, 200 );

	}

	public function getTemplateGroupsList() {

		$searchQuery = $this->param( 'searchQuery' );
		$page        = (int) $this->param( 'page' ) ?: 1;
		$groups = [];
		$closureAuthor = function ( $v ) {
			return array(
				'title'      => $v->user_nicename,
				'value'      => $v->ID,
				'groupValue' => 'author',
			);
		};
		$get_users     = get_users( [
			'fields'         => [ 'ID', 'user_nicename' ],
			'search'         => $searchQuery ? "*$searchQuery*" : "",
			'search_columns' => array( 'user_nicename', 'user_email', 'display_name' ),
			'offset'         => ( $page - 1 ) * 30,
			'number'         => 30
		] );
		$groups[]      = array(
			'title' => 'Specific Author',
			'value' => Brizy_Admin_Rule::TEMPLATE,
			'items' => array_map( $closureAuthor, $get_users ),
		);
		$groups = array_values( array_filter( $groups, function ( $o ) {
			return ! is_null( $o );
		} ) );
		wp_send_json_success( $groups, 200 );
	}

	private function getCustomPostsList( $groupValue, $templateType, $context ) {
		$postTypes = get_post_types( [ 'public' => true, 'show_ui' => true ], 'objects' );
		$postTypes = array_diff_key( $postTypes, array_flip( [
			'attachment',
			'elementor_library',
			Brizy_Admin_Stories_Main::CP_STORY,
		] ) );
		$postTypes = array_filter( $postTypes, function ( $type ) use ( $groupValue, $templateType, $context ) {

			if ( $context == 'template-rules' ) {
				if ( $type->name == 'product' ) {
					return $templateType == 'single_product';
				} else {
					return $templateType == 'single';
				}
			}

			return $type->public && $type->show_ui;
		} );

		return array_map( function ( $t ) use ( $groupValue ) {
			$t->groupValue = $groupValue;

			return $t;
		}, array_values( $postTypes ) );
	}

	private function getWooPageList( $groupValue, $templateType ) {
		return [
			[
				'title'      => 'Shop Page',
				'value'      => 'shop_page',
				'groupValue' => Brizy_Admin_Rule::WOO_SHOP_PAGE,
			],
		];
	}

	private function getArchivesList( $groupValue, $templateType ) {
		global $wp_post_types;

		return array_values( array_filter( $wp_post_types, function ( $type ) use ( $groupValue, $templateType ) {
			$type->groupValue = $groupValue;
			$is_product       = $type->name == 'product';
			if ( $templateType == 'product_archive' ) {
				return $is_product && $type->public && $type->show_ui && $type->has_archive;
			} else {
				return ! $is_product && $type->public && $type->show_ui && $type->has_archive;
			}
		} ) );
	}

	private function getTaxonomyList( $groupValue, $templateType ) {
		$terms = get_taxonomies( array( 'public' => true, 'show_ui' => true ), 'objects' );

		return array_values( array_filter( $terms, function ( $term ) use ( $groupValue, $templateType ) {
			$term->groupValue = $groupValue;
			$is_product_term  = $term->name == 'product_cat' || $term->name == 'product_tag';
			if ( $templateType == 'product_archive' ) {
				return $is_product_term;
			} else {
				return ! $is_product_term;
			}
		} ) );
	}

	public function geTemplateList( $context, $templateType ) {


		$isPopupOrBlockRules = $context == 'popup-rules' || $context == 'global-block-rules';
		$list                = array(
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Author page',
				'value'      => 'author',
				'groupValue' => Brizy_Admin_Rule::TEMPLATE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Search page',
				'value'      => 'search',
				'groupValue' => Brizy_Admin_Rule::TEMPLATE,
			) : null,
			$templateType === 'single' || $isPopupOrBlockRules ? array(
				'title'      => 'Front page',
				'value'      => 'front_page',
				'groupValue' => Brizy_Admin_Rule::TEMPLATE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Blog / Posts page',
				'value'      => 'home_page',
				'groupValue' => Brizy_Admin_Rule::TEMPLATE,
			) : null,
			$templateType === 'single' || $isPopupOrBlockRules ? array(
				'title'      => '404 page',
				'value'      => '404',
				'groupValue' => Brizy_Admin_Rule::TEMPLATE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Day Archive page',
				'value'      => '',
				'groupValue' => Brizy_Admin_Rule::DAY_ARCHIVE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Month Archive page',
				'value'      => '',
				'groupValue' => Brizy_Admin_Rule::MONTH_ARCHIVE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Year Archive page',
				'value'      => '',
				'groupValue' => Brizy_Admin_Rule::YEAR_ARCHIVE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Date Archive page',
				'value'      => '',
				'groupValue' => Brizy_Admin_Rule::DATE_ARCHIVE,
			) : null,
			$templateType === 'archive' || $isPopupOrBlockRules ? array(
				'title'      => 'Archive page',
				'value'      => '',
				'groupValue' => Brizy_Admin_Rule::ARCHIVE,
			) : null,
		);
		if ( ( $context !== 'template-rules' && $templateType === 'single' ) || $context == 'global-block-rules' ) {

			$list[] = array(
				'title'      => 'Brizy Templates',
				'value'      => 'editor-template',
				'groupValue' => Brizy_Admin_Rule::BRIZY_TEMPLATE,
			);
		}

		return array_values( array_filter( $list, function ( $o ) {
			return ! is_null( $o );
		} ) );
	}


}
