/*
 This file is part of GNU Taler
 (C) 2022-2024 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,
  Amounts,
  Duration,
  HttpStatusCode,
  TalerCorebankApi,
  TalerError,
  TalerErrorCode,
  TranslatedString,
  assertUnreachable,
  parsePaytoUri,
} from "@gnu-taler/taler-util";
import {
  Attention,
  Loading,
  LocalNotificationBanner,
  RouteDefinition,
  ShowInputErrorLabel,
  Time,
  useBankCoreApiContext,
  useLocalNotification,
  useNavigationContext,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
import { useWithdrawalDetails } from "../hooks/account.js";
import { ChallengeInProgess, useBankState } from "../hooks/bank-state.js";
import { useConversionInfo } from "../hooks/regional.js";
import { useSessionState } from "../hooks/session.js";
import { undefinedIfEmpty } from "../utils.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
import { OperationNotFound } from "./WithdrawalQRCode.js";

const TAN_PREFIX = "T-";
const TAN_REGEX = /^([Tt](-)?)?[0-9]*$/;
export function SolveChallengePage({
  onChallengeCompleted,
  routeClose,
}: {
  onChallengeCompleted: () => void;
  routeClose: RouteDefinition;
}): VNode {
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();
  const { i18n } = useTranslationContext();
  const [bankState, updateBankState] = useBankState();
  const [code, setCode] = useState<string | undefined>(undefined);
  const [notification, notify, handleError] = useLocalNotification();
  const { state } = useSessionState();
  const creds = state.status !== "loggedIn" ? undefined : state;
  const { navigateTo } = useNavigationContext();

  if (!bankState.currentChallenge) {
    return (
      <div>
        <span>no challenge to solve </span>
        <a
          href={routeClose.url({})}
          name="close"
          class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
        >
          <i18n.Translate>Continue</i18n.Translate>
        </a>
      </div>
    );
  }

  const ch = bankState.currentChallenge;
  const errors = undefinedIfEmpty({
    code: !code
      ? i18n.str`Required`
      : !TAN_REGEX.test(code)
        ? i18n.str`Confirmation codes are numerical, possibly beginning with 'T-.'`
        : undefined,
  });

  async function startChallenge() {
    if (!creds) return;
    await handleError(async () => {
      const resp = await api.sendChallenge(creds, ch.id);
      if (resp.type === "ok") {
        const newCh = structuredClone(ch);
        newCh.sent = AbsoluteTime.now();
        newCh.info = resp.body;
        updateBankState("currentChallenge", newCh);
      } else {
        const newCh = structuredClone(ch);
        newCh.sent = AbsoluteTime.now();
        newCh.info = undefined;
        updateBankState("currentChallenge", newCh);
        switch (resp.case) {
          case HttpStatusCode.NotFound:
            return notify({
              type: "error",
              title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.Unauthorized:
            return notify({
              type: "error",
              title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
            return notify({
              type: "error",
              title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          default:
            assertUnreachable(resp);
        }
      }
    });
  }

  async function completeChallenge() {
    if (!creds || !code) return;
    const tan = code.toUpperCase().startsWith(TAN_PREFIX)
      ? code.substring(TAN_PREFIX.length)
      : code;
    await handleError(async () => {
      {
        const resp = await api.confirmChallenge(creds, ch.id, { tan });
        if (resp.type === "fail") {
          setCode("");
          switch (resp.case) {
            case HttpStatusCode.NotFound:
              return notify({
                type: "error",
                title: i18n.str`Challenge not found.`,
                description: resp.detail.hint as TranslatedString,
                debug: resp.detail,
                when: AbsoluteTime.now(),
              });
            case HttpStatusCode.Unauthorized:
              return notify({
                type: "error",
                title: i18n.str`This user is not authorized to complete this challenge.`,
                description: resp.detail.hint as TranslatedString,
                debug: resp.detail,
                when: AbsoluteTime.now(),
              });
            case HttpStatusCode.TooManyRequests:
              return notify({
                type: "error",
                title: i18n.str`Too many attempts, try another code.`,
                description: resp.detail.hint as TranslatedString,
                debug: resp.detail,
                when: AbsoluteTime.now(),
              });
            case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED:
              return notify({
                type: "error",
                title: i18n.str`The confirmation code is wrong, try again.`,
                description: resp.detail.hint as TranslatedString,
                debug: resp.detail,
                when: AbsoluteTime.now(),
              });
            case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED:
              return notify({
                type: "error",
                title: i18n.str`The operation expired.`,
                description: resp.detail.hint as TranslatedString,
                debug: resp.detail,
                when: AbsoluteTime.now(),
              });
            default:
              assertUnreachable(resp);
          }
        }
      }
      {
        const resp = await (async (ch: ChallengeInProgess) => {
          switch (ch.operation) {
            case "delete-account":
              return await api.deleteAccount(creds, ch.id);
            case "update-account":
              return await api.updateAccount(creds, ch.request, ch.id);
            case "update-password":
              return await api.updatePassword(creds, ch.request, ch.id);
            case "create-transaction":
              return await api.createTransaction(
                creds,
                ch.request,
                undefined,
                ch.id,
              );
            case "confirm-withdrawal":
              return await api.confirmWithdrawalById(creds, ch.request, ch.id);
            case "create-cashout":
              return await api.createCashout(creds, ch.request, ch.id);
            default:
              assertUnreachable(ch);
          }
        })(ch);

        if (resp.type === "fail") {
          if (resp.case !== HttpStatusCode.Accepted) {
            return notify({
              type: "error",
              title: i18n.str`The operation failed.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          }
          // another challenge required, save the request and the ID
          // @ts-expect-error no need to check the type of request, since it will be the same as the previous request
          updateBankState("currentChallenge", {
            operation: ch.operation,
            id: String(resp.body.challenge_id),
            location: ch.location,
            sent: AbsoluteTime.never(),
            request: ch.request,
          });
          return notify({
            type: "info",
            title: i18n.str`The operation needs another confirmation to complete.`,
            when: AbsoluteTime.now(),
          });
        }
        updateBankState("currentChallenge", undefined);
        return onChallengeCompleted();
      }
    });
  }

  return (
    <Fragment>
      <LocalNotificationBanner notification={notification} />
      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-6 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
        <div class="px-4 sm:px-0">
          <h2 class="text-base font-semibold leading-7 text-gray-900">
            <span
              class="text-sm text-black font-semibold leading-6 "
              id="availability-label"
            >
              <i18n.Translate>Confirm the operation</i18n.Translate>
            </span>
          </h2>
          <p class="mt-2 text-sm text-gray-500">
            <i18n.Translate>
              This operation is protected with second factor authentication. In
              order to complete it we need to verify your identity using the
              authentication channel you provided.
            </i18n.Translate>
          </p>
        </div>

        <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2">
          <ChallengeDetails
            challenge={bankState.currentChallenge}
            onStart={startChallenge}
            onCancel={() => {
              updateBankState("currentChallenge", undefined);
              navigateTo(ch.location);
            }}
          />
          {ch.info && (
            <div class="mt-2">
              <form
                class="bg-white shadow-sm ring-1 ring-gray-900/5"
                autoCapitalize="none"
                autoCorrect="off"
                onSubmit={(e) => {
                  e.preventDefault();
                }}
              >
                <div class="px-4 py-4">
                  <label for="withdraw-amount">
                    <i18n.Translate>Enter the confirmation code</i18n.Translate>
                  </label>
                  <div class="mt-2">
                    <div class="relative rounded-md shadow-sm">
                      <input
                        type="text"
                        // class="block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                        aria-describedby="answer"
                        autoFocus
                        class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                        value={code ?? ""}
                        required
                        onPaste={(e) => {
                          e.preventDefault();
                          const pasted = e.clipboardData?.getData("text/plain");
                          if (!pasted) return;
                          if (pasted.toUpperCase().startsWith(TAN_PREFIX)) {
                            const sub = pasted.substring(TAN_PREFIX.length);
                            setCode(sub);
                            return;
                          }
                          setCode(pasted);
                        }}
                        name="answer"
                        id="answer"
                        autocomplete="off"
                        onChange={(e): void => {
                          setCode(e.currentTarget.value);
                        }}
                      />
                    </div>
                    <ShowInputErrorLabel
                      message={errors?.code}
                      isDirty={code !== undefined}
                    />
                  </div>
                  <p class="mt-2 text-sm text-gray-500">
                    {((ch: TalerCorebankApi.TanChannel): VNode => {
                      switch (ch) {
                        case TalerCorebankApi.TanChannel.SMS:
                          return (
                            <i18n.Translate>
                              You should have received a code in your phone.
                            </i18n.Translate>
                          );
                        case TalerCorebankApi.TanChannel.EMAIL:
                          return (
                            <i18n.Translate>
                              You should have received a code in your email.
                            </i18n.Translate>
                          );
                        default:
                          assertUnreachable(ch);
                      }
                    })(ch.info.tan_channel)}
                  </p>
                  <p class="mt-2 text-sm text-gray-500">
                    <i18n.Translate>
                      The confirmation code starts with "{TAN_PREFIX}" followed
                      by numbers.
                    </i18n.Translate>
                  </p>
                </div>
                <div class="flex items-center justify-between border-gray-900/10 px-4 py-4 ">
                  <div />
                  <button
                    type="submit"
                    name="confirm"
                    class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                    disabled={!!errors}
                    onClick={(e) => {
                      completeChallenge();
                      e.preventDefault();
                    }}
                  >
                    <i18n.Translate>Confirm</i18n.Translate>
                  </button>
                </div>
              </form>
            </div>
          )}
        </div>
      </div>
    </Fragment>
  );
}

function ChallengeDetails({
  challenge,
  onStart,
  onCancel,
}: {
  challenge: ChallengeInProgess;
  onStart: () => void;
  onCancel: () => void;
}): VNode {
  const { i18n } = useTranslationContext();
  const { config } = useBankCoreApiContext();

  const firstTime = AbsoluteTime.isNever(challenge.sent);
  useEffect(() => {
    if (firstTime) {
      onStart();
    }
  }, []);

  const subtitle = ((op): TranslatedString => {
    switch (op) {
      case "delete-account":
        return i18n.str`Removing account`;
      case "update-account":
        return i18n.str`Updating account values`;
      case "update-password":
        return i18n.str`Updating password`;
      case "create-transaction":
        return i18n.str`Making a wire transfer`;
      case "confirm-withdrawal":
        return i18n.str`Confirming withdrawal`;
      case "create-cashout":
        return i18n.str`Making a cashout`;
    }
  })(challenge.operation);

  return (
    <div class="px-4 mt-4 ">
      <div class="w-full">
        <div class="border-gray-100">
          <h2 class="text-base font-semibold leading-10 text-gray-900">
            <span class=" text-black font-semibold leading-6 ">
              <i18n.Translate>Operation:</i18n.Translate>
            </span>{" "}
            &nbsp;
            <span class=" text-black font-normal leading-6 ">{subtitle}</span>
          </h2>
          <dl class="divide-y divide-gray-100">
            {((): VNode => {
              switch (challenge.operation) {
                case "delete-account":
                  return (
                    <Fragment>
                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                        <dt class="text-sm font-medium leading-6 text-gray-900">
                          <i18n.Translate>Type</i18n.Translate>
                        </dt>
                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                          <i18n.Translate>
                            Updating account settings
                          </i18n.Translate>
                        </dd>
                      </div>
                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                        <dt class="text-sm font-medium leading-6 text-gray-900">
                          <i18n.Translate>Account</i18n.Translate>
                        </dt>
                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                          {challenge.request}
                        </dd>
                      </div>
                    </Fragment>
                  );
                case "create-transaction": {
                  const payto = parsePaytoUri(challenge.request.payto_uri)!;
                  return (
                    <Fragment>
                      {challenge.request.amount && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>Amount</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            <RenderAmount
                              value={Amounts.parseOrThrow(
                                challenge.request.amount,
                              )}
                              spec={config.currency_specification}
                            />
                          </dd>
                        </div>
                      )}
                      {payto.isKnown && payto.targetType === "iban" && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>To account</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {payto.iban}
                          </dd>
                        </div>
                      )}
                    </Fragment>
                  );
                }
                case "confirm-withdrawal":
                  return <ShowWithdrawalDetails id={challenge.request} />;
                case "create-cashout": {
                  return <ShowCashoutDetails request={challenge.request} />;
                }
                case "update-account": {
                  return (
                    <Fragment>
                      {challenge.request.cashout_payto_uri !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>Cashout account</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {challenge.request.cashout_payto_uri}
                          </dd>
                        </div>
                      )}
                      {challenge.request.contact_data?.email !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>Email</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {challenge.request.contact_data?.email}
                          </dd>
                        </div>
                      )}
                      {challenge.request.contact_data?.phone !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>Phone</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {challenge.request.contact_data?.phone}
                          </dd>
                        </div>
                      )}
                      {challenge.request.debit_threshold !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>Debit threshold</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            <RenderAmount
                              value={Amounts.parseOrThrow(
                                challenge.request.debit_threshold,
                              )}
                              spec={config.currency_specification}
                            />
                          </dd>
                        </div>
                      )}
                      {challenge.request.is_public !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>
                              Is this account public?
                            </i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {challenge.request.is_public
                              ? i18n.str`Enable`
                              : i18n.str`Disable`}
                          </dd>
                        </div>
                      )}
                      {challenge.request.name !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>Name</i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {challenge.request.name}
                          </dd>
                        </div>
                      )}
                      {challenge.request.tan_channel !== undefined && (
                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                          <dt class="text-sm font-medium leading-6 text-gray-900">
                            <i18n.Translate>
                              Authentication channel
                            </i18n.Translate>
                          </dt>
                          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                            {challenge.request.tan_channel ?? i18n.str`Remove`}
                          </dd>
                        </div>
                      )}
                    </Fragment>
                  );
                }
                case "update-password": {
                  return (
                    <Fragment>
                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                        <dt class="text-sm font-medium leading-6 text-gray-900">
                          <i18n.Translate>New password</i18n.Translate>
                        </dt>
                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                          {challenge.request.new_password}
                        </dd>
                      </div>
                    </Fragment>
                  );
                }
                default:
                  assertUnreachable(challenge);
              }
            })()}
          </dl>
          {challenge.info && (
            <h2 class="text-base font-semibold leading-7 text-gray-900  mt-4">
              <span
                class="text-sm text-black font-semibold leading-6 "
                id="availability-label"
              >
                <i18n.Translate>Challenge details</i18n.Translate>
              </span>
            </h2>
          )}
          <dl class="divide-y divide-gray-100">
            {challenge.sent.t_ms !== "never" && (
              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                <dt class="text-sm font-medium leading-6 text-gray-900">
                  <i18n.Translate>Sent at</i18n.Translate>
                </dt>
                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                  <Time
                    format="dd/MM/yyyy HH:mm:ss"
                    timestamp={challenge.sent}
                    relative={Duration.fromSpec({ days: 1 })}
                  />
                </dd>
              </div>
            )}
            {challenge.info && (
              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                <dt class="text-sm font-medium leading-6 text-gray-900">
                  {((ch: TalerCorebankApi.TanChannel): VNode => {
                    switch (ch) {
                      case TalerCorebankApi.TanChannel.SMS:
                        return <i18n.Translate>To phone</i18n.Translate>;
                      case TalerCorebankApi.TanChannel.EMAIL:
                        return <i18n.Translate>To email</i18n.Translate>;
                      default:
                        assertUnreachable(ch);
                    }
                  })(challenge.info.tan_channel)}
                </dt>
                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                  {challenge.info.tan_info}
                </dd>
              </div>
            )}
          </dl>
        </div>
        <div class="mt-6 mb-4 flex justify-between">
          <button
            type="button"
            name="cancel"
            class="text-sm font-semibold leading-6 text-gray-900"
            onClick={onCancel}
          >
            <i18n.Translate>Cancel</i18n.Translate>
          </button>
          {challenge.info ? (
            <button
              type="submit"
              name="send again"
              class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
              onClick={(e) => {
                onStart();
                e.preventDefault();
              }}
            >
              <i18n.Translate>Send again</i18n.Translate>
            </button>
          ) : (
            <div> sending code ...</div>
          )}
        </div>
      </div>
    </div>
  );
}

function ShowWithdrawalDetails({ id }: { id: string }): VNode {
  const details = useWithdrawalDetails(id);
  const { i18n } = useTranslationContext();
  const { config } = useBankCoreApiContext();
  if (!details) {
    return <Loading />;
  }
  if (details instanceof TalerError) {
    return <ErrorLoadingWithDebug error={details} />;
  }
  if (details.type === "fail") {
    switch (details.case) {
      case HttpStatusCode.BadRequest:
      case HttpStatusCode.NotFound:
        return <OperationNotFound routeClose={undefined} />;
      default:
        assertUnreachable(details);
    }
  }

  return (
    <Fragment>
      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
        <dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
          {details.body.amount !== undefined ? (
            <RenderAmount
              value={Amounts.parseOrThrow(details.body.amount)}
              spec={config.currency_specification}
            />
          ) : (
            <i18n.Translate>No amount specified yet.</i18n.Translate>
          )}
        </dd>
      </div>
      {details.body.selected_reserve_pub !== undefined && (
        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
          <dt class="text-sm font-medium leading-6 text-gray-900">
            <i18n.Translate>Withdraw id</i18n.Translate>
          </dt>
          <dd
            class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"
            title={details.body.selected_reserve_pub}
          >
            {details.body.selected_reserve_pub.substring(0, 16)}...
          </dd>
        </div>
      )}
      {details.body.selected_exchange_account !== undefined && (
        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
          <dt class="text-sm font-medium leading-6 text-gray-900">
            <i18n.Translate>To account</i18n.Translate>
          </dt>
          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
            {details.body.selected_exchange_account}
          </dd>
        </div>
      )}
    </Fragment>
  );
}

function ShowCashoutDetails({
  request,
}: {
  request: TalerCorebankApi.CashoutRequest;
}): VNode {
  const { i18n } = useTranslationContext();
  const info = useConversionInfo();
  if (!info) {
    return <Loading />;
  }

  if (info instanceof TalerError) {
    return <ErrorLoadingWithDebug error={info} />;
  }
  if (info.type === "fail") {
    switch (info.case) {
      case HttpStatusCode.NotImplemented: {
        return (
          <Attention type="danger" title={i18n.str`Cashout are disabled`}>
            <i18n.Translate>
              Cashout should be enable by configuration and the conversion rate
              should be initialized with fee, ratio and rounding mode.
            </i18n.Translate>
          </Attention>
        );
      }
      default:
        assertUnreachable(info.case);
    }
  }

  return (
    <Fragment>
      {request.subject !== undefined && (
        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
          <dt class="text-sm font-medium leading-6 text-gray-900">
            <i18n.Translate>Subject</i18n.Translate>
          </dt>
          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
            {request.subject}
          </dd>
        </div>
      )}
      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
        <dt class="text-sm font-medium leading-6 text-gray-900">Debit</dt>
        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
          <RenderAmount
            value={Amounts.parseOrThrow(request.amount_credit)}
            spec={info.body.regional_currency_specification}
          />
        </dd>
      </div>
      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
        <dt class="text-sm font-medium leading-6 text-gray-900">Credit</dt>
        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
          <RenderAmount
            value={Amounts.parseOrThrow(request.amount_credit)}
            spec={info.body.fiat_currency_specification}
          />
        </dd>
      </div>
    </Fragment>
  );
}
