<?php
/**
 * Costudio_Review_Prompt — asks for a WordPress.org review after the first
 * successfully completed production.
 *
 * Guideline-safe by design:
 * - Renders ONLY on the plugin's own admin pages (Guideline 11 — no global nags).
 * - Never shows before the user has experienced value (>= 1 completed production).
 * - Every choice is explicit and persisted: "Maybe later" snoozes for 14 days,
 *   "Don't show again" hides it forever.
 * - Zero JavaScript: actions are nonce-verified admin links handled on admin_init.
 *
 * @package Contentosapp_Studio
 * @since   1.2.7
 */

defined( 'ABSPATH' ) || exit;

class Costudio_Review_Prompt {

	/** Option holding the prompt state (first success timestamp + user choice). */
	const OPTION = 'costudio_review_prompt_state';

	/**
	 * Transient that caches "no completed production yet" so pre-existing
	 * installs (whose productions completed before this feature shipped and
	 * therefore never fired the recorder hook) are backfilled with at most
	 * one existence query per day.
	 */
	const NO_SUCCESS_TRANSIENT = 'costudio_review_no_success';

	/** WordPress.org new-review URL for this plugin. */
	const REVIEW_URL = 'https://wordpress.org/support/plugin/contentosapp-studio/reviews/#new-post';

	/** How long "Maybe later" hides the prompt. */
	const SNOOZE_SECONDS = 14 * DAY_IN_SECONDS;

	/**
	 * Wire hooks.
	 *
	 * The completion recorder MUST be registered unconditionally: productions
	 * finish inside background REST/cron requests where is_admin() is false.
	 */
	public static function register_hooks(): void {
		add_action( 'costudio_production_completed', array( __CLASS__, 'record_success' ) );

		if ( is_admin() ) {
			add_action( 'admin_init', array( __CLASS__, 'handle_action' ) );
			add_action( 'admin_notices', array( __CLASS__, 'render_notice' ) );
		}
	}

	/**
	 * Record the moment of the first successful production.
	 *
	 * Hooked to costudio_production_completed (fires in background context).
	 */
	public static function record_success(): void {
		$state = self::get_state();
		if ( ! empty( $state['first_success_at'] ) ) {
			return;
		}
		$state['first_success_at'] = time();
		update_option( self::OPTION, $state );
		delete_transient( self::NO_SUCCESS_TRANSIENT );
	}

	/**
	 * Handle the notice's "Maybe later" / "Don't show again" links.
	 *
	 * Plain nonce-verified GET links (no JS): the notice re-renders on the
	 * redirected request with the persisted state applied.
	 */
	public static function handle_action(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- presence check only; the nonce is verified below before any state change.
		if ( ! isset( $_GET['costudio_review_action'] ) ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified right below.
		$action = sanitize_key( wp_unslash( $_GET['costudio_review_action'] ) );
		$nonce  = isset( $_GET['_costudio_review_nonce'] )
			? sanitize_text_field( wp_unslash( $_GET['_costudio_review_nonce'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this IS the nonce being verified.
			: '';

		if ( ! wp_verify_nonce( $nonce, 'costudio_review_prompt' ) ) {
			return;
		}

		$state = self::get_state();

		if ( 'dismiss' === $action ) {
			$state['status']       = 'dismissed';
			$state['snooze_until'] = 0;
		} elseif ( 'snooze' === $action ) {
			$state['status']       = 'snoozed';
			$state['snooze_until'] = time() + self::SNOOZE_SECONDS;
		} else {
			return;
		}

		update_option( self::OPTION, $state );

		wp_safe_redirect( remove_query_arg( array( 'costudio_review_action', '_costudio_review_nonce' ) ) );
		exit;
	}

	/**
	 * Render the review request on the plugin's own admin pages.
	 */
	public static function render_notice(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		// Limit to the plugin's own admin pages (Guideline 11 — no global admin nags).
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin routing parameter.
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
		if ( strpos( $page, 'costudio' ) !== 0 || 'costudio-setup' === $page ) {
			return;
		}

		$state = self::get_state();

		if ( 'dismissed' === $state['status'] ) {
			return;
		}
		if ( 'snoozed' === $state['status'] && time() < (int) $state['snooze_until'] ) {
			return;
		}
		if ( ! self::has_completed_production() ) {
			return;
		}

		$snooze_url  = wp_nonce_url(
			add_query_arg( 'costudio_review_action', 'snooze' ),
			'costudio_review_prompt',
			'_costudio_review_nonce'
		);
		$dismiss_url = wp_nonce_url(
			add_query_arg( 'costudio_review_action', 'dismiss' ),
			'costudio_review_prompt',
			'_costudio_review_nonce'
		);

		// Deliberately NOT a `.notice` element: WordPress core JS relocates
		// div.notice after the page's first heading, which drops it inside the
		// plugin's hero header. The plugin's banner pattern (see
		// fos-tel-consent-banner) renders in place, above the page content.
		?>
		<div class="fos-review-banner" role="region" aria-label="<?php esc_attr_e( 'Enjoying Contentosapp Studio?', 'contentosapp-studio' ); ?>">
			<div class="fos-review-banner__icon" aria-hidden="true">⭐</div>
			<div class="fos-review-banner__body">
				<h3 class="fos-review-banner__title">
					<?php esc_html_e( 'Enjoying Contentosapp Studio?', 'contentosapp-studio' ); ?>
				</h3>
				<p class="fos-review-banner__desc">
					<?php esc_html_e( 'Your article was produced successfully. If the 7-agent pipeline saved you time, would you leave a quick review on WordPress.org? It helps other publishers find the plugin — and genuinely helps us keep improving it.', 'contentosapp-studio' ); ?>
				</p>
				<div class="fos-review-banner__actions">
					<a class="fos-tel-btn fos-tel-btn--primary" href="<?php echo esc_url( self::REVIEW_URL ); ?>" target="_blank" rel="noopener noreferrer">
						★ <?php esc_html_e( 'Review on WordPress.org', 'contentosapp-studio' ); ?>
					</a>
					<a class="fos-tel-btn fos-tel-btn--ghost" href="<?php echo esc_url( $snooze_url ); ?>">
						<?php esc_html_e( 'Maybe later', 'contentosapp-studio' ); ?>
					</a>
					<a class="fos-tel-link" href="<?php echo esc_url( $dismiss_url ); ?>">
						<?php esc_html_e( "I already did — don't show this again", 'contentosapp-studio' ); ?>
					</a>
				</div>
			</div>
		</div>
		<?php
	}

	// ── Internal helpers ──────────────────────────────────────────────────────

	/**
	 * Current prompt state merged over defaults.
	 *
	 * @return array{first_success_at:int,status:string,snooze_until:int}
	 */
	private static function get_state(): array {
		$state = get_option( self::OPTION, array() );
		if ( ! is_array( $state ) ) {
			$state = array();
		}
		return array_merge(
			array(
				'first_success_at' => 0,
				'status'           => '',
				'snooze_until'     => 0,
			),
			$state
		);
	}

	/**
	 * Whether this install has at least one successfully completed production.
	 *
	 * Fast path: the recorder hook already stamped the option. Backfill path
	 * (installs whose successes predate this feature): a single indexed
	 * existence check, with a daily negative-result cache.
	 */
	private static function has_completed_production(): bool {
		$state = self::get_state();
		if ( ! empty( $state['first_success_at'] ) ) {
			return true;
		}

		if ( get_transient( self::NO_SUCCESS_TRANSIENT ) ) {
			return false;
		}

		global $wpdb;
		$table = $wpdb->prefix . Costudio_Production::TABLE;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotSafe -- single existence check on a plugin-owned table (name from a class constant, not user input); positive result is persisted in an option, negative result cached in a transient.
		$found = (int) $wpdb->get_var( "SELECT id FROM {$table} WHERE status = 'completed' LIMIT 1" );

		if ( $found > 0 ) {
			self::record_success();
			return true;
		}

		set_transient( self::NO_SUCCESS_TRANSIENT, 1, DAY_IN_SECONDS );
		return false;
	}
}
