/*
 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 {
  AmountJson,
  AmountString,
  Amounts,
  CurrencySpecification,
  FRAC_SEPARATOR,
  HostPortPath,
  HttpStatusCode,
  IbanString,
  PaytoType,
  Paytos,
  TalerCorebankApi,
  TalerErrorCode,
  TranslatedString,
  assertUnreachable
} from "@gnu-taler/taler-util";
import {
  ButtonBetter,
  InternationalizationAPI,
  LocalNotificationBanner,
  RouteDefinition,
  ShowInputErrorLabel,
  makeSafeCall,
  notifyInfo,
  useBankCoreApiContext,
  useChallengeHandler,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, Ref, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { LoggedIn, useSessionState } from "../hooks/session.js";
import { undefinedIfEmpty, validateIBAN, validateTalerBank } from "../utils.js";
import { SolveMFAChallenges } from "./SolveMFA.js";
import { IntAmountJson, IntAmounts } from "./regional/CreateCashout.js";

const TALER_SCREEN_ID = 106;

export interface Props {
  focus?: boolean;
  withAccount?: string;
  withSubject?: string;
  withAmount?: string;
  onSuccess: () => void;
  routeCancel?: RouteDefinition;
  routeCashout?: RouteDefinition;
  limit: IntAmountJson;
  balance: AmountJson;
}

export function PaytoWireTransferForm({
  focus,
  withAccount,
  withSubject,
  withAmount,
  onSuccess,
  routeCancel,
  routeCashout,
  limit,
  balance,
}: Props): VNode {
  const [inputType, setInputType] = useState<"form" | "payto" | "qr">("form");
  const isRawPayto = inputType !== "form";

  const { state: credentials } = useSessionState();
  const {
    lib: { bank: api },
    config,
    url,
  } = useBankCoreApiContext();

  const sendingToFixedAccount = withAccount !== undefined;

  const [account, setAccount] = useState<string | undefined>(withAccount);
  const [subject, setSubject] = useState<string | undefined>(withSubject);
  const [amount, setAmount] = useState<string | undefined>(withAmount);

  const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
    undefined,
  );
  const { i18n } = useTranslationContext();
  const wireFee =
    config.wire_transfer_fees === undefined
      ? Amounts.zeroOfCurrency(config.currency)
      : Amounts.parseOrThrow(config.wire_transfer_fees);

  const trimmedAmountStr = amount?.trim();
  const limitWithFee = IntAmounts.from(limit)
    .deduce(wireFee)
    .getResultZeroIfNegative();
  const parsedAmount = Amounts.parse(
    `${limitWithFee.currency}:${trimmedAmountStr}`,
  );
  const [notification, notifyOnError] = useLocalNotificationBetter();
  const mfa = useChallengeHandler();

  const paytoType =
    config.wire_type === "X_TALER_BANK"
      ? ("x-taler-bank" as const)
      : ("iban" as const);

  const errorsWire = undefinedIfEmpty({
    account: !account
      ? i18n.str`Required`
      : paytoType === "iban"
        ? validateIBAN(account, i18n)
        : paytoType === "x-taler-bank"
          ? validateTalerBank(account, i18n)
          : undefined,
    subject: !subject ? i18n.str`Required` : validateSubject(subject, i18n),
    amount: !trimmedAmountStr
      ? i18n.str`Required`
      : !parsedAmount
        ? i18n.str`Not valid`
        : validateAmount(parsedAmount, limitWithFee, i18n),
  });

  const foundPayto = searchPaytoInHumanReadableText(rawPaytoInput);
  const parsed = !foundPayto ? undefined : Paytos.fromString(foundPayto);

  const errorsPayto = undefinedIfEmpty({
    rawPaytoInput: !rawPaytoInput
      ? i18n.str`Required`
      : !parsed || parsed.type === "fail"
        ? i18n.str`Does not follow the pattern`
        : validateRawPayto(
            parsed.body,
            limitWithFee,
            url.host,
            i18n,
            paytoType,
          ),
  });

  let payto_uri: Paytos.FullPaytoString | undefined;
  let sendingAmount: AmountString | undefined;

  let acName: string | undefined;
  if (isRawPayto) {
    const res = Paytos.fromString(rawPaytoInput!);

    if (res && res.type === "ok") {
      const p = res.body;
      sendingAmount = p.params.amount as AmountString;
      delete p.params.amount;
      // if this payto is valid then it already have message
      payto_uri = Paytos.toFullString(p);
      acName =
        p.targetType === undefined
          ? undefined
          : p.targetType === PaytoType.IBAN
            ? p.iban
            : p.targetType === PaytoType.TalerReserve ||
                p.targetType === PaytoType.TalerReserveHttp
              ? undefined // FIXME: unsupported payto://
              : p.targetType === PaytoType.Bitcoin
                ? p.address
                : p.targetType === PaytoType.Ethereum
                  ? p.address
                  : p.targetType === PaytoType.TalerBank
                    ? p.account
                    : assertUnreachable(p);
    }
  } else if (account && subject) {
    // if (!account || !subject) return;
    let payto;
    acName = account;
    switch (paytoType) {
      case "x-taler-bank": {
        payto = Paytos.createTalerBank(url.host as HostPortPath, account);
        break;
      }
      case "iban": {
        payto = Paytos.createIban(account as IbanString, undefined);
        break;
      }
      default:
        assertUnreachable(paytoType);
    }

    payto.params.message = encodeURIComponent(subject);
    payto_uri = Paytos.toFullString(payto);
    sendingAmount =
      `${limitWithFee.currency}:${trimmedAmountStr}` as AmountString;
  }
  const puri = payto_uri;
  const sAmount = sendingAmount;

  const request: TalerCorebankApi.CreateTransactionRequest | undefined =
    !payto_uri
      ? undefined
      : {
          payto_uri: puri!,
          amount: sAmount,
        };

  type reqType = TalerCorebankApi.CreateTransactionRequest;

  const [doTransfer, repeatTransfer, lastCallingArgs] = mfa.withMfaHandler(
    ({ challengeIds, onChallengeRequired }) =>
      makeSafeCall(
        i18n,
        (credentials: LoggedIn, request: reqType) =>
          api.createTransaction(credentials, request, {
            challengeIds,
          }),
        (success) => {
          notifyInfo(i18n.str`The wire transfer was successfully completed!`);
          onSuccess();
          setAmount(undefined);
          setAccount(undefined);
          setSubject(undefined);
          rawPaytoInputSetter(undefined);
        },
        (fail) => {
          switch (fail.case) {
            case HttpStatusCode.BadRequest:
              return i18n.str`The request was invalid or the payto://-URI used unacceptable features.`;
            case HttpStatusCode.Unauthorized:
              return i18n.str`Not enough permission to complete the operation.`;
            case TalerErrorCode.BANK_ADMIN_CREDITOR:
              return i18n.str`The bank administrator cannot be the transfer creditor.`;
            case TalerErrorCode.BANK_UNKNOWN_CREDITOR:
              return i18n.str`The destination account "${
                acName ?? puri
              }" was not found.`;
            case TalerErrorCode.BANK_SAME_ACCOUNT:
              return i18n.str`The origin and the destination of the transfer can't be the same.`;
            case TalerErrorCode.BANK_UNALLOWED_DEBIT:
              return i18n.str`Your balance is not sufficient for the operation.`;
            case HttpStatusCode.NotFound:
              return i18n.str`The origin account "${puri}" was not found.`;
            case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: {
              return i18n.str`The attempt to create the transaction has failed. Please try again.`;
            }
            case HttpStatusCode.Accepted: {
              onChallengeRequired(fail.body);
              return i18n.str`A second factor authentication is required.`;
            }
            default:
              assertUnreachable(fail);
          }
        },
      ),
  );
  const sendHandler =
    !request || credentials.status !== "loggedIn"
      ? undefined
      : () => notifyOnError(doTransfer)(credentials, request);

  if (mfa.pendingChallenge && repeatTransfer) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        description={i18n.str`Confirm wire transfer.`}
        onCancel={mfa.doCancelChallenge}
        username={lastCallingArgs[0].username}
        onCompleted={repeatTransfer}
      />
    );
  }

  return (
    <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-6 my-4 md:grid-cols-3 bg-gray-100 px-4 pb-4 rounded-lg">
      <div>
        <fieldset class="px-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
          <legend class="sr-only">
            <i18n.Translate>Input wire transfer detail</i18n.Translate>
          </legend>
          <div class="-space-y-px rounded-md ">
            <label
              data-checked={inputType === "form"}
              class="group rounded-tl-md rounded-tr-md relative flex cursor-pointer border p-4 focus:outline-none bg-white data-[checked=true]:z-10 data-[checked=true]:border-indigo-200 data-[checked=true]:bg-indigo-50"
            >
              <input
                type="radio"
                name="input-type"
                onChange={() => {
                  if (parsed && parsed.type === "ok") {
                    switch (parsed.body.targetType) {
                      case PaytoType.Ethereum:
                      case PaytoType.Bitcoin:
                      case undefined:
                      case PaytoType.TalerReserve:
                      case PaytoType.TalerReserveHttp: {
                        // FIXME: unsupported payto
                        break;
                      }
                      case PaytoType.IBAN: {
                        setAccount(parsed.body.iban);
                        break;
                      }
                      case PaytoType.TalerBank: {
                        setAccount(parsed.body.account);
                        break;
                      }
                      default: {
                        assertUnreachable(parsed.body);
                      }
                    }
                    const amountStr = !parsed.body.params
                      ? undefined
                      : parsed.body.params["amount"];
                    if (amountStr) {
                      const amount = Amounts.parse(amountStr);
                      if (amount) {
                        setAmount(Amounts.stringifyValue(amount));
                      }
                    }
                    const subject = !parsed.body.params["message"]
                      ? parsed.body.params["subject"]
                      : parsed.body.params["message"];
                    if (subject) {
                      setSubject(subject);
                    }
                  }
                  setInputType("form");
                }}
                checked={inputType === "form"}
                value="form"
                class="mt-0.5 h-4 w-4 shrink-0 cursor-pointer text-indigo-600 border-gray-300 focus:ring-indigo-600 active:ring-2 active:ring-offset-2 active:ring-indigo-600"
              />
              <span class="ml-3 flex flex-col">
                {/* <!-- Checked: "text-indigo-900", Not Checked: "text-gray-900" --> */}
                <span
                  data-checked={inputType === "form"}
                  class="block text-sm font-medium data-[checked=true]:text-indigo-900"
                >
                  <i18n.Translate>Using a form</i18n.Translate>
                </span>
              </span>
            </label>
            {sendingToFixedAccount ? undefined : (
              <Fragment>
                <label
                  data-checked={inputType === "payto"}
                  class="relative flex cursor-pointer border p-4 focus:outline-none bg-white data-[checked=true]:z-10 data-[checked=true]:border-indigo-200 data-[checked=true]:bg-indigo-50"
                >
                  <input
                    type="radio"
                    name="input-type"
                    onChange={() => {
                      if (account) {
                        let payto: Paytos.URI;
                        switch (paytoType) {
                          case "x-taler-bank": {
                            payto = Paytos.createTalerBank(
                              url.host as HostPortPath,
                              account,
                            );
                            if (parsedAmount) {
                              payto.params["amount"] =
                                Amounts.stringify(parsedAmount);
                            }
                            if (subject) {
                              payto.params["message"] = subject;
                            }
                            break;
                          }
                          case "iban": {
                            payto = Paytos.createIban(
                              account as IbanString,
                              undefined,
                            );
                            if (parsedAmount) {
                              payto.params["amount"] =
                                Amounts.stringify(parsedAmount);
                            }
                            if (subject) {
                              payto.params["message"] = subject;
                            }
                            break;
                          }
                          default:
                            assertUnreachable(paytoType);
                        }
                        rawPaytoInputSetter(Paytos.toFullString(payto));
                      }
                      setInputType("payto");
                    }}
                    checked={inputType === "payto"}
                    value="payto"
                    class="mt-0.5 h-4 w-4 shrink-0 cursor-pointer text-indigo-600 border-gray-300 focus:ring-indigo-600 active:ring-2 active:ring-offset-2 active:ring-indigo-600"
                  />
                  <span class="ml-3 flex flex-col">
                    <span
                      data-checked={inputType === "payto"}
                      class="block  font-medium data-[checked=true]:text-indigo-900"
                    >
                      payto:// URI
                    </span>
                    <span
                      data-checked={inputType === "payto"}
                      class="block text-sm text-gray-500 data-[checked=true]:text-indigo-600"
                    >
                      <i18n.Translate>
                        A special URI that specifies the amount to be
                        transferred and the destination account.
                      </i18n.Translate>
                    </span>
                  </span>
                </label>
                {
                  //FIXME: add QR support
                  false && (
                    <label
                      data-checked={inputType === "qr"}
                      class="rounded-bl-md rounded-br-md relative flex cursor-pointer border p-4 focus:outline-none bg-white data-[checked=true]:z-10 data-[checked=true]:border-indigo-200 data-[checked=true]:bg-indigo-50"
                    >
                      <input
                        type="radio"
                        name="input-type"
                        onChange={() => {
                          setInputType("qr");
                        }}
                        checked={inputType === "qr"}
                        value="qr"
                        class="mt-0.5 h-4 w-4 shrink-0 cursor-pointer text-indigo-600 border-gray-300 focus:ring-indigo-600 active:ring-2 active:ring-offset-2 active:ring-indigo-600"
                      />
                      <span class="ml-3 flex flex-col">
                        <span
                          data-checked={inputType === "qr"}
                          class="block font-medium data-[checked=true]:text-indigo-900"
                        >
                          <i18n.Translate>QR code</i18n.Translate>
                        </span>
                        <span
                          data-checked={inputType === "qr"}
                          class="block text-sm text-gray-500 data-[checked=true]:text-indigo-600"
                        >
                          <i18n.Translate>
                            If your device has a camera, you can import a
                            payto:// URI from a QR code.
                          </i18n.Translate>
                        </span>
                      </span>
                    </label>
                  )
                }
              </Fragment>
            )}
          </div>
          {routeCashout && config.allow_conversion ? (
            <a
              name="do cashout"
              href={routeCashout.url({})}
              class="bg-white p-4 rounded-lg text-sm font-semibold leading-6 text-gray-900"
            >
              <i18n.Translate>Cashout</i18n.Translate>
            </a>
          ) : undefined}
        </fieldset>
      </div>

      <form
        class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md sm:rounded-xl md:col-span-2 w-fit mx-auto"
        autoCapitalize="none"
        autoCorrect="off"
        onSubmit={(e) => {
          e.preventDefault();
        }}
      >
        <div class="m-4">
          {!isRawPayto ? (
            <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 ">
              {(() => {
                switch (paytoType) {
                  case "x-taler-bank": {
                    return (
                      <TextField
                        id="x-taler-bank"
                        required
                        label={i18n.str`Recipient`}
                        help={i18n.str`ID of the recipient's account`}
                        error={errorsWire?.account}
                        onChange={setAccount}
                        value={account}
                        placeholder={i18n.str`username`}
                        focus={focus}
                        disabled={sendingToFixedAccount}
                      />
                    );
                  }
                  case "iban": {
                    return (
                      <TextField
                        id="iban"
                        required
                        label={i18n.str`Recipient`}
                        help={i18n.str`IBAN of the recipient's account`}
                        placeholder={"CC0123456789" as TranslatedString}
                        error={errorsWire?.account}
                        onChange={(v) => setAccount(v.toUpperCase())}
                        value={account}
                        focus={focus}
                        disabled={sendingToFixedAccount}
                      />
                    );
                  }
                  default:
                    assertUnreachable(paytoType);
                }
              })()}

              <div class="sm:col-span-5">
                <label
                  for="subject"
                  class="block text-sm font-medium leading-6 text-gray-900"
                >
                  {i18n.str`Transfer subject`}
                  <b class="text-[red]"> *</b>
                </label>
                <div class="mt-2">
                  <textarea
                    type="textarea"
                    rows={3}
                    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"
                    name="subject"
                    id="subject"
                    autocomplete="off"
                    placeholder={i18n.str`Subject`}
                    value={subject ?? ""}
                    required
                    onInput={(e): void => {
                      setSubject(e.currentTarget.value);
                    }}
                  />
                  <ShowInputErrorLabel
                    message={errorsWire?.subject}
                    isDirty={subject !== undefined}
                  />
                </div>
                <p class="mt-2 text-sm text-gray-500">
                  <i18n.Translate>
                    Some text to identify the transfer
                  </i18n.Translate>
                </p>
              </div>

              <div class="sm:col-span-5">
                <label
                  for="amount"
                  class="block text-sm font-medium leading-6 text-gray-900"
                >
                  {i18n.str`Amount`}
                  <b class="text-[red]"> *</b>
                </label>
                <InputAmount
                  name="amount"
                  left
                  currency={limitWithFee.currency}
                  value={trimmedAmountStr}
                  onChange={(d) => {
                    setAmount(d);
                  }}
                />
                <ShowInputErrorLabel
                  message={errorsWire?.amount}
                  isDirty={trimmedAmountStr !== undefined}
                />
                <p class="mt-2 text-sm text-gray-500">
                  <i18n.Translate>Amount to transfer</i18n.Translate>
                </p>
              </div>
            </div>
          ) : (
            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6 w-full">
              <div class="sm:col-span-6">
                <label
                  for="address"
                  class="block text-sm font-medium leading-6 text-gray-900"
                >
                  {i18n.str`Payto URI:`}
                  <b class="text-[red]"> *</b>
                </label>
                <div class="mt-2">
                  <textarea
                    ref={focus ? doAutoFocus : undefined}
                    name="address"
                    id="address"
                    type="textarea"
                    rows={5}
                    class="block overflow-hidden w-44 sm:w-96 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={rawPaytoInput ?? ""}
                    required
                    title={i18n.str`Uniform resource identifier of the target account`}
                    placeholder={((): TranslatedString => {
                      switch (paytoType) {
                        case "x-taler-bank":
                          return i18n.str`payto://x-taler-bank/[bank-host]/[receiver-account]?message=[subject]&amount=[${limitWithFee.currency}:X.Y]`;
                        case "iban":
                          return i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limitWithFee.currency}:X.Y]`;
                      }
                    })()}
                    onInput={(e): void => {
                      rawPaytoInputSetter(e.currentTarget.value);
                    }}
                  />
                  <ShowInputErrorLabel
                    message={errorsPayto?.rawPaytoInput}
                    isDirty={rawPaytoInput !== undefined}
                  />
                </div>
              </div>
            </div>
          )}
          {Amounts.isNonZero(limitWithFee) ? (
            <p class="mt-2 text-sm text-gray-900">
              <i18n.Translate>
                The maximum amount for a wire transfer is{" "}
                <RenderAmount
                  value={limitWithFee}
                  spec={config.currency_specification}
                />
              </i18n.Translate>
            </p>
          ) : undefined}
        </div>
        {Amounts.isZero(wireFee) ? undefined : (
          <div class="px-4 my-4">
            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
              <div class="sm:col-span-6">
                <dl class="mt-4 space-y-4">
                  <Fragment>
                    <div class="flex items-center justify-between ">
                      <dt class="flex items-center text-sm text-gray-600">
                        <span>
                          <i18n.Translate>Cost</i18n.Translate>
                        </span>
                      </dt>
                      <dd class="text-sm text-gray-900">
                        <RenderAmount
                          value={wireFee}
                          negative
                          withColor
                          spec={config.currency_specification}
                        />
                      </dd>
                    </div>
                  </Fragment>
                </dl>
              </div>
            </div>
          </div>
        )}
        <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
          {routeCancel ? (
            <a
              name="cancel"
              href={routeCancel.url({})}
              class="text-sm font-semibold leading-6 text-gray-900"
            >
              <i18n.Translate>Cancel</i18n.Translate>
            </a>
          ) : (
            <div />
          )}
          <ButtonBetter
            type="submit"
            name="send"
            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={isRawPayto ? !!errorsPayto : !!errorsWire}
            onClick={sendHandler}
          >
            <i18n.Translate>Send</i18n.Translate>
          </ButtonBetter>
        </div>
        <LocalNotificationBanner notification={notification} />
      </form>
    </div>
  );
}

/**
 * Show the element when the load ended
 * @param element
 */
export function doAutoFocus(element: HTMLElement | null) {
  if (element) {
    setTimeout(() => {
      element.focus({ preventScroll: true });
      element.scrollIntoView({
        behavior: "smooth",
        block: "center",
        inline: "center",
      });
    }, 100);
  }
}

export function InputAmount(
  {
    currency,
    name,
    value,
    left,
    placeholder,
    onChange,
  }: {
    currency: string;
    name: string;
    left?: boolean | undefined;
    value: string | undefined;
    placeholder?: string | undefined;
    onChange?: (s: string) => void;
  },
  ref: Ref<HTMLInputElement>,
): VNode {
  const { config } = useBankCoreApiContext();
  return (
    <div class="mt-2">
      <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
        <div class="pointer-events-none inset-y-0 flex items-center px-3">
          <span class="text-gray-500 sm:text-sm">{currency}</span>
        </div>
        <input
          type="number"
          data-left={left}
          class="disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  placeholder:text-gray-400  sm:text-sm sm:leading-6"
          placeholder={placeholder ?? "0.00"}
          aria-describedby="price-currency"
          ref={ref}
          name={name}
          id={name}
          autocomplete="off"
          value={value ?? ""}
          disabled={!onChange}
          onInput={(e) => {
            if (!onChange) return;
            const l = e.currentTarget.value.length;
            const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR);
            if (
              sep_pos !== -1 &&
              l - sep_pos - 1 >
                config.currency_specification.num_fractional_input_digits
            ) {
              e.currentTarget.value = e.currentTarget.value.substring(
                0,
                sep_pos +
                  config.currency_specification.num_fractional_input_digits +
                  1,
              );
            }
            onChange(e.currentTarget.value);
          }}
        />
      </div>
    </div>
  );
}

/**
 * send to web-utils
 * @param param0
 * @returns
 */
export function RenderAmount({
  value,
  spec,
  negative = false,
  withColor = false,
  hideSmall = false,
  withSign = false,
}: {
  spec: CurrencySpecification;
  value: AmountJson;
  hideSmall?: boolean;
  negative?: boolean;
  withColor?: boolean;
  withSign?: boolean;
}): VNode {
  const neg = !!negative; // convert to true or false

  const { currency, normal, small } = Amounts.stringifyValueWithSpec(
    value,
    spec,
  );

  return (
    <span
      data-negative={withColor ? neg : undefined}
      class="whitespace-nowrap data-[negative=false]:text-green-600 data-[negative=true]:text-red-600"
    >
      {withSign && negative ? "- " : undefined}
      {currency} {normal}{" "}
      {!hideSmall && small && <sup class="-ml-1">{small}</sup>}
    </span>
  );
}

function validateRawPayto(
  parsed: Paytos.URI,
  limit: AmountJson,
  host: string,
  i18n: InternationalizationAPI,
  type: "iban" | "x-taler-bank",
): TranslatedString | undefined {
  let result: TranslatedString | undefined;
  switch (type) {
    case "x-taler-bank": {
      if (parsed.targetType !== "x-taler-bank") {
        return i18n.str`Only "x-taler-bank" target are supported`;
      }

      if (parsed.host !== host) {
        return i18n.str`Only this host is allowed. Use "${host}"`;
      }

      if (!parsed.account) {
        return i18n.str`Account name is missing`;
      }
      const result = validateTalerBank(parsed.account, i18n);
      if (result) return result;
      break;
    }
    case "iban": {
      if (parsed.targetType !== "iban") {
        return i18n.str`Only "IBAN" target are supported`;
      }
      const result = validateIBAN(parsed.iban, i18n);
      if (result) return result;
      break;
    }
    default:
      assertUnreachable(type);
  }
  if (!parsed.params.amount) {
    return i18n.str`Missing "amount" parameter to specify the amount to be transferred`;
  }
  const amount = Amounts.parse(parsed.params.amount);
  if (!amount) {
    return i18n.str`The "amount" parameter is not valid`;
  }
  result = validateAmount(amount, limit, i18n);
  if (result) return result;

  if (!parsed.params.message) {
    return i18n.str`"message" parameters to specify a reference text for the transfer are missing`;
  }
  result = validateSubject(parsed.params.message, i18n);
  if (result) return result;

  return undefined;
}

function validateAmount(
  amount: AmountJson,
  limit: AmountJson,
  i18n: InternationalizationAPI,
): TranslatedString | undefined {
  if (amount.currency !== limit.currency) {
    return i18n.str`The only currency allowed is "${limit.currency}"`;
  }
  if (Amounts.isZero(amount)) {
    return i18n.str`You cannot transfer an amount of zero.`;
  }
  if (Amounts.cmp(limit, amount) === -1) {
    return i18n.str`The balance is not sufficient`;
  }
  return undefined;
}

function validateSubject(
  text: string,
  i18n: InternationalizationAPI,
): TranslatedString | undefined {
  if (text.length < 2) {
    return i18n.str`Please enter a longer subject`;
  }
  return undefined;
}

interface PaytoFieldProps {
  id: string;
  label: TranslatedString;
  required?: boolean;
  help?: TranslatedString;
  placeholder?: TranslatedString;
  error: string | undefined;
  value: string | undefined;
  rightIcons?: VNode;
  onChange: (p: string) => void;
  focus?: boolean;
  disabled?: boolean;
}

function Wrapper({
  withIcon,
  children,
}: {
  withIcon: boolean;
  children: ComponentChildren;
}): VNode {
  if (withIcon) {
    return <div class="flex justify-between">{children}</div>;
  }
  return <Fragment>{children}</Fragment>;
}

export function TextField({
  id,
  label,
  help,
  focus,
  disabled,
  onChange,
  placeholder,
  rightIcons,
  required,
  value,
  error,
}: PaytoFieldProps): VNode {
  return (
    <div class="sm:col-span-5">
      <label for={id} class="block text-sm font-medium leading-6 text-gray-900">
        {label}
        {required && <b class="text-[red]"> *</b>}
      </label>
      <div class="mt-2">
        <Wrapper withIcon={rightIcons !== undefined}>
          <input
            ref={focus ? doAutoFocus : undefined}
            type="text"
            class="block w-full disabled:bg-gray-200 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"
            name={id}
            id={id}
            disabled={disabled}
            value={value ?? ""}
            placeholder={placeholder}
            autocomplete="off"
            required
            onInput={(e): void => {
              onChange(e.currentTarget.value);
            }}
          />
          {rightIcons}
        </Wrapper>
        <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
      </div>
      {help && <p class="mt-2 text-sm text-gray-500">{help}</p>}
    </div>
  );
}

const PAYTO_START_REGEX = /(?:payto:\/\/[^\s]*)/;

function searchPaytoInHumanReadableText(
  text: string | undefined,
): string | undefined {
  if (!text) return undefined;
  const result = PAYTO_START_REGEX.exec(text);
  if (!result) return undefined;
  return result[0];
}
