/*
 This file is part of GNU Taler
 (C) 2022 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,
  ExtendedStatus,
  Location,
  MerchantInfo,
  NotificationType,
  OrderShortInfo,
  parsePaytoUri,
  PaytoUri,
  stringifyPaytoUri,
  TalerProtocolTimestamp,
  Transaction,
  TransactionDeposit,
  TransactionType,
  TranslatedString,
  WithdrawalType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react";
import { differenceInSeconds, isPast } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import emptyImg from "../../static/img/empty.png";
import { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { CopyButton } from "../components/CopyButton.js";
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js";
import { JustInDevMode } from "../components/JustInDevMode.js";
import { Loading } from "../components/Loading.js";
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js";
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
import {
  CenteredDialog,
  InfoBox,
  ListOfProducts,
  Overlay,
  Row,
  SmallLightText,
  SubTitle,
  WarningBox,
} 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 { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
import { SafeHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js";
import { assertUnreachable } from "../utils/index.js";

interface Props {
  tid: string;
  goToWalletHistory: (currency?: string) => Promise<void>;
}

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

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

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

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

  const currency = Amounts.parse(state.response.amountRaw)?.currency;

  return (
    <TransactionView
      transaction={state.response}
      onSend={async () => {
        null;
      }}
      onCancel={async () => {
        await api.wallet.call(WalletApiOperation.AbortTransaction, {
          transactionId,
        });
        goToWalletHistory(currency);
      }}
      onDelete={async () => {
        await api.wallet.call(WalletApiOperation.DeleteTransaction, {
          transactionId,
        });
        goToWalletHistory(currency);
      }}
      onRetry={async () => {
        await api.wallet.call(WalletApiOperation.RetryTransaction, {
          transactionId,
        });
        goToWalletHistory(currency);
      }}
      onRefund={async (purchaseId) => {
        await api.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, {
          purchaseId,
        });
      }}
      onBack={() => goToWalletHistory(currency)}
    />
  );
}

export interface WalletTransactionProps {
  transaction: Transaction;
  onSend: () => Promise<void>;
  onCancel: () => Promise<void>;
  onDelete: () => Promise<void>;
  onRetry: () => Promise<void>;
  onRefund: (id: string) => Promise<void>;
  onBack: () => 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,
  onSend,
  onCancel,
  children,
}: TransactionTemplateProps): VNode {
  const { i18n } = useTranslationContext();
  const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
  const [confirmBeforeCancel, setConfirmBeforeCancel] = useState(false);
  const { safely } = useAlertContext();

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

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

  const SHOWING_RETRY_THRESHOLD_SECS = 30;

  const showSend = false;
  const hasCancelTransactionImplemented =
    transaction.type === TransactionType.Payment;

  const transactionStillActive =
    transaction.extendedStatus !== ExtendedStatus.Aborted &&
    transaction.extendedStatus !== ExtendedStatus.Done &&
    transaction.extendedStatus !== ExtendedStatus.Failed;

  // show retry if there is an error in an active state, or after some time
  // if it is not aborting
  const showRetry =
    transactionStillActive &&
    (transaction.error !== undefined ||
      (transaction.extendedStatus !== ExtendedStatus.Aborting &&
        (transaction.timestamp.t_s === "never" ||
          differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) >
            SHOWING_RETRY_THRESHOLD_SECS)));

  return (
    <Fragment>
      <section style={{ padding: 8, textAlign: "center" }}>
        {transaction?.error ? (
          transaction.error.code === 7025 ? (
            <AlertView
              alert={{
                type: "warning",
                message: i18n.str`KYC check required for the transaction to complete`,
                description:
                  transaction.error.kycUrl &&
                  typeof transaction.error.kycUrl === "string" ? (
                    <div>
                      <i18n.Translate>
                        Follow this link to the{` `}
                        <a href={transaction.error.kycUrl}>KYC verifier</a>
                      </i18n.Translate>
                    </div>
                  ) : (
                    i18n.str`No more information has been provided`
                  ),
              }}
            />
          ) : (
            <ErrorAlertView
              error={alertFromError(
                i18n.str`There was an error trying to complete the transaction`,
                transaction.error,
              )}
            />
          )
        ) : undefined}
        {transaction.extendedStatus === ExtendedStatus.Pending && (
          <WarningBox>
            <i18n.Translate>This transaction is not completed</i18n.Translate>
          </WarningBox>
        )}
        {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(
                    onDelete,
                    i18n.str`Could not forget transaction`,
                  )}
                >
                  <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(
                    onCancel,
                    i18n.str`Could not cancel the active transaction`,
                  )}
                >
                  <i18n.Translate>Yes</i18n.Translate>
                </Button>
              </footer>
            </CenteredDialog>
          </Overlay>
        ) : undefined}
      </section>
      <section>{children}</section>
      <footer>
        <div>
          {showSend ? (
            <Button
              variant="contained"
              onClick={safely(onSend, i18n.str`Could not send`)}
            >
              <i18n.Translate>Send</i18n.Translate>
            </Button>
          ) : null}
        </div>
        <div>
          {showRetry ? (
            <Button
              variant="contained"
              onClick={safely(onRetry, i18n.str`Could not retry`)}
            >
              <i18n.Translate>Retry</i18n.Translate>
            </Button>
          ) : null}
          {transactionStillActive ? (
            hasCancelTransactionImplemented ? (
              <Button
                variant="contained"
                color="error"
                onClick={doCheckBeforeCancel as SafeHandler<void>}
              >
                <i18n.Translate>Cancel</i18n.Translate>
              </Button>
            ) : (
              //WORKAROUND
              //Able to delete tx in dev mode
              //FIXME: remove this when DD37 is implemented
              <JustInDevMode>
                <Button
                  variant="contained"
                  color="error"
                  onClick={doCheckBeforeForget as SafeHandler<void>}
                >
                  <i18n.Translate>Forget</i18n.Translate>
                </Button>
              </JustInDevMode>
            )
          ) : (
            <Button
              variant="contained"
              color="error"
              onClick={doCheckBeforeForget as SafeHandler<void>}
            >
              <i18n.Translate>Forget</i18n.Translate>
            </Button>
          )}
        </div>
      </footer>
    </Fragment>
  );
}

export function TransactionView({
  transaction,
  onDelete,
  onRetry,
  onSend,
  onRefund,
  onCancel,
}: 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) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Withdrawal`}
          total={effective}
          kind="positive"
        >
          {transaction.exchangeBaseUrl}
        </Header>

        {transaction.extendedStatus !==
        ExtendedStatus.Pending ? undefined : transaction.withdrawalDetails
            .type === WithdrawalType.ManualTransfer ? (
          //manual withdrawal
          <Fragment>
            <BankDetailsByPaytoType
              amount={raw}
              exchangeBaseUrl={transaction.exchangeBaseUrl}
              payto={parsePaytoUri(
                transaction.withdrawalDetails.exchangePaytoUris[0],
              )}
              subject={transaction.withdrawalDetails.reservePub}
            />
            <table>
              <tbody>
                <tr>
                  <td>
                    <pre>
                      <b>
                        <a
                          target="_bank"
                          rel="noreferrer"
                          title="RFC 8905 for designating targets for payments"
                          href="https://tools.ietf.org/html/rfc8905"
                        >
                          Payto URI
                        </a>
                      </b>
                    </pre>
                  </td>
                  <td width="100%" style={{ wordBreak: "break-all" }}>
                    {transaction.withdrawalDetails.exchangePaytoUris[0]}
                  </td>
                  <td>
                    <CopyButton
                      getContent={() =>
                        transaction.withdrawalDetails.type ===
                        WithdrawalType.ManualTransfer
                          ? transaction.withdrawalDetails.exchangePaytoUris[0]
                          : ""
                      }
                    />
                  </td>
                </tr>
              </tbody>
            </table>
            <WarningBox>
              <i18n.Translate>
                Make sure to use the correct subject, otherwise the money will
                not arrive in this wallet.
              </i18n.Translate>
            </WarningBox>
          </Fragment>
        ) : (
          //integrated bank withdrawal
          <Fragment>
            {!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>
        )}
        <Part
          title={i18n.str`Details`}
          text={
            <WithdrawDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
      </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}
        onRetry={onRetry}
        onSend={onSend}
        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.fromTimestamp(
                                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(
                      () => onRefund(transaction.proposalId),
                      i18n.str`Could not refund`,
                    )}
                  >
                    <i18n.Translate>Accept</i18n.Translate>
                  </Button>
                </div>
              </div>
            )}
          </InfoBox>
        )}
        <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}
              proposalId={transaction.proposalId}
            />
          }
          kind="neutral"
        />
      </TransactionTemplate>
    );
  }

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

    const wireTime = AbsoluteTime.fromTimestamp(
      transaction.wireTransferDeadline,
    );
    const shouldBeWired = wireTime.t_ms !== "never" && isPast(wireTime.t_ms);
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        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: "warning",
              message: i18n.str`Wire transfer is not initiated`,
              description: i18n.str` `,
            }}
          />
        ) : 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.str` `,
            }}
          />
        )}
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.Refresh) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        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.Tip) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Tip`}
          total={effective}
          kind="positive"
        >
          {transaction.merchantBaseUrl}
        </Header>
        {/* <Part
          title={i18n.str`Merchant`}
          text={<MerchantDetails merchant={transaction.merchant} />}
          kind="neutral"
        /> */}
        <Part
          title={i18n.str`Details`}
          text={
            <TipDetails amount={getAmountWithFee(effective, raw, "credit")} />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.Refund) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Refund`}
          total={effective}
          kind="positive"
        >
          {transaction.info.summary}
        </Header>

        <Part
          title={i18n.str`Merchant`}
          text={transaction.info.merchant.name as TranslatedString}
          kind="neutral"
        />
        <Part
          title={i18n.str`Original order ID`}
          text={
            <a
              href={Pages.balanceTransaction({
                tid: transaction.refundedTransactionId,
              })}
            >
              {transaction.info.orderId}
            </a>
          }
          kind="neutral"
        />
        <Part
          title={i18n.str`Purchase summary`}
          text={transaction.info.summary 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}
        onSend={onSend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Credit`}
          total={effective}
          kind="positive"
        >
          <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"
        />
        {transaction.extendedStatus ===
          ExtendedStatus.Pending /** pending is not-pay */ &&
          !transaction.error && (
            <Part
              title={i18n.str`URI`}
              text={<ShowQrWithCopy text={transaction.talerUri} />}
              kind="neutral"
            />
          )}
        <Part
          title={i18n.str`Details`}
          text={
            <InvoiceDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.PeerPullDebit) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        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={
            <InvoiceDetails
              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}
        onSend={onSend}
        onCancel={onCancel}
      >
        <Header
          timestamp={transaction.timestamp}
          type={i18n.str`Debit`}
          total={total}
          kind="negative"
        >
          <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"
        />
        {/* {transaction.pending && ( //pending is not-received
            )} */}
        <Part
          title={i18n.str`URI`}
          text={<ShowQrWithCopy text={transaction.talerUri} />}
          kind="neutral"
        />
        <Part
          title={i18n.str`Details`}
          text={
            <TransferDetails
              amount={getAmountWithFee(effective, raw, "debit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }

  if (transaction.type === TransactionType.PeerPushCredit) {
    return (
      <TransactionTemplate
        transaction={transaction}
        onDelete={onDelete}
        onRetry={onRetry}
        onSend={onSend}
        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={
            <TransferDetails
              amount={getAmountWithFee(effective, raw, "credit")}
            />
          }
        />
      </TransactionTemplate>
    );
  }
  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>
  );
}

function DeliveryDetails({
  date,
  location,
}: {
  date: TalerProtocolTimestamp | undefined;
  location: Location | undefined;
}): VNode {
  const { i18n } = useTranslationContext();
  return (
    <PurchaseDetailsTable>
      {location && (
        <Fragment>
          {location.country && (
            <tr>
              <td>
                <i18n.Translate>Country</i18n.Translate>
              </td>
              <td>{location.country}</td>
            </tr>
          )}
          {location.address_lines && (
            <tr>
              <td>
                <i18n.Translate>Address lines</i18n.Translate>
              </td>
              <td>{location.address_lines}</td>
            </tr>
          )}
          {location.building_number && (
            <tr>
              <td>
                <i18n.Translate>Building number</i18n.Translate>
              </td>
              <td>{location.building_number}</td>
            </tr>
          )}
          {location.building_name && (
            <tr>
              <td>
                <i18n.Translate>Building name</i18n.Translate>
              </td>
              <td>{location.building_name}</td>
            </tr>
          )}
          {location.street && (
            <tr>
              <td>
                <i18n.Translate>Street</i18n.Translate>
              </td>
              <td>{location.street}</td>
            </tr>
          )}
          {location.post_code && (
            <tr>
              <td>
                <i18n.Translate>Post code</i18n.Translate>
              </td>
              <td>{location.post_code}</td>
            </tr>
          )}
          {location.town_location && (
            <tr>
              <td>
                <i18n.Translate>Town location</i18n.Translate>
              </td>
              <td>{location.town_location}</td>
            </tr>
          )}
          {location.town && (
            <tr>
              <td>
                <i18n.Translate>Town</i18n.Translate>
              </td>
              <td>{location.town}</td>
            </tr>
          )}
          {location.district && (
            <tr>
              <td>
                <i18n.Translate>District</i18n.Translate>
              </td>
              <td>{location.district}</td>
            </tr>
          )}
          {location.country_subdivision && (
            <tr>
              <td>
                <i18n.Translate>Country subdivision</i18n.Translate>
              </td>
              <td>{location.country_subdivision}</td>
            </tr>
          )}
        </Fragment>
      )}

      {!location || !date ? undefined : (
        <tr>
          <td colSpan={2}>
            <hr />
          </td>
        </tr>
      )}
      {date && (
        <Fragment>
          <tr>
            <td>
              <i18n.Translate>Date</i18n.Translate>
            </td>
            <td>
              <Time
                timestamp={AbsoluteTime.fromTimestamp(date)}
                format="dd MMMM yyyy, HH:mm"
              />
            </td>
          </tr>
        </Fragment>
      )}
    </PurchaseDetailsTable>
  );
}

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 fee =
    direction === "credit"
      ? Amounts.sub(raw, effective).amount
      : Amounts.sub(effective, raw).amount;

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

  return {
    total: effective,
    value: raw,
    fee,
    maxFrac,
  };
}

export function InvoiceDetails({ 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) && (
        <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>
  );
}

export function TransferDetails({ 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) && (
        <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>
  );
}

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

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

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

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

      {Amounts.isNonZero(amount.fee) && (
        <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>
  );
}

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

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

  const hasProducts = info.products && info.products.length > 0;

  const hasShipping =
    info.delivery_date !== undefined || info.delivery_location !== undefined;

  const showLargePic = (): void => {
    return;
  };

  return (
    <PurchaseDetailsTable>
      <tr>
        <td>
          <i18n.Translate>Price</i18n.Translate>
        </td>
        <td>
          <Amount value={price.value} />
        </td>
      </tr>
      {Amounts.isNonZero(price.fee) && (
        <tr>
          <td>
            <i18n.Translate>Transaction 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.total} />
            </td>
          </tr>
        </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>
      )}
      <tr>
        <td>
          <ShowFullContractTermPopup proposalId={proposalId} />
        </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) && (
        <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 TrackingDepositDetails({
  trackingState,
}: {
  trackingState: TransactionDeposit["trackingState"];
}): VNode {
  const { i18n } = useTranslationContext();

  const trackByWtid = Object.values(trackingState ?? {}).reduce((prev, cur) => {
    const am = Amounts.parseOrThrow(cur.amountEffective);
    const sum = !prev[cur.wireTransferId]
      ? am
      : Amounts.add(prev[cur.wireTransferId], am).amount;
    prev[cur.wireTransferId] = sum;
    return prev;
  }, {} as Record<string, AmountJson>);
  const wireTransfers = Object.entries(trackByWtid).map(([id, amountJson]) => ({
    id,
    amount: Amounts.stringify(amountJson),
  }));

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

      {wireTransfers.map((wire) => (
        <tr>
          <td>{wire.id}</td>
          <td>
            <Amount value={wire.amount} />
          </td>
        </tr>
      ))}
    </PurchaseDetailsTable>
  );
}
function DepositDetails({ amount }: { amount: AmountWithFee }): VNode {
  const { i18n } = useTranslationContext();

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

      {Amounts.isNonZero(amount.fee) && (
        <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 transfer</i18n.Translate>
        </td>
        <td>
          <Amount value={amount.total} maxFracSize={amount.maxFrac} />
        </td>
      </tr>
    </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 TipDetails({ amount }: { amount: AmountWithFee }): VNode {
  const { i18n } = useTranslationContext();

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

      {Amounts.isNonZero(amount.fee) && (
        <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: TalerProtocolTimestamp;
  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.fromTimestamp(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.toString()}
              </a>
            </SmallLightText>
          </Fragment>
        );
      }
      case "iban": {
        return <div>{payto.targetPath.substring(0, 20)}</div>;
      }
    }
  }
  return <Fragment>{stringifyPaytoUri(payto)}</Fragment>;
}

function ShowQrWithCopy({ text }: { text: string }): VNode {
  const [showing, setShowing] = useState(false);
  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>
      </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>
    </div>
  );
}
