/*
 This file is part of GNU Taler
 (C) 2021-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/>
 */

/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */

import { CacheEvictor, TalerMerchantApi, TalerMerchantInstanceCacheEviction, TalerMerchantManagementCacheEviction, assertUnreachable, canonicalizeBaseUrl } from "@gnu-taler/taler-util";
import {
  BrowserHashNavigationProvider,
  ConfigResultFail,
  MerchantApiProvider,
  TalerWalletIntegrationBrowserProvider,
  TranslationProvider,
  useTranslationContext
} from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { SWRConfig } from "swr";
import { Routing } from "./Routing.js";
import { Loading } from "./components/exception/loading.js";
import { NotificationCard } from "./components/menu/index.js";
import { SettingsProvider } from "./context/settings.js";
import { revalidateBankAccountDetails, revalidateInstanceBankAccounts } from "./hooks/bank.js";
import { revalidateBackendInstances, revalidateInstanceDetails, revalidateManagedInstanceDetails } from "./hooks/instance.js";
import { revalidateInstanceOtpDevices, revalidateOtpDeviceDetails } from "./hooks/otp.js";
import { revalidateInstanceProducts, revalidateProductDetails } from "./hooks/product.js";
import { revalidateInstanceTemplates, revalidateTemplateDetails } from "./hooks/templates.js";
import { revalidateInstanceTransfers } from "./hooks/transfer.js";
import { revalidateInstanceWebhooks, revalidateWebhookDetails } from "./hooks/webhooks.js";
import { strings } from "./i18n/strings.js";
import { MerchantUiSettings, buildDefaultBackendBaseURL, fetchSettings } from "./settings.js";
import { revalidateInstanceOrders, revalidateOrderDetails } from "./hooks/order.js";
const WITH_LOCAL_STORAGE_CACHE = false;

export function Application(): VNode {
  const [settings, setSettings] = useState<MerchantUiSettings>();
  useEffect(() => {
    fetchSettings(setSettings);
  }, []);
  if (!settings) return <Loading />;

  const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL);
  return (
    <SettingsProvider value={settings}>
      <TranslationProvider
        source={strings}
        completeness={{
          es: strings["es"].completeness,
          de: strings["de"].completeness,
        }}
      >
        <MerchantApiProvider baseUrl={new URL("./", baseUrl)} frameOnError={OnConfigError} evictors={{
          management: swrCacheEvictor
        }}>
          <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>
        </MerchantApiProvider>
      </TranslationProvider>
    </SettingsProvider>
  );
}

function getInitialBackendBaseURL(
  backendFromSettings: string | undefined,
): string {
  /**
   * For testing purpose
   */
  const overrideUrl =
    typeof localStorage !== "undefined"
      ? localStorage.getItem("merchant-base-url")
      : undefined;
  let result: string;

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

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 OnConfigError({ state }: { state: ConfigResultFail<TalerMerchantApi.VersionResponse> | undefined }): VNode {
  const { i18n } = useTranslationContext();
  if (!state) {
    return <i18n.Translate>checking compatibility with server...</i18n.Translate>
  }
  switch (state.type) {
    case "error": {
      return <NotificationCard
        notification={{
          message: i18n.str`Contacting the server failed`,
          description: state.error.message,
          details: JSON.stringify(state.error.errorDetail, undefined, 2),
          type: "ERROR",
        }}
      />
    }
    case "incompatible": {
      return <NotificationCard
        notification={{
          message: i18n.str`The server version is not supported`,
          description: i18n.str`Supported version "${state.supported}", server version "${state.result.version}".`,
          type: "WARN",
        }}
      />
    }
    default: assertUnreachable(state)
  }
}

const swrCacheEvictor= new class implements CacheEvictor<TalerMerchantManagementCacheEviction | TalerMerchantInstanceCacheEviction> {
  async notifySuccess(op: TalerMerchantManagementCacheEviction | TalerMerchantInstanceCacheEviction) {
    switch(op) {
      case TalerMerchantManagementCacheEviction.CREATE_INSTANCE: {
        await Promise.all([
          revalidateBackendInstances()
        ])
        return
      }
      case TalerMerchantManagementCacheEviction.UPDATE_INSTANCE: {
        await Promise.all([
          revalidateManagedInstanceDetails()
        ])
        return
      }
      case TalerMerchantManagementCacheEviction.DELETE_INSTANCE:{
        await Promise.all([
          revalidateBackendInstances()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_CURRENT_INSTANCE:{
        await Promise.all([
          revalidateInstanceDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_CURRENT_INSTANCE:{
        await Promise.all([
          revalidateInstanceDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_BANK_ACCOUNT:{
        await Promise.all([
          revalidateInstanceBankAccounts()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_BANK_ACCOUNT:{
        await Promise.all([
          revalidateBankAccountDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_BANK_ACCOUNT:{
        await Promise.all([
          revalidateInstanceBankAccounts()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_PRODUCT:{
        await Promise.all([
          revalidateInstanceProducts()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT:{
        await Promise.all([
          revalidateProductDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_PRODUCT:{
        await Promise.all([
          revalidateInstanceProducts()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_TRANSFER:{
        await Promise.all([
          revalidateInstanceTransfers()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_TRANSFER:{
        await Promise.all([
          revalidateInstanceTransfers()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_DEVICE:{
        await Promise.all([
          revalidateInstanceOtpDevices()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_DEVICE:{
        await Promise.all([
          revalidateOtpDeviceDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_DEVICE:{
        await Promise.all([
          revalidateInstanceOtpDevices()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_TEMPLATE:{
        await Promise.all([
          revalidateInstanceTemplates()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_TEMPLATE:{
        await Promise.all([
          revalidateTemplateDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_TEMPLATE:{
        await Promise.all([
          revalidateInstanceTemplates()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_WEBHOOK:{
        await Promise.all([
          revalidateInstanceWebhooks()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_WEBHOOK:{
        await Promise.all([
          revalidateWebhookDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_WEBHOOK:{
        await Promise.all([
          revalidateInstanceWebhooks()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.CREATE_ORDER:{
        await Promise.all([
          revalidateInstanceOrders()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.UPDATE_ORDER: {
        await Promise.all([
          revalidateOrderDetails()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.DELETE_ORDER: {
        await Promise.all([
          revalidateInstanceOrders()
        ])
        return
      }
      case TalerMerchantInstanceCacheEviction.LAST:
      // case TalerMerchantInstanceCacheEviction.CREATE_TOKENFAMILY:{
      //   await Promise.all([
      //     reva
      //   ])
      //   return
      // }
      // case TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY:{
      //   await Promise.all([
      //   ])
      //   return
      // }
      // case TalerMerchantInstanceCacheEviction.DELETE_TOKENFAMILY:{
      //   await Promise.all([
      //   ])
      //   return
      // }
    }
  }

}
