import { mapConcurrent } from "../../concurrent.js";
import { createBucketKey } from "../bucket-key.js";

const UTXO_CONCURRENCY = 4;

const utxosByBucketKey =
  /** @type {Map<string, Promise<Map<string, ApiUtxo[]>>>} */ (new Map());

/**
 * @typedef {import("../../scan/index.js").WalletAddress} WalletAddress
 */

/**
 * @typedef {Object} ApiUtxoStatus
 * @property {boolean} confirmed
 *
 * @typedef {Object} ApiUtxo
 * @property {string} txid
 * @property {number} vout
 * @property {number} value
 * @property {ApiUtxoStatus} status
 */

/**
 * @typedef {Object} UtxoClient
 * @property {(address: string, options?: { cache?: boolean }) => Promise<ApiUtxo[]>} getAddressUtxos
 */

/**
 * @typedef {Object} AddressUtxos
 * @property {ApiUtxo[]} utxos
 */

/**
 * @param {unknown} error
 */
function isNotFound(error) {
  return (
    error instanceof Error &&
    /** @type {{ status?: unknown }} */ (error).status === 404
  );
}

/**
 * @param {UtxoClient} client
 * @param {string} address
 * @returns {Promise<ApiUtxo[]>}
 */
async function fetchAddressUtxos(client, address) {
  try {
    return await client.getAddressUtxos(address, { cache: false });
  } catch (error) {
    if (isNotFound(error)) return [];

    throw error;
  }
}

/**
 * @param {UtxoClient} client
 * @param {readonly string[]} addresses
 * @returns {Promise<Map<string, ApiUtxo[]>>}
 */
async function fetchBucketUtxos(client, addresses) {
  const entries = await mapConcurrent(
    addresses,
    UTXO_CONCURRENCY,
    async (address) => {
      return /** @type {const} */ ([
        address,
        await fetchAddressUtxos(client, address),
      ]);
    },
  );

  return new Map(entries);
}

/**
 * @param {UtxoClient} client
 * @param {WalletAddress} address
 * @returns {Promise<AddressUtxos>}
 */
async function load(client, address) {
  if (address.balance <= 0 || address.historyAddresses.length === 0) {
    return {
      utxos: [],
    };
  }

  const key = createBucketKey(address.historyAddresses);
  let utxos = utxosByBucketKey.get(key);

  if (!utxos) {
    utxos = fetchBucketUtxos(client, address.historyAddresses).catch(
      (error) => {
        utxosByBucketKey.delete(key);
        throw error;
      },
    );
    utxosByBucketKey.set(key, utxos);
  }

  const bucketUtxos = await utxos;

  return {
    utxos: bucketUtxos.get(address.address) ?? [],
  };
}

export const utxoCache = /** @type {const} */ ({
  load,
});
