/* Recruitment Desk — the product's own parts. Nothing here holds data or
   assigns a status tone: StatusBadge does the tone, metrics.js does the
   arithmetic, enums.js names the transitions. Reported in the README as local
   additions (AGENTS §9): the stepper, the note thread and its reaction bar, the
   follower stack, the scorecard matrix, the stage strip and the board card. */
(() => {
  const {
    Button, Input, Textarea, Select, Checkbox, RadioGroup, Switch,
    Field, FieldLabel, FieldDescription, FieldError,
    Card, CardPanel, Badge, Avatar, MenuItem, MenuGroupLabel, Progress, Separator,
    Alert, AlertTitle, AlertDescription,
    Dialog, DialogPopup, DialogHeader, DialogTitle, DialogDescription, DialogPanel, DialogFooter,
    AlertDialog, AlertDialogPopup, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter,
    Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent,
  } = window.OriginDesignSystem_7c998b;

  /* ── Figures ──────────────────────────────────────────────────────────── */

  /* A figure is checkable three ways (AGENTS §2): the rows sit beside it, it
     drills through to them, or it names a predicate. `onDrill` is the second. */
  function StatTile({ label, value, qualifier, onDrill, drillLabel }) {
    return (
      <Card>
        <div className="rd-tile">
          <span className="rd-tile__label">{label}</span>
          {onDrill
            ? <button type="button" className="rd-tile__value rd-link" onClick={onDrill} aria-label={drillLabel || ("Show the " + label.toLowerCase())}>{value}</button>
            : <span className="rd-tile__value">{value}</span>}
          {qualifier ? <span className="rd-tile__qual">{qualifier}</span> : null}
        </div>
      </Card>
    );
  }

  function Bar({ ratio, tone }) {
    return (
      <span className={"rd-bar" + (tone ? " rd-bar--" + tone : "")}>
        <span className="rd-bar__fill" style={{ width: Math.max(0, Math.min(1, ratio)) * 100 + "%" }} />
      </span>
    );
  }

  /* A clock cell. Whether "past" is a problem is a property of the column,
     declared by the caller once — never guessed per row. */
  function Clock({ at, now, problem, absolute }) {
    if (!at) return <span className="rd-muted">Never</span>;
    const late = problem && at < now;
    const soon = problem && !late && at - now < 3 * DAY;
    return (
      <span className={"rd-clock" + (late ? " rd-clock--danger" : soon ? " rd-clock--warning" : "")} title={formatAbsolute(at)}>
        {absolute ? formatDay(at) : formatWhen(at, now)}
      </span>
    );
  }

  function KeyValues({ items }) {
    return (
      <div className="rd-kv">
        {items.map((item) => (
          <span className="rd-part" key={item.label}>
            <span className="rd-kv__label">{item.label}</span>
            <span className="rd-kv__value">{item.value}</span>
            {item.note ? <span className="rd-foot">{item.note}</span> : null}
          </span>
        ))}
      </div>
    );
  }

  /* ── People ───────────────────────────────────────────────────────────── */

  function StaffCell({ person, meta, size = 26 }) {
    if (!person) return <span className="rd-muted">Unassigned</span>;
    return (
      <span className="rd-person">
        <Avatar size={size} src={person.photo} alt="" fallback={person.initials} />
        <span className="rd-record">
          <span className="rd-record__name">{person.name}</span>
          <span className="rd-record__meta">{meta || person.title}</span>
        </span>
      </span>
    );
  }

  /* The candidate, with their face, at every scale. The name is the link to the
     one application route; nothing else in the cell navigates. */
  function CandidateCell({ application, meta, onNavigate, size = 32 }) {
    const c = application.candidate;
    return (
      <span className="rd-person">
        <Avatar size={size} src={c.photo} alt="" fallback={c.initials} />
        <span className="rd-record">
          {onNavigate
            ? <button type="button" className="rd-link rd-record__name" onClick={() => onNavigate("/applications/" + application.id)}>{c.name}</button>
            : <span className="rd-record__name">{c.name}</span>}
          <span className="rd-record__meta">{meta === undefined ? c.headline : meta}</span>
        </span>
      </span>
    );
  }

  /* Everybody following this application — the owner, the watchers and anybody
     who has written on it. One list, so the faces and the count agree. */
  function Followers({ people, size = 24, label = "Following this application" }) {
    if (!people.length) return null;
    return (
      <span className="rd-watch">
        <span className="rd-faces" aria-hidden="true">
          {people.slice(0, 5).map((person) => <Avatar key={person.id} size={size} src={person.photo} alt="" fallback={person.initials} />)}
        </span>
        <span className="rd-foot">
          {label}: {people.slice(0, 3).map(firstNameOf).join(", ")}
          {people.length > 3 ? " and " + plural(people.length - 3, "other", "others") : ""}
        </span>
      </span>
    );
  }

  /* ── The stepper: this product's signature ────────────────────────────────
     The same seven steps, in APPLICATION_PATH order, wherever an application
     appears. A step's state comes from the record (stepState) and a completed
     step names the sub-record that completed it (stepEvidence) — never a claim
     the fixture cannot support. */
  function Stepper({ application, data, now, mini }) {
    const currentLabel = statusValue(APPLICATION_STAGE, application.stage).label;
    const stopped = STAGE_SETTLED.indexOf(application.stage) > -1 && application.stage !== "hired";
    return (
      <div className={"rd-stepper" + (mini ? " rd-stepper--mini" : "")} role="group"
        aria-label={stopped ? currentLabel + " at " + statusValue(APPLICATION_STAGE, application.lastLiveStage || "applied").label.toLowerCase() : "Stage: " + currentLabel}>
        {APPLICATION_PATH.map((stage, i) => {
          const state = stepState(application, stage);
          const value = statusValue(APPLICATION_STAGE, stage);
          const over = state === "current" ? stageOverBy(application, now) : 0;
          const evidence = stepEvidence(application, stage, data, now);
          const meta = state === "current" && !stopped
            ? plural(daysInStage(application, now), "day", "days") + " in this step"
              + (stageAllowance(application) ? " of " + stageAllowance(application) : "")
              + (over > 0 ? " — " + plural(over, "day", "days") + " over" : "")
            : state === "done" ? evidence
              : state === "stopped" ? (application.stage === "declined" ? "Declined here " + formatDayShort(application.decidedAt) : "Withdrew here " + formatDayShort(application.decidedAt))
                : STAGE_MEANS[stage];
          const pip = (
            <span className="rd-step__pip">{state === "done" ? <Icon name="check" size={12} /> : state === "stopped" ? <Icon name="x" size={12} /> : i + 1}</span>
          );
          return (
            <div className="rd-step" data-state={state} key={stage}>
              <span className="rd-step__track">
                <span className="rd-step__line" data-done={state === "done"} />
                {mini ? <FloatingTip side="bottom" label={value.label + (meta ? " — " + meta : "")}>{pip}</FloatingTip> : pip}
              </span>
              {mini ? null : (
                <span className="rd-step__text">
                  <span className="rd-step__label">{value.label}</span>
                  {meta ? <span className="rd-step__meta">{meta}</span> : null}
                </span>
              )}
            </div>
          );
        })}
      </div>
    );
  }

  /* One line under the stepper saying what the step is waiting on and who owes
     it — recession is named, never implied (AGENTS §5.4). */
  function StageNote({ application, data, now }) {
    const stage = application.stage;
    const owner = staffById(data.staff, application.ownerId);
    if (stage === "declined" || stage === "withdrawn") {
      return (
        <p className="rd-stage-note">
          <Icon name="circle-slash" size={14} />
          {stage === "declined"
            ? labelOf(DECLINE_REASONS, application.declineReason) + " · decided " + formatDay(application.decidedAt) + " by " + firstNameOf(staffById(data.staff, application.decidedById))
            : labelOf(WITHDRAW_REASONS, application.withdrawReason) + " · " + formatDay(application.decidedAt)}
          {application.wouldReconsider ? " · in the talent pool" : ""}
        </p>
      );
    }
    if (stage === "hired") {
      return <p className="rd-stage-note"><Icon name="circle-check" size={14} />Accepted {formatDay(application.offer.acceptedAt)} · starting {formatDay(application.offer.startAt)} · handed to onboarding</p>;
    }
    const waiting = APPLICATION_FLAGS.awaitsUs(application)
      ? "Waiting on the desk" + (owner ? " — " + owner.name : "")
      : stage === "offer" ? "Waiting on " + application.candidate.name.split(" ")[0] + " until " + formatDay(application.offer.expiresAt)
        : "Waiting on referees and the DBS service";
    return (
      <p className="rd-stage-note">
        <Icon name={APPLICATION_FLAGS.awaitsUs(application) ? "hand" : "clock" } size={14} />
        {waiting} · {STAGE_MEANS[stage]}
      </p>
    );
  }

  /* ── Scorecards: two cards side by side on one grid, header and rows on the
     same tracks, so a wrapped area label cannot shift a column. ──────────── */
  function Pips({ value, max = 5 }) {
    return (
      <span className="rd-pips" aria-hidden="true">
        {Array.from({ length: max }).map((_, i) => <span className="rd-pip" key={i} data-on={i < value} />)}
      </span>
    );
  }

  function ScoreMatrix({ application, data }) {
    const score = scoresOf(application);
    if (!score.count) return null;
    const columns = "minmax(0,1.2fr) " + score.cards.map(() => "minmax(0,1fr)").join(" ");
    return (
      <div className="rd-matrix" style={{ gridTemplateColumns: columns }}>
        <span className="rd-matrix__head">Area</span>
        {score.cards.map((card) => (
          <span className="rd-matrix__head" key={card.byId}>{firstNameOf(staffById(data.staff, card.byId))} · {formatDayShort(card.at)}</span>
        ))}
        {SCORE_AREAS.map((area) => (
          <React.Fragment key={area.value}>
            <span>{area.label}</span>
            {score.cards.map((card) => (
              <span className="rd-matrix__cell" key={card.byId + area.value}><Pips value={card.scores[area.value]} />{card.scores[area.value]}</span>
            ))}
          </React.Fragment>
        ))}
        <span className="rd-matrix__head">Average</span>
        {score.cards.map((card) => <span className="rd-matrix__cell" key={"avg" + card.byId}>{score.cardAverage(card).toFixed(2)}</span>)}
      </div>
    );
  }

  /* ── The thread: how the desk actually talks ──────────────────────────────
     A mention pulls somebody in, a reaction is how the rest of the panel agrees
     without writing "agreed", and the seen line is who has read it. */
  function NoteBody({ body, staff }) {
    const parts = body.split(/(@p-[a-z]+)/g);
    return (
      <p className="rd-note__text">
        {parts.map((part, i) => {
          if (part.indexOf("@p-") !== 0) return <React.Fragment key={i}>{part}</React.Fragment>;
          const person = staffById(staff, part.slice(1));
          return <span className="rd-mention" key={i}>@{person ? person.name : part.slice(1)}</span>;
        })}
      </p>
    );
  }

  function ReactionBar({ note, me, onReact }) {
    const rows = reactionsOf(note);
    return (
      <span className="rd-react">
        {rows.map(({ reaction, who }) => {
          const mine = who.indexOf(me.id) > -1;
          const names = who.map((id) => id.replace("p-", "")).length;
          return (
            <FloatingTip key={reaction.value} side="bottom" label={reaction.label + " · " + plural(names, "person", "people")}>
              <button type="button" className="rd-react__btn" data-mine={mine}
                aria-label={(mine ? "Remove your " : "Add ") + reaction.label.toLowerCase() + " — " + who.length + " so far"}
                onClick={() => onReact(note, reaction.value)}>
                <Icon name={reaction.icon} size={12} />{who.length}
              </button>
            </FloatingTip>
          );
        })}
        <Floating align="start" width="12rem" label="React"
          trigger={<button type="button" className="rd-react__btn" aria-label="React to this note"><Icon name="smile-plus" size={12} /></button>}>
          {(close) => REACTIONS.map((reaction) => (
            <MenuItem key={reaction.value} onClick={() => { close(); onReact(note, reaction.value); }}>{reaction.label}</MenuItem>
          ))}
        </Floating>
      </span>
    );
  }

  function Note({ note, staff, me, now, reply, onReact, onReply }) {
    const author = staffById(staff, note.authorId);
    const seen = (note.seenBy || []).length;
    return (
      <div className={"rd-note" + (reply ? " rd-note--reply" : "")}>
        <Avatar size={reply ? 26 : 32} src={author.photo} alt="" fallback={author.initials} />
        <div className="rd-note__body">
          <div className="rd-note__head">
            <span className="rd-note__who">{author.name}</span>
            <span className="rd-note__role">{labelOf(DESK_ROLES, author.role)}</span>
            {note.kind !== "note" ? <Badge size="sm" variant="secondary">{labelOf(NOTE_KINDS, note.kind)}</Badge> : null}
            <span className="rd-note__at" title={formatAbsolute(note.at)}>{formatWhen(note.at, now)}</span>
          </div>
          <NoteBody body={note.body} staff={staff} />
          <div className="rd-note__foot">
            <ReactionBar note={note} me={me} onReact={onReact} />
            {onReply ? <Button variant="ghost" size="xs" onClick={() => onReply(note)}>Reply</Button> : null}
            {seen ? <span className="rd-note__seen"><Icon name="check-check" size={12} />Read by {seen}</span> : null}
          </div>
        </div>
      </div>
    );
  }

  /* Instant model, and consequential: a note posts on send and raises an undo
     toast. Its own control is not a form — there is no Save on this screen. */
  function Composer({ me, onPost, replyingTo, staff, onCancelReply }) {
    const [body, setBody] = React.useState("");
    const [kind, setKind] = React.useState("note");
    React.useEffect(() => { if (replyingTo) setBody("@" + replyingTo.authorId + " "); }, [replyingTo]);
    const send = () => { if (!body.trim()) return; onPost(body.trim(), kind, replyingTo); setBody(""); setKind("note"); };
    return (
      <div className="rd-composer">
        <Avatar size={32} src={me.photo} alt="" fallback={me.initials} />
        <div className="rd-composer__body">
          <Textarea rows={3} value={body} onChange={(e) => setBody(e.target.value)}
            aria-label={replyingTo ? "Reply to " + staffById(staff, replyingTo.authorId).name : "Write a note on this application"}
            placeholder={replyingTo ? "Reply to " + firstNameOf(staffById(staff, replyingTo.authorId)) + "…" : "What did you find? Type @ and a colleague's id to pull them in."} />
          <div className="rd-composer__foot">
            <span className="rd-composer__hint">
              {replyingTo ? "Replying to " + staffById(staff, replyingTo.authorId).name + "'s note." : "Notes are visible to everybody on the desk and to the hiring manager. The candidate never sees them."}
            </span>
            <span className="rd-filters__select">
              <label className="rd-sr" htmlFor="rd-note-kind">Kind of note</label>
              <Select id="rd-note-kind" size="sm" items={NOTE_KINDS.map((k) => ({ value: k.value, label: k.label }))} value={kind} onValueChange={setKind} />
            </span>
            {replyingTo ? <Button variant="ghost" size="sm" onClick={onCancelReply}>Cancel</Button> : null}
            <Button size="sm" disabled={!body.trim()} onClick={send}>Post the note</Button>
          </div>
        </div>
      </div>
    );
  }

  /* ── The stage strip and the board card ───────────────────────────────── */
  function StageStrip({ applications, requisitionId }) {
    const strip = stageStrip(applications, requisitionId);
    if (!strip.total) return <p className="rd-foot">No live applications on this requisition.</p>;
    return (
      <span className="rd-part">
        <span className="rd-strip" role="img" aria-label={strip.segments.filter((s) => s.count).map((s) => s.count + " " + s.label.toLowerCase()).join(", ")}>
          {strip.segments.filter((s) => s.count).map((segment) => (
            <span className="rd-strip__seg" key={segment.stage} data-stage={segment.stage} style={{ width: (segment.count / strip.total) * 100 + "%" }} />
          ))}
        </span>
        <span className="rd-striplegend">
          {strip.segments.filter((s) => s.count).map((segment) => <span key={segment.stage}>{segment.label} {segment.count}</span>)}
        </span>
      </span>
    );
  }

  function AppCard({ application, data, now, onNavigate }) {
    const stalled = APPLICATION_FLAGS.isStalled(application, now);
    return (
      <button type="button" className="rd-appcard" onClick={() => onNavigate("/applications/" + application.id)}>
        <span className="rd-appcard__head">
          <Avatar size={32} src={application.candidate.photo} alt="" fallback={application.candidate.initials} />
          <span className="rd-record">
            <span className="rd-record__name">{application.candidate.name}</span>
            <span className="rd-record__meta">{application.candidate.headline}</span>
          </span>
        </span>
        <Stepper application={application} data={data} now={now} mini />
        <span className="rd-appcard__meta">
          <span className={stalled ? "rd-clock rd-clock--danger" : "rd-clock"}>{plural(daysInStage(application, now), "day", "days")} in step</span>
          {application.watcherIds.length ? <span>· {plural(application.watcherIds.length, "watcher", "watchers")}</span> : null}
          {stalled ? <Badge size="sm" variant="warning">Over its {stageAllowance(application)} days</Badge> : null}
        </span>
      </button>
    );
  }

  /* ── Actions ──────────────────────────────────────────────────────────── */

  function Transitions({ transitions, onRun, size = "xs" }) {
    if (!transitions.length) return null;
    return (
      <span className="rd-actions__row">
        {transitions.map((t) => {
          const button = (
            <Button key={t.id} size={size} disabled={!!t.disabled}
              variant={t.destructive ? "destructive-outline" : t.primary ? "default" : "outline"}
              onClick={() => onRun(t)}>{t.label}</Button>
          );
          return t.disabled && t.disabledReason
            ? <FloatingTip key={t.id} label={t.disabledReason} side="bottom">{button}</FloatingTip>
            : button;
        })}
      </span>
    );
  }

  function RowActions({ transitions, onRun, label, extra }) {
    const primary = transitions.filter((t) => !t.destructive && t.primary);
    const rest = transitions.filter((t) => !t.primary);
    if (!primary.length && !rest.length && !extra) return <span className="rd-muted">—</span>;
    return (
      <span className="rd-actions__row">
        {primary.length ? <Transitions transitions={primary} onRun={onRun} /> : null}
        {rest.length || extra ? (
          <Floating align="end" width="16rem" label={label}
            trigger={<Button variant="ghost" size="icon-xs" aria-label={label}><Icon name="ellipsis" size={14} /></Button>}>
            {(close) => (
              <>
                {extra ? extra(close) : null}
                {rest.map((t) => (
                  <MenuItem key={t.id} variant={t.destructive ? "destructive" : undefined} disabled={!!t.disabled}
                    onClick={() => { close(); onRun(t); }}>{t.label}</MenuItem>
                ))}
              </>
            )}
          </Floating>
        ) : null}
      </span>
    );
  }

  function ExportMenu({ scope, onExport }) {
    return (
      <Floating align="end" width="11rem" label="Export"
        trigger={<Button variant="outline" size="sm"><Icon name="download" size={14} />Export</Button>}>
        {(close) => (
          <>
            <MenuGroupLabel>{scope}</MenuGroupLabel>
            {["CSV", "XLSX"].map((format) => <MenuItem key={format} onClick={() => { close(); onExport(format); }}>{format}</MenuItem>)}
          </>
        )}
      </Floating>
    );
  }

  /* ── List furniture: same order on every list screen ──────────────────── */

  function ScopeBar({ controls, note, dirty, onClear, switches }) {
    return (
      <div className="rd-scope">
        <span className="rd-scope__label">Scope</span>
        {(controls || []).map((c) => (
          <span className="rd-scope__group" key={c.id}>
            <label className="rd-foot" htmlFor={"rd-scope-" + c.id}>{c.label}</label>
            <span className="rd-scope__select"><Select id={"rd-scope-" + c.id} size="sm" items={c.items} value={c.value} onValueChange={c.onChange} /></span>
          </span>
        ))}
        {(switches || []).map((s) => (
          <span className="rd-scope__group" key={s.id}>
            <Switch checked={s.checked} onCheckedChange={s.onChange} aria-label={s.label} id={"rd-sw-" + s.id} />
            <label className="rd-foot" htmlFor={"rd-sw-" + s.id}>{s.label}</label>
          </span>
        ))}
        {dirty ? <Button variant="ghost" size="sm" onClick={onClear}>Reset scope</Button> : null}
        {note ? <p className="rd-scope__note">{note}</p> : null}
      </div>
    );
  }

  function FilterBar({ query, onQuery, placeholder, selects = [], onClear, dirty, trailing }) {
    return (
      <div className="rd-filters">
        <div className="rd-filters__text">
          <Input size="sm" value={query} placeholder={placeholder} aria-label={placeholder} startAddon={<Icon name="search" size={14} />} onChange={(e) => onQuery(e.target.value)} />
        </div>
        {selects.map((s) => (
          <span className="rd-filters__select" key={s.id}>
            <label className="rd-sr" htmlFor={"rd-flt-" + s.id}>{s.label}</label>
            <Select id={"rd-flt-" + s.id} size="sm" items={s.items} value={s.value} onValueChange={s.onChange} />
          </span>
        ))}
        {dirty ? <Button variant="ghost" size="sm" onClick={onClear}>Clear filters</Button> : null}
        {trailing}
      </div>
    );
  }

  function BulkBar({ count, note, children, onClear }) {
    if (!count) return null;
    return (
      <div className="rd-bulk">
        <span className="rd-bulk__count">{count} selected</span>
        {note ? <span className="rd-foot">{note}</span> : null}
        <span className="rd-bulk__actions">
          {children}
          <Button variant="ghost" size="sm" onClick={onClear}>Clear selection</Button>
        </span>
      </div>
    );
  }

  function NoResults({ title, description, onClear, action }) {
    return (
      <CardPanel>
        <Empty>
          <EmptyHeader>
            <EmptyMedia variant="icon"><Icon name="search-x" size={18} /></EmptyMedia>
            <EmptyTitle>{title}</EmptyTitle>
            <EmptyDescription>{description}</EmptyDescription>
          </EmptyHeader>
          <EmptyContent>{onClear ? <Button onClick={onClear}>Clear filters</Button> : action}</EmptyContent>
        </Empty>
      </CardPanel>
    );
  }

  /* ── Commit surfaces ──────────────────────────────────────────────────── */

  function ConfirmDialog({ request, onCancel, onConfirm }) {
    if (!request) return null;
    return (
      <AlertDialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
        <AlertDialogPopup>
          <AlertDialogHeader>
            <AlertDialogTitle>{request.title}</AlertDialogTitle>
            <AlertDialogDescription>{request.description}</AlertDialogDescription>
          </AlertDialogHeader>
          {request.rows ? (
            <div className="rd-confirm__rows">
              {request.rows.map((row) => <div className="rd-confirm__row" key={row.key}><span>{row.label}</span><span className="rd-muted">{row.value}</span></div>)}
            </div>
          ) : null}
          <AlertDialogFooter>
            <Button variant="outline" onClick={onCancel}>Cancel</Button>
            <Button variant={request.destructive ? "destructive" : "default"} onClick={onConfirm}>{request.confirmLabel}</Button>
          </AlertDialogFooter>
        </AlertDialogPopup>
      </AlertDialog>
    );
  }

  /* A staged dialog — its own commit model, so an instant screen may open one.
     Submit is disabled until dirty; errors render on submit, never on paint. */
  function FormDialog({ request, onCancel, onSubmit }) {
    const [values, setValues] = React.useState({});
    const [touched, setTouched] = React.useState(false);
    const [submitted, setSubmitted] = React.useState(false);
    React.useEffect(() => { setValues(request ? request.initial || {} : {}); setTouched(false); setSubmitted(false); }, [request]);
    if (!request) return null;
    const errors = request.validate ? request.validate(values) : {};
    const valid = Object.keys(errors).length === 0;
    const set = (id, value) => { setTouched(true); setValues((v) => Object.assign({}, v, { [id]: value })); };
    const fields = request.fields.filter((f) => !f.when || f.when(values));
    return (
      <Dialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
        <DialogPopup>
          <DialogHeader>
            <DialogTitle>{request.title}</DialogTitle>
            <DialogDescription>{request.description}</DialogDescription>
          </DialogHeader>
          <DialogPanel>
            <div className="rd-fields">
              {request.preview ? request.preview(values) : null}
              {fields.map((field) => {
                const id = "rd-f-" + field.id;
                const invalid = submitted && !!errors[field.id];
                return (
                  <Field key={field.id}>
                    <FieldLabel htmlFor={id}>{field.label}</FieldLabel>
                    {field.kind === "radio" ? (
                      <RadioGroup name={id} items={field.options} value={values[field.id] || ""} onValueChange={(v) => set(field.id, v)} />
                    ) : field.kind === "select" ? (
                      <Select id={id} items={field.options} value={values[field.id] || ""} placeholder={field.placeholder} invalid={invalid} onValueChange={(v) => set(field.id, v)} />
                    ) : field.kind === "checks" ? (
                      <div className="rd-checks">
                        {field.options.map((option) => {
                          const on = (values[field.id] || []).indexOf(option.value) > -1;
                          return (
                            <label className="rd-check" key={option.value}>
                              <Checkbox checked={on} onCheckedChange={() => set(field.id, on ? (values[field.id] || []).filter((v) => v !== option.value) : (values[field.id] || []).concat([option.value]))} />
                              <span className="rd-check__text"><span>{option.label}</span>{option.note ? <span className="rd-foot">{option.note}</span> : null}</span>
                            </label>
                          );
                        })}
                      </div>
                    ) : field.kind === "toggle" ? (
                      <label className="rd-check"><Switch checked={!!values[field.id]} onCheckedChange={(v) => set(field.id, v)} /><span className="rd-check__text"><span>{field.toggleLabel}</span></span></label>
                    ) : field.kind === "area" ? (
                      <Textarea id={id} rows={3} placeholder={field.placeholder} value={values[field.id] || ""} invalid={invalid} onChange={(e) => set(field.id, e.target.value)} />
                    ) : field.kind === "day" ? (
                      <Input id={id} type="date" value={values[field.id] || ""} invalid={invalid} onChange={(e) => set(field.id, e.target.value)} />
                    ) : field.kind === "time" ? (
                      <Input id={id} type="time" value={values[field.id] || ""} invalid={invalid} onChange={(e) => set(field.id, e.target.value)} />
                    ) : field.kind === "scores" ? (
                      <div className="rd-checks">
                        {SCORE_AREAS.map((area) => (
                          <span className="rd-scope__group" key={area.value}>
                            <span className="rd-filters__select">
                              <label className="rd-sr" htmlFor={id + "-" + area.value}>{area.label}</label>
                              <Select id={id + "-" + area.value} size="sm"
                                items={[1, 2, 3, 4, 5].map((n) => ({ value: String(n), label: n + " of 5" }))}
                                value={String((values[field.id] || {})[area.value] || "")} placeholder="Score"
                                onValueChange={(v) => set(field.id, Object.assign({}, values[field.id] || {}, { [area.value]: Number(v) }))} />
                            </span>
                            <span className="rd-foot">{area.label}</span>
                          </span>
                        ))}
                      </div>
                    ) : (
                      <Input id={id} placeholder={field.placeholder} value={values[field.id] || ""} invalid={invalid} onChange={(e) => set(field.id, e.target.value)} />
                    )}
                    {invalid ? <FieldError>{errors[field.id]}</FieldError> : field.hint ? <FieldDescription>{field.hint}</FieldDescription> : null}
                  </Field>
                );
              })}
            </div>
          </DialogPanel>
          <DialogFooter>
            {touched ? <Button variant="outline" onClick={onCancel}>Cancel</Button> : null}
            <Button disabled={!touched} onClick={() => { setSubmitted(true); if (valid) onSubmit(values); }}>{request.submitLabel}</Button>
          </DialogFooter>
        </DialogPopup>
      </Dialog>
    );
  }

  function FormBar({ dirty, note, onCancel, onSave, label }) {
    return (
      <div className="rd-formbar">
        <span className="rd-formbar__note">{note}</span>
        {dirty ? <Button variant="outline" size="sm" onClick={onCancel}>Cancel</Button> : null}
        <Button size="sm" disabled={!dirty} onClick={onSave}>{label}</Button>
      </div>
    );
  }

  function RouteNotBuilt({ path, onNavigate }) {
    return (
      <div className="rd-page">
        <Card>
          <CardPanel>
            <Empty>
              <EmptyHeader>
                <EmptyMedia variant="icon"><Icon name="signpost" size={18} /></EmptyMedia>
                <EmptyTitle>Nothing is registered at {path}</EmptyTitle>
                <EmptyDescription>Every screen in the Recruitment Desk has a route in nav.js. This path is not one of them.</EmptyDescription>
              </EmptyHeader>
              <EmptyContent><Button onClick={() => onNavigate("/")}>Back to your desk</Button></EmptyContent>
            </Empty>
          </CardPanel>
        </Card>
      </div>
    );
  }

  Object.assign(window, {
    StatTile, Bar, Clock, KeyValues, StaffCell, CandidateCell, Followers,
    Stepper, StageNote, Pips, ScoreMatrix, NoteBody, ReactionBar, Note, Composer,
    StageStrip, AppCard, Transitions, RowActions, ExportMenu,
    ScopeBar, FilterBar, BulkBar, NoResults, ConfirmDialog, FormDialog, FormBar, RouteNotBuilt,
  });
})();
