<?php
/**
 * Admin settings and summaries.
 *
 * @package TechDexSearchScrapeGuard
 */

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

final class TDSSG_Admin {
	const DASHBOARD_PAGE_SLUG = 'techdex-search-scrape-guard';
	const SETTINGS_PAGE_SLUG  = 'tdssg-settings-log';
	const CHANGELOG_PAGE_SLUG = 'tdssg-changelog';
	const PRODUCT_KEY         = 'techdex-search-scrape-guard';
	const DONATE_URL          = 'https://buy.stripe.com/14AbJ1bp63hF3en6yvdEs0i';

	/**
	 * Register hooks.
	 */
	public static function init() {
		add_action( 'admin_menu', array( __CLASS__, 'add_page' ) );
		add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
		add_action( 'admin_init', array( __CLASS__, 'add_privacy_policy_content' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		add_action( 'current_screen', array( __CLASS__, 'suppress_third_party_admin_notices' ) );
		add_action( 'admin_notices', array( __CLASS__, 'render_admin_notice' ) );
		add_action( 'wp_ajax_tdssg_fetch_events', array( __CLASS__, 'ajax_fetch_events' ) );
		add_action( 'wp_ajax_tdssg_clear_events', array( __CLASS__, 'ajax_clear_events' ) );
		add_filter( 'plugin_action_links_' . plugin_basename( TDSSG_FILE ), array( __CLASS__, 'action_links' ) );
		add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_row_meta' ), 10, 2 );
		add_filter( 'plugins_api', array( __CLASS__, 'plugin_information' ), 10, 3 );
	}

	/**
	 * Add the settings page.
	 */
	public static function add_page() {
		add_menu_page(
			__( 'TechDex Search Scrape Guard', 'techdex-search-scrape-guard' ),
			__( 'Search Scrape Guard', 'techdex-search-scrape-guard' ),
			'manage_options',
			self::DASHBOARD_PAGE_SLUG,
			array( __CLASS__, 'render_dashboard_page' ),
			'dashicons-shield-alt',
			58.1
		);

		add_submenu_page(
			self::DASHBOARD_PAGE_SLUG,
			__( 'Dashboard', 'techdex-search-scrape-guard' ),
			__( 'Dashboard', 'techdex-search-scrape-guard' ),
			'manage_options',
			self::DASHBOARD_PAGE_SLUG,
			array( __CLASS__, 'render_dashboard_page' )
		);

		add_submenu_page(
			self::DASHBOARD_PAGE_SLUG,
			__( 'Search Scrape Guard', 'techdex-search-scrape-guard' ),
			__( 'Settings & Log', 'techdex-search-scrape-guard' ),
			'manage_options',
			self::SETTINGS_PAGE_SLUG,
			array( __CLASS__, 'render_page' )
		);

		add_submenu_page(
			null,
			__( 'Search Scrape Guard Changelog', 'techdex-search-scrape-guard' ),
			__( 'Changelog', 'techdex-search-scrape-guard' ),
			'manage_options',
			self::CHANGELOG_PAGE_SLUG,
			array( __CLASS__, 'render_changelog_page' )
		);
	}

	/**
	 * Add direct access to the settings and log screen.
	 *
	 * @param array $links Existing plugin links.
	 * @return array
	 */
	public static function action_links( $links ) {
		$action_links = array(
			'<a href="' . esc_url( self::settings_url() ) . '">' . esc_html__( 'Settings', 'techdex-search-scrape-guard' ) . '</a>',
			'<a href="' . esc_url( self::DONATE_URL ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Donate', 'techdex-search-scrape-guard' ) . '</a>',
		);

		return array_merge( $action_links, $links );
	}

	/**
	 * Add a standard plugin-details link before the WordPress.org listing exists.
	 *
	 * @param array  $links Existing row metadata.
	 * @param string $file  Current plugin file.
	 * @return array
	 */
	public static function plugin_row_meta( $links, $file ) {
		if ( plugin_basename( TDSSG_FILE ) !== $file ) {
			return $links;
		}

		$details_url = self::plugin_details_url();

		foreach ( $links as $link ) {
			if ( is_string( $link ) && ( false !== strpos( $link, 'open-plugin-details-modal' ) || false !== stripos( $link, 'View Details' ) ) ) {
				return $links;
			}
		}

		$links[]     = '<a href="' . esc_url( $details_url ) . '" class="thickbox open-plugin-details-modal" aria-label="' . esc_attr__( 'View TechDex Search Scrape Guard details', 'techdex-search-scrape-guard' ) . '">' . esc_html__( 'View Details', 'techdex-search-scrape-guard' ) . '</a>';

		return $links;
	}

	/**
	 * Supply local details for the standard WordPress plugin modal.
	 *
	 * @param false|object|array $result Existing result.
	 * @param string             $action Plugins API action.
	 * @param object             $args   Request arguments.
	 * @return object|false|array
	 */
	public static function plugin_information( $result, $action, $args ) {
		if ( 'plugin_information' !== $action || ! is_object( $args ) || empty( $args->slug ) || self::PRODUCT_KEY !== $args->slug ) {
			return $result;
		}

		return (object) array(
			'name'           => __( 'TechDex Search Scrape Guard', 'techdex-search-scrape-guard' ),
			'slug'           => self::PRODUCT_KEY,
			'version'        => TDSSG_VERSION,
			'author'         => '<a href="https://techdex.net" target="_blank" rel="noopener noreferrer">Dexter Nelson | TechDex Development and Solutions</a>',
			'author_profile' => 'https://techdex.net',
			'homepage'       => 'https://techdex.net',
			'requires'       => '6.0',
			'requires_php'   => '7.4',
			'tested'         => '7.0',
			'sections'       => array(
				'description'  => __( 'Protect public WordPress search from abusive queries, automation, deep pagination, and rate spikes before the expensive main search query runs.', 'techdex-search-scrape-guard' ),
				'installation' => __( 'Install and activate the plugin, then open Search Scrape Guard > Settings & Log to review the defaults and optional Turnstile settings.', 'techdex-search-scrape-guard' ),
				'changelog'    => __( 'Version 0.4.1 completes the WordPress.org compliance pass, improves form behavior, and loads plugin-row behavior through a local enqueued script.', 'techdex-search-scrape-guard' ),
			),
			'external'       => true,
		);
	}

	/**
	 * Register the settings option.
	 */
	public static function register_settings() {
		register_setting(
			'tdssg_settings_group',
			TDSSG_Settings::OPTION_KEY,
			array(
				'type'              => 'array',
				'sanitize_callback' => array( 'TDSSG_Settings', 'sanitize' ),
				'default'           => TDSSG_Settings::defaults(),
			)
		);
	}

	/**
	 * Add suggested privacy-policy text for the local security log and Turnstile.
	 */
	public static function add_privacy_policy_content() {
		if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
			return;
		}

		$content  = '<p>' . __( 'TechDex Search Scrape Guard stores a bounded local log of rejected public-search requests. The log contains at most 250 coalesced signatures and may include a masked IPv4 /24 or IPv6 /48 network, rejection reason, HTTP status, latest search sample, reported browser or user-agent string, timestamps, and an occurrence count. Raw IP addresses are not retained.', 'techdex-search-scrape-guard' ) . '</p>';
		$content .= '<p>' . __( 'If Cloudflare Turnstile keys are configured, a visitor who is asked to verify a search loads Cloudflare Turnstile and sends a verification token and connection IP address to Cloudflare for validation. Turnstile may be used for required verification or after a search rate limit is reached. The operational log can be cleared by an administrator, and plugin settings and tables are removed when the plugin is uninstalled.', 'techdex-search-scrape-guard' ) . '</p>';

		wp_add_privacy_policy_content(
			__( 'TechDex Search Scrape Guard', 'techdex-search-scrape-guard' ),
			wp_kses_post( $content )
		);
	}

	/**
	 * Load the log panel only on this plugin's settings page.
	 *
	 * @param string $hook_suffix Current admin hook.
	 */
	public static function enqueue_assets( $hook_suffix ) {
		if ( 'plugins.php' === $hook_suffix ) {
			wp_enqueue_script(
				'tdssg-plugin-row',
				plugins_url( 'assets/plugin-row.js', TDSSG_FILE ),
				array(),
				TDSSG_VERSION,
				true
			);
			return;
		}

		if ( ! self::is_plugin_admin_screen( $hook_suffix ) ) {
			return;
		}

		wp_enqueue_style(
			'tdssg-admin',
			plugins_url( 'assets/admin.css', TDSSG_FILE ),
			array(),
			TDSSG_VERSION
		);

		if ( 'search-scrape-guard_page_' . self::SETTINGS_PAGE_SLUG !== $hook_suffix ) {
			return;
		}

		wp_enqueue_script( 'tdssg-log-panel', plugins_url( 'assets/log-panel.js', TDSSG_FILE ), array(), TDSSG_VERSION, true );
		wp_localize_script(
			'tdssg-log-panel',
			'TDSSGLog',
			array(
				'ajaxUrl'      => admin_url( 'admin-ajax.php' ),
				'nonce'        => wp_create_nonce( 'tdssg_log_actions' ),
				'interval'     => 30000,
				'confirmClear' => __( 'Clear all recorded search-protection events? This cannot be undone.', 'techdex-search-scrape-guard' ),
				'emptyMessage' => __( 'No search-protection events have been recorded.', 'techdex-search-scrape-guard' ),
				'errorMessage' => __( 'The search-protection log could not be refreshed.', 'techdex-search-scrape-guard' ),
				'clearingText' => __( 'Clearing...', 'techdex-search-scrape-guard' ),
				'clearText'    => __( 'Clear log', 'techdex-search-scrape-guard' ),
				'updatedText'  => __( 'Last updated:', 'techdex-search-scrape-guard' ),
				'nextRefresh'  => __( 'Next refresh in', 'techdex-search-scrape-guard' ),
				'filteredText' => __( 'Filter:', 'techdex-search-scrape-guard' ),
				'pageLabel'    => __( 'Page', 'techdex-search-scrape-guard' ),
				'ofLabel'      => __( 'of', 'techdex-search-scrape-guard' ),
				'eventsLabel'  => __( 'signatures', 'techdex-search-scrape-guard' ),
			)
		);
	}

	/**
	 * Keep unrelated plugin notices out of this plugin's screens.
	 *
	 * @param WP_Screen|object $screen Current admin screen.
	 */
	public static function suppress_third_party_admin_notices( $screen ) {
		if ( ! is_object( $screen ) || empty( $screen->id ) || ! self::is_plugin_admin_screen( (string) $screen->id ) ) {
			return;
		}

		remove_all_actions( 'admin_notices' );
		remove_all_actions( 'all_admin_notices' );
		add_action( 'admin_notices', array( __CLASS__, 'render_admin_notice' ) );
	}

	/**
	 * Render only plugin-specific notices on plugin screens.
	 */
	public static function render_admin_notice() {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;

		if ( ! is_object( $screen ) || empty( $screen->id ) || ! self::is_plugin_admin_screen( (string) $screen->id ) ) {
			return;
		}

		if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'TechDex Search Scrape Guard settings saved.', 'techdex-search-scrape-guard' ) . '</p></div>';
		}
	}

	/**
	 * Return a filtered, paginated event page over AJAX.
	 */
	public static function ajax_fetch_events() {
		self::verify_log_request();

		$page   = isset( $_POST['page_number'] ) ? max( 1, absint( wp_unslash( $_POST['page_number'] ) ) ) : 1;
		$search = isset( $_POST['log_search'] ) ? sanitize_text_field( wp_unslash( $_POST['log_search'] ) ) : '';

		wp_send_json_success( self::events_response( $page, $search ) );
	}

	/**
	 * Clear the bounded event table over AJAX.
	 */
	public static function ajax_clear_events() {
		self::verify_log_request();

		if ( false === TDSSG_Logger::clear() ) {
			wp_send_json_error( array( 'message' => __( 'The search-protection log could not be cleared.', 'techdex-search-scrape-guard' ) ), 500 );
		}

		wp_send_json_success(
			array(
				'events'     => array(),
				'pagination' => array(
					'page'  => 1,
					'pages' => 1,
					'total' => 0,
				),
			)
		);
	}

	/**
	 * Render the plugin dashboard.
	 */
	public static function render_dashboard_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to manage search protection.', 'techdex-search-scrape-guard' ) );
		}

		$settings = TDSSG_Settings::get_all();
		$total    = TDSSG_Logger::count();

		self::render_admin_page_header(
			'dashboard',
			__( 'About TechDex Search Scrape Guard', 'techdex-search-scrape-guard' ),
			__( 'Protect public WordPress search before abusive requests become expensive database work.', 'techdex-search-scrape-guard' )
		);
		?>
		<div class="wrap tdssg-page-content">
			<div class="tdssg-dashboard-grid">
				<div class="tdssg-card tdssg-stat-card">
					<h2><?php esc_html_e( 'Protection', 'techdex-search-scrape-guard' ); ?></h2>
					<p class="tdssg-stat"><?php echo ! empty( $settings['enabled'] ) ? esc_html__( 'Enabled', 'techdex-search-scrape-guard' ) : esc_html__( 'Paused', 'techdex-search-scrape-guard' ); ?></p>
				</div>
				<div class="tdssg-card tdssg-stat-card">
					<h2><?php esc_html_e( 'Recorded Signatures', 'techdex-search-scrape-guard' ); ?></h2>
					<p class="tdssg-stat"><?php echo esc_html( number_format_i18n( $total ) ); ?></p>
				</div>
				<div class="tdssg-card tdssg-stat-card">
					<h2><?php esc_html_e( 'Anonymous Page Limit', 'techdex-search-scrape-guard' ); ?></h2>
					<p class="tdssg-stat"><?php echo esc_html( number_format_i18n( (int) $settings['max_page'] ) ); ?></p>
				</div>
				<div class="tdssg-card">
					<h2><?php esc_html_e( 'Quick Access', 'techdex-search-scrape-guard' ); ?></h2>
					<p><a class="button button-primary" href="<?php echo esc_url( self::settings_url() ); ?>"><?php esc_html_e( 'Open Settings & Log', 'techdex-search-scrape-guard' ); ?></a></p>
					<p><a class="button button-secondary" href="<?php echo esc_url( self::DONATE_URL ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Donate', 'techdex-search-scrape-guard' ); ?></a></p>
				</div>
			</div>

			<div class="tdssg-card">
				<h2><?php esc_html_e( 'What This Plugin Does', 'techdex-search-scrape-guard' ); ?></h2>
				<p><?php esc_html_e( 'Search Scrape Guard evaluates public WordPress searches during request parsing. Invalid or abusive requests receive a lightweight response before WordPress builds the main posts search query.', 'techdex-search-scrape-guard' ); ?></p>
				<p><?php esc_html_e( 'The guard is intentionally limited to search traffic. Administration, REST, AJAX, cron, and WP-CLI requests are excluded.', 'techdex-search-scrape-guard' ); ?></p>
			</div>

			<div class="tdssg-card">
				<h2><?php esc_html_e( 'How It Works', 'techdex-search-scrape-guard' ); ?></h2>
				<ol>
					<li><?php esc_html_e( 'A public search request reaches WordPress.', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'The plugin checks the query, pagination, automation signature, verification state, and bounded rate limit.', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'Valid searches continue normally. Rejected searches stop before the expensive main search query runs.', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'A privacy-conscious, coalesced event signature is retained for operational review.', 'techdex-search-scrape-guard' ); ?></li>
				</ol>
			</div>

			<div class="tdssg-card tdssg-creator-card">
				<div class="tdssg-creator-photo">
					<a href="<?php echo esc_url( plugins_url( 'assets/dexter-nelson-headshot.jpg', TDSSG_FILE ) ); ?>" target="_blank" rel="noopener noreferrer">
						<img src="<?php echo esc_url( plugins_url( 'assets/dexter-nelson-headshot.jpg', TDSSG_FILE ) ); ?>" alt="<?php esc_attr_e( 'Dexter Nelson', 'techdex-search-scrape-guard' ); ?>">
					</a>
				</div>
				<div>
					<h2><?php esc_html_e( 'About the Creator', 'techdex-search-scrape-guard' ); ?></h2>
					<p><?php esc_html_e( 'TechDex Search Scrape Guard was created by Dexter Nelson after a real production incident exposed WordPress search as a resource-amplification and search-spam vector.', 'techdex-search-scrape-guard' ); ?></p>
					<p><?php esc_html_e( 'Dexter builds practical publishing, security, and workflow tools through TechDex Development and Solutions.', 'techdex-search-scrape-guard' ); ?></p>
					<p><a href="https://dexternelson.com" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Learn more about Dexter Nelson', 'techdex-search-scrape-guard' ); ?></a></p>
					<p><a href="<?php echo esc_url( self::DONATE_URL ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Support this free plugin with a donation', 'techdex-search-scrape-guard' ); ?></a></p>
				</div>
			</div>

			<div class="tdssg-card">
				<h2><?php esc_html_e( 'Included Features', 'techdex-search-scrape-guard' ); ?></h2>
				<ul>
					<li><?php esc_html_e( 'Early empty-query, length, word-count, pagination, and automation checks', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'Bounded fixed-slot rate limiting without per-IP transients', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'Optional Cloudflare Turnstile browser verification', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'Search noindex and virtual robots.txt guidance', 'techdex-search-scrape-guard' ); ?></li>
					<li><?php esc_html_e( 'Searchable, paginated, automatically refreshing operational log', 'techdex-search-scrape-guard' ); ?></li>
				</ul>
			</div>
		</div>
		<?php
	}

	/**
	 * Render the settings page.
	 */
	public static function render_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$settings = TDSSG_Settings::get_all();
		$events   = TDSSG_Logger::recent( 50 );
		$total    = TDSSG_Logger::count();
		$pages    = max( 1, (int) ceil( $total / 50 ) );
		self::render_admin_page_header(
			'settings',
			__( 'Search Protection Settings & Log', 'techdex-search-scrape-guard' ),
			__( 'Configure public-search limits and review the bounded operational event log.', 'techdex-search-scrape-guard' )
		);
		?>
		<div class="wrap tdssg-page-content">

			<form class="tdssg-card" method="post" action="options.php" autocomplete="off">
				<h2><?php esc_html_e( 'Protection Settings', 'techdex-search-scrape-guard' ); ?></h2>
				<?php settings_fields( 'tdssg_settings_group' ); ?>
				<table class="form-table" role="presentation">
					<?php self::checkbox_row( 'enabled', __( 'Enable protection', 'techdex-search-scrape-guard' ), $settings ); ?>
					<?php self::number_row( 'max_length', __( 'Maximum search characters', 'techdex-search-scrape-guard' ), $settings, 20, 500 ); ?>
					<?php self::number_row( 'max_words', __( 'Maximum search words', 'techdex-search-scrape-guard' ), $settings, 2, 50 ); ?>
					<?php self::number_row( 'max_page', __( 'Maximum anonymous search page', 'techdex-search-scrape-guard' ), $settings, 1, 100 ); ?>
					<?php self::number_row( 'logged_in_max_page', __( 'Maximum logged-in search page', 'techdex-search-scrape-guard' ), $settings, 1, 200 ); ?>
					<?php self::checkbox_row( 'block_chrome_30', __( 'Block Chrome 30 signature', 'techdex-search-scrape-guard' ), $settings ); ?>
					<?php self::number_row( 'rate_limit', __( 'Searches per window', 'techdex-search-scrape-guard' ), $settings, 1, 120 ); ?>
					<?php self::number_row( 'rate_window', __( 'Rate window in seconds', 'techdex-search-scrape-guard' ), $settings, 10, 3600 ); ?>
					<?php self::number_row( 'cooldown', __( 'Cooldown in seconds', 'techdex-search-scrape-guard' ), $settings, 30, 86400 ); ?>
					<?php self::checkbox_row( 'require_verification', __( 'Require Turnstile verification before searching', 'techdex-search-scrape-guard' ), $settings ); ?>
					<?php self::text_row( 'turnstile_site_key', __( 'Turnstile site key', 'techdex-search-scrape-guard' ), $settings, 'text' ); ?>
					<?php self::text_row( 'turnstile_secret_key', __( 'Turnstile secret key', 'techdex-search-scrape-guard' ), $settings, 'password' ); ?>
					<?php self::number_row( 'verified_cookie_lifetime', __( 'Verified cookie lifetime in seconds', 'techdex-search-scrape-guard' ), $settings, 60, 86400 ); ?>
					<?php self::checkbox_row( 'add_robots_directives', __( 'Add search exclusions to virtual robots.txt', 'techdex-search-scrape-guard' ), $settings ); ?>
					<?php self::checkbox_row( 'ensure_search_noindex', __( 'Ensure search results are noindex', 'techdex-search-scrape-guard' ), $settings ); ?>
				</table>
				<?php submit_button(); ?>
			</form>

			<h2><?php esc_html_e( 'Recent search-protection events', 'techdex-search-scrape-guard' ); ?></h2>
			<div id="tdssg-log-panel" class="tdssg-log-panel" data-pages="<?php echo esc_attr( $pages ); ?>">
				<div class="tdssg-log-toolbar">
					<p><?php esc_html_e( 'The latest 250 browser signatures are shown in pages of 50 and update automatically every 30 seconds. Repeated events are combined into one row.', 'techdex-search-scrape-guard' ); ?> <span class="tdssg-log-countdown" aria-live="off"></span> <span class="tdssg-log-status" role="status" aria-live="polite"></span></p>
					<div>
						<button type="button" class="button tdssg-refresh-log"><?php esc_html_e( 'Refresh now', 'techdex-search-scrape-guard' ); ?></button>
						<button type="button" class="button button-link-delete tdssg-clear-log"><?php esc_html_e( 'Clear log', 'techdex-search-scrape-guard' ); ?></button>
					</div>
				</div>
				<div class="tdssg-log-search" role="search">
					<label for="tdssg-log-search"><?php esc_html_e( 'Search the log', 'techdex-search-scrape-guard' ); ?></label>
					<input id="tdssg-log-search" class="regular-text" type="search" autocomplete="off" placeholder="<?php esc_attr_e( 'Browser string, reason, network, or search text', 'techdex-search-scrape-guard' ); ?>">
					<button type="button" class="button tdssg-search-log"><?php esc_html_e( 'Search', 'techdex-search-scrape-guard' ); ?></button>
					<button type="button" class="button-link tdssg-clear-search" hidden><?php esc_html_e( 'Clear search', 'techdex-search-scrape-guard' ); ?></button>
					<span class="description"><?php esc_html_e( 'Connection addresses are retained only as masked IPv4 /24 or IPv6 /48 networks.', 'techdex-search-scrape-guard' ); ?></span>
				</div>
				<div class="tdssg-log-scroll" tabindex="0">
					<table class="widefat striped">
						<thead>
							<tr>
								<th><?php esc_html_e( 'Last seen (UTC)', 'techdex-search-scrape-guard' ); ?></th>
								<th><?php esc_html_e( 'Network', 'techdex-search-scrape-guard' ); ?></th>
								<th><?php esc_html_e( 'Reason', 'techdex-search-scrape-guard' ); ?></th>
								<th><?php esc_html_e( 'HTTP', 'techdex-search-scrape-guard' ); ?></th>
								<th><?php esc_html_e( 'Count', 'techdex-search-scrape-guard' ); ?></th>
								<th><?php esc_html_e( 'Latest search sample', 'techdex-search-scrape-guard' ); ?></th>
								<th><?php esc_html_e( 'Browser / user agent', 'techdex-search-scrape-guard' ); ?></th>
							</tr>
						</thead>
						<tbody class="tdssg-log-body">
						<?php if ( ! $events ) : ?>
							<tr><td colspan="7"><?php esc_html_e( 'No search-protection events have been recorded.', 'techdex-search-scrape-guard' ); ?></td></tr>
						<?php else : ?>
							<?php foreach ( $events as $event ) : ?>
								<?php self::render_event_row( $event ); ?>
							<?php endforeach; ?>
						<?php endif; ?>
						</tbody>
					</table>
				</div>
				<div class="tdssg-log-pagination">
					<button type="button" class="button tdssg-page-previous" disabled><?php esc_html_e( 'Previous', 'techdex-search-scrape-guard' ); ?></button>
					<span class="tdssg-page-status" role="status" aria-live="polite"><?php
					/* translators: 1: Total number of log pages. 2: Total number of logged signatures. */
					echo esc_html( sprintf( __( 'Page 1 of %1$d (%2$d signatures)', 'techdex-search-scrape-guard' ), $pages, $total ) );
					?></span>
					<button type="button" class="button tdssg-page-next" <?php disabled( $pages, 1 ); ?>><?php esc_html_e( 'Next', 'techdex-search-scrape-guard' ); ?></button>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Render the user-facing changelog.
	 */
	public static function render_changelog_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to view this changelog.', 'techdex-search-scrape-guard' ) );
		}

		self::render_admin_page_header(
			'changelog',
			__( 'Plugin Changelog', 'techdex-search-scrape-guard' ),
			__( 'Review the release history for TechDex Search Scrape Guard.', 'techdex-search-scrape-guard' )
		);
		?>
		<div class="wrap tdssg-page-content">
			<?php foreach ( self::changelog_entries() as $entry ) : ?>
				<div class="tdssg-card tdssg-changelog-entry">
					<div class="tdssg-changelog-heading">
						<h2><?php echo esc_html( $entry['version'] ); ?></h2>
						<p><?php echo esc_html( $entry['date'] ); ?></p>
					</div>
					<ul>
						<?php foreach ( $entry['items'] as $item ) : ?>
							<li><?php echo esc_html( $item ); ?></li>
						<?php endforeach; ?>
					</ul>
				</div>
			<?php endforeach; ?>
		</div>
		<?php
	}

	/**
	 * Render the shared dashboard header and tab navigation.
	 *
	 * @param string $active_tab Active tab.
	 * @param string $title      Page title.
	 * @param string $description Page description.
	 */
	private static function render_admin_page_header( $active_tab, $title, $description ) {
		$tabs = array(
			'dashboard' => array( 'label' => __( 'Dashboard', 'techdex-search-scrape-guard' ), 'url' => self::dashboard_url() ),
			'settings'  => array( 'label' => __( 'Settings & Log', 'techdex-search-scrape-guard' ), 'url' => self::settings_url() ),
			'changelog' => array( 'label' => __( 'Changelog', 'techdex-search-scrape-guard' ), 'url' => self::changelog_url() ),
		);
		?>
		<div class="wrap tdssg-admin-header">
			<div class="tdssg-admin-hero">
				<h1><?php echo esc_html( $title ); ?></h1>
				<p><?php echo esc_html( $description ); ?></p>
				<nav class="tdssg-admin-tabs" aria-label="<?php esc_attr_e( 'Search Scrape Guard pages', 'techdex-search-scrape-guard' ); ?>">
					<?php foreach ( $tabs as $key => $tab ) : ?>
						<a class="<?php echo esc_attr( $active_tab === $key ? 'is-active' : '' ); ?>" href="<?php echo esc_url( $tab['url'] ); ?>" <?php if ( $active_tab === $key ) : ?>aria-current="page"<?php endif; ?>><?php echo esc_html( $tab['label'] ); ?></a>
					<?php endforeach; ?>
				</nav>
			</div>
		</div>
		<?php
	}

	/**
	 * Return concise release-history entries for the dashboard.
	 *
	 * @return array
	 */
	private static function changelog_entries() {
		return array(
			array(
				'version' => __( 'Version 0.4.1', 'techdex-search-scrape-guard' ),
				'date'    => __( 'July 31, 2026', 'techdex-search-scrape-guard' ),
				'items'   => array(
					__( 'Completed the WordPress.org guideline and Plugin Check compliance pass.', 'techdex-search-scrape-guard' ),
					__( 'Improved autocomplete and password-manager positioning on editable fields.', 'techdex-search-scrape-guard' ),
					__( 'Moved plugin-row behavior into a locally enqueued script.', 'techdex-search-scrape-guard' ),
					__( 'Clarified when the optional Cloudflare Turnstile service is contacted.', 'techdex-search-scrape-guard' ),
				),
			),
			array(
				'version' => __( 'Version 0.4.0', 'techdex-search-scrape-guard' ),
				'date'    => __( 'July 31, 2026', 'techdex-search-scrape-guard' ),
				'items'   => array(
					__( 'Added the TechDex dashboard, Settings & Log tab, and in-plugin changelog.', 'techdex-search-scrape-guard' ),
					__( 'Added donation, creator, plugin details, and plugin-row links for the free release.', 'techdex-search-scrape-guard' ),
					__( 'Added plugin-screen notice isolation and tightened verification request handling.', 'techdex-search-scrape-guard' ),
					__( 'Expanded repository, privacy, and external-service documentation.', 'techdex-search-scrape-guard' ),
				),
			),
			array(
				'version' => __( 'Version 0.3.0', 'techdex-search-scrape-guard' ),
				'date'    => __( 'July 27, 2026', 'techdex-search-scrape-guard' ),
				'items'   => array(
					__( 'Added separate anonymous and logged-in search pagination ceilings.', 'techdex-search-scrape-guard' ),
					__( 'Kept logged-in limits at or above the anonymous limit during sanitization.', 'techdex-search-scrape-guard' ),
				),
			),
			array(
				'version' => __( 'Version 0.2.1', 'techdex-search-scrape-guard' ),
				'date'    => __( 'July 25, 2026', 'techdex-search-scrape-guard' ),
				'items'   => array(
					__( 'Made the bounded operational log the single reporting source.', 'techdex-search-scrape-guard' ),
					__( 'Removed redundant daily summary storage and added upgrade cleanup.', 'techdex-search-scrape-guard' ),
				),
			),
			array(
				'version' => __( 'Version 0.2.0', 'techdex-search-scrape-guard' ),
				'date'    => __( 'July 24, 2026', 'techdex-search-scrape-guard' ),
				'items'   => array(
					__( 'Added bounded, coalesced operational logging with search and pagination.', 'techdex-search-scrape-guard' ),
					__( 'Added browser strings, masked networks, search samples, refresh, and log search.', 'techdex-search-scrape-guard' ),
				),
			),
			array(
				'version' => __( 'Version 0.1.0', 'techdex-search-scrape-guard' ),
				'date'    => __( 'July 24, 2026', 'techdex-search-scrape-guard' ),
				'items'   => array(
					__( 'Introduced early public-search validation, bounded rate limiting, optional Turnstile, lightweight responses, and crawler guidance.', 'techdex-search-scrape-guard' ),
				),
			),
		);
	}

	/**
	 * Return the dashboard URL.
	 *
	 * @return string
	 */
	private static function dashboard_url() {
		return add_query_arg( 'page', self::DASHBOARD_PAGE_SLUG, admin_url( 'admin.php' ) );
	}

	/**
	 * Return the settings and log URL.
	 *
	 * @return string
	 */
	private static function settings_url() {
		return add_query_arg( 'page', self::SETTINGS_PAGE_SLUG, admin_url( 'admin.php' ) );
	}

	/**
	 * Return the changelog URL.
	 *
	 * @return string
	 */
	private static function changelog_url() {
		return add_query_arg( 'page', self::CHANGELOG_PAGE_SLUG, admin_url( 'admin.php' ) );
	}

	/**
	 * Return the standard WordPress plugin-details modal URL.
	 *
	 * @return string
	 */
	private static function plugin_details_url() {
		return add_query_arg(
			array(
				'tab'       => 'plugin-information',
				'plugin'    => self::PRODUCT_KEY,
				'TB_iframe' => 'true',
				'width'     => 600,
				'height'    => 550,
			),
			admin_url( 'plugin-install.php' )
		);
	}

	/**
	 * Determine whether an admin hook or screen ID belongs to this plugin.
	 *
	 * @param string $screen_id Admin screen identifier.
	 * @return bool
	 */
	private static function is_plugin_admin_screen( $screen_id ) {
		return in_array(
			(string) $screen_id,
			array(
				'toplevel_page_' . self::DASHBOARD_PAGE_SLUG,
				'search-scrape-guard_page_' . self::SETTINGS_PAGE_SLUG,
				'admin_page_' . self::CHANGELOG_PAGE_SLUG,
			),
			true
		);
	}

	/**
	 * Render a checkbox setting row.
	 *
	 * @param string $key      Setting key.
	 * @param string $label    Label.
	 * @param array  $settings Settings.
	 */
	private static function checkbox_row( $key, $label, $settings ) {
		?>
		<tr>
			<th scope="row"><?php echo esc_html( $label ); ?></th>
			<td>
				<label>
					<input type="checkbox" autocomplete="off" name="<?php echo esc_attr( TDSSG_Settings::OPTION_KEY . '[' . $key . ']' ); ?>" value="1" <?php checked( ! empty( $settings[ $key ] ) ); ?>>
					<?php esc_html_e( 'Enabled', 'techdex-search-scrape-guard' ); ?>
				</label>
			</td>
		</tr>
		<?php
	}

	/**
	 * Render a number setting row.
	 *
	 * @param string $key      Setting key.
	 * @param string $label    Label.
	 * @param array  $settings Settings.
	 * @param int    $min      Minimum.
	 * @param int    $max      Maximum.
	 */
	private static function number_row( $key, $label, $settings, $min, $max ) {
		?>
		<tr>
			<th scope="row"><label for="tdssg-<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $label ); ?></label></th>
			<td><input class="small-text" id="tdssg-<?php echo esc_attr( $key ); ?>" type="number" min="<?php echo esc_attr( $min ); ?>" max="<?php echo esc_attr( $max ); ?>" autocomplete="off" name="<?php echo esc_attr( TDSSG_Settings::OPTION_KEY . '[' . $key . ']' ); ?>" value="<?php echo esc_attr( $settings[ $key ] ); ?>"></td>
		</tr>
		<?php
	}

	/**
	 * Render a text setting row.
	 *
	 * @param string $key      Setting key.
	 * @param string $label    Label.
	 * @param array  $settings Settings.
	 * @param string $type     Input type.
	 */
	private static function text_row( $key, $label, $settings, $type ) {
		$autocomplete = 'password' === $type ? 'new-password' : 'off';
		?>
		<tr>
			<th scope="row"><label for="tdssg-<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $label ); ?></label></th>
			<td><input class="regular-text" id="tdssg-<?php echo esc_attr( $key ); ?>" type="<?php echo esc_attr( $type ); ?>" autocomplete="<?php echo esc_attr( $autocomplete ); ?>" name="<?php echo esc_attr( TDSSG_Settings::OPTION_KEY . '[' . $key . ']' ); ?>" value="<?php echo esc_attr( $settings[ $key ] ); ?>"></td>
		</tr>
		<?php
	}

	/**
	 * Verify an administrator log request.
	 */
	private static function verify_log_request() {
		check_ajax_referer( 'tdssg_log_actions', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'You are not allowed to manage this search-protection log.', 'techdex-search-scrape-guard' ) ), 403 );
		}
	}

	/**
	 * Build the event payload for the initial or AJAX page.
	 *
	 * @param int    $page   Requested page.
	 * @param string $search Optional filter.
	 * @return array
	 */
	private static function events_response( $page, $search ) {
		$per_page = 50;
		$total    = TDSSG_Logger::count( $search );
		$pages    = max( 1, (int) ceil( $total / $per_page ) );
		$page     = min( max( 1, absint( $page ) ), $pages );
		$rows     = TDSSG_Logger::recent( $per_page, ( $page - 1 ) * $per_page, $search );
		$events   = array_map(
			static function ( $event ) {
				return array(
					'time'       => (string) $event->last_seen,
					'network'    => (string) $event->network_masked,
					'reason'     => str_replace( '_', ' ', (string) $event->reason ),
					'status'     => (string) absint( $event->status_code ),
					'count'      => (string) absint( $event->event_count ),
					'search'     => (string) $event->search_sample,
					'user_agent' => (string) $event->user_agent,
				);
			},
			$rows
		);

		return array(
			'events'     => $events,
			'pagination' => array(
				'page'  => $page,
				'pages' => $pages,
				'total' => $total,
			),
		);
	}

	/**
	 * Render one initial event row.
	 *
	 * @param object $event Event row.
	 */
	private static function render_event_row( $event ) {
		?>
		<tr>
			<td><?php echo esc_html( $event->last_seen ); ?></td>
			<td><code><?php echo esc_html( $event->network_masked ); ?></code></td>
			<td><?php echo esc_html( str_replace( '_', ' ', $event->reason ) ); ?></td>
			<td><?php echo esc_html( (string) absint( $event->status_code ) ); ?></td>
			<td><?php echo esc_html( (string) absint( $event->event_count ) ); ?></td>
			<td class="tdssg-search-cell"><?php echo esc_html( $event->search_sample ?: '-' ); ?></td>
			<td class="tdssg-user-agent-cell"><code><?php echo esc_html( $event->user_agent ?: '-' ); ?></code></td>
		</tr>
		<?php
	}

}
