<?php
/**
 * @package Polylang-Pro
 */

defined( 'ABSPATH' ) || exit;

/**
 * Expose terms language and translations in the REST API for templates in particular by filtering the queries.
 *
 * @since 3.2
 */
class PLL_FSE_REST_Template extends PLL_REST_Post {
	/**
	 * The post type of the template.
	 *
	 * @var string
	 */
	protected $post_type = '';

	/**
	 * Constructor
	 *
	 * @since 3.2
	 *
	 * @param PLL_REST_API $rest_api      Instance of PLL_REST_API.
	 * @param string[]     $content_types Array with post types as keys and values.
	 * @return void
	 */
	public function __construct( PLL_REST_API &$rest_api, array $content_types ) {
		parent::__construct( $rest_api, $content_types );

		$this->setter_id_name = 'wp_id';
		$this->getter_id_name = 'wp_id';
	}

	/**
	 * Adds the corresponding hooks.
	 *
	 * @since 3.2
	 *
	 * @return self
	 */
	public function init() {
		add_filter( 'rest_dispatch_request', array( $this, 'get_current_post_type_from_route' ), 10, 2 );
		return $this;
	}

	/**
	 * Overloads parent's method to add the delete link and default language into a translation table data for template post types.
	 * See PLL_REST_Post::generate_translation_data.
	 *
	 * @since 3.2
	 *
	 * @param int          $id       The id of the existing post to get data for the translations table element.
	 * @param int          $tr_id    The id of the translated post for the given language if exists.
	 * @param PLL_Language $language The given language object.
	 * @return array The translation data of the given language.
	 */
	public function get_translation_table_data( $id, $tr_id, $language ) {
		$translation_data = parent::get_translation_table_data( $id, $tr_id, $language );

		// Gets the template id in the `theme // post name` format to be able to delete it from the UI.
		if ( PLL_FSE_Tools::is_template_post_type( $this->post_type ) && ! empty( $tr_id ) ) {
			$templates = get_block_templates( array( 'wp_id' => $tr_id ), $this->post_type );
			if ( ! empty( $templates ) ) {
				$template = reset( $templates );
				$translation_data['template']['id'] = $template->id;
			}
		}

		return $translation_data;
	}

	/**
	 * Returns the slug of the language assigned to the given post.
	 * Overrides the parent method.
	 *
	 * @since 3.2
	 *
	 * @param array $object Post array.
	 * @return string|false Template's language slug. Default language slug if no language
	 *                      is assigned to the template yet. False on failure.
	 */
	public function get_language( $object ) {
		if ( ! empty( $object[ $this->getter_id_name ] ) ) {
			$language = $this->model->{$this->type}->get_language( $object[ $this->getter_id_name ] );

			if ( ! empty( $language ) ) {
				return $language->slug;
			}
		}

		$lang = $this->model->get_default_language();

		if ( ! empty( $lang ) ) {
			return $lang->slug;
		}

		return false;
	}

	/**
	 * Filters templates by language.
	 *
	 * @since 3.2
	 *
	 * @param WP_Query $query WP_Query object.
	 * @return void
	 */
	public function parse_query( $query ) {
		if ( ! empty( $query->query_vars['post_name__in'] ) && is_array( $query->query_vars['post_name__in'] ) ) {
			// Do not filter query for a single item.
			return;
		}

		if ( isset( $query->query_vars['lang'] ) && empty( $query->query_vars['lang'] ) ) {
			// We've been asking not to filter by language.
			return;
		}

		if ( ! PLL_FSE_Tools::is_template_query( $query ) ) {
			// Not a template part query.
			return;
		}

		// Filter a templates list query by the default language.
		if ( ! empty( $this->request ) ) {
			$lang = $this->request->get_param( 'lang' );
		}

		if ( empty( $lang ) ) {
			$lang = $this->model->options['default_lang'];
		}

		// Since it's a template part query, take care of the ones stored as files with the next hook.
		add_filter( 'get_block_templates', array( $this, 'filter_template_part_list' ), 10, 3 );

		$pll_query = new PLL_Query( $query, $this->model );
		$pll_query->query->set( 'lang', $lang );
		$pll_query->filter_query( $this->model->get_language( $lang ) );
	}

	/**
	 * Returns the current post type from the REST route
	 *
	 * @since 3.2
	 *
	 * @param mixed           $result  Response to replace the requested version with. Can be anything a normal
	 *                                 endpoint can return, or null to not hijack the request.
	 * @param WP_REST_Request $request Request used to generate the response.
	 * @return mixed                   Unchanged value.
	 */
	public function get_current_post_type_from_route( $result, $request ) {
		if ( ! $request instanceof WP_REST_Request ) {
			return $result;
		}

		$route = new PLL_FSE_REST_Route( $request->get_route() );

		if ( ! $route->is_template_route() ) {
			return $result;
		}

		$params = $request->get_params();

		if ( isset( $params['postType'] ) && PLL_FSE_Tools::is_template_post_type( (string) $params['postType'] ) ) {
			$this->post_type = $params['postType'];
		} else {
			$post_type = $route->get_post_type();

			if ( PLL_FSE_Tools::is_template_post_type( $post_type ) ) {
				$this->post_type = $post_type;
			}
		}

		return $result;
	}

	/**
	 * Filters template part list according to the REST request parameters.
	 * Filtering by language is already done on a `WP_Query` level, see {`self::parse_query()`}.
	 *
	 * @since 3.3.2
	 *
	 * @param WP_Block_Template[] $templates     Array of found block templates.
	 * @param array               $query         Arguments to retrieve templates.
	 * @param string              $template_type 'wp_template' or 'wp_template_part'.
	 * @return WP_Block_Template[] Array of filtered block templates.
	 */
	public function filter_template_part_list( $templates, $query, $template_type ) {
		if ( empty( $this->request ) || isset( $query['wp_id'] ) ) {
			return $templates;
		}

		if ( 'wp_template_part' !== $template_type ) {
			return $templates;
		}

		$lang = $this->request->get_param( 'lang' );

		if ( empty( $lang ) ) {
			return $templates;
		}

		$lang = $this->model->get_language( $lang );

		if ( empty( $lang ) ) {
			return $templates;
		}

		$with_untranslated = $this->request->get_param( 'include_untranslated' );

		if ( ! empty( $with_untranslated ) ) {
			$templates = array_merge( $templates, $this->get_untranslated_template_parts( $lang ) );
		}

		return $this->remove_unwanted_template_part_files( $templates, $lang );
	}

	/**
	 * Filters out template part files if they are already existing translations of them in the list.
	 *
	 * @since 3.3.2
	 *
	 * @param WP_Block_Template[] $templates     Array of found block templates.
	 * @param PLL_Language        $current_lang  Current language object.
	 * @return WP_Block_Template[] Array of filtered block templates.
	 */
	private function remove_unwanted_template_part_files( array $templates, PLL_Language $current_lang ) {
		$templates_to_remove = array();

		// First, let's find template part objects with a language.
		foreach ( $templates as $template ) {
			$template_slug = new PLL_FSE_Template_Slug(
				$template->slug,
				array( $current_lang->slug )
			);

			if ( ! empty( $template->wp_id ) && ! empty( $this->model->post->get_language( $template->wp_id ) ) ) {
				// Current template is custom and has a language.
				$templates_to_remove[] = $template_slug->get_template_slug();
			}
		}

		// Then remove duplicates stored as files.
		foreach ( $templates as $i => $template ) {
			if ( empty( $template->wp_id ) && in_array( $template->slug, $templates_to_remove, true ) ) {
				// Duplicated template part stored in a file.
				unset( $templates[ $i ] );
				continue;
			}
		}

		return $templates;
	}

	/**
	 * Returns template parts without translation in the given language.
	 *
	 * @since 3.3.2
	 *
	 * @param PLL_Language $lang The language to check against.
	 * @return WP_Block_Template[] Array of block template parts objects.
	 */
	private function get_untranslated_template_parts( PLL_Language $lang ) {
		$def_lang = $this->model->get_default_language();

		if ( empty( $def_lang ) ) {
			// No default language.
			return array();
		}

		if ( $lang->slug === $def_lang->slug ) {
			// Untranslated template parts are not available for the default language.
			return array();
		}

		$untranslated_posts          = $this->model->post->get_untranslated( $this->post_type, $lang, $def_lang );
		$untranslated_template_parts = array();

		foreach ( $untranslated_posts as $untranslated_post ) {
			$untranslated_template_part = _build_block_template_result_from_post( $untranslated_post );

			if ( is_wp_error( $untranslated_template_part ) ) {
				continue;
			}

			$untranslated_template_parts[] = $untranslated_template_part;
		}

		return $untranslated_template_parts;
	}

	/**
	 * Returns edit post link for site editor.
	 *
	 * @since 3.4.5
	 *
	 * @param int $post_id ID of the post to get edit link from.
	 * @return string|null The edit post link for the given post. Null if none found.
	 */
	protected function get_site_editor_edit_post_link( $post_id ) {
		$post_type = (string) get_post_type( $post_id );
		if ( empty( $post_type ) ) {
			return (string) get_edit_post_link( $post_id, 'keep ampersand' );
		}

		if ( version_compare( $GLOBALS['wp_version'], '6.3', '>=' ) ) {
			return (string) get_edit_post_link( $post_id, 'keep ampersand' );
		}

		// Backward compatibility with WP **<** 6.3.
		$templates = get_block_templates( array( 'wp_id' => $post_id ), 'wp_template_part' );
		if ( empty( $templates ) ) {
			return (string) get_edit_post_link( $post_id, 'keep ampersand' );
		}

		$template = reset( $templates );
		if ( $template->wp_id !== $post_id ) {
			return (string) get_edit_post_link( $post_id, 'keep ampersand' );
		}

		return add_query_arg(
			array(
				'postId'   => rawurlencode( $template->id ), // postId refers to the WP_Block_Template id (i.e. "theme-slug//template-slug").
				'postType' => $template->type,
				'path'     => rawurlencode( '/template-parts/single' ),
				'canvas'   => 'edit',
			),
			admin_url( 'site-editor.php' )
		);
	}
}
