<?php
/**
 * HtaccessAjax — ALL AJAX handlers for the .htaccess module.
 *
 * Every operation that writes to .htaccess is recorded in the change-log
 * table (ultraguard_htaccess_change_log) via the private log_change() helper.
 * The log captures: user, reason, file size, SHA-256 hash, backup path, IP.
 *
 * Covers actions from every htaccess subpage:
 *   Dashboard       : refresh stats, create backup, download, verify, clear cache, emergency access
 *   Security Rules  : toggle feature, apply/test/enable/disable/backup/restore/export rules, generate
 *   Backups         : create/restore/delete/download/compare/validate backups, settings
 *   Security Events : filter/export/clear/refresh events, whitelist IP, event details
 *   Custom Rules    : save/preview/validate/backup/restore/import/export/optimize htaccess
 *   Settings        : save settings, test, view, reset, restore backup
 *
 * @package UltraguardPro\modules\htaccess\ajax
 */

namespace UltraguardPro\modules\htaccess\ajax;

use UltraguardPro\Plugin;
use UltraguardPro\modules\htaccess\db\HtaccessChangeLogTable;
use UltraguardPro\support\AjaxResponseBuilder;

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

// phpcs:disable WordPress.Security.NonceVerification.Missing -- Htaccess AJAX actions are registered through the shared protected AJAX manager.

class HtaccessAjax {

	private Plugin $plugin;
	private string $htaccess_path;
	private string $backup_dir;

	public function __construct( Plugin $plugin ) {
		$this->plugin        = $plugin;
		$this->htaccess_path = ABSPATH . '.htaccess';
		$upload_dir          = wp_upload_dir();
		$this->backup_dir    = trailingslashit( $upload_dir['basedir'] ) . 'ultraguard-htaccess-backups/';
	}

	public function register(): void {
		$ajax    = $this->plugin->ajax;
		$actions = [
			// ── Legacy ──────────────────────────────────────────────────────
			'ultraguard_htaccess_read'    => [ $this, 'handle_read' ],
			'ultraguard_htaccess_save'    => [ $this, 'handle_save' ],
			'ultraguard_htaccess_restore' => [ $this, 'handle_restore' ],
			'ultraguard_htaccess_status'  => [ $this, 'handle_status' ],

			// ── Dashboard page ────────────────────────────────────────────
			'ultraguard_refresh_htaccess_stats'   => [ $this, 'handle_refresh_stats' ],
			'ultraguard_create_htaccess_backup'   => [ $this, 'handle_create_backup' ],
			'ultraguard_download_htaccess'         => [ $this, 'handle_download_htaccess' ],
			'ultraguard_verify_htaccess'           => [ $this, 'handle_verify_htaccess' ],
			'ultraguard_clear_htaccess_cache'      => [ $this, 'handle_clear_cache' ],
			'ultraguard_toggle_emergency_access'   => [ $this, 'handle_emergency_access' ],

			// ── Security Rules page ───────────────────────────────────────
			'ultraguard_toggle_security_feature'   => [ $this, 'handle_toggle_feature' ],
			'ultraguard_apply_htaccess_rules'      => [ $this, 'handle_apply_rules' ],
			'ultraguard_test_htaccess_rules'       => [ $this, 'handle_test_rules' ],
			'ultraguard_enable_all_security'       => [ $this, 'handle_enable_all_security' ],
			'ultraguard_disable_all_security'      => [ $this, 'handle_disable_all_security' ],
			'ultraguard_backup_security_rules'     => [ $this, 'handle_create_backup' ],
			'ultraguard_restore_security_rules'    => [ $this, 'handle_restore_security_rules' ],
			'ultraguard_export_security_rules'     => [ $this, 'handle_export_security_rules' ],
			'ultraguard_copy_security_rules'       => [ $this, 'handle_copy_security_rules' ],
			'ultraguard_generate_htaccess_rules'   => [ $this, 'handle_generate_rules' ],
			'ultraguard_refresh_security_stats'    => [ $this, 'handle_refresh_stats' ],
			'ultraguard_download_security_rules'   => [ $this, 'handle_download_htaccess' ],

			// ── Backups page ──────────────────────────────────────────────
			'ultraguard_restore_backup'            => [ $this, 'handle_restore_backup' ],
			'ultraguard_delete_backup'             => [ $this, 'handle_delete_backup' ],
			'ultraguard_download_backup'           => [ $this, 'handle_download_backup' ],
			'ultraguard_download_backup_file'      => [ $this, 'handle_download_backup' ],
			'ultraguard_download_backup_zip'       => [ $this, 'handle_download_backup_zip' ],
			'ultraguard_download_all_backups'      => [ $this, 'handle_download_backup_zip' ],
			'ultraguard_compare_backups'           => [ $this, 'handle_compare_backups' ],
			'ultraguard_validate_backups'          => [ $this, 'handle_validate_backups' ],
			'ultraguard_cleanup_backups'           => [ $this, 'handle_cleanup_backups' ],
			'ultraguard_restore_latest'            => [ $this, 'handle_restore_latest' ],
			'ultraguard_save_backup_settings'      => [ $this, 'handle_save_backup_settings' ],
			'ultraguard_toggle_auto_backup'        => [ $this, 'handle_toggle_auto_backup' ],
			'ultraguard_export_backup_settings'    => [ $this, 'handle_export_backup_settings' ],
			'ultraguard_import_backup_settings'    => [ $this, 'handle_import_backup_settings' ],

			// ── Security Events page ──────────────────────────────────────
			'ultraguard_filter_events'             => [ $this, 'handle_filter_events' ],
			'ultraguard_export_security_logs'      => [ $this, 'handle_export_security_logs' ],
			'ultraguard_clear_security_logs'       => [ $this, 'handle_clear_security_logs' ],
			'ultraguard_refresh_security_logs'     => [ $this, 'handle_refresh_security_logs' ],
			'ultraguard_whitelist_ip_from_event'   => [ $this, 'handle_whitelist_from_event' ],
			'ultraguard_event_details'             => [ $this, 'handle_event_details' ],
			'ultraguard_bulk_ignore_events'        => [ $this, 'handle_bulk_ignore_events' ],
			'ultraguard_bulk_export_events'        => [ $this, 'handle_export_security_logs' ],
			'ultraguard_update_realtime_monitor'   => [ $this, 'handle_update_realtime' ],

			// ── Custom Rules page ─────────────────────────────────────────
			'ultraguard_save_custom_rules'         => [ $this, 'handle_save_custom_rules' ],
			'ultraguard_preview_htaccess'          => [ $this, 'handle_preview_htaccess' ],
			'ultraguard_validate_htaccess_syntax'  => [ $this, 'handle_validate_syntax' ],
			'ultraguard_backup_htaccess'           => [ $this, 'handle_create_backup' ],
			'ultraguard_export_htaccess'           => [ $this, 'handle_export_htaccess' ],
			'ultraguard_import_htaccess'           => [ $this, 'handle_import_htaccess' ],
			'ultraguard_optimize_htaccess'         => [ $this, 'handle_optimize_htaccess' ],
			'ultraguard_insert_template'           => [ $this, 'handle_insert_template' ],
			'ultraguard_check_conflicts'           => [ $this, 'handle_check_conflicts' ],

			// ── Change Log page ────────────────────────────────────────────
			'ultraguard_get_htaccess_change_log'   => [ $this, 'handle_get_change_log' ],
			'ultraguard_verify_htaccess_integrity'  => [ $this, 'handle_verify_integrity' ],
			'ultraguard_export_htaccess_change_log' => [ $this, 'handle_export_change_log' ],
			// Row-level record mutations (table action buttons)
			'ultraguard_delete_htaccess_log_entry'  => [ $this, 'handle_delete_log_entry' ],
			'ultraguard_ignore_security_event'      => [ $this, 'handle_ignore_security_event' ],
			'ultraguard_delete_security_event'      => [ $this, 'handle_delete_security_event' ],

			// ── Settings page ──────────────────────────────────────────────
			'ultraguard_save_htaccess_settings'    => [ $this, 'handle_save_settings' ],
			'ultraguard_test_htaccess'             => [ $this, 'handle_test_htaccess' ],
			'ultraguard_view_htaccess'             => [ $this, 'handle_read' ],
			'ultraguard_reset_htaccess'            => [ $this, 'handle_reset_htaccess' ],
			'ultraguard_restore_htaccess_backup'   => [ $this, 'handle_restore_backup' ],
			'ultraguard_download_htaccess_backup'  => [ $this, 'handle_download_backup' ],
			'ultraguard_download_htaccess_zip'     => [ $this, 'handle_download_backup_zip' ],
			// ── Realtime fallback polling ─────────────────────────────────────
			'ultraguard_rt_htaccess_status'        => [ $this, 'handle_rt_htaccess_status' ],
		];

		foreach ( $actions as $action => $cb ) {
			$ajax->register_action( $action, $cb );
		}
	}

	// =========================================================================
	// LEGACY HANDLERS
	// =========================================================================

	public function handle_read(): void {
		$this->cap();
		if ( ! file_exists( $this->htaccess_path ) ) {
			AjaxResponseBuilder::make()->error( __( '.htaccess file not found', 'ultraguard-security-suite' ) )->addNotification( __( '.htaccess file not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		AjaxResponseBuilder::make()
			->success()
			->addData( 'content',  file_get_contents( $this->htaccess_path ) )
			->addData( 'writable', /* phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- direct writability check for diagnostic response. */ is_writable( $this->htaccess_path ) )
			->addData( 'path',     $this->htaccess_path )
			->addData( 'size',     filesize( $this->htaccess_path ) )
			->addData( 'modified', filemtime( $this->htaccess_path ) )
			->send();
	}

	public function handle_save(): void {
		$this->cap();
		$content = sanitize_textarea_field( wp_unslash( $_POST['content'] ?? '' ) );
		$content = preg_replace( '/<\?(?:php|=)?.*?\?>/si', '', $content );
		$backup  = $this->maybe_backup( 'before_manual_save' );
		if ( file_put_contents( $this->htaccess_path, $content ) !== false ) {
			$this->log_change( 'manual_save', $content, $backup );
			do_action( 'ultraguard_htaccess_updated' );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess saved', 'ultraguard-security-suite' ) )
				->addNotification( __( '.htaccess saved successfully.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not write .htaccess', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not write .htaccess. Check file permissions.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_restore(): void {
		$this->cap();
		$backup = sanitize_text_field( wp_unslash( $_POST['backup_path'] ?? '' ) );
		if ( empty( $backup ) || ! file_exists( $backup ) || ! str_contains( $backup, 'ultraguard' ) ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid backup path', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid backup path.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$pre_backup = $this->maybe_backup( 'before_restore' );
		if ( copy( $backup, $this->htaccess_path ) ) {
			$restored = file_get_contents( $this->htaccess_path );
			$this->log_change( 'restore', $restored, $pre_backup );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess restored', 'ultraguard-security-suite' ) )
				->addNotification( __( '.htaccess restored from backup.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Restore failed', 'ultraguard-security-suite' ) )->addNotification( __( 'Restore failed. Check file permissions.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_status(): void {
		$this->cap();
		AjaxResponseBuilder::make()
			->success()
			->addData( 'exists',   file_exists( $this->htaccess_path ) )
			->addData( 'writable', /* phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- direct writability check for diagnostic response. */ is_writable( $this->htaccess_path ) )
			->addData( 'size',     file_exists( $this->htaccess_path ) ? filesize( $this->htaccess_path ) : 0 )
			->addData( 'modified', file_exists( $this->htaccess_path ) ? filemtime( $this->htaccess_path ) : null )
			->send();
	}

	// =========================================================================
	// DASHBOARD PAGE
	// =========================================================================

	public function handle_refresh_stats(): void {
		$this->cap();
		$backups       = $this->list_backups();
		$exists        = file_exists( $this->htaccess_path );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- direct writability check for diagnostics.
		$writable      = $exists && is_writable( $this->htaccess_path );
		$size          = $exists ? filesize( $this->htaccess_path ) : 0;
		$backup_count  = count( $backups );
		$sec_score     = $this->calculate_security_score();
		$recent_chg    = $this->get_recent_change_count( 7 );

		AjaxResponseBuilder::make()
			->success( __( 'Stats refreshed', 'ultraguard-security-suite' ) )
			->addData( 'exists',         $exists )
			->addData( 'writable',       $writable )
			->addData( 'size',           $size )
			->addData( 'backup_count',   $backup_count )
			->addData( 'latest_backup',  $backups[0] ?? null )
			->addData( 'security_score', $sec_score )
			->addData( 'recent_changes', $recent_chg )
			->updateText( '#ultraguard-htaccess-stat-score',       (string) $sec_score )
			->updateText( '#ultraguard-htaccess-stat-backups',     (string) $backup_count )
			->updateText( '#ultraguard-htaccess-stat-changes',     (string) $recent_chg )
			->updateText( '#ultraguard-htaccess-stat-size',        size_format( $size ) )
			->send();
	}

	public function handle_create_backup(): void {
		$this->cap();
		if ( ! file_exists( $this->htaccess_path ) ) {
			AjaxResponseBuilder::make()->error( __( '.htaccess not found', 'ultraguard-security-suite' ) )->addNotification( __( '.htaccess file not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$path = $this->do_backup( 'manual' );
		if ( $path ) {
			AjaxResponseBuilder::make()
				->success( __( 'Backup created', 'ultraguard-security-suite' ) )
				->addData( 'backup_path', $path )
				->addNotification( __( 'Backup created successfully.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Backup failed', 'ultraguard-security-suite' ) )->addNotification( __( 'Backup failed. Check directory permissions.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_download_htaccess(): void {
		$this->cap();
		if ( ! file_exists( $this->htaccess_path ) ) {
			AjaxResponseBuilder::make()->error( __( '.htaccess not found', 'ultraguard-security-suite' ) )->addNotification( __( '.htaccess file not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$dir  = wp_upload_dir();
		$path = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
		wp_mkdir_p( $path );
		$file = 'htaccess-' . gmdate( 'Y-m-d' ) . '.txt';
		copy( $this->htaccess_path, $path . $file );
		$dl_url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
		AjaxResponseBuilder::make()
			->success( __( '.htaccess ready for download', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $dl_url )
			->addNotification( __( '.htaccess ready for download.', 'ultraguard-security-suite' ), 'success' )
			->download( $dl_url, 'htaccess.txt' )
			->send();
	}

	public function handle_verify_htaccess(): void {
		$this->cap();

		// Deep structural verification.
		$content = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		$issues  = [];

		if ( str_contains( $content, '<?php' ) ) {
			$issues[] = __( 'PHP code detected — security risk', 'ultraguard-security-suite' );
		}
		if ( str_contains( $content, 'ExecCGI' ) ) {
			$issues[] = __( 'ExecCGI directive found', 'ultraguard-security-suite' );
		}

		// Cross-check with change-log hash.
		$integrity = apply_filters( 'ultraguard_htaccess_verify_integrity', null );
		$hash_ok   = is_array( $integrity ) ? $integrity['match'] : null;

		$valid = empty( $issues );
		$msg   = $valid ? __( '.htaccess verified OK', 'ultraguard-security-suite' ) : __( 'Issues found', 'ultraguard-security-suite' );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'valid', $valid )
			->addData( 'issues', $issues )
			->addData( 'hash_match', $hash_ok )
			->addNotification( $msg, $valid ? 'success' : 'warning' )
			->send();
	}

	public function handle_clear_cache(): void {
		$this->cap();
		$this->plugin->cache->flush_group( 'htaccess' );
		$this->plugin->cache->flush_group( 'ultraguard_htaccess_change_log' );
		AjaxResponseBuilder::make()
			->success( __( '.htaccess cache cleared', 'ultraguard-security-suite' ) )
			->addNotification( __( '.htaccess cache cleared.', 'ultraguard-security-suite' ), 'info' )
			->send();
	}

	public function handle_emergency_access(): void {
		$this->cap();
		$enabled = filter_var( wp_unslash( $_POST['enabled'] ?? '0' ), FILTER_VALIDATE_BOOLEAN );
		update_option( 'ultraguard_htaccess_emergency_mode', $enabled );
		if ( $enabled ) {
			$this->write_emergency_htaccess();
		} else {
			$this->restore_from_latest_backup();
		}
		$msg = $enabled ? __( 'Emergency access enabled', 'ultraguard-security-suite' ) : __( 'Emergency mode disabled', 'ultraguard-security-suite' );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'enabled', $enabled )
			->addNotification( $msg, $enabled ? 'warning' : 'info' )
			->send();
	}

	// =========================================================================
	// SECURITY RULES PAGE
	// =========================================================================

	public function handle_toggle_feature(): void {
		$this->cap();
		$feature = sanitize_key( wp_unslash( $_POST['feature'] ?? ''  ) );
		$enabled = filter_var( wp_unslash( $_POST['enabled'] ?? '0' ), FILTER_VALIDATE_BOOLEAN );
		if ( empty( $feature ) ) {
			AjaxResponseBuilder::make()->error( __( 'Feature key required', 'ultraguard-security-suite' ) )->addNotification( __( 'Feature key required.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		if ( ! in_array( $feature, $this->security_features(), true ) ) {
			AjaxResponseBuilder::make()->error( __( 'Unknown security feature.', 'ultraguard-security-suite' ) )->addNotification( __( 'Unknown security feature.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}

		$backup = $this->maybe_backup( 'before_toggle_' . $feature );
		update_option( 'ultraguard_htaccess_feature_' . $feature, $enabled );
		$written = $this->apply_all_enabled_features( $backup, 'toggle_feature', $feature );

		do_action( 'ultraguard_htaccess_feature_toggled', $feature, $enabled );

		$msg = $enabled
			/* translators: %s: string */
			? sprintf( __( 'Security rule "%s" enabled and applied to .htaccess.', 'ultraguard-security-suite' ), $feature )
			/* translators: %s: string */
			: sprintf( __( 'Security rule "%s" disabled and removed from .htaccess.', 'ultraguard-security-suite' ), $feature );

		if ( ! $written ) {
			$msg .= ' ' . __( '(Warning: .htaccess is not writable — change saved to database only.)', 'ultraguard-security-suite' );
		}

		$toggle_sel = '.ultraguard-security-feature[data-feature="' . esc_js( $feature ) . '"]';
		$builder = AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'feature', $feature )
			->addData( 'enabled', $enabled )
			->addData( 'written', $written )
			->addNotification( $msg, $enabled ? 'success' : 'info' )
			->updateAttribute( $toggle_sel . ' input[type="checkbox"]', 'checked', $enabled ? 'checked' : null );
		if ( $enabled ) {
			$builder->addClass( $toggle_sel, 'feature-enabled' )->removeClass( $toggle_sel, 'feature-disabled' );
		} else {
			$builder->removeClass( $toggle_sel, 'feature-enabled' )->addClass( $toggle_sel, 'feature-disabled' );
		}
		$builder->send();
	}

	/**
	 * Apply all currently-enabled security features to .htaccess.
	 * Replaces the UltraGuard Security Rules block in-place or appends it.
	 *
	 * @param string|null $backup_path  Pre-write backup (for the log entry).
	 * @param string      $reason       Log reason, defaults to 'apply_rules'.
	 * @param string|null $feature_key  Populated for toggle_feature calls.
	 * @return bool  True if .htaccess was written successfully.
	 */
	private function apply_all_enabled_features(
		?string $backup_path = null,
		string  $reason      = 'apply_rules',
		?string $feature_key = null
	): bool {
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- direct writability check before writing .htaccess.
		if ( ! file_exists( $this->htaccess_path ) || ! is_writable( $this->htaccess_path ) ) {
			return false;
		}

		$active_features = [];
		foreach ( $this->security_features() as $f ) {
			if ( get_option( 'ultraguard_htaccess_feature_' . $f, false ) ) {
				$active_features[] = $f;
			}
		}

		$rules_content = $this->generate_htaccess_from_rules( $active_features );
		$current       = file_get_contents( $this->htaccess_path );
		$marker_begin  = "# BEGIN UltraGuard Security Rules\n";
		$marker_end    = "# END UltraGuard Security Rules\n";

		if ( str_contains( $current, $marker_begin ) ) {
			$new = preg_replace(
				'/' . preg_quote( $marker_begin, '/' ) . '.*?' . preg_quote( $marker_end, '/' ) . '/s',
				$marker_begin . ( $rules_content ? $rules_content . "\n" : '' ) . $marker_end,
				$current
			);
		} else {
			$new = $marker_begin . ( $rules_content ? $rules_content . "\n" : '' ) . $marker_end . "\n" . $current;
		}

		$written = file_put_contents( $this->htaccess_path, $new );
		if ( $written !== false ) {
			$this->log_change( $reason, $new, $backup_path, $feature_key );
			do_action( 'ultraguard_htaccess_updated' );
		}
		return $written !== false;
	}

	public function handle_apply_rules(): void {
		$this->cap();
		// Sanitize each rule value before use.
            $rules = array_map( 'sanitize_text_field',
                wp_unslash( (array) ( $_POST['rules'] ?? [] ) )
            );
		$content = $this->generate_htaccess_from_rules( $rules );
		$backup  = $this->maybe_backup( 'before_apply_rules' );
		$current = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		$marker  = "# BEGIN UltraGuard Security Rules\n";
		$end     = "# END UltraGuard Security Rules\n";

		if ( str_contains( $current, $marker ) ) {
			$new = preg_replace(
				'/' . preg_quote( $marker, '/' ) . '.*?' . preg_quote( $end, '/' ) . '/s',
				$marker . $content . "\n" . $end,
				$current
			);
		} else {
			$new = $marker . $content . "\n" . $end . "\n" . $current;
		}

		if ( file_put_contents( $this->htaccess_path, $new ) !== false ) {
			$this->log_change( 'apply_rules', $new, $backup );
			do_action( 'ultraguard_htaccess_rules_applied' );
			AjaxResponseBuilder::make()
				->success( __( 'Security rules applied to .htaccess', 'ultraguard-security-suite' ) )
				->addNotification( __( 'Security rules applied successfully.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not write .htaccess', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not write .htaccess. Check permissions.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_test_rules(): void {
		$this->cap();
		$rules = array_map( 'sanitize_text_field',
            wp_unslash( (array) ( $_POST['rules'] ?? [] ) )
        );
		AjaxResponseBuilder::make()
			->success( __( 'Rules validated', 'ultraguard-security-suite' ) )
			->addData( 'preview', $this->generate_htaccess_from_rules( $rules ) )
			->addData( 'valid', true )
			->addNotification( __( 'Rules validated successfully.', 'ultraguard-security-suite' ), 'success' )
			->send();
	}

	public function handle_enable_all_security(): void {
		$this->cap();
		$backup = $this->maybe_backup( 'before_enable_all' );
		foreach ( $this->security_features() as $feature ) {
			update_option( 'ultraguard_htaccess_feature_' . $feature, true );
		}
		$written = $this->apply_all_enabled_features( $backup, 'apply_rules' );
		$msg     = __( 'All security features enabled and applied to .htaccess.', 'ultraguard-security-suite' );
		if ( ! $written ) { $msg .= ' ' . __( '(Warning: .htaccess is not writable.)', 'ultraguard-security-suite' ); }
		AjaxResponseBuilder::make()->success( $msg )->addData( 'written', $written )->addNotification( $msg, 'success' )->send();
	}

	public function handle_disable_all_security(): void {
		$this->cap();
		$backup = $this->maybe_backup( 'before_disable_all' );
		foreach ( $this->security_features() as $feature ) {
			update_option( 'ultraguard_htaccess_feature_' . $feature, false );
		}
		$written = $this->apply_all_enabled_features( $backup, 'apply_rules' );
		$msg     = __( 'All security features disabled and removed from .htaccess.', 'ultraguard-security-suite' );
		if ( ! $written ) { $msg .= ' ' . __( '(Warning: .htaccess is not writable.)', 'ultraguard-security-suite' ); }
		AjaxResponseBuilder::make()->success( $msg )->addData( 'written', $written )->addNotification( $msg, 'info' )->send();
	}

	public function handle_restore_security_rules(): void { $this->handle_restore_latest(); }

	public function handle_export_security_rules(): void {
		$this->cap();
		$content = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		if ( preg_match( '/# BEGIN UltraGuard Security Rules(.*?)# END UltraGuard Security Rules/s', $content, $m ) ) {
			$rules = trim( $m[1] );
		} else {
			$rules = $content;
		}
		$dir  = wp_upload_dir();
		$path = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
		wp_mkdir_p( $path );
		$file = 'security-rules-' . gmdate( 'Y-m-d' ) . '.txt';
		file_put_contents( $path . $file, $rules );
		$url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
		AjaxResponseBuilder::make()
			->success( __( 'Security rules exported', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $url )
			->addNotification( __( 'Security rules exported.', 'ultraguard-security-suite' ), 'success' )
			->download( $url )
			->send();
	}

	public function handle_copy_security_rules(): void {
		$this->cap();
		$content = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		AjaxResponseBuilder::make()->success( __( 'Rules ready to copy', 'ultraguard-security-suite' ) )->addData( 'content', $content )->addNotification( __( 'Rules copied to clipboard.', 'ultraguard-security-suite' ), 'info' )->send();
	}

	public function handle_generate_rules(): void {
		$this->cap();
		$features = [];
		foreach ( $this->security_features() as $f ) {
			if ( get_option( 'ultraguard_htaccess_feature_' . $f, false ) ) { $features[] = $f; }
		}
		$rules = $this->generate_htaccess_from_rules( $features );
		AjaxResponseBuilder::make()->success( __( 'Rules generated', 'ultraguard-security-suite' ) )->addData( 'rules', $rules )->addData( 'features', $features )->addNotification( __( 'Rules generated successfully.', 'ultraguard-security-suite' ), 'success' )->send();
	}

	// =========================================================================
	// BACKUPS PAGE
	// =========================================================================

	public function handle_restore_backup(): void {
		$this->cap();
		$file = sanitize_file_name( wp_unslash( $_POST['backup_file'] ?? ''  ) );
		$path = $this->backup_dir . $file;
		if ( empty( $file ) || ! file_exists( $path ) || ! str_contains( $path, $this->backup_dir ) ) {
			AjaxResponseBuilder::make()->error( __( 'Backup file not found', 'ultraguard-security-suite' ) )->addNotification( __( 'Backup file not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$pre = $this->maybe_backup( 'before_restore' );
		if ( copy( $path, $this->htaccess_path ) ) {
			$restored = file_get_contents( $this->htaccess_path );
			$this->log_change( 'restore', $restored, $pre );
			do_action( 'ultraguard_htaccess_restored', $file );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess restored from backup', 'ultraguard-security-suite' ) )
				->addNotification( __( '.htaccess restored from backup.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Restore failed', 'ultraguard-security-suite' ) )->addNotification( __( 'Restore failed.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_delete_backup(): void {
		$this->cap();
		$file = sanitize_file_name( wp_unslash( $_POST['backup_file'] ?? ''  ) );
		if ( empty( $file ) ) {
			AjaxResponseBuilder::make()->error( __( 'No file specified', 'ultraguard-security-suite' ) )->addNotification( __( 'No file specified.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$path = $this->backup_dir . $file;

		// Use realpath() to resolve symlinks and any ../ sequences, then confirm the
		// resolved path is strictly inside the backup directory.  A filename like
		// "../wp-config.php" would resolve outside the directory and be rejected.
		$real    = realpath( $path );
		$real_bd = realpath( $this->backup_dir );

		if ( $real === false || $real_bd === false || strpos( $real, $real_bd . DIRECTORY_SEPARATOR ) !== 0 ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid backup file', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid backup file.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}

		wp_delete_file( $real );
		AjaxResponseBuilder::make()
			->success( __( 'Backup deleted', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Backup deleted.', 'ultraguard-security-suite' ), 'info' )
			->addRemove( '.ultraguard-backup-row[data-backup-file="' . esc_js( basename( $file ) ) . '"]', true, 300 )
			->send();
	}

	public function handle_download_backup(): void {
		$this->cap();
		$file = sanitize_file_name( wp_unslash( $_POST['backup_file'] ?? ''  ) );
		$path = $this->backup_dir . $file;
		if ( ! file_exists( $path ) ) {
			AjaxResponseBuilder::make()->error( __( 'Backup not found', 'ultraguard-security-suite' ) )->addNotification( __( 'Backup not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$dir     = wp_upload_dir();
		$exp_dir = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
		wp_mkdir_p( $exp_dir );
		copy( $path, $exp_dir . $file );
		$url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
		AjaxResponseBuilder::make()
			->success( __( 'Backup ready for download', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $url )
			->addNotification( __( 'Backup ready for download.', 'ultraguard-security-suite' ), 'success' )
			->download( $url )
			->send();
	}

	public function handle_download_backup_zip(): void {
		$this->cap();
		$backups = $this->list_backups();
		$dir     = wp_upload_dir();
		$exp_dir = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
		wp_mkdir_p( $exp_dir );
		$zip_file = $exp_dir . 'htaccess-backups-' . gmdate( 'Y-m-d' ) . '.zip';
		if ( class_exists( 'ZipArchive' ) ) {
			$zip = new \ZipArchive();
			$zip->open( $zip_file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE );
			foreach ( $backups as $b ) {
				if ( file_exists( $b['path'] ) ) { $zip->addFile( $b['path'], $b['name'] ); }
			}
			$zip->close();
		}
		$url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . basename( $zip_file );
		AjaxResponseBuilder::make()
			->success( __( 'Backup zip ready', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $url )
			->addData( 'count', count( $backups ) )
			->addNotification( __( 'Backup zip ready.', 'ultraguard-security-suite' ), 'success' )
			->download( $url )
			->send();
	}

	public function handle_compare_backups(): void {
		$this->cap();
		$file_a    = sanitize_file_name( wp_unslash( $_POST['backup_a'] ?? ''  ) );
		$file_b    = sanitize_file_name( wp_unslash( $_POST['backup_b'] ?? ''  ) );
		$content_a = file_exists( $this->backup_dir . $file_a ) ? file_get_contents( $this->backup_dir . $file_a ) : '';
		$content_b = $file_b === 'current'
			? ( file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '' )
			: ( file_exists( $this->backup_dir . $file_b ) ? file_get_contents( $this->backup_dir . $file_b ) : '' );
		$identical = ( $content_a === $content_b );
		AjaxResponseBuilder::make()
			->success( __( 'Comparison complete', 'ultraguard-security-suite' ) )
			->addData( 'file_a',    $content_a )
			->addData( 'file_b',    $content_b )
			->addData( 'identical', $identical )
			->addData( 'diff_lines', $this->compute_diff_line_count( $content_a, $content_b ) )
			->addNotification( $identical ? __( 'Backups are identical.', 'ultraguard-security-suite' ) : __( 'Backups differ.', 'ultraguard-security-suite' ), $identical ? 'info' : 'warning' )
			->send();
	}

	public function handle_validate_backups(): void {
		$this->cap();
		$backups = $this->list_backups();
		$valid   = 0;
		foreach ( $backups as $b ) {
			if ( file_exists( $b['path'] ) && filesize( $b['path'] ) > 0 ) { $valid++; }
		}
		/* translators: %1$d: integer, %2$d: integer */
		$msg = sprintf( __( '%1$d of %2$d backups are valid', 'ultraguard-security-suite' ), $valid, count( $backups ) );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'total', count( $backups ) )
			->addData( 'valid', $valid )
			->addNotification( $msg, $valid === count( $backups ) ? 'success' : 'warning' )
			->send();
	}

	public function handle_cleanup_backups(): void {
		$this->cap();
		$keep    = max( 1, absint( $_POST['keep'] ?? 10 ) );
		$backups = $this->list_backups();
		$deleted = 0;
		$real_bd = realpath( $this->backup_dir );
		foreach ( array_slice( $backups, $keep ) as $b ) {
			$real = realpath( $b['path'] ?? '' );
			// Confirm path is inside the backup directory before deleting.
			if ( $real && $real_bd && strpos( $real, $real_bd . DIRECTORY_SEPARATOR ) === 0 ) {
				wp_delete_file( $real );
				$deleted++;
			}
		}
		/* translators: %d: integer */
		$msg = sprintf( __( '%d old backups removed', 'ultraguard-security-suite' ), $deleted );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'deleted', $deleted )
			->addData( 'remaining', count( $backups ) - $deleted )
			->addNotification( $msg, 'info' )
			->send();
	}

	public function handle_restore_latest(): void {
		$this->cap();
		$backups = $this->list_backups();
		if ( empty( $backups ) ) {
			AjaxResponseBuilder::make()->error( __( 'No backups found', 'ultraguard-security-suite' ) )->addNotification( __( 'No backups found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$pre = $this->maybe_backup( 'before_restore' );
		if ( copy( $backups[0]['path'], $this->htaccess_path ) ) {
			$restored = file_get_contents( $this->htaccess_path );
			$this->log_change( 'restore', $restored, $pre );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess restored from latest backup', 'ultraguard-security-suite' ) )
				->addNotification( __( 'Restored from latest backup.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Restore failed', 'ultraguard-security-suite' ) )->addNotification( __( 'Restore failed.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_save_backup_settings(): void {
		$this->cap();
		update_option( 'ultraguard_htaccess_backup_keep',         absint( $_POST['keep_count'] ?? 10 ) );
		update_option( 'ultraguard_htaccess_auto_backup_enabled', filter_var( wp_unslash( $_POST['auto_backup'] ?? '0' ), FILTER_VALIDATE_BOOLEAN ) );
		update_option( 'ultraguard_htaccess_backup_on_save',      filter_var( wp_unslash( $_POST['backup_on_save'] ?? '1' ), FILTER_VALIDATE_BOOLEAN ) );
		AjaxResponseBuilder::make()
			->success( __( 'Backup settings saved', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Backup settings saved.', 'ultraguard-security-suite' ), 'success' )
			->send();
	}

	public function handle_toggle_auto_backup(): void {
		$this->cap();
		$enabled = filter_var( wp_unslash( $_POST['enabled'] ?? '0' ), FILTER_VALIDATE_BOOLEAN );
		update_option( 'ultraguard_htaccess_auto_backup_enabled', $enabled );
		$msg = $enabled ? __( 'Auto-backup enabled', 'ultraguard-security-suite' ) : __( 'Auto-backup disabled', 'ultraguard-security-suite' );
		$builder = AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'enabled', $enabled )
			->addNotification( $msg, $enabled ? 'success' : 'info' )
			->updateAttribute( '#ultraguard-htaccess-auto-backup-toggle', 'data-enabled', $enabled ? '1' : '0' );
		if ( $enabled ) {
			$builder->addClass( '#ultraguard-htaccess-auto-backup-toggle', 'is-enabled' )
			        ->removeClass( '#ultraguard-htaccess-auto-backup-toggle', 'is-disabled' );
		} else {
			$builder->removeClass( '#ultraguard-htaccess-auto-backup-toggle', 'is-enabled' )
			        ->addClass( '#ultraguard-htaccess-auto-backup-toggle', 'is-disabled' );
		}
		$builder->send();
	}

	public function handle_export_backup_settings(): void {
		$this->cap();
		$s = [
			'keep_count'    => \UltraguardPro\ultraguard_pro()->get_option( 'htaccess_backup_keep', 10 ),
			'auto_backup'   => \UltraguardPro\ultraguard_pro()->get_option( 'htaccess_auto_backup_enabled', false ),
			'backup_on_save'=> \UltraguardPro\ultraguard_pro()->get_option( 'htaccess_backup_on_save', true ),
		];
		AjaxResponseBuilder::make()->success( __( 'Backup settings exported', 'ultraguard-security-suite' ) )->addData( 'settings', $s )->addNotification( __( 'Backup settings exported.', 'ultraguard-security-suite' ), 'info' )->send();
	}

	public function handle_import_backup_settings(): void {
		$this->cap();
		$json = sanitize_textarea_field( wp_unslash( $_POST['settings_json'] ?? '' ) );
		$s    = json_decode( $json, true );
		if ( ! is_array( $s ) ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid JSON', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid JSON format.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		if ( isset( $s['keep_count'] ) )    { update_option( 'ultraguard_htaccess_backup_keep', absint( $s['keep_count'] ) ); }
		if ( isset( $s['auto_backup'] ) )   { update_option( 'ultraguard_htaccess_auto_backup_enabled', (bool) $s['auto_backup'] ); }
		if ( isset( $s['backup_on_save'] ) ){ update_option( 'ultraguard_htaccess_backup_on_save', (bool) $s['backup_on_save'] ); }
		AjaxResponseBuilder::make()
			->success( __( 'Backup settings imported', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Backup settings imported.', 'ultraguard-security-suite' ), 'success' )
			->addReload( 1000 )
			->send();
	}

	// =========================================================================
	// SECURITY EVENTS PAGE
	// =========================================================================

	public function handle_filter_events(): void {
		$this->cap();
		$page  = max( 1, absint( $_POST['page'] ?? 1 ) );
		$type  = sanitize_key( wp_unslash( $_POST['event_type'] ?? ''  ) );
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		$conds = $type ? [ 'event_type' => $type ] : [];
		$data  = $table ? $table->paginate( $page, 50, $conds, 'created_at', 'DESC' ) : [ 'records' => [], 'total' => 0 ];
		AjaxResponseBuilder::make()->success()->mergeData( (array) $data )->send();
	}

	public function handle_export_security_logs(): void {
		$this->cap();
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		$csv   = $table ? $table->export_to_csv( [], [ 'event_type', 'severity', 'ip_address', 'message', 'created_at' ], 100000 ) : '';
		$dir   = wp_upload_dir();
		$path  = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
		wp_mkdir_p( $path );
		$file  = 'security-events-' . gmdate( 'Y-m-d' ) . '.csv';
		file_put_contents( $path . $file, $csv );
		$url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
		AjaxResponseBuilder::make()
			->success( __( 'Security events exported', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $url )
			->addNotification( __( 'Security events exported.', 'ultraguard-security-suite' ), 'success' )
			->download( $url )
			->send();
	}

	public function handle_clear_security_logs(): void {
		$this->cap();
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		if ( $table ) { $table->truncate(); }
		AjaxResponseBuilder::make()
			->success( __( 'Security logs cleared', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Security logs cleared.', 'ultraguard-security-suite' ), 'success' )
			->emptyElement( '#ultraguard-htaccess-events-table tbody' )
			->updateText( '#ultraguard-htaccess-events-count', '0' )
			->send();
	}

	public function handle_refresh_security_logs(): void {
		$this->cap();
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		$total = $table ? $table->count() : 0;
		$today = $table ? $table->count( [ 'created_at >=' => gmdate( 'Y-m-d 00:00:00' ) ] ) : 0;
		AjaxResponseBuilder::make()
			->success( __( 'Security logs refreshed', 'ultraguard-security-suite' ) )
			->addData( 'total', $total )
			->addData( 'today', $today )
			->updateText( '#ultraguard-htaccess-events-total', (string) $total )
			->updateText( '#ultraguard-htaccess-events-today', (string) $today )
			->send();
	}

	public function handle_whitelist_from_event(): void {
		$this->cap();
		$ip = sanitize_text_field( wp_unslash( $_POST['ip_address'] ?? '' ) );
		if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid IP', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid IP address.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$wl_t = $this->plugin->modules->get_db_tables()->get_table( 'ip_whitelist' );
		if ( $wl_t ) { $wl_t->add_ip( $ip, 'whitelisted_from_security_event' ); }
		AjaxResponseBuilder::make()
			/* translators: %s: string */
			->success( sprintf( __( 'IP %s whitelisted', 'ultraguard-security-suite' ), $ip ) )
			/* translators: %s: string */
			->addNotification( sprintf( __( 'IP %s whitelisted.', 'ultraguard-security-suite' ), $ip ), 'success' )
			->send();
	}

	public function handle_event_details(): void {
		$this->cap();
		$id    = absint( $_POST['event_id'] ?? 0 );
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		$event = $table ? $table->get( $id ) : null;
		if ( ! $event ) {
			AjaxResponseBuilder::make()->error( __( 'Event not found', 'ultraguard-security-suite' ) )->addNotification( __( 'Event not found.', 'ultraguard-security-suite' ), 'error' )->send( 404 );
			return;
		}
		AjaxResponseBuilder::make()->success()->mergeData( (array) $event )->send();
	}

	public function handle_bulk_ignore_events(): void {
		$this->cap();
		$ids   = array_map( 'absint', (array) wp_unslash( $_POST['event_ids'] ?? [] ) );
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		$count = 0;
		if ( $table ) {
			foreach ( $ids as $id ) {
				if ( $table->update( $id, [ 'status' => 'ignored' ] ) ) { $count++; }
			}
		}
		/* translators: %d: number */
		$msg = sprintf( _n( '%d event ignored', '%d events ignored', $count, 'ultraguard-security-suite' ), $count );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'ignored', $count )
			->addNotification( $msg, 'info' )
			->addReload( 800 )
			->send();
	}

	public function handle_update_realtime(): void {
		$this->cap();
		$enabled = filter_var( wp_unslash( $_POST['enabled'] ?? '0' ), FILTER_VALIDATE_BOOLEAN );
		update_option( 'ultraguard_htaccess_realtime_monitor', $enabled );
		$msg = $enabled ? __( 'Realtime monitor enabled', 'ultraguard-security-suite' ) : __( 'Realtime monitor disabled', 'ultraguard-security-suite' );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'enabled', $enabled )
			->addNotification( $msg, $enabled ? 'success' : 'info' )
			->send();
	}

	// =========================================================================
	// CUSTOM RULES PAGE
	// =========================================================================

	public function handle_save_custom_rules(): void {
		$this->cap();
		$rules_block = sanitize_textarea_field( wp_unslash( $_POST['custom_rules'] ?? '' ) );
		$rules_block = preg_replace( '/<\?(?:php|=)?.*?\?>/si', '', $rules_block );
		$backup      = $this->maybe_backup( 'before_custom_rules' );
		$current     = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		$marker      = "# BEGIN UltraGuard Custom Rules\n";
		$end         = "# END UltraGuard Custom Rules\n";

		if ( str_contains( $current, $marker ) ) {
			$new = preg_replace(
				'/' . preg_quote( $marker, '/' ) . '.*?' . preg_quote( $end, '/' ) . '/s',
				$marker . $rules_block . "\n" . $end,
				$current
			);
		} else {
			$new = $current . "\n" . $marker . $rules_block . "\n" . $end;
		}

		if ( file_put_contents( $this->htaccess_path, $new ) !== false ) {
			$this->log_change( 'custom_rules_saved', $new, $backup );
			do_action( 'ultraguard_htaccess_custom_rules_saved' );
			AjaxResponseBuilder::make()
				->success( __( 'Custom rules saved', 'ultraguard-security-suite' ) )
				->addNotification( __( 'Custom rules saved to .htaccess.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not write .htaccess', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not write .htaccess. Check permissions.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_preview_htaccess(): void {
		$this->cap();
		$content = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		AjaxResponseBuilder::make()->success( __( 'Preview loaded', 'ultraguard-security-suite' ) )->addData( 'content', $content )->addData( 'size', strlen( $content ) )->send();
	}

	public function handle_validate_syntax(): void {
		$this->cap();
		$content = sanitize_textarea_field(
			wp_unslash( $_POST['htaccess_content'] ?? ( file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '' ) )
		);
		$issues  = [];

		if ( str_contains( $content, '<?php' ) || str_contains( $content, '<?' ) ) {
			$issues[] = __( 'PHP tags detected', 'ultraguard-security-suite' );
		}

		// Balanced IfModule blocks.
		$open  = substr_count( $content, '<IfModule' );
		$close = substr_count( $content, '</IfModule>' );
		if ( $open !== $close ) {
			$issues[] = sprintf(
				/* translators: %1$d: count of opening IfModule blocks, %2$d: count of closing IfModule blocks. */
				__( 'Unbalanced IfModule blocks: %1$d open, %2$d close', 'ultraguard-security-suite' ),
				$open, $close
			);
		}

		$lines = explode( "\n", $content );
		foreach ( $lines as $i => $line ) {
			$line = trim( $line );
			if ( empty( $line ) || str_starts_with( $line, '#' ) ) { continue; }
			if ( preg_match( '/^Require\s+ip\s+(\S+)$/', $line, $m ) && ! filter_var( trim( $m[1] ), FILTER_VALIDATE_IP ) ) {
				/* translators: %d: integer */
				$issues[] = sprintf( __( 'Line %d: invalid IP in Require directive', 'ultraguard-security-suite' ), $i + 1 );
			}
		}

		$valid  = empty( $issues );
		$msg    = $valid ? __( 'Syntax is valid', 'ultraguard-security-suite' ) : __( 'Syntax issues found', 'ultraguard-security-suite' );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'valid', $valid )
			->addData( 'issues', $issues )
			->addNotification( $msg, $valid ? 'success' : 'warning' )
			->send();
	}

	public function handle_export_htaccess(): void { $this->handle_download_htaccess(); }

	public function handle_import_htaccess(): void {
		$this->cap();
		$content = sanitize_textarea_field( wp_unslash( $_POST['htaccess_content'] ?? '' ) );
		$content = preg_replace( '/<\?(?:php|=)?.*?\?>/si', '', $content );
		if ( empty( trim( $content ) ) ) {
			AjaxResponseBuilder::make()->error( __( 'Content cannot be empty', 'ultraguard-security-suite' ) )->addNotification( __( 'Content cannot be empty.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$backup = $this->maybe_backup( 'before_import' );
		if ( file_put_contents( $this->htaccess_path, $content ) !== false ) {
			$this->log_change( 'import', $content, $backup );
			do_action( 'ultraguard_htaccess_updated' );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess content imported', 'ultraguard-security-suite' ) )
				->closeModal( 'htaccess-import-rules-modal' )
				->addReload( 800 )
				->addNotification( __( '.htaccess content imported.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not write .htaccess', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not write .htaccess.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_optimize_htaccess(): void {
		$this->cap();
		if ( ! file_exists( $this->htaccess_path ) ) {
			AjaxResponseBuilder::make()->error( __( '.htaccess not found', 'ultraguard-security-suite' ) )->addNotification( __( '.htaccess file not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$backup  = $this->maybe_backup( 'before_optimize' );
		$content = file_get_contents( $this->htaccess_path );
		// Remove triplicate+ blank lines.
		$content = preg_replace( "/(\r?\n){3,}/", "\n\n", $content );
		// Strip trailing whitespace per line.
		$content = implode( "\n", array_map( 'rtrim', explode( "\n", $content ) ) );
		if ( file_put_contents( $this->htaccess_path, $content ) !== false ) {
			$this->log_change( 'optimize', $content, $backup );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess optimised', 'ultraguard-security-suite' ) )
				->addData( 'size', strlen( $content ) )
				->addNotification( __( '.htaccess optimised successfully.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not write .htaccess', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not write .htaccess.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	public function handle_insert_template(): void {
		$this->cap();
		$template_key = sanitize_key( wp_unslash( $_POST['template'] ?? ''  ) );
		$template     = $this->get_htaccess_template( $template_key );
		if ( ! $template ) {
			AjaxResponseBuilder::make()->error( __( 'Template not found', 'ultraguard-security-suite' ) )->addNotification( __( 'Template not found.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}

		$current_rules = sanitize_textarea_field( wp_unslash( $_POST['custom_rules'] ?? '' ) );
		$current_rules = trim( (string) $current_rules );
		$template      = trim( (string) $template );

		if ( '' === $current_rules ) {
			$merged_rules = $template;
		} elseif ( str_contains( $current_rules, $template ) ) {
			$merged_rules = $current_rules;
		} else {
			$merged_rules = $current_rules . "\n\n" . $template;
		}

		AjaxResponseBuilder::make()
			->success( __( 'Template inserted', 'ultraguard-security-suite' ) )
			->addData( 'content', $merged_rules )
			->addData( 'saved_values', [ 'custom_rules' => $merged_rules ] )
			->updateText( '[name="custom_rules"]', $merged_rules, 'val' )
			->addNotification( __( 'Template inserted into the editor.', 'ultraguard-security-suite' ), 'success' )
			->send();
	}

	public function handle_check_conflicts(): void {
		$this->cap();
		$content   = file_exists( $this->htaccess_path ) ? file_get_contents( $this->htaccess_path ) : '';
		$conflicts = [];
		if ( substr_count( $content, 'RewriteEngine On' ) > 1 ) {
			$conflicts[] = __( 'Multiple RewriteEngine On directives found', 'ultraguard-security-suite' );
		}
		// Duplicate block markers.
		foreach ( [ 'WordPress', 'UltraGuard Security Rules', 'UltraGuard Custom Rules' ] as $block ) {
			if ( substr_count( $content, "# BEGIN {$block}" ) > 1 ) {
				/* translators: %s: string */
				$conflicts[] = sprintf( __( 'Duplicate "%s" block detected', 'ultraguard-security-suite' ), $block );
			}
		}
		$has_conflicts = ! empty( $conflicts );
		$msg = $has_conflicts ? __( 'Conflicts found', 'ultraguard-security-suite' ) : __( 'No conflicts detected', 'ultraguard-security-suite' );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'conflicts', $conflicts )
			->addData( 'has_conflicts', $has_conflicts )
			->addNotification( $msg, $has_conflicts ? 'warning' : 'success' )
			->send();
	}

	// =========================================================================
	// CHANGE LOG PAGE (new)
	// =========================================================================

	public function handle_get_change_log(): void {
		$this->cap();
		$page  = max( 1, absint( $_POST['page'] ?? 1 ) );
		$table = $this->change_log_table();
		if ( ! $table ) {
			AjaxResponseBuilder::make()->success()->addData( 'records', [] )->addData( 'total', 0 )->send();
			return;
		}
		$data = $table->paginate( $page, 25, [], 'created_at', 'DESC' );
		AjaxResponseBuilder::make()->success()->mergeData( (array) $data )->send();
	}

	public function handle_verify_integrity(): void {
		$this->cap();
		$table = $this->change_log_table();
		if ( ! $table ) {
			AjaxResponseBuilder::make()->error( __( 'Change log table unavailable', 'ultraguard-security-suite' ) )->addNotification( __( 'Change log table unavailable.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		$result = $table->verify_integrity();
		$msg = $result['match']
			? __( '.htaccess integrity verified — file matches the last logged write.', 'ultraguard-security-suite' )
			: __( 'Integrity mismatch — .htaccess may have been modified outside of UltraGuard.', 'ultraguard-security-suite' );
		AjaxResponseBuilder::make()
			->success( $msg )
			->mergeData( $result )
			->addNotification( $msg, $result['match'] ? 'success' : 'warning' )
			->updateText( '#ultraguard-htaccess-integrity-status', $result['match'] ? __( 'Verified', 'ultraguard-security-suite' ) : __( 'Mismatch!', 'ultraguard-security-suite' ) )
			->send();
	}

	public function handle_export_change_log(): void {
		$this->cap();
		$table = $this->change_log_table();
		$csv   = $table ? $table->export_to_csv( [], [ 'user_login', 'change_reason', 'feature_key', 'file_size', 'content_hash', 'ip_address', 'created_at' ], 100000 ) : '';
		$dir   = wp_upload_dir();
		$path  = trailingslashit( $dir['basedir'] ) . 'ultraguard-security-suite/exports/';
		wp_mkdir_p( $path );
		$file  = 'htaccess-change-log-' . gmdate( 'Y-m-d' ) . '.csv';
		file_put_contents( $path . $file, $csv );
		$url = trailingslashit( $dir['baseurl'] ) . 'ultraguard-security-suite/exports/' . $file;
		AjaxResponseBuilder::make()
			->success( __( 'Change log exported', 'ultraguard-security-suite' ) )
			->addData( 'download_url', $url )
			->addNotification( __( 'Change log exported.', 'ultraguard-security-suite' ), 'success' )
			->download( $url )
			->send();
	}

	// =========================================================================
	// SETTINGS PAGE
	// =========================================================================

	public function handle_save_settings(): void {
		$this->cap();
		$allowed = [
			'htaccess_auto_backup', 'htaccess_backup_keep', 'htaccess_backup_on_save',
			'htaccess_syntax_highlight', 'htaccess_line_numbers', 'htaccess_validate_on_save',
			'htaccess_track_changes',
		];
		foreach ( $allowed as $key ) {
			if ( array_key_exists( $key, $_POST ) ) {
				update_option( 'ultraguard_' . $key, sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) );
			}
		}
		do_action( 'ultraguard_htaccess_settings_saved' );
		AjaxResponseBuilder::make()
			->success( __( '.htaccess settings saved', 'ultraguard-security-suite' ) )
			->addNotification( __( 'Settings saved.', 'ultraguard-security-suite' ), 'success' )
			->send();
	}

	public function handle_test_htaccess(): void {
		$this->cap();
		$checks = [
			'exists'   => file_exists( $this->htaccess_path ),
			'writable' => /* phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- direct writability check for diagnostics. */ is_writable( $this->htaccess_path ),
			'size'     => file_exists( $this->htaccess_path ) ? filesize( $this->htaccess_path ) : 0,
		];
		$score = 0;
		if ( $checks['exists'] )   { $score += 50; }
		if ( $checks['writable'] ) { $score += 25; }
		if ( $checks['size'] > 0 ) { $score += 25; }

		// Integrity check from change log.
		$table     = $this->change_log_table();
		$integrity = $table ? $table->verify_integrity() : null;

		/* translators: %d: integer */
		$msg = sprintf( __( '.htaccess health score: %d%%', 'ultraguard-security-suite' ), $score );
		AjaxResponseBuilder::make()
			->success( $msg )
			->addData( 'checks', $checks )
			->addData( 'score', $score )
			->addData( 'integrity', $integrity )
			->addNotification( $msg, $score >= 75 ? 'success' : ( $score >= 50 ? 'warning' : 'error' ) )
			->updateText( '#ultraguard-htaccess-health-score', (string) $score )
			->send();
	}

	public function handle_reset_htaccess(): void {
		$this->cap();
		$backup  = $this->maybe_backup( 'before_reset' );
		$default = "# BEGIN WordPress\n<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteBase /\nRewriteRule ^index\\.php$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . /index.php [L]\n</IfModule>\n# END WordPress\n";
		if ( file_put_contents( $this->htaccess_path, $default ) !== false ) {
			$this->log_change( 'reset', $default, $backup );
			do_action( 'ultraguard_htaccess_updated' );
			AjaxResponseBuilder::make()
				->success( __( '.htaccess reset to WordPress default', 'ultraguard-security-suite' ) )
				->addNotification( __( '.htaccess reset to WordPress defaults.', 'ultraguard-security-suite' ), 'success' )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not write .htaccess', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not write .htaccess.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	// =========================================================================
	// PRIVATE HELPERS
	// =========================================================================

	/**
	 * Retrieve the change-log table object (or null if not yet installed).
	 */
	private function change_log_table(): ?HtaccessChangeLogTable {
		$db = $this->plugin->modules->get_db_tables();
		/** @var HtaccessChangeLogTable|null $table */
		$table = $db ? $db->get_table( 'htaccess_change_log' ) : null;
		return ( $table instanceof HtaccessChangeLogTable ) ? $table : null;
	}

	/**
	 * Log a file-write event to the change-log table.
	 * Silently no-ops if the table is unavailable.
	 *
	 * @param string      $reason
	 * @param string      $content     Content that was written.
	 * @param string|null $backup_path Pre-write backup path.
	 * @param string|null $feature_key Only for toggle_feature.
	 */
	private function log_change(
		string  $reason,
		string  $content,
		?string $backup_path = null,
		?string $feature_key = null
	): void {
		// Respect the track_changes setting, but always log resets/restores.
		$always_log = in_array( $reason, [ 'reset', 'restore' ], true );
		if ( ! $always_log && ! \UltraguardPro\ultraguard_pro()->get_option( 'htaccess_track_changes', true ) ) {
			return;
		}

		$table = $this->change_log_table();
		if ( $table ) {
			$table->record( $reason, $content, $backup_path, $feature_key );
		}
	}

	/**
	 * Return the count of change-log entries in the last N days.
	 */
	private function get_recent_change_count( int $days = 7 ): int {
		$table = $this->change_log_table();
		return $table ? $table->count_recent( $days ) : 0;
	}

	private function do_backup( string $reason = 'manual' ): ?string {
		if ( ! file_exists( $this->htaccess_path ) ) { return null; }
		wp_mkdir_p( $this->backup_dir );
		$file = 'htaccess-' . $reason . '-' . gmdate( 'Y-m-d-His' ) . '.txt';
		$dest = $this->backup_dir . $file;
		return copy( $this->htaccess_path, $dest ) ? $dest : null;
	}

	/**
	 * Conditionally create a backup and return its path (or null).
	 */
	private function maybe_backup( string $reason ): ?string {
		if (
			\UltraguardPro\ultraguard_pro()->get_option( 'htaccess_backup_on_save', true ) ||
			\UltraguardPro\ultraguard_pro()->get_option( 'htaccess_auto_backup_enabled', false )
		) {
			return $this->do_backup( $reason );
		}
		return null;
	}

	private function list_backups(): array {
		if ( ! is_dir( $this->backup_dir ) ) { return []; }
		$files = glob( $this->backup_dir . 'htaccess-*.txt' );
		if ( ! $files ) { return []; }
		rsort( $files );
		return array_map( fn( $f ) => [
			'path'     => $f,
			'name'     => basename( $f ),
			'size'     => filesize( $f ),
			'modified' => filemtime( $f ),
		], $files );
	}

	private function restore_from_latest_backup(): void {
		$backups = $this->list_backups();
		if ( ! empty( $backups ) && file_exists( $backups[0]['path'] ) ) {
			copy( $backups[0]['path'], $this->htaccess_path );
		}
	}

	private function write_emergency_htaccess(): void {
		$content = "# UltraGuard Emergency Access Mode\n# All restrictions temporarily removed.\nAllow from all\n";
		file_put_contents( $this->htaccess_path, $content );
		$this->log_change( 'manual_save', $content );
	}

	private function calculate_security_score(): int {
		$score = 0;
		if ( ! file_exists( $this->htaccess_path ) ) { return 0; }
		$content = file_get_contents( $this->htaccess_path );
		foreach ( [ 'Options -Indexes', 'X-Frame-Options', 'X-Content-Type-Options', 'BEGIN UltraGuard' ] as $marker ) {
			if ( str_contains( $content, $marker ) ) { $score += 25; }
		}
		return $score;
	}

	private function security_features(): array {
		return [
			'disable_directory_listing',
			'protect_wp_config',
			'protect_wp_includes',
			'protect_uploads_php',
			'block_xmlrpc',
			'security_headers',
			'block_hotlinking',
			'block_author_scan',
			'block_bad_bots',
			'prevent_script_injection',
			'limit_file_access',
		];
	}

	private function compute_diff_line_count( string $a, string $b ): int {
		$lines_a = explode( "\n", $a );
		$lines_b = explode( "\n", $b );
		$diff    = array_diff( $lines_a, $lines_b );
		return count( $diff );
	}

	private function generate_htaccess_from_rules( array $rules ): string {
		$plain_blocks   = [];
		$file_blocks    = [];
		$header_lines   = [];
		$rewrite_blocks = [];

		foreach ( $rules as $rule ) {
			$rule = sanitize_key( $rule );
			switch ( $rule ) {
				case 'disable_directory_listing':
					$plain_blocks[] = "# Disable directory listing\nOptions -Indexes";
					break;

				case 'protect_wp_config':
					$file_blocks[] = "# Protect wp-config.php\n<Files wp-config.php>\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order deny,allow\n    Deny from all\n  </IfModule>\n</Files>";
					break;

				case 'protect_wp_includes':
					// No RewriteBase — only needed when substituting a path, not with [F] flags
					$rewrite_blocks[] = "  # Protect wp-includes — block direct PHP execution\n  RewriteRule ^wp-includes/[^/]+\\.php$ - [F,L]\n  RewriteRule ^wp-includes/js/tinymce/langs/.+\\.php - [F,L]\n  RewriteRule ^wp-includes/theme-compat/ - [F,L]";
					break;

				case 'protect_uploads_php':
					$rewrite_blocks[] = "  # Block PHP execution in the uploads directory\n  RewriteRule ^wp-content/uploads/.*\\.php[0-9]?$ - [F,L]";
					break;

				case 'block_xmlrpc':
					$file_blocks[] = "# Block XML-RPC (brute-force / DDoS amplification vector)\n<Files xmlrpc.php>\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order deny,allow\n    Deny from all\n  </IfModule>\n</Files>";
					break;

				case 'security_headers':
					// BUG FIX: X-XSS-Protection → "0" (XSS Auditor removed; "1" exploitable per OWASP)
					// BUG FIX: HSTS uses env=HTTPS — only sent on HTTPS responses per RFC 6797
					// BUG FIX: use "Header always set" not "Header set" for reliable delivery
					$header_lines[] = "  Header always set X-Frame-Options \"SAMEORIGIN\"";
					$header_lines[] = "  Header always set X-Content-Type-Options \"nosniff\"";
					$header_lines[] = "  Header always set X-XSS-Protection \"0\"";
					$header_lines[] = "  Header always set Referrer-Policy \"strict-origin-when-cross-origin\"";
					$header_lines[] = "  Header always set Permissions-Policy \"camera=(), microphone=(), geolocation=()\"";
					$header_lines[] = "  Header always set Strict-Transport-Security \"max-age=31536000; includeSubDomains\" env=HTTPS";
					break;

				case 'block_hotlinking':
					$rewrite_blocks[] = "  # Block image hotlinking\n  RewriteCond %{HTTP_REFERER} !^$\n  RewriteCond %{HTTP_REFERER} !^https?://(%{HTTP_HOST}|www\\.%{HTTP_HOST})/ [NC]\n  RewriteRule \\.(jpe?g|png|gif|svg|webp|ico)$ - [F,L,NC]";
					break;

				case 'block_author_scan':
					$rewrite_blocks[] = "  # Block user enumeration via ?author=N\n  RewriteCond %{QUERY_STRING} ^author=\\d\n  RewriteRule .* - [F,L]";
					break;

				case 'block_bad_bots':
					// BUG FIX: removed duplicate 'masscan'; removed broken curl(?!/wp) lookahead
					// WordPress never sends a curl/… UA so the exemption never fired.
					// curl omitted entirely — used by CDN health-checks and legitimate services.
					$rewrite_blocks[] = "  # Block common malicious scanners and crawlers\n  RewriteCond %{HTTP_USER_AGENT} (libwww-perl|wget|python-requests|nikto|sqlmap|nmap|masscan|zgrab|shodan|nuclei|dirbuster|wfuzz|acunetix|nessus|openvas) [NC]\n  RewriteRule .* - [F,L]";
					break;

				case 'prevent_script_injection':
					// BUG FIX: {2} not {0,2} — URL-encoded bytes are always exactly 2 hex digits
					$rewrite_blocks[] = "  # Prevent common query-string injection patterns\n  RewriteCond %{QUERY_STRING} (<|>|'|%3C|%3E|%27) [NC,OR]\n  RewriteCond %{QUERY_STRING} GLOBALS(=|\\[|%[0-9A-Z]{2}) [OR]\n  RewriteCond %{QUERY_STRING} _REQUEST(=|\\[|%[0-9A-Z]{2})\n  RewriteRule .* - [F,L]";
					break;

				case 'limit_file_access':
					// BUG FIX: wp-cron.php removed — blocking it breaks WP-Cron on shared hosting
					// where visitor-triggered cron is the only mechanism available.
					$file_blocks[] = "# Block direct access to sensitive WordPress files\n# NOTE: wp-cron.php is intentionally excluded. Only block it if you\n# have a real system cron job configured as a replacement.\n<FilesMatch \"^(readme\\.html|license\\.txt|wp-activate\\.php)$\">\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order deny,allow\n    Deny from all\n  </IfModule>\n</FilesMatch>";
					break;

				default:
					$cloud_rule = $this->get_cloud_htaccess_rule( $rule );
					if ( is_array( $cloud_rule ) && ! empty( $cloud_rule['content'] ) ) {
						$title         = sanitize_text_field( (string) ( $cloud_rule['title'] ?? $rule ) );
						$plain_blocks[] = "# Cloud Rule: {$title}\n" . trim( (string) $cloud_rule['content'] );
					}
					break;
			}
		}

		// ── Assemble output ─────────────────────────────────────────────────────
		$output = [];

		foreach ( $plain_blocks as $block ) {
			$output[] = $block;
		}
		foreach ( $file_blocks as $block ) {
			$output[] = $block;
		}

		// All headers in ONE <IfModule mod_headers.c> wrapper
		if ( ! empty( $header_lines ) ) {
			$output[] = "# Security HTTP headers\n<IfModule mod_headers.c>\n" . implode( "\n", $header_lines ) . "\n</IfModule>";
		}

		// All rewrite rules in ONE <IfModule mod_rewrite.c> with a single RewriteEngine On
		if ( ! empty( $rewrite_blocks ) ) {
			$output[] = "<IfModule mod_rewrite.c>\n  RewriteEngine On\n\n" . implode( "\n\n", $rewrite_blocks ) . "\n</IfModule>";
		}

		return implode( "\n\n", $output );
	}

	private function get_htaccess_template( string $key ): ?string {
		$templates = [
			'wp_rewrite'        => "# BEGIN WordPress\n<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteBase /\nRewriteRule ^index\\.php$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . /index.php [L]\n</IfModule>\n# END WordPress",
			'security_headers'  => "<IfModule mod_headers.c>\n  Header always set X-Frame-Options \"SAMEORIGIN\"\n  Header always set X-Content-Type-Options \"nosniff\"\n  Header always set X-XSS-Protection \"0\"\n  Header always set Referrer-Policy \"strict-origin-when-cross-origin\"\n  Header always set Permissions-Policy \"camera=(), microphone=(), geolocation=()\"\n  Header always set Strict-Transport-Security \"max-age=31536000; includeSubDomains\" env=HTTPS\n</IfModule>",
			'gzip_compression'  => "<IfModule mod_deflate.c>\n  AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json application/xml text/plain\n</IfModule>",
			'browser_caching'   => "<IfModule mod_expires.c>\n  ExpiresActive On\n  ExpiresByType image/jpeg \"access plus 1 year\"\n  ExpiresByType image/png \"access plus 1 year\"\n  ExpiresByType image/webp \"access plus 1 year\"\n  ExpiresByType text/css \"access plus 1 month\"\n  ExpiresByType application/javascript \"access plus 1 month\"\n  ExpiresByType application/woff2 \"access plus 1 year\"\n</IfModule>",
			'protect_wp_config' => "<Files wp-config.php>\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order deny,allow\n    Deny from all\n  </IfModule>\n</Files>",
			'disable_xmlrpc'    => "<Files xmlrpc.php>\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order deny,allow\n    Deny from all\n  </IfModule>\n</Files>",
		];
		if ( isset( $templates[ $key ] ) ) {
			return $templates[ $key ];
		}

		$cloud_rule = $this->get_cloud_htaccess_rule( $key );
		if ( is_array( $cloud_rule ) && ! empty( $cloud_rule['content'] ) ) {
			return (string) $cloud_rule['content'];
		}

		return null;
	}

	private function get_cloud_htaccess_rule( string $key ): ?array {
		$rules = get_option( 'ultraguard_cloud_htaccess_rules', [] );
		if ( ! is_array( $rules ) ) {
			return null;
		}

		foreach ( $rules as $rule ) {
			if ( ! is_array( $rule ) ) {
				continue;
			}

			if ( sanitize_key( (string) ( $rule['id'] ?? '' ) ) === $key ) {
				return $rule;
			}
		}

		return null;
	}

	// ── Row-level record mutations (table action buttons) ────────────────────

	/**
	 * Delete a single .htaccess change log entry by ID.
	 * POST: log_id (int)
	 */
	public function handle_delete_log_entry(): void {
		$this->cap();
		$id    = absint( $_POST['log_id'] ?? 0 );
		$table = $this->change_log_table();
		if ( ! $id || ! $table ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid log ID.', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid log ID.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		if ( $table->delete( $id ) ) {
			AjaxResponseBuilder::make()
				->success( __( 'Log entry deleted.', 'ultraguard-security-suite' ) )
				->addNotification( __( 'Log entry deleted.', 'ultraguard-security-suite' ), 'info' )
				->addRemove( '#ug-htaccess-changelog-row-' . $log_id, true, 300 )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not delete log entry.', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not delete log entry.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	/**
	 * Mark a security event as ignored.
	 * POST: event_id (int)
	 */
	public function handle_ignore_security_event(): void {
		$this->cap();
		$id    = absint( $_POST['event_id'] ?? 0 );
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		if ( ! $id ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid event ID.', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid event ID.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		if ( $table && $table->update( $id, [ 'status' => 'ignored' ] ) ) {
			AjaxResponseBuilder::make()
				->success( __( 'Event marked as ignored.', 'ultraguard-security-suite' ) )
				->addNotification( __( 'Event marked as ignored.', 'ultraguard-security-suite' ), 'info' )
				->addClass( '#ug-htaccess-event-row-' . $event_id, 'event-ignored' )
				->updateText( '#ug-htaccess-event-row-' . $event_id . ' .ultraguard-cell-status', __( 'Ignored', 'ultraguard-security-suite' ) )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not update event.', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not update event.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	/**
	 * Hard-delete a single security event log entry.
	 * POST: event_id (int)
	 */
	public function handle_delete_security_event(): void {
		$this->cap();
		$id    = absint( $_POST['event_id'] ?? 0 );
		$table = $this->plugin->modules->get_db_tables()->get_table( 'security_logs' );
		if ( ! $id ) {
			AjaxResponseBuilder::make()->error( __( 'Invalid event ID.', 'ultraguard-security-suite' ) )->addNotification( __( 'Invalid event ID.', 'ultraguard-security-suite' ), 'error' )->send();
			return;
		}
		if ( $table && $table->delete( $id ) ) {
			AjaxResponseBuilder::make()
				->success( __( 'Security event deleted.', 'ultraguard-security-suite' ) )
				->addNotification( __( 'Security event deleted.', 'ultraguard-security-suite' ), 'info' )
				->addRemove( '#ug-htaccess-event-row-' . $event_id, true, 300 )
				->send();
		} else {
			AjaxResponseBuilder::make()->error( __( 'Could not delete event.', 'ultraguard-security-suite' ) )->addNotification( __( 'Could not delete event.', 'ultraguard-security-suite' ), 'error' )->send();
		}
	}

	private function cap(): void {
        if ( ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
			AjaxResponseBuilder::make()->permissionError()->send( 403 );
		}
	}
	// ── Realtime fallback handler ─────────────────────────────────────────────

	/**
	 * RT fallback: htaccess_status channel.
	 * Mirrors SseStream::fetch_htaccess_status() — reads from the same filesystem
	 * paths and change-log table used by handle_refresh_stats().
	 */
	public function handle_rt_htaccess_status(): void {
		if ( ! check_ajax_referer( 'ultraguard_admin_nonce', 'nonce', false ) ) {
			AjaxResponseBuilder::make()
				->nonceError( __( 'Security check failed.', 'ultraguard-security-suite' ) )
				->send( 403 );
		}
        if ( ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
			AjaxResponseBuilder::make()->permissionError()->send( 403 );
			return;
		}

		$exists   = file_exists( $this->htaccess_path );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- direct writability check for diagnostics.
		$writable = $exists && is_writable( $this->htaccess_path );

		$db           = $this->plugin->modules->get_db_tables();
		$change_tbl   = $db ? $db->get_table( 'htaccess_change_log' ) : null;
		$today        = gmdate( 'Y-m-d 00:00:00' );
		$blocks_today = $change_tbl ? (int) $change_tbl->count( [ 'created_at >=' => $today ] ) : 0;
		$rules_active = $change_tbl ? (int) $change_tbl->count( [ 'type' => 'security_rule' ] ) : 0;

		$last_modified_human = '';
		if ( $exists ) {
			$mtime = filemtime( $this->htaccess_path );
			if ( $mtime ) {
				$last_modified_human = human_time_diff( $mtime, time() ) . ' ' . __( 'ago', 'ultraguard-security-suite' );
			}
		}

		$status       = $exists ? ( $writable ? 'ok' : 'warning' ) : 'error';
		$status_label = $exists
			? ( $writable ? __( 'Protected', 'ultraguard-security-suite' ) : __( 'Read-only', 'ultraguard-security-suite' ) )
			: __( 'Missing', 'ultraguard-security-suite' );

		AjaxResponseBuilder::make()
			->success()
			->addData( 'status',              $status )
			->addData( 'status_label',        $status_label )
			->addData( 'rules_active',        $rules_active )
			->addData( 'blocks_today',        $blocks_today )
			->addData( 'last_modified_human', $last_modified_human )
			->addData( 'integrity_ok',        $exists && $writable )
			->updateText( '#ultraguard-htaccess-rt-status',        $status_label )
			->updateText( '#ultraguard-htaccess-rt-rules-active',  (string) $rules_active )
			->updateText( '#ultraguard-htaccess-rt-blocks-today',  (string) $blocks_today )
			->updateText( '#ultraguard-htaccess-rt-last-modified', $last_modified_human )
			->send();
	}

}

// phpcs:enable
