<?php
/**
 * Handles rewrite rules added or modified by PRO Views v2.
 *
 * @since   4.7.9
 *
 * @package Tribe\Events\Pro\Views\V2
 */

namespace Tribe\Events\Pro\Views\V2;

use Tribe\Events\Pro\Views\V2\Views\Organizer_View;
use Tribe\Events\Pro\Views\V2\Views\Photo_View;
use Tribe\Events\Pro\Views\V2\Views\Venue_View;
use Tribe__Events__Main as TEC;
use Tribe__Events__Organizer as Organizer;
use Tribe__Events__Rewrite as TEC_Rewrite;
use Tribe__Events__Venue as Venue;

/**
 * Class Rewrite
 *
 * @since   4.7.9
 *
 * @package Tribe\Events\Pro\Views\V2
 */
class Rewrite {
	/**
	 * A canary string rule used to know whether the rewrite rules have been filtered or not.
	 *
	 * @since 6.0.0
	 *
	 * @var string
	 */
	private $map_pagination_canary_key;

	/**
	 * Filters the base rewrite rules to add venue and organizer as translate-able pieces.
	 *
	 * @since 5.0.0
	 *
	 * @param array  $bases  Original set of bases used in TEC.
	 *
	 * @return array         Bases after adding Venue and Organizer
	 */
	public function add_base_rewrites( $bases ) {
		$localized_venue_slug = esc_html_x( 'venue', 'The archive for events, "/venue/" URL string component.', 'tribe-events-calendar-pro' );

		if ( $localized_venue_slug === 'venue' ) {
			// Try translating it using The Events Calendar's translation.
			$localized_venue_slug = __( 'venue', 'the-events-calendar' );
		}

		$localized_organizer_slug = esc_html_x( 'organizer', 'The archive for events, "/organizer/" URL string component.', 'tribe-events-calendar-pro' );

		if ( $localized_organizer_slug === 'organizer' ) {
			// Try translating it using The Events Calendar's translation.
			$localized_organizer_slug = __( 'organizer', 'the-events-calendar' );
		}

		$localized_venues_slug = esc_html_x( 'venues', 'The archive for events for multiple venue, "/venues/" URL string component.', 'tribe-events-calendar-pro' );

		if ( $localized_venues_slug === 'venues' ) {
			// Try translating it using The Events Calendar's translation.
			$localized_venues_slug = __( 'venues', 'the-events-calendar' );
		}

		$localized_organizers_slug = esc_html_x( 'organizers', 'The archive for events for multiple organizers, "/organizers/" URL string component.', 'tribe-events-calendar-pro' );

		if ( $localized_organizers_slug === 'organizers' ) {
			// Try translating it using The Events Calendar's translation.
			$localized_organizers_slug = __( 'organizers', 'the-events-calendar' );
		}

		$bases['venue'] = [ 'venue', $localized_venue_slug ];
		$bases['organizer'] = [ 'organizer', $localized_organizer_slug ];

		$bases['venues'] = [ 'venues', $localized_venues_slug ];
		$bases['organizers'] = [ 'organizers', $localized_organizers_slug ];

		return $bases;
	}

	/**
	 * Filters the `redirect_canonical` to prevent any redirects on venue and organizer URLs.
	 *
	 * @since 5.0.0
	 *
	 * @param mixed $redirect_url URL which we will redirect to.
	 *
	 * @return string             Original URL redirect or False to prevent canonical redirect.
	 */
	public function filter_prevent_canonical_redirect( $redirect_url = null ) {
		// When dealing with admin urls bail early.
		if ( is_admin() ) {
			return $redirect_url;
		}

		$context = tribe_context();

		if ( $context->get( 'organizer_post_type', false ) ) {
			return false;
		}

		if ( $context->get( 'venue_post_type', false ) ) {
			return false;
		}

		return $redirect_url;
	}

	/**
	 * Add rewrite routes for PRO Views v2.
	 *
	 * @since 4.7.9
	 *
	 * @param TEC_Rewrite $rewrite The Events Calendar rewrite handler object.
	 */
	public function add_rewrites( TEC_Rewrite $rewrite ) {
		$rewrite->add(
			[
				'{{ venue }}',
				'([^/]+)',
				'{{ page }}',
				'(\d+)',
			],
			[
				'eventDisplay' => Venue_View::get_view_slug(),
				Venue::POSTTYPE => '%1',
				'paged'=> '%2',
			]
		);
		$rewrite->add(
			[
				'{{ venue }}',
				'([^/]+)',
			],
			[
				'eventDisplay' => Venue_View::get_view_slug(),
				Venue::POSTTYPE => '%1',
			]
		);

		$rewrite->add(
			[
				'{{ organizer }}',
				'([^/]+)',
				'{{ page }}',
				'(\d+)',
			],
			[
				'eventDisplay' => Organizer_View::get_view_slug(),
				Organizer::POSTTYPE => '%1',
				'paged'=> '%2',
			]
		);
		$rewrite->add(
			[
				'{{ organizer }}',
				'([^/]+)',
			],
			[
				'eventDisplay' => Organizer_View::get_view_slug(),
				Organizer::POSTTYPE => '%1',
			]
		);

		$rewrite->archive(
			[
				'{{ photo }}',
				'{{ page }}',
				'(\d+)',
			],
			[
				'eventDisplay' => Photo_View::get_view_slug(),
				'paged'        => '%1',
			]
		);
	}

	/**
	 * Filters the geocode based rewrite rules to add rules to paginate the Map View..
	 *
	 * @since 4.7.9
	 * @since 5.1.4 Update the method to add the support for the completely localized version of the rules.
	 *
	 * @param array<string,string> $rules The geocode based rewrite rules.
	 * @param array<string,string> $bases The geocode rewrite bases.
	 *
	 * @return array The filtered geocode based rewrite rules.
	 */
	public function add_map_pagination_rules( array $rules, array $bases ) {
		if ( $this->map_pagination_canary_key && isset( $rules[ $this->map_pagination_canary_key ] ) ) {
			return $rules;
		}

		/*
		 * We use this "hidden" dependency here and now because that's when we're sure the object was correctly built
		 * and ready to provide the information we need.
		 */
		$tec_bases = TEC_Rewrite::instance()->bases;
		$page_base = isset( $tec_bases->page ) ? $tec_bases->page : false;

		// Create the fully localized version of the rules.
		$updated_rules = [];
		foreach ( $rules as $rule => $query_string ) {
			// Remove the leading `(.*)`.
			$updated_rule = str_replace( '(.*)', '', $rule );
			// Replace `events/` with the regular expression that will capture all its translations, incl. English.
			$updated_rule = str_replace( $bases['base'], $tec_bases->archive . '/', $updated_rule );

			/*
			 * In the 2 following lines of code we use the bases, themselves regular expression, to match the fragment
			 * we need to replace in the regular expression that is part of the rewrite rule.
			 * We can do this as the rule, as built from the `Geo_Loc` class, will use the slugs as literal matches, not
			 * regular expression, hence we can find and replace them using the base regular expressions.
			 */

			// Replace the `category` part with the regular expression that will capture all its translations, incl. En.
			$tax          = $tec_bases->tax;
			$updated_rule = preg_replace( '~' . $tax . '~', $tax, $updated_rule, 1, $is_tax_rule );

			if ( $is_tax_rule ) {
				/*
				 * Map view category match would be the 2nd one, we need to make it become the first one as we've
				 * removed the leading `(.*)`.
				 */
				$query_string = str_replace( TEC::TAXONOMY . '=$matches[2]', TEC::TAXONOMY . '=$matches[1]', $query_string );
			}

			// Replace the `tag` part with the regular expression that will capture all its translations, incl. En.
			$tag          = $tec_bases->tag;
			$updated_rule = preg_replace( '~' . $tag . '~', $tag, $updated_rule, 1, $is_tag_rule );

			if ( $is_tag_rule ) {
				/*
				 * Map view tag match would be the 2nd one, we need to make it become the first one as we've
				 * removed the leading `(.*)`.
				 */
				$query_string = str_replace( 'tag=$matches[2]', 'tag=$matches[1]', $query_string );
			}

			$updated_rules[ $updated_rule ] = $query_string;
		}

		if ( false === $page_base ) {
			return $rules;
		}

		$pagination_rules = [];
		foreach ( $updated_rules as $regex => $rewrite ) {
			$key            = rtrim( $regex, '/?$' ) . '/' . $page_base . '/(\\d+)/?$';
			$is_tax_rule    = preg_match( '/(' . TEC::TAXONOMY . '|tag)=\$matches/', $rewrite );
			$page_match_pos = $is_tax_rule ? 2 : 1;
			$value          = false !== strpos( $rewrite, '?' ) ?
				$rewrite . '&paged=$matches[' . $page_match_pos . ']'
				: '?paged=$matches[' . $page_match_pos . ']';

			$pagination_rules[ $key ] = $value;
		}

		$this->map_pagination_canary_key = key( $pagination_rules );

		// It's important these rules are prepended to the pagination ones, not appended.
		return $pagination_rules + $updated_rules + $rules;
	}

	/**
	 * Filters the handled rewrite rules, the one used to parse plain links into permalinks, to add the ones
	 * managed by PRO.
	 *
	 * @since 5.0.1
	 *
	 * @param array<string,string> $handled_rules The handled rules, as produced by The Events Calendar base code; in
	 *                                            the same format used by WordPress to store and manage rewrite rules.
	 * @param array<string,string> $all_rules     All the rewrite rules handled by WordPress.
	 *
	 * @return array<string,string> The filtered rewrite rules, including the ones handled by Events PRO; in the same
	 *                              format used by WordPress to handle rewrite rules.
	 */
	public function filter_handled_rewrite_rules( array $handled_rules = [], array $all_rules = [] ) {
		$linked_post_rules = [];
		$venue_match       = Venue::POSTTYPE . '=';
		$organizer_match   = Organizer::POSTTYPE . '=';
		foreach ( $all_rules as $rewrite => $query ) {
			if (
				is_string( $query )
				&& ( false !== strpos( $query, $venue_match ) || false !== strpos( $query, $organizer_match ) )
			) {
				$linked_post_rules[ $rewrite ] = $query;
			}
		}

		return array_unique( array_merge( $handled_rules, $linked_post_rules ) );
	}

	/**
	 * Filters the query vars map used by the Rewrite component to parse plain links into permalinks to add the elements
	 * needed to support PRO components.
	 *
	 * @since 5.0.1
	 *
	 * @param array<string,string> $query_vars_map The query variables map, as produced by The Events Calendar code.
	 *                                             Shape is `[ <pattern> => <query_var> ].
	 *
	 * @return array<string,string> The query var map, filtered to add the query vars handled by PRO.
	 */
	public function filter_rewrite_query_vars_map( array $query_vars_map = [] ) {
		$query_vars_map['venue']     = Venue::POSTTYPE;
		$query_vars_map['organizer'] = Organizer::POSTTYPE;
		$query_vars_map['venues'] = 'post_type';
		$query_vars_map['organizers'] = 'post_type';

		return $query_vars_map;
	}

	/**
	 * Filters The Events Calendar custom rewrite rules to fix the order and relative position of some and play
	 * nice with Views v2 canonical URL needs.
	 *
	 * The operations performed by this method are pretty expensive (filtering and sorting preserving keys) and is
	 * meant to be used once when rewrite rules are generated. It's NOT the kind of method that should run on each
	 * request.
	 *
	 * @since 5.0.3
	 *
	 * @param array<string,string> $rewrite_rules An array of The Events Calendar custom rewrite rules, in the same
	 *                                            format used by WordPress: a map of each rule regular expression to the
	 *                                            corresponding query string.
	 *
	 * @return array<string,string> The input map of The Events Calendar rewrite rules, updated to satisfy the needs
	 *                              of Views v2 canonical URL building.
	 */
	public function filter_events_rewrite_rules_custom( array $rewrite_rules ) {
		$rules_by_type = [ 'map' => [], 'week' => [], 'other' => [] ];

		// Divide the rules by type. Using the view slug is fine as it's in the query var, thus not translated.
		foreach ( $rewrite_rules as $regex => $query_string ) {
			if ( false !== strpos( $query_string, 'eventDisplay=map' ) ) {
				$rules_by_type['map'][ $regex ] = $query_string;
				continue;
			}

			if ( false !== strpos( $query_string, 'eventDisplay=week' ) ) {
				if ( false !== strpos( $regex, '/(\d{2})/?$' ) ) {
					// Discard week number rules: we do not support them in Views v2.
					continue;
				}
				$rules_by_type['week'][ $regex ] = $query_string;
				continue;
			}

			$rules_by_type['other'][ $regex ] = $query_string;
		}

		// Sort the rules to be map, week and others.
		$rewrite_rules = $rules_by_type['map'] + $rules_by_type['week'] + $rules_by_type['other'];

		return $rewrite_rules;
	}
}
