/* Iwakiri White Label host shell. Wraps PayBO as the "Payments" module.

   Structure (top → bottom):
     1. Navy strip with Saldo / Fido / Disponibilità + language + user avatar
        (same pattern as the existing White Label)
     2. Blue horizontal nav with the White Label's host menu items. "Payments"
        is the active parent.
     3. Sticky secondary nav (light strip) showing all PayBO sections as icon +
        label chips — always visible, equivalent chrome weight to the left rail.
     4. Floating left icon rail for fast switching between PayBO sections.

   Notes:
   - Only the "Payments" item of the host nav is wired — clicking any other
     host entry emits a neutral demo toast clarifying that it lives in the
     White Label, not PayBO.
   - This is the layout the White Label engineers should hook into:
     `<PayboApp />` goes inside the "Payments" route of the host. */

/* Pending cascade holds. Upstream: CascadeHoldService::pendingCount(), the
   only badge PayBO renders, and it is hidden entirely at zero. Declared once
   here so the Payments parent and the To-Confirm section cannot disagree.
   Must match the row count PayboToConfirm.jsx generates. */
const PAYBO_TO_CONFIRM_COUNT = 9;

/* Report dropdown — the list of reports surfaced when hovering the "Report"
   item of the blue host nav. Labels resolve via window.T() at render time. */
const REPORT_MENU = [
  { id: "bonusRetention",     i18n: "report.bonusRetention",     label: "Bonus Retention", route: "bonus-retention" },
  { id: "conversion",         i18n: "report.conversion",         label: "Conversion",      route: "report-conversion" },
  { id: "players",            i18n: "report.players",            label: "Players Report", route: "report-players" },
  { id: "betting",            i18n: "report.betting",            label: "Betting", route: "report-betting" },
  { id: "betType",            i18n: "report.betType",            label: "Bet type", route: "report-bettype" },
  { id: "daily",              i18n: "report.daily",              label: "Daily Report", route: "report-daily" },
  { id: "transactions",       i18n: "report.transactions",       label: "Transactions", route: "report-transactions" },
  { id: "creditTransactions", i18n: "report.creditTransactions", label: "Credit Transactions", route: "report-credittx" },
  { id: "networkLiabilities", i18n: "report.networkLiabilities", label: "Network liabilities", route: "report-network-liab" },
  { id: "withdrawalRequests", i18n: "report.withdrawalRequests", label: "My withdrawal requests", route: "report-withdrawals" },
  /* Affiliates has NO sidebar entry on the real platform — it is reachable by
     URL only. Listed here anyway, because a prototype nobody can navigate to
     is not a prototype; the screen itself says it is URL-only upstream. */
  { id: "affiliates",        i18n: "report.affiliates",        label: "Affiliates report", route: "report-affiliates" },
  { id: "commissions",        i18n: "report.commissions",        label: "Commissions", route: "report-commissions" },
  { id: "providerCosts",      i18n: "report.providerCosts",      label: "Provider Costs", route: "report-costs" },
  { id: "netWin",             i18n: "report.netWin",             label: "NetWin", route: "report-netwin" },
  { id: "dailyPerformance",   i18n: "report.dailyPerformance",   label: "Daily Performance", route: "report-dailyperf" },
  { id: "summary",            i18n: "report.summary",            label: "Summary", route: "report-summary" },
  { id: "business",           i18n: "report.business",           label: "Business report", route: "report-business" },
];

/* Settings dropdown (host menu). */
const SETTINGS_MENU = [
  { id: "languages",         label: "Languages",               route: "settings-languages" },
  { id: "depositMethods",    label: "Deposit methods",         route: "settings-deposit-methods" },
  { id: "withdrawalMethods", label: "Withdrawal methods",      route: "settings-withdrawal-methods" },
  { id: "currencyRates",     label: "Currency Exchange Rates", route: "settings-currencies" },
  { id: "deletedUsers",      label: "Deleted Users",           route: "settings-deleted-users" },
  { id: "jobs",              label: "Jobs",                    route: "settings-jobs" },
  { id: "support",           label: "Support",                 route: "settings-support" },
  { id: "vendorsGroups",     label: "Vendors Groups",          route: "settings-vendors" },
];

/* Commissions dropdown (host menu). */
const COMMISSIONS_MENU = [
  { id: "sportProfiles", label: "Sport profiles",      route: "comm-sport-profiles" },
  { id: "commProfiles",  label: "Commission profiles", route: "comm-profiles" },
  { id: "commPayments",  label: "Commission Payments", route: "comm-payments" },
  { id: "commCashback",  label: "Commission Cashback",  route: "comm-cashback" },
  { id: "commCbPay",     label: "Cashback Payments",    route: "comm-cashback-payments" },
];

/* Host nav items — labels resolve via window.T() at render time so they
   update live when the operator flips the language. */
const HOST_NAV = [
  { id: "dashboard",  i18n: "host.dashboard",   label: "Dashboard", route: "host-dashboard" },
  { id: "players",    i18n: "host.players",     label: "Players", route: "host-players" },
  { id: "lista",      i18n: "host.listaUtenti", label: "Lista utenti", route: "host-users" },
  /* "Lista Scommesse" / "Bets" was removed (Aug 2026, Arbi): it duplicated
     Sport coupons — same underlying sport-bet list — and was the only nav item
     in the whole shell with no route, so it always dead-ended. Sport coupons is
     the surviving entry. */
  { id: "sportcoupons", i18n: "host.sportCoupons", label: "Sport coupons", route: "host-sportcoupons" },
  { id: "sportbet",     label: "Bet from backoffice", route: "sport-bet" },
  { id: "deposit-host", i18n: "host.deposit",   label: "Deposit", route: "host-deposit" },
  /* Withdraw. On the real platform Deposit and Withdraw are a PAIR of dashboard
     buttons (dashboard/index.blade.php:255-272), not sidebar entries, and they
     render only for skin_id in (3,5,6,19) and never for Administration, Custom
     Care or Affiliates. The prototype already surfaces Deposit here, so its
     counterpart sits next to it rather than being unreachable. */
  { id: "withdraw-host", i18n: "host.withdraw", label: "Withdraw", route: "host-withdraw-to" },
  /* count mirrors the To-Confirm badge below — upstream that is the only
     number PayBO surfaces, so the parent must not invent a different one. */
  { id: "payments",   i18n: "host.payments",    label: "Payments", primary: true, count: PAYBO_TO_CONFIRM_COUNT },
  { id: "bonus",      i18n: "host.bonus",       label: "Promozioni Bonus", route: "bonus-programs" },
  { id: "report",     i18n: "host.report",      label: "Report", dropdown: true, menu: REPORT_MENU },
  { id: "voucher",    i18n: "host.voucher",     label: "Voucher", route: "host-vouchers" },
  { id: "settings",   i18n: "host.settings",    label: "Impostazioni", dropdown: true, menu: SETTINGS_MENU },
  { id: "commission", i18n: "host.commission",  label: "Commissioni",  dropdown: true, menu: COMMISSIONS_MENU },
  { id: "cms",        i18n: "host.cms",         label: "CMS",          dropdown: true, children: [
    { id: "cms-banners",        label: "Banners", nav: "cms-banners" },
    { id: "cms-faq",            label: "FAQ" , nav: "cms-faq" },
    { id: "cms-faq-cat",        label: "FAQ Categories" , nav: "cms-faq-cat" },
    { id: "cms-blogs",          label: "Blogs", nav: "cms-blogs" },
    { id: "cms-blog-cat",       label: "Blog Categories", nav: "cms-blog-cat" },
    { id: "cms-promotions",     label: "Promotions" , nav: "cms-promotions" },
    { id: "cms-provider-promo", label: "Provider Promotions" , nav: "cms-provider-promo" },
    { id: "cms-promoplay",      label: "Promoplay Promotions" , nav: "cms-promoplay" },
    { id: "cms-promo-triggers", label: "Promo Triggers" , nav: "cms-promo-triggers" },
    { id: "cms-help-cat",       label: "Help categories", nav: "cms-help-cat" },
    { id: "cms-help-pages",     label: "Help pages", nav: "cms-help-pages" },
    // Skins is the only CMS entry wired into PayBO — it opens the Skins
    // manager where the per-skin Customer.io / Third Party config lives.
    { id: "cms-skins",          label: "Skins", nav: "host-skins" },
    { id: "cms-game-cat",       label: "Game categories", nav: "cms-game-cat" },
    { id: "cms-game-subcat",    label: "Game subcategories", nav: "cms-game-subcat" },
    { id: "cms-game-labels",    label: "Game Labels", nav: "cms-game-labels" },
    { id: "cms-game-import",    label: "Game import", nav: "cms-game-import" },
    { id: "cms-providers",      label: "Providers", nav: "cms-providers" },
    { id: "cms-launch-urls",    label: "Game Launch URL", nav: "cms-launch-urls" },
    { id: "cms-oauth",          label: "OAuth Clients", nav: "cms-oauth" },
  ]},
  /* count is null here and filled at render from api.pendingCounts() — see
     usePendingCounts below. Upstream these three badges come from
     GET /getPendingCounts (UsersController), which the real layout polls every
     3 seconds. They were hardcoded 559 / 17 / 0, which read as live data and
     was not. WIRED — bucket A1. */
  { id: "deposits-list",  i18n: "host.depositsList",  label: "Deposits",  route: "host-deposits",  countKey: "pending_deposits" },
  { id: "withdraws-list", i18n: "host.withdrawsList", label: "Withdraws", route: "host-withdraws", countKey: "pending_withdrawals" },
  /* Messages has NO counter upstream: getPendingCounts returns deposits and
     withdrawals only. A third badge here would be inventing a number. */
  { id: "messaggi",   i18n: "host.messaggi",    label: "Messaggi", route: "host-messages" },
];

/* GET /getPendingCounts, polled. The only badge data isystem actually exposes.
   With API_BASE empty this resolves from mock instantly and nothing changes on
   screen; pointed at a real base it goes live with no other edit. Failure is
   silent BY DESIGN — a badge is not worth an error banner across the chrome of
   every route, so a failed poll leaves the last good value and the tooltip
   says when it was read. */
const usePendingCounts = () => {
  const [counts, setCounts] = React.useState(null);
  const [stamp, setStamp] = React.useState(null);
  const [failing, setFailing] = React.useState(false);
  React.useEffect(() => {
    let alive = true;
    let timer = null;
    const tick = async () => {
      const res = await window.api.pendingCounts();
      if (!alive) return;
      if (res.ok) { setCounts(res.data); setFailing(false); setStamp(new Date().toLocaleTimeString()); }
      else setFailing(true);
      // Upstream polls every 3s. That cadence is right against a real backend
      // and pointless against a constant, so mock resolves once and stops.
      if (res.source === "live") timer = setTimeout(tick, (res.meta && res.meta.pollMs) || 3000);
    };
    tick();
    return () => { alive = false; if (timer) clearTimeout(timer); };
  }, []);
  return { counts, stamp, failing };
};

// PayBO sections — feed the secondary nav + the icon rail.
// Every item carries an i18n key (resolved at render) plus the English
// fallback so the old label keeps rendering if a translation is missing.
const PAYBO_GROUPS = [
  { i18n: "sub.overview", label: "Overview", items: [
    { id: "dashboard",    i18n: "sub.dashboard",    label: "Dashboard",       icon: "dashboard" },
  ]},
  { i18n: "sub.payments", label: "Payments", items: [
    { id: "transactions", i18n: "sub.transactions", label: "Transactions",    icon: "arrow_down_up", hint: "All deposits & withdrawals" },
    { id: "deposits",     i18n: "sub.deposits",     label: "Deposits",        icon: "arrow_down",    hint: "Incoming — filtered view" },
    /* No badge. Upstream exactly ONE PayBO section carries one — To-Confirm,
       and only when > 0 (payments/index.blade.php:30, from
       CascadeHoldService::pendingCount). A count on Withdrawals implied a
       queue that has no counter in the real product. */
    { id: "withdrawals",  i18n: "sub.withdrawals",  label: "Withdrawals",     icon: "arrow_up",      hint: "Outgoing — filtered view" },
    /* Its own section upstream, not a tab: AdminPaymentsController's route
       constraint lists to-confirm alongside transactions. Distinct from the
       "To confirm" STATUS TAB inside Transactions — that filters transactions,
       this queues payment_cascade_holds. */
    { id: "to-confirm",   i18n: "sub.toConfirm",    label: "To Confirm",      icon: "flag",          hint: "Withdrawals held mid-cascade", badge: PAYBO_TO_CONFIRM_COUNT || null },
    { id: "methods",      i18n: "sub.methods",      label: "Payment methods", icon: "credit_card",   hint: "Thresholds · limits · fees" },
  ]},
  { i18n: "sub.players", label: "Players", items: [
    { id: "players",      i18n: "sub.players",      label: "Players",         icon: "users" },
  ]},
  { i18n: "sub.finance", label: "Finance", items: [
    { id: "reports",      i18n: "sub.reports",      label: "Reports",         icon: "chart", hint: "Volume · fees · methods · geo" },
    { id: "activity",     i18n: "sub.activity",     label: "Activity log",    icon: "activity", hint: "Operator action history" },
  ]},
  { i18n: "sub.platform", label: "Platform", items: [
    { id: "settings",     i18n: "sub.settingsPage", label: "Settings",        icon: "settings" },
    { id: "frontend",     i18n: "sub.frontend",     label: "Frontend",        icon: "eye", hint: "Live player-facing preview · driven by backend limits" },
    { id: "devphase",     i18n: "sub.development",  label: "Development",     icon: "activity" },
  ]},
];

const PayboHost = ({ brand, active, onNav, onOpenBrandSwitch, onOpenTweaks, navMode, children, viewCurrency, onSetViewCurrency, brandLocked = false }) => {
  const PAYBO_ITEMS = PAYBO_GROUPS.flatMap(g => g.items);
  window.useLocale && window.useLocale(); // re-render on language switch
  const T = window.T || ((k, fb) => fb || k);
  // Routes owned by host nav items — so the right top-nav item highlights when
  // a host report/page is open instead of always lighting up "Payments".
  const ROUTE_TO_NAV = { "host-dashboard": "dashboard", "bonus-retention": "report", "report-conversion": "report", "report-summary": "report", "bonus-programs": "bonus", "host-players": "players", "host-users": "lista", "host-sportcoupons": "sportcoupons", "sport-bet": "sportbet", "host-deposit": "deposit-host", "host-withdraw-to": "withdraw-host", "report-players": "report", "report-betting": "report", "report-bettype": "report", "report-daily": "report", "report-transactions": "report", "report-credittx": "report", "report-withdrawals": "report", "report-commissions": "report", "report-costs": "report", "report-netwin": "report", "report-dailyperf": "report", "report-business": "report", "report-network-liab": "report", "report-affiliates": "report", "host-vouchers": "voucher",
    "settings-languages": "settings", "settings-deposit-methods": "settings", "settings-withdrawal-methods": "settings", "settings-currencies": "settings", "settings-deleted-users": "settings", "settings-support": "settings", "settings-vendors": "settings",
    "comm-sport-profiles": "commission", "comm-profiles": "commission", "comm-payments": "commission",
    "cms-banners": "cms", "cms-blogs": "cms", "cms-blog-cat": "cms", "cms-help-cat": "cms", "cms-help-pages": "cms", "host-skins": "cms",
    "cms-game-cat": "cms", "cms-game-subcat": "cms", "cms-game-labels": "cms", "cms-game-import": "cms", "cms-providers": "cms", "cms-launch-urls": "cms", "cms-oauth": "cms",
    "host-deposits": "deposits-list", "host-withdraws": "withdraws-list", "host-messages": "messaggi" };
  const activeNav = ROUTE_TO_NAV[active] || "payments";
  // The PayBO secondary nav + icon rail belong to the Payments section only —
  // they appear when a PayBO page is open, not on the white-label host pages.
  const showPaybo = activeNav === "payments";
  const [muted, setMuted] = React.useState(false);
  const [mobileOpen, setMobileOpen] = React.useState(false);
  /* Which drawer groups are expanded. The drawer used to render every submenu
     open at once — 76 flat rows on a phone — so finding anything meant scrolling
     past everything. Undefined = "not touched", which falls back to
     auto-expanding whichever group contains the current page. */
  const [openGroups, setOpenGroups] = React.useState({});
  const toggleGroup = (id) => setOpenGroups(g => ({ ...g, [id]: !(g[id] !== undefined ? g[id] : false) }));
  const [langOpen, setLangOpen] = React.useState(false);
  const [curOpen, setCurOpen] = React.useState(false);
  const [displayCurrency, setDisplayCurrency] = React.useState(() => {
    return pbStore.get("pb-display-currency", "EUR") || "EUR";
  });
  React.useEffect(() => {
    pbStore.set("pb-display-currency", displayCurrency);
  }, [displayCurrency]);
  const [viewCurOpen, setViewCurOpen] = React.useState(false);

  // Hover dropdown for host nav items that carry a `menu` (e.g. "Report").
  // The blue nav is `overflow-x: auto`, which clips descendants, so the menu
  // renders fixed-positioned (like the language picker) using coords measured
  // from the hovered item. A short close delay lets the cursor travel from the
  // trigger into the menu without it snapping shut.
  const [navMenu, setNavMenu] = React.useState(null); // { id, top, left }
  const navMenuTimer = React.useRef(null);
  const cancelNavMenuClose = () => {
    if (navMenuTimer.current) { clearTimeout(navMenuTimer.current); navMenuTimer.current = null; }
  };
  const openNavMenu = (id, el) => {
    cancelNavMenuClose();
    const r = el.getBoundingClientRect();
    const width = 248;
    const left = Math.min(Math.max(8, r.left), window.innerWidth - width - 8);
    setNavMenu({ id, top: r.bottom, left });
  };
  const scheduleNavMenuClose = () => {
    cancelNavMenuClose();
    navMenuTimer.current = setTimeout(() => setNavMenu(null), 140);
  };
  React.useEffect(() => () => cancelNavMenuClose(), []);

  const [langPos, setLangPos] = React.useState({ top: 0, right: 0 });
  const langRef = React.useRef(null);
  const langMenuRef = React.useRef(null);
  // User dropdown. The real platform hangs My account off exactly this avatar
  // (header.blade.php:420) and repeats it in the mobile sidebar; the prototype
  // had neither, which is part of why /myaccount stayed unnoticed.
  // Last time the operator hit the balance refresh. Upstream updateBalance()
  // re-reads users.balance / users.credits over AJAX; here the figures are
  // fixed, so the stamp is the only honest feedback the control can give.
  // Live badge counts — GET /getPendingCounts via api.js (bucket A1).
  const { counts: pending, stamp: pendingStamp, failing: pendingFailing } = usePendingCounts();
  const navCount = (item) => item.countKey
    ? (pending ? pending[item.countKey] : null)
    : (item.count != null ? item.count : null);
  // Only the two badges that actually come from the endpoint get its
  // provenance; the Payments count is PAYBO_TO_CONFIRM_COUNT and would be
  // mislabelled by it.
  const countTitle = (item) => !item.countKey ? undefined
    : pendingFailing ? "Could not refresh — showing the last value read."
    : (pendingStamp ? `GET /getPendingCounts · read ${pendingStamp}` : "GET /getPendingCounts");
  const [balanceStamp, setBalanceStamp] = React.useState(null);
  const [userOpen, setUserOpen] = React.useState(false);
  const [userPos, setUserPos] = React.useState({ top: 0, right: 0 });
  const userRef = React.useRef(null);
  const userMenuRef = React.useRef(null);
  const toggleUser = () => {
    if (!userOpen && userRef.current) {
      const r = userRef.current.getBoundingClientRect();
      setUserPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
    }
    setUserOpen(o => !o);
  };
  React.useEffect(() => {
    if (!userOpen) return;
    const onDocClick = (e) => {
      const inTrigger = userRef.current && userRef.current.contains(e.target);
      const inMenu    = userMenuRef.current && userMenuRef.current.contains(e.target);
      if (!inTrigger && !inMenu) setUserOpen(false);
    };
    const onEsc = (e) => { if (e.key === "Escape") setUserOpen(false); };
    const reposition = () => {
      if (userRef.current) {
        const r = userRef.current.getBoundingClientRect();
        setUserPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
      }
    };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onEsc);
    window.addEventListener("resize", reposition);
    window.addEventListener("scroll", reposition, true);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onEsc);
      window.removeEventListener("resize", reposition);
      window.removeEventListener("scroll", reposition, true);
    };
  }, [userOpen]);
  const [curPos, setCurPos] = React.useState({ top: 0, right: 0 });
  const curRef = React.useRef(null);
  const curMenuRef = React.useRef(null);
  const [viewCurPos, setViewCurPos] = React.useState({ top: 0, right: 0 });
  const viewCurRef = React.useRef(null);
  const viewCurMenuRef = React.useRef(null);
  // The CMS-style click dropdown reuses the navMenu state declared above;
  // navMenuRef lets the outside-click handler ignore clicks inside the panel.
  const navMenuRef = React.useRef(null);
  React.useEffect(() => {
    if (!navMenu) return;
    const onDocClick = (e) => {
      const inMenu   = navMenuRef.current && navMenuRef.current.contains(e.target);
      const onParent = e.target.closest && e.target.closest("[data-id]") &&
                       e.target.closest("[data-id]").dataset.id === navMenu.id;
      if (!inMenu && !onParent) setNavMenu(null);
    };
    const onEsc = (e) => { if (e.key === "Escape") setNavMenu(null); };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onEsc);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onEsc);
    };
  }, [navMenu]);
  const toggleLang = () => {
    if (!langOpen && langRef.current) {
      const r = langRef.current.getBoundingClientRect();
      setLangPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
    }
    setLangOpen(o => !o);
  };
  React.useEffect(() => {
    if (!langOpen) return;
    const onDocClick = (e) => {
      const inTrigger = langRef.current && langRef.current.contains(e.target);
      const inMenu    = langMenuRef.current && langMenuRef.current.contains(e.target);
      if (!inTrigger && !inMenu) setLangOpen(false);
    };
    const onEsc = (e) => { if (e.key === "Escape") setLangOpen(false); };
    // Reposition the menu if the viewport scrolls or resizes while open —
    // it's fixed-positioned, so stale coords would leave it floating.
    const reposition = () => {
      if (langRef.current) {
        const r = langRef.current.getBoundingClientRect();
        setLangPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
      }
    };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onEsc);
    window.addEventListener("resize", reposition);
    window.addEventListener("scroll", reposition, true);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onEsc);
      window.removeEventListener("resize", reposition);
      window.removeEventListener("scroll", reposition, true);
    };
  }, [langOpen]);
  const toggleCurMenu = () => {
    if (!curOpen && curRef.current) {
      const r = curRef.current.getBoundingClientRect();
      setCurPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
    }
    setCurOpen(o => !o);
  };
  React.useEffect(() => {
    if (!curOpen) return;
    const onDocClick = (e) => {
      const inTrigger = curRef.current && curRef.current.contains(e.target);
      const inMenu    = curMenuRef.current && curMenuRef.current.contains(e.target);
      if (!inTrigger && !inMenu) setCurOpen(false);
    };
    const onEsc = (e) => { if (e.key === "Escape") setCurOpen(false); };
    const reposition = () => {
      if (curRef.current) {
        const r = curRef.current.getBoundingClientRect();
        setCurPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
      }
    };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onEsc);
    window.addEventListener("resize", reposition);
    window.addEventListener("scroll", reposition, true);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onEsc);
      window.removeEventListener("resize", reposition);
      window.removeEventListener("scroll", reposition, true);
    };
  }, [curOpen]);
  const toggleViewCurMenu = () => {
    if (!viewCurOpen && viewCurRef.current) {
      const r = viewCurRef.current.getBoundingClientRect();
      setViewCurPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
    }
    setViewCurOpen(o => !o);
  };
  React.useEffect(() => {
    if (!viewCurOpen) return;
    const onDocClick = (e) => {
      const inTrigger = viewCurRef.current && viewCurRef.current.contains(e.target);
      const inMenu    = viewCurMenuRef.current && viewCurMenuRef.current.contains(e.target);
      if (!inTrigger && !inMenu) setViewCurOpen(false);
    };
    const onEsc = (e) => { if (e.key === "Escape") setViewCurOpen(false); };
    const reposition = () => {
      if (viewCurRef.current) {
        const r = viewCurRef.current.getBoundingClientRect();
        setViewCurPos({ top: r.bottom + 6, right: Math.max(8, window.innerWidth - r.right) });
      }
    };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onEsc);
    window.addEventListener("resize", reposition);
    window.addEventListener("scroll", reposition, true);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onEsc);
      window.removeEventListener("resize", reposition);
      window.removeEventListener("scroll", reposition, true);
    };
  }, [viewCurOpen]);
  const closeMobile = () => setMobileOpen(false);
  const currentLang = (window.AVAILABLE_LOCALES || []).find(l => l.id === (window.getLocale ? window.getLocale() : "it")) || { id: "it", flag: "🇮🇹", label: "Italiano" };
  const toggleMuted = () => {
    const next = !muted;
    setMuted(next);
    if (window.PAYBO?.emitToast) {
      window.PAYBO.emitToast({
        id: `mute-${Date.now()}`,
        tx_id: next ? "Notifications muted" : "Notifications on",
        amount: 0, currency: "DEMO",
        player: "You",
        reason: next ? "New To-Confirm toasts paused for this session." : "Toasts will surface again.",
      });
    }
  };

  // Compact format — e.g. 902967933047.26 → "€902.97B" — so the three
  // stats fit in the navy strip on typical laptop widths. Exact value is
  // exposed via the title attribute for hover-to-reveal. The underlying
  // ledger is kept in EUR; both formatters convert into whichever
  // currency the operator picked in the navy strip's currency selector.
  /* Converts FROM the wallet's own currency, not from a hardcoded EUR. The
     old versions passed "EUR" as the source for a figure that was never in
     euros, so the strip was mislabelling the amount before it converted it.

     A null from fxConvert means the rate table has not loaded or does not
     carry one of the two currencies. That renders "—". It must not fall back
     to the unconverted number with the target symbol in front — that is the
     same amount claiming to be a different amount of money. */
  const fmtEUR = (n) => {
    if (n == null) return "—";
    const from = (wallet && wallet.cur) || "EUR";
    const v = window.fxConvert ? window.fxConvert(n, from, displayCurrency) : null;
    if (v == null) return "—";
    const sym = window.currencySymbol ? window.currencySymbol(displayCurrency) : "EUR ";
    try {
      return sym + new Intl.NumberFormat("en-US", {
        notation: "compact", maximumFractionDigits: 2
      }).format(v);
    } catch (e) {
      return sym + Math.round(v).toLocaleString();
    }
  };
  const fmtEURExact = (n) => {
    if (n == null) return "—";
    const from = (wallet && wallet.cur) || "EUR";
    const v = window.fxConvert ? window.fxConvert(n, from, displayCurrency) : null;
    if (v == null) {
      return walletErr
        ? `Balance unavailable: ${walletErr.message || "the read failed"}`
        : `No exchange rate for ${from} to ${displayCurrency} — nothing is converted rather than guessed.`;
    }
    const sym = window.currencySymbol ? window.currencySymbol(displayCurrency) : "EUR ";
    return sym + v.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  };

  /* Labels mirror the Italian host (Saldo / Fido / Disponibilità).
     ----------------------------------------------------------------------
     THESE WERE THREE HARDCODED NUMBERS — 902,967,933,047.26 and two more —
     rendered on the navy strip, which is on EVERY one of the 73 screens. So
     every screen the ledger calls LIVE has been carrying a fabricated network
     balance in its header, and `tools/wiringstate.js` never saw it because it
     classifies `src/pages/` and this is `src/host.jsx`.

     Now the signed-in operator's OWN wallet, in their OWN currency:
     balance_withdrawable, credits, and the generated `available` column. Their
     own rather than the subtree total, because Saldo/Fido/Disponibilità on the
     real host is the account you are logged in as — `user_subnet_balance`
     exists if the network figure is wanted instead.

     Null until it loads, and null renders as "—". A balance is the one number
     on this platform where a placeholder is indefensible. */
  const [wallet, setWallet] = React.useState(null);
  const [walletErr, setWalletErr] = React.useState(null);
  const loadWallet = React.useCallback(() => {
    if (!window.sb || !window.sb.live || !window.sb.signedIn) return;
    Promise.resolve(window.sb.me()).then(me => {
      if (!me || !me.ok || !me.data) { setWalletErr(me && me.error); return; }
      return Promise.resolve(window.sb.list("balances", { limit: 1, filters: { user: me.data.id } }))
        .then(r => {
          if (!r || !r.ok) { setWalletErr(r && r.error); return; }
          const row = (r.data || [])[0] || null;
          setWalletErr(null);
          setWallet(row ? {
            saldo:  Number(row.balance_withdrawable) || 0,
            fido:   Number(row.credits) || 0,
            dispon: Number(row.available) || 0,
            cur:    (row.users && row.users.currency) || me.data.currency || "EUR",
          } : { saldo: 0, fido: 0, dispon: 0, cur: me.data.currency || "EUR" });
        });
    });
  }, []);
  React.useEffect(() => {
    loadWallet();
    const fn = () => loadWallet();
    window.addEventListener("sb:session", fn);
    return () => window.removeEventListener("sb:session", fn);
  }, [loadWallet]);
  const curList = window.CURRENCY_LIST || [{ code: "EUR", symbol: "€", label: "Euro" }];
  const currentCur = curList.find(c => c.code === displayCurrency) || curList[0];

  return (
    <div className="paybo-host" data-layout={navMode || "hybrid"}>
      {/* ------- 1. navy top strip ------- */}
      <div className="paybo-host__navy">
        <button className="paybo-host__hamburger" title="Menu" onClick={() => setMobileOpen(true)}>
          <Icon name="list" size={16}/>
        </button>
        <div className="paybo-host__navy-logo">
          <span className="gdot">I</span>
          <span>IWAKIRI</span>
        </div>
        {/* Three-column grid layout: [Logo] [Stats · centered] [Actions · right] */}
        <div className="paybo-host__navy-stats">
          <div className="stat" title={fmtEURExact(wallet && wallet.saldo)}>
            <div className="lbl">{T("navy.saldo","Saldo")}</div>
            <div className="val">{fmtEUR(wallet && wallet.saldo)}</div>
          </div>
          <div className="stat" title={fmtEURExact(wallet && wallet.fido)}>
            <div className="lbl">{T("navy.fido","Fido")}</div>
            <div className="val">{fmtEUR(wallet && wallet.fido)}</div>
          </div>
          <div className="stat" title={balanceStamp ? `${fmtEURExact(wallet && wallet.dispon)} · refreshed ${balanceStamp}` : fmtEURExact(wallet && wallet.dispon)}>
            <div className="lbl">{T("navy.disponibilita","Disponibilità")}</div>
            <div className="val">
              {fmtEUR(wallet && wallet.dispon)}
              {/* This used to be a bare ⟳ glyph that did nothing. Upstream it is a
                  real control: header.blade.php:333 renders fa-refresh wired to
                  updateBalance(user_id, '#cur_agent_balance', …), which re-reads
                  the operator's balance and credits without a page load. */}
              <button className="paybo-host__refresh" title={T("navy.refreshBalance","Refresh balance")}
                aria-label={T("navy.refreshBalance","Refresh balance")}
                onClick={() => {
                  loadWallet(); setBalanceStamp(new Date().toLocaleTimeString());
                  window.PAYBO && window.PAYBO.emitToast && window.PAYBO.emitToast({
                    id: `bal-${Date.now()}`, tx_id: T("navy.balanceRefreshed","Balance refreshed"),
                    amount: 0, currency: "HOST", player: T("navy.title","Iwakiri White Label"),
                    reason: "Real platform: updateBalance() re-reads users.balance and users.credits over AJAX. Prototype figures are fixed, so the values do not move.",
                  });
                }}>
                <Icon name="refresh" size={11}/>
              </button>
            </div>
          </div>
        </div>
        <div className="paybo-host__navy-actions">
          <button className="icon-btn" title={muted ? T("navy.muted","Notifications muted") : T("navy.mute","Mute notifications")} onClick={toggleMuted}
            style={muted ? {color:"var(--err-500)", position:"relative"} : null}>
            <Icon name="bell" size={14}/>
            {muted && <span style={{position:"absolute", inset:0, display:"grid", placeItems:"center", pointerEvents:"none", fontSize:18, color:"var(--err-500)", fontWeight:300}}>/</span>}
          </button>
          {onOpenTweaks && (
            <button
              onClick={onOpenTweaks}
              /* Classed so the mobile rule can drop the label and the ⌘K hint —
                 the styling stays inline, this is only a styling hook. */
              className="paybo-host__tweaks-btn"
              title={`${T("navy.tweaks","Tweaks")} (Cmd/Ctrl+K)`}
              style={{
                display:"inline-flex", alignItems:"center", gap:6,
                padding:"6px 10px", borderRadius:7,
                background:"rgba(255,255,255,0.08)",
                border:"1px solid rgba(255,255,255,0.14)",
                color:"#fff", cursor:"pointer",
                fontSize:11.5, fontWeight:600, fontFamily:"inherit",
                lineHeight:1,
              }}>
              <Icon name="settings" size={13}/>
              <span>{T("navy.tweaks","Tweaks")}</span>
              <span style={{
                fontSize:9.5, fontWeight:700, padding:"1px 5px", borderRadius:3,
                background:"rgba(255,255,255,0.12)", color:"rgba(255,255,255,0.78)",
                letterSpacing:"0.04em",
              }}>⌘K</span>
            </button>
          )}
          {/* Display-currency selector — same fixed-position dropdown trick
              as the language selector below. Only changes how the navy
              strip's Balance / Credit / Available are displayed; the
              underlying network ledger stays in EUR. */}
          <div ref={curRef} className="lang" style={{cursor:"pointer", width:58, fontSize:12, fontWeight:700, color:"var(--text-primary)", letterSpacing:"-0.01em"}} onClick={toggleCurMenu} title={`Display currency: ${currentCur.label}`}>
            <span>{currentCur.symbol} {currentCur.code}</span>
          </div>
          {curOpen && (
            <div ref={curMenuRef} style={{
              position:"fixed", top: curPos.top, right: curPos.right, zIndex:500,
              background:"#fff", border:"1px solid var(--paybo-border)",
              borderRadius:8, boxShadow:"0 8px 24px rgba(15,20,32,.2)",
              padding:4, minWidth:200, maxHeight:320, overflow:"auto",
            }}>
              {curList.map(c => (
                <button key={c.code}
                  onClick={() => { setDisplayCurrency(c.code); setCurOpen(false); }}
                  style={{
                    display:"flex", alignItems:"center", gap:8, width:"100%",
                    padding:"8px 10px", border:0, background: displayCurrency===c.code ? "var(--p-50)" : "transparent",
                    borderRadius:6, cursor:"pointer", fontSize:12.5, fontFamily:"inherit", textAlign:"left",
                    color:"var(--text-primary)",
                  }}>
                  <span style={{width:20, fontWeight:700, color:"var(--text-secondary)"}}>{c.symbol}</span>
                  <span style={{flex:1, minWidth:0}}>{c.label}</span>
                  <span style={{fontSize:10.5, color:"var(--text-tertiary)", fontWeight:600}}>{c.code}</span>
                  {displayCurrency===c.code && <Icon name="check" size={12} style={{color:"var(--p-500)"}}/>}
                </button>
              ))}
            </div>
          )}
          {/* Language selector — trigger stays inside the navy strip but the
              dropdown renders with position: fixed so it escapes the navy's
              overflow: hidden clip. Click outside / Escape closes. */}
          <div ref={langRef} className="lang" style={{cursor:"pointer"}} onClick={toggleLang} title={currentLang.label}>
            <span>{currentLang.flag}</span>
          </div>
          {langOpen && (
            <div ref={langMenuRef} style={{
              position:"fixed", top: langPos.top, right: langPos.right, zIndex:500,
              background:"#fff", border:"1px solid var(--paybo-border)",
              borderRadius:8, boxShadow:"0 8px 24px rgba(15,20,32,.2)",
              padding:4, minWidth:160,
            }}>
              {(window.AVAILABLE_LOCALES || []).map(l => (
                <button key={l.id}
                  onClick={() => { window.setLocale(l.id); setLangOpen(false); }}
                  style={{
                    display:"flex", alignItems:"center", gap:8, width:"100%",
                    padding:"8px 10px", border:0, background: currentLang.id===l.id ? "var(--p-50)" : "transparent",
                    color:"var(--text-primary)", fontSize:13, fontWeight:500, cursor:"pointer",
                    borderRadius:6, fontFamily:"inherit",
                  }}>
                  <span style={{fontSize:16}}>{l.flag}</span>
                  <span style={{flex:1, textAlign:"left"}}>{l.label}</span>
                  {currentLang.id===l.id && <Icon name="check" size={11} style={{color:"var(--p-500)"}}/>}
                </button>
              ))}
            </div>
          )}
          <div ref={userRef} className="avatar paybo-host__avatar" onClick={toggleUser}
            style={{cursor:"pointer"}} title="j.moreno — account">
            <Icon name="user" size={14}/>
          </div>
          {userOpen && (
            <div ref={userMenuRef} style={{
              position:"fixed", top: userPos.top, right: userPos.right, zIndex:500,
              background:"#fff", border:"1px solid var(--paybo-border)",
              borderRadius:8, boxShadow:"0 8px 24px rgba(15,20,32,.2)",
              padding:4, minWidth:210,
            }}>
              <div style={{padding:"8px 10px 6px", borderBottom:"1px solid var(--paybo-border)", marginBottom:4}}>
                <div style={{fontSize:12.5, fontWeight:700, color:"var(--text-primary)"}}>j.moreno</div>
                <div style={{fontSize:11, color:"var(--text-tertiary)"}}>Skin admin · Iwakiri BR</div>
              </div>
              <button
                onClick={() => { setUserOpen(false); onNav("host-myaccount"); }}
                style={{
                  display:"flex", alignItems:"center", gap:8, width:"100%",
                  padding:"8px 10px", border:0, background:"transparent",
                  color:"var(--text-primary)", fontSize:13, fontWeight:500, cursor:"pointer",
                  borderRadius:6, fontFamily:"inherit", textAlign:"left",
                }}>
                <Icon name="user" size={13}/><span style={{flex:1}}>My account</span>
              </button>
              {/* Logout IS in the real dropdown (header.blade.php:425 — GET /logout
                  behind a confirm), so leaving it out made this menu structurally
                  wrong. It is not an invented action; it is a real one the
                  prototype cannot perform, which is exactly what NoBackend is
                  for — there is no session to end. */}
              <NoBackend need="GET /logout" what="Log out"
                className="paybo-host__usermenu-item">
                <Icon name="logout" size={13}/><span style={{flex:1}}>{T("navy.logout","Logout")}</span>
              </NoBackend>
            </div>
          )}
        </div>
      </div>

      {/* ------- 2. blue host nav (White Label top) ------- */}
      <nav className="paybo-host__nav">
        {HOST_NAV.map(item => {
          const isActive = item.id === activeNav || (navMenu && navMenu.id === item.id);
          const isPayments = item.id === "payments";
          const handleClick = (e) => {
            // Items that own a real page (e.g. Promozioni Bonus) navigate.
            if (item.route) { onNav(item.route); return; }
            // Payments opens the PayBO dashboard (and reveals the PayBO subnav).
            if (isPayments) { onNav("dashboard"); return; }
            // Items with a click submenu (e.g. CMS) toggle a navigable dropdown.
            if (item.children && item.children.length) {
              if (navMenu && navMenu.id === item.id) { setNavMenu(null); return; }
              const r = e.currentTarget.getBoundingClientRect();
              setNavMenu({ id: item.id, top: r.bottom, left: r.left });
              return;
            }
            if (!isPayments && window.PAYBO?.emitToast) {
              // Safety net only: as of Aug 2026 every HOST_NAV entry and every
              // submenu item resolves to a route, so this branch is unreachable.
              // It stays so a newly added item that forgets its `route` says so
              // instead of silently doing nothing.
              window.PAYBO.emitToast({
                id: `host-${item.id}-${Date.now()}`,
                tx_id: item.label,
                amount: 0, currency: "HOST",
                player: "Iwakiri White Label",
                reason: `No prototype page is registered for this menu item.`,
              });
            }
          };
          const hasMenu = Array.isArray(item.menu) && item.menu.length > 0;
          return (
            <div key={item.id}
              className={`paybo-host__nav-item ${isActive ? "active" : ""} ${item.align === "right" ? "right" : ""} ${hasMenu && navMenu?.id === item.id ? "menu-open" : ""}`}
              data-id={item.id}
              onClick={hasMenu ? (e) => openNavMenu(item.id, e.currentTarget) : handleClick}
              onMouseEnter={hasMenu ? (e) => openNavMenu(item.id, e.currentTarget) : undefined}
              onMouseLeave={hasMenu ? scheduleNavMenuClose : undefined}>
              <span>{T(item.i18n, item.label)}</span>
              {navCount(item) != null && <span className="count" title={countTitle(item)}>{navCount(item)}</span>}
              {item.dropdown && <span className="caret">▾</span>}
              {item.primary && <span className="caret">▾</span>}
            </div>
          );
        })}
      </nav>

      {/* Host-nav dropdowns — fixed-positioned so they escape the blue nav's
          overflow clip. "Report" uses a hover `menu`; "CMS" uses a click
          `children` submenu whose entries can route into PayBO (e.g. Skins). */}
      {navMenu && (() => {
        const item = HOST_NAV.find(i => i.id === navMenu.id);
        if (!item) return null;

        // Report-style hover menu (items carrying `menu`).
        if (item.menu) {
          return (
            <div ref={navMenuRef} className="paybo-host__nav-menu"
              style={{ top: navMenu.top, left: navMenu.left }}
              onMouseEnter={cancelNavMenuClose}
              onMouseLeave={scheduleNavMenuClose}>
              {item.menu.map(m => {
                const label = T(m.i18n, m.label);
                return (
                  <button key={m.id}
                    className="paybo-host__nav-menu-item"
                    onClick={() => {
                      setNavMenu(null);
                      // Implemented reports navigate; the rest emit the demo toast.
                      if (m.route) { onNav(m.route); return; }
                      if (window.PAYBO?.emitToast) {
                        window.PAYBO.emitToast({
                          id: `host-report-${m.id}-${Date.now()}`,
                          tx_id: label, amount: 0, currency: "HOST",
                          player: "Iwakiri White Label",
                          reason: `${T(item.i18n, item.label)} · no prototype page is registered for this menu item.`,
                        });
                      }
                    }}>
                    {label}
                  </button>
                );
              })}
            </div>
          );
        }

        // CMS-style click submenu (items carrying `children`).
        if (item.children) {
          const handleSub = (c) => {
            setNavMenu(null);
            if (c.nav) { onNav(c.nav); return; }
            if (window.PAYBO?.emitToast) {
              window.PAYBO.emitToast({
                id: `host-${c.id}-${Date.now()}`,
                tx_id: c.label, amount: 0, currency: "HOST",
                player: "Iwakiri White Label",
                reason: `No prototype page is registered for this menu item.`,
              });
            }
          };
          return (
            <div ref={navMenuRef} style={{
              position:"fixed", top: navMenu.top + 2, left: navMenu.left, zIndex:500,
              background:"#fff", border:"1px solid var(--paybo-border)",
              borderRadius:8, boxShadow:"0 8px 24px rgba(15,20,32,.2)",
              padding:4, minWidth:230, maxHeight:"70vh", overflowY:"auto",
            }}>
              {item.children.map(c => (
                <button key={c.id}
                  onClick={() => handleSub(c)}
                  style={{
                    display:"flex", alignItems:"center", gap:8, width:"100%",
                    padding:"9px 11px", border:0, background:"transparent",
                    color:"var(--text-primary)", fontSize:13, fontWeight: c.nav ? 600 : 500,
                    cursor:"pointer", borderRadius:6, fontFamily:"inherit", textAlign:"left",
                  }}
                  onMouseEnter={e=>e.currentTarget.style.background="var(--p-50)"}
                  onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
                  <span style={{flex:1}}>{T(c.i18n, c.label)}</span>
                  {c.nav && <Icon name="chevron_right" size={12} style={{color:"var(--p-500)"}}/>}
                </button>
              ))}
            </div>
          );
        }
        return null;
      })()}

      {/* ------- 3. secondary nav (PayBO sections) — Payments only ------- */}
      {showPaybo && (
      <nav className="paybo-host__subnav">
        <div className="paybo-host__subnav-scroll">
          {PAYBO_ITEMS.map(it => (
            <button key={it.id}
              className={`paybo-host__subnav-item ${active === it.id ? "active" : ""}`}
              onClick={() => onNav(it.id)}
              title={it.hint || it.label}>
              <Icon name={it.icon} size={13}/>
              <span>{T(it.i18n, it.label)}</span>
              {it.v2 && <span className="paybo-host__subnav-v2">v2</span>}
              {it.badge != null && <span className="paybo-host__subnav-badge">{it.badge}</span>}
            </button>
          ))}
        </div>
        {/* The brand switcher lives here and ONLY here — same as upstream
            (admin/payments/index.blade.php:87-94, POST /payments/brand/). It is
            not in the host header, the host sidebar, or any mobile menu.

            brandLocked: an operator with a single skin does not get a switcher
            at all upstream. The button becomes a static div with a padlock, and
            the brand modal is not even included in the page
            (index.blade.php:78-85 and :113). Modelled so the state is visible
            rather than only described. */}
        {brandLocked ? (
          <div className="paybo-host__subnav-brand paybo-host__subnav-brand--locked"
            title={T("navy.brandLocked","Locked to your brand — your account has access to one skin")}>
            <span style={{width:14, height:14, borderRadius:4, background: brand.color, color:"#fff", fontSize:8, fontWeight:700, display:"grid", placeItems:"center"}}>{brand.short}</span>
            <span className="bname">{brand.name}</span>
            <Icon name="lock" size={11}/>
          </div>
        ) : (
          <button className="paybo-host__subnav-brand" onClick={onOpenBrandSwitch} title={T("navy.switchBrand","Switch brand")}>
            <span style={{width:14, height:14, borderRadius:4, background: brand.color, color:"#fff", fontSize:8, fontWeight:700, display:"grid", placeItems:"center"}}>{brand.short}</span>
            <span className="bname">{brand.name}</span>
            <Icon name="chevron_down" size={11}/>
          </button>
        )}
        {/* View-currency override — only meaningful once a single brand is
            selected (its own currency is the baseline to convert from).
            Distinct from the navy strip's picker, which only affects the
            host-wide Balance/Credit/Available. */}
        {!brand.isAll && onSetViewCurrency && (
          <button ref={viewCurRef} className="paybo-host__subnav-brand" onClick={toggleViewCurMenu}
            title={viewCurrency ? `Viewing ${brand.name} in ${viewCurrency} — click to change or reset` : `View ${brand.name}'s numbers in another currency`}>
            <Icon name="refresh" size={12} style={{opacity:.7}}/>
            <span className="bname">{viewCurrency ? `View: ${viewCurrency}` : `View: ${brand.currency}`}</span>
            <Icon name="chevron_down" size={11}/>
          </button>
        )}
      </nav>
      )}
      {viewCurOpen && (
        <div ref={viewCurMenuRef} style={{
          position:"fixed", top: viewCurPos.top, right: viewCurPos.right, zIndex:500,
          background:"#fff", border:"1px solid var(--paybo-border)",
          borderRadius:8, boxShadow:"0 8px 24px rgba(15,20,32,.2)",
          padding:4, minWidth:220, maxHeight:340, overflow:"auto",
        }}>
          <button
            onClick={() => { onSetViewCurrency(null); setViewCurOpen(false); }}
            style={{
              display:"flex", alignItems:"center", gap:8, width:"100%",
              padding:"8px 10px", border:0, background: !viewCurrency ? "var(--p-50)" : "transparent",
              borderRadius:6, cursor:"pointer", fontSize:12.5, fontFamily:"inherit", textAlign:"left",
              color:"var(--text-primary)", fontWeight:600,
            }}>
            <span style={{width:20, fontWeight:700, color:"var(--text-secondary)"}}>{brand.currency && window.currencySymbol ? window.currencySymbol(brand.currency) : ""}</span>
            <span style={{flex:1, minWidth:0}}>{brand.name}'s own currency ({brand.currency})</span>
            {!viewCurrency && <Icon name="check" size={12} style={{color:"var(--p-500)"}}/>}
          </button>
          <div style={{height:1, background:"var(--border-subtle)", margin:"4px 6px"}}/>
          {curList.map(c => (
            <button key={c.code}
              onClick={() => { onSetViewCurrency(c.code); setViewCurOpen(false); }}
              style={{
                display:"flex", alignItems:"center", gap:8, width:"100%",
                padding:"8px 10px", border:0, background: viewCurrency===c.code ? "var(--p-50)" : "transparent",
                borderRadius:6, cursor:"pointer", fontSize:12.5, fontFamily:"inherit", textAlign:"left",
                color:"var(--text-primary)",
              }}>
              <span style={{width:20, fontWeight:700, color:"var(--text-secondary)"}}>{c.symbol}</span>
              <span style={{flex:1, minWidth:0}}>{c.label}</span>
              <span style={{fontSize:10.5, color:"var(--text-tertiary)", fontWeight:600}}>{c.code}</span>
              {viewCurrency===c.code && <Icon name="check" size={12} style={{color:"var(--p-500)"}}/>}
            </button>
          ))}
        </div>
      )}

      {/* ------- 4. floating icon rail — Payments only ------- */}
      {showPaybo && (
      <aside className="paybo-host__rail">
        {PAYBO_ITEMS.map(it => (
          <button key={it.id}
            className={`paybo-host__rail-btn ${active === it.id ? "active" : ""}`}
            title={it.label}
            onClick={() => onNav(it.id)}>
            <Icon name={it.icon} size={16}/>
            {it.badge != null && (
              <span style={{
                position:"absolute", top:4, right:4,
                minWidth:15, height:15, padding:"0 4px",
                borderRadius:999,
                background:"#dc2626", color:"#fff",
                fontSize:9, fontWeight:700,
                display:"grid", placeItems:"center",
                border:"1.5px solid #fff",
                lineHeight:1,
              }}>{it.badge}</span>
            )}
          </button>
        ))}
        <div style={{height:1, background:"var(--paybo-border)", margin:"4px 4px"}}/>
        {onOpenTweaks && (
          <button className="paybo-host__rail-btn"
            title="Design tweaks"
            onClick={onOpenTweaks}>
            <Icon name="settings" size={16}/>
          </button>
        )}
      </aside>
      )}

      {/* ------- main content (PayBO page area) ------- */}
      {/* data-route is additive: a per-screen CSS scoping hook so a screen can be
          styled without editing its (protected) file and without widening a shared
          rule onto other screens. No behaviour, no layout, no existing class changes. */}
      <main className="paybo-host__main" data-route={active}>
        {children}
      </main>

      {/* ------- 5. mobile drawer — shown only on narrow widths.
          Contains every nav reach-point: host nav (Dashboard, Lista utenti,
          Payments, ...), PayBO subnav (Transactions, Deposits, Players, ...),
          Tweaks, and the stats strip (Saldo / Fido / Disponibilità) so the
          whole app is reachable from a single left-hand menu. */}
      <div className={`paybo-host__mobile ${mobileOpen ? "open" : ""}`}>
        <div className="paybo-host__mobile-scrim" onClick={closeMobile}/>
        <aside className="paybo-host__mobile-drawer">
          <div className="paybo-host__mobile-head">
            <div style={{display:"flex", alignItems:"center", gap:10}}>
              <span className="gdot">I</span>
              <span style={{fontWeight:700, fontSize:15, letterSpacing:"0.08em"}}>IWAKIRI</span>
            </div>
            <button className="icon-btn" onClick={closeMobile} style={{color:"var(--text-primary)"}}>
              <Icon name="x" size={14}/>
            </button>
          </div>

          <div className="paybo-host__mobile-stats">
            <div><div className="lbl">{T("navy.saldo","Saldo")}</div><div className="val">{fmtEUR(wallet && wallet.saldo)}</div></div>
            <div><div className="lbl">{T("navy.fido","Fido")}</div><div className="val">{fmtEUR(wallet && wallet.fido)}</div></div>
            <div><div className="lbl">{T("navy.disponibilita","Disponibilità")}</div><div className="val">{fmtEUR(wallet && wallet.dispon)}</div></div>
          </div>

          <div className="paybo-host__mobile-section">
            <div className="paybo-host__mobile-sectitle">{T("sub.hostGroup","White Label host")}</div>
            {HOST_NAV.map(item => {
              const isPayments = item.id === "payments";
              const label = T(item.i18n, item.label);
              /* Sub-entries arrive three ways now:
                 · CMS carries `children` (each with `nav`)
                 · Report / Impostazioni / Commissioni carry `menu` (each with `route`)
                 · Payments has neither — it IS the PayBO surface, so it takes
                   PAYBO_GROUPS as its children.
                 The drawer used to render only `children`, so the three `menu`
                 dropdowns had no reachable items on a phone at all, and PayBO
                 lived in a second "PayBO sections" block further down that
                 duplicated the Payments row above it. One expandable group per
                 area now, the same way the desktop dropdowns behave. */
              const subs = isPayments
                ? PAYBO_GROUPS.flatMap(g => g.items.map(it => ({
                    id: it.id, label: T(it.i18n, it.label), route: it.id,
                    icon: it.icon, badge: it.badge, group: T(g.i18n, g.label),
                  })))
                : (item.children || item.menu || []).map(c => ({
                    id: c.id, label: T(c.i18n, c.label), route: c.nav || c.route,
                  }));
              const go = (routeId) => { closeMobile(); if (routeId) onNav(routeId); };
              /* The old code hardcoded `item.id === "payments"`, so the
                 Payments row was highlighted permanently no matter where the
                 operator actually was. Highlight what is really open. */
              const onPayboSurface = ((window.pathForActive && window.pathForActive(active)) || "").startsWith("/payments");
              const isActive = isPayments
                ? onPayboSurface
                : (item.route === active || subs.some(x => x.route === active));
              const holdsActive = subs.some(x => x.route === active);
              const expanded = openGroups[item.id] !== undefined ? openGroups[item.id] : holdsActive;
              return (
                <React.Fragment key={item.id}>
                  <button
                    className={`paybo-host__mobile-item${isActive ? " active" : ""}${subs.length ? " paybo-host__mobile-item--group" : ""}${subs.length && expanded ? " open" : ""}`}
                    aria-expanded={subs.length ? expanded : undefined}
                    onClick={() => {
                      // A parent toggles its own section instead of navigating.
                      /* Payments now has subs too, so it expands like the rest
                         instead of jumping straight to the PayBO dashboard. */
                      if (subs.length) { toggleGroup(item.id); return; }
                      /* This used to fire a toast for EVERY host item instead of
                         navigating, so the entire Host nav was dead on phones
                         while the same items worked on desktop. */
                      go(item.route);
                    }}>
                    <span>{label}</span>
                    {navCount(item) != null && <span className="count" title={countTitle(item)}>{navCount(item)}</span>}
                    {subs.length > 0 && (
                      <span className="paybo-host__mobile-caret" aria-hidden="true">
                        <Icon name="chevron_down" size={12}/>
                      </span>
                    )}
                  </button>
                  {subs.length > 0 && expanded && (
                    <div className="paybo-host__mobile-sub">
                      {subs.map((c, i) => (
                        <React.Fragment key={c.id}>
                          {/* PayBO's own grouping (Overview / Payments / Players /
                              Finance / Platform) is worth keeping — 10 flat rows
                              read as a list, 10 grouped rows read as a map. */}
                          {c.group && c.group !== (subs[i - 1] || {}).group && (
                            <div className="paybo-host__mobile-subgroup">{c.group}</div>
                          )}
                          <button
                            className={`paybo-host__mobile-item paybo-host__mobile-item--sub ${active === c.route ? "active" : ""}`}
                            onClick={() => go(c.route)}>
                            {c.icon && <Icon name={c.icon} size={13}/>}
                            <span>{c.label}</span>
                            {c.badge != null && <span className="count">{c.badge}</span>}
                          </button>
                        </React.Fragment>
                      ))}
                    </div>
                  )}
                </React.Fragment>
              );
            })}
          </div>

          {/* The standalone "PayBO sections" block that used to sit here was
              removed (Arbi, Aug 2026): it listed the same ten destinations that
              the Payments row above already owns, so the drawer showed PayBO
              twice. Payments is now an expandable group like Report or CMS and
              carries them as its children. */}

          <div className="paybo-host__mobile-section paybo-host__mobile-section--footer">
            {/* My account. The real sidebar carries this entry as mobile-only
                (sidebar.blade.php:80-82, class `hide-on-desktop`) — on desktop
                it lives in the header dropdown instead. Same split here. */}
            <button className={`paybo-host__mobile-item${active === "host-myaccount" ? " active" : ""}`}
              onClick={() => { onNav("host-myaccount"); closeMobile(); }}>
              <Icon name="user" size={13}/>
              <span>{T("navy.myAccount","My account")}</span>
            </button>
            {onOpenTweaks && (
              <button className="paybo-host__mobile-item"
                onClick={() => { onOpenTweaks(); closeMobile(); }}>
                <Icon name="settings" size={13}/>
                <span>{T("navy.tweaks","Tweaks")}</span>
                <span className="count" style={{background:"var(--n-75)", color:"var(--text-tertiary)"}}>⌘K</span>
              </button>
            )}
            {/* NO brand switcher here. Upstream the switcher exists in exactly one
                place — the PayBO subnav (admin/payments/index.blade.php:87-94,
                POST /payments/brand/). It is not in the host header, not in the
                host sidebar, and not in any mobile menu. Putting it in this
                drawer implied the whole backoffice is brand-scoped; it is not,
                the host is scoped by user hierarchy and skin assignment.
                Removed Aug 2026 on the same reasoning that removed the
                duplicate "PayBO sections" block. */}
            {/* Mobile language picker */}
            {(window.AVAILABLE_LOCALES || []).length > 1 && (
              <div style={{padding:"10px 12px 4px"}}>
                <div style={{fontSize:10, fontWeight:700, letterSpacing:".08em", textTransform:"uppercase", color:"var(--paybo-muted)", marginBottom:6}}>{T("tweaks.language","Language")}</div>
                <div style={{display:"flex", gap:6, flexWrap:"wrap"}}>
                  {window.AVAILABLE_LOCALES.map(l => (
                    <button key={l.id}
                      onClick={() => { window.setLocale(l.id); }}
                      style={{
                        display:"inline-flex", alignItems:"center", gap:6,
                        padding:"6px 10px", borderRadius:7, cursor:"pointer",
                        border: "1px solid " + (currentLang.id===l.id ? "var(--p-500)" : "var(--paybo-border)"),
                        background: currentLang.id===l.id ? "var(--p-50)" : "#fff",
                        color:"var(--text-primary)", fontSize:12, fontWeight:600, fontFamily:"inherit",
                      }}>
                      <span style={{fontSize:14}}>{l.flag}</span>
                      <span>{l.label}</span>
                    </button>
                  ))}
                </div>
              </div>
            )}
          </div>
        </aside>
      </div>
    </div>
  );
};
