<?php
/**
 * BaseCloud Boost — One-time upgrade migrations
 *
 * Data migrations that must run exactly once on existing installs when they
 * update the plugin. Unlike Activator::maybe_upgrade() (which re-runs
 * idempotent environment refreshes on every version change), each migration
 * here records a completion flag so a client's deliberate re-configuration is
 * never overridden twice.
 *
 * @package BaseCloud\Boost\Core
 */

declare( strict_types=1 );

namespace BaseCloud\Boost\Core;

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

/**
 * Runs version-gated, once-only option migrations and their admin notices.
 */
class Upgrade {

	/**
	 * Flag option: stores the plugin version at which the "remove_query_strings"
	 * force-disable migration ran. Its presence means "never run again", so a
	 * deliberate re-enable by the site owner sticks.
	 */
	private const RQS_MIGRATED_OPTION = 'bcboost_rqs_migrated';

	/**
	 * Flag option: a truthy value queues the dismissible admin notice explaining
	 * why "Remove query strings" was switched off.
	 */
	private const RQS_NOTICE_OPTION = 'bcboost_rqs_notice';

	/**
	 * Nonce action for dismissing the notice.
	 */
	private const RQS_DISMISS_ACTION = 'bcboost_dismiss_rqs_notice';

	/**
	 * Flag option: stores the version at which the "serve_avif" force-disable
	 * migration ran. Presence = never run again (a deliberate re-enable sticks).
	 */
	private const AVIF_MIGRATED_OPTION = 'bcboost_avif_migrated';

	/** Flag option: queues the dismissible notice explaining the AVIF switch-off. */
	private const AVIF_NOTICE_OPTION = 'bcboost_avif_notice';

	/** Nonce action for dismissing the AVIF notice. */
	private const AVIF_DISMISS_ACTION = 'bcboost_dismiss_avif_notice';

	/**
	 * Register hooks. Priority 5 on admin_init so the migration lands before
	 * Activator::maybe_upgrade() (priority 10) refreshes .htaccess / the options
	 * cache and purges — the refreshed state then reflects the migrated options.
	 *
	 * @return void
	 */
	public function boot(): void {
		add_action( 'admin_init', [ $this, 'maybe_migrate' ], 5 );
		add_action( 'admin_init', [ $this, 'maybe_migrate_avif' ], 5 );
		add_action( 'admin_init', [ $this, 'handle_notice_dismiss' ], 6 );
		add_action( 'admin_init', [ $this, 'handle_avif_notice_dismiss' ], 6 );
		add_action( 'admin_notices', [ $this, 'render_notice' ] );
		add_action( 'admin_notices', [ $this, 'render_avif_notice' ] );
	}

	/**
	 * One-time migration: force-disable `remove_query_strings` on installs that
	 * still have the old default (true) saved.
	 *
	 * Why: stripping ?ver= removes the browser cache-busting token while our
	 * .htaccess rules give CSS/JS long browser lifetimes — edited assets kept the
	 * same URL and visitors saw month-old CSS after every "Boost Cache". The
	 * option has zero PageSpeed benefit, so existing installs are switched off
	 * once. The completion flag ensures a deliberate re-enable afterwards is
	 * respected forever.
	 *
	 * @return void
	 */
	public function maybe_migrate(): void {
		if ( '' !== (string) get_option( self::RQS_MIGRATED_OPTION, '' ) ) {
			return; // Already migrated (any prior version) — never touch it again.
		}

		try {
			// Fresh-from-DB read: never base a migration write on a possibly-stale
			// object-cache copy (see Settings_API::persist()).
			wp_cache_delete( BCBOOST_OPTIONS_KEY, 'options' );
			wp_cache_delete( 'alloptions', 'options' );
			$options = get_option( BCBOOST_OPTIONS_KEY, [] );

			if ( is_array( $options ) && ! empty( $options['remove_query_strings'] ) ) {
				$options['remove_query_strings'] = false;
				\BaseCloud\Boost\Admin\Settings_API::persist( $options );

				// Queue the explanatory notice (autoload off — it is admin-only).
				update_option( self::RQS_NOTICE_OPTION, 1, false );

				if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
					error_log( '[BaseCloud Boost] Upgrade migration: force-disabled "remove_query_strings" (stale-asset risk, no PSI benefit). Re-enabling it later will stick.' );
				}
			}
		} catch ( \Throwable $e ) {
			// Fail open — a migration must never break wp-admin. Retry next load
			// by NOT recording completion.
			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
				error_log( '[BaseCloud Boost] Upgrade migration failed: ' . $e->getMessage() );
			}
			return;
		}

		update_option( self::RQS_MIGRATED_OPTION, BCBOOST_VERSION, false );
	}

	/**
	 * One-time migration (1.3.5): force-disable `serve_avif` on installs that
	 * still have the old default (true) saved.
	 *
	 * Why: AVIF proved fragile in the field — a host's encoder emitted
	 * complete-but-undecodable files that rendered as blank/broken images
	 * (bfap.co.za), and validating AVIF requires a same-host decoder many
	 * servers lack. WebP delivers most of the byte savings with none of that
	 * risk, so WebP-only is now the standard; AVIF stays available as a
	 * deliberate opt-in, and re-enabling it after this migration sticks.
	 *
	 * @return void
	 */
	public function maybe_migrate_avif(): void {
		if ( '' !== (string) get_option( self::AVIF_MIGRATED_OPTION, '' ) ) {
			return; // Already migrated — never touch the setting again.
		}

		try {
			// Fresh-from-DB read (see Settings_API::persist()).
			wp_cache_delete( BCBOOST_OPTIONS_KEY, 'options' );
			wp_cache_delete( 'alloptions', 'options' );
			$options = get_option( BCBOOST_OPTIONS_KEY, [] );

			if ( is_array( $options ) && ! empty( $options['serve_avif'] ) ) {
				$options['serve_avif'] = false;
				\BaseCloud\Boost\Admin\Settings_API::persist( $options );

				update_option( self::AVIF_NOTICE_OPTION, 1, false );

				if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
					error_log( '[BaseCloud Boost] Upgrade migration: force-disabled "serve_avif" (field reliability — WebP-only is the new standard). Re-enabling it later will stick.' );
				}
			}
		} catch ( \Throwable $e ) {
			// Fail open; retry next load by not recording completion.
			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
				error_log( '[BaseCloud Boost] AVIF upgrade migration failed: ' . $e->getMessage() );
			}
			return;
		}

		update_option( self::AVIF_MIGRATED_OPTION, BCBOOST_VERSION, false );
	}

	/**
	 * Render the dismissible notice explaining the forced setting change.
	 *
	 * @return void
	 */
	public function render_notice(): void {
		if ( ! current_user_can( 'manage_options' ) || ! get_option( self::RQS_NOTICE_OPTION ) ) {
			return;
		}

		// Raw URL (no HTML entities): wp_nonce_url() would emit &#038;, whose "#"
		// truncates the URL when used inside fetch(). esc_url() re-adds entities
		// safely for the href context only.
		$dismiss_url = add_query_arg( [
			'bcboost_dismiss_rqs_notice' => '1',
			'_wpnonce'                   => wp_create_nonce( self::RQS_DISMISS_ACTION ),
		] );

		printf(
			'<div class="notice notice-warning is-dismissible" id="bcboost-rqs-notice"><p><strong>%s</strong> %s <a href="%s">%s</a></p></div>',
			esc_html__( 'BaseCloud Boost disabled “Remove query strings”', 'basecloud-boost' ),
			esc_html__( '— it caused visitors to see outdated CSS/JS after cache clears, and it has no PageSpeed benefit. If you re-enable it under Advanced settings, your choice will be kept.', 'basecloud-boost' ),
			esc_url( $dismiss_url ),
			esc_html__( 'Dismiss', 'basecloud-boost' )
		);

		// Make WordPress's own dismiss (×) button persist the dismissal too.
		printf(
			'<script>document.addEventListener("click",function(e){var n=document.getElementById("bcboost-rqs-notice");if(n&&n.contains(e.target)&&e.target.classList.contains("notice-dismiss")){fetch(%s,{credentials:"same-origin"});}});</script>',
			wp_json_encode( esc_url_raw( $dismiss_url ) )
		);
	}

	/**
	 * Render the dismissible notice for the AVIF switch-off.
	 *
	 * @return void
	 */
	public function render_avif_notice(): void {
		if ( ! current_user_can( 'manage_options' ) || ! get_option( self::AVIF_NOTICE_OPTION ) ) {
			return;
		}

		$dismiss_url = add_query_arg( [
			'bcboost_dismiss_avif_notice' => '1',
			'_wpnonce'                    => wp_create_nonce( self::AVIF_DISMISS_ACTION ),
		] );

		printf(
			'<div class="notice notice-info is-dismissible" id="bcboost-avif-notice"><p><strong>%s</strong> %s <a href="%s">%s</a></p></div>',
			esc_html__( 'BaseCloud Boost switched image serving to WebP-only', 'basecloud-boost' ),
			esc_html__( '— AVIF caused broken or blank images on some servers, while WebP delivers nearly the same savings reliably. Your original images and existing files are untouched. You can re-enable AVIF under Media settings and your choice will be kept.', 'basecloud-boost' ),
			esc_url( $dismiss_url ),
			esc_html__( 'Dismiss', 'basecloud-boost' )
		);

		printf(
			'<script>document.addEventListener("click",function(e){var n=document.getElementById("bcboost-avif-notice");if(n&&n.contains(e.target)&&e.target.classList.contains("notice-dismiss")){fetch(%s,{credentials:"same-origin"});}});</script>',
			wp_json_encode( esc_url_raw( $dismiss_url ) )
		);
	}

	/**
	 * Handle the persistent dismissal of the AVIF notice.
	 *
	 * @return void
	 */
	public function handle_avif_notice_dismiss(): void {
		if ( ! isset( $_GET['bcboost_dismiss_avif_notice'] ) ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		check_admin_referer( self::AVIF_DISMISS_ACTION );

		delete_option( self::AVIF_NOTICE_OPTION );

		wp_safe_redirect( remove_query_arg( [ 'bcboost_dismiss_avif_notice', '_wpnonce' ] ) );
		exit;
	}

	/**
	 * Handle the persistent dismissal of the notice (link click or × via fetch).
	 *
	 * @return void
	 */
	public function handle_notice_dismiss(): void {
		if ( ! isset( $_GET['bcboost_dismiss_rqs_notice'] ) ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		check_admin_referer( self::RQS_DISMISS_ACTION );

		delete_option( self::RQS_NOTICE_OPTION );

		// Plain link navigation gets bounced back without the dismiss args;
		// background fetch() from the × button ignores the redirect.
		wp_safe_redirect( remove_query_arg( [ 'bcboost_dismiss_rqs_notice', '_wpnonce' ] ) );
		exit;
	}
}
