/*
 This file is part of GNU Taler
 (C) 2022-2025 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

import {
  AbsoluteTime,
  AmountJson,
  Amounts,
  AmountString,
  assertUnreachable,
  DenomLossEventType,
  Duration,
  MerchantInfo,
  NotificationType,
  OrderShortInfo,
  parsePaytoUri,
  PaytoUri,
  ScopeInfo,
  stringifyPaytoUri,
  TalerErrorCode,
  TalerPreciseTimestamp,
  Transaction,
  TransactionAction,
  TransactionDeposit,
  TransactionIdStr,
  TransactionInternalWithdrawal,
  TransactionMajorState,
  TransactionMinorState,
  TransactionType,
  TransactionWithdrawal,
  TranslatedString,
  WithdrawalType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { styled } from "@linaria/react";
import { isPast } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js";
import { EnabledBySettings } from "../components/EnabledBySettings.js";
import { Loading } from "../components/Loading.js";
import { Kind, Part, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js";
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
import {
  CenteredDialog,
  ErrorBox,
  InfoBox,
  Link,
  Overlay,
  SmallLightText,
  SubTitle,
  SvgIcon,
} from "../components/styled/index.js";
import { Time } from "../components/Time.js";
import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useSettings } from "../hooks/useSettings.js";
import { Button } from "../mui/Button.js";
import { SafeHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js";
import refreshIcon from "../svg/refresh_24px.inline.svg";

interface Props {
  tid: string;
  goToWalletHistory: (scope?: ScopeInfo) => Promise<void>;
  onForwardToContact?: (id: TransactionIdStr) => Promise<void>;
}

export function TransactionPage({
  tid,
  goToWalletHistory,
  onForwardToContact,
}: Props): VNode {
  const transactionId = tid as TransactionIdStr; //FIXME: validate
  const { i18n } = useTranslationContext();
  const api = useBackendContext();
  const state = useAsyncAsHook(
    () =>
      api.wallet.call(WalletApiOperation.GetTransactionById, {
        transactionId,
      }),
    [transactionId],
  );

  useEffect(() =>
    api.listener.onUpdateNotification(
      [NotificationType.TransactionStateTransition],
      state?.retry,
    ),
  );

  if (!state) {
    return <Loading />;
  }

  if (state.hasError) {
    return (
      <ErrorAlertView
        error={alertFromError(
          i18n,
          i18n.str`Could not load transaction information`,
          state,
        )}
      />
    );
  }

  const txScope = !state.response.scopes.length
    ? undefined
    : state.response.scopes[0];

  return (
    <TransactionView
      transaction={state.response}
      onCancel={async () => {
        await api.wallet.call(WalletApiOperation.FailTransaction, {
          transactionId,
        });
        // goToWalletHistory(txScope);
      }}
      onSuspend={async () => {
        await api.wallet.call(WalletApiOperation.SuspendTransaction, {
          transactionId,
        });
        // goToWalletHistory(txScope);
      }}
      onResume={async () => {
        await api.wallet.call(WalletApiOperation.ResumeTransaction, {
          transactionId,
        });
        // goToWalletHistory(txScope);
      }}
      onAbort={async () => {
        await api.wallet.call(WalletApiOperation.AbortTransaction, {
          transactionId,
        });
        // goToWalletHistory(txScope);
      }}
      onRetry={async () => {
        await api.wallet.call(WalletApiOperation.RetryTransaction, {
          transactionId,
        });
        // goToWalletHistory(txScope);
      }}
      onDelete={async () => {
        await api.wallet.call(WalletApiOperation.DeleteTransaction, {
          transactionId,
        });
        goToWalletHistory(txScope);
      }}
      onRefund={async (transactionId) => {
        await api.wallet.call(WalletApiOperation.StartRefundQuery, {
          transactionId,
        });
      }}
      onBack={() => goToWalletHistory(txScope)}
      onForwardToContact={onForwardToContact}
    />
  );
}

export interface WalletTransactionProps {
  transaction: Transaction;
  onCancel: () => Promise<void>;
  onSuspend: () => Promise<void>;
  onResume: () => Promise<void>;
  onAbort: () => Promise<void>;
  onDelete: () => Promise<void>;
  onRetry: () => Promise<void>;
  onRefund: (id: TransactionIdStr) => Promise<void>;
  onBack: () => Promise<void>;
  onForwardToContact?: (id: TransactionIdStr) => Promise<void>;
}

const PurchaseDetailsTable = styled.table`
  width: 100%;

  & > tr > td:nth-child(2n) {
    text-align: right;
  }
`;

type TransactionTemplateProps = Omit<
  Omit<WalletTransactionProps, "onRefund">,
  "onBack"
> & {
  children: ComponentChildren;
};

function TransactionTemplate({
  transaction,
  onDelete,
  onRetry,
  onAbort,
  onResume,
  onSuspend,
  onCancel,
  children,
}: TransactionTemplateProps): VNode {
  const { i18n } = useTranslationContext();
  const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
  const [confirmBeforeCancel, setConfirmBeforeCancel] = useState(false);
  const { safely } = useAlertContext();
  const [settings] = useSettings();

  async function doCheckBeforeForget(): Promise<void> {
    if (
      transaction.txState.major === TransactionMajorState.Pending &&
      transaction.type === TransactionType.Withdrawal
    ) {
      setConfirmBeforeForget(true);
    } else {
      onDelete();
    }
  }

  async function doCheckBeforeCancel(): Promise<void> {
    setConfirmBeforeCancel(true);
  }

  const showButton = getShowButtonStates(transaction);

  let pendingOrKycInfo: VNode | undefined = undefined;

  switch (transaction.txState.major) {
    case TransactionMajorState.Pending: {
      switch (transaction.txState.minor) {
        case TransactionMinorState.KycAuthRequired: {
          pendingOrKycInfo = (
            <AlertView
              alert={{
                type: "warning",
                message: i18n.str`Bank account confirmation required for the transaction to complete.`,
                description: (
                  <div>
                    <p>
                      Your account:{" "}
                      <code>
                        {transaction.kycAuthTransferInfo?.debitPaytoUri}
                      </code>
                    </p>
                    <p>
                      Deposit account public key:{" "}
                      <code>{transaction.kycAuthTransferInfo?.accountPub}</code>
                    </p>
                    <p>
                      Payment targets:{" "}
                      <ul>
                        {transaction.kycAuthTransferInfo?.creditPaytoUris.map(
                          (x) => (
                            <li>
                              <code>{x}</code>
                            </li>
                          ),
                        )}
                      </ul>
                    </p>
                  </div>
                ),
              }}
            />
          );
          break;
        }
        case TransactionMinorState.MergeKycRequired:
        case TransactionMinorState.KycRequired:
        case TransactionMinorState.BalanceKycRequired: {
          pendingOrKycInfo = (
            <AlertView
              alert={{
                type: "warning",
                message: i18n.str`KYC check required for the transaction to complete.`,
                description: !transaction.kycUrl ? (
                  i18n.str`No additional information has been provided.`
                ) : (
                  <div>
                    <i18n.Translate>
                      Follow this link to the{` `}
                      <a
                        rel="noreferrer"
                        target="_bank"
                        href={transaction.kycUrl}
                      >
                        KYC status page.
                      </a>
                    </i18n.Translate>
                  </div>
                ),
              }}
            />
          );
          break;
        }
        default: {
          // Plain pending, no KYC.
          pendingOrKycInfo = (
            <InfoBox>
              <div style={{ justifyContent: "center", lineHeight: "25px" }}>
                <i18n.Translate>
                  This transaction is pending to complete.
                </i18n.Translate>
                <Link onClick={onRetry} style={{ padding: 0 }}>
                  <SvgIcon
                    title={i18n.str`Retry`}
                    dangerouslySetInnerHTML={{ __html: refreshIcon }}
                    color="black"
                  />
                </Link>
              </div>
            </InfoBox>
          );
        }
      }
    }
  }

  return (
    <Fragment>
      <section style={{ padding: 8, textAlign: "center" }}>
        {transaction?.error &&
        // FIXME: wallet core should stop sending this error on KYC
        transaction.error.code !==
          TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED ? (
          <ErrorAlertView
            error={alertFromError(
              i18n,
              i18n.str`There was an error trying to complete the transaction.`,
              transaction.error,
            )}
          />
        ) : undefined}
        {pendingOrKycInfo}
        {transaction.txState.major === TransactionMajorState.Aborted && (
          <InfoBox>
            <i18n.Translate>This transaction was aborted.</i18n.Translate>
          </InfoBox>
        )}
        {transaction.txState.major === TransactionMajorState.Failed && (
          <ErrorAlertView
            error={alertFromError(
              i18n,
              i18n.str`This transaction failed.`,
              transaction.error!,
            )}
          />
        )}
        {confirmBeforeForget ? (
          <Overlay>
            <CenteredDialog>
              <header>
                <i18n.Translate>Caution!</i18n.Translate>
              </header>
              <section>
                <i18n.Translate>
                  If you have already wired money to the exchange you will loose
                  the chance to get the coins form it.
                </i18n.Translate>
              </section>
              <footer>
                <Button
                  variant="contained"
                  color="secondary"
                  onClick={
                    (async () =>
                      setConfirmBeforeForget(false)) as SafeHandler<void>
                  }
                >
                  <i18n.Translate>Cancel</i18n.Translate>
                </Button>

                <Button
                  variant="contained"
                  color="error"
                  onClick={safely("delete transaction", onDelete)}
                >
                  <i18n.Translate>Confirm</i18n.Translate>
                </Button>
              </footer>
            </CenteredDialog>
          </Overlay>
        ) : undefined}
        {confirmBeforeCancel ? (
          <Overlay>
            <CenteredDialog>
              <header>
                <i18n.Translate>Caution!</i18n.Translate>
              </header>
              <section>
                <i18n.Translate>
                  Doing a cancellation while the transaction still active might
                  result in lost coins. Do you still want to cancel the
                  transaction?
                </i18n.Translate>
              </section>
              <footer>
                <Button
                  variant="contained"
                  color="secondary"
                  onClick={
                    (async () =>
                      setConfirmBeforeCancel(false)) as SafeHandler<void>
                  }
                >
                  <i18n.Translate>No</i18n.Translate>
                </Button>

                <Button
                  variant="contained"
                  color="error"
                  onClick={safely("cancel active transaction", onCancel)}
                >
                  <i18n.Translate>Yes</i18n.Translate>
                </Button>
              </footer>
            </CenteredDialog>
          </Overlay>
        ) : undefined}
      </section>
      <section>{children}</section>
      <footer>
        <div />
        <div>
          {showButton.abort && (
            <Button
              variant="contained"
              onClick={safely("abort transaction", onAbort)}
            >
              <i18n.Translate>Abort</i18n.Translate>
            </Button>
          )}
          {showButton.resume && settings.suspendIndividualTransaction && (
            <Button
              variant="contained"
              onClick={safely("resume transaction", onResume)}
            >
              <i18n.Translate>Resume</i18n.Translate>
            </Button>
          )}
          {showButton.suspend && settings.suspendIndividualTransaction && (
            <Button
              variant="contained"
              onClick={safely("suspend transaction", onSuspend)}
            >
              <i18n.Translate>Suspend</i18n.Translate>
            </Button>
          )}
          {showButton.fail && (
            <Button
              variant="contained"
              color="error"
              onClick={doCheckBeforeCancel as SafeHandler<void>}
            >
              <i18n.Translate>Cancel</i18n.Translate>
            </Button>
          )}
          {showButton.remove && (
            <Button
              variant="contained"
              color="error"
              onClick={doCheckBeforeForget as SafeHandler<void>}
            >
              <i18n.Translate>Delete</i18n.Translate>
            </Button>
          )}
        </div>
      </footer>
    </Fragment>
  );
}

export function TransactionView({
  transaction,
  onDelete,
  onAbort,
  // onBack,
  onResume,
  onSuspend,
  onRetry,
  onRefund,
  onCancel,
  onForwardToContact,
}: WalletTransactionProps): VNode {
  const { i18n } = useTranslationContext();
  const { safely } = useAlertContext();

  const raw = Amounts.parseOrThrow(transaction.amountRaw);
  const effective = Amounts.parseOrThrow(transaction.amountEffective);

  if (
    transaction.type === TransactionType.Withdrawal ||
    transaction.type === TransactionType.InternalWithdrawal
  ) {
    // const conversion =
    //   transaction.withdrawalDetails.type === WithdrawalType.ManualTransfer
    //     ? transaction.withdrawalDetails.exchangeCreditAccountDetails ?? []
    //     : [];
    const blockedByKycOrAml =
      transaction.txState.minor === TransactionMinorState.KycRequired;
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Withdrawal`}
          total={effective}
          kind="positive"
        >
          {transaction.exchangeBaseUrl}
        </Header>

        {transaction.txState.major !== TransactionMajorState.Pending ||
        blockedByKycOrAml ? undefined : transaction.withdrawalDetails.type ===
            WithdrawalType.ManualTransfer &&
          transaction.withdrawalDetails.exchangeCreditAccountDetails ? (
          <Fragment>
            <InfoBox>
              {transaction.withdrawalDetails.exchangeCreditAccountDetails
                .length > 1 ? (
                <span>
                  <i18n.Translate>
                    Now the payment service provider is waiting for{" "}
                    <Amount value={raw} /> to be transferred. Select one of the
                    accounts and use the information below to complete the
                    operation by making a wire transfer from your bank account.
                  </i18n.Translate>
                </span>
              ) : (
                <span>
                  <i18n.Translate>
                    Now the payment service provider is waiting for{" "}
                    <Amount value={raw} /> to be transferred. Use the
                    information below to complete the operation by making a wire
                    transfer from your bank account.
                  </i18n.Translate>
                </span>
              )}
            </InfoBox>
            <BankDetailsByPaytoType
              amount={raw}
              accounts={
                transaction.withdrawalDetails.exchangeCreditAccountDetails ?? []
              }
              subject={transaction.withdrawalDetails.reservePub}
            />
          </Fragment>
        ) : (
          //integrated bank withdrawal
          <ShowWithdrawalDetailForBankIntegrated transaction={transaction} />
        )}
        <Part
          title={i18n.str`Details`}
          text={
            <WithdrawDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
        {transaction.txState.major === TransactionMajorState.Aborted &&
        transaction.withdrawalDetails.type === WithdrawalType.ManualTransfer ? (
          <AlertView
            alert={{
              type: "info",
              message: i18n.str`Withdrawal incomplete.`,
              description: (
                <i18n.Translate>
                  If you have already sent money to the service provider account
                  it will wire it back at{" "}
                  <Time
                    timestamp={AbsoluteTime.addDuration(
                      AbsoluteTime.fromPreciseTimestamp(transaction.timestamp),
                      Duration.fromTalerProtocolDuration(
                        transaction.withdrawalDetails.reserveClosingDelay,
                      ),
                    )}
                    format="dd MMMM yyyy, HH:mm"
                  />
                </i18n.Translate>
              ),
            }}
          />
        ) : undefined}
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.Payment) {
    const pendingRefund =
      transaction.refundPending === undefined
        ? undefined
        : Amounts.parseOrThrow(transaction.refundPending);

    const effectiveRefund = Amounts.parseOrThrow(
      transaction.totalRefundEffective,
    );

    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onRetry={onRetry}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          total={effective}
          type={i18n.str`Payment`}
          kind="negative"
        >
          {transaction.info.fulfillmentUrl ? (
            <a
              href={transaction.info.fulfillmentUrl}
              target="_bank"
              rel="noreferrer"
            >
              {transaction.info.summary}
            </a>
          ) : (
            transaction.info.summary
          )}
        </Header>
        <br />
        {transaction.refunds.length > 0 ? (
          <Part
            title={i18n.str`Refunds`}
            text={
              <table>
                {transaction.refunds.map((r, i) => {
                  return (
                    <tr key={i}>
                      <td>
                        <i18n.Translate>
                          {<Amount value={r.amountEffective} />}{" "}
                          <a
                            href={`#${Pages.balanceTransaction({
                              tid: r.transactionId,
                            })}`}
                          >
                            was refunded
                          </a>{" "}
                          on{" "}
                          {
                            <Time
                              timestamp={AbsoluteTime.fromProtocolTimestamp(
                                r.timestamp,
                              )}
                              format="dd MMMM yyyy"
                            />
                          }
                          .
                        </i18n.Translate>
                      </td>
                    </tr>
                  );
                })}
              </table>
            }
            kind="neutral"
          />
        ) : undefined}
        {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && (
          <InfoBox>
            {transaction.refundQueryActive ? (
              <i18n.Translate>Refund is in progress.</i18n.Translate>
            ) : (
              <i18n.Translate>
                Merchant created a refund for this order but was not
                automatically picked up.
              </i18n.Translate>
            )}
            <Part
              title={i18n.str`Offer`}
              text={<Amount value={pendingRefund} />}
              kind="positive"
            />
            {transaction.refundQueryActive ? undefined : (
              <div>
                <div />
                <div>
                  <Button
                    variant="contained"
                    onClick={safely("refund transaction", () =>
                      onRefund(transaction.transactionId),
                    )}
                  >
                    <i18n.Translate>Accept</i18n.Translate>
                  </Button>
                </div>
              </div>
            )}
          </InfoBox>
        )}
        {transaction.posConfirmation ? (
          <AlertView
            alert={{
              type: "info",
              message: i18n.str`Confirmation code`,
              description: <pre>{transaction.posConfirmation}</pre>,
            }}
          />
        ) : undefined}
        <Part
          title={i18n.str`Merchant`}
          text={<MerchantDetails merchant={transaction.info.merchant} />}
          kind="neutral"
        />
        <Part
          title={i18n.str`Invoice ID`}
          text={transaction.info.orderId as TranslatedString}
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <PurchaseDetails
              price={getAmountWithFee(effective, raw, "debit")}
              effectiveRefund={effectiveRefund}
              info={transaction.info}
            />
          }
          kind="neutral"
        />
        <ShowFullContractTermPopup transactionId={transaction.transactionId} />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.Deposit) {
    const payto = parsePaytoUri(transaction.targetPaytoUri);

    const wireTime = AbsoluteTime.fromProtocolTimestamp(
      transaction.wireTransferDeadline,
    );
    const shouldBeWired = wireTime.t_ms !== "never" && isPast(wireTime.t_ms);
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Deposit`}
          total={effective}
          kind="negative"
        >
          {!payto ? transaction.targetPaytoUri : <NicePayto payto={payto} />}
        </Header>
        {payto && <PartPayto payto={payto} kind="neutral" />}
        <Part
          title={i18n.str`Details`}
          text={
            <DepositDetails
              amount={getAmountWithFee(effective, raw, "debit")}
            />
          }
          kind="neutral"
        />
        {!shouldBeWired ? (
          <Part
            title={i18n.str`Wire transfer deadline.`}
            text={
              <Time timestamp={wireTime} format="dd MMMM yyyy 'at' HH:mm" />
            }
            kind="neutral"
          />
        ) : transaction.wireTransferProgress === 0 ? (
          <AlertView
            alert={{
              type: "info",
              message: i18n.str`Wire transfer still pending.`,
              description: (
                <i18n.Translate>
                  The service provider deadline to make the wire transfer is:{" "}
                  <Time
                    timestamp={AbsoluteTime.fromProtocolTimestamp(
                      transaction.wireTransferDeadline,
                    )}
                    format="dd MMMM yyyy, HH:mm"
                  />
                </i18n.Translate>
              ),
            }}
          />
        ) : transaction.wireTransferProgress === 100 ? (
          <Fragment>
            <AlertView
              alert={{
                type: "success",
                message: i18n.str`Wire transfer completed.`,
                description: i18n.str` `,
              }}
            />
            <Part
              title={i18n.str`Transfer details`}
              text={
                <TrackingDepositDetails
                  trackingState={transaction.trackingState}
                />
              }
              kind="neutral"
            />
          </Fragment>
        ) : (
          <AlertView
            alert={{
              type: "info",
              message: i18n.str`Wire transfer in progress.`,
              description: (
                <i18n.Translate>
                  The service provider deadline to make the wire transfer is:{" "}
                  <Time
                    timestamp={AbsoluteTime.fromProtocolTimestamp(
                      transaction.wireTransferDeadline,
                    )}
                    format="dd MMMM yyyy, HH:mm"
                  />
                </i18n.Translate>
              ),
            }}
          />
        )}
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.Refresh) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Refresh`}
          total={effective}
          kind="negative"
        >
          {"Refresh"}
        </Header>
        <Part
          title={i18n.str`Details`}
          text={
            <RefreshDetails
              amount={getAmountWithFee(effective, raw, "debit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.Refund) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Refund`}
          total={effective}
          kind="positive"
        >
          {transaction.paymentInfo ? (
            <a
              href={`#${Pages.balanceTransaction({
                tid: transaction.refundedTransactionId,
              })}`}
            >
              {transaction.paymentInfo.summary}
            </a>
          ) : (
            <span style={{ color: "gray" }}>-- deleted --</span>
          )}
        </Header>

        <Part
          title={i18n.str`Merchant`}
          text={
            (transaction.paymentInfo
              ? transaction.paymentInfo.merchant.name
              : "-- deleted --") as TranslatedString
          }
          kind="neutral"
        />
        <Part
          title={i18n.str`Purchase summary`}
          text={
            (transaction.paymentInfo
              ? transaction.paymentInfo.summary
              : "-- deleted --") as TranslatedString
          }
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <RefundDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.PeerPullCredit) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Credit`}
          total={effective}
          kind="positive"
        >
          <i18n.Translate>Invoice</i18n.Translate>
        </Header>

        {transaction.txState.major === TransactionMajorState.Pending &&
          transaction.txState.minor === TransactionMinorState.Ready &&
          transaction.talerUri &&
          !transaction.error && (
            <Part
              title={i18n.str`URI`}
              text={<ShowQrWithCopy text={transaction.talerUri} startOpen  onForwardToContact={() => { onForwardToContact && onForwardToContact(transaction.transactionId)} }/>}
              kind="neutral"
            />
          )}
        {transaction.info.summary ? (
          <Part
            title={i18n.str`Subject`}
            text={transaction.info.summary as TranslatedString}
            kind="neutral"
          />
        ) : undefined}
        <Part
          title={i18n.str`Exchange`}
          text={transaction.exchangeBaseUrl as TranslatedString}
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <InvoiceCreationDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.PeerPullDebit) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Debit`}
          total={effective}
          kind="negative"
        >
          <i18n.Translate>Invoice</i18n.Translate>
        </Header>

        {transaction.info.summary ? (
          <Part
            title={i18n.str`Subject`}
            text={transaction.info.summary as TranslatedString}
            kind="neutral"
          />
        ) : undefined}
        <Part
          title={i18n.str`Exchange`}
          text={transaction.exchangeBaseUrl as TranslatedString}
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <InvoicePaymentDetails
              amount={getAmountWithFee(effective, raw, "debit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.PeerPushDebit) {
    const total = Amounts.parseOrThrow(transaction.amountEffective);
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Debit`}
          total={total}
          kind="negative"
        >
          <i18n.Translate>Transfer</i18n.Translate>
        </Header>

        {transaction.talerUri && (
          <Part
            title={i18n.str`URI`}
            text={<ShowQrWithCopy text={transaction.talerUri} startOpen onForwardToContact={() => { onForwardToContact && onForwardToContact(transaction.transactionId)} } />}
            kind="neutral"
          />
        )}
        {transaction.info.summary ? (
          <Part
            title={i18n.str`Subject`}
            text={transaction.info.summary as TranslatedString}
            kind="neutral"
          />
        ) : undefined}
        <Part
          title={i18n.str`Exchange`}
          text={transaction.exchangeBaseUrl as TranslatedString}
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <TransferCreationDetails
              amount={getAmountWithFee(effective, raw, "debit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.PeerPushCredit) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onAbort={onAbort}
        onResume={onResume}
        onSuspend={onSuspend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Credit`}
          total={effective}
          kind="positive"
        >
          <i18n.Translate>Transfer</i18n.Translate>
        </Header>

        {transaction.info.summary ? (
          <Part
            title={i18n.str`Subject`}
            text={transaction.info.summary as TranslatedString}
            kind="neutral"
          />
        ) : undefined}
        <Part
          title={i18n.str`Exchange`}
          text={transaction.exchangeBaseUrl as TranslatedString}
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <TransferPickupDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.DenomLoss) {
    switch (transaction.lossEventType) {
      case DenomLossEventType.DenomExpired: {
        return (
          <TransactionTemplate
            transaction={transaction}
            onDelete={onDelete}
            onRetry={onRetry}
            onAbort={onAbort}
            onResume={onResume}
            onSuspend={onSuspend}
            onCancel={onCancel}
          >
            <Header
              timestamp={transaction.timestamp}
              type={i18n.str`Debit`}
              total={effective}
              kind="negative"
            >
              <i18n.Translate>Lost</i18n.Translate>
            </Header>

            <Part
              title={i18n.str`Exchange`}
              text={transaction.exchangeBaseUrl as TranslatedString}
              kind="neutral"
            />
            <Part
              title={i18n.str`Reason`}
              text={i18n.str`Denomination expired.`}
            />
          </TransactionTemplate>
        );
      }
      case DenomLossEventType.DenomVanished: {
        return (
          <TransactionTemplate
            transaction={transaction}
            onDelete={onDelete}
            onRetry={onRetry}
            onAbort={onAbort}
            onResume={onResume}
            onSuspend={onSuspend}
            onCancel={onCancel}
          >
            <Header
              timestamp={transaction.timestamp}
              type={i18n.str`Debit`}
              total={effective}
              kind="negative"
            >
              <i18n.Translate>Lost</i18n.Translate>
            </Header>

            <Part
              title={i18n.str`Exchange`}
              text={transaction.exchangeBaseUrl as TranslatedString}
              kind="neutral"
            />
            <Part
              title={i18n.str`Reason`}
              text={i18n.str`Denomination vanished.`}
            />
          </TransactionTemplate>
        );
      }
      case DenomLossEventType.DenomUnoffered: {
        return (
          <TransactionTemplate
            transaction={transaction}
            onDelete={onDelete}
            onRetry={onRetry}
            onAbort={onAbort}
            onResume={onResume}
            onSuspend={onSuspend}
            onCancel={onCancel}
          >
            <Header
              timestamp={transaction.timestamp}
              type={i18n.str`Debit`}
              total={effective}
              kind="negative"
            >
              <i18n.Translate>Lost</i18n.Translate>
            </Header>

            <Part
              title={i18n.str`Exchange`}
              text={transaction.exchangeBaseUrl as TranslatedString}
              kind="neutral"
            />
            <Part
              title={i18n.str`Reason`}
              text={i18n.str`Denomination is unoffered.`}
            />
          </TransactionTemplate>
        );
      }
      default: {
        assertUnreachable(transaction.lossEventType);
      }
    }
  }
  if (transaction.type === TransactionType.Recoup) {
    throw Error("recoup transaction not implemented");
  }
  assertUnreachable(transaction);
}

export function MerchantDetails({
  merchant,
}: {
  merchant: MerchantInfo;
}): VNode {
  return (
    <div style={{ display: "flex", flexDirection: "row" }}>
      {merchant.logo && (
        <div>
          <img
            src={merchant.logo}
            style={{ width: 64, height: 64, margin: 4 }}
          />
        </div>
      )}
      <div>
        <p style={{ marginTop: 0 }}>{merchant.name}</p>
        {merchant.website && (
          <a
            href={merchant.website}
            target="_blank"
            style={{ textDecorationColor: "gray" }}
            rel="noreferrer"
          >
            <SmallLightText>{merchant.website}</SmallLightText>
          </a>
        )}
        {merchant.email && (
          <a
            href={`mailto:${merchant.email}`}
            style={{ textDecorationColor: "gray" }}
          >
            <SmallLightText>{merchant.email}</SmallLightText>
          </a>
        )}
      </div>
    </div>
  );
}

export function ExchangeDetails({ exchange }: { exchange: string }): VNode {
  return (
    <div>
      <p style={{ marginTop: 0 }}>
        <a rel="noreferrer" target="_blank" href={exchange}>
          {exchange}
        </a>
      </p>
    </div>
  );
}

export interface AmountWithFee {
  value: AmountJson;
  fee: AmountJson;
  total: AmountJson;
  maxFrac: number;
}

export function getAmountWithFee(
  effective: AmountJson,
  raw: AmountJson,
  direction: "credit" | "debit",
): AmountWithFee {
  const total = direction === "credit" ? effective : raw;
  const value = direction === "debit" ? effective : raw;
  const fee = Amounts.sub(value, total).amount;

  const maxFrac = [effective, raw, fee]
    .map((a) => Amounts.maxFractionalDigits(a))
    .reduce((c, p) => Math.max(c, p), 0);

  return {
    total,
    value,
    fee,
    maxFrac,
  };
}

export function InvoiceCreationDetails({
  amount,
}: {
  amount: AmountWithFee;
}): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Invoice</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
        </td>
      </tr>

      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Total</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.total} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

export function InvoicePaymentDetails({
  amount,
}: {
  amount: AmountWithFee;
}): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Invoice</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
        </td>
      </tr>

      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Total</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.value} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

export function TransferCreationDetails({
  amount,
}: {
  amount: AmountWithFee;
}): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Sent</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
        </td>
      </tr>

      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Transfer</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.total} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

export function TransferPickupDetails({
  amount,
}: {
  amount: AmountWithFee;
}): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Transfer</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
        </td>
      </tr>

      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Total</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.total} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

export function WithdrawDetails({
  conversion,
  amount,
  bankFee,
}: {
  conversion?: AmountJson;
  amount: AmountWithFee;
  bankFee?: AmountJson;
}): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      {conversion ? (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Transfer</i18n.Translate>
            </td>
            <td>
              <Amount value={conversion} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          {conversion.fraction === amount.value.fraction &&
          conversion.value === amount.value.value ? undefined : (
            <tr>
              <td>
                <i18n.Translate>Converted</i18n.Translate>
              </td>
              <td>
                <Amount value={amount.value} maxFracSize={amount.maxFrac} />
              </td>
            </tr>
          )}
        </Fragment>
      ) : (
        <tr>
          <td>
            <i18n.Translate>Transfer</i18n.Translate>
          </td>
          <td>
            <Amount value={amount.value} maxFracSize={amount.maxFrac} />
          </td>
        </tr>
      )}
      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Total</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.total} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
      {!bankFee || Amounts.isZero(bankFee) ? undefined : (
        <tr>
          <td>
            <i18n.Translate>Bank fee</i18n.Translate>
          </td>
          <td>
            <Amount value={bankFee} maxFracSize={amount.maxFrac} />
          </td>
        </tr>
      )}
    </PurchaseDetailsTable>
  );
}

export function PurchaseDetails({
  price,
  effectiveRefund,
  info: _info,
}: {
  price: AmountWithFee;
  effectiveRefund?: AmountJson;
  info: OrderShortInfo;
}): VNode {
  const { i18n } = useTranslationContext();

  const total = Amounts.add(price.value, price.fee).amount;

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Price</i18n.Translate>
        </td>
        <td>
          <Amount value={price.total} />
        </td>
      </tr>
      {Amounts.isNonZero(price.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={price.fee} />
            </td>
          </tr>
          {effectiveRefund && Amounts.isNonZero(effectiveRefund) ? (
            <Fragment>
              <tr>
                <td colSpan={2}>
                  <hr />
                </td>
              </tr>
              <tr>
                <td>
                  <i18n.Translate>Subtotal</i18n.Translate>
                </td>
                <td>
                  <Amount value={price.total} />
                </td>
              </tr>
              <tr>
                <td>
                  <i18n.Translate>Refunded</i18n.Translate>
                </td>
                <td>
                  <Amount value={effectiveRefund} negative />
                </td>
              </tr>
              <tr>
                <td colSpan={2}>
                  <hr />
                </td>
              </tr>
              <tr>
                <td>
                  <i18n.Translate>Total</i18n.Translate>
                </td>
                <td>
                  <Amount value={Amounts.sub(total, effectiveRefund).amount} />
                </td>
              </tr>
            </Fragment>
          ) : (
            <Fragment>
              <tr>
                <td colSpan={2}>
                  <hr />
                </td>
              </tr>
              <tr>
                <td>
                  <i18n.Translate>Total</i18n.Translate>
                </td>
                <td>
                  <Amount value={price.value} />
                </td>
              </tr>
            </Fragment>
          )}
        </Fragment>
      )}

      {/* {hasProducts && (
        <tr>
          <td colSpan={2}>
            <PartCollapsible
              big
              title={i18n.str`Products`}
              text={
                <ListOfProducts>
                  {info.products?.map((p, k) => (
                    <Row key={k}>
                      <a href="#" onClick={showLargePic}>
                        <img src={p.image ? p.image : emptyImg} />
                      </a>
                      <div>
                        {p.quantity && p.quantity > 0 && (
                          <SmallLightText>
                            x {p.quantity} {p.unit}
                          </SmallLightText>
                        )}
                        <div>{p.description}</div>
                      </div>
                    </Row>
                  ))}
                </ListOfProducts>
              }
            />
          </td>
        </tr>
      )} */}
      {/* {hasShipping && (
        <tr>
          <td colSpan={2}>
            <PartCollapsible
              big
              title={i18n.str`Delivery`}
              text={
                <DeliveryDetails
                  date={info.delivery_date}
                  location={info.delivery_location}
                />
              }
            />
          </td>
        </tr>
      )} */}
    </PurchaseDetailsTable>
  );
}

function RefundDetails({ amount }: { amount: AmountWithFee }): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Refund</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
        </td>
      </tr>

      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Total</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.total} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

type AmountAmountByWireTransferByWire = {
  id: string;
  amount: AmountString;
}[];

function calculateAmountByWireTransfer(
  state: TransactionDeposit["trackingState"],
): AmountAmountByWireTransferByWire {
  const allTracking = Object.values(state ?? {});

  //group tracking by wtid, sum amounts
  const trackByWtid = allTracking.reduce(
    (prev, cur) => {
      const fee = Amounts.parseOrThrow(cur.wireFee);
      const raw = Amounts.parseOrThrow(cur.amountRaw);
      const total = !prev[cur.wireTransferId]
        ? raw
        : Amounts.add(prev[cur.wireTransferId].total, raw).amount;

      prev[cur.wireTransferId] = {
        total,
        fee,
      };
      return prev;
    },
    {} as Record<string, { total: AmountJson; fee: AmountJson }>,
  );

  //remove wire fee from total amount
  return Object.entries(trackByWtid).map(([id, info]) => ({
    id,
    amount: Amounts.stringify(Amounts.sub(info.total, info.fee).amount),
  }));
}

function TrackingDepositDetails({
  trackingState,
}: {
  trackingState: TransactionDeposit["trackingState"];
}): VNode {
  const { i18n } = useTranslationContext();

  const wireTransfers = calculateAmountByWireTransfer(trackingState);

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Transfer identification</i18n.Translate>
        </td>
        <td>
          <i18n.Translate>Amount</i18n.Translate>
        </td>
      </tr>

      {wireTransfers.map((wire) => (
        <tr key={wire.id}>
          <td>{wire.id}</td>
          <td>
            <Amount value={wire.amount} />
          </td>
        </tr>
      ))}
    </PurchaseDetailsTable>
  );
}

export function DepositDetails({ amount }: { amount: AmountWithFee }): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Sent</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
        </td>
      </tr>

      {Amounts.isNonZero(amount.fee) && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Fees</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
          <tr>
            <td colSpan={2}>
              <hr />
            </td>
          </tr>
          <tr>
            <td>
              <i18n.Translate>Total</i18n.Translate>
            </td>
            <td>
              <Amount value={amount.total} maxFracSize={amount.maxFrac} />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

function RefreshDetails({ amount }: { amount: AmountWithFee }): VNode {
  const { i18n } = useTranslationContext();

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Refresh</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.value} maxFracSize={amount.maxFrac} />
        </td>
      </tr>
      <tr>
        <td>
          <i18n.Translate>Fees</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.fee} maxFracSize={amount.maxFrac} />
        </td>
      </tr>
      <tr>
        <td colSpan={2}>
          <hr />
        </td>
      </tr>
      <tr>
        <td>
          <i18n.Translate>Total</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
        </td>
      </tr>
    </PurchaseDetailsTable>
  );
}

function Header({
  timestamp,
  total,
  children,
  kind,
  type,
}: {
  timestamp: TalerPreciseTimestamp;
  total: AmountJson;
  children: ComponentChildren;
  kind: Kind;
  type: TranslatedString;
}): VNode {
  return (
    <div
      style={{
        display: "flex",
        justifyContent: "space-between",
        flexDirection: "row",
      }}
    >
      <div>
        <SubTitle>{children}</SubTitle>
        <Time
          timestamp={AbsoluteTime.fromPreciseTimestamp(timestamp)}
          format="dd MMMM yyyy, HH:mm"
        />
      </div>
      <div>
        <SubTitle>
          <Part
            title={type}
            text={<Amount value={total} negative={kind === "negative"} />}
            kind={kind}
          />
        </SubTitle>
      </div>
    </div>
  );
}

function NicePayto({ payto }: { payto: PaytoUri }): VNode {
  if (payto.isKnown) {
    switch (payto.targetType) {
      case "bitcoin": {
        return <div>{payto.targetPath.substring(0, 20)}...</div>;
      }
      case "x-taler-bank": {
        const url = new URL("/", `https://${payto.host}`);
        return (
          <Fragment>
            <div>{"payto.account"}</div>
            <SmallLightText>
              <a href={url.href} target="_bank" rel="noreferrer">
                {url.href}
              </a>
            </SmallLightText>
          </Fragment>
        );
      }
      case "iban": {
        return <div>{payto.targetPath.substring(0, 20)}</div>;
      }
    }
  }
  return <Fragment>{stringifyPaytoUri(payto)}</Fragment>;
}

function ShowQrWithCopy({
  text,
  startOpen,
  onForwardToContact,
}: {
  text: string;
  startOpen?: boolean;
  onForwardToContact?: () => void;
}): VNode {
  const [showing, setShowing] = useState(startOpen);
  const { i18n } = useTranslationContext();
  async function copy(): Promise<void> {
    navigator.clipboard.writeText(text);
  }
  async function toggle(): Promise<void> {
    setShowing((s) => !s);
  }
  if (showing) {
    return (
      <div>
        <QR text={text} />
        <Button onClick={copy as SafeHandler<void>}>
          <i18n.Translate>copy</i18n.Translate>
        </Button>
        <Button onClick={toggle as SafeHandler<void>}>
          <i18n.Translate>hide qr</i18n.Translate>
        </Button>
        <EnabledBySettings name="p2p_aliases">
        {(onForwardToContact) && (
          <Button onClick={onForwardToContact as SafeHandler<void>}>
            <i18n.Translate>forward to contact</i18n.Translate>
          </Button>)}
        </EnabledBySettings>
      </div>
    );
  }
  return (
    <div>
      <div>{text.substring(0, 64)}...</div>
      <Button onClick={copy as SafeHandler<void>}>
        <i18n.Translate>copy</i18n.Translate>
      </Button>
      <Button onClick={toggle as SafeHandler<void>}>
        <i18n.Translate>show qr</i18n.Translate>
      </Button>
      <EnabledBySettings name="p2p_aliases">
      {(onForwardToContact) && (
        <Button onClick={onForwardToContact as SafeHandler<void>}>
          <i18n.Translate>forward to contact</i18n.Translate>
        </Button>)}
      </EnabledBySettings>
    </div>
  );
}

function getShowButtonStates(transaction: Transaction) {
  let abort = false;
  let fail = false;
  let resume = false;
  let remove = false;
  let suspend = false;

  transaction.txActions.forEach((a) => {
    switch (a) {
      case TransactionAction.Delete:
        remove = true;
        break;
      case TransactionAction.Suspend:
        suspend = true;
        break;
      case TransactionAction.Resume:
        resume = true;
        break;
      case TransactionAction.Abort:
        abort = true;
        break;
      case TransactionAction.Fail:
        fail = true;
        break;
      case TransactionAction.Retry:
        break;
      default:
        assertUnreachable(a);
        break;
    }
  });
  return { abort, fail, resume, remove, suspend };
}

function ShowWithdrawalDetailForBankIntegrated({
  transaction,
}: {
  transaction: TransactionWithdrawal | TransactionInternalWithdrawal;
}): VNode {
  const { i18n } = useTranslationContext();
  const [showDetails, setShowDetails] = useState(false);
  if (
    transaction.txState.major !== TransactionMajorState.Pending ||
    transaction.withdrawalDetails.type === WithdrawalType.ManualTransfer
  ) {
    return <Fragment />;
  }
  const raw = Amounts.parseOrThrow(transaction.amountRaw);
  return (
    <Fragment>
      <EnabledBySettings name="advancedMode">
        <a
          href="#"
          onClick={(e) => {
            e.preventDefault();
            setShowDetails(!showDetails);
          }}
        >
          Show details.
        </a>
      </EnabledBySettings>

      {showDetails && (
        <BankDetailsByPaytoType
          amount={raw}
          accounts={
            transaction.withdrawalDetails.exchangeCreditAccountDetails ?? []
          }
          subject={transaction.withdrawalDetails.reservePub}
        />
      )}
      {!transaction.withdrawalDetails.confirmed &&
      transaction.withdrawalDetails.bankConfirmationUrl ? (
        <InfoBox>
          <div style={{ display: "block" }}>
            <i18n.Translate>
              Wire transfer need a confirmation. Go to the{" "}
              <a
                href={transaction.withdrawalDetails.bankConfirmationUrl}
                target="_blank"
                rel="noreferrer"
                style={{ display: "inline" }}
              >
                <i18n.Translate>bank site</i18n.Translate>
              </a>{" "}
              and check wire transfer operation to exchange account is complete.
            </i18n.Translate>
          </div>
        </InfoBox>
      ) : undefined}
      {transaction.withdrawalDetails.confirmed &&
        !transaction.withdrawalDetails.reserveIsReady && (
          <InfoBox>
            <i18n.Translate>
              Bank has confirmed the wire transfer. Waiting for the exchange to
              send the coins.
            </i18n.Translate>
          </InfoBox>
        )}
      {transaction.withdrawalDetails.confirmed &&
        transaction.withdrawalDetails.reserveIsReady && (
          <InfoBox>
            <i18n.Translate>
              Exchange is ready to send the coins, withdrawal in progress.
            </i18n.Translate>
          </InfoBox>
        )}
    </Fragment>
  );
}
