<?php
/**
 * DevDome Tools — the Suite Hub (v1).
 *
 * The always-present "DevDome Tools → Overview" screen: a registry-driven list of every
 * DevDome plugin (installed + available), live status tiles, an aggregated health band,
 * and the seams for reporting + a Site-Monitor host-takeover. It lives in
 * devdome-core so it loads ONCE (highest version across the suite) and works with any
 * subset of plugins — including just one.
 *
 * HARD RULE: render is READ-ONLY. Provider callbacks (`tiles`/`health`) read a small
 * pre-computed option/transient blob ONLY — never a DB query or HTTP call on render.
 * No cron, no data collection here; heavy providers (Site Monitor) compute on their own
 * cron and write `<prefix>_hub_summary` (e.g. devdsame_hub_summary) (autoload off).
 */

defined('ABSPATH') || exit;

if (!defined('DEVDCOREV1_TOOLS_MENU_SLUG')) {
    define('DEVDCOREV1_TOOLS_MENU_SLUG', 'devdcorev1-tools');
}

/* ------------------------------------------------------------------ catalog */

/**
 * The ONE hardcoded list — marketing data for plugins that may NOT be installed.
 * Installed plugins self-register (§ registry) and their live descriptor always wins;
 * the catalog only ever fills gaps + powers the "available to install" upsell cards.
 */
function devdcorev1_hub_catalog()
{
    // wporg_slug: set ONLY for plugins published on WordPress.org (enables one-click Install).
    // listed: public-catalog flag — an entry only renders as an "available" promo tile when true
    // (installed plugins always show). Flip to true when a plugin is released; never delete entries.
    // Empty => the card shows "Get it →" (link out to devdome.com) only. plugin_file: folder/main.php.
    // Canonical suite order (position): insight -> protection -> money/links -> cleanup -> backup.
    $list = array(
        'devdome-analytics'        => array('name' => 'Analytics',        'icon' => 'dashicons-chart-area',  'desc' => 'Privacy-first traffic analytics with real, bot-filtered stats.',            'get_url' => 'https://devdome.com/analytics',        'position' => 10,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-analytics/devdome-analytics.php'),
        'devdome-site-monitor'     => array('name' => 'Site Monitor',     'icon' => 'dashicons-heart',       'desc' => 'Uptime, speed, security, SEO &amp; affiliate health in one dashboard.',     'get_url' => 'https://devdome.com/site-monitor',     'position' => 20,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-site-monitor/devdome-site-monitor.php'),
        'devdome-bot-protection'   => array('name' => 'Bot Protection',   'icon' => 'dashicons-shield',      'desc' => 'Block fake clicks &amp; bot traffic so your stats stay real.',               'get_url' => 'https://devdome.com/bot-protection',   'position' => 30,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-bot-protection/devdome-bot-protection.php'),
        'devdome-security-scanner' => array('name' => 'Security Scanner', 'icon' => 'dashicons-shield-alt',  'desc' => 'Scan for malware, vulnerabilities &amp; hardening gaps.',                    'get_url' => 'https://devdome.com/security-scanner', 'position' => 40,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-security-scanner/devdome-security-scanner.php'),
        'devdome-redirect-manager' => array('name' => 'Redirect Manager', 'icon' => 'dashicons-randomize',   'desc' => 'Rule-based redirects &amp; smart affiliate links with per-rule stats.',              'get_url' => 'https://devdome.com/redirect-manager', 'position' => 50,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-redirect-manager/devdome-redirect-manager.php'),
        'devdome-affiliate-manager'=> array('name' => 'Affiliate Manager','icon' => 'dashicons-admin-links', 'desc' => 'Sitewide affiliate tags, geo-routing, link scanning &amp; click stats.',      'get_url' => 'https://devdome.com/affiliate-manager','position' => 60,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-affiliate-manager/devdome-affiliate-manager.php'),
        'devdome-product-importer' => array('name' => 'Product Importer', 'icon' => 'dashicons-cart',        'desc' => 'Import Amazon products into WooCommerce with AI content &amp; transit links.', 'get_url' => 'https://devdome.com/product-importer', 'position' => 70,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-product-importer/devdome-product-importer.php'),
        'devdome-safe-media-cleaner'    => array('name' => 'Safe Media Cleaner', 'icon' => 'dashicons-format-image','desc' => 'Find &amp; remove unused media to reclaim disk space.',                      'get_url' => 'https://devdome.com/media-cleaner',    'position' => 80,  'wporg_slug' => '', 'listed' => true, 'plugin_file' => 'devdome-safe-media-cleaner/devdome-safe-media-cleaner.php'),
        'devdome-admin-cleaner'    => array('name' => 'Admin Cleaner',    'icon' => 'dashicons-hidden',      'desc' => 'Hide admin nags, declutter the dashboard &amp; create safer client screens.', 'get_url' => 'https://devdome.com/admin-cleaner',    'position' => 90,  'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-admin-cleaner/devdome-admin-cleaner.php'),
        'devdome-backup-migration' => array('name' => 'Backup &amp; Recovery', 'icon' => 'dashicons-backup', 'desc' => 'Reversible updates: a restore point, verify &amp; auto-rollback on every update.', 'get_url' => 'https://devdome.com/backup-migration', 'position' => 100, 'wporg_slug' => '', 'listed' => false, 'plugin_file' => 'devdome-backup-migration/devdome-backup-migration.php'),
    );
    // Filterable so the "available" list can later come from api.devdome.com (this stays the offline fallback).
    return apply_filters('devdcorev1_hub_catalog', $list);
}

/** Health-score weight per slug — HUB-owned (a provider can't vote up its own importance). */
function devdcorev1_hub_weights()
{
    return array(
        'devdome-site-monitor' => 40,
        'devdome-bot-protection' => 20,
        'devdome-analytics' => 10,
        'devdome-redirect-manager' => 10,
        'devdome-affiliate-manager' => 10,
        'devdome-product-importer' => 10,
    );
}

/* ----------------------------------------------------------------- registry */

/**
 * The merged, normalized, sorted plugin list driving the Overview. Live descriptors
 * (`devdcorev1_suite_register` filter) win per key; the catalog fills gaps and adds the
 * not-installed entries. Cap-gated, position-then-name sorted, resolved tiles attached.
 */
function devdcorev1_hub_registry()
{
    static $cache = null;
    if ($cache !== null) {
        return $cache;
    }

    $live = apply_filters('devdcorev1_suite_register', array());
    if (!is_array($live)) {
        $live = array();
    }
    $catalog = devdcorev1_hub_catalog();

    $slugs = array_unique(array_merge(array_keys($catalog), array_keys($live)));
    $rows = array();
    foreach ($slugs as $slug) {
        $cat  = (isset($catalog[$slug]) && is_array($catalog[$slug])) ? $catalog[$slug] : array();
        $desc = (isset($live[$slug]) && is_array($live[$slug])) ? $live[$slug] : null;
        $installed = ($desc !== null);
        $d = $installed ? array_merge($cat, $desc) : $cat; // live wins per key, catalog fills gaps

        // Cap-gate: hide a card the current user can't open.
        $cap = isset($d['cap']) ? (string) $d['cap'] : 'manage_options';
        if ($installed && !current_user_can($cap)) {
            continue;
        }

        $rows[] = array(
            'slug'      => (string) $slug,
            'name'      => isset($d['name']) ? (string) $d['name'] : (string) $slug,
            'desc'      => isset($d['desc']) ? (string) $d['desc'] : '',
            'icon'      => isset($d['icon']) ? (string) $d['icon'] : 'dashicons-screenoptions',
            'position'  => isset($d['position']) ? (int) $d['position'] : 500, // never 0 (would jump above Overview)
            'installed' => $installed,
            'version'   => isset($d['version']) ? (string) $d['version'] : '',
            'page'      => isset($d['page']) ? (string) $d['page'] : '',
            'get_url'   => isset($d['get_url']) ? (string) $d['get_url'] : 'https://devdome.com/',
            'tiles'     => $installed ? devdcorev1_hub_resolve_tiles($d) : array(),
            'listed'    => !empty($d['listed']),
            'plugin_file' => isset($d['plugin_file']) ? (string) $d['plugin_file'] : '', // folder/main.php — powers the per-card Disable link
        );
    }

    usort($rows, function ($a, $b) {
        if ($a['position'] !== $b['position']) {
            return $a['position'] - $b['position'];
        }
        return strcasecmp($a['name'], $b['name']);
    });

    $cache = $rows;
    return $cache;
}

/** Resolve a descriptor's `tiles` callback safely (read-only; a throw degrades to no tiles). */
function devdcorev1_hub_resolve_tiles($d)
{
    if (empty($d['tiles']) || !is_callable($d['tiles'])) {
        return array();
    }
    try {
        $tiles = call_user_func($d['tiles']);
    } catch (\Throwable $e) {
        return array();
    }
    if (!is_array($tiles)) {
        return array();
    }
    $out = array();
    foreach ($tiles as $t) {
        if (!is_array($t)) {
            continue;
        }
        $out[] = array(
            'label' => isset($t['label']) ? (string) $t['label'] : '',
            'value' => array_key_exists('value', $t) ? $t['value'] : null, // null => grey "not connected"
            'unit'  => isset($t['unit']) ? (string) $t['unit'] : '',
            'fmt'   => isset($t['fmt']) ? (string) $t['fmt'] : 'text',
            'state' => isset($t['state']) ? (string) $t['state'] : 'idle',
            'href'  => isset($t['href']) ? (string) $t['href'] : '',
        );
    }
    return $out;
}

/** Format a tile value for display. null/'' => grey em-dash; unknown fmt => plain text. */
function devdcorev1_hub_fmt($value, $fmt)
{
    if ($value === null || $value === '') {
        return '—';
    }
    switch ($fmt) {
        case 'int':
            return number_format_i18n((int) $value);
        case 'pct':
            return (string) (0 + $value) . '%';
        case 'money':
            return '$' . number_format_i18n((float) $value, 2);
        case 'ms':
            return (string) (int) $value . ' ms';
        default:
            return (string) $value;
    }
}

/* ------------------------------------------------------------------- health */

/**
 * Aggregated health. Weighted mean over ONLY providers that supply a numeric score;
 * none present => null (rendered as "—" + install tip, never a fabricated number).
 * v1 ships NO `health` callbacks, so this returns null by design.
 */
function devdcorev1_hub_health()
{
    $live = apply_filters('devdcorev1_suite_register', array());
    if (!is_array($live)) {
        $live = array();
    }
    $weights = devdcorev1_hub_weights();
    $sum = 0.0;
    $wsum = 0.0;
    $issues = array();
    $scorers = array();    // slugs that supplied a numeric score
    $scope_label = '';     // the single-provider scope label (e.g. "Protection")

    foreach ($live as $slug => $d) {
        if (!is_array($d) || empty($d['health']) || !is_callable($d['health'])) {
            continue;
        }
        try {
            $h = call_user_func($d['health']);
        } catch (\Throwable $e) {
            continue;
        }
        if (!is_array($h)) {
            continue;
        }
        if (isset($h['score']) && $h['score'] !== null) {
            $w = isset($weights[$slug]) ? (float) $weights[$slug] : 5.0; // unknown slug => small weight
            $sum += ((float) $h['score']) * $w;
            $wsum += $w;
            $scorers[] = (string) $slug;
            $scope_label = isset($h['scope_label']) ? (string) $h['scope_label'] : (isset($d['name']) ? (string) $d['name'] : (string) $slug);
        }
        if (!empty($h['issues']) && is_array($h['issues'])) {
            foreach ($h['issues'] as $iss) {
                if (is_array($iss)) {
                    $iss['_slug'] = (string) $slug;
                    $issues[] = $iss;
                }
            }
        }
    }

    $score = $wsum > 0 ? (int) round($sum / $wsum) : null;
    // Bare "Site Health" (whole-site) is reserved for when a true whole-site provider
    // (Site Monitor's WP-Health monitor) contributes — never fabricated from the tools alone.
    // 1 tool => its own scope ("Protection: 80"); 2+ tools => a qualified "DevDome health" aggregate.
    $has_sm = in_array('devdome-site-monitor', $scorers, true);
    if ($score === null || $has_sm) {
        $label = 'Site Health';
    } elseif (count($scorers) === 1 && $scope_label !== '') {
        $label = $scope_label;
    } else {
        $label = 'DevDome health';
    }

    return array(
        'score'  => $score,
        'issues' => $issues,
        'label'  => $label,
        'scoped' => ($score !== null && !$has_sm), // still "install Site Monitor for whole-site" until SM exists
    );
}

/* --------------------------------------------------------------------- menu */

/**
 * Force the suite submenus into the canonical product order, regardless of plugin load order.
 * Runs after every plugin has registered (priority 999). Keyed by submenu page-slug; unknown
 * slugs sort to the end. The Dashboard (the parent slug) always stays first.
 */
function devdcorev1_hub_submenu_rank($slug, $parent)
{
    if ($slug === $parent) {
        return 0; // Dashboard (the parent slug, shown first)
    }
    // Token match on the page-slug so suffixes (-dashboard, -settings, sub-pages) still sort right.
    // Checked in order; first token found wins. The product order is the canonical suite order.
    $tokens = array(
        'analytics'      => 10,
        'site-monitor'   => 20,
        'bot-protection' => 30,
        'security'       => 40,
        'redirect'       => 50,
        'rm-'            => 50,
        'aff'            => 60,
        'importer'       => 70,
        'media'          => 80,
        'admin-cleaner'  => 90,
        'backup'         => 100,
    );
    foreach ($tokens as $token => $rank) {
        if (strpos($slug, $token) !== false) {
            return $rank;
        }
    }
    return 500; // unknown (e.g. a leftover Account page) sorts to the end
}

function devdcorev1_hub_reorder_submenus()
{
    global $submenu;
    $parent = defined('DEVDCOREV1_TOOLS_MENU_SLUG') ? DEVDCOREV1_TOOLS_MENU_SLUG : 'devdcorev1-tools';
    if (empty($submenu[$parent]) || !is_array($submenu[$parent])) {
        return;
    }
    usort($submenu[$parent], function ($a, $b) use ($parent) {
        $ra = devdcorev1_hub_submenu_rank((string) $a[2], $parent);
        $rb = devdcorev1_hub_submenu_rank((string) $b[2], $parent);
        if ($ra === $rb) {
            return strcmp((string) $a[0], (string) $b[0]);
        }
        return $ra - $rb;
    });
}
add_action('admin_menu', 'devdcorev1_hub_reorder_submenus', 999);

/** Enqueue the self-contained, .dd-app-scoped hub styles on the Overview screen ONLY. */
function devdcorev1_hub_enqueue($hook)
{
    if ($hook !== 'toplevel_page_' . DEVDCOREV1_TOOLS_MENU_SLUG) {
        return;
    }
    $css = __DIR__ . '/assets/hub.css';
    $ver = file_exists($css) ? filemtime($css) : (defined('DEVDCOREV1_VERSION') ? DEVDCOREV1_VERSION : '1.2.0');
    wp_enqueue_style('devdcorev1-hub', plugins_url('assets/hub.css', __FILE__), array('dashicons'), $ver);
}
add_action('admin_enqueue_scripts', 'devdcorev1_hub_enqueue');

/** Dismiss the "Connect to DevDome" strip without connecting (N6). */
function devdcorev1_hub_handle_dismiss_connect()
{
    if (empty($_GET['devdcorev1_hub_dismiss_connect']) || !current_user_can('manage_options')) {
        return;
    }
    check_admin_referer('devdcorev1_hub_dismiss');
    update_option('devdcorev1_hub_connect_dismissed', 1, false);
    wp_safe_redirect(add_query_arg(array('page' => DEVDCOREV1_TOOLS_MENU_SLUG), admin_url('admin.php')));
    exit;
}
add_action('admin_init', 'devdcorev1_hub_handle_dismiss_connect');

/* ------------------------------------------------------------------- render */

/**
 * The Overview entry point. Host-takeover seam: Site Monitor (later) may claim the screen
 * via `devdcorev1_suite_hub_renderer` and skin it into the full "Site Health" dashboard. The
 * host call is wrapped — a throwing host falls back to the default renderer, never a WSOD.
 * The menu shim delegates to this when devdome-core 1.2.0+ is present.
 */
function devdcorev1_hub_render_overview()
{
    $host = apply_filters('devdcorev1_suite_hub_renderer', null);
    if (is_callable($host)) {
        try {
            call_user_func($host);
            return;
        } catch (\Throwable $e) {
            // fall through to the default Overview
        }
    }
    devdcorev1_hub_render_default();
}

/** Core's registry-driven Overview (the default v1 hub). */
function devdcorev1_hub_render_default()
{
    $rows   = devdcorev1_hub_registry();
    $health = devdcorev1_hub_health();

    $active    = array_values(array_filter($rows, function ($r) { return $r['installed']; }));
    $not_inst  = array_values(array_filter($rows, function ($r) { return !$r['installed']; }));

    // Split the not-registered catalog entries into INACTIVE (present on disk but off) vs AVAILABLE (not installed).
    if (!function_exists('is_plugin_active')) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }
    $inactive  = array();
    $available = array();
    foreach ($not_inst as $r) {
        $pf = $r['plugin_file'];
        if ($pf && file_exists(WP_PLUGIN_DIR . '/' . $pf) && !is_plugin_active($pf)) {
            $inactive[] = $r;
        } elseif (!empty($r['listed'])) {
            $available[] = $r; // unreleased plugins stay out of the public promo list
        }
    }

    // Real WP update map (devdome-* only), keyed by plugin file.
    $updates = devdcorev1_hub_plugin_updates();

    // Regroup health issues back under their plugin slug (each plugin owns its own issues).
    $issues_by_slug = array();
    if (!empty($health['issues']) && is_array($health['issues'])) {
        foreach ($health['issues'] as $iss) {
            if (is_array($iss) && !empty($iss['_slug'])) {
                $issues_by_slug[(string) $iss['_slug']][] = $iss;
            }
        }
    }

    // Counts for the summary band.
    $active_n = count($active);
    $issue_n  = is_array($health['issues']) ? count($health['issues']) : 0;
    $update_n = 0;
    foreach ($active as $r) {
        if ($r['plugin_file'] && isset($updates[$r['plugin_file']])) {
            $update_n++;
        }
    }
    $score = $health['score'];
    $ring_pct = ($score === null) ? 0 : max(0, min(100, (int) $score));
    $ring_off = 213.6 - (213.6 * $ring_pct / 100); // r=34 circumference ~213.6

    // Connect state (server-verified via the site token, cached — never bare local options)
    // + one-click OAuth URL (Analytics handles the ?dd_connect= return; ID form is the fallback).
    $conn_state        = devdcorev1_connection_state();
    $connected         = !empty($conn_state['ok']);
    $connect_dismissed = (bool) get_option('devdcorev1_hub_connect_dismissed', 0);
    $account_url = rtrim((string) apply_filters('devdcorev1_account_url', 'https://devdome.com'), '/');
    $connect_url = $account_url . '/connect/?' . http_build_query(array(
        'site'   => (string) wp_parse_url(home_url(), PHP_URL_HOST),
        'return' => admin_url('admin.php?page=' . DEVDCOREV1_TOOLS_MENU_SLUG),
        'state'  => wp_create_nonce('devdcorev1_hub_connect'),
    ));
    $dismiss_url = wp_nonce_url(add_query_arg(array('page' => DEVDCOREV1_TOOLS_MENU_SLUG, 'devdcorev1_hub_dismiss_connect' => 1), admin_url('admin.php')), 'devdcorev1_hub_dismiss');

    $can_deactivate = current_user_can('activate_plugins');

    // Reusable inline SVGs.
    $ic_arrow  = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>';
    $ic_update = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>';
    $ic_power  = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.8 0"/></svg>';
    $ic_dl     = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>';
    $ic_check  = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
    $ic_caret  = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>';
    ?>
    <div class="dd-app">
        <?php
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- returns fully-escaped HTML.
        if (function_exists('devdcorev1_hub_install_notice')) { echo devdcorev1_hub_install_notice(); }
        ?>
        <div class="ddh">

            <!-- header -->
            <div class="ddh-head">
                <span class="ddh-brand">DD</span>
                <div class="ddh-htt">
                    <h1>DevDome</h1>
                    <p>Your DevDome plugin suite, all in one place &mdash; open any tool and see what needs attention.</p>
                </div>
            </div>

            <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only notice flag set by our own redirect; no data is processed.
            if (isset($_GET['ddacct']) && 'disconnected' === $_GET['ddacct']) : ?>
                <div style="margin:0 0 14px;padding:11px 16px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;color:#374151;font-weight:600;">Disconnected. This site is no longer linked to a DevDome account.</div>
            <?php endif; ?>

            <!-- connect -->
            <?php if ($connected) : ?>
                <div class="ddh-conn is-on">
                    <span class="ddh-ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg></span>
                    <div class="ddh-cbody"><strong>Connected to your DevDome account</strong><span><?php echo esc_html($conn_state['email'] !== '' ? 'Signed in as ' . $conn_state['email'] . ($conn_state['account_id'] !== '' ? ' (' . $conn_state['account_id'] . ').' : '.') : 'Email alerts are connected to your DevDome account.'); ?></span></div>
                    <form method="post" action="" style="margin:0;">
                        <?php wp_nonce_field('devdcorev1_account', '_ddacct'); ?>
                        <button type="submit" name="devdcorev1_disconnect" value="1" class="ddh-btn ddh-btn-ghost">Disconnect</button>
                    </form>
                </div>
            <?php elseif (!$connect_dismissed) : ?>
                <div class="ddh-conn">
                    <span class="ddh-ci">DD</span>
                    <div class="ddh-cbody">
                        <strong>Connect this site to your DevDome account</strong>
                        <span>Connect a free DevDome account to get optional email alerts for this site.</span>
                    </div>
                    <div class="ddh-cctl">
                        <div class="ddh-cpanel">
                            <a class="ddh-btn ddh-btn-solid ddh-cgo" href="<?php echo esc_url($connect_url); ?>">Connect Via DevDome Account</a>
                            <p class="ddh-chint">Opens devdome.com to sign in, then links this site.</p>
                        </div>
                    </div>
                    <a class="ddh-cx" title="Dismiss" href="<?php echo esc_url($dismiss_url); ?>">&times;</a>
                </div>
            <?php endif; ?>

            <!-- summary -->
            <div class="ddh-sum">
                <div class="ddh-sum-score">
                    <div class="ddh-ring">
                        <svg width="80" height="80" viewBox="0 0 80 80">
                            <circle cx="40" cy="40" r="34" fill="none" stroke="#e8eef9" stroke-width="9"/>
                            <?php if ($score !== null) : ?>
                            <circle cx="40" cy="40" r="34" fill="none" stroke="#2563eb" stroke-width="9" stroke-linecap="round" stroke-dasharray="213.6" stroke-dashoffset="<?php echo esc_attr($ring_off); ?>"/>
                            <?php endif; ?>
                        </svg>
                        <div class="ddh-ring-txt"><b><?php echo $score === null ? '&mdash;' : (int) $score; ?></b><span>Health</span></div>
                    </div>
                    <div class="ddh-sum-label">
                        <h2>Suite health</h2>
                        <p><?php if ($score === null) : ?>Install <strong style="color:#2563eb;">Site Monitor</strong> for a full score<?php else : ?>From your <span class="gd"><?php echo (int) $active_n; ?> active</span> plugins<?php endif; ?></p>
                    </div>
                </div>
                <div class="ddh-kpis">
                    <div class="ddh-kpi">
                        <span class="ddh-kpi-ic good"><?php echo $ic_check; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
                        <span class="ddh-kpi-tx"><b><?php echo (int) $active_n; ?></b><span>Active</span></span>
                    </div>
                    <div class="ddh-kpi">
                        <span class="ddh-kpi-ic warn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/></svg></span>
                        <span class="ddh-kpi-tx"><b class="<?php echo $issue_n ? 'warn' : ''; ?>"><?php echo (int) $issue_n; ?></b><span>Issues to review</span></span>
                    </div>
                    <div class="ddh-kpi">
                        <span class="ddh-kpi-ic blue"><?php echo $ic_update; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
                        <span class="ddh-kpi-tx"><b><?php echo (int) $update_n; ?></b><span>Updates</span></span>
                    </div>
                </div>
            </div>

            <!-- active -->
            <div class="ddh-sec"><h3>Active</h3><span class="ddh-count"><?php echo (int) $active_n; ?></span>
                <?php if ($update_n > 0) :
                    $upd_all = wp_nonce_url(self_admin_url('update-core.php'), ''); ?>
                    <a class="ddh-btn ddh-btn-ghost ddh-btn-sm ddh-updall" href="<?php echo esc_url(self_admin_url('update-core.php')); ?>"><?php echo $ic_update; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>Update all (<?php echo (int) $update_n; ?>)</a>
                <?php endif; ?>
            </div>
            <?php if (empty($active)) : ?>
                <div class="ddh-empty">No DevDome plugins active yet &mdash; add one from <strong>More tools</strong> below.</div>
            <?php else : ?>
            <div class="ddh-list">
                <?php foreach ($active as $r) :
                    $r_issues = isset($issues_by_slug[$r['slug']]) ? $issues_by_slug[$r['slug']] : array();
                    $has = !empty($r_issues);
                    $pf  = $r['plugin_file'];
                    $new_ver = ($pf && isset($updates[$pf])) ? $updates[$pf] : '';
                    $open_url = $r['page'] ? admin_url('admin.php?page=' . $r['page']) : '';
                    $upd_url  = ($pf && $new_ver !== '') ? wp_nonce_url(self_admin_url('update.php?action=upgrade-plugin&plugin=' . rawurlencode($pf)), 'upgrade-plugin_' . $pf) : '';
                    ?>
                    <?php if ($has) : ?><details class="ddh-row"><summary class="ddh-rsum"><?php else : ?><div class="ddh-row"><div class="ddh-rsum"><?php endif; ?>
                        <span class="ddh-logo"><?php echo devdcorev1_hub_logo_svg($r['slug']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
                        <div class="ddh-rid">
                            <div class="ddh-rname"><?php echo esc_html($r['name']); ?></div>
                            <div class="ddh-rver">v<?php echo esc_html($r['version'] ? $r['version'] : '1.0'); ?><?php if ($new_ver !== '') : ?> <span class="ddh-up">&rarr; <?php echo esc_html($new_ver); ?></span><?php if ($upd_url) : ?><a class="ddh-upbtn" href="<?php echo esc_url($upd_url); ?>" onclick="event.stopPropagation()"><?php echo $ic_update; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>Update</a><?php endif; ?><?php endif; ?></div>
                        </div>
                        <?php if ($has) : ?>
                            <span class="ddh-st warn"><span class="dot"></span><?php echo (int) count($r_issues); ?> issue<?php echo count($r_issues) === 1 ? '' : 's'; ?><span class="caret"><?php echo $ic_caret; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span></span>
                        <?php else : ?>
                            <span class="ddh-st ok"><?php echo $ic_check; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>No issues</span>
                        <?php endif; ?>
                        <?php if ($open_url) : ?><a class="ddh-btn ddh-btn-ghost" href="<?php echo esc_url($open_url); ?>" onclick="event.stopPropagation()">Open <?php echo $ic_arrow; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a><?php endif; ?>
                    <?php if ($has) : ?></summary><div class="ddh-drop">
                        <?php foreach ($r_issues as $iss) : ?>
                            <div class="ddh-iss"><b><?php echo esc_html(isset($iss['problem']) ? $iss['problem'] : 'Needs attention'); ?></b><?php if (!empty($iss['fix'])) : ?><span><?php echo esc_html($iss['fix']); ?></span><?php elseif (!empty($iss['why_it_matters'])) : ?><span><?php echo esc_html($iss['why_it_matters']); ?></span><?php endif; ?></div>
                        <?php endforeach; ?>
                    </div></details><?php else : ?></div></div><?php endif; ?>
                <?php endforeach; ?>
            </div>
            <?php endif; ?>

            <!-- inactive -->
            <?php if (!empty($inactive)) : ?>
                <div class="ddh-sec"><h3>Inactive</h3><span class="ddh-count"><?php echo (int) count($inactive); ?></span></div>
                <div class="ddh-list">
                    <?php foreach ($inactive as $r) :
                        $pf = $r['plugin_file'];
                        $act_url = ($pf && $can_deactivate) ? wp_nonce_url(self_admin_url('plugins.php?action=activate&plugin=' . rawurlencode($pf)), 'activate-plugin_' . $pf) : '';
                        ?>
                        <div class="ddh-row inact"><div class="ddh-rsum">
                            <span class="ddh-logo"><?php echo devdcorev1_hub_logo_svg($r['slug']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
                            <div class="ddh-rid"><div class="ddh-rname"><?php echo esc_html($r['name']); ?></div><?php if ($r['version']) : ?><div class="ddh-rver">v<?php echo esc_html($r['version']); ?></div><?php endif; ?></div>
                            <span class="ddh-st off"><span class="odot"></span>Inactive</span>
                            <?php if ($act_url) : ?><a class="ddh-btn ddh-btn-solid" href="<?php echo esc_url($act_url); ?>"><?php echo $ic_power; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>Activate</a><?php endif; ?>
                        </div></div>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>

            <!-- available -->
            <?php if (!empty($available)) : ?>
                <div class="ddh-sec"><h3>More tools to add</h3><span class="ddh-count"><?php echo (int) count($available); ?></span></div>
                <div class="ddh-list">
                    <?php foreach ($available as $r) : ?>
                        <div class="ddh-row avail"><div class="ddh-rsum">
                            <span class="ddh-logo"><?php echo devdcorev1_hub_logo_svg($r['slug']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
                            <div class="ddh-rid"><div class="ddh-rname"><?php echo esc_html($r['name']); ?></div><div class="ddh-rsub"><?php echo esc_html(wp_strip_all_tags($r['desc'])); ?></div></div>
                            <span class="ddh-st off"><span class="odot"></span>Not installed</span>
                            <?php
                            if (function_exists('devdcorev1_hub_install_actions')) {
                                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc_url-built anchor HTML.
                                echo devdcorev1_hub_install_actions($r);
                            } else { ?>
                                <a class="ddh-btn ddh-btn-solid" href="<?php echo esc_url($r['get_url']); ?>" target="_blank" rel="noopener"><?php echo $ic_dl; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>Get it</a>
                            <?php } ?>
                        </div></div>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>

        </div>

    </div>
    <?php
}

/** Hub screen assets ride wp_add_inline_style()/wp_add_inline_script() on handle-only registrations. */
function devdcorev1_hub_enqueue_assets($hook)
{
    $slug = defined('DEVDCOREV1_TOOLS_MENU_SLUG') ? DEVDCOREV1_TOOLS_MENU_SLUG : 'devdcorev1-tools';
    if ('toplevel_page_' . $slug !== $hook) {
        return;
    }
    $ver = defined('DEVDCOREV1_VERSION') ? DEVDCOREV1_VERSION : '1.0';
    wp_register_style('devdcorev1-hub', false, array(), $ver);
    wp_enqueue_style('devdcorev1-hub');
    wp_add_inline_style('devdcorev1-hub', devdcorev1_hub_inline_css());
    wp_register_script('devdcorev1-hub', false, array(), $ver, true);
    wp_enqueue_script('devdcorev1-hub');
    wp_add_inline_script('devdcorev1-hub', devdcorev1_hub_inline_js());
}
add_action('admin_enqueue_scripts', 'devdcorev1_hub_enqueue_assets');

/** The hub Overview stylesheet (scoped to .dd-app / .ddh-*). */
function devdcorev1_hub_inline_css()
{
    return <<<'CSS'
        .dd-app{background:#f4f6fa;font-family:Inter,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:#0f172a;min-height:100vh;margin:0 0 0 -20px;padding:0}
        .dd-app .ddh{max-width:1000px;margin:0;padding:30px 26px 64px}
        .dd-app .ddh *{box-sizing:border-box}
        .dd-app .ddh-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:12.5px;font-weight:600;border-radius:9px;padding:7px 14px;text-decoration:none;flex:none;cursor:pointer;line-height:1;border:1px solid transparent;white-space:nowrap;transition:.12s}
        .dd-app .ddh-btn svg{width:14px;height:14px}
        .dd-app .ddh-btn-ghost{color:#2563eb;background:#eaf1ff;border-color:#cfe0ff}
        .dd-app .ddh-btn-ghost:hover{background:#2563eb;color:#fff;border-color:#2563eb}
        .dd-app .ddh-btn-solid{color:#fff;background:#2563eb;border-color:#2563eb;box-shadow:0 4px 10px -3px rgba(37,99,235,.5)}
        .dd-app .ddh-btn-solid:hover{background:#1d4ed8;border-color:#1d4ed8;color:#fff}
        .dd-app .ddh-btn-sm{padding:6px 11px;font-size:12px}
        .dd-app .ddh-head{display:flex;align-items:center;gap:14px;margin-bottom:24px}
        .dd-app .ddh-brand{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;border-radius:13px;background:linear-gradient(150deg,#3b82f6,#2563eb 55%,#1d4ed8);box-shadow:0 8px 18px -6px rgba(37,99,235,.55);flex:none;color:#fff;font-weight:800;letter-spacing:-1px;font-size:17px}
        .dd-app .ddh-htt h1{margin:0;font-size:21px;font-weight:800;letter-spacing:-.02em;color:#0f172a}
        .dd-app .ddh-htt p{margin:3px 0 0;font-size:13.5px;color:#475569;font-weight:450;line-height:1.45}
        .dd-app .ddh-conn{display:flex;align-items:center;gap:16px;background:#fff;border:1px solid #cfe0ff;border-radius:16px;padding:18px 20px;margin-bottom:28px;box-shadow:0 1px 2px rgba(15,23,42,.04),0 12px 30px -20px rgba(15,23,42,.30);position:relative}
        .dd-app .ddh-conn.is-on{border-color:#a7f3d0;background:#f0fdf6}
        .dd-app .ddh-ci{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;border-radius:12px;background:linear-gradient(150deg,#3b82f6,#2563eb 55%,#1d4ed8);box-shadow:0 7px 15px -6px rgba(37,99,235,.6);flex:none;color:#fff;font-weight:800;letter-spacing:-1px;font-size:18px}
        .dd-app .ddh-conn.is-on .ddh-ci{background:#10b981;box-shadow:none}
        .dd-app .ddh-ci svg{width:23px;height:23px}
        .dd-app .ddh-cbody{flex:1;min-width:0}
        .dd-app .ddh-cbody strong{display:block;font-size:14.5px;font-weight:700;color:#0f172a}
        .dd-app .ddh-cbody span{font-size:12.5px;color:#475569;line-height:1.45}
        .dd-app .ddh-cctl{display:flex;flex-direction:column;gap:10px;flex:none;width:272px}
        .dd-app .ddh-cpanel{display:flex;flex-direction:column;gap:8px;margin:0}
        .dd-app .ddh-cpanel[hidden]{display:none}
        .dd-app .ddh-cgo{width:100%;padding:9px 14px}
        .dd-app .ddh-cinput{font-family:inherit;font-size:13px;border:1px solid #e6eaf1;border-radius:9px;padding:9px 12px;width:100%;color:#0f172a;background:#fff}
        .dd-app .ddh-chint{font-size:11px;color:#8a94a6;text-align:center;margin:0}
        .dd-app .ddh-cx{position:absolute;top:11px;right:14px;color:#aab2c2;font-size:17px;line-height:1;text-decoration:none}
        .dd-app .ddh-cx:hover{color:#475569}
        .dd-app .ddh-sum{display:flex;align-items:stretch;background:#fff;border:1px solid #e6eaf1;border-radius:18px;box-shadow:0 1px 2px rgba(15,23,42,.05),0 22px 50px -24px rgba(37,99,235,.30);padding:6px;margin-bottom:32px;overflow:hidden}
        .dd-app .ddh-sum-score{display:flex;align-items:center;gap:18px;padding:20px 26px;flex:none}
        .dd-app .ddh-ring{position:relative;width:80px;height:80px;flex:none}
        .dd-app .ddh-ring svg{transform:rotate(-90deg)}
        .dd-app .ddh-ring-txt{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;line-height:1}
        .dd-app .ddh-ring-txt b{font-size:25px;font-weight:800;letter-spacing:-.02em;color:#2563eb}
        .dd-app .ddh-ring-txt span{font-size:9.5px;font-weight:700;letter-spacing:.08em;color:#8a94a6;text-transform:uppercase;margin-top:2px}
        .dd-app .ddh-sum-label{min-width:140px}
        .dd-app .ddh-sum-label h2{margin:0;font-size:15px;font-weight:700;letter-spacing:-.01em;color:#0f172a}
        .dd-app .ddh-sum-label p{margin:5px 0 0;font-size:12px;color:#8a94a6;line-height:1.5}
        .dd-app .ddh-sum-label .gd{color:#2563eb;font-weight:600}
        .dd-app .ddh-kpis{display:flex;flex:1;align-items:stretch;margin:6px 6px 6px 0;border-radius:13px;background:#f6f8fc;border:1px solid #eef1f7}
        .dd-app .ddh-kpi{flex:1;display:flex;align-items:center;gap:11px;padding:18px 22px;position:relative}
        .dd-app .ddh-kpi+.ddh-kpi::before{content:"";position:absolute;left:0;top:26%;bottom:26%;width:1px;background:#e6eaf1}
        .dd-app .ddh-kpi-ic{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:10px;flex:none}
        .dd-app .ddh-kpi-ic svg{width:17px;height:17px}
        .dd-app .ddh-kpi-ic.good{background:#e9f7ee;color:#15a34a}
        .dd-app .ddh-kpi-ic.warn{background:#fdf4e6;color:#c2740a}
        .dd-app .ddh-kpi-ic.blue{background:#eaf1ff;color:#2563eb}
        .dd-app .ddh-kpi-tx{display:flex;align-items:baseline;gap:7px;min-width:0}
        .dd-app .ddh-kpi-tx b{font-size:19px;font-weight:800;letter-spacing:-.02em;line-height:1;font-variant-numeric:tabular-nums;color:#0f172a}
        .dd-app .ddh-kpi-tx b.warn{color:#c2740a}
        .dd-app .ddh-kpi-tx span{font-size:12px;font-weight:600;color:#8a94a6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
        .dd-app .ddh-sec{display:flex;align-items:center;gap:10px;margin:0 2px 13px}
        .dd-app .ddh-sec h3{margin:0;font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#8a94a6}
        .dd-app .ddh-count{display:inline-flex;align-items:center;justify-content:center;min-width:21px;height:21px;padding:0 7px;border-radius:999px;background:#eaf1ff;color:#2563eb;font-size:11px;font-weight:800}
        .dd-app .ddh-updall{margin-left:auto}
        .dd-app .ddh-empty{border:1px dashed #cfe0ff;border-radius:14px;background:#fff;padding:18px;font-size:13px;color:#8a94a6;margin-bottom:30px}
        .dd-app .ddh-list{background:#fff;border:1px solid #e6eaf1;border-radius:16px;box-shadow:0 1px 2px rgba(15,23,42,.04),0 12px 30px -20px rgba(15,23,42,.30);overflow:hidden;margin-bottom:30px}
        .dd-app .ddh-row{border-top:1px solid #eef1f7}
        .dd-app .ddh-list>.ddh-row:first-child{border-top:0}
        .dd-app details.ddh-row>summary{list-style:none}
        .dd-app details.ddh-row>summary::-webkit-details-marker{display:none}
        .dd-app .ddh-rsum{display:flex;align-items:center;gap:14px;padding:15px 18px;transition:background .12s}
        .dd-app details.ddh-row>summary.ddh-rsum{cursor:pointer}
        .dd-app .ddh-rsum:hover{background:#fbfcff}
        .dd-app .ddh-logo{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:12px;background:linear-gradient(150deg,#3b82f6,#2563eb 55%,#1d4ed8);box-shadow:0 6px 13px -6px rgba(37,99,235,.6);flex:none}
        .dd-app .ddh-logo svg{width:23px;height:23px;color:#fff}
        .dd-app .ddh-rid{flex:1;min-width:0}
        .dd-app .ddh-rname{font-size:14.5px;font-weight:700;letter-spacing:-.01em;color:#0f172a}
        .dd-app .ddh-rver{font-size:11.5px;color:#aab2c2;font-weight:600;margin-top:3px}
        .dd-app .ddh-up{color:#2563eb;font-weight:700}
        .dd-app .ddh-upbtn{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:700;color:#2563eb;background:#eaf1ff;border:1px solid #cfe0ff;border-radius:7px;padding:2px 8px;text-decoration:none;margin-left:8px;transition:.12s}
        .dd-app .ddh-upbtn:hover{background:#2563eb;color:#fff;border-color:#2563eb}
        .dd-app .ddh-upbtn svg{width:11px;height:11px}
        .dd-app .ddh-rsub{font-size:12px;color:#8a94a6;font-weight:500;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
        .dd-app .ddh-st{display:inline-flex;align-items:center;gap:7px;flex:none;font-size:12px;font-weight:600;border-radius:999px}
        .dd-app .ddh-st.warn{background:#fdf4e6;color:#c2740a;border:1px solid #f1dcb6;padding:6px 6px 6px 11px}
        .dd-app .ddh-st.ok{color:#8a94a6;padding:6px 4px 6px 0}
        .dd-app .ddh-st.ok svg{width:14px;height:14px;color:#15a34a}
        .dd-app .ddh-st.off{color:#8a94a6;padding:6px 4px 6px 0}
        .dd-app .ddh-st.off .odot{width:7px;height:7px;border-radius:50%;background:#c3cad6}
        .dd-app .ddh-st.warn .dot{width:7px;height:7px;border-radius:50%;background:#e08a14}
        .dd-app .ddh-st .caret{display:inline-flex}
        .dd-app .ddh-st .caret svg{width:15px;height:15px;display:block;color:#c08a2e}
        .dd-app details.ddh-row[open] .ddh-st .caret{transform:rotate(180deg)}
        .dd-app .ddh-drop{padding:2px 18px 16px 76px;background:#fcfdff}
        .dd-app .ddh-iss{border:1px solid #f1dcb6;background:#fdf4e6;border-radius:11px;padding:11px 13px;margin-top:8px}
        .dd-app .ddh-iss b{display:block;font-size:12.5px;font-weight:700;color:#92560a}
        .dd-app .ddh-iss span{display:block;font-size:11.5px;color:#475569;margin-top:3px;line-height:1.45}
        .dd-app .ddh-row.inact .ddh-logo,.dd-app .ddh-row.avail .ddh-logo{background:#e9edf4;box-shadow:none}
        .dd-app .ddh-row.inact .ddh-logo svg,.dd-app .ddh-row.avail .ddh-logo svg{color:#9aa3b4}
        .dd-app .ddh-row.inact .ddh-rname,.dd-app .ddh-row.avail .ddh-rname{color:#475569}
        .dd-app .ddh-btn.is-loading{pointer-events:none;cursor:default;opacity:.92}
        .dd-app .ddh-spin{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:ddh-spin .6s linear infinite;flex:none}
        @keyframes ddh-spin{to{transform:rotate(360deg)}}
        @media (max-width:782px){.dd-app{margin-left:-10px}.dd-app .ddh-conn{flex-wrap:wrap}.dd-app .ddh-cctl{width:100%}.dd-app .ddh-sum{flex-wrap:wrap}}
CSS;
}

/** Install-button spinner feedback (footer inline script). */
function devdcorev1_hub_inline_js()
{
    return <<<'JS'
        (function(){
            // Install buttons trigger a full-page install+redirect — swap to an inline spinner on click
            // so the user gets immediate feedback during the (multi-second) download/activate.
            document.querySelectorAll('.dd-app a.ddh-install').forEach(function(a){
                a.addEventListener('click', function(){
                    if (a.classList.contains('is-loading')) { return; }
                    a.style.minWidth = a.offsetWidth + 'px';
                    a.classList.add('is-loading');
                    a.setAttribute('aria-busy', 'true');
                    a.innerHTML = '<span class="ddh-spin" aria-hidden="true"></span>Installing…';
                });
            });
        })();
JS;
}

/** Per-plugin logo (white glyph on the blue tile) resolved by slug keyword. Hardcoded allowlist -> safe to echo. */
function devdcorev1_hub_logo_svg($slug)
{
    $s = (string) $slug;
    if (strpos($s, 'site-monitor') !== false)       { $p = '<path d="M22 12h-4l-3 8L9 4l-3 8H2"/>'; }
    elseif (strpos($s, 'analytic') !== false)        { $p = '<path d="M3 3v18h18"/><path d="M7 13l3-4 3 3 5-7"/>'; }
    elseif (strpos($s, 'redirect') !== false)        { $p = '<path d="M16 3h5v5"/><path d="M21 3 13 11"/><path d="M8 21H3v-5"/><path d="M3 21l8-8"/>'; }
    elseif (strpos($s, 'bot') !== false)             { $p = '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>'; }
    elseif (strpos($s, 'security') !== false || strpos($s, 'scanner') !== false) { $p = '<rect x="4" y="10.5" width="16" height="11" rx="2.2"/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5"/><path d="M12 14.5v3"/>'; }
    elseif (strpos($s, 'affiliate') !== false)       { $p = '<path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1.5-1.5"/>'; }
    elseif (strpos($s, 'product') !== false || strpos($s, 'import') !== false) { $p = '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>'; }
    elseif (strpos($s, 'media') !== false)           { $p = '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/>'; }
    elseif (strpos($s, 'admin') !== false)           { $p = '<path d="M5 3v4M3 5h4M6 17v4M4 19h4"/><path d="M13 3l2.5 6.5L22 12l-6.5 2.5L13 21l-2.5-6.5L4 12l6.5-2.5z"/>'; }
    elseif (strpos($s, 'backup') !== false)          { $p = '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.6 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.6 3 8 3s8-1.34 8-3"/>'; }
    else { $p = '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>'; }
    return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' . $p . '</svg>';
}

/** Real WP plugin-update map for devdome-* plugins: plugin_file => new_version. */
function devdcorev1_hub_plugin_updates()
{
    $out = array();
    $upd = get_site_transient('update_plugins');
    if (is_object($upd) && !empty($upd->response) && is_array($upd->response)) {
        foreach ($upd->response as $file => $data) {
            if (stripos((string) $file, 'devdome') !== false) {
                $out[(string) $file] = (is_object($data) && isset($data->new_version)) ? (string) $data->new_version : '';
            }
        }
    }
    return $out;
}
