// App shell: sidebar, topbar, routing, command palette, toasts, tweaks
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#1F6B4E",
  "dark": false,
  "density": "regular"
}/*EDITMODE-END*/;

// Preview flag. Modules marked `soon` are still being built: they are hidden
// from the sidebar, the command palette and routing until someone opens the
// domain with ?preview=1. The choice sticks for that browser (?preview=0 undoes
// it), so the flag survives reloads and in-app navigation.
const PREVIEW_KEY = "erpvision.preview";
function readPreviewFlag() {
  let q = null;
  try { q = new URLSearchParams(window.location.search).get("preview"); } catch (e) {}
  try {
    if (q === "1" || q === "true") { localStorage.setItem(PREVIEW_KEY, "1"); return true; }
    if (q === "0" || q === "false") { localStorage.removeItem(PREVIEW_KEY); return false; }
    return localStorage.getItem(PREVIEW_KEY) === "1";
  } catch (e) { return q === "1" || q === "true"; }
}
const PREVIEW = readPreviewFlag();

// `perm` stays in English — it is a key into D.permModules / role.access,
// not display text. The visible label comes from `labelKey`.
const NAV = [
  { id: "dashboard", labelKey: "nav.dashboard", icon: "grid", el: () => window.Dashboard, perm: "Dashboard", soon: true },
  { id: "workspace", labelKey: "nav.workspace", icon: "calendar", el: () => window.Workspace, perm: "Workspace", soon: true },
  { id: "crm", labelKey: "nav.crm", icon: "target", el: () => window.CRM, perm: "Sales & CRM", soon: true },
  { id: "accounting", labelKey: "nav.accounting", icon: "ledger", el: () => window.Accounting, perm: "Accounting", soon: true },
  { id: "inventory", labelKey: "nav.inventory", icon: "box", el: () => window.Inventory, perm: "Inventory" },
  { id: "mfg", labelKey: "nav.mfg", icon: "cog", el: () => window.Manufacturing, perm: "Manufacturing", soon: true },
  { id: "hr", labelKey: "nav.hr", icon: "users", el: () => window.HR, perm: "HR & People" },
  { id: "rent", labelKey: "nav.rent", icon: "building", el: () => window.Rent, perm: "Rent", soon: true },
  { id: "installments", labelKey: "nav.installments", icon: "ledger", el: () => window.Installments, soon: true },
  { id: "users", labelKey: "nav.users", icon: "shield", el: () => window.Users, perm: "Users & Permissions" },
  { id: "myportal", labelKey: "nav.myportal", icon: "user", el: () => window.MyPortal }
];

// Which NAV items the session can see. "My Portal" (no perm key) is always visible.
function navFor(session) {
  const shipped = NAV.filter((n) => PREVIEW || !n.soon);
  if (session.kind === "employee") return shipped.filter((n) => !n.perm); // employees: My Portal only
  const u = D.users.find((x) => x.email.toLowerCase() === (session.email || "").toLowerCase());
  const role = u ? D.roles.find((r) => r.id === u.role) : null;
  return shipped.filter((n) => {
    if (!n.perm) return true;
    if (!role || role.id === "owner" || role.id === "admin") return true;
    return (role.access[n.perm] || "none") !== "none";
  });
}

// Contextual topbar action per route — omit a route to show no global action there.
const PRIMARY_ACTION = {
  accounting: { labelKey: "acct.newInvoice", icon: "plus", run: (go) => { go("accounting"); setTimeout(() => window.dispatchEvent(new CustomEvent("erp:new-invoice")), 60); } }
};

// Language menu. Built from I18N.list(), so a language added in index.html
// shows up here with no change to this file.
function LangPop() {
  const { useState, useEffect, useRef } = React;
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const lang = useLang();
  const langs = I18N.list();

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); };
  }, [open]);

  if (langs.length < 2) return null;
  const active = langs.find((l) => l.code === lang) || langs[0];
  return (
    <div className="notif" ref={ref}>
      <button className={"notif-btn lang-btn" + (open ? " on" : "")} onClick={() => setOpen((o) => !o)}
        title={T("app.language")} aria-label={T("app.language")}>
        <span className="lang-code">{active.code.toUpperCase()}</span>
      </button>
      {open ? (
        <div className="notif-pop ap-pop">
          <div className="notif-head"><b>{T("app.language")}</b></div>
          <div className="ap-body">
            {langs.map((l) => (
              <button key={l.code} className={"dt-menu-row" + (l.code === lang ? " on" : "")}
                onClick={() => { I18N.setLang(l.code); setOpen(false); }}>
                <Icon name={l.code === lang ? "check" : "globe"} size={15} />
                <span style={{ flex: 1 }}>{l.label}</span>
                <span className="mono" style={{ fontSize: 11, opacity: .6 }}>{l.code.toUpperCase()}</span>
              </button>
            ))}
          </div>
        </div>
      ) : null}
    </div>
  );
}

function CommandPalette({ open, onClose, go, toggleDark, nav }) {
  const { useState, useEffect, useRef } = React;
  const [q, setQ] = useState("");
  const [hi, setHi] = useState(0);
  const ref = useRef(null);

  // Only what this session can actually open — a hidden module must not be
  // reachable through the palette either.
  const actions = nav.map((n) => ({ id: n.id, icon: n.icon, label: T("cmd.goTo", { name: T(n.labelKey) }), run: () => go(n.id) })
    ).concat(nav.some((n) => n.id === "accounting")
      ? [{ id: "newinv", icon: "plus", label: T("acct.newInvoice"), run: () => { go("accounting"); setTimeout(() => window.dispatchEvent(new CustomEvent("erp:new-invoice")), 60); } }]
      : []
    ).concat([
      { id: "dark", icon: "spark", label: T("cmd.toggleDark"), run: toggleDark },
      { id: "lang", icon: "globe", label: T("cmd.switchLanguage"), run: () => I18N.setLang(I18N.lang === "ar" ? "en" : "ar") }
    ]);
  const visible = actions.filter((a) => a.label.toLowerCase().includes(q.toLowerCase()));

  useEffect(() => { if (open) { setQ(""); setHi(0); setTimeout(() => ref.current && ref.current.focus(), 30); } }, [open]);
  useEffect(() => { setHi(0); }, [q]);

  if (!open) return null;
  const pick = (a) => { onClose(); a.run(); };
  return (
    <div className="scrim top" onClick={onClose}>
      <div className="cmdk" onClick={(e) => e.stopPropagation()}>
        <div className="cmdk-input">
          <Icon name="search" size={16} />
          <input ref={ref} placeholder={T("cmd.placeholder")} value={q}
            onChange={(e) => setQ(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "ArrowDown") { e.preventDefault(); setHi((h) => Math.min(h + 1, visible.length - 1)); }
              if (e.key === "ArrowUp") { e.preventDefault(); setHi((h) => Math.max(h - 1, 0)); }
              if (e.key === "Enter" && visible[hi]) pick(visible[hi]);
            }} />
          <kbd>esc</kbd>
        </div>
        <ul className="cmdk-list">
          {visible.length === 0 ? <li className="cmdk-empty">{T("cmd.noMatches")}</li> : visible.map((a, i) => (
            <li key={a.id} className={"cmdk-item" + (i === hi ? " hi" : "")}
              onMouseEnter={() => setHi(i)} onClick={() => pick(a)}>
              <Icon name={a.icon} size={16} />{a.label}
              {i === hi ? <kbd>↵</kbd> : null}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function NotifBell({ go }) {
  const { useState, useEffect, useRef } = React;
  const [open, setOpen] = useState(false);
  const [items, setItems] = useState(D.notifications);
  const ref = useRef(null);
  const unread = items.filter((n) => !n.read).length;

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); };
  }, [open]);

  const markAll = () => setItems((xs) => xs.map((n) => ({ ...n, read: true })));
  const openItem = (n) => {
    setItems((xs) => xs.map((x) => (x.id === n.id ? { ...x, read: true } : x)));
    setOpen(false);
    if (n.route) go(n.route);
  };

  return (
    <div className="notif" ref={ref}>
      <button className={"notif-btn" + (open ? " on" : "")} onClick={() => setOpen((o) => !o)} title={T("notif.title")} aria-label={T("notif.title")}>
        <Icon name="bell" size={18} />
        {unread > 0 ? <span className="notif-dot">{unread}</span> : null}
      </button>
      {open ? (
        <div className="notif-pop">
          <div className="notif-head">
            <b>{T("notif.title")}</b>
            {unread > 0 ? <button className="notif-mark" onClick={markAll}>{T("notif.markAllRead")}</button> : <span className="notif-allread">{T("notif.allCaughtUp")}</span>}
          </div>
          <ul className="notif-list">
            {items.map((n) => (
              <li key={n.id}>
                <button className={"notif-item" + (n.read ? "" : " unread")} onClick={() => openItem(n)}>
                  <span className={"notif-ic " + n.tone}><Icon name={n.icon} size={15} stroke={2} /></span>
                  <span className="notif-main">
                    <span className="notif-title">{n.title}</span>
                    <span className="notif-body">{n.body}</span>
                    <span className="notif-time">{n.time}</span>
                  </span>
                  {n.read ? null : <span className="notif-unreaddot"></span>}
                </button>
              </li>
            ))}
          </ul>
          <button className="notif-foot" onClick={() => { setOpen(false); go("dashboard"); }}>{T("notif.viewAll")}</button>
        </div>
      ) : null}
    </div>
  );
}

function AppearancePop({ t, setTweak }) {
  const { useState, useEffect, useRef } = React;
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); };
  }, [open]);
  const accents = ["#1F6B4E", "#3454D1", "#7A5AE0", "#C2410C"];
  const densityLabel = { compact: T("appearance.compact"), regular: T("appearance.regular"), comfy: T("appearance.comfy") };
  return (
    <div className="notif" ref={ref}>
      <button className={"notif-btn" + (open ? " on" : "")} onClick={() => setOpen((o) => !o)} title={T("appearance.title")} aria-label={T("appearance.title")}>
        <Icon name="spark" size={17} />
      </button>
      {open ? (
        <div className="notif-pop ap-pop">
          <div className="notif-head"><b>{T("appearance.title")}</b></div>
          <div className="ap-body">
            <div className="ap-row">
              <span className="ap-label">{T("appearance.theme")}</span>
              <div className="ap-seg">
                <button className={!t.dark ? "on" : ""} onClick={() => setTweak("dark", false)}><Icon name="sun" size={14} />{T("appearance.light")}</button>
                <button className={t.dark ? "on" : ""} onClick={() => setTweak("dark", true)}><Icon name="moon" size={14} />{T("appearance.dark")}</button>
              </div>
            </div>
            <div className="ap-row">
              <span className="ap-label">{T("appearance.accent")}</span>
              <div className="ap-swatches">
                {accents.map((c) => (
                  <button key={c} className={"ap-swatch" + (t.accent === c ? " on" : "")} style={{ background: c }}
                    onClick={() => setTweak("accent", c)} aria-label={T("appearance.accentSwatch", { color: c })}></button>
                ))}
              </div>
            </div>
            <div className="ap-row">
              <span className="ap-label">{T("appearance.density")}</span>
              <div className="ap-seg">
                {["compact", "regular", "comfy"].map((d) => (
                  <button key={d} className={t.density === d ? "on" : ""} onClick={() => setTweak("density", d)}>{densityLabel[d]}</button>
                ))}
              </div>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  );
}

function App({ session, onLogout }) {
  const { useState, useEffect } = React;
  const nav = navFor(session);
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  // A saved route for a module this session cannot see (hidden, or no
  // permission) would leave the sidebar with nothing selected — start on the
  // first visible page instead.
  const [route, setRoute] = useState(() => {
    const saved = localStorage.getItem("erpvision.route");
    return nav.some((n) => n.id === saved) ? saved : nav[0].id;
  });
  const [pal, setPal] = useState(false);
  const [toasts, setToasts] = useState([]);

  // Same rule for navigation: a notification or module pointing at a hidden
  // route lands on the first visible page rather than on nothing.
  const go = (r) => {
    const to = nav.some((n) => n.id === r) ? r : nav[0].id;
    setRoute(to);
    localStorage.setItem("erpvision.route", to);
  };

  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setPal((p) => !p); }
      if (e.key === "Escape") setPal(false);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  useEffect(() => {
    const onToast = (e) => {
      const id = Date.now() + Math.random();
      setToasts((ts) => [...ts, { id, msg: e.detail }]);
      setTimeout(() => setToasts((ts) => ts.filter((x) => x.id !== id)), 3200);
    };
    window.addEventListener("erp:toast", onToast);
    return () => window.removeEventListener("erp:toast", onToast);
  }, []);

  const current = NAV.find((n) => n.id === route && nav.some((v) => v.id === n.id)) || nav[0];
  const PageEl = current.el();
  const [sideOpen, setSideOpen] = useState(false);
  useEffect(() => { setSideOpen(false); }, [route]);

  return (
    <div className="shell" data-theme={t.dark ? "dark" : "light"} data-density={t.density}
      style={{ "--accent": t.accent }}>
      <aside className={"side" + (sideOpen ? " open" : "")}>
        <div className="logo">
          <span className="logo-mark"></span>
          <span className="logo-word display">{T("app.brand")}</span>
          <span className="logo-tag">{T("app.brandTag")}</span>
        </div>
        <nav className="nav">
          {nav.map((n) => (
            <button key={n.id} className={"nav-item" + (route === n.id ? " on" : "")} onClick={() => go(n.id)} title={T(n.labelKey)}>
              <Icon name={n.icon} size={17} />
              <span className="nav-label">{T(n.labelKey)}</span>
            </button>
          ))}
          {/* Unlocked modules look like shipped ones — say why they are here. */}
          {PREVIEW ? (
            <div className="nav-preview" title={T("app.previewHint")}>
              <Icon name="spark" size={13} />
              <span className="nav-label">{T("app.previewMode")}</span>
            </div>
          ) : null}
        </nav>
        <div className="side-foot">
          <Avatar name={session.name} size={32} />
          <span className="side-user">
            <b>{session.name}</b>
            <small>{TV(session.role || D.user.role)}</small>
          </span>
        </div>
        {/* Signing out gets its own labelled row: as a bare chevron tucked beside
            the avatar it read as "collapse the sidebar", and RTL flipped it to
            point that way too. */}
        <button className="side-logout" onClick={onLogout} aria-label={T("app.signOut")} title={T("app.signOut")}>
          <Icon name="logout" size={16} />
          <span className="nav-label">{T("app.signOut")}</span>
        </button>
      </aside>
      {sideOpen ? <div className="side-scrim" onClick={() => setSideOpen(false)}></div> : null}

      <main className="main">
        <header className="topbar">
          <div className="topbar-left">
            <button className="nav-burger" onClick={() => setSideOpen(true)} aria-label={T("app.openMenu")}><Icon name="menu" size={18} /></button>
            <h2 className="topbar-title display">{T(current.labelKey)}</h2>
            <KpiToggle />
          </div>
          <div className="topbar-right">
            <button className="searchpill" onClick={() => setPal(true)}>
              <Icon name="search" size={14} />
              <span>{T("app.searchOrJump")}</span>
              <kbd>⌘K</kbd>
            </button>
            <LangPop />
            <AppearancePop t={t} setTweak={setTweak} />
            <NotifBell go={go} />
            {PRIMARY_ACTION[route] ? (
              <Btn variant="primary" icon={PRIMARY_ACTION[route].icon}
                onClick={() => PRIMARY_ACTION[route].run(go)}>
                {T(PRIMARY_ACTION[route].labelKey)}
              </Btn>
            ) : null}
          </div>
        </header>
        <div className="content" key={route}>
          <PageEl go={go} />
        </div>
      </main>

      <CommandPalette open={pal} onClose={() => setPal(false)} go={go} nav={nav}
        toggleDark={() => setTweak("dark", !t.dark)} />

      <div className="toasts">
        {toasts.map((x) => (
          <div key={x.id} className="toast"><Icon name="check" size={14} stroke={2.4} />{x.msg}</div>
        ))}
      </div>

      <TweaksPanel>
        <TweakSection label={T("appearance.theme")} />
        <TweakColor label={T("appearance.accent")} value={t.accent}
          options={["#1F6B4E", "#3454D1", "#7A5AE0", "#C2410C"]}
          onChange={(v) => setTweak("accent", v)} />
        <TweakToggle label={T("appearance.darkMode")} value={t.dark} onChange={(v) => setTweak("dark", v)} />
        <TweakSection label={T("appearance.layout")} />
        <TweakRadio label={T("appearance.density")} value={t.density} options={["compact", "regular", "comfy"]}
          onChange={(v) => setTweak("density", v)} />
      </TweaksPanel>
    </div>
  );
}

function Root() {
  const { useState, useEffect } = React;
  const [session, setSess] = useState(() => getSession());
  useLang(); // re-render the whole tree when the language changes

  // Drop the boot splash (index.html) once there is a real screen behind it.
  // useEffect fires after the commit but can still be ahead of the paint, so
  // the rAF waits one frame — without it the splash fades out over an empty
  // shell for a frame or two, which reads as a flicker.
  useEffect(() => {
    requestAnimationFrame(() => window.__bootDone && window.__bootDone());
  }, []);

  const login = (acct) => setSess(acct);
  const logout = () => { clearSession(); if (window.API) API.logout(); setSess(null); };
  if (!session) return <Login onLogin={login} />;
  window.__session = session;
  return <App session={session} onLogout={logout} />;
}

ReactDOM.createRoot(document.getElementById("root")).render(<Root />);
