/*
 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/>
 */
import {
  AbsoluteTime,
  NotificationType,
  ObservabilityEventType,
  RequestProgressNotification,
  TalerErrorCode,
  TalerErrorDetail,
  TaskProgressNotification,
  WalletNotification,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, JSX, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { Pages } from "../NavigationBar.js";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useSettings } from "../hooks/useSettings.js";
import { Button } from "../mui/Button.js";
import { WxApiType } from "../wxApi.js";
import { Modal } from "./Modal.js";
import { Time } from "./Time.js";

interface Props extends JSX.HTMLAttributes {}

export function WalletActivity({}: Props): VNode {
  const { i18n } = useTranslationContext();
  const [settings, updateSettings] = useSettings();
  const api = useBackendContext();
  useEffect(() => {
    document.body.style.marginBottom = "250px";
    return () => {
      document.body.style.marginBottom = "0px";
    };
  });
  const [table, setTable] = useState<"tasks" | "events">("tasks");
  return (
    <div
      style={{
        position: "fixed",
        bottom: 0,
        background: "white",
        zIndex: 1,
        height: 250,
        overflowY: "scroll",
        width: "100%",
      }}
    >
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          float: "right",
        }}
      >
        <div />
        <div>
          <div
            style={{ padding: 4, margin: 2, border: "solid 1px black" }}
            onClick={() => {
              updateSettings("showWalletActivity", false);
            }}
          >
            close
          </div>
        </div>
      </div>
      <div style={{ display: "flex", justifyContent: "space-around" }}>
        <Button
          variant={table === "tasks" ? "contained" : "outlined"}
          style={{ margin: 4 }}
          onClick={async () => {
            setTable("tasks");
          }}
        >
          <i18n.Translate>Tasks</i18n.Translate>
        </Button>
        <Button
          variant={table === "events" ? "contained" : "outlined"}
          style={{ margin: 4 }}
          onClick={async () => {
            setTable("events");
          }}
        >
          <i18n.Translate>Events</i18n.Translate>
        </Button>
      </div>
      {(function (): VNode {
        switch (table) {
          case "events": {
            return <ObservabilityEventsTable />;
          }
          case "tasks": {
            return <ActiveTasksTable />;
          }
          default: {
            assertUnreachable(table);
          }
        }
      })()}
    </div>
  );
}

interface MoreInfoPRops {
  events: (WalletNotification & { when: AbsoluteTime })[];
  onClick: (content: VNode) => void;
}
type Notif = {
  id: string;
  events: (WalletNotification & { when: AbsoluteTime })[];
  description: string;
  start: AbsoluteTime;
  end: AbsoluteTime;
  reference:
    | {
        eventType: NotificationType;
        referenceType: "task" | "transaction" | "operation" | "exchange";
        id: string;
      }
    | undefined;
  MoreInfo: (p: MoreInfoPRops) => VNode;
};

function ShowBalanceChange({ events }: MoreInfoPRops): VNode {
  if (!events.length) return <Fragment />;
  const not = events[0];
  if (not.type !== NotificationType.BalanceChange) return <Fragment />;
  return (
    <Fragment>
      <dt>Transaction</dt>
      <dd>
        <a
          title={not.hintTransactionId}
          href={Pages.balanceTransaction({ tid: not.hintTransactionId })}
        >
          {not.hintTransactionId.substring(0, 10)}
        </a>
      </dd>
    </Fragment>
  );
}

function ShowBackupOperationError({ events, onClick }: MoreInfoPRops): VNode {
  if (!events.length) return <Fragment />;
  const not = events[0];
  if (not.type !== NotificationType.BackupOperationError) return <Fragment />;
  return (
    <Fragment>
      <dt>Error</dt>
      <dd>
        <a
          href="#"
          onClick={(e) => {
            e.preventDefault();
            const error = not.error;
            onClick(
              <Fragment>
                <dl>
                  <dt>Code</dt>
                  <dd>
                    {TalerErrorCode[error.code]} ({error.code})
                  </dd>
                  <dt>Hint</dt>
                  <dd>{error.hint ?? "--"}</dd>
                  <dt>Time</dt>
                  <dd>
                    <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" />
                  </dd>
                </dl>
                <pre
                  style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
                >
                  {JSON.stringify(error, undefined, 2)}
                </pre>
              </Fragment>,
            );
          }}
        >
          {TalerErrorCode[not.error.code]}
        </a>
      </dd>
    </Fragment>
  );
}

function ShowTransactionStateTransition({
  events,
  onClick,
}: MoreInfoPRops): VNode {
  if (!events.length) return <Fragment />;
  const not = events[0];
  if (not.type !== NotificationType.TransactionStateTransition)
    return <Fragment />;
  return (
    <Fragment>
      <dt>Old state</dt>
      <dd>
        {not.oldTxState.major} - {not.oldTxState.minor ?? ""}
      </dd>
      <dt>New state</dt>
      <dd>
        {not.newTxState.major} - {not.newTxState.minor ?? ""}
      </dd>
      <dt>Transaction</dt>
      <dd>
        <a
          title={not.transactionId}
          href={Pages.balanceTransaction({ tid: not.transactionId })}
        >
          {not.transactionId.substring(0, 10)}
        </a>
      </dd>
      {not.errorInfo ? (
        <Fragment>
          <dt>Error</dt>
          <dd>
            <a
              href="#"
              onClick={(e) => {
                if (!not.errorInfo) return;
                e.preventDefault();
                const error = not.errorInfo;
                onClick(
                  <Fragment>
                    <dl>
                      <dt>Code</dt>
                      <dd>
                        {TalerErrorCode[error.code]} ({error.code})
                      </dd>
                      <dt>Hint</dt>
                      <dd>{error.hint ?? "--"}</dd>
                      <dt>Message</dt>
                      <dd>{error.message ?? "--"}</dd>
                    </dl>
                  </Fragment>,
                );
              }}
            >
              {TalerErrorCode[not.errorInfo.code]}
            </a>
          </dd>
        </Fragment>
      ) : undefined}
      <dt>Experimental</dt>
      <dd>
        <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
          {JSON.stringify(not.experimentalUserData, undefined, 2)}
        </pre>
      </dd>
    </Fragment>
  );
}
function ShowExchangeStateTransition({
  events,
  onClick,
}: MoreInfoPRops): VNode {
  if (!events.length) return <Fragment />;
  const not = events[0];
  if (not.type !== NotificationType.ExchangeStateTransition)
    return <Fragment />;
  return (
    <Fragment>
      <dt>Exchange</dt>
      <dd>{not.exchangeBaseUrl}</dd>
      {not.oldExchangeState &&
        not.newExchangeState.exchangeEntryStatus !==
          not.oldExchangeState?.exchangeEntryStatus && (
          <Fragment>
            <dt>Entry status</dt>
            <dd>
              from {not.oldExchangeState.exchangeEntryStatus} to{" "}
              {not.newExchangeState.exchangeEntryStatus}
            </dd>
          </Fragment>
        )}
      {not.oldExchangeState &&
        not.newExchangeState.exchangeUpdateStatus !==
          not.oldExchangeState?.exchangeUpdateStatus && (
          <Fragment>
            <dt>Update status</dt>
            <dd>
              from {not.oldExchangeState.exchangeUpdateStatus} to{" "}
              {not.newExchangeState.exchangeUpdateStatus}
            </dd>
          </Fragment>
        )}
      {not.oldExchangeState &&
        not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && (
          <Fragment>
            <dt>Tos status</dt>
            <dd>
              from {not.oldExchangeState.tosStatus} to{" "}
              {not.newExchangeState.tosStatus}
            </dd>
          </Fragment>
        )}
    </Fragment>
  );
}

type ObservaNotifWithTime = (
  | TaskProgressNotification
  | RequestProgressNotification
) & {
  when: AbsoluteTime;
};
function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode {
  // let prev: ObservaNotifWithTime;
  const asd = events.map((not) => {
    if (
      not.type !== NotificationType.RequestObservabilityEvent &&
      not.type !== NotificationType.TaskObservabilityEvent
    )
      return <Fragment />;

    const title = (function () {
      switch (not.event.type) {
        case ObservabilityEventType.HttpFetchFinishError:
        case ObservabilityEventType.HttpFetchFinishSuccess:
        case ObservabilityEventType.HttpFetchStart:
          return "HTTP Request";
        case ObservabilityEventType.DbQueryFinishSuccess:
        case ObservabilityEventType.DbQueryFinishError:
        case ObservabilityEventType.DbQueryStart:
          return "Database";
        case ObservabilityEventType.RequestFinishSuccess:
        case ObservabilityEventType.RequestFinishError:
        case ObservabilityEventType.RequestStart:
          return "Wallet";
        case ObservabilityEventType.CryptoFinishSuccess:
        case ObservabilityEventType.CryptoFinishError:
        case ObservabilityEventType.CryptoStart:
          return "Crypto";
        case ObservabilityEventType.TaskStart:
          return "Task start";
        case ObservabilityEventType.TaskStop:
          return "Task stop";
        case ObservabilityEventType.TaskReset:
          return "Task reset";
        case ObservabilityEventType.ShepherdTaskResult:
          return "Schedule";
        case ObservabilityEventType.DeclareTaskDependency:
          return "Task dependency";
        case ObservabilityEventType.Message:
          return "Message";
      }
    })();

    return (
      <ShowObervavilityDetails title={title} notif={not} onClick={onClick} />
    );
  });
  return (
    <table>
      <thead>
        <td>Event</td>
        <td>Info</td>
        <td>Start</td>
        <td>End</td>
      </thead>
      <tbody>{asd}</tbody>
    </table>
  );
}

function ShowObervavilityDetails({
  title,
  notif,
  onClick,
  prev,
}: {
  title: string;
  notif: ObservaNotifWithTime;
  prev?: ObservaNotifWithTime;
  onClick: (content: VNode) => void;
}): VNode {
  switch (notif.event.type) {
    case ObservabilityEventType.HttpFetchStart:
    case ObservabilityEventType.HttpFetchFinishError:
    case ObservabilityEventType.HttpFetchFinishSuccess: {
      return (
        <tr>
          <td>
            <a
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onClick(
                  <Fragment>
                    <pre
                      style={{
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {JSON.stringify({ event: notif, prev }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>
            {notif.event.url}{" "}
            {prev?.event.type ===
            ObservabilityEventType.HttpFetchFinishSuccess ? (
              `(${prev.event.status})`
            ) : prev?.event.type ===
              ObservabilityEventType.HttpFetchFinishError ? (
              <a
                href="#"
                onClick={(e) => {
                  e.preventDefault();
                  if (
                    prev.event.type !==
                    ObservabilityEventType.HttpFetchFinishError
                  )
                    return;
                  const error = prev.event.error;
                  onClick(
                    <Fragment>
                      <dl>
                        <dt>Code</dt>
                        <dd>
                          {TalerErrorCode[error.code]} ({error.code})
                        </dd>
                        <dt>Hint</dt>
                        <dd>{error.hint ?? "--"}</dd>
                        <dt>Time</dt>
                        <dd>
                          <Time
                            timestamp={error.when}
                            format="yyyy/MM/dd HH:mm:ss"
                          />
                        </dd>
                      </dl>
                      <pre
                        style={{
                          whiteSpace: "pre-wrap",
                          wordBreak: "break-word",
                        }}
                      >
                        {JSON.stringify(error, undefined, 2)}
                      </pre>
                    </Fragment>,
                  );
                }}
              >
                fail
              </a>
            ) : undefined}
          </td>
          <td>
            {" "}
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
          <td>
            {" "}
            <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
        </tr>
      );
    }
    case ObservabilityEventType.DbQueryStart:
    case ObservabilityEventType.DbQueryFinishSuccess:
    case ObservabilityEventType.DbQueryFinishError: {
      return (
        <tr>
          <td>
            <a
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onClick(
                  <Fragment>
                    <pre
                      style={{
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {JSON.stringify({ event: notif, prev }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>
            {notif.event.location} {notif.event.name}
          </td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
          <td>
            <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
        </tr>
      );
    }

    case ObservabilityEventType.TaskStart:
    case ObservabilityEventType.TaskStop:
    case ObservabilityEventType.DeclareTaskDependency:
    case ObservabilityEventType.TaskReset: {
      return (
        <tr>
          <td>
            <a
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onClick(
                  <Fragment>
                    <pre
                      style={{
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {JSON.stringify({ event: notif, prev }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.taskId}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
          <td>
            <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
        </tr>
      );
    }
    case ObservabilityEventType.ShepherdTaskResult: {
      return (
        <tr>
          <td>
            <a
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onClick(
                  <Fragment>
                    <pre
                      style={{
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {JSON.stringify({ event: notif, prev }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.resultType}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
          <td>
            <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
        </tr>
      );
    }
    case ObservabilityEventType.CryptoStart:
    case ObservabilityEventType.CryptoFinishSuccess:
    case ObservabilityEventType.CryptoFinishError: {
      return (
        <tr>
          <td>
            <a
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onClick(
                  <Fragment>
                    <pre
                      style={{
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {JSON.stringify({ event: notif, prev }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.operation}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
          <td>
            <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
        </tr>
      );
    }
    case ObservabilityEventType.RequestStart:
    case ObservabilityEventType.RequestFinishSuccess:
    case ObservabilityEventType.RequestFinishError: {
      return (
        <tr>
          <td>
            <a
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onClick(
                  <Fragment>
                    <pre
                      style={{
                        whiteSpace: "pre-wrap",
                        wordBreak: "break-word",
                      }}
                    >
                      {JSON.stringify({ event: notif, prev }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.type}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
          <td>
            <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
          </td>
        </tr>
      );
    }
    case ObservabilityEventType.Message:
      // FIXME
      return <></>;
  }
}

function getNotificationFor(
  id: string,
  event: WalletNotification,
  start: AbsoluteTime,
  list: Notif[],
): Notif | undefined {
  const eventWithTime = { ...event, when: start };
  switch (event.type) {
    case NotificationType.BalanceChange: {
      return {
        id,
        events: [eventWithTime],
        reference: {
          eventType: event.type,
          referenceType: "transaction",
          id: event.hintTransactionId,
        },
        description: "Balance change",
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowBalanceChange,
      };
    }
    case NotificationType.BackupOperationError: {
      return {
        id,
        events: [eventWithTime],
        reference: undefined,
        description: "Backup error",
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowBackupOperationError,
      };
    }
    case NotificationType.TransactionStateTransition: {
      const found = list.find(
        (a) =>
          a.reference?.eventType === event.type &&
          a.reference.id === event.transactionId,
      );
      if (found) {
        found.end = start;
        found.events.unshift(eventWithTime);
        return undefined;
      }
      return {
        id,
        events: [eventWithTime],
        reference: {
          eventType: event.type,
          referenceType: "transaction",
          id: event.transactionId,
        },
        description: event.type,
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowTransactionStateTransition,
      };
    }
    case NotificationType.ExchangeStateTransition: {
      const found = list.find(
        (a) =>
          a.reference?.eventType === event.type &&
          a.reference.id === event.exchangeBaseUrl,
      );
      if (found) {
        found.end = start;
        found.events.unshift(eventWithTime);
        return undefined;
      }
      return {
        id,
        events: [eventWithTime],
        description: "Exchange update",
        reference: {
          eventType: event.type,
          referenceType: "exchange",
          id: event.exchangeBaseUrl,
        },
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowExchangeStateTransition,
      };
    }
    case NotificationType.TaskObservabilityEvent: {
      const found = list.find(
        (a) =>
          a.reference?.eventType === event.type &&
          a.reference.id === event.taskId,
      );
      if (found) {
        found.end = start;
        found.events.unshift(eventWithTime);
        return undefined;
      }
      return {
        id,
        events: [eventWithTime],
        reference: {
          eventType: event.type,
          referenceType: "task",
          id: event.taskId,
        },
        description: `Task update ${event.taskId}`,
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowObservabilityEvent,
      };
    }
    case NotificationType.WithdrawalOperationTransition: {
      const found = list.find(
        (a) =>
          a.reference?.eventType === event.type && a.reference.id === event.uri,
      );
      if (found) {
        found.end = start;
        found.events.unshift(eventWithTime);
        return undefined;
      }
      return {
        id,
        events: [eventWithTime],
        reference: {
          eventType: event.type,
          referenceType: "task",
          id: event.uri,
        },
        description: `Withdrawal operation updated`,
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowObservabilityEvent,
      };
    }
    case NotificationType.RequestObservabilityEvent: {
      const found = list.find(
        (a) =>
          a.reference?.eventType === event.type &&
          a.reference.id === event.requestId,
      );
      if (found) {
        found.end = start;
        found.events.unshift(eventWithTime);
        return undefined;
      }
      return {
        id,
        events: [eventWithTime],
        reference: {
          eventType: event.type,
          referenceType: "operation",
          id: event.requestId,
        },
        description: `wallet.${event.operation}(${event.requestId})`,
        start,
        end: AbsoluteTime.never(),
        MoreInfo: ShowObservabilityEvent,
      };
    }
    case NotificationType.Idle:
      return undefined;
    default: {
      assertUnreachable(event);
    }
  }
}

function refresh(api: WxApiType, onUpdate: (list: Notif[]) => void) {
  api.background
    .call("getNotifications", undefined)
    .then((notif) => {
      const list: Notif[] = [];
      for (const n of notif) {
        if (
          n.notification.type === NotificationType.RequestObservabilityEvent &&
          n.notification.operation === "getActiveTasks"
        ) {
          //ignore monitor request
          continue;
        }
        const event = getNotificationFor(
          String(list.length),
          n.notification,
          n.when,
          list,
        );
        // pepe.
        if (event) {
          list.unshift(event);
        }
      }
      onUpdate(list);
    })
    .catch((error) => {
      console.log(error);
    });
}

export function ObservabilityEventsTable({}: {}): VNode {
  const { i18n } = useTranslationContext();
  const api = useBackendContext();

  const [notifications, setNotifications] = useState<Notif[]>([]);
  const [showDetails, setShowDetails] = useState<VNode>();

  useEffect(() => {
    let lastTimeout: ReturnType<typeof setTimeout>;
    function periodicRefresh() {
      refresh(api, setNotifications);

      lastTimeout = setTimeout(() => {
        periodicRefresh();
      }, 1000);

      //clear on unload
      return () => {
        clearTimeout(lastTimeout);
      };
    }
    return periodicRefresh();
  }, [1]);

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <div
          style={{ padding: 4, margin: 2, border: "solid 1px black" }}
          onClick={() => {
            api.background.call("clearNotifications", undefined).then((d) => {
              refresh(api, setNotifications);
            });
          }}
        >
          clear
        </div>
      </div>
      {showDetails && (
        <Modal
          title="event details"
          onClose={{
            onClick: (async () => {
              setShowDetails(undefined);
            }) as any,
          }}
        >
          {showDetails}
        </Modal>
      )}
      {notifications.map((not) => {
        return (
          <details key={not.id}>
            <summary>
              <div
                style={{
                  width: "90%",
                  display: "inline-flex",
                  justifyContent: "space-between",
                  padding: 4,
                }}
              >
                <div style={{ padding: 4 }}>{not.description}</div>
                <div style={{ padding: 4 }}>
                  <Time timestamp={not.start} format="yyyy/MM/dd HH:mm:ss" />
                </div>
                <div style={{ padding: 4 }}>
                  <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss" />
                </div>
              </div>
            </summary>
            <not.MoreInfo
              events={not.events}
              onClick={(details) => {
                setShowDetails(details);
              }}
            />
          </details>
        );
      })}
    </div>
  );
}

function ErroDetailModal({
  error,
  onClose,
}: {
  error: TalerErrorDetail;
  onClose: () => void;
}): VNode {
  return (
    <Modal
      title="Full detail"
      onClose={{
        onClick: onClose as any,
      }}
    >
      <dl>
        <dt>Code</dt>
        <dd>
          {TalerErrorCode[error.code]} ({error.code})
        </dd>
        <dt>Hint</dt>
        <dd>{error.hint ?? "--"}</dd>
        <dt>Time</dt>
        <dd>
          <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" />
        </dd>
      </dl>
      <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
        {JSON.stringify(error, undefined, 2)}
      </pre>
    </Modal>
  );
}

export function ActiveTasksTable({}: {}): VNode {
  const { i18n } = useTranslationContext();
  const api = useBackendContext();
  const state = useAsyncAsHook(() => {
    return api.wallet.call(WalletApiOperation.GetActiveTasks, {});
  });
  const [showError, setShowError] = useState<TalerErrorDetail>();
  const tasks = state && !state.hasError ? state.response.tasks : [];

  useEffect(() => {
    if (!state || state.hasError) return;
    const lastTimeout = setTimeout(() => {
      state.retry();
    }, 1000);
    return () => {
      clearTimeout(lastTimeout);
    };
  }, [tasks]);

  // const listenAllEvents = Array.from<NotificationType>({ length: 1 });
  // listenAllEvents.includes = () => true
  // useEffect(() => {
  //   return api.listener.onUpdateNotification(listenAllEvents, (notif) => {
  //     state?.retry()
  //   });
  // });
  return (
    <Fragment>
      {showError && (
        <ErroDetailModal
          error={showError}
          onClose={async () => {
            setShowError(undefined);
          }}
        />
      )}

      <table style={{ width: "100%" }}>
        <thead>
          <tr>
            <th>
              <i18n.Translate>Type</i18n.Translate>
            </th>
            <th>
              <i18n.Translate>Id</i18n.Translate>
            </th>
            <th>
              <i18n.Translate>Since</i18n.Translate>
            </th>
            <th>
              <i18n.Translate>Next try</i18n.Translate>
            </th>
            <th>
              <i18n.Translate>Error</i18n.Translate>
            </th>
            <th>
              <i18n.Translate>Transaction</i18n.Translate>
            </th>
          </tr>
        </thead>
        <tbody>
          {tasks.map((task) => {
            const [type, id] = task.taskId.split(":");
            return (
              <tr>
                <td>{type}</td>
                <td title={id}>{id.substring(0, 10)}</td>
                <td>
                  <Time
                    timestamp={task.firstTry}
                    format="yyyy/MM/dd HH:mm:ss"
                  />
                </td>
                <td>
                  <Time timestamp={task.nextTry} format="yyyy/MM/dd HH:mm:ss" />
                </td>
                <td>
                  {!task.lastError?.code ? (
                    ""
                  ) : (
                    <a
                      href="#"
                      onClick={(e) => {
                        e.preventDefault();
                        setShowError(task.lastError);
                      }}
                    >
                      {TalerErrorCode[task.lastError.code]}
                    </a>
                  )}
                </td>
                <td>
                  {task.transaction ? (
                    <a
                      title={task.transaction}
                      href={Pages.balanceTransaction({ tid: task.transaction })}
                    >
                      {task.transaction.substring(0, 10)}
                    </a>
                  ) : (
                    "--"
                  )}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </Fragment>
  );
}
