// 独立查校页 — A6
// Route: /#/gaokao/search?province=BJ&subjects=456&minScore=600&maxScore=700&minProb=50&year=2024
// Filters PB_SCHOOLS + PB_CUTOFFS client-side. No new API endpoints.

const GaokaoSearchPage = () => {
  const lang = useLang();

  // ── Parse URL params ────────────────────────────────────────────────────────
  const getParams = () => {
    const hash = window.location.hash || '';
    const qi = hash.indexOf('?');
    // QA 2026-06-10 — 无查询串时也必须走默认值：原先 `return {}` 让 minProb
    // 等全部 undefined，页面字面渲染出「最低概率 undefined%」。
    const q = new URLSearchParams(qi < 0 ? '' : hash.slice(qi + 1));
    return {
      province:  q.get('province')  || 'BJ',
      subjects:  q.get('subjects')  || '',
      minScore:  parseInt(q.get('minScore')  || '600', 10),
      maxScore:  parseInt(q.get('maxScore')  || '700', 10),
      minProb:   parseInt(q.get('minProb')   || '50',  10),
      year:      parseInt(q.get('year')      || '2024', 10),
      // 院校库 (all-schools library) search params — shareable via URL
      q:         q.get('q')         || '',
      loc:       q.get('loc')       || '',
      level:     q.get('level')     || '',
      nature:    q.get('nature')    || '',
      tag:       q.get('tag')       || '',
    };
  };

  const [params, setParams] = React.useState(getParams);

  React.useEffect(() => {
    const onHash = () => setParams(getParams());
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  // Local filter state (mirrors URL, editable before apply)
  const [province,  setProvince]  = React.useState(params.province);
  const [subjects,  setSubjects]  = React.useState(params.subjects);
  const [minScore,  setMinScore]  = React.useState(params.minScore);
  const [maxScore,  setMaxScore]  = React.useState(params.maxScore);
  const [minProb,   setMinProb]   = React.useState(params.minProb);
  const [year,      setYear]      = React.useState(params.year);

  // Sync local state when URL changes (e.g. back-button)
  React.useEffect(() => {
    setProvince(params.province);
    setSubjects(params.subjects);
    setMinScore(params.minScore);
    setMaxScore(params.maxScore);
    setMinProb(params.minProb);
    setYear(params.year);
  }, [
    params.province, params.subjects,
    params.minScore, params.maxScore, params.minProb, params.year,
  ]);

  // ── 院校库 (all-universities) search — discoverability for EVERY school ───────
  // The score-based search above only covers schools that have cutoff data (~50).
  // This searches data/cn-school-ref/school-basic-cards.json (~2945 schools) by
  // name + 所在地/层次/性质/标签, so any university is findable → its 院校详情页.
  const [allCards, setAllCards] = React.useState([]);
  // 'loading' | 'error' | 'ready' — a failed fetch used to leave allCards
  // empty forever and every search showed the misleading "未找到匹配院校".
  const [libStatus, setLibStatus] = React.useState('loading');
  const [nameQuery,    setNameQuery]    = React.useState(params.q || '');
  const [locProvince,  setLocProvince]  = React.useState(params.loc || '');
  const [levelFilter,  setLevelFilter]  = React.useState(params.level || '');
  const [natureFilter, setNatureFilter] = React.useState(params.nature || '');
  const [tagFilter,    setTagFilter]    = React.useState(params.tag || '');

  React.useEffect(() => {
    let cancelled = false;
    fetch('/data/cn-school-ref/school-basic-cards.json')
      .then(r => { if (!r.ok) throw new Error('http ' + r.status); return r.json(); })
      .then(j => {
        if (cancelled) return;
        const byName = (j && j.byName) || {};
        setAllCards(Object.keys(byName).map(k => byName[k]).filter(c => c && c.nameZh));
        setLibStatus('ready');
      })
      .catch(() => { if (!cancelled) { setAllCards([]); setLibStatus('error'); } });
    return () => { cancelled = true; };
  }, []);

  const locOptions = React.useMemo(
    () => Array.from(new Set(allCards.map(c => c.province).filter(Boolean))).sort(),
    [allCards],
  );

  const libraryActive = nameQuery.trim() !== '' || locProvince !== '' || levelFilter !== '' || natureFilter !== '' || tagFilter !== '';

  const libraryResults = React.useMemo(() => {
    if (!libraryActive) return [];
    const qq = nameQuery.trim();
    const scored = [];
    for (const c of allCards) {
      const nm = c.nameZh || '';
      if (qq && !nm.includes(qq)) continue;
      if (locProvince && c.province !== locProvince) continue;
      if (levelFilter && c.level !== levelFilter) continue;
      if (natureFilter && !String(c.nature || '').includes(natureFilter)) continue;
      if (tagFilter && !(c.tags || []).includes(tagFilter)) continue;
      // relevance: exact > prefix > contains; then 985 > 211 > 双一流 > none; shorter name first
      let s = 0;
      if (qq) { if (nm === qq) s += 100; else if (nm.startsWith(qq)) s += 50; else s += 10; }
      const tags = c.tags || [];
      if (tags.includes('985')) s += 6; else if (tags.includes('211')) s += 4; else if (tags.includes('双一流')) s += 2;
      scored.push({ c, s, len: nm.length });
    }
    scored.sort((a, b) => (b.s - a.s) || (a.len - b.len) || a.c.nameZh.localeCompare(b.c.nameZh));
    return scored.slice(0, 400).map(x => x.c);
  }, [allCards, libraryActive, nameQuery, locProvince, levelFilter, natureFilter, tagFilter]);

  const clearLibrary = () => { setNameQuery(''); setLocProvince(''); setLevelFilter(''); setNatureFilter(''); setTagFilter(''); };

  // Push filters into URL hash
  const applyFilters = () => {
    clearLibrary(); // score 筛选 switches out of 院校库 mode so score results show
    const p = new URLSearchParams();
    p.set('province', province);
    if (subjects) p.set('subjects', subjects);
    p.set('minScore', String(minScore));
    p.set('maxScore', String(maxScore));
    p.set('minProb',  String(minProb));
    p.set('year',     String(year));
    window.location.hash = '#/gaokao/search?' + p.toString();
  };

  // ── Data from browser shims ─────────────────────────────────────────────────
  const SCHOOLS  = (window.PB_SCHOOLS  || []);
  const CUTOFFS  = (window.PB_CUTOFFS  || {});
  const _pbs     = window.PBSubjects;

  // ── Subject combos for the current province/year ───────────────────────────
  const combos = React.useMemo(
    () => (_pbs ? _pbs.getCombosForProvince(params.province, params.year) : []),
    [params.province, params.year],
  );

  // ── HI 真实数据通道（QA 2026-06-10）────────────────────────────────────────
  // 海南的分数匹配不再走 mock 骨架：直接加载考试局真实投档线（本科批 +
  // 选科过滤），用同年一分一段换算位次，概率走统一冲稳保模型
  // （PBAdmissionModel.assess，多年位次史定 σ）。
  const [hiData, setHiData] = React.useState(null);       // { byYear: {y: file} }
  const [hiRankTable, setHiRankTable] = React.useState(null);
  React.useEffect(() => {
    if (params.province !== 'HI') { setHiData(null); setHiRankTable(null); return undefined; }
    let cancelled = false;
    const years = [params.year, params.year - 1, params.year - 2];
    Promise.all(years.map((y) =>
      fetch(`/data/admissions/HI/${y}-3plus3.json`).then((r) => (r.ok ? r.json() : null)).catch(() => null)
    )).then((files) => {
      if (cancelled) return;
      const byYear = {};
      years.forEach((y, i) => { if (files[i]) byYear[y] = files[i]; });
      setHiData({ byYear });
    });
    fetch(`/data/yifenyiduan/HI/${params.year}-3plus3.json`)
      .then((r) => (r.ok ? r.json() : null))
      .then((t) => { if (!cancelled) setHiRankTable(t); })
      .catch(() => { if (!cancelled) setHiRankTable(null); });
    return () => { cancelled = true; };
  }, [params.province, params.year]);

  // 一分一段 score→rank（与 page-gaokao.jsx lookupRankInTable 同实现：
  // rows 为 [score, count, cumulative] 降序，缺档线性插值）。
  const lookupRankInTable = (table, sc) => {
    if (!table || !Array.isArray(table.rows) || table.rows.length === 0) return null;
    let above = null, below = null;
    for (const row of table.rows) {
      if (row[0] === sc) return { rank: row[2], exact: true };
      if (row[0] > sc && (!above || row[0] < above[0])) above = row;
      if (row[0] < sc && (!below || row[0] > below[0])) below = row;
    }
    if (above && below) {
      const ratio = (above[0] - sc) / (above[0] - below[0]);
      return { rank: Math.round(above[2] + (below[2] - above[2]) * ratio), interpolated: true };
    }
    if (above) return { rank: above[2], exact: false };
    if (below) return { rank: below[2], exact: false };
    return null;
  };

  // ── Province list ───────────────────────────────────────────────────────────
  const PROVINCE_LIST = [
    { code: 'BJ', zh: '北京',   en: 'Beijing'    },
    { code: 'SH', zh: '上海',   en: 'Shanghai'   },
    { code: 'GD', zh: '广东',   en: 'Guangdong'  },
    { code: 'ZJ', zh: '浙江',   en: 'Zhejiang'   },
    { code: 'JS', zh: '江苏',   en: 'Jiangsu'    },
    { code: 'SD', zh: '山东',   en: 'Shandong'   },
    { code: 'HI', zh: '海南',   en: 'Hainan'     },
  ];

  // HI 有 2023–2025 真实数据；演示省份维持旧的 mock 年份。
  const YEAR_LIST = province === 'HI' ? [2025, 2024, 2023] : [2024, 2023, 2022];
  // HI 为 900 标准分制；演示省份维持 750 制输入范围。
  const scoreInputMin = province === 'HI' ? 100 : 400;
  const scoreInputMax = province === 'HI' ? 900 : 750;

  // ── Probability calculation (same formula as compare page) ─────────────────
  const getProb = (schoolId, userMinScore) => {
    // PB_CUTOFFS 按省份嵌套（{BJ: {tsinghua: {...}}}）。此前页面 0 结果的
    // 真因是 getParams 无查询串时返回 {}（minScore/minProb 全 undefined，
    // 过滤条件全军覆没），已在上方修复。
    const provCutoffs = (CUTOFFS[params.province] || {})[schoolId];
    if (!provCutoffs) return null;
    const yearData = provCutoffs[String(params.year)] || provCutoffs[String(params.year - 1)];
    if (!yearData) return null;
    const cutoffScore = yearData[0];
    const cutoffRank  = yearData[1];
    // Score-based probability
    const delta = userMinScore - cutoffScore;
    if (delta >= 20)  return { prob: Math.min(98, 85 + delta * 0.5), bucket: 'safety', cutoffScore, cutoffRank };
    if (delta >= 0)   return { prob: Math.round(70 + delta * 0.75),  bucket: 'match',  cutoffScore, cutoffRank };
    if (delta >= -20) return { prob: Math.max(20, 60 + delta * 2),   bucket: 'match',  cutoffScore, cutoffRank };
    if (delta >= -40) return { prob: Math.max(5,  30 + delta * 1.5), bucket: 'reach',  cutoffScore, cutoffRank };
    return { prob: 5, bucket: 'reach', cutoffScore, cutoffRank };
  };

  // ── Subject compatibility ───────────────────────────────────────────────────
  const selectedCombo = combos.find(c => c.id === params.subjects) || null;
  const subjectCompat = (reqStr) => {
    if (!selectedCombo) return true; // no filter
    const req = String(reqStr || '').replace(/^0+/, '').replace(/0+$/, '');
    if (!req || req === '0') return true;
    const userSet = new Set((selectedCombo.codes || '').split(''));
    for (const ch of req) {
      if (!userSet.has(ch)) return false;
    }
    return true;
  };

  // ── HI 真实结果：本科批最低分组 + 统一模型概率 ──────────────────────────────
  const hiRankInfo = React.useMemo(() => {
    if (params.province !== 'HI' || !hiRankTable || !(params.minScore > 0)) return null;
    return lookupRankInTable(hiRankTable, params.minScore);
  }, [params.province, params.minScore, hiRankTable]);

  const hiResults = React.useMemo(() => {
    if (params.province !== 'HI') return null;
    const yFile = hiData && hiData.byYear && hiData.byYear[params.year];
    if (!yFile || !Array.isArray(yFile.schools)) return [];
    const model = window.PBAdmissionModel;
    const isMain = (g) => !g.batch || g.batch === '本科批'; // 专科/提前组不混入
    const userRank = hiRankInfo ? hiRankInfo.rank : null;

    // name → lowest-cutoff compatible group rank, per history year（海南校代码跨年
    // 复用，按校名对齐历史，不能按 code）
    const bestByYear = {};
    for (const y of [params.year - 2, params.year - 1, params.year]) {
      const f = hiData.byYear[y];
      if (!f || !Array.isArray(f.schools)) continue;
      const m = new Map();
      for (const s of f.schools) {
        const gs = (s.groups || []).filter((g) => g && g.rank != null && isMain(g) && subjectCompat(g.subj));
        if (!gs.length) continue;
        m.set(s.name, gs.reduce((a, g) => (!a || g.score < a.score ? g : a), null));
      }
      bestByYear[y] = m;
    }

    const out = [];
    for (const s of yFile.schools) {
      const best = bestByYear[params.year] && bestByYear[params.year].get(s.name);
      if (!best) continue;
      // 分数窗口（与演示路径同语义：投档线落在 [minScore-30, maxScore+10]）
      if (params.minScore > 0 && (best.score > params.maxScore + 10 || best.score < params.minScore - 30)) continue;

      let probPack = null;
      if (model && userRank > 0) {
        const hist = [params.year - 2, params.year - 1, params.year].map((y) => {
          const g = bestByYear[y] && bestByYear[y].get(s.name);
          return g ? g.rank : null;
        });
        // predictedCutoffOverride 锚定到本年展示的投档位次（与卡片数字一致），
        // 历史位次只用于 σ——否则历史年份的中外合作/民族班低分组会把预测线
        // 拉偏（实测：厦大 654/#4,768 对位次 23,459 曾显示 44%）。
        const a = model.assess({ userRank, cutoffRanksByYear: hist, predictedCutoffOverride: best.rank });
        if (a) {
          probPack = {
            prob: a.probability, bucket: a.bucket,
            cutoffScore: best.score, cutoffRank: best.rank,
            lowConfidence: a.lowConfidence,
          };
        }
      }
      if (!probPack) probPack = { prob: null, bucket: 'match', cutoffScore: best.score, cutoffRank: best.rank };
      if (probPack.prob != null && probPack.prob < params.minProb) continue;

      out.push({
        id: s.code,
        detailKey: s.name, // 院校详情页按规范化校名解析
        name: { zh: s.name, en: s.name },
        location: { zh: '', en: '' },
        tier: null,
        prob: probPack,
      });
    }
    out.sort((a, b) => (b.prob.cutoffScore || 0) - (a.prob.cutoffScore || 0));
    return out;
  }, [params.province, params.year, params.subjects, params.minScore, params.maxScore, params.minProb, hiData, hiRankInfo]);

  // ── Filter SCHOOLS（演示省份 mock 骨架路径）──────────────────────────────────
  // M8 data-authenticity: non-HI provinces use a 20-school mock skeleton with
  // fabricated cutoff scores. We still use getProb() internally for the score-
  // window filter (to return plausible cards), but we STRIP the probability value
  // from the result so no fabricated percentages appear in the UI. Only 位次/投档分
  // is shown (already disclosed as demo data in the source note below). The minProb
  // slider is also hidden for non-HI since it cannot gate real probability values.
  const mockResults = React.useMemo(() => {
    return SCHOOLS.filter(school => {
      // Score range filter (still use getProb for cutoffScore only)
      const prob = getProb(school.id, params.minScore);
      if (!prob) return false; // no cutoff skeleton data for this province
      // Score-range filter: keep schools whose cutoff is roughly within [minScore-30, maxScore+10]
      if (params.minScore > 0 && (prob.cutoffScore > params.maxScore + 10 || prob.cutoffScore < params.minScore - 30)) return false;
      // For non-HI: skip minProb filter (no real probability to filter on)
      // Subject compat (school.subjectReq optional field)
      if (!subjectCompat(school.subjectReq)) return false;
      return true;
    }).map(school => {
      const probRaw = getProb(school.id, params.minScore);
      return {
        ...school,
        // M8: strip fabricated probability — keep cutoffScore/cutoffRank for display,
        // set prob to null so the prob cell renders '—' and no bucket badge appears.
        prob: probRaw ? { cutoffScore: probRaw.cutoffScore, cutoffRank: probRaw.cutoffRank, prob: null, bucket: 'match' } : null,
      };
    }).sort((a, b) => {
      // Sort by cutoff score desc
      const sa = a.prob ? a.prob.cutoffScore : 0;
      const sb = b.prob ? b.prob.cutoffScore : 0;
      return sb - sa;
    });
  }, [
    params.province, params.subjects, params.minScore,
    params.maxScore, params.year,
  ]);

  // HI 走真实数据；演示省份走 mock 骨架（结果区有显著"演示数据"披露）。
  const results = hiResults != null ? hiResults : mockResults;

  // ── Helpers ────────────────────────────────────────────────────────────────
  const bucketColor = { safety: 'var(--pb-success)', match: 'var(--pb-navy-700)', reach: 'var(--pb-danger)' };
  const bucketLabel = {
    safety: lang === 'zh' ? '保底' : 'Safety',
    match:  lang === 'zh' ? '匹配' : 'Match',
    reach:  lang === 'zh' ? '冲刺' : 'Reach',
  };

  const tierColors = {
    '985-A': { bg: '#fef3c7', color: '#92400e' },
    '985':   { bg: '#dbeafe', color: '#1e40af' },
    '211':   { bg: '#d1fae5', color: '#065f46' },
  };

  const TierBadge = ({ tier }) => {
    const s = tierColors[tier] || { bg: 'var(--pb-surface-alt)', color: 'var(--pb-fg-muted)' };
    return (
      <span style={{
        padding: '2px 7px', fontSize: 10, fontWeight: 600,
        borderRadius: 4, background: s.bg, color: s.color, letterSpacing: '0.03em',
      }}>
        {tier}
      </span>
    );
  };

  // ── Navigate to compare page with this school ──────────────────────────────
  const goToCompare = (schoolId) => {
    const p = new URLSearchParams();
    p.set('ids', schoolId);
    p.set('province', params.province);
    p.set('year', String(params.year));
    p.set('score', String(params.minScore));
    window.location.hash = '#/gaokao/compare?' + p.toString();
  };

  // ── Navigate to the school detail page (院校详情页) ─────────────────────────
  const goToDetail = (schoolId) => {
    const p = new URLSearchParams();
    p.set('province', params.province);
    p.set('year', String(params.year));
    if (params.minScore) p.set('score', String(params.minScore));
    window.location.hash = `#/gaokao/school/${schoolId}?${p.toString()}`;
  };

  // ── Filter bar style helpers ───────────────────────────────────────────────
  const inputStyle = {
    border: '1px solid var(--pb-border)',
    borderRadius: 6,
    padding: '6px 10px',
    fontSize: 13,
    background: 'var(--pb-surface)',
    color: 'var(--pb-fg)',
    height: 34,
    outline: 'none',
  };

  const labelStyle = {
    fontSize: 12,
    color: 'var(--pb-fg-muted)',
    marginBottom: 4,
    display: 'block',
    fontWeight: 500,
  };

  // ── Render ────────────────────────────────────────────────────────────────
  return (
    <div className="pb-scroll" data-testid="search-page">
      <PBNav active="gaokao" />

      {/* Page header */}
      <section style={{
        padding: '48px 48px 24px',
        background: 'var(--pb-navy-900)',
        color: '#fff',
      }}>
        <div style={{ maxWidth: 1100, margin: '0 auto' }}>
          <div className="pb-eyebrow" style={{ color: 'var(--pb-gold-500)', marginBottom: 10 }}>
            {lang === 'zh' ? '院校检索 · School Finder' : 'School Finder'}
          </div>
          <div style={{ fontFamily: 'var(--pb-serif)', fontSize: 30, fontWeight: 600, marginBottom: 8 }}>
            {lang === 'zh' ? '查校' : 'Search Schools'}
          </div>
          <p style={{ fontSize: 15, color: 'rgba(255,255,255,0.65)', lineHeight: 1.6 }}>
            {lang === 'zh'
              ? '按分数范围匹配院校，或在下方「院校库」按校名查找全国院校。'
              : 'Match schools by score range, or search all universities by name in the library below.'}
          </p>
        </div>
      </section>

      {/* Sticky filter bar — frosted glass (matches pb-nav) via shared class */}
      <div
        data-testid="search-filter-bar"
        className="pb-gk-sticky-filter"
      >
        <div style={{
          maxWidth: 1100, margin: '0 auto',
          padding: '12px 48px',
          display: 'flex', flexWrap: 'wrap', gap: 16, alignItems: 'flex-end',
        }}>
          {/* Province */}
          <div>
            <label style={labelStyle}>{lang === 'zh' ? '省份' : 'Province'}</label>
            <select
              value={province}
              onChange={e => setProvince(e.target.value)}
              style={{ ...inputStyle, paddingRight: 28 }}
            >
              {PROVINCE_LIST.map(p => (
                <option key={p.code} value={p.code}>
                  {lang === 'zh' ? p.zh : p.en}
                </option>
              ))}
            </select>
          </div>

          {/* Subject combo */}
          <div>
            <label style={labelStyle}>{lang === 'zh' ? '选科组合' : 'Subject Combo'}</label>
            <select
              value={subjects}
              onChange={e => setSubjects(e.target.value)}
              style={{ ...inputStyle, paddingRight: 28, minWidth: 120 }}
            >
              <option value="">{lang === 'zh' ? '不限选科' : 'Any subjects'}</option>
              {combos.map(c => (
                <option key={c.id} value={c.id}>
                  {lang === 'zh' ? c.labelZh : c.labelEn}
                </option>
              ))}
            </select>
          </div>

          {/* Score range */}
          <div>
            <label style={labelStyle}>{lang === 'zh' ? '最低分' : 'Min Score'}</label>
            <input
              type="number" min={scoreInputMin} max={scoreInputMax} step={1}
              value={minScore}
              onChange={e => setMinScore(parseInt(e.target.value, 10) || 0)}
              style={{ ...inputStyle, width: 80 }}
            />
          </div>
          <div>
            <label style={labelStyle}>{lang === 'zh' ? '最高分' : 'Max Score'}</label>
            <input
              type="number" min={scoreInputMin} max={scoreInputMax} step={1}
              value={maxScore}
              onChange={e => setMaxScore(parseInt(e.target.value, 10) || 0)}
              style={{ ...inputStyle, width: 80 }}
            />
          </div>

          {/* Min probability — only shown for HI (real data); non-HI has no verified prob */}
          {province === 'HI' && (
          <div style={{ minWidth: 160 }}>
            <label style={labelStyle}>
              {lang === 'zh' ? '最低概率 ' : 'Min Probability '}
              <span style={{
                display: 'inline-block', minWidth: 34, textAlign: 'center',
                fontFamily: 'var(--pb-mono)', fontWeight: 700, fontSize: 11,
                color: 'var(--pb-gold-700)', background: 'var(--pb-gold-100)',
                borderRadius: 999, padding: '1px 6px', marginLeft: 2,
              }}>{minProb}%</span>
            </label>
            <input
              type="range" min={0} max={90} step={5}
              value={minProb}
              onChange={e => setMinProb(parseInt(e.target.value, 10))}
              style={{ width: '100%', accentColor: 'var(--pb-gold-500)' }}
            />
          </div>
          )}

          {/* Year */}
          <div>
            <label style={labelStyle}>{lang === 'zh' ? '数据年份' : 'Data Year'}</label>
            <select
              value={year}
              onChange={e => setYear(parseInt(e.target.value, 10))}
              style={{ ...inputStyle, paddingRight: 28 }}
            >
              {YEAR_LIST.map(y => (
                <option key={y} value={y}>{y}</option>
              ))}
            </select>
          </div>

          {/* Apply */}
          <div>
            <button
              className="pb-btn pb-btn-primary"
              onClick={applyFilters}
              style={{ height: 34, padding: '0 20px', fontSize: 13 }}
            >
              {lang === 'zh' ? '筛选' : 'Search'}
            </button>
          </div>
        </div>
      </div>

      {/* Results */}
      <section style={{ maxWidth: 1100, margin: '0 auto', padding: '32px 48px 80px' }}>
        {/* ── 院校库 — find ANY of the ~2900 national universities by name/filters ── */}
        <div data-testid="library-search-panel" style={{
          border: '1px solid var(--pb-border)', borderRadius: 12,
          background: 'var(--pb-surface)', padding: '16px 20px', marginBottom: 24,
        }}>
          <div style={{ fontFamily: 'var(--pb-serif)', fontSize: 16, fontWeight: 600, marginBottom: 4 }}>
            {lang === 'zh' ? '院校库 · 搜索全国院校' : 'School Library · Search All Universities'}
          </div>
          <div style={{ fontSize: 12, color: 'var(--pb-fg-muted)', marginBottom: 12 }}>
            {lang === 'zh'
              ? `按校名或地区 / 层次 / 性质 / 标签筛选，共收录 ${allCards.length} 所院校。`
              : `Search by name or filter — ${allCards.length} universities indexed.`}
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'flex-end' }}>
            <div style={{ flex: '1 1 240px' }}>
              <label style={labelStyle}>{lang === 'zh' ? '校名关键词' : 'School name'}</label>
              <input
                data-testid="library-search-input"
                type="text"
                value={nameQuery}
                onChange={e => setNameQuery(e.target.value)}
                placeholder={lang === 'zh' ? '如：北京大学 / 师范 / 海南' : 'e.g. Peking University'}
                style={{ ...inputStyle, width: '100%' }}
              />
            </div>
            <div>
              <label style={labelStyle}>{lang === 'zh' ? '所在地' : 'Location'}</label>
              <select value={locProvince} onChange={e => setLocProvince(e.target.value)} style={{ ...inputStyle, paddingRight: 28 }}>
                <option value="">{lang === 'zh' ? '全部' : 'All'}</option>
                {locOptions.map(p => (<option key={p} value={p}>{p}</option>))}
              </select>
            </div>
            <div>
              <label style={labelStyle}>{lang === 'zh' ? '层次' : 'Level'}</label>
              <select value={levelFilter} onChange={e => setLevelFilter(e.target.value)} style={{ ...inputStyle, paddingRight: 28 }}>
                <option value="">{lang === 'zh' ? '全部' : 'All'}</option>
                <option value="本科">{lang === 'zh' ? '本科' : 'Undergraduate'}</option>
                <option value="专科">{lang === 'zh' ? '专科' : 'Vocational'}</option>
              </select>
            </div>
            <div>
              <label style={labelStyle}>{lang === 'zh' ? '性质' : 'Type'}</label>
              <select value={natureFilter} onChange={e => setNatureFilter(e.target.value)} style={{ ...inputStyle, paddingRight: 28 }}>
                <option value="">{lang === 'zh' ? '全部' : 'All'}</option>
                <option value="公办">{lang === 'zh' ? '公办' : 'Public'}</option>
                <option value="民办">{lang === 'zh' ? '民办' : 'Private'}</option>
                <option value="中外合作">{lang === 'zh' ? '中外合作' : 'Joint'}</option>
              </select>
            </div>
            <div>
              <label style={labelStyle}>{lang === 'zh' ? '标签' : 'Tag'}</label>
              <select value={tagFilter} onChange={e => setTagFilter(e.target.value)} style={{ ...inputStyle, paddingRight: 28 }}>
                <option value="">{lang === 'zh' ? '全部' : 'All'}</option>
                <option value="985">985</option>
                <option value="211">211</option>
                <option value="双一流">{lang === 'zh' ? '双一流' : 'Double First-Class'}</option>
              </select>
            </div>
            {libraryActive && (
              <button
                data-testid="library-clear"
                onClick={clearLibrary}
                style={{ ...inputStyle, cursor: 'pointer', color: 'var(--pb-fg-muted)' }}
              >
                {lang === 'zh' ? '清除' : 'Clear'}
              </button>
            )}
          </div>
        </div>

        {libraryActive && (
          <div data-testid="library-results">
            <div style={{ marginBottom: 16, fontSize: 14, color: 'var(--pb-fg-muted)' }}>
              {libStatus === 'ready'
                ? <>
                    {lang === 'zh' ? `院校库找到 ${libraryResults.length} 所院校` : `${libraryResults.length} schools found`}
                    {libraryResults.length >= 400 && (lang === 'zh' ? '（仅显示前 400，请细化筛选）' : ' (showing first 400 — refine to narrow)')}
                  </>
                : (libStatus === 'loading'
                    ? (lang === 'zh' ? '院校库加载中…' : 'Loading school library…')
                    : '')}
            </div>
            {libraryResults.length === 0 ? (
              <div data-testid="library-empty" style={{
                textAlign: 'center', padding: '60px 24px', background: 'var(--pb-surface)',
                border: '1px solid var(--pb-border)', borderRadius: 12, color: 'var(--pb-fg-muted)',
              }}>
                {libStatus === 'error'
                  ? (lang === 'zh' ? '院校库加载失败，请刷新页面重试。' : 'Failed to load the school library — please refresh.')
                  : libStatus === 'loading'
                    ? (lang === 'zh' ? '院校库加载中…' : 'Loading school library…')
                    : (lang === 'zh' ? '未找到匹配院校，换个关键词或放宽筛选。' : 'No matching schools — try another keyword.')}
              </div>
            ) : (
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 14 }}>
                {libraryResults.map((c, i) => (
                  <div
                    key={(c.profileCode || c.metaCode || c.nameZh) + '-' + i}
                    data-testid={`library-result-${c.profileCode || c.metaCode || i}`}
                    onClick={() => goToDetail(c.metaCode || c.profileCode || c.nameZh)}
                    style={{
                      background: 'var(--pb-surface)', border: '1px solid var(--pb-border)',
                      borderRadius: 10, padding: '14px 16px', cursor: 'pointer',
                      display: 'flex', flexDirection: 'column', gap: 8,
                      transition: 'box-shadow 0.15s, transform 0.15s',
                    }}
                    onMouseEnter={e => { e.currentTarget.style.boxShadow = '0 6px 20px -6px rgba(11,31,58,0.18)'; e.currentTarget.style.transform = 'translateY(-2px)'; }}
                    onMouseLeave={e => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'none'; }}
                  >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
                      <div style={{ fontFamily: 'var(--pb-serif)', fontSize: 15, fontWeight: 600, lineHeight: 1.3 }}>{c.nameZh}</div>
                      <div style={{ display: 'flex', gap: 4, flexShrink: 0, flexWrap: 'wrap' }}>
                        {(c.tags || []).map(t => <TierBadge key={t} tier={t} />)}
                      </div>
                    </div>
                    <div style={{ fontSize: 12, color: 'var(--pb-fg-muted)', display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                      <span>📍 {[c.province, c.city].filter(Boolean).join(' ') || '—'}</span>
                      {c.level && <span>{c.level}</span>}
                      {c.nature && <span>{c.nature}</span>}
                    </div>
                    <div style={{ fontSize: 12, color: 'var(--pb-gold-600)', marginTop: 'auto' }}>
                      {lang === 'zh' ? '查看详情 →' : 'View details →'}
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        )}

        {!libraryActive && (<>
        {/* 数据来源披露 — HI 真实 / 其余省份演示骨架 */}
        <div data-testid="search-data-source-note" style={{
          marginBottom: 16, padding: '10px 14px', borderRadius: 10, fontSize: 12, lineHeight: 1.6,
          border: '1px solid var(--pb-border)',
          background: params.province === 'HI' ? 'rgba(4,120,87,0.06)' : 'rgba(234,179,8,0.10)',
          color: 'var(--pb-fg-muted)',
        }}>
          {params.province === 'HI'
            ? (lang === 'zh'
                ? `数据来源：海南省考试局 ${params.year} 年本科批真实投档线（按选科过滤，取各校最低分组）；位次由同年一分一段表换算${hiRankInfo ? `，你的 ${params.minScore} 分 ≈ 位次 #${Number(hiRankInfo.rank).toLocaleString()}${hiRankInfo.interpolated ? '（插值）' : ''}` : ''}；概率为统一冲稳保模型点估计，仅供参考。`
                : `Source: real ${params.year} HI admission cutoffs (lowest compatible group per school); rank converted via the official score-rank table; probabilities from the unified admission model.`)
            : (lang === 'zh'
                ? '演示数据：本省分数匹配目前基于 20 所代表性院校骨架（非真实投档线），仅供功能演示；正式数据接入前请以测算页与官方公布为准。'
                : 'Demo data: score matching for this province uses a 20-school skeleton (not real cutoffs) until real data is wired up.')}
        </div>
        {/* HIPAGE-4: HI uses the 900-档标准分; annotate the score scale so 投档分/最低分
            are not mistaken for a 750-scale 原始分. HI-only (gated on province==='HI'). */}
        {params.province === 'HI' && (
          <div data-testid="search-hi-std-scale-note" style={{
            marginBottom: 16, fontSize: 11, color: 'var(--pb-fg-muted)',
          }}>
            {lang === 'zh' ? '分数为标准分（满分900）' : 'Scores are standard scores (max 900)'}
          </div>
        )}
        {/* Result count */}
        <div style={{ marginBottom: 20, fontSize: 14, color: 'var(--pb-fg-muted)' }}>
          {results.length > 0
            ? (lang === 'zh'
                ? `找到 ${results.length} 所符合条件的院校`
                : `Found ${results.length} school${results.length > 1 ? 's' : ''} matching your criteria`)
            : null}
        </div>

        {results.length === 0 ? (
          <div
            data-testid="search-empty-state"
            style={{
              textAlign: 'center', padding: '80px 24px',
              background: 'var(--pb-surface)',
              border: '1px solid var(--pb-border)',
              borderRadius: 12,
            }}
          >
            <div style={{ fontSize: 40, marginBottom: 16 }}>🔍</div>
            <div style={{ fontFamily: 'var(--pb-serif)', fontSize: 22, fontWeight: 600, marginBottom: 12 }}>
              {lang === 'zh' ? '无符合院校，放宽筛选条件试试' : 'No schools found — try relaxing your filters'}
            </div>
            <p style={{ fontSize: 14, color: 'var(--pb-fg-muted)', lineHeight: 1.65, maxWidth: 440, margin: '0 auto 24px' }}>
              {lang === 'zh'
                ? '调低最低概率、扩大分数区间，或切换省份查看更多结果。'
                : 'Lower the minimum probability, widen the score range, or switch province to see more results.'}
            </p>
            <button
              className="pb-btn pb-btn-secondary"
              onClick={() => { setMinProb(20); setMinScore(Math.max(400, minScore - 30)); applyFilters(); }}
            >
              {lang === 'zh' ? '放宽条件' : 'Relax filters'}
            </button>
          </div>
        ) : (
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
            gap: 16,
          }}>
            {results.map(school => {
              const name    = lang === 'zh' ? school.name.zh : school.name.en;
              const city    = (school.location && (lang === 'zh' ? school.location.zh : school.location.en)) || '';
              const prob    = school.prob;
              const bucket  = prob ? prob.bucket : 'match';
              const probPct = (prob && prob.prob != null && Number.isFinite(Number(prob.prob))) ? Math.round(prob.prob) : null;

              return (
                <div
                  key={school.id}
                  data-testid={`search-result-${school.id}`}
                  onClick={() => goToDetail(school.detailKey || school.id)}
                  style={{
                    background: 'var(--pb-surface)',
                    border: '1px solid var(--pb-border)',
                    borderRadius: 10,
                    padding: '20px 20px 16px',
                    cursor: 'pointer',
                    transition: 'box-shadow 0.15s, transform 0.15s',
                    display: 'flex', flexDirection: 'column', gap: 10,
                  }}
                  onMouseEnter={e => {
                    e.currentTarget.style.boxShadow = '0 6px 20px -6px rgba(11,31,58,0.18)';
                    e.currentTarget.style.transform = 'translateY(-2px)';
                  }}
                  onMouseLeave={e => {
                    e.currentTarget.style.boxShadow = 'none';
                    e.currentTarget.style.transform = 'none';
                  }}
                >
                  {/* Header row */}
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
                    <div style={{ fontFamily: 'var(--pb-serif)', fontSize: 17, fontWeight: 600, lineHeight: 1.35 }}>
                      {name}
                    </div>
                    {school.tier ? <TierBadge tier={school.tier} /> : null}
                  </div>

                  {/* City（HI 真实行暂无城市维度 — 留空不渲染占位符） */}
                  {city ? (
                    <div style={{ fontSize: 12, color: 'var(--pb-fg-muted)' }}>
                      📍 {city}
                    </div>
                  ) : null}

                  {/* Cutoff stats */}
                  {prob && (
                    <div style={{
                      display: 'grid', gridTemplateColumns: '1fr 1fr 1fr',
                      gap: 8, background: 'var(--pb-surface-alt)',
                      borderRadius: 6, padding: '10px 12px',
                    }}>
                      <div style={{ textAlign: 'center' }}>
                        <div style={{ fontSize: 11, color: 'var(--pb-fg-muted)', marginBottom: 2 }}>
                          {/* HIPAGE-4: HI 投档分 is the 900-档标准分, not a 750 原始分. */}
                          {params.province === 'HI'
                            ? (lang === 'zh' ? '投档分(标准分)' : 'Cut Score (std)')
                            : (lang === 'zh' ? '投档分' : 'Cut Score')}
                        </div>
                        <div style={{ fontFamily: 'var(--pb-mono)', fontSize: 16, fontWeight: 700 }}>
                          {prob.cutoffScore}
                        </div>
                      </div>
                      <div style={{ textAlign: 'center' }}>
                        <div style={{ fontSize: 11, color: 'var(--pb-fg-muted)', marginBottom: 2 }}>
                          {lang === 'zh' ? '投档位次' : 'Cut Rank'}
                        </div>
                        <div style={{ fontFamily: 'var(--pb-mono)', fontSize: 16, fontWeight: 700 }}>
                          {prob.cutoffRank != null ? prob.cutoffRank.toLocaleString() : '—'}
                        </div>
                      </div>
                      <div style={{ textAlign: 'center' }}>
                        <div style={{ fontSize: 11, color: 'var(--pb-fg-muted)', marginBottom: 2 }}>
                          {lang === 'zh' ? '估算概率' : 'Est. Prob'}
                        </div>
                        <div style={{
                          fontFamily: 'var(--pb-mono)', fontSize: 16, fontWeight: 700,
                          color: bucketColor[bucket],
                        }}>
                          {probPct != null ? `${probPct}%` : '—'}
                        </div>
                      </div>
                    </div>
                  )}

                  {/* Bucket badge + CTA */}
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                    {prob && probPct != null && (
                      <span style={{
                        fontSize: 11, fontWeight: 600, padding: '2px 8px',
                        borderRadius: 4, border: `1px solid ${bucketColor[bucket]}`,
                        color: bucketColor[bucket],
                      }}>
                        {bucketLabel[bucket]}
                      </span>
                    )}
                    <button
                      data-testid={`search-compare-${school.id}`}
                      onClick={(e) => { e.stopPropagation(); goToCompare(school.id); }}
                      style={{
                        marginLeft: 'auto', fontSize: 12, color: 'var(--pb-fg-muted)',
                        background: 'none', border: '1px solid var(--pb-border)',
                        borderRadius: 5, padding: '3px 10px', cursor: 'pointer',
                      }}
                    >
                      {lang === 'zh' ? '加入对比' : 'Compare'}
                    </button>
                  </div>
                </div>
              );
            })}
          </div>
        )}
        </>)}
      </section>

      <PBFooter />
    </div>
  );
};
