/*
 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 { Amounts, PreparePayResult } 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 { AmountFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
import { RecursiveState } from "../../utils/index.js";
import { Props, State } from "./index.js";

export function useComponentState({
  talerTemplateUri,
  cancel,
  goToWalletManualWithdraw,
  onSuccess,
}: Props): RecursiveState<State> {
  const api = useBackendContext();
  const { i18n } = useTranslationContext();
  const { safely } = useAlertContext();

  const [newOrder, setNewOrder] = useState("");

  const hook = useAsyncAsHook(async () => {
    if (!talerTemplateUri) throw Error("ERROR_NO-URI-FOR-PAYMENT-TEMPLATE");
    const templateP = await api.wallet.call(
      WalletApiOperation.CheckPayForTemplate,
      { talerPayTemplateUri: talerTemplateUri },
    );
    const requireMoreInfo =
      !templateP.templateDetails.template_contract.amount ||
      !templateP.templateDetails.template_contract.summary;
    let payStatus: PreparePayResult | undefined = undefined;
    if (!requireMoreInfo) {
      payStatus = await api.wallet.call(
        WalletApiOperation.PreparePayForTemplate,
        { talerPayTemplateUri: talerTemplateUri },
      );
    }
    const balance = await api.wallet.call(WalletApiOperation.GetBalances, {});
    return { payStatus, balance, uri: talerTemplateUri, templateP };
  }, []);

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

  if (hook.hasError) {
    return {
      status: "error",
      error: alertFromError(
        i18n,
        i18n.str`Could not load the status of the order template`,
        hook,
      ),
    };
  }

  if (hook.response.payStatus) {
    return {
      status: "order-ready",
      error: undefined,
      cancel,
      goToWalletManualWithdraw,
      onSuccess,
      talerPayUri: hook.response.payStatus.talerUri!,
    };
  }

  if (newOrder) {
    return {
      status: "order-ready",
      error: undefined,
      cancel,
      goToWalletManualWithdraw,
      onSuccess,
      talerPayUri: newOrder,
    };
  }

  return () => {
    const cfg = hook.response.templateP.templateDetails.template_contract;
    const def = hook.response.templateP.templateDetails.editable_defaults;

    const fixedAmount =
      cfg.amount !== undefined ? Amounts.parseOrThrow(cfg.amount) : undefined;
    const fixedSummary = cfg.summary;

    const defaultAmount =
      def?.amount !== undefined ? Amounts.parseOrThrow(def.amount) : undefined;
    const defaultSummary = def?.summary;

    const zero = fixedAmount
      ? Amounts.zeroOfAmount(fixedAmount)
      : cfg.currency !== undefined
        ? Amounts.zeroOfCurrency(cfg.currency)
        : defaultAmount !== undefined
          ? Amounts.zeroOfAmount(defaultAmount)
          : def?.currency !== undefined
            ? Amounts.zeroOfCurrency(def.currency)
            : Amounts.zeroOfCurrency(
                hook.response.templateP.supportedCurrencies[0],
              );

    const [amount, setAmount] = useState(defaultAmount ?? fixedAmount ?? zero);
    const [summary, setSummary] = useState(defaultSummary ?? fixedSummary ?? "");

    async function createOrder() {
      try {
        const templateParams: Record<string, string> = {};
        if (amount && !fixedAmount) {
          templateParams["amount"] = Amounts.stringify(amount);
        }
        if (summary && !fixedSummary) {
          templateParams["summary"] = summary;
        }
        const payStatus = await api.wallet.call(
          WalletApiOperation.PreparePayForTemplate,
          {
            talerPayTemplateUri: talerTemplateUri,
            templateParams,
          },
        );
        setNewOrder(payStatus.talerUri!);
      } catch (e) {
        console.error(e);
      }
    }

    const errors = undefinedIfEmpty({
      amount:
        fixedAmount !== undefined
          ? undefined
          : amount && Amounts.isZero(amount)
            ? i18n.str`required`
            : undefined,
      summary:
        fixedSummary !== undefined
          ? undefined
          : summary !== undefined && !summary
            ? i18n.str`required`
            : undefined,
    });
    return {
      status: "fill-template",
      error: undefined,
      minAge: cfg.minimum_age ?? 0,
      amount: {
        onInput:
          fixedAmount !== undefined
            ? undefined
            : (a) => {
                setAmount(a);
              },
        value: amount,
        error: errors?.amount,
      } as AmountFieldHandler,
      summary: {
        onInput:
          fixedSummary !== undefined
            ? undefined
            : (t) => {
                setSummary(t);
              },
        value: summary,
        error: errors?.summary,
      } as TextFieldHandler,
      onCreate: {
        onClick: errors
          ? undefined
          : safely("create order for pay template", createOrder),
      },
    };
  };
}

function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
  return Object.keys(obj).some(
    (k) => (obj as Record<string, unknown>)[k] !== undefined,
  )
    ? obj
    : undefined;
}
