<?php

namespace Ignite\Core\Http\ViewComposers;

use Ignite\Core\Contracts\Repositories\PageRepository;
use Ignite\Core\Models\Menu\Item;
use Ignite\Core\Models\Menu\NavigationMenu;
use Illuminate\Support\HtmlString;
use Illuminate\View\View;

class NavigationMenuComposer
{
    /**
     * @var PageRepository
     */
    protected $pageRepository;

    /**
     * @var NavigationMenu
     */
    protected $manager;

    /**
     * @param PageRepository $pages
     * @param NavigationMenu $manager
     */
    public function __construct(PageRepository $pages, NavigationMenu $manager)
    {
        $this->pageRepository = $pages;
        $this->manager = $manager;
    }

    /**
     * Bind data to the view.
     *
     * @param  View $view
     * @return void
     */
    public function compose(View $view)
    {
        if (! config('core.theming.top_navigation_grouped', true)) {
            $this->composeLegacy($view);
            return;
        }

        $this->manager->setCollapseSingleChildSubmenu(true);
        // Start from Core pages, then let the menu event listeners extend the tree before we regroup it.
        $this->manager->merge($this->pageRepository->findForNavigation());
        $this->removeHomeMenuItemWhenHidden();

        $this->addCoreDirectItems();
        $this->addOverviewMenu();
        $this->addHelpMenu();

        $view->with('menu', new HtmlString($this->manager->render()));
    }

    /**
     * Bind legacy (flat) top navigation behavior.
     *
     * @param  View $view
     * @return void
     */
    protected function composeLegacy(View $view): void
    {
        $pages = $this->pageRepository->findForNavigation();
        $navigation = $this->navigationConfig();
        // reset position and label for each page
        foreach ($pages as $page) {
            $label = data_get($navigation, $page->getCode() . '.label');
            $position = data_get($navigation, $page->getCode() . '.position');
            if (!empty($label)) {
                $page->setLabel($label);
            }
            if (!empty($position)) {
                $page->setPosition($position);
            }
        }
        $this->manager->merge($pages);

        $this->removeHomeMenuItemWhenHidden();

        if (! auth()->check()) {
            $this->manager->merge(collect($this->getUnauthenticatedItems()));
        } else {
            $this->manager->remove('/enroll');

            $this->addChildrenToParent(
                $this->getAuthenticatedItems(),
                $this->createTopMenuItem(
                    '/menu-account',
                    data_get($navigation, 'account.label'),
                    (int) data_get($navigation, 'account.position'),
                    true
                )
            );
        }

        $view->with('menu', new HtmlString($this->manager->render()));
    }

    /**
     * Remove the Home item when it should not be visible in top navigation.
     *
     * @return void
     */
    protected function removeHomeMenuItemWhenHidden(): void
    {
        if (!auth()->check() && !config('core.theming.enable_home_as_guest', true)) {
            $this->manager->remove('/');
        }
    }

    /**
     * Add Core-owned direct links that are not backed by Page entities.
     *
     * @return void
     */
    protected function addCoreDirectItems(): void
    {
        $navigation = $this->navigationConfig();

        if (! auth()->check()) {
            // Guests only need a single sign-in entry in the top navigation.
            $this->manager->add(
                $this->createTopMenuItem(
                    url('/login'),
                    data_get($navigation, 'sign_in.label'),
                    (int) data_get($navigation, 'sign_in.position')
                )
            );
            return;
        }
        $this->manager->remove('/enroll');

        $this->addChildrenToParent(
            $this->getAuthenticatedItems(),
            $this->createTopMenuItem(
                '/menu-account',
                data_get($navigation, 'account.label'),
                (int) data_get($navigation, 'account.position'),
                true
            )
        );
    }

    /**
     * Get the additional menu links for when a user is authenticated.
     *
     * @return array
     */
    protected function getAuthenticatedItems()
    {
        $navigation = $this->navigationConfig();

        $items = [
            $this->createTopMenuItem(
                url('/participant/profile'),
                data_get($navigation, 'profile.label'),
                (int) data_get($navigation, 'profile.position')
            ),
            $this->createTopMenuItem(
                url('/transactions'),
                data_get($navigation, 'transactions.label'),
                (int) data_get($navigation, 'transactions.position')
            ),
            $this->createTopMenuItem(
                url('/logout'),
                data_get($navigation, 'logout.label'),
                (int) data_get($navigation, 'logout.position')
            ),
        ];

        if (auth()->user()->can('core.dashboard.browse')) {
            $items[] = $this->createTopMenuItem(
                url('/admin'),
                data_get($navigation, 'admin.label'),
                (int) data_get($navigation, 'admin.position')
            );
        }

        return $items;
    }

    /**
     * Get unauthenticated items for legacy top navigation mode.
     *
     * @return array
     */
    protected function getUnauthenticatedItems()
    {
        $navigation = $this->navigationConfig();

        return [
            $this->createTopMenuItem(
                url('/login'),
                data_get($navigation, 'sign_in.label'),
                (int) data_get($navigation, 'sign_in.position')
            )
        ];
    }

    /**
     * Group overview pages and direct links under a single parent when appropriate.
     *
     * @return void
     */
    protected function addOverviewMenu(): void
    {
        if (! auth()->check()) {
            return;
        }

        $navigation = $this->navigationConfig();

        $this->addChildrenToParent(
            [ $this->manager->get('/eligibility') ],
            $this->createTopMenuItem(
                '/menu-overview',
                data_get($navigation, 'overview.label'),
                (int) data_get($navigation, 'overview.position'),
                true
            )
        );
    }

    /**
     * Group help pages under a single parent when more than one viewable item remains.
     *
     * @return void
     */
    protected function addHelpMenu(): void
    {
        $navigation = $this->navigationConfig();

        $this->addChildrenToParent(
            [
                $this->manager->get('/faq'),
                $this->manager->get('/terms'),
                $this->manager->get('/contact'),
                $this->manager->get('/privacy'),
            ],
            $this->createTopMenuItem(
                '/menu-help',
                data_get($navigation, 'help.label'),
                (int) data_get($navigation, 'help.position'),
                true
            )
        );
    }

    /**
     * Add a collection of child items to a parent menu item, then remove them from the top-level.
     *
     * @param array
     * @param Item
     */
    protected function addChildrenToParent(array $items, Item $parent)
    {
        $this->manager->add($parent);
        collect($items)
            ->filter(function ($item) {
                return $item;
            })->each(function ($item) use($parent) {
                $this->manager->addChildToParentMenu($item, $parent->getMenuKey());
                // remove items from top-level navigation
                $this->manager->remove($item->getMenuKey());
            });
    }

    /**
     * Create a top navigation menu item from config-backed values.
     *
     * @param  string $url
     * @param  string $label
     * @param  int $position
     * @param  bool $allow
     * @return Item
     */
    protected function createTopMenuItem(string $url, string $label, int $position, bool $allow = true): Item
    {
        return new Item($url, $label, $position, $allow);
    }

    /**
     * Get the merged navigation configuration.
     *
     * @return array
     */
    protected function navigationConfig(): array
    {
        return config('core.navigation', []);
    }

}
