<?php
/**
 * Used by the blogging prompt feature.
 *
 * @package automattic/jetpack
 */

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

/**
 * Hooked functions.
 */

/**
 * Adds the blogging prompt key post meta to the list of allowed post meta to be updated by rest api.
 *
 * @param array $keys Array of post meta keys that are allowed public metadata.
 *
 * @return array
 */
function jetpack_blogging_prompts_add_meta_data( $keys ) {
	$keys[] = '_jetpack_blogging_prompt_key';
	return $keys;
}

add_filter( 'rest_api_allowed_public_metadata', 'jetpack_blogging_prompts_add_meta_data' );

/**
 * Sets up a new post as an answer to a blogging prompt (classic new-post screen).
 *
 * When we know a user is explicitly answering a prompt, pre-populate the post meta to mark the post as a prompt response,
 * in case they decide to remove the block from the post content, preventing they meta from being added later.
 *
 * REST creations (e.g. the Write editor's POST /wp/v2/posts?answer_prompt=…) are
 * handled by jetpack_setup_blogging_prompt_response_rest() on rest_after_insert_post
 * instead — that hook runs after the REST controller sets the request's tags, so the
 * prompt tags we add aren't overwritten.
 *
 * Called on `wp_insert_post` hook.
 *
 * @param int $post_id ID of post being inserted.
 * @return void
 */
function jetpack_setup_blogging_prompt_response( $post_id ) {
	if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
		return;
	}

	if ( ! jetpack_is_new_post_screen() ) {
		return;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Clicking a prompt response link can happen from notifications, Calypso, wp-admin, email, etc and only sets up a response post (tag, meta, prompt text); the user must take action to actually publish the post.
	$prompt_id = isset( $_GET['answer_prompt'] ) ? absint( $_GET['answer_prompt'] ) : 0;
	if ( $prompt_id ) {
		jetpack_apply_blogging_prompt_response( $post_id, $prompt_id );
	}
}

add_action( 'wp_insert_post', 'jetpack_setup_blogging_prompt_response' );

/**
 * Sets up a REST-created post (e.g. the Write editor) as an answer to a prompt.
 *
 * Runs on `rest_after_insert_post`, which fires after the REST controller has set
 * the request's tags — so the prompt tags added here survive.
 *
 * @param WP_Post         $post     Inserted post object.
 * @param WP_REST_Request $request  Request object.
 * @param bool            $creating True when creating, false when updating.
 * @return void
 */
function jetpack_setup_blogging_prompt_response_rest( $post, $request, $creating ) {
	if ( ! $creating || ! $post instanceof WP_Post || 'post' !== $post->post_type ) {
		return;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only GET param forwarded by the Write editor on the create request; only sets up a response post (tag, meta).
	$prompt_id = isset( $_GET['answer_prompt'] ) ? absint( $_GET['answer_prompt'] ) : 0;
	if ( $prompt_id ) {
		jetpack_apply_blogging_prompt_response( $post->ID, $prompt_id );
	}
}

add_action( 'rest_after_insert_post', 'jetpack_setup_blogging_prompt_response_rest', 10, 3 );

/**
 * Stamp a post as a blogging-prompt answer: prompt-key meta + prompt tags.
 *
 * Shared by the classic (wp_insert_post) and REST (rest_after_insert_post) entry
 * points so both mark the answer identically.
 *
 * @param int $post_id   Post ID.
 * @param int $prompt_id Prompt ID.
 * @return void
 */
function jetpack_apply_blogging_prompt_response( $post_id, $prompt_id ) {
	// Make sure the prompt exists.
	$prompt = jetpack_get_blogging_prompt_by_id( $prompt_id );

	if ( ! $prompt ) {
		return;
	}

	update_post_meta( $post_id, '_jetpack_blogging_prompt_key', $prompt_id );
	wp_add_post_tags( $post_id, array( 'dailyprompt', "dailyprompt-$prompt_id" ) );
	if ( is_array( $prompt ) && array_key_exists( 'bloganuary_id', $prompt ) ) {
		wp_add_post_tags( $post_id, array( 'bloganuary', $prompt['bloganuary_id'] ) );
	}
}

/**
 * When a published posts answers a blogging prompt, store the prompt id in the post meta.
 *
 * @param int          $post_id     Post ID.
 * @param WP_Post      $post        Post object.
 * @param bool         $update      Whether this is an existing post being updated.
 * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
 *                                  to the update for updated posts.
 */
function jetpack_mark_if_post_answers_blogging_prompt( $post_id, $post, $update, $post_before ) {
	if ( ! $post instanceof WP_Post ) {
		return;
	}

	$post_type    = $post->post_type ?? null;
	$post_content = $post->post_content ?? null;

	if ( 'post' !== $post_type || ! $post_content ) {
		return;
	}

	$new_status = $post->post_status ?? null;
	$old_status = $post_before && isset( $post_before->post_status ) ? $post_before->post_status : null;

	// Make sure we are publishing a post, and it's not already published.
	if ( 'publish' !== $new_status || 'publish' === $old_status ) {
		return;
	}

	$scanner = \Automattic\Block_Scanner::create( $post->post_content );
	if ( ! $scanner ) {
		return;
	}

	$prompt_id          = null;
	$total_blocks       = 0;
	$found_prompt_block = false;

	while ( $scanner->next_delimiter() ) {
		if ( $scanner->opens_block() ) {
			++$total_blocks;

			if ( ! $found_prompt_block && $scanner->is_block_type( 'jetpack/blogging-prompt' ) ) {
				$attributes = $scanner->allocate_and_return_parsed_attributes();
				if ( $attributes && isset( $attributes['promptId'] ) ) {
					$prompt_id = absint( $attributes['promptId'] );
				}
				$found_prompt_block = true;
			}

			// Early exit: if we found the prompt and have >1 blocks, we have all info needed
			if ( $found_prompt_block && $total_blocks > 1 ) {
				break;
			}
		}
	}

	if ( ! $found_prompt_block || ! $prompt_id || $total_blocks <= 1 ) {
		return;
	}

	$has_prompt_tag = has_tag( 'dailyprompt', $post ) || has_tag( "dailyprompt-{$prompt_id}", $post );

	if ( ! $has_prompt_tag ) {
		return;
	}

	update_post_meta( $post->ID, '_jetpack_blogging_prompt_key', $prompt_id );
}

add_action( 'wp_after_insert_post', 'jetpack_mark_if_post_answers_blogging_prompt', 10, 4 );

/**
 * Utility functions.
 */

/**
 * Build the blogging-prompts route for the REST context we're running in.
 *
 * The endpoint sets `wpcom_is_site_specific_endpoint`, so WordPress.com's
 * centralized REST API registers it site-scoped, as
 * `/wpcom/v3/sites/<blog_id>/blogging-prompts/<id>`. Everywhere else — Atomic,
 * self-hosted, and ordinary wp-admin requests on Simple — it registers at
 * `/wpcom/v3/blogging-prompts/<id>`. Which shape is live depends on the request
 * we happen to be running inside, and asking for the wrong one just 404s and
 * silently loses the prompt, so resolve it against the route table each time.
 *
 * Call this only after the endpoint file is required, so its routes are in
 * place by the time `rest_get_server()` fires `rest_api_init`.
 *
 * @since 16.2
 *
 * @param int $prompt_id ID of the prompt to fetch.
 * @return string REST route for that prompt.
 */
function jetpack_get_blogging_prompt_route( $prompt_id ) {
	foreach ( array_keys( rest_get_server()->get_routes() ) as $route ) {
		if ( str_starts_with( $route, '/wpcom/v3/blogging-prompts/' ) ) {
			return sprintf( '/wpcom/v3/blogging-prompts/%d', $prompt_id );
		}
	}

	return sprintf( '/wpcom/v3/sites/%d/blogging-prompts/%d', get_current_blog_id(), $prompt_id );
}

/**
 * Retrieve a blogging prompt by prompt ID.
 *
 * @param int $prompt_id ID of the prompt fetch.
 * @return array|null Prompt object or null.
 */
function jetpack_get_blogging_prompt_by_id( $prompt_id ) {
	// Ensure the REST API endpoint we need is loaded.
	require_once __DIR__ . '/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php';

	$locale = get_locale();
	$route  = jetpack_get_blogging_prompt_route( $prompt_id );

	$request = new WP_REST_Request( 'GET', $route );
	$request->set_param( '_locale', $locale );
	$request->set_param( 'force_year', gmdate( 'Y' ) );

	$response = rest_do_request( $request );

	if ( $response->is_error() || WP_Http::OK !== $response->get_status() ) {
		return null;
	}

	$prompt = $response->get_data();

	return $prompt;
}

/**
 * Retrieve daily blogging prompts from the wpcom API and cache them.
 *
 * @param int $time Unix timestamp representing the day for which to get blogging prompts.
 * @return stdClass[]|null Array of blogging prompt objects or null.
 */
function jetpack_get_daily_blogging_prompts( $time = 0 ) {
	$timestamp = $time ? $time : time();

	// Include prompts from the previous day, just in case someone has an outdated prompt id.
	$day_before    = wp_date( 'Y-m-d', $timestamp - DAY_IN_SECONDS );
	$locale        = get_locale();
	$transient_key = 'jetpack_blogging_prompt_' . $day_before . '_' . $locale;
	$daily_prompts = get_transient( $transient_key );

	// Return the cached prompt, if we have it. Otherwise fetch it from the API.
	if ( false !== $daily_prompts ) {
		return $daily_prompts;
	}

	$blog_id = \Jetpack_Options::get_option( 'id' );
	$path    = '/sites/' . rawurldecode( $blog_id ) . '/blogging-prompts?from=' . rawurldecode( $day_before ) . '&number=10&_locale=' . rawurldecode( $locale );

	$args = array(
		'headers' => array(
			'Content-Type'    => 'application/json',
			'X-Forwarded-For' => ( new \Automattic\Jetpack\Status\Visitor() )->get_ip( true ),
		),
		// `method` and `url` are needed for using `WPCOM_API_Direct::do_request`
		// `wpcom_json_api_request_as_user` will generate and overwrite these.
		'method'  => \WP_REST_Server::READABLE,
		'url'     => JETPACK__WPCOM_JSON_API_BASE . '/wpcom/v2' . $path,
	);

	if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
		// This will load the library, but it may be too late to automatically load any endpoints using WPCOM_API_Direct::register_endpoints.
		// In that case, call `wpcom_rest_api_v2_load_plugin_files( 'wp-content/rest-api-plugins/endpoints/blogging-prompts.php' )`
		// on the `init` hook to load the blogging-prompts endpoint before calling this function.
		require_once WP_CONTENT_DIR . '/lib/wpcom-api-direct/wpcom-api-direct.php';
		$response = \WPCOM_API_Direct::do_request( $args );
	} else {
		$response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user( $path, 'v2', $args, null, 'wpcom' );
	}
	$response_status = wp_remote_retrieve_response_code( $response );

	if ( is_wp_error( $response ) || $response_status !== \WP_Http::OK ) {
		return null;
	}

	$body = json_decode( wp_remote_retrieve_body( $response ) );

	if ( ! $body || ! isset( $body->prompts ) ) {
		return null;
	}

	$prompts = $body->prompts;
	set_transient( $transient_key, $prompts, DAY_IN_SECONDS );

	return $prompts;
}

/**
 * Determines if the site has publish posts or plans to publish posts.
 *
 * @return bool
 */
function jetpack_has_or_will_publish_posts() {
	// Lets count the posts.
	$count_posts_object = wp_count_posts( 'post' );
	$count_posts        = (int) $count_posts_object->publish + (int) $count_posts_object->future + (int) $count_posts_object->draft;

	return $count_posts_object->publish >= 2 || $count_posts >= 100;
}

/**
 * Determines if the site has a posts page or shows posts on the front page.
 *
 * @return bool
 */
function jetpack_has_posts_page() {
	// The site is set up to be a blog.
	if ( 'posts' === get_option( 'show_on_front' ) ) {
		return true;
	}

	// There is a page set to show posts.
	$is_posts_page_set = (int) get_option( 'page_for_posts' ) > 0;
	if ( $is_posts_page_set ) {
		return true;
	}

	return false;
}

/**
 * Determines if site had the "Write" intent set when created.
 *
 * @return bool
 */
function jetpack_has_write_intent() {
	return 'write' === get_option( 'site_intent', '' );
}

/**
 * Determines if the current screen (in wp-admin) is creating a new post.
 *
 * /wp-admin/post-new.php
 *
 * @return bool
 */
function jetpack_is_new_post_screen() {
	global $current_screen;

	if (
		$current_screen instanceof \WP_Screen &&
		'add' === $current_screen->action &&
		'post' === $current_screen->post_type
	) {
		return true;
	}

	return false;
}

/**
 * Determines if the site might have a blog.
 *
 * @return bool
 */
function jetpack_is_potential_blogging_site() {
	return jetpack_has_write_intent() || jetpack_has_posts_page() || jetpack_has_or_will_publish_posts();
}
