<?php

namespace Yoast\WP\SEO\Premium\Integrations;

use WP_Post;
use WPSEO_Remote_Request;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
use Yoast\WP\SEO\Helpers\Request_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Index_Now_Ping class.
 */
class Index_Now_Ping implements Integration_Interface {

	use No_Conditionals;

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	private $options_helper;

	/**
	 * The request helper.
	 *
	 * @var Request_Helper
	 */
	private $request_helper;

	/**
	 * The post type helper.
	 *
	 * @var Post_Type_Helper
	 */
	private $post_type_helper;

	/**
	 * The IndexNow endpoint URL we're using.
	 *
	 * @var string
	 */
	private $endpoint;

	/**
	 * Index_Now_Ping integration constructor.
	 *
	 * @param Options_Helper   $options_helper   The option helper.
	 * @param Request_Helper   $request_helper   The request helper.
	 * @param Post_Type_Helper $post_type_helper The post type helper.
	 */
	public function __construct(
		Options_Helper $options_helper,
		Request_Helper $request_helper,
		Post_Type_Helper $post_type_helper
	) {
		$this->options_helper   = $options_helper;
		$this->request_helper   = $request_helper;
		$this->post_type_helper = $post_type_helper;

		/**
		 * Filter: 'Yoast\WP\SEO\indexnow_endpoint' - Allow changing the Indexnow endpoint.
		 *
		 * Note: This is a Premium plugin-only hook.
		 *
		 * @since 18.8
		 *
		 * @param string The IndexNow endpoint URL.
		 */
		$this->endpoint = \apply_filters( 'Yoast\WP\SEO\indexnow_endpoint', 'https://api.indexnow.org/indexnow' );
	}

	/**
	 * Registers the hooks this integration acts on.
	 *
	 * @return void
	 */
	public function register_hooks() {
		if ( $this->options_helper->get( 'enable_index_now' ) === false ) {
			return;
		}

		if ( \wp_get_environment_type() !== 'production' ) {
			return;
		}

		/**
		 * Please note that the name transition_post_status is misleading.
		 * The hook does not only fire on a post status transition but also when a post is updated
		 * while the status is not changed from one to another at all.
		 */
		\add_action( 'transition_post_status', [ $this, 'ping_index_now' ], 10, 3 );
	}

	/**
	 * Pings IndexNow for changes.
	 *
	 * @param string  $new_status The new status for the post.
	 * @param string  $old_status The old status for the post.
	 * @param WP_Post $post       The post.
	 *
	 * @return void
	 */
	public function ping_index_now( $new_status, $old_status, $post ) {
		if ( $new_status !== 'publish' && $old_status !== 'publish' ) {
			// If we're not transitioning to or from a published status, do nothing.
			return;
		}

		// The block editor saves published posts twice, we want to ping only on the first request.
		if ( $new_status === 'publish' && $this->request_helper->is_rest_request() ) {
			return;
		}

		if ( ! $post instanceof \WP_Post ) {
			return;
		}

		if ( ! \in_array( $post->post_type, $this->post_type_helper->get_accessible_post_types(), true )
			|| ! $this->post_type_helper->is_indexable( $post->post_type ) ) {
			return;
		}

		// Bail out if last ping was less than two minutes ago.
		$indexnow_last_ping = \get_post_meta( $post->ID, '_yoast_indexnow_last_ping', true );
		if ( \is_numeric( $indexnow_last_ping ) && \abs( \time() - ( (int) $indexnow_last_ping ) ) < 120 ) {
			return;
		}

		$key       = $this->options_helper->get( 'index_now_key' );
		$permalink = $this->get_permalink( $post );
		$urls      = [ $permalink ];

		if ( $post->post_type === 'post' ) {
			$urls[] = \get_home_url();
		}

		if ( ! empty( \get_option( 'permalink_structure' ) ) ) {
			$key_location = \trailingslashit( \get_home_url() ) . 'yoast-index-now-' . $key . '.txt';
		}
		else {
			$key_location = \add_query_arg( 'yoast_index_now_key', $key, \trailingslashit( \get_home_url() ) );
		}

		$content = (object) [
			'host'        => \wp_parse_url( \get_home_url(), PHP_URL_HOST ),
			'key'         => $key,
			'keyLocation' => $key_location,
			'urlList'     => $urls,
		];

		// Set a 'content-type' header of 'application/json' and an identifying source header.
		// The "false" on the end of the x-source-info header determines whether this is a manual submission or not.
		$request_args = [
			'headers' => [
				'content-type'  => 'application/json; charset=utf-8',
				'x-source-info' => 'https://yoast.com/wordpress/plugins/seo-premium/' . WPSEO_PREMIUM_VERSION . '/false',
			],
		];

		$request = new \WPSEO_Remote_Request( $this->endpoint, $request_args );
		// phpcs:ignore Yoast.Yoast.AlternativeFunctions.json_encode_wp_json_encode -- This is being sent to an API, not displayed.
		$request->set_body( \wp_json_encode( $content ) );
		$request->send();

		\update_post_meta( $post->ID, '_yoast_indexnow_last_ping', time() );
	}

	/**
	 * Determines the (former) permalink for a post.
	 *
	 * @param \WP_Post $post Post object.
	 *
	 * @return string Permalink.
	 */
	private function get_permalink( \WP_Post $post ) {
		if ( \in_array( $post->post_status, [ 'trash', 'draft', 'pending', 'future' ], true ) ) {
			if ( $post->post_status === 'trash' ) {
				// Fix the post_name.
				$post->post_name = \preg_replace( '/__trashed$/', '', $post->post_name );
			}
			// Force post_status to publish briefly, so we get the correct URL.
			$post->post_status = 'publish';
		}

		return \get_permalink( $post );
	}
}
