<?php

declare(strict_types=1);

namespace ShieldX\Security;

defined('ABSPATH') || exit;

use ShieldX\Storage\FilesystemAdapter;
use ShieldX\Support\RequestPath;
use ShieldX\Support\WordPressPaths;

// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_is_writable,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.NonceVerification.Recommended -- Hardening inspects request context for runtime header/admin protection and reports local wp-config.php writability as a compatibility signal.

final class Hardening
{
    private const FILE_EDIT_MARKER_START = '/* BEGIN SHIELD-X DISALLOW_FILE_EDIT */';
    private const FILE_EDIT_MARKER_END = '/* END SHIELD-X DISALLOW_FILE_EDIT */';
    private const ROOT_RULES_MARKER = 'SHIELD-X HARDENING ROOT RULES';
    private const DATA_NO_EXEC_MARKER = 'SHIELD-X HARDENING NO EXEC';
    private const HTACCESS_PREFLIGHT_OPTION = 'shieldx_hardening_htaccess_preflight';
    private const HTACCESS_PREFLIGHT_FORMAT_VERSION = 8;
    private const HTACCESS_SANDBOX_PREFIX = 'shieldx-config-check-';
    private const HTACCESS_SANDBOX_INDEX = 'index.php';
    private const HTACCESS_SANDBOX_BACKUP = 'htaccess.bak';
    private const HTACCESS_BROWSER_JOB_PREFIX = 'shieldx_hardening_browser_probe_';
    private const HTACCESS_BROWSER_JOB_TTL_SECONDS = 300;
    private const HTACCESS_BROWSER_JOB_RETENTION_SECONDS = 3600;
    private const HTACCESS_BROWSER_CLEANUP_HOOK = 'shieldx_cleanup_htaccess_browser_job';
    private const EXECUTABLE_PATTERN = '~\.(?:php|phar|phtml|php[0-9]?|pht|phps|cgi|pl|py|asp|aspx|sh|exe)(?:$|[?#])~i';
    private const BOT_USER_AGENT_PATTERN = '~(?:acunetix|ahrefsbot|blexbot|curl|dirbuster|gobuster|go-http-client|hydra|libwww-perl|masscan|nikto|nmap|python-requests|sqlmap|wpscan|wget|zgrab)~i';
    private const RECOMMENDED_DISABLED_FUNCTIONS = [
        'exec',
        'passthru',
        'shell_exec',
        'system',
        'proc_open',
        'popen',
        'pcntl_exec',
        'dl',
    ];

    /** @var HardeningSettings */
    private $settings;

    public function __construct(HardeningSettings $settings)
    {
        $this->settings = $settings;
    }

    public function boot(): void
    {
        add_action(self::HTACCESS_BROWSER_CLEANUP_HOOK, [self::class, 'cleanupBrowserHtaccessPreflight'], 10, 1);

        if ($this->settings->disableFileEdit()) {
            self::defineFileEditConstant();
        }

        if ($this->settings->disableAdminScriptsConcatenation()) {
            self::disableAdminScriptConcatenationRuntime();
        }

        if ($this->settings->forceSslEnabled()) {
            add_action('init', [$this, 'enforceForceSsl'], -2000);
        }

        if ($this->settings->hasResponseHeaders()) {
            add_action('send_headers', [$this, 'sendSecurityHeaders'], PHP_INT_MAX);
        }

        if ($this->settings->genericLoginErrors()) {
            add_filter('login_errors', [$this, 'genericLoginErrorMessage'], 999);
            add_filter('lostpassword_errors', [$this, 'genericLostPasswordErrors'], 999, 2);
        }

        if ($this->settings->hideWordPressSignature()) {
            add_action('init', [$this, 'installSignatureHooks'], 1);
            add_action('send_headers', [$this, 'removeSignatureHeaders'], 999);
            add_filter('the_generator', '__return_empty_string', 999);
            add_filter('style_loader_src', [$this, 'removeWordPressVersionQuery'], 999, 1);
            add_filter('script_loader_src', [$this, 'removeWordPressVersionQuery'], 999, 1);
            add_filter('x_redirect_by', [$this, 'redirectByHeader'], 999);
        }

        if ($this->settings->blockXmlRpc()) {
            add_filter('xmlrpc_enabled', '__return_false', 999);
        }

        if ($this->settings->disablePingbacks()) {
            add_filter('xmlrpc_methods', [$this, 'removePingbackMethods'], 999);
            add_filter('pings_open', '__return_false', 999);
            add_filter('pre_option_default_ping_status', [$this, 'closedOption'], 999);
        }

        if ($this->hasRuntimeRequestHardening()) {
            add_action('init', [$this, 'enforceRequestHardening'], -1500);
        }

        if ($this->settings->adminGateEnabled()) {
            add_action('init', [$this, 'enforceAdminGate'], -1000);
        }
    }

    public static function defineFileEditConstant(): void
    {
        if (!defined('DISALLOW_FILE_EDIT')) {
            define('DISALLOW_FILE_EDIT', true);
        }
    }

    public static function disableAdminScriptConcatenationRuntime(): void
    {
        $GLOBALS['concatenate_scripts'] = false;
    }

    public function sendSecurityHeaders(): void
    {
        if (headers_sent()) {
            return;
        }

        foreach (self::responseHeaderValues($this->settings, $_SERVER) as $name => $value) {
            header($name . ': ' . $value, true);
        }

        if ($this->settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_HIDE_POWERED_BY)
            && function_exists('header_remove')) {
            header_remove('X-Powered-By');
        }
    }

    /**
     * @param array<string,mixed> $server
     * @return array<string,string>
     */
    private static function responseHeaderValues(HardeningSettings $settings, array $server): array
    {
        $headers = [];

        if ($settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_MIME_TYPE_PROTECTION)) {
            $headers['X-Content-Type-Options'] = 'nosniff';
        }

        if ($settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_FRAME_EMBEDDING_PROTECTION)) {
            $headers['X-Frame-Options'] = 'SAMEORIGIN';
            $headers['Content-Security-Policy'] = "frame-ancestors 'self'";
        }

        if ($settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_REFERRER_PRIVACY)) {
            $headers['Referrer-Policy'] = 'strict-origin-when-cross-origin';
        }

        if ($settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_CROSS_DOMAIN_POLICY)) {
            $headers['X-Permitted-Cross-Domain-Policies'] = 'none';
        }

        if ($settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_HSTS)
            && self::isSecureRequest($server)) {
            $headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains';
        }

        if ($settings->responseHeaderEnabled(HardeningSettings::RESPONSE_HEADER_PERMISSIONS_POLICY)) {
            $headers['Permissions-Policy'] = 'camera=(), display-capture=(), geolocation=(), microphone=(), payment=(), usb=()';
        }

        return $headers;
    }

    /**
     * @param array<string,string> $methods
     * @return array<string,string>
     */
    public function removePingbackMethods(array $methods): array
    {
        unset($methods['pingback.ping'], $methods['pingback.extensions.getPingbacks']);

        return $methods;
    }

    public function closedOption(): string
    {
        return 'closed';
    }

    /**
     * Keep login failures useful without revealing whether the username exists.
     *
     * @param string $error
     */
    public function genericLoginErrorMessage($error = ''): string
    {
        $codes = self::currentLoginErrorCodes();
        if (empty($codes)) {
            return (string) $error;
        }

        foreach (['invalid_username', 'incorrect_password', 'invalid_email'] as $enumerationCode) {
            if (in_array($enumerationCode, $codes, true)) {
                return __('Login failed. Check your username or email address and password, then try again.', 'xerotact-shield-x-security');
            }
        }

        return (string) $error;
    }

    /**
     * WordPress still needs an error object on failed reset attempts, but the
     * message should not disclose whether the submitted username/email exists.
     *
     * @param mixed $errors
     * @param mixed $userData
     * @return mixed
     */
    public function genericLostPasswordErrors($errors, $userData = null)
    {
        if (!is_object($errors) || !method_exists($errors, 'has_errors') || !$errors->has_errors()) {
            return $errors;
        }

        if (!class_exists('\WP_Error')) {
            return $errors;
        }

        return new \WP_Error(
            'shieldx_generic_password_reset',
            __('If an account matches the submitted identifier, WordPress will email reset instructions.', 'xerotact-shield-x-security')
        );
    }

    /**
     * @return array<int,string>
     */
    private static function currentLoginErrorCodes(): array
    {
        if (!isset($GLOBALS['errors']) || !is_object($GLOBALS['errors']) || !method_exists($GLOBALS['errors'], 'get_error_codes')) {
            return [];
        }

        $codes = $GLOBALS['errors']->get_error_codes();
        if (!is_array($codes)) {
            return [];
        }

        return array_values(array_filter(array_map('strval', $codes)));
    }

    /**
     * @return array{path:string,exists:bool,writable:bool,has_define:bool,configured:bool,managed:bool}
     */
    public static function wpConfigFileEditStatus(string $path = ''): array
    {
        $path = $path !== '' ? $path : self::wpConfigPath();
        $status = [
            'path' => $path,
            'exists' => is_file($path),
            'writable' => false,
            'has_define' => false,
            'configured' => false,
            'managed' => false,
        ];

        if (!$status['exists']) {
            return $status;
        }

        $status['writable'] = is_writable($path);
        $contents = file_get_contents($path);
        if (!is_string($contents)) {
            return $status;
        }

        $status['managed'] = strpos($contents, self::FILE_EDIT_MARKER_START) !== false
            && strpos($contents, self::FILE_EDIT_MARKER_END) !== false;

        $define = self::fileEditDefineInfo($contents);
        if (!empty($define['has_define'])) {
            $status['has_define'] = true;
            $status['configured'] = !empty($define['literal']) && !empty($define['configured']);
        }

        return $status;
    }

    /**
     * @return array{has_define:bool,count:int,literal:bool,configured:bool,statement:string,offset:int,length:int}
     */
    private static function fileEditDefineInfo(string $contents): array
    {
        $result = [
            'has_define' => false,
            'count' => 0,
            'literal' => false,
            'configured' => false,
            'statement' => '',
            'offset' => -1,
            'length' => 0,
        ];
        $scanContents = self::phpCodeWithCommentsMasked($contents);
        $definePattern = '/define\s*\(\s*([\'"])DISALLOW_FILE_EDIT\1\s*,/i';
        $defineCount = preg_match_all($definePattern, $scanContents, $defineMatches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
        if (!is_int($defineCount) || $defineCount < 1) {
            return $result;
        }

        $result['has_define'] = true;
        $result['count'] = $defineCount;
        $firstOffset = isset($defineMatches[0][0][1]) ? (int) $defineMatches[0][0][1] : -1;
        $literalPattern = '/define\s*\(\s*([\'"])DISALLOW_FILE_EDIT\1\s*,\s*(true|false|1|0)\s*\)\s*;/i';
        if (preg_match($literalPattern, $scanContents, $literalMatch, PREG_OFFSET_CAPTURE) !== 1) {
            return $result;
        }

        $literalOffset = isset($literalMatch[0][1]) ? (int) $literalMatch[0][1] : -1;
        if ($defineCount !== 1 || $literalOffset !== $firstOffset) {
            return $result;
        }

        $statement = isset($literalMatch[0][0]) ? (string) $literalMatch[0][0] : '';
        $value = isset($literalMatch[2][0]) ? strtolower((string) $literalMatch[2][0]) : '';
        $result['literal'] = $statement !== '';
        $result['configured'] = in_array($value, ['true', '1'], true);
        $result['statement'] = $statement;
        $result['offset'] = $literalOffset;
        $result['length'] = strlen($statement);

        return $result;
    }

    private static function phpCodeWithCommentsMasked(string $contents): string
    {
        if (!function_exists('token_get_all')) {
            $masked = (string) preg_replace_callback(
                '~/\*.*?\*/|//[^\r\n]*|#[^\r\n]*~s',
                static function (array $matches): string {
                    return (string) preg_replace('/[^\r\n]/', ' ', (string) ($matches[0] ?? ''));
                },
                $contents
            );

            return $masked;
        }

        $tokens = token_get_all($contents);
        $masked = '';
        foreach ($tokens as $token) {
            if (is_string($token)) {
                $masked .= $token;
                continue;
            }

            if (!is_array($token) || count($token) < 2) {
                continue;
            }

            $text = (string) $token[1];
            if (in_array((int) $token[0], [T_COMMENT, T_DOC_COMMENT, T_INLINE_HTML], true)) {
                $masked .= (string) preg_replace('/[^\r\n]/', ' ', $text);
                continue;
            }

            $masked .= $text;
        }

        return $masked;
    }

    /**
     * @return array{ok:bool,changed:bool,message:string,status:array<string,mixed>}
     */
    public static function syncFileEditConfig(bool $enabled, string $path = '', string $nonceAction = '', string $nonce = ''): array
    {
        return self::syncFileEditConfigWithAuthorization($enabled, $path, $nonceAction, $nonce, false);
    }

    /**
     * This recovery-only entry point is intentionally named rather than
     * accepting an empty admin nonce. Plugin::performEmergencyRecovery()
     * reaches it only after recovery-token and 2FA verification.
     *
     * @return array{ok:bool,changed:bool,message:string,status:array<string,mixed>}
     */
    public static function removeFileEditConfigForEmergencyRecovery(): array
    {
        return self::syncFileEditConfigWithAuthorization(false, '', '', '', true);
    }

    /**
     * @return array{ok:bool,changed:bool,message:string,status:array<string,mixed>}
     */
    private static function syncFileEditConfigWithAuthorization(bool $enabled, string $path, string $nonceAction, string $nonce, bool $trustedRecovery): array
    {
        $path = $path !== '' ? $path : self::wpConfigPath();
        $status = self::wpConfigFileEditStatus($path);
        $guardError = self::adminWriteGuard($nonceAction, $nonce, $trustedRecovery);
        if ($guardError !== '') {
            return [
                'ok' => false,
                'changed' => false,
                'message' => $guardError,
                'status' => $status,
            ];
        }

        if (empty($status['exists'])) {
            return [
                'ok' => false,
                'changed' => false,
                'message' => 'wp-config.php was not found.',
                'status' => $status,
            ];
        }

        $contents = file_get_contents($path);
        if (!is_string($contents)) {
            return [
                'ok' => false,
                'changed' => false,
                'message' => 'wp-config.php could not be read.',
                'status' => $status,
            ];
        }

        if ($enabled) {
            if (!empty($status['has_define']) && !empty($status['configured'])) {
                return [
                    'ok' => true,
                    'changed' => false,
                    'message' => 'DISALLOW_FILE_EDIT is already present in wp-config.php.',
                    'status' => $status,
                ];
            }

            if (!empty($status['has_define']) && empty($status['managed'])) {
                $define = self::fileEditDefineInfo($contents);
                $statement = (string) ($define['statement'] ?? '');
                if (
                    empty($define['literal'])
                    || !empty($define['configured'])
                    || (int) ($define['count'] ?? 0) !== 1
                    || $statement === ''
                    || strpos($statement, "\n") !== false
                    || strpos($statement, "\r") !== false
                ) {
                    return [
                        'ok' => false,
                        'changed' => false,
                        'message' => 'wp-config.php defines DISALLOW_FILE_EDIT in a form SHIELD-X cannot safely replace.',
                        'status' => $status,
                    ];
                }

                $offset = (int) ($define['offset'] ?? -1);
                $length = (int) ($define['length'] ?? 0);
                if ($offset < 0 || $length < 1) {
                    return [
                        'ok' => false,
                        'changed' => false,
                        'message' => 'wp-config.php defines DISALLOW_FILE_EDIT in a form SHIELD-X cannot safely replace.',
                        'status' => $status,
                    ];
                }

                $updated = substr($contents, 0, $offset)
                    . self::fileEditConfigBlock($statement)
                    . substr($contents, $offset + $length);
            } else {
                $restoreDefinition = !empty($status['managed'])
                    ? self::managedFileEditRestoreDefinition($contents)
                    : '';
                $updated = self::replaceManagedFileEditBlock($contents, self::fileEditConfigBlock($restoreDefinition));
                if ($updated === $contents) {
                    $updated = self::insertFileEditBlock($contents, self::fileEditConfigBlock());
                    if ($updated === null) {
                        return [
                            'ok' => false,
                            'changed' => false,
                            'message' => 'wp-config.php has no safe PHP insertion point for DISALLOW_FILE_EDIT.',
                            'status' => $status,
                        ];
                    }
                }
            }
        } else {
            $updated = self::replaceManagedFileEditBlock(
                $contents,
                self::managedFileEditRestoreDefinition($contents)
            );
            if ($updated === $contents) {
                return [
                    'ok' => true,
                    'changed' => false,
                    'message' => 'No SHIELD-X managed wp-config.php block was present.',
                    'status' => $status,
                ];
            }
        }

        if (!self::writePhpConfigFile($path, $updated, 0644)) {
            return [
                'ok' => false,
                'changed' => false,
                'message' => 'wp-config.php could not be safely written.',
                'status' => $status,
            ];
        }

        return [
            'ok' => true,
            'changed' => true,
            'message' => $enabled ? 'DISALLOW_FILE_EDIT was added to wp-config.php.' : 'SHIELD-X managed DISALLOW_FILE_EDIT block was removed from wp-config.php.',
            'status' => self::wpConfigFileEditStatus($path),
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function syncWebServerRules(HardeningSettings $settings, string $rootDir = '', string $contentDir = '', ?callable $rootPreflightRequester = null, string $nonceAction = '', string $nonce = ''): array
    {
        return self::syncWebServerRulesWithAuthorization($settings, $rootDir, $contentDir, $rootPreflightRequester, $nonceAction, $nonce, false);
    }

    /**
     * Creates short-lived public sandboxes that the authenticated admin browser
     * can verify when a CDN blocks the server-side loopback request.
     *
     * @return array<string,mixed>
     */
    public static function prepareBrowserHtaccessPreflight(HardeningSettings $settings, string $rootDir = '', string $nonceAction = '', string $nonce = ''): array
    {
        $guardError = self::adminWriteGuard($nonceAction, $nonce);
        if ($guardError !== '') {
            return [
                'ok' => false,
                'status' => 'unauthorized',
                'message' => $guardError,
                'job_id' => '',
                'probes' => [],
            ];
        }

        $rootDir = self::rootPath($rootDir);
        if ($rootDir === '' || !is_dir($rootDir)) {
            return [
                'ok' => false,
                'status' => 'root_unavailable',
                'message' => 'WordPress root path is not available for the browser .htaccess test.',
                'job_id' => '',
                'probes' => [],
            ];
        }

        if (!self::htaccessRulesSupported()) {
            return [
                'ok' => true,
                'status' => 'not_supported',
                'message' => 'This web server does not appear to honor Apache .htaccess rules.',
                'job_id' => '',
                'probes' => [],
            ];
        }

        self::sweepStaleSandboxes($rootDir);
        $bodies = [];
        $rootBody = self::rootHtaccessBody($settings);
        if (trim($rootBody) !== '') {
            $bodies['root'] = $rootBody;
        }
        if ($settings->dataPhpExecutionProtection()) {
            $bodies['data'] = self::sandboxNoExecHtaccessBody();
        }

        if ($bodies === []) {
            return [
                'ok' => true,
                'status' => 'not_needed',
                'message' => 'No .htaccess browser test is required for the selected settings.',
                'job_id' => '',
                'probes' => [],
            ];
        }

        $sandboxes = [];
        $publicProbes = [];
        foreach ($bodies as $label => $body) {
            $sandbox = self::createHtaccessSandbox($rootDir, $body);
            if (empty($sandbox['ok'])) {
                self::cleanupBrowserSandboxes($sandboxes, $rootDir);

                $failure = [
                    'ok' => false,
                    'status' => (string) ($sandbox['status'] ?? 'sandbox_failed'),
                    'message' => (string) ($sandbox['message'] ?? 'SHIELD-X could not prepare the browser .htaccess test.'),
                    'url' => '',
                    'http_code' => 0,
                    'server' => '',
                    'cf_ray' => '',
                    'cf_cache_status' => '',
                    'cf_mitigated' => '',
                    'content_type' => '',
                    'checked_at' => gmdate('Y-m-d H:i:s'),
                    'expires_at' => '',
                    'job_id' => '',
                    'probes' => [],
                ];
                self::saveLastHtaccessPreflight($failure);

                return $failure;
            }

            $sandboxes[] = [
                'label' => (string) $label,
                'body' => (string) $body,
                'path' => (string) $sandbox['path'],
                'directory_method' => (string) ($sandbox['directory_method'] ?? 'wordpress'),
            ];
            $publicProbes[] = [
                'label' => (string) $label,
                'url' => (string) $sandbox['url'],
                'token' => (string) $sandbox['token'],
                'filesystem_status' => (string) ($sandbox['filesystem_status'] ?? 'created_verified'),
                'directory_method' => (string) ($sandbox['directory_method'] ?? 'wordpress'),
            ];
        }

        try {
            $jobId = 'hp-' . bin2hex(random_bytes(16));
        } catch (\Throwable $error) {
            self::cleanupBrowserSandboxes($sandboxes, $rootDir);

            return [
                'ok' => false,
                'status' => 'job_random_failed',
                'message' => 'SHIELD-X could not create a secure browser preflight job.',
                'job_id' => '',
                'probes' => [],
            ];
        }

        $job = [
            'identity' => self::browserPreflightIdentity(),
            'fingerprint' => self::htaccessSettingsFingerprint($settings),
            'root' => $rootDir,
            'sandboxes' => $sandboxes,
            'probes' => $publicProbes,
            'expires' => time() + self::HTACCESS_BROWSER_JOB_TTL_SECONDS,
        ];
        $transientKey = self::browserPreflightTransientKey($jobId);
        if (
            $transientKey === ''
            || !function_exists('set_transient')
            || !set_transient($transientKey, $job, self::HTACCESS_BROWSER_JOB_TTL_SECONDS)
        ) {
            self::cleanupBrowserSandboxes($sandboxes, $rootDir);

            return [
                'ok' => false,
                'status' => 'job_store_failed',
                'message' => 'SHIELD-X could not store the browser .htaccess test safely.',
                'job_id' => '',
                'probes' => [],
            ];
        }

        return [
            'ok' => true,
            'status' => 'browser_pending',
            'message' => 'Browser .htaccess verification is ready.',
            'job_id' => $jobId,
            'probes' => $publicProbes,
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function consumeBrowserHtaccessPreflight(string $jobId, HardeningSettings $settings, string $nonceAction = '', string $nonce = ''): array
    {
        $guardError = self::adminWriteGuard($nonceAction, $nonce);
        if ($guardError !== '') {
            return [
                'ok' => false,
                'status' => 'unauthorized',
                'message' => $guardError,
            ];
        }

        $transientKey = self::browserPreflightTransientKey($jobId);
        $job = $transientKey !== '' && function_exists('get_transient') ? get_transient($transientKey) : false;
        if (!is_array($job)) {
            return [
                'ok' => false,
                'status' => 'browser_job_missing',
                'message' => 'The browser .htaccess verification expired. Run the test again.',
            ];
        }

        if (!hash_equals((string) ($job['identity'] ?? ''), self::browserPreflightIdentity())) {
            return [
                'ok' => false,
                'status' => 'browser_job_owner_mismatch',
                'message' => 'The browser .htaccess verification belongs to another administrator session.',
            ];
        }

        self::deleteBrowserPreflightJob($transientKey, $job);
        if (
            (int) ($job['expires'] ?? 0) < time()
            || !hash_equals((string) ($job['fingerprint'] ?? ''), self::htaccessSettingsFingerprint($settings))
        ) {
            return [
                'ok' => false,
                'status' => 'browser_job_mismatch',
                'message' => 'The selected hardening rules changed after the browser test. Run the test again.',
            ];
        }

        return [
            'ok' => true,
            'status' => 'browser_passed',
            'message' => 'The administrator browser confirmed the .htaccess sandbox token.',
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function cancelBrowserHtaccessPreflight(string $jobId, string $nonceAction = '', string $nonce = ''): array
    {
        $guardError = self::adminWriteGuard($nonceAction, $nonce);
        if ($guardError !== '') {
            return ['ok' => false, 'status' => 'unauthorized', 'message' => $guardError];
        }

        $transientKey = self::browserPreflightTransientKey($jobId);
        $job = $transientKey !== '' && function_exists('get_transient') ? get_transient($transientKey) : false;
        if (!is_array($job)) {
            return ['ok' => true, 'status' => 'already_removed', 'message' => 'Browser .htaccess verification was already removed.'];
        }
        if (!hash_equals((string) ($job['identity'] ?? ''), self::browserPreflightIdentity())) {
            return ['ok' => false, 'status' => 'browser_job_owner_mismatch', 'message' => 'The browser .htaccess verification belongs to another administrator session.'];
        }

        self::deleteBrowserPreflightJob($transientKey, $job);

        return ['ok' => true, 'status' => 'cancelled', 'message' => 'Browser .htaccess verification was removed.'];
    }

    /**
     * Persists the current browser failure before removing its short-lived
     * sandbox, so the administrator can review the URL and response evidence.
     *
     * @param array<string,mixed> $diagnostic
     * @return array<string,mixed>
     */
    public static function recordBrowserHtaccessPreflightFailure(string $jobId, array $diagnostic, string $nonceAction = '', string $nonce = ''): array
    {
        $guardError = self::adminWriteGuard($nonceAction, $nonce);
        if ($guardError !== '') {
            return ['ok' => false, 'status' => 'unauthorized', 'message' => $guardError];
        }

        $transientKey = self::browserPreflightTransientKey($jobId);
        $job = $transientKey !== '' && function_exists('get_transient') ? get_transient($transientKey) : false;
        if (!is_array($job)) {
            return ['ok' => false, 'status' => 'browser_job_missing', 'message' => 'The browser .htaccess verification expired before its diagnostic could be saved.'];
        }
        if (!hash_equals((string) ($job['identity'] ?? ''), self::browserPreflightIdentity())) {
            return ['ok' => false, 'status' => 'browser_job_owner_mismatch', 'message' => 'The browser .htaccess verification belongs to another administrator session.'];
        }
        if ((int) ($job['expires'] ?? 0) < time()) {
            self::deleteBrowserPreflightJob($transientKey, $job);

            return ['ok' => false, 'status' => 'browser_job_expired', 'message' => 'The browser .htaccess verification expired before its diagnostic could be saved.'];
        }

        $requestedLabel = isset($diagnostic['label']) && is_scalar($diagnostic['label'])
            ? strtolower((string) preg_replace('/[^a-z0-9_-]/i', '', self::sanitizeText((string) $diagnostic['label'])))
            : '';
        $selectedProbe = null;
        $selectedProbeIndex = null;
        $rootProbe = null;
        $rootProbeIndex = null;
        $firstProbe = null;
        $firstProbeIndex = null;
        foreach ((array) ($job['probes'] ?? []) as $probeIndex => $probe) {
            if (!is_array($probe)) {
                continue;
            }
            if ($firstProbe === null) {
                $firstProbe = $probe;
                $firstProbeIndex = $probeIndex;
            }

            $probeLabel = isset($probe['label']) && is_scalar($probe['label'])
                ? strtolower((string) preg_replace('/[^a-z0-9_-]/i', '', self::sanitizeText((string) $probe['label'])))
                : '';
            if ($probeLabel === 'root') {
                $rootProbe = $probe;
                $rootProbeIndex = $probeIndex;
            }
            if ($requestedLabel !== '' && hash_equals($probeLabel, $requestedLabel)) {
                $selectedProbe = $probe;
                $selectedProbeIndex = $probeIndex;
                break;
            }
        }
        if ($selectedProbe === null) {
            $selectedProbe = $rootProbe !== null ? $rootProbe : $firstProbe;
            $selectedProbeIndex = $rootProbe !== null ? $rootProbeIndex : $firstProbeIndex;
        }
        if (!is_array($selectedProbe) || empty($selectedProbe['url'])) {
            self::deleteBrowserPreflightJob($transientKey, $job);

            return ['ok' => false, 'status' => 'browser_probe_missing', 'message' => 'The browser .htaccess diagnostic did not match a prepared sandbox URL.'];
        }

        $httpCode = max(0, min(599, (int) ($diagnostic['http_code'] ?? 0)));
        $server = isset($diagnostic['server']) && is_scalar($diagnostic['server'])
            ? substr(self::sanitizeText((string) $diagnostic['server']), 0, 160)
            : '';
        $cfRay = isset($diagnostic['cf_ray']) && is_scalar($diagnostic['cf_ray'])
            ? substr(self::sanitizeText((string) $diagnostic['cf_ray']), 0, 160)
            : '';
        $cfCacheStatus = isset($diagnostic['cf_cache_status']) && is_scalar($diagnostic['cf_cache_status'])
            ? substr(self::sanitizeText((string) $diagnostic['cf_cache_status']), 0, 80)
            : '';
        $cfMitigated = isset($diagnostic['cf_mitigated']) && is_scalar($diagnostic['cf_mitigated'])
            ? substr(self::sanitizeText((string) $diagnostic['cf_mitigated']), 0, 80)
            : '';
        $contentType = isset($diagnostic['content_type']) && is_scalar($diagnostic['content_type'])
            ? substr(self::sanitizeText((string) $diagnostic['content_type']), 0, 160)
            : '';
        $cloudflareValue = $diagnostic['cloudflare'] ?? false;
        $cloudflare = is_bool($cloudflareValue)
            ? $cloudflareValue
            : (is_scalar($cloudflareValue) && in_array(strtolower((string) $cloudflareValue), ['1', 'true', 'yes', 'on'], true));
        if (
            $httpCode === 403
            && (string) ($selectedProbe['directory_method'] ?? 'wordpress') !== 'direct'
        ) {
            $retry = self::prepareDirectBrowserSandboxRetry(
                $job,
                (string) ($selectedProbe['label'] ?? ''),
                $selectedProbeIndex
            );
            if (!empty($retry['ok']) && function_exists('set_transient')) {
                $retryJob = (array) ($retry['job'] ?? []);
                $retryTtl = max(1, (int) ($retryJob['expires'] ?? time()) - time());
                $stored = set_transient($transientKey, $retryJob, $retryTtl);
                $storedJob = function_exists('get_transient') ? get_transient($transientKey) : false;
                if (
                    $stored
                    && is_array($storedJob)
                    && hash_equals((string) ($retryJob['identity'] ?? ''), (string) ($storedJob['identity'] ?? ''))
                    && (string) ($retry['new_path'] ?? '') !== ''
                    && (string) ($retry['new_path'] ?? '') === (string) (($storedJob['sandboxes'][(int) ($retry['sandbox_index'] ?? -1)]['path'] ?? ''))
                ) {
                    self::cleanupBrowserSandboxes(
                        [['path' => (string) ($retry['old_path'] ?? '')]],
                        (string) ($job['root'] ?? '')
                    );

                    return [
                        'ok' => true,
                        'status' => 'retry_ready',
                        'message' => 'The HTTP 403 sandbox was rebuilt with direct mkdir(0755) and is ready for one retry.',
                        'retry_probe' => (array) ($retry['probe'] ?? []),
                    ];
                }

                self::cleanupBrowserSandboxes(
                    [['path' => (string) ($retry['new_path'] ?? '')]],
                    (string) ($job['root'] ?? '')
                );
            }
        }
        $manualTestExpires = time() + self::HTACCESS_BROWSER_JOB_TTL_SECONDS;
        $job['expires'] = $manualTestExpires;
        if (!function_exists('set_transient')) {
            self::deleteBrowserPreflightJob($transientKey, $job);

            return [
                'ok' => false,
                'status' => 'browser_job_store_failed',
                'message' => 'SHIELD-X could not keep the browser .htaccess sandbox available for manual testing.',
            ];
        }
        set_transient($transientKey, $job, self::HTACCESS_BROWSER_JOB_RETENTION_SECONDS);
        $storedJob = function_exists('get_transient') ? get_transient($transientKey) : false;
        if (
            !is_array($storedJob)
            || (int) ($storedJob['expires'] ?? 0) !== $manualTestExpires
            || !hash_equals((string) ($job['identity'] ?? ''), (string) ($storedJob['identity'] ?? ''))
        ) {
            self::deleteBrowserPreflightJob($transientKey, $job);

            return [
                'ok' => false,
                'status' => 'browser_job_store_failed',
                'message' => 'SHIELD-X could not keep the browser .htaccess sandbox available for manual testing.',
            ];
        }
        self::scheduleBrowserPreflightCleanup($jobId, $manualTestExpires);

        $preflight = [
            'ok' => false,
            'status' => $cloudflare ? 'cdn_intercepted' : 'browser_probe_failed',
            'message' => $cloudflare
                ? 'The sandbox .htaccess file was created and verified on the host, but Cloudflare returned a challenge or block page before the token could be verified. Any host-created sandbox .htaccess was preserved as htaccess.bak.'
                : 'The sandbox .htaccess file was created and verified on the host, but the administrator browser could not verify the current token. Any host-created sandbox .htaccess was preserved as htaccess.bak.',
            'url' => (string) $selectedProbe['url'],
            'filesystem_status' => (string) ($selectedProbe['filesystem_status'] ?? 'created_verified'),
            'directory_method' => (string) ($selectedProbe['directory_method'] ?? 'wordpress'),
            'http_code' => $httpCode,
            'server' => $server,
            'cf_ray' => $cfRay,
            'cf_cache_status' => $cfCacheStatus,
            'cf_mitigated' => $cfMitigated,
            'content_type' => $contentType,
            'checked_at' => gmdate('Y-m-d H:i:s'),
            'expires_at' => gmdate('Y-m-d H:i:s', $manualTestExpires),
            'format_version' => self::HTACCESS_PREFLIGHT_FORMAT_VERSION,
        ];

        self::saveLastHtaccessPreflight($preflight);

        return [
            'ok' => true,
            'status' => 'recorded',
            'message' => 'The current browser .htaccess diagnostic was saved.',
            'preflight' => $preflight,
        ];
    }

    /**
     * This recovery-only entry point is intentionally named rather than
     * accepting an empty admin nonce. Plugin::performEmergencyRecovery()
     * reaches it only after recovery-token and 2FA verification.
     *
     * @return array<string,mixed>
     */
    public static function removeWebServerRulesForEmergencyRecovery(HardeningSettings $settings): array
    {
        return self::syncWebServerRulesWithAuthorization($settings, '', '', null, '', '', true);
    }

    /**
     * @return array<string,mixed>
     */
    private static function syncWebServerRulesWithAuthorization(HardeningSettings $settings, string $rootDir, string $contentDir, ?callable $rootPreflightRequester, string $nonceAction, string $nonce, bool $trustedRecovery): array
    {
        $rootDir = self::rootPath($rootDir);
        $contentDir = self::contentPath($contentDir, $rootDir);
        $result = [
            'ok' => true,
            'changed' => false,
            'root_path' => $rootDir !== '' ? $rootDir . '/.htaccess' : '',
            'root_status' => 'not_applied',
            'root_preflight' => [],
            'data_preflight' => [],
            'data_directories' => [],
            'messages' => [],
        ];
        $guardError = self::adminWriteGuard($nonceAction, $nonce, $trustedRecovery);
        if ($guardError !== '') {
            $result['ok'] = false;
            $result['messages'][] = $guardError;

            return $result;
        }

        if ($rootDir === '') {
            $result['ok'] = false;
            $result['messages'][] = 'WordPress root path is not available.';

            return $result;
        }

        if (!self::htaccessRulesSupported()) {
            $result['root_status'] = 'not_supported';
            $result['root_preflight'] = [
                'ok' => true,
                'status' => 'not_supported',
                'message' => 'This web server does not appear to honor Apache .htaccess rules; SHIELD-X runtime hardening remains active.',
                'url' => '',
                'http_code' => 0,
                'checked_at' => gmdate('Y-m-d H:i:s'),
            ];
            $result['messages'][] = (string) $result['root_preflight']['message'];

            foreach (self::dataNoExecDirectories($contentDir) as $directory) {
                $result['data_directories'][] = [
                    'path' => $directory,
                    'status' => 'not_supported',
                    'message' => 'Data-directory .htaccess stubs are not supported on this web server.',
                ];
            }

            return $result;
        }

        self::sweepStaleSandboxes($rootDir);
        $rootBody = self::rootHtaccessBody($settings);
        $rootPreflight = self::preflightRootHtaccess($rootDir, $rootBody, $rootPreflightRequester);
        $result['root_preflight'] = $rootPreflight;
        self::saveLastHtaccessPreflight($rootPreflight);

        if (empty($rootPreflight['ok'])) {
            $result['ok'] = false;
            $result['messages'][] = (string) ($rootPreflight['message'] ?? 'Root .htaccess sandbox preflight failed.');
        } else {
            $rootWrite = self::syncMarkedHtaccess($rootDir . '/.htaccess', self::ROOT_RULES_MARKER, $rootBody, false);
            if ((string) ($rootWrite['status'] ?? '') === 'write_failed') {
                $result['ok'] = false;
            }
            $result['root_status'] = (string) ($rootWrite['status'] ?? 'not_applied');
            $result['changed'] = !empty($rootWrite['changed']);
            $result['messages'][] = (string) $rootWrite['message'];
        }

        $dataPreflight = [
            'ok' => true,
            'status' => 'not_needed',
            'message' => 'Data-directory .htaccess preflight was not needed because no data-directory stubs are enabled.',
            'url' => '',
            'http_code' => 0,
            'checked_at' => gmdate('Y-m-d H:i:s'),
        ];
        if ($settings->dataPhpExecutionProtection()) {
            $dataPreflight = self::preflightRootHtaccess($rootDir, self::sandboxNoExecHtaccessBody(), $rootPreflightRequester);
            $dataPreflight['message'] = !empty($dataPreflight['ok'])
                ? 'Data-directory .htaccess sandbox preflight passed before applying no-exec stubs.'
                : (string) ($dataPreflight['message'] ?? 'Data-directory .htaccess sandbox preflight failed, so no-exec stubs were not applied.');
        }
        $result['data_preflight'] = $dataPreflight;

        if (empty($dataPreflight['ok'])) {
            $result['ok'] = false;
            $result['messages'][] = (string) ($dataPreflight['message'] ?? 'Data-directory .htaccess sandbox preflight failed.');

            foreach (self::dataNoExecDirectories($contentDir) as $directory) {
                $result['data_directories'][] = [
                    'path' => $directory,
                    'status' => 'preflight_failed',
                    'message' => 'No-exec stub was not written because the sandbox preflight failed.',
                ];
            }

            return $result;
        }

        foreach (self::dataNoExecDirectories($contentDir) as $directory) {
            $write = self::syncMarkedHtaccess(
                $directory . '/.htaccess',
                self::DATA_NO_EXEC_MARKER,
                $settings->dataPhpExecutionProtection() ? self::noExecHtaccessBody() : '',
                true
            );
            $result['data_directories'][] = [
                'path' => $directory,
                'status' => (string) $write['status'],
                'message' => (string) $write['message'],
            ];
            if ((string) ($write['status'] ?? '') === 'write_failed') {
                $result['ok'] = false;
            }
            $result['changed'] = !empty($result['changed']) || !empty($write['changed']);
            $result['messages'][] = (string) $write['message'];
        }

        return $result;
    }

    private static function adminWriteGuard(string $nonceAction, string $nonce = '', bool $trustedRecovery = false): string
    {
        if ($trustedRecovery) {
            return '';
        }

        if (defined('WP_CLI') && WP_CLI) {
            // WP-CLI has no browser nonce. Require a command-scoped confirmation and
            // an administrator context instead, so a direct wp eval stays opt-in.
            if (getenv('SHIELDX_CLI_CONFIRM') === 'yes') {
                if (!function_exists('current_user_can')) {
                    return 'Administrator permission could not be verified for hardening file changes.';
                }

                $capability = defined('SHIELDX_MANAGE_CAPABILITY') ? (string) SHIELDX_MANAGE_CAPABILITY : 'manage_options';

                return current_user_can($capability)
                    ? ''
                    : 'WP-CLI hardening file changes require an administrator context. Re-run with --user=<admin>.';
            }

            return 'WP-CLI hardening file changes require SHIELDX_CLI_CONFIRM=yes and an administrator-gated command.';
        }

        if (!function_exists('current_user_can')) {
            return 'Administrator permission could not be verified for hardening file changes.';
        }

        $capability = defined('SHIELDX_MANAGE_CAPABILITY') ? (string) SHIELDX_MANAGE_CAPABILITY : 'manage_options';
        if (!current_user_can($capability)) {
            return 'Administrator permission is required to change hardening files.';
        }

        if ($nonceAction === '') {
            return 'Missing security token context.';
        }

        if (!function_exists('wp_verify_nonce')) {
            return 'WordPress nonce verification is not available.';
        }

        if ($nonce === '') {
            $nonce = self::requestNonce();
        }

        return $nonce !== '' && wp_verify_nonce($nonce, $nonceAction) !== false
            ? ''
            : 'Security check failed. Refresh the page and try again.';
    }

    private static function requestNonce(): string
    {
        static $nonce = null;

        if ($nonce !== null) {
            return $nonce;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This helper only extracts a POSTed nonce; adminWriteGuard() verifies it immediately.
        $post = isset($_POST) && is_array($_POST) ? $_POST : [];
        $post = function_exists('wp_unslash') ? wp_unslash($post) : $post;

        foreach (['_wpnonce', '_ajax_nonce'] as $key) {
            if (isset($post[$key]) && is_scalar($post[$key])) {
                $nonce = self::sanitizeText((string) $post[$key]);

                return $nonce;
            }
        }

        return $nonce = '';
    }

    /**
     * @return array<string,mixed>
     */
    public static function webServerRuleStatus(HardeningSettings $settings, string $rootDir = '', string $contentDir = ''): array
    {
        $rootDir = self::rootPath($rootDir);
        $contentDir = self::contentPath($contentDir, $rootDir);
        $rootPath = $rootDir !== '' ? $rootDir . '/.htaccess' : '';
        $rootBody = self::rootHtaccessBody($settings);
        $dataDirs = [];

        if (!self::htaccessRulesSupported()) {
            foreach (self::dataNoExecDirectories($contentDir) as $directory) {
                $dataDirs[] = [
                    'path' => $directory,
                    'status' => 'not_supported',
                ];
            }

            return [
                'root_path' => $rootPath,
                'root_status' => 'not_supported',
                'root_preflight' => [
                    'ok' => true,
                    'status' => 'not_supported',
                    'message' => 'This web server does not appear to honor Apache .htaccess rules; SHIELD-X runtime hardening remains active.',
                    'url' => '',
                    'http_code' => 0,
                    'checked_at' => gmdate('Y-m-d H:i:s'),
                ],
                'data_preflight' => [],
                'data_directories' => $dataDirs,
            ];
        }

        foreach (self::dataNoExecDirectories($contentDir) as $directory) {
            $dataDirs[] = [
                'path' => $directory,
                'status' => self::markedBlockStatus($directory . '/.htaccess', self::DATA_NO_EXEC_MARKER, self::noExecHtaccessBody(), !$settings->dataPhpExecutionProtection()),
            ];
        }

        return [
            'root_path' => $rootPath,
            'root_status' => self::markedBlockStatus($rootPath, self::ROOT_RULES_MARKER, $rootBody, $rootBody === ''),
            'root_preflight' => self::lastHtaccessPreflight(),
            'data_preflight' => [],
            'data_directories' => $dataDirs,
        ];
    }

    /**
     * @return array<int,array<string,string>>
     */
    public static function securityMeasures(HardeningSettings $settings): array
    {
        $adminUsernameStatus = self::defaultAdminUsernameExists() ? 'warning' : 'ready';
        $dbPrefixStatus = self::usesDefaultDbPrefix() ? 'warning' : 'ready';
        $keysStatus = self::securityKeysConfigured() ? 'ready' : 'warning';

        return [
            self::measure('default_admin_username', 'Change default administrator username', $adminUsernameStatus, $adminUsernameStatus === 'ready' ? 'No admin login named "admin" detected.' : 'A login named "admin" exists. Rename through a guided user-management workflow.', 'Manual review'),
            self::measure('xmlrpc', 'Block access to xmlrpc.php', $settings->blockXmlRpc() ? 'ready' : 'warning', $settings->blockXmlRpc() ? 'Blocked by WordPress runtime and supported web-server rules.' : 'Enable XML-RPC blocking if the site does not need Jetpack, mobile apps, or remote publishing.', 'Reversible'),
            self::measure('wp_includes_php', 'Forbid execution of PHP scripts in wp-includes', $settings->directCoreAccessProtection() ? 'ready' : 'warning', $settings->directCoreAccessProtection() ? 'Direct wp-includes PHP requests are denied.' : 'Enable direct core access protection.', 'Reversible'),
            self::measure('uploads_php', 'Forbid execution of PHP scripts in wp-content/uploads', $settings->dataPhpExecutionProtection() ? 'ready' : 'warning', $settings->dataPhpExecutionProtection() ? 'Data-directory PHP execution deny stubs are managed.' : 'Enable data-directory PHP execution protection.', 'Reversible'),
            self::measure('admin_script_concat', 'Disable scripts concatenation for WordPress admin panel', $settings->disableAdminScriptsConcatenation() ? 'ready' : 'warning', $settings->disableAdminScriptsConcatenation() ? 'Admin script concatenation is disabled at runtime.' : 'Enable this if admin asset debugging or proxy compatibility needs it.', 'Reversible'),
            self::measure('pingbacks', 'Turn off pingbacks', $settings->disablePingbacks() ? 'ready' : 'warning', $settings->disablePingbacks() ? 'Pingback methods and open ping checks are disabled.' : 'Enable pingback protection to reduce XML-RPC abuse.', 'Reversible'),
            self::measure('file_editor', 'Disable file editing in WordPress Dashboard', $settings->disableFileEdit() ? 'ready' : 'warning', $settings->disableFileEdit() ? 'DISALLOW_FILE_EDIT is active or managed.' : 'Enable file editor protection.', 'Reversible'),
            self::measure('bot_protection', 'Enable bot protection', $settings->botProtectionEnabled() ? 'ready' : 'warning', $settings->botProtectionEnabled() ? 'Known scanner user agents are blocked on login, XML-RPC, direct-core, sensitive-file, and data-executable surfaces.' : 'Enable basic bad-bot user-agent blocking, then tune WAF allowlists if needed.', 'Reversible'),
            self::measure('potential_sensitive', 'Block access to potentially sensitive files', $settings->sensitiveFileProtection() ? 'ready' : 'warning', $settings->sensitiveFileProtection() ? 'Sensitive root and backup file patterns are denied.' : 'Enable sensitive file protection.', 'Reversible'),
            self::measure('htaccess_htpasswd', 'Block access to .htaccess and .htpasswd', $settings->sensitiveFileProtection() ? 'ready' : 'warning', $settings->sensitiveFileProtection() ? '.htaccess and .htpasswd are included in the deny list.' : 'Enable sensitive file protection.', 'Reversible'),
            self::measure('author_scans', 'Block author scans', $settings->blockAuthorScans() ? 'ready' : 'warning', $settings->blockAuthorScans() ? 'Numeric author discovery requests are denied.' : 'Enable author scan blocking to reduce username enumeration.', 'Reversible'),
            self::measure('direct_access', 'Restrict access to files and directories', ($settings->directCoreAccessProtection() && $settings->sensitiveFileProtection()) ? 'ready' : 'warning', 'Combines direct core access and sensitive-file rules.', 'Reversible'),
            self::measure('security_keys', 'Configure security keys', $keysStatus, $keysStatus === 'ready' ? 'WordPress salts/keys appear configured.' : 'Some WordPress salts/keys are missing or use placeholders.', 'Manual review'),
            self::measure('directory_browsing', 'Block directory browsing', $settings->directoryBrowsingProtection() ? 'ready' : 'warning', $settings->directoryBrowsingProtection() ? 'Options -Indexes is included in the managed root rule block when .htaccess is supported.' : 'Enable this only when the host allows directory-index controls.', 'Reversible'),
            self::measure('wp_config', 'Block access to wp-config.php', $settings->sensitiveFileProtection() ? 'ready' : 'warning', $settings->sensitiveFileProtection() ? 'wp-config.php is included in the sensitive file deny list.' : 'Enable sensitive file protection.', 'Reversible'),
            self::measure('cache_php', 'Disable PHP execution in cache directories', $settings->dataPhpExecutionProtection() ? 'ready' : 'warning', $settings->dataPhpExecutionProtection() ? 'Cache directories are included in no-exec stubs when present.' : 'Enable data-directory PHP execution protection.', 'Reversible'),
            self::measure('db_prefix', 'Change default database table prefix', $dbPrefixStatus, $dbPrefixStatus === 'ready' ? 'Database prefix is not the default wp_ prefix.' : 'The site appears to use the default wp_ prefix. Changing it requires a tested migration.', 'Manual migration'),
            self::measure('sensitive_files', 'Block access to sensitive files', $settings->sensitiveFileProtection() ? 'ready' : 'warning', $settings->sensitiveFileProtection() ? 'Common logs, backups, env files, and dumps are covered.' : 'Enable sensitive file protection.', 'Reversible'),
        ];
    }

    /**
     * @param array<int,string>|null $callableFunctions
     * @return array<string,mixed>
     */
    public static function phpDisabledFunctionsStatus(?string $rawDisabled = null, ?array $callableFunctions = null): array
    {
        $raw = $rawDisabled;
        if ($raw === null) {
            $raw = function_exists('ini_get') ? (string) ini_get('disable_functions') : '';
        }

        $disabled = self::normalizeFunctionList($raw);
        $recommended = self::recommendedDisabledFunctions();
        $missing = array_values(array_diff($recommended, $disabled));
        $callable = $callableFunctions === null
            ? self::callableFunctions($recommended)
            : self::normalizeFunctionList(implode(',', $callableFunctions));
        $stillCallable = array_values(array_intersect($missing, $callable));
        $configuredCount = count(array_intersect($recommended, $disabled));
        $line = 'disable_functions = ' . implode(',', $recommended);
        $userIniFilename = function_exists('ini_get') ? (string) ini_get('user_ini.filename') : '';

        return [
            'raw' => trim((string) $raw),
            'disabled' => $disabled,
            'recommended' => $recommended,
            'missing_recommended' => $missing,
            'still_callable' => $stillCallable,
            'configured_count' => $configuredCount,
            'recommended_count' => count($recommended),
            'all_recommended_configured' => $configuredCount === count($recommended),
            'risk_status' => empty($stillCallable) ? 'ready' : 'warning',
            'ini_level' => 'PHP_INI_SYSTEM',
            'runtime_sapi' => PHP_SAPI,
            'user_ini_filename' => $userIniFilename,
            'user_ini_supported' => false,
            'user_ini_line' => $line,
            'user_ini_note' => '.user.ini cannot reliably set disable_functions because PHP exposes it as PHP_INI_SYSTEM. Use php.ini, the hosting panel, Apache/PHP-FPM pool config, or Docker conf.d, then reload PHP.',
            'php_ini_line' => $line,
            'docker_ini_file' => 'docker/php/conf.d/shieldx-disable-functions.ini',
            'docker_ini_contents' => str_replace(' = ', '=', $line),
        ];
    }

    public function enforceForceSsl(): void
    {
        // A direct PHP process can bootstrap WordPress without defining WP_CLI.
        // It is not an HTTP request and must never be redirected or terminated.
        if (PHP_SAPI === 'cli') {
            return;
        }

        if (!self::shouldForceSslRedirect($_SERVER, $this->settings)) {
            return;
        }

        // WordPress owns the canonical site URL; do not trust request Host
        // headers when building the redirect target.
        $url = self::forcedSslUrl($_SERVER);
        if ($url === '') {
            return;
        }

        $status = 307;

        if (function_exists('wp_safe_redirect') && wp_safe_redirect($url, $status, 'SHIELD-X')) {
            exit;
        }
    }

    public function enforceRequestHardening(): void
    {
        $uri = self::serverText('REQUEST_URI', true);
        $decision = self::requestHardeningDecision([
            'uri' => $uri,
            'query' => $this->settings->blockAuthorScans() ? self::sanitizeInputArray($_GET) : [],
            'user_agent' => self::serverText('HTTP_USER_AGENT'),
        ], $this->settings);

        if (($decision['action'] ?? 'allow') !== 'block') {
            return;
        }

        self::denyAs403();
    }

    public function installSignatureHooks(): void
    {
        remove_action('wp_head', 'wp_generator');
        remove_action('wp_head', 'rsd_link');
        remove_action('wp_head', 'wlwmanifest_link');
        remove_action('wp_head', 'wp_shortlink_wp_head');
        remove_action('wp_head', 'rest_output_link_wp_head');
    }

    public function removeSignatureHeaders(): void
    {
        if (function_exists('header_remove')) {
            header_remove('X-Pingback');
        }
    }

    public function redirectByHeader($header): string
    {
        return 'SHIELD-X';
    }

    public function removeWordPressVersionQuery(string $src): string
    {
        $version = function_exists('get_bloginfo') ? (string) get_bloginfo('version') : '';

        if ($version === '') {
            return $src;
        }

        $query = '';
        if (strpos($src, '?') !== false) {
            $query = explode('#', explode('?', $src, 2)[1], 2)[0];
        }

        if ($query === '') {
            return $src;
        }

        parse_str($query, $params);

        if (isset($params['ver']) && (string) $params['ver'] === $version && function_exists('remove_query_arg')) {
            return remove_query_arg('ver', $src);
        }

        return $src;
    }

    public function enforceAdminGate(): void
    {
        if (self::shouldSkipGate()) {
            return;
        }

        $requestUri = '';
        if (isset($_SERVER['REQUEST_URI']) && is_scalar($_SERVER['REQUEST_URI'])) {
            $requestUri = self::serverText('REQUEST_URI', true);
        }

        $path = self::requestPath($requestUri);

        if (!self::isProtectedAdminPath($path) || self::isAlwaysAllowedPath($path)) {
            return;
        }

        if (self::hasAuthenticatedWordPressSession()) {
            return;
        }

        if (self::hasAccessKey(self::sanitizeInputArray($_GET), $this->settings->adminGateKey())) {
            self::setAccessCookie($this->settings);
            if (
                self::hasValidAccessCookie(self::sanitizeInputArray($_COOKIE), $this->settings)
                && self::redirectAfterAccessKey($requestUri, $this->settings->adminGateKey())
            ) {
                exit;
            }

            return;
        }

        if (self::hasValidAccessCookie(self::sanitizeInputArray($_COOKIE), $this->settings)) {
            return;
        }

        self::denyAs404();
    }

    public static function requestPath(string $uri): string
    {
        $path = explode('?', explode('#', $uri, 2)[0], 2)[0];
        if (preg_match('~^https?://[^/]+(?P<path>/.*)?$~i', $path, $matches)) {
            $path = isset($matches['path']) && $matches['path'] !== '' ? $matches['path'] : '/';
        }

        return strtolower($path !== '' ? $path : $uri);
    }

    private static function normalizedSecurityPath(string $path): string
    {
        $path = self::requestPath($path);
        $path = RequestPath::decodeTwice($path);
        $path = str_replace('\\', '/', $path);

        return '/' . ltrim(strtolower($path !== '' ? $path : '/'), '/');
    }

    /**
     * @param array<string,mixed> $request
     * @return array{action:string,reason?:string,matched_value?:string}
     */
    public static function requestHardeningDecision(array $request, HardeningSettings $settings): array
    {
        $uri = (string) ($request['uri'] ?? '');
        $path = RequestPath::decodeTwice(self::requestPath($uri));
        $query = isset($request['query']) && is_array($request['query']) ? $request['query'] : [];
        $userAgent = (string) ($request['user_agent'] ?? '');

        if ($settings->blockXmlRpc() && self::isXmlRpcPath($path)) {
            return ['action' => 'block', 'reason' => 'hardening_xmlrpc', 'matched_value' => $path];
        }

        if ($settings->blockAuthorScans() && self::isAuthorScan($uri, $query) && !self::hasAuthenticatedWordPressSession()) {
            return ['action' => 'block', 'reason' => 'hardening_author_scan', 'matched_value' => $uri];
        }

        if (
            $settings->botProtectionEnabled()
            && $userAgent !== ''
            && preg_match(self::BOT_USER_AGENT_PATTERN, $userAgent) === 1
            && self::isBotProtectionSurface($path)
            && !self::isAuthenticatedRestRequest($path)
        ) {
            return ['action' => 'block', 'reason' => 'hardening_bot_user_agent', 'matched_value' => $userAgent];
        }

        if ($settings->directCoreAccessProtection() && self::isDirectCoreAccessPath($path)) {
            return ['action' => 'block', 'reason' => 'hardening_direct_core_access', 'matched_value' => $path];
        }

        if ($settings->sensitiveFileProtection() && self::isSensitiveFilePath($path)) {
            return ['action' => 'block', 'reason' => 'hardening_sensitive_file', 'matched_value' => $path];
        }

        if ($settings->dataPhpExecutionProtection() && self::isDataExecutablePath($path)) {
            return ['action' => 'block', 'reason' => 'hardening_data_php_execution', 'matched_value' => $path];
        }

        return ['action' => 'allow'];
    }

    /**
     * @param array<string,mixed> $server
     */
    public static function shouldForceSslRedirect(array $server, HardeningSettings $settings): bool
    {
        if (!$settings->forceSslEnabled() || self::shouldSkipSslRedirect($server)) {
            return false;
        }

        return self::trustedRedirectHost($server) !== '' && !self::isSecureRequest($server);
    }

    /**
     * @param array<string,mixed> $server
     */
    public static function isSecureRequest(array $server): bool
    {
        if (function_exists('is_ssl') && is_ssl()) {
            return true;
        }

        $https = strtolower((string) ($server['HTTPS'] ?? ''));
        if (in_array($https, ['on', '1', 'true'], true)) {
            return true;
        }

        if ((string) ($server['SERVER_PORT'] ?? '') === '443') {
            return true;
        }

        $forwardedProto = ClientIpResolver::trustedForwardedProto($server, WafSettings::fromWordPress());
        if ($forwardedProto === 'https') {
            return true;
        }

        if (self::homeUrlUsesHttps() && self::rawForwardedProto($server) === 'https') {
            return true;
        }

        return false;
    }

    /**
     * @param array<string,mixed> $server
     */
    private static function rawForwardedProto(array $server): string
    {
        $value = (string) ($server['HTTP_X_FORWARDED_PROTO'] ?? '');
        $value = strtolower(trim(explode(',', $value, 2)[0]));

        return in_array($value, ['http', 'https'], true) ? $value : '';
    }

    private static function homeUrlUsesHttps(): bool
    {
        if (!function_exists('home_url')) {
            return false;
        }

        $url = (string) home_url('/');
        if (function_exists('wp_parse_url')) {
            return strtolower((string) wp_parse_url($url, PHP_URL_SCHEME)) === 'https';
        }

        return stripos($url, 'https://') === 0;
    }

    /**
     * @param array<string,mixed> $server
     */
    public static function forcedSslUrl(array $server): string
    {
        $host = self::trustedRedirectHost($server);
        if ($host === '') {
            return '';
        }

        $uri = preg_replace('/[\r\n\x00-\x1F\x7F]+/', '', (string) ($server['REQUEST_URI'] ?? '/'));
        $uri = is_string($uri) && $uri !== '' ? $uri : '/';
        if ($uri[0] !== '/' || strpos($uri, '//') === 0) {
            $uri = '/';
        }

        return 'https://' . $host . $uri;
    }

    /**
     * @param array<string,mixed> $server
     */
    private static function trustedRedirectHost(array $server): string
    {
        if (function_exists('home_url')) {
            $homeHost = self::hostFromUrl((string) home_url('/'));
            if ($homeHost !== '') {
                return preg_replace('/:80$/', '', $homeHost) ?: $homeHost;
            }
        }

        $host = (string) ($server['HTTP_HOST'] ?? ($server['SERVER_NAME'] ?? ''));
        $host = explode("\n", str_replace("\r", "\n", $host), 2)[0];
        $host = trim($host);

        if ($host === '' || strpos($host, '/') !== false || strpos($host, '\\') !== false || strpos($host, '@') !== false) {
            return '';
        }

        $parsedHost = self::hostFromUrl('http://' . $host . '/');

        return $parsedHost !== '' ? (preg_replace('/:80$/', '', $parsedHost) ?: $parsedHost) : '';
    }

    private static function hostFromUrl(string $url): string
    {
        if (function_exists('wp_parse_url')) {
            $parts = wp_parse_url($url);
            $host = is_array($parts) ? (string) ($parts['host'] ?? '') : '';
            $port = is_array($parts) ? (int) ($parts['port'] ?? 0) : 0;
        } else {
            if (preg_match('~^[a-z][a-z0-9+.-]*://(\[[^\]]+\]|[^/:?#]+)(?::(\d{1,5}))?~i', $url, $matches) !== 1) {
                return '';
            }

            $host = trim((string) $matches[1], '[]');
            $port = isset($matches[2]) ? (int) $matches[2] : 0;
        }

        if ($host === '') {
            return '';
        }

        $host = strtolower($host);
        if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && !filter_var($host, FILTER_VALIDATE_IP)) {
            return '';
        }

        if ($port > 0 && $port <= 65535 && $port !== 80 && $port !== 443) {
            return $host . ':' . (string) $port;
        }

        return $host;
    }

    public static function isProtectedAdminPath(string $path): bool
    {
        $path = self::normalizedSecurityPath($path);

        return preg_match('~(?:^|/)wp-admin(?:/|$)~', $path) === 1
            || preg_match('~(?:^|/)wp-login\.php$~', $path) === 1;
    }

    public static function isAlwaysAllowedPath(string $path): bool
    {
        $basename = basename(self::normalizedSecurityPath($path));

        return in_array($basename, ['admin-ajax.php', 'admin-post.php'], true);
    }

    /**
     * @param array<string,mixed> $query
     */
    public static function hasAccessKey(array $query, string $key): bool
    {
        return $key !== '' && array_key_exists($key, $query);
    }

    private static function redirectAfterAccessKey(string $requestUri, string $key): bool
    {
        if ($requestUri === '' || headers_sent()) {
            return false;
        }

        $redirect = self::urlWithoutAccessKey($requestUri, $key);
        if ($redirect === '' || $redirect === $requestUri) {
            return false;
        }

        // Prevent the one-time entry URL from becoming a Referer before the
        // browser follows the keyless redirect.
        header('Referrer-Policy: no-referrer', true);
        if (function_exists('wp_safe_redirect')) {
            return (bool) wp_safe_redirect($redirect, 303);
        }

        header('Location: ' . $redirect, true, 303);

        return true;
    }

    private static function urlWithoutAccessKey(string $requestUri, string $key): string
    {
        $parts = explode('?', $requestUri, 2);
        $path = (string) ($parts[0] ?? '');
        if ($path === '' || strpbrk($path, "\r\n") !== false) {
            return '';
        }

        if (!isset($parts[1])) {
            return $path;
        }

        $query = [];
        foreach (explode('&', $parts[1]) as $pair) {
            $name = explode('=', $pair, 2)[0];
            if (rawurldecode($name) === $key) {
                continue;
            }

            $query[] = $pair;
        }

        return $query === [] ? $path : $path . '?' . implode('&', $query);
    }

    /**
     * @param array<string,mixed> $cookies
     */
    public static function hasValidAccessCookie(array $cookies, HardeningSettings $settings): bool
    {
        if (!isset($cookies[self::cookieName()]) || !is_scalar($cookies[self::cookieName()])) {
            return false;
        }

        $expected = self::cookieValue($settings->adminGateKey());

        return $expected !== '' && hash_equals($expected, (string) $cookies[self::cookieName()]);
    }

    public static function hasAuthenticatedWordPressSession(): bool
    {
        if (function_exists('is_user_logged_in') && is_user_logged_in()) {
            return true;
        }

        if (function_exists('get_current_user_id') && (int) get_current_user_id() > 0) {
            return true;
        }

        if (function_exists('wp_validate_auth_cookie')) {
            foreach (['auth', 'secure_auth', 'logged_in'] as $scheme) {
                try {
                    if ((int) wp_validate_auth_cookie('', $scheme) > 0) {
                        return true;
                    }
                } catch (\Throwable $throwable) {
                    continue;
                }
            }
        }

        return false;
    }

    public static function setAccessCookie(HardeningSettings $settings): void
    {
        $ttl = defined('DAY_IN_SECONDS') ? DAY_IN_SECONDS : 86400;
        if (function_exists('apply_filters')) {
            try {
                $ttl = (int) apply_filters('shieldx_admin_gate_cookie_ttl', $ttl);
            } catch (\Throwable $error) {
                \ShieldX\Support\ErrorLog::write('SHIELD-X admin gate cookie TTL filter failed: ' . $error->getMessage());
            }
        }
        $ttl = max(300, $ttl);

        $value = self::cookieValue($settings->adminGateKey());
        if ($value === '') {
            return;
        }

        self::writeCookie($value, time() + $ttl, $settings->adminGateStrictCookie());
    }

    public static function clearAccessCookie(): void
    {
        $ttl = defined('HOUR_IN_SECONDS') ? HOUR_IN_SECONDS : 3600;

        self::writeCookie('', time() - $ttl);
    }

    public static function cookieName(): string
    {
        return 'shieldx_admin_gate';
    }

    public static function cookieValue(string $key): string
    {
        $secret = function_exists('wp_salt') ? (string) wp_salt('auth') : '';
        if ($secret === '') {
            return '';
        }

        return hash_hmac('sha256', $key, $secret);
    }

    private static function writeCookie(string $value, int $expires, bool $strictSameSite = false): void
    {
        if (headers_sent()) {
            return;
        }

        $secure = function_exists('is_ssl') && is_ssl();
        $options = [
            'expires' => $expires,
            'path' => self::safeCookiePath(),
            'secure' => $secure,
            'httponly' => true,
            'samesite' => $strictSameSite && $secure ? 'Strict' : 'Lax',
        ];

        $domain = self::safeCookieDomain();
        if ($domain !== '') {
            $options['domain'] = $domain;
        }

        if (!setcookie(self::cookieName(), $value, $options)) {
            return;
        }

        if ($expires < time() || $value === '') {
            unset($_COOKIE[self::cookieName()]);
            return;
        }

        $_COOKIE[self::cookieName()] = $value;
    }

    private static function safeCookiePath(): string
    {
        $path = defined('COOKIEPATH') && COOKIEPATH ? (string) COOKIEPATH : '/';
        $path = preg_replace('/[\r\n\x00-\x1F\x7F]+/', '', $path);
        $path = is_string($path) ? trim($path) : '/';

        if ($path === '' || $path[0] !== '/' || strpos($path, '..') !== false || strpos($path, '\\') !== false) {
            return '/';
        }

        return $path;
    }

    private static function safeCookieDomain(): string
    {
        if (!defined('COOKIE_DOMAIN') || !COOKIE_DOMAIN) {
            return '';
        }

        $domain = preg_replace('/[\r\n\x00-\x1F\x7F]+/', '', (string) COOKIE_DOMAIN);
        $domain = is_string($domain) ? strtolower(trim($domain)) : '';
        $host = ltrim($domain, '.');

        if (
            $host === ''
            || strpos($host, '/') !== false
            || strpos($host, '\\') !== false
            || strpos($host, '@') !== false
            || (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && !filter_var($host, FILTER_VALIDATE_IP))
        ) {
            return '';
        }

        return strpos($domain, '.') === 0 ? '.' . $host : $host;
    }

    private static function shouldSkipGate(): bool
    {
        return (defined('WP_CLI') && WP_CLI)
            || (function_exists('wp_doing_cron') && wp_doing_cron())
            || (defined('REST_REQUEST') && REST_REQUEST);
    }

    /**
     * @param array<string,mixed> $server
     */
    private static function shouldSkipSslRedirect(array $server): bool
    {
        if ((defined('WP_CLI') && WP_CLI) || (function_exists('wp_doing_cron') && wp_doing_cron())) {
            return true;
        }

        if (self::isLocalDevelopmentHost($server)) {
            return true;
        }

        $requestUri = (string) ($server['REQUEST_URI'] ?? '');

        return self::isEmergencyRecoverySslEndpoint($requestUri);
    }

    private static function isEmergencyRecoverySslEndpoint(string $requestUri): bool
    {
        return rtrim(self::requestPath($requestUri), '/') === rtrim(self::emergencyRecoveryPath(), '/');
    }

    private static function emergencyRecoveryPath(): string
    {
        if (function_exists('home_url')) {
            $path = self::requestPath((string) home_url('/shieldx-recovery'));
            if ($path !== '') {
                return rtrim($path, '/');
            }
        }

        return '/shieldx-recovery';
    }

    /**
     * Local Docker and desktop hosts often expose WordPress over plain HTTP only.
     * Force SSL would redirect those installs to a TLS endpoint that does not exist.
     *
     * @param array<string,mixed> $server
     */
    private static function isLocalDevelopmentHost(array $server): bool
    {
        // Prefer the configured site host. HTTP_HOST is attacker-controlled on
        // public requests and must not be able to suppress an HTTPS redirect.
        $host = '';
        if (function_exists('home_url')) {
            $host = self::hostFromUrl((string) home_url('/'));
        } else {
            $host = (string) ($server['HTTP_HOST'] ?? ($server['SERVER_NAME'] ?? ''));
        }
        $host = strtolower(trim($host));

        if ($host === '') {
            return false;
        }

        $host = preg_replace('/:\d+$/', '', $host);
        $host = is_string($host) ? trim($host, '[]') : '';

        return in_array($host, ['localhost', '127.0.0.1', '::1'], true);
    }

    private function hasRuntimeRequestHardening(): bool
    {
        return $this->settings->blockXmlRpc()
            || $this->settings->blockAuthorScans()
            || $this->settings->botProtectionEnabled()
            || $this->settings->directCoreAccessProtection()
            || $this->settings->sensitiveFileProtection()
            || $this->settings->dataPhpExecutionProtection();
    }

    private static function wpConfigPath(): string
    {
        $root = WordPressPaths::root();
        if ($root !== '') {
            $path = $root . '/wp-config.php';
            if (is_file($path)) {
                return $path;
            }

            $parent = dirname($root) . '/wp-config.php';
            if (is_file($parent)) {
                return $parent;
            }

            return $path;
        }

        return '';
    }

    private static function rootPath(string $rootDir = ''): string
    {
        if ($rootDir !== '') {
            return rtrim(str_replace('\\', '/', $rootDir), '/');
        }

        return WordPressPaths::root();
    }

    private static function contentPath(string $contentDir = '', string $rootDir = ''): string
    {
        if ($contentDir !== '') {
            return rtrim(str_replace('\\', '/', $contentDir), '/');
        }

        if (defined('SHIELDX_CONTENT_DIR')) {
            return rtrim(str_replace('\\', '/', (string) SHIELDX_CONTENT_DIR), '/');
        }

        return WordPressPaths::contentDir();
    }

    private static function htaccessRulesSupported(): bool
    {
        $server = strtolower(self::serverText('SERVER_SOFTWARE'));
        $sapi = strtolower(PHP_SAPI);

        if ($server === '' && $sapi === '') {
            return true;
        }

        if (strpos($server, 'apache') !== false || strpos($server, 'litespeed') !== false || strpos($sapi, 'apache') !== false || strpos($sapi, 'litespeed') !== false) {
            return true;
        }

        if (strpos($server, 'nginx') !== false || strpos($server, 'microsoft-iis') !== false || strpos($server, 'iis') !== false) {
            return false;
        }

        return true;
    }

    private static function serverText(string $key, bool $preserveUrlEncoding = false): string
    {
        if (!isset($_SERVER[$key]) || !is_scalar($_SERVER[$key])) {
            return '';
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Dynamic server value is unslashed here and sanitized before return.
        $value = function_exists('wp_unslash') ? wp_unslash($_SERVER[$key]) : $_SERVER[$key];

        return self::sanitizeText($value, $preserveUrlEncoding);
    }

    /**
     * @param mixed $value
     */
    private static function sanitizeText($value, bool $preserveUrlEncoding = false): string
    {
        $value = is_scalar($value) ? (string) $value : '';
        $value = preg_replace('/[\r\n\x00-\x1F\x7F]+/', '', $value);
        $value = is_string($value) ? trim($value) : '';

        if ($preserveUrlEncoding) {
            return $value;
        }

        return function_exists('sanitize_text_field') ? sanitize_text_field($value) : $value;
    }

    /**
     * @param array<mixed,mixed> $input
     * @return array<string,mixed>
     */
    private static function sanitizeInputArray(array $input): array
    {
        $sanitized = [];

        foreach ($input as $key => $value) {
            $cleanKey = function_exists('sanitize_key')
                ? sanitize_key((string) $key)
                : strtolower((string) preg_replace('/[^a-z0-9_\-]+/', '', (string) $key));
            if ($cleanKey === '') {
                continue;
            }

            if (is_array($value)) {
                $sanitized[$cleanKey] = self::sanitizeInputArray($value);
                continue;
            }

            if (is_scalar($value)) {
                $unslashed = function_exists('wp_unslash') ? wp_unslash($value) : $value;
                $sanitized[$cleanKey] = self::sanitizeText($unslashed);
            }
        }

        return $sanitized;
    }

    /**
     * @return array<int,string>
     */
    private static function recommendedDisabledFunctions(): array
    {
        return self::RECOMMENDED_DISABLED_FUNCTIONS;
    }

    /**
     * @return array<int,string>
     */
    private static function normalizeFunctionList(string $raw): array
    {
        $items = array_filter(array_map(static function (string $item): string {
            return strtolower(trim($item));
        }, explode(',', $raw)), static function (string $item): bool {
            return $item !== '';
        });

        return array_values(array_unique($items));
    }

    /**
     * @param array<int,string> $functions
     * @return array<int,string>
     */
    private static function callableFunctions(array $functions): array
    {
        $callable = [];

        foreach ($functions as $function) {
            if (function_exists($function)) {
                $callable[] = $function;
            }
        }

        return $callable;
    }

    /**
     * @return array<string,string>
     */
    private static function measure(string $id, string $label, string $status, string $detail, string $type): array
    {
        return [
            'id' => $id,
            'label' => $label,
            'status' => $status,
            'detail' => $detail,
            'type' => $type,
        ];
    }

    private static function defaultAdminUsernameExists(): bool
    {
        if (!function_exists('get_user_by')) {
            return false;
        }

        return (bool) get_user_by('login', 'admin');
    }

    private static function usesDefaultDbPrefix(): bool
    {
        global $wpdb;

        if (!is_object($wpdb) || !isset($wpdb->prefix)) {
            return false;
        }

        return (string) $wpdb->prefix === 'wp_';
    }

    private static function securityKeysConfigured(): bool
    {
        $keys = ['AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT'];

        foreach ($keys as $key) {
            if (!defined($key)) {
                return false;
            }

            $value = (string) constant($key);
            if ($value === '' || stripos($value, 'put your unique phrase here') !== false) {
                return false;
            }
        }

        return true;
    }

    private static function rootHtaccessBody(HardeningSettings $settings): string
    {
        $parts = [];

        if ($settings->blockXmlRpc()) {
            $parts[] = <<<'HTACCESS'
<Files "xmlrpc.php">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
  </IfModule>
</Files>
HTACCESS;
        }

        if ($settings->directCoreAccessProtection()) {
            $parts[] = <<<'HTACCESS'
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^wp-admin/includes/ - [F,L]
  RewriteRule !^wp-includes/ - [S=3]
  RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
  RewriteRule ^wp-includes/js/tinymce/langs/.+\.php$ - [F,L]
  RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
HTACCESS;
        }

        if ($settings->directoryBrowsingProtection()) {
            $parts[] = <<<'HTACCESS'
Options -Indexes
HTACCESS;
        }

        if ($settings->sensitiveFileProtection()) {
            $parts[] = <<<'HTACCESS'
<FilesMatch "^(wp-config\.php|\.htaccess|\.htpasswd|\.user\.ini|\.env|debug\.log|composer\.(json|lock)|package(-lock)?\.json|yarn\.lock|phpunit\.xml(\.dist)?|readme\.html|license\.txt)$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
  </IfModule>
</FilesMatch>
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_URI} !/wp-content/uploads/ [NC]
  RewriteRule (^|/)[^/]+\.(bak|backup|old|orig|save|sql|zip|tar|gz)$ - [F,L]
</IfModule>
HTACCESS;
        }

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

    private static function noExecHtaccessBody(): string
    {
        return <<<'HTACCESS'
<IfModule mod_authz_core.c>
  <FilesMatch "\.(?i:php|phar|phtml|php[0-9]?|pht|phps|cgi|pl|py|asp|aspx|sh|exe)$">
    Require all denied
  </FilesMatch>
</IfModule>
<IfModule !mod_authz_core.c>
  <FilesMatch "\.(?i:php|phar|phtml|php[0-9]?|pht|phps|cgi|pl|py|asp|aspx|sh|exe)$">
    Order allow,deny
    Deny from all
  </FilesMatch>
</IfModule>
<IfModule mod_mime.c>
  RemoveHandler .php .phar .phtml .php3 .php4 .php5 .php7 .pht .phps .cgi .pl .py
  RemoveType    .php .phar .phtml .php3 .php4 .php5 .php7 .pht .phps
</IfModule>
HTACCESS;
    }

    private static function sandboxNoExecHtaccessBody(): string
    {
        return <<<'HTACCESS'
# The temporary probe endpoint is index.php, so plain .php must remain executable
# inside this sandbox. Applied data-directory stubs use the full no-exec rules.
<IfModule mod_authz_core.c>
  <FilesMatch "\.(?i:phar|phtml|php[0-9]|pht|phps|cgi|pl|py|asp|aspx|sh|exe)$">
    Require all denied
  </FilesMatch>
</IfModule>
<IfModule !mod_authz_core.c>
  <FilesMatch "\.(?i:phar|phtml|php[0-9]|pht|phps|cgi|pl|py|asp|aspx|sh|exe)$">
    Order allow,deny
    Deny from all
  </FilesMatch>
</IfModule>
<IfModule mod_mime.c>
  RemoveHandler .phar .phtml .php3 .php4 .php5 .php7 .pht .phps .cgi .pl .py
  RemoveType    .phar .phtml .php3 .php4 .php5 .php7 .pht .phps
</IfModule>
HTACCESS;
    }

    /**
     * @return array<int,string>
     */
    private static function dataNoExecDirectories(string $contentDir): array
    {
        if ($contentDir === '') {
            return [];
        }

        $contentRoot = realpath($contentDir);
        if (!is_string($contentRoot)) {
            return [];
        }
        $contentRoot = rtrim(str_replace('\\', '/', $contentRoot), '/');

        $directories = [
            $contentDir . '/uploads',
            $contentDir . '/cache',
            $contentDir . '/upgrade',
        ];
        $existing = [];

        foreach ($directories as $directory) {
            $directory = rtrim(str_replace('\\', '/', $directory), '/');
            if (!is_dir($directory) || is_link($directory)) {
                continue;
            }

            $realDirectory = realpath($directory);
            if (!is_string($realDirectory)) {
                continue;
            }

            $realDirectory = rtrim(str_replace('\\', '/', $realDirectory), '/');
            if (self::pathIsInside($contentRoot, $realDirectory)) {
                $existing[$realDirectory] = $realDirectory;
            }
        }

        return array_values($existing);
    }

    private static function pathIsInside(string $root, string $path): bool
    {
        $root = rtrim(str_replace('\\', '/', $root), '/');
        $path = rtrim(str_replace('\\', '/', $path), '/');

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

        return $path === $root || strpos($path, $root . '/') === 0;
    }

    /**
     * @return array<string,mixed>
     */
    private static function createHtaccessSandbox(string $rootDir, string $body, bool $forceDirectDirectory = false): array
    {
        $rootDir = rtrim(str_replace('\\', '/', $rootDir), '/');
        try {
            $sandboxName = self::HTACCESS_SANDBOX_PREFIX . bin2hex(random_bytes(12));
            $token = bin2hex(random_bytes(16));
        } catch (\Throwable $error) {
            return [
                'ok' => false,
                'status' => 'sandbox_random_failed',
                'message' => 'SHIELD-X could not create a secure .htaccess sandbox token, so root rules were not applied.',
            ];
        }

        $sandboxDir = $rootDir . '/' . $sandboxName;
        $directoryMethod = 'existing';
        if ($forceDirectDirectory) {
            $sandboxReady = !file_exists($sandboxDir) && !is_link($sandboxDir);
            if ($sandboxReady) {
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- A single direct mkdir retry is intentionally used after an HTTP 403 from a WordPress-created sandbox.
                $sandboxReady = mkdir($sandboxDir, 0755);
            }
            $directoryMethod = 'direct';
        } else {
            $sandboxReady = is_dir($sandboxDir);
            if (!$sandboxReady && function_exists('wp_mkdir_p')) {
                $sandboxReady = wp_mkdir_p($sandboxDir);
                if ($sandboxReady) {
                    $directoryMethod = 'wordpress';
                }
            }
            if (!$sandboxReady) {
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- Unit tests may run before WordPress loads; production prefers wp_mkdir_p() above.
                $sandboxReady = mkdir($sandboxDir, 0755, true);
                if ($sandboxReady) {
                    $directoryMethod = 'direct';
                }
            }
        }
        if (!$sandboxReady) {
            return [
                'ok' => false,
                'status' => 'sandbox_create_failed',
                'message' => 'SHIELD-X could not create an .htaccess sandbox folder, so root rules were not applied.',
            ];
        }

        $htaccessFile = $sandboxDir . '/.htaccess';
        $backupFile = $sandboxDir . '/' . self::HTACCESS_SANDBOX_BACKUP;
        $indexFile = $sandboxDir . '/' . self::HTACCESS_SANDBOX_INDEX;
        $htaccessExisted = is_file($htaccessFile) || is_link($htaccessFile);
        $sandboxRules = self::markedHtaccessBlock(self::ROOT_RULES_MARKER, $body)
            . "\n\n"
            . self::sandboxBackupProtectionRules()
            . "\n\n"
            . self::sandboxProbeResponseRules($token)
            . "\n\n"
            . self::sandboxCacheControlRules()
            . "\n";
        $indexBody = '<!doctype html><html lang="en"><head><meta charset="utf-8">'
            . '<title>SHIELD-X configuration check</title></head><body>'
            . '<p>shield-x htaccess preflight ' . $token . '</p>'
            . '</body></html>' . "\n";

        if ($htaccessExisted) {
            $backup = self::preserveSandboxHtaccessAsBackup($htaccessFile, $backupFile);
            if (empty($backup['ok'])) {
                self::removeSandbox($sandboxDir);

                return [
                    'ok' => false,
                    'status' => (string) ($backup['status'] ?? 'sandbox_htaccess_backup_failed'),
                    'message' => 'SHIELD-X found a host-created sandbox .htaccess file but could not preserve it as htaccess.bak. Check hosting or cPanel filesystem ownership and permissions. No HTTP probe was attempted, and no hardening files were changed.',
                ];
            }
        }

        $htaccessWrite = self::writeAndVerifyHostFile($htaccessFile, $sandboxRules, 0644);
        if (empty($htaccessWrite['ok'])) {
            self::removeSandbox($sandboxDir);

            return [
                'ok' => false,
                'status' => (string) ($htaccessWrite['status'] ?? 'sandbox_htaccess_write_failed'),
                'message' => 'SHIELD-X could not create and verify its sandbox .htaccess file on this host. Check hosting or cPanel filesystem ownership and permissions. No HTTP probe was attempted, and no hardening files were changed.',
            ];
        }

        $indexWrite = self::writeAndVerifyHostFile($indexFile, $indexBody, 0644);
        if (empty($indexWrite['ok'])) {
            self::removeSandbox($sandboxDir);

            return [
                'ok' => false,
                'status' => (string) ($indexWrite['status'] ?? 'sandbox_index_write_failed'),
                'message' => 'SHIELD-X created and verified the sandbox .htaccess file, but could not create and verify index.php on this host. Check hosting filesystem ownership and permissions. No browser or CDN request was made, and no hardening files were changed.',
            ];
        }

        $relativePath = $sandboxName . '/';
        $urls = self::sandboxProbeUrls($relativePath);

        return [
            'ok' => true,
            'status' => 'ready',
            'message' => 'The .htaccess sandbox is ready.',
            'path' => $sandboxDir,
            'relative_path' => $relativePath,
            'url' => isset($urls[0]) ? (string) $urls[0] : '/' . $relativePath,
            'token' => $token,
            'filesystem_status' => $htaccessExisted ? 'backup_preserved_verified' : 'created_verified',
            'directory_method' => $directoryMethod,
        ];
    }

    private static function sandboxBackupProtectionRules(): string
    {
        return <<<'HTACCESS'
<Files "htaccess.bak">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
  </IfModule>
</Files>
HTACCESS;
    }

    private static function sandboxProbeResponseRules(string $token): string
    {
        return 'ErrorDocument 403 "shield-x htaccess preflight ' . $token . '"';
    }

    private static function sandboxCacheControlRules(): string
    {
        return <<<'HTACCESS'
<IfModule mod_headers.c>
  Header always set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
  Header always set Pragma "no-cache"
  Header always set Expires "0"
</IfModule>
HTACCESS;
    }

    /**
     * @return array<string,mixed>
     */
    private static function preflightRootHtaccess(string $rootDir, string $body, ?callable $requester = null): array
    {
        if (trim($body) === '') {
            return [
                'ok' => true,
                'status' => 'not_needed',
                'message' => 'Root .htaccess preflight was not needed because no root hardening rules are enabled.',
                'url' => '',
                'http_code' => 0,
                'checked_at' => gmdate('Y-m-d H:i:s'),
            ];
        }

        $sandbox = self::createHtaccessSandbox($rootDir, $body);
        if (empty($sandbox['ok'])) {
            return [
                'ok' => false,
                'status' => (string) ($sandbox['status'] ?? 'sandbox_failed'),
                'message' => (string) ($sandbox['message'] ?? 'SHIELD-X could not create the .htaccess sandbox.'),
                'url' => '',
                'http_code' => 0,
                'checked_at' => gmdate('Y-m-d H:i:s'),
            ];
        }

        $sandboxDir = (string) $sandbox['path'];

        register_shutdown_function(static function () use ($sandboxDir): void {
            self::removeSandbox($sandboxDir);
        });

        $result = self::probeSandboxUrls(
            (string) $sandbox['relative_path'],
            (string) $sandbox['token'],
            $requester
        );
        $fallbackUsed = false;
        if (
            (int) ($result['http_code'] ?? 0) === 403
            && (string) ($sandbox['directory_method'] ?? '') !== 'direct'
        ) {
            self::removeSandbox($sandboxDir);
            $fallback = self::createHtaccessSandbox($rootDir, $body, true);
            if (!empty($fallback['ok'])) {
                $fallbackDir = (string) $fallback['path'];
                register_shutdown_function(static function () use ($fallbackDir): void {
                    self::removeSandbox($fallbackDir);
                });

                $result = self::probeSandboxUrls(
                    (string) $fallback['relative_path'],
                    (string) $fallback['token'],
                    $requester
                );
                self::removeSandbox($fallbackDir);
                $sandbox = $fallback;
                $fallbackUsed = true;
            }
        }
        self::removeSandbox($sandboxDir);

        if (!empty($result['ok'])) {
            return [
                'ok' => true,
                'status' => 'passed',
                'message' => 'Root .htaccess sandbox preflight passed before applying SHIELD-X rules.',
                'url' => (string) ($result['url'] ?? ''),
                'filesystem_status' => (string) ($sandbox['filesystem_status'] ?? 'created_verified'),
                'directory_method' => (string) ($sandbox['directory_method'] ?? 'wordpress'),
                'fallback_used' => $fallbackUsed,
                'http_code' => (int) ($result['http_code'] ?? 0),
                'server' => (string) ($result['server'] ?? ''),
                'cf_ray' => (string) ($result['cf_ray'] ?? ''),
                'cf_cache_status' => (string) ($result['cf_cache_status'] ?? ''),
                'cf_mitigated' => (string) ($result['cf_mitigated'] ?? ''),
                'content_type' => (string) ($result['content_type'] ?? ''),
                'checked_at' => gmdate('Y-m-d H:i:s'),
            ];
        }

        return [
            'ok' => false,
            'status' => (string) ($result['status'] ?? 'failed'),
            'message' => (string) ($result['message'] ?? 'Root .htaccess sandbox preflight failed, so SHIELD-X did not change the root .htaccess file.'),
            'url' => (string) ($result['url'] ?? ''),
            'filesystem_status' => (string) ($sandbox['filesystem_status'] ?? 'created_verified'),
            'directory_method' => (string) ($sandbox['directory_method'] ?? 'wordpress'),
            'fallback_used' => $fallbackUsed,
            'http_code' => (int) ($result['http_code'] ?? 0),
            'server' => (string) ($result['server'] ?? ''),
            'cf_ray' => (string) ($result['cf_ray'] ?? ''),
            'cf_cache_status' => (string) ($result['cf_cache_status'] ?? ''),
            'cf_mitigated' => (string) ($result['cf_mitigated'] ?? ''),
            'content_type' => (string) ($result['content_type'] ?? ''),
            'checked_at' => gmdate('Y-m-d H:i:s'),
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private static function probeSandboxUrls(string $relativeProbePath, string $expectedToken, ?callable $requester = null): array
    {
        $urls = self::sandboxProbeUrls($relativeProbePath);
        if (empty($urls)) {
            return [
                'ok' => true,
                'status' => 'skipped',
                'message' => 'Root .htaccess sandbox preflight was skipped because WordPress URL helpers are unavailable.',
                'url' => '',
                'http_code' => 0,
            ];
        }

        $last = [
            'ok' => false,
            'status' => 'probe_unavailable',
            'message' => 'Root .htaccess sandbox URL could not be reached.',
            'url' => '',
            'http_code' => 0,
        ];

        foreach ($urls as $url) {
            $response = self::requestSandboxProbe($url, $expectedToken, $requester);
            $response['url'] = $url;
            $last = $response;

            if (!empty($response['ok'])) {
                return $response;
            }

            $code = (int) ($response['http_code'] ?? 0);
            if ($code >= 500) {
                return $response;
            }
        }

        return $last;
    }

    /**
     * @return array<int,string>
     */
    private static function sandboxProbeUrls(string $relativeProbePath): array
    {
        if (!function_exists('home_url')) {
            return ['/' . $relativeProbePath];
        }

        $relativeProbePath = ltrim($relativeProbePath, '/');
        $home = (string) home_url('/');

        return [rtrim($home, '/') . '/' . $relativeProbePath];
    }

    /**
     * @return array<string,mixed>
     */
    private static function requestSandboxProbe(string $url, string $expectedToken, ?callable $requester = null): array
    {
        if ($requester !== null) {
            return self::normalizeProbeResponse($requester($url, $expectedToken), $expectedToken);
        }

        if (!function_exists('wp_remote_get')) {
            return [
                'ok' => true,
                'status' => 'skipped',
                'message' => 'Root .htaccess sandbox preflight was skipped because the WordPress HTTP API is unavailable.',
                'http_code' => 0,
            ];
        }

        $args = [
            'timeout' => 5,
            'redirection' => 0,
            'sslverify' => true,
            'headers' => [
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Cache-Control' => 'no-cache, no-store, max-age=0',
                'Pragma' => 'no-cache',
            ],
        ];
        if (function_exists('home_url') && function_exists('wp_parse_url')) {
            $parts = wp_parse_url((string) home_url('/'));
            $host = is_array($parts) && isset($parts['host']) && is_string($parts['host']) ? $parts['host'] : '';
            $port = is_array($parts) && isset($parts['port']) && is_int($parts['port']) ? $parts['port'] : null;
            if (is_string($host) && $host !== '') {
                $args['headers']['Host'] = $host . (is_int($port) ? ':' . (string) $port : '');
            }
        }

        $response = wp_remote_get($url, $args);
        if (function_exists('is_wp_error') && is_wp_error($response)) {
            // Docker-backed local sites commonly expose localhost:PORT to the
            // browser while Apache listens on port 80 inside the container.
            // Retry only that proven loopback shape, keep the public Host
            // header, and continue to fail closed if the retry is unavailable.
            $fallbackUrl = self::localSandboxProbeFallbackUrl($url);
            if ($fallbackUrl !== $url) {
                $response = wp_remote_get($fallbackUrl, $args);
            }
        }
        if (function_exists('is_wp_error') && is_wp_error($response)) {
            return [
                'ok' => false,
                'status' => 'http_error',
                'message' => method_exists($response, 'get_error_message') ? $response->get_error_message() : 'Sandbox probe request failed.',
                'http_code' => 0,
            ];
        }

        $code = function_exists('wp_remote_retrieve_response_code') ? (int) wp_remote_retrieve_response_code($response) : 0;
        $body = function_exists('wp_remote_retrieve_body') ? (string) wp_remote_retrieve_body($response) : '';
        $headers = function_exists('wp_remote_retrieve_headers') ? wp_remote_retrieve_headers($response) : [];

        return self::normalizedProbeResult($code, $body, $headers, $expectedToken);
    }

    private static function localSandboxProbeFallbackUrl(string $url, string $homeUrl = ''): string
    {
        if (!defined('WP_ENVIRONMENT_TYPE') || (string) WP_ENVIRONMENT_TYPE !== 'local' || !function_exists('wp_parse_url')) {
            return $url;
        }

        if ($homeUrl === '' && function_exists('home_url')) {
            $homeUrl = (string) home_url('/');
        }

        $parts = wp_parse_url($url);
        $homeParts = wp_parse_url($homeUrl);
        if (!is_array($parts) || !is_array($homeParts)) {
            return $url;
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? ''));
        $host = strtolower((string) ($parts['host'] ?? ''));
        $port = isset($parts['port']) ? (int) $parts['port'] : 0;
        $homeScheme = strtolower((string) ($homeParts['scheme'] ?? ''));
        $homeHost = strtolower((string) ($homeParts['host'] ?? ''));
        $homePort = isset($homeParts['port']) ? (int) $homeParts['port'] : 0;
        if (
            $scheme !== 'http'
            || $scheme !== $homeScheme
            || !in_array($host, ['localhost', '127.0.0.1', '::1'], true)
            || $host !== $homeHost
            || $port <= 0
            || $port === 80
            || $port !== $homePort
        ) {
            return $url;
        }

        $path = (string) ($parts['path'] ?? '/');
        $query = isset($parts['query']) && is_string($parts['query']) && $parts['query'] !== '' ? '?' . $parts['query'] : '';

        return 'http://127.0.0.1' . ($path !== '' ? $path : '/') . $query;
    }

    /**
     * @param mixed $response
     * @return array<string,mixed>
     */
    private static function normalizeProbeResponse($response, string $expectedToken): array
    {
        if (is_int($response)) {
            return self::normalizedProbeResult($response, '', [], $expectedToken);
        }

        if (is_array($response)) {
            $code = (int) ($response['http_code'] ?? $response['code'] ?? 0);
            $body = (string) ($response['body'] ?? '');
            $result = self::normalizedProbeResult($code, $body, $response['headers'] ?? [], $expectedToken);
            if (array_key_exists('ok', $response)) {
                $result['ok'] = (bool) $response['ok'];
                $result['status'] = (string) ($response['status'] ?? ($result['ok'] ? 'passed' : $result['status']));
                $result['message'] = (string) ($response['message'] ?? ($result['ok'] ? 'Sandbox .htaccess probe returned the expected token.' : $result['message']));
            }

            return $result;
        }

        return [
            'ok' => false,
            'status' => 'probe_unavailable',
            'message' => 'Sandbox .htaccess probe returned an invalid response.',
            'http_code' => 0,
        ];
    }

    /**
     * @param mixed $headers
     * @return array<string,mixed>
     */
    private static function normalizedProbeResult(int $code, string $body, $headers, string $expectedToken): array
    {
        $headers = self::normalizeProbeHeaders($headers);
        $server = substr(self::sanitizeText((string) ($headers['server'] ?? '')), 0, 160);
        $cfRay = substr(self::sanitizeText((string) ($headers['cf-ray'] ?? '')), 0, 160);
        $cfCacheStatus = substr(self::sanitizeText((string) ($headers['cf-cache-status'] ?? '')), 0, 80);
        $cfMitigated = substr(self::sanitizeText((string) ($headers['cf-mitigated'] ?? '')), 0, 80);
        $contentType = substr(self::sanitizeText((string) ($headers['content-type'] ?? '')), 0, 160);
        $ok = $code >= 200 && $code < 400 && $expectedToken !== '' && strpos($body, $expectedToken) !== false;
        $cloudflare = self::isCloudflareInterception($body, $server, $cfRay, $cfMitigated, $contentType);

        if ($ok) {
            $status = 'passed';
            $message = 'Sandbox .htaccess probe returned the expected token.';
        } elseif ($cloudflare) {
            $status = 'cdn_intercepted';
            $message = 'Cloudflare intercepted the sandbox .htaccess probe before the expected token reached SHIELD-X.';
        } elseif ($code >= 500) {
            $status = 'server_error';
            $message = 'Sandbox .htaccess probe returned HTTP ' . (string) $code . '. Root rules were not applied.';
        } else {
            $status = 'probe_token_missing';
            $message = 'Sandbox .htaccess probe did not return the expected token.';
        }

        return [
            'ok' => $ok,
            'status' => $status,
            'message' => $message,
            'http_code' => $code,
            'server' => $server,
            'cf_ray' => $cfRay,
            'cf_cache_status' => $cfCacheStatus,
            'cf_mitigated' => $cfMitigated,
            'content_type' => $contentType,
        ];
    }

    private static function isCloudflareInterception(string $body, string $server, string $cfRay, string $cfMitigated, string $contentType): bool
    {
        if (strtolower(trim($cfMitigated)) === 'challenge') {
            return true;
        }

        if (stripos($server, 'cloudflare') === false && $cfRay === '') {
            return false;
        }

        if ($contentType !== '' && stripos($contentType, 'text/html') === false) {
            return false;
        }

        $body = strtolower($body);
        foreach ([
            'attention required! | cloudflare',
            'cloudflare ray id',
            'sorry, you have been blocked',
            '<title>just a moment...</title>',
            '/cdn-cgi/challenge-platform/',
        ] as $marker) {
            if (strpos($body, $marker) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param mixed $headers
     * @return array<string,string>
     */
    private static function normalizeProbeHeaders($headers): array
    {
        if (is_object($headers)) {
            if (method_exists($headers, 'getAll')) {
                $headers = $headers->getAll();
            } elseif (method_exists($headers, 'get_all')) {
                $headers = $headers->get_all();
            } elseif ($headers instanceof \Traversable) {
                $headers = iterator_to_array($headers);
            } else {
                $headers = (array) $headers;
            }
        }
        if (!is_array($headers)) {
            return [];
        }

        $normalized = [];
        foreach ($headers as $name => $value) {
            $name = strtolower(trim((string) $name));
            if ($name === '' || (!is_scalar($value) && !is_array($value))) {
                continue;
            }
            if (is_array($value)) {
                $value = implode(', ', array_map('strval', array_filter($value, 'is_scalar')));
            }
            $normalized[$name] = trim((string) $value);
        }

        return $normalized;
    }

    /**
     * @param array<string,mixed> $preflight
     */
    private static function saveLastHtaccessPreflight(array $preflight): void
    {
        if (function_exists('update_option')) {
            $preflight['format_version'] = self::HTACCESS_PREFLIGHT_FORMAT_VERSION;
            update_option(self::HTACCESS_PREFLIGHT_OPTION, $preflight, false);
        }
    }

    /**
     * @return array<string,mixed>
     */
    private static function lastHtaccessPreflight(): array
    {
        $fallback = [
            'ok' => true,
            'status' => 'unknown',
            'message' => 'No root .htaccess sandbox preflight has been recorded yet.',
            'url' => '',
            'filesystem_status' => '',
            'http_code' => 0,
            'server' => '',
            'cf_ray' => '',
            'cf_cache_status' => '',
            'cf_mitigated' => '',
            'content_type' => '',
            'checked_at' => '',
            'expires_at' => '',
            'format_version' => self::HTACCESS_PREFLIGHT_FORMAT_VERSION,
        ];

        if (!function_exists('get_option')) {
            return $fallback;
        }

        $stored = get_option(self::HTACCESS_PREFLIGHT_OPTION, []);
        if (
            is_array($stored)
            && $stored !== []
            && (int) ($stored['format_version'] ?? 0) !== self::HTACCESS_PREFLIGHT_FORMAT_VERSION
        ) {
            return array_merge($fallback, [
                'ok' => false,
                'status' => 'retest_required',
                'message' => 'The saved .htaccess sandbox result uses an outdated diagnostic format. Save hardening settings to run the current browser test.',
            ]);
        }

        return is_array($stored) ? array_merge($fallback, $stored) : $fallback;
    }

    public static function cleanupBrowserHtaccessPreflight(string $jobId): void
    {
        $transientKey = self::browserPreflightTransientKey($jobId);
        $job = $transientKey !== '' && function_exists('get_transient') ? get_transient($transientKey) : false;
        if (!is_array($job)) {
            return;
        }

        $expires = (int) ($job['expires'] ?? 0);
        if ($expires > time()) {
            self::scheduleBrowserPreflightCleanup($jobId, $expires);

            return;
        }

        self::deleteBrowserPreflightJob($transientKey, $job);
        self::clearExpiredBrowserPreflightUrl($job);
    }

    private static function browserPreflightTransientKey(string $jobId): string
    {
        $jobId = trim($jobId);
        if (preg_match('/^hp-[a-f0-9]{32}$/', $jobId) !== 1) {
            return '';
        }

        return self::HTACCESS_BROWSER_JOB_PREFIX . substr(hash('sha256', $jobId), 0, 40);
    }

    private static function browserPreflightIdentity(): string
    {
        $userId = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
        $sessionToken = function_exists('wp_get_session_token') ? (string) wp_get_session_token() : '';

        return hash('sha256', (string) $userId . '|' . $sessionToken);
    }

    private static function scheduleBrowserPreflightCleanup(string $jobId, int $timestamp): void
    {
        $args = [$jobId];
        if (
            function_exists('wp_next_scheduled')
            && wp_next_scheduled(self::HTACCESS_BROWSER_CLEANUP_HOOK, $args) !== false
        ) {
            return;
        }

        if (function_exists('wp_schedule_single_event')) {
            wp_schedule_single_event($timestamp, self::HTACCESS_BROWSER_CLEANUP_HOOK, $args);
        }
    }

    /**
     * @param array<string,mixed> $job
     */
    private static function clearExpiredBrowserPreflightUrl(array $job): void
    {
        if (!function_exists('get_option')) {
            return;
        }

        $preflight = get_option(self::HTACCESS_PREFLIGHT_OPTION, []);
        $currentUrl = is_array($preflight) ? (string) ($preflight['url'] ?? '') : '';
        if ($currentUrl === '') {
            return;
        }

        foreach ((array) ($job['probes'] ?? []) as $probe) {
            if (!is_array($probe) || empty($probe['url'])) {
                continue;
            }
            if (!hash_equals((string) $probe['url'], $currentUrl)) {
                continue;
            }

            $preflight['url'] = '';
            $preflight['expires_at'] = '';
            self::saveLastHtaccessPreflight($preflight);

            return;
        }
    }

    private static function htaccessSettingsFingerprint(HardeningSettings $settings): string
    {
        $dataBody = $settings->dataPhpExecutionProtection() ? self::noExecHtaccessBody() : '';

        return hash('sha256', self::rootHtaccessBody($settings) . "\0" . $dataBody);
    }

    /**
     * @param array<string,mixed> $job
     * @return array<string,mixed>
     */
    private static function prepareDirectBrowserSandboxRetry(array $job, string $label, ?int $probeIndex): array
    {
        $rootDir = rtrim(str_replace('\\', '/', (string) ($job['root'] ?? '')), '/');
        $sandboxes = isset($job['sandboxes']) && is_array($job['sandboxes']) ? $job['sandboxes'] : [];
        $sandboxIndex = null;
        $sandboxRecord = null;
        foreach ($sandboxes as $index => $candidate) {
            if (
                !is_array($candidate)
                || (string) ($candidate['label'] ?? '') !== $label
                || (string) ($candidate['directory_method'] ?? 'wordpress') === 'direct'
            ) {
                continue;
            }

            $sandboxIndex = $index;
            $sandboxRecord = $candidate;
            break;
        }
        if (
            $rootDir === ''
            || $sandboxIndex === null
            || !is_array($sandboxRecord)
            || trim((string) ($sandboxRecord['body'] ?? '')) === ''
        ) {
            return [
                'ok' => false,
                'status' => 'direct_retry_unavailable',
            ];
        }

        $fallback = self::createHtaccessSandbox($rootDir, (string) $sandboxRecord['body'], true);
        if (empty($fallback['ok'])) {
            return [
                'ok' => false,
                'status' => (string) ($fallback['status'] ?? 'direct_retry_failed'),
            ];
        }

        $probe = [
            'label' => $label,
            'url' => (string) $fallback['url'],
            'token' => (string) $fallback['token'],
            'filesystem_status' => (string) ($fallback['filesystem_status'] ?? 'created_verified'),
            'directory_method' => 'direct',
        ];
        $job['sandboxes'][$sandboxIndex] = [
            'label' => $label,
            'body' => (string) $sandboxRecord['body'],
            'path' => (string) $fallback['path'],
            'directory_method' => 'direct',
        ];
        if (
            $probeIndex !== null
            && isset($job['probes'])
            && is_array($job['probes'])
            && array_key_exists($probeIndex, $job['probes'])
        ) {
            $job['probes'][$probeIndex] = $probe;
        } else {
            foreach ((array) ($job['probes'] ?? []) as $index => $candidate) {
                if (is_array($candidate) && (string) ($candidate['label'] ?? '') === $label) {
                    $job['probes'][$index] = $probe;
                    break;
                }
            }
        }

        return [
            'ok' => true,
            'status' => 'retry_ready',
            'job' => $job,
            'probe' => $probe,
            'sandbox_index' => $sandboxIndex,
            'old_path' => (string) ($sandboxRecord['path'] ?? ''),
            'new_path' => (string) $fallback['path'],
        ];
    }

    /**
     * @param array<string,mixed> $job
     */
    private static function deleteBrowserPreflightJob(string $transientKey, array $job): void
    {
        $sandboxes = isset($job['sandboxes']) && is_array($job['sandboxes']) ? $job['sandboxes'] : [];
        self::cleanupBrowserSandboxes($sandboxes, (string) ($job['root'] ?? ''));
        if ($transientKey !== '' && function_exists('delete_transient')) {
            delete_transient($transientKey);
        }
    }

    /**
     * @param array<int,mixed> $sandboxes
     */
    private static function cleanupBrowserSandboxes(array $sandboxes, string $rootDir): void
    {
        $rootDir = rtrim(str_replace('\\', '/', $rootDir), '/');
        if ($rootDir === '') {
            return;
        }

        foreach ($sandboxes as $sandbox) {
            if (!is_array($sandbox)) {
                continue;
            }

            $path = rtrim(str_replace('\\', '/', (string) ($sandbox['path'] ?? '')), '/');
            $name = basename($path);
            if (
                $path === ''
                || dirname($path) !== $rootDir
                || !self::isHtaccessSandboxName($name)
                || is_link($path)
            ) {
                continue;
            }

            self::removeSandbox($path);
        }
    }

    private static function removeSandbox(string $path): void
    {
        if (!file_exists($path)) {
            return;
        }

        if (is_file($path) || is_link($path)) {
            self::deleteFile($path);
            return;
        }

        $items = scandir($path);
        if (is_array($items)) {
            foreach ($items as $item) {
                if ($item === '.' || $item === '..') {
                    continue;
                }

                self::removeSandbox($path . '/' . $item);
            }
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Sandbox cleanup removes the temporary empty directory after files are deleted.
        rmdir($path);
    }

    private static function sweepStaleSandboxes(string $rootDir): void
    {
        $rootDir = rtrim(str_replace('\\', '/', $rootDir), '/');
        if ($rootDir === '' || !is_dir($rootDir)) {
            return;
        }

        $items = glob($rootDir . '/' . self::HTACCESS_SANDBOX_PREFIX . '*');
        if (!is_array($items)) {
            return;
        }

        $cutoff = time() - (self::HTACCESS_BROWSER_JOB_TTL_SECONDS + 60);
        foreach ($items as $item) {
            if (!is_string($item) || !is_dir($item) || is_link($item)) {
                continue;
            }

            $modified = filemtime($item);
            if ($modified !== false && $modified <= $cutoff) {
                self::removeSandbox($item);
            }
        }
    }

    private static function isHtaccessSandboxName(string $name): bool
    {
        return preg_match('/^' . preg_quote(self::HTACCESS_SANDBOX_PREFIX, '/') . '[a-f0-9]{24}$/', $name) === 1;
    }

    /**
     * @return array<string,mixed>
     */
    private static function syncMarkedHtaccess(string $path, string $marker, string $body, bool $deleteIfEmpty): array
    {
        if ($path === '' || !is_dir(dirname($path))) {
            return [
                'changed' => false,
                'status' => 'missing_directory',
                'message' => 'Directory is not available for ' . $marker . '.',
            ];
        }

        if (is_link($path)) {
            return [
                'changed' => false,
                'status' => 'write_failed',
                'message' => 'Refusing to write ' . $marker . ' through a symlink.',
            ];
        }

        $existing = is_file($path) ? file_get_contents($path) : '';
        $existing = is_string($existing) ? $existing : '';
        $without = self::removeMarkedBlock($existing, $marker);

        if ($body === '') {
            if ($without === $existing) {
                return [
                    'changed' => false,
                    'status' => 'disabled',
                    'message' => $marker . ' is disabled.',
                ];
            }

            if ($deleteIfEmpty && trim($without) === '') {
                if (!self::deleteFile($path)) {
                    return [
                        'changed' => false,
                        'status' => 'write_failed',
                        'message' => 'Unable to remove ' . $marker . '.',
                    ];
                }

                return [
                    'changed' => true,
                    'status' => 'removed',
                    'message' => $marker . ' was removed.',
                ];
            }

            if (!self::writeHostFile($path, self::normalizeHtaccessContent($without), 0644)) {
                return [
                    'changed' => false,
                    'status' => 'write_failed',
                    'message' => 'Unable to remove ' . $marker . '.',
                ];
            }

            return [
                'changed' => true,
                'status' => 'removed',
                'message' => $marker . ' was removed.',
            ];
        }

        $next = self::normalizeHtaccessContent($without) . self::markedHtaccessBlock($marker, $body) . "\n";
        if ($next === $existing) {
            return [
                'changed' => false,
                'status' => 'active',
                'message' => $marker . ' is already active.',
            ];
        }

        if (!self::writeHostFile($path, $next, 0644)) {
            return [
                'changed' => false,
                'status' => 'write_failed',
                'message' => 'Unable to write ' . $marker . '.',
            ];
        }

        return [
            'changed' => true,
            'status' => 'active',
            'message' => $marker . ' was applied.',
        ];
    }

    private static function markedBlockStatus(string $path, string $marker, string $body, bool $disabled): string
    {
        if ($disabled) {
            return 'disabled';
        }

        if ($path === '' || !is_file($path)) {
            return 'not_applied';
        }

        $contents = file_get_contents($path);
        if (!is_string($contents)) {
            return 'not_applied';
        }

        return strpos($contents, self::markedHtaccessBlock($marker, $body)) !== false ? 'active' : 'needs_apply';
    }

    private static function markedHtaccessBlock(string $marker, string $body): string
    {
        return '# BEGIN ' . $marker . "\n" . trim($body) . "\n# END " . $marker;
    }

    private static function removeMarkedBlock(string $content, string $marker): string
    {
        $pattern = '/\n?# BEGIN ' . preg_quote($marker, '/') . '\R.*?# END ' . preg_quote($marker, '/') . '\R?/s';

        return (string) preg_replace($pattern, "\n", $content);
    }

    private static function normalizeHtaccessContent(string $content): string
    {
        $content = trim($content);

        return $content === '' ? '' : $content . "\n\n";
    }

    private static function deleteFile(string $path): bool
    {
        if (is_file($path) || is_link($path)) {
            $result = FilesystemAdapter::fromWordPress()->delete($path);

            return !empty($result['ok']);
        }

        return true;
    }

    private static function writeHostFile(string $path, string $contents, int $mode): bool
    {
        $result = FilesystemAdapter::fromWordPress()->putContents($path, $contents, $mode);

        return !empty($result['ok']);
    }

    /**
     * @return array{ok:bool,status:string}
     */
    private static function writeAndVerifyHostFile(string $path, string $contents, int $mode): array
    {
        $written = FilesystemAdapter::fromWordPress()->putContents($path, $contents, $mode);
        if (empty($written['ok'])) {
            return [
                'ok' => false,
                'status' => basename($path) === '.htaccess'
                    ? 'sandbox_htaccess_write_failed'
                    : 'sandbox_index_write_failed',
            ];
        }

        clearstatcache(false, $path);
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Exact read-back verification is required before any public sandbox request is allowed.
        $readBack = is_file($path) ? file_get_contents($path) : false;
        if (!is_string($readBack) || !hash_equals($contents, $readBack)) {
            return [
                'ok' => false,
                'status' => basename($path) === '.htaccess'
                    ? 'sandbox_htaccess_verify_failed'
                    : 'sandbox_index_verify_failed',
            ];
        }

        return [
            'ok' => true,
            'status' => 'verified',
        ];
    }

    /**
     * @return array{ok:bool,status:string}
     */
    private static function preserveSandboxHtaccessAsBackup(string $source, string $backup): array
    {
        if (is_link($source) || file_exists($backup) || is_link($backup)) {
            return [
                'ok' => false,
                'status' => 'sandbox_htaccess_backup_unsafe',
            ];
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Exact contents must be verified after the WordPress Filesystem move.
        $originalContents = is_file($source) ? file_get_contents($source) : false;
        if (!is_string($originalContents)) {
            return [
                'ok' => false,
                'status' => 'sandbox_htaccess_backup_read_failed',
            ];
        }

        $moved = FilesystemAdapter::fromWordPress()->move($source, $backup, false);
        if (empty($moved['ok'])) {
            return [
                'ok' => false,
                'status' => 'sandbox_htaccess_backup_move_failed',
            ];
        }

        clearstatcache(false, $source);
        clearstatcache(false, $backup);
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Exact read-back verification is required before SHIELD-X creates its own sandbox .htaccess.
        $backupContents = is_file($backup) && !is_link($backup) ? file_get_contents($backup) : false;
        if (
            file_exists($source)
            || is_link($source)
            || !is_string($backupContents)
            || !hash_equals($originalContents, $backupContents)
        ) {
            return [
                'ok' => false,
                'status' => 'sandbox_htaccess_backup_verify_failed',
            ];
        }

        return [
            'ok' => true,
            'status' => 'verified',
        ];
    }

    private static function writePhpConfigFile(string $path, string $contents, int $mode): bool
    {
        if (!self::phpCodeParses($contents)) {
            return false;
        }

        $directory = dirname($path);
        $basename = basename($path);
        if ($directory === '' || $directory === '.' || $basename === '') {
            return false;
        }

        try {
            $token = bin2hex(random_bytes(8));
        } catch (\Throwable $error) {
            return false;
        }

        $temporary = rtrim(str_replace('\\', '/', $directory), '/') . '/.' . $basename . '.shieldx-' . $token . '.tmp';
        $filesystem = FilesystemAdapter::fromWordPress();
        $written = $filesystem->putContents($temporary, $contents, $mode);
        if (empty($written['ok'])) {
            return false;
        }

        $readBack = file_get_contents($temporary);
        if (!is_string($readBack) || $readBack !== $contents || !self::phpCodeParses($readBack)) {
            $filesystem->delete($temporary);

            return false;
        }

        $moved = $filesystem->move($temporary, $path, true);
        if (!empty($moved['ok'])) {
            return true;
        }

        $filesystem->delete($temporary);

        return false;
    }

    private static function phpCodeParses(string $contents): bool
    {
        if (function_exists('token_get_all') && defined('TOKEN_PARSE')) {
            try {
                token_get_all($contents, TOKEN_PARSE);

                return true;
            } catch (\ParseError $error) {
                return false;
            } catch (\Throwable $error) {
                return false;
            }
        }

        return strpos($contents, '<?') !== false;
    }

    private static function isXmlRpcPath(string $path): bool
    {
        return rtrim($path, '/') === '/xmlrpc.php' || substr($path, -11) === '/xmlrpc.php';
    }

    /**
     * @param array<string,mixed> $query
     */
    private static function isAuthorScan(string $uri, array $query): bool
    {
        if (isset($query['author']) && preg_match('/^\d+$/', (string) $query['author']) === 1) {
            return true;
        }

        return preg_match('/(?:\?|&)author=\d+(?:&|$)/i', $uri) === 1;
    }

    private static function isBotProtectionSurface(string $path): bool
    {
        return self::isProtectedAdminPath($path)
            || self::isXmlRpcPath($path)
            || self::isDirectCoreAccessPath($path)
            || self::isSensitiveFilePath($path)
            || self::isDataExecutablePath($path);
    }

    private static function isAuthenticatedRestRequest(string $path): bool
    {
        $path = self::normalizedSecurityPath($path);
        $isRest = (defined('REST_REQUEST') && REST_REQUEST)
            || strpos($path, '/wp-json/') === 0
            || $path === '/wp-json';

        return $isRest && self::hasAuthenticatedWordPressSession();
    }

    private static function isDirectCoreAccessPath(string $path): bool
    {
        return strpos($path, '/wp-admin/includes/') !== false
            || preg_match('#/wp-includes/[^/]+\.php$#i', $path) === 1
            || preg_match('#/wp-includes/js/tinymce/langs/.+\.php$#i', $path) === 1
            || strpos($path, '/wp-includes/theme-compat/') !== false;
    }

    private static function isSensitiveFilePath(string $path): bool
    {
        $path = self::normalizedSecurityPath($path);
        if (preg_match('#/(?:wp-config\.php|\.htaccess|\.htpasswd|\.user\.ini|\.env|debug\.log|composer\.(?:json|lock)|package(?:-lock)?\.json|yarn\.lock|phpunit\.xml(?:\.dist)?|readme\.html|license\.txt)$#i', $path) === 1) {
            return true;
        }

        if (preg_match('#/[^/]*?\.(?:bak|backup|old|orig|save|sql|zip|tar|gz)$#i', $path) !== 1) {
            return false;
        }

        return !self::isUploadsPath($path);
    }

    private static function isDataExecutablePath(string $path): bool
    {
        $path = self::normalizedSecurityPath((string) (preg_replace('/[\x00-\x1F\x7F]+/', '', $path) ?: ''));
        $contentPath = self::contentUrlPath();

        return preg_match('#' . preg_quote($contentPath, '#') . '/(?:uploads|cache|upgrade)/#i', $path) === 1
            && preg_match(self::EXECUTABLE_PATTERN, $path) === 1;
    }

    private static function isUploadsPath(string $path): bool
    {
        $contentPath = self::contentUrlPath();

        return strpos($path, $contentPath . '/uploads/') === 0;
    }

    private static function contentUrlPath(): string
    {
        $path = '';
        if (function_exists('content_url')) {
            $url = (string) content_url('/');
            if (function_exists('wp_parse_url')) {
                $parsed = wp_parse_url($url, PHP_URL_PATH);
                $path = is_string($parsed) ? $parsed : '';
            } elseif (preg_match('~^[a-z][a-z0-9+.-]*://[^/]+(?P<path>/.*)?$~i', $url, $matches) === 1) {
                $path = (string) ($matches['path'] ?? '');
            }
        }

        $path = self::normalizedSecurityPath($path !== '' ? $path : '/wp-content');

        return rtrim($path, '/');
    }

    private static function fileEditConfigBlock(string $restoreDefinition = ''): string
    {
        $restore = $restoreDefinition !== ''
            ? '/* SHIELD-X RESTORE DISALLOW_FILE_EDIT: ' . $restoreDefinition . " */\n"
            : '';

        return self::FILE_EDIT_MARKER_START . "\n"
            . $restore
            . "define( 'DISALLOW_FILE_EDIT', true );\n"
            . self::FILE_EDIT_MARKER_END . "\n";
    }

    private static function managedFileEditRestoreDefinition(string $contents): string
    {
        $pattern = '~^\s*/\* SHIELD-X RESTORE DISALLOW_FILE_EDIT: (?P<definition>define\s*\(\s*([\'"])DISALLOW_FILE_EDIT\2\s*,\s*(?:false|0)\s*\)\s*;) \*/\s*$~mi';
        if (preg_match($pattern, $contents, $matches) !== 1) {
            return '';
        }

        return isset($matches['definition']) ? (string) $matches['definition'] : '';
    }

    private static function replaceManagedFileEditBlock(string $contents, string $replacement): string
    {
        $pattern = '~\R?/\* BEGIN SHIELD-X DISALLOW_FILE_EDIT \*/.*?/\* END SHIELD-X DISALLOW_FILE_EDIT \*/\R?~s';
        $replacement = $replacement === '' ? '' : "\n" . $replacement;
        $updated = preg_replace($pattern, $replacement, $contents, 1);

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

    private static function insertFileEditBlock(string $contents, string $block): ?string
    {
        foreach ([
            "/* That's all, stop editing! Happy publishing. */",
            "require_once ABSPATH . 'wp-settings.php';",
            'require_once ABSPATH . "wp-settings.php";',
        ] as $anchor) {
            $position = strpos($contents, $anchor);
            if ($position !== false) {
                return substr($contents, 0, $position) . "\n" . $block . substr($contents, $position);
            }
        }

        $closingTag = strrpos($contents, '?>');
        if ($closingTag !== false) {
            return rtrim(substr($contents, 0, $closingTag)) . "\n\n" . $block . substr($contents, $closingTag);
        }

        return null;
    }

    private static function denyAs404(): void
    {
        if (function_exists('status_header')) {
            status_header(404);
        } else {
            http_response_code(404);
        }

        if (function_exists('nocache_headers')) {
            nocache_headers();
        }

        header('Content-Type: text/html; charset=utf-8', true, 404);
        echo '<!doctype html><html><head><meta charset="utf-8"><title>404 Not Found</title></head><body><h1>404 Not Found</h1></body></html>';
        exit;
    }

    private static function denyAs403(): void
    {
        if (function_exists('status_header')) {
            status_header(403);
        } else {
            http_response_code(403);
        }

        if (function_exists('nocache_headers')) {
            nocache_headers();
        }

        if (!headers_sent()) {
            header('Content-Type: text/html; charset=utf-8', true, 403);
        }

        echo '<!doctype html><html><head><meta charset="utf-8"><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1></body></html>';
        exit;
    }
}
