/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/indent */
import type {
  ChainType,
  PaymentDetails,
  PriceCurrency,
  PriceRecurring,
  TInvoiceExpanded,
  TLineItemExpanded,
  TPaymentCurrency,
  TPaymentCurrencyExpanded,
  TPaymentMethod,
  TPaymentMethodExpanded,
  TPrice,
  TProductExpanded,
  TSubscriptionExpanded,
  TSubscriptionItemExpanded,
} from '@blocklet/payment-types';
import { BN, fromUnitToToken } 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';

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);
}

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 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})`;
  }

  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
) => {
  if (!currency) {
    return '';
  }
  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,
    },
  ];
}

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

  let quantity = t('common.qty', locale, { count: item.quantity });
  if (price.recurring?.usage_type === 'metered' || +item.quantity === 1) {
    quantity = '';
  }
  const unitValue = new BN(getPriceUintAmountByCurrency(price, currency));

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

  const unit = `${fromUnitToToken(unitValue, currency.decimal)} ${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 '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
) {
  if (items.find((x) => (x.upsell_price || x.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) => {
      if (x.custom_amount) {
        return acc.add(new BN(x.custom_amount));
      }

      const price = upsell ? x.upsell_price || x.price : x.price;
      const unitPrice = getPriceUintAmountByCurrency(price, currency);
      if (price.custom_unit_amount) {
        if (unitPrice) {
          return acc.add(new BN(unitPrice).mul(new BN(x.quantity)));
        }
      }

      if (price?.type === 'recurring') {
        renew = renew.add(new BN(unitPrice).mul(new BN(x.quantity)));

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

      return acc.add(new BN(unitPrice).mul(new BN(x.quantity)));
    }, new BN(0))
    .toString();

  return { subtotal: total, total, renew: renew.toString(), 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 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();
  if (trialEnd > 0 && trialEnd > now) {
    if (trialEnd - now < 3600) {
      return {
        count: Math.ceil((trialEnd - now) / 60),
        interval: t('common.minute', locale),
      };
    }
    if (trialEnd - now < 86400) {
      return {
        count: Math.ceil((trialEnd - now) / 3600),
        interval: t('common.hour', locale),
      };
    }
    return {
      count: Math.ceil((trialEnd - now) / 86400),
      interval: t('common.day', locale),
    };
  }
  if (trialInDays > 0) {
    return {
      count: trialInDays,
      interval: t('common.day', locale),
    };
  }
  return {
    count: 0,
    interval: t('common.day', locale),
  };
}
export function formatCheckoutHeadlines(
  items: TLineItemExpanded[],
  currency: TPaymentCurrency,
  { trialInDays, trialEnd }: { trialInDays: number; trialEnd: number },
  locale: string = 'en'
): {
  action: string;
  amount: string;
  then?: string;
  secondary?: string;
  showThen?: boolean;
  actualAmount: string;
  priceDisplay: string;
} {
  const brand = getStatementDescriptor(items);
  const { total } = getCheckoutAmount(items, currency, trialInDays > 0);
  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) : '',
      fromUnitToToken(
        items.reduce((acc, x) => {
          if (x.price.recurring?.usage_type === 'metered') {
            return acc;
          }

          return acc.add(
            new BN(getPriceUintAmountByCurrency(x.upsell_price || x.price, currency)).mul(new BN(x.quantity))
          );
        }, new BN(0)),
        currency.decimal
      ),
      currency.symbol,
    ]
      .filter(Boolean)
      .join(' ');
    if (items.length > 1) {
      if (trialResult.count > 0) {
        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: formatMeteredThen(subscription, recurring, hasMetered && Number(subscription) === 0, locale),
          showThen: true,
          actualAmount: '0',
        };
        return {
          ...result,
          priceDisplay: formatPriceDisplay(result, recurring, hasMetered, locale),
        };
      }
      const result = {
        action: t('payment.checkout.sub2', locale, { name, count: items.length - 1 }),
        amount,
        then: hasMetered ? t('payment.checkout.meteredThen', locale, { recurring }) : recurring,
        showThen: hasMetered,
        actualAmount,
      };
      return {
        ...result,
        priceDisplay: formatPriceDisplay(result, recurring, hasMetered, locale),
      };
    }

    if (trialResult.count > 0) {
      const result = {
        action: t('payment.checkout.try1', locale, { name }),
        amount: t('payment.checkout.free', locale, { count: trialResult.count, interval: trialResult.interval }),
        then: formatMeteredThen(subscription, recurring, hasMetered && Number(subscription) === 0, locale),
        showThen: true,
        actualAmount: '0',
      };
      return {
        ...result,
        priceDisplay: formatPriceDisplay(result, recurring, hasMetered, locale),
      };
    }
    const result = {
      action: t('payment.checkout.sub1', locale, { name }),
      amount,
      then: hasMetered ? t('payment.checkout.meteredThen', locale, { recurring }) : recurring,
      showThen: hasMetered && !differentRecurring,
      actualAmount,
    };
    return {
      ...result,
      priceDisplay: formatPriceDisplay(result, 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(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
      }, new BN(0)),
    currency.decimal
  );

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

  return {
    ...result,
    priceDisplay: formatPriceDisplay(result, recurring, hasMetered, locale),
  };
}

export function formatAmount(amount: string, decimals: number) {
  return fromUnitToToken(amount, decimals);
}

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: 'willEnd', 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: '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 unitValue = new BN(price.custom_unit_amount || price.unit_amount);
  const currency: TPaymentCurrency = 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 {
  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),
  };
  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.type === 'arcblock') {
    return currency.type !== 'credit';
  }
  return true;
}
