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

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

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

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

import {
  parsePaytoUri,
  PaytoString,
  stringifyPaytoUri,
  WalletBankAccountInfo,
  WireTypeDetails,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { useState } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { AccountByType, Props, State } from "./index.js";

export function useComponentState({
  scope,
  onAccountSelected,
  onCancel,
}: Props): State {
  const api = useBackendContext();
  const { pushAlertOnError } = useAlertContext();
  const { i18n } = useTranslationContext();

  const hook = useAsyncAsHook(() =>
    api.wallet.call(WalletApiOperation.ListBankAccounts, {
      currency: scope.currency,
    }),
  );

  const hook2 = useAsyncAsHook(() =>
    api.wallet.call(WalletApiOperation.GetDepositWireTypesForCurrency, {
      currency: scope.currency,
      scopeInfo: scope,
    }),
  );

  if (!hook || !hook2) {
    return {
      status: "loading",
      error: undefined,
    };
  }

  if (hook.hasError) {
    return {
      status: "error",
      error: alertFromError(
        i18n,
        i18n.str`Could not load known bank accounts`,
        hook,
      ),
    };
  }

  if (hook2.hasError) {
    return {
      status: "error",
      error: alertFromError(
        i18n,
        i18n.str`Could not load supported wire methods`,
        hook2,
      ),
    };
  }

  if (hook2.response.wireTypeDetails.length === 0) {
    return {
      status: "error",
      error: {
        type: "error",
        message: i18n.str`No wire methods supported for this currency`,
        description: i18n.str`Try another wire method.`,
        cause: new Error("something"),
        context: {},
      },
    };
  }

  const [payto, setPayto] = useState("");
  const [label, setLabel] = useState("");
  const [type, setType] = useState(hook2.response.wireTypeDetails[0]);

  const detailsByType: Record<string, WireTypeDetails> = {};
  const accountTypeName: Record<string, string> = {};
  hook2.response.wireTypeDetails.forEach((t) => {
    detailsByType[t.paymentTargetType] = t;
    if (t.paymentTargetType === "iban") {
      accountTypeName[t.paymentTargetType] = "IBAN";
    } else if (t.paymentTargetType === "x-taler-bank") {
      accountTypeName[t.paymentTargetType] = "x-taler-bank";
    } else if (t.paymentTargetType === "bitcoin") {
      accountTypeName[t.paymentTargetType] = "Bitcoin";
    }
  });

  const uri = parsePaytoUri(payto);
  const found =
    hook.response.accounts.findIndex((a) => a.paytoUri === payto) !== -1;

  async function addAccount(): Promise<void> {
    if (!uri || found) return;

    const normalizedPayto = stringifyPaytoUri(uri);
    await api.wallet.call(WalletApiOperation.AddBankAccount, {
      label,
      currencies: [scope.currency],
      paytoUri: normalizedPayto,
    });
    onAccountSelected(normalizedPayto);
  }

  const paytoUriError = found ? "that account is already present" : undefined;

  const unableToAdd =
    !type || !label || paytoUriError !== undefined || uri === undefined;

  const accountByType: AccountByType = {
    iban: [],
    bitcoin: [],
    "x-taler-bank": [],
  };

  hook.response.accounts.forEach((acc) => {
    const p = parsePaytoUri(acc.paytoUri)!;
    accountByType[p.targetType].push(acc);
  });

  async function deleteAccount(account: WalletBankAccountInfo): Promise<void> {
    const payto = account.paytoUri;
    await api.wallet.call(WalletApiOperation.ForgetBankAccount, {
      bankAccountId: account.bankAccountId,
    });
    hook?.retry();
  }

  return {
    status: "ready",
    error: undefined,
    currency: scope.currency,
    details: type,
    accountType: {
      list: accountTypeName,
      value: type.paymentTargetType,
      onChange: pushAlertOnError(async (v) => {
        setType(detailsByType[v]);
      }),
    },
    selectAccount: async (w) => {
      onAccountSelected(w.paytoUri as PaytoString); // trust in the wallet that the payto is correct
    },
    alias: {
      value: label,
      onInput: pushAlertOnError(async (v) => {
        setLabel(v);
      }),
    },
    uri: {
      value: payto,
      error: paytoUriError,
      onInput: pushAlertOnError(async (v) => {
        setPayto(v);
      }),
    },
    accountByType,
    deleteAccount: pushAlertOnError(deleteAccount),
    onAccountAdded: {
      onClick: unableToAdd ? undefined : pushAlertOnError(addAccount),
    },
    onCancel: {
      onClick: pushAlertOnError(async () => onCancel()),
    },
  };
}
