/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/indent */
import type {
  ChainType,
  PaymentDetails,
  PriceCurrency,
  PriceRecurring,
  TCoupon,
  TInvoiceExpanded,
  TLineItemExpanded,
  TPaymentCurrency,
  TPaymentCurrencyExpanded,
  TPaymentMethod,
  TPaymentMethodExpanded,
  TPrice,
  TProductExpanded,
  TSubscriptionExpanded,
  TSubscriptionItemExpanded,
} from '@blocklet/payment-types';
import { BN, fromUnitToToken, fromTokenToUnit } from '@ocap/util';
import omit from 'lodash/omit';
import trimEnd from 'lodash/trimEnd';
import numbro from 'numbro';
// eslint-disable-next-line import/no-extraneous-dependencies
import stringWidth from 'string-width';
import { defaultCountries } from 'react-international-phone';
import { joinURL, withQuery } from 'ufo';

import { t } from '../locales';
import dayjs from './dayjs';
import type { ActionProps, PricingRenderProps } from '../types';

export const PAYMENT_KIT_DID = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';

/**
 * Format coupon discount terms for display
 */
export const formatCouponTerms = (coupon: TCoupon, currency: TPaymentCurrency, locale: string = 'en'): string => {
  let couponOff = '';

  if (coupon.percent_off && coupon.percent_off > 0) {
    couponOff = t('payment.checkout.coupon.percentage', locale, { percent: coupon.percent_off });
  }

  if (coupon.amount_off && coupon.amount_off !== '0') {
    const { symbol } = currency;
    couponOff =
      coupon.currency_id === currency.id
        ? coupon.amount_off || ''
        : coupon.currency_options?.[currency.id]?.amount_off || '';
    if (couponOff) {
      couponOff = t('payment.checkout.coupon.fixedAmount', locale, {
        amount: formatAmount(couponOff, currency.decimal),
        symbol,
      });
    }
  }

  if (!couponOff) {
    return t('payment.checkout.coupon.noDiscount');
  }

  return t(`payment.checkout.coupon.terms.${coupon.duration}`, locale, {
    couponOff,
    months: coupon.duration_in_months || 0,
  });
};

export const isPaymentKitMounted = () => {
  return (window.blocklet?.componentMountPoints || []).some((x: any) => x.did === PAYMENT_KIT_DID);
};

export const getPrefix = (): string => {
  const prefix = window.blocklet?.prefix || '/';
  const baseUrl = window.location?.origin; // required when use payment feature cross origin
  if (window.__PAYMENT_KIT_BASE_URL) {
    try {
      const tmp = new URL(window.__PAYMENT_KIT_BASE_URL);
      if (tmp.origin !== window.location.origin) {
        return window.__PAYMENT_KIT_BASE_URL;
      }
    } catch (err) {
      console.warn('Invalid baseUrl for PaymentProvider', window.__PAYMENT_KIT_BASE_URL);
    }
  }
  const componentId = (window.blocklet?.componentId || '').split('/').pop();
  if (componentId === PAYMENT_KIT_DID) {
    return joinURL(baseUrl, prefix);
  }
  const component = (window.blocklet?.componentMountPoints || []).find((x: any) => x?.did === PAYMENT_KIT_DID);
  if (component) {
    return joinURL(baseUrl, component.mountPoint);
  }

  return joinURL(baseUrl, prefix);
};

export function isCrossOrigin() {
  try {
    const prefix = getPrefix();
    const prefixOrigin = new URL(prefix).origin;
    const currentOrigin = window.location.origin;
    return prefixOrigin !== currentOrigin;
  } catch (error) {
    return false;
  }
}

export function formatToDate(date: Date | string | number, locale = 'en', format = 'YYYY-MM-DD HH:mm:ss') {
  if (!date) {
    return '-';
  }

  return dayjs(date).locale(formatLocale(locale)).format(format);
}

export function formatToDatetime(date: Date | string | number, locale = 'en') {
  if (!date) {
    return '-';
  }

  return dayjs(date).locale(formatLocale(locale)).format('lll');
}

export function formatTime(date: Date | string | number, format = 'YYYY-MM-DD HH:mm:ss', locale = 'en') {
  if (!date) {
    return '-';
  }

  return dayjs(date).locale(formatLocale(locale)).format(format);
}

export function formatDateTime(date: Date | string | number, locale = 'en') {
  return dayjs(date).locale(formatLocale(locale)).format('YYYY-MM-DD HH:mm');
}

export const formatLocale = (locale = 'en') => {
  if (locale === 'tw') {
    return 'zh';
  }

  return locale;
};

export const formatPrettyMsLocale = (locale: string) => (locale === 'zh' ? 'zh_CN' : 'en_US');

export const formatError = (err: any) => {
  if (!err) {
    return 'Unknown error';
  }

  const { details, errors, response } = err;

  // graphql error
  if (Array.isArray(errors)) {
    return errors.map((x) => x.message).join('\n');
  }

  // joi validate error
  if (Array.isArray(details)) {
    const formatted = details.map((e) => {
      const errorMessage = e.message.replace(/["]/g, "'");
      const errorPath = e.path.join('.');
      return `${errorPath}: ${errorMessage}`;
    });

    return `Validate failed: ${formatted.join(';')}`;
  }

  // axios error
  if (response) {
    return response.data?.error || `${err.message}: ${JSON.stringify(response.data)}`;
  }

  return err.message;
};

export function formatBNStr(
  str: string = '',
  decimals: number = 18,
  precision: number = 6,
  trim: boolean = true,
  thousandSeparated: boolean = true
) {
  if (!str) {
    return '0';
  }
  return formatNumber(fromUnitToToken(str, decimals), precision, trim, thousandSeparated);
}

const CURRENCY_SYMBOLS: Record<string, string> = {
  USD: '$',
  EUR: '€',
  GBP: '£',
  JPY: '¥',
  CNY: '¥',
  KRW: '₩',
  INR: '₹',
  AUD: 'A$',
  CAD: 'C$',
  CHF: 'Fr',
  HKD: 'HK$',
  SGD: 'S$',
  NZD: 'NZ$',
};

/**
 * Format credit amount for display (data contexts: balances, transactions, notifications)
 * - If currency has symbol mapping (USD -> $): shows "$20"
 * - If no symbol mapping: shows "20 minutes" or "20 arcsphere credits"
 *
 * @param formattedAmount - Already formatted amount string (e.g., "20", "1,000.50")
 * @param currencySymbol - Currency symbol (USD, EUR, minutes, etc.)
 * @param showUnit - Whether to show the unit (default: true)
 */
export function formatCreditAmount(formattedAmount: string, currencySymbol: string, showUnit: boolean = true): string {
  const mappedSymbol = CURRENCY_SYMBOLS[currencySymbol];
  if (mappedSymbol) {
    return `${mappedSymbol}${formattedAmount}`;
  }
  return `${formattedAmount} ${showUnit ? currencySymbol : ''}`;
}

/**
 * Format credit amount for checkout/purchase scenarios
 * - If currency has symbol mapping (USD -> $): shows "$20 of credits" (en) or "$20 的信用额度" (zh)
 * - If no symbol mapping: shows "20 minutes" or "20 arcsphere credits"
 *
 * @param formattedAmount - Already formatted amount string (e.g., "20", "1,000.50")
 * @param currencySymbol - Currency symbol (USD, EUR, minutes, etc.)
 * @param locale - Locale for translation (default: 'en')
 * @param showUnit - Whether to show the unit (default: true)
 */
export function formatCreditForCheckout(
  formattedAmount: string,
  currencySymbol: string,
  locale: string = 'en',
  showUnit: boolean = true
): string {
  const mappedSymbol = CURRENCY_SYMBOLS[currencySymbol];
  if (mappedSymbol) {
    return `${mappedSymbol}${formattedAmount} ${showUnit ? t('common.credits', locale) : ''}`;
  }
  return `${formattedAmount} ${showUnit ? currencySymbol : ''}`;
}
export function formatNumber(
  n: number | string,
  precision: number = 6,
  trim: boolean = true,
  thousandSeparated: boolean = true
) {
  if (!n || n === '0') {
    return '0';
  }
  const num = numbro(n);
  const options = {
    thousandSeparated,
    ...((precision || precision === 0) && { mantissa: precision }),
  };
  const result = num.format(options);
  if (!trim) {
    return result;
  }
  const [left, right] = result.split('.');
  return right ? [left, trimEnd(right, '0')].filter(Boolean).join('.') : left;
}

export function formatDynamicPrice(
  n: number | string,
  isDynamic: boolean,
  precision: number = 6,
  trim: boolean = true,
  thousandSeparated: boolean = true
) {
  if (!isDynamic) {
    return formatNumber(n, precision, trim, thousandSeparated);
  }
  const num = Number(n);
  if (!Number.isFinite(num)) {
    return formatNumber(n, precision, trim, thousandSeparated);
  }
  const abs = Math.abs(num);
  const targetPrecision = abs > 0 && abs < 0.01 ? precision : 2;
  return formatNumber(n, targetPrecision, trim, thousandSeparated);
}

const USD_DECIMALS = 8;

export function getUsdAmountFromBaseAmount(
  amount: string | number | undefined,
  quantity: number,
  scale = BASE_AMOUNT_SCALE
): string | null {
  if (amount === undefined || amount === null || !quantity || quantity <= 0) {
    return null;
  }
  const scaled = toScaledBaseAmount(amount, quantity, scale);
  return formatScaledAmount(scaled, scale);
}

export function getUsdAmountFromTokenUnits(
  tokenAmount: BN | string,
  tokenDecimals: number,
  exchangeRate?: string | null
): string | null {
  if (!exchangeRate && exchangeRate !== '0') {
    return null;
  }
  if (tokenDecimals === undefined || tokenDecimals === null) {
    return null;
  }

  try {
    // exchangeRate is in token format (e.g., "0.25535177995959574"), may have more than 8 decimal places
    // tokenAmount is in unit format (smallest unit)
    // We need to calculate: (tokenAmount / 10^tokenDecimals) * exchangeRate
    // To avoid precision loss, we use: (tokenAmount * exchangeRate) / 10^tokenDecimals
    // But exchangeRate is a decimal string, so we need to handle it carefully

    const amountBN = tokenAmount instanceof BN ? tokenAmount : new BN(tokenAmount || '0');
    const tokenScale = new BN(10).pow(new BN(tokenDecimals));
    if (tokenScale.isZero()) {
      return null;
    }

    // Convert exchangeRate (token format) to BN with proper precision
    // exchangeRate may have more than 8 decimal places, so we need to preserve all precision
    // We'll use a higher precision scale for the rate calculation
    const rateBN = new BN(exchangeRate.replace('.', ''));
    const rateDecimalPlaces = exchangeRate.includes('.') ? exchangeRate.split('.')[1]?.length || 0 : 0;
    const rateScale = new BN(10).pow(new BN(rateDecimalPlaces));

    // Calculate: (amountBN * rateBN * USD_PRECISION_SCALE) / (tokenScale * rateScale)
    // The USD_PRECISION_SCALE keeps enough trailing digits so the final conversion via fromUnitToToken
    // produces a value with 8 decimal places.
    const usdPrecisionScale = new BN(10).pow(new BN(USD_DECIMALS));
    const usdUnit = amountBN.mul(rateBN).mul(usdPrecisionScale).div(tokenScale.mul(rateScale));

    // Convert from unit format to token format with 8 decimals
    return fromUnitToToken(usdUnit.toString(), USD_DECIMALS);
  } catch {
    return null;
  }
}

export function formatUsdAmount(amount: string | null, locale: string = 'en'): string | null {
  if (!amount && amount !== '0') {
    return null;
  }
  const num = Number(amount);
  if (!Number.isFinite(num)) {
    return null;
  }
  return new Intl.NumberFormat(locale, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(num);
}

export function formatExchangeRate(amount: string | null): string | null {
  if (!amount && amount !== '0') {
    return null;
  }
  const value = String(amount);
  const num = Number(value);
  if (!Number.isFinite(num)) {
    return null;
  }
  return value;
}

/**
 * Format exchange rate with currency symbol for display
 * @param rate - The exchange rate value
 * @param currency - The currency code (default: 'USD')
 * @param decimals - Number of decimal places (default: 2, use 4 for live exchange rates)
 * @returns Formatted string like "$0.12" for USD, or "0.12 EUR" for other currencies
 */
export function formatExchangeRateDisplay(
  rate: string | number | null | undefined,
  currency: string = 'USD',
  decimals: number = 2
): string | null {
  if (rate === null || rate === undefined) {
    return null;
  }
  const num = Number(rate);
  if (!Number.isFinite(num)) {
    return null;
  }
  const formattedValue = num.toFixed(decimals);
  if (currency === 'USD') {
    return `$${formattedValue}`;
  }
  return `${formattedValue} ${currency}`;
}

export const formatPrice = (
  price: TPrice,
  currency: TPaymentCurrency,
  unit_label?: string,
  quantity: number = 1,
  bn: boolean = true,
  locale: string = 'en'
) => {
  if (!currency) {
    return '';
  }
  if (price.custom_unit_amount) {
    return `Custom (${currency.symbol})`;
  }

  // For dynamic pricing, display USD reference price
  if (
    (price as any).pricing_type === 'dynamic' &&
    (price as any).base_amount &&
    (price as any).base_currency === 'USD'
  ) {
    const baseAmount = (price as any).base_amount;
    const usdAmount = Number(baseAmount) * quantity;
    const formattedUsd = formatUsdAmount(usdAmount.toString(), locale);

    if (price?.type === 'recurring' && price.recurring) {
      const recurring = formatRecurring(price.recurring, false, 'slash', locale);

      if (unit_label) {
        return `$${formattedUsd} / ${unit_label} ${recurring}`;
      }
      if (price.recurring.usage_type === 'metered') {
        return `$${formattedUsd} / unit ${recurring}`;
      }

      return `$${formattedUsd} ${recurring}`;
    }

    return `$${formattedUsd}`;
  }

  // For fixed pricing, display token amount
  const unit = getPriceUintAmountByCurrency(price, currency);
  const amount = bn
    ? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString()
    : +unit * quantity;
  if (price?.type === 'recurring' && price.recurring) {
    const recurring = formatRecurring(price.recurring, false, 'slash', locale);

    if (unit_label) {
      return `${amount} ${currency.symbol} / ${unit_label} ${recurring}`;
    }
    if (price.recurring.usage_type === 'metered') {
      return `${amount} ${currency.symbol} / unit ${recurring}`;
    }

    return `${amount} ${currency.symbol} ${recurring}`;
  }

  return `${amount} ${currency.symbol}`;
};

export const formatPriceAmount = (
  price: TPrice,
  currency: TPaymentCurrency,
  unit_label?: string,
  quantity: number = 1,
  bn: boolean = true,
  locale: string = 'en'
) => {
  if (!currency) {
    return '';
  }

  // For dynamic pricing, display USD reference price
  if (
    (price as any).pricing_type === 'dynamic' &&
    (price as any).base_amount &&
    (price as any).base_currency === 'USD'
  ) {
    const baseAmount = (price as any).base_amount;
    const usdAmount = Number(baseAmount) * quantity;
    const formattedUsd = formatUsdAmount(usdAmount.toString(), locale);

    if (price?.type === 'recurring' && price.recurring) {
      if (unit_label) {
        return `$${formattedUsd} / ${unit_label}`;
      }
      if (price.recurring.usage_type === 'metered') {
        return `$${formattedUsd} / unit`;
      }

      return `$${formattedUsd}`;
    }

    return `$${formattedUsd}`;
  }

  // For fixed pricing, display token amount
  const unit = getPriceUintAmountByCurrency(price, currency);
  const amount = bn
    ? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString()
    : +unit * quantity;
  if (price?.type === 'recurring' && price.recurring) {
    if (unit_label) {
      return `${amount} ${currency.symbol} / ${unit_label}`;
    }
    if (price.recurring.usage_type === 'metered') {
      return `${amount} ${currency.symbol} / unit`;
    }

    return `${amount} ${currency.symbol}`;
  }

  return `${amount} ${currency.symbol}`;
};

export function getStatementDescriptor(items: any[]) {
  for (const item of items) {
    if (item.price?.product?.statement_descriptor) {
      return item.price?.product?.statement_descriptor;
    }
  }

  return window.blocklet?.appName;
}

export function formatRecurring(
  recurring: PriceRecurring,
  translate: boolean = true,
  separator: string = 'per',
  locale: string = 'en'
) {
  const intervals = {
    hour: 'hourly',
    day: 'daily',
    week: 'weekly',
    month: 'monthly',
    year: 'yearly',
  };

  if (!recurring) {
    return '';
  }

  if (+recurring.interval_count === 1) {
    const interval = t(`common.${recurring.interval}`, locale);
    // @ts-ignore
    return translate ? t(`common.${intervals[recurring.interval]}`, locale) : separator ? t(`common.${separator}`, locale, { interval }) : interval; // prettier-ignore
  }

  if (recurring.interval === 'month') {
    if (recurring.interval_count === 3) {
      return t('common.month3', locale);
    }
    if (recurring.interval_count === 6) {
      return t('common.month6', locale);
    }
  }

  return t('common.recurring', locale, {
    count: recurring.interval_count,
    interval: t(`common.${recurring.interval}s`, locale),
  });
}

export function getPriceUintAmountByCurrency(price: TPrice, currency: TPaymentCurrency) {
  const options = getPriceCurrencyOptions(price);
  const option = options.find((x) => x.currency_id === currency?.id);
  if (option) {
    if (option.custom_unit_amount) {
      return option.custom_unit_amount.preset || option.custom_unit_amount.presets[0];
    }
    return option.unit_amount;
  }

  if (price.currency_id === currency?.id) {
    if (price.custom_unit_amount) {
      return price.custom_unit_amount.preset || price.custom_unit_amount.presets[0];
    }
    return price.unit_amount;
  }

  console.warn(`Currency ${currency?.id} not configured for price`, price);
  return '0';
}

export function getPriceCurrencyOptions(price: TPrice): PriceCurrency[] {
  if (Array.isArray(price.currency_options)) {
    return price.currency_options;
  }

  return [
    {
      currency_id: price.currency_id,
      unit_amount: price.unit_amount,
      custom_unit_amount: price.custom_unit_amount || null,
      tiers: null,
    },
  ];
}

const BASE_AMOUNT_SCALE = 8;

function formatScaledAmount(value: BN, scale = BASE_AMOUNT_SCALE) {
  const isNegative = value.isNeg();
  const absValue = value.abs();
  const str = absValue.toString().padStart(scale + 1, '0');
  const integerPart = str.slice(0, str.length - scale) || '0';
  const fraction = str.slice(-scale).replace(/0+$/, '');
  const formatted = fraction ? `${integerPart}.${fraction}` : integerPart;
  return isNegative ? `-${formatted}` : formatted;
}

function toScaledBaseAmount(amount: string | number | undefined, quantity: number, scale = BASE_AMOUNT_SCALE) {
  if (!amount || !quantity) {
    return new BN(0);
  }
  const normalized = String(amount);
  const [integer = '0', fraction = ''] = normalized.split('.');
  const frac = `${fraction}`.padEnd(scale, '0').slice(0, scale);
  const digits = `${integer.replace('-', '')}${frac}` || '0';
  const scaled = new BN(digits).mul(new BN(quantity));
  return normalized.startsWith('-') ? scaled.neg() : scaled;
}

export function getLineItemAmounts(
  item: TLineItemExpanded,
  currency: TPaymentCurrency,
  { useUpsell = true, exchangeRate = null }: { useUpsell?: boolean; exchangeRate?: string | null } = {}
): { unitAmount: BN; totalAmount: BN; isDynamicQuote: boolean } {
  if (!currency) {
    return { unitAmount: new BN(0), totalAmount: new BN(0), isDynamicQuote: false };
  }

  const price = (useUpsell ? item.upsell_price || item.price : item.price) as any;
  const quantity = new BN(item.quantity || 0);
  const quoteAmount = (item as any)?.quoted_amount;
  const quoteCurrencyId = (item as any)?.quote_currency_id;
  const isDynamicPrice = price?.pricing_type === 'dynamic';

  // For dynamic pricing, only use quoted_amount if the quote was created for the current currency
  // This prevents using a quote with wrong decimal precision when user switches currencies
  // e.g., ABT quote (decimal=18) should not be used with USD (decimal=2)
  const isQuoteValidForCurrency = isDynamicPrice && quoteAmount && quoteCurrencyId === currency.id;

  if (isQuoteValidForCurrency) {
    const totalAmount = new BN(quoteAmount || '0');
    const unitAmount = quantity.gt(new BN(0)) ? totalAmount.add(quantity).sub(new BN(1)).div(quantity) : new BN(0);

    return { unitAmount, totalAmount, isDynamicQuote: true };
  }

  // For dynamic pricing without valid quote, calculate from base_amount and exchange rate
  if (isDynamicPrice && exchangeRate && price?.base_amount) {
    const rate = Number(exchangeRate);
    if (rate > 0 && Number.isFinite(rate)) {
      // base_amount is stored in dollar format (e.g., "1.00" = $1.00)
      const baseAmountUsd = Number(price.base_amount);
      if (baseAmountUsd > 0 && Number.isFinite(baseAmountUsd)) {
        // Calculate token amount: USD / rate
        const tokenAmount = baseAmountUsd / rate;
        const unitAmount = fromTokenToUnit(tokenAmount.toFixed(currency.decimal || 8), currency.decimal || 8);
        return {
          unitAmount,
          totalAmount: unitAmount.mul(quantity),
          isDynamicQuote: false,
        };
      }
    }
  }

  // For all other cases (non-dynamic, or quote not valid for current currency), use currency_options
  const unitAmount = new BN(getPriceUintAmountByCurrency(price, currency));
  return {
    unitAmount,
    totalAmount: unitAmount.mul(quantity),
    isDynamicQuote: false,
  };
}

export type QuoteLockInfo = {
  baseAmount: string;
  baseCurrency: string;
  tokenAmount: string;
  tokenSymbol: string;
  expiresAt: number | null;
};

export function getQuoteLockInfo(items: TLineItemExpanded[], currency?: TPaymentCurrency | null): QuoteLockInfo | null {
  if (!items?.length || !currency) {
    return null;
  }

  const dynamicItems = items.filter((item) => {
    const price = (item.upsell_price || item.price) as any;
    return price?.pricing_type === 'dynamic' && (item as any)?.quoted_amount;
  });
  if (!dynamicItems.length) {
    return null;
  }

  let totalBaseAmount = new BN(0);
  let totalTokenAmount = new BN(0);
  let expiresAt: number | null = null;

  dynamicItems.forEach((item) => {
    const price = (item.upsell_price || item.price) as any;
    const quoteAmount = new BN((item as any)?.quoted_amount || '0');
    totalTokenAmount = totalTokenAmount.add(quoteAmount);

    const baseAmount = price?.base_amount;
    if (baseAmount) {
      totalBaseAmount = totalBaseAmount.add(toScaledBaseAmount(baseAmount, item.quantity));
    }

    if ((item as any)?.expires_at) {
      expiresAt = expiresAt === null ? (item as any)?.expires_at : Math.min(expiresAt, (item as any)?.expires_at);
    }
  });

  return {
    baseAmount: formatScaledAmount(totalBaseAmount, BASE_AMOUNT_SCALE),
    baseCurrency: ((dynamicItems[0]?.upsell_price || dynamicItems[0]?.price) as any)?.base_currency || 'USD',
    tokenAmount: fromUnitToToken(totalTokenAmount, currency.decimal).toString(),
    tokenSymbol: currency.symbol,
    expiresAt,
  };
}

export function formatLineItemPricing(
  item: TLineItemExpanded,
  currency: TPaymentCurrency,
  {
    trialEnd,
    trialInDays,
    exchangeRate = null,
  }: { trialEnd: number; trialInDays: number; exchangeRate?: string | null },
  locale: string = 'en'
): { primary: string; secondary?: string; quantity: string } {
  if (!currency) {
    return { primary: '', secondary: '', quantity: '' };
  }
  const price = item.upsell_price || item.price;
  if (!price) {
    return { primary: '', secondary: '', quantity: '' };
  }

  let quantity = t('common.qty', locale, { count: item.quantity });
  if (price.recurring?.usage_type === 'metered' || +item.quantity === 1) {
    quantity = '';
  }
  const { unitAmount, totalAmount } = getLineItemAmounts(item, currency, { exchangeRate });
  const isDynamic = (price as any)?.pricing_type === 'dynamic';

  const formatLineItemAmount = (bn: BN) => {
    const value = fromUnitToToken(bn, currency.decimal);
    return formatDynamicPrice(value, isDynamic, 6);
  };

  const total = `${formatLineItemAmount(totalAmount)} ${currency.symbol}`;

  const unit = `${formatLineItemAmount(unitAmount)} ${currency.symbol}`;

  const trialResult = getFreeTrialTime({ trialInDays, trialEnd }, locale);

  const appendUnit = (v: string, alt: string) => {
    if (price.product.unit_label) {
      return `${v}/${price.product.unit_label}`;
    }
    if (price.recurring?.usage_type === 'metered' || item.quantity === 1) {
      return alt;
    }

    return quantity ? t('common.each', locale, { unit }) : '';
  };

  if (price.type === 'recurring' && price.recurring) {
    if (trialResult.count > 0) {
      return {
        primary: t('common.trial', locale, { count: trialResult.count, interval: trialResult.interval }),
        secondary: `${appendUnit(total, total)} ${formatRecurring(price.recurring, false, 'slash', locale)}`,
        quantity,
      };
    }

    return {
      primary: total,
      secondary: appendUnit(total, ''),
      quantity,
    };
  }

  return {
    primary: total,
    secondary: appendUnit(total, ''),
    quantity,
  };
}

export function getSubscriptionStatusColor(status: string) {
  switch (status) {
    case 'active':
      return 'success';
    case 'trialing':
      return 'primary';
    case 'incomplete':
    case 'incomplete_expired':
    case 'paused':
      return 'warning';
    case 'past_due':
    case 'unpaid':
      return 'error';
    case 'canceled':
    default:
      return 'default';
  }
}

export function getPaymentIntentStatusColor(status: string) {
  switch (status) {
    case 'succeeded':
      return 'success';
    case 'requires_payment_method':
    case 'requires_confirmation':
    case 'requires_action':
    case 'requires_capture':
      return 'warning';
    case 'canceled':
    case 'processing':
    default:
      return 'default';
  }
}

export function getRefundStatusColor(status: string) {
  switch (status) {
    case 'succeeded':
      return 'success';
    case 'requires_action':
      return 'warning';
    case 'canceled':
    case 'pending':
    default:
      return 'default';
  }
}

export function getPayoutStatusColor(status: string) {
  switch (status) {
    case 'paid':
      return 'success';
    case 'deferred':
      return 'warning';
    case 'failed':
      return 'warning';
    case 'canceled':
    case 'pending':
    case 'in_transit':
    default:
      return 'default';
  }
}

export function getInvoiceStatusColor(status: string) {
  switch (status) {
    case 'paid':
      return 'success';
    case 'open':
      return 'secondary';
    case 'uncollectible':
      return 'warning';
    case 'draft':
    case 'void':
    default:
      return 'default';
  }
}

export function getWebhookStatusColor(status: string) {
  switch (status) {
    case 'enabled':
      return 'success';
    case 'disabled':
    default:
      return 'default';
  }
}

export function getCheckoutAmount(
  items: TLineItemExpanded[],
  currency: TPaymentCurrency,
  trialing = false,
  upsell = true,
  { exchangeRate = null }: { exchangeRate?: string | null } = {}
) {
  if (
    items.find((x) => {
      const price = upsell ? x.upsell_price || x.price : x.price;
      return price?.custom_unit_amount;
    }) &&
    items.length > 1
  ) {
    throw new Error('Multiple items with custom unit amount are not supported');
  }

  let renew = new BN(0);
  const total = items
    .filter((x) => {
      const price = upsell ? x.upsell_price || x.price : x.price;
      return price != null;
    })
    .reduce((acc, x) => {
      // For custom_amount, we need to check if it was created for the current currency
      // custom_amount can be set from: 1) quoted_amount when a quote is applied, or 2) user input for custom pricing
      // If quote_currency_id exists and doesn't match current currency, we should NOT use custom_amount
      // because the quote was created for a different currency (e.g., ABT quote shouldn't be used for USD)
      const quoteCurrencyId = (x as any)?.quote_currency_id;
      const isQuoteForDifferentCurrency = quoteCurrencyId && quoteCurrencyId !== currency.id;

      if (x.custom_amount && !isQuoteForDifferentCurrency) {
        return acc.add(new BN(x.custom_amount));
      }

      const price = upsell ? x.upsell_price || x.price : x.price;
      const { totalAmount } = getLineItemAmounts(x, currency, { useUpsell: upsell, exchangeRate });

      if (price?.type === 'recurring') {
        renew = renew.add(totalAmount);

        if (trialing) {
          return acc;
        }
        if (price.recurring?.usage_type === 'metered') {
          return acc;
        }
      }

      return acc.add(totalAmount);
    }, new BN(0))
    .toString();

  return {
    subtotal: total,
    total,
    renew: formatDynamicPrice(renew.toString(), !!exchangeRate),
    discount: '0',
    shipping: '0',
    tax: '0',
  };
}

export function getRecurringPeriod(recurring: PriceRecurring) {
  const { interval } = recurring;
  const count = +recurring.interval_count || 1;
  const dayInMs = 24 * 60 * 60 * 1000;

  switch (interval) {
    case 'hour':
      return 60 * 60 * 1000;
    case 'day':
      return count * dayInMs;
    case 'week':
      return count * 7 * dayInMs;
    case 'month':
      return count * 30 * dayInMs;
    case 'year':
      return count * 365 * dayInMs;
    default:
      throw new Error(`Unsupported recurring interval: ${interval}`);
  }
}

export function formatUpsellSaving(items: TLineItemExpanded[], currency: TPaymentCurrency) {
  if (items[0]?.upsell_price_id) {
    return '0';
  }
  if (!items[0]?.price?.upsell?.upsells_to) {
    return '0';
  }

  const from = getCheckoutAmount(items, currency, false, false);
  const to = getCheckoutAmount(
    items.map((x) => ({
      ...x,
      upsell_price_id: x.price.upsell?.upsells_to_id,
      upsell_price: x.price.upsell?.upsells_to,
    })) as TLineItemExpanded[],
    currency,
    false,
    true
  );

  const fromRecurring = items[0].price?.recurring as PriceRecurring;
  const toRecurring = items[0].price?.upsell?.upsells_to?.recurring as PriceRecurring;
  if (!fromRecurring || !toRecurring) {
    return '0';
  }

  const factor = Math.floor(getRecurringPeriod(toRecurring) / getRecurringPeriod(fromRecurring));

  const before = new BN(from.total).mul(new BN(factor));
  const after = new BN(to.total);

  return Number(before.sub(after).mul(new BN(100)).div(before).toString()).toFixed(0);
}

export function formatMeteredThen(
  subscription: string,
  recurring: string,
  hasMetered: boolean,
  locale: string = 'en'
): string {
  if (hasMetered) {
    return t('payment.checkout.meteredThen', locale, { subscription, recurring });
  }
  return t('payment.checkout.then', locale, { subscription, recurring });
}

export function formatThenValue(
  subscription: string,
  recurring: string,
  hasMetered: boolean,
  locale: string = 'en'
): string {
  if (hasMetered) {
    return t('payment.checkout.metered', locale, { recurring });
  }
  return [subscription, recurring].filter(Boolean).join(' ');
}

export function formatPriceDisplay(
  { amount, then, actualAmount, showThen }: { amount: string; then?: string; actualAmount: string; showThen?: boolean },
  recurring: string,
  hasMetered: boolean,
  locale: string = 'en'
) {
  if (Number(actualAmount) === 0 && hasMetered) {
    return t('payment.checkout.metered', locale, { recurring });
  }
  if (showThen) {
    return [amount, then].filter(Boolean).join(', ');
  }
  return [amount, then].filter(Boolean).join(' ');
}

export function hasMultipleRecurringIntervals(items: TLineItemExpanded[]): boolean {
  const intervals = new Set<string>();
  for (const item of items) {
    if (item.price?.recurring?.interval && item.price?.type === 'recurring') {
      intervals.add(`${item.price.recurring.interval}-${item.price.recurring.interval_count}`);
      if (intervals.size > 1) {
        return true;
      }
    }
  }
  return false;
}

export function getFreeTrialTime(
  { trialInDays, trialEnd }: { trialInDays: number; trialEnd: number },
  locale: string = 'en'
): {
  count: number;
  interval: string;
} {
  const now = dayjs().unix();
  const plural = (singular: string, count: number) => t(`common.${count > 1 ? `${singular}s` : singular}`, locale);
  if (trialEnd > 0 && trialEnd > now) {
    if (trialEnd - now < 3600) {
      const count = Math.ceil((trialEnd - now) / 60);
      return { count, interval: plural('minute', count) };
    }
    if (trialEnd - now < 86400) {
      const count = Math.ceil((trialEnd - now) / 3600);
      return { count, interval: plural('hour', count) };
    }
    const count = Math.ceil((trialEnd - now) / 86400);
    return { count, interval: plural('day', count) };
  }
  if (trialInDays > 0) {
    return { count: trialInDays, interval: plural('day', trialInDays) };
  }
  return { count: 0, interval: t('common.day', locale) };
}
export function formatCheckoutHeadlines(
  items: TLineItemExpanded[],
  currency: TPaymentCurrency,
  { trialInDays, trialEnd }: { trialInDays: number; trialEnd: number },
  locale: string = 'en',
  { exchangeRate = null }: { exchangeRate?: string | null } = {}
): {
  action: string;
  amount: string;
  then?: string;
  thenValue?: string;
  secondary?: string;
  showThen?: boolean;
  actualAmount: string;
  priceDisplay: string;
} {
  const brand = getStatementDescriptor(items);
  const { total } = getCheckoutAmount(items, currency, trialInDays > 0, true, { exchangeRate });
  const actualAmount = fromUnitToToken(total, currency.decimal);
  const amount = `${fromUnitToToken(total, currency.decimal)} ${currency.symbol}`;
  const trialResult = getFreeTrialTime({ trialInDays, trialEnd }, locale);

  // empty
  if (items.length === 0) {
    return {
      action: t('payment.checkout.empty', locale),
      amount: '0',
      then: '',
      actualAmount: '0',
      priceDisplay: '0',
    };
  }

  const { name } = items[0]?.price?.product || { name: '' };

  // all one time
  if (items.every((x) => x.price?.type === 'one_time')) {
    const action = t('payment.checkout.pay', locale, { payee: brand });
    if (items.length > 1) {
      return { action, amount, actualAmount, priceDisplay: amount };
    }

    return { action, amount, then: '', actualAmount, priceDisplay: amount };
  }

  const item = items.find((x) => x.price?.type === 'recurring');
  const recurring = formatRecurring(
    (item?.upsell_price || item?.price)?.recurring as PriceRecurring,
    false,
    'per',
    locale
  );

  const hasMetered = items.some((x) => x.price?.type === 'recurring' && x.price?.recurring?.usage_type === 'metered');
  const differentRecurring = hasMultipleRecurringIntervals(items);
  // all recurring
  if (items.every((x) => x.price?.type === 'recurring')) {
    // check if there has different recurring price
    const subscription = [
      hasMetered ? t('payment.checkout.least', locale) : '',
      formatDynamicPrice(
        fromUnitToToken(
          items.reduce((acc, x) => {
            const price = (x.upsell_price || x.price) as any;
            if (price?.recurring?.usage_type === 'metered') {
              return acc;
            }

            return acc.add(getLineItemAmounts(x, currency, { exchangeRate }).totalAmount);
          }, new BN(0)),
          currency.decimal
        ),
        !!exchangeRate
      ),
      currency.symbol,
    ]
      .filter(Boolean)
      .join(' ');
    if (items.length > 1) {
      if (trialResult.count > 0) {
        const thenDisplay = formatMeteredThen(
          subscription,
          recurring,
          hasMetered && Number(subscription) === 0,
          locale
        );
        const thenValue = formatThenValue(subscription, recurring, hasMetered && Number(subscription) === 0, locale);
        const result = {
          action: t('payment.checkout.try2', locale, { name, count: items.length - 1 }),
          amount: t('payment.checkout.free', locale, { count: trialResult.count, interval: trialResult.interval }),
          then: thenDisplay,
          thenValue,
          showThen: true,
          actualAmount: '0',
        };
        return {
          ...result,
          priceDisplay: formatPriceDisplay(
            { amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen },
            recurring,
            hasMetered,
            locale
          ),
        };
      }
      const thenDisplay = hasMetered ? t('payment.checkout.meteredThen', locale, { recurring }) : recurring;
      const thenValue = hasMetered ? t('payment.checkout.metered', locale, { recurring }) : recurring;
      const result = {
        action: t('payment.checkout.sub2', locale, { name, count: items.length - 1 }),
        amount,
        then: thenDisplay,
        thenValue,
        showThen: hasMetered,
        actualAmount,
      };
      return {
        ...result,
        priceDisplay: formatPriceDisplay(
          { amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen },
          recurring,
          hasMetered,
          locale
        ),
      };
    }

    if (trialResult.count > 0) {
      const thenDisplay = formatMeteredThen(subscription, recurring, hasMetered && Number(subscription) === 0, locale);
      const thenValue = formatThenValue(subscription, recurring, hasMetered && Number(subscription) === 0, locale);
      const result = {
        action: t('payment.checkout.try1', locale, { name }),
        amount: t('payment.checkout.free', locale, { count: trialResult.count, interval: trialResult.interval }),
        then: thenDisplay,
        thenValue,
        showThen: true,
        actualAmount: '0',
      };
      return {
        ...result,
        priceDisplay: formatPriceDisplay(
          { amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen },
          recurring,
          hasMetered,
          locale
        ),
      };
    }
    const thenDisplay = hasMetered ? t('payment.checkout.meteredThen', locale, { recurring }) : recurring;
    const thenValue = hasMetered ? t('payment.checkout.metered', locale, { recurring }) : recurring;
    const result = {
      action: t('payment.checkout.sub1', locale, { name }),
      amount,
      then: thenDisplay,
      thenValue,
      showThen: hasMetered && !differentRecurring,
      actualAmount,
    };
    return {
      ...result,
      priceDisplay: formatPriceDisplay(
        { amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen },
        recurring,
        hasMetered,
        locale
      ),
    };
  }

  // mixed
  const subscription = fromUnitToToken(
    items
      .filter((x) => x.price?.type === 'recurring')
      .reduce((acc, x) => {
        if (x.price?.recurring?.usage_type === 'metered') {
          return acc;
        }
        return acc.add(getLineItemAmounts(x, currency, { useUpsell: false, exchangeRate }).totalAmount);
      }, new BN(0)),
    currency.decimal
  );

  const thenDisplay = formatMeteredThen(
    `${subscription} ${currency.symbol}`,
    recurring,
    hasMetered && Number(subscription) === 0,
    locale
  );
  const thenValue = formatThenValue(
    `${subscription} ${currency.symbol}`,
    recurring,
    hasMetered && Number(subscription) === 0,
    locale
  );
  const result = {
    action: t('payment.checkout.pay', locale, { payee: brand }),
    amount,
    then: thenDisplay,
    thenValue,
    showThen: !differentRecurring,
    actualAmount,
  };

  return {
    ...result,
    priceDisplay: formatPriceDisplay(
      { amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen },
      recurring,
      hasMetered,
      locale
    ),
  };
}

export function formatAmount(amount: string, decimals: number, precision: number = 2) {
  const tokenAmount = fromUnitToToken(amount, decimals);
  const numericValue = Number(tokenAmount);
  if (!Number.isFinite(numericValue)) {
    return formatBNStr(amount, decimals, precision, true, false);
  }
  const abs = Math.abs(numericValue);
  const targetPrecision = abs > 0 && abs < 0.01 ? decimals : 2;
  return formatNumber(tokenAmount, targetPrecision, true, false);
}

export function findCurrency(methods: TPaymentMethodExpanded[], currencyId: string): TPaymentCurrencyExpanded | null {
  for (const method of methods) {
    for (const currency of method.payment_currencies) {
      if (currency.id === currencyId) {
        return { object: 'payment_currency', ...currency, paymentMethod: omit(method, ['payment_currencies']) };
      }
    }
  }

  return null;
}

export function isValidCountry(code: string) {
  return defaultCountries.some((x: any) => x[1] === code);
}

export function stopEvent(e: React.SyntheticEvent<any>) {
  try {
    e.stopPropagation();
    e.preventDefault();
    // eslint-disable-next-line no-empty
  } catch {
    // Do nothing
  }
}

export function sleep(ms: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

export function formatSubscriptionProduct(items: TSubscriptionItemExpanded[], maxLength = 2) {
  const names = items.map((x) => x.price?.product?.name).filter(Boolean);
  return (
    names.slice(0, maxLength).join(', ') + (names.length > maxLength ? ` and ${names.length - maxLength} more` : '')
  );
}

export const getLineTimeInfo = (time: number, locale = 'en') => {
  const isToday = dayjs().isSame(dayjs(time), 'day');
  const timeFormat = isToday ? 'HH:mm:ss' : 'YYYY-MM-DD';
  return {
    time: formatToDate(time, locale, timeFormat),
    isToday,
  };
};

export const getSubscriptionTimeSummary = (subscription: TSubscriptionExpanded, locale = 'en') => {
  if (subscription.status === 'active' || subscription.status === 'trialing') {
    if (subscription.cancel_at) {
      const endTime = subscription.cancel_at * 1000;
      const { time, isToday } = getLineTimeInfo(endTime, locale);
      return { action: endTime > Date.now() ? 'willEnd' : 'ended', time, isToday };
    }
    if (subscription.cancel_at_period_end) {
      const endTime = subscription.current_period_end * 1000;
      const { time, isToday } = getLineTimeInfo(endTime, locale);
      return { action: 'willEnd', time, isToday };
    }
    const { time, isToday } = getLineTimeInfo(subscription.current_period_end * 1000, locale);
    return { action: 'renew', time, isToday };
  }
  if (subscription.status === 'past_due') {
    const endTime = (subscription.cancel_at || subscription.current_period_end) * 1000;
    const { time, isToday } = getLineTimeInfo(endTime, locale);
    return { action: endTime > Date.now() ? 'willEnd' : 'ended', time, isToday };
  }
  if (subscription.status === 'canceled') {
    const { time, isToday } = getLineTimeInfo(subscription.canceled_at * 1000, locale);
    return { action: 'ended', time, isToday };
  }
  return null;
};

export const getSubscriptionAction = (
  subscription: TSubscriptionExpanded,
  actionProps: ActionProps
): {
  action: string;
  variant: string;
  color: string;
  canRenew: boolean;
  text?: string;
  sx?: any;
} | null => {
  if (subscription.status === 'active' || subscription.status === 'trialing') {
    if (subscription.cancel_at_period_end) {
      if (subscription.cancelation_details?.reason === 'payment_failed') {
        return null;
      }

      return { action: 'recover', variant: 'contained', color: 'primary', canRenew: false, ...actionProps?.recover };
    }

    if (subscription.cancel_at && subscription.cancel_at !== subscription.current_period_end) {
      return null;
    }

    return { action: 'cancel', variant: 'outlined', color: 'inherit', canRenew: false, ...actionProps?.cancel };
  }

  if (subscription.status === 'past_due') {
    const canRenew = subscription.cancel_at && subscription.cancel_at !== subscription.current_period_end;
    return { action: 'pastDue', variant: 'contained', color: 'primary', canRenew, ...actionProps?.pastDue };
  }

  if (subscription.status !== 'canceled' && subscription.cancel_at_period_end) {
    return { action: 'recover', variant: 'contained', color: 'primary', canRenew: false, ...actionProps?.recover };
  }

  return null;
};

export const mergeExtraParams = (extra: Record<string, any> = {}) => {
  const params = new URLSearchParams(window.location.search);
  Object.keys(extra).forEach((key) => {
    params.set(key, extra[key]);
  });

  return params.toString();
};

export const flattenPaymentMethods = (methods: TPaymentMethodExpanded[] = []): TPaymentCurrency[] => {
  const out: TPaymentCurrency[] = [];

  methods.forEach((method: any) => {
    const currencies = method.paymentCurrencies || method.payment_currencies || [];
    currencies.forEach((currency: any) => {
      out.push({
        ...currency,
        method,
      });
    });
  });

  return out;
};

export const getTxLink = (method: TPaymentMethod, details: PaymentDetails) => {
  if (!details) {
    return { text: 'N/A', link: '', gas: '' };
  }
  if (method.type === 'arcblock' && details.arcblock?.tx_hash) {
    return {
      link: joinURL(method.settings.arcblock?.explorer_host as string, '/txs', details.arcblock?.tx_hash as string),
      text: details.arcblock?.tx_hash as string,
      gas: '',
    };
  }
  if (method.type === 'bitcoin' && details.bitcoin?.tx_hash) {
    return {
      link: joinURL(method.settings.bitcoin?.explorer_host as string, '/tx', details.bitcoin?.tx_hash as string),
      text: details.bitcoin?.tx_hash as string,
      gas: '',
    };
  }
  if (method.type === 'ethereum' && details.ethereum?.tx_hash) {
    return {
      link: joinURL(method.settings.ethereum?.explorer_host as string, '/tx', details.ethereum?.tx_hash as string),
      text: (details.ethereum?.tx_hash as string).toUpperCase(),
      gas: new BN(details.ethereum.gas_price).mul(new BN(details.ethereum.gas_used)).toString(),
    };
  }
  if (method.type === 'base' && details.base?.tx_hash) {
    return {
      link: joinURL(method.settings.base?.explorer_host as string, '/tx', details.base?.tx_hash as string),
      text: (details.base?.tx_hash as string).toUpperCase(),
      gas: '',
    };
  }
  if (method.type === 'stripe') {
    const dashboard = method.livemode ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test';
    return {
      link: joinURL(
        method.settings.stripe?.dashboard || dashboard,
        'payments',
        details.stripe?.payment_intent_id as string
      ),
      text: details.stripe?.payment_intent_id as string,
      gas: '',
    };
  }

  return { text: 'N/A', link: '', gas: '' };
};

export function getQueryParams(url: string): Record<string, string> {
  const queryParams: Record<string, string> = {};
  const urlObj = new URL(url);
  urlObj.searchParams.forEach((value, key) => {
    queryParams[key] = value;
  });
  return queryParams;
}

export function lazyLoad(lazyRun: () => void) {
  if ('requestIdleCallback' in window) {
    (window as any).requestIdleCallback(() => {
      lazyRun();
    });
    return;
  }
  if (document.readyState === 'complete') {
    lazyRun();
    return;
  }
  if ('onload' in window) {
    (window as any).onload = () => {
      lazyRun();
    };
    return;
  }
  setTimeout(() => {
    lazyRun();
  }, 0);
}

export function formatTotalPrice({
  product,
  quantity = 1,
  priceId,
  locale = 'en',
}: {
  product: TProductExpanded;
  quantity: number;
  priceId?: string;
  locale: string;
}): PricingRenderProps {
  const { prices = [], default_price_id: defaultPriceId } = product ?? { prices: [], default_price_id: '' };
  const price = prices?.find((x) => x.id === (priceId || defaultPriceId));
  if (!price || !product) {
    return {
      totalPrice: '0',
      unitPrice: '',
      quantity: t('common.qty', locale, { count: quantity }),
      totalAmount: '0',
    };
  }

  const currency: TPaymentCurrency = price?.currency ?? {};
  const unitValue = new BN(getPriceUintAmountByCurrency(price, currency));

  const total = `${fromUnitToToken(unitValue.mul(new BN(quantity)), currency.decimal)} ${currency.symbol} `;

  const unit = `${fromUnitToToken(unitValue, currency.decimal)} ${currency.symbol} `;

  const appendUnit = (v: string, alt: string) => {
    if (product.unit_label) {
      return `${v}/${price.product.unit_label}`;
    }
    if (price.recurring?.usage_type === 'metered' || quantity === 1) {
      return alt;
    }
    return quantity ? t('common.each', locale, { unit }) : '';
  };

  return {
    totalPrice: total,
    unitPrice: appendUnit(total, ''),
    quantity: t('common.qty', locale, { count: quantity }),
    totalAmount: unitValue.mul(new BN(quantity)).toString(),
  };
}

export function formatQuantityInventory(price: TPrice, quantity: string | number, locale = 'en') {
  const q = Number(quantity);
  const {
    quantity_available: quantityAvailable = 0,
    quantity_sold: quantitySold = 0,
    quantity_limit_per_checkout: quantityLimitPerCheckout = 0,
  } = price || {};
  if (quantityAvailable > 0 && quantitySold + q > quantityAvailable) {
    return t('common.quantityNotEnough', locale);
  }
  if (quantityLimitPerCheckout > 0 && quantityLimitPerCheckout < q) {
    return t('common.quantityLimitPerCheckout', locale);
  }
  return '';
}

export function formatSubscriptionStatus(status: string) {
  if (status === 'canceled') {
    return 'Ended';
  }
  return status;
}

export function formatAmountPrecisionLimit(amount: string, locale = 'en', precision: number = 6) {
  if (!amount) {
    return '';
  }
  const [, decimal] = String(amount).split('.');
  if (decimal && decimal.length > precision) {
    return t('common.amountPrecisionLimit', locale, { precision });
  }
  return '';
}

export function getWordBreakStyle(value: any): 'break-word' | 'break-all' {
  if (typeof value === 'string' && /\s/.test(value)) {
    return 'break-word';
  }
  return 'break-all';
}

export function isMobileSafari() {
  const ua = navigator.userAgent.toLowerCase();
  const isSafari = ua.indexOf('safari') > -1 && ua.indexOf('chrome') === -1;
  const isMobile = ua.indexOf('mobile') > -1 || /iphone|ipad|ipod/.test(ua);
  const isIOS = /iphone|ipad|ipod/.test(ua);
  return isSafari && isMobile && isIOS;
}

export function truncateText(text: string, maxLength: number, useWidth: boolean = false): string {
  if (!text || !maxLength) {
    return text;
  }

  if (!useWidth) {
    if (text.length <= maxLength) {
      return text;
    }
    return `${text.substring(0, maxLength)}...`;
  }

  let width = 0;
  let truncated = '';

  for (let i = 0; i < text.length; i++) {
    const charWidth = stringWidth(text.charAt(i));
    if (width + charWidth > maxLength) {
      break;
    }
    truncated += text.charAt(i);
    width += charWidth;
  }

  if (truncated === text) {
    return truncated;
  }

  return `${truncated}...`;
}

export function getCustomerAvatar(
  did: string | undefined,
  updated_at: string | number | undefined,
  imageSize: number = 48
): string {
  if (!did) return '';
  const updated = typeof updated_at === 'number' ? updated_at : dayjs(updated_at).unix();
  return `/.well-known/service/user/avatar/${did}?imageFilter=resize&w=${imageSize}&h=${imageSize}&updateAt=${updated || dayjs().unix()}`;
}

// 判断是否存在txHash
export function hasDelegateTxHash(details: PaymentDetails, paymentMethod: TPaymentMethod) {
  return (
    paymentMethod?.type &&
    ['arcblock', 'ethereum', 'base'].includes(paymentMethod?.type) &&
    // @ts-ignore
    details?.[paymentMethod?.type]?.tx_hash
  );
}

export function getInvoiceDescriptionAndReason(invoice: TInvoiceExpanded, locale = 'en') {
  const { billing_reason: reason, description } = invoice;
  const reasonMap = {
    subscription_create: t('payment.invoice.reason.creation', locale),
    subscription_cycle: t('payment.invoice.reason.cycle', locale),
    subscription_update: t('payment.invoice.reason.update', locale),
    subscription_recover: t('payment.invoice.reason.recover', locale),
    subscription_threshold: t('payment.invoice.reason.threshold', locale),
    subscription_cancel: t('payment.invoice.reason.cancel', locale),
    manual: t('payment.invoice.reason.manual', locale),
    upcoming: t('payment.invoice.reason.upcoming', locale),
    slash_stake: t('payment.invoice.reason.slashStake', locale),
    stake: t('payment.invoice.reason.stake', locale),
    return_stake: t('payment.invoice.reason.returnStake', locale),
    recharge: t('payment.invoice.reason.recharge', locale),
    stake_overdraft_protection: t('payment.invoice.reason.stake', locale),
    overdraft_protection: t('payment.invoice.reason.fee', locale),
    auto_recharge: t('payment.invoice.reason.recharge', locale),
  };
  let invoiceType = t('payment.invoice.reason.payment', locale);
  if (reason.includes('stake') || reason.includes('recharge') || reason === 'overdraft_protection') {
    invoiceType = reasonMap[reason as keyof typeof reasonMap];
  }
  if (description?.startsWith('Subscription ') || description?.startsWith('Slash stake')) {
    return {
      description: reasonMap[reason as keyof typeof reasonMap],
      reason: reasonMap[reason as keyof typeof reasonMap],
      type: invoiceType,
    };
  }
  const descMap = {
    'Stake for subscription plan change': t('payment.invoice.reason.stakeForChangePlan', locale),
    'Stake for subscription payment change': t('payment.invoice.reason.stakeForChangePayment', locale),
    'Stake for subscription': t('payment.invoice.reason.staking', locale),
    'Return Subscription staking': t('payment.invoice.reason.returnStake', locale),
    'Recharge for subscription': t('payment.invoice.reason.rechargeForSubscription', locale),
    'Add funds for subscription': t('payment.invoice.reason.rechargeForSubscription', locale),
    'Overdraft protection': t('payment.invoice.reason.overdraftProtection', locale),
    'Stake for subscription overdraft protection': t(
      'payment.invoice.reason.stakeForSubscriptionOverdraftProtection',
      locale
    ),
    'Re-stake to resume subscription': t('payment.invoice.reason.reStakeToResumeSubscription', locale),
  };

  return {
    description: descMap[description as keyof typeof descMap] || description,
    reason: reasonMap[reason as keyof typeof reasonMap] || reason,
    type: invoiceType,
  };
}

export function getPaymentKitComponent() {
  const paymentKit = window.blocklet?.componentMountPoints?.find((c: any) => c.did === PAYMENT_KIT_DID);
  return paymentKit || null;
}

export function openDonationSettings(openInNewTab: boolean = false) {
  const paymentKit = getPaymentKitComponent();
  if (paymentKit) {
    const mountPoint = paymentKit.mountPoint.endsWith('/') ? paymentKit.mountPoint.slice(0, -1) : paymentKit.mountPoint;
    window.open(`${window.location.origin}${mountPoint}/integrations/donations`, openInNewTab ? '_blank' : '_self');
  }
}

export function getUserProfileLink(userDid: string, locale = 'en') {
  return joinURL(
    window.location.origin,
    withQuery('.well-known/service/user', {
      locale,
      did: userDid,
    })
  );
}

export function parseMarkedText(text: string): Array<{
  type: 'text' | 'marked';
  content: string;
}> {
  if (!text) return [];
  const parts = text.split(/(#([^#]*)#)/);
  const result: { type: 'text' | 'marked'; content: string }[] = [];

  for (let i = 0; i < parts.length; i++) {
    const part = parts[i];
    // eslint-disable-next-line no-continue
    if (!part) continue;
    if (i % 3 === 0) {
      result.push({ type: 'text', content: part });
    } else if (i % 3 === 1 && part.startsWith('#') && part.endsWith('#')) {
      const content = part.slice(1, -1);
      if (content.length >= 0) {
        result.push({ type: 'marked', content });
      }
    }
  }

  return result.filter((p) => p.content !== '');
}

export function getTokenBalanceLink(method: TPaymentMethod, address: string) {
  if (!method || !address) {
    return '';
  }
  const explorerHost = (method?.settings?.[method?.type as ChainType] as any)?.explorer_host || '';
  if (method.type === 'arcblock' && address) {
    return joinURL(explorerHost, 'accounts', address, 'tokens');
  }
  if (['ethereum', 'base'].includes(method.type) && address) {
    return joinURL(explorerHost, 'address', address);
  }
  return '';
}

export function isCreditMetered(price: TPrice): boolean {
  return !!(price.type === 'recurring' && price.recurring?.usage_type === 'metered' && price.recurring?.meter_id);
}

export function showStaking(method: TPaymentMethod, currency: TPaymentCurrency, noStake: boolean) {
  if (noStake) {
    return false;
  }
  if (method && method.type === 'arcblock') {
    return currency.type !== 'credit';
  }
  return false;
}

export function formatLinkWithLocale(url: string, locale?: string) {
  if (!locale || !url) {
    return url;
  }
  try {
    const urlObj = new URL(url);
    urlObj.searchParams.set('locale', locale);
    return urlObj.toString();
  } catch (error) {
    if (/[?&]locale=[^&]*/.test(url)) {
      return url.replace(/([?&])locale=[^&]*/, `$1locale=${locale}`);
    }
    const separator = url.includes('?') ? '&' : '?';
    return `${url}${separator}locale=${locale}`;
  }
}

// Compute text color that contrasts with primary.main, works in both light and dark mode
export function primaryContrastColor(theme: {
  palette: { primary: { main: string }; getContrastText: (bg: string) => string };
}): string {
  return theme.palette.getContrastText(theme.palette.primary.main);
}
