/* Settings — seven registered screens in one namespace, reached from the account
   menu, with the within-destination level as a tab strip derived from the route
   table (§5, §6.3). Never a third navigation column.

   Commit model: every screen here is STAGED. Nothing applies until Save, Save
   and Cancel are disabled until the form is dirty, checkboxes are submitted with
   the form rather than applying themselves, and errors render on validation
   rather than on first paint (§3).

   Each screen has a real effect, stated on the screen: bed counts are the
   denominator of every occupancy figure, the role matrix decides which actions
   appear across the product, the write-off threshold decides whether a balance
   can be written off here or has to go for approval, and an integration that is
   off is not offered in the import dialog. */
(() => {
  const {
    Button, Badge, Input, Select, Checkbox, Field, FieldLabel, FieldDescription, FieldError, Alert, AlertTitle, AlertDescription,
    Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
  } = window.OriginDesignSystem_7c998b;

  function SettingsTabs({ path, onNavigate }) {
    return (
      <div className="hd-tabs" role="tablist" aria-label="Settings">
        {HD_SETTINGS_NAV.map((route) => (
          <button type="button" key={route.path} className="hd-tab" role="tab"
            aria-current={route.path === path ? "page" : undefined} aria-selected={route.path === path}
            onClick={() => onNavigate(route.path)}>{route.label}</button>
        ))}
      </div>
    );
  }

  /* Every settings screen is the same shape: header, the tab strip, the form in
     cards, then one sticky staged bar. */
  function SettingsPage({ path, title, sub, onNavigate, dirty, note, label, onCancel, onSave, children }) {
    return (
      <>
        <PageHeader path={path} title={title} sub={sub} />
        <SettingsTabs path={path} onNavigate={onNavigate} />
        {children}
        <FormBar dirty={dirty} note={note} label={label} onCancel={onCancel} onSave={onSave} />
      </>
    );
  }

  function useStaged(initial) {
    const [values, setValues] = React.useState(initial);
    const [submitted, setSubmitted] = React.useState(false);
    const set = (id, value) => setValues((v) => Object.assign({}, v, { [id]: value }));
    const reset = () => { setValues(initial); setSubmitted(false); };
    const dirty = JSON.stringify(values) !== JSON.stringify(initial);
    return { values: values, set: set, setValues: setValues, submitted: submitted, setSubmitted: setSubmitted, dirty: dirty, reset: reset };
  }

  /* ── Hospital ────────────────────────────────────────────────────────── */
  function HospitalSettingsScreen({ data, hospital, viewer, onNavigate, onSave }) {
    const form = useStaged({ name: hospital.name, trust: hospital.trust, recordsEmail: hospital.recordsEmail, retentionYears: String(hospital.retentionYears), timezone: hospital.timezone });
    const { values, set, submitted } = form;
    const errors = {};
    if (!values.name.trim()) errors.name = "The hospital's name appears on every export.";
    if (!values.trust.trim()) errors.trust = "Name the trust this hospital belongs to.";
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(values.recordsEmail.trim())) errors.recordsEmail = "Enter a valid email address.";
    const years = Number(values.retentionYears);
    if (!(years >= 1 && years <= 30)) errors.retentionYears = "Between 1 and 30 years.";
    const valid = Object.keys(errors).length === 0;

    return (
      <SettingsPage path="/settings" title="Hospital" onNavigate={onNavigate}
        sub={"Applies to everybody at " + hospital.short + " — " + data.staff.length + " people across " + WARDS.length + " wards."}
        dirty={form.dirty} label="Save hospital"
        note={"Saving rewrites the header of every export and the address a rejected import row is sent to."}
        onCancel={form.reset} onSave={() => { form.setSubmitted(true); if (valid) onSave(values); }}>
        <Band title="Identity" note="What the hospital is called on anything that leaves the building.">
          <div className="hd-fields">
            <Field>
              <FieldLabel htmlFor="hd-hs-name">Hospital name</FieldLabel>
              <Input id="hd-hs-name" value={values.name} invalid={submitted && !!errors.name} onChange={(e) => set("name", e.target.value)} />
              {submitted && errors.name ? <FieldError>{errors.name}</FieldError> : <FieldDescription>Written into the header row of every export file.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel htmlFor="hd-hs-trust">Trust</FieldLabel>
              <Input id="hd-hs-trust" value={values.trust} invalid={submitted && !!errors.trust} onChange={(e) => set("trust", e.target.value)} />
              {submitted && errors.trust ? <FieldError>{errors.trust}</FieldError> : <FieldDescription>Shown on the sign-in screen and on invoices.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel>Timezone</FieldLabel>
              <Select aria-label="Timezone" value={values.timezone} onValueChange={(v) => set("timezone", v)}
                items={[{ value: "Europe/London", label: "Europe/London" }, { value: "Europe/Dublin", label: "Europe/Dublin" }, { value: "UTC", label: "UTC" }]} />
              <FieldDescription>Every clock in Ward Desk, including quiet hours and the drug round.</FieldDescription>
            </Field>
          </div>
        </Band>
        <Band title="Records and retention" note="Where records work goes, and how long files are kept.">
          <div className="hd-fields">
            <Field>
              <FieldLabel htmlFor="hd-hs-email">Records office</FieldLabel>
              <Input id="hd-hs-email" value={values.recordsEmail} invalid={submitted && !!errors.recordsEmail} onChange={(e) => set("recordsEmail", e.target.value)} />
              {submitted && errors.recordsEmail ? <FieldError>{errors.recordsEmail}</FieldError> : <FieldDescription>Where a rejected import row is sent for a human to place.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel htmlFor="hd-hs-years">Keep exports for</FieldLabel>
              <Input id="hd-hs-years" value={values.retentionYears} invalid={submitted && !!errors.retentionYears} onChange={(e) => set("retentionYears", e.target.value)} />
              {submitted && errors.retentionYears ? <FieldError>{errors.retentionYears}</FieldError> : <FieldDescription>Years. Files are deleted the day after they age out; {exports_(data.transfers).length} exports are held now.</FieldDescription>}
            </Field>
          </div>
        </Band>
      </SettingsPage>
    );
  }

  /* ── Wards and beds ──────────────────────────────────────────────────── */
  function WardSettingsScreen({ data, now, viewer, onNavigate, onSave }) {
    const form = useStaged({ rows: data.wardBeds.map((w) => ({ ward: w.ward, beds: String(w.beds), day: String(w.establishment.day), late: String(w.establishment.late), night: String(w.establishment.night) })) });
    const { values, submitted } = form;
    const setRow = (ward, key, value) => form.setValues((v) => ({ rows: v.rows.map((r) => (r.ward === ward ? Object.assign({}, r, { [key]: value }) : r)) }));

    const errors = {};
    values.rows.forEach((row) => {
      if (!/^\d+$/.test(row.beds) || Number(row.beds) < 1 || Number(row.beds) > 60) errors[row.ward + "beds"] = true;
      ["day", "late", "night"].forEach((shift) => {
        if (!/^\d+$/.test(row[shift]) || Number(row[shift]) > 12) errors[row.ward + shift] = true;
      });
    });
    const valid = Object.keys(errors).length === 0;
    const totalBeds = values.rows.reduce((n, r) => n + (Number(r.beds) || 0), 0);
    const occupied = onWard(data.admissions).concat(fitForDischarge(data.admissions)).length;
    const shortNow = understaffedShifts(data.rota, data.wardBeds).length;

    return (
      <SettingsPage path="/settings/wards" title="Wards and beds" onNavigate={onNavigate}
        sub="The bed count is the denominator of every occupancy figure in the product; the establishment is what the rota is measured against."
        dirty={form.dirty} label="Save wards"
        note={"Saving moves occupancy on Today, Wards and Reports at once — " + occupied + " occupied beds against " + totalBeds + " as entered."}
        onCancel={form.reset} onSave={() => { form.setSubmitted(true); if (valid) onSave(values.rows); }}>
        {submitted && !valid ? (
          <div className="hd-alertwrap">
            <Alert variant="error">
              <AlertTitle>Some numbers cannot be saved</AlertTitle>
              <AlertDescription>A ward holds between 1 and 60 beds, and a shift's establishment is between 0 and 12 people.</AlertDescription>
            </Alert>
          </div>
        ) : null}
        <Band flush title="Beds and nursing establishment" note="One row per ward. A shift with fewer people rostered than its establishment shows as short on the rota.">
          <TableWrap>
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Ward</TableHead>
                  <TableHead>Beds</TableHead>
                  <TableHead>Day shift</TableHead>
                  <TableHead>Late shift</TableHead>
                  <TableHead>Night shift</TableHead>
                  <TableHead>Occupied now</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {values.rows.map((row) => {
                  const wardOccupied = data.admissions.filter((a) => a.ward === row.ward && (a.status === "on_ward" || a.status === "fit_for_discharge")).length;
                  const over = wardOccupied > (Number(row.beds) || 0);
                  return (
                    <TableRow key={row.ward}>
                      <TableCell>
                        <span className="hd-person__text">
                          <span className="hd-person__name">{wardName(row.ward)}</span>
                          <span className="hd-person__meta">{labelOf(WARDS, row.ward).split(" — ")[1]}</span>
                        </span>
                      </TableCell>
                      {["beds", "day", "late", "night"].map((key) => (
                        <TableCell key={key} style={{ width: "7rem" }}>
                          <Input size="sm" value={row[key]} aria-label={wardName(row.ward) + " " + (key === "beds" ? "beds" : key + " shift establishment")}
                            invalid={submitted && !!errors[row.ward + key]} onChange={(e) => setRow(row.ward, key, e.target.value)} />
                        </TableCell>
                      ))}
                      <TableCell>
                        <span className={over ? "hd-warn" : ""}>{wardOccupied}</span>
                        {over ? <span className="hd-footnote"> — more people than beds</span> : null}
                      </TableCell>
                    </TableRow>
                  );
                })}
              </TableBody>
            </Table>
          </TableWrap>
          <p className="hd-footnote" style={{ paddingInline: "var(--card-pad)", paddingTop: "var(--space-3)" }}>
            {totalBeds} beds entered · {shortNow} {shortNow === 1 ? "shift is" : "shifts are"} short against the establishment saved now ·{" "}
            <button type="button" className="hd-linkbtn" onClick={() => onNavigate("/rota")}>open the rota</button>
          </p>
        </Band>
      </SettingsPage>
    );
  }

  /* ── Roles and permissions ───────────────────────────────────────────── */
  const CAPABILITIES = [
    { id: "verify", label: "Verify medication orders", note: "The first dose cannot be given until somebody with this verifies the order." },
    { id: "move", label: "Move admissions and beds", note: "Assign a bed, mark fit for discharge, discharge, transfer out." },
    { id: "consent", label: "Record consent", note: "A scheduled procedure cannot start without it." },
    { id: "import", label: "Import data", note: "Writes into the patient index." },
    { id: "bill", label: "Bill and chase", note: "Issue an invoice, record a payment, chase a payer." },
  ];

  function RoleSettingsScreen({ data, viewer, onNavigate, onSave }) {
    const form = useStaged({
      rows: STAFF_ROLES.map((role) => ({
        role: role.value,
        verify: !!ROLE_CAN_VERIFY_MEDICATION[role.value],
        move: !!ROLE_CAN_MOVE_ADMISSION[role.value],
        consent: !!ROLE_CAN_RECORD_CONSENT[role.value],
        import: !!ROLE_CAN_IMPORT[role.value],
        bill: !!ROLE_CAN_BILL[role.value],
      })),
    });
    const { values } = form;
    const setCell = (role, cap, on) => form.setValues((v) => ({ rows: v.rows.map((r) => (r.role === role ? Object.assign({}, r, { [cap]: on }) : r)) }));
    const countFor = (cap) => data.staff.filter((s) => {
      const row = values.rows.find((r) => r.role === s.staffRole);
      return row && row[cap] && s.status !== "left" && s.status !== "suspended";
    }).length;
    const orphaned = CAPABILITIES.filter((cap) => countFor(cap.id) === 0);
    const valid = orphaned.length === 0;

    return (
      <SettingsPage path="/settings/roles" title="Roles and permissions" onNavigate={onNavigate}
        sub="These decide which actions appear across the product. An action somebody may not take is shown disabled with the reason, never hidden."
        dirty={form.dirty} label="Save permissions"
        note={"Your own role is " + labelOf(STAFF_ROLES, viewer.staffRole) + ", so saving this can change what you can do."}
        onCancel={form.reset} onSave={() => { form.setSubmitted(true); if (valid) onSave(values.rows); }}>
        {form.submitted && !valid ? (
          <div className="hd-alertwrap">
            <Alert variant="error">
              <AlertTitle>Nobody would be able to do some of this work</AlertTitle>
              <AlertDescription>{orphaned.map((c) => c.label.toLowerCase()).join(", ")} — at least one role that somebody actually holds has to keep each of these.</AlertDescription>
            </Alert>
          </div>
        ) : null}
        <Band flush title="What each role may do" note="Checkboxes here are submitted with the form; nothing changes until you save.">
          <div className="hd-tablewrap">
            <table className="hd-matrix">
              <thead>
                <tr>
                  <th scope="col">Role</th>
                  <th scope="col">People</th>
                  {CAPABILITIES.map((cap) => <th scope="col" key={cap.id} style={{ minWidth: "9rem" }}>{cap.label}</th>)}
                </tr>
              </thead>
              <tbody>
                {values.rows.map((row) => {
                  const people = data.staff.filter((s) => s.staffRole === row.role);
                  return (
                    <tr key={row.role}>
                      <th scope="row" style={{ fontWeight: "var(--font-weight-medium)", color: "var(--foreground)" }}>
                        {labelOf(STAFF_ROLES, row.role)}
                        {row.role === viewer.staffRole ? <Badge size="sm" variant="secondary" style={{ marginInlineStart: "var(--space-2)" }}>Yours</Badge> : null}
                      </th>
                      <td>
                        {people.length
                          ? <button type="button" className="hd-linkbtn" onClick={() => onNavigate("/employees")}>{people.length}</button>
                          : <span className="hd-muted">nobody</span>}
                      </td>
                      {CAPABILITIES.map((cap) => (
                        <td key={cap.id}>
                          <Checkbox checked={!!row[cap.id]} aria-label={labelOf(STAFF_ROLES, row.role) + " — " + cap.label}
                            onCheckedChange={(on) => setCell(row.role, cap.id, on)} />
                        </td>
                      ))}
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </Band>
        <Band tint title="What each permission gates" note="Counted from the roles as entered above, over the people who currently hold them.">
          <div className="hd-split">
            {CAPABILITIES.map((cap) => (
              <div className={"hd-tile" + (countFor(cap.id) ? "" : " hd-tile--tint")} key={cap.id}>
                <span className="hd-tile__label">{cap.label}</span>
                <span className="hd-tile__value">{countFor(cap.id)}</span>
                <span className="hd-footnote">{countFor(cap.id) === 1 ? "person could do this" : "people could do this"} · {cap.note}</span>
              </div>
            ))}
          </div>
        </Band>
      </SettingsPage>
    );
  }

  /* ── Billing ─────────────────────────────────────────────────────────── */
  function BillingSettingsScreen({ data, now, billing, viewer, onNavigate, onSave }) {
    const form = useStaged({
      referencePrefix: billing.referencePrefix,
      defaultPayer: billing.defaultPayer,
      threshold: (billing.writeOffThresholdMinor / 100).toFixed(0),
      approver: billing.approver,
    });
    const { values, set, submitted } = form;
    const errors = {};
    if (!/^[A-Z]{2}-\d{4}$/.test(values.referencePrefix.trim())) errors.referencePrefix = "Two letters, a dash and a year — HG-2026.";
    if (!/^\d+$/.test(values.threshold) || Number(values.threshold) < 100) errors.threshold = "A whole number of pounds, 100 or more.";
    if (!values.approver.trim()) errors.approver = "Name the role that approves a write-off above the threshold.";
    const valid = Object.keys(errors).length === 0;
    const thresholdMinor = Number(values.threshold) * 100;
    const above = data.invoices.filter((i) => INVOICE_FLAGS.billable(i) && invoiceBalance(i, data.payments) > thresholdMinor);

    return (
      <SettingsPage path="/settings/billing" title="Billing" onNavigate={onNavigate}
        sub="How invoices are referenced, who is billed by default, and the point at which writing off a balance stops being a local decision."
        dirty={form.dirty} label="Save billing"
        note={"Payment terms are not set here — they come from each payer's contract."}
        onCancel={form.reset} onSave={() => { form.setSubmitted(true); if (valid) onSave({ referencePrefix: values.referencePrefix.trim(), defaultPayer: values.defaultPayer, writeOffThresholdMinor: thresholdMinor, approver: values.approver.trim() }); }}>
        <Band title="Invoicing">
          <div className="hd-fields">
            <Field>
              <FieldLabel htmlFor="hd-bs-prefix">Reference prefix</FieldLabel>
              <Input id="hd-bs-prefix" value={values.referencePrefix} invalid={submitted && !!errors.referencePrefix} onChange={(e) => set("referencePrefix", e.target.value)} />
              {submitted && errors.referencePrefix ? <FieldError>{errors.referencePrefix}</FieldError> : <FieldDescription>Every invoice raised from now takes this prefix; the {data.invoices.length} that exist keep theirs.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel>Default payer</FieldLabel>
              <Select aria-label="Default payer" value={values.defaultPayer} onValueChange={(v) => set("defaultPayer", v)} items={PAYERS} />
              <FieldDescription>Pre-selected when somebody raises a new invoice.</FieldDescription>
            </Field>
          </div>
        </Band>
        <Band title="Writing off a balance" note="Above the threshold, writing off stops being a local decision and goes for approval instead.">
          <div className="hd-fields">
            <Field>
              <FieldLabel htmlFor="hd-bs-threshold">Threshold in pounds</FieldLabel>
              <Input id="hd-bs-threshold" value={values.threshold} invalid={submitted && !!errors.threshold} onChange={(e) => set("threshold", e.target.value)} />
              {submitted && errors.threshold ? <FieldError>{errors.threshold}</FieldError> : <FieldDescription>{above.length} of the {invoicesBillable(data.invoices).length} issued invoices carry a balance above this.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel htmlFor="hd-bs-approver">Approved by</FieldLabel>
              <Input id="hd-bs-approver" value={values.approver} invalid={submitted && !!errors.approver} onChange={(e) => set("approver", e.target.value)} />
              {submitted && errors.approver ? <FieldError>{errors.approver}</FieldError> : <FieldDescription>Named in the request Ward Desk raises instead of writing the balance off.</FieldDescription>}
            </Field>
          </div>
        </Band>
        <Band tint flush title={"Payment terms (" + data.contracts.length + ")"} note="Read-only here: terms belong to the payer's contract, so they are changed on Contracts and tariffs.">
          <TableWrap>
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Payer</TableHead>
                  <TableHead>Contract</TableHead>
                  <TableHead>Terms</TableHead>
                  <TableHead>Runs to</TableHead>
                  <TableHead>Action</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {data.contracts.map((contract) => (
                  <TableRow key={contract.id}>
                    <TableCell>{labelOf(PAYERS, contract.payer)}</TableCell>
                    <TableCell>{contract.name}</TableCell>
                    <TableCell>{contract.dueDays ? contract.dueDays + " days" : "Before care"}</TableCell>
                    <TableCell>
                      {contract.expiresAt ? <Clock at={contract.expiresAt} now={now} problem={CONTRACT_FLAGS.expiringSoon(contract, now)} /> : <span className="hd-muted">No end date</span>}
                    </TableCell>
                    <TableCell><Button variant="outline" size="sm" onClick={() => onNavigate("/contracts")}>Open contract</Button></TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </TableWrap>
        </Band>
      </SettingsPage>
    );
  }

  /* ── Integrations and feeds ──────────────────────────────────────────── */
  function IntegrationSettingsScreen({ data, now, integrations, viewer, onNavigate, onSave }) {
    const form = useStaged({ rows: integrations.map((row) => Object.assign({}, row)) });
    const { values } = form;
    const setRow = (source, key, on) => form.setValues((v) => ({ rows: v.rows.map((r) => (r.source === source ? Object.assign({}, r, { [key]: on }) : r)) }));
    const enabled = values.rows.filter((r) => r.enabled);

    return (
      <SettingsPage path="/settings/integrations" title="Integrations and feeds" onNavigate={onNavigate}
        sub="Where data comes in from. A feed that is off is not offered when somebody starts an import, and nothing arrives from it."
        dirty={form.dirty} label="Save integrations"
        note={enabled.length + " of " + values.rows.length + " feeds would be available in the import dialog."}
        onCancel={form.reset} onSave={() => onSave(values.rows)}>
        <Band flush title="Feeds" note="Auto-accepting matched rows skips the review step for rows that match a patient on name and date of birth.">
          <TableWrap>
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Feed</TableHead>
                  <TableHead>Format</TableHead>
                  <TableHead>Last run</TableHead>
                  <TableHead>Rows brought in</TableHead>
                  <TableHead>Available</TableHead>
                  <TableHead>Auto-accept matched rows</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {values.rows.map((row) => {
                  const jobs = data.transfers.filter((t) => t.source === row.source);
                  const last = jobs.slice().sort((a, b) => b.startedAt - a.startedAt)[0];
                  const label = labelOf(IMPORT_SOURCES, row.source);
                  return (
                    <TableRow key={row.source}>
                      <TableCell>
                        <span className="hd-person__text">
                          <span className="hd-person__name">{label.split(" — ")[0]}</span>
                          <span className="hd-person__meta">{jobs.length} {jobs.length === 1 ? "job" : "jobs"} run</span>
                        </span>
                      </TableCell>
                      <TableCell>{label.split(" — ")[1] || "CSV"}</TableCell>
                      <TableCell>{last ? <Clock at={last.startedAt} now={now} /> : <span className="hd-muted">Never</span>}</TableCell>
                      <TableCell>
                        {last
                          ? <span className="hd-actions__row"><StatusBadge values={TRANSFER_STATUS} value={last.status} /><span className="hd-footnote">{formatCount(last.accepted)} accepted</span></span>
                          : <span className="hd-muted">—</span>}
                      </TableCell>
                      <TableCell>
                        <Checkbox checked={!!row.enabled} aria-label={label + " available for import"} onCheckedChange={(on) => setRow(row.source, "enabled", on)} />
                      </TableCell>
                      <TableCell>
                        <Checkbox checked={!!row.autoAccept} disabled={!row.enabled} aria-label={label + " auto-accepts matched rows"} onCheckedChange={(on) => setRow(row.source, "autoAccept", on)} />
                      </TableCell>
                    </TableRow>
                  );
                })}
              </TableBody>
            </Table>
          </TableWrap>
        </Band>
        <Band tint title="What is waiting" note="Read from the jobs themselves, not from this form.">
          <div className="hd-split">
            <div className="hd-tile">
              <span className="hd-tile__label">Imports needing review</span>
              <span className="hd-tile__value">{transfersNeedingReview(data.transfers).length}</span>
              <span className="hd-footnote">{transfersNeedingReview(data.transfers).reduce((n, t) => n + (t.pending || 0), 0)} rows to decide · <button type="button" className="hd-linkbtn" onClick={() => onNavigate("/data")}>open them</button></span>
            </div>
            <div className="hd-tile">
              <span className="hd-tile__label">Jobs that failed</span>
              <span className="hd-tile__value">{failedTransfers(data.transfers).length}</span>
              <span className="hd-footnote">{failedTransfers(data.transfers).length ? "nothing was written by them" : "every job finished"}</span>
            </div>
            <div className="hd-tile hd-tile--tint">
              <span className="hd-tile__label">Rows brought in</span>
              <span className="hd-tile__value">{formatCount(rowsAccepted(data.transfers))}</span>
              <span className="hd-footnote">across {imports(data.transfers).length} imports</span>
            </div>
          </div>
        </Band>
      </SettingsPage>
    );
  }

  /* ── Notifications ───────────────────────────────────────────────────── */
  function NotificationSettingsScreen({ data, prefs, hospital, viewer, onNavigate, onSave }) {
    const form = useStaged({ rows: prefs.rows.map((r) => Object.assign({}, r)), from: prefs.quietHours.from, to: prefs.quietHours.to });
    const { values, set, submitted } = form;
    const setRow = (id, channel, on) => form.setValues((v) => Object.assign({}, v, { rows: v.rows.map((r) => (r.id === id ? Object.assign({}, r, { [channel]: on }) : r)) }));
    const errors = {};
    if (!/^\d\d:\d\d$/.test(values.from)) errors.from = "Use HH:MM.";
    if (!/^\d\d:\d\d$/.test(values.to)) errors.to = "Use HH:MM.";
    const valid = Object.keys(errors).length === 0;
    const paged = values.rows.filter((r) => r.pager).length;

    return (
      <SettingsPage path="/settings/notifications" title="Notifications" onNavigate={onNavigate}
        sub="Who is told what, and when they are left alone. In-app notifications always arrive; email and the pager respect quiet hours."
        dirty={form.dirty} label="Save notifications"
        note={paged + " of " + values.rows.length + " events would page somebody."}
        onCancel={form.reset} onSave={() => { form.setSubmitted(true); if (valid) onSave({ rows: values.rows, quietHours: { from: values.from, to: values.to } }); }}>
        <Band title="Events" note="Checkboxes here are submitted with the form; nothing changes until you save.">
          <div className="hd-tablewrap">
            <table className="hd-matrix">
              <thead>
                <tr>
                  <th scope="col">Event</th>
                  <th scope="col">Email</th>
                  <th scope="col">Pager</th>
                  <th scope="col">In Ward Desk</th>
                </tr>
              </thead>
              <tbody>
                {values.rows.map((row) => (
                  <tr key={row.id}>
                    <th scope="row" style={{ fontWeight: "var(--font-weight-normal)", color: "var(--foreground)" }}>{row.label}</th>
                    {["email", "pager", "inApp"].map((channel) => (
                      <td key={channel}>
                        <Checkbox checked={!!row[channel]} aria-label={row.label + " — " + (channel === "inApp" ? "in Ward Desk" : channel)}
                          onCheckedChange={(on) => setRow(row.id, channel, on)} />
                      </td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </Band>
        <Band title="Quiet hours" note={"Nothing is emailed or paged between these times, " + hospital.timezone + "."}>
          <div className="hd-fields">
            <Field>
              <FieldLabel htmlFor="hd-ns-from">From</FieldLabel>
              <Input id="hd-ns-from" value={values.from} invalid={submitted && !!errors.from} onChange={(e) => set("from", e.target.value)} />
              {submitted && errors.from ? <FieldError>{errors.from}</FieldError> : <FieldDescription>24-hour clock.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel htmlFor="hd-ns-to">Until</FieldLabel>
              <Input id="hd-ns-to" value={values.to} invalid={submitted && !!errors.to} onChange={(e) => set("to", e.target.value)} />
              {submitted && errors.to ? <FieldError>{errors.to}</FieldError> : null}
            </Field>
          </div>
        </Band>
      </SettingsPage>
    );
  }

  /* ── Security ────────────────────────────────────────────────────────── */
  function SecuritySettingsScreen({ data, now, security, viewer, onNavigate, onSave }) {
    const form = useStaged({ sessionMinutes: String(security.sessionMinutes), requireTwoFactor: !!security.requireTwoFactor, passwordAgeDays: String(security.passwordAgeDays) });
    const { values, set, submitted } = form;
    const errors = {};
    if (!/^\d+$/.test(values.passwordAgeDays) || Number(values.passwordAgeDays) < 30 || Number(values.passwordAgeDays) > 730) errors.passwordAgeDays = "Between 30 and 730 days.";
    const valid = Object.keys(errors).length === 0;

    return (
      <SettingsPage path="/settings/security" title="Security" onNavigate={onNavigate}
        sub="Ward Desk holds patient data, and a ward station is a shared screen. These settings apply to everybody who signs in."
        dirty={form.dirty} label="Save security"
        note={"A shorter session locks a ward station sooner; " + data.staff.filter((s) => s.status === "active").length + " active accounts are affected."}
        onCancel={form.reset} onSave={() => { form.setSubmitted(true); if (valid) onSave({ sessionMinutes: Number(values.sessionMinutes), requireTwoFactor: values.requireTwoFactor, passwordAgeDays: Number(values.passwordAgeDays) }); }}>
        <Band title="Signing in">
          <div className="hd-fields">
            <Field>
              <FieldLabel>Lock an idle session after</FieldLabel>
              <Select aria-label="Session timeout" value={values.sessionMinutes} onValueChange={(v) => set("sessionMinutes", v)}
                items={[{ value: "15", label: "15 minutes" }, { value: "30", label: "30 minutes" }, { value: "60", label: "60 minutes" }]} />
              <FieldDescription>A ward station left open is the commonest way a record is seen by the wrong person.</FieldDescription>
            </Field>
            <Field>
              <FieldLabel htmlFor="hd-ss-age">Passwords expire after</FieldLabel>
              <Input id="hd-ss-age" value={values.passwordAgeDays} invalid={submitted && !!errors.passwordAgeDays} onChange={(e) => set("passwordAgeDays", e.target.value)} />
              {submitted && errors.passwordAgeDays ? <FieldError>{errors.passwordAgeDays}</FieldError> : <FieldDescription>Days. The reset flow is the same one on the sign-in screen.</FieldDescription>}
            </Field>
            <Field>
              <FieldLabel>Second factor</FieldLabel>
              <span className="hd-person">
                <Checkbox checked={values.requireTwoFactor} aria-label="Require a second factor for everybody" onCheckedChange={(on) => set("requireTwoFactor", on)} />
                <span className="hd-pair__value">Require a second factor for everybody</span>
              </span>
              <FieldDescription>Submitted with this form, like every control on this screen.</FieldDescription>
            </Field>
          </div>
        </Band>
        <Band tint title="What a password must be" note="Read-only: the rules the reset screen validates against, so the two cannot disagree.">
          <div className="hd-checklist">
            {PASSWORD_RULES.map((rule) => (
              <span className="hd-checklist__item" key={rule.id}><Icon name="check" size={14} />{rule.label}</span>
            ))}
          </div>
        </Band>
        <Band flush title={"Your signed-in sessions (" + data.sessions.length + ")"} note="Read from the session store; ending one is done by the trust's identity service, not here.">
          <TableWrap>
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Where</TableHead>
                  <TableHead>Browser</TableHead>
                  <TableHead>Address</TableHead>
                  <TableHead>Last seen</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {data.sessions.map((session) => (
                  <TableRow key={session.id}>
                    <TableCell>
                      <span className="hd-actions__row">
                        {session.device}
                        {session.current ? <Badge size="sm" variant="success">This one</Badge> : null}
                      </span>
                    </TableCell>
                    <TableCell>{session.browser}</TableCell>
                    <TableCell style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)" }}>{session.ip}</TableCell>
                    <TableCell><Clock at={session.lastSeenAt} now={now} /></TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </TableWrap>
        </Band>
      </SettingsPage>
    );
  }

  Object.assign(window, {
    SettingsTabs, SettingsPage, useStaged, HospitalSettingsScreen, WardSettingsScreen, RoleSettingsScreen,
    BillingSettingsScreen, IntegrationSettingsScreen, NotificationSettingsScreen, SecuritySettingsScreen, CAPABILITIES,
  });
})();
