// ============================================
// IMPROVED FLASHNET SWAP & LIGHTNING PAYMENT FUNCTIONS
// Based on Official Flashnet SDK Documentation v0.4.2+
// ============================================

import {
  FlashnetError,
  getErrorMetadata,
  isFlashnetError,
  isFlashnetErrorCode,
} from '@flashnet/sdk';
import {
  getFlashnetClient,
  getSparkAddress,
  getSingleTxDetails,
  getSparkLightningPaymentStatus,
  initializeFlashnet,
  selectSparkRuntime,
  sendSparkTokens,
  validateWebViewResponse,
} from '.';
import { getPublicKey } from 'nostr-tools';
import i18next from 'i18next';
import {
  OPERATION_TYPES,
  sendWebViewRequestGlobal,
} from '../../../context-store/webViewContext';
import {
  FLASHNET_ERROR_CODE_REGEX,
  FLASHNET_REFUND_REGEX,
  USDB_TOKEN_ID,
} from '../../constants';
import {
  isFlashnetTransfer,
  setFlashnetTransfer,
} from './handleFlashnetTransferIds';
import { getLocalStorageItem, setLocalStorageItem } from '../localStorage';
import {
  bulkUpdateSparkTransactions,
  getSingleSparkLightningRequest,
} from './transactions';
import { decode } from 'bolt11';
import fetchBackend from '../../../db/handleBackend';
import { privateKeyFromSeedWords } from '../nostrCompatability';
import {
  getLightningInvoiceAmountSats,
  isUsableOrchestraQuote,
  mapOrchestraQuoteToLightningQuote,
  normalizeOrchestraBackendError,
} from './orchestraLightning';

// ============================================
// CONSTANTS & PURE UTILITIES
// ============================================
import {
  BTC_ASSET_ADDRESS,
  USD_ASSET_ADDRESS,
  FLASHNET_POOL_IDENTITY_KEY,
  DEFAULT_SLIPPAGE_BPS,
  SEND_AMOUNT_INCREASE_BUFFER,
  DEFAULT_MAX_SLIPPAGE_BPS,
  INTEGRATOR_FEE,
  INTEGRATOR_FEE_BPS,
  satsToDollars,
  dollarsToSats,
  calculateFlashnetAmountIn,
  convertToDecimals,
} from './swapAmountUtils';

export {
  BTC_ASSET_ADDRESS,
  USD_ASSET_ADDRESS,
  FLASHNET_POOL_IDENTITY_KEY,
  DEFAULT_SLIPPAGE_BPS,
  SEND_AMOUNT_INCREASE_BUFFER,
  DEFAULT_MAX_SLIPPAGE_BPS,
  INTEGRATOR_FEE,
  INTEGRATOR_FEE_BPS,
  satsToDollars,
  dollarsToSats,
  calculateFlashnetAmountIn,
  convertToDecimals,
};

// ============================================
// HELPER FUNCTIONS
// ============================================

/**
 * Format error for logging
 */
const formatError = (error, operation) => {
  if (isFlashnetError(error)) {
    return {
      operation,
      errorCode: error.errorCode,
      category: error.category,
      message: error.userMessage,
      userMessage: i18next.t(`flashnetUserMessages.${error.errorCode}`, {
        defaultValue: error.userMessage,
      }),
      actionHint: error.actionHint,
      requestId: error.requestId,
      isRetryable: error.isRetryable,
      recovery: error.recovery,
      transferIds: error.transferIds,
      clawbackAttempted: error.wasClawbackAttempted?.() || false,
      fundsRecovered: error.wereAllTransfersRecovered?.() || false,
    };
  }

  let parsedError;
  if (typeof error === 'object') {
    parsedError = error.message;
  } else {
    parsedError = error;
  }

  const match = parsedError.match(FLASHNET_ERROR_CODE_REGEX);
  const errorCode = match?.[0] ?? null;

  if (errorCode) {
    const metadata = getErrorMetadata(errorCode);

    return {
      operation,
      errorCode: errorCode,
      category: metadata.category,
      message: metadata.userMessage,
      userMessage: i18next.t(`flashnetUserMessages.${errorCode}`, {
        defaultValue: metadata.userMessage,
      }),
      actionHint: metadata.actionHint,
      isRetryable: metadata.isRetryable,
      recovery: metadata.recovery,
      transferIds: metadata.transferIds,
      clawbackAttempted: metadata.wasClawbackAttempted?.() || false,
      fundsRecovered: metadata.wereAllTransfersRecovered?.() || false,
    };
  }

  return {
    operation,
    message: error?.message || String(error),
  };
};

/**
 * Calculate minimum output with slippage tolerance
 */
export const calculateMinOutput = (expectedOutput, slippageBps) => {
  const amount = BigInt(expectedOutput);
  const slippageFactor = BigInt(10000 - slippageBps);
  const minAmount = (amount * slippageFactor) / 10000n;
  return minAmount.toString();
};

// ============================================
// POOL DISCOVERY & QUERYING
// ============================================

/**
 * Find the best pool for a given token pair
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} tokenAAddress - First asset address
 * @param {string} tokenBAddress - Second asset address
 * @param {object} options - Optional filters
 * @returns {Promise<object>} Pool details
 */
export const findBestPool = async (
  mnemonic,
  tokenAAddress,
  tokenBAddress,
  options = {},
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.findBestPool,
        {
          mnemonic,
          tokenAAddress,
          tokenBAddress,
          options,
        },
      );
      return validateWebViewResponse(response, 'Not able to find best pool');
    } else {
      const client = getFlashnetClient(mnemonic);
      console.log(client, tokenAAddress, tokenBAddress);

      const pools = await client.listPools({
        assetAAddress: tokenAAddress,
        assetBAddress: tokenBAddress,
        sort: 'TVL_DESC', // Prefer highest TVL (best liquidity)
        minTvl: options.minTvl || 1000,
        limit: options.limit || 10,
      });
      console.log(pools, 'test');

      if (!pools.pools || pools.pools.length === 0) {
        throw new Error(
          i18next.t('screens.inAccount.swapsPage.noPoolsFoundError', {
            tokenAAddress,
            tokenBAddress,
          }),
        );
      }

      // Return the best pool (highest TVL)
      return {
        didWork: true,
        pool: pools.pools[0],
        totalAvailable: pools.totalCount,
      };
    }
  } catch (error) {
    const formatted = formatError(error, 'findBestPool');
    console.warn('Find best pool error:', formatError(error, 'findBestPool'));
    if (formatted.message === 'Flashnet client not initialized') {
      initializeFlashnet(mnemonic);
    }
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'findBestPool'),
    };
  }
};

/**
 * Get detailed information about a specific pool
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} poolId - Pool's lpPublicKey
 * @returns {Promise<object>} Pool details
 */
export const getPoolDetails = async (mnemonic, poolId) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.getPoolDetails,
        {
          mnemonic,
          poolId,
        },
      );
      return validateWebViewResponse(response, 'Not able to get pool detials');
    } else {
      const client = getFlashnetClient(mnemonic);
      const pool = await client.getPool(poolId);

      return {
        didWork: true,
        pool,
        marketData: {
          tvl: pool.tvlAssetB,
          volume24h: pool.volume24hAssetB,
          priceChange24h: pool.priceChangePercent24h,
          currentPrice: pool.currentPriceAInB,
          reserves: {
            assetA: pool.assetAReserve,
            assetB: pool.assetBReserve,
          },
        },
      };
    }
  } catch (error) {
    console.warn(
      'Get pool details error:',
      formatError(error, 'getPoolDetails'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'getPoolDetails'),
    };
  }
};

/**
 * List all available pools with optional filters
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} filters - Optional filtering/sorting parameters
 * @returns {Promise<object>} List of pools
 */
export const listAllPools = async (mnemonic, filters = {}) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.listAllPools,
        {
          mnemonic,
          filters,
        },
      );
      return validateWebViewResponse(response, 'Not able to list all pools');
    } else {
      const client = getFlashnetClient(mnemonic);

      const response = await client.listPools({
        minTvl: filters.minTvl || 0,
        minVolume24h: filters.minVolume24h || 0,
        sort: filters.sort || 'TVL_DESC',
        limit: filters.limit || 50,
        offset: filters.offset || 0,
        hostNames: filters.hostNames,
        curveTypes: filters.curveTypes,
      });

      return {
        didWork: true,
        pools: response.pools,
        totalCount: response.totalCount,
      };
    }
  } catch (error) {
    console.warn('List pools error:', formatError(error, 'listAllPools'));
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'listAllPools'),
    };
  }
};

/**
 * Gets the minimum swap amounts for a given asset
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} filters - Asset hex
 * @returns {Promise<object>} List of pools
 */
export const minFlashnetSwapAmounts = async (mnemonic, assetHex) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.minFlashnetSwapAmounts,
        { mnemonic, assetHex },
      );
      return validateWebViewResponse(
        response,
        'Not able to get min flashnet swap limits',
      );
    } else {
      const client = getFlashnetClient(mnemonic);
      const minMap = await client.getMinAmountsMap();

      const assetData = minMap.get(assetHex.toLowerCase());

      return {
        didWork: true,
        assetData: assetData,
      };
    }
  } catch (error) {
    console.warn(
      'List pools error:',
      formatError(error, 'minFlashnetSwapAmounts'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'minFlashnetSwapAmounts'),
    };
  }
};

// ============================================
// SWAP SIMULATION & EXECUTION
// ============================================

/**
 * Simulate a swap to get expected output and price impact
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Swap parameters
 * @returns {Promise<object>} Simulation results
 */
export const simulateSwap = async (
  mnemonic,
  {
    poolId,
    assetInAddress,
    assetOutAddress,
    amountIn,
    integratorFeeRateBps = INTEGRATOR_FEE_BPS,
  },
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);
    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.simulateSwap,
        {
          mnemonic,
          poolId,
          assetInAddress,
          assetOutAddress,
          amountIn,
          integratorFeeRateBps,
        },
      );
      return validateWebViewResponse(
        response,
        'Not able to simulate flashnet swap',
      );
    } else {
      const client = getFlashnetClient(mnemonic);

      const simulation = await client.simulateSwap({
        poolId,
        assetInAddress,
        assetOutAddress,
        amountIn: amountIn.toString(),
        integratorBps: integratorFeeRateBps,
      });

      return {
        didWork: true,
        simulation: {
          expectedOutput: simulation.amountOut,
          executionPrice: simulation.executionPrice,
          priceImpact: simulation.priceImpactPct,
          poolId: simulation.poolId,
          feePaidAssetIn: simulation.feePaidAssetIn,
        },
      };
    }
  } catch (error) {
    console.warn('Simulate swap error:', formatError(error, 'simulateSwap'));
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'simulateSwap'),
    };
  }
};

/**
 * Execute a token swap with slippage protection
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Swap parameters
 * @returns {Promise<object>} Swap result
 */
export const executeSwap = async (
  mnemonic,
  {
    poolId,
    assetInAddress,
    assetOutAddress,
    amountIn,
    minAmountOut, // Optional - will be calculated if not provided
    maxSlippageBps = DEFAULT_SLIPPAGE_BPS,
    integratorFeeRateBps = INTEGRATOR_FEE_BPS,
  },
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);
    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.executeSwap,
        {
          mnemonic,
          poolId,
          assetInAddress,
          assetOutAddress,
          amountIn,
          minAmountOut,
          maxSlippageBps,
          integratorFeeRateBps,
        },
      );
      if (!response.didWork) {
        if (assetInAddress === BTC_ASSET_ADDRESS) {
          const id = getRefundTxidFromErrormessage(response.error);
          if (id) {
            setFlashnetTransfer(id);
          }
        }
        const error = formatAndReturnWebviewError(response);
        return error;
      }
      return validateWebViewResponse(response, 'Not able to executeSwap');
    } else {
      const client = getFlashnetClient(mnemonic);

      // Simulate first if minAmountOut not provided
      let calculatedMinOut = minAmountOut;
      if (!calculatedMinOut) {
        const simulation = await client.simulateSwap({
          poolId,
          assetInAddress,
          assetOutAddress,
          amountIn: amountIn.toString(),
          integratorBps: integratorFeeRateBps,
        });
        calculatedMinOut = calculateMinOutput(
          simulation.amountOut,
          maxSlippageBps,
        );
      }

      // Execute the swap
      const swap = await client.executeSwap({
        poolId,
        assetInAddress,
        assetOutAddress,
        amountIn: amountIn.toString(),
        minAmountOut: calculatedMinOut.toString(),
        maxSlippageBps,
        integratorFeeRateBps,
        integratorPublicKey: process.env.BLITZ_SPARK_PUBLICKEY,
      });

      return {
        didWork: true,
        swap: {
          amountOut: swap.amountOut,
          executionPrice: swap.executionPrice,
          feeAmount: swap.feeAmount,
          flashnetRequestId: swap.flashnetRequestId,
          outboundTransferId: swap.outboundTransferId,
          poolId: swap.poolId,
        },
      };
    }
  } catch (error) {
    const errorDetails = formatError(error, 'executeSwap');
    console.warn('Execute swap error:', errorDetails);

    const id = getRefundTxidFromErrormessage(error.message);
    if (id) {
      setFlashnetTransfer(id);
    }

    // Check for auto-clawback results
    if (isFlashnetError(error) && error.wasClawbackAttempted()) {
      errorDetails.clawbackSummary = {
        attempted: true,
        allRecovered: error.wereAllTransfersRecovered(),
        partialRecovered: error.werePartialTransfersRecovered(),
        recoveredCount: error.getRecoveredTransferCount?.() || 0,
        recoveredIds: error.getRecoveredTransferIds?.() || [],
        unrecoveredIds: error.getUnrecoveredTransferIds?.() || [],
      };
    }

    return {
      didWork: false,
      error: error.message,
      details: errorDetails,
    };
  }
};

// ============================================
// BITCOIN <-> TOKEN SWAPS (Convenience Functions)
// ============================================

/**
 * Swap Bitcoin to Token
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Swap parameters
 * @returns {Promise<object>} Swap result
 */
export const swapBitcoinToToken = async (
  mnemonic,
  {
    tokenAddress,
    amountSats,
    poolId = null,
    maxSlippageBps = DEFAULT_SLIPPAGE_BPS,
  },
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);
    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.swapBitcoinToToken,
        {
          mnemonic,
          tokenAddress,
          amountSats,
          poolId,
          maxSlippageBps,
        },
      );

      if (!response.didWork) {
        const id = getRefundTxidFromErrormessage(response.error);
        if (id) {
          setFlashnetTransfer(id);
        }
        const error = formatAndReturnWebviewError(response);
        return error;
      }
      return validateWebViewResponse(
        response,
        'Not able to swapBitcoinToToken',
      );
    } else {
      // Find best pool if not provided
      let targetPoolId = poolId;
      if (!targetPoolId) {
        const poolResult = await findBestPool(
          mnemonic,
          BTC_ASSET_ADDRESS,
          tokenAddress,
        );
        if (!poolResult.didWork) {
          throw new Error('No suitable pool found for BTC/' + tokenAddress);
        }
        targetPoolId = poolResult.pool.lpPublicKey;
      }

      return await executeSwap(mnemonic, {
        poolId: targetPoolId,
        assetInAddress: BTC_ASSET_ADDRESS,
        assetOutAddress: tokenAddress,
        amountIn: amountSats,
        maxSlippageBps,
      });
    }
  } catch (error) {
    console.warn(
      'Swap BTC to token error:',
      formatError(error, 'swapBitcoinToToken'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'swapBitcoinToToken'),
    };
  }
};

/**
 * Swap Token to Bitcoin
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Swap parameters
 * @returns {Promise<object>} Swap result
 */
export const swapTokenToBitcoin = async (
  mnemonic,
  {
    tokenAddress,
    tokenAmount,
    poolId = null,
    maxSlippageBps = DEFAULT_SLIPPAGE_BPS,
  },
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);
    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.swapTokenToBitcoin,
        {
          mnemonic,
          tokenAddress,
          tokenAmount,
          poolId,
          maxSlippageBps,
        },
      );

      if (!response.didWork) {
        const error = formatAndReturnWebviewError(response);
        return error;
      }
      return validateWebViewResponse(
        response,
        'Not able to swapTokenToBitcoin',
      );
    } else {
      // Find best pool if not provided
      let targetPoolId = poolId;
      if (!targetPoolId) {
        const poolResult = await findBestPool(
          mnemonic,
          tokenAddress,
          BTC_ASSET_ADDRESS,
        );
        if (!poolResult.didWork) {
          throw new Error(
            'No suitable pool found for ' + tokenAddress + '/BTC',
          );
        }
        targetPoolId = poolResult.pool.lpPublicKey;
      }

      return await executeSwap(mnemonic, {
        poolId: targetPoolId,
        assetInAddress: tokenAddress,
        assetOutAddress: BTC_ASSET_ADDRESS,
        amountIn: tokenAmount,
        maxSlippageBps,
      });
    }
  } catch (error) {
    console.warn(
      'Swap token to BTC error:',
      formatError(error, 'swapTokenToBitcoin'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'swapTokenToBitcoin'),
    };
  }
};

// ============================================
// LIGHTNING PAYMENTS WITH TOKEN
// ============================================

/**
 * Get quote for paying a Lightning invoice with tokens
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} invoice - BOLT11 invoice
 * @param {string} tokenAddress - Token to spend
 * @returns {Promise<object>} Payment quote
 */
export const getLightningPaymentQuote = async (
  mnemonic,
  invoice,
  tokenAddress,
  _integratorFeeRateBps = INTEGRATOR_FEE_BPS,
  _maxSlippageBps = DEFAULT_MAX_SLIPPAGE_BPS,
  options = {},
) => {
  try {
    if (tokenAddress !== USD_ASSET_ADDRESS) {
      throw new Error('Only USDB Lightning payments are supported');
    }

    const invoiceAmountSats = getLightningInvoiceAmountSats(
      invoice,
      options.amountSats,
    );
    const privateKey =
      options.contactsPrivateKey || (await privateKeyFromSeedWords(mnemonic));
    const publicKey = options.publicKey || getPublicKey(privateKey);
    const sparkAddressResponse = options.refundAddress
      ? { didWork: true, response: options.refundAddress }
      : await getSparkAddress(mnemonic);

    if (!sparkAddressResponse.didWork || !sparkAddressResponse.response) {
      throw new Error(
        sparkAddressResponse.error || 'Unable to derive Spark refund address',
      );
    }

    const result = await fetchBackend(
      'createFlashnetStablecoinQuoteV2',
      {
        recipientAddress: invoice,
        destinationChain: 'lightning',
        destinationAsset: 'BTC',
        amountSats: invoiceAmountSats,
        sourceMethod: 'usdb',
        refundAddress: sparkAddressResponse.response,
      },
      privateKey,
      publicKey,
    );

    const quote = mapOrchestraQuoteToLightningQuote(result, invoiceAmountSats);
    return { didWork: true, quote };
  } catch (error) {
    const backendError = normalizeOrchestraBackendError(
      error,
      'Unable to get Lightning quote',
    );
    console.warn('Get Lightning quote error:', {
      operation: 'getLightningPaymentQuote',
      message: backendError.message,
      code: backendError.code,
      minimumSats: backendError.minimumSats,
    });
    return {
      didWork: false,
      error: backendError.message,
      details: {
        operation: 'getLightningPaymentQuote',
        message: backendError.message,
        code: backendError.code,
        minimumSats: backendError.minimumSats,
      },
    };
  }
};

/**
 * Pay a Lightning invoice using tokens
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Payment parameters
 * @returns {Promise<object>} Payment result
 */
export const payLightningWithToken = async (
  mnemonic,
  {
    invoice,
    tokenAddress,
    maxSlippageBps = DEFAULT_MAX_SLIPPAGE_BPS,
    integratorFeeRateBps = INTEGRATOR_FEE_BPS,
    quote = null,
    amountSats,
    contactsPrivateKey,
    publicKey,
    refundAddress,
  },
) => {
  try {
    if (tokenAddress !== USD_ASSET_ADDRESS) {
      throw new Error('Only USDB Lightning payments are supported');
    }

    const privateKey =
      contactsPrivateKey || (await privateKeyFromSeedWords(mnemonic));
    const resolvedPublicKey = publicKey || getPublicKey(privateKey);
    const sparkAddressResponse = refundAddress
      ? { didWork: true, response: refundAddress }
      : await getSparkAddress(mnemonic);

    if (!sparkAddressResponse.didWork || !sparkAddressResponse.response) {
      throw new Error(
        sparkAddressResponse.error || 'Unable to derive Spark source address',
      );
    }

    let paymentQuote = quote;
    if (!isUsableOrchestraQuote(paymentQuote)) {
      const quoteResponse = await getLightningPaymentQuote(
        mnemonic,
        invoice,
        tokenAddress,
        integratorFeeRateBps,
        maxSlippageBps,
        {
          amountSats,
          contactsPrivateKey: privateKey,
          publicKey: resolvedPublicKey,
          refundAddress: sparkAddressResponse.response,
        },
      );

      if (!quoteResponse.didWork) {
        throw new Error(quoteResponse.error || 'Unable to create quote');
      }
      paymentQuote = quoteResponse.quote;
    }

    const tokenAmount = Number(paymentQuote.tokenAmountRequired);
    if (!Number.isFinite(tokenAmount) || tokenAmount <= 0) {
      throw new Error('Invalid Orchestra token amount');
    }

    const tokenPayment = await sendSparkTokens({
      tokenIdentifier: USDB_TOKEN_ID,
      tokenAmount: Number(Math.ceil(tokenAmount)),
      receiverSparkAddress: paymentQuote.depositAddress,
      mnemonic,
    });

    if (!tokenPayment.didWork) {
      throw new Error(tokenPayment.error || 'Unable to send USDB deposit');
    }

    const sparkTxHash = tokenPayment.response;
    await fetchBackend(
      'submitFlashnetStablecoinOrder',
      {
        quoteId: paymentQuote.quoteId,
        sparkTxHash,
        sourceSparkAddress: sparkAddressResponse.response,
      },
      privateKey,
      resolvedPublicKey,
    );

    return {
      didWork: true,
      result: {
        success: true,
        tokenAmountSpent: tokenAmount,
        btcAmountReceived:
          Number(paymentQuote.estimatedOut) ||
          Number(paymentQuote.invoiceAmountSats) ||
          0,
        swapTransferId: sparkTxHash,
        ammFeePaid: Number(paymentQuote.estimatedAmmFee || 0),
        lightningFeePaid: 0,
        poolId: paymentQuote.quoteId,
        quoteId: paymentQuote.quoteId,
        depositAddress: paymentQuote.depositAddress,
        expiresAt: paymentQuote.expiresAt,
        orchestra: true,
        sourceSparkAddress: sparkAddressResponse.response,
      },
    };
  } catch (error) {
    const backendError = normalizeOrchestraBackendError(
      error,
      'Unable to pay Lightning invoice with USDB',
    );
    console.warn('Pay Lightning with token error:', {
      operation: 'payLightningWithToken',
      message: backendError.message,
      code: backendError.code,
      minimumSats: backendError.minimumSats,
    });
    return {
      didWork: false,
      error: backendError.message,
      details: {
        operation: 'payLightningWithToken',
        message: backendError.message,
        code: backendError.code,
        minimumSats: backendError.minimumSats,
      },
    };
  }
};

// ============================================
// SWAP HISTORY
// ============================================

/**
 * Get user's swap history
 * @param {string} mnemonic - Wallet mnemonic
 * @param {number} limit - Number of swaps to retrieve
 * @returns {Promise<object>} Swap history
 */
export const getUserSwapHistory = async (mnemonic, limit = 50, offset) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);
    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.getUserSwapHistory,
        {
          mnemonic,
          limit,
          offset,
        },
      );
      return validateWebViewResponse(
        response,
        'Not able to getUserSwapHistory',
      );
    } else {
      const client = getFlashnetClient(mnemonic);

      let result;
      if (offset) {
        result = await client.getUserSwaps(undefined, { limit, offset });
      } else {
        result = await client.getUserSwaps(undefined, { limit });
      }

      console.log('User swap history response:', result);

      return {
        didWork: true,
        swaps: result.swaps || [],
        totalCount: result.totalCount || 0,
      };
    }
  } catch (error) {
    console.warn(
      'Get swap history error:',
      formatError(error, 'getUserSwapHistory'),
    );
    return {
      didWork: false,
      error: error.message,
      swaps: [],
      details: formatError(error, 'getUserSwapHistory'),
    };
  }
};

// ============================================
// Track current LN -> USD swaps for the session
// ============================================

let activeSwapTransferIds = new Set();

/**
 * Checks to see if we have any pending ln -> btc swaps
 * @returns {Boolean} True if we have pending swaps
 */
export const isSwapActive = () => {
  return activeSwapTransferIds.size > 0;
};

/**
 * Gets pending swaps ids
 * @returns {Set} Set of pending swap transfers
 */
export const getActiveSwapTransferIds = () => {
  return new Set(activeSwapTransferIds);
};

/**
 * Adds a transfer id to the pending swaps set
 * @param {string} transferId - Outbound transfer id of an execution resposne
 * @returns {undefined}
 */
export const addActiveSwap = transferId => {
  activeSwapTransferIds.add(transferId);
  console.log(
    `[Swap Tracker] Added swap ${transferId}, total active: ${activeSwapTransferIds.size}`,
  );
};

/**
 * Removes a transfer id to the pending swaps set
 * @param {string} transferId - Outbound transfer id of an execution resposne
 * @returns {undefined}
 */
export const removeActiveSwap = transferId => {
  activeSwapTransferIds.delete(transferId);
  console.log(
    `[Swap Tracker] Removed swap ${transferId}, total active: ${activeSwapTransferIds.size}`,
  );
};

// ============================================
// AUTO SWAP COMPLETION TRACKING
// ============================================

// Storage key constant
const PENDING_SWAP_CONFIRMATIONS_KEY = 'pendingSwapConfirmations';

/**
 * Saves an active swap to be checked later if not completed
 * @param {string} sparkRequestID - Lightning spark request ID
 * @param {string} outboundTransferId - Outbound transfer id of an execution resposne
 * @returns {Promise<undefined>}
 */
export const savePendingSwapConfirmation = async (
  sparkRequestID,
  outboundTransferId,
) => {
  try {
    const existing = await getLocalStorageItem(PENDING_SWAP_CONFIRMATIONS_KEY);
    const pending = existing ? JSON.parse(existing) : {};

    pending[sparkRequestID] = {
      outboundTransferId,
      timestamp: Date.now(),
      lastChecked: null,
    };

    await setLocalStorageItem(
      PENDING_SWAP_CONFIRMATIONS_KEY,
      JSON.stringify(pending),
    );
    console.log('[Swap Retry] Saved pending confirmation:', sparkRequestID);
  } catch (err) {
    console.error('[Swap Retry] Failed to save pending confirmation:', err);
  }
};

/**
 * Removes an active swap to be checked later if not completed
 * @param {string} sparkRequestID - Lightning spark request ID
 * @returns {Promise<undefined>}
 */
export const removePendingSwapConfirmation = async sparkRequestID => {
  try {
    const existing = await getLocalStorageItem(PENDING_SWAP_CONFIRMATIONS_KEY);
    if (!existing) return;

    const pending = JSON.parse(existing);
    delete pending[sparkRequestID];

    await setLocalStorageItem(
      PENDING_SWAP_CONFIRMATIONS_KEY,
      JSON.stringify(pending),
    );
    console.log('[Swap Retry] Removed pending confirmation:', sparkRequestID);
  } catch (err) {
    console.error('[Swap Retry] Failed to remove pending confirmation:', err);
  }
};

/**
 * Gets all active swaps to be checked if not completed
 * @param {string} sparkRequestID - Lightning spark request ID
 * @returns {Promise<object>} - object of active swaps
 */
export const getPendingSwapConfirmations = async () => {
  try {
    const existing = await getLocalStorageItem(PENDING_SWAP_CONFIRMATIONS_KEY);
    return existing ? JSON.parse(existing) : {};
  } catch (err) {
    console.error('[Swap Retry] Failed to get pending confirmations:', err);
    return {};
  }
};

/**
 * Completes the auto swap process
 * @param {string} sparkRequestID - Lightning spark request ID
 * @param {string} outboundTransferId - Outbound swap transfer ID
 * @param {Object} lightningRequest - Lightning request response by spark
 * @param {Object} txDetails - Saved ln swap payment details
 * @param {Object} result - Execution result
 * @param {string} invoice - Lightning invoice
 * @returns {Promise<boolean>} - true or false based on sucess
 */
export const completeSwapConfirmation = async (
  sparkRequestID,
  outboundTransferId,
  lightningRequest,
  txDetails,
  result,
  invoice,
  mnemoinc,
  sparkInfoRef,
) => {
  try {
    console.log(
      '[Swap Confirmation] Starting confirmation for:',
      sparkRequestID,
    );

    const realFeeAmount = Math.round(
      dollarsToSats(
        result.swap.feeAmount / Math.pow(10, 6),
        result.swap.executionPrice,
      ),
    );

    const userSwaps = await getUserSwapHistory(mnemoinc, 5);

    const swap = userSwaps.swaps.find(
      savedSwap => savedSwap.outboundTransferId === outboundTransferId,
    );

    if (!swap) {
      console.error('[Swap Confirmation] Swap not found in history');
      return false;
    }

    const description = invoice
      ? decode(invoice).tags.find(tag => tag.tagName === 'description')?.data ||
        lightningRequest?.description ||
        ''
      : lightningRequest?.description || '';

    // Clear funding and ln payment from tx list
    setFlashnetTransfer(swap.inboundTransferId);
    setFlashnetTransfer(txDetails.id);

    const tx = {
      id: swap.outboundTransferId,
      paymentStatus: 'completed',
      paymentType: 'spark',
      accountId: sparkInfoRef.current.identityPubKey,
      details: {
        fee: realFeeAmount,
        totalFee: realFeeAmount,
        supportFee: 0,
        amount: parseFloat(result.swap.amountOut),
        description: description,
        address: sparkInfoRef.current.sparkAddress,
        time: Date.now(),
        createdAt: Date.now(),
        direction: 'INCOMING',
        isLRC20Payment: true,
        LRC20Token: USDB_TOKEN_ID,
        showSwapLabel: true,
        currentPriceAInB: swap.price,
        ln_funding_id: txDetails.id,
      },
    };

    await bulkUpdateSparkTransactions([tx], 'fullUpdate-tokens');
    removeActiveSwap(outboundTransferId);

    await removePendingSwapConfirmation(sparkRequestID);

    console.log('[Swap Confirmation] Completed successfully');
    return true;
  } catch (err) {
    console.error('[Swap Confirmation] Error:', err);
    return false;
  }
};

export const retryPendingSwapConfirmations = async (mnemoinc, sparkInfoRef) => {
  try {
    console.log('[Swap Retry] Checking for pending confirmations...');

    const pending = await getPendingSwapConfirmations();
    const sparkRequestIDs = Object.keys(pending);

    if (sparkRequestIDs.length === 0) {
      console.log('[Swap Retry] No pending confirmations found');
      return;
    }

    console.log(
      `[Swap Retry] Found ${sparkRequestIDs.length} pending confirmation(s)`,
    );

    for (const sparkRequestID of sparkRequestIDs) {
      const { outboundTransferId, timestamp, lastChecked } =
        pending[sparkRequestID];

      // Only remove if too old AND has been checked at least once
      const MAX_AGE = 24 * 60 * 60 * 1000;
      if (lastChecked && Date.now() - timestamp > MAX_AGE) {
        console.warn(
          `[Swap Retry] Confirmation too old (checked at least once), removing:`,
          sparkRequestID,
        );
        await removePendingSwapConfirmation(sparkRequestID);
        continue;
      }

      try {
        console.log(`[Swap Retry] Retrying confirmation for:`, sparkRequestID);

        // Update lastChecked timestamp
        const updatedPending = await getPendingSwapConfirmations();
        if (updatedPending[sparkRequestID]) {
          updatedPending[sparkRequestID].lastChecked = Date.now();
          await setLocalStorageItem(
            PENDING_SWAP_CONFIRMATIONS_KEY,
            JSON.stringify(updatedPending),
          );
        }

        let swap = null;
        let offset = 0;
        const batchSize = 10;
        const maxSwaps = 50;

        while (!swap && offset < maxSwaps) {
          const userSwaps = await getUserSwapHistory(
            mnemoinc,
            batchSize,
            offset,
          );

          if (!userSwaps?.swaps || userSwaps.swaps.length === 0) {
            break;
          }

          swap = userSwaps.swaps.find(
            s => s.outboundTransferId === outboundTransferId,
          );

          if (!swap) {
            offset += batchSize;
          }
        }

        if (!swap) {
          console.error(
            '[Swap Retry] Swap not found in history after checking up to 50 swaps',
          );
          continue;
        }

        // Get the lightning request
        const lightningRequest = await getSparkLightningPaymentStatus({
          lightningInvoiceId: sparkRequestID,
          mnemonic: mnemoinc,
        });

        if (!lightningRequest?.transfer?.sparkId) {
          console.error('[Swap Retry] Lightning request not found');
          await removePendingSwapConfirmation(sparkRequestID);
          continue;
        }

        // Get transaction details
        const txDetails = await getSingleTxDetails(
          mnemoinc,
          lightningRequest?.transfer?.sparkId,
        );

        if (!txDetails) {
          console.error('[Swap Retry] Transaction details not found');
          continue;
        }

        // Check if already completed
        if (isFlashnetTransfer(lightningRequest.transfer.sparkId)) {
          console.log('[Swap Retry] Already completed, removing');
          await removePendingSwapConfirmation(sparkRequestID);
          continue;
        }

        const result = {
          swap: {
            ...swap,
            feeAmount: swap.feePaid || 0,
            executionPrice: swap.price,
            amountOut: swap.amountOut,
          },
        };

        const invoice = lightningRequest?.invoice?.encodedInvoice || '';

        lightningRequest.description = lightningRequest?.invoice?.memo || '';

        const success = await completeSwapConfirmation(
          sparkRequestID,
          outboundTransferId,
          lightningRequest,
          txDetails,
          result,
          invoice,
          mnemoinc,
          sparkInfoRef,
        );

        if (success) {
          console.log(
            '[Swap Retry] Successfully completed pending confirmation',
          );
        } else {
          console.warn('[Swap Retry] Failed to complete confirmation');
        }

        // Add delay between retries
        await new Promise(res => setTimeout(res, 1000));
      } catch (err) {
        console.error(`[Swap Retry] Error processing ${sparkRequestID}:`, err);
      }
    }

    console.log('[Swap Retry] Finished processing pending confirmations');
  } catch (err) {
    console.error('[Swap Retry] Error in retry process:', err);
  }
};

// ============================================
// UTILITY FUNCTIONS
// ============================================

/**
 * Calculate expected output for a given input amount
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Calculation parameters
 * @returns {Promise<object>} Expected output
 */
export const calculateSwapOutput = async (
  mnemonic,
  { poolId, assetInAddress, assetOutAddress, amountIn },
) => {
  return await simulateSwap(mnemonic, {
    poolId,
    assetInAddress,
    assetOutAddress,
    amountIn,
  });
};

/**
 * Check if a swap is viable (sufficient liquidity, reasonable price impact)
 * @param {string} mnemonic - Wallet mnemonic
 * @param {object} params - Swap parameters
 * @param {number} maxPriceImpactPct - Maximum acceptable price impact %
 * @returns {Promise<object>} Viability check result
 */
export const checkSwapViability = async (
  mnemonic,
  params,
  maxPriceImpactPct = 5,
) => {
  try {
    const simulation = await simulateSwap(mnemonic, params);

    if (!simulation.didWork) {
      return {
        viable: false,
        reason: 'Simulation failed',
        error: simulation.error,
      };
    }

    const priceImpact = parseFloat(simulation.simulation.priceImpact);

    if (priceImpact > maxPriceImpactPct) {
      return {
        viable: false,
        reason: `Price impact too high: ${priceImpact.toFixed(
          2,
        )}% (max: ${maxPriceImpactPct}%)`,
        priceImpact,
        simulation: simulation.simulation,
      };
    }

    return {
      viable: true,
      priceImpact,
      simulation: simulation.simulation,
    };
  } catch (error) {
    return {
      viable: false,
      reason: error.message,
      error: formatError(error, 'checkSwapViability'),
    };
  }
};

/**
 * Get current price for a token pair
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} poolId - Pool ID
 * @returns {Promise<object>} Current price
 */
export const getCurrentPrice = async (mnemonic, poolId) => {
  try {
    const poolResult = await getPoolDetails(mnemonic, poolId);

    if (!poolResult.didWork) {
      throw new Error('Failed to get pool details');
    }

    return {
      didWork: true,
      price: poolResult.marketData.currentPrice,
      priceChange24h: poolResult.marketData.priceChange24h,
      volume24h: poolResult.marketData.volume24h,
      tvl: poolResult.marketData.tvl,
    };
  } catch (error) {
    console.warn(
      'Get current price error:',
      formatError(error, 'getCurrentPrice'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'getCurrentPrice'),
    };
  }
};

/**
 * Convert exchangeRate to fiat price
 * @param {string|number|bigint} currentPriceAinB - Price of Bitcoin in dollars
 * @returns {number} Amount in dollars
 */
export function currentPriceAinBToPriceDollars(currentPriceAInB) {
  try {
    const numPrice =
      typeof currentPriceAInB === 'bigint'
        ? Number(currentPriceAInB)
        : Number(currentPriceAInB || 0);

    if (isNaN(numPrice)) {
      return 0;
    }

    return (numPrice * 100000000) / 1000000;
  } catch (error) {
    console.error('Error in currentPriceAinBToPriceDollars:', error, {
      currentPriceAInB,
    });
    return 0;
  }
}

// ============================================
// ERROR HANDLING UTILITIES
// ============================================

/**
 * Check if error is a Flashnet error and handle accordingly
 * @param {Error} error - The error to check
 * @returns {object} Error information
 */
export const handleFlashnetError = error => {
  if (!isFlashnetErrorCode(error.errorCode)) {
    return {
      isFlashnetError: false,
      message: error.message,
    };
  }
  const flashnetError = new FlashnetError(error.error, {
    response: {
      ...error,
    },
  });

  const errorInfo = {
    isFlashnetError: true,
    errorCode: flashnetError.errorCode,
    category: flashnetError.category,
    message: flashnetError.userMessage,
    userMessage: i18next.t(`flashnetUserMessages.${flashnetError.errorCode}`, {
      defaultValue: flashnetError.userMessage,
    }),
    actionHint: flashnetError.actionHint,
    isRetryable: flashnetError.isRetryable,
    recovery: flashnetError.recovery,
  };

  // Check for specific error types
  if (flashnetError.isSlippageError()) {
    errorInfo.type = 'slippage';
    errorInfo.userMessage = 'screens.inAccount.swapsPage.slippageError';
  } else if (flashnetError.isInsufficientLiquidityError()) {
    errorInfo.type = 'insufficient_liquidity';
    errorInfo.userMessage = 'screens.inAccount.swapsPage.noLiquidity';
  } else if (flashnetError.isAuthError()) {
    errorInfo.type = 'authentication';
    errorInfo.userMessage = 'screens.inAccount.swapsPage.authenticationError';
  } else if (flashnetError.isPoolNotFoundError()) {
    errorInfo.type = 'pool_not_found';
    errorInfo.userMessage = 'screens.inAccount.swapsPage.noPoolError';
  }
  errorInfo.userMessage = i18next.t(errorInfo.userMessage);

  // Check fund recovery status
  // if (flashnetError.wasClawbackAttempted?.()) {
  //   errorInfo.clawback = {
  //     attempted: true,
  //     allRecovered: flashnetError.wereAllTransfersRecovered?.() || false,
  //     partialRecovered:
  //       flashnetError.werePartialTransfersRecovered?.() || false,
  //     recoveredCount: flashnetError.getRecoveredTransferCount?.() || 0,
  //   };

  //   if (errorInfo.clawback.allRecovered) {
  //     errorInfo.userMessage =
  //       i18next.t(errorInfo.userMessage) +
  //       ' ' +
  //       i18next.t('screens.inAccount.swapsPage.automaticRecovery');
  //   } else if (errorInfo.clawback.partialRecovered) {
  //     errorInfo.userMessage =
  //       i18next.t(errorInfo.userMessage) +
  //       ' ' +
  //       i18next.t('screens.inAccount.swapsPage.semiRecovered');
  //   }
  // } else if (flashnetError.willAutoRefund?.()) {
  //   errorInfo.autoRefund = true;
  //   errorInfo.userMessage =
  //     i18next.t(errorInfo.userMessage) +
  //     ' ' +
  //     i18next.t('screens.inAccount.swapsPage.willRecover');
  // }

  return errorInfo;
};

/**
 * Get refund txid from error message
 * @param {string} error - The error to check
 * @returns {txid|undefined} transaction id
 */

const getRefundTxidFromErrormessage = message => {
  try {
    const match = message.match(FLASHNET_REFUND_REGEX);

    if (match) {
      const transferID = match[1]; // first capture group
      return transferID;
    } else {
      console.log('No transfer ID found');
    }
  } catch (err) {
    console.log('error getting txid from error message', err);
  }
};

/**
 * Format and return errormessage from webview
 * @param {Object} error - The error to check
 * @returns {Object} error response
 */

const formatAndReturnWebviewError = receivedError => {
  try {
    if (receivedError.formatted) {
      console.log(receivedError.formatted.errorCode);
      const error = handleFlashnetError(receivedError.formatted);

      return {
        didWork: false,
        error: receivedError.error,
        details: error,
      };
    }
  } catch (err) {
    console.log('error getting txid from error message', err);
  }
};

// ============================================
// MANUAL CLAWBACK & RECOVERY
// ============================================

/**
 * Manually request clawback for a failed swap
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} sparkTransferId - Transfer ID to recover
 * @param {string} poolId - Pool ID where funds are stuck
 * @returns {Promise<object>} Clawback result
 */
export const requestManualClawback = async (
  mnemonic,
  sparkTransferId,
  poolId,
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.requestClawback,
        { mnemonic, sparkTransferId, poolId },
      );
      return validateWebViewResponse(response, 'Not able to request clawback');
    } else {
      const client = getFlashnetClient(mnemonic);

      console.log({
        sparkTransferId,
        poolId,
      });
      const result = await client.clawback({
        sparkTransferId,
        lpIdentityPublicKey: poolId,
      });

      if (!result || result.error) {
        return {
          didWork: false,
          error: result?.error || 'Clawback request failed',
        };
      }

      if (result.accepted) {
        return {
          didWork: true,
          accepted: true,
          message: 'Clawback request accepted',
          internalRequestId: result.internalRequestId,
        };
      } else {
        return {
          didWork: false,
          accepted: false,
          error: result.error || 'Clawback request rejected by pool',
        };
      }
    }
  } catch (error) {
    console.warn(
      'Manual clawback error:',
      formatError(error, 'requestManualClawback'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'requestManualClawback'),
    };
  }
};

/**
 * Manually request clawback Eligibility
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} sparkTransferId - Transfer ID to recover
 * @returns {Promise<object>} Clawback result
 */
export const checkClawbackEligibility = async (mnemonic, sparkTransferId) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.checkClawbackEligibility,
        { mnemonic, sparkTransferId },
      );
      return validateWebViewResponse(
        response,
        'Not able to checkClawbackEligibility',
      );
    } else {
      const client = getFlashnetClient(mnemonic);

      const eligibility = await client.checkClawbackEligibility({
        sparkTransferId,
      });

      if (eligibility.accepted) {
        console.log('Transfer is eligible for clawback');
        return {
          didWork: true,
          error: null,
          response: true,
        };
      } else {
        console.log('Cannot clawback:', eligibility.error);
        return {
          didWork: true,
          error: eligibility.error,
          response: false,
        };
      }
    }
  } catch (error) {
    console.warn(
      'Manual clawback error:',
      formatError(error, 'requestManualClawback'),
    );
    return {
      didWork: false,
      error: error.message,
      response: false,
    };
  }
};

/**
 * Check status of a manual clawback request
 * @param {string} mnemonic - Wallet mnemonic
 * @param {string} internalRequestId - Request ID from clawback request
 * @returns {Promise<object>} Clawback status
 */
export const checkClawbackStatus = async (mnemonic, internalRequestId) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.checkClawbackStatus,
        { mnemonic, internalRequestId },
      );
      return validateWebViewResponse(
        response,
        'Not able to check clawback status',
      );
    } else {
      const client = getFlashnetClient(mnemonic);
      const status = await client.checkClawbackStatus({ internalRequestId });

      return {
        didWork: true,
        status: status.status,
        transferId: status.transferId,
        isComplete: status.status === 'completed',
        isFailed: status.status === 'failed',
      };
    }
  } catch (error) {
    console.warn(
      'Check clawback status error:',
      formatError(error, 'checkClawbackStatus'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'checkClawbackStatus'),
    };
  }
};

/**
 * Batch request clawback for multiple transfers
 * @param {string} mnemonic - Wallet mnemonic
 * @param {Array<{transferId: string, poolId: string}>} transfers - Transfers to recover
 * @returns {Promise<object>} Batch clawback results
 */
export const requestBatchClawback = async (mnemonic, transferIds, poolId) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    console.log(runtime, mnemonic, transferIds, poolId);
    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.requestBatchClawback,
        { mnemonic, transferIds, poolId },
      );
      return validateWebViewResponse(
        response,
        'Not able to check clawback status',
      );
    } else {
      const client = getFlashnetClient(mnemonic);
      const result = await client.clawbackMultiple(transferIds, poolId);

      return {
        didWork: true,
        result,
      };
    }
  } catch (error) {
    console.warn(
      'Batch clawback error:',
      formatError(error, 'requestBatchClawback'),
    );
    return {
      didWork: false,
      error: error.message,
    };
  }
};

/**
 * Batch request clawback for multiple transfers
 * @param {string} mnemonic - Wallet mnemonic
 * @param {Array<{transferId: string, poolId: string}>} transfers - Transfers to recover
 * @returns {Promise<object>} Batch clawback results
 */
export const listClawbackableTransfers = async (
  mnemonic,
  limit = 100,
  offset,
) => {
  try {
    const runtime = await selectSparkRuntime(mnemonic);

    if (runtime === 'webview') {
      const response = await sendWebViewRequestGlobal(
        OPERATION_TYPES.listClawbackableTransfers,
        { mnemonic, limit },
      );
      return validateWebViewResponse(
        response,
        'Not able to listClawbackableTransfers',
      );
    } else {
      const client = getFlashnetClient(mnemonic);
      const resposne = await client.listClawbackableTransfers({ limit });

      return {
        didWork: true,
        resposne: resposne,
      };
    }
  } catch (error) {
    console.warn(
      'Check clawback status error:',
      formatError(error, 'checkClawbackStatus'),
    );
    return {
      didWork: false,
      error: error.message,
      details: formatError(error, 'checkClawbackStatus'),
    };
  }
};

// Export all functions
export default {
  // Pool functions
  findBestPool,
  getPoolDetails,
  listAllPools,

  // Swap functions
  simulateSwap,
  executeSwap,
  swapBitcoinToToken,
  swapTokenToBitcoin,

  // Lightning functions
  getLightningPaymentQuote,
  payLightningWithToken,

  // History functions
  getUserSwapHistory,

  // Utility functions
  // calculateSwapOutput,
  // checkSwapViability,
  // getCurrentPrice,
  handleFlashnetError,
  satsToDollars,
  dollarsToSats,

  // Manual recovery functions
  requestManualClawback,
  checkClawbackStatus,
  requestBatchClawback,
};
