<?php

namespace Mtc\Core\Admin;

use Illuminate\Database\Eloquent\Builder;
use \Illuminate\Database\Eloquent\Model;
use AdminUser;
use CMSNav;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Twig\Environment;

/**
 * Admin\Menu class.  This looks after the admin_menu table
 * using Eloquent base modelling
 *
 * @author william.cameron
 */
class Menu extends Model
{

    public $timestamps = false;
    protected $table = "admin_menu";

    /**
     * Cached menu items keyed by id including bound roles.
     * @var array<int,Menu|null>
     */
    protected static array $cachedById = [];

    /**
     * Cached menu items keyed by constant including bound roles.
     * @var array<string,Menu|null>
     */
    protected static array $cachedByConstant = [];

    protected static bool $cacheInitialized = false;

    protected static function ensureCacheLoaded(): void
    {
        if (self::$cacheInitialized) {
            return;
        }

        self::$cacheInitialized = true;

        $menuItems = self::query()
            ->with('boundRoles')
            ->get();

        foreach ($menuItems as $menu) {
            self::$cachedById[$menu->id] = $menu;
            if (!empty($menu->constant)) {
                self::$cachedByConstant[$menu->constant] = $menu;
            }
        }
    }

    const GLOBAL_KEY = 'admin_menu';

    const MENU_MTC_ONLY = 'mtc. only';
    const MENU_CONFIGURATION = 'Configuration';

    /**
     * Fallback icons for top-level sections that don't have an `icon` set
     * in the admin_menu table. Keys are matched as case-insensitive
     * substrings against the section title; first match wins. Order
     * matters — more specific keys first.
     */
    private const SECTION_ICON_FALLBACKS = [
        'mtc'             => 'fa fa-wrench',
        'administration'  => 'fa fa-user-shield',
        'admin user'      => 'fa fa-user-shield',
        'permission'      => 'fa fa-key',
        'configuration'   => 'fa fa-cog',
        'setting'         => 'fa fa-cog',
        'communication'   => 'fa fa-envelope',
        'content manager' => 'fa fa-copy',
        'content'         => 'fa fa-file-text',
        'cms'             => 'fa fa-file-text',
        'page'            => 'fa fa-file-text',
        'seo'             => 'fa fa-search',
        'file manager'    => 'fa fa-folder-open',
        'shipping'        => 'fa fa-truck',
        'shop'            => 'fa fa-shopping-cart',
        'order'           => 'fa fa-shopping-bag',
        'basket'          => 'fa fa-shopping-basket',
        'product'         => 'fa fa-cube',
        'doctor'          => 'fa fa-user-md',
        'clinic'          => 'fa fa-stethoscope',
        'prescription'    => 'fa fa-medkit',
        'review'          => 'fa fa-star-half-o',
        'member'          => 'fa fa-users',
        'customer'        => 'fa fa-users',
        'message'         => 'fa fa-comments',
        'email'           => 'fa fa-envelope',
        'form'            => 'fa fa-list-alt',
        'report'          => 'fa fa-bar-chart',
        'black list'      => 'fa fa-ban',
        'support'         => 'fa fa-life-ring',
        'favourite'       => 'fa fa-star',
        'site'            => 'fa fa-sitemap',
    ];

    private const SECTION_ICON_DEFAULT = 'fa fa-folder';

    /**
     * Icon to display for this menu item — the explicit `icon` column
     * if set, otherwise a fallback chosen by matching the title against
     * SECTION_ICON_FALLBACKS, otherwise SECTION_ICON_DEFAULT.
     */
    public function displayIcon(): string
    {
        if (!empty($this->icon)) {
            return $this->icon;
        }

        $title = strtolower((string) $this->title);
        foreach (self::SECTION_ICON_FALLBACKS as $needle => $icon) {
            if (strpos($title, $needle) !== false) {
                return $icon;
            }
        }

        return self::SECTION_ICON_DEFAULT;
    }

    protected $fillable = [
        'sub_id',
        'title',
        'path',
        'activePath',
        'constant',
        'icon',
        'new_window',
        'order',
    ];

    /**
     * Does this menu item have sub items
     *
     * @return bool
     */
    public function hasSubItems()
    {
        return ! $this->subItems->isEmpty();
    }

    /**
     * configures and handles the relation of subitems for this menu item
     *
     * @return HasMany
     */
    public function subItems()
    {
        return $this->hasMany(self::class, 'sub_id', 'id')
            ->orderBy('order', 'ASC')
            ->orderBy('title', 'ASC');
    }

    public function boundRoles()
    {
        return $this->hasMany(RolePermission::class, 'permission_id');
    }

    public static function getWithBoundRoles(int $id): ?self
    {
        self::ensureCacheLoaded();

        return self::$cachedById[$id] ?? null;
    }

    public static function getByConstant(string $constant): ?self
    {
        self::ensureCacheLoaded();

        return self::$cachedByConstant[$constant] ?? null;
    }

    /**
     * Define relationship with parent menu item
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public function parent()
    {
        return $this->belongsTo(self::class, 'sub_id');
    }

    /**
     * Generate HTML for all sub pages of a particular ID
     *
     * @param $start Int  ID of parent to get children of
     * @return $navigation mixed Menu structure in HTML
     */
    public static function getSubPages($start = 0)
    {
        $navigation = '';
        $sub_pages = self::where('sub_id', $start)
                    ->orderBy('order', 'ASC')
                    ->orderBy('title', 'ASC')
                    ->get();

        if (!empty($sub_pages)) {
            ob_start();
            require SITE_PATH . '/core/admin/includes/admin_menu_nav.php';
            $navigation = ob_get_contents();
        }

        return $navigation;
    }


    /**
     * Scope - ChildOf()
     * Define scope to check if menu item is a child of different menu item
     *
     * @param Builder $query Query builder object
     * @param int $menu_id parent menu item ID
     * @return Builder
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public function scopeChildOf($query, $menu_id)
    {
        return $query->where('sub_id', $menu_id);
    }

    /**
     * Scope - isRoot()
     * Find only root level menu item
     *
     * @param Builder $query Query builder object
     * @return Builder
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public function scopeIsRoot($query)
    {
        return $query->ChildOf(0);
    }

    /**
     * Check whether the current admin user is allowed to view this page
     * @return bool whether user is allowed to this admin menu
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public function allowed(): bool
    {
        // If the user doesn't have access to this admin menu, hide it
        if (!AdminUser::can($this->id, $_SESSION['adminId'])) {
            return false;
        }

        // If there's no constant, show it always
        if (empty($this->constant)) {
            return true;
        }

        // If the constant is config value and it's enabled, show it
        if (!empty(config($this->constant))) {
            return true;
        }

        // If it's the old approach with constant from settings, and it's true, show it
        return defined($this->constant) &&
            constant($this->constant);
    }

    /**
     * Check whether menu item has children that are active
     * @return bool whether menu item has children that are active
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public function hasActiveChild(): bool
    {
        foreach ($this->subItems as $child_menu) {
            if ($child_menu->activePath && strstr($_SERVER['SCRIPT_NAME'], $child_menu->activePath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check whether menu item is in active path
     * @return bool whether the menu item is within active path
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public function isActive()
    {
        return $this->activePath && (bool)strstr($_SERVER['REQUEST_URI'], $this->activePath);
    }

    /**
     * Check if CMS tree should be shown for this menu item
     * @return string CMS tree under this item or empty string
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     * //TODO: Move this to CMS
     */
    public function showCMSTreeIfAllowed($admin_user)
    {
        $is_admin = $admin_user instanceof AdminUser ?
            $admin_user->isMtcAdmin() :
            $admin_user->role === 0;

        if (
            !empty($this->title)
            && !empty($this->parent->title)
            && $this->activePath
            && strstr($_SERVER['SCRIPT_NAME'], $this->activePath)
            && $this->parent->title === "Content Manager"
            && $this->title === "Manage Pages"
        ) {

            $site_tree = CMSNav::siteTree(0, PHP_INT_MAX, [
                'check_innav' => false,
                'check_published' => false
            ]);

            $current_active_page = filter_input(INPUT_GET, 'page');

            $options = [
                'first_container_id' => 'site-tree',
                'title_container_tag' => 'span',
                'active_page_id' => $current_active_page,
                'is_admin' => $is_admin
            ];

            return CMSNav::generateSiteTreeMenu($site_tree, $options);
        }
        return '';
    }

    /**
     * Check if CMS tree should be shown for this menu item
     * @return string CMS tree under this item or empty string
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     * //TODO: Move this to CMS
     */
    public static function showCMSTree(User $admin_user)
    {
        if (stripos(request()->getRequestUri(), 'admin/content_manager') === false) {
            return '';
        }

        $site_tree = CMSNav::siteTree(0, PHP_INT_MAX, [
            'check_innav' => false,
            'check_published' => false
        ]);


        $is_admin = $admin_user->hasRole('mtc');

        $options = [
            'first_container_id' => 'site-tree',
            'title_container_tag' => 'span',
            'active_page_id' => request()->input('page', 0),
            'is_admin' => $is_admin
        ];

        ob_start();
        CMSNav::generateSiteTreeMenu($site_tree, $options);
        return ob_get_clean();
    }

    /**
     * Gets admin menu and injects plugin menus
     *
     * @return mixed
     */
    public static function getAdminMenu()
    {
        static $cachedMenu;

        if ($cachedMenu !== null) {
            return $cachedMenu;
        }

        $adminMenu = self::query()
            ->with([
                'subItems',
                'subItems.parent',
                'subItems.boundRoles',
                'boundRoles',
            ])
            ->orderBy('order', 'ASC')
            ->isRoot()
            ->get()
            ->reject(function (Menu $menu_item) {
                // We can't allow non-mtc users to see mtc only
                if ($_SESSION['adminId'] != MTCADMIN_USERID && $menu_item->id == 1) {
                    return true;
                }

                // reject disallowed entries
                if (!$menu_item->allowed()) {
                    return true;
                }

                // reject menus without children
                if (count($menu_item->subItems) == 0) {
                    return true;
                }

                // Reject sections whose children are RBAC permission
                // definitions rather than navigable pages. These rows
                // exist in admin_menu so the roles/permissions UI can
                // read them, but they have no path so clicking does
                // nothing.
                $hasNavigableChild = $menu_item->subItems
                    ->contains(fn(Menu $sub) => !empty($sub->path) && $sub->allowed());
                if (!$hasNavigableChild) {
                    return true;
                }

                // Check if we have child elements that are allowed to show
                $allowed_menu_entries = $menu_item->subItems
                    ->each(function (Menu $sub_item) {
                        // reject disallowed entries
                        if (!$sub_item->allowed()) {
                            return false;
                        }

                        // allow if no constant is set
                        if (empty($sub_item->constant)) {
                            return true;
                        }

                        // Check the constant value
                        return constant($sub_item->constant);
                    });

                // Reject elements without any allowed children
                return count($allowed_menu_entries) == 0;
            });

        return $cachedMenu = $adminMenu;
    }
}
