"use client";
import { MyinfoRecords } from "./myinfo-records";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";
import { z } from "zod";
import { applicationStatuses } from "@/lib/application-record";
import {
  documentError,
  staffDocumentKinds,
  staffDocumentLabels,
  type StaffDocumentKind,
} from "@/lib/application-schema";
import {
  DECLARATION_VERSION,
  detailFor,
  dueDiligenceKeys,
  dueDiligenceQuestions,
} from "@/lib/application-declaration";
import { singaporeDate } from "@/lib/application-particulars";
import {
  emptyStaffDeclaration,
  emptyStaffSection,
  staffCreateFailures,
} from "@/lib/staff-create";
import {
  STAFF_NOTE_LENGTH,
  STAFF_NOTE_LIMIT,
  staffTeamSchema,
  staffViewSchema,
  type StaffTeam,
  type StaffView,
} from "@/lib/staff-model";
import { interests } from "@/lib/contact-schema";
import {
  lockedStaffDetails,
  staffDetailAudit,
  staffDetailEditLabels,
  staffDetailFailures,
  staffDetailInputType,
  staffDetailKeys,
  staffDetailLabel,
  staffDetailOptional,
  staffDetailOptions,
  staffDetailSections,
  staffDetailTitles,
  staffDetailValues,
  type StaffDetailSection,
} from "@/lib/staff-details";
import {
  emptyStaffSearch,
  hasSearchWords,
  staffSearchResultSchema,
  type StaffListItem,
  type StaffSearch,
} from "@/lib/staff-search";
const label = (value: string) =>
  value.replaceAll("_", " ").replaceAll("-", " ");
const date = (value: string) =>
  new Intl.DateTimeFormat("en-SG", {
    dateStyle: "medium",
    timeStyle: "short",
    timeZone: "Asia/Singapore",
  }).format(new Date(value));
async function api(path: string, method = "GET", body?: unknown) {
  const response = await fetch(path, {
    method,
    headers:
      body === undefined ? undefined : { "Content-Type": "application/json" },
    body: body === undefined ? undefined : JSON.stringify(body),
    cache: "no-store",
    signal: AbortSignal.timeout(20000),
  });
  if (response.status === 401 || response.status === 403) {
    window.location.replace("/staff/login");
    throw new Error("Sign in again to continue.");
  }
  if (!response.ok) {
    const result = z
      .object({ message: z.string().max(500) })
      .safeParse(await response.json().catch(() => null));
    throw new Error(
      result.success
        ? result.data.message
        : "The request failed. Please retry.",
    );
  }
  return (await response.json()) as unknown;
}
const previewApi: typeof api = async (path, method, body) =>
  (await import("@/lib/staff-demo")).staffDemoApi(path, method, body);
const hexDigest = async (file: Blob) =>
  Array.from(
    new Uint8Array(
      await crypto.subtle.digest("SHA-256", await file.arrayBuffer()),
    ),
    (byte) => byte.toString(16).padStart(2, "0"),
  ).join("");
async function uploadDocument(
  call: typeof api,
  preview: boolean,
  mock: boolean,
  id: string,
  kind: StaffDocumentKind,
  file: File,
) {
  const path = `/api/staff/applications/${id}/documents`;
  const target = z
    .object({ url: z.url(), headers: z.record(z.string(), z.string()) })
    .parse(
      await call(path, "POST", {
        action: "prepare",
        value: {
          kind,
          name: file.name,
          type: file.type,
          size: file.size,
          sha256: await hexDigest(file),
        },
      }),
    );
  if (preview)
    (await import("@/lib/staff-demo")).staffDemoUpload(id, kind, file);
  else {
    const url = new URL(target.url);
    if (
      !(mock
        ? url.origin === "http://localhost:3101"
        : url.protocol === "https:" &&
          !url.port &&
          (url.hostname === "storage.googleapis.com" ||
            url.hostname.endsWith(".storage.googleapis.com"))) ||
      url.username ||
      url.password ||
      url.hash
    )
      throw new Error(
        "Upload instructions could not be verified. Please retry.",
      );
    const response = await fetch(target.url, {
      method: "PUT",
      headers: target.headers,
      body: file,
      signal: AbortSignal.timeout(60000),
    });
    if (!response.ok && response.status !== 412)
      throw new Error(
        `${staffDocumentLabels[kind]} could not be uploaded. Try again.`,
      );
  }
  await call(path, "POST", { action: "verify", value: { kind } });
}
function auditText(value: string): string {
  const names: Record<string, string> = {
    retention: "Retention",
    review: "Review",
    policyVersion: "Policy",
    receivedAt: "Received",
    latestDocumentReceivedAt: "Latest receipt boundary",
    lifecycle: "Loan",
    outcome: "Outcome",
    loanEndedAt: "Loan ended",
    borrowerRelationshipEndedAt: "Relationship ended",
    classificationReviewed: "Classification reviewed",
    hold: "Hold",
    basis: "Basis",
    nextReviewAt: "Next review",
  };
  const flatten = (input: unknown): string =>
    input && typeof input === "object"
      ? Object.entries(input)
          .map(([key, item]) => `${names[key] ?? label(key)}: ${flatten(item)}`)
          .join("; ")
      : input === null
        ? "Not recorded"
        : String(input);
  try {
    return flatten(JSON.parse(value));
  } catch {
    return label(value);
  }
}
function DetailFields({
  section,
  prefix,
  values,
  locked = [],
  errors,
  onChange,
}: {
  section: StaffDetailSection;
  prefix: string;
  values: Record<string, string>;
  locked?: string[];
  errors: Record<string, string>;
  onChange: (
    update: (current: Record<string, string>) => Record<string, string>,
  ) => void;
}) {
  return (
    <>
      {staffDetailKeys(section, values).map((key) => {
        const id = `${prefix}-${section}-${key}`,
          choices = staffDetailOptions(section, key),
          isLocked = locked.includes(key),
          failure = errors[key],
          optional = staffDetailOptional(section, key, values);
        const common = {
          id,
          value: values[key] ?? "",
          "aria-invalid": failure ? true : undefined,
          "aria-describedby": failure
            ? `${id}-error`
            : isLocked
              ? `${id}-note`
              : undefined,
        };
        const update = (value: string) =>
          onChange((current) => ({
            ...current,
            [key]: value,
            ...(key === "purpose" && value !== "Other"
              ? { purposeOther: "" }
              : {}),
            ...(key === "repaymentSource" && value !== "Other"
              ? { repaymentOther: "" }
              : {}),
          }));
        return (
          <div key={key}>
            <label htmlFor={id}>
              {staffDetailLabel(section, key)}
              {optional ? " (optional)" : ""}
            </label>
            {choices && !isLocked ? (
              <select
                {...common}
                onChange={(event) => update(event.target.value)}
              >
                <option value="">
                  {optional ? "Not provided" : "Choose an option"}
                </option>
                {choices.map((choice) => (
                  <option key={choice} value={choice}>
                    {choice}
                  </option>
                ))}
              </select>
            ) : (
              <input
                {...common}
                type={staffDetailInputType(key)}
                readOnly={isLocked}
                maxLength={250}
                onChange={(event) => update(event.target.value)}
              />
            )}
            {isLocked && (
              <p id={`${id}-note`} className="staff-muted">
                Verified by Myinfo and cannot be edited.
              </p>
            )}
            {failure && (
              <p id={`${id}-error`} className="staff-error">
                {failure}
              </p>
            )}
          </div>
        );
      })}
    </>
  );
}
function StaffRecord({
  record,
  team,
  changed,
  saved,
  savedCount,
  clearSaved,
  preview,
  mock,
}: {
  record: StaffView;
  team: StaffTeam | null;
  changed: (message?: string) => void;
  saved: string;
  savedCount: number;
  clearSaved: () => void;
  preview: boolean;
  mock: boolean;
}) {
  const call = preview ? previewApi : api;
  const savedRef = useRef<HTMLParagraphElement>(null);
  useEffect(() => {
    if (saved) savedRef.current?.focus();
  }, [saved, savedCount]);
  const [status, setStatus] = useState(record.status),
    [busy, setBusy] = useState(false),
    [error, setError] = useState("");
  const lifecycle = record.retention.lifecycle;
  const initialDate = (value: string | null) =>
    value
      ? new Date(Date.parse(value) + 8 * 3600000).toISOString().slice(0, 10)
      : "";
  const [reviewed, setReviewed] = useState(
      record.retention.classificationReviewed,
    ),
    [hold, setHold] = useState(record.retention.hold);
  const [loanEnd, setLoanEnd] = useState(
    initialDate(
      lifecycle.outcome === "approved" ? lifecycle.loanEndedAt : null,
    ),
  );
  const [relationshipEnd, setRelationshipEnd] = useState(
    initialDate(
      lifecycle.outcome === "approved"
        ? lifecycle.borrowerRelationshipEndedAt
        : null,
    ),
  );
  const [basis, setBasis] = useState(record.retentionReview.basis),
    [reviewDate, setReviewDate] = useState(
      initialDate(record.retentionReview.nextReviewAt),
    );
  const [assignee, setAssignee] = useState(record.assignee ?? ""),
    [note, setNote] = useState("");
  const assignable = [
    ...new Set([
      ...(team?.team ?? []),
      ...(record.assignee ? [record.assignee] : []),
    ]),
  ];
  const [editing, setEditing] = useState<StaffDetailSection | null>(null),
    [draftDetails, setDraftDetails] = useState<Record<string, string>>({}),
    [detailErrors, setDetailErrors] = useState<Record<string, string>>({});
  const detailsEditable =
    Boolean(record.submittedAt) && !record.archivedAt && !record.deleting;
  const editForm = useRef<HTMLFormElement>(null),
    lastEdited = useRef<StaffDetailSection | null>(null);
  useEffect(() => {
    if (editing)
      editForm.current
        ?.querySelector<HTMLElement>("input:not([readonly]), select")
        ?.focus();
    else if (lastEdited.current)
      document.getElementById(`edit-${lastEdited.current}`)?.focus();
  }, [editing]);
  const [uploadKind, setUploadKind] = useState<StaffDocumentKind | "">(""),
    [uploadFile, setUploadFile] = useState<File | null>(null),
    [uploadError, setUploadError] = useState("");
  const missingKinds = staffDocumentKinds.filter(
    (kind) =>
      !record.documents.some(
        (document) => document.kind === kind && document.verified,
      ),
  );
  const documentsAddable =
    Boolean(record.submittedAt) &&
    !record.archivedAt &&
    !record.deleting &&
    missingKinds.length > 0;
  const instant = (value: string) =>
    value ? new Date(`${value}T23:59:59.999+08:00`).toISOString() : null;
  async function save(
    action: "status" | "retention" | "archive" | "assignment" | "note",
  ) {
    if (busy) return;
    setBusy(true);
    setError("");
    clearSaved();
    try {
      const value =
        action === "status"
          ? { revision: record.revision, status }
          : action === "archive"
            ? { revision: record.revision, archived: !record.archivedAt }
            : action === "assignment"
              ? { revision: record.revision, assignee: assignee || null }
              : action === "note"
                ? { text: note }
                : {
                    revision: record.revision,
                    classificationReviewed: reviewed,
                    hold,
                    loanEndedAt: instant(loanEnd),
                    borrowerRelationshipEndedAt: instant(relationshipEnd),
                    basis,
                    nextReviewAt: instant(reviewDate),
                  };
      await call(`/api/staff/applications/${record.id}`, "PATCH", {
        action,
        value,
      });
      if (action === "note") setNote("");
      changed(
        {
          status: "Status saved.",
          retention: "Retention review saved.",
          assignment: "Assignment saved.",
          note: "Note added.",
          archive: record.archivedAt
            ? "Application restored."
            : "Application archived.",
        }[action],
      );
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : "The change could not be saved.",
      );
    } finally {
      setBusy(false);
    }
  }
  async function download(kind: string) {
    if (busy) return;
    setBusy(true);
    setError("");
    clearSaved();
    try {
      const path = `/api/staff/applications/${record.id}/download`;
      if (preview) {
        const result = z
          .object({ url: z.string().startsWith("blob:"), name: z.string() })
          .parse(await call(path, "POST", { kind }));
        const link = document.createElement("a");
        link.href = result.url;
        link.download = result.name;
        link.click();
        setTimeout(() => URL.revokeObjectURL(result.url), 60000);
      } else {
        const result = z
          .object({ url: z.url() })
          .parse(await call(path, "POST", { kind }));
        window.location.assign(result.url);
      }
    } catch {
      setError("The document is unavailable. Reload the record and retry.");
    } finally {
      setBusy(false);
    }
  }
  async function addDocument() {
    if (busy) return;
    const kind =
      uploadKind && missingKinds.includes(uploadKind)
        ? uploadKind
        : missingKinds[0];
    const problem = !uploadFile
      ? "Choose a file to upload."
      : documentError(uploadFile);
    setUploadError(problem ?? "");
    if (problem) {
      document.getElementById("document-file")?.focus();
      return;
    }
    setBusy(true);
    setError("");
    clearSaved();
    try {
      await uploadDocument(call, preview, mock, record.id, kind, uploadFile!);
      changed(`${staffDocumentLabels[kind]} uploaded.`);
    } catch (error) {
      setUploadError(
        error instanceof Error
          ? error.message
          : "The document could not be uploaded. Try again.",
      );
    } finally {
      setBusy(false);
    }
  }
  function startEdit(section: StaffDetailSection) {
    lastEdited.current = section;
    clearSaved();
    setError("");
    setDetailErrors({});
    setDraftDetails(staffDetailValues(record, section));
    setEditing(section);
  }
  async function saveDetails(section: StaffDetailSection) {
    if (busy) return;
    const failures = staffDetailFailures(section, draftDetails),
      visible = staffDetailKeys(section, draftDetails).filter(
        (key) => failures[key],
      );
    setDetailErrors(
      Object.fromEntries(visible.map((key) => [key, failures[key]])),
    );
    if (visible.length) {
      document.getElementById(`detail-${section}-${visible[0]}`)?.focus();
      return;
    }
    setBusy(true);
    setError("");
    clearSaved();
    try {
      await call(`/api/staff/applications/${record.id}`, "PATCH", {
        action: "details",
        value: { revision: record.revision, section, values: draftDetails },
      });
      lastEdited.current = null;
      setEditing(null);
      changed("Details saved.");
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : "The change could not be saved.",
      );
    } finally {
      setBusy(false);
    }
  }
  function details(section: StaffDetailSection) {
    const stored = staffDetailValues(record, section),
      locked = lockedStaffDetails(record, section, stored);
    const present = section === "applicant" || Boolean(record[section]);
    if (!present && !detailsEditable) return null;
    const title =
      section === "applicant" ? null : <h3>{staffDetailTitles[section]}</h3>;
    if (editing !== section) {
      const shown = staffDetailKeys(section, stored).filter(
        (key) => section !== "applicant" || key !== "name",
      );
      return (
        <>
          {title}
          {present ? (
            <dl className="staff-details">
              {shown.map((key) => (
                <div key={key}>
                  <dt>{staffDetailLabel(section, key)}</dt>
                  <dd>
                    {stored[key] || "Not provided"}
                    {locked.includes(key) && (
                      <span className="staff-verified">
                        {" "}
                        · Verified by Myinfo
                      </span>
                    )}
                  </dd>
                </div>
              ))}
            </dl>
          ) : (
            <p>Not recorded for this application.</p>
          )}
          {detailsEditable && (
            <button
              type="button"
              id={`edit-${section}`}
              className="staff-edit"
              disabled={busy || editing !== null}
              onClick={() => startEdit(section)}
            >
              {staffDetailEditLabels[section]}
            </button>
          )}
        </>
      );
    }
    return (
      <>
        {title}
        <form
          ref={editForm}
          className="staff-edit-form"
          noValidate
          aria-label={staffDetailEditLabels[section]}
          onSubmit={(event) => {
            event.preventDefault();
            void saveDetails(section);
          }}
        >
          <fieldset disabled={busy}>
            <DetailFields
              section={section}
              prefix="detail"
              values={draftDetails}
              locked={locked}
              errors={detailErrors}
              onChange={setDraftDetails}
            />
            {error && (
              <p role="alert" className="staff-error">
                {error}
              </p>
            )}
            <p className="staff-muted">
              Save or cancel these changes before making other changes to this
              application.
            </p>
            <div className="staff-filter-actions">
              <button type="submit" className="staff-primary">
                Save changes
              </button>
              <button
                type="button"
                onClick={() => {
                  setError("");
                  setDetailErrors({});
                  setEditing(null);
                }}
              >
                Cancel
              </button>
            </div>
          </fieldset>
        </form>
      </>
    );
  }
  return (
    <>
      {saved && (
        <p
          ref={savedRef}
          className="staff-notice staff-saved"
          role="status"
          tabIndex={-1}
        >
          {saved}
        </p>
      )}
      <div className="staff-card">
        <p className="staff-eyebrow">
          {record.source === "staff"
            ? "ENTERED BY STAFF"
            : record.submittedAt
              ? "SUBMITTED APPLICATION"
              : "UNFINISHED APPLICATION"}
        </p>
        <h2>{record.fields.name}</h2>
        <p className="staff-reference">{record.reference}</p>
        {record.archivedAt && (
          <p className="staff-notice">
            Archived {date(record.archivedAt)}. It is hidden from active
            applications and kept under the retention policy.{" "}
            {record.submittedAt
              ? "Restore it to change its status."
              : "Restoring it, or the applicant completing it, returns it to the active list."}
          </p>
        )}
        <p>
          {record.source === "staff" ? (
            `Entered by staff ${date(record.createdAt)} for an application taken by phone or in person.`
          ) : (
            <>
              Received {date(record.createdAt)}.{" "}
              {record.submittedAt
                ? `Submitted ${date(record.submittedAt)}.`
                : "The applicant has not completed submission."}
            </>
          )}
        </p>
        {details("applicant")}
        {details("particulars")}
        {details("loanContext")}
        {record.declaration && (
          <>
            <h3>Due diligence and declarations</h3>
            <dl className="staff-details">
              {dueDiligenceKeys.map((key) => {
                const detail = (
                  detailFor as Partial<
                    Record<string, "bankruptcyDetails" | "convictionDetails">
                  >
                )[key];
                return (
                  <div key={key}>
                    <dt>{dueDiligenceQuestions[key]}</dt>
                    <dd>
                      {String(record.declaration![key] ?? "Not recorded")}
                      {detail && record.declaration![detail] ? (
                        <>
                          <br />
                          {String(record.declaration![detail])}
                        </>
                      ) : null}
                    </dd>
                  </div>
                );
              })}
              {record.declaration.capture === "paper" ? (
                <div>
                  <dt>Declarations signed on paper</dt>
                  <dd>
                    Version {record.declaration.version}, signed{" "}
                    {String(record.declaration.signedOn)} and recorded by{" "}
                    {String(record.declaration.recordedBy)}
                    {record.declaration.bankruptcy === "Yes"
                      ? "; bankruptcy affirmation not applicable"
                      : ""}
                  </dd>
                </div>
              ) : (
                <div>
                  <dt>Declarations accepted</dt>
                  <dd>
                    Version {record.declaration.version}
                    {record.declaration.bankruptcy === "Yes"
                      ? "; bankruptcy affirmation not applicable"
                      : ""}
                  </dd>
                </div>
              )}
            </dl>
          </>
        )}
        {details("assessment")}
        {record.myinfo && (
          <MyinfoRecords snapshot={record.myinfo} demo={preview} />
        )}
        <h3>Supporting documents</h3>
        {record.documents.length ? (
          <ul className="staff-documents">
            {record.documents.map((document) => (
              <li key={document.kind}>
                <span>
                  <strong>{staffDocumentLabels[document.kind]}</strong>
                  <br />
                  {document.name}
                  {document.uploadedBy && (
                    <>
                      <br />
                      <span className="staff-muted">
                        Uploaded by {document.uploadedBy}
                      </span>
                    </>
                  )}
                </span>
                <button
                  disabled={busy || editing !== null || !document.verified}
                  onClick={() => void download(document.kind)}
                >
                  {document.verified ? "Download" : "Unverified"}
                </button>
              </li>
            ))}
          </ul>
        ) : (
          <p>No documents attached.</p>
        )}
        {documentsAddable && (
          <form
            className="staff-upload"
            noValidate
            onSubmit={(event) => {
              event.preventDefault();
              void addDocument();
            }}
          >
            <fieldset disabled={busy || editing !== null}>
              <legend>Add a document</legend>
              <label htmlFor="document-kind">Document type</label>
              <select
                id="document-kind"
                value={
                  uploadKind && missingKinds.includes(uploadKind)
                    ? uploadKind
                    : missingKinds[0]
                }
                onChange={(event) =>
                  setUploadKind(event.target.value as StaffDocumentKind)
                }
              >
                {missingKinds.map((kind) => (
                  <option key={kind} value={kind}>
                    {staffDocumentLabels[kind]}
                  </option>
                ))}
              </select>
              <label htmlFor="document-file">
                File (PDF, JPG or PNG, up to 10 MB)
              </label>
              <input
                id="document-file"
                type="file"
                accept="application/pdf,image/jpeg,image/png"
                aria-invalid={uploadError ? true : undefined}
                aria-describedby={
                  uploadError ? "document-file-error" : undefined
                }
                onChange={(event) => {
                  setUploadFile(event.target.files?.[0] ?? null);
                  setUploadError("");
                }}
              />
              {uploadError && (
                <p
                  id="document-file-error"
                  role="alert"
                  className="staff-error"
                >
                  {uploadError}
                </p>
              )}
              <button type="submit" className="staff-primary">
                Upload document
              </button>
            </fieldset>
          </form>
        )}
        <p className="staff-muted">
          Download links expire after one minute. Handle downloaded files under
          the same retention policy.
        </p>
      </div>
      <div className="staff-card">
        <h2>Review status</h2>
        <p>
          {record.source === "staff" ? (
            "Notification: not needed, because staff entered this application."
          ) : (
            <>
              Notification: {label(record.notificationState)}
              {record.notificationState === "review"
                ? " — verify delivery manually; automatic retries have stopped."
                : "."}
            </>
          )}
        </p>
        <form
          onSubmit={(event) => {
            event.preventDefault();
            void save("status");
          }}
        >
          <fieldset
            disabled={
              busy ||
              editing !== null ||
              !record.submittedAt ||
              Boolean(record.archivedAt)
            }
          >
            <label htmlFor="review-status">Application status</label>
            <select
              id="review-status"
              value={status}
              onChange={(event) =>
                setStatus(event.target.value as typeof status)
              }
            >
              {applicationStatuses
                .filter((value) => value !== "pending" || status === "pending")
                .map((value) => (
                  <option key={value} value={value}>
                    {label(value)}
                  </option>
                ))}
            </select>
            <button className="staff-primary" type="submit">
              Save status
            </button>
          </fieldset>
        </form>
      </div>
      <div className="staff-card">
        <h2>Assignment and notes</h2>
        <form
          onSubmit={(event) => {
            event.preventDefault();
            void save("assignment");
          }}
        >
          <fieldset
            disabled={
              busy ||
              editing !== null ||
              !team?.me ||
              Boolean(record.archivedAt)
            }
          >
            <label htmlFor="assignee">Assigned to</label>
            <select
              id="assignee"
              value={assignee}
              onChange={(event) => setAssignee(event.target.value)}
            >
              <option value="">Unassigned</option>
              {assignable.map((email) => (
                <option key={email} value={email}>
                  {email === team?.me
                    ? `${email} (you)`
                    : team && !team.team.includes(email)
                      ? `${email} (no longer on the staff list)`
                      : email}
                </option>
              ))}
            </select>
            {team && !team.me && (
              <p className="staff-muted">
                The staff list could not be loaded. Reload the page to change
                the assignment.
              </p>
            )}
            {record.archivedAt && (
              <p className="staff-muted">
                Restore this application to change its assignment.
              </p>
            )}
            <button className="staff-primary" type="submit">
              Save assignment
            </button>
          </fieldset>
        </form>
        <h3>Internal notes</h3>
        <p className="staff-muted">
          Notes are visible to staff only and cannot be edited or deleted. They
          are kept with the application under the retention policy.
        </p>
        {record.notes.length ? (
          <ol className="staff-notes">
            {record.notes.map((item) => (
              <li key={item.id}>
                <p>
                  <strong>{item.author}</strong> · {date(item.at)}
                </p>
                <p className="staff-note-text">{item.text}</p>
              </li>
            ))}
          </ol>
        ) : (
          <p>No notes yet.</p>
        )}
        <form
          onSubmit={(event) => {
            event.preventDefault();
            void save("note");
          }}
        >
          <fieldset
            disabled={
              busy ||
              editing !== null ||
              record.deleting ||
              record.notes.length >= STAFF_NOTE_LIMIT
            }
          >
            <label htmlFor="new-note">Add a note</label>
            <textarea
              id="new-note"
              required
              maxLength={STAFF_NOTE_LENGTH}
              value={note}
              onChange={(event) => setNote(event.target.value)}
              aria-describedby="new-note-count"
            />
            <p id="new-note-count" className="staff-muted">
              {record.notes.length >= STAFF_NOTE_LIMIT
                ? `This application has reached the ${STAFF_NOTE_LIMIT}-note limit.`
                : `${note.length} of ${STAFF_NOTE_LENGTH} characters`}
            </p>
            <button className="staff-primary" type="submit">
              Add note
            </button>
          </fieldset>
        </form>
      </div>
      <div className="staff-card">
        <h2>Retention review</h2>
        <p>
          Five-year statutory lifecycle. Approved records stay retained until
          both the loan and borrower relationship have ended. Holds prevent
          scheduled deletion.
        </p>
        <form
          onSubmit={(event) => {
            event.preventDefault();
            void save("retention");
          }}
        >
          <fieldset disabled={busy || editing !== null}>
            <label className="staff-check">
              <input
                type="checkbox"
                checked={reviewed}
                onChange={(event) => setReviewed(event.target.checked)}
              />
              Record classification reviewed
            </label>
            <label htmlFor="retention-hold">Retention hold</label>
            <select
              id="retention-hold"
              value={hold}
              onChange={(event) => setHold(event.target.value as typeof hold)}
            >
              {[
                "none",
                "investigation",
                "litigation",
                "regulatory",
                "business-review",
              ].map((value) => (
                <option key={value} value={value}>
                  {label(value)}
                </option>
              ))}
            </select>
            {lifecycle.outcome === "approved" && (
              <>
                <label htmlFor="loan-ended">
                  Loan fully repaid or terminated (Singapore date)
                </label>
                <input
                  id="loan-ended"
                  type="date"
                  value={loanEnd}
                  onChange={(event) => setLoanEnd(event.target.value)}
                />
                <label htmlFor="relationship-ended">
                  Borrower relationship ended (Singapore date)
                </label>
                <input
                  id="relationship-ended"
                  type="date"
                  value={relationshipEnd}
                  onChange={(event) => setRelationshipEnd(event.target.value)}
                />
                <p className="staff-muted">
                  Leave dates empty while active. Date-only records use the end
                  of the selected day.
                </p>
              </>
            )}
            <label htmlFor="retention-basis">Review basis</label>
            <textarea
              id="retention-basis"
              required
              maxLength={500}
              value={basis}
              onChange={(event) => setBasis(event.target.value)}
            />
            <label htmlFor="next-review">
              Next hold review (Singapore date)
            </label>
            <input
              id="next-review"
              type="date"
              required={hold !== "none"}
              value={reviewDate}
              onChange={(event) => setReviewDate(event.target.value)}
            />
            <button type="submit" className="staff-primary">
              Save retention review
            </button>
          </fieldset>
        </form>
      </div>
      <div className="staff-card">
        <h2>
          {record.archivedAt ? "Restore application" : "Archive application"}
        </h2>
        <p>
          {record.archivedAt
            ? "Restoring returns this application to the active list."
            : "Archive duplicates and abandoned attempts to hide them from the active list. Archiving does not delete anything: the record stays under the retention policy, and you can restore it at any time."}
        </p>
        <button
          className="staff-primary"
          disabled={busy || editing !== null || record.deleting}
          onClick={() => void save("archive")}
        >
          {record.archivedAt ? "Restore application" : "Archive application"}
        </button>
      </div>
      {error && !editing && (
        <div role="alert" className="staff-card staff-error">
          <p>{error}</p>
          <button onClick={() => changed()}>Reload record</button>
        </div>
      )}
      <div className="staff-card">
        <h2>Change history</h2>
        {record.history.length ? (
          <ol className="staff-history">
            {record.history.map((entry, index) => (
              <li key={index}>
                <p>
                  <strong>{entry.actor}</strong> · {date(entry.at)}
                </p>
                <details>
                  <summary>
                    {entry.field === "status"
                      ? `${label(entry.before)} → ${label(entry.after)}`
                      : entry.field === "archive"
                        ? entry.after === "archived"
                          ? "Archived"
                          : "Restored"
                        : entry.field === "assignment"
                          ? "Assignment changed"
                          : entry.field === "details"
                            ? `Edited ${staffDetailAudit(entry.before)?.label ?? "details"}`
                            : entry.field === "document"
                              ? "Document added"
                              : entry.field === "created"
                                ? "Entered by staff"
                                : "Retention details changed"}
                  </summary>
                  <p>
                    Previous:{" "}
                    {entry.field === "assignment"
                      ? entry.before
                      : entry.field === "details"
                        ? staffDetailAudit(entry.before)?.value ||
                          "Not provided"
                        : entry.field === "document"
                          ? "Not attached"
                          : entry.field === "created"
                            ? "Not recorded"
                            : auditText(entry.before)}
                  </p>
                  <p>
                    Saved:{" "}
                    {entry.field === "assignment" ||
                    entry.field === "document" ||
                    entry.field === "created"
                      ? entry.after
                      : entry.field === "details"
                        ? staffDetailAudit(entry.after)?.value || "Not provided"
                        : auditText(entry.after)}
                  </p>
                </details>
              </li>
            ))}
          </ol>
        ) : (
          <p>No staff changes recorded.</p>
        )}
      </div>
    </>
  );
}
const createdNotice: { id?: string; message?: string } = {};
function StaffCreate({
  call,
  preview,
  mock,
}: {
  call: typeof api;
  preview: boolean;
  mock: boolean;
}) {
  const router = useRouter();
  const [values, setValues] = useState(
    () =>
      Object.fromEntries(
        staffDetailSections.map((section) => [
          section,
          emptyStaffSection(section),
        ]),
      ) as Record<StaffDetailSection, Record<string, string>>,
  );
  const [declaration, setDeclaration] = useState(emptyStaffDeclaration),
    [files, setFiles] = useState<Partial<Record<StaffDocumentKind, File>>>({});
  const [errors, setErrors] = useState<Record<string, string>>({}),
    [busy, setBusy] = useState(false),
    [progress, setProgress] = useState(""),
    [error, setError] = useState("");
  const [requestId] = useState(() => crypto.randomUUID());
  const mounted = useRef(true);
  useEffect(() => {
    mounted.current = true;
    return () => {
      mounted.current = false;
    };
  }, []);
  const within = (section: string) =>
    Object.fromEntries(
      Object.entries(errors)
        .filter(([key]) => key.startsWith(`${section}.`))
        .map(([key, message]) => [key.slice(section.length + 1), message]),
    );
  async function create() {
    if (busy) return;
    const fileFailures = Object.fromEntries(
      Object.entries(files).flatMap(([kind, file]) => {
        const problem = file ? documentError(file) : null;
        return problem ? [[`document.${kind}`, problem]] : [];
      }),
    );
    const failures = {
        ...staffCreateFailures({ ...values, declaration }),
        ...fileFailures,
      },
      first = Object.keys(failures)[0];
    setErrors(failures);
    setError(first ? "Check the highlighted fields and try again." : "");
    if (first) {
      document.getElementById(`create-${first.replace(".", "-")}`)?.focus();
      return;
    }
    setBusy(true);
    setProgress("Creating the application...");
    try {
      let created: StaffView,
        existing = false;
      try {
        created = staffViewSchema.parse(
          await call("/api/staff/applications", "POST", {
            requestId,
            ...values,
            declaration,
          }),
        );
      } catch (error) {
        const found = await call(`/api/staff/applications/${requestId}`)
          .then((result) => staffViewSchema.parse(result))
          .catch(() => null);
        if (!found || found.source !== "staff") throw error;
        created = found;
        existing = true;
      }
      const failed: string[] = [];
      for (const [kind, file] of Object.entries(files) as [
        StaffDocumentKind,
        File,
      ][]) {
        if (
          created.documents.some(
            (document) => document.kind === kind && document.verified,
          )
        )
          continue;
        setProgress(`Uploading ${staffDocumentLabels[kind]}...`);
        try {
          await uploadDocument(call, preview, mock, created.id, kind, file);
        } catch {
          failed.push(staffDocumentLabels[kind]);
        }
      }
      if (!mounted.current) return;
      const outcome = existing
        ? "This application had already been created, so no duplicate was made. Check its details and correct anything that changed."
        : "Application created.";
      createdNotice.id = created.id;
      createdNotice.message = failed.length
        ? `${outcome} ${failed.join(", ")} could not be uploaded. Add ${failed.length === 1 ? "it" : "them"} under Supporting documents.`
        : outcome;
      router.push(`/staff/${created.id}`);
    } catch (error) {
      if (!mounted.current) return;
      setError(
        error instanceof Error
          ? error.message
          : "The application could not be created. Try again.",
      );
      setProgress("");
      setBusy(false);
    }
  }
  const answer = (key: string) => (event: { target: { value: string } }) =>
    setDeclaration((current) => ({
      ...current,
      answers: {
        ...current.answers,
        [key]: event.target.value,
        ...(key === "bankruptcy" && event.target.value !== "Yes"
          ? { bankruptcyDetails: "" }
          : {}),
        ...(key === "convicted" && event.target.value !== "Yes"
          ? { convictionDetails: "" }
          : {}),
      },
    }));
  const declarationErrors = within("declaration");
  const described = (id: string, message?: string) => ({
    "aria-invalid": message ? true : undefined,
    "aria-describedby": message ? `${id}-error` : undefined,
  });
  const shown = (id: string, message?: string) =>
    message ? (
      <p id={`${id}-error`} className="staff-error">
        {message}
      </p>
    ) : null;
  return (
    <>
      {busy ? (
        <span className="staff-back staff-link-disabled" aria-disabled="true">
          Back to applications
        </span>
      ) : (
        <Link className="staff-back" href="/staff">
          Back to applications
        </Link>
      )}
      <div className="staff-card">
        <h2>New application</h2>
        <p>
          Enter an application taken by phone or in person. The applicant signs
          MaxCredit&apos;s declaration form on paper; upload the signed form
          with the documents.
        </p>
        <form
          className="staff-create"
          noValidate
          onSubmit={(event) => {
            event.preventDefault();
            void create();
          }}
        >
          <fieldset disabled={busy}>
            {staffDetailSections.map((section) => (
              <section
                key={section}
                aria-labelledby={`create-${section}-heading`}
              >
                <h3 id={`create-${section}-heading`}>
                  {staffDetailTitles[section]}
                </h3>
                <DetailFields
                  section={section}
                  prefix="create"
                  values={values[section]}
                  errors={within(section)}
                  onChange={(update) =>
                    setValues((current) => ({
                      ...current,
                      [section]: update(current[section]),
                    }))
                  }
                />
              </section>
            ))}
            <section aria-labelledby="create-declaration-heading">
              <h3 id="create-declaration-heading">
                Due diligence and declarations
              </h3>
              {dueDiligenceKeys.map((key) => {
                const id = `create-declaration-${key}`,
                  detail = (
                    detailFor as Partial<
                      Record<string, "bankruptcyDetails" | "convictionDetails">
                    >
                  )[key],
                  detailId = `create-declaration-${detail}`;
                return (
                  <div key={key}>
                    <label htmlFor={id}>{dueDiligenceQuestions[key]}</label>
                    <select
                      id={id}
                      value={declaration.answers[key]}
                      {...described(id, declarationErrors[key])}
                      onChange={answer(key)}
                    >
                      <option value="">Choose Yes or No</option>
                      <option value="Yes">Yes</option>
                      <option value="No">No</option>
                    </select>
                    {shown(id, declarationErrors[key])}
                    {detail && declaration.answers[key] === "Yes" && (
                      <>
                        <label htmlFor={detailId}>Give details</label>
                        <textarea
                          id={detailId}
                          maxLength={300}
                          value={declaration.answers[detail]}
                          {...described(detailId, declarationErrors[detail])}
                          onChange={answer(detail)}
                        />
                        {shown(detailId, declarationErrors[detail])}
                      </>
                    )}
                  </div>
                );
              })}
              <label
                className="staff-check"
                htmlFor="create-declaration-signed"
              >
                <input
                  id="create-declaration-signed"
                  type="checkbox"
                  checked={declaration.signed}
                  {...described(
                    "create-declaration-signed",
                    declarationErrors.signed,
                  )}
                  onChange={(event) =>
                    setDeclaration((current) => ({
                      ...current,
                      signed: event.target.checked,
                    }))
                  }
                />
                The applicant signed MaxCredit&apos;s application declarations
                (version {DECLARATION_VERSION}) on paper.
              </label>
              {shown("create-declaration-signed", declarationErrors.signed)}
              <label htmlFor="create-declaration-signedOn">
                Date signed (Singapore date)
              </label>
              <input
                id="create-declaration-signedOn"
                type="date"
                max={singaporeDate()}
                value={declaration.signedOn}
                {...described(
                  "create-declaration-signedOn",
                  declarationErrors.signedOn,
                )}
                onChange={(event) =>
                  setDeclaration((current) => ({
                    ...current,
                    signedOn: event.target.value,
                  }))
                }
              />
              {shown("create-declaration-signedOn", declarationErrors.signedOn)}
            </section>
            <section aria-labelledby="create-documents-heading">
              <h3 id="create-documents-heading">Documents (optional)</h3>
              <p className="staff-muted">
                PDF, JPG or PNG, up to 10 MB each. You can also add documents
                after creating the application.
              </p>
              {staffDocumentKinds.map((kind) => {
                const id = `create-document-${kind}`;
                return (
                  <div key={kind}>
                    <label htmlFor={id}>{staffDocumentLabels[kind]}</label>
                    <input
                      id={id}
                      type="file"
                      accept="application/pdf,image/jpeg,image/png"
                      {...described(id, errors[`document.${kind}`])}
                      onChange={(event) => {
                        const file = event.target.files?.[0];
                        setFiles((current) => {
                          const next = { ...current };
                          if (file) next[kind] = file;
                          else delete next[kind];
                          return next;
                        });
                      }}
                    />
                    {shown(id, errors[`document.${kind}`])}
                  </div>
                );
              })}
            </section>
            {error && (
              <p role="alert" className="staff-error">
                {error}
              </p>
            )}
            <p className="staff-muted" role="status">
              {progress}
            </p>
            <div className="staff-filter-actions">
              <button type="submit" className="staff-primary">
                Create application
              </button>
              {busy ? (
                <span className="staff-link-disabled" aria-disabled="true">
                  Cancel
                </span>
              ) : (
                <Link href="/staff">Cancel</Link>
              )}
            </div>
          </fieldset>
        </form>
      </div>
    </>
  );
}
const remembered: { search: Omit<StaffSearch, "cursor"> } = {
  search: emptyStaffSearch,
};
function StaffList({
  call,
  team,
}: {
  call: typeof api;
  team: StaffTeam | null;
}) {
  const [draft, setDraft] = useState(remembered.search),
    [search, setSearch] = useState(remembered.search);
  const [cursor, setCursor] = useState<StaffSearch["cursor"]>(null),
    [reload, setReload] = useState(0);
  const [results, setResults] = useState<{
    key: string;
    records: StaffListItem[];
    next: StaffSearch["cursor"];
    scanned: number;
  } | null>(null);
  const [failure, setFailure] = useState<{
    key: string;
    message: string;
  } | null>(null);
  const key = JSON.stringify({ search, cursor, reload });
  useEffect(() => {
    let active = true;
    call("/api/staff/applications/search", "POST", { ...search, cursor })
      .then((result) => {
        if (!active) return;
        const page = staffSearchResultSchema.parse(result);
        setResults((current) => ({
          key,
          records:
            cursor && current
              ? [...current.records, ...page.records]
              : page.records,
          next: page.next,
          scanned: (cursor && current ? current.scanned : 0) + page.scanned,
        }));
        setFailure(null);
      })
      .catch((error) => {
        if (active)
          setFailure({
            key,
            message:
              error instanceof Error
                ? error.message
                : "The applications could not be loaded.",
          });
      });
    return () => {
      active = false;
    };
  }, [call, search, cursor, reload, key]);
  const list = useRef<HTMLUListElement>(null),
    summaryRef = useRef<HTMLParagraphElement>(null),
    focusFrom = useRef<number | null>(null);
  useEffect(() => {
    if (focusFrom.current === null || results?.key !== key) return;
    const link = list.current?.querySelectorAll("a")[focusFrom.current];
    (link ?? summaryRef.current)?.focus();
    focusFrom.current = null;
  }, [results, key]);
  function apply(value: Omit<StaffSearch, "cursor">) {
    focusFrom.current = null;
    remembered.search = value;
    setDraft(value);
    setCursor(null);
    setSearch(value);
  }
  const failed = failure?.key === key,
    loading = results?.key !== key && !failed;
  const visible = results !== null && (results.key === key || cursor !== null),
    shown = visible ? results.records : [],
    next = visible && !failed ? results.next : null;
  const filtered =
    search.view !== "active" ||
    search.assignee !== "all" ||
    search.status !== "all" ||
    search.interest !== "all" ||
    Boolean(search.from || search.to) ||
    hasSearchWords(search.query);
  const summary = loading
    ? cursor
      ? "Loading more applications..."
      : "Searching applications..."
    : failed || !results
      ? ""
      : shown.length
        ? `Showing ${shown.length} ${shown.length === 1 ? "application" : "applications"}${next && filtered ? ` from the ${results.scanned} searched so far. Load more to keep searching` : ""}.`
        : next
          ? `No matches in the ${results.scanned} applications searched so far. Load more to keep searching.`
          : filtered
            ? "No applications match these filters."
            : "No applications to review.";
  const set =
    <K extends keyof StaffSearch>(name: K) =>
    (event: { target: { value: string } }) =>
      setDraft((current) => ({ ...current, [name]: event.target.value }));
  return (
    <div className="staff-card">
      <div className="staff-card-heading">
        <h2>Received applications</h2>
        <Link className="staff-new" href="/staff/new">
          New application
        </Link>
      </div>
      <p>
        Submitted applications and unfinished attempts are available for review.
      </p>
      <form
        className="staff-filters"
        role="search"
        aria-label="Applications"
        onSubmit={(event) => {
          event.preventDefault();
          apply({ ...draft, query: draft.query.trim() });
        }}
      >
        <div className="staff-filter-wide">
          <label htmlFor="staff-search">
            Search by name, reference, NRIC or FIN, phone or email
          </label>
          <input
            id="staff-search"
            type="search"
            autoComplete="off"
            spellCheck={false}
            maxLength={100}
            value={draft.query}
            onChange={set("query")}
          />
        </div>
        <div>
          <label htmlFor="staff-view">Show</label>
          <select id="staff-view" value={draft.view} onChange={set("view")}>
            <option value="active">Active applications</option>
            <option value="archived">Archived applications</option>
            <option value="all">Active and archived</option>
          </select>
        </div>
        <div>
          <label htmlFor="staff-assignee">Assigned to</label>
          <select
            id="staff-assignee"
            value={draft.assignee}
            onChange={set("assignee")}
          >
            <option value="all">Anyone</option>
            <option value="unassigned">Unassigned</option>
            {team?.me && <option value={team.me}>Me</option>}
            {[
              ...new Set([
                ...(team?.team ?? []),
                ...(draft.assignee.includes("@") ? [draft.assignee] : []),
              ]),
            ]
              .filter((email) => email !== team?.me)
              .map((email) => (
                <option key={email} value={email}>
                  {email}
                </option>
              ))}
          </select>
        </div>
        <div>
          <label htmlFor="staff-status">Status</label>
          <select
            id="staff-status"
            value={draft.status}
            onChange={set("status")}
          >
            <option value="all">All statuses</option>
            {applicationStatuses.map((value) => (
              <option key={value} value={value}>
                {value === "pending" ? "pending (unfinished)" : label(value)}
              </option>
            ))}
          </select>
        </div>
        <div>
          <label htmlFor="staff-interest">Loan type</label>
          <select
            id="staff-interest"
            value={draft.interest}
            onChange={set("interest")}
          >
            <option value="all">All loan types</option>
            {interests.map((value) => (
              <option key={value} value={value}>
                {value}
              </option>
            ))}
          </select>
        </div>
        <div>
          <label htmlFor="staff-order">Sort</label>
          <select id="staff-order" value={draft.order} onChange={set("order")}>
            <option value="newest">Newest first</option>
            <option value="oldest">Oldest first</option>
          </select>
        </div>
        <div>
          <label htmlFor="staff-from">Received from (Singapore date)</label>
          <input
            id="staff-from"
            type="date"
            max={draft.to || undefined}
            value={draft.from}
            onChange={set("from")}
          />
        </div>
        <div>
          <label htmlFor="staff-to">Received to (Singapore date)</label>
          <input
            id="staff-to"
            type="date"
            min={draft.from || undefined}
            value={draft.to}
            onChange={set("to")}
          />
        </div>
        <div className="staff-filter-wide staff-filter-actions">
          <button type="submit" className="staff-primary">
            Search
          </button>
          <button type="button" onClick={() => apply(emptyStaffSearch)}>
            Clear filters
          </button>
        </div>
      </form>
      {failed && (
        <p className="staff-error" role="alert">
          {failure.message}{" "}
          <button onClick={() => setReload((value) => value + 1)}>Retry</button>
        </p>
      )}
      <p className="staff-muted" role="status" tabIndex={-1} ref={summaryRef}>
        {summary}
      </p>
      {shown.length > 0 && (
        <ul className="staff-list" ref={list}>
          {shown.map((item) => (
            <li key={item.id}>
              <div>
                <Link href={`/staff/${item.id}`}>{item.name}</Link>
                <p className="staff-reference">{item.reference}</p>
                <p>
                  {item.interest} · {date(item.createdAt)}
                  {item.assignee
                    ? ` · Assigned to ${item.assignee === team?.me ? "you" : item.assignee}`
                    : ""}
                </p>
              </div>
              <span className="staff-badge">
                {item.deleting
                  ? "deleting"
                  : item.archived
                    ? `archived · ${label(item.status)}`
                    : label(item.status)}
              </span>
            </li>
          ))}
        </ul>
      )}
      {next && (
        <div className="staff-pagination">
          <button
            aria-disabled={loading}
            onClick={() => {
              if (loading) return;
              focusFrom.current = shown.length;
              setCursor(next);
            }}
          >
            {loading ? "Loading more..." : "Load more"}
          </button>
        </div>
      )}
    </div>
  );
}
export function StaffDesk({
  id,
  create = false,
  preview = false,
  mock = false,
}: {
  id?: string;
  create?: boolean;
  preview?: boolean;
  mock?: boolean;
}) {
  const call = preview ? previewApi : api;
  const [record, setRecord] = useState<StaffView | null>(null),
    [reload, setReload] = useState(0),
    [error, setError] = useState("");
  const [team, setTeam] = useState<StaffTeam | null>(null);
  useEffect(() => {
    let active = true;
    call("/api/staff/team")
      .then((result) => {
        if (active) setTeam(staffTeamSchema.parse(result));
      })
      .catch(() => {
        if (active) setTeam({ me: "", team: [] });
      });
    return () => {
      active = false;
    };
  }, [call]);
  const pendingSaved = useRef("");
  const [saved, setSaved] = useState<{
    id?: string;
    message: string;
    count: number;
  }>({ message: "", count: 0 });
  const clearSaved = useCallback(
    () =>
      setSaved((current) =>
        current.message ? { message: "", count: current.count } : current,
      ),
    [],
  );
  const refresh = useCallback(
    (message = "") => {
      pendingSaved.current = message;
      clearSaved();
      setReload((value) => value + 1);
    },
    [clearSaved],
  );
  useEffect(() => {
    if (!id) return;
    let active = true;
    call(`/api/staff/applications/${id}`)
      .then((result) => {
        if (!active) return;
        setRecord(staffViewSchema.parse(result));
        setError("");
        const message =
          pendingSaved.current ||
          (createdNotice.id === id ? (createdNotice.message ?? "") : "");
        pendingSaved.current = "";
        if (createdNotice.id === id) {
          createdNotice.id = undefined;
          createdNotice.message = undefined;
        }
        if (message)
          setSaved((current) => ({ id, message, count: current.count + 1 }));
      })
      .catch((error) => {
        if (active) {
          pendingSaved.current = "";
          setRecord(null);
          setError(
            error instanceof Error
              ? error.message
              : "The application could not be loaded.",
          );
        }
      });
    return () => {
      active = false;
    };
  }, [call, id, reload]);
  async function logout() {
    try {
      await call("/api/staff/session", "DELETE", {});
      window.location.replace("/staff/login");
    } catch {
      setError("Sign-out could not be confirmed. Please retry.");
    }
  }
  return (
    <>
      <header className="staff-heading">
        <div>
          <p className="staff-eyebrow">MAXCREDIT / STAFF</p>
          <h1>Application desk</h1>
        </div>
        <button onClick={() => void logout()}>Sign out</button>
      </header>
      {preview && (
        <p className="staff-notice">
          Preview · Sample applications only. Nothing is sent to MaxCredit, and
          changes reset when you reload.
        </p>
      )}
      {id && (
        <Link className="staff-back" href="/staff">
          Back to applications
        </Link>
      )}
      {error && (
        <p className="staff-card staff-error" role="alert">
          {error} {id && <button onClick={() => refresh()}>Retry</button>}
        </p>
      )}
      {create ? (
        <StaffCreate call={call} preview={preview} mock={mock} />
      ) : id ? (
        record ? (
          <StaffRecord
            key={`${record.id}-${record.revision}`}
            record={record}
            team={team}
            changed={refresh}
            saved={saved.id === id ? saved.message : ""}
            savedCount={saved.count}
            clearSaved={clearSaved}
            preview={preview}
            mock={mock}
          />
        ) : (
          !error && <p role="status">Loading application...</p>
        )
      ) : (
        <StaffList call={call} team={team} />
      )}
    </>
  );
}
