/*
 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 {
  AbsoluteTime,
  AmountString,
  Amounts,
  CurrencySpecification,
  Duration,
  HttpStatusCode,
  TalerCorebankApi,
  TalerError,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  Attention,
  ErrorLoading,
  RouteDefinition,
  useBankCoreApiContext,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { format, sub } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";

import { Transactions } from "../../components/Transactions/index.js";
import { useConversionInfo, useLastMonitorInfo } from "../../hooks/regional.js";
import { RenderAmount } from "../PaytoWireTransferForm.js";
import { WireTransfer } from "../WireTransfer.js";
import { AccountList } from "./AccountList.js";
import { ConversionClassList } from "./ConversionClassList.js";

const TALER_SCREEN_ID = 122;

/**
 * Query account information and show QR code if there is pending withdrawal
 */
interface Props {
  routeDownloadStats: RouteDefinition;
  routeCreateWireTransfer: RouteDefinition<{
    account?: string;
    subject?: string;
    amount?: string;
  }>;

  routeCreateAccount: RouteDefinition;
  routeRemoveAccount: RouteDefinition<{ account: string }>;
  routeShowAccount: RouteDefinition<{ account: string }>;
  routeUpdatePasswordAccount: RouteDefinition<{ account: string }>;
  routeShowCashoutsAccount: RouteDefinition<{ account: string }>;

  routeCreateConversionRateClass: RouteDefinition;
  routeShowConversionRateClass: RouteDefinition<{ classId: string }>;
}
export function AdminHome({
  routeCreateAccount,
  routeRemoveAccount,
  routeShowAccount,
  routeUpdatePasswordAccount,
  routeDownloadStats,
  routeCreateWireTransfer,
  routeCreateConversionRateClass,
  routeShowConversionRateClass,
}: Props): VNode {
  const { config } = useBankCoreApiContext();
  return (
    <Fragment>
      <Metrics routeDownloadStats={routeDownloadStats} />
      <WireTransfer />
      <Transactions
        account="admin"
        routeCreateWireTransfer={routeCreateWireTransfer}
      />
      <AccountList
        routeCreate={routeCreateAccount}
        routeRemoveAccount={routeRemoveAccount}
        routeShowAccount={routeShowAccount}
        routeUpdatePasswordAccount={routeUpdatePasswordAccount}
      />
      {!config.allow_conversion ? undefined : (
        <ConversionClassList
          routeCreate={routeCreateConversionRateClass}
          routeShowDetails={routeShowConversionRateClass}
        />
      )}
    </Fragment>
  );
}

function getDateForTimeframeStart(
  date: AbsoluteTime,
  timeframe: TalerCorebankApi.MonitorTimeframeParam,
  locale: Locale,
): string {
  if (date.t_ms === "never") return "--";
  switch (timeframe) {
    case TalerCorebankApi.MonitorTimeframeParam.hour:
      return `${format(date.t_ms, "HH:00", { locale })}hs`;
    case TalerCorebankApi.MonitorTimeframeParam.day:
      return format(date.t_ms, "EEEE", { locale });
    case TalerCorebankApi.MonitorTimeframeParam.month:
      return format(date.t_ms, "MMMM", { locale });
    case TalerCorebankApi.MonitorTimeframeParam.year:
      return format(date.t_ms, "yyyy", { locale });
    case TalerCorebankApi.MonitorTimeframeParam.decade:
      return format(date.t_ms, "yyyy", { locale });
  }
  assertUnreachable(timeframe);
}

function getDateForTimeframeEnd(
  date: AbsoluteTime,
  timeframe: TalerCorebankApi.MonitorTimeframeParam,
  locale: Locale,
): string {
  if (date.t_ms === "never") return "--";
  switch (timeframe) {
    case TalerCorebankApi.MonitorTimeframeParam.hour: {
      const end = AbsoluteTime.addDuration(
        date,
        Duration.fromSpec({ hours: 1 }),
      );
      if (end.t_ms === "never")
        throw Error(`abs time plus 1 hour duration can't be 'never'`);
      return `${format(end.t_ms, "HH:00", { locale })}hs`;
    }
    case TalerCorebankApi.MonitorTimeframeParam.day: {
      const end = AbsoluteTime.addDuration(
        date,
        Duration.fromSpec({ days: 1 }),
      );
      if (end.t_ms === "never")
        throw Error(`abs time plus 1 day duration can't be 'never'`);
      return format(end.t_ms, "EEEE", { locale });
    }
    case TalerCorebankApi.MonitorTimeframeParam.month: {
      const end = AbsoluteTime.addDuration(
        date,
        Duration.fromSpec({ months: 1 }),
      );
      if (end.t_ms === "never")
        throw Error(`abs time plus 1 month duration can't be 'never'`);
      return format(end.t_ms, "MMMM", { locale });
    }
    case TalerCorebankApi.MonitorTimeframeParam.year: {
      const end = AbsoluteTime.addDuration(
        date,
        Duration.fromSpec({ years: 1 }),
      );
      if (end.t_ms === "never")
        throw Error(`abs time plus 1 year duration can't be 'never'`);
      return format(end.t_ms, "yyyy", { locale });
    }
    case TalerCorebankApi.MonitorTimeframeParam.decade: {
      const end = AbsoluteTime.addDuration(
        date,
        Duration.fromSpec({ years: 10 }),
      );
      if (end.t_ms === "never")
        throw Error(`abs time plus 10 years duration can't be 'never'`);
      return format(end.t_ms, "yyyy", { locale });
    }
  }
  assertUnreachable(timeframe);
}

export function getTimeframesForDate(
  time: Date,
  timeframe: TalerCorebankApi.MonitorTimeframeParam,
): { current: AbsoluteTime; previous: AbsoluteTime } {
  switch (timeframe) {
    case TalerCorebankApi.MonitorTimeframeParam.hour:
      return {
        current: AbsoluteTime.fromMilliseconds(
          sub(time, { hours: 1 }).getTime(),
        ),
        previous: AbsoluteTime.fromMilliseconds(
          sub(time, { hours: 2 }).getTime(),
        ),
      };
    case TalerCorebankApi.MonitorTimeframeParam.day:
      return {
        current: AbsoluteTime.fromMilliseconds(
          sub(time, { days: 1 }).getTime(),
        ),
        previous: AbsoluteTime.fromMilliseconds(
          sub(time, { days: 2 }).getTime(),
        ),
      };
    case TalerCorebankApi.MonitorTimeframeParam.month:
      return {
        current: AbsoluteTime.fromMilliseconds(
          sub(time, { months: 1 }).getTime(),
        ),
        previous: AbsoluteTime.fromMilliseconds(
          sub(time, { months: 2 }).getTime(),
        ),
      };
    case TalerCorebankApi.MonitorTimeframeParam.year:
      return {
        current: AbsoluteTime.fromMilliseconds(
          sub(time, { years: 1 }).getTime(),
        ),
        previous: AbsoluteTime.fromMilliseconds(
          sub(time, { years: 2 }).getTime(),
        ),
      };
    case TalerCorebankApi.MonitorTimeframeParam.decade:
      return {
        current: AbsoluteTime.fromMilliseconds(
          sub(time, { years: 10 }).getTime(),
        ),
        previous: AbsoluteTime.fromMilliseconds(
          sub(time, { years: 20 }).getTime(),
        ),
      };
    default:
      assertUnreachable(timeframe);
  }
}

function Metrics({
  routeDownloadStats,
}: {
  routeDownloadStats: RouteDefinition;
}): VNode {
  const { i18n, dateLocale } = useTranslationContext();
  const [metricType, setMetricType] =
    useState<TalerCorebankApi.MonitorTimeframeParam>(
      TalerCorebankApi.MonitorTimeframeParam.hour,
    );
  const { config } = useBankCoreApiContext();
  const respInfo = useConversionInfo();
  const params = getTimeframesForDate(new Date(), metricType);

  const resp = useLastMonitorInfo(params.current, params.previous, metricType);
  if (!resp) return <Fragment />;
  if (resp instanceof TalerError) {
    return <ErrorLoading error={resp} />;
  }
  if (respInfo && respInfo instanceof TalerError) {
    return <ErrorLoading error={respInfo} />;
  }
  if (respInfo && respInfo.type === "fail") {
    switch (respInfo.case) {
      case HttpStatusCode.NotImplemented: {
        return (
          <Attention type="danger" title={i18n.str`Cashout is disabled`}>
            <i18n.Translate>
              Cashout should be enabled in the configuration, the conversion
              rate should be initialized with fee(s), rates and a rounding mode.
            </i18n.Translate>
          </Attention>
        );
      }
      default: {
        assertUnreachable(respInfo);
      }
    }
  }

  if (resp.current.type !== "ok") {
    switch (resp.current.case) {
      case HttpStatusCode.BadRequest:
        return (
          <Attention
            type="warning"
            title={i18n.str`Querying for the current stats failed`}
          >
            <i18n.Translate>The request parameters are wrong</i18n.Translate>
          </Attention>
        );
      case HttpStatusCode.Unauthorized:
        return (
          <Attention
            type="warning"
            title={i18n.str`Querying for the current stats failed`}
          >
            <i18n.Translate>The user is unauthorized</i18n.Translate>
          </Attention>
        );
      default: {
        assertUnreachable(resp.current);
      }
    }
  }
  if (resp.previous.type !== "ok") {
    switch (resp.previous.case) {
      case HttpStatusCode.BadRequest:
        return (
          <Attention
            type="warning"
            title={i18n.str`Querying for the previous stats failed`}
          >
            <i18n.Translate>The request parameters are wrong</i18n.Translate>
          </Attention>
        );
      case HttpStatusCode.Unauthorized:
        return (
          <Attention
            type="warning"
            title={i18n.str`Querying for the previous stats failed`}
          >
            <i18n.Translate>The user is unauthorized</i18n.Translate>
          </Attention>
        );
      default: {
        assertUnreachable(resp.previous);
      }
    }
  }
  return (
    <div class="px-4 mt-4">
      <div class="sm:flex sm:items-center mb-4">
        <div class="sm:flex-auto">
          <h1 class="text-base font-semibold leading-6 text-gray-900">
            <i18n.Translate>Transaction volume report</i18n.Translate>
          </h1>
        </div>
      </div>

      <div class="sm:hidden">
        <label for="tabs" class="sr-only">
          <i18n.Translate>Select a section</i18n.Translate>
        </label>
        <select
          id="tabs"
          name="tabs"
          class="block w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
          onChange={(e) => {
            setMetricType(
              parseInt(
                e.currentTarget.value,
                10,
              ) as TalerCorebankApi.MonitorTimeframeParam,
            );
          }}
        >
          <option
            value={TalerCorebankApi.MonitorTimeframeParam.hour}
            selected={metricType == TalerCorebankApi.MonitorTimeframeParam.hour}
          >
            <i18n.Translate>Last hour</i18n.Translate>
          </option>
          <option
            value={TalerCorebankApi.MonitorTimeframeParam.day}
            selected={metricType == TalerCorebankApi.MonitorTimeframeParam.day}
          >
            <i18n.Translate>Previous day</i18n.Translate>
          </option>
          <option
            value={TalerCorebankApi.MonitorTimeframeParam.month}
            selected={
              metricType == TalerCorebankApi.MonitorTimeframeParam.month
            }
          >
            <i18n.Translate>Last month</i18n.Translate>
          </option>
          <option
            value={TalerCorebankApi.MonitorTimeframeParam.year}
            selected={metricType == TalerCorebankApi.MonitorTimeframeParam.year}
          >
            <i18n.Translate>Last year</i18n.Translate>
          </option>
        </select>
      </div>
      <div class="hidden sm:block">
        {/* FIXME: This should be LINKS */}
        <nav
          class="isolate flex divide-x divide-gray-200 rounded-lg shadow"
          aria-label="Tabs"
        >
          <button
            type="button"
            name="set last hour"
            onClick={(e) => {
              e.preventDefault();
              setMetricType(TalerCorebankApi.MonitorTimeframeParam.hour);
            }}
            data-selected={
              metricType == TalerCorebankApi.MonitorTimeframeParam.hour
            }
            class="rounded-l-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10"
          >
            <span>
              <i18n.Translate>Last hour</i18n.Translate>
            </span>
            <span
              aria-hidden="true"
              data-selected={
                metricType == TalerCorebankApi.MonitorTimeframeParam.hour
              }
              class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"
            ></span>
          </button>
          <button
            type="button"
            name="set previous day"
            onClick={(e) => {
              e.preventDefault();
              setMetricType(TalerCorebankApi.MonitorTimeframeParam.day);
            }}
            data-selected={
              metricType == TalerCorebankApi.MonitorTimeframeParam.day
            }
            class="             text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10"
          >
            <span>
              <i18n.Translate>Previous day</i18n.Translate>
            </span>
            <span
              aria-hidden="true"
              data-selected={
                metricType == TalerCorebankApi.MonitorTimeframeParam.day
              }
              class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"
            ></span>
          </button>
          <button
            type="button"
            name="set last month"
            onClick={(e) => {
              e.preventDefault();
              setMetricType(TalerCorebankApi.MonitorTimeframeParam.month);
            }}
            data-selected={
              metricType == TalerCorebankApi.MonitorTimeframeParam.month
            }
            class="rounded-r-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10"
          >
            <span>
              <i18n.Translate>Last month</i18n.Translate>
            </span>
            <span
              aria-hidden="true"
              data-selected={
                metricType == TalerCorebankApi.MonitorTimeframeParam.month
              }
              class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"
            ></span>
          </button>
          <button
            type="button"
            name="set last year"
            onClick={(e) => {
              e.preventDefault();
              setMetricType(TalerCorebankApi.MonitorTimeframeParam.year);
            }}
            data-selected={
              metricType == TalerCorebankApi.MonitorTimeframeParam.year
            }
            class="rounded-r-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10"
          >
            <span>
              <i18n.Translate>Last Year</i18n.Translate>
            </span>
            <span
              aria-hidden="true"
              data-selected={
                metricType == TalerCorebankApi.MonitorTimeframeParam.year
              }
              class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"
            ></span>
          </button>
        </nav>
      </div>

      <div class="w-full flex justify-between">
        <h1 class="text-base text-gray-900 mt-5">
          {i18n.str`Trading volume from ${getDateForTimeframeStart(
            params.current,
            metricType,
            dateLocale,
          )} to ${getDateForTimeframeEnd(
            params.current,
            metricType,
            dateLocale,
          )}`}
        </h1>
      </div>
      <dl class="mt-5 grid grid-cols-1 md:grid-cols-2  divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:divide-x md:divide-y-0">
        {!respInfo ||
        resp.current.body.type !== "with-conversions" ||
        resp.previous.body.type !== "with-conversions" ? undefined : (
          <Fragment>
            <div class="px-4 py-5 sm:p-6">
              <dt class="text-base font-normal text-gray-900">
                <i18n.Translate>Cashin</i18n.Translate>
                <div class="text-xs text-gray-500">
                  <i18n.Translate>
                    Transferred from an external account to an account in this
                    bank.
                  </i18n.Translate>
                </div>
              </dt>
              <MetricValueAmount
                current={resp.current.body.cashinFiatVolume}
                previous={resp.previous.body.cashinFiatVolume}
                spec={respInfo.body.fiat_currency_specification}
              />
            </div>
            <div class="px-4 py-5 sm:p-6">
              <dt class="text-base font-normal text-gray-900">
                <i18n.Translate>Cashout</i18n.Translate>
              </dt>
              <div class="text-xs text-gray-500">
                <i18n.Translate>
                  Transferred from an account in this bank to an external
                  account.
                </i18n.Translate>
              </div>
              <MetricValueAmount
                current={resp.current.body.cashoutFiatVolume}
                previous={resp.previous.body.cashoutFiatVolume}
                spec={respInfo.body.fiat_currency_specification}
              />
            </div>
          </Fragment>
        )}
        <div class="px-4 py-5 sm:p-6">
          <dt class="text-base font-normal text-gray-900">
            <i18n.Translate>Payin</i18n.Translate>
            <div class="text-xs text-gray-500">
              <i18n.Translate>
                Transferred from an account to a Taler exchange.
              </i18n.Translate>
            </div>
          </dt>
          <MetricValueAmount
            current={resp.current.body.talerInVolume}
            previous={resp.previous.body.talerInVolume}
            spec={config.currency_specification}
          />
        </div>
        <div class="px-4 py-5 sm:p-6">
          <dt class="text-base font-normal text-gray-900">
            <i18n.Translate>Payout</i18n.Translate>
            <div class="text-xs text-gray-500">
              <i18n.Translate>
                Transferred from a Taler exchange to another account.
              </i18n.Translate>
            </div>
          </dt>
          <MetricValueAmount
            current={resp.current.body.talerOutVolume}
            previous={resp.previous.body.talerOutVolume}
            spec={config.currency_specification}
          />
        </div>
        <div class="px-4 py-5 sm:p-6">
          <dt class="text-base font-normal text-gray-900">
            <i18n.Translate>Payin</i18n.Translate>
            <div class="text-xs text-gray-500">
              <i18n.Translate>
                Transferred from an account to a Taler exchange.
              </i18n.Translate>
            </div>
          </dt>
          <MetricValueNumber
            current={resp.current.body.talerInCount}
            previous={resp.previous.body.talerInCount}
          />
        </div>
        <div class="px-4 py-5 sm:p-6">
          <dt class="text-base font-normal text-gray-900">
            <i18n.Translate>Payout</i18n.Translate>
            <div class="text-xs text-gray-500">
              <i18n.Translate>
                Transferred from a Taler exchange to another account.
              </i18n.Translate>
            </div>
          </dt>
          <MetricValueNumber
            current={resp.current.body.talerOutCount}
            previous={resp.previous.body.talerOutCount}
          />
        </div>
      </dl>
      <div class="flex justify-end mt-4">
        <a
          href={routeDownloadStats.url({})}
          name="download stats"
          class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
        >
          <i18n.Translate>Download stats as CSV</i18n.Translate>
        </a>
      </div>
    </div>
  );
}

function MetricValueAmount({
  current,
  previous,
  spec,
}: {
  spec: CurrencySpecification;
  current: AmountString | undefined;
  previous: AmountString | undefined;
}): VNode {
  const { i18n } = useTranslationContext();
  const cmp = current && previous ? Amounts.cmp(current, previous) : 0;
  const cv = !current ? undefined : Amounts.stringifyValue(current);
  const currAmount = !cv ? undefined : Number.parseFloat(cv);
  const prevAmount = !previous
    ? undefined
    : Number.parseFloat(Amounts.stringifyValue(previous));

  const rate =
    !currAmount ||
    Number.isNaN(currAmount) ||
    !prevAmount ||
    Number.isNaN(prevAmount)
      ? 0
      : cmp === -1
        ? 1 - Math.round(currAmount) / Math.round(prevAmount)
        : cmp === 1
          ? Math.round(currAmount) / Math.round(prevAmount) - 1
          : 0;

  const negative = cmp === 0 ? undefined : cmp === -1;
  const rateStr = `${(Math.abs(rate) * 100).toFixed(2)}%`;
  return (
    <Fragment>
      <dd class="mt-1 block ">
        <div class="flex justify-start text-2xl items-baseline font-semibold text-indigo-600">
          {!current ? (
            "-"
          ) : (
            <RenderAmount
              value={Amounts.parseOrThrow(current)}
              spec={spec}
              hideSmall
            />
          )}
        </div>
        <div class="flex flex-col">
          <div class="flex justify-end items-baseline text-2xl font-semibold text-indigo-600">
            <small class="ml-2 text-sm font-medium text-gray-500">
              <i18n.Translate>previous</i18n.Translate>{" "}
              {!previous ? (
                "-"
              ) : (
                <RenderAmount
                  value={Amounts.parseOrThrow(previous)}
                  spec={spec}
                  hideSmall
                />
              )}
            </small>
          </div>
          {!!rate && (
            <span
              data-negative={negative}
              class="flex items-center gap-x-1.5 w-fit rounded-md bg-green-100 text-green-800 data-[negative=true]:bg-red-100 px-2 py-1 text-xs font-medium data-[negative=true]:text-red-700 whitespace-pre"
            >
              {negative ? (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="w-6 h-6"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M12 4.5v15m0 0l6.75-6.75M12 19.5l-6.75-6.75"
                  />
                </svg>
              ) : (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="w-6 h-6"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M12 19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75"
                  />
                </svg>
              )}

              {negative ? (
                <span class="sr-only">
                  <i18n.Translate>Decreased by</i18n.Translate>
                </span>
              ) : (
                <span class="sr-only">
                  <i18n.Translate>Increased by</i18n.Translate>
                </span>
              )}
              {rateStr}
            </span>
          )}
        </div>
      </dd>
    </Fragment>
  );
}

function MetricValueNumber({
  current,
  previous,
}: {
  current: number | undefined;
  previous: number | undefined;
}): VNode {
  const { i18n } = useTranslationContext();

  const cmp = current && previous ? (current < previous ? -1 : 1) : 0;

  const rate =
    !current || Number.isNaN(current) || !previous || Number.isNaN(previous)
      ? 0
      : cmp === -1
        ? 1 - Math.round(current) / Math.round(previous)
        : cmp === 1
          ? Math.round(current) / Math.round(previous) - 1
          : 0;

  const negative = cmp === 0 ? undefined : cmp === -1;
  const rateStr = `${(Math.abs(rate) * 100).toFixed(2)}%`;
  return (
    <Fragment>
      <dd class="mt-1 block ">
        <div class="flex justify-start text-2xl items-baseline font-semibold text-indigo-600">
          {!current ? "-" : current}
        </div>
        <div class="flex flex-col">
          <div class="flex justify-end items-baseline text-2xl font-semibold text-indigo-600">
            <small class="ml-2 text-sm font-medium text-gray-500">
              <i18n.Translate>previous</i18n.Translate>{" "}
              {!previous ? "-" : previous}
            </small>
          </div>
          {!!rate && (
            <span
              data-negative={negative}
              class="flex items-center gap-x-1.5 w-fit rounded-md bg-green-100 text-green-800 data-[negative=true]:bg-red-100 px-2 py-1 text-xs font-medium data-[negative=true]:text-red-700 whitespace-pre"
            >
              {negative ? (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="w-6 h-6"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M12 4.5v15m0 0l6.75-6.75M12 19.5l-6.75-6.75"
                  />
                </svg>
              ) : (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="w-6 h-6"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M12 19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75"
                  />
                </svg>
              )}

              {negative ? (
                <span class="sr-only">
                  <i18n.Translate>Decreased by</i18n.Translate>
                </span>
              ) : (
                <span class="sr-only">
                  <i18n.Translate>Increased by</i18n.Translate>
                </span>
              )}
              {rateStr}
            </span>
          )}
        </div>
      </dd>
    </Fragment>
  );
}
