from odoo import api, fields, models, tools


class IrUiMenu(models.Model):
    _inherit = "ir.ui.menu"

    # groups= is a view-layer nicety (hides the field from anyone who can't
    # act on it) -- the real access control is the model's own existing
    # ir.model.access.csv rows (base.group_system already has exclusive
    # write/create/unlink on ir.ui.menu; base.group_user is read-only).
    xeno_extra_user_ids = fields.Many2many(
        "res.users", "ir_ui_menu_xeno_extra_user_rel", "menu_id", "user_id",
        string="Also Visible To", groups="base.group_system",
        help="Users who can see this menu even if none of their Groups "
             "match. Purely additive -- never removes visibility from "
             "anyone who already has it via Groups (or the default, when "
             "Groups is empty).",
    )
    xeno_icon = fields.Char(
        string="Sidebar Icon", groups="base.group_system",
        help="FontAwesome class (e.g. fa-plane) shown for this item in "
             "the custom sidebar. Leave blank to keep today's default: "
             "apps fall back to a built-in module->icon map, sub-menu "
             "items show no icon.",
    )

    @api.model
    @tools.ormcache("self.env.uid", "debug")
    def _visible_menu_ids(self, debug=False):
        """Core's own version is keyed by the user's *set of groups*
        (frozenset(self.env.user._get_group_ids())), which is correct only
        as long as visibility is purely group-derived -- two users with
        identical groups always see identical menus. Once individual
        per-user grants (xeno_extra_user_ids) exist, that's no longer
        true, so this override re-keys the cache to the actual user id;
        otherwise one user's individual grant could leak to/from anyone
        who happens to share their exact group set. Registry-wide
        clear_cache() (already called by ir.ui.menu's own create/write/
        unlink) clears this cache too, same as core's.
        """
        visible = super()._visible_menu_ids(debug=debug)
        extra = self.sudo().search([("xeno_extra_user_ids", "in", self.env.uid)])
        return visible | frozenset(extra.ids) if extra else visible

    def load_web_menus(self, debug):
        """Injects each menu's xeno_icon (if set) into the JSON payload the
        webclient actually consumes. NOT an override of load_menus (see
        note below) -- load_web_menus (defined in the "web" module, not
        "base") is what /web/webclient/load_menus really calls, and it
        rebuilds a brand-new dict with a fixed, named set of keys (id,
        name, children, appID, xmlid, actionID, actionModel, actionPath,
        webIcon, webIconData, webIconDataMimetype) -- it does NOT pass
        through arbitrary extra keys from load_menus()'s own dict, so
        patching that one alone would be silently dropped before ever
        reaching the browser. No new cache layer is needed here:
        load_web_menus itself isn't ormcache-decorated (it relies entirely
        on load_menus's own per-user cache underneath), so this override
        just wraps that already-correct result with a cheap batched merge.
        """
        web_menus = super().load_web_menus(debug)
        ids = [k for k in web_menus if k != "root"]
        icons = {m.id: m.xeno_icon for m in self.sudo().browse(ids) if m.xeno_icon}
        for menu_id, icon in icons.items():
            web_menus[menu_id]["xenoIcon"] = icon
        return web_menus
