// =================================================================
// pw-screens-op-staff.jsx — Operator · Staff and access
//
// Where a superadmin creates managers and decides what each one can see. Superadmin-only: the API
// gates every /admin/v1/staff route on the destructive capability, which a manager does not hold.
// A manager who could edit assignments could grant itself the scope it was denied, which would make
// every other check decorative.
//
// The screen's one real job beyond CRUD is making "empty means all" impossible to misread. An empty
// assignment list means *every* publisher, so a blank cell would be the most dangerous thing this
// page could render. Scope is therefore always spelled out in words, and removing the last
// assignment warns that it widens access rather than narrowing it.
// =================================================================

const STAFF_ROLES = [
  { value: "manager", label: "Manager" },
  { value: "superadmin", label: "Superadmin" },
];

const scopeSummary = (axis, total, noun) => {
  if (!axis) return "—";
  if (axis.unscoped) return `All ${noun}`;
  if (!axis.ids.length) return "None";
  return `${axis.ids.length} of ${total} ${noun}`;
};

const StaffEditor = ({ staff, data, onClose, onSaved, notify }) => {
  const publishers = data.publishers || [];
  const advertisers = data.advertisers || [];
  const [pubIds, setPubIds] = React.useState([]);
  const [advIds, setAdvIds] = React.useState([]);
  const [busy, setBusy] = React.useState(false);

  // Re-seed whenever a different staff member is opened. This component stays mounted with
  // `staff === null` between openings, so `useState(staff?.…)` captures the null and never updates:
  // the editor would open with nothing ticked no matter what the person is actually assigned, and
  // saving would wipe their scope — which under "empty means all" *widens* their access to every
  // tenant. The dangerous direction, silently. Found by opening the editor on a scoped manager and
  // counting the ticks.
  React.useEffect(() => {
    setPubIds(staff?.scope?.publishers?.ids ? [...staff.scope.publishers.ids] : []);
    setAdvIds(staff?.scope?.advertisers?.ids ? [...staff.scope.advertisers.ids] : []);
  }, [staff && staff.staff_id]);

  if (!staff) return null;

  const toggle = (list, setList, id) =>
    setList(list.includes(id) ? list.filter((x) => x !== id) : list.concat(id));

  const save = async () => {
    setBusy(true);
    try {
      await window.__api.write("adminSetStaffAssignments", null, {
        params: { staff_id: staff.staff_id },
        body: { publisher_ids: pubIds, advertiser_ids: advIds },
        ok: "Access updated",
        err: "Couldn’t update access",
      });
      onSaved();
      onClose();
    } catch (e) {
      notify((e && e.message) || "Couldn’t update access.", "bad");
    } finally {
      setBusy(false);
    }
  };

  const Axis = ({ title, noun, rows, idKey, selected, setSelected }) => (
    <div style={{ marginTop: 14 }}>
      <div style={{ fontSize: 12.5, color: "var(--muted)", marginBottom: 6 }}>{title}</div>
      {/* The warning has to sit where the choice is made. Somebody clearing the last checkbox to
          "take access away" is the exact mistake this rule invites. */}
      {selected.length === 0 && (
        <div style={{ fontSize: 13, lineHeight: 1.5, color: "var(--warn)", background: "var(--warn-soft)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 11px", marginBottom: 8 }}>
          Nothing selected means <strong>every {noun.replace(/s$/, "")}</strong>, not none. Selecting
          one or more restricts this person to those.
        </div>
      )}
      <div style={{ maxHeight: 168, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 8 }}>
        {rows.length === 0 ? (
          <div style={{ padding: 12, fontSize: 13, color: "var(--muted)" }}>No {noun} yet.</div>
        ) : rows.map((r) => (
          <label key={r[idKey]} style={{ display: "flex", gap: 9, alignItems: "center", padding: "7px 11px", borderBottom: "1px solid var(--line-2)", fontSize: 13.5, cursor: "pointer" }}>
            <input type="checkbox" checked={selected.includes(r[idKey])} onChange={() => toggle(selected, setSelected, r[idKey])} />
            <span style={{ color: "var(--ink-2)" }}>{r.name}</span>
          </label>
        ))}
      </div>
    </div>
  );

  return (
    <Modal open={!!staff} onClose={onClose} title={`Access · ${staff.name}`} width={560}
      footer={<>
        <Btn kind="secondary" onClick={onClose}>Cancel</Btn>
        <Btn kind="primary" disabled={busy} onClick={save}>{busy ? "Saving…" : "Save access"}</Btn>
      </>}>
      {staff.role === "superadmin" ? (
        <div style={{ fontSize: 13.5, lineHeight: 1.6, color: "var(--ink-2)" }}>
          A superadmin is unscoped by definition — assignments are ignored for this account. Change
          the role to Manager first if this person should be restricted.
        </div>
      ) : (
        <>
          <Axis title="Publishers" noun="publishers" rows={publishers} idKey="publisher_id" selected={pubIds} setSelected={setPubIds} />
          <Axis title="Advertisers" noun="advertisers" rows={advertisers} idKey="advertiser_id" selected={advIds} setSelected={setAdvIds} />
        </>
      )}
    </Modal>
  );
};

const StaffCreateModal = ({ open, onClose, onCreated, notify }) => {
  const [f, setF] = React.useState({ name: "", email: "", role: "manager", password: "" });
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => { if (open) setF({ name: "", email: "", role: "manager", password: "" }); }, [open]);
  const set = (patch) => setF((prev) => ({ ...prev, ...patch }));

  // Mirrors StaffCreateRequest so the operator sees the rule before a round trip, not instead of
  // the server applying it.
  const emailOk = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(f.email.trim());
  const canSave = f.name.trim() && emailOk && f.password.length >= 12 && !busy;

  const save = async () => {
    if (!canSave) return;
    setBusy(true);
    try {
      await window.__api.write("adminCreateStaff", null, {
        body: { name: f.name.trim(), email: f.email.trim(), role: f.role, password: f.password },
        ok: `Created ${f.name.trim()}`,
        err: "Couldn’t create that account",
      });
      onCreated();
      onClose();
    } catch (e) {
      notify((e && e.message) || "Couldn’t create that account.", "bad");
    } finally {
      setBusy(false);
    }
  };

  return (
    <Modal open={open} onClose={onClose} title="Add staff" width={480}
      footer={<>
        <Btn kind="secondary" onClick={onClose}>Cancel</Btn>
        <Btn kind="primary" disabled={!canSave} onClick={save}>{busy ? "Creating…" : "Create account"}</Btn>
      </>}>
      <FormField label="Name"><Input value={f.name} onChange={(e) => set({ name: e.target.value })} placeholder="Jane Doe" /></FormField>
      <FormField label="Email"><Input type="email" value={f.email} onChange={(e) => set({ email: e.target.value })} placeholder="jane@krafts.ai" /></FormField>
      <FormField label="Role">
        <SegmentedControl value={f.role} onChange={(v) => set({ role: v })} options={STAFF_ROLES.map((r) => ({ label: r.label, value: r.value }))} />
      </FormField>
      <FormField label="Password" hint="At least 12 characters. Shown once here and never again — the API stores only a hash.">
        <Input type="password" value={f.password} onChange={(e) => set({ password: e.target.value })} />
      </FormField>
      {f.role === "superadmin" && (
        <div style={{ fontSize: 13, lineHeight: 1.55, color: "var(--warn)", background: "var(--warn-soft)", border: "1px solid var(--line)", borderRadius: 9, padding: "9px 12px" }}>
          A superadmin has unrestricted access to every tenant and can delete and revoke. Only the
          two operations a manager cannot perform separate the roles.
        </div>
      )}
      <div style={{ marginTop: 10, fontSize: 12.5, lineHeight: 1.5, color: "var(--muted)" }}>
        A new manager starts with <strong>no assignments</strong>, which means <strong>every</strong>
        publisher and advertiser. Set their access straight after creating them.
      </div>
    </Modal>
  );
};

const OpStaffScreen = ({ data, setData }) => {
  const notify = (msg, tone) => (window.__toast ? window.__toast(msg, tone) : null);
  const [creating, setCreating] = React.useState(false);
  const [editing, setEditing] = React.useState(null);

  const reload = React.useCallback(() => (
    window.__api.read("adminListStaff")
      .then((r) => setData((d) => ({ ...d, staff: r })))
      .catch(() => notify("Could not load staff.", "bad"))
  ), [setData]);

  React.useEffect(() => { reload(); }, [reload]);

  const payload = (data && data.staff) || null;
  const rows = (payload && payload.staff) || [];
  const publisherCount = (data.publishers || []).length;
  const advertiserCount = (data.advertisers || []).length;

  const setStatus = async (row, status) => {
    try {
      await window.__api.write("adminUpdateStaff", null, {
        params: { staff_id: row.staff_id },
        body: { status },
        ok: status === "active" ? "Reinstated" : "Suspended",
        err: "Couldn’t change that account",
      });
      reload();
    } catch (e) { notify((e && e.message) || "Couldn’t change that account.", "bad"); }
  };

  return (
    <Screen>
      <PageHead icon={IconUsers} title="Staff and access"
        subtitle="Who can sign in to the platform, and which tenants each of them can see."
        actions={<Btn kind="primary" size="sm" onClick={() => setCreating(true)}>Add staff</Btn>} />

      <Card title="Accounts" subtitle="Select a row to change what it can see." padded={false}>
        <DataTable
          rows={rows}
          keyField="staff_id"
          rowsPerPage={25}
          defaultSort={{ key: "created_at", dir: "asc" }}
          empty="No staff accounts yet."
          onRowClick={(r) => setEditing(r)}
          columns={[
            { key: "name", label: "Name", render: (r) => <strong style={{ color: "var(--ink)" }}>{r.name}</strong> },
            { key: "email", label: "Email" },
            { key: "role", label: "Role", render: (r) => <Pill tone={r.role === "superadmin" ? "warn" : "accent"}>{r.role}</Pill> },
            {
              key: "scope_publishers", label: "Publishers",
              // Never a blank cell. "All publishers" and "none selected" look identical in the data
              // and mean opposite things, so the words do the work.
              render: (r) => <span style={{ fontSize: 13 }}>{scopeSummary(r.scope?.publishers, publisherCount, "publishers")}</span>,
            },
            {
              key: "scope_advertisers", label: "Advertisers",
              render: (r) => <span style={{ fontSize: 13 }}>{scopeSummary(r.scope?.advertisers, advertiserCount, "advertisers")}</span>,
            },
            { key: "status", label: "Status", render: (r) => <StatusPill status={r.status} /> },
            {
              key: "last_login_at", label: "Last sign-in",
              render: (r) => <span style={{ fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--muted)" }}>{r.last_login_at ? fmt.dateTime(r.last_login_at) : "never"}</span>,
            },
            {
              key: "actions", label: "",
              render: (r) => (
                <Btn kind="secondary" size="sm"
                  onClick={(e) => { e.stopPropagation(); setStatus(r, r.status === "active" ? "suspended" : "active"); }}>
                  {r.status === "active" ? "Suspend" : "Reinstate"}
                </Btn>
              ),
            },
          ]}
        />
      </Card>

      <StaffCreateModal open={creating} onClose={() => setCreating(false)} onCreated={reload} notify={notify} />
      <StaffEditor staff={editing} data={data} onClose={() => setEditing(null)} onSaved={reload} notify={notify} />
    </Screen>
  );
};

Object.assign(window, { OpStaffScreen });
