<?php
/**
 * Livada SEO — IndexNow Module
 *
 * Integrates with the IndexNow protocol to notify search engines immediately
 * when a post is published.
 *
 * Responsibilities:
 *   1. On `transition_post_status`: schedules a single-event WP-Cron ping for
 *      every URL that transitions into `publish` status.
 *   2. On `template_redirect`: serves the IndexNow key file at `/{key}.txt` so
 *      search engines can verify ownership of the site.
 *
 * The IndexNow key UUID is generated once at plugin activation and stored under
 * the `indexnow_key` settings key.
 *
 * @package Livada_SEO
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Livada_SEO_IndexNow {

	// ── Bootstrap ─────────────────────────────────────────────────────────────

	/**
	 * Registers all hooks for this module.
	 * Called once by Livada_SEO_Loader::load_class().
	 */
	public static function init(): void {
		// Schedule a ping whenever a post transitions to published.
		add_action( 'transition_post_status', [ __CLASS__, 'on_post_published' ], 10, 3 );

		// Register the WP-Cron callback that performs the actual HTTP ping.
		add_action( 'livada_seo_indexnow_ping', [ __CLASS__, 'send_ping' ] );

		// Serve the key verification file before any template is loaded.
		add_action( 'template_redirect', [ __CLASS__, 'maybe_serve_key_file' ], 0 );
	}

	// ── Post publish hook ─────────────────────────────────────────────────────

	/**
	 * Schedules a single IndexNow ping when a post transitions to `publish`.
	 *
	 * Fires on `transition_post_status` for all post types. Any previous status
	 * (including `new`, `draft`, `future`, etc.) triggers the ping so that
	 * first-time publications and re-publications are both caught.
	 *
	 * @param string  $new_status The new post status.
	 * @param string  $old_status The previous post status.
	 * @param WP_Post $post       The post object.
	 */
	public static function on_post_published( string $new_status, string $old_status, WP_Post $post ): void {
		if ( 'publish' !== $new_status ) {
			return;
		}

		$url = get_permalink( $post );

		if ( ! $url ) {
			return;
		}

		wp_schedule_single_event( time(), 'livada_seo_indexnow_ping', [ $url ] );
	}

	// ── Ping sender ───────────────────────────────────────────────────────────

	/**
	 * Sends the IndexNow HTTP notification for the given URL.
	 *
	 * Invoked by WP-Cron via the `livada_seo_indexnow_ping` action.
	 *
	 * Request body (JSON):
	 *   {
	 *     "host":        "example.com",
	 *     "key":         "uuid-v4-string",
	 *     "keyLocation": "https://example.com/{key}.txt",
	 *     "urlList":     ["https://example.com/my-post/"]
	 *   }
	 *
	 * @param string $url The permalink to notify search engines about.
	 */
	public static function send_ping( string $url ): void {
		$key = (string) Livada_SEO_Loader::get_setting( 'indexnow_key', '' );

		if ( $key === '' || $url === '' ) {
			return;
		}

		$host         = (string) wp_parse_url( home_url(), PHP_URL_HOST );
		$key_location = untrailingslashit( home_url() ) . '/' . $key . '.txt';

		$body = wp_json_encode( [
			'host'        => $host,
			'key'         => $key,
			'keyLocation' => $key_location,
			'urlList'     => [ $url ],
		] );

		wp_remote_post(
			'https://api.indexnow.org/indexnow',
			[
				'timeout'     => 10,
				'redirection' => 0,
				'headers'     => [
					'Content-Type' => 'application/json; charset=utf-8',
				],
				'body'        => $body,
			]
		);
	}

	// ── Key file server ───────────────────────────────────────────────────────

	/**
	 * Serves the IndexNow key verification file if the current request matches
	 * `/{key}.txt`.
	 *
	 * Outputs the raw key string as `text/plain` and halts execution so WordPress
	 * never attempts to load a theme template.
	 *
	 * Hooked on `template_redirect` at priority 0 to run before any other handler.
	 */
	public static function maybe_serve_key_file(): void {
		$key = (string) Livada_SEO_Loader::get_setting( 'indexnow_key', '' );

		if ( $key === '' ) {
			return;
		}

		// Compare against the raw REQUEST_URI to avoid any permalink resolution.
		$request_uri = isset( $_SERVER['REQUEST_URI'] )
			? (string) $_SERVER['REQUEST_URI']
			: '';

		// Strip query string before comparing.
		$path = (string) strtok( $request_uri, '?' );

		if ( $path !== '/' . $key . '.txt' ) {
			return;
		}

		// Serve the key file.
		if ( ! headers_sent() ) {
			header( 'Content-Type: text/plain; charset=utf-8' );
			header( 'Cache-Control: public, max-age=86400' );
		}

		echo esc_html( $key );
		exit;
	}
}
