const { useEffect, useMemo, useState, useCallback, useRef } = React;

const TOKEN_KEY = 'cfg_console_token';

async function apiRequest(path, options = {}) {
  const response = await fetch(path, {
    method: options.method || 'GET',
    headers: {
      'Content-Type': 'application/json',
      ...(options.headers || {}),
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  let data = null;
  try { data = await response.json(); } catch (_) { data = null; }

  if (!response.ok || !data || data.ok === false) {
    throw new Error((data && data.error) || `HTTP ${response.status}`);
  }
  return data;
}

function authHeader(token) {
  return { Authorization: `Bearer ${token}` };
}

function LoginPage({ onLoggedIn }) {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState('');

  async function submit(e) {
    e.preventDefault();
    setLoading(true); setErr('');
    try {
      const data = await apiRequest('/api/login', { method: 'POST', body: { username, password } });
      onLoggedIn(data.token, data.username || username);
    } catch (e) { setErr(e.message); }
    finally { setLoading(false); }
  }

  return (
    <div style={{ maxWidth: 400, margin: '100px auto', background: '#fff', padding: 20, border: '1px solid #ccc' }}>
      <h2>系统登录</h2>
      <form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        <input type="text" value={username} onChange={e => setUsername(e.target.value)} placeholder="用户名" />
        <input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="密码" />
        <button type="submit" className="btn-primary" disabled={loading}>{loading ? '登录中...' : '登录'}</button>
        {err && <div style={{ color: 'red' }}>{err}</div>}
      </form>
    </div>
  );
}

function StatusMsg({ msg, isErr }) {
  if (!msg) return null;
  return <div className={`status-msg ${isErr ? 'status-err' : 'status-ok'}`}>{msg}</div>;
}

// Editable Cell Component
function EditableCell({ value, onChange, onSave }) {
  const [editing, setEditing] = useState(false);
  const [val, setVal] = useState(value);

  useEffect(() => { setVal(value); }, [value]);

  const handleBlur = () => {
    setEditing(false);
    if (val !== value) onSave(val);
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter') {
      e.target.blur();
    }
  };

  if (editing) {
    const chars = Math.max(val ? val.toString().length + 2 : 4, 4);
    return (
      <input
        autoFocus
        type="text"
        value={val}
        onChange={e => setVal(e.target.value)}
        onBlur={handleBlur}
        onKeyDown={handleKeyDown}
        style={{ width: `${chars}ch`, maxWidth: '100%', padding: '2px 4px', boxSizing: 'border-box' }}
      />
    );
  }
  return <div onClick={() => setEditing(true)} style={{ minHeight: '20px', cursor: 'pointer', padding: '2px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} title={value || ''}>{value || ''}</div>;
}

function parseUpstreamRows(text) {
  const rows = [];
  String(text || '').split(/\r?\n/).forEach((raw) => {
    const line = String(raw || '').trim();
    if (!line) return;

    const seg = line.split('|').map((s) => s.trim()).filter(Boolean);
    let url = line;
    let min = '';
    let max = '';

    if (seg.length >= 3 && /^\d+$/.test(seg[1]) && /^\d+$/.test(seg[2])) {
      url = seg[0];
      min = seg[1];
      max = seg[2];
    } else if (seg.length >= 2) {
      url = seg[0];
      const match = seg.slice(1).join('|').match(/^\[\s*(\d+)\s*,\s*(\d+)\s*\]$/);
      if (match) {
        min = match[1];
        max = match[2];
      }
    } else {
      const inlineRange = line.match(/^(.*?)\s+\[\s*(\d+)\s*,\s*(\d+)\s*\]\s*$/);
      if (inlineRange) {
        url = inlineRange[1].trim();
        min = inlineRange[2];
        max = inlineRange[3];
      }
    }

    rows.push({ url: String(url || '').trim(), min, max });
  });

  if (!rows.length) rows.push({ url: '', min: '', max: '' });
  return rows;
}

function serializeUpstreamRows(rows) {
  return (rows || [])
    .map((row) => {
      const url = String(row && row.url || '').trim();
      if (!url) return '';
      const min = String(row && row.min || '').trim();
      const max = String(row && row.max || '').trim();
      if (/^\d+$/.test(min) && /^\d+$/.test(max)) return `${url}|${min}|${max}`;
      return url;
    })
    .filter(Boolean)
    .join('\n');
}

function getTodayDateSeed() {
  const d = new Date();
  const yy = String(d.getFullYear()).slice(-2);
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const dd = String(d.getDate()).padStart(2, '0');
  return `${yy}${mm}${dd}`;
}

function normalizeDateSeedInput(value) {
  return String(value || '').trim();
}

function normalizeJumpInput(value) {
  return String(value || '').trim();
}

function isValidDateSeed(value) {
  const text = normalizeDateSeedInput(value);
  if (!/^\d{6}[A-Za-z0-9_-]*$/.test(text)) return false;
  const mm = Number(text.slice(2, 4));
  const dd = Number(text.slice(4, 6));
  return mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31;
}

function DateSeedGenerateDialog({ open, currentSeed, todaySeed, onSelect, disabled }) {
  if (!open) return null;

  return (
    <div className="dialog-backdrop" onClick={() => !disabled && onSelect('cancel')}>
      <div className="dialog-card" onClick={(e) => e.stopPropagation()}>
        <h4>请选择生成日期</h4>
        <p>当前日期变量是 {currentSeed}，今天是 {todaySeed}。</p>
        <div style={{ marginTop: 10, color: '#475569', fontSize: '13px', lineHeight: '1.6' }}>
          <div>点击“使用日期框日期生成”：使用 {currentSeed} 生成。</div>
          <div>点击“使用当前日期生成”：使用 {todaySeed} 生成，并同步日期变量。</div>
          <div>点击“取消生成”：取消本次生成（不修改日期变量）。</div>
        </div>
        <div className="dialog-actions">
          <button onClick={() => onSelect('cancel')} disabled={disabled}>取消生成</button>
          <button onClick={() => onSelect('today')} disabled={disabled}>使用当前日期生成</button>
          <button className="btn-primary" onClick={() => onSelect('input')} disabled={disabled}>使用日期框日期生成</button>
        </div>
      </div>
    </div>
  );
}

function ProxyTab({ token }) {
  const [proxies, setProxies] = useState([]);
  const [selected, setSelected] = useState(new Set());
  const [importText, setImportText] = useState('');
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState({ msg: '', isErr: false });
  const [testResults, setTestResults] = useState({});
  const [testProgress, setTestProgress] = useState({ processed: 0, total: 0 });
  const [runningTest, setRunningTest] = useState(false);
  const [viewMode, setViewMode] = useState('config'); // 'config' | 'test' | 'ipcount'
  const [showExportDialog, setShowExportDialog] = useState(false);
  const [showSelectDialog, setShowSelectDialog] = useState(false);
  const [showIpCountSelectDialog, setShowIpCountSelectDialog] = useState(false);
  const [showIpCountExportDialog, setShowIpCountExportDialog] = useState(false);
  const [jumpProxy, setJumpProxy] = useState('');
  const [savedJumpProxy, setSavedJumpProxy] = useState('');
  const [savingJumpProxy, setSavingJumpProxy] = useState(false);
  const [testSites, setTestSites] = useState([]);
  const todayDateSeed = useMemo(() => getTodayDateSeed(), []);
  const [dateSeed, setDateSeed] = useState(() => getTodayDateSeed());
  const [savedDateSeed, setSavedDateSeed] = useState(() => getTodayDateSeed());
  const [savingDateSeed, setSavingDateSeed] = useState(false);
  const [showDateSeedGenerateDialog, setShowDateSeedGenerateDialog] = useState(false);
  const dateSeedGenerateResolveRef = useRef(null);
  const proxiesRef = useRef([]);

  // Connectivity test settings
  const [testRounds, setTestRounds] = useState(() => Number(localStorage.getItem('test_rounds')) || 3);
  const [testBatchSize, setTestBatchSize] = useState(() => {
    const v = localStorage.getItem('test_batchSize');
    return v !== null ? Number(v) : 0;
  });

  // IP Count Test state
  const [ipCountRunning, setIpCountRunning] = useState(false);
  const [ipCountProgress, setIpCountProgress] = useState(null); // { results: { [name]: stats }, done }
  const [ipCountMaxIps, setIpCountMaxIps] = useState(() => Number(localStorage.getItem('ipcount_maxIps')) || 350);
  const [ipCountMaxSuccess, setIpCountMaxSuccess] = useState(() => Number(localStorage.getItem('ipcount_maxSuccess')) || 350);
  const [ipCountMaxQueries, setIpCountMaxQueries] = useState(() => Number(localStorage.getItem('ipcount_maxQueries')) || 1000);
  const [ipCountBatchSize, setIpCountBatchSize] = useState(() => Number(localStorage.getItem('ipcount_batchSize')) || 10);
  const [ipCountConvergeThreshold, setIpCountConvergeThreshold] = useState(() => Number(localStorage.getItem('ipcount_converge')) || 30);
  const [ipCountConcurrency, setIpCountConcurrency] = useState(() => Number(localStorage.getItem('ipcount_concurrency')) || 50);

  // Persist IP count settings
  useEffect(() => { localStorage.setItem('ipcount_maxIps', ipCountMaxIps); }, [ipCountMaxIps]);
  useEffect(() => { localStorage.setItem('ipcount_maxSuccess', ipCountMaxSuccess); }, [ipCountMaxSuccess]);
  useEffect(() => { localStorage.setItem('ipcount_maxQueries', ipCountMaxQueries); }, [ipCountMaxQueries]);
  useEffect(() => { localStorage.setItem('ipcount_batchSize', ipCountBatchSize); }, [ipCountBatchSize]);
  useEffect(() => { localStorage.setItem('ipcount_converge', ipCountConvergeThreshold); }, [ipCountConvergeThreshold]);
  useEffect(() => { localStorage.setItem('ipcount_concurrency', ipCountConcurrency); }, [ipCountConcurrency]);
  useEffect(() => { localStorage.setItem('test_rounds', testRounds); }, [testRounds]);
  useEffect(() => { localStorage.setItem('test_batchSize', testBatchSize); }, [testBatchSize]);

  // Cross analysis: auto-compute whenever ipCountProgress changes
  const ipCountCrossAnalysis = useMemo(() => {
    if (!ipCountProgress || !ipCountProgress.results) return null;
    const results = ipCountProgress.results;
    const names = Object.keys(results).filter(n => results[n] && results[n].ips && Object.keys(results[n].ips).length > 0);
    if (names.length < 2) return null;
    const ipOwners = new Map();
    for (const name of names) {
      const ips = results[name].ips || {};
      for (const ip of Object.keys(ips)) {
        if (!ipOwners.has(ip)) ipOwners.set(ip, new Set());
        ipOwners.get(ip).add(name);
      }
    }
    const analysis = {};
    for (const name of names) {
      const ips = Object.keys(results[name].ips || {});
      let shared = 0;
      const overlapWith = {};
      for (const ip of ips) {
        const owners = ipOwners.get(ip);
        if (owners.size > 1) {
          shared++;
          for (const other of owners) {
            if (other !== name) overlapWith[other] = (overlapWith[other] || 0) + 1;
          }
        }
      }
      analysis[name] = {
        total: ips.length,
        shared,
        exclusive: ips.length - shared,
        sharedRate: ips.length > 0 ? Math.round(shared / ips.length * 100) : 0,
        overlapWith,
      };
    }
    return analysis;
  }, [ipCountProgress]);

  const loadProxies = useCallback(async () => {
    try {
      const data = await apiRequest('/api/proxy', { headers: authHeader(token) });
      setProxies(data.data || []);
      setSelected(new Set());
      const oldProxies = proxiesRef.current;
      proxiesRef.current = data.data || [];

      // Remap test results when proxy names change (e.g. after delete+renumber)
      // Match by proxy URL sequentially to find old→new name mapping
      const newProxies = data.data || [];
      if (oldProxies.length > 0 && newProxies.length > 0 && oldProxies.length !== newProxies.length) {
        setTestResults(prev => {
          if (!prev || Object.keys(prev).length === 0) return prev;
          const nameMap = {};
          let newIdx = 0;
          for (let oldIdx = 0; oldIdx < oldProxies.length && newIdx < newProxies.length; oldIdx++) {
            const oldP = oldProxies[oldIdx];
            const newP = newProxies[newIdx];
            if ((oldP.proxy || '') === (newP.proxy || '')) {
              if (oldP.name !== newP.name) {
                nameMap[oldP.name] = newP.name;
              }
              newIdx++;
            }
            // else: old proxy was deleted/skipped
          }
          // Only remap if there are actual name changes
          if (Object.keys(nameMap).length === 0) return prev;
          const next = {};
          for (const [oldName, value] of Object.entries(prev)) {
            const newName = nameMap[oldName] || oldName;
            // Skip entries whose old name no longer exists and has no mapping
            const stillExists = newProxies.some(p => p.name === newName);
            if (stillExists) {
              next[newName] = { ...value, name: newName };
            }
          }
          return next;
        });
      }

      if (!ipCountRunning) {
        setIpCountProgress(prev => {
          if (!prev || !prev.results || Object.keys(prev.results).length === 0) return prev;
          // Sequential diff: walk old and new lists, match by proxy URL in order
          const remapped = {};
          let newIdx = 0;
          for (let oldIdx = 0; oldIdx < oldProxies.length && newIdx < newProxies.length; oldIdx++) {
            const oldP = oldProxies[oldIdx];
            const newP = newProxies[newIdx];
            if ((oldP.proxy || '') === (newP.proxy || '')) {
              // Same proxy, remap result to new name
              const oldResult = prev.results[oldP.name];
              if (oldResult) {
                remapped[newP.name] = { ...oldResult, name: newP.name };
              }
              newIdx++;
            }
            // else: old proxy was deleted, skip it
          }
          if (Object.keys(remapped).length === 0) return null;
          return { ...prev, results: remapped };
        });
      }
    } catch (e) {
      setStatus({ msg: `加载失败: ${e.message}`, isErr: true });
    }
  }, [token]);

  const loadRunningTest = useCallback(async () => {
    try {
      const data = await apiRequest('/api/proxy-test/running', { headers: authHeader(token) });
      if (!data.ok) return;

      // 1. Restore latest completed test results (survive page refresh)
      if (data.latestResults && Array.isArray(data.latestResults) && data.latestResults.length > 0) {
        const resultMap = {};
        data.latestResults.forEach(r => { resultMap[r.name] = r; });
        setTestResults(prev => ({ ...resultMap, ...prev }));
        setViewMode('test');
      }

      // 2. Resume running connectivity test
      if (data.running && data.job) {
        setRunningTest(true);
        setViewMode('test');
        setStatus({ msg: '发现后台有正在进行的测试任务，已恢复进度。', isErr: false });

        let resultsArray = (data.job.result && data.job.result.results) || (data.job.progress && data.job.progress.results);
        if (resultsArray) {
          const resultMap = {};
          resultsArray.forEach(r => { resultMap[r.name] = r; });
          setTestResults(prev => ({ ...prev, ...resultMap }));
        }

        const jobId = data.job.id;
        const poll = setInterval(async () => {
          try {
            const st = await apiRequest(`/api/proxy-test/job/${jobId}`, { headers: authHeader(token) });
            const job = st.job;
            let currentResults = (job.result && job.result.results) || (job.progress && job.progress.results);
            if (currentResults) {
              const rMap = {};
              currentResults.forEach(r => { rMap[r.name] = r; });
              setTestResults(prev => ({ ...prev, ...rMap }));
            }
            if (job.progress) {
              setTestProgress({ processed: job.progress.processed || 0, total: job.progress.total || 0 });
            }
            if (job.status === 'done' || job.status === 'fail') {
              clearInterval(poll);
              setRunningTest(false);
              setStatus({ msg: `测试 ${job.status === 'done' ? '完成' : '失败'}.`, isErr: job.status === 'fail' });
            }
          } catch (e) {
            clearInterval(poll);
            setRunningTest(false);
          }
        }, 1500);
      }

      // 3. Resume running IP count test, or restore completed results
      if (data.ipCountRunning) {
        setIpCountRunning(true);
        if (!data.running) setViewMode('ipcount');
        setStatus(prev => prev.msg ? prev : { msg: '发现后台有正在进行的IP数量测试，已恢复进度。', isErr: false });
        if (data.ipCountProgress) {
          setIpCountProgress(data.ipCountProgress);
        }
        const poll = setInterval(async () => {
          try {
            const st = await apiRequest('/api/proxy-test/ip-count/status', { headers: authHeader(token) });
            if (st.progress) {
              setIpCountProgress(prev => {
                const existing = (prev && prev.results) ? { ...prev.results } : {};
                const incoming = st.progress.results || {};
                return { ...st.progress, results: { ...existing, ...incoming } };
              });
            }
            if (!st.running || (st.progress && st.progress.done)) {
              clearInterval(poll);
              setIpCountRunning(false);
              setStatus({ msg: 'IP数量测试完成', isErr: false });
            }
          } catch (e) {
            clearInterval(poll);
            setIpCountRunning(false);
          }
        }, 1500);
      } else if (data.ipCountProgress && data.ipCountProgress.results && Object.keys(data.ipCountProgress.results).length > 0) {
        // Restore completed IP count results
        setIpCountProgress(data.ipCountProgress);
      }
    } catch (e) {
      // Ignore errors if running job check fails
    }
  }, [token]);

  const loadTestSites = useCallback(async () => {
    try {
      const data = await apiRequest('/api/proxy-test/config', { headers: authHeader(token) });
      if (data.ok) setTestSites(data.sites || []);
    } catch (e) {
      console.warn('Failed to load test sites:', e);
    }
  }, [token]);

  const loadDateSeed = useCallback(async () => {
    try {
      const data = await apiRequest('/api/control/date-seed', { headers: authHeader(token) });
      const next = normalizeDateSeedInput(data.dateSeed || todayDateSeed);
      const finalSeed = isValidDateSeed(next) ? next : todayDateSeed;
      setDateSeed(finalSeed);
      setSavedDateSeed(finalSeed);
    } catch (e) {
      setStatus({ msg: `日期变量加载失败: ${e.message}`, isErr: true });
    }
  }, [token, todayDateSeed]);

  const loadJumpSettings = useCallback(async () => {
    try {
      const data = await apiRequest('/api/proxy/jump-settings', { headers: authHeader(token) });
      const jump = String((data.data && data.data.jump) || '').trim();
      setJumpProxy(jump);
      setSavedJumpProxy(jump);
    } catch (e) {
      setStatus({ msg: `Jump 设置加载失败: ${e.message}`, isErr: true });
    }
  }, [token]);

  const persistJumpSetting = useCallback(async (jump, { silent = false } = {}) => {
    const normalized = String(jump || '').trim();
    setSavingJumpProxy(true);
    try {
      await apiRequest('/api/proxy/jump-settings', {
        method: 'POST',
        headers: authHeader(token),
        body: { jump: normalized },
      });
      setJumpProxy(normalized);
      setSavedJumpProxy(normalized);
      if (!silent) {
        setStatus({
          msg: normalized
            ? `Jump 已自动保存：${normalized}`
            : 'Jump 为空，已自动保存并将从生成配置中移除。',
          isErr: false,
        });
      }
      return normalized;
    } finally {
      setSavingJumpProxy(false);
    }
  }, [token]);

  const persistDateSeed = useCallback(async (seed, { silent = false } = {}) => {
    const normalized = normalizeDateSeedInput(seed);
    if (!isValidDateSeed(normalized)) {
      throw new Error('日期变量必须以 YYMMDD 开头，可加后缀，例如 260224、260224-1、260224a。');
    }

    setSavingDateSeed(true);
    try {
      await apiRequest('/api/control/date-seed', {
        method: 'POST',
        headers: authHeader(token),
        body: { dateSeed: normalized },
      });
      setDateSeed(normalized);
      setSavedDateSeed(normalized);
      window.dispatchEvent(new CustomEvent('date-seed-updated', { detail: { dateSeed: normalized } }));
      if (!silent) {
        setStatus({ msg: `日期变量已自动保存：${normalized}`, isErr: false });
      }
      return normalized;
    } finally {
      setSavingDateSeed(false);
    }
  }, [token]);

  const askDateSeedChoiceForGenerate = useCallback(() => (
    new Promise((resolve) => {
      dateSeedGenerateResolveRef.current = resolve;
      setShowDateSeedGenerateDialog(true);
    })
  ), []);

  const handleDateSeedGenerateChoice = useCallback((choice) => {
    setShowDateSeedGenerateDialog(false);
    const resolve = dateSeedGenerateResolveRef.current;
    dateSeedGenerateResolveRef.current = null;
    if (resolve) resolve(choice);
  }, []);

  useEffect(() => {
    loadProxies();
    loadRunningTest();
    loadTestSites();
    loadDateSeed();
    loadJumpSettings();
  }, [loadProxies, loadRunningTest, loadTestSites, loadDateSeed, loadJumpSettings]);

  useEffect(() => {
    const onDateSeedUpdated = (evt) => {
      const next = normalizeDateSeedInput(evt && evt.detail && evt.detail.dateSeed);
      if (!isValidDateSeed(next)) return;
      setDateSeed(next);
      setSavedDateSeed(next);
    };
    window.addEventListener('date-seed-updated', onDateSeedUpdated);
    return () => window.removeEventListener('date-seed-updated', onDateSeedUpdated);
  }, []);

  useEffect(() => (
    () => {
      const resolve = dateSeedGenerateResolveRef.current;
      dateSeedGenerateResolveRef.current = null;
      if (resolve) resolve('cancel');
    }
  ), []);

  useEffect(() => {
    const current = normalizeDateSeedInput(dateSeed);
    if (!isValidDateSeed(current)) return;
    if (current === savedDateSeed) return;

    const timer = setTimeout(() => {
      persistDateSeed(current, { silent: true }).catch((e) => {
        setStatus({ msg: `自动保存日期变量失败: ${e.message}`, isErr: true });
      });
    }, 600);

    return () => clearTimeout(timer);
  }, [dateSeed, savedDateSeed, persistDateSeed]);

  useEffect(() => {
    const current = String(jumpProxy || '').trim();
    const previous = String(savedJumpProxy || '').trim();
    if (current === previous) return;

    const timer = setTimeout(() => {
      persistJumpSetting(current, { silent: true }).catch((e) => {
        setStatus({ msg: `自动保存 Jump 失败: ${e.message}`, isErr: true });
      });
    }, 600);

    return () => clearTimeout(timer);
  }, [jumpProxy, savedJumpProxy, persistJumpSetting]);

  const handleImport = async () => {
    if (!importText.trim()) return;
    const previousCount = proxies.length;
    setLoading(true);
    try {
      const startIdx = proxies.length > 0 ? parseInt(proxies[proxies.length - 1].name.replace(/[^\d]/g, '') || 0) + 1 : 1;
      const res = await apiRequest('/api/proxy/import', {
        method: 'POST',
        headers: authHeader(token),
        body: { text: importText, startIndex: startIdx }
      });
      const importedCount = Number(res && res.imported || 0);
      if (!(previousCount === 0 && importedCount === 0)) {
        setStatus({ msg: `成功导入 ${importedCount} 条代理。`, isErr: false });
      }
      setImportText('');
      loadProxies();
    } catch (e) {
      setStatus({ msg: `导入失败: ${e.message}`, isErr: true });
    } finally { setLoading(false); }
  };

  const handleAction = async (actionPath, payload) => {
    setLoading(true);
    const names = Array.from(selected);
    if (!names.length) {
      setStatus({ msg: "未选择任何项目。", isErr: true });
      setLoading(false); return;
    }
    try {
      await apiRequest(actionPath, {
        method: 'POST',
        headers: authHeader(token),
        body: { names, ...payload }
      });
      // After delete: server renumbers remaining proxies sequentially.
      // loadProxies() will detect the name changes and remap testResults by matching proxy URLs.
      if (actionPath === '/api/proxy/delete') {
        setSelected(new Set());
        await loadProxies();
        setStatus({ msg: `已删除 ${names.length} 个代理（已重新编号）。`, isErr: false });
      } else {
        setStatus({ msg: `成功对 ${names.length} 个项目执行操作。`, isErr: false });
        loadProxies();
      }
    } catch (e) {
      setStatus({ msg: `操作失败: ${e.message}`, isErr: true });
    } finally { setLoading(false); }
  };

  const toggleCheckboxStatus = async (p, checked) => {
    try {
      await apiRequest('/api/proxy/toggle', {
        method: 'POST',
        headers: authHeader(token),
        body: { names: [p.name], disable: !checked }
      });
      loadProxies();
    } catch (e) {
      setStatus({ msg: `操作失败: ${e.message}`, isErr: true });
    }
  };

  const handleSaveProxyField = async (name, field, val) => {
    try {
      let finalField = field;
      let finalValue = val;

      if (field === 'minRange' || field === 'maxRange') {
        const proxy = proxies.find(p => p.name === name);
        if (!proxy) return;
        let min = '', max = '';

        if (proxy.randRange) {
          const match = proxy.randRange.match(/\[\s*(\d+)\s*,\s*(\d+)\s*\]/);
          if (match) {
            min = match[1];
            max = match[2];
          }
        }

        if (field === 'minRange') min = val;
        else max = val;

        // Default missing value
        if (min && !max) max = min;
        if (max && !min) min = '0';

        finalField = 'proxyRandRange';
        finalValue = (min && max) ? `[${min}, ${max}]` : '';
      }

      await apiRequest('/api/proxy/update', {
        method: 'POST',
        headers: authHeader(token),
        body: { name, field: finalField, value: finalValue }
      });
      loadProxies();
    } catch (e) {
      setStatus({ msg: `保存失败: ${e.message}`, isErr: true });
    }
  };

  const toggleSelectAll = (e) => {
    if (e.target.checked) setSelected(new Set(proxies.map(p => p.name)));
    else setSelected(new Set());
  };

  const toggleSelect = (name) => {
    const next = new Set(selected);
    if (next.has(name)) next.delete(name);
    else next.add(name);
    setSelected(next);
  };

  const resolveDateSeedForGenerate = async () => {
    const current = normalizeDateSeedInput(dateSeed);
    if (!isValidDateSeed(current)) {
      setStatus({ msg: '日期变量必须以 YYMMDD 开头，可加后缀，例如 260224、260224-1、260224a。', isErr: true });
      return null;
    }

    let finalSeed = current;
    if (current !== todayDateSeed) {
      const choice = await askDateSeedChoiceForGenerate();
      if (choice === 'today') {
        finalSeed = todayDateSeed;
      } else if (choice !== 'input') {
        setStatus({ msg: '已取消生成，日期变量保持不变。', isErr: false });
        return null;
      }
    }

    if (finalSeed !== savedDateSeed) {
      try {
        await persistDateSeed(finalSeed, { silent: true });
      } catch (e) {
        setStatus({ msg: `保存日期变量失败: ${e.message}`, isErr: true });
        return null;
      }
    }

    return finalSeed;
  };

  const handleGenerateConfig = async () => {
    const selectedSeed = await resolveDateSeedForGenerate();
    if (!selectedSeed) return;

    try {
      await persistJumpSetting(jumpProxy, { silent: true });
    } catch (e) {
      setStatus({ msg: `保存 Jump 失败: ${e.message}`, isErr: true });
      return;
    }

    setLoading(true);
    try {
      await apiRequest('/api/proxy/generate', {
        method: 'POST',
        headers: authHeader(token),
      });
      setStatus({
        msg: `生成成功（使用日期 ${selectedSeed}）。`,
        isErr: false,
      });
      loadProxies();
    } catch (e) {
      setStatus({ msg: `生成失败: ${e.message}`, isErr: true });
    } finally {
      setLoading(false);
    }
  };

  // ─── IP Count Test ─────────────────────────────────────
  const startIpCountTest = async () => {
    const toTest = proxies.filter(p => selected.has(p.name) || selected.size === 0);
    if (!toTest.length) {
      setStatus({ msg: '没有可用的代理模板。', isErr: true });
      return;
    }

    const templates = toTest.map(p => {
      let randRange = null;
      if (p.randRange) {
        const match = p.randRange.match(/\[\s*(\d+)\s*,\s*(\d+)\s*\]/);
        if (match) randRange = [Number(match[1]), Number(match[2])];
      }
      return { name: p.name, proxyTemplate: p.proxy || '', randRange };
    });

    setIpCountRunning(true);
    setViewMode('ipcount');
    setStatus({ msg: `开始IP数量测试: ${templates.length} 个代理...`, isErr: false });

    // Clear only the results for proxies being tested, keep others
    setIpCountProgress(prev => {
      const kept = (prev && prev.results) ? { ...prev.results } : {};
      for (const t of templates) delete kept[t.name];
      return { results: kept, done: false };
    });

    try {
      await apiRequest('/api/proxy-test/ip-count/start', {
        method: 'POST',
        headers: authHeader(token),
        body: {
          templates,
          jumpProxy: String(jumpProxy || '').trim(),
          batchSize: ipCountBatchSize,
          concurrencyPerTemplate: ipCountConcurrency,
          maxUniqueIps: ipCountMaxIps,
          maxSuccess: ipCountMaxSuccess,
          maxQueries: ipCountMaxQueries,
          convergeThreshold: ipCountConvergeThreshold,
        },
      });

      const poll = setInterval(async () => {
        try {
          const st = await apiRequest('/api/proxy-test/ip-count/status', { headers: authHeader(token) });
          if (st.progress) {
            // Merge new results with existing (non-participating) results
            setIpCountProgress(prev => {
              const existing = (prev && prev.results) ? { ...prev.results } : {};
              const incoming = st.progress.results || {};
              return { ...st.progress, results: { ...existing, ...incoming } };
            });
          }
          if (!st.running || (st.progress && st.progress.done)) {
            clearInterval(poll);
            setIpCountRunning(false);
            setStatus({ msg: 'IP数量测试完成', isErr: false });
          }
        } catch (e) {
          clearInterval(poll);
          setIpCountRunning(false);
        }
      }, 1500);
    } catch (err) {
      setStatus({ msg: `IP数量测试失败: ${err.message}`, isErr: true });
      setIpCountRunning(false);
    }
  };

  const handleStopIpCount = async () => {
    try {
      await apiRequest('/api/proxy-test/ip-count/stop', {
        method: 'POST',
        headers: authHeader(token),
      });
      setStatus({ msg: '正在停止IP数量测试...', isErr: false });
    } catch (_) { }
  };

  const startTest = async () => {
    const toTest = proxies.filter(p => selected.has(p.name) || selected.size === 0);
    if (!toTest.length) {
      setStatus({ msg: '没有可用的代理可供测试。', isErr: true });
      return;
    }
    setRunningTest(true);
    setTestProgress({ processed: 0, total: toTest.length });
    // Clear only the results for proxies being tested, keep others
    setTestResults(prev => {
      const kept = { ...prev };
      for (const p of toTest) delete kept[p.name];
      return kept;
    });
    setStatus({ msg: `开始测试 ${toTest.length} 条代理...`, isErr: false });

    const yamlText = "instances:\n" + toTest.map(p => {
      const pRange = p.randRange ? `\n            randRange: ${p.randRange}` : '';
      return `  - name: ${p.name}\n    config:\n      proxy:\n        upstream:\n          - url: ${p.proxy || ''}${pRange}`;
    }).join('\n');

    try {
      const data = await apiRequest('/api/proxy-test/start', {
        method: 'POST',
        headers: authHeader(token),
        body: { yamlText, rounds: testRounds, concurrency: testBatchSize || toTest.length, siteConcurrency: testRounds, jumpProxy: String(jumpProxy || '').trim() }
      });

      const jobId = data.jobId;
      const poll = setInterval(async () => {
        try {
          const st = await apiRequest(`/api/proxy-test/job/${jobId}`, { headers: authHeader(token) });
          const job = st.job;

          let resultsArray = (job.result && job.result.results) || (job.progress && job.progress.results);
          if (resultsArray) {
            const resultMap = {};
            resultsArray.forEach(r => { resultMap[r.name] = r; });
            setTestResults(prev => ({ ...prev, ...resultMap }));
          }
          if (job.progress) {
            setTestProgress({ processed: job.progress.processed || 0, total: job.progress.total || 0 });
          }
          if (job.status === 'done' || job.status === 'fail') {
            clearInterval(poll);
            setRunningTest(false);
            setStatus({ msg: `测试 ${job.status === 'done' ? '完成' : '失败'}.`, isErr: job.status === 'fail' });
          }
        } catch (e) {
          clearInterval(poll);
          setRunningTest(false);
        }
      }, 1500);

    } catch (err) {
      setStatus({ msg: `启动测试失败: ${err.message}`, isErr: true });
      setRunningTest(false);
    }
  };

  function parseRatio(score) {
    if (!score || score === 'N/A') return { ok: 0, total: 0 };
    const m = String(score).match(/^(\d+)\/(\d+)$/);
    if (!m) return { ok: 0, total: 0 };
    return { ok: Number(m[1]), total: Number(m[2]) };
  }

  const renderBadge = (score) => {
    if (!score || score === 'N/A') return null;
    const { ok, total } = parseRatio(score);
    let cls = 'warn';
    if (total > 0 && ok === total) cls = 'ok';
    if (total > 0 && ok === 0) cls = 'err';
    return <span className={`badge ${cls}`}>{score}</span>;
  };

  const renderSiteResult = (score, forbidden, cf, errCount, time) => {
    if (!score) return <span style={{ color: '#999', fontSize: '12px' }}>等待测试...</span>;
    if (score === 'N/A') return <span className="badge err">测试失败</span>;
    const { ok, total } = parseRatio(score);
    let cls = 'warn';
    if (total > 0 && ok === total) cls = 'ok';
    if (total > 0 && ok === 0) cls = 'err';

    return (
      <div style={{ fontSize: '11px', lineHeight: '1.2' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
          <span className={`badge ${cls}`} style={{ padding: '1px 4px' }}>{score}</span>
          <span style={{ color: '#666', whiteSpace: 'nowrap' }}>{time}</span>
        </div>
        {(forbidden > 0 || cf > 0 || errCount > 0) && (
          <div style={{ marginTop: 2, display: 'flex', gap: '4px' }}>
            {forbidden > 0 && (
              <span title="403 Forbidden" style={{ color: '#d93025' }}>
                403:{forbidden}
              </span>
            )}
            {cf > 0 && (
              <span title="Cloudflare Challenge" style={{ color: '#d93025', marginLeft: forbidden > 0 ? 4 : 0 }}>
                CF:{cf}
              </span>
            )}
            {errCount > 0 && (
              <span title="Connection Error / Timeout" style={{ color: '#d93025', marginLeft: (forbidden > 0 || cf > 0) ? 4 : 0 }}>
                Err:{errCount}
              </span>
            )}
          </div>
        )}
      </div>
    );
  };

  // ===== Export Proxy Feature =====
  // Collect all unique score values per site from current test results
  const exportScoreOptions = useMemo(() => {
    const siteScores = {};
    testSites.forEach(site => { siteScores[site.key] = new Set(); });
    proxies.forEach(p => {
      const tr = testResults[p.name];
      if (!tr) return;
      testSites.forEach(site => {
        const score = tr[site.key];
        if (score) siteScores[site.key].add(score);
      });
    });
    // Convert to sorted arrays
    const result = {};
    Object.keys(siteScores).forEach(key => {
      const arr = Array.from(siteScores[key]);
      // Sort: full pass first, then partial, then N/A
      arr.sort((a, b) => {
        if (a === 'N/A') return 1;
        if (b === 'N/A') return -1;
        const ra = parseRatio(a), rb = parseRatio(b);
        return rb.ok - ra.ok;
      });
      result[key] = arr;
    });
    return result;
  }, [testResults, testSites, proxies]);

  const handleExportProxies = (filters) => {
    // filters: { [siteKey]: Set<score> } — proxy must match ALL site filters
    const matched = proxies.filter(p => {

      const tr = testResults[p.name];
      if (!tr) return false;
      for (const siteKey of Object.keys(filters)) {
        const allowedScores = filters[siteKey];
        if (!allowedScores || allowedScores.size === 0) continue;
        const proxyScore = tr[siteKey] || '';
        if (!allowedScores.has(proxyScore)) return false;
      }
      return true;
    });

    if (matched.length === 0) {
      setStatus({ msg: '没有符合筛选条件的代理。', isErr: true });
      return;
    }

    const lines = matched.map(p => {
      const url = p.proxy || '';
      if (p.randRange) {
        const match = p.randRange.match(/\[\s*(\d+)\s*,\s*(\d+)\s*\]/);
        if (match) return `${url}|${match[1]}|${match[2]}`;
      }
      return url;
    });

    const text = lines.join('\n');
    navigator.clipboard.writeText(text).then(() => {
      setStatus({ msg: `已导出 ${matched.length} 条代理到剪贴板。`, isErr: false });
    }).catch(() => {
      // Fallback: show in a prompt
      prompt('复制以下代理配置：', text);
      setStatus({ msg: `已导出 ${matched.length} 条代理。`, isErr: false });
    });
    setShowExportDialog(false);
  };

  const handleSelectByResult = ({ filters, latencyThreshold, nameFilter, regionFilter, untestedOnly, action }) => {
    const hasScoreFilter = Object.values(filters).some(set => set.size > 0);
    const hasLatencyFilter = latencyThreshold > 0;
    const hasNameFilter = nameFilter && nameFilter.length > 0;
    const hasRegionFilter = regionFilter && regionFilter.length > 0;

    const matched = proxies.filter(p => {
      const tr = testResults[p.name];

      // Untested filter
      if (untestedOnly && !tr) return true;

      // Name filter
      if (hasNameFilter && p.name.toLowerCase().includes(nameFilter.toLowerCase())) return true;

      // Region filter
      if (hasRegionFilter && tr && tr.providerArea === regionFilter) return true;

      if (!tr) return false;

      // Score filter: proxy must match ALL site filters
      let matchesScore = true;
      if (hasScoreFilter) {
        for (const siteKey of Object.keys(filters)) {
          const allowedScores = filters[siteKey];
          if (!allowedScores || allowedScores.size === 0) continue;
          const proxyScore = tr[siteKey] || '';
          if (!allowedScores.has(proxyScore)) { matchesScore = false; break; }
        }
        if (matchesScore) return true;
      }

      // Latency filter
      if (hasLatencyFilter) {
        for (const site of testSites) {
          const timeStr = tr[`${site.key}_time`];
          if (!timeStr || timeStr === 'N/A' || timeStr === '...') continue;
          const ms = parseInt(String(timeStr).replace(/ms$/i, ''), 10);
          if (!isNaN(ms) && ms > latencyThreshold) return true;
        }
      }

      return false;
    });

    if (matched.length === 0) {
      setStatus({ msg: '没有符合筛选条件的代理。', isErr: true });
      return;
    }

    const matchedNames = new Set(matched.map(p => p.name));
    if (action === 'deselect') {
      setSelected(prev => {
        const next = new Set(prev);
        for (const name of matchedNames) next.delete(name);
        return next;
      });
      setStatus({ msg: `已取消选中 ${matched.length} 条代理。`, isErr: false });
    } else {
      setSelected(prev => {
        const next = new Set(prev);
        for (const name of matchedNames) next.add(name);
        return next;
      });
      setStatus({ msg: `已选中 ${matched.length} 条代理。`, isErr: false });
    }
    setShowSelectDialog(false);
  };

  const handleIpCountSelect = ({ filters, untestedOnly, action }) => {
    const matched = proxies.filter(p => {
      // Untested filter
      if (untestedOnly && (!ipCountProgress || !ipCountProgress.results || !ipCountProgress.results[p.name])) return true;

      if (!ipCountProgress || !ipCountProgress.results) return false;
      const ipr = ipCountProgress.results[p.name];
      if (!ipr) return false;

      if (filters.length === 0) return false;
      // All enabled filters must match (AND logic)
      for (const f of filters) {
        let value;
        if (f.field === 'uniqueIps') {
          value = ipr.uniqueIps || 0;
        } else if (f.field === 'dupRate') {
          value = ipr.success > 0 ? Math.round((ipr.success - (ipr.uniqueIps || 0)) / ipr.success * 100) : 0;
        } else if (f.field === 'crossRate') {
          const cross = crossAnalysis && crossAnalysis[p.name];
          value = cross ? cross.sharedRate : 0;
        } else continue;
        if (f.min !== null && value < f.min) return false;
        if (f.max !== null && value > f.max) return false;
      }
      return true;
    });

    if (matched.length === 0) {
      setStatus({ msg: '没有符合筛选条件的代理。', isErr: true });
      return;
    }

    const matchedNames = new Set(matched.map(p => p.name));
    if (action === 'deselect') {
      setSelected(prev => {
        const next = new Set(prev);
        for (const name of matchedNames) next.delete(name);
        return next;
      });
      setStatus({ msg: `已取消选中 ${matched.length} 条代理。`, isErr: false });
    } else {
      setSelected(prev => {
        const next = new Set(prev);
        for (const name of matchedNames) next.add(name);
        return next;
      });
      setStatus({ msg: `已选中 ${matched.length} 条代理。`, isErr: false });
    }
    setShowIpCountSelectDialog(false);
  };

  const handleIpCountExport = ({ filters }) => {
    if (!ipCountProgress || !ipCountProgress.results) {
      setStatus({ msg: '没有IP数量测试结果。', isErr: true });
      return;
    }
    const matched = proxies.filter(p => {
      const ipr = ipCountProgress.results[p.name];
      if (!ipr) return false;
      for (const f of filters) {
        let value;
        if (f.field === 'uniqueIps') value = ipr.uniqueIps || 0;
        else if (f.field === 'dupRate') value = ipr.success > 0 ? Math.round((ipr.success - (ipr.uniqueIps || 0)) / ipr.success * 100) : 0;
        else if (f.field === 'crossRate') { const cross = crossAnalysis && crossAnalysis[p.name]; value = cross ? cross.sharedRate : 0; }
        else continue;
        if (f.operator === '>' && !(value > f.threshold)) return false;
        if (f.operator === '<' && !(value < f.threshold)) return false;
      }
      return true;
    });
    if (matched.length === 0) { setStatus({ msg: '没有符合筛选条件的代理。', isErr: true }); return; }
    const lines = matched.map(p => {
      const url = p.proxy || '';
      if (p.randRange) {
        const match = p.randRange.match(/\[\s*(\d+)\s*,\s*(\d+)\s*\]/);
        if (match) return `${url}|${match[1]}|${match[2]}`;
      }
      return url;
    });
    navigator.clipboard.writeText(lines.join('\n')).then(() => {
      setStatus({ msg: `已导出 ${matched.length} 条代理到剪贴板。`, isErr: false });
    }).catch(() => {
      prompt('复制以下代理配置：', lines.join('\n'));
      setStatus({ msg: `已导出 ${matched.length} 条代理。`, isErr: false });
    });
    setShowIpCountExportDialog(false);
  };

  return (
    <div>
      <StatusMsg {...status} />
      <div className="top-grid">
        <div className="panel-card import-card">
          <h3 className="panel-title">代理导入</h3>
          <textarea
            className="import-area"
            value={importText}
            onChange={e => setImportText(e.target.value)}
            placeholder="粘贴代理配置（支持批量导入，自动根据模板去重）..."
          />
          <div className="import-btn-row">
            <button className="btn-primary" onClick={handleImport} disabled={loading || !importText.trim()}>批量导入</button>
          </div>
        </div>

        <div className="panel-card">
          <h3 className="panel-title">代理操作</h3>
          <div className="toolbar-actions">
            <div className="toolbar-row" style={{ alignItems: 'center' }}>
              <span style={{ color: '#475569', fontWeight: 600 }}>日期变量 (YYMMDD+后缀)</span>
              <input
                type="text"
                value={dateSeed}
                onChange={(e) => setDateSeed(normalizeDateSeedInput(e.target.value))}
                placeholder={todayDateSeed}
                style={{ width: '160px' }}
                disabled={loading || runningTest || savingDateSeed}
              />
              <span className="field-help">{savingDateSeed ? '日期变量自动保存中...' : '修改后自动保存'}</span>
            </div>
            <div className="toolbar-row">
              <button className="btn-danger" onClick={() => handleAction('/api/proxy/delete', {})} disabled={loading || selected.size === 0}>批量删除</button>
              <button
                onClick={() => {
                  if (viewMode === 'ipcount') setShowIpCountSelectDialog(true);
                  else setShowSelectDialog(true);
                }}
                disabled={
                  viewMode === 'ipcount'
                    ? (ipCountRunning || !ipCountProgress || !ipCountProgress.results || Object.keys(ipCountProgress.results).length === 0)
                    : false
                }
              >按结果选中</button>
              <button
                onClick={() => {
                  if (viewMode === 'ipcount') setShowIpCountExportDialog(true);
                  else setShowExportDialog(true);
                }}
                disabled={
                  viewMode === 'ipcount'
                    ? (ipCountRunning || !ipCountProgress || !ipCountProgress.results || Object.keys(ipCountProgress.results).length === 0)
                    : false
                }
              >导出代理</button>
            </div>
            <div className="toolbar-row" style={{ alignItems: 'center' }}>
              <label style={{ cursor: 'pointer' }}>
                <input type="radio" value="config" checked={viewMode === 'config'} onChange={() => setViewMode('config')} /> 代理配置视图
              </label>
              <label style={{ cursor: 'pointer' }}>
                <input type="radio" value="test" checked={viewMode === 'test'} onChange={() => setViewMode('test')} /> 连通性测试
              </label>
              <label style={{ cursor: 'pointer' }}>
                <input type="radio" value="ipcount" checked={viewMode === 'ipcount'} onChange={() => setViewMode('ipcount')} /> IP数量测试
              </label>
              <input
                type="text"
                placeholder="跳板代理 (可选)"
                value={jumpProxy}
                onChange={(e) => setJumpProxy(e.target.value)}
                style={{ width: '220px' }}
                disabled={runningTest || ipCountRunning || savingJumpProxy}
              />
              <button onClick={handleGenerateConfig} disabled={loading || runningTest || ipCountRunning || savingJumpProxy || savingDateSeed}>生成配置</button>
            </div>
            <div className="toolbar-row" style={{ alignItems: 'center' }}>
              {viewMode === 'test' && (
                <>
                  <span style={{ color: '#475569', fontSize: '12px' }}>同时测试:</span>
                  <input type="number" value={testBatchSize} onChange={e => setTestBatchSize(Math.max(0, Number(e.target.value) || 0))} placeholder="0=不限" style={{ width: '50px' }} disabled={runningTest} />
                  <span style={{ color: '#475569', fontSize: '12px' }}>换IP次数:</span>
                  <input type="number" value={testRounds} onChange={e => setTestRounds(Math.max(1, Math.min(10, Number(e.target.value) || 3)))} style={{ width: '50px' }} disabled={runningTest} />
                  <button className="btn-primary" onClick={startTest} disabled={runningTest || ipCountRunning}>
                    {runningTest ? (testProgress.total > 0 ? `测试中... (${testProgress.processed}/${testProgress.total})` : '测试中...') : (selected.size > 0 ? '测试选中代理' : '测试所有代理')}
                  </button>
                  {runningTest && (
                    <button style={{ background: '#dc2626', color: '#fff', border: 'none', borderRadius: '4px', padding: '4px 12px', cursor: 'pointer' }} onClick={async () => {
                      try {
                        await apiRequest('/api/proxy-test/stop', { method: 'POST', headers: authHeader(token) });
                        setStatus({ msg: '正在停止测试...', isErr: false });
                      } catch (_) { }
                    }}>停止测试</button>
                  )}

                </>
              )}
              {viewMode === 'ipcount' && (
                <>
                  <span style={{ color: '#475569', fontSize: '12px' }}>IP上限:</span>
                  <input type="number" value={ipCountMaxIps} onChange={e => setIpCountMaxIps(Math.max(1, Number(e.target.value) || 500))} style={{ width: '70px' }} disabled={ipCountRunning} />
                  <span style={{ color: '#475569', fontSize: '12px' }}>成功上限:</span>
                  <input type="number" value={ipCountMaxSuccess} onChange={e => setIpCountMaxSuccess(Math.max(1, Number(e.target.value) || 500))} style={{ width: '70px' }} disabled={ipCountRunning} />
                  <span style={{ color: '#475569', fontSize: '12px' }}>查询上限:</span>
                  <input type="number" value={ipCountMaxQueries} onChange={e => setIpCountMaxQueries(Math.max(1, Number(e.target.value) || 1500))} style={{ width: '70px' }} disabled={ipCountRunning} />
                  <span style={{ color: '#475569', fontSize: '12px' }}>并发:</span>
                  <input type="number" value={ipCountConcurrency} onChange={e => setIpCountConcurrency(Math.max(1, Math.min(200, Number(e.target.value) || 50)))} style={{ width: '60px' }} disabled={ipCountRunning} />
                  <span style={{ color: '#475569', fontSize: '12px' }}>同时测试:</span>
                  <input type="number" value={ipCountBatchSize} onChange={e => setIpCountBatchSize(Math.max(1, Math.min(50, Number(e.target.value) || 10)))} style={{ width: '50px' }} disabled={ipCountRunning} />
                  <span style={{ color: '#475569', fontSize: '12px' }}>收敛%:</span>
                  <input type="number" value={ipCountConvergeThreshold} onChange={e => setIpCountConvergeThreshold(Math.max(1, Math.min(100, Number(e.target.value) || 30)))} style={{ width: '55px' }} disabled={ipCountRunning} />
                  {!ipCountRunning ? (
                    <button className="btn-primary" onClick={startIpCountTest} disabled={runningTest}>
                      {selected.size > 0 ? `IP测试(选中${selected.size}个)` : 'IP测试(全部)'}
                    </button>
                  ) : (
                    <button className="btn-danger" onClick={handleStopIpCount}>停止IP测试</button>
                  )}

                </>
              )}
            </div>
          </div>
        </div>
      </div>

      <div className="table-container">
        <table>
          <thead>
            <tr>
              <th className="checkbox-cell"><input type="checkbox" checked={proxies.length > 0 && selected.size === proxies.length} onChange={toggleSelectAll} /></th>
              <th>名称</th>
              {viewMode === 'config' && (
                <>
                  <th>代理模板</th>
                  <th>最小范围</th>
                  <th>最大范围</th>
                  <th>地区 (注释)</th>
                </>
              )}

              {viewMode === 'test' && (
                <>
                  {testSites.map(site => (
                    <th key={site.key}>
                      {site.label} {site.ignoreChallenges ? '(时间)' : '(CF/403/时间)'}
                    </th>
                  ))}
                </>
              )}
              {viewMode === 'ipcount' && (
                <>
                  <th>查询</th>
                  <th>成功</th>
                  <th>失败</th>
                  <th>唯一IP</th>
                  <th>重复率</th>
                  <th>状态</th>
                  <th>耗时</th>
                  <th>交叉重复</th>
                </>
              )}
            </tr>
          </thead>
          <tbody>
            {proxies.map(p => {
              const tr = testResults[p.name] || {};
              // Parse min and max from randRange text, typically "[1000, 2000]"
              let minRange = '', maxRange = '';
              if (p.randRange) {
                const match = p.randRange.match(/\[\s*(\d+)\s*,\s*(\d+)\s*\]/);
                if (match) {
                  minRange = match[1];
                  maxRange = match[2];
                } else {
                  minRange = p.randRange;
                }
              }

              return (
                <tr key={p.name}>
                  <td className="checkbox-cell"><input type="checkbox" checked={selected.has(p.name)} onChange={() => toggleSelect(p.name)} /></td>
                  <td style={{ maxWidth: '200px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} title={viewMode === 'config' ? p.name : (p.commentRegion ? `${p.name} - ${p.commentRegion}` : p.name)}>
                    {viewMode === 'config' ? p.name : (p.commentRegion ? `${p.name} - ${p.commentRegion}` : p.name)}
                  </td>
                  {viewMode === 'config' && (
                    <>
                      <td style={{ maxWidth: '400px' }}><EditableCell value={p.proxy} onSave={(v) => handleSaveProxyField(p.name, 'proxy', v)} /></td>
                      <td style={{ maxWidth: '100px' }}><EditableCell value={minRange} onSave={(v) => handleSaveProxyField(p.name, 'minRange', v)} /></td>
                      <td style={{ maxWidth: '100px' }}><EditableCell value={maxRange} onSave={(v) => handleSaveProxyField(p.name, 'maxRange', v)} /></td>
                      <td style={{ maxWidth: '150px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} title={p.commentRegion}>{p.commentRegion}</td>
                    </>
                  )}

                  {viewMode === 'test' && (
                    <>
                      {testSites.map(site => (
                        <td key={site.key}>
                          {renderSiteResult(
                            tr[site.key],
                            site.ignoreChallenges ? undefined : tr[`${site.key}_403`],
                            site.ignoreChallenges ? undefined : tr[`${site.key}_cf`],
                            site.ignoreChallenges ? undefined : tr[`${site.key}_err`],
                            tr[`${site.key}_time`]
                          )}
                        </td>
                      ))}
                    </>
                  )}
                  {viewMode === 'ipcount' && (() => {
                    const ipr = ipCountProgress && ipCountProgress.results && ipCountProgress.results[p.name];
                    if (!ipr) return (
                      <>
                        <td colSpan={8} style={{ color: '#999', fontSize: '12px' }}>{'\u7b49\u5f85\u6d4b\u8bd5...'}</td>
                      </>
                    );
                    const statusText = ipr.done
                      ? (ipr.converged ? '✓ 收敛' : ipr.stoppedByLimit ? '✓ 达IP限' : ipr.stoppedByMaxSuccess ? '✓ 达成功限' : ipr.stoppedByMaxQueries ? '✓ 达查询限' : ipr.stoppedByUser ? '■ 停止' : '✓ 完成')
                      : (ipr.totalQueries > 0 ? '▶ 测试中...' : '⏳ 待测试');
                    const statusColor = ipr.done ? '#16a34a' : ipr.totalQueries > 0 ? '#2563eb' : '#94a3b8';
                    const dupRate = ipr.dupRate != null ? ipr.dupRate : (ipr.success > 0 ? Math.round((ipr.success - ipr.uniqueIps) / ipr.success * 100) : 0);
                    const cross = ipCountCrossAnalysis && ipCountCrossAnalysis[p.name];
                    const crossRate = cross ? cross.sharedRate : 0;
                    const crossTitle = cross && cross.overlapWith && Object.keys(cross.overlapWith).length > 0
                      ? Object.entries(cross.overlapWith).sort((a, b) => b[1] - a[1]).map(([n, c]) => `${n}: ${c}\u4e2a\u91cd\u590d`).join('\n')
                      : '\u65e0\u4ea4\u53c9';
                    return (
                      <>
                        <td>{ipr.totalQueries}</td>
                        <td style={{ color: '#16a34a' }}>{ipr.success}</td>
                        <td style={{ color: ipr.fail > 0 ? '#dc2626' : '#999' }}>{ipr.fail}</td>
                        <td><strong>{ipr.uniqueIps}</strong></td>
                        <td>
                          <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                            <div style={{ width: '50px', height: '6px', background: '#e2e8f0', borderRadius: '3px', overflow: 'hidden' }}>
                              <div style={{ width: `${dupRate}%`, height: '100%', background: dupRate > 50 ? '#dc2626' : dupRate > 20 ? '#f59e0b' : '#16a34a', transition: 'width 0.3s' }} />
                            </div>
                            <span style={{ fontSize: '11px', color: dupRate > 50 ? '#dc2626' : dupRate > 20 ? '#f59e0b' : '#16a34a', fontWeight: 600 }}>{dupRate}%</span>
                          </div>
                        </td>
                        <td style={{ color: statusColor, fontSize: '12px', whiteSpace: 'nowrap' }}>{statusText}</td>
                        <td style={{ fontSize: '12px', color: '#64748b' }}>{ipr.elapsed ? `${(ipr.elapsed / 1000).toFixed(1)}s` : ''}</td>
                        <td title={crossTitle} style={{ cursor: cross ? 'help' : 'default' }}>
                          {cross ? (
                            <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                              <div style={{ width: '50px', height: '6px', background: '#e2e8f0', borderRadius: '3px', overflow: 'hidden' }}>
                                <div style={{ width: `${crossRate}%`, height: '100%', background: crossRate > 50 ? '#dc2626' : crossRate > 20 ? '#f59e0b' : '#16a34a', transition: 'width 0.3s' }} />
                              </div>
                              <span style={{ fontSize: '11px', color: crossRate > 50 ? '#dc2626' : crossRate > 20 ? '#f59e0b' : '#16a34a' }}>{cross.shared}/{cross.total} ({crossRate}%)</span>
                            </div>
                          ) : <span style={{ color: '#999', fontSize: '11px' }}>-</span>}
                        </td>
                      </>
                    );
                  })()}
                </tr>
              );
            })}
            {proxies.length === 0 && <tr><td colSpan={viewMode === 'config' ? 7 : 7} style={{ textAlign: 'center', padding: '32px', color: '#64748b' }}>没有找到代理配置。</td></tr>}
          </tbody>
        </table>
      </div>

      <DateSeedGenerateDialog
        open={showDateSeedGenerateDialog}
        currentSeed={normalizeDateSeedInput(dateSeed)}
        todaySeed={todayDateSeed}
        onSelect={handleDateSeedGenerateChoice}
        disabled={loading || runningTest || savingDateSeed}
      />

      {showExportDialog && (
        <ExportProxyDialog
          testSites={testSites}
          scoreOptions={exportScoreOptions}
          onExport={handleExportProxies}
          onClose={() => setShowExportDialog(false)}
        />
      )}

      {showSelectDialog && (
        <SelectByResultDialog
          testSites={testSites}
          scoreOptions={exportScoreOptions}
          proxies={proxies}
          testResults={testResults}
          onApply={handleSelectByResult}
          onClose={() => setShowSelectDialog(false)}
        />
      )}

      {showIpCountExportDialog && (
        <IpCountExportDialog
          onExport={handleIpCountExport}
          onClose={() => setShowIpCountExportDialog(false)}
        />
      )}

      {showIpCountSelectDialog && (
        <IpCountSelectDialog
          onApply={handleIpCountSelect}
          onClose={() => setShowIpCountSelectDialog(false)}
        />
      )}
    </div>
  );
}

// ========== Export Proxy Dialog ==========
function ExportProxyDialog({ testSites, scoreOptions, onExport, onClose }) {
  // filters: { [siteKey]: Set<score> }
  const [filters, setFilters] = useState(() => {
    const init = {};
    testSites.forEach(site => { init[site.key] = new Set(); });
    return init;
  });

  const toggleScore = (siteKey, score) => {
    setFilters(prev => {
      const next = { ...prev };
      const set = new Set(next[siteKey] || []);
      if (set.has(score)) set.delete(score);
      else set.add(score);
      next[siteKey] = set;
      return next;
    });
  };

  const selectAllScores = (siteKey) => {
    setFilters(prev => {
      const next = { ...prev };
      next[siteKey] = new Set(scoreOptions[siteKey] || []);
      return next;
    });
  };

  const clearAllScores = (siteKey) => {
    setFilters(prev => {
      const next = { ...prev };
      next[siteKey] = new Set();
      return next;
    });
  };

  const hasAnyFilter = Object.values(filters).some(set => set.size > 0);

  const getBadgeClass = (score) => {
    if (!score || score === 'N/A') return 'err';
    const m = String(score).match(/^(\d+)\/(\d+)$/);
    if (!m) return 'warn';
    const ok = Number(m[1]), total = Number(m[2]);
    if (total > 0 && ok === total) return 'ok';
    if (total > 0 && ok === 0) return 'err';
    return 'warn';
  };

  return (
    <div className="dialog-backdrop" onClick={onClose}>
      <div className="dialog-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '520px' }}>
        <h4>导出代理 — 按测试结果筛选</h4>
        <p style={{ color: '#64748b', fontSize: '13px', marginBottom: '12px' }}>
          选择要导出的测试结果状态，代理需同时满足所有已选站点的条件。<br />
          未选择任何状态的站点将被忽略（不参与筛选）。
        </p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
          {testSites.map(site => {
            const options = scoreOptions[site.key] || [];
            if (options.length === 0) return null;
            const selected = filters[site.key] || new Set();
            return (
              <div key={site.key} style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' }}>
                  <span style={{ fontWeight: 600, fontSize: '13px' }}>{site.label}</span>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <button
                      onClick={() => selectAllScores(site.key)}
                      style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: 'none', border: '1px solid #ccc', borderRadius: '3px' }}
                    >全选</button>
                    <button
                      onClick={() => clearAllScores(site.key)}
                      style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: 'none', border: '1px solid #ccc', borderRadius: '3px' }}
                    >清除</button>
                  </div>
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                  {options.map(score => {
                    const isSelected = selected.has(score);
                    const badgeCls = getBadgeClass(score);
                    return (
                      <label
                        key={score}
                        style={{
                          display: 'flex',
                          alignItems: 'center',
                          gap: '4px',
                          padding: '3px 8px',
                          borderRadius: '4px',
                          border: isSelected ? '2px solid #3b82f6' : '1px solid #e2e8f0',
                          background: isSelected ? '#eff6ff' : '#fff',
                          cursor: 'pointer',
                          fontSize: '12px',
                        }}
                      >
                        <input
                          type="checkbox"
                          checked={isSelected}
                          onChange={() => toggleScore(site.key, score)}
                          style={{ margin: 0 }}
                        />
                        <span className={`badge ${badgeCls}`} style={{ padding: '1px 4px', fontSize: '11px' }}>
                          {score}
                        </span>
                      </label>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>
        <div className="dialog-actions" style={{ marginTop: '16px' }}>
          <button onClick={onClose}>取消</button>
          <button
            className="btn-primary"
            onClick={() => onExport(filters)}
            disabled={!hasAnyFilter}
          >
            导出到剪贴板
          </button>
        </div>
      </div>
    </div>
  );
}

// ========== Select By Result Dialog ==========
function SelectByResultDialog({ testSites, scoreOptions, proxies, testResults, onApply, onClose }) {
  const [filters, setFilters] = useState(() => {
    const init = {};
    testSites.forEach(site => { init[site.key] = new Set(); });
    return init;
  });
  const [latencyThreshold, setLatencyThreshold] = useState('');
  const [nameFilter, setNameFilter] = useState('');
  const [regionFilter, setRegionFilter] = useState('');
  const [untestedOnly, setUntestedOnly] = useState(false);

  const toggleScore = (siteKey, score) => {
    setFilters(prev => {
      const next = { ...prev };
      const set = new Set(next[siteKey] || []);
      if (set.has(score)) set.delete(score);
      else set.add(score);
      next[siteKey] = set;
      return next;
    });
  };

  const selectAllScores = (siteKey) => {
    setFilters(prev => {
      const next = { ...prev };
      next[siteKey] = new Set(scoreOptions[siteKey] || []);
      return next;
    });
  };

  const clearAllScores = (siteKey) => {
    setFilters(prev => {
      const next = { ...prev };
      next[siteKey] = new Set();
      return next;
    });
  };

  const selectFailed = (siteKey) => {
    const options = scoreOptions[siteKey] || [];
    const failScores = options.filter(s => {
      if (s === 'N/A') return true;
      const m = String(s).match(/^(\d+)\/(\d+)$/);
      if (!m) return false;
      return Number(m[1]) === 0;
    });
    setFilters(prev => {
      const next = { ...prev };
      next[siteKey] = new Set(failScores);
      return next;
    });
  };

  const selectNotPerfect = (siteKey) => {
    const options = scoreOptions[siteKey] || [];
    const notPerfect = options.filter(s => {
      if (s === 'N/A') return true;
      const m = String(s).match(/^(\d+)\/(\d+)$/);
      if (!m) return true;
      return Number(m[1]) < Number(m[2]);
    });
    setFilters(prev => {
      const next = { ...prev };
      next[siteKey] = new Set(notPerfect);
      return next;
    });
  };

  const hasScoreFilter = Object.values(filters).some(set => set.size > 0);
  const parsedLatency = parseInt(latencyThreshold, 10);
  const hasLatencyFilter = !isNaN(parsedLatency) && parsedLatency > 0;
  const hasNameFilter = nameFilter.trim().length > 0;
  const hasRegionFilter = regionFilter.trim().length > 0;
  const hasAnyFilter = hasScoreFilter || hasLatencyFilter || hasNameFilter || hasRegionFilter || untestedOnly;

  const regionOptions = useMemo(() => {
    const regions = new Set();
    (proxies || []).forEach(p => {
      const tr = (testResults || {})[p.name];
      if (tr && tr.providerArea) regions.add(tr.providerArea);
    });
    return Array.from(regions).sort();
  }, [proxies, testResults]);

  const getBadgeClass = (score) => {
    if (!score || score === 'N/A') return 'err';
    const m = String(score).match(/^(\d+)\/(\d+)$/);
    if (!m) return 'warn';
    const ok = Number(m[1]), total = Number(m[2]);
    if (total > 0 && ok === total) return 'ok';
    if (total > 0 && ok === 0) return 'err';
    return 'warn';
  };

  return (
    <div className="dialog-backdrop" onClick={onClose}>
      <div className="dialog-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '720px', maxHeight: '80vh', overflowY: 'auto' }}>
        <h4>按条件选中 / 取消选中</h4>
        <p style={{ color: '#64748b', fontSize: '13px', marginBottom: '12px' }}>
          多个条件之间是“或”关系，满足任一条件即匹配。
        </p>

        {/* Name and region filters */}
        <div style={{ display: 'flex', gap: '12px', marginBottom: '12px' }}>
          <div style={{ flex: 1, border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              <span style={{ fontWeight: 600, fontSize: '13px', whiteSpace: 'nowrap' }}>名称包含</span>
              <input type="text" value={nameFilter} onChange={(e) => setNameFilter(e.target.value)} placeholder="输入关键字" style={{ flex: 1, padding: '4px 8px', fontSize: '13px' }} />
            </div>
          </div>
          <div style={{ flex: 1, border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              <span style={{ fontWeight: 600, fontSize: '13px', whiteSpace: 'nowrap' }}>区域</span>
              <select value={regionFilter} onChange={(e) => setRegionFilter(e.target.value)} style={{ flex: 1, padding: '4px 8px', fontSize: '13px', borderRadius: '4px', border: '1px solid #cbd5e1' }}>
                <option value="">不限</option>
                {regionOptions.map(r => <option key={r} value={r}>{r}</option>)}
              </select>
            </div>
          </div>
        </div>

        {/* Untested filter */}
        <div style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px', marginBottom: '12px' }}>
          <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
            <input type="checkbox" checked={untestedOnly} onChange={(e) => setUntestedOnly(e.target.checked)} />
            <span style={{ fontWeight: 600, fontSize: '13px' }}>未测试的代理</span>
            <span style={{ fontSize: '12px', color: '#64748b' }}>(没有测试结果的代理)</span>
          </label>
        </div>

        {/* Latency threshold filter */}
        <div style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px', marginBottom: '12px' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
            <span style={{ fontWeight: 600, fontSize: '13px', whiteSpace: 'nowrap' }}>延迟筛选</span>
            <span style={{ fontSize: '12px', color: '#64748b' }}>任意站点平均延迟超过</span>
            <input
              type="number"
              value={latencyThreshold}
              onChange={(e) => setLatencyThreshold(e.target.value)}
              placeholder="例如 5000"
              style={{ width: '100px', padding: '4px 8px', fontSize: '13px' }}
              min="0"
              step="500"
            />
            <span style={{ fontSize: '12px', color: '#64748b' }}>ms</span>
          </div>
          <div style={{ display: 'flex', gap: '6px', marginTop: '6px' }}>
            {[3000, 5000, 7000, 10000].map(v => (
              <button
                key={v}
                onClick={() => setLatencyThreshold(String(v))}
                style={{
                  fontSize: '11px', padding: '2px 8px', cursor: 'pointer',
                  background: String(parsedLatency) === String(v) ? '#eff6ff' : '#f8fafc',
                  border: String(parsedLatency) === String(v) ? '2px solid #3b82f6' : '1px solid #ccc',
                  borderRadius: '3px',
                  color: String(parsedLatency) === String(v) ? '#2563eb' : '#475569',
                }}
              >{`>${v}ms`}</button>
            ))}
            <button
              onClick={() => setLatencyThreshold('')}
              style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: 'none', border: '1px solid #ccc', borderRadius: '3px' }}
            >清除</button>
          </div>
        </div>

        {/* Score filters per site */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
          {testSites.map(site => {
            const options = scoreOptions[site.key] || [];
            if (options.length === 0) return null;
            const selected = filters[site.key] || new Set();
            return (
              <div key={site.key} style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' }}>
                  <span style={{ fontWeight: 600, fontSize: '13px' }}>{site.label}</span>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <button
                      onClick={() => selectFailed(site.key)}
                      style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: '#fee2e2', border: '1px solid #fca5a5', borderRadius: '3px', color: '#dc2626' }}
                    >全失败</button>
                    <button
                      onClick={() => selectNotPerfect(site.key)}
                      style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: '#fef3c7', border: '1px solid #fcd34d', borderRadius: '3px', color: '#92400e' }}
                    >非满分</button>
                    <button
                      onClick={() => selectAllScores(site.key)}
                      style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: 'none', border: '1px solid #ccc', borderRadius: '3px' }}
                    >全选</button>
                    <button
                      onClick={() => clearAllScores(site.key)}
                      style={{ fontSize: '11px', padding: '2px 6px', cursor: 'pointer', background: 'none', border: '1px solid #ccc', borderRadius: '3px' }}
                    >清除</button>
                  </div>
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                  {options.map(score => {
                    const isSelected = selected.has(score);
                    const badgeCls = getBadgeClass(score);
                    return (
                      <label
                        key={score}
                        style={{
                          display: 'flex',
                          alignItems: 'center',
                          gap: '4px',
                          padding: '3px 8px',
                          borderRadius: '4px',
                          border: isSelected ? '2px solid #3b82f6' : '1px solid #e2e8f0',
                          background: isSelected ? '#eff6ff' : '#fff',
                          cursor: 'pointer',
                          fontSize: '12px',
                        }}
                      >
                        <input
                          type="checkbox"
                          checked={isSelected}
                          onChange={() => toggleScore(site.key, score)}
                          style={{ margin: 0 }}
                        />
                        <span className={`badge ${badgeCls}`} style={{ padding: '1px 4px', fontSize: '11px' }}>
                          {score}
                        </span>
                      </label>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>
        <div className="dialog-actions" style={{ marginTop: '16px' }}>
          <button onClick={onClose}>取消</button>
          <button
            onClick={() => onApply({ filters, latencyThreshold: parsedLatency || 0, nameFilter: nameFilter.trim(), regionFilter: regionFilter.trim(), untestedOnly, action: 'deselect' })}
            disabled={!hasAnyFilter}
            style={{ background: '#f1f5f9', color: '#475569', border: '1px solid #cbd5e1' }}
          >
            取消选中符合条件
          </button>
          <button
            className="btn-primary"
            onClick={() => onApply({ filters, latencyThreshold: parsedLatency || 0, nameFilter: nameFilter.trim(), regionFilter: regionFilter.trim(), untestedOnly, action: 'select' })}
            disabled={!hasAnyFilter}
          >
            选中符合条件
          </button>
        </div>
      </div>
    </div>
  );
}

// ========== IP Count Select Dialog ==========
function IpCountSelectDialog({ onApply, onClose }) {
  const [ipMin, setIpMin] = useState('');
  const [ipMax, setIpMax] = useState('');
  const [dupMin, setDupMin] = useState('');
  const [dupMax, setDupMax] = useState('');
  const [crossMin, setCrossMin] = useState('');
  const [crossMax, setCrossMax] = useState('');
  const [untestedOnly, setUntestedOnly] = useState(false);

  const buildFilters = () => {
    const filters = [];
    const ipMinP = parseInt(ipMin, 10), ipMaxP = parseInt(ipMax, 10);
    if (!isNaN(ipMinP) || !isNaN(ipMaxP)) filters.push({ field: 'uniqueIps', min: isNaN(ipMinP) ? null : ipMinP, max: isNaN(ipMaxP) ? null : ipMaxP });
    const dupMinP = parseInt(dupMin, 10), dupMaxP = parseInt(dupMax, 10);
    if (!isNaN(dupMinP) || !isNaN(dupMaxP)) filters.push({ field: 'dupRate', min: isNaN(dupMinP) ? null : dupMinP, max: isNaN(dupMaxP) ? null : dupMaxP });
    const crossMinP = parseInt(crossMin, 10), crossMaxP = parseInt(crossMax, 10);
    if (!isNaN(crossMinP) || !isNaN(crossMaxP)) filters.push({ field: 'crossRate', min: isNaN(crossMinP) ? null : crossMinP, max: isNaN(crossMaxP) ? null : crossMaxP });
    return filters;
  };

  const hasFilter = buildFilters().length > 0 || untestedOnly;

  const rowStyle = { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' };
  const labelStyle = { fontWeight: 600, fontSize: '13px', width: '80px', flexShrink: 0 };
  const inputStyle = { width: '80px', padding: '4px 8px', fontSize: '13px' };

  return (
    <div className="dialog-backdrop" onClick={onClose}>
      <div className="dialog-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '600px' }}>
        <h4>按IP测试结果选中 / 取消选中</h4>
        <p style={{ color: '#64748b', fontSize: '13px', marginBottom: '12px' }}>
          设置筛选范围（多个条件同时满足），不填则不限制。
        </p>

        {/* Untested filter */}
        <div style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '10px', marginBottom: '12px' }}>
          <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
            <input type="checkbox" checked={untestedOnly} onChange={(e) => setUntestedOnly(e.target.checked)} />
            <span style={{ fontWeight: 600, fontSize: '13px' }}>未测试的代理</span>
            <span style={{ fontSize: '12px', color: '#64748b' }}>(没有IP数量测试结果的代理)</span>
          </label>
        </div>

        <div style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '12px' }}>
          <div style={rowStyle}>
            <span style={labelStyle}>唯一IP数</span>
            <span style={{ fontSize: '12px', color: '#64748b' }}>≥</span>
            <input type="number" value={ipMin} onChange={e => setIpMin(e.target.value)} placeholder="不限" min="0" style={inputStyle} />
            <span style={{ fontSize: '12px', color: '#64748b' }}>且 ≤</span>
            <input type="number" value={ipMax} onChange={e => setIpMax(e.target.value)} placeholder="不限" min="0" style={inputStyle} />
          </div>
          <div style={rowStyle}>
            <span style={labelStyle}>重复率 %</span>
            <span style={{ fontSize: '12px', color: '#64748b' }}>≥</span>
            <input type="number" value={dupMin} onChange={e => setDupMin(e.target.value)} placeholder="不限" min="0" max="100" style={inputStyle} />
            <span style={{ fontSize: '12px', color: '#64748b' }}>且 ≤</span>
            <input type="number" value={dupMax} onChange={e => setDupMax(e.target.value)} placeholder="不限" min="0" max="100" style={inputStyle} />
            <span style={{ fontSize: '11px', color: '#94a3b8' }}>%</span>
          </div>
          <div style={rowStyle}>
            <span style={labelStyle}>交叉重复 %</span>
            <span style={{ fontSize: '12px', color: '#64748b' }}>≥</span>
            <input type="number" value={crossMin} onChange={e => setCrossMin(e.target.value)} placeholder="不限" min="0" max="100" style={inputStyle} />
            <span style={{ fontSize: '12px', color: '#64748b' }}>且 ≤</span>
            <input type="number" value={crossMax} onChange={e => setCrossMax(e.target.value)} placeholder="不限" min="0" max="100" style={inputStyle} />
            <span style={{ fontSize: '11px', color: '#94a3b8' }}>%</span>
          </div>
        </div>

        <div className="dialog-actions" style={{ marginTop: '16px' }}>
          <button onClick={onClose}>取消</button>
          <button
            onClick={() => onApply({ filters: buildFilters(), untestedOnly, action: 'deselect' })}
            disabled={!hasFilter}
            style={{ background: '#f1f5f9', color: '#475569', border: '1px solid #cbd5e1' }}
          >
            取消选中符合条件
          </button>
          <button
            className="btn-primary"
            onClick={() => onApply({ filters: buildFilters(), untestedOnly, action: 'select' })}
            disabled={!hasFilter}
          >
            选中符合条件
          </button>
        </div>
      </div>
    </div>
  );
}

// ========== IP Count Export Dialog ==========
function IpCountExportDialog({ onExport, onClose }) {
  const [ipOp, setIpOp] = useState('>');
  const [ipVal, setIpVal] = useState('');
  const [dupOp, setDupOp] = useState('>');
  const [dupVal, setDupVal] = useState('');
  const [crossOp, setCrossOp] = useState('>');
  const [crossVal, setCrossVal] = useState('');

  const buildFilters = () => {
    const filters = [];
    const ipParsed = parseInt(ipVal, 10);
    if (!isNaN(ipParsed) && ipParsed >= 0) filters.push({ field: 'uniqueIps', operator: ipOp, threshold: ipParsed });
    const dupParsed = parseInt(dupVal, 10);
    if (!isNaN(dupParsed) && dupParsed >= 0) filters.push({ field: 'dupRate', operator: dupOp, threshold: dupParsed });
    const crossParsed = parseInt(crossVal, 10);
    if (!isNaN(crossParsed) && crossParsed >= 0) filters.push({ field: 'crossRate', operator: crossOp, threshold: crossParsed });
    return filters;
  };

  const hasFilter = buildFilters().length > 0;

  const rowStyle = { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' };
  const labelStyle = { fontWeight: 600, fontSize: '13px', width: '80px', flexShrink: 0 };
  const selectStyle = { padding: '4px 8px', fontSize: '13px', borderRadius: '4px', border: '1px solid #cbd5e1' };
  const inputStyle = { width: '90px', padding: '4px 8px', fontSize: '13px' };

  return (
    <div className="dialog-backdrop" onClick={onClose}>
      <div className="dialog-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '420px' }}>
        <h4>导出代理 — 按IP测试结果筛选</h4>
        <p style={{ color: '#64748b', fontSize: '13px', marginBottom: '12px' }}>
          设置筛选条件（多个条件同时满足），将符合条件的代理导出到剪贴板。
        </p>

        <div style={{ border: '1px solid #e2e8f0', borderRadius: '6px', padding: '12px' }}>
          <div style={rowStyle}>
            <span style={labelStyle}>唯一IP数</span>
            <select value={ipOp} onChange={e => setIpOp(e.target.value)} style={selectStyle}>
              <option value=">">大于 (&gt;)</option>
              <option value="<">小于 (&lt;)</option>
            </select>
            <input type="number" value={ipVal} onChange={e => setIpVal(e.target.value)} placeholder="不限" min="0" style={inputStyle} />
          </div>
          <div style={rowStyle}>
            <span style={labelStyle}>重复率 %</span>
            <select value={dupOp} onChange={e => setDupOp(e.target.value)} style={selectStyle}>
              <option value=">">大于 (&gt;)</option>
              <option value="<">小于 (&lt;)</option>
            </select>
            <input type="number" value={dupVal} onChange={e => setDupVal(e.target.value)} placeholder="不限" min="0" max="100" style={inputStyle} />
            <span style={{ fontSize: '11px', color: '#94a3b8' }}>%</span>
          </div>
          <div style={rowStyle}>
            <span style={labelStyle}>交叉重复 %</span>
            <select value={crossOp} onChange={e => setCrossOp(e.target.value)} style={selectStyle}>
              <option value=">">大于 (&gt;)</option>
              <option value="<">小于 (&lt;)</option>
            </select>
            <input type="number" value={crossVal} onChange={e => setCrossVal(e.target.value)} placeholder="不限" min="0" max="100" style={inputStyle} />
            <span style={{ fontSize: '11px', color: '#94a3b8' }}>%</span>
          </div>
        </div>

        <div className="dialog-actions" style={{ marginTop: '16px' }}>
          <button onClick={onClose}>取消</button>
          <button
            className="btn-primary"
            onClick={() => onExport({ filters: buildFilters() })}
            disabled={!hasFilter}
          >
            导出到剪贴板
          </button>
        </div>
      </div>
    </div>
  );
}

function InfoTab({ token }) {
  const [infos, setInfos] = useState([]);
  const [selected, setSelected] = useState(new Set());
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState({ msg: '', isErr: false });
  const [importText, setImportText] = useState('');
  const [showImportModeDialog, setShowImportModeDialog] = useState(false);
  const [globalUpstreamRows, setGlobalUpstreamRows] = useState([{ url: '', min: '', max: '' }]);
  const [globalJump, setGlobalJump] = useState('');
  const [savedGlobalUpstreamText, setSavedGlobalUpstreamText] = useState('');
  const [savedGlobalJump, setSavedGlobalJump] = useState('');
  const [savingGlobalProxy, setSavingGlobalProxy] = useState(false);
  const [proxySettingsReady, setProxySettingsReady] = useState(false);
  const todayDateSeed = useMemo(() => getTodayDateSeed(), []);
  const [dateSeed, setDateSeed] = useState(() => getTodayDateSeed());
  const [savedDateSeed, setSavedDateSeed] = useState(() => getTodayDateSeed());
  const [savingDateSeed, setSavingDateSeed] = useState(false);
  const [showDateSeedGenerateDialog, setShowDateSeedGenerateDialog] = useState(false);
  const dateSeedGenerateResolveRef = useRef(null);

  const loadInfos = useCallback(async () => {
    try {
      const data = await apiRequest('/api/info', { headers: authHeader(token) });

      // Attempt to extract country code and raw phone number from phone logic if they aren't explicit
      // Though our api only serves idCard, fullName, phone, email, and maybe phoneCountryCode 
      setInfos(data.data || []);
      setSelected(new Set());
    } catch (e) {
      setStatus({ msg: `加载失败: ${e.message}`, isErr: true });
    }
  }, [token]);

  const loadProxySettings = useCallback(async () => {
    try {
      const data = await apiRequest('/api/info/proxy-settings', { headers: authHeader(token) });
      const settings = data.data || {};
      const nextRows = parseUpstreamRows(settings.upstreamText || '');
      const nextUpstreamText = String(serializeUpstreamRows(nextRows) || '').trim();
      const nextJump = normalizeJumpInput(settings.jump || '');
      setGlobalUpstreamRows(nextRows);
      setGlobalJump(nextJump);
      setSavedGlobalUpstreamText(nextUpstreamText);
      setSavedGlobalJump(nextJump);
    } catch (e) {
      setStatus({ msg: `加载统一代理设置失败: ${e.message}`, isErr: true });
    } finally {
      setProxySettingsReady(true);
    }
  }, [token]);

  const loadDateSeed = useCallback(async () => {
    try {
      const data = await apiRequest('/api/control/date-seed', { headers: authHeader(token) });
      const next = normalizeDateSeedInput(data.dateSeed || todayDateSeed);
      const finalSeed = isValidDateSeed(next) ? next : todayDateSeed;
      setDateSeed(finalSeed);
      setSavedDateSeed(finalSeed);
    } catch (e) {
      setStatus({ msg: `日期变量加载失败: ${e.message}`, isErr: true });
    }
  }, [token, todayDateSeed]);

  const persistDateSeed = useCallback(async (seed, { silent = false } = {}) => {
    const normalized = normalizeDateSeedInput(seed);
    if (!isValidDateSeed(normalized)) {
      throw new Error('日期变量必须以 YYMMDD 开头，可加后缀，例如 260224、260224-1、260224a。');
    }

    setSavingDateSeed(true);
    try {
      await apiRequest('/api/control/date-seed', {
        method: 'POST',
        headers: authHeader(token),
        body: { dateSeed: normalized },
      });
      setDateSeed(normalized);
      setSavedDateSeed(normalized);
      window.dispatchEvent(new CustomEvent('date-seed-updated', { detail: { dateSeed: normalized } }));
      if (!silent) {
        setStatus({ msg: `日期变量已自动保存：${normalized}`, isErr: false });
      }
      return normalized;
    } finally {
      setSavingDateSeed(false);
    }
  }, [token]);

  const askDateSeedChoiceForGenerate = useCallback(() => (
    new Promise((resolve) => {
      dateSeedGenerateResolveRef.current = resolve;
      setShowDateSeedGenerateDialog(true);
    })
  ), []);

  const handleDateSeedGenerateChoice = useCallback((choice) => {
    setShowDateSeedGenerateDialog(false);
    const resolve = dateSeedGenerateResolveRef.current;
    dateSeedGenerateResolveRef.current = null;
    if (resolve) resolve(choice);
  }, []);

  const persistGlobalProxySettings = useCallback(async ({ upstreamText, jump }, { silent = false } = {}) => {
    const normalizedUpstreamText = String(upstreamText || '').trim();
    const normalizedJump = normalizeJumpInput(jump);

    setSavingGlobalProxy(true);
    try {
      await apiRequest('/api/info/proxy-settings', {
        method: 'POST',
        headers: authHeader(token),
        body: {
          upstreamText: normalizedUpstreamText,
          jump: normalizedJump,
        }
      });
      setSavedGlobalUpstreamText(normalizedUpstreamText);
      setSavedGlobalJump(normalizedJump);
      if (!silent) {
        setStatus({ msg: '统一代理设置已自动保存。', isErr: false });
      }
      return { upstreamText: normalizedUpstreamText, jump: normalizedJump };
    } finally {
      setSavingGlobalProxy(false);
    }
  }, [token]);

  useEffect(() => {
    loadInfos();
    loadProxySettings();
    loadDateSeed();
  }, [loadInfos, loadProxySettings, loadDateSeed]);

  useEffect(() => {
    const onDateSeedUpdated = (evt) => {
      const next = normalizeDateSeedInput(evt && evt.detail && evt.detail.dateSeed);
      if (!isValidDateSeed(next)) return;
      setDateSeed(next);
      setSavedDateSeed(next);
    };
    window.addEventListener('date-seed-updated', onDateSeedUpdated);
    return () => window.removeEventListener('date-seed-updated', onDateSeedUpdated);
  }, []);

  useEffect(() => (
    () => {
      const resolve = dateSeedGenerateResolveRef.current;
      dateSeedGenerateResolveRef.current = null;
      if (resolve) resolve('cancel');
    }
  ), []);

  useEffect(() => {
    const current = normalizeDateSeedInput(dateSeed);
    if (!isValidDateSeed(current)) return;
    if (current === savedDateSeed) return;

    const timer = setTimeout(() => {
      persistDateSeed(current, { silent: true }).catch((e) => {
        setStatus({ msg: `自动保存日期变量失败: ${e.message}`, isErr: true });
      });
    }, 600);

    return () => clearTimeout(timer);
  }, [dateSeed, savedDateSeed, persistDateSeed]);

  useEffect(() => {
    if (!proxySettingsReady || savingGlobalProxy) return;

    const currentUpstreamText = String(serializeUpstreamRows(globalUpstreamRows) || '').trim();
    const currentJump = normalizeJumpInput(globalJump);
    if (currentUpstreamText === savedGlobalUpstreamText && currentJump === savedGlobalJump) return;

    const timer = setTimeout(() => {
      persistGlobalProxySettings(
        { upstreamText: currentUpstreamText, jump: currentJump },
        { silent: true }
      ).catch((e) => {
        setStatus({ msg: `自动保存统一代理设置失败: ${e.message}`, isErr: true });
      });
    }, 600);

    return () => clearTimeout(timer);
  }, [
    proxySettingsReady,
    savingGlobalProxy,
    globalUpstreamRows,
    globalJump,
    savedGlobalUpstreamText,
    savedGlobalJump,
    persistGlobalProxySettings
  ]);

  const openImportModeDialog = () => {
    if (!importText.trim() || loading) return;
    if (infos.length === 0) {
      handleImport('append');
      return;
    }
    setShowImportModeDialog(true);
  };

  const handleImport = async (mode = 'append') => {
    if (!importText.trim()) return;
    const importMode = mode === 'replace' ? 'replace' : 'append';
    const previousCount = infos.length;

    setLoading(true);
    try {
      const startIdx = importMode === 'replace'
        ? 1
        : (infos.length > 0 ? parseInt(infos[infos.length - 1].name.replace(/[^\d]/g, '') || 0) + 1 : 1);

      const res = await apiRequest('/api/info/import', {
        method: 'POST',
        headers: authHeader(token),
        body: { text: importText, startIndex: startIdx, mode: importMode }
      });
      const importedCount = Number(res && res.imported || 0);
      if (!(previousCount === 0 && importedCount === 0)) {
        setStatus({ msg: `成功${importMode === 'replace' ? '替换并导入' : '追加导入'} ${importedCount} 条信息。`, isErr: false });
      }
      setImportText('');
      setShowImportModeDialog(false);
      loadInfos();
    } catch (e) {
      setStatus({ msg: `导入失败: ${e.message}`, isErr: true });
    } finally { setLoading(false); }
  };


  const handleAction = async (actionPath, payload) => {
    setLoading(true);
    const names = Array.from(selected);
    if (!names.length) {
      setStatus({ msg: "未选择任何项目。", isErr: true });
      setLoading(false); return;
    }
    try {
      await apiRequest(actionPath, {
        method: 'POST',
        headers: authHeader(token),
        body: { names, ...payload }
      });
      setStatus({ msg: `成功对 ${names.length} 个项目执行操作。`, isErr: false });
      loadInfos();
    } catch (e) {
      setStatus({ msg: `操作失败: ${e.message}`, isErr: true });
    } finally { setLoading(false); }
  };

  const toggleCheckboxStatus = async (p, checked) => {
    try {
      await apiRequest('/api/info/toggle', {
        method: 'POST',
        headers: authHeader(token),
        body: { names: [p.name], disable: !checked }
      });
      loadInfos();
    } catch (e) {
      setStatus({ msg: `操作失败: ${e.message}`, isErr: true });
    }
  };

  const toggleSelectAll = (e) => {
    if (e.target.checked) setSelected(new Set(infos.map(p => p.name)));
    else setSelected(new Set());
  };

  const toggleSelect = (name) => {
    const next = new Set(selected);
    if (next.has(name)) next.delete(name);
    else next.add(name);
    setSelected(next);
  };

  const handleSaveField = async (name, field, val) => {
    try {
      await apiRequest('/api/info/update', {
        method: 'POST',
        headers: authHeader(token),
        body: { name, field, value: val }
      });
      loadInfos();
    } catch (e) {
      setStatus({ msg: `保存失败: ${e.message}`, isErr: true });
    }
  };

  const resolveGlobalProxySettingsForGenerate = async () => {
    if (!proxySettingsReady) return true;

    const currentUpstreamText = String(serializeUpstreamRows(globalUpstreamRows) || '').trim();
    const currentJump = normalizeJumpInput(globalJump);
    if (currentUpstreamText === savedGlobalUpstreamText && currentJump === savedGlobalJump) {
      return true;
    }

    try {
      await persistGlobalProxySettings(
        { upstreamText: currentUpstreamText, jump: currentJump },
        { silent: true }
      );
      return true;
    } catch (e) {
      setStatus({ msg: `保存统一代理设置失败: ${e.message}`, isErr: true });
      return false;
    }
  };

  const resolveDateSeedForGenerate = async () => {
    const current = normalizeDateSeedInput(dateSeed);
    if (!isValidDateSeed(current)) {
      setStatus({ msg: '日期变量必须以 YYMMDD 开头，可加后缀，例如 260224、260224-1、260224a。', isErr: true });
      return null;
    }

    let finalSeed = current;
    if (current !== todayDateSeed) {
      const choice = await askDateSeedChoiceForGenerate();
      if (choice === 'today') {
        finalSeed = todayDateSeed;
      } else if (choice !== 'input') {
        setStatus({ msg: '已取消生成，日期变量保持不变。', isErr: false });
        return null;
      }
    }

    if (finalSeed !== savedDateSeed) {
      try {
        await persistDateSeed(finalSeed, { silent: true });
      } catch (e) {
        setStatus({ msg: `保存日期变量失败: ${e.message}`, isErr: true });
        return null;
      }
    }

    return finalSeed;
  };

  const handleGenerateConfig = async () => {
    const selectedSeed = await resolveDateSeedForGenerate();
    if (!selectedSeed) return;
    const proxySettingsReadyToUse = await resolveGlobalProxySettingsForGenerate();
    if (!proxySettingsReadyToUse) return;

    setLoading(true);
    try {
      await apiRequest('/api/info/generate', {
        method: 'POST',
        headers: authHeader(token),
      });
      setStatus({
        msg: `生成成功（使用日期 ${selectedSeed}）。`,
        isErr: false,
      });
    } catch (e) {
      setStatus({ msg: `生成失败: ${e.message}`, isErr: true });
    } finally {
      setLoading(false);
    }
  };

  const addUpstreamRow = () => {
    setGlobalUpstreamRows((prev) => [...prev, { url: '', min: '', max: '' }]);
  };

  const removeUpstreamRow = (index) => {
    setGlobalUpstreamRows((prev) => {
      const next = prev.filter((_, i) => i !== index);
      return next.length ? next : [{ url: '', min: '', max: '' }];
    });
  };

  const updateUpstreamRow = (index, field, value) => {
    setGlobalUpstreamRows((prev) => prev.map((row, i) => (
      i === index ? { ...row, [field]: value } : row
    )));
  };

  return (
    <div>
      <StatusMsg {...status} />
      <div className="top-grid">
        <div className="panel-card import-card">
          <h3 className="panel-title">账号导入</h3>
          <textarea
            className="import-area"
            value={importText}
            onChange={e => setImportText(e.target.value)}
            placeholder="粘贴用户信息（证件号 姓名 手机号 邮箱 国家代码），支持制表符分隔，根据ID+名字自动去重"
          />
          <div className="import-btn-row">
            <button className="btn-primary" onClick={openImportModeDialog} disabled={loading || !importText.trim()}>批量导入</button>
          </div>
        </div>

        <div className="panel-card">
          <h3 className="panel-title">信息与统一代理设置</h3>
          <div className="toolbar-actions">
            <div className="toolbar-row" style={{ alignItems: 'center' }}>
              <span style={{ color: '#475569', fontWeight: 600 }}>日期变量 (YYMMDD+后缀)</span>
              <input
                type="text"
                value={dateSeed}
                onChange={(e) => setDateSeed(normalizeDateSeedInput(e.target.value))}
                placeholder={todayDateSeed}
                style={{ width: '160px' }}
                disabled={loading || savingDateSeed}
              />
              <span className="field-help">{savingDateSeed ? '日期变量自动保存中...' : '修改后自动保存'}</span>
            </div>
            <div className="toolbar-row">
              <button onClick={() => handleAction('/api/info/toggle', { disable: false })} disabled={loading || selected.size === 0}>批量启用</button>
              <button onClick={() => handleAction('/api/info/toggle', { disable: true })} disabled={loading || selected.size === 0}>批量禁用 (注释)</button>
              <button className="btn-danger" onClick={() => handleAction('/api/info/delete', {})} disabled={loading || selected.size === 0}>批量删除</button>
            </div>

            <div className="panel-card" style={{ marginTop: 2 }}>
              <h4 className="panel-title" style={{ marginBottom: 8 }}>统一 Upstream</h4>
              {globalUpstreamRows.map((row, idx) => (
                <div key={`up-${idx}`} className="proxy-row">
                  <input
                    type="text"
                    value={row.url}
                    onChange={(e) => updateUpstreamRow(idx, 'url', e.target.value)}
                    placeholder="http://user:pass@host:port"
                  />
                  <input
                    type="text"
                    value={row.min}
                    onChange={(e) => updateUpstreamRow(idx, 'min', e.target.value)}
                    placeholder="min"
                  />
                  <input
                    type="text"
                    value={row.max}
                    onChange={(e) => updateUpstreamRow(idx, 'max', e.target.value)}
                    placeholder="max"
                  />
                  <button onClick={() => removeUpstreamRow(idx)} disabled={loading || globalUpstreamRows.length === 1}>删除</button>
                </div>
              ))}
              <div className="toolbar-row" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
                <button onClick={addUpstreamRow} disabled={loading}>+ 添加一条</button>
                <span className="field-help">留空 min/max 不写 randRange</span>
              </div>
            </div>

            <div className="toolbar-row" style={{ alignItems: 'center' }}>
              <input
                type="text"
                value={globalJump}
                onChange={(e) => setGlobalJump(e.target.value)}
                placeholder="统一 Jump (可选)"
                style={{ width: '280px' }}
              />
              <span className="field-help">{savingGlobalProxy ? '统一代理自动保存中...' : '统一代理修改后自动保存'}</span>
              <button onClick={handleGenerateConfig} disabled={loading || savingGlobalProxy}>生成配置</button>
            </div>
          </div>
        </div>
      </div>

      <div className="table-container">
        <table>
          <thead>
            <tr>
              <th className="checkbox-cell"><input type="checkbox" checked={infos.length > 0 && selected.size === infos.length} onChange={toggleSelectAll} /></th>
              <th>名称</th>
              <th>证件号 (IDNum)</th>
              <th>姓名 (Name)</th>
              <th>国家代码</th>
              <th>手机号 (Phone)</th>
              <th>邮箱 (Email)</th>
              <th>启用</th>
            </tr>
          </thead>
          <tbody>
            {infos.map(p => (
              <tr key={p.name} className={p.isDisabled ? 'row-disabled' : ''}>
                <td className="checkbox-cell"><input type="checkbox" checked={selected.has(p.name)} onChange={() => toggleSelect(p.name)} /></td>
                <td>{p.name}</td>
                <td><EditableCell value={p.idCard} onSave={(v) => handleSaveField(p.name, 'idCard', v)} /></td>
                <td><EditableCell value={p.fullName} onSave={(v) => handleSaveField(p.name, 'fullName', v)} /></td>
                <td><EditableCell value={p.phoneCountryCode} onSave={(v) => handleSaveField(p.name, 'phoneCountryCode', v)} /></td>
                <td><EditableCell value={p.phone} onSave={(v) => handleSaveField(p.name, 'phone', v)} /></td>
                <td><EditableCell value={p.email} onSave={(v) => handleSaveField(p.name, 'email', v)} /></td>
                <td><input type="checkbox" checked={!p.isDisabled} onChange={(e) => toggleCheckboxStatus(p, e.target.checked)} /></td>
              </tr>
            ))}
            {infos.length === 0 && <tr><td colSpan="8" style={{ textAlign: 'center', padding: '32px', color: '#64748b' }}>没有找到用户信息。</td></tr>}
          </tbody>
        </table>
      </div>

      <DateSeedGenerateDialog
        open={showDateSeedGenerateDialog}
        currentSeed={normalizeDateSeedInput(dateSeed)}
        todaySeed={todayDateSeed}
        onSelect={handleDateSeedGenerateChoice}
        disabled={loading || savingDateSeed}
      />

      {showImportModeDialog && (
        <div className="dialog-backdrop" onClick={() => !loading && setShowImportModeDialog(false)}>
          <div className="dialog-card" onClick={(e) => e.stopPropagation()}>
            <h4>选择导入方式</h4>
            <p>请选择本次账号导入是追加还是替换。</p>
            <div className="dialog-actions">
              <button onClick={() => setShowImportModeDialog(false)} disabled={loading}>取消</button>
              <button onClick={() => handleImport('append')} disabled={loading}>追加导入</button>
              <button className="btn-danger" onClick={() => handleImport('replace')} disabled={loading}>替换导入</button>
            </div>
            <div className="field-help">替换导入会先清空当前所有账号数据，再导入新内容。</div>
          </div>
        </div>
      )}
    </div>
  );
}

function ControlTab({ token }) {
  const [status, setStatus] = useState('');
  const [logs, setLogs] = useState([]);
  const [logLoading, setLogLoading] = useState(false);
  const [autoRefresh, setAutoRefresh] = useState(true);
  const [systemState, setSystemState] = useState({ running: false });

  const loadLogs = useCallback(async (silent = false) => {
    if (!silent) setLogLoading(true);
    try {
      const data = await apiRequest('/api/control/logs?limit=300', { headers: authHeader(token) });
      setLogs(Array.isArray(data.lines) ? data.lines : []);
    } catch (e) {
      if (!silent) setStatus(`日志加载失败: ${e.message}`);
    } finally {
      if (!silent) setLogLoading(false);
    }
  }, [token]);

  const loadSystemState = useCallback(async () => {
    try {
      const data = await apiRequest('/api/control/state', { headers: authHeader(token) });
      setSystemState(data.state || { running: false });
    } catch (_e) {
      setSystemState({ running: false });
    }
  }, [token]);

  useEffect(() => {
    loadLogs(true);
    loadSystemState();
  }, [loadLogs, loadSystemState]);

  useEffect(() => {
    if (!autoRefresh) return;
    const timer = setInterval(() => {
      loadLogs(true);
      loadSystemState();
    }, 3000);
    return () => clearInterval(timer);
  }, [autoRefresh, loadLogs, loadSystemState]);

  const doAction = async (path, label) => {
    try {
      const data = await apiRequest(path, { method: 'POST', headers: authHeader(token) });
      const msg = String((data && data.message) || label || '操作完成');
      setStatus(`成功: ${msg}`);
      if (data && data.state) {
        setSystemState(data.state);
      } else {
        loadSystemState();
      }
      loadLogs(true);
    } catch (e) {
      setStatus(`错误: ${e.message}`);
      loadSystemState();
    }
  };

  const clearLogs = async () => {
    const ok = window.confirm('确定清空环境控制日志吗？');
    if (!ok) return;

    try {
      const data = await apiRequest('/api/control/logs/clear', {
        method: 'POST',
        headers: authHeader(token),
      });
      const msg = String((data && data.message) || '已清空日志');
      setStatus(`成功: ${msg}`);
      loadLogs(false);
    } catch (e) {
      setStatus(`错误: ${e.message}`);
    }
  };

  return (
    <div className="testing-panel">
      <h2 style={{ marginTop: 0, borderBottom: '1px solid #e2e8f0', paddingBottom: '12px', fontSize: '18px' }}>系统控制 (System Control)</h2>
      <div style={{ display: 'flex', gap: 10, marginTop: '16px' }}>
        <button
          className="btn-primary"
          onClick={() => doAction('/api/control/start-all', '已启动所有服务')}
          disabled={systemState.running}
        >
          启动系统
        </button>
        <button
          className="btn-danger"
          onClick={() => doAction('/api/control/stop-all', '已停止所有服务')}
          disabled={!systemState.running}
        >
          停止系统
        </button>
      </div>
      <p style={{ marginTop: 10, color: '#475569' }}>
        当前状态: {systemState.running ? '运行中' : '已停止'}
      </p>
      {status && <p style={{ marginTop: 15, fontWeight: '500', color: '#333', whiteSpace: 'pre-line' }}>{status}</p>}

      <div className="log-panel">
        <div className="log-toolbar">
          <strong>环境控制日志</strong>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <label style={{ cursor: 'pointer', fontSize: '12px', color: '#475569' }}>
              <input
                type="checkbox"
                checked={autoRefresh}
                onChange={(e) => setAutoRefresh(e.target.checked)}
                style={{ marginRight: 4 }}
              />
              自动刷新
            </label>
            <button onClick={() => loadLogs(false)} disabled={logLoading}>
              {logLoading ? '刷新中...' : '刷新日志'}
            </button>
            <button className="btn-danger" onClick={clearLogs} disabled={logLoading}>清空日志</button>
          </div>
        </div>
        <pre className="log-box">{logs.length ? logs.join('\n') : '暂无日志输出。'}</pre>
      </div>
    </div>
  );
}

function App() {
  const [token, setToken] = useState(localStorage.getItem(TOKEN_KEY) || '');
  const [username, setUsername] = useState('');
  const [tab, setTab] = useState('proxy');

  useEffect(() => {
    async function check() {
      if (!token) return;
      try {
        const data = await apiRequest('/api/me', { headers: authHeader(token) });
        setUsername(data.username);
      } catch (_) {
        setToken('');
        localStorage.removeItem(TOKEN_KEY);
      }
    }
    check();
  }, [token]);

  const onLogout = async () => {
    try { await apiRequest('/api/logout', { method: 'POST', headers: authHeader(token) }); } catch (e) { }
    setToken('');
    localStorage.removeItem(TOKEN_KEY);
  };

  if (!token) {
    return <LoginPage onLoggedIn={(t, u) => { setToken(t); setUsername(u); localStorage.setItem(TOKEN_KEY, t); }} />;
  }

  return (
    <>
      <div className="header">
        <h1>配置与信息管理系统</h1>
        <div style={{ display: 'flex', alignItems: 'center', gap: '16px', color: 'var(--text-muted)' }}>
          <span>用户: <strong style={{ color: 'var(--text)' }}>{username}</strong></span>
          <button onClick={onLogout} style={{ padding: '6px 12px', fontSize: '13px' }}>退出登录</button>
        </div>
      </div>
      <div className="tabs">
        <button className={`tab-btn ${tab === 'proxy' ? 'active' : ''}`} onClick={() => setTab('proxy')}>代理管理</button>
        <button className={`tab-btn ${tab === 'info' ? 'active' : ''}`} onClick={() => setTab('info')}>信息管理</button>
        <button className={`tab-btn ${tab === 'control' ? 'active' : ''}`} onClick={() => setTab('control')}>环境控制</button>
      </div>
      {tab === 'proxy' && <ProxyTab token={token} />}
      {tab === 'info' && <InfoTab token={token} />}
      {tab === 'control' && <ControlTab token={token} />}
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
