<?php

namespace wpbuddy\rich_snippets;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly


/**
 * Class Schemas_Model.
 *
 * The model to handle schema types and properties from schema.org.
 *
 * Any schema.org related stuff.
 *
 * @package wpbuddy\rich_snippets
 *
 * @since   2.0.0
 */
final class Schemas_Model {


	/**
	 * Primitive Types
	 * @var string[]
	 * @since 2.14.0
	 */
	const PRIMITIVE_TYPES = [
		'http://schema.org/Boolean',
		'http://schema.org/Date',
		'http://schema.org/DateTime',
		'http://schema.org/Number',
		'http://schema.org/Text',
		'http://schema.org/Time',
		'http://schema.org/CssSelectorType',
		'http://schema.org/URL',
		'http://schema.org/XPathType',
		'http://schema.org/False',
		'http://schema.org/True',
		'http://schema.org/Float',
		'http://schema.org/Integer',
	];


	/**
	 * Returns an array of popular classes.
	 *
	 * @return array
	 * @since 2.0.0
	 *
	 */
	public static function get_popular_types() {

		/**
		 * Popular schema types filter.
		 *
		 * Returns popular schema classes.
		 *
		 * @hook  wpbuddy/rich_snippets/schemas/types/popular
		 *
		 * @param {array} $popular_schemas An array of popular schema classes.
		 *
		 * @returns {array} An array of popular schema classes where the key is the schema URL (http://schema.org/Product) and the value is the label.
		 *
		 * @since 2.0.0
		 *
		 */
		return apply_filters(
			'wpbuddy/rich_snippets/schemas/types/popular',
			array(
				'http://schema.org/Product'       => __( 'Product', 'rich-snippets-schema' ),
				'http://schema.org/Event'         => __( 'Event', 'rich-snippets-schema' ),
				'http://schema.org/LocalBusiness' => __( 'Local Business', 'rich-snippets-schema' ),
				'http://schema.org/Restaurant'    => __( 'Restaurant', 'rich-snippets-schema' ),
				'http://schema.org/Recipe'        => __( 'Recipe', 'rich-snippets-schema' ),
				'http://schema.org/Article'       => __( 'Article', 'rich-snippets-schema' ),
				'http://schema.org/Review'        => __( 'Review', 'rich-snippets-schema' ),
				'http://schema.org/Movie'         => __( 'Movie', 'rich-snippets-schema' ),
				'http://schema.org/VideoObject'   => __( 'VideoObject', 'rich-snippets-schema' ),
			)
		);
	}


	/**
	 * Returns settings from a single property.
	 *
	 * @param string $id
	 *
	 * @return bool|\wpbuddy\rich_snippets\Schema_Property Returns a Schema_Property object if property was found.
	 *     Otherwise false.
	 * @since 2.0.0
	 *
	 */
	public static function get_property_by_id( $id ) {

		$all_props = self::get_properties( array( 'return_type' => 'all' ) );

		if ( is_wp_error( $all_props ) ) {
			return false;
		}

		foreach ( $all_props as $prop ) {
			if ( $prop->id === $id ) {
				return $prop;
			}
		}

		return false;
	}


	/**
	 * Searches for properties.
	 *
	 * If no class param is given, this will return all properties.
	 *
	 * @param array ['class']
	 *
	 * @param array $args [] {
	 *                     Array of arguments.
	 *
	 * @type string $class The schema.org class type (ie. http://schema.org/Product).
	 * @type string $type Could exact|all|required|guess. Exact means "only properties that can be attached to a
	 *     specific class-only".
	 * @type string $q The search term.
	 * }
	 *
	 * @return Schema_Property[]|\WP_Error
	 * @since 2.0.0
	 *
	 * @note $type = all can return more than 800+ items at once. You should consider adding a search term ($q).
	 *
	 */
	public static function get_properties( $args ) {

		$args = wp_parse_args( $args, array(
			'schema_type'   => '',
			'return_type'   => '',
			'q'             => '',
			'preconfigured' => false,
		) );

		$params = http_build_query( array(
			'schema_type'   => $args['schema_type'],
			'return_type'   => $args['return_type'],
			'q'             => $args['q'],
			'preconfigured' => Helper_Model::instance()->string_to_bool( $args['preconfigured'] ),
		) );

		$response = WPBuddy_Model::request(
			'/v3/schemas/properties/?' . $params
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		if ( ! isset( $response->properties ) || ( isset( $response->properties ) && ! is_array( $response->properties ) ) ) {
			return new \WP_Error(
				'wpbuddy/rich_snippets/rest/properties',
				_x( 'The WP-Buddy API did not return any properties.', 'Thrown error on rest api when there was no list of properties found.', 'rich-snippets-schema' )
			);
		}

		$response->properties = array_map( function ( $obj ) {

			return new Schema_Property( $obj );
		}, $response->properties );

		return $response->properties;
	}


	/**
	 * Returns all types.
	 *
	 * If a search parameter is given, this will only return those results.
	 *
	 * @param string $q Search parameter
	 *
	 * @return array|\WP_Error
	 * @since 2.0.0
	 *
	 */
	public static function get_types( $q = null ) {

		$params = http_build_query( array(
			'q' => $q,
		) );

		$response = WPBuddy_Model::request(
			'/v3/schemas/types?' . $params
		);

		if ( ! isset( $response->classes ) ) {
			return new \WP_Error(
				'wpbuddy/rich_snippets/rest/types',
				_x( 'The WP-Buddy API did not return any schema types.', 'Thrown error on rest api when there was no list of classes found.', 'rich-snippets-schema' )
			);
		}

		return $response->classes;
	}


	/**
	 * Returns details about a schema type.
	 *
	 * @param string $type The Schema Type (i.e. http://schema.org/Product)
	 *
	 * @return Schema_Class|\WP_Error
	 * @since 2.14.0
	 *
	 */
	public static function get_type_details( $type ) {

		$params = http_build_query( array(
			'type' => $type,
		) );

		$response = WPBuddy_Model::request(
			'/v3/schema/?' . $params
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$schema = new Schema_Class( $response );

		return $schema;
	}


	/**
	 * @param array $type Schema Types (ie. http://schema.org/Event)
	 *
	 * @return bool
	 * @since 2.15.4
	 */
	public static function is_enumeration( $type ) {
		$class = self::get_type_details( $type );

		if ( is_wp_error( $class ) ) {
			return false;
		}

		foreach ( $class->subclass_of as $subclass ) {
			if ( false !== stripos( $subclass, '/Enumeration' ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Return all children for a schema type.
	 *
	 * @param string $schema_type
	 *
	 * @return array|\WP_Error
	 * @since 2.3.0
	 *
	 */
	public static function get_children( string $schema_type ) {

		$is_pro = rich_snippets() instanceof \wpbuddy\rich_snippets\pro\Rich_Snippets_Plugin_Pro;

		# do not access this route as it only would result in an unauthorized request for free users
		if ( ! $is_pro ) {
			return [];
		}

		$params = http_build_query( array(
			'parent_schema_type' => $schema_type,
		) );

		$response = WPBuddy_Model::request(
			'/v3/schemas/type_children/?' . $params
		);

		if ( ! isset( $response->children ) ) {
			return new \WP_Error(
				'wpbuddy/rich_snippets/rest/type_children',
				_x( 'The WP-Buddy API did not return any children.', 'Thrown error on rest api when there was no list of children found.', 'rich-snippets-schema' )
			);
		}

		return (array) $response->children;
	}

	/**
	 * Returns all types.
	 *
	 * If a search parameter is given, this will only return those results.
	 *
	 * @param array $schema_types
	 *
	 * @return array|\WP_Error
	 * @since 2.0.0
	 *
	 */
	public static function get_type_descendants( $schema_types ) {

		$is_pro = rich_snippets() instanceof \wpbuddy\rich_snippets\pro\Rich_Snippets_Plugin_Pro;

		# do not access this route as it only would result in an unauthorized request for free users
		if ( ! $is_pro ) {
			return [];
		}

		$params = http_build_query( array(
			'schema_types' => $schema_types,
		) );

		$response = WPBuddy_Model::request(
			'/v3/schemas/type_descendants/?' . $params
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		if ( ! isset( $response->descendants ) ) {
			return new \WP_Error(
				'wpbuddy/rich_snippets/rest/type_descendants',
				_x( 'The WP-Buddy API did not return any descendants.', 'Thrown error on rest api when there was no list of type descendants found.', 'rich-snippets-schema' )
			);
		}

		return $response->descendants;
	}
}
