<?php

declare(strict_types=1);

namespace FleekDash\Admin\Menu;

/**
 * Admin Menu Context Builder
 *
 * Ensures WordPress admin menu is loaded by emulating admin context.
 * Used when REST API needs $menu and $submenu globals (e.g. outside wp-admin).
 *
 * @package FleekDash\Admin\Menu
 */
class AdminMenuContextBuilder
{
    /**
     * Log heavy admin-context hooks when they exceed this wall-clock budget (seconds).
     * Tuned to catch plugins doing synchronous remote HTTP calls during admin_menu / current_screen.
     */
    private const HOOK_SLOW_BUDGET_SECONDS = 1.5;

    /**
     * Ensure WordPress admin menu is loaded (emulate admin context).
     *
     * This makes REST API requests work exactly like admin page requests,
     * where globals $menu and $submenu are available.
     *
     * Can be disabled entirely via the `fleekdash_rest_admin_menu_hooks_enabled` filter
     * (return false) for sites where admin_menu / current_screen callbacks are too slow
     * for REST; in that case consumers must rely on the persisted user_meta snapshot.
     */
    public function ensureLoaded(): void
    {
        global $menu, $submenu;

        $isRest = \defined('REST_REQUEST') && REST_REQUEST;
        if (
            !$isRest &&
            !empty($menu) &&
            \is_array($menu) &&
            \count($menu) > 0
        ) {
            return;
        }

        // REST: menu.php is require_once — clearing $menu a second time in the same request cannot repopulate.
        if (
            $isRest &&
            !empty($menu) &&
            \is_array($menu) &&
            \count($menu) > 0
        ) {
            return;
        }

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

        // Only clear globals when this request can actually repopulate them. require_once of
        // wp-admin/menu.php is a no-op after the first include; if admin_menu already fired, our
        // did_action guard below will skip it too. Clearing without the ability to rebuild would
        // leave $menu / $submenu permanently empty for the rest of the request.
        if ($this->canRebuildGlobalsThisRequest()) {
            $menu = [];
            $submenu = [];
        }
        $this->initializeContext();
        $this->loadWordPressAdminFiles();
        $this->maybeFireAcfInit();
        $this->fireAdminMenuHooksIfNeeded();
        $this->safelyRunTimed(
            'current_screen:dashboard',
            static function (): void {
                WordPressAdminMenuLoader::setDashboardScreenAfterAdminMenu();
            },
        );
        $this->filterNullMenuItemsFromGlobals();
        $this->ensureDefaultMenuOrderComplete();
    }

    private function canRebuildGlobalsThisRequest(): bool
    {
        $menuPhpNotLoaded = !WordPressAdminMenuLoader::isAdminMenuPhpLoaded();
        $adminMenuNotFired = \did_action('admin_menu') === 0;
        return $menuPhpNotLoaded && $adminMenuNotFired;
    }

    private function initializeContext(): void
    {
        if (!\defined('WP_ADMIN')) {
            \define('WP_ADMIN', true);
        }
        if (!isset($_SERVER['PHP_SELF'])) {
            $_SERVER['PHP_SELF'] = '/wp-admin/admin.php';
        }
        if (!isset($_SERVER['REQUEST_URI'])) {
            $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
        }
        global $pagenow;
        if (!isset($pagenow)) {
            $pagenow = 'admin.php';
        }
        WordPressAdminMenuLoader::initializeMenuGlobals();
    }

    private function loadWordPressAdminFiles(): void
    {
        WordPressAdminMenuLoader::loadCoreAdminFiles('FleekDash AdminMenuContextBuilder');
        WordPressAdminMenuLoader::loadMenuPhp();
    }

    private function maybeFireAcfInit(): void
    {
        $this->safelyRunTimed('acf/init', static function (): void {
            \do_action('acf/init');
        });
    }

    private function fireAdminMenuHooksIfNeeded(): void
    {
        $this->runActionSafely('admin_menu');
        $this->runActionSafely('_admin_menu');
    }

    private function runActionSafely(string $hook): void
    {
        if (\did_action($hook)) {
            return;
        }

        $this->safelyRunTimed($hook, static function () use ($hook): void {
            \do_action($hook);
        });
    }

    /**
     * Run a closure and measure its wall-clock cost. Exceptions are swallowed (same guarantee as
     * the previous try/catch around do_action) but a warning is logged via error_log when the hook
     * exceeds {@see self::HOOK_SLOW_BUDGET_SECONDS}, which helps attribute REST menu slowness to a
     * specific hook (admin_menu, acf/init, current_screen, …).
     */
    private function safelyRunTimed(string $label, \Closure $run): void
    {
        $start = \microtime(true);
        try {
            $run();
        } catch (\Throwable $e) {
            if (\defined('WP_DEBUG') && WP_DEBUG) {
                error_log(
                    '[FleekDash AdminMenuContextBuilder] Error in hook ' . $label . ': ' . $e->getMessage(),
                );
            }
        }
        $elapsed = \microtime(true) - $start;
        if ($elapsed >= self::HOOK_SLOW_BUDGET_SECONDS) {
            error_log(\sprintf(
                '[FleekDash AdminMenuContextBuilder] Slow hook "%s" in REST context: %.3fs (threshold %.1fs)',
                $label,
                $elapsed,
                self::HOOK_SLOW_BUDGET_SECONDS,
            ));
        }
    }

    private function filterNullMenuItemsFromGlobals(): void
    {
        WordPressAdminMenuLoader::filterNullMenuItems();
    }

    private function ensureDefaultMenuOrderComplete(): void
    {
        WordPressAdminMenuLoader::ensureDefaultMenuOrderComplete();
    }
}
