<?php

declare(strict_types=1);

namespace FleekDash\Admin\Menu;

/**
 * Menu Capture Service
 *
 * Captures WordPress admin menu during real wp-admin requests and persists per-user snapshots.
 * The REST pipeline favors snapshots over partial globals to survive contexts where plugins
 * do not fully register their admin menus.
 *
 * Capture priority (see {@see ensureMenuCapturedInContext()}):
 *
 * Non-REST (real wp-admin):
 *   1. `$GLOBALS['menu']` if already populated → source `globals`
 *   2. Fresh per-user snapshot → source `user_meta_snapshot`
 *   3. Stale per-user snapshot → source `user_meta_snapshot_stale`
 *   4. Build from scratch → source `built_from_scratch`
 *
 * REST:
 *   1. Fresh per-user snapshot → source `user_meta_snapshot`
 *   2. Stale per-user snapshot → source `user_meta_snapshot_stale`
 *   3. Build from scratch → source `built_from_scratch`
 *   4. Still empty → source `missing_snapshot`
 *
 * Note: {@see \FleekDash\API\MenuDataAPI::resolveRawMenuAndSubmenu()} may short-circuit with a
 * fresh snapshot before this service is consulted (see `fleekdash_menu_fast_path_enabled`).
 *
 * @package FleekDash\Admin\Menu
 */
class MenuCaptureService
{
    private MenuFromScratchBuilder $scratchBuilder;

    public function __construct()
    {
        $this->scratchBuilder = new MenuFromScratchBuilder();
    }

    /**
     * Stored WordPress menu (in-memory only, per request)
     *
     * @var array
     */
    private $storedMenu = [];

    /**
     * Stored WordPress submenu (in-memory only, per request)
     *
     * @var array
     */
    private $storedSubmenu = [];

    /**
     * Where the currently stored menu came from.
     *
     * Values:
     * - globals: captured from WordPress globals in a real admin request
     * - user_meta_snapshot: loaded from a fresh persisted snapshot captured in wp-admin
     * - user_meta_snapshot_stale: loaded from a stale persisted snapshot as last-chance fallback
     * - built_from_scratch: rebuilt by firing hooks when no snapshot was available
     * - missing_snapshot: REST-only terminal state when nothing above produced a menu
     * - none: not captured yet
     *
     * @var string
     */
    private string $lastSource = 'none';

    /**
     * Capture the complete WordPress menu after all plugins have registered their items
     * This method is designed to be called from WordPress hooks (admin_menu, wp_before_admin_bar_render)
     *
     * @param array|null $menu WordPress global $menu array
     * @param array|null $submenu WordPress global $submenu array
     * @return void
     */
    public function captureAdminMenu($menu = null, $submenu = null)
    {
        // Force WordPress to build the menu if it hasn't already
        if (!\did_action('admin_menu')) {
            \do_action('_wp_menu_output');
        }

        // Use provided menu or global menu
        if ($menu === null) {
            global $menu;
        }
        if ($submenu === null) {
            global $submenu;
        }

        // Always store the latest menu (capture can be called multiple times in the same request)
        $this->storeMenuIfAvailable($menu, $submenu);
    }

    /**
     * Ensure menu is captured before use.
     *
     * Priority logic is resolved in {@see ensureMenuCapturedInContext()} and differs between
     * non-REST (prefers globals → snapshot → scratch) and REST (snapshot → stale → scratch →
     * `missing_snapshot`). See class-level docblock for the canonical order.
     *
     * Does not clear cache on every call — only when necessary — to prevent unnecessary reloads
     * when accessing the iframe admin.
     *
     * @return void
     */
    public function ensureMenuCaptured()
    {
        if ($this->storedMenu !== null && !empty($this->storedMenu)) {
            return;
        }

        $isRest = \defined('REST_REQUEST') && REST_REQUEST;
        $this->ensureMenuCapturedInContext($isRest);
    }

    private function ensureMenuCapturedInContext(bool $isRest): void
    {
        $user_id = (int) \get_current_user_id();
        $captured = false;

        if (!$isRest) {
            global $menu, $submenu;
            if (!empty($menu) && \is_array($menu) && \count($menu) > 0) {
                $this->storeMenuIfAvailable($menu, $submenu);
                $this->lastSource = 'globals';
                $captured = true;
            }
        }

        if (!$captured && $user_id > 0 && $this->loadSnapshotForUser($user_id, false)) {
            $this->lastSource = 'user_meta_snapshot';
            $captured = true;
        }

        if (!$captured && $user_id > 0 && $this->loadSnapshotForUser($user_id, true)) {
            $this->lastSource = 'user_meta_snapshot_stale';
            $captured = true;
        }

        if (!$captured) {
            $this->buildMenuFromScratch();
            if (!empty($this->storedMenu)) {
                $this->lastSource = 'built_from_scratch';
                $captured = true;
            } elseif ($isRest) {
                $this->lastSource = 'missing_snapshot';
            }
        }
    }

    /**
     * Build menu from scratch by firing WordPress admin_menu hooks.
     *
     * This is used when globals are not available (REST API context).
     */
    private function buildMenuFromScratch(): void
    {
        $this->scratchBuilder->build();
        global $menu, $submenu;
        if (!empty($menu) && \is_array($menu)) {
            $this->storeMenuIfAvailable($menu, $submenu);
        }
    }

    /**
     * Store menu if it's available and not empty
     * Sorts menu by WordPress position keys for consistent ordering
     *
     * Stores menu in-memory. Persistence is handled separately via persistSnapshotForCurrentUser().
     *
     * @param array $menu WordPress menu array
     * @param array $submenu WordPress submenu array
     * @return void
     */
    private function storeMenuIfAvailable($menu, $submenu)
    {
        if (!empty($menu) && \is_array($menu)) {
            // NULL SAFETY: Filter out null items before storing to prevent warnings
            $filtered_menu = \array_filter($menu, 'is_array');

            // CRITICAL: Sort menu by array keys (positions) to ensure consistent order
            // WordPress uses array keys as positions: $menu[2], $menu[5], $menu[10], etc.
            // Use ksort() to sort by keys numerically
            \ksort($filtered_menu, SORT_NUMERIC);

            // TRUST WORDPRESS: The menu captured from admin context already contains
            // only menus from active plugins. No filtering needed.
            $this->storedMenu = $filtered_menu;
        }

        if (!empty($submenu) && \is_array($submenu)) {
            // NULL SAFETY: Filter out null items in submenu arrays
            $filtered_submenu = [];
            foreach ($submenu as $key => $submenu_items) {
                if ($submenu_items !== null && \is_array($submenu_items)) {
                    $filtered_submenu[$key] = \array_filter($submenu_items, 'is_array');
                }
            }
            $this->storedSubmenu = $filtered_submenu;
        }
    }

    /**
     * Persist the latest captured menu snapshot for the current user.
     *
     * This should be called ONLY during real wp-admin requests (shutdown hook),
     * so that REST endpoints can return the exact same menu the user sees.
     */
    public function persistSnapshotForCurrentUser(): void
    {
        $user_id = (int) \get_current_user_id();
        MenuSnapshotUserMeta::persistIfChanged(
            $user_id,
            $this->storedMenu,
            $this->storedSubmenu,
        );
    }

    /**
     * Load persisted snapshot for a user into in-memory storage.
     *
     * @param int $user_id User ID
     * @param bool $allowStale If true, loads stale snapshots (marked with updated_at = -1)
     * @return bool True if snapshot was loaded
     */
    public function loadSnapshotForUser(int $user_id, bool $allowStale = false): bool
    {
        $restored = MenuSnapshotUserMeta::readForRestoration($user_id, $allowStale);
        if ($restored === null) {
            return false;
        }

        $this->storedMenu = $restored['menu'];
        $this->storedSubmenu = $restored['submenu'];
        $this->lastSource = $restored['is_stale']
            ? 'user_meta_snapshot_stale'
            : 'user_meta_snapshot';

        return true;
    }

    public function getLastSource(): string
    {
        return $this->lastSource;
    }

    /**
     * Get stored menu items (in-memory only)
     *
     * @return array Stored WordPress menu
     */
    public function getStoredMenu(): array
    {
        return $this->storedMenu;
    }

    /**
     * Get stored submenu items (in-memory only)
     *
     * @return array Stored WordPress submenu
     */
    public function getStoredSubmenu(): array
    {
        return $this->storedSubmenu;
    }

    /**
     * Check if menu has been captured
     *
     * @return bool True if menu is captured and stored
     */
    public function hasStoredMenu(): bool
    {
        return !empty($this->storedMenu);
    }

    /**
     * Clear in-memory cache only
     *
     * @return void
     */
    public function clearInMemoryCache(): void
    {
        $this->storedMenu = [];
        $this->storedSubmenu = [];
        $this->lastSource = 'none';
    }

    /**
     * Force recapture of menu
     *
     * @return void
     */
    public function forceRecapture(): void
    {
        $this->clearInMemoryCache();

        global $menu, $submenu;

        if (!empty($menu)) {
            $this->storeMenuIfAvailable($menu, $submenu);
        } else {
            $this->buildMenuFromScratch();
        }
    }

    /**
     * Get cache status for debugging
     *
     * Shows current menu capture status including snapshot information.
     *
     * @return array Cache status information
     */
    public function getCacheStatus(): array
    {
        $user_id = (int) \get_current_user_id();
        $has_snapshot = false;
        $snapshot_updated_at = 0;
        $is_stale = false;

        if ($user_id > 0) {
            $snapshot_updated_at = MenuSnapshotUserMeta::getUpdatedAt($user_id);
            // Snapshot exists if we have menu data (regardless of updated_at value)
            $snapshot_data = \get_user_meta($user_id, MenuSnapshotUserMeta::META_SNAPSHOT, true);
            $has_snapshot = !empty($snapshot_data) && \is_array($snapshot_data);
            // Stale if updated_at is -1
            $is_stale = $snapshot_updated_at === -1;
        }

        if ($snapshot_updated_at > 0) {
            $snapshotUpdatedAtDisplay = \date('Y-m-d H:i:s', $snapshot_updated_at);
        } elseif ($is_stale) {
            $snapshotUpdatedAtDisplay = 'stale';
        } else {
            $snapshotUpdatedAtDisplay = null;
        }

        return [
            'in_memory_menu_count' => \count($this->storedMenu),
            'in_memory_submenu_count' => \count($this->storedSubmenu),
            'last_source' => $this->lastSource,
            'has_snapshot' => $has_snapshot,
            'is_snapshot_stale' => $is_stale,
            'snapshot_updated_at' => $snapshotUpdatedAtDisplay,
            'note' => 'Menu source priority: fresh_snapshot > built_from_scratch > stale_snapshot (fallback)',
        ];
    }

}
