<?php
/**
 * Admin UI and AJAX layer.
 *
 * @package NaveenCodesImageOptimizer
 */

namespace NaveenCodesImageOptimizer;

use WP_Query;

defined( 'ABSPATH' ) || exit;

/**
 * Admin screens, notice logic, AJAX actions, and media library integration.
 */
class Admin {

	/**
	 * User meta key for the donation notice dismissal.
	 */
	const NOTICE_META_KEY = 'naveencodes_donation_notice_dismissed';

	/**
	 * User meta key for the review notice dismissal.
	 */
	const REVIEW_NOTICE_META_KEY = 'naveencodes_review_notice_dismissed';

	/**
	 * Option key for bulk scan session state.
	 */
	const BULK_SESSION_OPTION = 'naveencodes_bulk_session';

	/**
	 * Settings.
	 *
	 * @var Settings
	 */
	protected $settings;

	/**
	 * Queue.
	 *
	 * @var Queue
	 */
	protected $queue;

	/**
	 * Logger.
	 *
	 * @var Logger
	 */
	protected $logger;

	/**
	 * Optimizer.
	 *
	 * @var Optimizer
	 */
	protected $optimizer;

	/**
	 * API client.
	 *
	 * @var Api_Client
	 */
	protected $api_client;

	/**
	 * Constructor.
	 *
	 * @param Settings   $settings  Settings.
	 * @param Queue      $queue     Queue.
	 * @param Logger     $logger    Logger.
	 * @param Optimizer  $optimizer Optimizer.
	 * @param Api_Client $api       API client.
	 */
	public function __construct( Settings $settings, Queue $queue, Logger $logger, Optimizer $optimizer, Api_Client $api ) {
		$this->settings   = $settings;
		$this->queue      = $queue;
		$this->logger     = $logger;
		$this->optimizer  = $optimizer;
		$this->api_client = $api;

		add_action( 'admin_menu', array( $this, 'register_menu' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
		add_action( 'admin_notices', array( $this, 'maybe_render_donation_notice' ) );
		add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) );
		add_filter( 'admin_footer_text', array( $this, 'add_tribute_footer' ) );

		add_action( 'wp_ajax_naveencodes_start_bulk_queue', array( $this, 'ajax_start_bulk_queue' ) );
		add_action( 'wp_ajax_naveencodes_process_queue_batch', array( $this, 'ajax_process_queue_batch' ) );
		add_action( 'wp_ajax_naveencodes_get_progress', array( $this, 'ajax_get_progress' ) );
		add_action( 'wp_ajax_naveencodes_clear_logs', array( $this, 'ajax_clear_logs' ) );
		add_action( 'wp_ajax_naveencodes_dismiss_notice', array( $this, 'ajax_dismiss_notice' ) );
		add_action( 'wp_ajax_naveencodes_optimize_attachment', array( $this, 'ajax_optimize_attachment' ) );
		add_action( 'wp_ajax_naveencodes_register_site', array( $this, 'ajax_register_site' ) );

		add_filter( 'plugin_action_links_' . NAVEENCODES_BASENAME, array( $this, 'add_plugin_action_links' ) );
		add_filter( 'manage_upload_columns', array( $this, 'register_media_column' ) );
		add_action( 'manage_media_custom_column', array( $this, 'render_media_column' ), 10, 2 );
	}

	/**
	 * Add quick links on the Plugins screen.
	 *
	 * @param array $links Existing action links.
	 * @return array
	 */
	public function add_plugin_action_links( $links ) {
		$settings_link = sprintf(
			'<a href="%s">%s</a>',
			esc_url( admin_url( 'admin.php?page=naveencodes-settings' ) ),
			esc_html__( 'Settings', 'naveencodes-image-optimizer' )
		);

		array_unshift( $links, $settings_link );

		return $links;
	}

	/**
	 * Register the admin menu tree.
	 *
	 * @return void
	 */
	public function register_menu() {
		$capability = 'manage_options';
		$slug       = 'naveencodes-image-optimizer';

		add_menu_page(
			__( 'NaveenCodes Image Optimizer', 'naveencodes-image-optimizer' ),
			__( 'NaveenCodes Image Optimizer', 'naveencodes-image-optimizer' ),
			$capability,
			$slug,
			array( $this, 'render_dashboard_page' ),
			'dashicons-format-image',
			58
		);

		add_submenu_page( $slug, __( 'Dashboard', 'naveencodes-image-optimizer' ), __( 'Dashboard', 'naveencodes-image-optimizer' ), $capability, $slug, array( $this, 'render_dashboard_page' ) );
		add_submenu_page( $slug, __( 'Optimize Media', 'naveencodes-image-optimizer' ), __( 'Optimize Media', 'naveencodes-image-optimizer' ), $capability, 'naveencodes-optimize-media', array( $this, 'render_optimize_media_page' ) );
		add_submenu_page( $slug, __( 'Batch Settings', 'naveencodes-image-optimizer' ), __( 'Batch Settings', 'naveencodes-image-optimizer' ), $capability, 'naveencodes-settings', array( $this, 'render_settings_page' ) );
		add_submenu_page( $slug, __( 'Logs', 'naveencodes-image-optimizer' ), __( 'Logs', 'naveencodes-image-optimizer' ), $capability, 'naveencodes-logs', array( $this, 'render_logs_page' ) );
		add_submenu_page( $slug, __( 'Support Development', 'naveencodes-image-optimizer' ), __( 'Donate', 'naveencodes-image-optimizer' ), $capability, 'naveencodes-donate', array( $this, 'render_donate_page' ) );
	}

	/**
	 * Load scripts/styles only on relevant admin screens.
	 *
	 * @param string $hook_suffix Current screen hook.
	 * @return void
	 */
	public function enqueue_assets( $hook_suffix ) {
		$allowed = false !== strpos( $hook_suffix, 'naveencodes-image-optimizer' ) || false !== strpos( $hook_suffix, 'naveencodes-' ) || 'upload.php' === $hook_suffix || 'index.php' === $hook_suffix;

		if ( ! $allowed ) {
			return;
		}

		$style_path    = NAVEENCODES_DIR . 'assets/css/admin.css';
		$script_path   = NAVEENCODES_DIR . 'assets/js/admin.js';
		$style_version = file_exists( $style_path ) ? (string) filemtime( $style_path ) : NAVEENCODES_VERSION;
		$script_version = file_exists( $script_path ) ? (string) filemtime( $script_path ) : NAVEENCODES_VERSION;

		wp_enqueue_style(
			'naveencodes-admin',
			NAVEENCODES_URL . 'assets/css/admin.css',
			array(),
			$style_version
		);

		wp_enqueue_script(
			'naveencodes-admin',
			NAVEENCODES_URL . 'assets/js/admin.js',
			array(),
			$script_version,
			true
		);

		$localize_data = array(
			'ajaxUrl'     => admin_url( 'admin-ajax.php' ),
			'nonce'       => wp_create_nonce( 'naveencodes_admin_nonce' ),
			'bulkSession' => $this->get_bulk_session(),
			'strings'     => array(
				'queueing'             => __( 'Scanning your media library and preparing the queue...', 'naveencodes-image-optimizer' ),
				'processing'           => __( 'Optimizing queued images...', 'naveencodes-image-optimizer' ),
				'completed'            => __( 'Bulk optimization completed.', 'naveencodes-image-optimizer' ),
				'error'                => __( 'An unexpected error occurred.', 'naveencodes-image-optimizer' ),
				'cleared'              => __( 'Logs cleared.', 'naveencodes-image-optimizer' ),
				'confirmClearLogs'     => __( 'Clear all stored optimizer logs?', 'naveencodes-image-optimizer' ),
				'optimizingSingle'     => __( 'Optimizing image...', 'naveencodes-image-optimizer' ),
				'singleOptimized'      => __( 'Image optimized successfully.', 'naveencodes-image-optimizer' ),
				'registering'          => __( 'Registering this website...', 'naveencodes-image-optimizer' ),
				'registered'           => __( 'Website registration is active.', 'naveencodes-image-optimizer' ),
				'refreshing'           => __( 'Refreshing progress...', 'naveencodes-image-optimizer' ),
				'reoptimize'           => __( 'Re-optimize', 'naveencodes-image-optimizer' ),
				'optimizeNow'          => __( 'Optimize Now', 'naveencodes-image-optimizer' ),
				'processingAttachment' => __( 'Processing...', 'naveencodes-image-optimizer' ),
				'startBulk'            => __( 'Start Bulk Optimization', 'naveencodes-image-optimizer' ),
				'resumeScan'           => __( 'Resume Scan', 'naveencodes-image-optimizer' ),
				'resumeOptimization'   => __( 'Resume Optimization', 'naveencodes-image-optimizer' ),
			),
		);

		if ( false !== strpos( $hook_suffix, 'naveencodes-optimize-media' ) ) {
			$localize_data['initialQueueCounts'] = $this->queue->get_status_counts();
		}

		wp_localize_script( 'naveencodes-admin', 'naveencodesAdmin', $localize_data );
	}

	/**
	 * Render dashboard page.
	 *
	 * @return void
	 */
	public function render_dashboard_page() {
		$this->render_template(
			'dashboard.php',
			array(
				'stats'        => $this->optimizer->get_stats(),
				'queue_counts' => $this->queue->get_status_counts(),
				'health'       => $this->get_cached_health_status(),
				'registration' => $this->get_registration_state(),
				'settings'     => $this->settings->all(),
				'recent_items' => $this->get_recent_attachments( 6 ),
				'banner'       => $this->get_admin_banner_data( 'dashboard' ),
			)
		);
	}

	/**
	 * Render bulk optimize page.
	 *
	 * @return void
	 */
	public function render_optimize_media_page() {
		$this->render_template(
			'optimize-media.php',
			array(
				'queue_counts'        => $this->queue->get_status_counts(),
				'stats'               => $this->optimizer->get_stats(),
				'health'              => $this->get_cached_health_status(),
				'settings'            => $this->settings->all(),
				'queue_sections_html' => $this->get_queue_sections_html(),
				'banner'              => $this->get_admin_banner_data( 'optimize' ),
				'bulk_session'        => $this->get_bulk_session(),
			)
		);
	}

	/**
	 * Render settings page.
	 *
	 * @return void
	 */
	public function render_settings_page() {
		$this->render_template(
			'settings.php',
			array(
				'health'       => $this->get_cached_health_status(),
				'registration' => $this->get_registration_state(),
				'settings'     => $this->settings->all(),
				'banner'       => $this->get_admin_banner_data( 'settings' ),
			)
		);
	}

	/**
	 * Render logs page.
	 *
	 * @return void
	 */
	public function render_logs_page() {
		$this->render_template(
			'logs.php',
			array(
				'logs' => $this->logger->get_logs( 100 ),
				'banner' => $this->get_admin_banner_data( 'logs' ),
			)
		);
	}

	/**
	 * Render donation page.
	 *
	 * @return void
	 */
	public function render_donate_page() {
		$this->render_template(
			'donate.php',
			array(
				'stats' => $this->optimizer->get_stats(),
				'banner' => $this->get_admin_banner_data( 'donate' ),
			)
		);
	}

	/**
	 * Register a non-intrusive dashboard widget on the main WordPress dashboard.
	 *
	 * @return void
	 */
	public function register_dashboard_widget() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		wp_add_dashboard_widget(
			'naveencodes_dashboard_widget',
			__( 'NaveenCodes Image Optimizer', 'naveencodes-image-optimizer' ),
			array( $this, 'render_dashboard_widget' )
		);
	}

	/**
	 * Render the WordPress dashboard widget.
	 *
	 * @return void
	 */
	public function render_dashboard_widget() {
		$stats        = $this->optimizer->get_stats();
		$queue_counts = $this->queue->get_status_counts();
		$health       = $this->get_cached_health_status();
		?>
		<div class="naveencodes-dashboard-widget">
			<p><?php esc_html_e( 'Unlimited image optimization, bulk processing, and per-image reporting from one workflow.', 'naveencodes-image-optimizer' ); ?></p>
			<div class="naveencodes-dashboard-widget-grid">
				<div><strong><?php echo esc_html( number_format_i18n( (int) $stats['total_optimized'] ) ); ?></strong><span><?php esc_html_e( 'Optimized', 'naveencodes-image-optimizer' ); ?></span></div>
				<div><strong><?php echo esc_html( size_format( (int) $stats['total_saved_bytes'], 2 ) ); ?></strong><span><?php esc_html_e( 'Saved', 'naveencodes-image-optimizer' ); ?></span></div>
				<div><strong><?php echo esc_html( number_format_i18n( (int) $queue_counts['pending'] ) ); ?></strong><span><?php esc_html_e( 'Queued', 'naveencodes-image-optimizer' ); ?></span></div>
				<div><strong><?php echo esc_html( $health['label'] ); ?></strong><span><?php esc_html_e( 'Health', 'naveencodes-image-optimizer' ); ?></span></div>
			</div>
			<p class="naveencodes-dashboard-widget-actions">
				<a class="button button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=naveencodes-optimize-media' ) ); ?>"><?php esc_html_e( 'Open Optimize Media', 'naveencodes-image-optimizer' ); ?></a>
				<a class="button button-secondary" href="<?php echo esc_url( admin_url( 'admin.php?page=naveencodes-image-optimizer' ) ); ?>"><?php esc_html_e( 'Open Plugin Dashboard', 'naveencodes-image-optimizer' ); ?></a>
			</p>
		</div>
		<?php
	}

	/**
	 * Bulk queue attachments a page at a time.
	 *
	 * @return void
	 */
	public function ajax_start_bulk_queue() {
		$this->verify_ajax_request();

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in verify_ajax_request().
		$page     = max( 1, absint( $_POST['page'] ?? 1 ) );
		$per_page = 100;
		$query    = new WP_Query(
			array(
				'post_type'      => 'attachment',
				'post_status'    => 'inherit',
				'post_mime_type' => 'image',
				'fields'         => 'ids',
				'posts_per_page' => $per_page,
				'paged'          => $page,
				'orderby'        => 'ID',
				'order'          => 'ASC',
				'no_found_rows'  => false,
			)
		);

		$queued = 0;

		$session = $this->get_bulk_session();
		if ( 1 === $page ) {
			$session = array(
				'scan_complete' => false,
				'last_page'     => 0,
				'total_pages'   => 0,
				'started_at'    => time(),
			);
		}

		foreach ( (array) $query->posts as $attachment_id ) {
			if ( ! $this->optimizer->is_supported_mime( get_post_mime_type( $attachment_id ) ) ) {
				continue;
			}

			if ( $this->queue->enqueue_attachment( $attachment_id ) ) {
				$queued++;
			}
		}

		$is_last_page             = $page >= (int) $query->max_num_pages;
		$session['last_page']     = $page;
		$session['total_pages']   = (int) $query->max_num_pages;
		$session['scan_complete'] = $is_last_page;
		update_option( self::BULK_SESSION_OPTION, $session, false );

		wp_send_json_success(
			array(
				'queued'      => $queued,
				'next_page'   => $page + 1,
				'completed'   => $is_last_page,
				'total_pages' => (int) $query->max_num_pages,
				'session'     => $session,
				'progress'    => $this->build_progress_payload(),
			)
		);
	}

	/**
	 * Process one queue batch.
	 *
	 * @return void
	 */
	public function ajax_process_queue_batch() {
		$this->verify_ajax_request();

		wp_send_json_success(
			array(
				'results'  => $this->optimizer->process_queue_batch(),
				'progress' => $this->build_progress_payload(),
			)
		);
	}

	/**
	 * Fetch current queue and analytics progress.
	 *
	 * @return void
	 */
	public function ajax_get_progress() {
		$this->verify_ajax_request();
		wp_send_json_success( $this->build_progress_payload() );
	}

	/**
	 * Optimize one attachment directly from the Media Library.
	 *
	 * @return void
	 */
	public function ajax_optimize_attachment() {
		$this->verify_ajax_request();

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in verify_ajax_request().
		$attachment_id = absint( $_POST['attachment_id'] ?? 0 );

		if ( ! $attachment_id ) {
			wp_send_json_error(
				array(
					'message' => __( 'Attachment ID is missing.', 'naveencodes-image-optimizer' ),
				),
				400
			);
		}

		$result = $this->optimizer->optimize_attachment( $attachment_id, 'manual-admin' );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error(
				array(
					'message'   => $result->get_error_message(),
					'cell_html' => $this->get_media_column_markup( $attachment_id ),
				),
				422
			);
		}

		if ( $this->queue->get_item( $attachment_id ) ) {
			$this->queue->mark_completed( $attachment_id );
		}

		wp_send_json_success(
			array(
				'message'    => __( 'Image optimized successfully.', 'naveencodes-image-optimizer' ),
				'cell_html'  => $this->get_media_column_markup( $attachment_id ),
				'attachment' => $this->get_attachment_view_data( $attachment_id ),
				'progress'   => $this->build_progress_payload(),
			)
		);
	}

	/**
	 * Force a site registration refresh.
	 *
	 * @return void
	 */
	public function ajax_register_site() {
		$this->verify_ajax_request();

		$result = Init::get_instance()->provision_site_key( true );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error(
				array(
					'message' => $result->get_error_message(),
				),
				422
			);
		}

		wp_send_json_success(
			array(
				'message'      => __( 'Website registration is active.', 'naveencodes-image-optimizer' ),
				'registration' => $this->get_registration_state(),
			)
		);
	}

	/**
	 * Clear logs through AJAX.
	 *
	 * @return void
	 */
	public function ajax_clear_logs() {
		$this->verify_ajax_request();
		$this->logger->clear();
		wp_send_json_success();
	}

	/**
	 * Persist admin notice dismissal.
	 *
	 * @return void
	 */
	public function ajax_dismiss_notice() {
		$this->verify_ajax_request();
		$type = isset( $_POST['notice_type'] ) ? sanitize_key( wp_unslash( $_POST['notice_type'] ) ) : 'donation';

		if ( 'review' === $type ) {
			update_user_meta( get_current_user_id(), self::REVIEW_NOTICE_META_KEY, 1 );
		} else {
			update_user_meta( get_current_user_id(), self::NOTICE_META_KEY, 1 );
		}

		wp_send_json_success();
	}

	/**
	 * Add plugin status column to Media Library.
	 *
	 * @param array $columns Existing columns.
	 * @return array
	 */
	public function register_media_column( $columns ) {
		$updated = array();

		foreach ( $columns as $key => $label ) {
			$updated[ $key ] = $label;

			if ( 'title' === $key ) {
				$updated['naveencodes_status'] = __( 'Optimizer', 'naveencodes-image-optimizer' );
			}
		}

		if ( ! isset( $updated['naveencodes_status'] ) ) {
			$updated['naveencodes_status'] = __( 'Optimizer', 'naveencodes-image-optimizer' );
		}

		return $updated;
	}

	/**
	 * Render the Media Library column content.
	 *
	 * @param string $column_name Column name.
	 * @param int    $attachment_id Attachment ID.
	 * @return void
	 */
	public function render_media_column( $column_name, $attachment_id ) {
		if ( 'naveencodes_status' !== $column_name ) {
			return;
		}

		echo wp_kses_post( $this->get_media_column_markup( $attachment_id ) );
	}

	/**
	 * Decide whether the review notice is eligible for the current admin.
	 *
	 * @return bool
	 */
	protected function should_render_review_notice() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return false;
		}

		if ( get_user_meta( get_current_user_id(), self::REVIEW_NOTICE_META_KEY, true ) ) {
			return false;
		}

		$activated_at = absint( get_option( Settings::ACTIVATED_OPTION, 0 ) );
		$stats        = $this->optimizer->get_stats();

		if ( $activated_at > ( time() - ( 7 * DAY_IN_SECONDS ) ) ) {
			return false;
		}

		return absint( $stats['total_optimized'] ) >= 10;
	}

	/**
	 * Review request after the plugin has shown value.
	 *
	 * @return void
	 */
	public function maybe_render_review_notice() {
		if ( ! $this->should_render_review_notice() ) {
			return;
		}

		$review_url = 'https://wordpress.org/support/plugin/naveencodes-image-optimizer/reviews/#new-post';
		$saved      = $this->optimizer->format_bytes( (int) $this->optimizer->get_stats()['total_saved_bytes'] );
		?>
		<div class="notice notice-success is-dismissible naveencodes-review-notice" data-naveencodes-dismiss="review">
			<p>
				<strong><?php esc_html_e( 'Enjoying NaveenCodes Image Optimizer?', 'naveencodes-image-optimizer' ); ?></strong>
				<?php
				printf(
					/* translators: %s: formatted saved bytes. */
					esc_html__( 'Your website has already saved %s with the plugin. If the workflow has been useful, a quick WordPress.org review helps other site owners discover it.', 'naveencodes-image-optimizer' ),
					esc_html( $saved )
				);
				?>
			</p>
			<p>
				<a class="button button-primary" href="<?php echo esc_url( $review_url ); ?>" target="_blank" rel="noopener noreferrer">
					<?php esc_html_e( 'Leave a Review', 'naveencodes-image-optimizer' ); ?>
				</a>
			</p>
		</div>
		<?php
	}

	/**
	 * Soft donation reminder after proven value.
	 *
	 * @return void
	 */
	public function maybe_render_donation_notice() {
		$this->maybe_render_review_notice();

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		if ( $this->should_render_review_notice() ) {
			return;
		}

		if ( get_user_meta( get_current_user_id(), self::NOTICE_META_KEY, true ) ) {
			return;
		}

		$activated_at = absint( get_option( Settings::ACTIVATED_OPTION, 0 ) );
		$stats        = $this->optimizer->get_stats();

		if ( $activated_at > ( time() - ( 7 * DAY_IN_SECONDS ) ) || absint( $stats['total_optimized'] ) < 30 ) {
			return;
		}

		$saved = $this->optimizer->format_bytes( (int) $stats['total_saved_bytes'] );
		?>
		<div class="notice notice-info is-dismissible naveencodes-donation-notice" data-naveencodes-dismiss="donation">
			<p>
				<strong><?php esc_html_e( 'NaveenCodes Image Optimizer', 'naveencodes-image-optimizer' ); ?></strong>
				<?php esc_html_e( 'saved', 'naveencodes-image-optimizer' ); ?>
				<?php echo esc_html( $saved ); ?>
				<?php esc_html_e( 'for your website. If it helps you, consider supporting development.', 'naveencodes-image-optimizer' ); ?>
			</p>
			<p>
				<a class="button button-secondary" href="<?php echo esc_url( admin_url( 'admin.php?page=naveencodes-donate' ) ); ?>">
					<?php esc_html_e( 'Support Development', 'naveencodes-image-optimizer' ); ?>
				</a>
			</p>
		</div>
		<?php
	}

	/**
	 * Build progress payload for dashboard and bulk UI.
	 *
	 * @return array
	 */
	protected function build_progress_payload() {
		$stats        = $this->optimizer->get_stats();
		$queue_counts = $this->queue->get_status_counts();
		$total        = max( 1, (int) $queue_counts['total'] );
		$done         = (int) $queue_counts['completed'] + (int) $queue_counts['failed'];

		return array(
			'stats'               => $stats,
			'queue_counts'        => $queue_counts,
			'percent'             => round( ( $done / $total ) * 100, 2 ),
			'health'              => $this->get_cached_health_status(),
			'queue_sections_html' => $this->get_queue_sections_html(),
			'bulk_session'        => $this->get_bulk_session(),
		);
	}

	/**
	 * Cached service health lookup.
	 *
	 * @return array
	 */
	protected function get_cached_health_status() {
		$cached = get_transient( 'naveencodes_health_status' );

		if ( is_array( $cached ) ) {
			return $cached;
		}

		$health = $this->api_client->check_health();

		if ( is_wp_error( $health ) ) {
			$health = $this->normalize_health_status(
				array(
					'status'  => 'down',
					'message' => $health->get_error_message(),
				)
			);
		} else {
			$health = $this->normalize_health_status( $health );
		}

		set_transient( 'naveencodes_health_status', $health, 5 * MINUTE_IN_SECONDS );

		return $health;
	}

	/**
	 * Normalize health data for the UI.
	 *
	 * @param array $health Raw health data.
	 * @return array
	 */
	protected function normalize_health_status( $health ) {
		$status = sanitize_key( $health['status'] ?? 'ok' );
		$map    = array(
			'ok'      => array(
				'label'       => __( 'Good', 'naveencodes-image-optimizer' ),
				'message'     => __( 'Server health is good.', 'naveencodes-image-optimizer' ),
				'badge_class' => 'naveencodes-badge--success',
			),
			'warning' => array(
				'label'       => __( 'Attention', 'naveencodes-image-optimizer' ),
				'message'     => __( 'The optimization service is responding with warnings.', 'naveencodes-image-optimizer' ),
				'badge_class' => 'naveencodes-badge--warning',
			),
			'down'    => array(
				'label'       => __( 'Offline', 'naveencodes-image-optimizer' ),
				'message'     => __( 'The optimization service is unavailable right now.', 'naveencodes-image-optimizer' ),
				'badge_class' => 'naveencodes-badge--danger',
			),
		);

		if ( ! isset( $map[ $status ] ) ) {
			$status = 'warning';
		}

		return array(
			'status'      => $status,
			'label'       => $map[ $status ]['label'],
			'message'     => $map[ $status ]['message'],
			'badge_class' => $map[ $status ]['badge_class'],
			'raw_message' => sanitize_text_field( (string) ( $health['message'] ?? '' ) ),
		);
	}

	/**
	 * Return website registration state.
	 *
	 * @return array
	 */
	protected function get_registration_state() {
		$api_key       = (string) get_option( Init::API_KEY_OPTION, '' );
		$domain        = wp_parse_url( home_url( '/' ), PHP_URL_HOST );
		$is_registered = '' !== $api_key;
		$key_length    = strlen( $api_key );
		$preview       = '';

		if ( $is_registered ) {
			$visible = substr( $api_key, max( 0, $key_length - 8 ) );
			$preview = str_repeat( '*', max( 0, $key_length - 8 ) ) . $visible;
		}

		return array(
			'is_registered' => $is_registered,
			'status_label'  => $is_registered ? __( 'Registered', 'naveencodes-image-optimizer' ) : __( 'Pending', 'naveencodes-image-optimizer' ),
			'badge_class'   => $is_registered ? 'naveencodes-badge--success' : 'naveencodes-badge--warning',
			'domain'        => sanitize_text_field( (string) $domain ),
			'key_preview'   => $preview,
			'message'       => $is_registered
				? __( 'This website is registered and using its own secure key.', 'naveencodes-image-optimizer' )
				: __( 'This website has not completed registration yet. You can register it from this screen.', 'naveencodes-image-optimizer' ),
		);
	}

	/**
	 * Build a reusable admin banner view model.
	 *
	 * @param string $context Screen context.
	 * @return array
	 */
	protected function get_admin_banner_data( $context ) {
		$stats = $this->optimizer->get_stats();
		$map   = array(
			'dashboard' => array(
				'title'   => __( 'Optimize faster. Review everything clearly.', 'naveencodes-image-optimizer' ),
				'summary' => __( 'Track savings, monitor health, and manage large media libraries without leaving WordPress.', 'naveencodes-image-optimizer' ),
			),
			'optimize'  => array(
				'title'   => __( 'Bulk optimization with clear progress.', 'naveencodes-image-optimizer' ),
				'summary' => __( 'Queue your entire library, resume safely, and confirm every result with before and after file sizes.', 'naveencodes-image-optimizer' ),
			),
			'settings'  => array(
				'title'   => __( 'Tune the profile for your website.', 'naveencodes-image-optimizer' ),
				'summary' => __( 'Adjust batch size, quality, resize rules, and reliability settings without exposing service credentials.', 'naveencodes-image-optimizer' ),
			),
			'logs'      => array(
				'title'   => __( 'Review activity without guesswork.', 'naveencodes-image-optimizer' ),
				'summary' => __( 'Logs help you trace recent optimization actions, failures, and recovery attempts.', 'naveencodes-image-optimizer' ),
			),
			'donate'    => array(
				'title'   => __( 'The plugin stays fully free.', 'naveencodes-image-optimizer' ),
				'summary' => __( 'If NaveenCodes Image Optimizer is useful for your website, you can support ongoing maintenance with an optional donation.', 'naveencodes-image-optimizer' ),
			),
		);

		$data = isset( $map[ $context ] ) ? $map[ $context ] : $map['dashboard'];

		return array(
			'title'      => $data['title'],
			'summary'    => $data['summary'],
			'highlights' => array(
				__( 'Unlimited use', 'naveencodes-image-optimizer' ),
				__( 'Bulk + single image actions', 'naveencodes-image-optimizer' ),
				__( 'No locked features', 'naveencodes-image-optimizer' ),
			),
			'stats'      => array(
				array(
					'label' => __( 'Images Optimized', 'naveencodes-image-optimizer' ),
					'value' => number_format_i18n( (int) $stats['total_optimized'] ),
				),
				array(
					'label' => __( 'Space Saved', 'naveencodes-image-optimizer' ),
					'value' => size_format( (int) $stats['total_saved_bytes'], 2 ),
				),
			),
		);
	}

	/**
	 * Get recently optimized attachments for admin pages.
	 *
	 * @param int $limit Number of attachments to return.
	 * @return array
	 */
	protected function get_recent_attachments( $limit = 8 ) {
		$query = new WP_Query(
			array(
				'post_type'      => 'attachment',
				'post_status'    => 'inherit',
				'post_mime_type' => 'image',
				'fields'         => 'ids',
				'posts_per_page' => max( 1, absint( $limit ) ),
				// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Limited query used for admin reporting.
				'meta_key'       => '_naveencodes_last_optimized_at',
				'orderby'        => 'meta_value',
				'order'          => 'DESC',
				// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Limited query used for admin reporting.
				'meta_query'     => array(
					array(
						'key'     => '_naveencodes_last_optimized_at',
						'compare' => 'EXISTS',
					),
				),
			)
		);

		$items = array();

		foreach ( (array) $query->posts as $attachment_id ) {
			$items[] = $this->get_attachment_view_data( $attachment_id );
		}

		return $items;
	}

	/**
	 * Build HTML for each queue status section.
	 *
	 * @return array
	 */
	protected function get_queue_sections_html() {
		$sections = array();

		foreach ( array( 'pending', 'processing', 'completed', 'failed' ) as $status ) {
			$sections[ $status ] = $this->render_queue_section_rows( $status );
		}

		return $sections;
	}

	/**
	 * Render one queue status table body.
	 *
	 * @param string $status Queue status.
	 * @return string
	 */
	protected function render_queue_section_rows( $status ) {
		$items = $this->get_queue_status_items( $status );

		ob_start();

		if ( empty( $items ) ) :
			?>
			<div class="naveencodes-empty-state naveencodes-empty-state--compact">
				<p><?php esc_html_e( 'No images in this status right now.', 'naveencodes-image-optimizer' ); ?></p>
			</div>
			<?php
		else :
			?>
			<table class="naveencodes-data-table naveencodes-data-table--compact">
				<thead>
					<tr>
						<th scope="col"><?php esc_html_e( 'Image', 'naveencodes-image-optimizer' ); ?></th>
						<th scope="col"><?php esc_html_e( 'Before', 'naveencodes-image-optimizer' ); ?></th>
						<th scope="col"><?php esc_html_e( 'After', 'naveencodes-image-optimizer' ); ?></th>
						<th scope="col"><?php esc_html_e( 'Updated', 'naveencodes-image-optimizer' ); ?></th>
						<th scope="col"><?php esc_html_e( 'Action', 'naveencodes-image-optimizer' ); ?></th>
					</tr>
				</thead>
				<tbody>
					<?php foreach ( $items as $item ) : ?>
						<tr>
							<td>
								<div class="naveencodes-table-media">
									<div class="naveencodes-media-thumb"><?php echo wp_kses_post( $item['thumbnail'] ); ?></div>
									<div>
										<strong><?php echo esc_html( $item['title'] ?: $item['filename'] ); ?></strong>
										<p class="naveencodes-media-filename"><?php echo esc_html( $item['filename'] ); ?></p>
									</div>
								</div>
							</td>
							<td><?php echo esc_html( $item['before_label'] ); ?></td>
							<td><?php echo esc_html( $item['after_label'] ); ?></td>
							<td>
								<span class="naveencodes-badge <?php echo esc_attr( $item['status_class'] ); ?>"><?php echo esc_html( $item['status_label'] ); ?></span>
								<p class="naveencodes-media-filename"><?php echo esc_html( $item['queue_updated_label'] ); ?></p>
							</td>
							<td>
								<?php if ( ! empty( $item['edit_url'] ) ) : ?>
									<a class="button button-secondary button-small" href="<?php echo esc_url( $item['edit_url'] ); ?>"><?php esc_html_e( 'Open in Media', 'naveencodes-image-optimizer' ); ?></a>
								<?php endif; ?>
							</td>
						</tr>
					<?php endforeach; ?>
				</tbody>
			</table>
			<?php
		endif;

		return (string) ob_get_clean();
	}

	/**
	 * Fetch queue items formatted for the admin UI.
	 *
	 * @param string $status Queue status.
	 * @param int    $limit  Number of rows.
	 * @return array
	 */
	protected function get_queue_status_items( $status, $limit = 12 ) {
		$rows  = $this->queue->get_items_by_status( $status, $limit );
		$items = array();

		foreach ( $rows as $row ) {
			$attachment_id = absint( $row['attachment_id'] ?? 0 );

			if ( ! $attachment_id || 'attachment' !== get_post_type( $attachment_id ) ) {
				continue;
			}

			$item                       = $this->get_attachment_view_data( $attachment_id );
			$item['queue_updated_label'] = $this->format_queue_updated_at( $row['updated_at'] ?? '' );
			$items[]                    = $item;
		}

		return $items;
	}

	/**
	 * Format queue updated timestamp for the admin UI.
	 *
	 * @param string $timestamp MySQL timestamp.
	 * @return string
	 */
	protected function format_queue_updated_at( $timestamp ) {
		if ( empty( $timestamp ) ) {
			return __( 'Updated recently', 'naveencodes-image-optimizer' );
		}

		$unix = mysql2date( 'U', $timestamp );

		if ( empty( $unix ) ) {
			return __( 'Updated recently', 'naveencodes-image-optimizer' );
		}

		return sprintf(
			/* translators: %s: human-readable relative time. */
			__( 'Updated %s ago', 'naveencodes-image-optimizer' ),
			human_time_diff( (int) $unix, current_time( 'timestamp' ) )
		);
	}

	/**
	 * Build one attachment view model.
	 *
	 * @param int $attachment_id Attachment ID.
	 * @return array
	 */
	protected function get_attachment_view_data( $attachment_id ) {
		$attachment_id = absint( $attachment_id );
		$report        = $this->optimizer->get_attachment_report( $attachment_id );
		$queue_item     = $this->queue->get_item( $attachment_id );
		$status         = $this->resolve_attachment_status( $queue_item, $report );
		$file_path      = get_attached_file( $attachment_id );
		$thumbnail      = wp_get_attachment_image(
			$attachment_id,
			array( 72, 72 ),
			true,
			array(
				'class'   => 'naveencodes-media-thumb-image',
				'loading' => 'lazy',
			)
		);

		if ( empty( $thumbnail ) ) {
			$thumbnail = '<span class="naveencodes-media-thumb-fallback" aria-hidden="true">' . esc_html( strtoupper( substr( (string) get_post_mime_type( $attachment_id ), 6, 3 ) ) ) . '</span>';
		}

		return array(
			'id'                => $attachment_id,
			'title'             => sanitize_text_field( wp_get_attachment_caption( $attachment_id ) ? wp_get_attachment_caption( $attachment_id ) : get_the_title( $attachment_id ) ),
			'filename'          => $file_path ? wp_basename( $file_path ) : '',
			'thumbnail'         => $thumbnail,
			'edit_url'          => get_edit_post_link( $attachment_id, '' ),
			'view_url'          => wp_get_attachment_url( $attachment_id ),
			'status'            => $status['status'],
			'status_label'      => $status['label'],
			'status_class'      => $status['class'],
			'can_optimize'      => 'processing' !== $status['status'],
			'before_label'      => $report['before_bytes'] ? $this->optimizer->format_bytes( $report['before_bytes'] ) : '--',
			'after_label'       => $report['after_bytes'] ? $this->optimizer->format_bytes( $report['after_bytes'] ) : '--',
			'saved_label'       => $report['saved_bytes'] ? $this->optimizer->format_bytes( $report['saved_bytes'] ) : '0 B',
			'compression_label' => $report['before_bytes'] ? number_format_i18n( $report['compression_percent'], 2 ) . '%' : '--',
			'last_optimized_at' => $report['last_optimized_at'],
			'last_error'        => $report['last_error'],
			'generated_formats' => implode( ', ', array_keys( $report['generated_formats'] ) ),
		);
	}

	/**
	 * Resolve the most relevant attachment status for the admin UI.
	 *
	 * @param array|null $queue_item Queue item data.
	 * @param array      $report     Attachment optimization report.
	 * @return array
	 */
	protected function resolve_attachment_status( $queue_item, $report ) {
		if ( ! empty( $queue_item['status'] ) && in_array( $queue_item['status'], array( 'pending', 'processing', 'failed' ), true ) ) {
			$map = array(
				'pending'    => array(
					'label' => __( 'Queued', 'naveencodes-image-optimizer' ),
					'class' => 'naveencodes-badge--info',
				),
				'processing' => array(
					'label' => __( 'Processing', 'naveencodes-image-optimizer' ),
					'class' => 'naveencodes-badge--warning',
				),
				'failed'     => array(
					'label' => __( 'Needs Attention', 'naveencodes-image-optimizer' ),
					'class' => 'naveencodes-badge--danger',
				),
			);

			return array(
				'status' => sanitize_key( $queue_item['status'] ),
				'label'  => $map[ $queue_item['status'] ]['label'],
				'class'  => $map[ $queue_item['status'] ]['class'],
			);
		}

		if ( ! empty( $report['is_optimized'] ) ) {
			return array(
				'status' => 'optimized',
				'label'  => __( 'Optimized by NaveenCodes Image Optimizer', 'naveencodes-image-optimizer' ),
				'class'  => 'naveencodes-badge--success',
			);
		}

		return array(
			'status' => 'idle',
			'label'  => __( 'Ready', 'naveencodes-image-optimizer' ),
			'class'  => 'naveencodes-badge--neutral',
		);
	}

	/**
	 * Render the Media Library cell markup.
	 *
	 * @param int $attachment_id Attachment ID.
	 * @return string
	 */
	protected function get_media_column_markup( $attachment_id ) {
		$item = $this->get_attachment_view_data( $attachment_id );

		ob_start();
		?>
		<div class="naveencodes-media-cell" data-naveencodes-media-cell data-naveencodes-attachment-id="<?php echo esc_attr( $item['id'] ); ?>">
			<span class="naveencodes-badge <?php echo esc_attr( $item['status_class'] ); ?>"><?php echo esc_html( $item['status_label'] ); ?></span>
			<div class="naveencodes-media-cell-meta">
				<span><?php esc_html_e( 'Before', 'naveencodes-image-optimizer' ); ?>: <?php echo esc_html( $item['before_label'] ); ?></span>
				<span><?php esc_html_e( 'After', 'naveencodes-image-optimizer' ); ?>: <?php echo esc_html( $item['after_label'] ); ?></span>
				<span><?php esc_html_e( 'Saved', 'naveencodes-image-optimizer' ); ?>: <?php echo esc_html( $item['saved_label'] ); ?></span>
			</div>
			<?php if ( ! empty( $item['last_error'] ) ) : ?>
				<p class="naveencodes-inline-error"><?php echo esc_html( $item['last_error'] ); ?></p>
			<?php endif; ?>
			<button
				type="button"
				class="button button-small button-secondary naveencodes-inline-button"
				data-naveencodes-optimize-attachment
				data-attachment-id="<?php echo esc_attr( $item['id'] ); ?>"
				<?php disabled( ! $item['can_optimize'] ); ?>
			>
				<?php echo esc_html( 'optimized' === $item['status'] ? __( 'Re-optimize', 'naveencodes-image-optimizer' ) : __( 'Optimize Now', 'naveencodes-image-optimizer' ) ); ?>
			</button>
		</div>
		<?php

		return (string) ob_get_clean();
	}

	/**
	 * Get the current bulk scan session state.
	 *
	 * @return array
	 */
	protected function get_bulk_session() {
		$defaults = array(
			'scan_complete' => false,
			'last_page'     => 0,
			'total_pages'   => 0,
			'started_at'    => 0,
		);

		$stored = get_option( self::BULK_SESSION_OPTION, array() );

		return wp_parse_args( is_array( $stored ) ? $stored : array(), $defaults );
	}

	/**
	 * Shared AJAX request validation.
	 *
	 * @return void
	 */
	protected function verify_ajax_request() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'You are not allowed to perform this action.', 'naveencodes-image-optimizer' ) ), 403 );
		}

		check_ajax_referer( 'naveencodes_admin_nonce', 'nonce' );
	}

	/**
	 * Render a template with local scope variables.
	 *
	 * @param string $template Template file name.
	 * @param array  $data     Template variables.
	 * @return void
	 */
	protected function render_template( $template, $data = array() ) {
		$file = NAVEENCODES_DIR . 'templates/' . ltrim( $template, '/' );

		if ( ! file_exists( $file ) ) {
			return;
		}

		$data = is_array( $data ) ? $data : array();
		extract( $data, EXTR_SKIP );
		include $file;
	}

	/**
	 * Add tribute footer on plugin pages.
	 *
	 * @param string $text Footer text.
	 * @return string
	 */
	public function add_tribute_footer( $text ) {
		$screen = get_current_screen();

		if ( ! $screen || false === strpos( $screen->id, 'naveencodes' ) ) {
			return $text;
		}

		return esc_html__( 'In loving memory of Maa ❤️ — Developed by Naveen Goyal (naveencodes.com)', 'naveencodes-image-optimizer' );
	}
}
