/*
 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 
 */
/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */
import { 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
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: (h: (p: T) => Promise) => SafeHandler;
  safely: (name: string, h: (p: T) => Promise) => SafeHandler;
};
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(initial);
type AlertWithDate = Alert & { since: Date };
type Props = Partial & {
  children: ComponentChildren;
};
export const AlertProvider = ({ children }: Props): VNode => {
  const timeout = 3000;
  const [alerts, setAlerts] = useState([]);
  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));
  };
  function pushAlertOnError(
    handler: (p: T) => Promise,
  ): SafeHandler {
    return withSafe(handler, (e) => {
      const a = alertFromError(e.message as TranslatedString, e);
      pushAlert(a);
    });
  }
  const { i18n } = useTranslationContext();
  function safely(
    name: string,
    handler: (p: T) => Promise,
  ): SafeHandler {
    const message = i18n.str`Error was thrown trying to: "${name}"`;
    return withSafe(handler, (e) => {
      const a = alertFromError(message, e);
      pushAlert(a);
    });
  }
  return h(Context.Provider, {
    value: { alerts, pushAlert, removeAlert, pushAlertOnError, safely },
    children,
  });
};
export const useAlertContext = (): Type => useContext(Context);
export function alertFromError(
  message: TranslatedString,
  error: HookError,
  ...context: any[]
): ErrorAlert;
export function alertFromError(
  message: TranslatedString,
  error: Error,
  ...context: any[]
): ErrorAlert;
export function alertFromError(
  message: TranslatedString,
  error: TalerErrorDetail,
  ...context: any[]
): ErrorAlert;
export function alertFromError(
  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") {
        cause = {
          details: error.details,
        };
      }
    } else {
      if (error instanceof BackgroundError) {
        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,
  };
}