/* ========================================================================
   roles-shell.jsx — role state, identity switcher, sidebar, toasts,
   permission-denied and removed-user states.
   Audit is deferred (design delta §4.1) — no audit is recorded or shown.
   ======================================================================== */
const { useState: uR, useEffect: eR, useMemo: mR, useRef: rR, useCallback: cR } = React;

const RolesCtx = React.createContext(null);
const useRoles = () => React.useContext(RolesCtx);

/* ---------- Provider ---------- */
function RolesProvider({ children, failMode }) {
  const [members, setMembers] = uR(() => window.MEMBERS.map((m) => ({ ...m })));
  const [actorId, setActorId] = uR(1);
  const [toasts, setToasts] = uR([]);
  const [signedOut, setSignedOut] = uR(false);

  const actor = mR(() => members.find((m) => m.id === actorId) || members[0], [members, actorId]);
  const role = actor.status === "removed" ? "analyst" : actor.role;

  const toast = cR((t) => {
    const id = Math.random().toString(36).slice(2);
    setToasts((list) => [...list, { id, ...t }]);
    setTimeout(() => setToasts((list) => list.filter((x) => x.id !== id)), t.sticky ? 12000 : 7000);
  }, []);
  const dismiss = (id) => setToasts((l) => l.filter((x) => x.id !== id));

  /* Email change: request → pending → confirm (design delta §5.2).
     A new request supersedes and kills the previous link. */
  const requestEmailChange = (me, next) => {
    setMembers((l) => l.map((m) => m.id === me.id
      ? { ...m, pending_email: next, pending_email_at: new Date().toISOString() } : m));
    toast({
      kind: "ok", title: "Check your new inbox",
      body: "We've sent a link to " + next + ". Your sign-in email stays as " + me.email + " until you follow it. We've also let " + me.email + " know."
    });
  };
  const cancelEmailChange = (me) => {
    setMembers((l) => l.map((m) => m.id === me.id ? { ...m, pending_email: null, pending_email_at: null } : m));
    toast({ kind: "ok", title: "Email change cancelled", body: "Your sign-in email is still " + me.email + "." });
  };
  const completeEmailChange = (memberId) => {
    setMembers((l) => l.map((m) => m.id === memberId && m.pending_email
      ? { ...m, email: m.pending_email, pending_email: null, pending_email_at: null } : m));
  };

  /* Resending kills the previous link and starts a fresh 48-hour window */
  const resendInvite = (m) => {
    setMembers((list) => list.map((x) => x.id === m.id ? { ...x, invited: new Date().toISOString() } : x));
    toast({
      kind: "ok", title: "Invitation resent",
      body: "A new link is on its way to " + m.email + ". The previous link no longer works, and the new one expires in " + window.INVITE_TTL_H + " hours."
    });
  };

  const value = {
    members, setMembers, actor, actorId, setActorId, role,
    can: (cap) => window.roleCan(role, cap),
    toast, toasts, dismiss, resendInvite,
    requestEmailChange, cancelEmailChange, completeEmailChange,
    signedOut, setSignedOut, failMode
  };

  /* Expose the active capability check to the shared MILO pages (they
     default to full access when this is absent). */
  window.MP_CAN = (cap) => window.roleCan(role, cap);
  window.MP_ROLE = role;
  window.MP_MEMBERS = members;
  /* USERS and MEMBERS are the same people seen from two sides. A rename or an
     email change on one must reach the other. */
  window.MP_SYNC_MEMBER = (email, patch) => {
    const key = String(email).toLowerCase();
    setMembers((l) => l.map((m) => String(m.email).toLowerCase() === key
      ? { ...m, ...(patch.name ? { name: patch.name } : {}),
          ...(patch.email ? { email: patch.email } : {}),
          ...(patch.initials ? { initials: patch.initials } : {}) }
      : m));
  };

  return <RolesCtx.Provider value={value}>{children}</RolesCtx.Provider>;
}

/* ---------- Role + status badges ---------- */
function RoleBadge({ role, small }) {
  const r = window.ROLES[role];
  if (!r) return null;
  return <Badge variant={r.tone} icon={r.icon}>{r.label}</Badge>;
}
function MemberStatusBadge({ status, expired }) {
  if (status === "pending" && expired) return <Badge variant="warn" icon="clock-alert">Invitation expired</Badge>;
  if (status === "pending") return <Badge variant="clay" icon="clock">Invitation pending</Badge>;
  if (status === "removed") return <Badge variant="muted" icon="user-x">Access removed</Badge>;
  return <Badge variant="ok" icon="circle-check">Active</Badge>;
}

/* ---------- Identity switcher (prototype affordance, sidebar footer) ---------- */
function IdentitySwitcher() {
  const { members, actor, setActorId, setSignedOut, toast, setMembers } = useRoles();
  const [open, setOpen] = uR(false);
  const wrap = rR(null);
  eR(() => {
    if (!open) return;
    const onDoc = (e) => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); };
    window.addEventListener("click", onDoc);
    return () => window.removeEventListener("click", onDoc);
  }, [open]);

  const personas = window.PERSONA_IDS.map((id) => members.find((m) => m.id === id)).filter(Boolean);

  /* Scenario F — role downgraded mid-session */
  const downgrade = () => {
    setMembers((list) => list.map((m) => m.id === actor.id ? { ...m, role: "analyst" } : m));
    setOpen(false);
    toast({ kind: "ok", title: "Your role has changed", body: "An Admin changed your role to Financial Analyst. Your access has been updated.", sticky: true });
  };
  /* Removal mid-session */
  const revoke = () => {
    setMembers((list) => list.map((m) => m.id === actor.id ? { ...m, status: "removed" } : m));
    setOpen(false);
    setSignedOut(true);
  };

  return (
    <div className="mp-idsw" ref={wrap}>
      {open ? (
        <div className="mp-idsw-pop">
          <div className="mp-idsw-pop-head">Viewing the platform as</div>
          {personas.map((p) => (
            <button key={p.id} type="button" className={"mp-idsw-opt" + (p.id === actor.id ? " is-active" : "")} onClick={() => { setActorId(p.id); setOpen(false); }}>
              <Avatar initials={p.initials} size={30} tone={"role-" + p.role} />
              <div>
                <div className="mp-idsw-opt-name">{p.name}</div>
                <div className="mp-idsw-opt-role">{window.ROLES[p.role].label}{p.status === "removed" ? " · access removed" : ""}</div>
              </div>
              {p.id === actor.id ? <Icon name="check" size={15} /> : null}
            </button>
          ))}
          <div className="mp-idsw-sim">
            <div className="mp-idsw-sim-title">Simulate a change to your session</div>
            <button type="button" className="mp-idsw-sim-btn" onClick={downgrade} disabled={actor.role === "analyst"}>
              <Icon name="arrow-down-circle" size={14} />
              <span>My role is downgraded to Financial Analyst</span>
            </button>
            <button type="button" className="mp-idsw-sim-btn" onClick={revoke} disabled={actor.role === "owner"}>
              <Icon name="user-x" size={14} />
              <span>My access is removed</span>
            </button>
          </div>
        </div>
      ) : null}
      <button type="button" className="mp-idsw-btn" onClick={() => setOpen((v) => !v)}>
        <Avatar initials={actor.initials} size={34} tone={"role-" + actor.role} />
        <div className="mp-idsw-meta">
          <div className="mp-idsw-label">Viewing as</div>
          <div className="mp-idsw-name">{actor.name}</div>
          <div className="mp-idsw-role">{window.ROLES[actor.role].label}</div>
        </div>
        <Icon name="chevrons-up-down" size={15} className="mp-idsw-caret" />
      </button>
    </div>
  );
}

/* ---------- Role-aware sidebar ---------- */
function RoleSidebar({ activePath }) {
  const { role } = useRoles();
  const items = window.ROLE_NAV.filter((it) => window.roleCan(role, it.cap));
  const grouped = {};
  items.forEach((it) => { (grouped[it.group] ??= []).push(it); });

  const [collapsed, setCollapsed] = uR(() => { try { return localStorage.getItem("mp-side-collapsed") === "1"; } catch (e) { return false; } });
  const toggle = () => setCollapsed((c) => { const n = !c; try { localStorage.setItem("mp-side-collapsed", n ? "1" : "0"); } catch (e) {} return n; });

  return (
    <aside className={"mp-side" + (collapsed ? " is-collapsed" : "")}>
      <div className="mp-side-inner">
        <div className="mp-side-brand">
          <img src="assets/logos/icon-colour-dark.svg" alt="" />
          <div className="mp-side-brand-text"><div className="mp-side-brand-name">Meaningful<br />Planet</div></div>
        </div>
        <div className="mp-side-workspace">
          <Icon name="building-2" size={15} className="mp-side-workspace-icon" />
          <span className="mp-side-workspace-name">{window.MILO_DATA.COMPANY}</span>
        </div>
        <nav className="mp-side-nav">
          {Object.entries(grouped).map(([group, list]) => (
            <div key={group} className="mp-side-section">
              <div className="mp-side-group">{group}</div>
              {list.map((it) => {
                const active = activePath === it.path || activePath.startsWith(it.path + "/");
                return (
                  <a key={it.path} href={"#" + it.path} className={"mp-side-item" + (active ? " is-active" : "")}>
                    <Icon name={it.icon} size={18} />
                    <span>{it.label}</span>
                  </a>
                );
              })}
            </div>
          ))}
        </nav>
        <button type="button" className="mp-side-toggle" onClick={toggle} aria-label={collapsed ? "Expand navigation" : "Collapse navigation"}>
          <Icon name="panel-left-close" size={16} className={collapsed ? "mp-side-toggle-ic is-flipped" : "mp-side-toggle-ic"} />
          <span>{collapsed ? "Expand Navigation" : "Collapse Navigation"}</span>
        </button>
        <IdentitySwitcher />
        <button type="button" className="mp-side-logout" onClick={() => {
          if (window.DEMO_TOAST) window.DEMO_TOAST("Log out ends your session and returns you to the sign-in page in the live platform.", arguments[0]);
        }}>
          <Icon name="log-out" size={16} /><span>Log Out</span>
        </button>
      </div>
    </aside>
  );
}

/* ---------- Toast stack ---------- */
function ToastStack() {
  const { toasts, dismiss } = useRoles();
  if (!toasts.length) return null;
  return (
    <div className="mp-toasts">
      {toasts.map((t) => (
        <div key={t.id} className={"mp-tc" + (t.kind === "err" ? " mp-tc--err" : "")}>
          <span className="mp-tc-ic"><Icon name={t.kind === "err" ? "triangle-alert" : "circle-check"} size={17} /></span>
          <div style={{ minWidth: 0 }}>
            <div className="mp-tc-title">{t.title}</div>
            {t.body ? <div className="mp-tc-body">{t.body}</div> : null}
          </div>
          <button className="mp-tc-x" onClick={() => dismiss(t.id)} aria-label="Dismiss"><Icon name="x" size={14} /></button>
        </div>
      ))}
    </div>
  );
}

/* ---------- Permission denied ---------- */
function PermissionDeniedPage() {
  return (
    <div className="mp-denied">
      <div className="mp-denied-ic"><Icon name="lock" size={28} /></div>
      <h1 className="mp-denied-title">You don't have access to this page</h1>
      <p className="mp-denied-msg">
        You do not have permission to access this page. If you think this is in error and you should have access to this page, please contact your account owner.
      </p>
      <div className="mp-denied-actions">
        <Button variant="primary" icon="arrow-left" href="#/dashboard">Return to Dashboard</Button>
        <Button variant="secondary" onClick={() => history.back()}>Go Back</Button>
      </div>
    </div>
  );
}

/* ---------- Removed-user signed-out state ---------- */
function SignedOutScreen() {
  const { actor, setSignedOut, setMembers, setActorId } = useRoles();
  const restore = () => {
    setMembers((l) => l.map((m) => m.id === actor.id ? { ...m, status: "active" } : m));
    setSignedOut(false);
    setActorId(1);
    window.location.hash = "/dashboard";
  };
  return (
    <div className="mp-signout">
      <div className="mp-signout-card">
        <img src="assets/logos/mp-logo-moss.svg" alt="Meaningful Planet" className="mp-signout-logo" />
        <h1 className="mp-signout-title">Sign In</h1>
        <p className="mp-signout-msg">Your session has ended.</p>
        <div className="mp-signout-note">
          <Icon name="user-x" size={16} />
          <span>Your access to this organisation has been removed. Contact your account owner if you believe this is an error.</span>
        </div>
        <div className="mp-signout-form">
          <div className="mp-field">
            <label className="mp-field-label">Email</label>
            <input className="mp-field-input" defaultValue={actor.email} readOnly />
          </div>
          <div className="mp-field">
            <label className="mp-field-label">Password</label>
            <input className="mp-field-input" type="password" defaultValue="••••••••••" readOnly />
          </div>
          <Button variant="primary" size="lg" className="is-disabled">Sign In</Button>
        </div>
        <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid var(--border-subtle)" }}>
          <button type="button" className="mp-linkbtn" onClick={restore}>
            <Icon name="rotate-ccw" size={14} /><span>Reset the Prototype and Sign Back In as the Account Owner</span>
          </button>
        </div>
      </div>
    </div>
  );
}

/* ---------- Read-only note ---------- */
function ReadOnlyNote({ children, plain, icon = "lock" }) {
  return (
    <div className={"mp-ro-note" + (plain ? " mp-ro-note--plain" : "")}>
      <Icon name={icon} size={15} />
      <span>{children}</span>
    </div>
  );
}

Object.assign(window, {
  RolesCtx, useRoles, RolesProvider, RoleBadge, MemberStatusBadge,
  IdentitySwitcher, RoleSidebar, ToastStack, PermissionDeniedPage, SignedOutScreen, ReadOnlyNote
});
