<?php

declare(strict_types=1);

namespace FleekDash\Admin\Menu;

/**
 * Menu From Scratch Builder
 *
 * Builds WordPress admin menu from scratch by firing hooks.
 * Used when globals are not available (e.g. REST API context).
 *
 * @package FleekDash\Admin\Menu
 */
class MenuFromScratchBuilder
{
    /**
     * Build menu by loading WordPress admin files and firing hooks.
     * Populates global $menu and $submenu.
     */
    public function build(): 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();

        // Core menu.php assigns to fixed keys only; it does not reset the full arrays. Clear before the
        // first include so leftover $menu/$submenu entries cannot survive. If menu.php already ran in
        // this request, require_once would not run again and admin_menu may be did_action — clearing
        // would leave an empty menu (see WordPressAdminMenuLoader::isAdminMenuPhpLoaded).
        global $menu, $submenu;
        if (!WordPressAdminMenuLoader::isAdminMenuPhpLoaded()) {
            $menu = [];
            $submenu = [];
        }

        WordPressAdminMenuLoader::loadCoreAdminFiles('FleekDash MenuFromScratchBuilder');
        WordPressAdminMenuLoader::loadMenuPhp();

        try {
            \do_action('acf/init');
        } catch (\Throwable $e) {
            // ignore
        }

        $this->runActionSafely('admin_menu');
        $this->runActionSafely('_admin_menu');
        WordPressAdminMenuLoader::setDashboardScreenAfterAdminMenu();
        WordPressAdminMenuLoader::filterNullMenuItems();
        WordPressAdminMenuLoader::ensureDefaultMenuOrderComplete();
    }

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

        global $wp_filter;

        if (!isset($wp_filter[$hook])) {
            try {
                \do_action($hook);
            } catch (\Throwable $e) {
                // swallow
            }
            return;
        }

        $callbacks = $this->getHookCallbacks($wp_filter[$hook] ?? null);
        if (empty($callbacks)) {
            return;
        }

        \ksort($callbacks, SORT_NUMERIC);
        foreach ($callbacks as $handlers) {
            $this->invokeHandlers($handlers, $hook);
        }
    }

    private function getHookCallbacks($hookObj): array
    {
        if ($hookObj instanceof \WP_Hook) {
            return $hookObj->callbacks ?? [];
        }
        if (\is_array($hookObj)) {
            return $hookObj;
        }
        return [];
    }

    private function invokeHandlers(array $handlers, string $hook): void
    {
        if (empty($handlers) || !\is_array($handlers)) {
            return;
        }
        foreach ($handlers as $handler) {
            $this->invokeSingleHandler($handler, $hook);
        }
    }

    private function invokeSingleHandler(array $handler, string $hook): void
    {
        $func = $handler['function'] ?? null;
        if (!$func) {
            return;
        }
        $accepted_args = (int) ($handler['accepted_args'] ?? 0);
        $args = $accepted_args > 0 ? \array_fill(0, $accepted_args, null) : [];
        try {
            \call_user_func_array($func, $args);
        } catch (\Throwable $e) {
            if (\defined('WP_DEBUG') && WP_DEBUG) {
                \error_log('[FleekDash MenuFromScratchBuilder] Error in hook ' . $hook . ': ' . $e->getMessage());
            }
        }
    }

}
