/*
 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, 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 { TextField } from "../mui/TextField.js";
import { SafeHandler } from "../mui/handlers.js";
import { WxApiType } from "../wxApi.js";
import { WalletActivityTrack } from "../wxBackend.js";
import { Checkbox } from "./Checkbox.js";
import { Modal } from "./Modal.js";
import { Time } from "./Time.js";

const OPEN_ACTIVITY_HEIGHT_PX = 250;
const CLOSE_ACTIVITY_HEIGHT_PX = 40;

export function WalletActivity(): VNode {
  const { i18n } = useTranslationContext();
  const [, updateSettings] = useSettings();

  const [collapsed, setCollcapsed] = useState(true);

  useEffect(() => {
    document.body.style.marginBottom = `${
      collapsed ? CLOSE_ACTIVITY_HEIGHT_PX : OPEN_ACTIVITY_HEIGHT_PX
    }px`;
    return () => {
      document.body.style.marginBottom = "0px";
    };
  }, [collapsed]);

  const [table, setTable] = useState<"tasks" | "events">("events");
  if (collapsed) {
    return (
      <div
        style={{
          position: "fixed",
          bottom: 0,
          background: "lightgrey",
          zIndex: 1,
          height: CLOSE_ACTIVITY_HEIGHT_PX,
          overflowY: "scroll",
          width: "100%",
        }}
        onClick={() => {
          setCollcapsed(!collapsed);
        }}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-around",
            marginTop: 10,
            cursor: "pointer",
          }}
        >
          <i18n.Translate>
            Click here to open the wallet activity tab.
          </i18n.Translate>
        </div>
      </div>
    );
  }
  return (
    <div
      style={{
        position: "fixed",
        bottom: 0,
        background: "lightgrey",
        zIndex: 1,
        height: OPEN_ACTIVITY_HEIGHT_PX,
        overflowY: "scroll",
        width: "100%",
      }}
    >
      <div
        style={{
          display: "flex",
          justifyContent: "space-around",
          cursor: "pointer",
        }}
        onClick={() => {
          setCollcapsed(!collapsed);
        }}
      >
        <Button
          variant={table === "events" ? "contained" : "outlined"}
          style={{ margin: 4 }}
          onClick={async () => {
            setTable("events");
          }}
        >
          <i18n.Translate>Events</i18n.Translate>
        </Button>
        <Button
          variant={table === "tasks" ? "contained" : "outlined"}
          style={{ margin: 4 }}
          onClick={async () => {
            setTable("tasks");
          }}
        >
          <i18n.Translate>Active tasks</i18n.Translate>
        </Button>

        <Button
          variant="outlined"
          style={{ margin: 4 }}
          onClick={async () => {
            updateSettings("showWalletActivity", false);
          }}
        >
          <i18n.Translate>Close</i18n.Translate>
        </Button>
      </div>
      <div
        style={{
          backgroundColor: "white",
        }}
      >
        {(function (): VNode {
          switch (table) {
            case "events": {
              return <ObservabilityEventsTable />;
            }
            case "tasks": {
              return <ActiveTasksTable />;
            }
            default: {
              assertUnreachable(table);
            }
          }
        })()}
      </div>
    </div>
  );
}

interface MoreInfoPRops {
  events: (WalletNotification & { when: AbsoluteTime })[];
  onClick: (content: VNode) => void;
}

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.SSS"
                    />
                  </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 }: 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, idx) => {
    if (
      not.type !== NotificationType.RequestObservabilityEvent &&
      not.type !== NotificationType.TaskObservabilityEvent
    )
      return <Fragment />;

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

    return (
      <ShowObervavilityDetails
        key={idx}
        title={title}
        notif={not}
        onClick={onClick}
      />
    );
  });
  return (
    <table style={{ width: "100%" }}>
      <thead>
        <td>Event</td>
        <td>Info</td>
        <td>When</td>
      </thead>
      <tbody>{asd}</tbody>
    </table>
  );
}

function ShowObervavilityDetails({
  title,
  notif,
  onClick,
}: {
  title: string;
  notif: 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 }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>
            {notif.event.url}{" "}
            {notif?.event.type ===
            ObservabilityEventType.HttpFetchFinishSuccess ? (
              `(${notif.event.status})`
            ) : notif?.event.type ===
              ObservabilityEventType.HttpFetchFinishError ? (
              <a
                href="#"
                onClick={(e) => {
                  e.preventDefault();
                  if (
                    notif.event.type !==
                    ObservabilityEventType.HttpFetchFinishError
                  )
                    return;
                  const error = notif.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.SSS"
                          />
                        </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.SSS" />
          </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 }, 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.SSS" />
          </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 }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.taskId}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" />
          </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 }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.resultType}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" />
          </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 }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.operation}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" />
          </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 }, undefined, 2)}
                    </pre>
                  </Fragment>,
                );
              }}
            >
              {title}
            </a>
          </td>
          <td>{notif.event.type}</td>
          <td>
            <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" />
          </td>
        </tr>
      );
    }
    case ObservabilityEventType.DeclareConcernsTransaction:
    case ObservabilityEventType.Message:
      // FIXME
      return <></>;
  }
}

function createTabId(tab: chrome.tabs.Tab | undefined) {
  return !tab ? "popup" : `${tab.windowId}:${tab.id}`;
}

function refresh(
  api: WxApiType,
  onUpdate: (list: WalletActivityTrack[]) => void,
  filter: string,
  fromView?: chrome.tabs.Tab,
) {
  api.background
    .call("getNotifications", { filter, operationsFrom: createTabId(fromView) })
    .then((notif) => {
      onUpdate(notif);
    })
    .catch((error) => {
      console.log(error);
    });
}

let currentTab: chrome.tabs.Tab | undefined;
// Allow running outside the extension for testing
// tslint:disable-next-line:no-string-literal
if (typeof chrome !== "undefined" && typeof chrome.tabs !== "undefined") {
  const p = chrome.tabs.getCurrent();
  // this may be called outside the render phase (in the background)
  // when this happen currentTab is not needed but also undefined
  if (p) {
    p.then((d) => {
      currentTab = d;
    });
  }
}

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

  const [notifications, setNotifications] = useState<WalletActivityTrack[]>([]);
  const [showDetails, setShowDetails] = useState<VNode>();
  const [filter, onChangeFilter] = useState("");
  const [onlyThisScreen, setOnlyThisScreen] = useState(true);

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

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

      return () => {
        clearTimeout(lastTimeout);
      };
    }
    return periodicRefresh();
  }, [filter, onlyThisScreen]);

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <TextField
          label="Filter"
          variant="outlined"
          value={filter}
          onChange={onChangeFilter}
        />
        <Checkbox
          label={i18n.str`All events`}
          name="terms"
          onToggle={async () => setOnlyThisScreen((v) => !v)}
          enabled={!onlyThisScreen}
        />
        <div
          style={{
            padding: 4,
            margin: 2,
            border: "solid 1px black",
            alignSelf: "center",
          }}
          onClick={() => {
            api.background.call("clearNotifications", undefined).then(() => {
              refresh(
                api,
                setNotifications,
                filter,
                onlyThisScreen ? currentTab : undefined,
              );
            });
          }}
        >
          clear
        </div>
      </div>
      {showDetails && (
        <Modal
          title="event details"
          onClose={{
            onClick: (async () => {
              setShowDetails(undefined);
            }) as SafeHandler<void>,
          }}
        >
          {showDetails}
        </Modal>
      )}
      {notifications.map((not) => {
        return (
          <details key={not.groupId}>
            <summary>
              <div
                style={{
                  width: "90%",
                  display: "inline-flex",
                  justifyContent: "space-between",
                  padding: 4,
                }}
              >
                <div style={{ padding: 4 }}>
                  {(() => {
                    switch (not.type) {
                      case NotificationType.BalanceChange:
                        return i18n.str`Balance change`;
                      case NotificationType.BackupOperationError:
                        return i18n.str`Backup failed`;
                      case NotificationType.TransactionStateTransition:
                        return i18n.str`Transaction updated`;
                      case NotificationType.ExchangeStateTransition:
                        return i18n.str`Exchange updated`;
                      case NotificationType.Idle:
                        return i18n.str`Idle`;
                      case NotificationType.TaskObservabilityEvent:
                        return i18n.str`task.${
                          (not.events[0] as TaskProgressNotification).taskId
                        }`;
                      case NotificationType.RequestObservabilityEvent:
                        return i18n.str`wallet.${
                          (not.events[0] as RequestProgressNotification)
                            .operation
                        }(${
                          (not.events[0] as RequestProgressNotification)
                            .requestId
                        })`;
                      case NotificationType.BankAccountChange:
                        return i18n.str`Bank account info changed`;
                      default: {
                        assertUnreachable(not.type);
                      }
                    }
                  })()}
                </div>
                <div style={{ padding: 4 }}>
                  <Time
                    timestamp={not.start}
                    format="yyyy/MM/dd HH:mm:ss.SSS"
                  />
                </div>
                <div style={{ padding: 4 }}>
                  <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss.SSS" />
                </div>
              </div>
            </summary>
            {(() => {
              switch (not.type) {
                case NotificationType.BalanceChange: {
                  return (
                    <ShowBalanceChange
                      events={not.events}
                      onClick={(details) => {
                        setShowDetails(details);
                      }}
                    />
                  );
                }
                case NotificationType.BackupOperationError: {
                  return (
                    <ShowBackupOperationError
                      events={not.events}
                      onClick={(details) => {
                        setShowDetails(details);
                      }}
                    />
                  );
                }
                case NotificationType.TransactionStateTransition: {
                  return (
                    <ShowTransactionStateTransition
                      events={not.events}
                      onClick={(details) => {
                        setShowDetails(details);
                      }}
                    />
                  );
                }
                case NotificationType.ExchangeStateTransition: {
                  return (
                    <ShowExchangeStateTransition
                      events={not.events}
                      onClick={(details) => {
                        setShowDetails(details);
                      }}
                    />
                  );
                }
                case NotificationType.Idle: {
                  return <div>not implemented</div>;
                }
                case NotificationType.TaskObservabilityEvent: {
                  return (
                    <ShowObservabilityEvent
                      events={not.events}
                      onClick={(details) => {
                        setShowDetails(details);
                      }}
                    />
                  );
                }
                case NotificationType.RequestObservabilityEvent: {
                  return (
                    <ShowObservabilityEvent
                      events={not.events}
                      onClick={(details) => {
                        setShowDetails(details);
                      }}
                    />
                  );
                }
                default:
                  return null;
              }
            })()}
          </details>
        );
      })}
    </div>
  );
}

function ErroDetailModal({
  error,
  onClose,
}: {
  error: TalerErrorDetail;
  onClose: () => void;
}): VNode {
  return (
    <Modal
      title="Full detail"
      onClose={{
        onClick: onClose as SafeHandler<void>,
      }}
    >
      <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.SSS" />
        </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]);

  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 key={id}>
                <td>{type}</td>
                <td title={id}>{id.substring(0, 10)}</td>
                <td>
                  <Time
                    timestamp={task.firstTry}
                    format="yyyy/MM/dd HH:mm:ss.SSS"
                  />
                </td>
                <td>
                  <Time
                    timestamp={task.nextTry}
                    format="yyyy/MM/dd HH:mm:ss.SSS"
                  />
                </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>
  );
}
