<?php

declare(strict_types=1);

namespace FleekDash\API;

use FleekDash\Admin\Menu\CoreMenuRestoreService;
use FleekDash\Admin\Menu\MenuItemFormatter;
use FleekDash\Admin\Menu\MenuOverrideStaleKeyNormalizer;
use FleekDash\Admin\Menu\MenuOverridesApplier;
use FleekDash\Admin\Menu\MenuOverridesService;
use FleekDash\Admin\Menu\MenuRoleProjectionService;
use FleekDash\Admin\Menu\RouteConverter;
use WP_REST_Request;

class MenuRolePayloadService
{
    private CoreMenuRestoreService $coreMenuRestoreService;

    public function __construct(
        private MenuOverridesService $menuOverridesService,
        private MenuOverridesApplier $menuOverridesApplier,
        private MenuRoleProjectionService $menuRoleProjectionService,
    ) {
        $this->coreMenuRestoreService = new CoreMenuRestoreService();
    }

    public function resolveCurrentUserRoleSlug(): string
    {
        $user = \wp_get_current_user();
        if ($user instanceof \WP_User && !empty($user->roles) && \is_array($user->roles)) {
            $roleSlug = (string) $user->roles[0];
            return $roleSlug !== '' ? $roleSlug : '';
        }
        return '';
    }

    /**
     * @param array<int, array<string, mixed>> $items
     * @return array<int, array<string, mixed>>
     */
    public function applyRoleOverridesToMenuItems(
        array $items,
        string $roleSlug,
        bool $allowVisibilityRemoval = true,
    ): array
    {
        $overrides = $this->menuOverridesService->getRoleOverrides($roleSlug);
        if (empty($overrides) || !\is_array($overrides)) {
            return $items;
        }
        if (!$allowVisibilityRemoval) {
            $overrides = $this->stripVisibilityRemovalOverrides($overrides);
        }

        $normalizer = new MenuOverrideStaleKeyNormalizer();
        $normalized = $normalizer->normalizeForTree($items, $overrides);
        if ($normalizer->didMutate()) {
            $this->menuOverridesService->saveRoleOverrides($roleSlug, $normalized);
        }

        return $this->menuOverridesApplier->apply($items, $normalized);
    }

    /**
     * Keep ordering and labels, but never remove items from runtime menu.
     *
     * @param array<string,mixed> $overrides
     * @return array<string,mixed>
     */
    private function stripVisibilityRemovalOverrides(array $overrides): array
    {
        $sanitized = [];
        foreach ($overrides as $key => $override) {
            if (!\is_array($override)) {
                continue;
            }
            if (isset($override['visible']) && $override['visible'] === false) {
                unset($override['visible']);
            }
            if (!empty($override)) {
                $sanitized[$key] = $override;
            }
        }
        return $sanitized;
    }

    /** @return array{data: array<string, mixed>} */
    public function resolveRoleMenuPayload(
        WP_REST_Request $request,
        int $currentUserId,
        array $menu,
        array $submenu,
    ): array {
        $roleSlug = $this->resolveRequestedRoleSlug($request);
        $includeDenied = $this->shouldIncludeDeniedItems($request);
        $menuItems = $this->formatAndEnrichMenuItemsForRole($menu, $submenu, $roleSlug, $includeDenied);
        $menuItems = $this->applyRoleOverridesToMenuItems($menuItems, $roleSlug);

        $rawMenuCount = \is_array($menu) ? \count($menu) : 0;
        $rawSubmenuCount = \is_array($submenu) ? \count($submenu) : 0;
        $restoredCoreCount = $this->countRestoredCoreItems($menuItems);

        $data = empty($menuItems)
            ? $this->buildRoleMenuFallbackPayload()
            : [
                'items' => $menuItems,
                'menu_hash' => MenuPayloadHash::hashNormalizedMenu($menuItems),
                'meta' => [
                    'role' => $roleSlug,
                    'user_id' => $currentUserId,
                    'capabilities' => \array_keys($this->resolveRoleCapabilities($roleSlug)),
                    'include_denied' => $includeDenied,
                ],
                'debug' => [
                    'source' => 'globals_role_projection',
                    'raw_menu_count' => $rawMenuCount,
                    'raw_submenu_count' => $rawSubmenuCount,
                    'formatted_count' => \count($menuItems),
                    'restored_core_count' => $restoredCoreCount,
                ],
            ];

        return ['data' => $data];
    }

    private function shouldIncludeDeniedItems(WP_REST_Request $request): bool
    {
        $value = $request->get_param('include_denied');
        return $value === true || $value === 1 || $value === '1' || $value === 'true';
    }

    private function resolveRequestedRoleSlug(WP_REST_Request $request): string
    {
        $roleSlug = \is_string($request->get_param('role')) ? (string) $request->get_param('role') : '';
        if ($roleSlug === '') {
            $roleSlug = $this->resolveCurrentUserRoleSlug();
        }
        $requestedRoleExists = $roleSlug !== '' && \get_role($roleSlug) instanceof \WP_Role;
        if (!$requestedRoleExists) {
            $fallbackRole = $this->resolveCurrentUserRoleSlug();
            $roleSlug = $fallbackRole !== '' ? $fallbackRole : 'subscriber';
        }
        return $roleSlug;
    }

    /** @return array<string, bool> */
    private function resolveRoleCapabilities(string $roleSlug): array
    {
        $role = \get_role($roleSlug);
        if ($role instanceof \WP_Role && \is_array($role->capabilities)) {
            return $role->capabilities;
        }
        return [];
    }

    /**
     * @param array<int, mixed> $menu
     * @param array<string, mixed> $submenu
     * @return array<int, array<string, mixed>>
     */
    private function formatAndEnrichMenuItemsForRole(array $menu, array $submenu, string $roleSlug, bool $includeDenied): array
    {
        $capabilities = $this->resolveRoleCapabilities($roleSlug);

        $formatter = new MenuItemFormatter(new RouteConverter());
        $menuItems = $formatter->formatMenuItems(
            $menu,
            $submenu,
            static function (string $capability) use ($capabilities): bool {
                if ($capability === '') {
                    return true;
                }
                return isset($capabilities[$capability]) && $capabilities[$capability] === true;
            },
            $includeDenied,
        );

        $menuItems = MenuItemTransformHelper::applyBackwardCompatibilityPayload($menuItems);
        if (!$includeDenied) {
            $menuItems = $this->menuRoleProjectionService->filterByCapabilities($menuItems, $capabilities);
        }
        $menuItems = $this->coreMenuRestoreService->restoreMissingTopLevelItems(
            $menuItems,
            static fn(string $capability): bool => $capability === '' || (isset($capabilities[$capability]) && $capabilities[$capability] === true),
            $submenu,
            $formatter,
            [],
            $includeDenied,
        );
        $menuItems = $this->appendIntegrationRoutesForRole($menuItems, $capabilities);
        return $includeDenied
            ? $this->menuRoleProjectionService->flagDeniedItems($menuItems, $capabilities)
            : $menuItems;
    }

    /**
     * @param array<int, array<string, mixed>> $items
     * @param array<string, bool> $capabilities
     * @return array<int, array<string, mixed>>
     */
    private function appendIntegrationRoutesForRole(array $items, array $capabilities): array
    {
        if (!\class_exists(\FleekDash\Integration\RouteManager::class)) {
            return $items;
        }

        $routes = \FleekDash\Integration\RouteManager::getAll(true, true);
        $routesArray = \is_array($routes) ? $routes : [];

        return $this->menuRoleProjectionService->appendAllowedIntegrationRoutes(
            $items,
            $routesArray,
            $capabilities,
            [MenuItemTransformHelper::class, 'convertIntegrationRouteToMenuItem'],
            [MenuItemTransformHelper::class, 'sortMenuItemsByPosition'],
        );
    }

    /** @return array<string, mixed> */
    private function buildRoleMenuFallbackPayload(): array
    {
        return [
            'items' => [],
            'menu_hash' => MenuPayloadHash::EMPTY,
            'debug' => [
                'error' => 'Menu generation failed - plugins may have errors in REST context',
                'message' => 'Some plugins (like WooCommerce) may fail when generating menu in REST API context.',
                'raw_menu_count' => 0,
            ],
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $items
     */
    private function countRestoredCoreItems(array $items): int
    {
        $count = 0;
        foreach ($items as $item) {
            if (!\is_array($item)) {
                continue;
            }
            $isCore = isset($item['is_restored_core']) && $item['is_restored_core'] === true;
            $isPostType = isset($item['is_restored_post_type']) && $item['is_restored_post_type'] === true;
            if ($isCore || $isPostType) {
                $count++;
            }
        }
        return $count;
    }
}

