// phone-sms/providers/five-sim.js — 5sim 接码平台适配层
(function attachFiveSimSmsProvider(root, factory) {
  root.PhoneSmsFiveSimProvider = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createFiveSimSmsProviderModule() {
  const PROVIDER_ID = '5sim';
  const DEFAULT_BASE_URL = 'https://5sim.net';
  const DEFAULT_PRODUCT = 'openai';
  const DEFAULT_OPERATOR = 'any';
  const DEFAULT_COUNTRY_ID = 'vietnam';
  const DEFAULT_COUNTRY_LABEL = '越南 (Vietnam)';
  const DEFAULT_REQUEST_TIMEOUT_MS = 20000;
  const DEFAULT_MAX_USES = 3;
  const FIVE_SIM_RATE_LIMIT_ERROR_PREFIX = 'FIVE_SIM_RATE_LIMIT::';
  const MAX_PRICE_CANDIDATES = 8;
  const SUPPORTED_COUNTRY_ITEMS = Object.freeze([
    { id: 'indonesia', label: '印度尼西亚 (Indonesia)' },
    { id: 'thailand', label: '泰国 (Thailand)' },
    { id: 'england', label: '英国 (England)' },
    { id: 'usa', label: '美国 (United States)' },
    { id: 'japan', label: '日本 (Japan)' },
    { id: 'germany', label: '德国 (Germany)' },
    { id: 'vietnam', label: '越南 (Vietnam)' },
  ]);
  const SUPPORTED_COUNTRY_ID_SET = new Set(SUPPORTED_COUNTRY_ITEMS.map((item) => item.id));
  const COUNTRY_CN_BY_ID = Object.freeze({
    afghanistan: '阿富汗',
    albania: '阿尔巴尼亚',
    algeria: '阿尔及利亚',
    angola: '安哥拉',
    argentina: '阿根廷',
    armenia: '亚美尼亚',
    australia: '澳大利亚',
    austria: '奥地利',
    azerbaijan: '阿塞拜疆',
    bahamas: '巴哈马',
    bahrain: '巴林',
    bangladesh: '孟加拉国',
    belarus: '白俄罗斯',
    belgium: '比利时',
    bolivia: '玻利维亚',
    bosnia: '波黑',
    brazil: '巴西',
    bulgaria: '保加利亚',
    cambodia: '柬埔寨',
    cameroon: '喀麦隆',
    canada: '加拿大',
    chile: '智利',
    china: '中国',
    colombia: '哥伦比亚',
    croatia: '克罗地亚',
    cyprus: '塞浦路斯',
    czech: '捷克',
    denmark: '丹麦',
    egypt: '埃及',
    england: '英国',
    estonia: '爱沙尼亚',
    ethiopia: '埃塞俄比亚',
    finland: '芬兰',
    france: '法国',
    georgia: '格鲁吉亚',
    germany: '德国',
    ghana: '加纳',
    greece: '希腊',
    hongkong: '中国香港',
    hungary: '匈牙利',
    india: '印度',
    indonesia: '印度尼西亚',
    ireland: '爱尔兰',
    israel: '以色列',
    italy: '意大利',
    japan: '日本',
    jordan: '约旦',
    kazakhstan: '哈萨克斯坦',
    kenya: '肯尼亚',
    kyrgyzstan: '吉尔吉斯斯坦',
    laos: '老挝',
    latvia: '拉脱维亚',
    lithuania: '立陶宛',
    malaysia: '马来西亚',
    mexico: '墨西哥',
    moldova: '摩尔多瓦',
    morocco: '摩洛哥',
    myanmar: '缅甸',
    nepal: '尼泊尔',
    netherlands: '荷兰',
    newzealand: '新西兰',
    nigeria: '尼日利亚',
    norway: '挪威',
    pakistan: '巴基斯坦',
    paraguay: '巴拉圭',
    peru: '秘鲁',
    philippines: '菲律宾',
    poland: '波兰',
    portugal: '葡萄牙',
    romania: '罗马尼亚',
    russia: '俄罗斯',
    saudiarabia: '沙特阿拉伯',
    serbia: '塞尔维亚',
    singapore: '新加坡',
    slovakia: '斯洛伐克',
    slovenia: '斯洛文尼亚',
    southafrica: '南非',
    spain: '西班牙',
    srilanka: '斯里兰卡',
    sweden: '瑞典',
    switzerland: '瑞士',
    taiwan: '中国台湾',
    tajikistan: '塔吉克斯坦',
    tanzania: '坦桑尼亚',
    thailand: '泰国',
    turkey: '土耳其',
    ukraine: '乌克兰',
    uruguay: '乌拉圭',
    usa: '美国',
    uzbekistan: '乌兹别克斯坦',
    venezuela: '委内瑞拉',
    vietnam: '越南',
  });

  function normalizeFiveSimCountryId(value, fallback = DEFAULT_COUNTRY_ID) {
    const normalized = String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '');
    if (normalized) {
      return normalized;
    }
    const fallbackSource = fallback === undefined || fallback === null ? DEFAULT_COUNTRY_ID : fallback;
    const normalizedFallback = String(fallbackSource).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '');
    if (!normalizedFallback) {
      return '';
    }
    return normalizedFallback || DEFAULT_COUNTRY_ID;
  }

  function getCountryIdFromPayload(record = {}, fallback = DEFAULT_COUNTRY_ID) {
    if (record?.country && typeof record.country === 'object' && !Array.isArray(record.country)) {
      return normalizeFiveSimCountryId(record.country.name || record.country.id || record.country.slug, fallback);
    }
    return normalizeFiveSimCountryId(record.countryId ?? record.country, fallback);
  }

  function getCountryLabelFromPayload(record = {}, fallbackLabel = DEFAULT_COUNTRY_LABEL, fallbackId = DEFAULT_COUNTRY_ID) {
    if (record?.country && typeof record.country === 'object' && !Array.isArray(record.country)) {
      const countryId = normalizeFiveSimCountryId(record.country.name || record.country.id || record.country.slug || fallbackId, fallbackId);
      return formatFiveSimCountryLabel(countryId, record.country.text_en || record.country.label || countryId, fallbackLabel || fallbackId);
    }
    const countryId = normalizeFiveSimCountryId(record.countryId ?? record.country ?? fallbackId, fallbackId);
    return normalizeFiveSimCountryLabel(record.countryLabel, formatFiveSimCountryLabel(countryId, countryId, fallbackLabel || fallbackId));
  }

  function normalizeFiveSimCountryLabel(value = '', fallback = DEFAULT_COUNTRY_LABEL) {
    return String(value || '').trim() || fallback;
  }

  function formatFiveSimCountryLabel(id = '', englishValue = '', fallback = DEFAULT_COUNTRY_LABEL) {
    const countryId = normalizeFiveSimCountryId(id, '');
    const english = normalizeFiveSimCountryLabel(englishValue || countryId || fallback, fallback);
    const chinese = COUNTRY_CN_BY_ID[countryId] || '';
    if (chinese && english) {
      if (String(english).includes(chinese)) {
        return english;
      }
      if (chinese.toLowerCase() !== english.toLowerCase()) {
        return `${chinese} (${english})`;
      }
    }
    return chinese || english;
  }

  function normalizeFiveSimOperator(value = '') {
    return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || DEFAULT_OPERATOR;
  }

  function normalizeFiveSimMaxPrice(value = '') {
    const rawValue = String(value ?? '').trim();
    if (!rawValue) {
      return '';
    }
    const numeric = Number(rawValue);
    if (!Number.isFinite(numeric) || numeric <= 0) {
      return '';
    }
    return String(Math.round(numeric * 10000) / 10000);
  }

  function normalizeFiveSimCountryFallback(value = []) {
    const source = Array.isArray(value)
      ? value
      : String(value || '')
        .split(/[\r\n,，;；]+/)
        .map((entry) => String(entry || '').trim())
        .filter(Boolean);
    const seen = new Set();
    const normalized = [];

    for (const entry of source) {
      let id = '';
      let label = '';

      if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
        id = normalizeFiveSimCountryId(entry.id ?? entry.countryId ?? entry.slug, '');
        label = String((entry.label ?? entry.countryLabel ?? entry.text_en ?? entry.name) || '').trim();
      } else {
        const text = String(entry || '').trim();
        const structured = text.match(/^([a-z0-9_-]+)\s*(?:[:|/-]\s*(.+))?$/i);
        id = normalizeFiveSimCountryId(structured?.[1] || text, '');
        label = String(structured?.[2] || '').trim();
      }

      if (!id || seen.has(id)) {
        continue;
      }
      seen.add(id);
      normalized.push({
        id,
        label: label || formatFiveSimCountryLabel(id, id, id),
      });
      if (normalized.length >= 20) {
        break;
      }
    }

    return normalized;
  }

  function normalizePrice(value) {
    const price = Number(value);
    if (!Number.isFinite(price) || price < 0) {
      return null;
    }
    return price;
  }

  function buildSortedUniquePriceCandidates(values = []) {
    return Array.from(
      new Set(
        values
          .map((value) => normalizePrice(value))
          .filter((value) => value !== null)
          .map((value) => Math.round(value * 10000) / 10000)
      )
    )
      .sort((left, right) => left - right)
      .slice(0, MAX_PRICE_CANDIDATES);
  }

  function describePayload(raw) {
    if (typeof raw === 'string') {
      return raw.trim();
    }
    if (raw && typeof raw === 'object') {
      const direct = String(raw.message || raw.msg || raw.error || raw.title || raw.status || '').trim();
      if (direct) {
        return direct;
      }
      try {
        return JSON.stringify(raw);
      } catch {
        return String(raw);
      }
    }
    return String(raw || '').trim();
  }

  function parsePayload(text) {
    const trimmed = String(text || '').trim();
    if (!trimmed) {
      return '';
    }
    if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
      try {
        return JSON.parse(trimmed);
      } catch {
        return trimmed;
      }
    }
    return trimmed;
  }

  function normalizeBaseUrl(value = '') {
    const trimmed = String(value || '').trim() || DEFAULT_BASE_URL;
    try {
      const url = new URL(trimmed);
      return `${url.origin}${url.pathname.replace(/\/+$/, '')}`;
    } catch {
      return DEFAULT_BASE_URL;
    }
  }

  function buildUrl(config = {}, path = '', query = {}) {
    const url = new URL(path, `${normalizeBaseUrl(config.baseUrl)}/`);
    Object.entries(query || {}).forEach(([key, value]) => {
      if (value === undefined || value === null || value === '') {
        return;
      }
      url.searchParams.set(key, String(value));
    });
    return url.toString();
  }

  async function fetchJson(config = {}, path = '', options = {}) {
    const { query = {}, actionLabel = '5sim 请求', requireAuth = true } = options;
    const token = String(config.apiKey || '').trim();
    if (requireAuth && !token) {
      throw new Error('缺少 5sim API Key，请先在侧边栏接码设置中填写并保存。');
    }
    const controller = typeof AbortController === 'function' ? new AbortController() : null;
    const timeoutId = controller
      ? setTimeout(() => controller.abort(), Number(config.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS)
      : null;

    try {
      const response = await config.fetchImpl(buildUrl(config, path, query), {
        method: 'GET',
        signal: controller?.signal,
        headers: {
          Accept: 'application/json',
          ...(requireAuth ? { Authorization: `Bearer ${token}` } : {}),
        },
      });
      const text = await response.text();
      const payload = parsePayload(text);
      if (!response.ok) {
        const error = new Error(`${actionLabel}失败：${describePayload(payload) || response.status}`);
        error.payload = payload;
        error.status = response.status;
        throw error;
      }
      return payload;
    } catch (error) {
      if (error?.name === 'AbortError') {
        throw new Error(`${actionLabel}超时。`);
      }
      throw error;
    } finally {
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
    }
  }

  function resolveConfig(state = {}, deps = {}) {
    return {
      apiKey: String(state.fiveSimApiKey || '').trim(),
      baseUrl: DEFAULT_BASE_URL,
      fetchImpl: deps.fetchImpl,
      requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
    };
  }

  function resolveCountryConfig(state = {}) {
    const rawCountryId = normalizeFiveSimCountryId(state.fiveSimCountryId, '');
    const id = rawCountryId || DEFAULT_COUNTRY_ID;
    return {
      id,
      label: formatFiveSimCountryLabel(id, state.fiveSimCountryLabel || id, DEFAULT_COUNTRY_LABEL),
    };
  }

  function resolveCountryCandidates(state = {}) {
    const primary = resolveCountryConfig(state);
    const fallbackList = normalizeFiveSimCountryFallback(state.fiveSimCountryFallback);
    const seen = new Set([primary.id]);
    const candidates = [primary];

    fallbackList.forEach((entry) => {
      const nextId = normalizeFiveSimCountryId(entry.id, '');
      if (!nextId || seen.has(nextId)) {
        return;
      }
      seen.add(nextId);
      candidates.push({
        id: nextId,
        label: formatFiveSimCountryLabel(nextId, entry.label || nextId, nextId),
      });
    });

    return candidates;
  }

  async function fetchBalance(state = {}, deps = {}) {
    const config = resolveConfig(state, deps);
    const payload = await fetchJson(config, '/v1/user/profile', {
      actionLabel: '5sim 查询余额',
    });
    return {
      balance: Number(payload?.balance),
      frozenBalance: Number(payload?.frozen_balance),
      rating: Number(payload?.rating),
      raw: payload,
    };
  }

  async function fetchCountries(_state = {}, deps = {}) {
    const config = {
      baseUrl: DEFAULT_BASE_URL,
      fetchImpl: deps.fetchImpl,
      requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
    };
    const payload = await fetchJson(config, '/v1/guest/countries', {
      actionLabel: '5sim 查询国家列表',
      requireAuth: false,
    });
    return Object.entries(payload || {})
      .map(([slug, entry]) => {
        const id = normalizeFiveSimCountryId(slug, '');
        if (!id) {
          return null;
        }
        return {
          id,
          label: formatFiveSimCountryLabel(id, entry?.text_en || slug, slug),
          searchText: [
            slug,
            formatFiveSimCountryLabel(id, entry?.text_en || slug, slug),
            entry?.text_en,
            entry?.text_ru,
            Object.keys(entry?.iso || {}).join(' '),
            Object.keys(entry?.prefix || {}).join(' '),
          ].filter(Boolean).join(' '),
        };
      })
      .filter(Boolean)
      .sort((left, right) => String(left.label || '').localeCompare(String(right.label || '')));
  }

  function collectPriceEntries(payload, entries = []) {
    if (Array.isArray(payload)) {
      payload.forEach((entry) => collectPriceEntries(entry, entries));
      return entries;
    }
    if (!payload || typeof payload !== 'object') {
      return entries;
    }

    const cost = normalizePrice(payload.cost ?? payload.Price ?? payload.price);
    if (cost !== null) {
      const count = Number(payload.count ?? payload.Qty ?? payload.qty);
      entries.push({
        cost,
        count: Number.isFinite(count) ? count : 0,
        inStock: !Number.isFinite(count) || count > 0,
      });
    }

    Object.values(payload).forEach((entry) => collectPriceEntries(entry, entries));
    return entries;
  }

  async function fetchPrices(state = {}, countryConfig = resolveCountryConfig(state), deps = {}) {
    const config = {
      baseUrl: DEFAULT_BASE_URL,
      fetchImpl: deps.fetchImpl,
      requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
    };
    return fetchJson(config, '/v1/guest/prices', {
      query: {
        country: normalizeFiveSimCountryId(countryConfig?.id),
        product: DEFAULT_PRODUCT,
      },
      actionLabel: '5sim 查询价格',
      requireAuth: false,
    });
  }

  async function fetchProducts(state = {}, countryConfig = resolveCountryConfig(state), deps = {}) {
    const config = {
      baseUrl: DEFAULT_BASE_URL,
      fetchImpl: deps.fetchImpl,
      requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
    };
    const operator = normalizeFiveSimOperator(state.fiveSimOperator);
    return fetchJson(
      config,
      `/v1/guest/products/${encodeURIComponent(normalizeFiveSimCountryId(countryConfig?.id))}/${encodeURIComponent(operator)}`,
      {
        actionLabel: '5sim 查询产品价格',
        requireAuth: false,
      }
    );
  }

  async function resolvePricePlan(state = {}, countryConfig = resolveCountryConfig(state), deps = {}) {
    const userLimitText = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
    const userLimit = userLimitText ? Number(userLimitText) : null;
    let priceCandidates = [];

    try {
      const productsPayload = await fetchProducts(state, countryConfig, deps);
      const openaiProduct = productsPayload?.[DEFAULT_PRODUCT];
      const productPrice = normalizePrice(openaiProduct?.Price ?? openaiProduct?.price ?? openaiProduct?.cost);
      const productQty = Number(openaiProduct?.Qty ?? openaiProduct?.qty ?? openaiProduct?.count);
      if (productPrice !== null && (!Number.isFinite(productQty) || productQty > 0)) {
        priceCandidates.push(productPrice);
      }
    } catch (_) {
      // Products endpoint is only used to find the buy-compatible operator price.
    }

    try {
      const payload = await fetchPrices(state, countryConfig, deps);
      priceCandidates = [
        ...priceCandidates,
        ...buildSortedUniquePriceCandidates(
          collectPriceEntries(payload, [])
            .filter((entry) => entry.inStock)
            .map((entry) => entry.cost)
        ),
      ];
    } catch (_) {
      // Best-effort lookup. Purchase can still be attempted without a catalog price.
    }
    priceCandidates = buildSortedUniquePriceCandidates(priceCandidates);

    const minCatalogPrice = priceCandidates.length > 0 ? priceCandidates[0] : null;
    if (userLimit !== null) {
      const bounded = priceCandidates.filter((price) => price <= userLimit);
      return {
        prices: bounded.length > 0 ? [userLimit, ...bounded.filter((price) => price !== userLimit)] : [userLimit],
        userLimit,
        minCatalogPrice,
      };
    }

    if (priceCandidates.length > 0) {
      return { prices: priceCandidates, userLimit: null, minCatalogPrice };
    }
    return { prices: [null], userLimit: null, minCatalogPrice: null };
  }

  function normalizeActivation(record, fallback = {}) {
    if (!record || typeof record !== 'object' || Array.isArray(record)) {
      return null;
    }
    const activationId = String(record.activationId ?? record.id ?? '').trim();
    const phoneNumber = String(record.phoneNumber ?? record.phone ?? '').trim();
    if (!activationId || !phoneNumber) {
      return null;
    }
    const countryId = getCountryIdFromPayload(record, fallback.countryId || DEFAULT_COUNTRY_ID);
    const countryLabel = getCountryLabelFromPayload(record, fallback.countryLabel || countryId, countryId);
    return {
      activationId,
      phoneNumber,
      provider: PROVIDER_ID,
      serviceCode: DEFAULT_PRODUCT,
      countryId,
      countryLabel,
      successfulUses: Math.max(0, Math.floor(Number(record.successfulUses) || 0)),
      maxUses: Math.max(1, Math.floor(Number(record.maxUses) || DEFAULT_MAX_USES)),
      operator: normalizeFiveSimOperator(record.operator || fallback.operator),
      ...(record.price !== undefined ? { price: Number(record.price) } : {}),
      ...(record.status ? { status: String(record.status) } : {}),
    };
  }

  function isNoNumbersPayload(payloadOrMessage) {
    const text = describePayload(payloadOrMessage);
    return /no\s+free\s+phones|no\s+numbers|not\s+found/i.test(text);
  }

  function isRateLimitPayload(payloadOrMessage) {
    const text = describePayload(payloadOrMessage);
    return /rate\s*limit|too\s*many\s*requests|request\s*limit|429/i.test(text);
  }

  function buildFiveSimRateLimitError(details = []) {
    const suffix = Array.isArray(details) && details.length
      ? `：${details.join(' | ')}。`
      : '。';
    return new Error(`${FIVE_SIM_RATE_LIMIT_ERROR_PREFIX}5sim 购买接口触发限流，请稍后再试${suffix}`);
  }

  function isTerminalError(payloadOrMessage) {
    const text = describePayload(payloadOrMessage);
    return /not\s+enough\s+(?:user\s+)?balance|not\s+enough\s+rating|unauthorized|invalid\s+token|banned|bad\s+(?:country|operator)|no\s+product|server\s+offline/i.test(text);
  }

  function assertMaxPriceCompatibleWithOperator(state = {}) {
    const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
    const operator = normalizeFiveSimOperator(state.fiveSimOperator);
    if (maxPrice && operator !== DEFAULT_OPERATOR) {
      throw new Error('5sim maxPrice only works when operator is "any"; clear the price limit or switch operator to any before buying a number.');
    }
  }

  async function buyActivationWithPrice(state = {}, countryConfig, maxPrice, deps = {}) {
    const config = resolveConfig(state, deps);
    const operator = normalizeFiveSimOperator(state.fiveSimOperator);
    const query = {};
    if (maxPrice !== null && maxPrice !== undefined) {
      query.maxPrice = maxPrice;
    }
    if (state.fiveSimReuseEnabled !== false) {
      query.reuse = 1;
    }
    const payload = await fetchJson(
      config,
      `/v1/user/buy/activation/${encodeURIComponent(normalizeFiveSimCountryId(countryConfig.id))}/${encodeURIComponent(operator)}/${DEFAULT_PRODUCT}`,
      {
        query,
        actionLabel: '5sim 购买手机号',
      }
    );
    const activation = normalizeActivation(payload, {
      countryId: countryConfig.id,
      countryLabel: countryConfig.label,
      operator,
    });
    if (!activation) {
      const error = new Error(`5sim 购买手机号返回不可用响应：${describePayload(payload) || '空响应'}`);
      error.payload = payload;
      throw error;
    }
    return activation;
  }

  async function requestActivation(state = {}, options = {}, deps = {}) {
    assertMaxPriceCompatibleWithOperator(state);

    const allCountryCandidates = resolveCountryCandidates(state);
    const blockedCountryIds = new Set(
      (Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : [])
        .map((value) => normalizeFiveSimCountryId(value, ''))
        .filter(Boolean)
    );
    let countryCandidates = allCountryCandidates.filter((entry) => !blockedCountryIds.has(normalizeFiveSimCountryId(entry.id, '')));
    if (!countryCandidates.length) {
      countryCandidates = allCountryCandidates;
      if (blockedCountryIds.size && typeof deps.addLog === 'function') {
        await deps.addLog(
          '步骤 9：已选 5sim 国家均达到临时收码失败跳过阈值，本轮解除跳过并重新尝试。',
          'warn'
        );
      }
    }

    const acquirePriority = String(state?.heroSmsAcquirePriority || 'country').trim().toLowerCase() === 'price' ? 'price' : 'country';
    const countryAttempts = countryCandidates.map((countryConfig, index) => ({
      index,
      countryConfig,
      pricePlan: null,
      orderingPrice: Number.POSITIVE_INFINITY,
    }));

    if (acquirePriority === 'price') {
      for (const attempt of countryAttempts) {
        const pricePlan = await resolvePricePlan(state, attempt.countryConfig, deps);
        const numericPrices = Array.isArray(pricePlan?.prices)
          ? pricePlan.prices.map(Number).filter((value) => Number.isFinite(value) && value >= 0)
          : [];
        const minCandidatePrice = numericPrices.length ? Math.min(...numericPrices) : null;
        const cappedByUserLimit = (
          pricePlan?.userLimit !== null
          && pricePlan?.userLimit !== undefined
          && pricePlan?.minCatalogPrice !== null
          && pricePlan?.minCatalogPrice !== undefined
          && Number(pricePlan.minCatalogPrice) > Number(pricePlan.userLimit)
        );
        attempt.pricePlan = pricePlan;
        attempt.orderingPrice = cappedByUserLimit
          ? Number.POSITIVE_INFINITY
          : (minCandidatePrice !== null ? minCandidatePrice : Number.POSITIVE_INFINITY);
      }
      countryAttempts.sort((left, right) => (
        left.orderingPrice !== right.orderingPrice
          ? left.orderingPrice - right.orderingPrice
          : left.index - right.index
      ));
    }

    const noNumbersByCountry = [];
    const rateLimitByCountry = [];
    let lastError = null;
    let lastFailureText = '';
    let sawOnlyRetryableFailures = true;
    for (const attempt of countryAttempts) {
      const countryConfig = attempt.countryConfig;
      const countryFailures = [];
      const pricePlan = attempt.pricePlan || await resolvePricePlan(state, countryConfig, deps);
      for (const maxPrice of pricePlan.prices) {
        try {
          const activation = await buyActivationWithPrice(state, countryConfig, maxPrice, deps);
          if (activation) {
            return activation;
          }
          lastFailureText = '空响应';
        } catch (error) {
          const payloadOrMessage = error?.payload || error?.message;
          if (isTerminalError(payloadOrMessage)) {
            throw new Error(`5sim 购买手机号失败：${describePayload(payloadOrMessage) || '空响应'}`);
          }
          const failureText = describePayload(payloadOrMessage) || lastFailureText;
          if (isRateLimitPayload(payloadOrMessage)) {
            lastFailureText = failureText;
            countryFailures.push(failureText || 'rate limit');
            continue;
          }
          if (isNoNumbersPayload(payloadOrMessage)) {
            lastFailureText = failureText;
            countryFailures.push(failureText || '无可用号码');
            continue;
          }
          sawOnlyRetryableFailures = false;
          lastError = error;
          lastFailureText = failureText;
        }
      }
      const countryFailureText = countryFailures.length
        ? Array.from(new Set(countryFailures)).join(', ')
        : (lastFailureText || '无可用号码');
      if (countryFailures.some((text) => isRateLimitPayload(text))) {
        rateLimitByCountry.push(`${countryConfig.label}: ${countryFailureText}`);
      } else {
        noNumbersByCountry.push(`${countryConfig.label}: ${countryFailureText}`);
      }
    }

    if (rateLimitByCountry.length) {
      throw buildFiveSimRateLimitError(rateLimitByCountry);
    }

    if (noNumbersByCountry.length && sawOnlyRetryableFailures) {
      throw new Error(`5sim 已尝试 ${countryCandidates.length} 个候选国家，均无可用号码：${noNumbersByCountry.join(' | ')}。`);
    }
    if (lastError) {
      throw lastError;
    }
    throw new Error(`5sim 获取手机号失败，最后状态：${lastFailureText || '未知'}。`);
  }

  async function reuseActivation(state = {}, activation, deps = {}) {
    const normalizedActivation = normalizeActivation(activation);
    if (!normalizedActivation) {
      throw new Error('缺少可复用的 5sim 手机号订单。');
    }
    const phoneDigits = String(normalizedActivation.phoneNumber || '').replace(/\D+/g, '');
    if (!phoneDigits) {
      throw new Error('可复用的 5sim 手机号无效。');
    }
    const config = resolveConfig(state, deps);
    const numberWithoutPlus = String(normalizedActivation.phoneNumber || '')
      .replace(/^\+/, '')
      .replace(/[^0-9]+/g, '');
    const payload = await fetchJson(config, `/v1/user/reuse/${DEFAULT_PRODUCT}/${numberWithoutPlus || phoneDigits}`, {
      actionLabel: '5sim 复用手机号',
    });
    return normalizeActivation(payload, normalizedActivation);
  }

  async function finishActivation(state = {}, activation, deps = {}) {
    const normalizedActivation = normalizeActivation(activation);
    if (!normalizedActivation) return '';
    const config = resolveConfig(state, deps);
    const payload = await fetchJson(config, `/v1/user/finish/${encodeURIComponent(normalizedActivation.activationId)}`, {
      actionLabel: '5sim 完成订单',
    });
    return describePayload(payload);
  }

  async function cancelActivation(state = {}, activation, deps = {}) {
    const normalizedActivation = normalizeActivation(activation);
    if (!normalizedActivation) return '';
    const config = resolveConfig(state, deps);
    const payload = await fetchJson(config, `/v1/user/cancel/${encodeURIComponent(normalizedActivation.activationId)}`, {
      actionLabel: '5sim 取消订单',
    });
    return describePayload(payload);
  }

  async function banActivation(state = {}, activation, deps = {}) {
    const normalizedActivation = normalizeActivation(activation);
    if (!normalizedActivation) return '';
    const config = resolveConfig(state, deps);
    const payload = await fetchJson(config, `/v1/user/ban/${encodeURIComponent(normalizedActivation.activationId)}`, {
      actionLabel: '5sim 拉黑号码',
    });
    return describePayload(payload);
  }

  function extractVerificationCode(rawCodeOrText) {
    const trimmed = String(rawCodeOrText || '').trim();
    if (!trimmed) {
      return '';
    }
    const digitMatch = trimmed.match(/\b(\d{4,8})\b/);
    return digitMatch?.[1] || trimmed;
  }

  function extractCodeFromOrder(payload) {
    const smsList = Array.isArray(payload?.sms) ? payload.sms : [];
    for (let index = smsList.length - 1; index >= 0; index -= 1) {
      const message = smsList[index] || {};
      const code = extractVerificationCode(message.code) || extractVerificationCode(message.text);
      if (code) {
        return code;
      }
    }
    return '';
  }

  async function pollActivationCode(state = {}, activation, options = {}, deps = {}) {
    const normalizedActivation = normalizeActivation(activation);
    if (!normalizedActivation) {
      throw new Error('缺少手机号接码订单。');
    }
    const config = resolveConfig(state, deps);
    const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 180000);
    const intervalMs = Math.max(1000, Number(options.intervalMs) || 5000);
    const maxRoundsRaw = Math.floor(Number(options.maxRounds));
    const maxRounds = Number.isFinite(maxRoundsRaw) && maxRoundsRaw > 0 ? maxRoundsRaw : 0;
    const start = Date.now();
    let pollCount = 0;
    let lastResponse = '';

    while (Date.now() - start < timeoutMs) {
      if (maxRounds > 0 && pollCount >= maxRounds) {
        break;
      }
      deps.throwIfStopped?.();
      const payload = await fetchJson(config, `/v1/user/check/${encodeURIComponent(normalizedActivation.activationId)}`, {
        actionLabel: '5sim 查询验证码',
      });
      pollCount += 1;
      lastResponse = describePayload(payload);
      if (typeof options.onStatus === 'function') {
        await options.onStatus({
          activation: normalizedActivation,
          elapsedMs: Date.now() - start,
          pollCount,
          statusText: String(payload?.status || lastResponse || '未知'),
          timeoutMs,
        });
      }
      const code = extractCodeFromOrder(payload);
      if (code) {
        return code;
      }
      const status = String(payload?.status || '').trim().toUpperCase();
      if (['CANCELED', 'BANNED', 'FINISHED', 'TIMEOUT'].includes(status)) {
        throw new Error(`5sim 查询验证码失败：订单状态 ${status}`);
      }
      if (typeof options.onWaitingForCode === 'function') {
        await options.onWaitingForCode({
          activation: normalizedActivation,
          elapsedMs: Date.now() - start,
          pollCount,
          statusText: String(payload?.status || lastResponse || '未知'),
          timeoutMs,
        });
      }
      await deps.sleepWithStop(intervalMs);
    }

    const suffix = lastResponse ? ` 5sim 最后状态：${lastResponse}` : '';
    throw new Error(`PHONE_CODE_TIMEOUT::等待手机验证码超时。${suffix}`);
  }

  function createProvider(deps = {}) {
    const providerDeps = {
      fetchImpl: deps.fetchImpl,
      sleepWithStop: deps.sleepWithStop,
      throwIfStopped: deps.throwIfStopped,
      addLog: deps.addLog,
      requestTimeoutMs: deps.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
    };
    return {
      id: PROVIDER_ID,
      label: '5sim',
      defaultCountryId: DEFAULT_COUNTRY_ID,
      defaultCountryLabel: DEFAULT_COUNTRY_LABEL,
      supportedCountries: SUPPORTED_COUNTRY_ITEMS,
      defaultProduct: DEFAULT_PRODUCT,
      defaultOperator: DEFAULT_OPERATOR,
      normalizeCountryId: normalizeFiveSimCountryId,
      normalizeCountryLabel: normalizeFiveSimCountryLabel,
      formatCountryLabel: formatFiveSimCountryLabel,
      normalizeCountryFallback: normalizeFiveSimCountryFallback,
      normalizeMaxPrice: normalizeFiveSimMaxPrice,
      normalizeOperator: normalizeFiveSimOperator,
      resolveCountryCandidates,
      requestActivation: (state, options) => requestActivation(state, options, providerDeps),
      reuseActivation: (state, activation) => reuseActivation(state, activation, providerDeps),
      finishActivation: (state, activation) => finishActivation(state, activation, providerDeps),
      cancelActivation: (state, activation) => cancelActivation(state, activation, providerDeps),
      banActivation: (state, activation) => banActivation(state, activation, providerDeps),
      pollActivationCode: (state, activation, options) => pollActivationCode(state, activation, options, providerDeps),
      fetchBalance: (state) => fetchBalance(state, providerDeps),
      fetchCountries: (state) => fetchCountries(state, providerDeps),
      fetchPrices: (state, countryConfig) => fetchPrices(state, countryConfig, providerDeps),
      collectPriceEntries,
      describePayload,
    };
  }

  return {
    PROVIDER_ID,
    FIVE_SIM_RATE_LIMIT_ERROR_PREFIX,
    DEFAULT_COUNTRY_ID,
    DEFAULT_COUNTRY_LABEL,
    DEFAULT_OPERATOR,
    DEFAULT_PRODUCT,
    SUPPORTED_COUNTRY_ITEMS,
    createProvider,
    normalizeFiveSimCountryFallback,
    normalizeFiveSimCountryId,
    normalizeFiveSimCountryLabel,
    formatFiveSimCountryLabel,
    normalizeFiveSimMaxPrice,
    normalizeFiveSimOperator,
  };
});
