<?php
namespace JET_ABAF\Components\Bricks_Views\Dynamic_Data;

defined( 'ABSPATH' ) || exit; // Exit if accessed directly.

class Providers {

	/**
	 * Holds the providers.
	 *
	 * @var array
	 */
	private $providers_keys = [];

	/**
	 * Holds the providers instances.
	 *
	 * @var array
	 */
	private static $providers = [];

	/**
	 * Holds the tags instances.
	 *
	 * @var array
	 */
	private $tags = [];

	/**
	 * Allowed tag prefixes.
	 *
	 * @var array
	 */
	private $allowed_prefixes = [ 'jet_booking_' ];

	public function __construct( $providers ) {
		$this->providers_keys = $providers;
	}

	/**
	 * Register providers and hooks.
	 *
	 * @param array $providers Providers list.
	 *
	 * @return void
	 */
	public static function register( $providers = [] ) {

		$instance = new self( $providers );

		// Register providers (priority 10000 due to CMB2 priority).
		add_action( 'init', [ $instance, 'register_providers' ], 10000 );

		// Register tags on init after register_providers.
		add_action( 'init', [ $instance, 'register_tags' ], 10001 );

		// Register providers during WP REST API call.
		add_action( 'rest_api_init', [ $instance, 'register_providers' ], 7 );

		// Register tags during REST requests.
		add_action( 'rest_api_init', [ $instance, 'register_tags' ], 8 );

		add_filter( 'bricks/dynamic_tags_list', [ $instance, 'add_tags_to_builder' ] );

		// Render dynamic data in the builder too (when template preview post ID is set).
		add_filter( 'bricks/frontend/render_data', [ $instance, 'render' ], 10, 2 );
		add_filter( 'bricks/dynamic_data/render_content', [ $instance, 'render' ], 10, 3 );
		add_filter( 'bricks/dynamic_data/render_tag', [ $instance, 'get_tag_value' ], 10, 3 );

		add_filter( 'bricks/dynamic_data/allowed_keys', [ $instance, 'register_allowed_keys' ] );

	}

	/**
	 * Register providers.
	 *
	 * @since 4.1.0
	 *
	 * @return void
	 */
	public function register_providers() {

		if ( is_admin() && ! bricks_is_ajax_call() && ! bricks_is_rest_call() ) {
			return;
		}

		foreach ( $this->providers_keys as $provider => $namespace ) {
			$classname                    = $namespace . '\Provider_' . str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $provider ) ) );
			self::$providers[ $provider ] = new $classname( str_replace( '-', '_', $provider ) );
		}

	}

	/**
	 * Register tags from all providers.
	 *
	 * @since 4.1.0
	 *
	 * @return void
	 */
	public function register_tags() {
		foreach ( self::$providers as $provider ) {
			$this->tags = array_merge( $this->tags, $provider->get_tags() );
		}
	}

	/**
	 * Get registered tags.
	 *
	 * @since 4.1.0
	 *
	 * @return array
	 */
	public function get_tags() {
		return $this->tags;
	}

	/**
	 * Adds tags to the tags picker list (used in the builder).
	 *
	 * @since 4.1.0
	 *
	 * @param array $tags Tags list.
	 *
	 * @return array
	 */
	public function add_tags_to_builder( $tags ) {

		$list = $this->get_tags();

		foreach ( $list as $tag ) {
			if ( isset( $tag['deprecated'] ) ) {
				continue;
			}

			$tags[] = [
				'name'  => $tag['name'],
				'label' => $tag['label'],
				'group' => $tag['group'],
			];
		}

		return $tags;

	}

	/**
	 * Dynamic tag exists in $content: Replaces dynamic tag with requested data.
	 *
	 * @since 4.1.0
	 *
	 * @param string   $content Content with tags.
	 * @param \WP_Post $post    Post object.
	 * @param string   $context Context.
	 *
	 * @return string
	 */
	public function render( $content, $post, $context = 'text' ) {

		$prefixes = [];

		foreach ( $this->allowed_prefixes as $prefix ) {
			$prefixes[] = $prefix . '[\wÀ-ÖØ-öø-ÿ\-\s\.\/:\(\)\'@|,$%#!+=<>&~\[\];?]+';
		}

		$prefixes = implode( '|', $prefixes );
		$pattern  = '/{(' . $prefixes . ')}/u';

		// Get a list of tags to exclude from the dynamic data logic.
		$exclude_tags = apply_filters( 'bricks/dynamic_data/exclude_tags', [] );

		// Get all registered tags except the excluded ones.
		$registered_tags = array_filter(
			array_keys( $this->get_tags() ),
			function ( $tag ) use ( $exclude_tags ) {
				return ! in_array( $tag, $exclude_tags, true );
			}
		);

		$dd_tags_in_content = [];
		$dd_tags_found      = [];

		// Find all dynamic data tags in the content.
		preg_match_all( $pattern, $content, $dd_tags_in_content );

		$dd_tags_in_content = ! empty( $dd_tags_in_content[1] ) ? $dd_tags_in_content[1] : [];

		if ( ! empty( $dd_tags_in_content ) ) {
			// Find all dynamic data tags in the content which starts with dynamic data tag from $registered_tags.
			$dd_tags_found = array_filter(
				$dd_tags_in_content,
				function ( $tag ) use ( $registered_tags ) {
					foreach ( $registered_tags as $all_tag ) {
						if ( strpos( $tag, $all_tag ) === 0 ) {
							return true;
						}
					}

					return false;
				}
			);
		}

		$dd_tag_count = count( $dd_tags_found );

		// Run the parser based on the count of found dynamic data tags.
		for ( $i = 0; $i < $dd_tag_count; $i ++ ) {
			preg_match_all( $pattern, $content, $matches );

			if ( empty( $matches[0] ) ) {
				return $content;
			}

			foreach ( $matches[1] as $key => $match ) {
				$tag = $matches[0][ $key ];

				if ( in_array( $match, $exclude_tags, true ) ) {
					continue;
				}

				$value = $this->get_tag_value( $match, $post, $context );

				// Value is a WP_Error: Set value to false to avoid error in builder.
				if ( is_a( $value, 'WP_Error' ) ) {
					$value = false;
				}

				$content = str_replace( $tag, $value, $content );
			}
		}

		return $content;

	}

	/**
	 * Get the value of a dynamic data tag.
	 *
	 * @since 4.1.0
	 *
	 * @param string   $tag     Tag name (without braces).
	 * @param \WP_Post $post    Post object.
	 * @param string   $context Context.
	 *
	 * @return string
	 */
	public function get_tag_value( $tag, $post, $context = 'text' ) {

		if ( ! is_string( $tag ) || ! $this->has_allowed_prefix( $tag ) ) {
			return $tag;
		}

		$tag = str_replace( [ '{', '}' ], '', $tag );

		// Parse the tag and extract arguments.
		$parser = new \Bricks\Integrations\Dynamic_Data\Dynamic_Data_Parser();
		$parsed = $parser->parse( $tag );

		$tag          = $parsed['tag'] ?? $tag;
		$args         = $parsed['args'];
		$original_tag = $parsed['original_tag'];

		$tags = $this->get_tags();

		if ( ! array_key_exists( $tag, $tags ) ) {
			$replace_tag = apply_filters( 'bricks/dynamic_data/replace_nonexistent_tags', false );

			return $replace_tag ? '' : '{' . $original_tag . '}';
		}

		$provider = str_replace( '_', '-', $tags[ $tag ]['provider'] );

		return self::$providers[ $provider ]->get_tag_value( $tag, $post, $args, $context );

	}

	/**
	 * Check if the tag has an allowed prefix.
	 *
	 * @since 4.1.0
	 *
	 * @param string $tag Tag name.
	 *
	 * @return bool
	 */
	public function has_allowed_prefix( $tag ) {

		foreach ( $this->allowed_prefixes as $prefix ) {
			if ( false !== strpos( $tag, $prefix ) ) {
				return true;
			}
		}

		return false;

	}

	/**
	 * Register allowed keys.
	 *
	 * Register allowed keys for dynamic data args.
	 *
	 * @since 4.1.0
	 *
	 * @param array $keys Allowed keys.
	 *
	 * @return array
	 */
	public function register_allowed_keys( $keys ) {
		return array_merge( $keys, [
			'jb-start',
			'jb-end',
			'jb-currency',
			'jb-position',
			'jb-show',
			'jb-dynamic',
			'jb-available-label',
			'jb-pending-label',
			'jb-reserved-label',
			'jb-units-type',
		] );
	}

}
