<?php

declare(strict_types=1);

namespace FleekDash\Admin\Menu;

final class CoreMenuRestoreService
{
    private ?MenuRouteResolver $routeResolver = null;

    /**
     * @var array<int, array{slug:string,name:string,capability:string,position:float}>
     */
    private const CORE_ITEMS = [
        ['slug' => 'index.php', 'name' => 'Dashboard', 'capability' => 'read', 'position' => 2.0],
        ['slug' => 'edit.php', 'name' => 'Posts', 'capability' => 'edit_posts', 'position' => 5.0],
        ['slug' => 'upload.php', 'name' => 'Media', 'capability' => 'upload_files', 'position' => 10.0],
        ['slug' => 'edit.php?post_type=page', 'name' => 'Pages', 'capability' => 'edit_pages', 'position' => 20.0],
        ['slug' => 'edit-comments.php', 'name' => 'Comments', 'capability' => 'moderate_comments', 'position' => 25.0],
        ['slug' => 'themes.php', 'name' => 'Appearance', 'capability' => 'switch_themes', 'position' => 60.0],
        ['slug' => 'plugins.php', 'name' => 'Plugins', 'capability' => 'activate_plugins', 'position' => 65.0],
        ['slug' => 'users.php', 'name' => 'Users', 'capability' => 'list_users', 'position' => 70.0],
        ['slug' => 'tools.php', 'name' => 'Tools', 'capability' => 'import', 'position' => 75.0],
        ['slug' => 'options-general.php', 'name' => 'Settings', 'capability' => 'manage_options', 'position' => 80.0],
    ];

    /**
     * @param array<int, array<string,mixed>> $items
     * @param callable(string):bool $canAccess
     * @param array<string, mixed> $rawSubmenu WordPress global $submenu shape
     * @param array<string, array<int, mixed>> $snapshotMenuRowBySlug Optional snapshot menu rows by slug for native titles
     * @return array<int, array<string,mixed>>
     */
    public function restoreMissingTopLevelItems(
        array $items,
        callable $canAccess,
        array $rawSubmenu,
        MenuItemFormatter $formatter,
        array $snapshotMenuRowBySlug = [],
        bool $includeDeniedItems = false,
    ): array {
        $existing = $this->buildExistingSlugMap($items);
        foreach (self::CORE_ITEMS as $coreItem) {
            $slug = $coreItem['slug'];
            if (isset($existing[$slug])) {
                continue;
            }
            if (!$includeDeniedItems && !$canAccess($coreItem['capability'])) {
                continue;
            }
            $fallbackRow = isset($snapshotMenuRowBySlug[$slug]) && \is_array($snapshotMenuRowBySlug[$slug])
                ? $snapshotMenuRowBySlug[$slug]
                : null;
            $built = $this->buildCoreItem($coreItem, $fallbackRow);
            $items[] = $this->attachSubmenuIfAny($formatter, $rawSubmenu, $slug, $built);
            $existing[$slug] = true;
        }
        $items = $this->restoreMissingPostTypeItems($items, $existing, $canAccess, $rawSubmenu, $formatter, $includeDeniedItems);
        return $items;
    }

    /**
     * Fill submenus for native parents whose submenu key is missing or empty (typical symptom
     * when wp-admin/menu.php was already require_once'd earlier in the request and admin_menu
     * cannot re-fire, leaving `$submenu['edit.php']`, `$submenu['upload.php']`, … empty).
     *
     * Uses {@see NativeSubmenuFallback} to produce WordPress-core baseline rows and reuses
     * {@see MenuItemFormatter::attachSubmenuRowsToTopLevel()} so capability filtering / routing
     * match real wp-admin behavior.
     *
     * @param array<int, array<string,mixed>> $items
     * @return array<int, array<string,mixed>>
     */
    public function ensureNativeSubmenusPresent(
        array $items,
        MenuItemFormatter $formatter,
    ): array {
        foreach ($items as $index => $item) {
            if (!\is_array($item)) {
                continue;
            }
            if ($this->itemAlreadyHasSubmenu($item)) {
                continue;
            }
            $slug = isset($item['source_slug']) && \is_string($item['source_slug'])
                ? $item['source_slug']
                : '';
            if ($slug === '') {
                continue;
            }
            $rows = NativeSubmenuFallback::getBaselineRows($slug);
            if ($rows === []) {
                continue;
            }
            $items[$index] = $formatter->attachSubmenuRowsToTopLevel($item, $rows);
        }
        return $items;
    }

    /** @param array<string, mixed> $item */
    private function itemAlreadyHasSubmenu(array $item): bool
    {
        if (!isset($item['submenu'])) {
            return false;
        }
        return \is_array($item['submenu']) && $item['submenu'] !== [];
    }

    /**
     * @param array<int, array<string,mixed>> $items
     * @param array<string,bool> $existing
     * @param callable(string):bool $canAccess
     * @param array<string, mixed> $rawSubmenu
     * @return array<int, array<string,mixed>>
     */
    private function restoreMissingPostTypeItems(
        array $items,
        array $existing,
        callable $canAccess,
        array $rawSubmenu,
        MenuItemFormatter $formatter,
        bool $includeDeniedItems,
    ): array {
        $postTypes = $this->loadUiPostTypes();
        if (empty($postTypes)) {
            return $items;
        }

        $fallbackPosition = 110.0;
        foreach ($postTypes as $postTypeName => $postTypeObject) {
            if (!$this->isValidPostTypeObject($postTypeName, $postTypeObject)) {
                continue;
            }
            if ($this->isExcludedCorePostType($postTypeName)) {
                continue;
            }
            if (!$this->shouldRestoreAsTopLevel($postTypeObject)) {
                continue;
            }
            $slug = 'edit.php?post_type=' . $postTypeName;
            if (isset($existing[$slug])) {
                continue;
            }
            $capability = $this->resolvePostTypeEditCapability($postTypeObject);
            if (!$includeDeniedItems && !$canAccess($capability)) {
                continue;
            }
            $name = $this->resolvePostTypeName($postTypeName, $postTypeObject);
            $position = $this->resolvePostTypePosition($postTypeObject, $fallbackPosition);
            $built = $this->buildRestoredPostTypeItem($slug, $name, $capability, $position);
            $items[] = $this->attachSubmenuIfAny($formatter, $rawSubmenu, $slug, $built);
            $existing[$slug] = true;
            $fallbackPosition += 1.0;
        }

        return $items;
    }

    /**
     * @param array<string, mixed> $item
     * @return array<string, mixed>
     */
    private function attachSubmenuIfAny(
        MenuItemFormatter $formatter,
        array $rawSubmenu,
        string $slug,
        array $item,
    ): array {
        if (!isset($rawSubmenu[$slug]) || !\is_array($rawSubmenu[$slug])) {
            return $item;
        }

        return $formatter->attachSubmenuRowsToTopLevel($item, $rawSubmenu[$slug]);
    }

    /** @return array<string,\WP_Post_Type> */
    private function loadUiPostTypes(): array
    {
        $postTypes = \get_post_types(['show_ui' => true], 'objects');
        return \is_array($postTypes) ? $postTypes : [];
    }

    private function isValidPostTypeObject(mixed $postTypeName, mixed $postTypeObject): bool
    {
        return \is_string($postTypeName) && $postTypeObject instanceof \WP_Post_Type;
    }

    private function isExcludedCorePostType(string $postTypeName): bool
    {
        return $postTypeName === 'post' || $postTypeName === 'page' || $postTypeName === 'attachment';
    }

    private function shouldRestoreAsTopLevel(\WP_Post_Type $postTypeObject): bool
    {
        $showInMenu = $postTypeObject->show_in_menu ?? true;
        if ($showInMenu === false) {
            return false;
        }
        if (\is_string($showInMenu) && $showInMenu !== '') {
            // Post type is explicitly attached as submenu of another parent.
            return false;
        }
        return true;
    }

    private function resolvePostTypeEditCapability(\WP_Post_Type $postTypeObject): string
    {
        return isset($postTypeObject->cap->edit_posts) && \is_string($postTypeObject->cap->edit_posts)
            ? $postTypeObject->cap->edit_posts
            : 'edit_posts';
    }

    private function resolvePostTypeName(string $postTypeName, \WP_Post_Type $postTypeObject): string
    {
        return isset($postTypeObject->labels->name) && \is_string($postTypeObject->labels->name)
            ? $postTypeObject->labels->name
            : \ucfirst($postTypeName);
    }

    private function resolvePostTypePosition(\WP_Post_Type $postTypeObject, float $fallbackPosition): float
    {
        return isset($postTypeObject->menu_position) && \is_numeric($postTypeObject->menu_position)
            ? (float) $postTypeObject->menu_position
            : $fallbackPosition;
    }

    /**
     * @param array<int, array<string,mixed>> $items
     * @return array<string,bool>
     */
    private function buildExistingSlugMap(array $items): array
    {
        $existing = [];
        foreach ($items as $item) {
            if (!\is_array($item)) {
                continue;
            }
            $slug = isset($item['source_slug']) && \is_string($item['source_slug'])
                ? $item['source_slug']
                : '';
            if ($slug !== '') {
                $existing[$slug] = true;
            }
        }
        return $existing;
    }

    private function getRouteResolver(): MenuRouteResolver
    {
        if ($this->routeResolver === null) {
            $this->routeResolver = new MenuRouteResolver(new RouteConverter());
        }
        return $this->routeResolver;
    }

    /**
     * Same URL / route_type rules as MenuItemFormatter (React vs iframe).
     *
     * @param array<string,mixed> $item
     * @return array<string,mixed>
     */
    private function applyRouteConverterToRestoredItem(array $item, string $rawSlug): array
    {
        $resolver = $this->getRouteResolver();
        $adminBaseUrl = \admin_url();
        $adminBasePath = trailingslashit((string) (parse_url($adminBaseUrl, PHP_URL_PATH) ?? ''));
        $decoded = html_entity_decode($rawSlug, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $routeData = $resolver->resolveRouteData($rawSlug, $decoded, $adminBaseUrl, $adminBasePath);
        $href = $resolver->constructHref($routeData['target_path'], $routeData['target_hash']);
        $url = $routeData['url'];
        if ($url === '' && !empty($routeData['target_path'])) {
            $url = \admin_url('admin.php?page=fleekdash') . '#/wp/' . $routeData['target_path'];
        }
        $item['href'] = $href;
        $item['url'] = $url;
        $item['is_wp_native'] = $routeData['is_wp_native'];
        $item['route_type'] = $resolver->resolveRouteType($routeData['route_info'], $routeData['is_wp_native']);
        $item['target'] = $routeData['should_open_new_tab'] ? '_blank' : '_self';
        return $item;
    }

    /**
     * @param array{slug:string,name:string,capability:string,position:float} $coreItem
     * @param array<int, mixed>|null $fallbackRow Raw WordPress menu row from snapshot (title HTML in [0])
     * @return array<string,mixed>
     */
    private function buildCoreItem(array $coreItem, ?array $fallbackRow = null): array
    {
        $slug = $coreItem['slug'];
        $position = $coreItem['position'];
        $name = $coreItem['name'];
        $fromSnapshot = $this->labelFromWpMenuRow($fallbackRow);
        if ($fromSnapshot !== null && $fromSnapshot !== '') {
            $name = $fromSnapshot;
        }
        $item = [
            'name' => $name,
            'title' => $name,
            'href' => $slug,
            'icon' => 'default',
            'capability' => $coreItem['capability'],
            'url' => '',
            'position' => $position,
            'order' => $position,
            'update_count' => 0,
            'is_wp_native' => true,
            'route_type' => 'wordpress_iframe',
            'target' => '_self',
            'id' => $slug . '_' . $position,
            'unique_id' => $slug . '_' . $position,
            'source_slug' => $slug,
            'is_restored_core' => true,
        ];
        return $this->applyRouteConverterToRestoredItem($item, $slug);
    }

    /**
     * @param array<int, mixed>|null $row
     */
    private function labelFromWpMenuRow(?array $row): ?string
    {
        $label = null;
        if ($row !== null && isset($row[0]) && \is_string($row[0])) {
            $stripped = \wp_strip_all_tags($row[0]);
            $decoded = \html_entity_decode($stripped, ENT_QUOTES | ENT_HTML5, 'UTF-8');
            $trimmed = \trim($decoded);
            if ($trimmed !== '') {
                $label = $trimmed;
            }
        }
        return $label;
    }

    /**
     * @return array<string,mixed>
     */
    private function buildRestoredPostTypeItem(
        string $slug,
        string $name,
        string $capability,
        float $position,
    ): array {
        $item = [
            'name' => $name,
            'title' => $name,
            'href' => $slug,
            'icon' => 'default',
            'capability' => $capability,
            'url' => '',
            'position' => $position,
            'order' => $position,
            'update_count' => 0,
            'is_wp_native' => true,
            'route_type' => 'wordpress_iframe',
            'target' => '_self',
            'id' => $slug . '_' . $position,
            'unique_id' => $slug . '_' . $position,
            'source_slug' => $slug,
            'is_restored_post_type' => true,
        ];
        return $this->applyRouteConverterToRestoredItem($item, $slug);
    }
}

