/*
 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 {
  CacheEvictor,
  TalerBankConversionCacheEviction,
  TalerCoreBankCacheEviction,
  assertUnreachable,
  canonicalizeBaseUrl,
  getGlobalLogLevel,
  setGlobalLogLevelFromString,
} from "@gnu-taler/taler-util";
import {
  BankApiProvider,
  BrowserHashNavigationProvider,
  ErrorLoading,
  Loading,
  TalerWalletIntegrationBrowserProvider,
  TranslationProvider,
  CommonPreferenceProvider,
  useCommonPreference,
} from "@gnu-taler/web-util/browser";
import { h, VNode, Fragment } from "preact";
import { useEffect, useState } from "preact/hooks";
import { SWRConfig } from "swr";
import { Routing } from "./Routing.js";
import { SettingsProvider } from "./context/settings.js";
import { strings } from "./i18n/strings.js";
import { BankFrame } from "./pages/BankFrame.js";
import { UiSettings, fetchSettings } from "./settings.js";
import {
  revalidateAccountDetails,
  revalidatePublicAccounts,
  revalidateTransactions,
} from "./hooks/account.js";
import {
  revalidateBusinessAccounts,
  revalidateCashouts,
  revalidateConversionInfo,
  revalidateConversionRateClassDetails,
  revalidateConversionRateClasses,
} from "./hooks/regional.js";
const WITH_LOCAL_STORAGE_CACHE = false;
export function App() {
  const [settings, setSettings] = useState<UiSettings>();
  useEffect(() => {
    fetchSettings(setSettings);
  }, []);
  if (!settings) return <Loading />;

  const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL);
  return (
    <SettingsProvider value={settings}>
      <TranslationProvider
        source={strings}
        completeness={{
          uk: strings["uk"].completeness,
          // tr: strings["tr"].completeness,
          ru: strings["ru"].completeness,
          // sv: strings["sv"].completeness,
          it: strings["it"].completeness,
          fr: strings["fr"].completeness,
          es: strings["es"].completeness,
          de: strings["de"].completeness,
        }}
      >
        <CommonPreferenceProvider showDebug={false}>
          <SubApp baseUrl={baseUrl} />
        </CommonPreferenceProvider>
      </TranslationProvider>
    </SettingsProvider>
  );
}

// @ts-expect-error creating a new property for window object
window.setGlobalLogLevelFromString = setGlobalLogLevelFromString;
// @ts-expect-error creating a new property for window object
window.getGlobalLevel = getGlobalLogLevel;

function localStorageProvider(): Map<unknown, unknown> {
  const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]"));

  window.addEventListener("beforeunload", () => {
    const appCache = JSON.stringify(Array.from(map.entries()));
    localStorage.setItem("app-cache", appCache);
  });
  return map;
}

function getInitialBackendBaseURL(
  backendFromSettings: string | undefined,
): string {
  const overrideUrl =
    typeof localStorage !== "undefined"
      ? localStorage.getItem("corebank-api-base-url")
      : undefined;
  let result: string;

  if (!overrideUrl) {
    // normal path
    if (!backendFromSettings) {
      console.error(
        "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
      );
      result = window.origin;
    } else {
      result = backendFromSettings;
    }
  } else {
    // testing/development path
    result = overrideUrl;
  }
  try {
    return canonicalizeBaseUrl(result);
  } catch (e) {
    // fall back
    return canonicalizeBaseUrl(window.origin);
  }
}

const evictBankSwrCache: CacheEvictor<TalerCoreBankCacheEviction> = {
  async notifySuccess(op) {
    switch (op) {
      case TalerCoreBankCacheEviction.DELETE_ACCOUNT: {
        await Promise.all([
          revalidatePublicAccounts(),
          revalidateBusinessAccounts(),
        ]);
        return;
      }
      case TalerCoreBankCacheEviction.CREATE_ACCOUNT: {
        // admin balance change on new account
        await Promise.all([
          revalidateAccountDetails(),
          revalidateTransactions(),
          revalidatePublicAccounts(),
          revalidateBusinessAccounts(),
        ]);
        return;
      }
      case TalerCoreBankCacheEviction.UPDATE_ACCOUNT: {
        await Promise.all([revalidateAccountDetails()]);
        return;
      }
      case TalerCoreBankCacheEviction.CREATE_TRANSACTION: {
        await Promise.all([
          revalidateAccountDetails(),
          revalidateTransactions(),
        ]);
        return;
      }
      case TalerCoreBankCacheEviction.CONFIRM_WITHDRAWAL: {
        await Promise.all([
          revalidateAccountDetails(),
          revalidateTransactions(),
        ]);
        return;
      }
      case TalerCoreBankCacheEviction.CREATE_CASHOUT: {
        await Promise.all([
          revalidateAccountDetails(),
          revalidateCashouts(),
          revalidateTransactions(),
        ]);
        return;
      }
      case TalerCoreBankCacheEviction.UPDATE_PASSWORD:
      case TalerCoreBankCacheEviction.ABORT_WITHDRAWAL:
      case TalerCoreBankCacheEviction.CREATE_WITHDRAWAL:
        return;
      case TalerCoreBankCacheEviction.UPDATE_CONVERSION_RATE_CLASS:
      case TalerCoreBankCacheEviction.CREATE_CONVERSION_RATE_CLASS:
      case TalerCoreBankCacheEviction.DELETE_CONVERSION_RATE_CLASS:
        {
          await Promise.all([
            revalidateConversionInfo(),
            revalidateCashouts(),
            revalidateTransactions(),
            revalidateConversionRateClassDetails(),
            revalidateConversionRateClasses(),
          ]);
        }
        return;
      default:
        assertUnreachable(op);
    }
  },
};

const evictConversionSwrCache: CacheEvictor<TalerBankConversionCacheEviction> =
  {
    async notifySuccess(op) {
      switch (op) {
        case TalerBankConversionCacheEviction.UPDATE_RATE: {
          await revalidateConversionInfo();
          return;
        }
        default:
          assertUnreachable(op);
      }
    },
  };

function SubApp({ baseUrl }: { baseUrl: string }) {
  return (
    <BankApiProvider
      baseUrl={new URL("/", baseUrl)}
      frameOnError={BankFrame}
      evictors={{
        bank: evictBankSwrCache,
        conversion: evictConversionSwrCache,
      }}
    >
      <SWRConfig
        value={{
          provider: WITH_LOCAL_STORAGE_CACHE ? localStorageProvider : undefined,
          // normally, do not revalidate
          revalidateOnFocus: false,
          revalidateOnReconnect: false,
          revalidateIfStale: false,
          revalidateOnMount: undefined,
          focusThrottleInterval: undefined,

          // normally, do not refresh
          refreshInterval: undefined,
          dedupingInterval: 2000,
          refreshWhenHidden: false,
          refreshWhenOffline: false,

          // ignore errors
          shouldRetryOnError: false,
          errorRetryCount: 0,
          errorRetryInterval: undefined,

          // do not go to loading again if already has data
          keepPreviousData: true,
        }}
      >
        <TalerWalletIntegrationBrowserProvider>
          <BrowserHashNavigationProvider>
            <Routing />
          </BrowserHashNavigationProvider>
        </TalerWalletIntegrationBrowserProvider>
      </SWRConfig>
    </BankApiProvider>
  );
}
