/* Ward Desk — the parts every screen shares.
   A Band is a full-bleed region divided by a hairline rule; there are no cards
   in this product, so nothing here draws a box. Nothing here holds data or
   assigns a status tone: StatusBadge does the tone, metrics.js does the
   arithmetic, enums.js decides which transitions exist. Reported in the README
   as local additions to the design system's inventory. */
(() => {
  const {
    Button, Input, Select, Checkbox, Textarea, Field, FieldLabel, FieldDescription, FieldError,
    Dialog, DialogPopup, DialogHeader, DialogTitle, DialogDescription, DialogPanel, DialogFooter,
    AlertDialog, AlertDialogPopup, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter,
    RadioGroup, MenuItem, MenuGroupLabel, Badge, Avatar, Separator,
    Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
    Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent,
  } = window.OriginDesignSystem_7c998b;

  /* ── Structure ─────────────────────────────────────────────────────────── */
  /* Four variants, and nothing else: default, flush (a child bleeds to the
     card edge), tint (recessed — work nobody can act on yet), fill (stretches to
     its row so cards side by side align their footers). */
  function Band({ title, note, actions, flush, tint, fill, children }) {
    return (
      <section className={"hd-band" + (flush ? " hd-band--flush" : "") + (tint ? " hd-band--tint" : "") + (fill ? " hd-band--fill" : "")}>
        {title || actions ? (
          <div className="hd-band__head">
            <div>
              <h2 className="hd-band__title">{title}</h2>
              {note ? <p className="hd-band__note">{note}</p> : null}
            </div>
            {actions ? <div className="hd-actions">{actions}</div> : null}
          </div>
        ) : null}
        {children}
      </section>
    );
  }

  /* Four figures to a row, or three where the screen has three. Every one
     carries a qualifier that decomposes its own figure's set. */
  function Figures({ items, three }) {
    return (
      <div className={"hd-figs" + (three ? " hd-figs--3" : "")}>
        {items.map((item) => (
          <div key={item.label}>
            <span className="hd-fig__label">{item.label}</span>
            <span className="hd-fig">{item.value}</span>
            <span className="hd-fig__qualifier">{item.qualifier}</span>
          </div>
        ))}
      </div>
    );
  }

  function Pairs({ items }) {
    return (
      <div className="hd-pairs">
        {items.map((item) => (
          <div className="hd-pair" key={item.label}>
            <span className="hd-pair__label">{item.label}</span>
            <span className="hd-pair__value">{item.value}</span>
          </div>
        ))}
      </div>
    );
  }

  function StockBar({ percent, tone }) {
    return (
      <span className="hd-bar" aria-hidden="true">
        <span className={"hd-bar__fill" + (tone ? " hd-bar__fill--" + tone : "")} style={{ width: Math.max(2, Math.min(100, percent)) + "%" }} />
      </span>
    );
  }

  /* ── People cells: the photo is part of identifying a person ────────────── */
  function PersonCell({ name, meta, avatar, size = 28, onOpen, badge }) {
    const label = (
      <span className="hd-person__text">
        <span className="hd-person__name">{onOpen ? <button type="button" className="hd-linkbtn" onClick={onOpen}>{name}</button> : name}</span>
        {meta ? <span className="hd-person__meta">{meta}</span> : null}
      </span>
    );
    return (
      <span className="hd-person">
        <Avatar size={size} src={avatar} alt={"Photograph of " + name} fallback={initialsOf(name)} />
        {label}
        {badge || null}
      </span>
    );
  }

  /* ── Time in a cell ────────────────────────────────────────────────────
     One format rule per column (§8.3). `problem` is a property of the column,
     declared by the caller once: whether a time in the past is a problem. */
  function Clock({ at, now, problem, dangerous }) {
    if (!at) return <span className="hd-muted">Never</span>;
    const past = at < now;
    const cls = past && problem ? (dangerous ? "hd-danger" : "hd-warn") : "";
    return <span className={cls} title={formatAbsolute(at)}>{formatWhen(at, now)}</span>;
  }

  /* ── Actions ───────────────────────────────────────────────────────────── */
  function Transitions({ transitions, onRun, size = "sm" }) {
    if (!transitions.length) return null;
    return (
      <span className="hd-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>
    );
  }

  /* A row carries the transition its status asks for — the primary one — as a
     button. Everything else goes in the row's menu: a list is not where you put
     an action that cannot be undone. */
  function RowActions({ transitions, onRun, label, extra }) {
    const primary = transitions.filter((t) => t.primary);
    const rest = transitions.filter((t) => !t.primary).concat(extra || []);
    if (!primary.length && !rest.length) return <span className="hd-muted">—</span>;
    return (
      <span className="hd-actions__row">
        {primary.length ? <Transitions transitions={primary} onRun={onRun} /> : null}
        {rest.length ? (
          <Floating align="end" width="14rem" label={label}
            trigger={<Button variant="ghost" size="icon-sm" aria-label={label}><Icon name="ellipsis" /></Button>}>
            {(close) => rest.map((t) => (
              <MenuItem key={t.id} disabled={!!t.disabled} variant={t.destructive ? "destructive" : "default"} onClick={() => { close(); onRun(t); }}>{t.label}</MenuItem>
            ))}
          </Floating>
        ) : null}
      </span>
    );
  }

  /* ── List furniture: same order on every list screen (§5) ───────────────── */
  function FilterBar({ query, onQuery, placeholder, selects, onClear, dirty }) {
    return (
      <div className="hd-filters">
        <div className="hd-filters__text">
          <Input size="sm" value={query} placeholder={placeholder} aria-label={placeholder} startAddon={<Icon name="search" />} onChange={(e) => onQuery(e.target.value)} />
        </div>
        {(selects || []).map((s) => (
          <span className="hd-filters__select" key={s.id}>
            <Select size="sm" items={s.items} value={s.value} aria-label={s.label} onValueChange={s.onChange} />
          </span>
        ))}
        {dirty ? <Button variant="ghost" size="sm" onClick={onClear}>Clear filters</Button> : null}
      </div>
    );
  }

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

  /* Export sends data out; Import brings it in (§8.2). Both are offered on every
     list, and both say what scope they are about to act on. */
  function ExportMenu({ scope, onExport, size = "sm" }) {
    return (
      <Floating align="end" width="13rem" label="Export"
        trigger={<Button variant="outline" size={size}><Icon name="download" />Export<Icon name="chevron-down" /></Button>}>
        {(close) => (
          <>
            <MenuGroupLabel>{scope}</MenuGroupLabel>
            {["CSV", "XLSX"].map((format) => (
              <MenuItem key={format} onClick={() => { close(); onExport(format); }}>{format}</MenuItem>
            ))}
          </>
        )}
      </Floating>
    );
  }

  function ImportButton({ onImport, viewer, size = "sm" }) {
    const may = ROLE_CAN_IMPORT[viewer.staffRole];
    const button = <Button variant="outline" size={size} disabled={!may} onClick={onImport}><Icon name="upload" />Import</Button>;
    return may ? button : <FloatingTip label={"Importing writes into the patient index. Your role is " + labelOf(STAFF_ROLES, viewer.staffRole) + "."} side="bottom">{button}</FloatingTip>;
  }

  function TableWrap({ children }) {
    return <div className="hd-tablewrap">{children}</div>;
  }

  function TableFoot({ shown, total, noun, children }) {
    return (
      <div className="hd-tablefoot">
        <span className="hd-footnote">Showing {shown} of {total} {noun}</span>
        {children ? <span className="hd-actions__row">{children}</span> : null}
      </div>
    );
  }

  function NoResults({ noun, onClear }) {
    return (
      <div style={{ padding: "var(--space-6) var(--gutter-page)" }}>
        <Empty>
          <EmptyHeader>
            <EmptyMedia variant="icon"><Icon name="search-x" size={18} /></EmptyMedia>
            <EmptyTitle>No {noun} match those filters</EmptyTitle>
            <EmptyDescription>Nothing in this list matches every filter at once. Clearing them brings the whole list back.</EmptyDescription>
          </EmptyHeader>
          <EmptyContent><Button onClick={onClear}>Clear filters</Button></EmptyContent>
        </Empty>
      </div>
    );
  }

  /* ── Notes: the social layer, instant, no Save anywhere near it ─────────── */
  function noteBody(body, staff) {
    const parts = String(body).split(/(@stf_\d+)/g);
    return parts.map((part, i) => {
      if (!/^@stf_\d+$/.test(part)) return part;
      const person = staffById(staff, part.slice(1));
      return <mark key={i}>@{person ? person.name : part.slice(1)}</mark>;
    });
  }

  const REACTIONS = [
    { key: "ack", label: "Acknowledged" },
    { key: "agree", label: "Agree" },
    { key: "seen", label: "Seen" },
  ];

  function Note({ comment, staff, viewer, now, onReact, context, plain }) {
    const author = staffById(staff, comment.authorId);
    return (
      <article className={"hd-note" + (plain ? " hd-note--plain" : "")}>
        <span className="hd-note__avatar">
          <Avatar size={32} src={author ? author.avatar : undefined} alt={author ? "Photograph of " + author.name : ""} fallback={initialsOf(author ? author.name : "?")} />
        </span>
        <div className="hd-note__head">
          <span className="hd-note__author">{author ? author.name : "Former colleague"}</span>
          <span className="hd-note__role">{author ? author.jobTitle : ""}</span>
          <span className="hd-note__time" title={formatAbsolute(comment.at)}>{formatWhen(comment.at, now)}</span>
          {context ? <span className="hd-note__role">on {context}</span> : null}
        </div>
        <p className="hd-note__body">{noteBody(comment.body, staff)}</p>
        <div className="hd-note__foot">
          {REACTIONS.map((r) => {
            const ids = comment.reactions[r.key] || [];
            if (!ids.length && r.key !== "ack") return null;
            const mine = ids.indexOf(viewer.id) > -1;
            return (
              <button type="button" key={r.key} className={"hd-react" + (mine ? " is-on" : "")}
                aria-pressed={mine} aria-label={r.label + (ids.length ? " — " + ids.map((id) => staffName(staff, id)).join(", ") : "")}
                onClick={() => onReact(comment, r.key)}>
                {r.label}{ids.length ? " " + ids.length : ""}
              </button>
            );
          })}
        </div>
      </article>
    );
  }

  /* Posting is instant: the note is written into the fixture and every count
     that reads it moves. Mentions are picked from the staff list rather than
     typed, so a mention always resolves to somebody real. */
  function NoteComposer({ staff, viewer, onPost, placeholder }) {
    const [body, setBody] = React.useState("");
    const [mention, setMention] = React.useState("");
    const dirty = body.trim().length > 0;
    const post = () => {
      const text = mention ? body.trim() + " @" + mention : body.trim();
      onPost(text);
      setBody("");
      setMention("");
    };
    return (
      <div className="hd-composer">
        <Textarea rows={2} value={body} placeholder={placeholder} aria-label="Write a note" onChange={(e) => setBody(e.target.value)} />
        <div className="hd-composer__foot">
          <span className="hd-filters__select">
            <Select size="sm" aria-label="Mention somebody" value={mention} onValueChange={setMention}
              items={[{ value: "", label: "Mention nobody" }].concat(staff.filter((s) => s.id !== viewer.id && s.status !== "left").map((s) => ({ value: s.id, label: s.name + " — " + s.jobTitle })))} />
          </span>
          <Button size="sm" disabled={!dirty} onClick={post}>Post note</Button>
        </div>
      </div>
    );
  }

  function NoteThread({ comments, staff, viewer, now, onReact, onPost, placeholder }) {
    return (
      <>
        <div className="hd-notes">
          {comments.length
            ? comments.map((c) => <Note key={c.id} comment={c} staff={staff} viewer={viewer} now={now} onReact={onReact} />)
            : <p className="hd-footnote">Nothing has been written on this record yet. A note here is visible to everybody with access to it.</p>}
        </div>
        <NoteComposer staff={staff} viewer={viewer} onPost={onPost} placeholder={placeholder} />
      </>
    );
  }

  /* ── Dialogs ───────────────────────────────────────────────────────────── */
  function ConfirmDialog({ request, onCancel, onConfirm }) {
    return (
      <AlertDialog open={!!request} onOpenChange={(open) => { if (!open) onCancel(); }}>
        <AlertDialogPopup>
          <AlertDialogHeader>
            <AlertDialogTitle>{request ? request.title : ""}</AlertDialogTitle>
            <AlertDialogDescription>{request ? request.description : ""}</AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <Button variant="outline" onClick={onCancel}>Cancel</Button>
            <Button variant="destructive" onClick={onConfirm}>{request ? request.confirmLabel : ""}</Button>
          </AlertDialogFooter>
        </AlertDialogPopup>
      </AlertDialog>
    );
  }

  /* A staged dialog — its own commit model, so an instant screen may open one. */
  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 })); };
    return (
      <Dialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
        <DialogPopup>
          <DialogHeader>
            <DialogTitle>{request.title}</DialogTitle>
            <DialogDescription>{request.description}</DialogDescription>
          </DialogHeader>
          <DialogPanel>
            <div className="hd-fields hd-fields--one">
              {request.fields.filter((f) => !f.when || f.when(values)).map((field) => (
                <Field key={field.id}>
                  <FieldLabel htmlFor={"hd-f-" + field.id}>{field.label}</FieldLabel>
                  {field.kind === "radio" ? (
                    <RadioGroup name={"hd-f-" + field.id} items={field.options} value={values[field.id] || ""} onValueChange={(v) => set(field.id, v)} />
                  ) : field.kind === "select" ? (
                    <Select items={field.options} value={values[field.id] || ""} aria-label={field.label} onValueChange={(v) => set(field.id, v)} />
                  ) : field.kind === "text" ? (
                    <Textarea id={"hd-f-" + field.id} rows={3} placeholder={field.placeholder} value={values[field.id] || ""} invalid={submitted && !!errors[field.id]} onChange={(e) => set(field.id, e.target.value)} />
                  ) : field.kind === "checkbox" ? (
                    <span className="hd-person">
                      <Checkbox id={"hd-f-" + field.id} checked={!!values[field.id]} onCheckedChange={(v) => set(field.id, v)} />
                      <span className="hd-pair__value">{field.checkboxLabel}</span>
                    </span>
                  ) : (
                    <Input id={"hd-f-" + field.id} placeholder={field.placeholder} value={values[field.id] || ""} invalid={submitted && !!errors[field.id]} onChange={(e) => set(field.id, e.target.value)} />
                  )}
                  {submitted && errors[field.id] ? <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>
    );
  }

  /* The staged form bar a staged screen ends with. Sticky to the bottom of the
     content column, which is the scroll owner. */
  function FormBar({ dirty, note, onCancel, onSave, label }) {
    return (
      <div className="hd-formbar">
        <span className="hd-formbar__note">{note}</span>
        {dirty ? <Button variant="outline" onClick={onCancel}>Cancel</Button> : null}
        <Button disabled={!dirty} onClick={onSave}>{label}</Button>
      </div>
    );
  }

  /* Not a route and not an empty state: the fallback for a hand-typed or stale
     path, which has no breadcrumb because breadcrumbs are made of the route
     table. */
  function RouteNotBuilt({ path, onNavigate }) {
    return (
      <div style={{ padding: "var(--space-8) var(--gutter-page)" }}>
        <Empty>
          <EmptyHeader>
            <EmptyMedia variant="icon"><Icon name="signpost" size={18} /></EmptyMedia>
            <EmptyTitle>Nothing is registered at {path}</EmptyTitle>
            <EmptyDescription>Ward Desk registers twenty destinations and the screens behind them. The route table decides what exists, so this address has no screen behind it.</EmptyDescription>
          </EmptyHeader>
          <EmptyContent><Button onClick={() => onNavigate("/")}>Go to Today</Button></EmptyContent>
        </Empty>
      </div>
    );
  }

  /* Sorting: one hook, one header control, one comparator, so every sortable
     table in the product behaves identically. */
  function useSort(key, dir) {
    const [sort, setSort] = React.useState({ key: key, dir: dir || "desc" });
    const onSort = (next) => setSort((s) => (s.key === next ? { key: next, dir: s.dir === "asc" ? "desc" : "asc" } : { key: next, dir: "desc" }));
    return { sort: sort, onSort: onSort };
  }

  function SortHead({ label, sortKey, sort, onSort }) {
    const on = sort.key === sortKey;
    return (
      <button type="button" className="hd-sort" data-on={on ? "true" : "false"}
        aria-label={"Sort by " + label + (on && sort.dir === "asc" ? ", highest first" : ", lowest first")}
        onClick={() => onSort(sortKey)}>
        {label}
        <Icon name={on ? (sort.dir === "asc" ? "arrow-up" : "arrow-down") : "chevrons-up-down"} size={12} />
      </button>
    );
  }

  function sortRows(rows, sort, accessors) {
    const get = accessors[sort.key];
    if (!get) return rows;
    return rows.slice().sort((a, b) => {
      const av = get(a), bv = get(b);
      if (typeof av === "string" || typeof bv === "string") {
        const cmp = String(av == null ? "" : av).localeCompare(String(bv == null ? "" : bv));
        return sort.dir === "asc" ? cmp : -cmp;
      }
      const an = av == null ? -Infinity : av, bn = bv == null ? -Infinity : bv;
      return sort.dir === "asc" ? an - bn : bn - an;
    });
  }

  function Chip({ children, variant = "secondary" }) {
    return <Badge size="sm" variant={variant}>{children}</Badge>;
  }

  /* An allergy is not a status — it is a fact about the person that every
     clinical screen has to carry. */
  function AllergyChips({ patient }) {
    if (!PATIENT_FLAGS.hasAllergyAlert(patient)) return null;
    return (
      <span className="hd-chips">
        {patient.allergies.map((a) => <Badge key={a} size="sm" variant="destructive">{a}</Badge>)}
      </span>
    );
  }

  Object.assign(window, {
    Band, Figures, Pairs, StockBar, PersonCell, Clock, Transitions, RowActions, FilterBar, BulkBar,
    ExportMenu, ImportButton, TableWrap, TableFoot, NoResults, Note, NoteThread, NoteComposer, noteBody, REACTIONS,
    ConfirmDialog, FormDialog, FormBar, RouteNotBuilt, useSort, SortHead, sortRows, Chip, AllergyChips,
  });
})();
