/*
 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 {
  AmountString,
  Amounts,
  HostPortPath,
  IbanString,
  PaytoString,
  Paytos,
  TalerCorebankApi,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  CopyButton,
  ShowInputErrorLabel,
  useBankCoreApiContext,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { ComponentChildren, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useSessionState } from "../../hooks/session.js";
import {
  ErrorMessageMappingFor,
  TanChannel,
  undefinedIfEmpty,
  validateIBAN,
  validateTalerBank,
} from "../../utils.js";
import {
  InputAmount,
  TextField,
  doAutoFocus,
} from "../PaytoWireTransferForm.js";
import { getRandomPassword } from "../rnd.js";
import { PaytoType } from "@gnu-taler/taler-util";

const TALER_SCREEN_ID = 120;

const EMAIL_REGEX =
  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;

export type AccountFormData = {
  debit_threshold?: string;
  isExchange?: boolean;
  isPublic?: boolean;
  name?: string;
  username?: string;
  payto_uri?: string;
  cashout_payto_uri?: string;
  email?: string;
  phone?: string;
  tan_channel?: TanChannel | "remove";
};

type ChangeByPurposeType = {
  create: (a: TalerCorebankApi.RegisterAccountRequest | undefined) => void;
  update: (a: TalerCorebankApi.AccountReconfiguration | undefined) => void;
  show: undefined;
};
/**
 * FIXME:
 * is_public is missing on PATCH
 * account email/password should require 2FA
 *
 *
 * @param param0
 * @returns
 */
export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
  template,
  username,
  purpose,
  onChange,
  focus,
  children,
}: {
  focus?: boolean;
  children: ComponentChildren;
  username?: string;
  template: TalerCorebankApi.AccountData | undefined;
  onChange: ChangeByPurposeType[PurposeType];
  purpose: PurposeType;
}): VNode {
  const { config, url } = useBankCoreApiContext();
  const { i18n } = useTranslationContext();
  const { state: credentials } = useSessionState();
  const [form, setForm] = useState<AccountFormData>({});

  const [errors, setErrors] = useState<
    ErrorMessageMappingFor<typeof defaultValue> | undefined
  >(undefined);

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

  const defaultValue: AccountFormData = {
    debit_threshold: Amounts.stringifyValue(
      template?.debit_threshold ??
        config.default_debit_threshold ??
        `${config.currency}:0`,
    ),
    isExchange: template?.is_taler_exchange,
    isPublic: template?.is_public,
    name: template?.name ?? "",
    cashout_payto_uri:
      getAccountId(
        cashoutPaytoType,
        template?.cashout_payto_uri as Paytos.FullPaytoString,
      ) ?? "",
    payto_uri:
      getAccountId(paytoType, template?.payto_uri as Paytos.FullPaytoString) ??
      "",
    email: template?.contact_data?.email ?? "",
    phone: template?.contact_data?.phone ?? "",
    username: username ?? "",
    tan_channel: template?.tan_channel,
  };

  const userIsAdmin =
    credentials.status !== "loggedIn" ? false : credentials.isUserAdministrator;

  const editableUsername = purpose === "create";
  const editableName =
    purpose === "create" ||
    (purpose === "update" && (config.allow_edit_name || userIsAdmin));

  const isCashoutEnabled = config.allow_conversion;
  const editableCashout =
    purpose === "create" ||
    (purpose === "update" &&
      (config.allow_edit_cashout_payto_uri || userIsAdmin));
  const editableThreshold =
    userIsAdmin && (purpose === "create" || purpose === "update");
  const editableAccount = purpose === "create" && userIsAdmin;

  const hasPhone = !!defaultValue.phone || !!form.phone;
  const hasEmail = !!defaultValue.email || !!form.email;

  function updateForm(newForm: typeof defaultValue): void {
    const trimmedDebitThresholdStr = newForm.debit_threshold?.trim();
    const parsedDebitThreshold = Amounts.parse(
      `${config.currency}:${trimmedDebitThresholdStr}`,
    );

    const errors = undefinedIfEmpty<
      ErrorMessageMappingFor<typeof defaultValue>
    >({
      cashout_payto_uri: !newForm.cashout_payto_uri
        ? undefined
        : !editableCashout
          ? undefined
          : !newForm.cashout_payto_uri
            ? undefined
            : cashoutPaytoType === "iban"
              ? validateIBAN(newForm.cashout_payto_uri, i18n)
              : cashoutPaytoType === "x-taler-bank"
                ? validateTalerBank(newForm.cashout_payto_uri, i18n)
                : undefined,

      payto_uri: !newForm.payto_uri
        ? undefined
        : !editableAccount
          ? undefined
          : !newForm.payto_uri
            ? undefined
            : paytoType === "iban"
              ? validateIBAN(newForm.payto_uri, i18n)
              : paytoType === "x-taler-bank"
                ? validateTalerBank(newForm.payto_uri, i18n)
                : undefined,

      email: !newForm.email
        ? undefined
        : !EMAIL_REGEX.test(newForm.email)
          ? i18n.str`Doesn't have the pattern of an email`
          : undefined,
      phone: !newForm.phone
        ? undefined
        : !newForm.phone.startsWith("+") // FIXME: better phone number check
          ? i18n.str`Should start with +`
          : !REGEX_JUST_NUMBERS_REGEX.test(newForm.phone)
            ? i18n.str`A phone number consists of numbers only`
            : undefined,
      debit_threshold: !editableThreshold
        ? undefined
        : !trimmedDebitThresholdStr
          ? undefined
          : !parsedDebitThreshold
            ? i18n.str`Not valid`
            : undefined,
      name: !editableName
        ? undefined // disabled
        : purpose === "update" && newForm.name === undefined
          ? undefined // the field hasn't been changed
          : !newForm.name
            ? i18n.str`Required`
            : undefined,
      username: !editableUsername
        ? undefined
        : !newForm.username
          ? i18n.str`Required`
          : undefined,
    });
    setErrors(errors);

    setForm(newForm);
    if (!onChange) return;

    if (errors) {
      onChange(undefined);
    } else {
      let cashout: Paytos.URI | undefined;
      if (newForm.cashout_payto_uri)
        switch (cashoutPaytoType) {
          case "x-taler-bank": {
            cashout = Paytos.createTalerBank(
              url.host as HostPortPath,
              newForm.cashout_payto_uri,
            );
            break;
          }
          case "iban": {
            cashout = Paytos.createIban(
              newForm.cashout_payto_uri as IbanString,
              undefined,
            );
            break;
          }
          default:
            assertUnreachable(cashoutPaytoType);
        }
      const cashoutURI = !cashout ? null : Paytos.toFullString(cashout);
      let internal: Paytos.URI | undefined;
      if (newForm.payto_uri)
        switch (paytoType) {
          case "x-taler-bank": {
            internal = Paytos.createTalerBank(
              url.host as HostPortPath,
              newForm.payto_uri,
            );
            break;
          }
          case "iban": {
            internal = Paytos.createIban(
              newForm.payto_uri as IbanString,
              undefined,
            );
            break;
          }
          default:
            assertUnreachable(paytoType);
        }
      const internalURI = !internal ? undefined : Paytos.toFullString(internal);

      const threshold = !parsedDebitThreshold
        ? undefined
        : Amounts.stringify(parsedDebitThreshold);

      switch (purpose) {
        case "create": {
          // typescript doesn't correctly narrow a generic type
          const callback = onChange as ChangeByPurposeType["create"];
          const result: TalerCorebankApi.RegisterAccountRequest = {
            name: newForm.name!,
            password: getRandomPassword(),
            username: newForm.username!,
            contact_data: undefinedIfEmpty({
              email: !newForm.email ? undefined : newForm.email,
              phone: !newForm.phone ? undefined : newForm.phone,
            }),
            debit_threshold: threshold ?? config.default_debit_threshold,
            cashout_payto_uri: cashoutURI === null ? undefined : cashoutURI,
            payto_uri: internalURI,
            is_public: newForm.isPublic,
            is_taler_exchange: newForm.isExchange,
            tan_channel:
              newForm.tan_channel === "remove"
                ? undefined
                : newForm.tan_channel,
          };
          callback(result);
          return;
        }
        case "update": {
          // typescript doesn't correctly narrow a generic type
          const callback = onChange as ChangeByPurposeType["update"];

          const result: TalerCorebankApi.AccountReconfiguration = {
            cashout_payto_uri: cashoutURI,
            contact_data: undefinedIfEmpty({
              email: !newForm.email ? undefined : newForm.email,
              phone: !newForm.phone ? undefined : newForm.phone,
            }),
            debit_threshold: threshold,
            is_public: newForm.isPublic,
            name: newForm.name,
            tan_channel:
              newForm.tan_channel === "remove" ? null : newForm.tan_channel,
          };
          callback(result);
          return;
        }
        case "show": {
          return;
        }
        default: {
          assertUnreachable(purpose);
        }
      }
    }
  }
  return (
    <form
      class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
      autoCapitalize="none"
      autoCorrect="off"
      onSubmit={(e) => {
        e.preventDefault();
      }}
    >
      <div class="px-4 py-6 sm:p-8">
        <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="username"
            >
              {i18n.str`Login username`}
              {editableUsername && <b class="text-[red]"> *</b>}
            </label>
            <div class="mt-2">
              <input
                ref={focus && purpose === "create" ? doAutoFocus : undefined}
                type="text"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="username"
                id="username"
                data-error={!!errors?.username && form.username !== undefined}
                disabled={!editableUsername}
                value={form.username ?? defaultValue.username}
                onChange={(e) => {
                  form.username = e.currentTarget.value;
                  updateForm(structuredClone(form));
                }}
                // placeholder=""
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.username}
                isDirty={form.username !== undefined}
              />
            </div>
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>Account ID for authentication</i18n.Translate>
            </p>
          </div>

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="name"
            >
              {i18n.str`Full name`}
              {editableName && <b class="text-[red]"> *</b>}
            </label>
            <div class="mt-2">
              <input
                type="text"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="name"
                data-error={!!errors?.name && form.name !== undefined}
                id="name"
                disabled={!editableName}
                value={form.name ?? defaultValue.name}
                onChange={(e) => {
                  form.name = e.currentTarget.value;
                  updateForm(structuredClone(form));
                }}
                // placeholder=""
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.name}
                isDirty={form.name !== undefined}
              />
            </div>
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>Name of the account holder</i18n.Translate>
            </p>
          </div>

          {purpose === "create" ? undefined : (
            <TextField
              id="internal-account"
              label={i18n.str`Internal account`}
              help={
                purpose === "create"
                  ? i18n.str`If this field is empty, a random account ID will be assigned`
                  : i18n.str`You can copy and share this IBAN number in order to receive wire transfers to your bank account`
              }
              error={errors?.payto_uri}
              onChange={(e) => {
                form.payto_uri = e as PaytoString;
                updateForm(structuredClone(form));
              }}
              rightIcons={
                <CopyButton
                  class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
                  getContent={() =>
                    form.payto_uri ?? defaultValue.payto_uri ?? ""
                  }
                />
              }
              value={(form.payto_uri ?? defaultValue.payto_uri) as PaytoString}
              disabled={!editableAccount}
            />
          )}

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="email"
            >
              {i18n.str`Email`}
            </label>
            <div class="mt-2">
              <input
                type="email"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="email"
                id="email"
                data-error={!!errors?.email && form.email !== undefined}
                disabled={purpose === "show"}
                value={form.email ?? defaultValue.email}
                onChange={(e) => {
                  form.email = e.currentTarget.value;
                  updateForm(structuredClone(form));
                }}
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.email}
                isDirty={form.email !== undefined}
              />
            </div>
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>
                To be used when second factor authentication is enabled
              </i18n.Translate>
            </p>
          </div>

          <div class="sm:col-span-5">
            <label
              class="block text-sm font-medium leading-6 text-gray-900"
              for="phone"
            >
              {i18n.str`Phone`}
            </label>
            <div class="mt-2">
              <input
                type="text"
                class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                name="phone"
                id="phone"
                disabled={purpose === "show"}
                value={form.phone ?? defaultValue.phone}
                data-error={!!errors?.phone && form.phone !== undefined}
                onChange={(e) => {
                  form.phone = e.currentTarget.value;
                  updateForm(structuredClone(form));
                }}
                autocomplete="off"
              />
              <ShowInputErrorLabel
                message={errors?.phone}
                isDirty={form.phone !== undefined}
              />
            </div>
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>
                To be used when second factor authentication is enabled
              </i18n.Translate>
            </p>
          </div>

          {!config.supported_tan_channels ||
          config.supported_tan_channels.length === 0 ? undefined : (
            <div class="sm:col-span-5">
              <label
                class="block text-sm font-medium leading-6 text-gray-900"
                for="channel"
              >
                {i18n.str`Enable second factor authentication`}
              </label>
              <div class="mt-2 max-w-xl text-sm text-gray-500">
                <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
                  {config.supported_tan_channels.indexOf(TanChannel.EMAIL) ===
                  -1 ? undefined : (
                    <label
                      onClick={(e) => {
                        if (!hasEmail) return;
                        if (form.tan_channel === TanChannel.EMAIL) {
                          form.tan_channel = "remove";
                        } else {
                          form.tan_channel = TanChannel.EMAIL;
                        }
                        updateForm(structuredClone(form));
                        e.preventDefault();
                      }}
                      data-disabled={purpose === "show" || !hasEmail}
                      data-selected={
                        (form.tan_channel ?? defaultValue.tan_channel) ===
                        TanChannel.EMAIL
                      }
                      class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border bg-white data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600"
                    >
                      <input
                        type="radio"
                        name="channel"
                        value="Newsletter"
                        class="sr-only"
                      />
                      <span class="flex flex-1">
                        <span class="flex flex-col">
                          <span
                            id="project-type-0-label"
                            class="block text-sm font-medium text-gray-900 "
                          >
                            <i18n.Translate>Using email</i18n.Translate>
                          </span>
                          {purpose !== "show" &&
                            !hasEmail &&
                            i18n.str`Add an email in your profile to enable this option`}
                        </span>
                      </span>
                      <svg
                        data-selected={
                          (form.tan_channel ?? defaultValue.tan_channel) ===
                          TanChannel.EMAIL
                        }
                        class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden"
                        viewBox="0 0 20 20"
                        fill="currentColor"
                        aria-hidden="true"
                      >
                        <path
                          fill-rule="evenodd"
                          d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
                          clip-rule="evenodd"
                        />
                      </svg>
                    </label>
                  )}

                  {config.supported_tan_channels.indexOf(TanChannel.SMS) ===
                  -1 ? undefined : (
                    <label
                      onClick={(e) => {
                        if (!hasPhone) return;
                        if (form.tan_channel === TanChannel.SMS) {
                          form.tan_channel = "remove";
                        } else {
                          form.tan_channel = TanChannel.SMS;
                        }
                        updateForm(structuredClone(form));
                        e.preventDefault();
                      }}
                      data-disabled={purpose === "show" || !hasPhone}
                      data-selected={
                        (form.tan_channel ?? defaultValue.tan_channel) ===
                        TanChannel.SMS
                      }
                      class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600"
                    >
                      <input
                        type="radio"
                        name="channel"
                        value="Existing Customers"
                        class="sr-only"
                      />
                      <span class="flex flex-1">
                        <span class="flex flex-col">
                          <span
                            id="project-type-1-label"
                            class="block text-sm font-medium text-gray-900"
                          >
                            <i18n.Translate>Using SMS</i18n.Translate>
                          </span>
                          {purpose !== "show" &&
                            !hasPhone &&
                            i18n.str`Add a phone number in your profile to enable this option`}
                        </span>
                      </span>
                      <svg
                        data-selected={
                          (form.tan_channel ?? defaultValue.tan_channel) ===
                          TanChannel.SMS
                        }
                        class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden"
                        viewBox="0 0 20 20"
                        fill="currentColor"
                        aria-hidden="true"
                      >
                        <path
                          fill-rule="evenodd"
                          d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
                          clip-rule="evenodd"
                        />
                      </svg>
                    </label>
                  )}
                </div>
              </div>
            </div>
          )}

          {isCashoutEnabled && (
            <TextField
              id="cashout-account"
              label={i18n.str`Cashout account`}
              help={i18n.str`External account number where the money is going to be sent when doing cashouts`}
              error={errors?.cashout_payto_uri}
              onChange={(e) => {
                form.cashout_payto_uri = e as PaytoString;
                updateForm(structuredClone(form));
              }}
              value={
                (form.cashout_payto_uri ??
                  defaultValue.cashout_payto_uri) as PaytoString
              }
              disabled={!editableCashout}
            />
          )}

          <div class="sm:col-span-5">
            <label
              for="debit"
              class="block text-sm font-medium leading-6 text-gray-900"
            >{i18n.str`Max debt`}</label>
            <InputAmount
              name="debit"
              left
              currency={config.currency}
              value={form.debit_threshold ?? defaultValue.debit_threshold}
              onChange={
                !editableThreshold
                  ? undefined
                  : (e) => {
                      form.debit_threshold = e as AmountString;
                      updateForm(structuredClone(form));
                    }
              }
            />
            <ShowInputErrorLabel
              message={
                errors?.debit_threshold
                  ? String(errors?.debit_threshold)
                  : undefined
              }
              isDirty={form.debit_threshold !== undefined}
            />
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>
                How much the balance can go below zero.
              </i18n.Translate>
            </p>
          </div>

          <div class="sm:col-span-5">
            <div class="flex items-center justify-between">
              <span class="flex flex-grow flex-col">
                <span
                  class="text-sm text-black font-medium leading-6 "
                  id="availability-label"
                >
                  <i18n.Translate>Is this account public?</i18n.Translate>
                </span>
              </span>
              <button
                type="button"
                name="is public"
                data-enabled={
                  form.isPublic ?? defaultValue.isPublic ? "true" : "false"
                }
                class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
                role="switch"
                aria-checked="false"
                aria-labelledby="availability-label"
                aria-describedby="availability-description"
                onClick={() => {
                  form.isPublic = !(form.isPublic ?? defaultValue.isPublic);
                  updateForm(structuredClone(form));
                }}
              >
                <span
                  aria-hidden="true"
                  data-enabled={
                    form.isPublic ?? defaultValue.isPublic ? "true" : "false"
                  }
                  class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
                ></span>
              </button>
            </div>
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>
                Public accounts have their balance publicly accessible
              </i18n.Translate>
            </p>
          </div>

          {purpose !== "create" || !userIsAdmin ? undefined : (
            <div class="sm:col-span-5">
              <div class="flex items-center justify-between">
                <span class="flex flex-grow flex-col">
                  <span
                    class="text-sm text-black font-medium leading-6 "
                    id="availability-label"
                  >
                    <i18n.Translate>
                      Does this account belong to a Payment Service Provider?
                    </i18n.Translate>
                  </span>
                </span>
                <button
                  type="button"
                  name="is exchange"
                  data-enabled={
                    form.isExchange ?? defaultValue.isExchange
                      ? "true"
                      : "false"
                  }
                  class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
                  role="switch"
                  aria-checked="false"
                  aria-labelledby="availability-label"
                  aria-describedby="availability-description"
                  onClick={() => {
                    form.isExchange = !form.isExchange;
                    updateForm(structuredClone(form));
                  }}
                >
                  <span
                    aria-hidden="true"
                    data-enabled={
                      form.isExchange ?? defaultValue.isExchange
                        ? "true"
                        : "false"
                    }
                    class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
                  ></span>
                </button>
              </div>
            </div>
          )}
        </div>
      </div>
      {children}
    </form>
  );
}

function getAccountId(
  type: "iban" | "x-taler-bank",
  s: Paytos.FullPaytoString | undefined,
): string | undefined {
  if (s === undefined) return undefined;
  const p = Paytos.fromString(s);
  if (p === undefined || p.type === "fail") return undefined;
  if (!p.body.targetType === undefined) return "<unknown>";
  if (type === "iban" && p.body.targetType === PaytoType.IBAN)
    return p.body.iban;
  if (type === "x-taler-bank" && p.body.targetType === PaytoType.TalerBank)
    return p.body.account;
  return "<unsupported>";
}
