/* Spend controls and the team — the two settings screens the cards and expenses
   destinations brought with them.

   Commit model: STAGED, like every settings screen in this product. Every
   control is a form field, nothing applies until Save, Save and Cancel are
   disabled until the form is dirty, and errors render on validation.

   Spend controls is policy, not preference: the numbers on it are the ones
   EXPENSE_FLAGS and CARD_FLAGS read, so the panel under the fields runs the
   real predicates over the real expenses with the DRAFT values, and saving
   really does move the sidebar badge. */
(() => {
  const {
    Card, CardHeader, CardTitle, CardDescription, CardPanel, CardFooter,
    Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
    Field, FieldLabel, FieldDescription, FieldError, Input, InputGroup, InputGroupAddon, InputGroupText, InputGroupInput,
    Select, Checkbox, Badge, Avatar,
  } = window.OriginDesignSystem_7c998b;

  const toCents = (text) => Math.round(Number(String(text).replace(/[^0-9.]/g, "")) * 100);
  const toDollars = (cents) => String(Math.round(cents) / 100);

  function SpendControlsScreen({ policy, data, now, onSave }) {
    const initial = React.useMemo(() => ({
      receipt: toDollars(policy.receiptThresholdCents),
      receiptCategories: policy.receiptCategories.slice(),
      cardWarnShare: String(policy.cardWarnShare),
      cardDefaultLimit: toDollars(policy.cardDefaultLimitCents),
    }), [policy]);
    const [draft, setDraft] = React.useState(initial);
    const [submitted, setSubmitted] = React.useState(false);
    React.useEffect(() => { setDraft(initial); setSubmitted(false); }, [initial]);
    const set = (fields) => setDraft((d) => Object.assign({}, d, fields));
    const dirty = JSON.stringify(draft) !== JSON.stringify(initial);

    const errors = {};
    const receiptCents = toCents(draft.receipt);
    const limitCents = toCents(draft.cardDefaultLimit);
    if (!(receiptCents >= 1000 && receiptCents <= 100000)) errors.receipt = "Between $10 and $1,000 — below ten dollars every coffee needs a receipt.";
    if (!(limitCents >= 10000)) errors.cardDefaultLimit = "At least $100, or a new card cannot buy anything.";
    const valid = Object.keys(errors).length === 0;

    /* The preview runs the real predicates, with the draft policy. */
    const drafted = { receiptThresholdCents: receiptCents, receiptCategories: draft.receiptCategories, cardWarnShare: Number(draft.cardWarnShare), cardDefaultLimitCents: limitCents };
    const waiting = expensesAwaitingReview(data.expenses);
    const owedNow = expensesMissingReceipt(waiting, policy);
    const owedDraft = valid ? expensesMissingReceipt(waiting, drafted) : owedNow;
    const approvableNow = reviewableExpenses(data.expenses, policy);
    const approvableDraft = valid ? reviewableExpenses(data.expenses, drafted) : approvableNow;
    const riskNow = cardsAtRisk(data.cards, data.expenses, now, policy);
    const riskDraft = valid ? cardsAtRisk(data.cards, data.expenses, now, drafted) : riskNow;

    return (
      <div className="fd-page">
        <PageHeader path="/settings/spend-controls" title="Spend controls" />
        <Card>
          <CardHeader>
            <CardTitle>What the workspace asks of a card expense</CardTitle>
            <CardDescription>These are the numbers the expense queue and the card meters read. Changing them changes what is waiting on you, not what has already been approved.</CardDescription>
          </CardHeader>
          <CardPanel>
            <div className="fd-fields">
              <Field>
                <FieldLabel htmlFor="fd-pol-receipt">Receipt required above</FieldLabel>
                <InputGroup data-invalid={submitted && !!errors.receipt ? "" : undefined}>
                  <InputGroupAddon><InputGroupText>$</InputGroupText></InputGroupAddon>
                  <InputGroupInput id="fd-pol-receipt" inputMode="decimal" value={draft.receipt} aria-invalid={(submitted && !!errors.receipt) || undefined} onChange={(e) => set({ receipt: e.target.value })} />
                </InputGroup>
                {submitted && errors.receipt ? <FieldError>{errors.receipt}</FieldError> : <FieldDescription>An expense at or above this needs its receipt attached before anybody can approve it.</FieldDescription>}
              </Field>
              <Field>
                <FieldLabel>Always need a receipt, whatever the amount</FieldLabel>
                <div className="fd-checks">
                  {EXPENSE_CATEGORIES.map((category) => (
                    <label className="fd-check fd-check--inline" key={category.value}>
                      <Checkbox
                        checked={draft.receiptCategories.indexOf(category.value) > -1}
                        onCheckedChange={() => set({ receiptCategories: draft.receiptCategories.indexOf(category.value) > -1 ? draft.receiptCategories.filter((v) => v !== category.value) : draft.receiptCategories.concat([category.value]) })}
                      />
                      <span className="fd-check__text"><span>{category.label}</span></span>
                    </label>
                  ))}
                </div>
                <FieldDescription>Checkboxes, because they are saved with the rest of this form rather than one at a time.</FieldDescription>
              </Field>
              <Field>
                <FieldLabel htmlFor="fd-pol-warn">Warn when a card reaches</FieldLabel>
                <Select
                  id="fd-pol-warn"
                  items={[{ value: "0.7", label: "70% of its limit" }, { value: "0.8", label: "80% of its limit" }, { value: "0.9", label: "90% of its limit" }]}
                  value={draft.cardWarnShare}
                  onValueChange={(value) => set({ cardWarnShare: value })}
                />
                <FieldDescription>Measured over the same rolling {CARD_WINDOW_DAYS} days the limit is.</FieldDescription>
              </Field>
              <Field>
                <FieldLabel htmlFor="fd-pol-limit">Default limit for a new card</FieldLabel>
                <InputGroup data-invalid={submitted && !!errors.cardDefaultLimit ? "" : undefined}>
                  <InputGroupAddon><InputGroupText>$</InputGroupText></InputGroupAddon>
                  <InputGroupInput id="fd-pol-limit" inputMode="decimal" value={draft.cardDefaultLimit} aria-invalid={(submitted && !!errors.cardDefaultLimit) || undefined} onChange={(e) => set({ cardDefaultLimit: e.target.value })} />
                </InputGroup>
                {submitted && errors.cardDefaultLimit ? <FieldError>{errors.cardDefaultLimit}</FieldError> : <FieldDescription>Per {CARD_WINDOW_DAYS} days. It is filled in for you when a card is issued, and can be changed there.</FieldDescription>}
              </Field>
            </div>
          </CardPanel>
          <CardFooter className="fd-card-footer">
            <span className="fd-footnote">
              {dirty && valid
                ? "At these settings: " + approvableDraft.length + " expenses could be approved now (" + approvableNow.length + " today), " + owedDraft.length + " would be waiting on a receipt (" + owedNow.length + " today), and " + riskDraft.length + " cards would be flagged near their limit (" + riskNow.length + " today)."
                : "Right now: " + approvableNow.length + " expenses can be approved, " + owedNow.length + " are waiting on a receipt, and " + riskNow.length + " cards are flagged near their limit."}
            </span>
          </CardFooter>
        </Card>
        <FormBar
          dirty={dirty}
          note={dirty ? "Unsaved changes. Saving recounts the expenses badge in the sidebar." : "Everything here is as it was last saved."}
          onCancel={() => { setDraft(initial); setSubmitted(false); }}
          onSave={() => { setSubmitted(true); if (valid) onSave(drafted); }}
          label="Save spend controls"
        />
      </div>
    );
  }

  function TeamScreen({ members, signedInId, data, onSave }) {
    const initial = React.useMemo(() => members.map((m) => ({ id: m.id, teamRole: m.teamRole, approvalLimit: toDollars(m.approvalLimit), canIssueCards: m.canIssueCards })), [members]);
    const [draft, setDraft] = React.useState(initial);
    React.useEffect(() => { setDraft(initial); }, [initial]);
    const dirty = JSON.stringify(draft) !== JSON.stringify(initial);
    const set = (id, fields) => setDraft((rows) => rows.map((r) => (r.id === id ? Object.assign({}, r, fields) : r)));
    const rowOf = (id) => draft.find((r) => r.id === id) || initial.find((r) => r.id === id);
    const approvers = draft.filter((r) => r.teamRole !== "member");
    const waiting = awaitingApprovalPayouts(data.payouts);
    /* The preview runs the real predicate over the real payouts with the DRAFT
       limit, so the form says what saving it would do. */
    const draftLimit = toCents((rowOf(signedInId) || {}).approvalLimit) || 0;
    const overYours = waiting.filter((p) => p.amount > draftLimit);

    return (
      <div className="fd-page">
        <PageHeader path="/settings/team" title="Team and approvals" />
        <Card>
          <CardHeader>
            <CardTitle>Who can approve what in {MERIDIAN_WORKSPACE.name}</CardTitle>
            <CardDescription>A role decides what somebody sees; the limit decides how much they can release on their own. Cards are issued by admins.</CardDescription>
          </CardHeader>
          <div className="fd-card-table">
            <Table variant="card">
              <TableHeader>
                <TableRow>
                  <TableHead>Person</TableHead>
                  <TableHead>Role</TableHead>
                  <TableHead className="fd-drop-md">Approves up to</TableHead>
                  <TableHead className="fd-drop-lg">Can issue cards</TableHead>
                  <TableHead className="fd-num">Cards held</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {members.map((member) => {
                  const row = rowOf(member.id);
                  const isSelf = member.id === signedInId;
                  const approves = row.teamRole !== "member";
                  const held = liveCards(data.cards).filter((c) => c.holderId === member.id);
                  return (
                    <TableRow key={member.id}>
                      <TableCell>
                        <span className="fd-holder">
                          <Avatar size={28} src={member.avatar} alt="" fallback={member.name.slice(0, 1)} />
                          <span className="fd-record">
                            <span className="fd-record__name">{member.name}{isSelf ? " (you)" : ""}</span>
                            <span className="fd-record__meta">{member.email}</span>
                          </span>
                        </span>
                      </TableCell>
                      <TableCell>
                        {isSelf ? (
                          <FloatingTip side="bottom" label="You cannot change your own role — ask another admin.">
                            <span className="fd-status"><Badge size="sm" variant="secondary">{labelOf(TEAM_ROLES, row.teamRole)}</Badge></span>
                          </FloatingTip>
                        ) : (
                          <span className="fd-cell-control">
                            <Select size="sm" items={TEAM_ROLES.map((r) => ({ value: r.value, label: r.label }))} value={row.teamRole}
                              onValueChange={(value) => set(member.id, { teamRole: value, approvalLimit: value === "member" ? "0" : row.approvalLimit, canIssueCards: value === "admin" ? row.canIssueCards : false })} />
                          </span>
                        )}
                        <span className="fd-sub">{(TEAM_ROLES.find((r) => r.value === row.teamRole) || {}).note}</span>
                      </TableCell>
                      <TableCell className="fd-drop-md">
                        {approves ? (
                          <span className="fd-cell-control">
                            <InputGroup className="o-input--sm">
                              <InputGroupAddon><InputGroupText>$</InputGroupText></InputGroupAddon>
                              <InputGroupInput inputMode="decimal" aria-label={"Approval limit for " + member.name} value={row.approvalLimit} onChange={(e) => set(member.id, { approvalLimit: e.target.value })} />
                            </InputGroup>
                          </span>
                        ) : (
                          <span className="fd-waiting">A member approves nothing.</span>
                        )}
                      </TableCell>
                      <TableCell className="fd-drop-lg">
                        {row.teamRole === "admin" ? (
                          <Checkbox checked={row.canIssueCards} aria-label={"Let " + member.name + " issue cards"} onCheckedChange={(checked) => set(member.id, { canIssueCards: !!checked })} />
                        ) : (
                          <FloatingTip side="bottom" label="Only an admin can issue cards. Change the role first.">
                            <span className="fd-waiting">Admins only</span>
                          </FloatingTip>
                        )}
                      </TableCell>
                      <TableCell className="fd-num">
                        {held.length ? held.map((c) => c.label).join(", ") : <span className="fd-muted">—</span>}
                      </TableCell>
                    </TableRow>
                  );
                })}
              </TableBody>
            </Table>
          </div>
          <CardFooter className="fd-card-footer">
            <span className="fd-footnote">
              {approvers.length} of {members.length} people can approve something, together up to {formatMoney(approvers.reduce((total, r) => total + toCents(r.approvalLimit), 0))} each run. {waiting.length} payouts are waiting on approval right now{overYours.length ? ", and " + overYours.length + " of them " + (overYours.length === 1 ? "is" : "are") + " above your own " + formatMoney(draftLimit) + " limit" : ", every one of them inside your own limit"}.
            </span>
          </CardFooter>
        </Card>
        <FormBar
          dirty={dirty}
          note={dirty ? "Unsaved changes to roles and limits." : "Everything here is as it was last saved."}
          onCancel={() => setDraft(initial)}
          onSave={() => onSave(draft.map((r) => ({ id: r.id, teamRole: r.teamRole, approvalLimit: toCents(r.approvalLimit) || 0, canIssueCards: r.canIssueCards })))}
          label="Save team"
        />
      </div>
    );
  }

  Object.assign(window, { SpendControlsScreen, TeamScreen });
})();
