/* page-users.jsx — Users module (list + create/edit), role-parameterised. IIFE-scoped; exports via window. */
(function(){
const { useState, useMemo } = React;

// ── In-file constants (component-scoped; not shared data) ───────────────────
// Self-edit guard seed ids (one per tier). ASSUMPTION flagged in notes — production
// derives "self" from the session principal, not a hardcoded seed.
const SELF_ID = { GA: "u_ga_01", CM: "u_cm_ph", AM: "u_am_01", OM: "u_om_01" };
// Tier-filtered role options for the editor (S3): a creator may only assign tiers at/below their own.
const ROLE_OPTIONS_FOR = (editorRole) => ({
  GA: ["GA", "CM", "AM", "OM"],
  CM: ["AM", "OM"],
  AM: ["OM"],
  OM: [],
}[editorRole] || []);
const ROLE_LABEL = { GA: "Global Admin", CM: "Country Manager", AM: "Area Manager", OM: "Outlet Manager" };
// language_pref (additive data field; option set flagged as assumption in notes).
const LANG_OPTIONS = [
  { value: "en", label: "English" },
  { value: "fil", label: "Filipino" },
  { value: "id", label: "Bahasa Indonesia" },
  { value: "th", label: "ไทย (Thai)" },
];
const LANG_LABEL = Object.fromEntries(LANG_OPTIONS.map(o => [o.value, o.label]));
// 4-state user status enum (owned by 01-modules; data agent migrates "disabled"→"inactive").
const USER_STATUS = ["invited", "active", "locked", "inactive"];
const STATUS_OPTS = USER_STATUS.map(s => ({ value: s, label: s }));

const AM_AREA_OF = (selfId) => (SB3_USERS.find(u => u.id === selfId) || {}).area_id || AM_AREA_ID;

/**
 * @spec GA-050 · CM-030 · AM-030 — Users list (role-parameterised; OM read-only)
 * One component for all 4 tiers. Internal branching drives scope / columns / filters / actions.
 */
function UsersList({ role = "GA" }) {
  if (!["GA", "CM", "AM"].includes(role)) return _notAuthorised; // OM has no list — nav lands on own UserDetail ("Me")

  // Deep-link contract (target side): incoming outlet_id / franchise_id / country_id pre-applies + removable chip.
  const incomingFilter = typeof window !== "undefined" && window.__sixhands_route_payload
    ? window.__sixhands_route_payload.filter : null;
  const ctxLabel = incomingFilter && (incomingFilter.outlet_id || incomingFilter.franchise_id || incomingFilter.country_id) ? incomingFilter.label : null;
  const ctxType = incomingFilter && incomingFilter.outlet_id ? "outlet"
    : incomingFilter && incomingFilter.franchise_id ? "franchise"
    : incomingFilter && incomingFilter.country_id ? "country" : null;

  const [roleFilter, setRoleFilter] = useState(incomingFilter && incomingFilter.country_id ? "" : "");
  const [statusFilter, setStatusFilter] = useState("");
  const [geo, setGeo] = useState({ countries: incomingFilter && incomingFilter.country_id ? [incomingFilter.country_id] : [], areas: [], outlets: [] });
  const [searchQuery, setSearchQuery] = useState("");
  const [sel, setSel] = useState({}); // { all?:bool, [id]:bool }
  const [confirm, setConfirm] = useState(null); // { kind:"lock"|"revoke", ids?:[], id? }
  const [toast, setToast] = useState(null);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  const [ctxOutletId, setCtxOutletId] = useState(incomingFilter && incomingFilter.outlet_id ? incomingFilter.outlet_id : null);
  const [ctxFranchiseId, setCtxFranchiseId] = useState(incomingFilter && incomingFilter.franchise_id ? incomingFilter.franchise_id : null);
  const [ctxChip, setCtxChip] = useState(ctxLabel ? { label: ctxLabel, type: ctxType } : null);
  const clearCtx = () => { setCtxChip(null); setCtxOutletId(null); setCtxFranchiseId(null); setGeo({ countries: [], areas: [], outlets: [] }); };

  const canManage = true; // GA/CM/AM = multi-select + bulk + create + edit
  const isOM = false;      // UsersList is GA/CM/AM only; OM uses UserDetail("Me")
  const roleLc = role.toLowerCase();
  const editRoute = `${roleLc}-user-edit`;
  const detailRoute = `${roleLc}-user-detail`;

  // Role-scoped base rows.
  const selfId = SELF_ID[role];
  const baseRows = useMemo(() => {
    if (role === "GA") return SB3_USERS;
    if (role === "CM") return SB3_USERS.filter(u => u.country_id === "PH" && ["area_manager", "outlet_manager"].includes(u.role_type));
    if (role === "AM") return SB3_USERS.filter(u => u.area_id === AM_AREA_OF(selfId) && u.role_type === "outlet_manager");
    // OM — own outlet's staff (the outlets this OM manages)
    const me = SB3_USERS.find(u => u.id === selfId);
    const myOutlets = (me && me.outlet_ids) || [];
    return SB3_USERS.filter(u => u.role_type === "outlet_manager" && (u.outlet_ids || []).some(o => myOutlets.includes(o)));
  }, [role, selfId]);

  const q = searchQuery.trim().toLowerCase();
  const allRows = baseRows
    .filter(u => !ctxOutletId || (u.outlet_ids || []).includes(ctxOutletId))
    .filter(u => !ctxFranchiseId || u.franchise_id === ctxFranchiseId)
    .filter(u => !roleFilter || u.role_type === ROLE_FROM_CODE[roleFilter])
    .filter(u => !statusFilter || u.status === statusFilter)
    .filter(u => !geo.countries.length || geo.countries.includes(u.country_id))
    .filter(u => !geo.areas.length || geo.areas.includes(u.area_id))
    .filter(u => !geo.outlets.length || (u.outlet_ids || []).some(o => geo.outlets.includes(o)))
    .filter(u => !q || u.full_name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q));
  const totalPages = Math.max(1, Math.ceil(allRows.length / pageSize));
  const rows = allRows.slice((page - 1) * pageSize, page * pageSize);

  const scopeOf = (u) =>
    u.role_type === "global_admin"    ? "Global"
  : u.role_type === "country_manager" ? _countryLabel(u.country_id)
  : u.role_type === "area_manager"    ? `${_countryLabel(u.country_id)} · ${_areaLabel(u.area_id)}`
  :                                     `Outlet: ${(u.outlet_ids || []).join(", ") || "—"}`;


  // ── Selection (GA/CM/AM only) ──────────────────────────────────────────────
  const selectedIds = Object.keys(sel).filter(k => k !== "all" && sel[k]);
  const onToggleSelected = (id) => {
    if (id === "all") {
      const allOn = rows.length > 0 && rows.every(r => sel[r.id]);
      const next = {};
      if (!allOn) rows.forEach(r => { next[r.id] = true; });
      setSel(next);
    } else {
      setSel(s => ({ ...s, [id]: !s[id] }));
    }
  };
  const selRows = SB3_USERS.filter(u => selectedIds.includes(u.id));
  const invitedSel = selRows.filter(u => u.status === "invited");
  const activeSel = selRows.filter(u => u.status === "active");
  const lockedSel = selRows.filter(u => u.status === "locked");
  const inactiveSel = selRows.filter(u => u.status === "inactive");
  // Reactivate covers locked + inactive — by the managing tier (the manageable-users list above is
  // already scoped to the next-higher authority chain: CM→AM/OM, AM→OM, GA→all). #7 / OQ-STATUS-7.
  const reactivatableSel = [...lockedSel, ...inactiveSel];

  const doResend = () => { setToast(`Invite resent to ${invitedSel.length} user${invitedSel.length === 1 ? "" : "s"}.`); setSel({}); };
  const doReactivate = () => { setToast(`Reactivated ${reactivatableSel.length} user${reactivatableSel.length === 1 ? "" : "s"}.`); setSel({}); };
  const doLock = () => { setToast(`Locked ${activeSel.length} user${activeSel.length === 1 ? "" : "s"}.`); setSel({}); setConfirm(null); };
  const doInactivate = () => { setToast(`Deactivated ${activeSel.length} user${activeSel.length === 1 ? "" : "s"}.`); setSel({}); setConfirm(null); };
  const doRevoke = () => { setToast(`Invite revoked.`); setConfirm(null); };

  const bulkActions = (
    <>
      {invitedSel.length > 0 && <Btn variant="secondary" size="sm" onClick={doResend}>Resend invite</Btn>}
      {reactivatableSel.length > 0 && <Btn variant="secondary" size="sm" onClick={doReactivate}>Reactivate</Btn>}
      {activeSel.length > 0 && <Btn variant="danger" size="sm" onClick={() => setConfirm({ kind: "lock" })}>Lock</Btn>}
      {activeSel.length > 0 && <Btn variant="danger" size="sm" onClick={() => setConfirm({ kind: "inactivate" })}>Deactivate</Btn>}
    </>
  );

  // ── Description per role ─────────────────────────────────────────────────
  const desc = role === "GA" ? "All staff accounts."
    : role === "CM" ? "Your country's staff."
    : role === "AM" ? "Your area's staff."
    : "Your outlet's staff.";

  // ── Add-new authority (RBAC, not tier label). OM has none. ───────────────
  const canCreate = role === "GA" || role === "CM" || role === "AM";

  // ── Geoscope options (tier-scoped via role; OM gets none) ────────────────
  const geoCountries = SB2_COUNTRIES.map(c => ({ value: c.country_id, label: `${c.flag} ${c.country_name}` }));
  const geoAreas = SB3_AREAS.map(a => ({ value: a.id, label: a.name, country_id: a.country_id }));
  const geoOutlets = SB3_OUTLETS.map(o => ({ value: o.id, label: o.name, country_id: o.country_id, area_id: o.area_id }));

  // primaryFilter: GA/CM/AM = [role_type, status]; OM = [status] only.
  const roleTypeOpts = [{ value: "", label: "All roles" }, ...["GA", "CM", "AM", "OM"].map(r => ({ value: r, label: ROLE_LABEL[r] }))];
  const statusInline = [{ value: "", label: "All statuses" }, ...STATUS_OPTS];
  const primaryFilter = isOM
    ? [{ key: "status", label: "Status", options: statusInline }]
    : [{ key: "role", label: "Role", options: roleTypeOpts }, { key: "status", label: "Status", options: statusInline }];
  const primaryValue = isOM ? { status: statusFilter } : { role: roleFilter, status: statusFilter };
  const onPrimaryChange = (next) => {
    if (next.role !== undefined) setRoleFilter(next.role);
    if (next.status !== undefined) setStatusFilter(next.status);
  };

  const advancedFilters = isOM ? [] : [
    { key: "geo", kind: "geoscope", label: "Network scope", role, countries: geoCountries, areas: geoAreas, outlets: geoOutlets },
  ];

  // Columns: OM drops scope, is read-only, phone redacted (phone not shown in list either way).
  const columns = [
    { label: "Name", sortable: true, sortKey: "full_name", render: r => (
      <div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
        <span style={{ ...T.primary() }}>{r.full_name}</span>
        <span style={{ ...T_MUTED, fontSize: 12 }}>{r.email}</span>
      </div>
    ) },
    { label: "Role", width: 130, sortable: true, sortKey: "role_type", render: r => <RoleBadge role={ROLE_CODE[r.role_type]}/> },
    { label: "Email", width: 260, sortable: true, sortKey: "email", render: r => _emailLink(r.email, { muted: true }) },
    ...(isOM ? [] : [{ label: "Scope", sortable: true, sortKey: "country_id", render: r => <span style={{ ...T_MUTED }}>{scopeOf(r)}</span> }]),
    { label: "Status", width: 110, sortable: true, sortKey: "status", render: r => <StatusPill status={r.status} kind="user"/> },
    { label: "Last login", width: 150, sortable: true, sortKey: "last_login", render: r => <span style={{ ...T_MUTED }}>{_fmtDateTime(r.last_login, r.country_id || "SG")}</span> },
    // Per-row Revoke invite (invited rows only; GA/CM/AM). Hover-only action column.
    ...(canManage ? [{ label: "", width: 130, render: r => r.status === "invited"
      ? <Btn variant="danger" size="sm" onClick={(e) => { e.stopPropagation(); setConfirm({ kind: "revoke", id: r.id, name: r.full_name }); }}>Revoke invite</Btn>
      : null }] : []),
  ];

  return (
    <div className="page-inner">
      <PageHead title="Users" description={desc}
        actions={canCreate
          ? <Btn variant="primary" onClick={() => _go(editRoute)}>New user</Btn>
          : null}/>

      <FilterBar
        primaryFilter={primaryFilter}
        primaryValue={primaryValue}
        onPrimaryChange={onPrimaryChange}
        advancedFilters={advancedFilters}
        advancedValues={isOM ? {} : { geo }}
        onAdvancedChange={(k, v) => { if (k === "geo") setGeo(v); }}
        onAdvancedReset={() => setGeo({ countries: [], areas: [], outlets: [] })}
        onReset={() => { setRoleFilter(""); setStatusFilter(""); setGeo({ countries: [], areas: [], outlets: [] }); setSearchQuery(""); }}
        search={searchQuery}
        onSearch={setSearchQuery}
        searchPlaceholder="Search users…"
      />

      {ctxChip && (
        <div style={{ marginBottom: 12 }}>
          <span className="chip on">
            {ctxChip.type === "outlet" ? "Outlet" : ctxChip.type === "franchise" ? "Franchisee" : "Country"}: {ctxChip.label}
            <button onClick={clearCtx} aria-label="Clear context filter"
              style={{ display: "inline-flex", alignItems: "center", background: "none", border: "none", padding: 0, marginLeft: 2, cursor: "pointer", color: "inherit" }}>
              <LIcon name="X" size={14}/>
            </button>
          </span>
        </div>
      )}

      {canManage && <BulkBar selectedCount={selectedIds.length} onDeselect={() => setSel({})} actions={bulkActions}/>}

      <Table
        emptyText="No users match these filters."
        columns={columns}
        rows={rows}
        selected={canManage ? sel : undefined}
        onToggleSelected={canManage ? onToggleSelected : undefined}
        onRow={r => _go(detailRoute, { id: r.id })}
      />

      <TableFooter page={page} totalPages={totalPages} onPage={setPage} count={pageSize} onCountChange={c => { setPageSize(c); setPage(1); }}/>

      <ConfirmModal
        open={!!confirm}
        destructive
        title={confirm && confirm.kind === "lock" ? `Lock ${activeSel.length} user${activeSel.length === 1 ? "" : "s"}?`
          : confirm && confirm.kind === "inactivate" ? `Deactivate ${activeSel.length} user${activeSel.length === 1 ? "" : "s"}?`
          : confirm && confirm.kind === "revoke" ? "Revoke invite?" : ""}
        body={confirm && confirm.kind === "lock"
          ? <>Locking blocks sign-in for the selected active user{activeSel.length === 1 ? "" : "s"} until reactivated. Out-of-scope rows are excluded.</>
          : confirm && confirm.kind === "inactivate"
          ? <>Deactivating sets the selected user{activeSel.length === 1 ? "" : "s"} to <strong>inactive</strong> — sign-in is blocked. A higher-tier manager can <strong>reactivate</strong> them later. Out-of-scope rows are excluded.</>
          : <>This permanently deletes the never-accepted invite stub. The link stops working immediately and cannot be undone.</>}
        confirmLabel={confirm && confirm.kind === "lock" ? "Lock users" : confirm && confirm.kind === "inactivate" ? "Deactivate" : "Revoke invite"}
        onCancel={() => setConfirm(null)}
        onConfirm={confirm && confirm.kind === "lock" ? doLock : confirm && confirm.kind === "inactivate" ? doInactivate : doRevoke}
      />

      <Toast message={toast || ""} show={!!toast}/>
    </div>
  );
}

/**
 * @spec GA-051 · CM-031 · AM-031 — User create / edit (role-parameterised; full page)
 * Tier-filtered role dropdown; role editable on edit, disabled on self-edit. OM has no editor route.
 */
function UserEditor({ role = "GA" }) {
  if (!["GA", "CM", "AM"].includes(role)) return _notAuthorised;

  const payloadId = typeof window !== "undefined" && window.__sixhands_route_payload ? window.__sixhands_route_payload.id : null;
  const existing = payloadId ? SB3_USERS.find(u => u.id === payloadId) || null : null;
  const isNew = !existing;
  const backRoute = role === "GA" ? "ga-users" : role === "CM" ? "cm-users" : "am-users";
  const isSelfEdit = !isNew && existing && existing.id === SELF_ID[role];

  const roleOpts = ROLE_OPTIONS_FOR(role);
  const [form, setForm] = useState(existing ? {
    ...existing, role_code: ROLE_CODE[existing.role_type],
    language_pref: existing.language_pref || "en",
  } : {
    id: "u_new", full_name: "", email: "", phone: "",
    role_code: roleOpts[0] || "", role_type: ROLE_FROM_CODE[roleOpts[0]] || "",
    country_id: role === "CM" ? "PH" : "", franchise_id: "", area_id: role === "AM" ? AM_AREA_ID : "", outlet_ids: [],
    language_pref: "en", status: "invited", mfa_enabled: false,
  });
  const [errors, setErrors] = useState({});
  const [savedToast, setSavedToast] = useState(false);
  const [statusConfirm, setStatusConfirm] = useState(null);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const code = form.role_code;
  const showCountry = code === "CM" || code === "AM" || code === "OM";
  const showFranchisee = code === "CM" || code === "AM" || code === "OM";
  const showArea = code === "AM" || code === "OM";
  const areaRequired = code === "AM";
  const showOutlets = code === "OM";

  const franchiseeOptions = SB3_FRANCHISEES.filter(f => f.status !== "inactive" && (!form.country_id || f.country_id === form.country_id));
  const areaOptions = SB3_AREAS.filter(a => (!form.country_id || a.country_id === form.country_id) && (!form.franchise_id || a.franchise_id === form.franchise_id));
  const outletOptions = SB3_OUTLETS.filter(o =>
    (!form.country_id || o.country_id === form.country_id) &&
    (!form.franchise_id || o.franchise_id === form.franchise_id) &&
    (!form.area_id || o.area_id === form.area_id)
  );

  // S3 — role editable on edit (tier-filtered), reset scope on change.
  const onChangeRole = (c) => setForm(f => ({
    ...f, role_code: c, role_type: ROLE_FROM_CODE[c] || "",
    country_id: role === "CM" ? "PH" : "", franchise_id: "", area_id: "", outlet_ids: [],
  }));

  const onChangeStatus = (next) => {
    const isDestructive = form.status === "active" && (next === "locked" || next === "inactive");
    if (isDestructive) { setStatusConfirm({ next }); return; }
    set("status", next);
  };

  const onChangeCountry = (v) => setForm(f => ({ ...f, country_id: v, franchise_id: "", area_id: "", outlet_ids: [] }));
  const onChangeFranchisee = (v) => setForm(f => ({ ...f, franchise_id: v, area_id: "", outlet_ids: [] }));
  const onChangeArea = (v) => setForm(f => ({ ...f, area_id: v, outlet_ids: [] }));
  const toggleOutlet = (id) => setForm(f => ({ ...f, outlet_ids: (f.outlet_ids || []).includes(id) ? f.outlet_ids.filter(x => x !== id) : [...(f.outlet_ids || []), id] }));

  const validate = () => {
    const e = {};
    if (!form.full_name) e.full_name = "Full name is required.";
    if (!form.email) e.email = "Work email is required.";
    if (!form.role_code) e.role_code = "Role is required.";
    if (showCountry && !form.country_id) e.country_id = "Country is required for this role.";
    if (showFranchisee && !form.franchise_id) e.franchise_id = "Franchisee is required for this role.";
    if (areaRequired && !form.area_id) e.area_id = "Area is required for Area Managers.";
    if (showOutlets && (!form.outlet_ids || form.outlet_ids.length === 0)) e.outlet_ids = "At least one outlet is required.";
    setErrors(e); return Object.keys(e).length === 0;
  };
  const save = () => { if (!validate()) return; setSavedToast(true); setTimeout(() => setSavedToast(false), 2200); };

  const TopBottomActions = (
    <>
      <Btn variant="secondary" onClick={() => _go(backRoute)}>Cancel</Btn>
      <Btn variant="primary" icon={<LIcon name="Check" size={16}/>} onClick={save}>{isNew ? "Invite user" : "Save changes"}</Btn>
    </>
  );

  return (
    <div className="page-inner">
      <PageHead title={isNew ? "New user" : `${form.full_name} · User`}
        actions={<div className="row" style={{ gap: 8 }}>{TopBottomActions}</div>}/>

      <FormSection title="Identity" description="Name and contact. Email is the login identifier.">
        <div className="row" style={{ gap: 16 }}>
          <div className="grow">
            <Field label="Full name" required error={errors.full_name}>
              <Input value={form.full_name} onChange={v => set("full_name", v)} placeholder="First Last" autoComplete="name"/>
            </Field>
          </div>
          <div className="grow">
            <Field label="Work email" required error={errors.email}>
              <Input type="email" value={form.email} onChange={v => set("email", v)} placeholder="user@company.com" autoComplete="email"/>
            </Field>
          </div>
        </div>
        <Field label="Phone">
          <Input type="tel" value={form.phone} onChange={v => set("phone", v)} placeholder="+63 917 …" autoComplete="tel"/>
        </Field>
      </FormSection>

      <FormSection title="Role & scope" description="Role sets the data boundary. Scope fields below adapt to the selection.">
        <Field label="Role" required error={errors.role_code} hint={isSelfEdit ? "You can't change your own role." : undefined}>
          <Select value={form.role_code} onChange={onChangeRole} placeholder="Choose role" disabled={isSelfEdit}>
            {roleOpts.map(c => <option key={c} value={c}>{c} — {ROLE_LABEL[c]}</option>)}
          </Select>
        </Field>
        {form.role_code === "GA" && (
          <InlineAlert kind="warn">
            <strong>Super-user.</strong> Global Admins have full platform authority and every action is recorded in the audit trail. Only HQ staff should hold this role.
          </InlineAlert>
        )}
        {showCountry && (
          <Field label="Country" required error={errors.country_id}>
            <Select value={form.country_id} onChange={onChangeCountry} placeholder="Choose country" disabled={role === "CM"}>
              {SB2_COUNTRIES.filter(c => c.status === "active" || c.status === "draft").map(c => <option key={c.country_id} value={c.country_id}>{c.flag} {c.country_name}</option>)}
            </Select>
          </Field>
        )}
        {showFranchisee && (
          <Field label="Franchisee" required error={errors.franchise_id} hint={form.country_id ? undefined : "Choose country first."}>
            <Select value={form.franchise_id} onChange={onChangeFranchisee} placeholder={form.country_id ? "Choose franchisee" : "(choose country first)"}>
              {franchiseeOptions.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
            </Select>
          </Field>
        )}
        {showArea && (
          <Field label="Area" required={areaRequired} error={errors.area_id} hint={form.franchise_id ? undefined : "Choose franchisee first."}>
            <Select value={form.area_id} onChange={onChangeArea} placeholder={form.franchise_id ? "Choose area" : "(choose franchisee first)"}>
              {areaOptions.map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
            </Select>
          </Field>
        )}
        {showOutlets && (
          <Field label="Outlets" required error={errors.outlet_ids} hint="Outlet Managers may manage one or more outlets within their area.">
            <div style={{ display: "flex", flexDirection: "column", gap: 2, padding: 4, border: "1.5px solid var(--line)", borderRadius: "var(--r)" }}>
              {outletOptions.length === 0 && <span style={{ ...T_MUTED, padding: 8 }}>Choose country / franchisee / area first.</span>}
              {outletOptions.map(o => (
                <CheckboxRow key={o.id} checked={(form.outlet_ids || []).includes(o.id)} onChange={() => toggleOutlet(o.id)} label={`${o.id} · ${o.name}`}/>
              ))}
            </div>
          </Field>
        )}
      </FormSection>

      <FormSection title="Account" description="Account language and state.">
        <div className="row" style={{ gap: 16 }}>
          <div className="grow">
            <Field label="Language">
              <Select value={form.language_pref} onChange={v => set("language_pref", v)} placeholder="Choose language">
                {LANG_OPTIONS.map(l => <option key={l.value} value={l.value}>{l.label}</option>)}
              </Select>
            </Field>
          </div>
          <div className="grow">
            <Field label="Status">
              <Select value={form.status} onChange={onChangeStatus} placeholder="Status">
                {STATUS_OPTS.map(s => <option key={s.value} value={s.value}>{s.label}</option>)}
              </Select>
            </Field>
          </div>
        </div>
      </FormSection>

      <ConfirmModal
        open={!!statusConfirm}
        destructive
        title={statusConfirm ? `Change status to ${statusConfirm.next}?` : ""}
        body={statusConfirm && (
          statusConfirm.next === "locked"
            ? <>Locking this user blocks sign-in attempts until reactivated.</>
            : statusConfirm.next === "inactive"
            ? <>Deactivating blocks sign-in. A higher-tier manager can reactivate this user later.</>
            : <>Updating this user's status.</>
        )}
        confirmLabel={statusConfirm ? `Set ${statusConfirm.next}` : "Confirm"}
        onCancel={() => setStatusConfirm(null)}
        onConfirm={() => { set("status", statusConfirm.next); setStatusConfirm(null); }}
      />

      <div className="row jc-e" style={{ gap: 8 }}>{TopBottomActions}</div>

      <Toast message="Saved" show={savedToast}/>
    </div>
  );
}

/**
 * @spec GA-052 — User detail (full-page, Network section). Read-only + Edit.
 * GA/CM/AM open from a list row; OM (no list) lands on their own record, titled "Me".
 */
function UserDetail({ role = "GA" }) {
  if (!["GA", "CM", "AM", "OM"].includes(role)) return _notAuthorised;
  const payloadId = typeof window !== "undefined" && window.__sixhands_route_payload ? window.__sixhands_route_payload.id : null;
  const selfId = SELF_ID[role];
  const u = SB3_USERS.find(x => x.id === (payloadId || selfId)) || SB3_USERS.find(x => x.id === selfId);
  const [confirm, setConfirm] = useState(null);
  const [toast, setToast] = useState(null);
  const fireToast = (m) => { setToast(m); setTimeout(() => setToast(null), 2400); };
  if (!u) return _notAuthorised;

  const isSelf = u.id === selfId;
  const isOM = role === "OM";
  const roleLc = role.toLowerCase();
  const canEdit = !isOM && !isSelf;                 // GA/CM/AM edit a managed user; never self/role; OM never edits
  const canSeeActivity = role === "GA";             // Activity/Audit is GA-only
  const isInvited = u.status === "invited";
  const outletsManaged = u.outlet_ids || [];
  const outletFilter = { filter: { outlet_id: outletsManaged[0], label: u.full_name } };

  return (
    <div className="page-inner">
      <PageHead
        back={isOM ? undefined : { label: "Users", onClick: () => _go(`${roleLc}-users`) }}
        title={isSelf ? "Me" : `${u.full_name} · User`}
        description={isSelf ? "Your profile, scope and access." : "Staff profile, scope and access."}
        status={<StatusPill status={u.status} kind="user"/>}
        actions={canEdit ? (
          <div className="row" style={{ gap: 8 }}>
            {isInvited ? (
              <>
                <Btn variant="danger" onClick={() => setConfirm(true)}>Revoke invite</Btn>
                <Btn variant="secondary" onClick={() => fireToast(`Invite email re-sent to ${u.email}.`)}>Resend invite</Btn>
              </>
            ) : (
              <Btn variant="secondary" onClick={() => fireToast(`Password-reset email sent to ${u.email}.`)}>Send password reset</Btn>
            )}
            <Btn variant="primary" onClick={() => _go(`${roleLc}-user-edit`, { id: u.id })}>Edit</Btn>
          </div>
        ) : undefined}
      />

      <FormSection title="Identity">
        <KeyValueGrid columns={2} items={[
          { label: "Name", value: u.full_name },
          { label: "Email", value: _emailLink(u.email) },
          { label: "Phone", value: (isOM && !isSelf) ? <span style={{ ...T_MUTED }}>Hidden</span> : _phoneLink(u.phone) },
        ]}/>
      </FormSection>

      <FormSection title="Role & scope">
        <KeyValueGrid columns={2} items={[
          { label: "Role", value: <RoleBadge role={ROLE_CODE[u.role_type]}/> },
          { label: "Country", value: u.country_id ? _countryLabel(u.country_id) : "— (global) —" },
          ...(u.franchise_id ? [{ label: "Franchisee", value: _franchiseeLabel(u.franchise_id) }] : []),
          ...(u.role_type === "area_manager" ? [{ label: "Area", value: _areaLabel(u.area_id) }] : []),
          ...(u.role_type === "outlet_manager" ? [{ label: "Outlets", value: outletsManaged.join(", ") || "—", mono: true }] : []),
        ]}/>
      </FormSection>

      <FormSection title="Account">
        <KeyValueGrid columns={2} items={[
          { label: "Status", value: <StatusPill status={u.status} kind="user"/> },
          { label: "Language", value: LANG_LABEL[u.language_pref] || "—" },
          { label: "Last login", value: _fmtDateTime(u.last_login, u.country_id || "SG") },
          { label: "Two-factor", value: u.mfa_enabled ? "Enrolled" : "Not enrolled" },
        ]}/>
      </FormSection>

      {(canSeeActivity || (u.role_type === "outlet_manager" && outletsManaged.length > 0)) && (
        <FormSection title="Related" description="Open the records linked to this user.">
          <div className="stat-panel stat-panel-snapshot">
            <div className="stat-panel-grid">
              {u.role_type === "outlet_manager" && outletsManaged.length > 0 && (
                <StatCard label="Outlets" value={outletsManaged.length} sub="Outlets this user manages"
                  icon={<LIcon name="Store" size={16}/>}
                  onClick={() => _go(`${roleLc}-outlets`, outletFilter)}/>
              )}
              {canSeeActivity && (
                <StatCard label="Audit Log" value="View" sub="This user's audit entries"
                  icon={<LIcon name="ScrollText" size={16}/>}
                  onClick={() => _go("ga-audit", { entity_type: "users", entity_id: u.id })}/>
              )}
            </div>
          </div>
        </FormSection>
      )}

      {canSeeActivity && (
        <FormSection title="Recent activity">
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            {[
              { ts: "Today 09:02", role: "GA", actor: "Ju Hu", action: "user.login.ok", entity_id: u.id, status: "ok" },
              { ts: "Yesterday", role: "GA", actor: "Tricia Tan", action: "user.mfa.enrolled", entity_id: u.id, status: "ok" },
              { ts: "3d ago", role: "GA", actor: "Ju Hu", action: "user.scope.updated", entity_id: u.id, status: "ok" },
              { ts: "8d ago", role: "GA", actor: "Ju Hu", action: "user.invited", entity_id: u.id, status: "info" },
            ].map((e, i) => <Card key={i} padding={12}><AuditRow entry={e}/></Card>)}
          </div>
          <Btn variant="ghost" size="sm" onClick={() => _go("ga-audit", { entity_type: "users", entity_id: u.id })}>View full audit trail</Btn>
        </FormSection>
      )}

      <ConfirmModal
        open={!!confirm}
        destructive
        title="Revoke invite?"
        body={<>This permanently deletes the never-accepted invite stub for <strong>{u.full_name}</strong>. The link stops working immediately and cannot be undone.</>}
        confirmLabel="Revoke invite"
        onCancel={() => setConfirm(null)}
        onConfirm={() => { setConfirm(null); setToast("Invite revoked."); setTimeout(() => _go(`${roleLc}-users`), 700); }}
      />
      <Toast message={toast || ""} show={!!toast}/>
    </div>
  );
}

Object.assign(window, { UsersList, UserEditor, UserDetail });
})();
