// sidepanel/ip-proxy-panel.js — IP代理面板（轻量解耦）
function normalizeIpProxyService(value = '') {
  const normalized = String(value || '').trim().toLowerCase();
  const enabledServices = Array.isArray(globalThis.IP_PROXY_ENABLED_SERVICES)
    ? globalThis.IP_PROXY_ENABLED_SERVICES
    : [DEFAULT_IP_PROXY_SERVICE];
  if (enabledServices.includes(normalized)) {
    return normalized;
  }
  return DEFAULT_IP_PROXY_SERVICE;
}

const ipProxyActionState = {
  busy: false,
  action: '',
  startedAt: 0,
};
const IP_PROXY_ACTION_LOCK_TIMEOUT_MS = 25000;
let ipProxyDeferredProbeTimer = 0;
const IP_PROXY_SECTION_EXPANDED_STORAGE_KEY = 'multipage-ip-proxy-section-expanded';
let ipProxySectionExpanded = false;

function readIpProxySectionExpanded() {
  try {
    return globalThis.localStorage?.getItem(IP_PROXY_SECTION_EXPANDED_STORAGE_KEY) === '1';
  } catch (err) {
    return false;
  }
}

function persistIpProxySectionExpanded(expanded) {
  try {
    if (expanded) {
      globalThis.localStorage?.setItem(IP_PROXY_SECTION_EXPANDED_STORAGE_KEY, '1');
    } else {
      globalThis.localStorage?.removeItem(IP_PROXY_SECTION_EXPANDED_STORAGE_KEY);
    }
  } catch (err) {
    // Ignore storage errors; the in-memory collapsed state is still enough for this session.
  }
}

function setIpProxySectionExpanded(expanded) {
  ipProxySectionExpanded = Boolean(expanded);
  persistIpProxySectionExpanded(ipProxySectionExpanded);
  if (typeof updateIpProxyUI === 'function') {
    updateIpProxyUI(latestState);
  }
}

function toggleIpProxySectionExpanded() {
  setIpProxySectionExpanded(!ipProxySectionExpanded);
}

function initIpProxySectionExpandedState() {
  ipProxySectionExpanded = readIpProxySectionExpanded();
  if (typeof updateIpProxyUI === 'function') {
    updateIpProxyUI(latestState);
  }
}

function normalizeIpProxyActionType(value = '') {
  const normalized = String(value || '').trim().toLowerCase();
  return normalized || 'action';
}

function getIpProxyActionLabel(action = '') {
  const normalized = normalizeIpProxyActionType(action);
  if (normalized === 'refresh') return '同步代理';
  if (normalized === 'next') return '切换代理';
  if (normalized === 'change') return '会话换出口';
  if (normalized === 'probe') return '检测出口';
  return '代理操作';
}

function isIpProxyActionBusy() {
  return Boolean(ipProxyActionState.busy);
}

function getIpProxyActionState() {
  return {
    busy: Boolean(ipProxyActionState.busy),
    action: normalizeIpProxyActionType(ipProxyActionState.action),
    startedAt: Number(ipProxyActionState.startedAt) || 0,
  };
}

function setIpProxyActionBusy(action = '', busy = false) {
  ipProxyActionState.busy = Boolean(busy);
  ipProxyActionState.action = ipProxyActionState.busy ? normalizeIpProxyActionType(action) : '';
  ipProxyActionState.startedAt = ipProxyActionState.busy ? Date.now() : 0;
}

async function runIpProxyActionWithLock(action = '', runner) {
  if (typeof runner !== 'function') {
    throw new Error('代理操作执行器无效。');
  }
  const nextAction = normalizeIpProxyActionType(action);
  const currentState = getIpProxyActionState();
  if (currentState.busy) {
    if (typeof showToast === 'function') {
      showToast(`${getIpProxyActionLabel(currentState.action)}进行中，请稍候。`, 'info', 1600);
    }
    return { skipped: true };
  }

  setIpProxyActionBusy(nextAction, true);
  if (typeof updateIpProxyUI === 'function') {
    updateIpProxyUI(latestState);
  }

  const actionLabel = getIpProxyActionLabel(nextAction);
  const timeoutMs = Math.max(5000, Number(IP_PROXY_ACTION_LOCK_TIMEOUT_MS) || 25000);
  let timeoutId = 0;
  try {
    const value = await Promise.race([
      Promise.resolve().then(() => runner()),
      new Promise((_, reject) => {
        timeoutId = globalThis.setTimeout(() => {
          reject(new Error(`${actionLabel}超时（${Math.round(timeoutMs / 1000)} 秒），已自动解锁，请重试。`));
        }, timeoutMs);
      }),
    ]);
    return { skipped: false, value };
  } finally {
    if (timeoutId) {
      globalThis.clearTimeout(timeoutId);
    }
    setIpProxyActionBusy(nextAction, false);
    if (typeof updateIpProxyUI === 'function') {
      updateIpProxyUI(latestState);
    }
  }
}

function scheduleIpProxyExitProbe(options = {}) {
  const {
    silent = true,
    delayMs = 80,
  } = options;
  const delay = Math.max(0, Number(delayMs) || 0);
  if (ipProxyDeferredProbeTimer) {
    globalThis.clearTimeout(ipProxyDeferredProbeTimer);
    ipProxyDeferredProbeTimer = 0;
  }
  ipProxyDeferredProbeTimer = globalThis.setTimeout(() => {
    ipProxyDeferredProbeTimer = 0;
    Promise.resolve()
      .then(() => probeIpProxyExit({ silent }))
      .catch(() => {});
  }, delay);
}

function normalizeIpProxyMode(value = '') {
  const normalized = String(value || '').trim().toLowerCase();
  return SUPPORTED_IP_PROXY_MODES.includes(normalized) ? normalized : DEFAULT_IP_PROXY_MODE;
}

function isIpProxyApiModeAvailable() {
  return Boolean(typeof IP_PROXY_API_MODE_ENABLED === 'undefined'
    ? false
    : IP_PROXY_API_MODE_ENABLED);
}

function isIpProxyAccountListAvailable() {
  return Boolean(typeof IP_PROXY_ACCOUNT_LIST_ENABLED === 'undefined'
    ? true
    : IP_PROXY_ACCOUNT_LIST_ENABLED);
}

function normalizeIpProxyModeForCurrentRelease(value = '') {
  const normalized = normalizeIpProxyMode(value);
  if (!isIpProxyApiModeAvailable() && normalized === 'api') {
    return 'account';
  }
  return normalized;
}

function normalizeIpProxyProtocol(value = '') {
  const normalized = String(value || '').trim().toLowerCase();
  return SUPPORTED_IP_PROXY_PROTOCOLS.includes(normalized) ? normalized : DEFAULT_IP_PROXY_PROTOCOL;
}

function normalizeIpProxyPort(value = '') {
  const numeric = Number.parseInt(String(value || '').trim(), 10);
  if (!Number.isInteger(numeric) || numeric <= 0 || numeric > 65535) {
    return 0;
  }
  return numeric;
}

function normalizeIpProxyPoolTargetCount(value = '', fallback = 20) {
  const rawValue = String(value ?? '').trim();
  if (!rawValue) {
    return String(Math.max(1, Math.min(500, Number(fallback) || 20)));
  }
  const numeric = Number.parseInt(rawValue, 10);
  if (!Number.isInteger(numeric)) {
    return String(Math.max(1, Math.min(500, Number(fallback) || 20)));
  }
  return String(Math.max(1, Math.min(500, numeric)));
}

function normalizeIpProxyAccountLifeMinutes(value = '', fallback = '') {
  const rawValue = String(value ?? '').trim();
  if (!rawValue) {
    return String(fallback || '').trim();
  }
  const numeric = Number.parseInt(rawValue, 10);
  if (!Number.isInteger(numeric)) {
    return String(fallback || '').trim();
  }
  return String(Math.max(1, Math.min(1440, numeric)));
}

function normalizeIpProxyAccountSessionPrefix(value = '') {
  return String(value || '').trim().replace(/[^A-Za-z0-9_-]/g, '').slice(0, 32);
}

function normalizeIpProxyAccountList(value = '') {
  return String(value || '')
    .replace(/\r/g, '')
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => {
      if (!line) {
        return false;
      }
      // 与后台保持一致：支持 # / // / ; 注释行，注释行不参与生效。
      if (/^(?:#|\/\/|;)/.test(line)) {
        return false;
      }
      return true;
    })
    .join('\n');
}

function normalizeIpProxyServiceProfile(rawValue = {}) {
  const raw = (rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue))
    ? rawValue
    : {};
  return {
    mode: normalizeIpProxyModeForCurrentRelease(raw.mode),
    apiUrl: String(raw.apiUrl || '').trim(),
    accountList: normalizeIpProxyAccountList(raw.accountList || ''),
    accountSessionPrefix: normalizeIpProxyAccountSessionPrefix(raw.accountSessionPrefix || ''),
    accountLifeMinutes: normalizeIpProxyAccountLifeMinutes(raw.accountLifeMinutes || ''),
    poolTargetCount: normalizeIpProxyPoolTargetCount(raw.poolTargetCount || '', 20),
    autoSyncEnabled: Boolean(raw.autoSyncEnabled),
    autoSyncIntervalMinutes: String(Math.max(1, Math.min(1440, Number.parseInt(String(raw.autoSyncIntervalMinutes ?? '').trim(), 10) || 15))),
    host: String(raw.host || '').trim(),
    port: String(normalizeIpProxyPort(raw.port || '') || ''),
    protocol: normalizeIpProxyProtocol(raw.protocol),
    username: String(raw.username || '').trim(),
    password: String(raw.password || ''),
    region: String(raw.region || '').trim(),
  };
}

function buildIpProxyServiceProfileFromFlatState(state = {}) {
  return normalizeIpProxyServiceProfile({
    mode: state?.ipProxyMode,
    apiUrl: state?.ipProxyApiUrl,
    accountList: state?.ipProxyAccountList,
    accountSessionPrefix: state?.ipProxyAccountSessionPrefix,
    accountLifeMinutes: state?.ipProxyAccountLifeMinutes,
    poolTargetCount: state?.ipProxyPoolTargetCount,
    autoSyncEnabled: state?.ipProxyAutoSyncEnabled,
    autoSyncIntervalMinutes: state?.ipProxyAutoSyncIntervalMinutes,
    host: state?.ipProxyHost,
    port: state?.ipProxyPort,
    protocol: state?.ipProxyProtocol,
    username: state?.ipProxyUsername,
    password: state?.ipProxyPassword,
    region: state?.ipProxyRegion,
  });
}

function normalizeIpProxyServiceProfiles(rawValue = {}, fallbackState = {}) {
  const raw = (rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue))
    ? rawValue
    : {};
  const fallbackProfile = buildIpProxyServiceProfileFromFlatState(fallbackState);
  const result = {};
  SUPPORTED_IP_PROXY_SERVICES.forEach((service) => {
    const serviceRaw = raw[service];
    if (serviceRaw && typeof serviceRaw === 'object' && !Array.isArray(serviceRaw)) {
      result[service] = normalizeIpProxyServiceProfile(serviceRaw);
      return;
    }
    result[service] = normalizeIpProxyServiceProfile(fallbackProfile);
  });
  return result;
}

function getIpProxyServiceProfilesFromState(state = latestState) {
  return normalizeIpProxyServiceProfiles(state?.ipProxyServiceProfiles || {}, state || {});
}

function getIpProxyServiceProfile(service = '', state = latestState) {
  const selectedService = normalizeIpProxyService(service || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE);
  const profiles = getIpProxyServiceProfilesFromState(state);
  const profile = normalizeIpProxyServiceProfile(profiles[selectedService] || {});
  const resolvedRegion = resolveIpProxyRegionFromInputs({
    service: selectedService,
    mode: profile.mode,
    host: profile.host,
    username: profile.username,
    region: profile.region,
  });
  if (resolvedRegion) {
    profile.region = resolvedRegion;
  }
  return profile;
}

function inferRegionFromProxyUsernameForPanel(username = '') {
  const text = String(username || '').trim();
  if (!text) return '';
  const match = text.match(/(?:^|[-_])(?:region|area|country)[-_:]?([A-Za-z]{2})\b/i);
  if (!match) return '';
  return String(match[1] || '').trim().toUpperCase();
}

function inferRegionFromProxyHostForPanel(host = '') {
  const text = String(host || '').trim().toLowerCase().replace(/\.$/, '');
  if (!text || !text.includes('.')) return '';
  const firstLabel = String(text.split('.')[0] || '').trim();
  if (!/^[a-z]{2}$/.test(firstLabel)) return '';
  return firstLabel.toUpperCase();
}

function normalizeExplicitRegionForPanel(region = '') {
  const text = String(region || '').trim().toUpperCase().replace(/[^A-Z]/g, '');
  return /^[A-Z]{2}$/.test(text) ? text : '';
}

function resolveIpProxyRegionFromInputs(options = {}) {
  const selectedService = normalizeIpProxyService(options?.service || DEFAULT_IP_PROXY_SERVICE);
  const selectedMode = normalizeIpProxyModeForCurrentRelease(options?.mode || DEFAULT_IP_PROXY_MODE);
  if (selectedMode !== 'account') {
    return '';
  }

  const host = String(options?.host || '').trim();
  const username = String(options?.username || '').trim();
  const region = String(options?.region || '').trim();
  if (selectedService === '711proxy') {
    const resolvedCode = typeof resolve711ProxyRegionFromInputs === 'function'
      ? resolve711ProxyRegionFromInputs({ host, username, region })
      : '';
    if (resolvedCode) {
      return String(resolvedCode || '').trim().toUpperCase();
    }
  }

  const fromUsername = inferRegionFromProxyUsernameForPanel(username);
  if (fromUsername) {
    return fromUsername;
  }
  const fromHost = inferRegionFromProxyHostForPanel(host);
  if (fromHost) {
    return fromHost;
  }
  return normalizeExplicitRegionForPanel(region);
}

function hasCurrentInputAccountListEntries() {
  if (!isIpProxyAccountListAvailable()) {
    return false;
  }
  return normalizeIpProxyAccountList(inputIpProxyAccountList?.value || '')
    .split('\n')
    .map((line) => line.trim())
    .filter(Boolean)
    .length > 0;
}

function syncIpProxyRegionInputFromCredentials(options = {}) {
  if (!inputIpProxyRegion) {
    return '';
  }
  const selectedService = normalizeIpProxyService(
    selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
  );
  const selectedMode = normalizeIpProxyModeForCurrentRelease(getSelectedIpProxyMode());
  if (selectedMode !== 'account' || hasCurrentInputAccountListEntries()) {
    return '';
  }
  const force = Boolean(options?.force);
  const currentRegion = String(inputIpProxyRegion.value || '').trim();
  const resolvedRegion = resolveIpProxyRegionFromInputs({
    service: selectedService,
    mode: selectedMode,
    host: inputIpProxyHost?.value || '',
    username: inputIpProxyUsername?.value || '',
    region: currentRegion,
  });
  if (!resolvedRegion) {
    return '';
  }
  const normalizedCurrent = normalizeExplicitRegionForPanel(currentRegion);
  if (force || !normalizedCurrent) {
    inputIpProxyRegion.value = resolvedRegion;
  }
  return resolvedRegion;
}

function buildCurrentIpProxyServiceProfileFromInputs() {
  const selectedService = normalizeIpProxyService(
    selectIpProxyService?.value || latestState?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
  );
  const selectedMode = normalizeIpProxyMode(getSelectedIpProxyMode());
  const effectiveMode = normalizeIpProxyModeForCurrentRelease(selectedMode);
  const rawRegion = String(inputIpProxyRegion?.value || '').trim();
  const finalRegion = resolveIpProxyRegionFromInputs({
    service: selectedService,
    mode: effectiveMode,
    host: inputIpProxyHost?.value || '',
    username: inputIpProxyUsername?.value || '',
    region: rawRegion,
  }) || rawRegion;
  return normalizeIpProxyServiceProfile({
    mode: effectiveMode,
    apiUrl: inputIpProxyApiUrl?.value || '',
    accountList: isIpProxyAccountListAvailable() ? (inputIpProxyAccountList?.value || '') : '',
    accountSessionPrefix: inputIpProxyAccountSessionPrefix?.value || '',
    accountLifeMinutes: inputIpProxyAccountLifeMinutes?.value || '',
    poolTargetCount: inputIpProxyPoolTargetCount?.value || '',
    autoSyncEnabled: Boolean(inputIpProxyAutoSyncEnabled?.checked),
    autoSyncIntervalMinutes: inputIpProxyAutoSyncIntervalMinutes?.value || '',
    host: inputIpProxyHost?.value || '',
    port: inputIpProxyPort?.value || '',
    protocol: selectIpProxyProtocol?.value || '',
    username: inputIpProxyUsername?.value || '',
    password: inputIpProxyPassword?.value || '',
    region: finalRegion,
  });
}

function buildIpProxyServiceProfilesPatch(selectedService = '', state = latestState) {
  const nextService = normalizeIpProxyService(
    selectedService
    || selectIpProxyService?.value
    || state?.ipProxyService
    || DEFAULT_IP_PROXY_SERVICE
  );
  const profiles = getIpProxyServiceProfilesFromState(state || {});
  profiles[nextService] = buildCurrentIpProxyServiceProfileFromInputs();
  return normalizeIpProxyServiceProfiles(profiles, state || {});
}

function buildIpProxyStatePatchFromServiceProfile(service = '', profile = {}) {
  const normalizedService = normalizeIpProxyService(service || DEFAULT_IP_PROXY_SERVICE);
  const normalizedProfile = normalizeIpProxyServiceProfile(profile);
  return {
    ipProxyService: normalizedService,
    ipProxyMode: normalizedProfile.mode,
    ipProxyApiUrl: normalizedProfile.apiUrl,
    ipProxyAccountList: normalizedProfile.accountList,
    ipProxyAccountSessionPrefix: normalizedProfile.accountSessionPrefix,
    ipProxyAccountLifeMinutes: normalizedProfile.accountLifeMinutes,
    ipProxyPoolTargetCount: normalizedProfile.poolTargetCount,
    ipProxyAutoSyncEnabled: normalizedProfile.autoSyncEnabled,
    ipProxyAutoSyncIntervalMinutes: Number.parseInt(String(normalizedProfile.autoSyncIntervalMinutes || '15').trim(), 10) || 15,
    ipProxyHost: normalizedProfile.host,
    ipProxyPort: normalizedProfile.port,
    ipProxyProtocol: normalizedProfile.protocol,
    ipProxyUsername: normalizedProfile.username,
    ipProxyPassword: normalizedProfile.password,
    ipProxyRegion: normalizedProfile.region,
  };
}

function applyIpProxyServiceProfileToInputs(profile = {}, options = {}) {
  const { keepMode = false } = options;
  const normalizedProfile = normalizeIpProxyServiceProfile(profile);
  if (!keepMode) {
    setIpProxyMode(normalizedProfile.mode);
  }
  if (inputIpProxyApiUrl) {
    inputIpProxyApiUrl.value = normalizedProfile.apiUrl;
  }
  if (inputIpProxyAccountList) {
    inputIpProxyAccountList.value = normalizedProfile.accountList;
  }
  if (inputIpProxyAccountSessionPrefix) {
    inputIpProxyAccountSessionPrefix.value = normalizedProfile.accountSessionPrefix;
  }
  if (inputIpProxyAccountLifeMinutes) {
    inputIpProxyAccountLifeMinutes.value = normalizedProfile.accountLifeMinutes;
  }
  if (inputIpProxyPoolTargetCount) {
    inputIpProxyPoolTargetCount.value = normalizedProfile.poolTargetCount;
  }
  if (inputIpProxyAutoSyncEnabled) {
    inputIpProxyAutoSyncEnabled.checked = Boolean(normalizedProfile.autoSyncEnabled);
  }
  if (inputIpProxyAutoSyncIntervalMinutes) {
    inputIpProxyAutoSyncIntervalMinutes.value = String(normalizedProfile.autoSyncIntervalMinutes || '15');
  }
  if (inputIpProxyHost) {
    inputIpProxyHost.value = normalizedProfile.host;
  }
  if (inputIpProxyPort) {
    inputIpProxyPort.value = normalizedProfile.port;
  }
  if (selectIpProxyProtocol) {
    selectIpProxyProtocol.value = normalizedProfile.protocol;
  }
  if (inputIpProxyUsername) {
    inputIpProxyUsername.value = normalizedProfile.username;
  }
  if (inputIpProxyPassword) {
    inputIpProxyPassword.value = normalizedProfile.password;
  }
  if (inputIpProxyRegion) {
    inputIpProxyRegion.value = normalizedProfile.region;
  }
}

function getSelectedIpProxyEnabled() {
  if (inputIpProxyEnabled) {
    return Boolean(inputIpProxyEnabled.checked);
  }
  const activeButton = ipProxyEnabledButtons.find((button) => button.classList.contains('is-active'));
  return String(activeButton?.dataset?.ipProxyEnabled) === 'true';
}

function setIpProxyEnabled(enabled) {
  const nextEnabled = Boolean(enabled);
  if (inputIpProxyEnabled) {
    inputIpProxyEnabled.checked = nextEnabled;
  }
  ipProxyEnabledButtons.forEach((button) => {
    const buttonValue = String(button?.dataset?.ipProxyEnabled) === 'true';
    button.classList.toggle('is-active', buttonValue === nextEnabled);
    button.setAttribute('aria-pressed', String(buttonValue === nextEnabled));
  });
}

function getSelectedIpProxyMode() {
  const activeButton = ipProxyModeButtons.find((button) => button.classList.contains('is-active'));
  return normalizeIpProxyModeForCurrentRelease(activeButton?.dataset?.ipProxyMode || DEFAULT_IP_PROXY_MODE);
}

function setIpProxyMode(mode) {
  const nextMode = normalizeIpProxyModeForCurrentRelease(mode);
  ipProxyModeButtons.forEach((button) => {
    const buttonMode = normalizeIpProxyMode(button?.dataset?.ipProxyMode || DEFAULT_IP_PROXY_MODE);
    button.classList.toggle('is-active', buttonMode === nextMode);
    button.setAttribute('aria-pressed', String(buttonMode === nextMode));
  });
}

function getIpProxyRuntimeFieldNames(mode = DEFAULT_IP_PROXY_MODE) {
  const normalizedMode = normalizeIpProxyModeForCurrentRelease(mode);
  if (normalizedMode === 'account') {
    return {
      poolKey: 'ipProxyAccountPool',
      indexKey: 'ipProxyAccountCurrentIndex',
      currentKey: 'ipProxyAccountCurrent',
    };
  }
  return {
    poolKey: 'ipProxyApiPool',
    indexKey: 'ipProxyApiCurrentIndex',
    currentKey: 'ipProxyApiCurrent',
  };
}

function getIpProxyRuntimeSnapshot(state = latestState, mode = normalizeIpProxyModeForCurrentRelease(state?.ipProxyMode)) {
  const normalizedMode = normalizeIpProxyModeForCurrentRelease(mode);
  const fields = getIpProxyRuntimeFieldNames(normalizedMode);
  const hasAccountListConfigured = normalizedMode === 'account'
    && normalizeIpProxyAccountList(state?.ipProxyAccountList || '').split('\n').filter(Boolean).length > 0;
  const hasModePool = Array.isArray(state?.[fields.poolKey]);
  const hasModeCurrent = state?.[fields.currentKey] !== undefined && state?.[fields.currentKey] !== null;
  const hasModeIndex = state?.[fields.indexKey] !== undefined && state?.[fields.indexKey] !== null;
  const modePool = Array.isArray(state?.[fields.poolKey]) ? state[fields.poolKey] : [];
  // 账号模式在“固定账号”场景下不应读取历史 runtime 缓存，否则会出现
  // “状态显示 A 节点、当前节点显示 B 节点”的错位。
  const allowAccountRuntimeCache = normalizedMode !== 'account' || hasAccountListConfigured;
  const pool = hasModePool && allowAccountRuntimeCache ? modePool : [];
  const current = hasModeCurrent && allowAccountRuntimeCache ? state?.[fields.currentKey] : null;
  const rawIndex = hasModeIndex && allowAccountRuntimeCache ? state?.[fields.indexKey] : 0;
  const index = Number.isFinite(Number(rawIndex)) ? Math.max(0, Math.floor(Number(rawIndex))) : 0;
  return {
    mode: normalizedMode,
    ...fields,
    hasModePool: hasModePool && allowAccountRuntimeCache,
    hasModeCurrent: hasModeCurrent && allowAccountRuntimeCache,
    hasModeIndex: hasModeIndex && allowAccountRuntimeCache,
    hasAccountListConfigured,
    pool,
    current,
    index,
  };
}

function buildIpProxyRuntimeStatePatchForMode(mode = DEFAULT_IP_PROXY_MODE, response = {}) {
  const normalizedMode = normalizeIpProxyModeForCurrentRelease(mode);
  const fields = getIpProxyRuntimeFieldNames(normalizedMode);
  const patch = {};
  if (response?.pool !== undefined) {
    patch[fields.poolKey] = Array.isArray(response.pool) ? response.pool : [];
    // 兼容旧字段：始终反映当前激活模式的运行态。
    patch.ipProxyPool = patch[fields.poolKey];
  }
  if (response?.index !== undefined) {
    const index = Number.isFinite(Number(response.index)) ? Math.max(0, Math.floor(Number(response.index))) : 0;
    patch[fields.indexKey] = index;
    patch.ipProxyCurrentIndex = index;
  }
  if (response?.current !== undefined) {
    patch[fields.currentKey] = response.current || null;
    patch.ipProxyCurrent = response.current || null;
  }
  return patch;
}

function getIpProxyCurrentEntry(state = latestState) {
  const mode = normalizeIpProxyModeForCurrentRelease(state?.ipProxyMode);
  if (mode === 'account') {
    const runtime = getIpProxyRuntimeSnapshot(state, mode);
    const poolEntry = runtime.hasModePool && runtime.pool.length
      ? runtime.pool[runtime.index % runtime.pool.length]
      : null;
    const modeCurrent = runtime.hasModeCurrent ? runtime.current : null;
    const hasRuntimeEntry = Boolean(poolEntry || modeCurrent);
    const current = poolEntry || modeCurrent || {};
    const host = String(current?.host || state?.ipProxyHost || '').trim();
    const port = Number(current?.port || normalizeIpProxyPort(state?.ipProxyPort));
    if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) {
      return null;
    }
    return {
      host,
      port,
      username: hasRuntimeEntry
        ? String(current?.username || '').trim()
        : String(current?.username || state?.ipProxyUsername || '').trim(),
      password: hasRuntimeEntry
        ? String(current?.password || '')
        : String(current?.password || state?.ipProxyPassword || ''),
      protocol: normalizeIpProxyProtocol(current?.protocol || state?.ipProxyProtocol),
      region: hasRuntimeEntry
        ? String(current?.region || '').trim()
        : String(current?.region || state?.ipProxyRegion || '').trim(),
      provider: normalizeIpProxyService(state?.ipProxyService),
    };
  }

  const runtime = getIpProxyRuntimeSnapshot(state, mode);
  if (runtime.pool.length) {
    const index = runtime.index % runtime.pool.length;
    const current = runtime.pool[index];
    if (current?.host && current?.port) {
      return current;
    }
  }
  const current = runtime.current;
  return current && current.host && current.port ? current : null;
}

function has711SessionTokenForPanel(username = '') {
  const text = String(username || '').trim();
  if (!text) {
    return false;
  }
  return /(?:^|[-_])session[-_:][A-Za-z0-9_-]+?(?=(?:[-_](?:sessTime|sessAuto|region|life|zone|ptype|country|area)\b)|$)/i.test(text);
}

function normalize711SessionIdForPanel(value = '') {
  return String(value || '').trim().replace(/[^A-Za-z0-9_-]/g, '').slice(0, 64);
}

function normalize711SessTimeForPanel(value = '') {
  const raw = String(value ?? '').trim();
  if (!raw) {
    return '';
  }
  const numeric = Number.parseInt(raw, 10);
  if (!Number.isInteger(numeric)) {
    return '';
  }
  return String(Math.max(1, Math.min(180, numeric)));
}

function normalize711RegionCodeForPanel(value = '') {
  const text = String(value || '').trim().toUpperCase().replace(/[^A-Z]/g, '');
  return /^[A-Z]{2}$/.test(text) ? text : '';
}

function shouldAutoSync711SessionFieldsForPanel(state = latestState) {
  const service = normalizeIpProxyService(
    selectIpProxyService?.value || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
  );
  const mode = normalizeIpProxyModeForCurrentRelease(getSelectedIpProxyMode());
  if (service !== '711proxy' || mode !== 'account') {
    return false;
  }
  return !hasCurrentInputAccountListEntries();
}

function parse711RegionFromUsernameForPanel(username = '') {
  const text = String(username || '').trim();
  if (!text) {
    return '';
  }
  const match = text.match(/(?:^|[-_])region[-_:]?([A-Za-z]{2})\b/i);
  return normalize711RegionCodeForPanel(match ? match[1] : '');
}

function apply711RegionToUsernameForPanel(username = '', regionCode = '', options = {}) {
  const text = String(username || '').trim();
  if (!text) {
    return '';
  }

  const normalizedRegion = normalize711RegionCodeForPanel(regionCode);
  const removeWhenEmpty = Boolean(options?.removeWhenEmpty);

  if (normalizedRegion) {
    if (/(?:^|[-_])region[-_:]?[A-Za-z]{2}\b/i.test(text)) {
      return text.replace(/((?:^|[-_])region[-_:]?)([A-Za-z]{2})\b/i, `$1${normalizedRegion}`);
    }
    return `${text}-region-${normalizedRegion}`;
  }

  if (!removeWhenEmpty) {
    return text;
  }

  return String(text.replace(/(^|[-_])region[-_:]?[A-Za-z]{2}\b/ig, '$1') || '')
    .replace(/[-_]{2,}/g, '-')
    .replace(/^[-_]+|[-_]+$/g, '')
    .trim();
}

function parse711SessionConfigFromUsernameForPanel(username = '') {
  const text = String(username || '').trim();
  if (!text) {
    return { sessionId: '', sessTime: '' };
  }
  const sessionMatch = text.match(/(?:^|[-_])session[-_:]([A-Za-z0-9_-]+?)(?=(?:[-_](?:sessTime|sessAuto|region|life|zone|ptype|country|area)\b)|$)/i);
  const sessTimeMatch = text.match(/(?:^|[-_])sessTime[-_:]?(\d+)\b/i);
  const lifeMatch = text.match(/(?:^|[-_])life[-_:]?(\d+)\b/i);
  return {
    sessionId: normalize711SessionIdForPanel(sessionMatch ? sessionMatch[1] : ''),
    sessTime: normalize711SessTimeForPanel(
      sessTimeMatch ? sessTimeMatch[1] : (lifeMatch ? lifeMatch[1] : '')
    ),
  };
}

function apply711SessionConfigToUsernameForPanel(username = '', options = {}) {
  const text = String(username || '').trim();
  if (!text) {
    return '';
  }

  const sessionId = normalize711SessionIdForPanel(options?.sessionId || '');
  const sessTime = normalize711SessTimeForPanel(options?.sessTime || '');
  const removeWhenEmpty = Boolean(options?.removeWhenEmpty);
  let next = text;

  if (sessionId) {
    if (/(?:^|[-_])session[-_:][A-Za-z0-9_-]+?(?=(?:[-_](?:sessTime|sessAuto|region|life|zone|ptype|country|area)\b)|$)/i.test(next)) {
      next = next.replace(
        /((?:^|[-_])session[-_:])([A-Za-z0-9_-]+?)(?=(?:[-_](?:sessTime|sessAuto|region|life|zone|ptype|country|area)\b)|$)/i,
        `$1${sessionId}`
      );
    } else {
      next = `${next}-session-${sessionId}`;
    }
  } else if (removeWhenEmpty) {
    next = next.replace(
      /(^|[-_])session[-_:][A-Za-z0-9_-]+?(?=(?:[-_](?:sessTime|sessAuto|region|life|zone|ptype|country|area)\b)|$)/ig,
      '$1'
    );
  }

  if (sessTime) {
    if (/(?:^|[-_])sessTime[-_:]?\d+\b/i.test(next)) {
      next = next.replace(/((?:^|[-_])sessTime[-_:]?)(\d+)\b/i, `$1${sessTime}`);
    } else if (/(?:^|[-_])life[-_:]?\d+\b/i.test(next)) {
      next = next.replace(/((?:^|[-_])life[-_:]?)(\d+)\b/i, `$1${sessTime}`);
    } else {
      next = `${next}-sessTime-${sessTime}`;
    }
  } else if (removeWhenEmpty) {
    next = next.replace(/(^|[-_])sessTime[-_:]?\d+\b/ig, '$1');
    next = next.replace(/(^|[-_])life[-_:]?\d+\b/ig, '$1');
  }

  return String(next || '')
    .replace(/[-_]{2,}/g, '-')
    .replace(/^[-_]+|[-_]+$/g, '')
    .trim();
}

function sync711SessionFieldsFromUsernameForPanel(options = {}) {
  if (!shouldAutoSync711SessionFieldsForPanel(options?.state || latestState)) {
    return { updated: false, sessionId: '', sessTime: '' };
  }
  const username = String(
    options?.username !== undefined
      ? options.username
      : (inputIpProxyUsername?.value || '')
  ).trim();
  if (!username) {
    return { updated: false, sessionId: '', sessTime: '' };
  }

  const parsed = parse711SessionConfigFromUsernameForPanel(username);
  let updated = false;

  if (parsed.sessionId && inputIpProxyAccountSessionPrefix) {
    const currentSession = normalizeIpProxyAccountSessionPrefix(
      inputIpProxyAccountSessionPrefix.value || ''
    );
    if (currentSession !== parsed.sessionId) {
      inputIpProxyAccountSessionPrefix.value = parsed.sessionId;
      updated = true;
    }
  }

  if (parsed.sessTime && inputIpProxyAccountLifeMinutes) {
    const currentLife = normalizeIpProxyAccountLifeMinutes(
      inputIpProxyAccountLifeMinutes.value || ''
    );
    if (currentLife !== parsed.sessTime) {
      inputIpProxyAccountLifeMinutes.value = parsed.sessTime;
      updated = true;
    }
  }

  return {
    updated,
    sessionId: parsed.sessionId,
    sessTime: parsed.sessTime,
  };
}

function sync711RegionFieldFromUsernameForPanel(options = {}) {
  if (!shouldAutoSync711SessionFieldsForPanel(options?.state || latestState)) {
    return { updated: false, region: '' };
  }
  if (!inputIpProxyRegion) {
    return { updated: false, region: '' };
  }

  const username = String(
    options?.username !== undefined
      ? options.username
      : (inputIpProxyUsername?.value || '')
  ).trim();
  const parsedRegion = parse711RegionFromUsernameForPanel(username);
  if (!parsedRegion) {
    return { updated: false, region: '' };
  }

  const currentRegion = normalize711RegionCodeForPanel(inputIpProxyRegion.value || '');
  if (currentRegion === parsedRegion) {
    return { updated: false, region: parsedRegion };
  }
  inputIpProxyRegion.value = parsedRegion;
  return { updated: true, region: parsedRegion };
}

function sync711UsernameFromSessionFieldsForPanel(options = {}) {
  if (!shouldAutoSync711SessionFieldsForPanel(options?.state || latestState)) {
    return { updated: false, username: '' };
  }
  if (!inputIpProxyUsername) {
    return { updated: false, username: '' };
  }

  const currentUsername = String(inputIpProxyUsername.value || '').trim();
  if (!currentUsername) {
    return { updated: false, username: '' };
  }

  const sessionId = normalize711SessionIdForPanel(
    options?.sessionId !== undefined
      ? options.sessionId
      : (inputIpProxyAccountSessionPrefix?.value || '')
  );
  const lifeMinutes = normalizeIpProxyAccountLifeMinutes(
    options?.sessTime !== undefined
      ? options.sessTime
      : (inputIpProxyAccountLifeMinutes?.value || '')
  );
  const sessTime = normalize711SessTimeForPanel(lifeMinutes);
  const nextUsername = apply711SessionConfigToUsernameForPanel(currentUsername, {
    sessionId,
    sessTime,
    removeWhenEmpty: Boolean(options?.removeWhenEmpty),
  });

  if (!nextUsername || nextUsername === currentUsername) {
    return { updated: false, username: currentUsername };
  }

  inputIpProxyUsername.value = nextUsername;
  return { updated: true, username: nextUsername };
}

function sync711UsernameFromRegionForPanel(options = {}) {
  if (!shouldAutoSync711SessionFieldsForPanel(options?.state || latestState)) {
    return { updated: false, username: '' };
  }
  if (!inputIpProxyUsername) {
    return { updated: false, username: '' };
  }

  const currentUsername = String(inputIpProxyUsername.value || '').trim();
  if (!currentUsername) {
    return { updated: false, username: '' };
  }

  const rawRegion = String(
    options?.region !== undefined
      ? options.region
      : (inputIpProxyRegion?.value || '')
  ).trim();
  const normalizedRegion = normalize711RegionCodeForPanel(rawRegion);
  const nextUsername = apply711RegionToUsernameForPanel(
    currentUsername,
    normalizedRegion,
    { removeWhenEmpty: Boolean(options?.removeWhenEmpty) }
  );

  if (!nextUsername || nextUsername === currentUsername) {
    return { updated: false, username: currentUsername };
  }

  inputIpProxyUsername.value = nextUsername;
  return { updated: true, username: nextUsername };
}

function canChangeIpProxyExitWithCurrentSession(state = latestState) {
  const mode = getSelectedIpProxyMode();
  const service = normalizeIpProxyService(
    selectIpProxyService?.value || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE
  );
  if (mode !== 'account' || service !== '711proxy') {
    return false;
  }
  const currentEntry = getIpProxyCurrentEntry(state);
  const username = String(currentEntry?.username || inputIpProxyUsername?.value || '').trim();
  return has711SessionTokenForPanel(username);
}

function buildIpProxyActionHintText(options = {}) {
  const mode = normalizeIpProxyModeForCurrentRelease(options?.mode || DEFAULT_IP_PROXY_MODE);
  const poolCount = Math.max(0, Number(options?.poolCount) || 0);
  const changeAvailable = Boolean(options?.changeAvailable);
  const dynamicPoolCount = poolCount > 0 ? poolCount : 1;

  if (mode === 'api') {
    const nextPart = poolCount > 1
      ? `下一条：当前共 ${dynamicPoolCount} 条节点，切到已拉取代理池的下一条节点。`
      : `下一条：当前仅 ${dynamicPoolCount} 条节点，执行重绑复测（不保证更换出口）。`;
    return `${nextPart} Change：仅账号模式可用。`;
  }

  const nextPart = poolCount > 1
    ? `下一条：当前共 ${dynamicPoolCount} 条节点，切到代理池的下一条节点。`
    : `下一条：当前仅 ${dynamicPoolCount} 条节点，执行重绑复测（不保证更换出口）。`;
  const changePart = changeAvailable
    ? 'Change：保持当前 session 重绑链路并复测出口。'
    : 'Change：需 711 账号模式且用户名包含 session。';
  return `${nextPart} ${changePart}`;
}

function setIpProxyCurrentDisplay(text = '', hasValue = false) {
  if (!ipProxyCurrent) return;
  ipProxyCurrent.textContent = text || '暂无可用代理';
  ipProxyCurrent.title = text || '暂无可用代理';
  ipProxyCurrent.classList.toggle('has-value', Boolean(hasValue));
}

function formatIpProxyCurrentDisplay(state = latestState) {
  const mode = normalizeIpProxyModeForCurrentRelease(state?.ipProxyMode);
  if (mode === 'account') {
    const current = getIpProxyCurrentEntry(state);
    if (!current) {
      return {
        text: '账号模式：请填写代理列表，或填写 Host / Port',
        hasValue: false,
      };
    }
    return {
      text: `${current.host}:${current.port}${current.region ? ` [${current.region}]` : ''}`,
      hasValue: true,
    };
  }

  const runtime = getIpProxyRuntimeSnapshot(state, mode);
  const current = getIpProxyCurrentEntry(state);
  const count = runtime.pool.length;
  const index = runtime.index;
  if (!current) {
    return {
      text: count ? `已拉取 ${count} 条，点击“下一条”切换` : '暂无可用代理',
      hasValue: false,
    };
  }
  const region = String(current.region || '').trim();
  const label = region ? `${current.host}:${current.port} [${region}]` : `${current.host}:${current.port}`;
  return {
    text: label,
    hasValue: true,
  };
}

function extractIpProxyEndpointToken(text = '') {
  const raw = String(text || '').trim();
  if (!raw) return '';
  const match = raw.match(/^([^\s\[]+:\d+)/);
  return match ? String(match[1]).toLowerCase() : '';
}

function extractIpProxyIndexToken(text = '') {
  const raw = String(text || '').trim();
  if (!raw) return '';
  const match = raw.match(/\((\d+\s*\/\s*\d+)\)\s*$/);
  if (!match) return '';
  return String(match[1]).replace(/\s+/g, '');
}

function buildIpProxyCurrentDisplayText(display = {}, runtimeStatus = {}) {
  const rawText = String(display?.text || '').trim();
  const hasValue = Boolean(display?.hasValue);
  if (!hasValue || !rawText) {
    return rawText;
  }
  return rawText;
}

function formatIpProxyRuntimeStatus(state = latestState) {
  const enabled = Boolean(state?.ipProxyEnabled);
  const mode = normalizeIpProxyModeForCurrentRelease(state?.ipProxyMode);
  const hasAccountListConfigured = mode === 'account'
    && normalizeIpProxyAccountList(state?.ipProxyAccountList || '').split('\n').filter(Boolean).length > 0;
  const accountSourceTag = mode === 'account'
    ? (hasAccountListConfigured ? '（账号列表）' : '（固定账号）')
    : '';
  const accountSourceDetail = mode === 'account'
    ? (
      hasAccountListConfigured
        ? '当前生效来源：账号列表（固定账号字段已忽略）。'
        : '当前生效来源：固定账号字段。'
    )
    : '';
  const activeEntry = getIpProxyCurrentEntry(state);
  const applied = Boolean(state?.ipProxyApplied);
  const reason = String(state?.ipProxyAppliedReason || '').trim().toLowerCase();
  const host = String(
    state?.ipProxyAppliedHost
    || activeEntry?.host
    || (mode === 'account' ? state?.ipProxyHost : '')
    || ''
  ).trim();
  const portValue = Number(
    state?.ipProxyAppliedPort
    || activeEntry?.port
    || (mode === 'account' ? normalizeIpProxyPort(state?.ipProxyPort) : 0)
  );
  const port = Number.isInteger(portValue) && portValue > 0 && portValue <= 65535 ? portValue : 0;
  const region = String(
    state?.ipProxyAppliedRegion
    || activeEntry?.region
    || (mode === 'account' ? state?.ipProxyRegion : '')
    || ''
  ).trim();
  const endpoint = host && port > 0 ? `${host}:${port}` : '';
  const endpointWithRegion = endpoint ? `${endpoint}${region ? ` [${region}]` : ''}` : '';
  const hasAuth = Boolean(state?.ipProxyAppliedHasAuth);
  const authSuffix = hasAuth ? '（需要鉴权）' : '';
  const errorText = String(state?.ipProxyAppliedError || '').trim();
  const warningText = String(state?.ipProxyAppliedWarning || '').trim();
  const exitIp = String(state?.ipProxyAppliedExitIp || '').trim();
  const exitRegion = String(state?.ipProxyAppliedExitRegion || '').trim();
  const exitDetecting = Boolean(state?.ipProxyAppliedExitDetecting);
  const exitError = String(state?.ipProxyAppliedExitError || '').trim();
  const exitSource = String(state?.ipProxyAppliedExitSource || '').trim().toLowerCase();
  const exitSourceSuffix = exitSource === 'page_context'
    ? '（页面探测）'
    : (exitSource === 'background_fallback'
      ? '（后台兜底，可能受全局代理影响）'
      : '');
  const endpointSummary = endpointWithRegion
    ? `${endpointWithRegion}${authSuffix}`
    : (endpoint ? `${endpoint}${authSuffix}` : '');
  const exitSummary = exitIp
    ? `${exitIp}${exitRegion ? ` [${exitRegion}]` : ''}${exitSourceSuffix}`
    : (exitDetecting ? '检测中...' : '未检测到');
  const details = [accountSourceDetail, errorText, warningText, exitError]
    .map((item) => String(item || '').trim())
    .filter(Boolean)
    .join('\n');

  if (!enabled) {
    return {
      stateClass: 'state-idle',
      text: '未启用，沿用浏览器默认/全局代理。',
      details: '',
      hideCurrentDisplay: false,
    };
  }

  if (applied) {
    const statusPrefix = endpointSummary
      ? `当前代理：${endpointSummary}${accountSourceTag}`
      : '当前代理：已启用';
    const statusText = `${statusPrefix}；当前出口：${exitSummary}`;
    const briefWarning = warningText
      ? '；地区校验未通过（详情可展开查看）'
      : '';
    return {
      stateClass: warningText ? 'state-warning' : 'state-applied',
      text: `${statusText}${briefWarning}`,
      details,
      hideCurrentDisplay: true,
    };
  }

  if (reason === 'missing_proxy_entry') {
    if (errorText) {
      return {
        stateClass: 'state-warning',
        text: '已启用，但当前没有可用代理（已阻断直连）。',
        details: errorText,
        hideCurrentDisplay: false,
      };
    }
    return {
      stateClass: 'state-warning',
      text: mode === 'account'
        ? '已启用，但账号模式没有可用代理。请先填写代理列表，或填写 Host/Port。已阻断所有网站直连。'
        : '已启用，但当前没有可用代理。请先点击“拉取”获取 IP 列表。已阻断所有网站直连。',
      details: '',
      hideCurrentDisplay: false,
    };
  }
  if (reason === 'proxy_api_unavailable') {
    return {
      stateClass: 'state-error',
      text: '已启用，但当前浏览器不支持扩展代理 API，无法应用。',
      details,
      hideCurrentDisplay: false,
    };
  }
  if (reason === 'apply_failed') {
    return {
      stateClass: 'state-error',
      text: '已启用，但代理应用失败（已回退默认代理）。',
      details: errorText || details,
      hideCurrentDisplay: false,
    };
  }
  if (reason === 'connectivity_failed') {
    const prefix = endpointSummary ? `当前代理：${endpointSummary}${accountSourceTag}` : '当前代理：未知';
    const targetUnreachable = /真实目标|chatgpt\.com 不可达|target:page_context/i.test(errorText || details);
    const exitPart = exitIp ? `；当前出口：${exitSummary}` : '';
    return {
      stateClass: 'state-error',
      text: targetUnreachable && exitIp
        ? `${prefix}${exitPart}；ChatGPT 目标不可达，请切换支持 ChatGPT 的节点。`
        : `${prefix}${exitPart}；连通性失败，请切换节点或重试。`,
      details: errorText || details,
      hideCurrentDisplay: true,
    };
  }

  return {
    stateClass: 'state-warning',
    text: endpointWithRegion
      ? `已启用，等待生效：${endpointWithRegion}${authSuffix}`
      : '已启用，等待拉取并应用代理。',
    details,
    hideCurrentDisplay: false,
  };
}

function setIpProxyRuntimeStatusDisplay(status = {}) {
  if (!ipProxyRuntimeStatus || !ipProxyRuntimeText) {
    return;
  }
  const text = String(status?.text || '未启用，沿用浏览器默认/全局代理。').trim();
  const stateClass = String(status?.stateClass || 'state-idle').trim() || 'state-idle';
  ipProxyRuntimeStatus.classList.remove('state-idle', 'state-applied', 'state-warning', 'state-error');
  ipProxyRuntimeStatus.classList.add(stateClass);
  ipProxyRuntimeText.textContent = text;
  ipProxyRuntimeText.title = text;
  if (ipProxyRuntimeDot) {
    ipProxyRuntimeDot.title = text;
  }
  const runtimeMeta = ipProxyCurrent?.closest?.('.ip-proxy-runtime-meta') || null;
  if (runtimeMeta) {
    runtimeMeta.style.display = status?.hideCurrentDisplay ? 'none' : '';
  }
  const detailsText = String(status?.details || '').trim();
  if (ipProxyRuntimeDetails && ipProxyRuntimeDetailsText) {
    if (detailsText) {
      ipProxyRuntimeDetails.hidden = false;
      ipProxyRuntimeDetailsText.textContent = detailsText;
    } else {
      ipProxyRuntimeDetails.hidden = true;
      ipProxyRuntimeDetails.open = false;
      ipProxyRuntimeDetailsText.textContent = '';
    }
  }
}

function normalizeIpProxyEnabledInlineRegion(value = '') {
  const normalized = String(value || '').trim();
  return normalized ? normalized.toUpperCase() : '';
}

function setIpProxyEnabledInlineStatus(state = {}, enabled = getSelectedIpProxyEnabled()) {
  if (!ipProxyEnabledStatus || !ipProxyEnabledStatusText) {
    return;
  }
  ipProxyEnabledStatus.classList.remove('is-off', 'is-on');

  if (!enabled) {
    const text = '未开启';
    ipProxyEnabledStatus.classList.add('is-off');
    ipProxyEnabledStatusText.textContent = text;
    ipProxyEnabledStatusText.title = text;
    ipProxyEnabledStatus.title = text;
    if (ipProxyEnabledStatusDot) {
      ipProxyEnabledStatusDot.title = text;
    }
    return;
  }

  const region = normalizeIpProxyEnabledInlineRegion(state?.ipProxyAppliedExitRegion)
    || normalizeIpProxyEnabledInlineRegion(state?.ipProxyAppliedRegion)
    || normalizeIpProxyEnabledInlineRegion(state?.ipProxyRegion);
  const text = region ? `已开启 · ${region}` : '已开启';
  ipProxyEnabledStatus.classList.add('is-on');
  ipProxyEnabledStatusText.textContent = text;
  ipProxyEnabledStatusText.title = text;
  ipProxyEnabledStatus.title = text;
  if (ipProxyEnabledStatusDot) {
    ipProxyEnabledStatusDot.title = text;
  }
}

function updateIpProxyPromoOverflow() {
  if (
    typeof ipProxyPromo === 'undefined'
    || typeof ipProxyPromoText === 'undefined'
    || !ipProxyPromo
    || !ipProxyPromoText
  ) {
    return;
  }
  if (!ipProxyPromo.getClientRects().length) {
    ipProxyPromo.classList.remove('is-overflowing');
    return;
  }
  const overflowing = ipProxyPromoText.scrollWidth > Math.max(0, ipProxyPromo.clientWidth - 2);
  ipProxyPromo.classList.toggle('is-overflowing', overflowing);
}

function scheduleIpProxyPromoOverflowCheck() {
  if (typeof globalThis.requestAnimationFrame === 'function') {
    globalThis.requestAnimationFrame(updateIpProxyPromoOverflow);
    return;
  }
  globalThis.setTimeout(updateIpProxyPromoOverflow, 0);
}

if (typeof globalThis.addEventListener === 'function') {
  globalThis.addEventListener('resize', scheduleIpProxyPromoOverflowCheck);
}

function updateIpProxyUI(state = latestState) {
  const enabled = getSelectedIpProxyEnabled();
  const showSettings = enabled && ipProxySectionExpanded;
  const mode = getSelectedIpProxyMode();
  const service = normalizeIpProxyService(selectIpProxyService?.value || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE);
  const apiModeAvailable = isIpProxyApiModeAvailable();
  const accountListAvailable = isIpProxyAccountListAvailable();
  const isApiMode = mode === 'api' && apiModeAvailable;
  const isAccountMode = mode === 'account';
  const showSessionOptions = isAccountMode && service === '711proxy';
  const hasAccountListConfigured = accountListAvailable && isAccountMode && hasCurrentInputAccountListEntries();
  const canOperate = !isAutoRunLockedPhase() && !isAutoRunScheduledPhase();
  const actionState = getIpProxyActionState();
  const actionBusy = Boolean(actionState.busy);
  const busyAction = normalizeIpProxyActionType(actionState.action);
  const runtimeState = state || latestState || {};

  if (rowIpProxyEnabled) {
    rowIpProxyEnabled.style.display = '';
  }
  if (btnToggleIpProxySection) {
    btnToggleIpProxySection.disabled = !enabled;
    btnToggleIpProxySection.textContent = showSettings ? '收起设置' : '展开设置';
    btnToggleIpProxySection.title = enabled
      ? (showSettings ? '收起 IP 代理设置' : '展开 IP 代理设置')
      : '开启 IP 代理后可展开设置';
    btnToggleIpProxySection.setAttribute('aria-expanded', String(showSettings));
  }
  if (rowIpProxyFold) {
    rowIpProxyFold.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyPromo) {
    rowIpProxyPromo.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyService) {
    rowIpProxyService.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyMode) {
    rowIpProxyMode.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyLayout) {
    rowIpProxyLayout.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyApiUrl) {
    rowIpProxyApiUrl.style.display = showSettings && apiModeAvailable && isApiMode ? '' : 'none';
  }
  if (rowIpProxyAccountList) {
    rowIpProxyAccountList.style.display = showSettings && isAccountMode && accountListAvailable ? '' : 'none';
  }
  if (rowIpProxyAccountSessionPrefix) {
    rowIpProxyAccountSessionPrefix.style.display = showSettings && showSessionOptions ? '' : 'none';
  }
  if (rowIpProxyAccountLifeMinutes) {
    rowIpProxyAccountLifeMinutes.style.display = showSettings && showSessionOptions ? '' : 'none';
  }
  if (rowIpProxyPoolTargetCount) {
    rowIpProxyPoolTargetCount.style.display = showSettings ? '' : 'none';
  }
  const autoSyncEnabledRow = typeof rowIpProxyAutoSyncEnabled !== 'undefined' ? rowIpProxyAutoSyncEnabled : null;
  const autoSyncIntervalRow = typeof rowIpProxyAutoSyncInterval !== 'undefined' ? rowIpProxyAutoSyncInterval : null;
  if (autoSyncEnabledRow) {
    autoSyncEnabledRow.style.display = showSettings ? '' : 'none';
  }
  if (autoSyncIntervalRow) {
    autoSyncIntervalRow.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyHost) {
    rowIpProxyHost.style.display = showSettings && isAccountMode ? '' : 'none';
  }
  if (rowIpProxyPort) {
    rowIpProxyPort.style.display = showSettings && isAccountMode ? '' : 'none';
  }
  if (rowIpProxyProtocol) {
    rowIpProxyProtocol.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyUsername) {
    rowIpProxyUsername.style.display = showSettings && isAccountMode ? '' : 'none';
  }
  if (rowIpProxyPassword) {
    rowIpProxyPassword.style.display = showSettings && isAccountMode ? '' : 'none';
  }
  if (rowIpProxyRegion) {
    rowIpProxyRegion.style.display = showSettings && isAccountMode ? '' : 'none';
  }
  if (rowIpProxyActions) {
    rowIpProxyActions.style.display = showSettings ? '' : 'none';
  }
  if (ipProxyActionButtons) {
    ipProxyActionButtons.style.display = showSettings ? '' : 'none';
  }
  if (rowIpProxyRuntimeStatus) {
    rowIpProxyRuntimeStatus.style.display = showSettings ? '' : 'none';
  }
  if (ipProxyLayout) {
    ipProxyLayout.classList.toggle('is-account-only', !apiModeAvailable);
  }
  scheduleIpProxyPromoOverflowCheck();
  if (selectIpProxyService) {
    selectIpProxyService.value = service;
    selectIpProxyService.disabled = true;
  }
  if (typeof updateIpProxyServiceLoginButtonState === 'function') {
    updateIpProxyServiceLoginButtonState({
      service,
      enabled,
    });
  }
  ipProxyModeButtons.forEach((button) => {
    const buttonMode = normalizeIpProxyMode(button?.dataset?.ipProxyMode || DEFAULT_IP_PROXY_MODE);
    const apiButton = buttonMode === 'api';
    button.disabled = apiButton && !apiModeAvailable;
    if (apiButton) {
      button.hidden = false;
    }
  });
  if (ipProxyApiPanel) {
    ipProxyApiPanel.classList.toggle('is-disabled', !apiModeAvailable);
    ipProxyApiPanel.setAttribute('aria-disabled', String(!apiModeAvailable));
    ipProxyApiPanel.hidden = !apiModeAvailable;
    ipProxyApiPanel.style.display = showSettings && apiModeAvailable ? '' : 'none';
  }
  if (inputIpProxyApiUrl) {
    inputIpProxyApiUrl.disabled = !enabled || !apiModeAvailable;
  }
  if (btnToggleIpProxyApiUrl) {
    btnToggleIpProxyApiUrl.disabled = !enabled || !apiModeAvailable;
  }
  if (inputIpProxyHost) {
    inputIpProxyHost.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (inputIpProxyPort) {
    inputIpProxyPort.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (selectIpProxyProtocol) {
    selectIpProxyProtocol.disabled = !enabled || (isAccountMode && hasAccountListConfigured);
  }
  if (inputIpProxyUsername) {
    inputIpProxyUsername.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (inputIpProxyPassword) {
    inputIpProxyPassword.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (btnToggleIpProxyUsername) {
    btnToggleIpProxyUsername.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (btnToggleIpProxyPassword) {
    btnToggleIpProxyPassword.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (inputIpProxyRegion) {
    inputIpProxyRegion.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (inputIpProxyAccountSessionPrefix) {
    inputIpProxyAccountSessionPrefix.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (inputIpProxyAccountLifeMinutes) {
    inputIpProxyAccountLifeMinutes.disabled = !enabled || !isAccountMode || hasAccountListConfigured;
  }
  if (inputIpProxyAccountList) {
    inputIpProxyAccountList.disabled = !enabled || !isAccountMode || !accountListAvailable;
  }
  const autoSyncEnabledInput = typeof inputIpProxyAutoSyncEnabled !== 'undefined' ? inputIpProxyAutoSyncEnabled : null;
  const autoSyncIntervalInput = typeof inputIpProxyAutoSyncIntervalMinutes !== 'undefined' ? inputIpProxyAutoSyncIntervalMinutes : null;
  if (autoSyncEnabledInput) {
    autoSyncEnabledInput.disabled = !enabled;
  }
  if (autoSyncIntervalInput) {
    const autoSyncEnabled = Boolean(autoSyncEnabledInput?.checked);
    if (!Number.isFinite(Number.parseInt(String(autoSyncIntervalInput.value || '').trim(), 10))) {
      autoSyncIntervalInput.value = '15';
    }
    autoSyncIntervalInput.disabled = !enabled || !autoSyncEnabled;
  }

  const runtimeStatus = formatIpProxyRuntimeStatus(runtimeState);
  setIpProxyRuntimeStatusDisplay(runtimeStatus);
  const currentDisplay = formatIpProxyCurrentDisplay(runtimeState);
  const currentDisplayText = buildIpProxyCurrentDisplayText(currentDisplay, runtimeStatus);
  setIpProxyCurrentDisplay(currentDisplayText, currentDisplay.hasValue);
  const runtimeSnapshot = getIpProxyRuntimeSnapshot(runtimeState, mode, service);
  const runtimePoolCount = Array.isArray(runtimeSnapshot?.pool) ? runtimeSnapshot.pool.length : 0;
  const runtimePoolCountForDisplay = runtimePoolCount > 0 ? runtimePoolCount : 1;
  const hasCurrentEntry = Boolean(getIpProxyCurrentEntry(runtimeState));
  const changeAvailable = canChangeIpProxyExitWithCurrentSession(runtimeState);
  const nextActionTitle = runtimePoolCount > 1
    ? `切换到代理池下一条节点并应用（当前共 ${runtimePoolCountForDisplay} 条）`
    : `当前仅 ${runtimePoolCountForDisplay} 条节点：重绑当前节点并复测连通性（不保证更换出口）`;

  if (btnIpProxyRefresh) {
    btnIpProxyRefresh.disabled = actionBusy || !enabled || !canOperate;
    btnIpProxyRefresh.textContent = busyAction === 'refresh'
      ? (isApiMode ? '拉取中...' : '同步中...')
      : (isApiMode ? '拉取' : '同步');
    btnIpProxyRefresh.title = isApiMode ? '拉取代理池并应用当前代理' : '同步账号代理列表并应用当前代理';
  }
  if (btnIpProxyNext) {
    btnIpProxyNext.disabled = actionBusy || !enabled || !canOperate || !hasCurrentEntry;
    btnIpProxyNext.textContent = busyAction === 'next' ? '切换中...' : '下一条';
    btnIpProxyNext.title = nextActionTitle;
  }
  if (btnIpProxyChange) {
    btnIpProxyChange.disabled = actionBusy || !enabled || !canOperate || !changeAvailable;
    btnIpProxyChange.textContent = busyAction === 'change' ? 'Change中...' : 'Change';
    btnIpProxyChange.title = changeAvailable
      ? '保持当前会话并刷新出口（仅 711 + session）'
      : '当前模式不支持 Change（需 711 账号模式且用户名包含 session）';
  }
  if (btnIpProxyProbe) {
    btnIpProxyProbe.disabled = actionBusy || !enabled || !canOperate;
    btnIpProxyProbe.textContent = busyAction === 'probe' ? '检测中...' : '检测出口';
  }
  if (btnIpProxyCheckIp) {
    btnIpProxyCheckIp.disabled = false;
    btnIpProxyCheckIp.title = '打开公网 IP 检测页';
  }
  if (ipProxyActionHint) {
    const actionHint = buildIpProxyActionHintText({
      mode,
      poolCount: runtimePoolCount,
      changeAvailable,
    });
    ipProxyActionHint.textContent = actionHint;
    ipProxyActionHint.title = actionHint;
  }
}

async function refreshIpProxyPoolByApi(options = {}) {
  const { silent = false } = options;
  const mode = normalizeIpProxyModeForCurrentRelease(getSelectedIpProxyMode());
  const response = await chrome.runtime.sendMessage({
    type: 'REFRESH_IP_PROXY_POOL',
    source: 'sidepanel',
    payload: {
      mode,
      skipExitProbe: true,
    },
  });
  if (response?.error) {
    throw new Error(response.error);
  }

  const responseMode = normalizeIpProxyModeForCurrentRelease(response?.mode || mode);
  const patch = {};
  Object.assign(patch, buildIpProxyRuntimeStatePatchForMode(responseMode, response));
  if (response?.proxyRouting && typeof response.proxyRouting === 'object') {
    patch.ipProxyApplied = Boolean(response.proxyRouting.applied);
    patch.ipProxyAppliedReason = String(response.proxyRouting.reason || '').trim().toLowerCase();
    patch.ipProxyAppliedHost = String(response.proxyRouting.host || '').trim();
    patch.ipProxyAppliedPort = Number(response.proxyRouting.port) || 0;
    patch.ipProxyAppliedRegion = String(response.proxyRouting.region || '').trim();
    patch.ipProxyAppliedHasAuth = Boolean(response.proxyRouting.hasAuth);
    patch.ipProxyAppliedProvider = normalizeIpProxyService(response.proxyRouting.provider || '');
    patch.ipProxyAppliedError = String(response.proxyRouting.error || '').trim();
    patch.ipProxyAppliedWarning = String(response.proxyRouting.warning || '').trim();
    patch.ipProxyAppliedExitIp = String(response.proxyRouting.exitIp || '').trim();
    patch.ipProxyAppliedExitRegion = String(response.proxyRouting.exitRegion || '').trim();
    patch.ipProxyAppliedExitDetecting = Boolean(response.proxyRouting.exitDetecting);
    patch.ipProxyAppliedExitError = String(response.proxyRouting.exitError || '').trim();
    patch.ipProxyAppliedExitSource = String(response.proxyRouting.exitSource || '').trim().toLowerCase();
    patch.ipProxyAppliedAt = Date.now();
  }
  if (Object.keys(patch).length) {
    syncLatestState(patch);
  }
  updateIpProxyUI(latestState);
  scheduleIpProxyExitProbe({ silent: true });

  if (!silent) {
    if (mode === 'account') {
      showToast(`已同步账号代理：${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800);
    } else {
      showToast(`已拉取代理池：${Number(response?.count) || 0} 条`, 'success', 1800);
    }
  }
  return response;
}

async function switchIpProxyToNext(options = {}) {
  const { silent = false } = options;
  const mode = normalizeIpProxyModeForCurrentRelease(getSelectedIpProxyMode());
  const response = await chrome.runtime.sendMessage({
    type: 'SWITCH_IP_PROXY',
    source: 'sidepanel',
    payload: {
      direction: 'next',
      mode,
      forceRefresh: false,
      skipExitProbe: true,
    },
  });
  if (response?.error) {
    throw new Error(response.error);
  }
  const responseMode = normalizeIpProxyModeForCurrentRelease(response?.mode || mode);
  const patch = {};
  Object.assign(patch, buildIpProxyRuntimeStatePatchForMode(responseMode, response));
  if (response?.proxyRouting && typeof response.proxyRouting === 'object') {
    patch.ipProxyApplied = Boolean(response.proxyRouting.applied);
    patch.ipProxyAppliedReason = String(response.proxyRouting.reason || '').trim().toLowerCase();
    patch.ipProxyAppliedHost = String(response.proxyRouting.host || '').trim();
    patch.ipProxyAppliedPort = Number(response.proxyRouting.port) || 0;
    patch.ipProxyAppliedRegion = String(response.proxyRouting.region || '').trim();
    patch.ipProxyAppliedHasAuth = Boolean(response.proxyRouting.hasAuth);
    patch.ipProxyAppliedProvider = normalizeIpProxyService(response.proxyRouting.provider || '');
    patch.ipProxyAppliedError = String(response.proxyRouting.error || '').trim();
    patch.ipProxyAppliedWarning = String(response.proxyRouting.warning || '').trim();
    patch.ipProxyAppliedExitIp = String(response.proxyRouting.exitIp || '').trim();
    patch.ipProxyAppliedExitRegion = String(response.proxyRouting.exitRegion || '').trim();
    patch.ipProxyAppliedExitDetecting = Boolean(response.proxyRouting.exitDetecting);
    patch.ipProxyAppliedExitError = String(response.proxyRouting.exitError || '').trim();
    patch.ipProxyAppliedExitSource = String(response.proxyRouting.exitSource || '').trim().toLowerCase();
    patch.ipProxyAppliedAt = Date.now();
  }
  if (Object.keys(patch).length) {
    syncLatestState(patch);
  }
  updateIpProxyUI(latestState);
  scheduleIpProxyExitProbe({ silent: true });
  if (!silent) {
    showToast(`已切换代理：${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800);
  }
  return response;
}

async function changeIpProxyExitBySession(options = {}) {
  const { silent = false } = options;
  const mode = normalizeIpProxyModeForCurrentRelease(getSelectedIpProxyMode());
  const response = await chrome.runtime.sendMessage({
    type: 'CHANGE_IP_PROXY_EXIT',
    source: 'sidepanel',
    payload: {
      mode,
      skipExitProbe: true,
    },
  });
  if (response?.error) {
    throw new Error(response.error);
  }
  const responseMode = normalizeIpProxyModeForCurrentRelease(response?.mode || mode);
  const patch = {};
  Object.assign(patch, buildIpProxyRuntimeStatePatchForMode(responseMode, response));
  if (response?.proxyRouting && typeof response.proxyRouting === 'object') {
    patch.ipProxyApplied = Boolean(response.proxyRouting.applied);
    patch.ipProxyAppliedReason = String(response.proxyRouting.reason || '').trim().toLowerCase();
    patch.ipProxyAppliedHost = String(response.proxyRouting.host || '').trim();
    patch.ipProxyAppliedPort = Number(response.proxyRouting.port) || 0;
    patch.ipProxyAppliedRegion = String(response.proxyRouting.region || '').trim();
    patch.ipProxyAppliedHasAuth = Boolean(response.proxyRouting.hasAuth);
    patch.ipProxyAppliedProvider = normalizeIpProxyService(response.proxyRouting.provider || '');
    patch.ipProxyAppliedError = String(response.proxyRouting.error || '').trim();
    patch.ipProxyAppliedWarning = String(response.proxyRouting.warning || '').trim();
    patch.ipProxyAppliedExitIp = String(response.proxyRouting.exitIp || '').trim();
    patch.ipProxyAppliedExitRegion = String(response.proxyRouting.exitRegion || '').trim();
    patch.ipProxyAppliedExitDetecting = Boolean(response.proxyRouting.exitDetecting);
    patch.ipProxyAppliedExitError = String(response.proxyRouting.exitError || '').trim();
    patch.ipProxyAppliedExitSource = String(response.proxyRouting.exitSource || '').trim().toLowerCase();
    patch.ipProxyAppliedAt = Date.now();
  }
  if (Object.keys(patch).length) {
    syncLatestState(patch);
  }
  updateIpProxyUI(latestState);
  scheduleIpProxyExitProbe({ silent: true });
  if (!silent) {
    showToast(`已执行 Change：${response?.display || formatIpProxyCurrentDisplay(latestState).text}`, 'success', 1800);
  }
  return response;
}

async function probeIpProxyExit(options = {}) {
  const { silent = false } = options;
  const response = await chrome.runtime.sendMessage({
    type: 'PROBE_IP_PROXY_EXIT',
    source: 'sidepanel',
    payload: {},
  });
  if (response?.error) {
    throw new Error(response.error);
  }
  const routing = response?.proxyRouting || {};
  syncLatestState({
    ipProxyApplied: Boolean(routing.applied),
    ipProxyAppliedReason: String(routing.reason || '').trim().toLowerCase(),
    ipProxyAppliedHost: String(routing.host || '').trim(),
    ipProxyAppliedPort: Number(routing.port) || 0,
    ipProxyAppliedRegion: String(routing.region || '').trim(),
    ipProxyAppliedHasAuth: Boolean(routing.hasAuth),
    ipProxyAppliedProvider: normalizeIpProxyService(routing.provider || ''),
    ipProxyAppliedError: String(routing.error || '').trim(),
    ipProxyAppliedWarning: String(routing.warning || '').trim(),
    ipProxyAppliedExitIp: String(routing.exitIp || '').trim(),
    ipProxyAppliedExitRegion: String(routing.exitRegion || '').trim(),
    ipProxyAppliedExitDetecting: Boolean(routing.exitDetecting),
    ipProxyAppliedExitError: String(routing.exitError || '').trim(),
    ipProxyAppliedExitSource: String(routing.exitSource || '').trim().toLowerCase(),
    ipProxyAppliedAt: Date.now(),
  });
  updateIpProxyUI(latestState);

  if (!silent) {
    const exitIp = String(routing?.exitIp || '').trim();
    const exitRegion = String(routing?.exitRegion || '').trim();
    const exitSource = String(routing?.exitSource || '').trim().toLowerCase();
    const sourceHint = exitSource === 'background_fallback'
      ? '（后台兜底，可能受全局代理影响）'
      : '';
    if (exitIp) {
      showToast(`出口检测成功：${exitIp}${exitRegion ? ` [${exitRegion}]` : ''}${sourceHint}`, 'success', 2600);
    } else {
      showToast('出口检测完成，但未获取到出口 IP', 'warn', 2200);
    }
  }
  return response;
}
