/*
 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/>
 */

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

import {
  TalerError,
  TalerErrorCode,
  TalerErrorDetail,
  TranslatedString,
} from "@gnu-taler/taler-util";
import { ComponentChildren, createContext, h, VNode } from "preact";
import { useContext, useState } from "preact/hooks";
import { HookError } from "../hooks/useAsyncAsHook.js";
import { SafeHandler, withSafe } from "../mui/handlers.js";
import { BackgroundError } from "../wxApi.js";
import {
  InternationalizationAPI,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { platform } from "../platform/foreground.js";

export type AlertType = "info" | "warning" | "error" | "success";

export interface InfoAlert {
  message: TranslatedString;
  description: TranslatedString | VNode;
  type: "info" | "warning" | "success";
}

export type Alert = InfoAlert | ErrorAlert;

export interface ErrorAlert {
  message: TranslatedString;
  description: TranslatedString | VNode;
  type: "error";
  context: object | undefined;
  cause: any | undefined;
}

type Type = {
  alerts: Alert[];
  pushAlert: (n: Alert) => void;
  removeAlert: (n: Alert) => void;
  /**
   *
   * @param h
   * @returns
   * @deprecated use safely
   */
  pushAlertOnError: <T>(h: (p: T) => Promise<void>) => SafeHandler<T>;
  safely: <T>(name: string, h: (p: T) => Promise<void>) => SafeHandler<T>;
};

const initial: Type = {
  alerts: [],
  pushAlertOnError: () => {
    throw Error("alert context not initialized");
  },
  safely: () => {
    throw Error("alert context not initialized");
  },
  pushAlert: () => {
    null;
  },
  removeAlert: () => {
    null;
  },
};

const Context = createContext<Type>(initial);

type AlertWithDate = Alert & { since: Date };

type Props = Partial<Type> & {
  children: ComponentChildren;
};

export const AlertProvider = ({ children }: Props): VNode => {
  const timeout = 3000;

  const [alerts, setAlerts] = useState<AlertWithDate[]>([]);

  const pushAlert = (n: Alert): void => {
    const entry = { ...n, since: new Date() };
    setAlerts((ns) => [...ns, entry]);
    if (n.type !== "error") {
      setTimeout(() => {
        setAlerts((ns) => ns.filter((x) => x.since !== entry.since));
      }, timeout);
    }
  };

  const removeAlert = (alert: Alert): void => {
    setAlerts((ns: AlertWithDate[]) => ns.filter((n) => n !== alert));
  };

  const { i18n } = useTranslationContext();

  function pushAlertOnError<T>(
    handler: (p: T) => Promise<void>,
  ): SafeHandler<T> {
    return withSafe(handler, (e) => {
      const a = alertFromError(i18n, e.message as TranslatedString, e);
      pushAlert(a);
    });
  }

  function safely<T>(
    name: string,
    handler: (p: T) => Promise<void>,
  ): SafeHandler<T> {
    const message = i18n.str`Error was thrown trying to: "${name}"`;
    return withSafe(handler, (e) => {
      const a = alertFromError(i18n, message, e);
      pushAlert(a);
    });
  }

  return h(Context.Provider, {
    value: { alerts, pushAlert, removeAlert, pushAlertOnError, safely },
    children,
  });
};

export const useAlertContext = (): Type => useContext(Context);

export function alertFromError(
  i18n: InternationalizationAPI,
  message: TranslatedString,
  error: HookError,
  ...context: any[]
): ErrorAlert;

export function alertFromError(
  i18n: InternationalizationAPI,
  message: TranslatedString,
  error: Error,
  ...context: any[]
): ErrorAlert;

export function alertFromError(
  i18n: InternationalizationAPI,
  message: TranslatedString,
  error: TalerErrorDetail,
  ...context: any[]
): ErrorAlert;

export function alertFromError(
  i18n: InternationalizationAPI,
  message: TranslatedString,
  error: HookError | TalerErrorDetail | Error,
  ...context: any[]
): ErrorAlert {
  let description: TranslatedString;
  let cause: any;

  if (typeof error === "object" && error !== null) {
    if ("code" in error) {
      //TalerErrorDetail
      description = (error.hint ??
        `Error code: ${error.code}`) as TranslatedString;
      cause = {
        details: error,
      };
    } else if ("hasError" in error) {
      //HookError
      description = error.message as TranslatedString;
      if (error.type === "taler") {
        const msg = isWalletNotAvailable(i18n, error.details);
        if (msg) {
          description = msg;
        } else {
          const msg2 = isHttpError(i18n, error.details);
          if (msg2) {
            description = msg2;
          }
        }
        cause = {
          details: error.details,
        };
      }
    } else {
      if (error instanceof BackgroundError) {
        const msg = isWalletNotAvailable(i18n, error.errorDetail);
        if (msg) {
          description = msg;
        } else {
          const msg2 = isHttpError(i18n, error.errorDetail);
          if (msg2) {
            description = msg2;
          } else {
            description = (error.errorDetail.hint ??
              `Error code: ${error.errorDetail.code}`) as TranslatedString;
          }
        }
        cause = {
          details: error.errorDetail,
          stack: error.stack,
        };
      } else {
        description = error.message as TranslatedString;
        cause = {
          stack: error.stack,
        };
      }
    }
  } else {
    description = "" as TranslatedString;
    cause = error;
  }

  return {
    type: "error",
    message,
    description,
    cause,
    context,
  };
}

function isWalletNotAvailable(
  i18n: InternationalizationAPI,
  detail: TalerErrorDetail,
): TranslatedString | undefined {
  if (
    detail.code === TalerErrorCode.WALLET_CORE_NOT_AVAILABLE &&
    detail.lastError
  ) {
    const le = detail.lastError as TalerErrorDetail;
    if (le.code === TalerErrorCode.WALLET_DB_UNAVAILABLE) {
      if (platform.isFirefox() && platform.runningOnPrivateMode()) {
        return i18n.str`Could not open the wallet database. Firefox is known to run into this problem under "permanent private mode".`;
      } else {
        return i18n.str`Could not open the wallet database.`;
      }
    } else {
      return (detail.hint ?? `Error code: ${detail.code}`) as TranslatedString;
    }
  }
  return undefined;
}

function isHttpError(
  i18n: InternationalizationAPI,
  detail: TalerErrorDetail,
): TranslatedString | undefined {
  if (
    detail.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR &&
    detail.errorResponse
  ) {
    const er = detail.errorResponse as TalerErrorDetail;
    return (
      (er.hint as TranslatedString) ??
      detail.hint ??
      i18n.str`Unexpected request error, code: ${er.code}`
    );
  }
  return undefined;
}
//
