<?php
/**
 * Handle logic for the conditional loading of snippets.
 * You'll find here the available logic options and how they apply to snippets.
 *
 * @package WPCode
 */

/**
 * The main conditional logic class that loads all the types.
 */
class WPCode_Conditional_Logic {

	/**
	 * Types of conditional logic.
	 *
	 * @var WPCode_Conditional_Type[]
	 */
	private $types = array();

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'plugins_loaded', array( $this, 'load_types' ), 0 );
	}

	/**
	 * Load the different conditional logic types.
	 *
	 * @return void
	 */
	public function load_types() {
		require_once WPCODE_PLUGIN_PATH . 'includes/conditional-logic/class-wpcode-conditional-type.php';
		require_once WPCODE_PLUGIN_PATH . 'includes/conditional-logic/class-wpcode-conditional-user.php';
		require_once WPCODE_PLUGIN_PATH . 'includes/conditional-logic/class-wpcode-conditional-page.php';
	}

	/**
	 * Register an auto-insert type.
	 *
	 * @param WPCode_Conditional_Type $type The type to add to the available types.
	 *
	 * @return void
	 */
	public function register_type( $type ) {
		$type_name                 = $type->name ?? '';
		$this->types[ $type_name ] = $type;
	}

	/**
	 * Get all the admin options for the conditional logic form.
	 *
	 * @param bool $for_js Whether to return the options for JS or not.
	 *
	 * @return array
	 */
	public function get_all_admin_options( $for_js = false ) {
		$options = array();
		foreach ( $this->types as $type ) {
			$type->load_type_options(); // Reload the options in case a global snippet made them get loaded before some post types were registered, for example.
			$options[ $type->get_name() ] = array(
				'label'    => $type->get_label(),
				'name'     => $type->get_name(),
				'category' => $type->get_category(),
				'options'  => $type->get_type_options(),
			);
		}

		// Sort options so the unavailable ones are at the end.
		$available_options   = array();
		$unavailable_options = array();
		foreach ( $options as $key => $option ) {
			if ( empty( $option['options'] ) ) {
				continue;
			}
			$first_option = reset( $option['options'] );
			if ( ! empty( $first_option['upgrade'] ) ) {
				$unavailable_options[ $key ] = $option;
			} else {
				$available_options[ $key ] = $option;
			}
		}

		// For the user logged_in option let's run esc_attr on the option values to save the values correctly.
		if ( $for_js && isset( $available_options['user']['options']['logged_in']['options'] ) ) {
			foreach ( $available_options['user']['options']['logged_in']['options'] as $key => $option ) {
				$available_options['user']['options']['logged_in']['options'][ $key ]['value'] = esc_attr( $option['value'] );
			}
		}

		return apply_filters( 'wpcode_conditional_logic_admin_options', array_merge( $available_options, $unavailable_options ) );
	}

	/**
	 * Goes through a list of snippets and filters out the ones
	 * that don't match the conditional logic rules.
	 *
	 * @param WPCode_Snippet[] $snippets An array of snippets.
	 *
	 * @return WPCode_Snippet[]
	 */
	public function check_snippets_conditions( $snippets ) {
		// If there's nothing to evaluate just return an empty array.
		if ( empty( $snippets ) ) {
			return array();
		}

		$filtered_snippets = array();
		foreach ( $snippets as $snippet ) {
			if ( ! $snippet->conditional_rules_enabled() ) {
				// If rules are disabled, ignore.
				$filtered_snippets[] = $snippet;
				continue;
			}
			$rules = $snippet->get_conditional_rules();
			if ( ! isset( $rules['show'] ) ) {
				continue;
			}
			$show          = 'show' === $rules['show'];
			$rules_are_met = $this->are_snippet_rules_met( $snippet );
			if ( $show && ! $rules_are_met ) {
				// If we should show based on conditions, and conditions
				// are not met, skip.
				continue;
			}
			if ( ! $show && $rules_are_met ) {
				// If we should hide based on conditions, and conditions
				// are met, skip.
				continue;
			}

			$filtered_snippets[] = $snippet;
		}

		return $filtered_snippets;
	}

	/**
	 * Takes a snippet and evaluates if conditional logic rules
	 * are met.
	 *
	 * @param WPCode_Snippet $snippet The snippet instance.
	 *
	 * @return bool
	 */
	public function are_snippet_rules_met( $snippet ) {
		$rules = $snippet->get_conditional_rules();
		if ( empty( $rules ) || ! is_array( $rules ) || empty( $rules['groups'] ) ) {
			return true;
		}

		// Go through all rule groups.
		foreach ( $rules['groups'] as $rule_group ) {
			// If any of the groups are met return true.
			if ( $this->are_group_rules_met( $rule_group, $snippet ) ) {
				return true;
			}
		}

		// If no group rules satisfy the conditions, return false.
		return false;
	}

	/**
	 * Evaluate all the rows of rules in a group.
	 *
	 * @param array          $rule_group An array of rows.
	 * @param WPCode_Snippet $snippet The snippet we are evaluating the rules for.
	 *
	 * @return bool
	 */
	public function are_group_rules_met( $rule_group, $snippet ) {
		foreach ( $rule_group as $rule_row ) {
			if ( ! isset( $rule_row['type'] ) || ! isset( $this->types[ $rule_row['type'] ] ) ) {
				continue;
			}
			$rule_type = $this->types[ $rule_row['type'] ];
			if ( ! $rule_type->evaluate_rule_row( $rule_row, $snippet ) ) {
				// If this row doesn't match, the whole group fails.
				return false;
			}
		}

		// If none of the rows failed the group matches.
		return true;
	}
}
