/*
 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 {
  AccessToken,
  OperationOk,
  TalerCoreBankResultByMethod,
  TalerHttpError,
  WithdrawalOperationStatus,
} from "@gnu-taler/taler-util";
import { useEffect, useState } from "preact/hooks";
import { useSessionState } from "./session.js";

// FIX default import https://github.com/microsoft/TypeScript/issues/49189
import _useSWR, { SWRHook, mutate } from "swr";
import { useBankCoreApiContext } from "@gnu-taler/web-util/browser";
import { PAGINATED_LIST_REQUEST } from "../utils.js";
const useSWR = _useSWR as unknown as SWRHook;

export interface InstanceTemplateFilter {
  // FIXME: add filter to the template list
  position?: string;
}

export function revalidateAccountDetails() {
  return mutate(
    (key) => Array.isArray(key) && key[key.length - 1] === "getAccount",
    undefined,
    { revalidate: true },
  );
}

export function useAccountDetails(account: string) {
  const { state: credentials } = useSessionState();
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();

  async function fetcher([username, token]: [string, AccessToken]) {
    return await api.getAccount({ username, token });
  }
  const token =
    credentials.status !== "loggedIn" ? undefined : credentials.token;
  const { data, error } = useSWR<
    TalerCoreBankResultByMethod<"getAccount">,
    TalerHttpError
  >([account, token, "getAccount"], fetcher, {});

  if (data) return data;
  if (error) return error;
  return undefined;
}

export function revalidateWithdrawalDetails() {
  return mutate(
    (key) => Array.isArray(key) && key[key.length - 1] === "getWithdrawalById",
    undefined,
    { revalidate: true },
  );
}

export function useWithdrawalDetails(wid: string) {
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();
  const [latestStatus, setLatestStatus] = useState<WithdrawalOperationStatus>();

  async function fetcher([wid, old_state]: [
    string,
    WithdrawalOperationStatus | undefined,
  ]) {
    return await api.getWithdrawalById(
      wid,
      old_state === undefined ? undefined : { old_state, timeoutMs: 15000 },
    );
  }

  const { data, error } = useSWR<
    TalerCoreBankResultByMethod<"getWithdrawalById">,
    TalerHttpError
  >([wid, latestStatus, "getWithdrawalById"], fetcher, {
    refreshInterval: 3000,
    refreshWhenHidden: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    refreshWhenOffline: false,
    errorRetryCount: 0,
    errorRetryInterval: 1,
    shouldRetryOnError: false,
    keepPreviousData: true,
  });

  const currentStatus =
    data !== undefined && data.type === "ok" ? data.body.status : undefined;

  useEffect(() => {
    if (currentStatus !== undefined && currentStatus !== latestStatus) {
      setLatestStatus(currentStatus);
    }
  }, [currentStatus]);

  if (data) return data;
  if (error) return error;
  return undefined;
}

export function revalidateTransactionDetails() {
  return mutate(
    (key) => Array.isArray(key) && key[key.length - 1] === "getTransactionById",
    undefined,
    { revalidate: true },
  );
}
export function useTransactionDetails(account: string, tid: number) {
  const { state: credentials } = useSessionState();
  const token =
    credentials.status !== "loggedIn" ? undefined : credentials.token;
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();

  async function fetcher([username, token, txid]: [
    string,
    AccessToken,
    number,
  ]) {
    return await api.getTransactionById({ username, token }, txid);
  }

  const { data, error } = useSWR<
    TalerCoreBankResultByMethod<"getTransactionById">,
    TalerHttpError
  >([account, token, tid, "getTransactionById"], fetcher, {
    refreshInterval: 0,
    refreshWhenHidden: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    refreshWhenOffline: false,
    errorRetryCount: 0,
    errorRetryInterval: 1,
    shouldRetryOnError: false,
    keepPreviousData: true,
  });

  if (data) return data;
  if (error) return error;
  return undefined;
}

export async function revalidatePublicAccounts() {
  return mutate(
    (key) => Array.isArray(key) && key[key.length - 1] === "getPublicAccounts",
    undefined,
    { revalidate: true },
  );
}
export function usePublicAccounts(
  filterAccount: string | undefined,
  initial?: number,
) {
  const [offset, setOffset] = useState<number | undefined>(initial);

  const {
    lib: { bank: api },
  } = useBankCoreApiContext();

  async function fetcher([account, txid]: [
    string | undefined,
    number | undefined,
  ]) {
    return await api.getPublicAccounts(
      { account },
      {
        limit: PAGINATED_LIST_REQUEST,
        offset: txid ? String(txid) : undefined,
        order: "asc",
      },
    );
  }

  const { data, error } = useSWR<
    TalerCoreBankResultByMethod<"getPublicAccounts">,
    TalerHttpError
  >([filterAccount, offset, "getPublicAccounts"], fetcher, {
    refreshInterval: 0,
    refreshWhenHidden: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    refreshWhenOffline: false,
    errorRetryCount: 0,
    errorRetryInterval: 1,
    shouldRetryOnError: false,
    keepPreviousData: true,
  });

  if (error) return error;
  if (data === undefined) return undefined;
  // if (data.type !== "ok") return data;

  //TODO: row_id should not be optional
  return buildPaginatedResult(
    data.body.public_accounts,
    offset,
    setOffset,
    (d) => d.row_id ?? 0,
  );
}

type PaginatedResult<T> = OperationOk<T> & {
  isLastPage: boolean;
  isFirstPage: boolean;
  loadNext(): void;
  loadFirst(): void;
};
//TODO: consider sending this to web-util
export function buildPaginatedResult<DataType, OffsetId>(
  data: DataType[],
  offset: OffsetId | undefined,
  setOffset: (o: OffsetId | undefined) => void,
  getId: (r: DataType) => OffsetId,
): PaginatedResult<DataType[]> {
  const isLastPage = data.length < PAGINATED_LIST_REQUEST;
  const isFirstPage = offset === undefined;

  const result = structuredClone(data);
  if (result.length == PAGINATED_LIST_REQUEST) {
    //do now show the last element, used to know if this is the last page
    result.pop();
  }
  return {
    type: "ok",
    body: result,
    isLastPage,
    isFirstPage,
    loadNext: () => {
      if (!result.length) return;
      const id = getId(result[result.length - 1]);
      setOffset(id);
    },
    loadFirst: () => {
      setOffset(undefined);
    },
  };
}

export function revalidateTransactions() {
  return mutate(
    (key) => Array.isArray(key) && key[key.length - 1] === "getTransactions",
    undefined,
    { revalidate: true },
  );
}
export function useTransactions(account: string, initial?: number) {
  const { state: credentials } = useSessionState();
  const token =
    credentials.status !== "loggedIn" ? undefined : credentials.token;

  const [offset, setOffset] = useState<number | undefined>(initial);
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();

  async function fetcher([username, token, txid]: [
    string,
    AccessToken,
    number | undefined,
  ]) {
    return await api.getTransactions(
      { username, token },
      {
        limit: PAGINATED_LIST_REQUEST,
        offset: txid ? String(txid) : undefined,
        order: "dec",
      },
    );
  }

  const { data, error } = useSWR<
    TalerCoreBankResultByMethod<"getTransactions">,
    TalerHttpError
  >([account, token, offset, "getTransactions"], fetcher, {
    refreshInterval: 10000,
    refreshWhenHidden: false,
    refreshWhenOffline: false,
    // revalidateOnMount: false,
    revalidateIfStale: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
  });
  if (error) return error;
  if (data === undefined) return undefined;
  if (data.type !== "ok") return data;

  return buildPaginatedResult(
    data.body.transactions,
    offset,
    setOffset,
    (d) => d.row_id,
  );
}
