/*
 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 { ExchangeEntryStatus, TalerExchangeHttpClient, canonicalizeBaseUrl, opKnownFailure, opKnownFailureWithBody, succeedOrThrow } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { BrowserFetchHttpLib } from "@gnu-taler/web-util/browser";
import { useCallback, useEffect, useState } from "preact/hooks";
import { useBackendContext } from "../../context/backend.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { withSafe } from "../../mui/handlers.js";
import { RecursiveState } from "../../utils/index.js";
import { Props, State } from "./index.js";

function urlFromInput(str: string): URL {
  let result: URL;
  try {
    result = new URL(str)
  } catch (original) {
    try {
      result = new URL(`https://${str}`)
    } catch (e) {
      throw original
    }
  }
  if (!result.pathname.endsWith("/")) {
    result.pathname = result.pathname + "/";
  }
  result.search = "";
  result.hash = "";
  return result;
}

export function useComponentState({ onBack, currency, noDebounce }: Props): RecursiveState<State> {
  const [verified, setVerified] = useState<string>();

  const api = useBackendContext();
  const hook = useAsyncAsHook(() =>
    api.wallet.call(WalletApiOperation.ListExchanges, {}),
  );
  const walletExchanges = !hook ? [] : hook.hasError ? [] : hook.response.exchanges
  const used = walletExchanges.filter(e => e.exchangeEntryStatus === ExchangeEntryStatus.Used);
  const preset = walletExchanges.filter(e => e.exchangeEntryStatus === ExchangeEntryStatus.Preset);

  if (!verified) {
    return (): State => {
      const checkExchangeBaseUrl_memo = useCallback(async function checkExchangeBaseUrl(str: string) {
        const baseUrl = urlFromInput(str)
        if (baseUrl.protocol !== "http:" && baseUrl.protocol !== "https:") {
          return opKnownFailure("invalid-protocol"as const)
        }
        const found = used.findIndex((e) => e.exchangeBaseUrl === baseUrl.href);
        if (found !== -1) {
          return opKnownFailure("already-active"as const);
        }

        /**
         * FIXME: For some reason typescript doesn't like the next BrowserFetchHttpLib
         * 
         * │ src/wallet/AddExchange/state.ts(68,63): error TS2345: Argument of type 'BrowserFetchHttpLib' is not assignable to parameter of ty
         * │   Types of property 'fetch' are incompatible.
         * │     Type '(requestUrl: string, options?: HttpRequestOptions | undefined) => Promise<HttpResponse>' is not assignable to type '(ur
         * │       Types of parameters 'options' and 'opt' are incompatible.
         * │         Type 'import("$PATH/wallet.git/packages/taler-util/lib/http-common", { wi
         * │           Type 'import("$PATH/wallet.git/packages/taler-util/lib/http-common", { 
         * │             Types of property 'cancellationToken' are incompatible.
         * │               Type 'import("$PATH/wallet.git/packages/taler-util/lib/Cancellation
         * │                 Type 'import("$PATH/wallet.git/packages/taler-util/lib/Cancellati
         * │                   Types have separate declarations of a private property '_isCancelled'.
         *
         */
        const api = new TalerExchangeHttpClient(baseUrl.href, {
          httpClient: new BrowserFetchHttpLib(),
        });
        const config = succeedOrThrow(await api.getConfig())
        if (currency !== undefined && currency !== config.currency) {
          return opKnownFailureWithBody("invalid-currency"as const, config.currency)
        }
        const keys = await api.getKeys()
        return keys
      }, [used])

      const { result, value: url, loading, update, error: requestError } = useDebounce(checkExchangeBaseUrl_memo, noDebounce ?? false)
      const [inputError, setInputError] = useState<string>()

      return {
        status: "verify",
        error: undefined,
        onCancel: onBack,
        expectedCurrency: currency,
        onAccept: async () => {
          if (!result || result.type !== "ok") return;
          setVerified(result.body.base_url)
        },
        result,
        loading,
        knownExchanges: preset.map(e => new URL(e.exchangeBaseUrl)),
        url: {
          value: url ?? "",
          error: inputError ?? requestError,
          onInput: withSafe(update, (e) => {
            setInputError(e.message)
          })
        },
      };
    }
  }

  async function onConfirm() {
    if (!verified) return;
    await api.wallet.call(WalletApiOperation.AddExchange, {
      exchangeBaseUrl: canonicalizeBaseUrl(verified),
      forceUpdate: true,
    });
    onBack();
  }

  return {
    status: "confirm",
    error: undefined,
    onCancel: onBack,
    onConfirm,
    url: verified
  };
}



function useDebounce<T>(
  onTrigger: (v: string) => Promise<T>,
  disabled: boolean,
): {
  loading: boolean;
  error?: Error;
  value: string | undefined;
  result: T | undefined;
  update: (s: string) => void;
} {
  const [value, setValue] = useState<string>();
  const [dirty, setDirty] = useState(false);
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState<T | undefined>(undefined);
  const [error, setError] = useState<Error | undefined>(undefined);

  const [handler, setHandler] = useState<number | undefined>(undefined);

  if (!disabled) {
    useEffect(() => {
      if (!value) return;
      clearTimeout(handler);
      const h = setTimeout(async () => {
        setDirty(true);
        setLoading(true);
        try {
          const result = await onTrigger(value);
          setResult(result);
          setError(undefined);
          setLoading(false);
        } catch (er) {
          if (er instanceof Error) {
            setError(er);
          } else {
            // @ts-expect-error cause still not in typescript
            setError(new Error('unknown error on debounce', { cause: er }))
          }
          setLoading(false);
          setResult(undefined);
        }
      }, 500);
      setHandler(h as unknown as number);
    }, [value, setHandler, onTrigger]);
  }

  return {
    error: dirty ? error : undefined,
    loading: loading,
    result: result,
    value: value,
    update: disabled ? onTrigger : setValue,
  };
}

