<?php
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2021 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */

namespace App\Navbar\LegacyHandler;

use App\Engine\LegacyHandler\LegacyHandler;
use App\Engine\LegacyHandler\LegacyScopeState;
use App\Module\Service\ModuleNameMapperInterface;
use App\Module\Service\ModuleRegistryInterface;
use App\Navbar\Entity\Navbar;
use App\Routes\Service\NavigationProviderInterface;
use App\Routes\Service\RouteConverterInterface;
use GroupedTabStructure;
use SugarView;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Class NavbarHandler
 */
class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
{
    public const HANDLER_KEY = 'navbar-handler';

    /**
     * @var ModuleNameMapperInterface
     */
    protected $moduleNameMapper;

    /**
     * @var RouteConverterInterface
     */
    protected $routeConverter;

    /**
     * @var array
     */
    protected $menuItemMap;

    /**
     * @var ModuleRegistryInterface
     */
    protected $moduleRegistry;

    /**
     * @var array
     */
    protected $moduleRouting;

    /**
     * @var array
     */
    protected $navbarAdministrationOverrides;

    /**
     * @var array
     */
    protected $quickActionsConfig;

    /**
     * SystemConfigHandler constructor.
     * @param string $projectDir
     * @param string $legacyDir
     * @param string $legacySessionName
     * @param string $defaultSessionName
     * @param LegacyScopeState $legacyScopeState
     * @param array $menuItemMap
     * @param ModuleNameMapperInterface $moduleNameMapper
     * @param RouteConverterInterface $routeConverter
     * @param ModuleRegistryInterface $moduleRegistry
     * @param RequestStack $session
     * @param array $moduleRouting
     * @param array $navbarAdministrationOverrides
     * @param array $quickActions
     */
    public function __construct(
        string $projectDir,
        string $legacyDir,
        string $legacySessionName,
        string $defaultSessionName,
        LegacyScopeState $legacyScopeState,
        array $menuItemMap,
        ModuleNameMapperInterface $moduleNameMapper,
        RouteConverterInterface $routeConverter,
        ModuleRegistryInterface $moduleRegistry,
        RequestStack $session,
        array $moduleRouting,
        array $navbarAdministrationOverrides,
        array $quickActions
    ) {
        parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState,
            $session);
        $this->moduleNameMapper = $moduleNameMapper;
        $this->routeConverter = $routeConverter;
        $this->menuItemMap = $menuItemMap;
        $this->moduleRegistry = $moduleRegistry;
        $this->moduleRouting = $moduleRouting;
        $this->navbarAdministrationOverrides = $navbarAdministrationOverrides;
        $this->quickActionsConfig = $quickActions;
    }

    /**
     * @inheritDoc
     */
    public function getHandlerKey(): string
    {
        return self::HANDLER_KEY;
    }

    /**
     * Get Navbar using legacy information
     * @return Navbar
     */
    public function getNavbar(): Navbar
    {
        $this->init();

        $this->disableTranslations();

        $this->startLegacyApp();

        $navbar = new Navbar();
        $sugarView = new SugarView();

        $accessibleModules = $this->getAccessibleModulesList();
        $accessibleModulesNameMap = $this->createFrontendNameMap($accessibleModules);
        $displayModules = $this->getDisplayEnabledModules();

        $displayModulesMameMap = [];

        foreach ($displayModules as $module => $value) {
            if (isset($accessibleModulesNameMap[$module])) {
                $displayModulesMameMap[$module] = $accessibleModulesNameMap[$module];
            }
        }

        $navbar->tabs = array_values($displayModulesMameMap);
        $navbar->groupedTabs = $this->fetchGroupedNavTabs($displayModules, $displayModulesMameMap);

        $navbar->modules = $this->buildModuleInfo($sugarView, $accessibleModulesNameMap);

        $navbar->userActionMenu = $this->fetchUserActionMenu();
        $navbar->maxTabs = $this->getMaxTabs();

        $navbar->type = $this->getNavigationType();

        $quickActions = $this->getQuickActions($navbar);

        $navbar->quickActions = $quickActions;

        $this->close();

        return $navbar;
    }

    /**
     * Get list of modules the user has access to
     * @return array
     */
    protected function getAccessibleModulesList(): array
    {
        return $this->moduleRegistry->getUserAccessibleModules();
    }

    /**
     * Map legacy names to front end names
     * @param array $legacyTabNames
     * @return array
     */
    protected function createFrontendNameMap(array $legacyTabNames): array
    {
        $map = [];

        foreach ($legacyTabNames as $legacyTabName) {
            $map[$legacyTabName] = $this->moduleNameMapper->toFrontEnd($legacyTabName);
        }

        return $map;
    }

    /**
     * Fetch modules that are configured to display
     * @return array
     * Based on @see SugarView::displayHeader
     */
    protected function getDisplayEnabledModules(): array
    {
        global $current_user;

        return query_module_access_list($current_user);
    }

    /**
     * Fetch Grouped Navigation tabs
     * @param array $displayModules
     * @param array $moduleNameMap
     * @return array
     * Based on @see SugarView::displayHeader
     */
    protected function fetchGroupedNavTabs(array $displayModules, array $moduleNameMap): array
    {
        $output = [];

        /* @noinspection PhpIncludeInspection */
        require_once 'include/GroupedTabs/GroupedTabStructure.php';

        $modules = get_val_array($displayModules);
        $groupedTabStructure = $this->getTabStructure($modules);

        foreach ($groupedTabStructure as $mainTab => $subModules) {
            $submoduleArray = [];

            foreach ($subModules['modules'] as $submodule => $submoduleLabel) {
                if (!empty($moduleNameMap[$submodule])) {
                    $submoduleArray[] = $moduleNameMap[$submodule];
                }
            }

            $output[] = [

                'name' => $mainTab,
                'labelKey' => $mainTab,
                'modules' => array_values($submoduleArray)

            ];
        }

        return $output;
    }

    /**
     * Get Tab Structure
     * @param array $modules
     * @return array
     */
    protected function getTabStructure(array $modules): array
    {
        return (new GroupedTabStructure())->get_tab_structure($modules, '', false, true);
    }

    /**
     * @param SugarView $sugarView
     * @param array $legacyNameMap
     * @return array
     */
    protected function buildModuleInfo(SugarView $sugarView, array $legacyNameMap): array
    {
        $modules = [];

        foreach ($legacyNameMap as $legacyName => $frontendName) {
            $menu = $this->buildSubModule($sugarView, $legacyName, $frontendName);
            $modules[$frontendName] = [
                'path' => $frontendName,
                'defaultRoute' => "./#/$frontendName",
                'name' => $frontendName,
                'labelKey' => $legacyName,
                'menu' => $menu
            ];

        }
        foreach ($this->navbarAdministrationOverrides ?? [] as $specialModule) {
            if (!empty($modules[$specialModule]) && !empty($modules['administration'])) {
                $modules[$specialModule] = $modules['administration'];
            }
        }

        return $modules;
    }

    /**
     * Retrieve legacy menu information and build suite 8 entry
     * @param SugarView $sugarView
     * @param string $legacyModule
     * @param string $frontendModule
     * @return array
     */
    protected function buildSubModule(SugarView $sugarView, string $legacyModule, string $frontendModule): array
    {
        $subMenu = [];
        $legacyMenuItems = $sugarView->getMenu($legacyModule) ?? [];

        if ($frontendModule === 'administration') {

            global $current_language;
            $admin_mod_strings = return_module_language($current_language, 'Administration') ?? [];

            $admin_group_header = [];
            require 'modules/Administration/metadata/adminpaneldefs.php';

            $admin_group_header = $admin_group_header ?? [];

            $administration_menu = [];
            foreach ($admin_group_header as $adminEntry) {

                $administration_menu[] = [
                    "",
                    $admin_mod_strings[$adminEntry[0] ?? ''] ?? '',
                    'View',
                    'Administration',
                    $adminEntry[3] ?? ''
                ];
            }

            $legacyMenuItems = $administration_menu ?? [];
        }

        foreach ($legacyMenuItems as $legacyMenuItem) {

            [$url, $label, $action] = $legacyMenuItem ?? [];
            $routeInfo = [
                'module' => 'Administration',
                'route' => '',
                'params' => []
            ];

            $quickAction = $legacyMenuItem[4] ?? null;
            $type = $legacyMenuItem[5] ?? '';
            $process = $legacyMenuItem[6] ?? [];

            if (!empty($url)) {
                $routeInfo = $this->routeConverter->parseUri($url) ?? [];
            }

            $subMenuItem = [
                'name' => $action,
                'labelKey' => $this->mapEntry($frontendModule, $action, 'labelKey', $label),
                'url' => $this->mapEntry($frontendModule, $action, 'url', $routeInfo['route'] ?? ''),
                'process' => $process['process'] ?? '',
                'params' => $routeInfo['params'] ?? [],
                'icon' => $this->mapEntry($frontendModule, $action, 'icon', ''),
                'actionLabelKey' => $this->mapEntry($frontendModule, $action, 'actionLabelKey', ''),
                'quickAction' => $quickAction ?? false,
                'type' => $type ?? ''
            ];

            if (!empty($subMenuItem) && (($quickAction ?? null) === null)) {
                $url = $subMenuItem['url'];
                $subMenuItem['quickAction'] = $this->isModuleQuickAction($url);
            }

            if (!empty($subMenuItem) && empty($type)) {
                $url = $subMenuItem['url'];
                $subMenuItem['type'] = $this->getActionType($url);
            }

            if (!empty($routeInfo['module'])) {
                $subMenuItem['module'] = $this->moduleNameMapper->toFrontEnd($routeInfo['module']);
            }

            if ($subMenuItem['module'] === 'security-groups' && $subMenuItem['actionLabelKey']) {
                $subMenuItem['labelKey'] = $subMenuItem['actionLabelKey'];
            }

            if ($subMenuItem['module'] === 'administration') {
                $subMenuItem['sublinks'] = $this->setLinks($legacyMenuItem[4]);
            }
            $subMenu[] = $subMenuItem;
        }

        return $subMenu;
    }

    /**
     * Convert legacy submenu data and return it
     * @param array $legacyArray
     * @return array
     */
    protected function setLinks(array $legacyArray): array
    {

        if (empty($legacyArray)) {
            return [];
        }

        foreach ($legacyArray as $linkGroupKey => $linkGroup) {
            $mappedLinkGroup = [];
            if (empty($linkGroup)) {
                continue;
            }
            foreach ($linkGroup as $linkKey => $link) {
                unset($mappedLinkGroup[$linkKey]);
                $path = $this->routeConverter->convertUri($link[3]) ?? '';

                $mappedLink = [
                    'name' => $link[0] ?? '',
                    'labelKey' => html_entity_decode($link[1] ?? '', ENT_QUOTES),
                    'actionLabelKey' => '',
                    'url' => $path,
                    'icon' => '',
                ];

                $query = parse_url($path, PHP_URL_QUERY);
                if ($query) {
                    parse_str($query, $params);
                    $mappedLink['params'] = $params;
                    $path = str_replace('?' . $query, '', $path);
                    $mappedLink['link'] = $path;
                }

                $mappedLinkGroup[$linkKey] = $mappedLink;
            }
            $mapEntry[$linkGroupKey] = $mappedLinkGroup;
            $linkGroupKeys = array_keys($mapEntry);

        }

        $linkGroups = [];
        for ($j = 0, $jMax = count($linkGroupKeys); $j < $jMax; $j++) {
            $linkGroup = $mapEntry[$linkGroupKeys[$j]];
            $links = array_values($linkGroup);
            for ($i = 0, $iMax = count($links); $i < $iMax; $i++) {
                $linkGroups[] = $links[$i];
            }
        }

        return $linkGroups;
    }

    /**
     * Map entry if defined on the configuration
     * @param string $moduleName
     * @param string $action
     * @param string $entry
     * @param string $default
     * @return string
     */
    protected function mapEntry(string $moduleName, string $action, string $entry, string $default): string
    {
        $module = $moduleName;
        if ($this->isEntryMapped($action, $entry, $module)) {
            $module = 'default';
        }

        if ($this->isEntryMapped($action, $entry, $module)) {
            return $default;
        }

        return $this->menuItemMap[$module][$action][$entry];
    }

    /**
     * Check if there is an entry mapped for the given module
     * @param string $action
     * @param string $entry
     * @param string $module
     * @return bool
     */
    protected function isEntryMapped(string $action, string $entry, string $module): bool
    {
        return empty($this->menuItemMap[$module]) ||
            empty($this->menuItemMap[$module][$action]) ||
            empty($this->menuItemMap[$module][$action][$entry]);
    }

    /**
     * Fetch the user action menu
     */
    protected function fetchUserActionMenu(): array
    {
        global $current_user;

        $actions['LBL_PROFILE'] = [
            'name' => 'profile',
            'labelKey' => 'LBL_PROFILE_EDIT',
            'url' => 'index.php?module=Users&action=EditView&record=' . $current_user->id,
            'icon' => '',
        ];

        // Order matters
        $actionLabelMap = [
            'LBL_PROFILE' => 'profile',
            'LBL_EMPLOYEES' => 'employees',
            'LBL_TRAINING' => 'training',
            'LBL_ADMIN' => 'admin',
            'LNK_ABOUT' => 'about',
            'LBL_LOGOUT' => 'logout',
        ];

        $actionKeys = array_keys($actionLabelMap);

        foreach ($this->getGlobalControlLinks() as $key => $value) {
            foreach ($value as $linkAttribute => $attributeValue) {
                // get the main link info
                if ($linkAttribute === 'linkinfo') {

                    $labelKey = key($attributeValue);
                    $name = $labelKey;
                    if (!empty($actionLabelMap[$labelKey])) {
                        $name = $actionLabelMap[$labelKey];
                    } else {
                        $actionKeys[] = $labelKey;
                    }

                    $actions[$labelKey] = [
                        'name' => $name,
                        'labelKey' => $labelKey,
                        'url' => current($attributeValue),
                        'icon' => '',
                    ];
                }
            }
        }

        $userActionMenu = [];

        foreach ($actionKeys as $key) {
            if (isset($actions[$key])) {
                $userActionMenu[] = $actions[$key];
            }
        }

        return array_values($userActionMenu);
    }

    /**
     * Get global control links from legacy
     * @return array
     */
    protected function getGlobalControlLinks(): array
    {
        $global_control_links = [];

        /* @noinspection PhpIncludeInspection */
        require 'include/globalControlLinks.php';

        return $global_control_links;
    }

    /**
     * Get max number of tabs
     * @return int
     * Based on @link SugarView
     */
    protected function getMaxTabs(): int
    {
        global $current_user;

        $maxTabs = $current_user->getPreference('max_tabs');

        // If the max_tabs isn't set incorrectly, set it within the range, to the default max sub tabs size
        if (!isset($maxTabs) || $maxTabs <= 0 || $maxTabs > 10) {
            // We have a default value. Use it
            if (isset($GLOBALS['sugar_config']['default_max_tabs'])) {
                $maxTabs = $GLOBALS['sugar_config']['default_max_tabs'];
            } else {
                $maxTabs = 8;
            }
        }

        return $maxTabs;
    }

    /**
     * @return string
     */
    protected function getNavigationType(): string
    {
        global $current_user;
        $navigationType = $current_user->getPreference('navigation_paradigm');

        if (empty($navigationType)) {
            global $sugar_config;
            $navigationType = $sugar_config['default_navigation_paradigm'] ?? 'm';
        }

        if (empty($navigationType)) {
            $navigationType = 'm';
        }

        return $navigationType;
    }

    /**
     * @inheritDoc
     */
    public function getModuleRouting(): array
    {
        $this->init();

        $visibleModules = $this->moduleNameMapper->getVisibleModules();
        $routes = $this->moduleRouting;

        foreach ($visibleModules as $visibleModule) {
            $frontendName = $this->moduleNameMapper->toFrontEnd($visibleModule);

            $name = $frontendName ?? $visibleModule;

            if (empty($routes[$name])) {
                $routes[$name] = [
                    'index' => true,
                    'list' => true,
                    'record' => true
                ];
            }
        }

        $this->close();

        return $routes;
    }

    /**
     * @param $url
     * @return bool
     */
    protected function isModuleQuickAction($url): bool
    {
        $regex = [
            '/(edit(\/)?$)|(edit(\/)?\?)/',
            '/(create(\/)?$)|(create(\/)?\?)/',
            '/\/import\//',
            '/(compose(\/)?$)|(compose(\/)?\?)/',
            '/(wizard-home(\/)?$)|(wizard-home(\/)?\?)/'
        ];
        foreach ($regex as $item) {
            if (preg_match($item, $url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param $url
     * @return string
     */
    protected function getActionType($url): string
    {
        $regex = [
            '/(edit(\/)?$)|(edit(\/)?\?)/',
            '/(create(\/)?$)|(create(\/)?\?)/',
            '/(compose(\/)?$)|(compose(\/)?\?)/',
            '/(wizard-home(\/)?$)|(wizard-home(\/)?\?)/'
        ];
        foreach ($regex as $item) {
            if (preg_match($item, $url)) {
                return 'create';
            }
        }
        return '';
    }

    /**
     * @param Navbar $navbar
     * @return array
     */
    protected function getQuickActions(Navbar $navbar): array
    {
        $useNavigationModules = $this->quickActionsConfig['use_navigation_modules'] ?? true;
        $preDefinedQuickActions = $this->quickActionsConfig['actions'] ?? [];
        $maxQuickActions = $this->quickActionsConfig['max_number'] ?? 7;

        if ($useNavigationModules === false && !empty($preDefinedQuickActions)) {
            return $preDefinedQuickActions;
        }

        $quickActions = [];
        $count = 0;
        foreach ($navbar->modules ?? [] as $module => $entry) {
            $menu = $entry['menu'] ?? [];

            if (empty($menu)) {
                continue;
            }

            foreach ($menu as $menuEntry) {
                $type = $menuEntry['type'] ?? '';
                if (empty($menuEntry) || $type !== 'create') {
                    continue;
                }

                $count++;

                $quickActions[] = [
                    'name' => $menuEntry['name'] ?? '',
                    'labelKey' => $menuEntry['labelKey'] ?? '',
                    'url' => str_replace('/#/', '/', $menuEntry['url'] ?? ''),
                    'params' => $menuEntry['params'] ?? [],
                    'icon' => $menuEntry['icon'] ?? '',
                    'quickAction' => $menuEntry['quickAction'] ?? false,
                    'type' => $type,
                    'module' => $module,
                    'process' => $menuEntry['process'] ?? null
                ];

                if ($count >= $maxQuickActions) {
                    break;
                }
            }

            if ($count >= $maxQuickActions) {
                break;
            }


        }

        return $quickActions;
    }
}
