/*
 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,
  ChallengerCacheEviction,
  assertUnreachable,
  canonicalizeBaseUrl,
  getGlobalLogLevel,
  setGlobalLogLevelFromString,
} from "@gnu-taler/taler-util";
import {
  BrowserHashNavigationProvider,
  ChallengerApiProvider,
  Loading,
  TalerWalletIntegrationBrowserProvider,
  TranslationProvider,
} 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 { SettingsProvider } from "./context/settings.js";
import { revalidateChallengeSession } from "./hooks/challenge.js";
import { strings } from "./i18n/strings.js";
import { Frame } from "./pages/Frame.js";
import { ChallengerUiSettings, fetchSettings } from "./settings.js";

const WITH_LOCAL_STORAGE_CACHE = false;

const evictBankSwrCache: CacheEvictor<ChallengerCacheEviction> = {
  async notifySuccess(op) {
    switch (op) {
      case ChallengerCacheEviction.CREATE_CHALLENGE: {
        await Promise.all([revalidateChallengeSession()]);
        return;
      }
      case ChallengerCacheEviction.SOLVE_CHALLENGE: {
        await Promise.all([revalidateChallengeSession()]);
        return;
      }
      default: {
        assertUnreachable(op);
      }
    }
  },
};

export function App(): VNode {
  const [settings, setSettings] = useState<ChallengerUiSettings>();
  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,
        }}
      >
        <ChallengerApiProvider
          baseUrl={new URL("/", baseUrl)}
          frameOnError={Frame}
          evictors={{
            challenger: evictBankSwrCache,
          }}
        >
          <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>
        </ChallengerApiProvider>
      </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("challenger-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);
  }
}
