<?php

declare(strict_types=1);

namespace SiteFort\Includes;

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

use SiteFort\Modules\Hardening\Hardening;

/**
 * Server configuration writer and state inspector.
 *
 * Supported targets:
 *   - Apache / LiteSpeed: .htaccess
 *   - Nginx: nginx.conf (path configurable in Settings > Advanced)
 */
class ServerConfigWriter {

    private const FORMAT_VERSION = 1;
    private const HASH_TRANSIENT = 'sitefort_server_rules_hash';
    private const LAST_SYNC_ERROR_TRANSIENT = 'sitefort_server_rules_last_error';
    private const VERIFICATION_STATE_KEY = 'server_rule_verification';
    private const VERIFICATION_LOCK_TRANSIENT = 'sitefort_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 = '/__sitefort_nginx_probe__';
    private const NGINX_ACTIVATION_PROBE_HEADER = 'X-SiteFort-Nginx';
    private const NGINX_ACTIVATION_PROBE_VALUE = 'active';
    private const NGINX_ACTIVATION_PROBE_BODY = 'sitefort-nginx-active';
    private const NGINX_ACTIVATION_PROBE_STATUS = 418;
    private const SERVER_HARDENING_TROUBLESHOOTING_URL = 'https://securewp.net/docs/troubleshooting/#server-hardening-troubleshooting';
    private const SERVER_HARDENING_TROUBLESHOOTING_LABEL = 'Troubleshooting Guide';
    private const WORDPRESS_BEGIN_MARKER = '# BEGIN WordPress';
    private const BEGIN_MARKER = '# BEGIN SiteFort - Do not modify or remove this line';
    private const END_MARKER = '# END SiteFort - Do not modify or remove this line';
    private static string $last_write_error_reason = '';

    private static function server_key_exists( string $key ): bool {
        return isset( $_SERVER[ $key ] ) && ! is_array( $_SERVER[ $key ] );
    }

    /**
     * Map the centrally-detected hosting platform onto the subset of
     * managed NGINX platforms this writer cares about.
     *
     * Delegates to HostingPlatform so detection rules stay aligned across
     * the WAF Manager and this server-rules writer.
     */
    private static function get_managed_nginx_platform(): string {
        switch ( HostingPlatform::detect() ) {
            case HostingPlatform::PLATFORM_WP_ENGINE: return 'wp_engine';
            case HostingPlatform::PLATFORM_PRESSABLE: return 'pressable';
            case HostingPlatform::PLATFORM_FLYWHEEL: return 'flywheel';
            default: return '';
        }
    }

    private static function is_managed_nginx_platform(): bool {
        return self::get_managed_nginx_platform() !== '';
    }

    /**
     * Detect the web server type.
     *
     * Priority order:
     *   1. Admin UI override (Settings > Advanced > Server Type)
     *   2. Managed-host aliases such as Flywheel
     *   3. $_SERVER['SERVER_SOFTWARE'] substring matching
     *   4. php_sapi_name() for LiteSpeed LSAPI
     *   5. apache_get_modules() for mod_php
     *   6. '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 ( strpos( $software, 'Flywheel/' ) === 0 ) {
            $detected = 'nginx';
        } elseif ( 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( 'sitefort_detected_server', $detected );
        }

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

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

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

    /**
     * Classify how the current server value was resolved for UX decisions.
     *
     * @return array{status:string,server_software:string,managed_platform: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' ) );
        $managed_platform = self::get_managed_nginx_platform();

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

        if ( $server === 'nginx' && $managed_platform !== '' ) {
            return [
                'status'           => 'platform_managed',
                'server_software'  => $software,
                'managed_platform' => $managed_platform,
            ];
        }

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

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

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

    /**
     * Whether SiteFort 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( 'sitefort_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     = self::get_server_target_issue( $server, $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 )
        );
    }

    /**
     * Build the verification identity for the active server target.
     */
    private static function build_verification_hash(): string {
        $server           = self::detect_server();
        $path             = self::get_config_file_path( $server );
        $target_issue     = self::get_server_target_issue( $server, $path );
        $nginx_conflicts  = [
            'detected'    => false,
            'fingerprint' => 'none',
            'message'     => '',
            'examples'    => [],
        ];

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

        return md5(
            'server=' . $server
            . ';path=' . $path
            . ';target_issue=' . $target_issue
            . ';nginx_conflicts=' . (string) ( $nginx_conflicts['fingerprint'] ?? 'none' )
        );
    }

    /**
     * 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 SiteFort's
     * desired managed block.
     *
     * This is intentionally rule-based rather than marker-based so SiteFort 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 SiteFort-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 SiteFort 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 SiteFort-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 [ '/__sitefort_probe__' ];
            }

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

        if ( str_contains( $matcher, '(log|lock)' ) ) {
            return [ '/sitefort-probe.log', '/sitefort-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, 'sitefort-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, 'sitefort-probe.php' ) ];
        }

        return [ self::append_probe_path_segment( $literal_prefix, '__sitefort_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(
            'SiteFort 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 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,
                '# SiteFort 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 SiteFort-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'] ?? '' );
        $managed_platform = (string) ( $detection['managed_platform'] ?? '' );
        $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    = self::get_server_target_issue( $server, $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.';

        $configured_nginx_path   = $server === 'nginx' ? self::get_configured_nginx_path_setting() : '';
        $custom_nginx_path_in_use = $configured_nginx_path !== '';
        $default_nginx_path      = $server === 'nginx' ? self::get_default_nginx_target_path() : '';

        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 ( $server === 'nginx' && $managed_platform !== '' && ! $custom_nginx_path_in_use && ( $desired_present || $managed_present ) ) {
            $mode    = 'manual_only';
            $message = self::build_managed_nginx_platform_message( $managed_platform, $inspection_path !== '' ? $inspection_path : $managed_target_path, $managed_present );
        } 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'] ?? 'SiteFort 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. SiteFort 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 SiteFort 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 );

        $state = [
            'mode'                  => $mode,
            'message'               => $message,
            'server'                => $server,
            'server_detection_status' => $detection_status,
            'server_software'       => $server_software,
            'managed_platform'      => $managed_platform,
            'path'                  => $inspection_path,
            'managed_target_path'   => $managed_target_path,
            'inspection_path'       => $inspection_path,
            'configured_target_path' => $configured_nginx_path,
            'default_target_path'   => $default_nginx_path,
            'custom_target_configured' => $custom_nginx_path_in_use,
            '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,
        ];

        $filtered_state = apply_filters( 'sitefort_server_rule_state', $state );

        return is_array( $filtered_state ) ? $filtered_state : $state;
    }

    /**
     * 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 ( $server === 'nginx' && self::is_managed_nginx_platform() ) {
            if ( $path !== '' ) {
                return $path;
            }

            $default_nginx_path = self::get_default_nginx_path();
            $issue              = self::get_managed_target_path_issue( $default_nginx_path );
            if ( $default_nginx_path !== '' && $issue === '' && file_exists( $default_nginx_path ) ) {
                return $default_nginx_path;
            }
        }

        if ( $path !== '' || $server !== 'unknown' ) {
            return $path;
        }

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

        return $apache_path;
    }

    /**
     * Write the 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 SiteFort 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 self::get_htaccess_path();
        }

        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 {
        $path = self::get_configured_nginx_path_setting();

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

            return '';
        }

        if ( self::is_managed_nginx_platform() ) {
            return '';
        }

        return self::get_default_nginx_path();
    }

    private static function get_default_nginx_path(): string {
        return self::validate_managed_target_path( ABSPATH . 'nginx.conf' ) ?: ABSPATH . 'nginx.conf';
    }

    public static function get_default_nginx_target_path(): string {
        return self::get_default_nginx_path();
    }

    public static function get_configured_nginx_path_setting(): string {
        $global = SettingsStore::get( SettingsStore::COL_GLOBAL );
        $path   = $global['nginx_file'] ?? '';

        return is_string( $path ) ? trim( $path ) : '';
    }

    public static function has_custom_nginx_path_setting(): bool {
        return self::get_configured_nginx_path_setting() !== '';
    }

    private static function get_htaccess_path(): string {
        return SitePathResolver::get_home_filesystem_path() . '.htaccess';
    }

    /**
     * Validate a managed server-config target path and return the normalized path
     * when it is safe for SiteFort 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 '';
    }

    private static function get_server_target_issue( string $server, string $path ): string {
        if ( $server === 'nginx' ) {
            $configured_nginx_path = self::get_configured_nginx_path_setting();
            if ( $configured_nginx_path !== '' ) {
                return self::get_managed_target_path_issue( $configured_nginx_path );
            }

            if ( self::is_managed_nginx_platform() ) {
                return 'platform_managed_nginx';
            }
        }

        return $path !== '' ? self::get_managed_target_path_issue( $path ) : 'no_target_path';
    }

    /**
     * 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 SiteFort-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 SiteFort may manage server-config files under.
     *
     * @return array<int,string>
     */
    private static function get_managed_root_paths(): array {
        $roots = [];

        foreach ( [ SitePathResolver::get_home_filesystem_path(), 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( 'sitefort_server_rules_apache', '' );
    }

    /**
     * Aggregate Nginx rules from all modules.
     */
    public static function get_nginx_rules(): string {
        $rules        = (string) apply_filters( 'sitefort_server_rules_nginx', '' );
        $shared_rules = self::get_shared_nginx_rules();

        return $shared_rules === '' ? $rules : $rules . $shared_rules;
    }

    /**
     * Render Nginx rules that must have a single shared owner across modules.
     */
    private static function get_shared_nginx_rules(): string {
        return self::get_shared_nginx_user_ini_deny_rules();
    }

    /**
     * Protect per-directory PHP config files exactly once based on the union of
     * hardening and WAF state.
     */
    private static function get_shared_nginx_user_ini_deny_rules(): string {
        if ( self::detect_server() !== 'nginx' ) {
            return '';
        }

        $paths = self::get_shared_nginx_user_ini_public_paths();
        if ( $paths === [] ) {
            return '';
        }

        $lines = [
            '',
            '# Protect per-directory PHP config files',
        ];

        foreach ( $paths as $path ) {
            $lines[] = "location = {$path} {";
            $lines[] = '  deny all;';
            $lines[] = '}';
        }

        $lines[] = '';

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

    /**
     * Return the public per-directory PHP config paths that must be protected
     * for the current combined hardening and WAF state.
     *
     * @return array<int,string>
     */
    private static function get_shared_nginx_user_ini_public_paths(): array {
        $paths    = [];
        $settings = Hardening::get_saved_settings();

        if ( ! empty( $settings['block_sensitive_files'] ) ) {
            $paths[] = SitePathResolver::get_home_root_file_path( '.user.ini' );

            $active_filename = SitePathResolver::get_user_ini_filename();
            if ( $active_filename !== '' ) {
                $paths[] = SitePathResolver::get_home_root_file_path( $active_filename );
            }
        }

        if (
            class_exists( '\\SiteFort\\Modules\\Firewall\\ExtendedProtection\\Manager' )
            && \SiteFort\Modules\Firewall\ExtendedProtection\Manager::requires_nginx_user_ini_public_deny_rule()
        ) {
            $active_filename = SitePathResolver::get_user_ini_filename();
            if ( $active_filename !== '' ) {
                $paths[] = SitePathResolver::get_home_root_file_path( $active_filename );
            }
        }

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

    /**
     * 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_verification_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_verification_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 server target 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;
        }

        $sync_hash = self::build_verification_hash();
        $stored    = self::get_stored_verification_state();

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

        $state    = self::build_verification_payload(
            $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( 'sitefort_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 . ' SiteFort 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 . ' SiteFort 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 'platform_managed_nginx':
                return self::build_managed_nginx_platform_message( self::get_managed_nginx_platform() );
            case 'symlink_target_not_allowed':
                return 'SiteFort 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 SiteFort 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 SiteFort 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 'platform_managed_nginx':
                return self::build_managed_nginx_platform_message( self::get_managed_nginx_platform() );
            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 SiteFort 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 SiteFort 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 'SiteFort 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 server 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.';
        }
    }

    private static function build_managed_nginx_platform_message( string $platform, string $path = '', bool $managed_present = false ): string {
        switch ( $platform ) {
            case 'wp_engine':
                $platform_label = 'WP Engine';
                break;
            case 'pressable':
                $platform_label = 'Pressable';
                break;
            case 'flywheel':
                $platform_label = 'Flywheel';
                break;
            default:
                $platform_label = 'this managed Nginx platform';
                break;
        }

        $prefix = $managed_present
            ? 'SiteFort generated the hardening rules, but they need to be applied manually.'
            : 'SiteFort cannot apply hardening rules automatically on this host.';

        if ( $path !== '' ) {
            return sprintf(
                '%s %s manages Nginx outside this site\'s filesystem. Open the manual rules below and add them through your host\'s Nginx include or control panel rather than relying on %s.',
                $prefix,
                $platform_label,
                $path
            );
        }

        return sprintf(
            '%s %s manages Nginx outside this site\'s filesystem. Open the manual rules below and add them through your host\'s Nginx include or control panel.',
            $prefix,
            $platform_label
        );
    }

    /**
     * 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 SiteFort-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 SiteFort 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 SiteFort 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 SiteFort 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, '# SiteFort 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 SiteFort-managed versioned blocks.
     */
    private static function get_managed_block_pattern(): string {
        $current_begin = preg_quote( self::BEGIN_MARKER, '/' );
        $current_end   = preg_quote( self::END_MARKER, '/' );

        return '/^[ \t]*' . $current_begin . '[ \t]*\R.*?^[ \t]*' . $current_end . '[ \t]*(?:\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, 'sitefort' );
            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 SiteFort may manage.
     *
     * @return array<int,string>
     */
    private static function get_possible_config_paths(): array {
        $paths = [
            self::get_htaccess_path(),
            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' => 'SiteFort 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 'SiteFort verified that the managed .htaccess rules are present in the active site configuration.';
        }

        return 'SiteFort 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',
            'SiteFort 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'] : 'SiteFort 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,
            [ 'sitefort_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'] : 'SiteFort 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',
                'SiteFort 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',
            'SiteFort 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'      => 'SiteFort 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'          => 'SiteFort/' . ( defined( 'SITEFORT_VERSION' ) ? SITEFORT_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( 'sitefort_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 verification 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;
    }

    /**
     * Preserve a successful activation result when only managed rule contents changed.
     *
     * @param array<string,mixed> $stored
     */
    private static function should_preserve_verified_activation_state( array $stored, string $sync_hash ): bool {
        if ( ! self::is_current_verification_state( $stored, $sync_hash ) ) {
            return false;
        }

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

        return in_array( $status, [ 'passed', 'partial' ], true ) && ! empty( $stored['supported'] );
    }

    /**
     * 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'  => '',
            'secondary_action' => '',
            'secondary_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';
                $banner['title']  = 'Server hardening not supported on this host';
                $banner['message'] = sprintf(
                    'Server-level hardening is available on Apache, LiteSpeed, and Nginx. This site is running on %s, so these protections will not be applied.',
                    $software_label
                );
                return self::attach_server_hardening_troubleshooting( $banner );
            }

            $banner['title']        = 'Select your web server to enable server hardening';
            $banner['message']      = 'The web server type for this site could not be detected automatically. Set it under Settings > Advanced to activate 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']    = 'Action needed to finish server hardening';
            $banner['message']  = $server === 'nginx'
                ? self::append_nginx_custom_path_guidance( $message )
                : $message;
            $banner['action']   = 'open_manual_rules';
            $banner['action_label'] = 'Open Manual Rules';
            return self::attach_server_hardening_troubleshooting( self::attach_nginx_custom_path_action( $banner, $server ) );
        }

        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 hardening rules are not yet active';
            $banner['message']  = $server === 'nginx'
                ? sprintf(
                    'The hardening rules are written to %s, but Nginx is not loading them. Include this file in your active server block and reload Nginx to apply the protections.%s',
                    $path !== '' ? $path : 'the configured Nginx config file'
                    ,
                    self::get_nginx_custom_path_guidance_suffix()
                )
                : 'The hardening rules have been generated, but the live server configuration is not enforcing them. Add the generated rules to your virtual host and reload the server to apply the protections.';

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

            return self::attach_server_hardening_troubleshooting( self::attach_nginx_custom_path_action( $banner, $server ) );
        }

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

        if ( $server === 'nginx' && $verification_status === 'pending' && $path !== '' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = 'One step left to activate server hardening';
            $banner['message']  = sprintf(
                'The hardening rules are ready at %s. Include this file in your Nginx server block and reload Nginx to apply the protections.%s',
                $path,
                self::get_nginx_custom_path_guidance_suffix()
            );
            $banner['action']       = 'open_manual_rules';
            $banner['action_label'] = 'Open Manual Rules';
            $banner['tertiary_action']       = 'verify';
            $banner['tertiary_action_label'] = 'Verify Activation';
            return self::attach_server_hardening_troubleshooting( self::attach_nginx_custom_path_action( $banner, $server ) );
        }

        if ( $verification_status === 'unsupported' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = 'Confirm server hardening is active';

            if ( $server === 'nginx' && $path !== '' ) {
                $banner['message'] = sprintf(
                    'The hardening rules are written to %s. This host does not support automatic verification, so confirm the file is included in your Nginx server block and reload Nginx to apply the protections.%s',
                    $path,
                    self::get_nginx_custom_path_guidance_suffix()
                );
            } else {
                $banner['message'] = is_string( $verification['message'] ?? null ) && trim( (string) $verification['message'] ) !== ''
                    ? trim( (string) $verification['message'] )
                    : 'This host does not support automatic verification. Confirm the generated rules are loaded by your active server configuration to keep the protections in place.';
            }

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

            return self::attach_server_hardening_troubleshooting( self::attach_nginx_custom_path_action( $banner, $server ) );
        }

        if ( $verification_status === 'unavailable' ) {
            $banner['show']     = true;
            $banner['severity'] = 'warning';
            $banner['title']    = 'Server hardening could not be verified';
            $banner['message']  = $server === 'nginx'
                ? sprintf(
                    'Verification could not complete on this host. Confirm %s is included in your Nginx server block, reload Nginx, and run verification again.%s',
                    $path !== '' ? $path : 'the configured Nginx config file'
                    ,
                    self::get_nginx_custom_path_guidance_suffix()
                )
                : 'Verification could not complete on this host. Confirm the generated rules are loaded by your server configuration, reload the server, and run verification again.';

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

            return self::attach_server_hardening_troubleshooting( self::attach_nginx_custom_path_action( $banner, $server ) );
        }

        return $banner;
    }

    /**
     * Add the Nginx custom path CTA to banners that already have a primary action.
     *
     * @param array<string,mixed> $banner
     * @return array<string,mixed>
     */
    private static function attach_nginx_custom_path_action( array $banner, string $server ): array {
        if ( $server !== 'nginx' ) {
            return $banner;
        }

        if ( ! empty( $banner['action'] ) && $banner['action'] === 'open_advanced_server_settings' ) {
            return $banner;
        }

        $action_key = empty( $banner['secondary_action'] ) ? 'secondary_action' : 'tertiary_action';
        $label_key  = $action_key === 'secondary_action' ? 'secondary_action_label' : 'tertiary_action_label';

        $banner[ $action_key ] = 'open_advanced_server_settings';
        $banner[ $label_key ]  = self::has_custom_nginx_path_setting() ? 'Review Config Path' : 'Change Config Path';

        return $banner;
    }

    /**
     * Append a short Nginx custom-path hint without duplicating existing guidance.
     */
    private static function append_nginx_custom_path_guidance( string $message ): string {
        if ( strpos( $message, 'Settings > Advanced' ) !== false || strpos( $message, 'Nginx Config File' ) !== false ) {
            return $message;
        }

        return $message . self::get_nginx_custom_path_guidance_suffix();
    }

    /**
     * Guidance appended to Nginx banners when a different include path may be needed.
     */
    private static function get_nginx_custom_path_guidance_suffix(): string {
        if ( self::has_custom_nginx_path_setting() ) {
            return ' If your server loads a different file, update the path under Settings > Advanced.';
        }

        return ' If your host loads a different config file, set its path under Settings > Advanced.';
    }

    /**
     * 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 SiteFort 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(
                'SiteFort 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(
                    ' SiteFort 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 = 'SiteFort 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(
                ' SiteFort 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 SiteFort 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 SiteFort 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 SiteFort 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 = SiteFortKeyManager::build_server_verification_signature( $method, $path, $query, $nonce, $timestamp );

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

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

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

    /**
     * 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 ) {
        $mode = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644;

        return FilesystemMutation::write_bytes_to_file( $file, $contents, (int) $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 );
            }
        }
    }

    /**
     * Move a file using the active WordPress filesystem adapter.
     */
    private static function move_file( string $source, string $destination ): bool {
        return FilesystemMutation::move_path( $source, $destination );
    }

    /**
     * Apply file permissions using the active WordPress filesystem adapter.
     */
    private static function set_file_permissions( string $file, int $permissions ): bool {
        return FilesystemMutation::apply_file_permissions( $file, $permissions );
    }

    /**
     * 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 );
    }
}
