<?php

declare(strict_types=1);

namespace ShieldX\Admin;

defined('ABSPATH') || exit;

use ShieldX\Core\AddonRegistry;
use ShieldX\Core\Plugin;
use ShieldX\Licensing\LicenseManager;
use ShieldX\Licensing\LicenseSettings;

/**
 * Registers the small, request-wide admin hooks without loading AdminPage.
 *
 * AdminPage is intentionally large because it contains every SHIELD-X admin
 * renderer and state-changing handler. Keeping it behind this loader prevents
 * ordinary wp-admin screens from parsing it, while each explicit handler below
 * still retains the existing capability and nonce checks in AdminPage.
 */
final class AdminPageLoader
{
    private const DASHBOARD_WIDGET_ID = 'shieldx_security_trends';

    private const DASHBOARD_CONTEXTS = ['normal', 'side', 'column3', 'column4'];

    /** @var Plugin */
    private $plugin;

    /** @var AdminPage|null */
    private $page;

    public function __construct(Plugin $plugin)
    {
        $this->plugin = $plugin;
        $this->page = null;
    }

    /**
     * Registers SHIELD-X menus without loading the page renderer.
     */
    public function register(): void
    {
        $capability = self::manageCapability();

        add_menu_page(
            __('SHIELD-X', 'xerotact-shield-x-security'),
            __('SHIELD-X', 'xerotact-shield-x-security'),
            $capability,
            'shield-x',
            [$this, 'renderOverview'],
            Iconoir::dataUri('health-shield'),
            65
        );

        add_submenu_page('shield-x', __('Overview', 'xerotact-shield-x-security'), __('Overview', 'xerotact-shield-x-security'), $capability, 'shield-x', [$this, 'renderOverview']);
        add_submenu_page('shield-x', __('Setup', 'xerotact-shield-x-security'), __('Setup', 'xerotact-shield-x-security'), $capability, 'shield-x-setup', [$this, 'renderSetup']);
        add_submenu_page('shield-x', __('Firewall', 'xerotact-shield-x-security'), __('Firewall', 'xerotact-shield-x-security'), $capability, 'shield-x-waf', [$this, 'renderWaf']);
        add_submenu_page('shield-x', __('Login Security', 'xerotact-shield-x-security'), __('Login Security', 'xerotact-shield-x-security'), $capability, 'shield-x-login-security', [$this, 'renderLoginSecurity']);
        add_submenu_page('shield-x', __('Malware/Virus', 'xerotact-shield-x-security'), __('Malware/Virus', 'xerotact-shield-x-security'), $capability, 'shield-x-scan', [$this, 'renderScan']);
        add_submenu_page('shield-x', __('Backup', 'xerotact-shield-x-security'), __('Backup', 'xerotact-shield-x-security'), $capability, 'shield-x-backup', [$this, 'renderBackup']);
        add_submenu_page('shield-x', __('Hardening', 'xerotact-shield-x-security'), __('Hardening', 'xerotact-shield-x-security'), $capability, 'shield-x-hardening', [$this, 'renderHardening']);
        add_submenu_page('shield-x', __('Lockdown', 'xerotact-shield-x-security'), __('Lockdown', 'xerotact-shield-x-security'), $capability, 'shield-x-lockdown', [$this, 'renderLockdown']);
        add_submenu_page('shield-x', __('Incident/Activities', 'xerotact-shield-x-security'), __('Incident/Activities', 'xerotact-shield-x-security'), $capability, 'shield-x-activity', [$this, 'renderActivity']);
        add_submenu_page('shield-x', __('Cleanup', 'xerotact-shield-x-security'), __('Cleanup', 'xerotact-shield-x-security'), $capability, 'shield-x-log-rotation', [$this, 'renderLogRotation']);
        add_submenu_page('shield-x', __('System', 'xerotact-shield-x-security'), __('System', 'xerotact-shield-x-security'), $capability, 'shield-x-system', [$this, 'renderSystem']);
        // Preserve the private-management route without exposing it in core navigation.
        add_submenu_page(null, __('Extensions', 'xerotact-shield-x-security'), __('Extensions', 'xerotact-shield-x-security'), $capability, 'shield-x-addons', [$this, 'renderAddons']);
        add_submenu_page(null, __('WAF Access', 'xerotact-shield-x-security'), __('WAF Access', 'xerotact-shield-x-security'), $capability, 'shield-x-waf-access', [$this, 'renderWafAccess']);
        add_submenu_page(null, __('Restore Point Contents', 'xerotact-shield-x-security'), __('Restore Point Contents', 'xerotact-shield-x-security'), $capability, 'shield-x-backup-restore', [$this, 'renderRestorePoint']);

        foreach (AddonRegistry::fromWordPress()->submenus() as $submenu) {
            $callback = $submenu['callback'] ?? null;
            if (!is_callable($callback)) {
                self::logInvalidAddonSubmenuCallback((string) ($submenu['menu_slug'] ?? ''));
                continue;
            }

            add_submenu_page(
                (string) ($submenu['parent_slug'] ?? 'shield-x'),
                (string) ($submenu['page_title'] ?? ''),
                (string) ($submenu['menu_title'] ?? ''),
                (string) ($submenu['capability'] ?? $capability),
                (string) ($submenu['menu_slug'] ?? ''),
                $callback,
                (int) ($submenu['position'] ?? 90)
            );
        }
    }

    private static function logInvalidAddonSubmenuCallback(string $menuSlug): void
    {
        static $loggedMenuSlugs = [];

        $menuSlug = $menuSlug !== '' ? $menuSlug : 'unknown';
        if (isset($loggedMenuSlugs[$menuSlug])) {
            return;
        }

        $loggedMenuSlugs[$menuSlug] = true;
        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- An invalid third-party add-on callback is logged once per menu slug so site owners can diagnose the skipped submenu.
        \ShieldX\Support\ErrorLog::write(sprintf('[SHIELD-X] Skipped add-on submenu "%s": callback is not callable.', $menuSlug));
    }

    /**
     * Registers the widget. AdminPage is loaded only if WordPress renders the
     * widget for a user who can manage SHIELD-X.
     */
    public function registerDashboardWidgets(): void
    {
        if (!function_exists('wp_add_dashboard_widget') || !self::currentUserCanManageShieldX()) {
            return;
        }

        wp_add_dashboard_widget(
            self::DASHBOARD_WIDGET_ID,
            __('SHIELD-X Security Trends', 'xerotact-shield-x-security'),
            [$this, 'renderDashboardSecurityTrendsWidget']
        );
    }

    /**
     * Keeps a stale dashboard order from moving the widget into a context that
     * WordPress does not render on the Dashboard screen.
     *
     * @param mixed $order
     * @return mixed
     */
    public function normalizeDashboardWidgetOrder($order)
    {
        if (!is_array($order)) {
            return $order;
        }

        $hasValidPlacement = false;
        $invalidContexts = [];

        foreach ($order as $context => $ids) {
            if (!is_string($ids)) {
                continue;
            }

            $boxIds = array_map('trim', explode(',', $ids));
            if (!in_array(self::DASHBOARD_WIDGET_ID, $boxIds, true)) {
                continue;
            }

            if (in_array((string) $context, self::DASHBOARD_CONTEXTS, true)) {
                $hasValidPlacement = true;
            } else {
                $invalidContexts[] = $context;
            }
        }

        if ($invalidContexts === []) {
            return $order;
        }

        foreach ($invalidContexts as $context) {
            $boxIds = array_map('trim', explode(',', (string) $order[$context]));
            $boxIds = array_values(array_filter(
                $boxIds,
                static function (string $boxId): bool {
                    return $boxId !== '' && $boxId !== self::DASHBOARD_WIDGET_ID;
                }
            ));
            $order[$context] = implode(',', $boxIds);
        }

        if (!$hasValidPlacement) {
            $normalIds = isset($order['normal']) && is_string($order['normal'])
                ? array_values(array_filter(array_map('trim', explode(',', $order['normal']))))
                : [];
            $normalIds[] = self::DASHBOARD_WIDGET_ID;
            $order['normal'] = implode(',', array_unique($normalIds));
        }

        return $order;
    }

    /**
     * Retains the lightweight admin-bar status on all wp-admin screens.
     *
     * @param mixed $wpAdminBar
     */
    public function renderAdminToolbarStatus($wpAdminBar): void
    {
        if (!self::currentUserCanManageShieldX() || !is_object($wpAdminBar) || !method_exists($wpAdminBar, 'add_node')) {
            return;
        }

        $license = self::toolbarLicenseSummary();
        $statusKey = LicenseSettings::sanitizeStatus((string) ($license['status'] ?? LicenseSettings::STATUS_INACTIVE));
        $statusLabel = (string) ($license['label'] ?? LicenseSettings::statusLabel($statusKey));
        $statusClass = $statusKey === LicenseSettings::STATUS_ACTIVE ? 'is-active' : 'needs-attention';

        $wpAdminBar->add_node([
            'id' => 'shieldx-status',
            'title' => sprintf(
                '<span class="ab-icon dashicons dashicons-shield-alt" aria-hidden="true"></span><span class="ab-label">%s</span><span class="%s">%s</span>',
                esc_html__('SHIELD-X', 'xerotact-shield-x-security'),
                esc_attr('shieldx-adminbar-state shieldx-adminbar-state--' . $statusClass),
                esc_html($statusLabel)
            ),
            'href' => admin_url('admin.php?page=shield-x'),
            'meta' => [
                'class' => 'shieldx-adminbar-status-node',
                'title' => __('Open SHIELD-X', 'xerotact-shield-x-security'),
            ],
        ]);

        $coreItems = [
            'overview' => [__('Overview', 'xerotact-shield-x-security'), 'shield-x'],
            'firewall' => [__('Firewall', 'xerotact-shield-x-security'), 'shield-x-waf'],
            'activity' => [__('Incident/Activities', 'xerotact-shield-x-security'), 'shield-x-activity'],
            'system' => [__('System', 'xerotact-shield-x-security'), 'shield-x-system'],
        ];

        foreach ($coreItems as $key => $item) {
            $wpAdminBar->add_node([
                'id' => 'shieldx-' . $key,
                'parent' => 'shieldx-status',
                'title' => esc_html((string) $item[0]),
                'href' => admin_url('admin.php?page=' . (string) $item[1]),
            ]);
        }

        foreach (AddonRegistry::fromWordPress()->submenus() as $submenu) {
            $slug = (string) ($submenu['menu_slug'] ?? '');
            $title = (string) ($submenu['menu_title'] ?? '');
            $capability = (string) ($submenu['capability'] ?? self::manageCapability());
            if ($slug === '' || $title === '' || (function_exists('current_user_can') && !current_user_can($capability))) {
                continue;
            }

            $wpAdminBar->add_node([
                'id' => 'shieldx-addon-' . sanitize_key($slug),
                'parent' => 'shieldx-status',
                'title' => esc_html($title),
                'href' => admin_url('admin.php?page=' . rawurlencode($slug)),
            ]);
        }
    }

    /**
     * Enqueues assets only for known SHIELD-X screens and the dashboard widget.
     */
    public function enqueue(string $hook): void
    {
        $isCoreHook = $hook === 'index.php'
            || $hook === 'toplevel_page_shield-x'
            || strpos($hook, 'shield-x_page_shield-x-') === 0
            || strpos($hook, 'admin_page_shield-x-') === 0;
        $isPossibleAddonHook = strpos($hook, 'shield-x_page_') === 0;
        if (!$isCoreHook && !$isPossibleAddonHook) {
            return;
        }

        $allowedHooks = [
            'index.php',
            'toplevel_page_shield-x',
            'admin_page_shield-x-addons',
            'shield-x_page_shield-x-setup',
            'shield-x_page_shield-x-waf',
            'admin_page_shield-x-waf-access',
            'shield-x_page_shield-x-waf-access',
            'admin_page_shield-x-backup-restore',
            'shield-x_page_shield-x-backup-restore',
            'shield-x_page_shield-x-login-security',
            'shield-x_page_shield-x-scan',
            'shield-x_page_shield-x-backup',
            'shield-x_page_shield-x-hardening',
            'shield-x_page_shield-x-lockdown',
            'shield-x_page_shield-x-activity',
            'shield-x_page_shield-x-log-rotation',
            'shield-x_page_shield-x-system',
            'admin_page_shield-x-virtual-patches',
        ];

        if (!in_array($hook, $allowedHooks, true) && $isPossibleAddonHook) {
            foreach (AddonRegistry::fromWordPress()->submenus() as $submenu) {
                $slug = (string) ($submenu['menu_slug'] ?? '');
                if ($slug !== '') {
                    $allowedHooks[] = 'shield-x_page_' . $slug;
                }
            }
        }

        if (!in_array($hook, $allowedHooks, true)) {
            return;
        }

        if ($hook === 'index.php' && !self::currentUserCanManageShieldX()) {
            return;
        }

        if ($hook === 'index.php') {
            $dashboardScript = $this->assetPath('assets/js/dashboard-widget-loader.js');
            wp_enqueue_script(
                'shield-x-dashboard-widget-loader',
                plugins_url($dashboardScript, SHIELDX_PLUGIN_FILE),
                [],
                $this->assetVersion($dashboardScript),
                true
            );

            return;
        }

        $adminCss = $this->assetPath('assets/css/admin.css');
        $adminScript = $this->assetPath('assets/js/admin.js');

        wp_enqueue_style(
            'shield-x-admin',
            plugins_url($adminCss, SHIELDX_PLUGIN_FILE),
            [],
            $this->assetVersion($adminCss)
        );

        wp_enqueue_script(
            'shield-x-admin',
            plugins_url($adminScript, SHIELDX_PLUGIN_FILE),
            ['wp-i18n'],
            $this->assetVersion($adminScript),
            true
        );
        wp_set_script_translations('shield-x-admin', 'xerotact-shield-x-security');
    }

    /**
     * Removes competing notices only on a SHIELD-X screen without constructing
     * the full page renderer.
     */
    public function suppressForeignAdminNotices(): void
    {
        if (!$this->isShieldXAdminScreen()) {
            return;
        }

        if (!apply_filters('shieldx_suppress_foreign_admin_notices', true)) {
            return;
        }

        foreach (['admin_notices', 'all_admin_notices', 'network_admin_notices', 'user_admin_notices'] as $hook) {
            $this->suppressForeignNoticeHook($hook);
        }
    }

    public function renderOverview(): void
    {
        $this->page()->renderOverview();
    }

    public function renderSetup(): void
    {
        $this->page()->renderSetup();
    }

    public function renderAddons(): void
    {
        $this->page()->renderAddons();
    }

    public function renderWaf(): void
    {
        $this->page()->renderWaf();
    }

    public function renderWafAccess(): void
    {
        $this->page()->renderWafAccess();
    }

    public function renderLoginSecurity(): void
    {
        $this->page()->renderLoginSecurity();
    }

    public function renderScan(): void
    {
        $this->page()->renderScan();
    }

    public function renderBackup(): void
    {
        $this->page()->renderBackup();
    }

    public function renderRestorePoint(): void
    {
        $this->page()->renderRestorePoint();
    }

    public function renderHardening(): void
    {
        $this->page()->renderHardening();
    }

    public function renderLockdown(): void
    {
        $this->page()->renderLockdown();
    }

    public function renderActivity(): void
    {
        $this->page()->renderActivity();
    }

    public function renderLogRotation(): void
    {
        $this->page()->renderLogRotation();
    }

    public function renderSystem(): void
    {
        $this->page()->renderSystem();
    }

    public function renderDashboardSecurityTrendsWidget(): void
    {
        if (!self::currentUserCanManageShieldX()) {
            return;
        }

        $adminCss = $this->dashboardWidgetAssetUrl('assets/css/admin.css');
        $adminScript = $this->dashboardWidgetAssetUrl('assets/js/admin.js');
        $this->page()->renderDashboardSecurityTrendsWidget($adminCss, $adminScript);
    }

    public function ajaxDashboardOverview(): void
    {
        $this->page()->ajaxDashboardOverview();
    }

    public function ajaxAdminPageContent(): void
    {
        $this->page()->ajaxAdminPageContent();
    }

    public function ajaxOverviewCardVisibility(): void
    {
        $this->page()->ajaxOverviewCardVisibility();
    }

    public function ajaxPrepareHardeningPreflight(): void
    {
        $this->page()->ajaxPrepareHardeningPreflight();
    }

    public function saveWafSettings(): void
    {
        $this->page()->saveWafSettings();
    }

    public function wafAccessAction(): void
    {
        $this->page()->wafAccessAction();
    }

    public function clearWafAccessTable(): void
    {
        $this->page()->clearWafAccessTable();
    }

    public function clearWafHits(): void
    {
        $this->page()->clearWafHits();
    }

    public function wafHitAction(): void
    {
        $this->page()->wafHitAction();
    }

    public function saveHumanChallengeSettings(): void
    {
        $this->page()->saveHumanChallengeSettings();
    }

    public function toggleModule(): void
    {
        $this->page()->toggleModule();
    }

    public function activateLicense(): void
    {
        $this->page()->activateLicense();
    }

    public function retryLicenseActivation(): void
    {
        $this->page()->retryLicenseActivation();
    }

    public function validateLicense(): void
    {
        $this->page()->validateLicense();
    }

    public function deactivateLicense(): void
    {
        $this->page()->deactivateLicense();
    }

    public function applySetupMode(): void
    {
        $this->page()->applySetupMode();
    }

    public function activityQuickBlock(): void
    {
        $this->page()->activityQuickBlock();
    }

    public function saveUserProfilerSettings(): void
    {
        $this->page()->saveUserProfilerSettings();
    }

    public function saveHardeningSettings(): void
    {
        $this->page()->saveHardeningSettings();
    }

    public function saveLockdownSettings(): void
    {
        $this->page()->saveLockdownSettings();
    }

    public function continuePermissionFix(): void
    {
        $this->page()->continuePermissionFix();
    }

    public function saveScanSettings(): void
    {
        $this->page()->saveScanSettings();
    }

    public function runScan(): void
    {
        $this->page()->runScan();
    }

    public function continueScan(): void
    {
        $this->page()->continueScan();
    }

    public function runVulnerabilityScan(): void
    {
        $this->page()->runVulnerabilityScan();
    }

    public function prepareCoreRelease(): void
    {
        $this->page()->prepareCoreRelease();
    }

    public function continueCoreRelease(): void
    {
        $this->page()->continueCoreRelease();
    }

    public function preparePluginPackages(): void
    {
        $this->page()->preparePluginPackages();
    }

public function continuePluginPackages(): void
{
$this->page()->continuePluginPackages();
}

public function uploadPluginPackage(): void
{
$this->page()->uploadPluginPackage();
}

public function trustInstalledPluginPackage(): void
{
$this->page()->trustInstalledPluginPackage();
}

public function uploadThemePackage(): void
{
$this->page()->uploadThemePackage();
}

public function trustInstalledThemePackage(): void
{
$this->page()->trustInstalledThemePackage();
}

public function restoreCoreFile(): void
{
$this->page()->restoreCoreFile();
}

    public function quarantineScanIssue(): void
    {
        $this->page()->quarantineScanIssue();
    }

    public function restoreQuarantinedIssue(): void
    {
        $this->page()->restoreQuarantinedIssue();
    }

    public function ignoreScanIssue(): void
    {
        $this->page()->ignoreScanIssue();
    }

    public function removeScanIgnore(): void
    {
        $this->page()->removeScanIgnore();
    }

    public function repairHtaccess(): void
    {
        $this->page()->repairHtaccess();
    }

    public function bulkScanAction(): void
    {
        $this->page()->bulkScanAction();
    }

    public function trustScannerFile(): void
    {
        $this->page()->trustScannerFile();
    }

    public function recheckScannerFile(): void
    {
        $this->page()->recheckScannerFile();
    }

    public function saveComparedPluginFile(): void
    {
        $this->page()->saveComparedPluginFile();
    }

    public function cleanupConfigFile(): void
    {
        $this->page()->cleanupConfigFile();
    }

    public function remediateDatabaseIssue(): void
    {
        $this->page()->remediateDatabaseIssue();
    }

    public function restoreDatabaseBackup(): void
    {
        $this->page()->restoreDatabaseBackup();
    }

    public function recheckDatabaseIssue(): void
    {
        $this->page()->recheckDatabaseIssue();
    }

    public function createRestorePoint(): void
    {
        $this->page()->createRestorePoint();
    }

    public function continueBackup(): void
    {
        $this->page()->continueBackup();
    }

    public function uploadRestorePointGoogle(): void
    {
        $this->page()->uploadRestorePointGoogle();
    }

    public function restoreRestorePoint(): void
    {
        $this->page()->restoreRestorePoint();
    }

    public function downloadRestoreHandoff(): void
    {
        $this->page()->downloadRestoreHandoff();
    }

    public function deleteRestorePoint(): void
    {
        $this->page()->deleteRestorePoint();
    }

    public function deleteRestorePointsBefore(): void
    {
        $this->page()->deleteRestorePointsBefore();
    }

    public function createDatabaseBackup(): void
    {
        $this->page()->createDatabaseBackup();
    }

    public function deleteDatabaseBackupsBefore(): void
    {
        $this->page()->deleteDatabaseBackupsBefore();
    }

    public function saveBackupSettings(): void
    {
        $this->page()->saveBackupSettings();
    }

    public function saveFileTypeSettings(): void
    {
        $this->page()->saveFileTypeSettings();
    }

    public function saveBackupStorage(): void
    {
        $this->page()->saveBackupStorage();
    }

    public function saveGoogleDriveOAuthApp(): void
    {
        $this->page()->saveGoogleDriveOAuthApp();
    }

    public function startGoogleDriveAuth(): void
    {
        $this->page()->startGoogleDriveAuth();
    }

    public function completeGoogleDriveAuth(): void
    {
        $this->page()->completeGoogleDriveAuth();
    }

    public function disconnectGoogleDrive(): void
    {
        $this->page()->disconnectGoogleDrive();
    }

    public function testGoogleDriveConnection(): void
    {
        $this->page()->testGoogleDriveConnection();
    }

    public function saveRestorePointSchedule(): void
    {
        $this->page()->saveRestorePointSchedule();
    }

    public function restoreManualDatabaseBackup(): void
    {
        $this->page()->restoreManualDatabaseBackup();
    }

    public function restoreShadowCopy(): void
    {
        $this->page()->restoreShadowCopy();
    }

    public function confirmShadowCopy(): void
    {
        $this->page()->confirmShadowCopy();
    }

    public function confirmShadowSession(): void
    {
        $this->page()->confirmShadowSession();
    }

    public function saveLoginSecuritySettings(): void
    {
        $this->page()->saveLoginSecuritySettings();
    }

    public function revokeLoginSessions(): void
    {
        $this->page()->revokeLoginSessions();
    }

    public function exportSettings(): void
    {
        $this->page()->exportSettings();
    }

    public function importSettings(): void
    {
        $this->page()->importSettings();
    }

    public function saveLogRotationSettings(): void
    {
        $this->page()->saveLogRotationSettings();
    }

    public function saveUninstallSettings(): void
    {
        $this->page()->saveUninstallSettings();
    }

    public function scanLogRotationFiles(): void
    {
        $this->page()->scanLogRotationFiles();
    }

    public function cleanupTemporaryFiles(): void
    {
        $this->page()->cleanupTemporaryFiles();
    }

    public function revealEmergencyRecoveryUrl(): void
    {
        $this->page()->revealEmergencyRecoveryUrl();
    }

    public function regenerateEmergencyRecoveryUrl(): void
    {
        $this->page()->regenerateEmergencyRecoveryUrl();
    }

    public function enableTwoFactor(): void
    {
        $this->page()->enableTwoFactor();
    }

    public function regenerateTwoFactorCodes(): void
    {
        $this->page()->regenerateTwoFactorCodes();
    }

    public function disableTwoFactor(): void
    {
        $this->page()->disableTwoFactor();
    }

    public function disableAdminTwoFactor(): void
    {
        $this->page()->disableAdminTwoFactor();
    }

    public function requestTwoFactorReset(): void
    {
        $this->page()->requestTwoFactorReset();
    }

    public function confirmTwoFactorReset(): void
    {
        $this->page()->confirmTwoFactorReset();
    }

    private function page(): AdminPage
    {
        if (!$this->page instanceof AdminPage) {
            $this->page = new AdminPage($this->plugin);
        }

        return $this->page;
    }

    /**
     * @return array<string,mixed>
     */
    private static function toolbarLicenseSummary(): array
    {
        // License state is session-sensitive. A site-wide transient can show a
        // different result than the page header for the same administrator.
        return LicenseManager::fromWordPress()->summaryForCurrentSession();
    }

    private function assetVersion(string $relativePath): string
    {
        $file = SHIELDX_PLUGIN_DIR . ltrim($relativePath, '/\\');
        if (!is_file($file) || !is_readable($file)) {
            return SHIELDX_VERSION;
        }

        $hash = md5_file($file);

        return SHIELDX_VERSION . '.' . (is_string($hash) ? substr($hash, 0, 12) : 'asset');
    }

    private function assetPath(string $relativePath): string
    {
        if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
            return $relativePath;
        }

        $minified = preg_replace('/\.(css|js)$/', '.min.$1', $relativePath);
        if (!is_string($minified) || $minified === $relativePath) {
            return $relativePath;
        }

        $source = SHIELDX_PLUGIN_DIR . ltrim($relativePath, '/\\');
        $file = SHIELDX_PLUGIN_DIR . ltrim($minified, '/\\');
        if (!is_file($file)) {
            return $relativePath;
        }

        if (is_file($source) && (int) filemtime($file) < (int) filemtime($source)) {
            return $relativePath;
        }

        return $minified;
    }

    private function dashboardWidgetAssetUrl(string $relativePath): string
    {
        $relativePath = $this->assetPath($relativePath);
        $assetUrl = add_query_arg(
            'ver',
            $this->assetVersion($relativePath),
            plugins_url($relativePath, SHIELDX_PLUGIN_FILE)
        );

        return self::isSameOriginUrl($assetUrl, home_url('/')) ? $assetUrl : '';
    }

    private static function isSameOriginUrl(string $candidateUrl, string $siteUrl): bool
    {
        $candidate = wp_parse_url($candidateUrl);
        $site = wp_parse_url($siteUrl);
        if (!is_array($candidate) || !is_array($site)) {
            return false;
        }

        $candidateScheme = strtolower((string) ($candidate['scheme'] ?? ''));
        $siteScheme = strtolower((string) ($site['scheme'] ?? ''));
        $candidateHost = strtolower((string) ($candidate['host'] ?? ''));
        $siteHost = strtolower((string) ($site['host'] ?? ''));
        if (!in_array($candidateScheme, ['http', 'https'], true)
            || !in_array($siteScheme, ['http', 'https'], true)
            || $candidateHost === ''
            || $siteHost === '') {
            return false;
        }

        $candidatePort = self::urlPort($candidate, $candidateScheme);
        $sitePort = self::urlPort($site, $siteScheme);

        return $candidatePort > 0
            && $sitePort > 0
            && $candidateScheme === $siteScheme
            && $candidateHost === $siteHost
            && $candidatePort === $sitePort;
    }

    /**
     * @param array<string,mixed> $parts
     */
    private static function urlPort(array $parts, string $scheme): int
    {
        if (!isset($parts['port'])) {
            return $scheme === 'https' ? 443 : 80;
        }

        $port = (int) $parts['port'];

        return $port > 0 && $port <= 65535 ? $port : 0;
    }

    private static function manageCapability(): string
    {
        return defined('SHIELDX_MANAGE_CAPABILITY') ? (string) SHIELDX_MANAGE_CAPABILITY : 'manage_options';
    }

    private static function currentUserCanManageShieldX(): bool
    {
        return function_exists('current_user_can') && current_user_can(self::manageCapability());
    }

    private function isShieldXAdminScreen(): bool
    {
        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        if ($screen && isset($screen->id) && $this->isShieldXAdminScreenId((string) $screen->id)) {
            return true;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The value only selects whether notices are hidden; it does not mutate state.
        if (!isset($_GET['page']) || !is_scalar($_GET['page'])) {
            return false;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The value only selects whether notices are hidden; it does not mutate state.
        $page = sanitize_key((string) wp_unslash($_GET['page']));

        return $page === 'shield-x' || strpos($page, 'shield-x-') === 0;
    }

    private function isShieldXAdminScreenId(string $screenId): bool
    {
        return $screenId === 'toplevel_page_shield-x'
            || preg_match('/(?:^|_)page_shield-x(?:-.+)?$/', $screenId) === 1
            || preg_match('/^shield-x(?:-.+)?$/', $screenId) === 1;
    }

    private function suppressForeignNoticeHook(string $hook): void
    {
        global $wp_filter;

        if (!isset($wp_filter[$hook]) || !is_object($wp_filter[$hook]) || !isset($wp_filter[$hook]->callbacks) || !is_array($wp_filter[$hook]->callbacks)) {
            return;
        }

        $removals = [];
        foreach ($wp_filter[$hook]->callbacks as $priority => $callbacks) {
            if (!is_array($callbacks)) {
                continue;
            }

            foreach ($callbacks as $callback) {
                $function = is_array($callback) && array_key_exists('function', $callback) ? $callback['function'] : null;
                if ($this->isShieldXNoticeCallback($function)) {
                    continue;
                }

                $removals[] = [
                    'callback' => $function,
                    'priority' => (int) $priority,
                ];
            }
        }

        foreach ($removals as $removal) {
            remove_action($hook, $removal['callback'], $removal['priority']);
        }
    }

    /**
     * @param mixed $callback
     */
    private function isShieldXNoticeCallback($callback): bool
    {
        if (is_string($callback)) {
            return strpos(ltrim($callback, '\\'), 'ShieldX\\') === 0
                || strpos($callback, 'shieldx_') === 0;
        }

        if (is_array($callback) && isset($callback[0])) {
            $owner = is_object($callback[0]) ? get_class($callback[0]) : (string) $callback[0];

            return strpos(ltrim($owner, '\\'), 'ShieldX\\') === 0
                || strpos($owner, 'ShieldX') === 0;
        }

        if ($callback instanceof \Closure) {
            return false;
        }

        if (is_object($callback)) {
            $owner = get_class($callback);

            return strpos(ltrim($owner, '\\'), 'ShieldX\\') === 0
                || strpos($owner, 'ShieldX') === 0;
        }

        return false;
    }
}
