<?php

declare(strict_types=1);

namespace SecurWP\Includes;

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

use SecurWP\Modules\Hardening\Hardening;

/**
 * Unified server configuration writer and state inspector.
 *
 * Aggregates rules from all SecurWP modules via WordPress filters and manages
 * the full lifecycle of the SecurWP-owned server config block:
 *   - rendering a canonical managed block
 *   - reading/removing legacy and current managed blocks
 *   - writing files safely
 *   - reporting drift, write state, and activation verification
 *
 * Supported targets:
 *   - Apache / LiteSpeed: .htaccess
 *   - Nginx: nginx.conf (path configurable in Settings > Advanced)
 */
class ServerConfigWriter {

    private const FORMAT_VERSION = 1;
    private const HASH_TRANSIENT = 'securwp_server_rules_hash';
    private const LAST_SYNC_ERROR_TRANSIENT = 'securwp_server_rules_last_error';
    private const VERIFICATION_STATE_KEY = 'server_rule_verification';
    private const VERIFICATION_LOCK_TRANSIENT = 'securwp_server_rule_verify_lock';
    private const PROBE_TIMEOUT = 8;
    private const PROBE_RESPONSE_LIMIT = 1024;
    private const NGINX_VERIFY_DELAY = 15 * MINUTE_IN_SECONDS;
    private const VERIFY_RETRY_INTERVAL = 6 * HOUR_IN_SECONDS;
    private const VERIFY_STEADY_INTERVAL = DAY_IN_SECONDS;
    private const NGINX_ACTIVATION_PROBE_PATH = '/__securwp_nginx_probe__';
    private const NGINX_ACTIVATION_PROBE_HEADER = 'X-SecurWP-Nginx';
    private const NGINX_ACTIVATION_PROBE_VALUE = 'active';
    private const NGINX_ACTIVATION_PROBE_BODY = 'securwp-nginx-active';
    private const NGINX_ACTIVATION_PROBE_STATUS = 418;
    private const SERVER_HARDENING_TROUBLESHOOTING_URL = 'https://securewp.net/docs/#server-hardening-troubleshooting';
    private const SERVER_HARDENING_TROUBLESHOOTING_LABEL = 'Troubleshooting Guide';
    private const WORDPRESS_BEGIN_MARKER = '# BEGIN WordPress';
    private const BEGIN_MARKER = '# BEGIN SecurWP - Do not modify or remove this line';
    private const END_MARKER = '# END SecurWP - Do not modify or remove this line';
    private const LEGACY_MARKER_PAIRS = [
        [ '# BEGIN SecurWP', '# END SecurWP' ],
        [ '# BEGIN SecurWP Security', '# END SecurWP Security' ],
        [ '# BEGIN SecurWP Hardening', '# END SecurWP Hardening' ],
    ];
    private static string $last_write_error_reason = '';

    /**
     * Detect the web server type.
     *
     * Priority order:
     *   1. Admin UI override (Settings > Advanced > Server Type)
     *   2. $_SERVER['SERVER_SOFTWARE'] substring matching
     *   3. php_sapi_name() for LiteSpeed LSAPI
     *   4. apache_get_modules() for mod_php
     *   5. 'unknown' fallback
     *
     * @return string 'apache', 'litespeed', 'nginx', or 'unknown'.
     */
    public static function detect_server(): string {
        $global   = SettingsStore::get( SettingsStore::COL_GLOBAL );
        $override = $global['server_type'] ?? 'auto';

        if ( 'auto' !== $override && in_array( $override, [ 'apache', 'litespeed', 'nginx' ], true ) ) {
            return $override;
        }

        $software = IpDetector::get_server_value( 'SERVER_SOFTWARE' );

        if ( stripos( $software, 'litespeed' ) !== false ) {
            $detected = 'litespeed';
        } elseif ( stripos( $software, 'nginx' ) !== false ) {
            $detected = 'nginx';
        } elseif ( stripos( $software, 'apache' ) !== false ) {
            $detected = 'apache';
        }

        if ( isset( $detected ) ) {
            return (string) apply_filters( 'securwp_detected_server', $detected );
        }

        if ( 'litespeed' === php_sapi_name() ) {
            return (string) apply_filters( 'securwp_detected_server', 'litespeed' );
        }

        if ( function_exists( 'apache_get_modules' ) ) {
            return (string) apply_filters( 'securwp_detected_server', 'apache' );
        }

        return (string) apply_filters( 'securwp_detected_server', 'unknown' );
    }

    /**
     * Classify how the current server value was resolved for UX decisions.
     *
     * @return array{status:string,server_software:string}
     */
    private static function get_server_detection_context( string $server ): array {
        $global   = SettingsStore::get( SettingsStore::COL_GLOBAL );
        $override = $global['server_type'] ?? 'auto';
        $software = trim( (string) IpDetector::get_server_value( 'SERVER_SOFTWARE' ) );

        if ( 'auto' !== $override && in_array( $override, [ 'apache', 'litespeed', 'nginx' ], true ) ) {
            return [
                'status'          => 'manual_override',
                'server_software' => $software,
            ];
        }

        if ( in_array( $server, [ 'apache', 'litespeed', 'nginx' ], true ) ) {
            return [
                'status'          => 'detected_supported',
                'server_software' => $software,
            ];
        }

        if ( $software !== '' ) {
            return [
                'status'          => 'detected_unsupported',
                'server_software' => $software,
            ];
        }

        return [
            'status'          => 'undetected',
            'server_software' => '',
        ];
    }

    /**
     * Whether SecurWP is allowed to write managed server files.
     */
    public static function can_write_to_files(): bool {
        $global    = SettingsStore::get( SettingsStore::COL_GLOBAL, [] );
        $can_write = ! isset( $global['write_to_files'] ) || (bool) $global['write_to_files'];

        return (bool) apply_filters( 'securwp_filter_can_write_to_files', $can_write );
    }

    /**
     * Transient key used by the server-rule sync lifecycle.
     */
    public static function get_hash_transient_key(): string {
        return self::HASH_TRANSIENT;
    }

    /**
     * Build a stable fingerprint for the current live verification plan.
     *
     * @param array<string,mixed> $plan
     */
    private static function build_probe_plan_fingerprint( array $plan ): string {
        $checks = [];

        foreach ( $plan['checks'] ?? [] as $check ) {
            if ( ! is_array( $check ) ) {
                continue;
            }

            $checks[] = [
                'control'          => is_string( $check['control'] ?? null ) ? (string) $check['control'] : '',
                'type'             => is_string( $check['type'] ?? null ) ? (string) $check['type'] : '',
                'label'            => is_string( $check['label'] ?? null ) ? (string) $check['label'] : '',
                'target_label'     => is_string( $check['target_label'] ?? null ) ? (string) $check['target_label'] : '',
                'public_path'      => is_string( $check['public_path'] ?? null ) ? (string) $check['public_path'] : '',
                'dir_path'         => is_string( $check['dir_path'] ?? null ) ? (string) $check['dir_path'] : '',
                'base_public_path' => is_string( $check['base_public_path'] ?? null ) ? (string) $check['base_public_path'] : '',
            ];
        }

        return md5(
            (string) wp_json_encode(
                [
                    'enabled_controls'         => isset( $plan['enabled_controls'] ) && is_array( $plan['enabled_controls'] )
                        ? array_values( array_filter( $plan['enabled_controls'], 'is_string' ) )
                        : [],
                    'unchecked_controls'       => isset( $plan['unchecked_controls'] ) && is_array( $plan['unchecked_controls'] )
                        ? array_values( array_filter( $plan['unchecked_controls'], 'is_string' ) )
                        : [],
                    'requires_persisted_rules' => ! empty( $plan['requires_persisted_rules'] ),
                    'checks'                   => $checks,
                ]
            )
        );
    }

    /**
     * Build the sync hash from live state, not just setting toggles.
     */
    public static function build_sync_hash(): string {
        $server           = self::detect_server();
        $path             = self::get_config_file_path( $server );
        $target_issue     = $path !== '' ? self::get_managed_target_path_issue( $path ) : '';
        $write_enabled    = self::can_write_to_files();
        $file_exists      = $target_issue === '' && $path !== '' && file_exists( $path );
        $file_writable    = $file_exists && is_file( $path ) && wp_is_writable( $path );
        $dir_writable     = $path !== '' && wp_is_writable( dirname( $path ) );
        $writable         = $target_issue === '' && $path !== '' && ( ( $file_exists && ( $file_writable || $dir_writable ) ) || ( ! $file_exists && $dir_writable ) );
        $desired_block    = self::get_managed_rules_for_server( $server );
        $current_snapshot = self::empty_managed_block_snapshot();
        $probe_plan       = self::build_probe_plan( Hardening::get_saved_settings(), $server );
        $nginx_conflicts  = [
            'detected'    => false,
            'fingerprint' => 'none',
            'message'     => '',
            'examples'    => [],
        ];

        if ( $path !== '' && $target_issue === '' ) {
            $current_snapshot = self::get_managed_block_snapshot( $path );
            if ( $server === 'nginx' ) {
                $nginx_conflicts = self::inspect_nginx_rule_conflicts( $path, $desired_block );
            }
        }

        return md5(
            'server=' . $server
            . ';path=' . $path
            . ';target_issue=' . $target_issue
            . ';write_enabled=' . ( $write_enabled ? '1' : '0' )
            . ';writable=' . ( $writable ? '1' : '0' )
            . ';desired=' . trim( $desired_block )
            . ';current=' . trim( (string) $current_snapshot['block'] )
            . ';count=' . (int) $current_snapshot['count']
            . ';nginx_conflicts=' . (string) ( $nginx_conflicts['fingerprint'] ?? 'none' )
            . ';verification=' . self::build_probe_plan_fingerprint( $probe_plan )
        );
    }

    /**
     * Persist the last sync failure when a server-rule write fails.
     */
    public static function record_sync_failure( string $path, string $desired_block, string $operation, string $reason ): void {
        set_transient(
            self::LAST_SYNC_ERROR_TRANSIENT,
            [
                'path'         => $path,
                'desired_hash' => md5( trim( $desired_block ) ),
                'operation'    => $operation,
                'reason'       => $reason,
                'recorded_at'  => time(),
            ],
            DAY_IN_SECONDS
        );
    }

    /**
     * Clear the last sync failure marker.
     */
    public static function clear_sync_failure(): void {
        delete_transient( self::LAST_SYNC_ERROR_TRANSIENT );
    }

    /**
     * Look up the persisted sync failure, scoped to the current desired block.
     *
     * @return array<string,mixed>|null
     */
    public static function get_sync_failure( string $path, string $desired_block ): ?array {
        $failure = get_transient( self::LAST_SYNC_ERROR_TRANSIENT );
        if ( ! is_array( $failure ) ) {
            return null;
        }

        $failure_path         = is_string( $failure['path'] ?? null ) ? (string) $failure['path'] : '';
        $failure_desired_hash = is_string( $failure['desired_hash'] ?? null ) ? (string) $failure['desired_hash'] : '';

        if ( $failure_path !== $path || $failure_desired_hash !== md5( trim( $desired_block ) ) ) {
            return null;
        }

        return $failure;
    }

    /**
     * Inspect unmanaged Nginx location rules for overlaps with SecurWP's
     * desired managed block.
     *
     * This is intentionally rule-based rather than marker-based so SecurWP can
     * detect conflicting protections written by any plugin, custom snippet, or
     * host panel include before attempting to update the file.
     *
     * @return array{detected:bool,fingerprint:string,message:string,examples:array<int,string>}
     */
    private static function inspect_nginx_rule_conflicts( string $file, string $desired_block ): array {
        static $cache = [];

        if ( $file === '' || trim( $desired_block ) === '' ) {
            return [
                'detected'    => false,
                'fingerprint' => 'none',
                'message'     => '',
                'examples'    => [],
            ];
        }

        clearstatcache( true, $file );
        $mtime = is_file( $file ) ? (string) @filemtime( $file ) : '0';
        $size  = is_file( $file ) ? (string) @filesize( $file ) : '0';
        $cache_key = md5( $file . '|' . $mtime . '|' . $size . '|' . md5( trim( $desired_block ) ) );

        if ( isset( $cache[ $cache_key ] ) ) {
            return $cache[ $cache_key ];
        }

        if ( ! is_file( $file ) || ! is_readable( $file ) ) {
            return $cache[ $cache_key ] = [
                'detected'    => false,
                'fingerprint' => 'none',
                'message'     => '',
                'examples'    => [],
            ];
        }

        $contents = self::read_file_contents( $file );
        if ( ! is_string( $contents ) || trim( $contents ) === '' ) {
            return $cache[ $cache_key ] = [
                'detected'    => false,
                'fingerprint' => 'none',
                'message'     => '',
                'examples'    => [],
            ];
        }

        $existing_rules = self::parse_nginx_location_rules( self::strip_managed_blocks_from_contents( $contents ) );
        $desired_rules  = self::parse_nginx_location_rules( self::strip_managed_block_wrapper( $desired_block ) );

        if ( $existing_rules === [] || $desired_rules === [] ) {
            return $cache[ $cache_key ] = [
                'detected'    => false,
                'fingerprint' => 'none',
                'message'     => '',
                'examples'    => [],
            ];
        }

        $conflicts = [];
        $seen      = [];

        foreach ( $desired_rules as $desired_rule ) {
            $desired_signature = (string) $desired_rule['signature'];
            $probe_paths       = self::build_nginx_rule_probe_paths( $desired_rule );

            foreach ( $existing_rules as $existing_rule ) {
                $existing_signature = (string) $existing_rule['signature'];
                $reason             = '';

                if ( $existing_signature === $desired_signature ) {
                    $reason = 'duplicate_location';
                } elseif ( ! empty( $existing_rule['blocking'] ) && self::nginx_rule_matches_any_path( $existing_rule, $probe_paths ) ) {
                    $reason = 'blocking_overlap';
                }

                if ( $reason === '' ) {
                    continue;
                }

                $key = $reason . '|' . $existing_signature;
                if ( isset( $seen[ $key ] ) ) {
                    continue;
                }

                $seen[ $key ] = true;
                $conflicts[]  = [
                    'reason'   => $reason,
                    'existing' => (string) $existing_rule['summary'],
                    'desired'  => (string) $desired_rule['summary'],
                ];
            }
        }

        if ( $conflicts === [] ) {
            return $cache[ $cache_key ] = [
                'detected'    => false,
                'fingerprint' => 'none',
                'message'     => '',
                'examples'    => [],
            ];
        }

        $examples = array_values(
            array_unique(
                array_map(
                    static function ( array $conflict ): string {
                        return (string) $conflict['existing'];
                    },
                    $conflicts
                )
            )
        );
        $examples = array_slice( $examples, 0, 3 );

        $fingerprint_parts = array_map(
            static function ( array $conflict ): string {
                return (string) $conflict['reason'] . '|' . (string) $conflict['existing'] . '|' . (string) $conflict['desired'];
            },
            $conflicts
        );
        sort( $fingerprint_parts, SORT_STRING );

        return $cache[ $cache_key ] = [
            'detected'    => true,
            'fingerprint' => md5( implode( ';', $fingerprint_parts ) ),
            'message'     => self::build_nginx_rule_conflict_message( $file ),
            'examples'    => $examples,
        ];
    }

    /**
     * Remove all SecurWP-managed blocks from an existing config file before
     * scanning unmanaged directives for conflicts.
     */
    private static function strip_managed_blocks_from_contents( string $contents ): string {
        $stripped = preg_replace( self::get_managed_block_pattern(), '', $contents );

        return is_string( $stripped ) ? $stripped : $contents;
    }

    /**
     * Parse the subset of Nginx location directives that SecurWP needs to
     * reason about for conflict detection.
     *
     * @return array<int,array{modifier:string,matcher:string,signature:string,summary:string,blocking:bool}>
     */
    private static function parse_nginx_location_rules( string $contents ): array {
        $normalized = self::normalize_line_endings( $contents, "\n" );
        if ( trim( $normalized ) === '' ) {
            return [];
        }

        $lines = preg_split( "/\n/", $normalized );
        if ( ! is_array( $lines ) ) {
            return [];
        }

        $rules = [];
        $count = count( $lines );

        for ( $index = 0; $index < $count; $index++ ) {
            $raw_line = (string) $lines[ $index ];
            $line     = trim( $raw_line );

            if ( $line === '' || str_starts_with( $line, '#' ) ) {
                continue;
            }

            if ( ! preg_match( '/^location\b\s*(=|\^~|~\*|~)?\s*([^\s{]+)\s*\{/', $line, $matches ) ) {
                continue;
            }

            $modifier = trim( (string) ( $matches[1] ?? '' ) );
            $matcher  = trim( (string) ( $matches[2] ?? '' ) );
            if ( $matcher === '' ) {
                continue;
            }

            $block_lines    = [ $raw_line ];
            $brace_balance  = substr_count( $raw_line, '{' ) - substr_count( $raw_line, '}' );

            while ( $brace_balance > 0 && $index + 1 < $count ) {
                $index++;
                $next_line      = (string) $lines[ $index ];
                $block_lines[]  = $next_line;
                $brace_balance += substr_count( $next_line, '{' ) - substr_count( $next_line, '}' );
            }

            $block    = implode( "\n", $block_lines );
            $analysis = self::analyze_nginx_location_block( $block );
            $rules[]  = [
                'modifier'  => $modifier,
                'matcher'   => $matcher,
                'signature' => self::build_nginx_location_signature( $modifier, $matcher ),
                'summary'   => self::build_nginx_location_summary( $modifier, $matcher ),
                'blocking'  => $analysis['blocking'],
            ];
        }

        return $rules;
    }

    /**
     * @return array{blocking:bool}
     */
    private static function analyze_nginx_location_block( string $block ): array {
        if ( preg_match( '/\bdeny\s+all\s*;/i', $block ) === 1 ) {
            return [ 'blocking' => true ];
        }

        if ( preg_match( '/\breturn\s+([0-9]{3})\b/i', $block, $matches ) === 1 ) {
            $status = (int) ( $matches[1] ?? 0 );

            return [ 'blocking' => $status >= 400 ];
        }

        return [ 'blocking' => false ];
    }

    private static function build_nginx_location_signature( string $modifier, string $matcher ): string {
        $normalized_modifier = $modifier !== '' ? $modifier : 'prefix';

        return $normalized_modifier . '|' . trim( $matcher );
    }

    private static function build_nginx_location_summary( string $modifier, string $matcher ): string {
        $parts = [ 'location' ];

        if ( $modifier !== '' ) {
            $parts[] = $modifier;
        }

        $parts[] = trim( $matcher );

        return implode( ' ', $parts );
    }

    /**
     * Build representative request paths for a SecurWP-generated Nginx rule.
     *
     * The conflict detector uses these probe paths against existing unmanaged
     * rules to determine whether another rule already owns the same protection
     * surface.
     *
     * @param array{modifier:string,matcher:string} $rule
     * @return array<int,string>
     */
    private static function build_nginx_rule_probe_paths( array $rule ): array {
        $modifier = (string) ( $rule['modifier'] ?? '' );
        $matcher  = (string) ( $rule['matcher'] ?? '' );

        if ( $matcher === '' ) {
            return [];
        }

        if ( $modifier === '=' ) {
            return [ $matcher ];
        }

        if ( $modifier === '' || $modifier === '^~' ) {
            $base = rtrim( $matcher, '/' );
            if ( $base === '' ) {
                return [ '/__securwp_probe__' ];
            }

            return [ $base . '/__securwp_probe__' ];
        }

        if ( str_contains( $matcher, '(log|lock)' ) ) {
            return [ '/securwp-probe.log', '/securwp-probe.lock' ];
        }

        if ( str_contains( $matcher, '/\\.' ) || str_contains( $matcher, '\\.(git|svn)' ) ) {
            return [ '/.git/config', '/.env' ];
        }

        $literal_prefix = self::extract_literal_prefix_from_nginx_regex( $matcher );
        if ( $literal_prefix === '' ) {
            return [];
        }

        if ( str_contains( $matcher, 'theme-compat/' ) ) {
            return [ self::append_probe_path_segment( $literal_prefix, 'securwp-probe.css' ) ];
        }

        if (
            str_contains( $matcher, '\\.php' )
            || str_contains( $matcher, 'phtml' )
            || str_contains( $matcher, 'pht' )
            || str_contains( $matcher, 'phar' )
            || str_contains( $matcher, 'phps' )
            || str_contains( $matcher, 'shtml' )
        ) {
            return [ self::append_probe_path_segment( $literal_prefix, 'securwp-probe.php' ) ];
        }

        return [ self::append_probe_path_segment( $literal_prefix, '__securwp_probe__' ) ];
    }

    private static function append_probe_path_segment( string $prefix, string $leaf ): string {
        $normalized = rtrim( $prefix, '/' );

        return $normalized === '' ? '/' . ltrim( $leaf, '/' ) : $normalized . '/' . ltrim( $leaf, '/' );
    }

    private static function extract_literal_prefix_from_nginx_regex( string $matcher ): string {
        $pattern = ltrim( $matcher );
        if ( str_starts_with( $pattern, '^' ) ) {
            $pattern = substr( $pattern, 1 );
        }

        $literal = '';
        $escaped = false;
        $length  = strlen( $pattern );

        for ( $i = 0; $i < $length; $i++ ) {
            $char = $pattern[ $i ];

            if ( $escaped ) {
                if ( in_array( $char, [ '/', '-', '.', '_' ], true ) ) {
                    $literal .= $char;
                    $escaped  = false;
                    continue;
                }

                break;
            }

            if ( $char === '\\' ) {
                $escaped = true;
                continue;
            }

            if ( preg_match( '/[A-Za-z0-9\/._-]/', $char ) === 1 ) {
                $literal .= $char;
                continue;
            }

            break;
        }

        return str_starts_with( $literal, '/' ) ? $literal : '';
    }

    /**
     * @param array{modifier:string,matcher:string} $rule
     * @param array<int,string> $paths
     */
    private static function nginx_rule_matches_any_path( array $rule, array $paths ): bool {
        foreach ( $paths as $path ) {
            if ( self::nginx_rule_matches_path( $rule, $path ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array{modifier:string,matcher:string} $rule
     */
    private static function nginx_rule_matches_path( array $rule, string $path ): bool {
        $modifier = (string) ( $rule['modifier'] ?? '' );
        $matcher  = (string) ( $rule['matcher'] ?? '' );

        if ( $matcher === '' || $path === '' ) {
            return false;
        }

        if ( $modifier === '=' ) {
            return $path === $matcher;
        }

        if ( $modifier === '' || $modifier === '^~' ) {
            return str_starts_with( $path, $matcher );
        }

        $delimiter = '#';
        $pattern   = $delimiter . str_replace( $delimiter, '\\' . $delimiter, $matcher ) . $delimiter;
        if ( $modifier === '~*' ) {
            $pattern .= 'i';
        }

        return @preg_match( $pattern, $path ) === 1;
    }

    /**
     */
    private static function build_nginx_rule_conflict_message( string $path ): string {
        $target = $path !== '' ? $path : 'the configured Nginx include';

        return sprintf(
            'SecurWP found existing Nginx rules in %s that already protect some of the same paths. Automatic updates were paused to avoid duplicate or conflicting directives. Open Manual Rules to compare the generated rules and keep one owner for these protections.',
            $target
        );
    }

    /**
     * Render the canonical managed block for a server target.
     */
    public static function get_managed_rules_for_server( string $server = '' ): string {
        if ( $server === '' ) {
            $server = self::detect_server();
        }

        $rules = self::canonicalize_rule_stanzas( self::get_rules_for_server( $server ) );
        if ( $rules === '' ) {
            return '';
        }

        return implode(
            "\n",
            [
                self::BEGIN_MARKER,
                '# SecurWP Config Details: ' . self::FORMAT_VERSION,
                $rules,
                self::END_MARKER,
            ]
        );
    }

    /**
     * Normalize aggregated module output and remove exact duplicate stanzas.
     *
     * The unified writer currently receives raw concatenated strings from
     * module filters. Stanza-level dedupe is the safest generic reconciliation
     * available without changing the filter contract to structured data.
     */
    private static function canonicalize_rule_stanzas( string $rules ): string {
        $normalized = trim( self::normalize_line_endings( $rules, "\n" ) );
        if ( $normalized === '' ) {
            return '';
        }

        $stanzas = preg_split( "/\n{2,}/", $normalized );
        if ( ! is_array( $stanzas ) ) {
            return trim( $normalized );
        }

        $unique = [];
        $seen   = [];

        foreach ( $stanzas as $stanza ) {
            $candidate = self::normalize_rule_stanza( (string) $stanza );
            if ( $candidate === '' ) {
                continue;
            }

            $fingerprint = self::fingerprint_rule_stanza( $candidate );
            if ( isset( $seen[ $fingerprint ] ) ) {
                continue;
            }

            $seen[ $fingerprint ] = true;
            $unique[]             = $candidate;
        }

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

    /**
     * Read the current SecurWP-managed block snapshot from a config file.
     *
     * @return array{block:string,count:int}
     */
    private static function get_managed_block_snapshot( string $file ): array {
        if ( $file === '' || ! is_file( $file ) || ! is_readable( $file ) ) {
            return self::empty_managed_block_snapshot();
        }

        $contents = @file_get_contents( $file );
        if ( ! is_string( $contents ) || $contents === '' ) {
            return self::empty_managed_block_snapshot();
        }

        if ( preg_match_all( self::get_managed_block_pattern(), $contents, $matches ) === false ) {
            return self::empty_managed_block_snapshot();
        }

        $blocks = isset( $matches[0] ) && is_array( $matches[0] ) ? $matches[0] : [];

        return [
            'block' => isset( $blocks[0] ) ? trim( (string) $blocks[0] ) : '',
            'count' => count( $blocks ),
        ];
    }

    /**
     * Compute the current managed server-rule state.
     *
     * @return array<string,mixed>
     */
    public static function get_state(): array {
        $server          = self::detect_server();
        $detection       = self::get_server_detection_context( $server );
        $detection_status = (string) ( $detection['status'] ?? 'undetected' );
        $server_software  = (string) ( $detection['server_software'] ?? '' );
        $plan            = self::build_probe_plan( Hardening::get_saved_settings(), $server );
        $requires_server_rules = ! empty( $plan['enabled_controls'] ) && ! empty( $plan['requires_persisted_rules'] );
        $managed_target_path = self::get_config_file_path( $server );
        $inspection_path = self::get_state_inspection_path( $server );
        $target_issue    = $managed_target_path !== '' ? self::get_managed_target_path_issue( $managed_target_path ) : '';
        $inspection_issue = $inspection_path !== '' ? self::get_managed_target_path_issue( $inspection_path ) : '';
        $write_enabled   = self::can_write_to_files();
        $desired_block   = self::get_managed_rules_for_server( $server );
        $desired_present = trim( $desired_block ) !== '';
        $file_exists     = $target_issue === '' && $managed_target_path !== '' && file_exists( $managed_target_path );
        $file_writable   = $file_exists && is_file( $managed_target_path ) && wp_is_writable( $managed_target_path );
        $dir_writable    = $target_issue === '' && $managed_target_path !== '' && wp_is_writable( dirname( $managed_target_path ) );
        $writable        = $target_issue === '' && $managed_target_path !== '' && ( ( $file_exists && ( $file_writable || $dir_writable ) ) || ( ! $file_exists && $dir_writable ) );
        $snapshot        = $inspection_path !== '' && $inspection_issue === '' ? self::get_managed_block_snapshot( $inspection_path ) : self::empty_managed_block_snapshot();
        $current_block   = (string) $snapshot['block'];
        $managed_count   = (int) $snapshot['count'];
        $managed_present = trim( $current_block ) !== '';
        $persisted       = $desired_present && $managed_count === 1 && trim( $current_block ) === trim( $desired_block );
        $drifted         = $managed_count > 1 || trim( $current_block ) !== trim( $desired_block );
        $sync_failure    = self::get_sync_failure( $managed_target_path, $desired_block );
        $nginx_conflicts = $server === 'nginx' && $inspection_path !== '' && $inspection_issue === ''
            ? self::inspect_nginx_rule_conflicts( $inspection_path, $desired_block )
            : [
                'detected'    => false,
                'fingerprint' => 'none',
                'message'     => '',
                'examples'    => [],
            ];
        $mode            = 'none';
        $message         = 'No server-level rules are needed for the current hardening configuration.';

        if ( $server === 'unknown' && ( $requires_server_rules || $managed_present ) ) {
            $mode    = 'unsupported_server';
            $message = self::build_unknown_server_message( $detection_status, $server_software, $managed_present, $inspection_path );
        } elseif ( $desired_present ) {
            if ( $target_issue !== '' ) {
                $mode    = 'manual_only';
                $message = self::describe_target_path_issue( $server, $target_issue );
            } elseif ( ! empty( $nginx_conflicts['detected'] ) ) {
                $mode    = 'manual_only';
                $message = (string) ( $nginx_conflicts['message'] ?? 'SecurWP found conflicting unmanaged Nginx rules and stopped automatic updates for the configured include file.' );
            } elseif ( $persisted ) {
                $mode    = 'persisted';
                $message = 'Server hardening rules are applied and current.';
            } elseif ( $managed_present ) {
                if ( $write_enabled && $writable ) {
                    if ( $sync_failure !== null ) {
                        $mode    = 'sync_failed';
                        $message = self::describe_sync_failure(
                            (string) ( $sync_failure['operation'] ?? 'apply' ),
                            (string) ( $sync_failure['reason'] ?? 'unknown' ),
                            $server
                        );
                    } else {
                        $mode    = 'repair_pending';
                        $message = 'The server hardening rules are out of date. SecurWP will update them automatically on the next sync.';
                    }
                } else {
                    $mode    = 'repair_required';
                    $message = self::build_manual_server_message( $managed_target_path, $write_enabled, $server, true );
                }
            } elseif ( $write_enabled && $writable ) {
                if ( $sync_failure !== null ) {
                    $mode    = 'sync_failed';
                    $message = self::describe_sync_failure(
                        (string) ( $sync_failure['operation'] ?? 'apply' ),
                        (string) ( $sync_failure['reason'] ?? 'unknown' ),
                        $server
                    );
                } else {
                    $mode    = 'sync_pending';
                    $message = 'Server hardening rules will be applied on the next sync.';
                }
            } else {
                $mode    = 'manual_only';
                $message = self::build_manual_server_message( $managed_target_path, $write_enabled, $server, false );
            }
        } elseif ( $managed_present ) {
            if ( $write_enabled && $writable ) {
                if ( $sync_failure !== null ) {
                    $mode    = 'removal_failed';
                    $message = self::describe_sync_failure(
                        (string) ( $sync_failure['operation'] ?? 'remove' ),
                        (string) ( $sync_failure['reason'] ?? 'unknown' ),
                        $server
                    );
                } else {
                    $mode    = 'removal_pending';
                    $message = 'Outdated server hardening rules remain in the configuration file and will be removed on the next sync.';
                }
            } else {
                $mode    = 'removal_required';
                $message = 'Outdated server hardening rules remain in the configuration file. Remove or update them manually, as SecurWP does not have write access to the server configuration.';
            }
        }

        $verification = self::sanitize_verification_state( self::build_verification_state( $desired_present, $persisted, $server ) );
        $banner       = self::build_banner_state( $server, $detection_status, $server_software, $mode, $message, $managed_target_path, $desired_present, $persisted, $verification );

        return [
            'mode'                  => $mode,
            'message'               => $message,
            'server'                => $server,
            'server_detection_status' => $detection_status,
            'server_software'       => $server_software,
            'path'                  => $inspection_path,
            'managed_target_path'   => $managed_target_path,
            'inspection_path'       => $inspection_path,
            'target_issue'          => $target_issue,
            'write_enabled'         => $write_enabled,
            'file_exists'           => $file_exists,
            'file_writable'         => $file_writable,
            'dir_writable'          => $dir_writable,
            'writable'              => $writable,
            'desired_rules_present' => $desired_present,
            'managed_block_present' => $managed_present,
            'persisted'             => $persisted,
            'drifted'               => $drifted,
            'conflict_detected'     => ! empty( $nginx_conflicts['detected'] ),
            'conflict_fingerprint'  => (string) ( $nginx_conflicts['fingerprint'] ?? 'none' ),
            'conflict_examples'     => isset( $nginx_conflicts['examples'] ) && is_array( $nginx_conflicts['examples'] )
                ? array_values( array_filter( $nginx_conflicts['examples'], 'is_string' ) )
                : [],
            'last_error_reason'     => is_array( $sync_failure ) ? (string) ( $sync_failure['reason'] ?? '' ) : '',
            'last_error_operation'  => is_array( $sync_failure ) ? (string) ( $sync_failure['operation'] ?? '' ) : '',
            'sync_hash'             => self::build_sync_hash(),
            'verification'          => $verification,
            'banner'                => $banner,
        ];
    }

    /**
     * Resolve a read-only inspection target for server-rule state.
     *
     * Unknown servers should not inherit Apache behavior for writing or
     * verification, but we still want visibility into an existing managed
     * .htaccess block for cleanup and drift reporting.
     */
    private static function get_state_inspection_path( string $server ): string {
        $path = self::get_config_file_path( $server );
        if ( $path !== '' || $server !== 'unknown' ) {
            return $path;
        }

        $apache_path = ABSPATH . '.htaccess';
        $issue       = self::get_managed_target_path_issue( $apache_path );
        if ( $issue !== '' ) {
            return '';
        }

        return $apache_path;
    }

    /**
     * Write the canonical managed block to the active server config file.
     *
     * @return bool True on success; false if the file could not be written.
     */
    public static function write(): bool {
        self::$last_write_error_reason = '';
        $state = self::get_state();

        if ( ! $state['write_enabled'] ) {
            self::$last_write_error_reason = 'write_disabled';
            return false;
        }

        $file = is_string( $state['managed_target_path'] ?? null ) ? (string) $state['managed_target_path'] : '';
        if ( $file === '' ) {
            self::$last_write_error_reason = 'no_target_path';
            return false;
        }

        $target_issue = is_string( $state['target_issue'] ?? null ) ? (string) $state['target_issue'] : '';
        if ( $target_issue !== '' ) {
            self::$last_write_error_reason = $target_issue;
            return false;
        }

        if ( ! empty( $state['conflict_detected'] ) ) {
            self::$last_write_error_reason = 'nginx_rule_conflict';
            return false;
        }

        $server        = is_string( $state['server'] ?? null ) ? (string) $state['server'] : self::detect_server();
        $managed_block = self::get_managed_rules_for_server( $server );
        $result        = self::write_managed_block( $file, $managed_block, $server );

        if ( $result !== true && is_string( $result ) ) {
            self::$last_write_error_reason = $result;
        } elseif ( $managed_block === '' ) {
            self::clear_verification_state();
        } else {
            self::mark_verification_pending();
        }

        return $result === true;
    }

    /**
     * Return the machine-readable reason from the most recent write attempt.
     */
    public static function get_last_write_error_reason(): string {
        return self::$last_write_error_reason;
    }

    /**
     * Remove all SecurWP rules from server config files.
     *
     * Called on plugin deactivation to clean up.
     */
    public static function remove(): bool {
        $all_ok = true;

        foreach ( self::get_possible_config_paths() as $file ) {
            if ( ! file_exists( $file ) ) {
                continue;
            }

            $result = self::write_managed_block( $file, '', self::detect_server_from_path( $file ) );
            if ( $result !== true ) {
                $all_ok = false;
            }
        }

        if ( $all_ok ) {
            delete_transient( self::HASH_TRANSIENT );
            self::clear_sync_failure();
            self::clear_verification_state();
        }

        return $all_ok;
    }

    /**
     * Get the config file path for a given server type.
     *
     * @param string $server Server type.
     * @return string Full file path, or empty string if not available.
     */
    public static function get_config_file_path( string $server = '' ): string {
        if ( empty( $server ) ) {
            $server = self::detect_server();
        }

        if ( in_array( $server, [ 'apache', 'litespeed' ], true ) ) {
            return ABSPATH . '.htaccess';
        }

        if ( 'nginx' === $server ) {
            return self::get_nginx_path();
        }

        return '';
    }

    /**
     * Get the path to the nginx config file.
     *
     * Reads from global settings (configurable in admin). Defaults to
     * ABSPATH/nginx.conf.
     *
     * @return string Full path to the nginx config file.
     */
    public static function get_nginx_path(): string {
        $global = SettingsStore::get( SettingsStore::COL_GLOBAL );
        $path   = $global['nginx_file'] ?? '';

        if ( ! empty( $path ) && is_string( $path ) ) {
            $validated = self::validate_managed_target_path( $path );
            if ( $validated !== '' ) {
                return $validated;
            }
        }

        return self::validate_managed_target_path( ABSPATH . 'nginx.conf' ) ?: ABSPATH . 'nginx.conf';
    }

    /**
     * Validate a managed server-config target path and return the normalized path
     * when it is safe for SecurWP to manage.
     */
    private static function validate_managed_target_path( string $path ): string {
        return self::get_managed_target_path_issue( $path ) === ''
            ? $path
            : '';
    }

    /**
     * Return a machine-readable issue code for an unsafe managed config path.
     */
    private static function get_managed_target_path_issue( string $path ): string {
        $path = trim( $path );
        if ( $path === '' ) {
            return 'no_target_path';
        }

        if ( strpos( $path, "\0" ) !== false ) {
            return 'unsafe_target_path';
        }

        if ( preg_match( '#^[a-z][a-z0-9+.-]*://#i', $path ) === 1 ) {
            return 'unsafe_target_path';
        }

        if ( function_exists( 'path_is_absolute' ) && ! path_is_absolute( $path ) ) {
            return 'unsafe_target_path';
        }

        if ( false !== strpos( $path, '..' ) ) {
            return 'unsafe_target_path';
        }

        $existing_parent = self::find_nearest_existing_parent( $path );
        if ( $existing_parent === '' ) {
            return 'unsafe_target_path';
        }

        $real_parent = realpath( $existing_parent );
        if ( ! is_string( $real_parent ) || $real_parent === '' ) {
            return 'unsafe_target_path';
        }

        if ( ! self::path_is_within_managed_root( $real_parent ) ) {
            return 'unsafe_target_path';
        }

        if ( file_exists( $path ) ) {
            if ( is_link( $path ) ) {
                return 'symlink_target_not_allowed';
            }

            $real_target = realpath( $path );
            if ( ! is_string( $real_target ) || $real_target === '' || ! self::path_is_within_managed_root( $real_target ) ) {
                return 'unsafe_target_path';
            }
        }

        return '';
    }

    /**
     * Locate the nearest existing parent directory for a potentially new target path.
     */
    private static function find_nearest_existing_parent( string $path ): string {
        $current = dirname( $path );

        while ( $current !== '' && $current !== '.' ) {
            if ( file_exists( $current ) ) {
                return is_dir( $current ) ? $current : dirname( $current );
            }

            $next = dirname( $current );
            if ( $next === $current ) {
                break;
            }

            $current = $next;
        }

        return '';
    }

    /**
     * Whether a real filesystem path stays within SecurWP-managed roots.
     */
    private static function path_is_within_managed_root( string $real_path ): bool {
        $candidate = wp_normalize_path( untrailingslashit( $real_path ) );

        foreach ( self::get_managed_root_paths() as $root ) {
            if ( $candidate === $root || str_starts_with( $candidate, $root . '/' ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Return normalized realpaths that SecurWP may manage server-config files under.
     *
     * @return array<int,string>
     */
    private static function get_managed_root_paths(): array {
        $roots = [];

        foreach ( [ ABSPATH, WP_CONTENT_DIR ] as $root ) {
            $real_root = realpath( $root );
            if ( is_string( $real_root ) && $real_root !== '' ) {
                $roots[] = wp_normalize_path( untrailingslashit( $real_root ) );
            }
        }

        return array_values( array_unique( $roots ) );
    }

    /**
     * Get aggregated rules for a specific server type.
     */
    public static function get_rules_for_server( string $server ): string {
        if ( in_array( $server, [ 'apache', 'litespeed' ], true ) ) {
            return self::get_apache_rules();
        }

        if ( 'nginx' === $server ) {
            return self::get_nginx_rules();
        }

        return '';
    }

    /**
     * Aggregate Apache/LiteSpeed rules from all modules.
     */
    public static function get_apache_rules(): string {
        return (string) apply_filters( 'securwp_server_rules_apache', '' );
    }

    /**
     * Aggregate Nginx rules from all modules.
     */
    public static function get_nginx_rules(): string {
        return (string) apply_filters( 'securwp_server_rules_nginx', '' );
    }

    /**
     * Build the verification payload for the current active rule set.
     *
     * @return array<string,mixed>
     */
    private static function build_verification_state( bool $desired_present, bool $persisted, string $server ): array {
        $settings  = Hardening::get_saved_settings();
        $sync_hash = self::build_sync_hash();
        $plan      = self::build_probe_plan( $settings, $server );
        $stored    = self::get_stored_verification_state();
        $requires_persisted_rules = ! empty( $plan['requires_persisted_rules'] );

        if ( empty( $plan['enabled_controls'] ) ) {
            return self::build_verification_payload(
                $sync_hash,
                'not_required',
                false,
                false,
                null,
                '',
                '',
                '',
                'No hardening verification checks are required for the current hardening state.',
                [],
                [],
                [],
                null,
                null
            );
        }

        if ( $requires_persisted_rules && ! $desired_present ) {
            return self::build_verification_payload(
                $sync_hash,
                'unsupported',
                false,
                false,
                null,
                '',
                '',
                'no_candidate',
                self::build_unsupported_verification_message( $plan['unchecked_controls'] ),
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                null
            );
        }

        if ( $requires_persisted_rules && ! $persisted ) {
            return self::build_verification_payload(
                $sync_hash,
                'pending',
                true,
                false,
                null,
                '',
                '',
                'not_persisted',
                'Hardening verification will run after the managed server rule block is persisted.',
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                null,
                time()
            );
        }

        if ( self::can_confirm_verification_from_persistence( $server, $plan ) ) {
            return self::build_verification_payload(
                $sync_hash,
                'passed',
                true,
                false,
                true,
                '',
                '',
                'persisted_config',
                self::build_persisted_verification_message( $server ),
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                time()
            );
        }

        if ( self::is_current_verification_state( $stored, $sync_hash ) ) {
            return self::merge_verification_plan( $stored, $plan );
        }

        return self::build_verification_payload(
            $sync_hash,
            'pending',
            true,
            false,
            null,
            '',
            '',
            'stale',
            'Hardening verification is pending for the current rule set.',
            [],
            $plan['enabled_controls'],
            $plan['unchecked_controls'],
            null,
            null,
            time()
        );
    }

    /**
     * Execute a fresh verification run and persist the result.
     */
    public static function run_verification(
        ?array $settings = null,
        ?bool $desired_present = null,
        ?bool $persisted = null,
        ?string $server = null,
        ?array $plan = null,
        ?string $sync_hash = null
    ): array {
        $settings       = is_array( $settings ) ? $settings : Hardening::get_saved_settings();
        $sync_hash      = is_string( $sync_hash ) && $sync_hash !== '' ? $sync_hash : self::build_sync_hash();
        $server         = is_string( $server ) && $server !== '' ? $server : self::detect_server();
        $plan           = is_array( $plan ) ? $plan : self::build_probe_plan( $settings, $server );
        $desired_present = is_bool( $desired_present ) ? $desired_present : trim( self::get_managed_rules_for_server( $server ) ) !== '';
        $requires_persisted_rules = ! empty( $plan['requires_persisted_rules'] );

        if ( empty( $plan['enabled_controls'] ) ) {
            self::clear_verification_state();
            return self::build_verification_payload(
                $sync_hash,
                'not_required',
                false,
                false,
                null,
                '',
                '',
                '',
                'No hardening verification checks are required for the current hardening state.',
                [],
                [],
                [],
                null,
                null
            );
        }

        if ( $requires_persisted_rules && ! $desired_present ) {
            $unsupported = self::build_verification_payload(
                $sync_hash,
                'unsupported',
                false,
                false,
                null,
                '',
                '',
                'no_candidate',
                self::build_unsupported_verification_message( $plan['unchecked_controls'] ),
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                null
            );
            self::persist_verification_state( $unsupported );
            return $unsupported;
        }

        $persisted = is_bool( $persisted ) ? $persisted : ! empty( self::get_state()['persisted'] );
        if ( $requires_persisted_rules && ! $persisted ) {
            $pending = self::build_verification_payload(
                $sync_hash,
                'pending',
                true,
                false,
                null,
                '',
                '',
                'not_persisted',
                'Hardening verification is waiting for the managed server rule block to be persisted.',
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                null,
                time()
            );
            self::persist_verification_state( $pending );
            return $pending;
        }

        if ( self::can_confirm_verification_from_persistence( $server, $plan ) ) {
            $confirmed = self::build_verification_payload(
                $sync_hash,
                'passed',
                true,
                false,
                true,
                '',
                '',
                'persisted_config',
                self::build_persisted_verification_message( $server ),
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                time(),
                time()
            );
            self::persist_verification_state( $confirmed );
            return $confirmed;
        }

        if ( empty( $plan['checks'] ) ) {
            $unsupported = self::build_verification_payload(
                $sync_hash,
                'unsupported',
                false,
                false,
                null,
                '',
                '',
                'no_candidate',
                self::build_unsupported_verification_message( $plan['unchecked_controls'] ),
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                null
            );
            self::persist_verification_state( $unsupported );
            return $unsupported;
        }

        if ( ! self::acquire_verification_lock() ) {
            $stored = self::get_stored_verification_state();
            if ( self::is_current_verification_state( $stored, $sync_hash ) ) {
                return self::merge_verification_plan( $stored, $plan );
            }

            return self::build_verification_payload(
                $sync_hash,
                'pending',
                true,
                false,
                null,
                '',
                '',
                'locked',
                'A hardening verification is already running for this rule set.',
                [],
                $plan['enabled_controls'],
                $plan['unchecked_controls'],
                null,
                null,
                time()
            );
        }

        $now           = time();
        $checks        = [];
        $attempted     = false;
        $target_url    = '';
        $target_label  = '';
        $last_success  = null;
        $failed_checks = [];
        $errored_checks = [];

        try {
            foreach ( $plan['checks'] as $check ) {
                $result    = self::execute_probe_check( $check );
                $checks[]  = $result;
                $attempted = true;

                if ( $target_url === '' && is_string( $result['target_url'] ?? null ) ) {
                    $target_url = (string) $result['target_url'];
                }
                if ( $target_label === '' && is_string( $result['target_label'] ?? null ) ) {
                    $target_label = (string) $result['target_label'];
                }

                if ( ! empty( $result['ok'] ) ) {
                    $last_success = $now;
                    continue;
                }

                if ( ( $result['reason'] ?? '' ) === 'probe_failed' ) {
                    $errored_checks[] = $result;
                    continue;
                }

                $failed_checks[] = $result;
            }
        } finally {
            self::release_verification_lock();
        }

        $status  = 'passed';
        $ok      = true;
        $reason  = 'blocked';
        $message = sprintf(
            'Verification confirmed %d hardening control%s enforced on the live site.',
            count( $checks ),
            count( $checks ) === 1 ? ' is' : 's are'
        );

        if ( ! empty( $failed_checks ) ) {
            $first_failed = $failed_checks[0];
            array_unshift( $checks, $first_failed );
            $status       = 'failed';
            $ok           = false;
            $target_url   = is_string( $first_failed['target_url'] ?? null ) ? (string) $first_failed['target_url'] : $target_url;
            $target_label = is_string( $first_failed['target_label'] ?? null ) ? (string) $first_failed['target_label'] : $target_label;
            $reason       = (string) ( $first_failed['reason'] ?? 'not_blocked' );
            $message      = self::build_failed_verification_message( $server, $first_failed );
        } elseif ( ! empty( $errored_checks ) ) {
            $first_errored = $errored_checks[0];
            array_unshift( $checks, $first_errored );
            $status  = 'unavailable';
            $ok      = null;
            $target_url   = is_string( $first_errored['target_url'] ?? null ) ? (string) $first_errored['target_url'] : $target_url;
            $target_label = is_string( $first_errored['target_label'] ?? null ) ? (string) $first_errored['target_label'] : $target_label;
            $reason  = 'probe_failed';
            $message = self::build_unavailable_verification_message( $errored_checks, $plan['unchecked_controls'] );
        } elseif ( ! empty( $plan['unchecked_controls'] ) ) {
            $status  = 'partial';
            $ok      = true;
            $reason  = 'partial';
            $message = sprintf(
                'Verification confirmed %d hardening control%s. %d additional control%s could not be checked automatically on this host.',
                count( $checks ),
                count( $checks ) === 1 ? '' : 's',
                count( $plan['unchecked_controls'] ),
                count( $plan['unchecked_controls'] ) === 1 ? '' : 's'
            );
        }

        $state = self::build_verification_payload(
            $sync_hash,
            $status,
            true,
            $attempted,
            $ok,
            $target_url,
            $target_label,
            $reason,
            $message,
            $checks,
            $plan['enabled_controls'],
            $plan['unchecked_controls'],
            $now,
            $last_success
        );

        $state = self::sanitize_verification_state( $state );
        self::persist_verification_state( $state );

        return $state;
    }

    /**
     * Mark the current sync hash as awaiting verification.
     */
    public static function mark_verification_pending(): void {
        $settings = Hardening::get_saved_settings();
        $server   = self::detect_server();
        $plan     = self::build_probe_plan( $settings, $server );
        if ( empty( $plan['enabled_controls'] ) ) {
            self::clear_verification_state();
            return;
        }

        $state    = self::build_verification_payload(
            self::build_sync_hash(),
            'pending',
            ! empty( $plan['enabled_controls'] ),
            false,
            null,
            '',
            '',
            'scheduled',
            'Hardening verification is queued for the current rule set.',
            [],
            $plan['enabled_controls'],
            $plan['unchecked_controls'],
            null,
            null,
            time()
        );

        self::persist_verification_state( $state );
    }

    /**
     * Remove any persisted verification state for server rules.
     */
    public static function clear_verification_state(): void {
        SettingsStore::delete_field( SettingsStore::COL_STATE, self::VERIFICATION_STATE_KEY );
        do_action( 'securwp_server_rule_state_changed', [] );
    }

    /**
     * Whether the current server-rule set should be re-verified.
     */
    public static function needs_verification_refresh(): bool {
        $state        = self::get_state();
        $verification = is_array( $state['verification'] ?? null ) ? $state['verification'] : [];
        $server       = is_string( $state['server'] ?? null ) ? (string) $state['server'] : self::detect_server();
        $plan         = self::build_probe_plan( Hardening::get_saved_settings(), $server );
        $requires_persisted_rules = ! empty( $plan['requires_persisted_rules'] );
        $policy       = self::get_verification_schedule_policy( $state );

        if ( empty( $plan['enabled_controls'] ) ) {
            return false;
        }

        if ( $requires_persisted_rules && ( empty( $state['desired_rules_present'] ) || empty( $state['persisted'] ) ) ) {
            return false;
        }

        if ( self::can_confirm_verification_from_persistence( $server, $plan ) ) {
            return false;
        }

        $status = is_string( $verification['status'] ?? null ) ? (string) $verification['status'] : 'pending';
        if ( $status === 'pending' ) {
            $delay = isset( $policy['maintenance_delay_seconds'] ) ? (int) $policy['maintenance_delay_seconds'] : 0;
            if ( $delay <= 0 ) {
                return false;
            }

            $recorded_at = isset( $verification['recorded_at'] ) ? (int) $verification['recorded_at'] : 0;
            if ( $recorded_at <= 0 ) {
                return true;
            }

            return ( time() - $recorded_at ) >= $delay;
        }

        if ( in_array( $status, [ 'failed', 'unavailable' ], true ) ) {
            $last_checked = isset( $verification['last_checked_at'] ) ? (int) $verification['last_checked_at'] : 0;
            $interval     = isset( $policy['retry_interval_seconds'] ) ? (int) $policy['retry_interval_seconds'] : self::VERIFY_RETRY_INTERVAL;

            return $last_checked <= 0 || ( time() - $last_checked ) >= $interval;
        }

        if ( $status === 'not_required' || $status === 'unsupported' ) {
            return false;
        }

        $last_checked = isset( $verification['last_checked_at'] ) ? (int) $verification['last_checked_at'] : 0;
        $interval     = isset( $policy['steady_interval_seconds'] ) ? (int) $policy['steady_interval_seconds'] : self::VERIFY_STEADY_INTERVAL;

        return $last_checked <= 0 || ( time() - $last_checked ) >= $interval;
    }

    /**
     * Resolve the verification cadence for the current environment.
     *
     * @param array<string,mixed>|null $state
     * @return array<string,mixed>
     */
    public static function get_verification_schedule_policy( ?array $state = null ): array {
        $state    = is_array( $state ) ? $state : self::get_state();
        $settings = Hardening::get_saved_settings();
        $server   = is_string( $state['server'] ?? null ) ? (string) $state['server'] : self::detect_server();
        $plan     = self::build_probe_plan( is_array( $settings ) ? $settings : [], $server );
        $mode     = is_string( $state['mode'] ?? null ) ? (string) $state['mode'] : 'none';

        $policy = [
            'single_event'              => false,
            'single_event_delay'        => 0,
            'maintenance_delay_seconds' => 0,
            'retry_interval_seconds'    => self::VERIFY_RETRY_INTERVAL,
            'steady_interval_seconds'   => self::VERIFY_STEADY_INTERVAL,
            'reason'                    => 'not_required',
        ];

        if ( empty( $plan['enabled_controls'] ) ) {
            return $policy;
        }

        $policy['reason'] = 'scheduled';

        $manual_modes = [
            'manual_only',
            'sync_failed',
            'repair_pending',
            'repair_required',
            'removal_pending',
            'removal_failed',
            'removal_required',
        ];

        if ( in_array( $mode, $manual_modes, true ) ) {
            $policy['reason'] = 'manual_action_required';
            return $policy;
        }

        $requires_persisted_rules = ! empty( $plan['requires_persisted_rules'] );
        $desired_present          = ! empty( $state['desired_rules_present'] );
        $persisted                = ! empty( $state['persisted'] );

        if ( $requires_persisted_rules && ( ! $desired_present || ! $persisted ) ) {
            $policy['reason'] = 'awaiting_persisted_rules';
            return $policy;
        }

        if ( self::can_confirm_verification_from_persistence( $server, $plan ) ) {
            $policy['reason'] = 'persisted_config_verified';
            return $policy;
        }

        if ( $server === 'nginx' && $requires_persisted_rules ) {
            $policy['single_event']              = true;
            $policy['single_event_delay']        = self::NGINX_VERIFY_DELAY;
            $policy['maintenance_delay_seconds'] = self::NGINX_VERIFY_DELAY;
            $policy['reason']                    = 'awaiting_nginx_activation';
            return $policy;
        }

        $policy['reason'] = 'unsupported_server';

        return $policy;
    }

    /**
     * Produce a message for manual-only or repair-required server-rule states.
     */
    private static function build_manual_server_message( string $path, bool $write_enabled, string $server, bool $stale_block ): string {
        $prefix = $stale_block
            ? 'The server hardening rules are out of date and need to be updated.'
            : 'Server hardening rules could not be applied automatically.';

        if ( $path === '' ) {
            return $prefix . ' The server configuration path could not be determined for this host. Open Manual Rules to view and copy the generated rules into your active server configuration.';
        }

        if ( ! $write_enabled ) {
            return $prefix . ' Automatic file updates are disabled in Advanced settings. Review the generated rules and apply them manually to ' . $path . '.';
        }

        if ( $server === 'nginx' ) {
            return $prefix . ' SecurWP does not have write access to ' . $path . '. Add the generated rules to this file or include them in your active Nginx server block, then reload Nginx.';
        }

        return $prefix . ' SecurWP does not have write access to ' . $path . '. Add the generated rules to this file or include them in your active virtual host configuration.';
    }

    /**
     * Describe why a configured server-rule target path is unsafe to manage.
     */
    private static function describe_target_path_issue( string $server, string $issue ): string {
        switch ( $issue ) {
            case 'symlink_target_not_allowed':
                return 'SecurWP cannot write to a server-rule target that is a symlink. Configure a real file path inside the site root or wp-content, then apply the generated rules there.';
            case 'unsafe_target_path':
                if ( $server === 'nginx' ) {
                    return 'The configured Nginx rule path is outside the allowed SecurWP write boundary. Configure a real file inside the site root or wp-content, then include it from your active Nginx server block.';
                }

                return 'The configured server rule path is outside the allowed SecurWP write boundary. Configure a real file inside the site root or wp-content, then apply the generated rules manually.';
            default:
                return 'The configured server rule path is not safe for automatic management. Open Manual Rules to copy the generated rules into your active server configuration.';
        }
    }

    /**
     * Describe a recorded sync failure.
     */
    private static function describe_sync_failure( string $operation, string $reason, string $server ): string {
        $verb = $operation === 'remove' ? 'remove' : 'write';

        switch ( $reason ) {
            case 'no_target_path':
                return 'The server configuration path could not be determined for this host. Open Manual Rules to view and copy the generated rules into your active server configuration.';
            case 'path_not_file':
                return 'The configured server rule path exists but is not a regular file. Correct the path in Advanced settings or apply the generated rules manually.';
            case 'symlink_target_not_allowed':
                return 'The configured server rule path is a symlink. Point SecurWP to a real file inside the site root or wp-content, or apply the generated rules manually.';
            case 'unsafe_target_path':
                return 'The configured server rule path is outside the allowed SecurWP write boundary. Correct the path in Advanced settings or apply the generated rules manually.';
            case 'directory_unavailable':
                return 'The directory for server hardening rules does not exist and could not be created. Create the directory or apply the generated rules manually.';
            case 'directory_not_writable':
                return 'The directory for server hardening rules is not writable. Adjust file permissions or apply the generated rules manually.';
            case 'file_not_writable':
                return 'The server rule file is not writable. Adjust file permissions or apply the generated rules manually.';
            case 'read_failed':
                return 'The existing server configuration file could not be read before attempting to ' . $verb . ' the generated rules. Review file permissions and apply the rules manually if needed.';
            case 'temp_file_failed':
                return 'A temporary file could not be created to safely update the server configuration. Review directory permissions or apply the generated rules manually.';
            case 'write_failed':
                return 'The server hardening rules could not be written to disk. Review file permissions or apply the rules manually.';
            case 'rename_failed':
                return 'The server rule file was written but could not be finalized. Review file permissions or apply the generated rules manually.';
            case 'post_write_verification_failed':
                return 'The server rule file was updated, but the saved contents could not be verified. Review the file and reapply the generated rules if needed.';
            case 'nginx_rule_conflict':
                return 'SecurWP found existing Nginx location rules that already manage paths it needs to protect. Automatic updates were stopped to avoid duplicate or conflicting directives. Review the generated rules and keep one owner for those protections.';
            default:
                if ( $server === 'nginx' ) {
                    return 'The Nginx hardening rules could not be ' . ( $operation === 'remove' ? 'removed' : 'written' ) . ' automatically. Review the include path and file permissions, or apply the generated rules manually.';
                }

                return 'The server hardening rules could not be ' . ( $operation === 'remove' ? 'removed' : 'written' ) . ' automatically. Review file permissions or apply the generated rules manually.';
        }
    }

    /**
     * Write the desired managed block to a server config file.
     *
     * @return true|string True on success, or a machine-readable failure reason.
     */
    private static function write_managed_block( string $file, string $managed_block, string $server = '' ) {
        $target_issue = self::get_managed_target_path_issue( $file );
        if ( $target_issue !== '' ) {
            return $target_issue;
        }

        if ( $server === '' ) {
            $server = self::detect_server_from_path( $file );
        }

        $existing_contents = '';
        $file_exists       = file_exists( $file );
        $dir               = dirname( $file );
        $dir_writable      = is_dir( $dir ) && wp_is_writable( $dir );
        $file_writable     = $file_exists && is_file( $file ) && wp_is_writable( $file );

        if ( $file_exists ) {
            if ( ! is_file( $file ) ) {
                return 'path_not_file';
            }

            $read = self::read_file_contents( $file );
            if ( ! is_string( $read ) ) {
                return 'read_failed';
            }

            $existing_contents = $read;
        }

        if ( ! is_dir( $dir ) ) {
            if ( ! wp_mkdir_p( $dir ) ) {
                return 'directory_unavailable';
            }

            $dir_writable = wp_is_writable( $dir );
        }

        if ( ! $file_exists && ! $dir_writable ) {
            return 'directory_not_writable';
        }

        if ( $file_exists && ! $file_writable && ! $dir_writable ) {
            return 'file_not_writable';
        }

        $line_ending   = self::get_line_ending( $existing_contents );
        $managed_block = trim( $managed_block );

        if ( $managed_block !== '' ) {
            $managed_block = self::normalize_line_endings( $managed_block, $line_ending );
        }

        $new_contents = self::replace_managed_block( $existing_contents, $managed_block, $server, $line_ending );
        if ( $new_contents === $existing_contents ) {
            return true;
        }

        return self::safe_write_file( $file, $new_contents, $file_exists, $file_writable, $dir_writable );
    }

    /**
     * Replace SecurWP-managed blocks while preserving surrounding file order.
     */
    private static function replace_managed_block( string $contents, string $managed_block, string $server, string $line_ending ): string {
        $pattern     = self::get_managed_block_pattern();
        $stripped = preg_replace( $pattern, '', $contents );

        if ( ! is_string( $stripped ) ) {
            return $contents;
        }

        $stripped = self::collapse_excess_blank_lines( $stripped, $line_ending );

        if ( $managed_block === '' ) {
            return $stripped;
        }

        $reconciled = self::remove_duplicate_managed_stanzas_from_contents( $stripped, $managed_block, $line_ending );
        $reconciled = self::remove_conflicting_singleton_directives( $reconciled, $managed_block, $server, $line_ending );
        return self::insert_managed_block_at_preferred_position( $reconciled, $managed_block, $server, $line_ending );
    }

    /**
     * Remove unmanaged stanzas that exactly match stanzas SecurWP will manage.
     */
    private static function remove_duplicate_managed_stanzas_from_contents( string $contents, string $managed_block, string $line_ending ): string {
        $managed_stanzas = self::extract_rule_stanzas( self::strip_managed_block_wrapper( $managed_block ) );
        if ( $managed_stanzas === [] ) {
            return $contents;
        }

        $updated = [];

        foreach ( self::extract_rule_stanzas( $contents ) as $stanza ) {
            $fingerprint = self::fingerprint_rule_stanza( $stanza );
            if ( isset( $managed_stanzas[ $fingerprint ] ) ) {
                continue;
            }

            $updated[] = $stanza;
        }

        if ( $updated === [] ) {
            return '';
        }

        return self::collapse_excess_blank_lines( implode( $line_ending . $line_ending, $updated ), $line_ending );
    }

    /**
     * Remove unmanaged singleton directives that SecurWP now owns.
     */
    private static function remove_conflicting_singleton_directives( string $contents, string $managed_block, string $server, string $line_ending ): string {
        switch ( $server ) {
            case 'apache':
            case 'litespeed':
                $directives = [ 'Options -Indexes' ];
                break;
            case 'nginx':
                $directives = [ 'autoindex off;' ];
                break;
            default:
                $directives = [];
                break;
        }

        if ( $directives === [] ) {
            return $contents;
        }

        $managed_directives = array_values(
            array_filter(
                $directives,
                static function ( string $directive ) use ( $managed_block ): bool {
                    return self::block_contains_exact_line( $managed_block, $directive );
                }
            )
        );
        if ( $managed_directives === [] ) {
            return $contents;
        }

        $normalized    = self::normalize_line_endings( $contents, $line_ending );
        $lines         = explode( $line_ending, $normalized );
        $updated_lines = [];
        $removed       = false;
        $nginx_depth   = 0;
        $apache_depth  = 0;

        foreach ( $lines as $line ) {
            $trimmed = trim( $line );

            if (
                $trimmed !== ''
                && in_array( $trimmed, $managed_directives, true )
                && $nginx_depth === 0
                && $apache_depth === 0
            ) {
                $removed = true;
            } else {
                $updated_lines[] = $line;
            }

            $nginx_depth  = self::update_nginx_block_depth( $nginx_depth, $trimmed );
            $apache_depth = self::update_apache_block_depth( $apache_depth, $trimmed );
        }

        if ( ! $removed ) {
            return $contents;
        }

        return self::collapse_excess_blank_lines( implode( $line_ending, $updated_lines ), $line_ending );
    }

    /**
     * Track Nginx brace nesting so singleton dedupe only touches top-level
     * unmanaged directives.
     */
    private static function update_nginx_block_depth( int $depth, string $line ): int {
        if ( $line === '' || str_starts_with( $line, '#' ) ) {
            return $depth;
        }

        $next_depth = $depth + substr_count( $line, '{' ) - substr_count( $line, '}' );

        return max( 0, $next_depth );
    }

    /**
     * Track Apache-style container nesting so singleton dedupe does not strip
     * directives from unrelated <IfModule>, <Directory>, or similar blocks.
     */
    private static function update_apache_block_depth( int $depth, string $line ): int {
        if ( $line === '' || str_starts_with( $line, '#' ) ) {
            return $depth;
        }

        if ( preg_match( '/^<\s*\/[A-Za-z][^>]*>\s*$/', $line ) === 1 ) {
            return max( 0, $depth - 1 );
        }

        if (
            preg_match( '/^<\s*[A-Za-z][^>]*>\s*$/', $line ) === 1
            && preg_match( '/\/>\s*$/', $line ) !== 1
        ) {
            return $depth + 1;
        }

        return $depth;
    }

    /**
     * Whether a managed block contains an exact directive line.
     */
    private static function block_contains_exact_line( string $block, string $line ): bool {
        return preg_match( '/^' . preg_quote( $line, '/' ) . '[ \t]*$/m', $block ) === 1;
    }

    /**
     * Remove the SecurWP wrapper lines from a rendered managed block.
     */
    private static function strip_managed_block_wrapper( string $managed_block ): string {
        $normalized = self::normalize_line_endings( trim( $managed_block ), "\n" );
        if ( $normalized === '' ) {
            return '';
        }

        $lines = preg_split( "/\n/", $normalized );
        if ( ! is_array( $lines ) ) {
            return '';
        }

        $payload = [];

        foreach ( $lines as $line ) {
            $trimmed = trim( (string) $line );

            if ( $trimmed === self::BEGIN_MARKER || $trimmed === self::END_MARKER ) {
                continue;
            }

            if ( str_starts_with( $trimmed, '# SecurWP Config Details:' ) ) {
                continue;
            }

            $payload[] = (string) $line;
        }

        return trim( implode( "\n", $payload ) );
    }

    /**
     * Split rules into normalized stanzas separated by blank lines.
     *
     * @return array<int,string>
     */
    private static function extract_rule_stanzas( string $rules ): array {
        $normalized = trim( self::normalize_line_endings( $rules, "\n" ) );
        if ( $normalized === '' ) {
            return [];
        }

        $stanzas = preg_split( "/\n{2,}/", $normalized );
        if ( ! is_array( $stanzas ) ) {
            return [];
        }

        $result = [];

        foreach ( $stanzas as $stanza ) {
            $candidate = trim( (string) $stanza );
            if ( $candidate === '' ) {
                continue;
            }

            $result[] = self::normalize_rule_stanza( $candidate );
        }

        return $result;
    }

    /**
     * Normalize trailing whitespace in a stanza before comparison or writeback.
     */
    private static function normalize_rule_stanza( string $stanza ): string {
        $normalized = self::normalize_line_endings( trim( $stanza ), "\n" );
        $lines      = preg_split( "/\n/", $normalized );

        if ( ! is_array( $lines ) ) {
            return trim( $normalized );
        }

        $cleaned = array_map(
            static function ( string $line ): string {
                return rtrim( $line, " \t" );
            },
            $lines
        );

        return trim( implode( "\n", $cleaned ) );
    }

    /**
     * Build a stable stanza fingerprint for exact-match dedupe.
     */
    private static function fingerprint_rule_stanza( string $stanza ): string {
        return md5( self::normalize_rule_stanza( $stanza ) );
    }

    /**
     * Insert the managed block at the canonical position for the config type.
     */
    private static function insert_managed_block_at_preferred_position( string $contents, string $managed_block, string $server, string $line_ending ): string {
        $managed_block = self::normalize_line_endings( trim( $managed_block ), $line_ending );
        if ( $managed_block === '' ) {
            return $contents;
        }

        $normalized = self::normalize_line_endings( $contents, $line_ending );
        if ( $normalized === '' ) {
            return $managed_block . $line_ending;
        }

        if ( in_array( $server, [ 'apache', 'litespeed' ], true ) ) {
            $inserted = preg_replace_callback(
                '/^[ \t]*' . preg_quote( self::WORDPRESS_BEGIN_MARKER, '/' ) . '[^\r\n]*(?:\R)?/m',
                static function ( array $matches ) use ( $managed_block, $line_ending ): string {
                    return $managed_block . $line_ending . $line_ending . (string) $matches[0];
                },
                $normalized,
                1
            );

            if ( is_string( $inserted ) && $inserted !== $normalized ) {
                return self::collapse_excess_blank_lines( $inserted, $line_ending );
            }
        }

        $trimmed = ltrim( $normalized, "\r\n" );
        return self::collapse_excess_blank_lines( $managed_block . $line_ending . $line_ending . $trimmed, $line_ending );
    }

    /**
     * Infer the server type from the managed config path.
     */
    private static function detect_server_from_path( string $file ): string {
        $basename = strtolower( basename( $file ) );

        if ( $basename === '.htaccess' ) {
            return 'apache';
        }

        if ( str_ends_with( $basename, '.conf' ) ) {
            return 'nginx';
        }

        return self::detect_server();
    }

    /**
     * Pattern covering both legacy insert_with_markers output and versioned blocks.
     */
    private static function get_managed_block_pattern(): string {
        $current_begin = preg_quote( self::BEGIN_MARKER, '/' );
        $current_end   = preg_quote( self::END_MARKER, '/' );
        $legacy_begin = [];
        $legacy_end   = [];

        foreach ( self::LEGACY_MARKER_PAIRS as $pair ) {
            $legacy_begin[] = '[ \t]*' . preg_quote( (string) $pair[0], '/' ) . '[ \t]*';
            $legacy_end[]   = '[ \t]*' . preg_quote( (string) $pair[1], '/' ) . '[ \t]*';
        }

        return '/^(?:[ \t]*' . $current_begin . '[ \t]*|' . implode( '|', $legacy_begin ) . ')\R.*?^(?:[ \t]*' . $current_end . '[ \t]*|' . implode( '|', $legacy_end ) . ')(?:\R)?/ims';
    }

    /**
     * Write a config file using an atomic temp-file swap when available, or a
     * direct overwrite fallback when the existing file is writable but the
     * parent directory is not.
     *
     * @return true|string True on success, or a machine-readable failure reason.
     */
    private static function safe_write_file( string $file, string $contents, bool $file_exists, bool $file_writable, bool $dir_writable ) {
        $dir = dirname( $file );
        if ( ! is_dir( $dir ) ) {
            return 'directory_unavailable';
        }

        if ( $dir_writable ) {
            $temp = tempnam( $dir, 'swp' );
            if ( ! is_string( $temp ) || $temp === '' ) {
                return $file_exists && $file_writable ? self::direct_write_file( $file, $contents ) : 'temp_file_failed';
            }

            $write_result = self::write_bytes_to_file( $temp, $contents );
            if ( $write_result !== true ) {
                wp_delete_file( $temp );

                return $file_exists && $file_writable ? self::direct_write_file( $file, $contents ) : 'write_failed';
            }

            if ( $file_exists ) {
                $perms = @fileperms( $file );
                if ( is_int( $perms ) ) {
                    self::set_file_permissions( $temp, $perms & 0777 );
                }
            } else {
                self::set_file_permissions( $temp, 0644 );
            }

            if ( self::move_file( $temp, $file ) ) {
                return self::verify_written_contents( $file, $contents ) ? true : 'post_write_verification_failed';
            }

            wp_delete_file( $temp );

            if ( $file_exists && $file_writable ) {
                return self::direct_write_file( $file, $contents );
            }

            return 'rename_failed';
        }

        if ( $file_exists && $file_writable ) {
            return self::direct_write_file( $file, $contents );
        }

        return $file_exists ? 'file_not_writable' : 'directory_not_writable';
    }

    /**
     * Overwrite an existing config file directly when atomic replacement is unavailable.
     *
     * @return true|string
     */
    private static function direct_write_file( string $file, string $contents ) {
        return self::with_temporary_file_permissions(
            $file,
            static function () use ( $file, $contents ) {
                $write_result = self::write_bytes_to_file( $file, $contents );
                if ( $write_result !== true ) {
                    return 'write_failed';
                }

                return self::verify_written_contents( $file, $contents )
                    ? true
                    : 'post_write_verification_failed';
            }
        );
    }

    /**
     * Detect the predominant line ending in an existing file.
     */
    private static function get_line_ending( string $contents ): string {
        if ( $contents === '' ) {
            return PHP_EOL;
        }

        $count = [
            "\n"   => preg_match_all( "/(?<!\r)\n/", $contents ),
            "\r"   => preg_match_all( "/\r(?!\n)/", $contents ),
            "\r\n" => preg_match_all( "/(?<!\r)\r\n/", $contents ),
        ];

        if ( array_sum( $count ) === 0 ) {
            return PHP_EOL;
        }

        $max = max( $count );
        foreach ( $count as $line_ending => $line_count ) {
            if ( $line_count === $max ) {
                return $line_ending;
            }
        }

        return PHP_EOL;
    }

    /**
     * Normalize line endings for insertion into an existing config file.
     */
    private static function normalize_line_endings( string $contents, string $line_ending ): string {
        return preg_replace( '/(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n/', $line_ending, $contents ) ?? $contents;
    }

    /**
     * Collapse large blank gaps left behind when removing duplicate managed blocks.
     */
    private static function collapse_excess_blank_lines( string $contents, string $line_ending ): string {
        $normalized = self::normalize_line_endings( $contents, $line_ending );
        $collapsed  = preg_replace( '/(?:' . preg_quote( $line_ending, '/' ) . '){3,}/', $line_ending . $line_ending, $normalized );

        return is_string( $collapsed ) ? $collapsed : $normalized;
    }

    /**
     * Return the empty managed-block snapshot shape used across the state model.
     *
     * @return array{block:string,count:int}
     */
    private static function empty_managed_block_snapshot(): array {
        return [
            'block' => '',
            'count' => 0,
        ];
    }

    /**
     * Enumerate all config files SecurWP may manage.
     *
     * @return array<int,string>
     */
    private static function get_possible_config_paths(): array {
        $paths = [
            ABSPATH . '.htaccess',
            self::get_nginx_path(),
        ];

        return array_values( array_unique( array_filter( $paths, static fn( $path ) => is_string( $path ) && $path !== '' ) ) );
    }

    /**
     * Build a probe plan for the currently enabled server-layer controls.
     *
     * @param array<string,mixed> $settings
     * @return array{checks:array<int,array<string,mixed>>,enabled_controls:array<int,string>,unchecked_controls:array<int,string>,requires_persisted_rules:bool}
     */
    private static function build_probe_plan( array $settings, string $server = '' ): array {
        if ( $server === '' ) {
            $server = self::detect_server();
        }

        $checks                   = [];
        $enabled_controls         = [];
        $unchecked_controls       = [];
        $requires_persisted_rules = false;

        if ( DataDirectoryProtection::get_public_path() !== null ) {
            $enabled_controls[] = 'protect_runtime_storage';
            $requires_persisted_rules = true;
        } else {
            $unchecked_controls[] = 'protect_runtime_storage';
        }

        if ( ! empty( $settings['block_sensitive_files'] ) ) {
            $enabled_controls[] = 'block_sensitive_files';
            $requires_persisted_rules = true;
        }

        if ( ! empty( $settings['disable_directory_browsing'] ) ) {
            $enabled_controls[] = 'disable_directory_browsing';
            $requires_persisted_rules = true;
        }

        if ( 'disable' === ( $settings['disable_xmlrpc'] ?? Hardening::get_default_settings()['disable_xmlrpc'] ) ) {
            $enabled_controls[] = 'disable_xmlrpc';
            $requires_persisted_rules = true;
        }

        foreach ( [
            'block_php_uploads' => 'uploads',
            'block_php_plugins' => 'plugins',
            'block_php_themes'  => 'themes',
        ] as $control => $directory ) {
            if ( empty( $settings[ $control ] ) ) {
                continue;
            }

            $enabled_controls[] = $control;
            $requires_persisted_rules = true;
        }

        if ( $server === 'nginx' && $requires_persisted_rules ) {
            $checks[] = self::build_nginx_activation_probe();
        }

        return [
            'checks'             => $checks,
            'enabled_controls'   => array_values( array_unique( $enabled_controls ) ),
            'unchecked_controls' => array_values( array_unique( $unchecked_controls ) ),
            'requires_persisted_rules' => $requires_persisted_rules,
        ];
    }

    /**
     * Build the dedicated Nginx activation probe definition.
     *
     * @return array<string,mixed>
     */
    private static function build_nginx_activation_probe(): array {
        return [
            'control'      => 'nginx_activation',
            'type'         => 'nginx_activation',
            'label'        => 'Nginx activation',
            'target_label' => 'SecurWP Nginx activation canary',
            'public_path'  => self::get_nginx_activation_probe_path(),
        ];
    }

    /**
     * Whether the current server plan can be verified solely from persisted config.
     *
     * Apache and LiteSpeed enforce the site-root .htaccess file directly, so a
     * persisted managed block is the authoritative runtime state for server-layer
     * protections. Nginx still needs an activation check because the include file
     * can exist on disk without being loaded by the active server block.
     *
     * @param array<string,mixed> $plan
     */
    private static function can_confirm_verification_from_persistence( string $server, array $plan ): bool {
        return in_array( $server, [ 'apache', 'litespeed' ], true )
            && ! empty( $plan['requires_persisted_rules'] )
            && empty( $plan['checks'] )
            && empty( $plan['unchecked_controls'] );
    }

    /**
     * Build a persistence-based verification message for .htaccess environments.
     */
    private static function build_persisted_verification_message( string $server ): string {
        if ( in_array( $server, [ 'apache', 'litespeed' ], true ) ) {
            return 'SecurWP verified that the managed .htaccess rules are present in the active site configuration.';
        }

        return 'SecurWP verified that the managed server rules are present in the configured server file.';
    }

    /**
     * Public request path for the Nginx activation probe.
     */
    public static function get_nginx_activation_probe_path(): string {
        return SitePathResolver::get_site_root_file_path( ltrim( self::NGINX_ACTIVATION_PROBE_PATH, '/' ) );
    }

    /**
     * Exact HTTP status returned by the Nginx activation probe.
     */
    public static function get_nginx_activation_probe_status(): int {
        return self::NGINX_ACTIVATION_PROBE_STATUS;
    }

    /**
     * Header emitted by the Nginx activation probe.
     */
    public static function get_nginx_activation_probe_header_name(): string {
        return self::NGINX_ACTIVATION_PROBE_HEADER;
    }

    /**
     * Header value emitted by the Nginx activation probe.
     */
    public static function get_nginx_activation_probe_header_value(): string {
        return self::NGINX_ACTIVATION_PROBE_VALUE;
    }

    /**
     * Body emitted by the Nginx activation probe.
     */
    public static function get_nginx_activation_probe_body(): string {
        return self::NGINX_ACTIVATION_PROBE_BODY;
    }

    /**
     * Execute a single probe check.
     *
     * @param array<string,mixed> $check
     * @return array<string,mixed>
     */
    private static function execute_probe_check( array $check ): array {
        $type = is_string( $check['type'] ?? null ) ? (string) $check['type'] : 'static';

        if ( $type === 'nginx_activation' ) {
            return self::execute_nginx_activation_probe( $check );
        }

        return self::build_probe_result(
            is_string( $check['control'] ?? null ) ? (string) $check['control'] : '',
            is_string( $check['label'] ?? null ) ? (string) $check['label'] : '',
            '',
            is_string( $check['target_label'] ?? null ) ? (string) $check['target_label'] : '',
            false,
            null,
            'probe_failed',
            'SecurWP could not run the requested verification check.'
        );
    }

    /**
     * Execute the dedicated Nginx activation probe.
     *
     * @param array<string,mixed> $check
     * @return array<string,mixed>
     */
    private static function execute_nginx_activation_probe( array $check ): array {
        $control      = is_string( $check['control'] ?? null ) ? (string) $check['control'] : '';
        $label        = is_string( $check['label'] ?? null ) ? (string) $check['label'] : 'Nginx activation';
        $target_label = is_string( $check['target_label'] ?? null ) ? (string) $check['target_label'] : 'SecurWP Nginx activation canary';
        $public_path  = is_string( $check['public_path'] ?? null ) ? (string) $check['public_path'] : '';
        $cache_buster = (string) ( microtime( true ) . '-' . wp_rand( 1000, 9999 ) );
        $request      = self::perform_http_request(
            $public_path,
            [ 'securwp_probe' => $cache_buster ],
            [
                'headers' => [
                    'Accept' => 'text/plain',
                ],
            ]
        );

        if ( isset( $request['error'] ) ) {
            return self::build_probe_result(
                $control,
                $label,
                is_string( $request['target_url'] ?? null ) ? (string) $request['target_url'] : '',
                $target_label,
                false,
                null,
                'probe_failed',
                is_string( $request['error'] ) ? (string) $request['error'] : 'SecurWP could not complete the Nginx activation verification request.'
            );
        }

        $target_url   = is_string( $request['target_url'] ?? null ) ? (string) $request['target_url'] : '';
        $status_code  = isset( $request['status_code'] ) ? (int) $request['status_code'] : 0;
        $headers      = isset( $request['headers'] ) && is_array( $request['headers'] ) ? $request['headers'] : [];
        $body         = is_string( $request['body'] ?? null ) ? trim( (string) $request['body'] ) : '';
        $header_name  = strtolower( self::get_nginx_activation_probe_header_name() );
        $header_value = self::normalize_response_header_value( $headers[ $header_name ] ?? null );
        $status_ok    = $status_code === self::get_nginx_activation_probe_status();
        $header_ok    = $header_value === self::get_nginx_activation_probe_header_value();
        $body_ok      = $body === self::get_nginx_activation_probe_body();

        if ( $status_ok && $header_ok && $body_ok ) {
            return self::build_probe_result(
                $control,
                $label,
                $target_url,
                $target_label,
                true,
                $status_code,
                'activation_verified',
                'SecurWP verified that the managed Nginx include is active on the live site.'
            );
        }

        return self::build_probe_result(
            $control,
            $label,
            $target_url,
            $target_label,
            false,
            $status_code,
            'activation_not_verified',
            'SecurWP could not confirm that the managed Nginx include is active. Ensure the file is included by the live server block and reload Nginx.'
        );
    }

    /**
     * Execute a same-origin HTTP request for a verification probe.
     *
     * @param array<string,string> $query_args
     * @param array<string,mixed>  $request_args
     * @return array<string,mixed>
     */
    private static function perform_http_request( string $public_path, array $query_args = [], array $request_args = [] ): array {
        $target_url = self::build_absolute_site_url( $public_path, $query_args );
        if ( $target_url === '' ) {
            return [
                'target_url' => '',
                'error'      => 'SecurWP could not build a same-origin verification URL for the activation check.',
            ];
        }

        $headers = self::build_probe_auth_headers(
            isset( $request_args['method'] ) && is_string( $request_args['method'] ) ? strtoupper( (string) $request_args['method'] ) : 'GET',
            $target_url
        ) + [
            'Cache-Control' => 'no-cache',
            'Pragma'        => 'no-cache',
        ];

        if ( isset( $request_args['headers'] ) && is_array( $request_args['headers'] ) ) {
            foreach ( $request_args['headers'] as $name => $value ) {
                if ( is_string( $name ) && is_string( $value ) ) {
                    $headers[ $name ] = $value;
                }
            }
        }

        $args = [
            'method'              => isset( $request_args['method'] ) && is_string( $request_args['method'] ) ? strtoupper( (string) $request_args['method'] ) : 'GET',
            'timeout'             => self::PROBE_TIMEOUT,
            'redirection'         => isset( $request_args['redirection'] ) ? max( 0, (int) $request_args['redirection'] ) : 0,
            'reject_unsafe_urls'  => true,
            'limit_response_size' => isset( $request_args['limit_response_size'] ) ? (int) $request_args['limit_response_size'] : self::PROBE_RESPONSE_LIMIT,
            'headers'             => $headers,
            'cookies'             => [],
            'user-agent'          => 'SecurWP/' . ( defined( 'SECURWP_VERSION' ) ? SECURWP_VERSION : 'dev' ) . '; server-rule-verification',
        ];

        if ( isset( $request_args['body'] ) && is_string( $request_args['body'] ) ) {
            $args['body'] = $request_args['body'];
        }

        $response = wp_remote_request( $target_url, $args );
        if ( is_wp_error( $response ) ) {
            return [
                'target_url' => $target_url,
                'error'      => $response->get_error_message(),
            ];
        }

        return [
            'target_url'  => $target_url,
            'status_code' => (int) wp_remote_retrieve_response_code( $response ),
            'headers'     => self::normalize_response_headers( wp_remote_retrieve_headers( $response ) ),
            'body'        => (string) wp_remote_retrieve_body( $response ),
        ];
    }

    /**
     * Normalize WP HTTP response headers into a lowercase string map.
     *
     * @param mixed $headers
     * @return array<string,string>
     */
    private static function normalize_response_headers( $headers ): array {
        if ( is_object( $headers ) && method_exists( $headers, 'getAll' ) ) {
            $headers = $headers->getAll();
        }

        if ( ! is_array( $headers ) ) {
            return [];
        }

        $normalized = [];

        foreach ( $headers as $name => $value ) {
            if ( ! is_string( $name ) ) {
                continue;
            }

            $normalized[ strtolower( $name ) ] = self::normalize_response_header_value( $value );
        }

        return $normalized;
    }

    /**
     * Convert a response header value into a stable string.
     *
     * @param mixed $value
     */
    private static function normalize_response_header_value( $value ): string {
        if ( is_array( $value ) ) {
            $value = reset( $value );
        }

        return is_scalar( $value ) ? trim( (string) $value ) : '';
    }

    /**
     * Build a normalized per-check probe result.
     *
     * @return array<string,mixed>
     */
    private static function build_probe_result( string $control, string $label, string $target_url, string $target_label, bool $ok, ?int $status_code, string $reason, string $message ): array {
        return [
            'control'      => $control,
            'label'        => $label,
            'target_url'   => $target_url,
            'target_label' => $target_label,
            'ok'           => $ok,
            'status_code'  => $status_code,
            'reason'       => $reason,
            'message'      => $message,
        ];
    }

    /**
     * Persist a canonical verification state payload.
     *
     * @param array<string,mixed> $state
     */
    private static function persist_verification_state( array $state ): void {
        $state = self::sanitize_verification_state( $state );
        SettingsStore::set_field( SettingsStore::COL_STATE, self::VERIFICATION_STATE_KEY, $state );
        do_action( 'securwp_server_rule_state_changed', $state );
    }

    /**
     * Read the persisted verification state.
     *
     * @return array<string,mixed>
     */
    private static function get_stored_verification_state(): array {
        $state = SettingsStore::get_field( SettingsStore::COL_STATE, self::VERIFICATION_STATE_KEY, [] );
        if ( ! is_array( $state ) ) {
            return [];
        }

        $sanitized = self::sanitize_verification_state( $state );

        if ( array_key_exists( 'target_url', $state ) || array_key_exists( 'checks', $state ) ) {
            SettingsStore::set_field( SettingsStore::COL_STATE, self::VERIFICATION_STATE_KEY, $sanitized );
        }

        return $sanitized;
    }

    /**
     * Reduce verification state to coarse, non-sensitive result metadata.
     *
     * The stored and public payload must never include loopback probe URLs,
     * query tokens, or per-check details. Those values are only needed during
     * the verification run itself.
     *
     * @param array<string,mixed> $state
     * @return array<string,mixed>
     */
    private static function sanitize_verification_state( array $state ): array {
        unset( $state['target_url'], $state['checks'] );

        if ( isset( $state['sync_hash'] ) && ! is_string( $state['sync_hash'] ) ) {
            $state['sync_hash'] = '';
        }

        if ( isset( $state['status'] ) && ! is_string( $state['status'] ) ) {
            $state['status'] = 'pending';
        }

        if ( isset( $state['supported'] ) ) {
            $state['supported'] = (bool) $state['supported'];
        }

        if ( isset( $state['attempted'] ) ) {
            $state['attempted'] = (bool) $state['attempted'];
        }

        if ( array_key_exists( 'ok', $state ) && $state['ok'] !== null ) {
            $state['ok'] = (bool) $state['ok'];
        }

        if ( array_key_exists( 'status_code', $state ) && $state['status_code'] !== null ) {
            $state['status_code'] = (int) $state['status_code'];
        }

        if ( isset( $state['target_label'] ) && ! is_string( $state['target_label'] ) ) {
            $state['target_label'] = '';
        }

        if ( isset( $state['reason'] ) && ! is_string( $state['reason'] ) ) {
            $state['reason'] = '';
        }

        if ( isset( $state['message'] ) && ! is_string( $state['message'] ) ) {
            $state['message'] = '';
        }

        if ( isset( $state['coverage'] ) && ! is_string( $state['coverage'] ) ) {
            $state['coverage'] = 'partial';
        }

        $state['enabled_controls'] = isset( $state['enabled_controls'] ) && is_array( $state['enabled_controls'] )
            ? array_values( array_filter( $state['enabled_controls'], 'is_string' ) )
            : [];
        $state['unchecked_controls'] = isset( $state['unchecked_controls'] ) && is_array( $state['unchecked_controls'] )
            ? array_values( array_filter( $state['unchecked_controls'], 'is_string' ) )
            : [];

        if ( array_key_exists( 'last_checked_at', $state ) && $state['last_checked_at'] !== null ) {
            $state['last_checked_at'] = (int) $state['last_checked_at'];
        }

        if ( array_key_exists( 'last_success_at', $state ) && $state['last_success_at'] !== null ) {
            $state['last_success_at'] = (int) $state['last_success_at'];
        }

        if ( array_key_exists( 'recorded_at', $state ) && $state['recorded_at'] !== null ) {
            $state['recorded_at'] = (int) $state['recorded_at'];
        }

        return $state;
    }

    /**
     * Merge dynamic probe-plan coverage into persisted verification state.
     *
     * @param array<string,mixed> $stored
     * @param array<string,mixed> $plan
     * @return array<string,mixed>
     */
    private static function merge_verification_plan( array $stored, array $plan ): array {
        $stored['supported']          = ! empty( $plan['enabled_controls'] );
        $stored['enabled_controls']   = $plan['enabled_controls'];
        $stored['unchecked_controls'] = $plan['unchecked_controls'];
        $stored['coverage']           = empty( $plan['unchecked_controls'] ) ? 'full' : 'partial';

        return $stored;
    }

    /**
     * Whether a stored verification payload still matches the current rule hash.
     *
     * @param array<string,mixed> $stored
     */
    private static function is_current_verification_state( array $stored, string $sync_hash ): bool {
        return is_string( $stored['sync_hash'] ?? null ) && (string) $stored['sync_hash'] === $sync_hash;
    }

    /**
     * Build the canonical verification payload shape.
     *
     * @param array<int,array<string,mixed>> $checks
     * @param array<int,string> $enabled_controls
     * @param array<int,string> $unchecked_controls
     * @return array<string,mixed>
     */
    private static function build_verification_payload(
        string $sync_hash,
        string $status,
        bool $supported,
        bool $attempted,
        ?bool $ok,
        string $target_url,
        string $target_label,
        string $reason,
        string $message,
        array $checks,
        array $enabled_controls,
        array $unchecked_controls,
        ?int $last_checked_at,
        ?int $last_success_at,
        ?int $recorded_at = null
    ): array {
        $primary_status_code = null;
        if ( ! empty( $checks ) && isset( $checks[0]['status_code'] ) ) {
            $primary_status_code = $checks[0]['status_code'] === null ? null : (int) $checks[0]['status_code'];
        }

        if ( $target_url === '' && ! empty( $checks ) && is_string( $checks[0]['target_url'] ?? null ) ) {
            $target_url = (string) $checks[0]['target_url'];
        }

        if ( $target_label === '' && ! empty( $checks ) && is_string( $checks[0]['target_label'] ?? null ) ) {
            $target_label = (string) $checks[0]['target_label'];
        }

        return [
            'sync_hash'          => $sync_hash,
            'status'             => $status,
            'supported'          => $supported,
            'attempted'          => $attempted,
            'ok'                 => $ok,
            'status_code'        => $primary_status_code,
            'target_url'         => $target_url,
            'target_label'       => $target_label,
            'reason'             => $reason,
            'message'            => $message,
            'coverage'           => empty( $unchecked_controls ) ? 'full' : 'partial',
            'checks'             => $checks,
            'enabled_controls'   => $enabled_controls,
            'unchecked_controls' => $unchecked_controls,
            'last_checked_at'    => $last_checked_at,
            'last_success_at'    => $last_success_at,
            'recorded_at'        => $recorded_at,
        ];
    }

    /**
     * Build a persistent banner descriptor for platform-aware UX.
     *
     * @param array<string,mixed> $verification
     * @return array<string,mixed>
     */
    private static function build_banner_state( string $server, string $detection_status, string $server_software, string $mode, string $message, string $path, bool $desired_present, bool $persisted, array $verification ): array {
        $banner = [
            'show'          => false,
            'severity'      => 'info',
            'title'         => '',
            'message'       => '',
            'action'        => '',
            'action_label'  => '',
            'documentation_url' => '',
            'documentation_label' => '',
        ];

        if ( $server === 'unknown' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';

            if ( $detection_status === 'detected_unsupported' ) {
                $software_label   = $server_software !== '' ? $server_software : 'this server environment';
                $banner['title']  = 'Server Hardening Is Not Supported Here';
                $banner['message'] = sprintf(
                    'SecurWP identified %s for this site. Server-level hardening supports Apache, LiteSpeed, and Nginx only, so server hardening protections will not be applied on this host.',
                    $software_label
                );
                return self::attach_server_hardening_troubleshooting( $banner );
            }

            $banner['title']        = 'Select the Server Type to Enable Server Hardening';
            $banner['message']      = 'SecurWP could not determine the active web server for this site. Choose the server type in Settings > Advanced before relying on server-level hardening protections.';
            $banner['action']       = 'open_advanced_server_settings';
            $banner['action_label'] = 'Open Advanced Settings';
            return self::attach_server_hardening_troubleshooting( $banner );
        }

        if ( ! $desired_present ) {
            return $banner;
        }

        if ( in_array( $mode, [ 'manual_only', 'sync_failed', 'repair_pending', 'repair_required', 'removal_pending', 'removal_failed', 'removal_required' ], true ) ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = $server === 'nginx' ? 'Review Nginx Hardening Setup' : 'Review Server Hardening Setup';
            $banner['message']  = $message;
            $banner['action']   = 'open_manual_rules';
            $banner['action_label'] = 'Open Manual Rules';
            return self::attach_server_hardening_troubleshooting( $banner );
        }

        if ( ! $persisted ) {
            return $banner;
        }

        $verification_status = is_string( $verification['status'] ?? null ) ? (string) $verification['status'] : 'pending';

        if ( $verification_status === 'failed' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = $server === 'nginx' ? 'Nginx Hardening Verification Failed' : 'Server Hardening Verification Failed';
            $banner['message']  = $server === 'nginx'
                ? sprintf(
                    'The Nginx hardening rules were applied to %s, but SecurWP could not verify that this include is active on the live server. Include this file in your active server block, reload Nginx, then run verification again.',
                    $path !== '' ? $path : 'the configured Nginx include'
                )
                : 'Server hardening rules were applied, but verification detected that protected resources are still accessible. Apply the generated rules to your active virtual host configuration, reload the server, then run verification again.';

            if ( $server === 'nginx' ) {
                $banner['action']       = 'verify';
                $banner['action_label'] = 'Verify Activation';
            } else {
                $banner['action']       = 'open_manual_rules';
                $banner['action_label'] = 'Open Manual Rules';
            }

            return self::attach_server_hardening_troubleshooting( $banner );
        }

        if ( $verification_status === 'partial' ) {
            return $banner;
        }

        if ( $server === 'nginx' && $verification_status === 'pending' && $path !== '' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = 'Complete Nginx Hardening Setup';
            $banner['message']  = sprintf(
                'Nginx hardening rules were applied to %s. Include this file in your active server block, then reload Nginx to activate the protections.',
                $path
            );
            $banner['action']       = 'verify';
            $banner['action_label'] = 'Verify Activation';
            return self::attach_server_hardening_troubleshooting( $banner );
        }

        if ( $verification_status === 'unsupported' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = $server === 'nginx' ? 'Review Nginx Hardening Setup' : 'Review Server Hardening Setup';

            if ( $server === 'nginx' && $path !== '' ) {
                $banner['message'] = sprintf(
                    'Nginx hardening rules were applied to %s, but SecurWP could not determine a compatible automatic activation check on this host. Review the active include path, reload Nginx, and confirm the protections from your live server configuration.',
                    $path
                );
            } else {
                $banner['message'] = is_string( $verification['message'] ?? null ) && trim( (string) $verification['message'] ) !== ''
                    ? trim( (string) $verification['message'] )
                    : 'SecurWP could not complete server hardening verification for this host. Review the generated rules and confirm they are loaded by the active server configuration.';
            }

            if ( $server !== 'nginx' ) {
                $banner['action']       = 'open_manual_rules';
                $banner['action_label'] = 'Open Manual Rules';
            }

            return self::attach_server_hardening_troubleshooting( $banner );
        }

        if ( $verification_status === 'unavailable' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = $server === 'nginx' ? 'Nginx Hardening Verification Unavailable' : 'Server Hardening Verification Unavailable';
            $banner['message']  = $server === 'nginx'
                ? sprintf(
                    'Automatic verification could not be completed. Review %s and ensure it is included in your active Nginx server block. Reload Nginx, then run verification again.',
                    $path !== '' ? $path : 'the configured Nginx include'
                )
                : 'Automatic verification could not be completed. Review your active server configuration, apply the generated rules if needed, then run verification again.';

            if ( $server === 'nginx' ) {
                $banner['action']       = 'verify';
                $banner['action_label'] = 'Verify Activation';
            } else {
                $banner['action']       = 'open_manual_rules';
                $banner['action_label'] = 'Open Manual Rules';
            }

            return self::attach_server_hardening_troubleshooting( $banner );
        }

        return $banner;
    }

    /**
     * Attach the shared troubleshooting guide to visible server hardening banners.
     *
     * @param array<string,mixed> $banner
     * @return array<string,mixed>
     */
    private static function attach_server_hardening_troubleshooting( array $banner ): array {
        if ( empty( $banner['show'] ) ) {
            return $banner;
        }

        $banner['documentation_url']   = self::SERVER_HARDENING_TROUBLESHOOTING_URL;
        $banner['documentation_label'] = self::SERVER_HARDENING_TROUBLESHOOTING_LABEL;

        return $banner;
    }

    /**
     * Build an operator-facing failure message from the first failing check.
     *
     * @param array<string,mixed> $failed_check
     */
    private static function build_failed_verification_message( string $server, array $failed_check ): string {
        $target_label = is_string( $failed_check['target_label'] ?? null ) ? (string) $failed_check['target_label'] : 'the protected resource';

        if ( $server === 'nginx' ) {
            return sprintf(
                'Verification could not confirm the SecurWP Nginx activation probe for %s. Ensure the managed include is loaded by the active server block, reload Nginx, then run verification again.',
                $target_label
            );
        }

        return sprintf(
            'Verification detected that %s is still accessible. Apply the generated rules to your active server configuration, reload the server, then run verification again.',
            $target_label
        );
    }

    /**
     * Build the operator-facing message for unsupported or undetected hosts.
     */
    private static function build_unknown_server_message( string $detection_status, string $server_software, bool $managed_present = false, string $inspection_path = '' ): string {
        if ( $detection_status === 'detected_unsupported' ) {
            $label = trim( $server_software ) !== '' ? trim( $server_software ) : 'this server environment';
            $message = sprintf(
                'SecurWP identified %s for this site. Server-level hardening supports Apache, LiteSpeed, and Nginx only, so these protections will not take effect on this host.',
                $label
            );
            if ( $managed_present && $inspection_path !== '' ) {
                $message .= sprintf(
                    ' SecurWP also found an existing managed rule block in %s. Review that file manually if this host was previously configured for a supported server.',
                    $inspection_path
                );
            }
            return $message;
        }

        $message = 'SecurWP could not determine the active web server for this site. Select the server type in Settings > Advanced before relying on server-level hardening protections.';
        if ( $managed_present && $inspection_path !== '' ) {
            $message .= sprintf(
                ' SecurWP found an existing managed rule block in %s, but it will not update or remove that file until the server type is confirmed.',
                $inspection_path
            );
        }

        return $message;
    }

    /**
     * Build a message when automatic verification is not available for the
     * current server-rule set on this host.
     *
     * @param array<int,string> $unchecked_controls
     */
    private static function build_unsupported_verification_message( array $unchecked_controls ): string {
        $detection = self::get_server_detection_context( self::detect_server() );
        $status    = (string) ( $detection['status'] ?? 'undetected' );
        $software  = trim( (string) ( $detection['server_software'] ?? '' ) );

        if ( $status === 'detected_unsupported' ) {
            $label   = $software !== '' ? $software : 'this server environment';
            $message = sprintf(
                'Automatic verification is not available because SecurWP identified %s for this site. Server-level hardening supports Apache, LiteSpeed, and Nginx only, so these protections will not take effect on this host.',
                $label
            );
        } else {
            $message = 'Automatic verification is not available because SecurWP could not determine the active web server for this site. Select the server type in Settings > Advanced before relying on server-level hardening protections.';
        }

        if ( ! empty( $unchecked_controls ) ) {
            $message .= ' Some enabled protections cannot be verified automatically on this host.';
        }

        return $message;
    }

    /**
     * Build a message when loopback verification could not complete cleanly.
     *
     * @param array<int,array<string,mixed>> $errored_checks
     * @param array<int,string> $unchecked_controls
     */
    private static function build_unavailable_verification_message( array $errored_checks, array $unchecked_controls ): string {
        $base = 'Automatic verification could not be completed for this host. Review the hardening configuration and confirm that the generated rules are applied to your active server configuration.';
        if ( ! empty( $unchecked_controls ) ) {
            return $base . ' Additionally, some enabled protections cannot be verified automatically on this host.';
        }

        return $base;
    }

    /**
     * Build an absolute same-origin URL for a public request path.
     *
     * @param array<string,string> $query_args
     */
    private static function build_absolute_site_url( string $public_path, array $query_args = [] ): string {
        $home = wp_parse_url( home_url( '/' ) );
        if ( ! is_array( $home ) || empty( $home['host'] ) ) {
            return '';
        }

        $scheme = is_string( $home['scheme'] ?? null ) ? (string) $home['scheme'] : ( is_ssl() ? 'https' : 'http' );
        $host   = (string) $home['host'];
        $port   = isset( $home['port'] ) ? ':' . (int) $home['port'] : '';
        $path   = SitePathResolver::normalize_request_path( $public_path, false );
        $url    = $scheme . '://' . $host . $port . $path;

        if ( ! empty( $query_args ) ) {
            $url = add_query_arg( $query_args, $url );
        }

        return $url;
    }

    /**
     * Build signed headers for an internal server-rule verification request.
     *
     * These headers let SecurWP distinguish its own loopback probes from normal
     * hostile requests without exposing any reusable token in persisted state.
     *
     * @return array<string,string>
     */
    private static function build_probe_auth_headers( string $method, string $target_url ): array {
        try {
            $nonce = bin2hex( random_bytes( 16 ) );
        } catch ( \Throwable $e ) {
            return [];
        }

        $timestamp = time();
        $path      = (string) ( wp_parse_url( $target_url, PHP_URL_PATH ) ?: '/' );
        $query     = (string) ( wp_parse_url( $target_url, PHP_URL_QUERY ) ?: '' );
        $signature = SecurWPKeyManager::build_server_verification_signature( $method, $path, $query, $nonce, $timestamp );

        if ( $signature === '' ) {
            return [];
        }

        return [
            'X-SecurWP-Verify-Version'   => '1',
            'X-SecurWP-Verify-Timestamp' => (string) $timestamp,
            'X-SecurWP-Verify-Nonce'     => $nonce,
            'X-SecurWP-Verify-Signature' => $signature,
        ];
    }

    /**
     * Read a file with a stream fallback for constrained hosts.
     *
     * @return string|false
     */
    private static function read_file_contents( string $file ) {
        $filesystem = self::get_filesystem();
        if ( ! $filesystem ) {
            return false;
        }

        $contents = $filesystem->get_contents( $file );
        if ( ! is_string( $contents ) ) {
            return false;
        }

        return $contents;
    }

    /**
     * Write bytes to disk using file_put_contents first, then a locked stream.
     *
     * @return true|string
     */
    private static function write_bytes_to_file( string $file, string $contents ) {
        $filesystem = self::get_filesystem();
        if ( ! $filesystem ) {
            return 'filesystem_unavailable';
        }

        $mode = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644;

        return $filesystem->put_contents( $file, $contents, $mode )
            ? true
            : 'write_failed';
    }

    /**
     * Temporarily add owner-write permission to an existing file when needed.
     *
     * @return true|string
     */
    private static function with_temporary_file_permissions( string $file, callable $writer ) {
        $original_perms = is_file( $file ) ? @fileperms( $file ) : false;
        $changed        = false;

        if ( is_int( $original_perms ) && ! wp_is_writable( $file ) ) {
            $mode = $original_perms & 0777;
            if ( self::set_file_permissions( $file, $mode | 0200 ) ) {
                $changed = true;
            }
        }

        try {
            return $writer();
        } finally {
            if ( $changed && is_int( $original_perms ) ) {
                self::set_file_permissions( $file, $original_perms & 0777 );
            }
        }
    }

    /**
     * Return the initialized WordPress filesystem adapter.
     *
     * @return \WP_Filesystem_Base|null
     */
    private static function get_filesystem(): ?\WP_Filesystem_Base {
        global $wp_filesystem;

        if ( $wp_filesystem instanceof \WP_Filesystem_Base ) {
            return $wp_filesystem;
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        WP_Filesystem();

        return $wp_filesystem instanceof \WP_Filesystem_Base ? $wp_filesystem : null;
    }

    /**
     * Move a file using the active WordPress filesystem adapter.
     */
    private static function move_file( string $source, string $destination ): bool {
        $filesystem = self::get_filesystem();
        return $filesystem ? (bool) $filesystem->move( $source, $destination, true ) : false;
    }

    /**
     * Apply file permissions using the active WordPress filesystem adapter.
     */
    private static function set_file_permissions( string $file, int $permissions ): bool {
        $filesystem = self::get_filesystem();
        return $filesystem ? (bool) $filesystem->chmod( $file, $permissions ) : false;
    }

    /**
     * Verify that a just-written file matches the expected contents.
     */
    private static function verify_written_contents( string $file, string $expected ): bool {
        $read = self::read_file_contents( $file );
        return is_string( $read ) && $read === $expected;
    }

    /**
     * Acquire a short verification lock to avoid concurrent loopback probes.
     */
    private static function acquire_verification_lock(): bool {
        if ( get_transient( self::VERIFICATION_LOCK_TRANSIENT ) ) {
            return false;
        }

        set_transient( self::VERIFICATION_LOCK_TRANSIENT, 1, MINUTE_IN_SECONDS );
        return true;
    }

    /**
     * Release the verification lock.
     */
    private static function release_verification_lock(): void {
        delete_transient( self::VERIFICATION_LOCK_TRANSIENT );
    }
}
