
// ─── H1.3 — ScoreRankEquivBlock ──────────────────────────────────────────────
// Renders a card beneath the gaokao result showing (1) the user's province rank
// for their score this year (一分一段 位次), and (2) equivalent scores at the same
// percentile rank in adjacent years (cross-year 同位分) where ≥2 years exist.
// Province-agnostic: works for every province/track in data/yifenyiduan/ (30+ as
// of 2025). Falls back gracefully when only a single year is available.
//
// Props:
//   province : string   — 'HI', 'GD', 'HE', …
//   year     : number   — the year the user selected
//   score    : number   — the user's estimated score
//   track    : string   — 'physics' | 'history' (hint from the user's 首选科目;
//                          ignored for 3+3 comprehensive provinces). Optional.
//   lang     : string   — 'zh' | 'en'

// ─── Algorithm (client-side mirror of lib/gaokao-rank-equiv.js) ──────────────
// The lib version uses require() (Node.js). Here we fetch() the JSON on demand
// and run the same rank↔score math in the browser.

function _rankFromScoreRows(rows, score) {
  if (!rows || rows.length === 0) return null;
  let above = null, below = null;
  for (const row of rows) {
    const s = row[0];
    if (s === score) return { rank: row[2], method: 'exact' };
    if (s > score) { if (!above || s < above[0]) above = row; }
    else           { if (!below || s > below[0]) below = row; }
  }
  if (above && below) {
    const ratio = (above[0] - score) / (above[0] - below[0]);
    return { rank: Math.round(above[2] + (below[2] - above[2]) * ratio), method: 'interpolated' };
  }
  return null; // out of range (above max or below min)
}

function _scoreFromRankRows(rows, rank) {
  if (!rows || rows.length === 0) return null;
  let above = null, below = null;
  for (const row of rows) {
    const r = row[2];
    if (r === rank) return { score: row[0], method: 'exact' };
    if (r < rank)  { if (!above || r > above[2]) above = row; }
    else           { if (!below || r < below[2]) below = row; }
  }
  if (above && below) {
    const rA = above[2]; const sA = above[0];
    const rB = below[2]; const sB = below[0];
    const ratio = (rank - rA) / (rB - rA);
    return { score: Math.round(sA + (sB - sA) * ratio), method: 'interpolated' };
  }
  if (above && !below) return { score: rows[rows.length - 1][0], method: 'clamped' };
  return null;
}

// Fetch a yfyd table; cache in module-level map to avoid refetching
const _yfydCache = {};
function _fetchYfyd(province, year, track) {
  const key = `${province}/${year}/${track}`;
  if (_yfydCache[key] !== undefined) return _yfydCache[key];
  _yfydCache[key] = fetch(`/data/yifenyiduan/${province}/${year}-${track}.json`)
    .then((r) => (r.ok ? r.json() : null))
    .catch(() => null);
  return _yfydCache[key];
}

// ─── Component ───────────────────────────────────────────────────────────────

const ScoreRankEquivBlock = ({ province, year, score, lang, track }) => {
  const [data, setData] = React.useState(null);
  const sc = Number(score);
  const yr = Number(year);
  const valid = !!province && !isNaN(sc) && sc > 0 && !isNaN(yr);

  React.useEffect(() => {
    if (!valid) { setData(null); return undefined; }
    let cancelled = false;
    setData({ loading: true });

    // Resolve the actual track for this province: 3+3 provinces only have
    // '3plus3'; 3+1+2 provinces have physics/history (hint from `track`).
    const tryTracks = ['3plus3', track === 'history' ? 'history' : 'physics', 'physics', 'history']
      .filter((t, i, a) => a.indexOf(t) === i);

    (async () => {
      let curTable = null, curTrack = null;
      for (const t of tryTracks) {
        const tbl = await _fetchYfyd(province, yr, t);
        if (tbl && Array.isArray(tbl.rows) && tbl.rows.length) { curTable = tbl; curTrack = t; break; }
      }
      if (!curTable) { if (!cancelled) setData({ none: true }); return; }

      const rr = _rankFromScoreRows(curTable.rows, sc);
      if (!rr) { if (!cancelled) setData({ none: true }); return; }

      // Cross-year equivalents — up to 2 nearest other years (prefer prior years).
      const candidateYears = [yr - 1, yr - 2, yr - 3, yr + 1].filter((y) => y !== yr);
      const results = [];
      for (const ty of candidateYears) {
        if (results.length >= 2) break;
        const tbl = await _fetchYfyd(province, ty, curTrack);
        if (!tbl || !Array.isArray(tbl.rows) || !tbl.rows.length) continue;
        const srr = _scoreFromRankRows(tbl.rows, rr.rank);
        if (srr) results.push({ toYear: ty, toScore: srr.score, method: srr.method });
      }
      if (cancelled) return;
      setData({
        curRank: rr.rank, curMethod: rr.method, results,
        track: curTrack, granularity: curTable.granularity || null,
      });
    })().catch(() => { if (!cancelled) setData({ none: true }); });

    return () => { cancelled = true; };
  }, [province, yr, sc, track, lang]);

  if (!valid || !data || data.loading || data.none) return null;

  const zh = lang === 'zh';
  const trackLabel = data.track === 'physics' ? (zh ? '物理' : 'Physics')
    : data.track === 'history' ? (zh ? '历史' : 'History') : '';
  const approx = data.curMethod !== 'exact';

  return (
    <div
      className="pb-card"
      style={{ padding: '20px 24px' }}
      data-testid="gaokao-rank-equiv-section"
    >
      <div className="pb-eyebrow" style={{ marginBottom: 8, color: 'var(--pb-navy-700)' }}>
        {zh ? 'H1.3 · 一分一段位次 & 同位分' : 'H1.3 · Province Rank & Cross-Year Equivalent'}
      </div>

      {/* Same-year province rank — works for every covered province */}
      <h4
        data-testid="gaokao-rank-equiv-current"
        style={{ fontFamily: 'var(--pb-serif)', fontSize: 18, fontWeight: 600, margin: '0 0 6px' }}
      >
        {zh
          ? `${yr} 年 ${sc} 分 ≈ 全省位次 #${data.curRank.toLocaleString()}${trackLabel ? `（${trackLabel}类）` : ''}`
          : `${yr} score ${sc} ≈ provincial rank #${data.curRank.toLocaleString()}${trackLabel ? ` (${trackLabel})` : ''}`}
      </h4>

      {data.results.length > 0 ? (
        <>
          <p style={{ fontSize: 12, color: 'var(--pb-fg-muted)', margin: '0 0 16px', lineHeight: 1.55 }}>
            {zh
              ? `同位次（#${data.curRank.toLocaleString()}）在其他年份对应的分数，用于校准历年投档线。`
              : `Score at the same rank (#${data.curRank.toLocaleString()}) in other years, to calibrate against historical cutoffs.`}
          </p>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <div style={{ padding: '12px 18px', borderRadius: 8, background: 'var(--pb-navy-900)', color: '#fff', minWidth: 100, textAlign: 'center' }}>
              <div style={{ fontSize: 11, fontWeight: 600, marginBottom: 4, opacity: 0.7, textTransform: 'uppercase', letterSpacing: '0.06em' }}>
                {yr} {zh ? '（估分年）' : '(this year)'}
              </div>
              <div style={{ fontFamily: 'var(--pb-mono)', fontSize: 24, fontWeight: 700 }}>{sc}</div>
              <div style={{ fontSize: 10, opacity: 0.6, marginTop: 2 }}>#{data.curRank.toLocaleString()}</div>
            </div>
            {data.results.map(({ toYear, toScore, method }) => (
              <div
                key={toYear}
                style={{ padding: '12px 18px', borderRadius: 8, background: 'var(--pb-surface-alt)', border: '1px solid var(--pb-border)', minWidth: 100, textAlign: 'center' }}
                data-testid={`gaokao-rank-equiv-year-${toYear}`}
              >
                <div style={{ fontSize: 11, fontWeight: 600, marginBottom: 4, color: 'var(--pb-fg-muted)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
                  {toYear} {zh ? '等位分' : 'equiv'}
                </div>
                <div style={{ fontFamily: 'var(--pb-mono)', fontSize: 24, fontWeight: 700, color: 'var(--pb-navy-900)' }}>{toScore}</div>
                {method === 'interpolated' && (
                  <div style={{ fontSize: 10, color: 'var(--pb-fg-muted)', marginTop: 2 }}>{zh ? '插值估算' : '≈ interpolated'}</div>
                )}
              </div>
            ))}
          </div>
        </>
      ) : (
        <p style={{ fontSize: 12, color: 'var(--pb-fg-muted)', margin: '0 0 0', lineHeight: 1.55 }}>
          {zh
            ? '该省份仅收录 2025 年一分一段数据，暂无跨年等位分换算。'
            : 'Only 2025 一分一段 data is available for this province; cross-year equivalence not yet possible.'}
        </p>
      )}

      <p
        style={{ fontSize: 11, color: 'var(--pb-fg-muted)', margin: '12px 0 0', lineHeight: 1.55 }}
        data-testid="gaokao-rank-equiv-source-note"
      >
        {zh
          ? `注：基于${province}省一分一段${data.granularity === 'anchors' ? '关键分数段（锚点插值）' : ''}数据，${approx ? '插值' : ''}仅供参考，实际以官方数据为准。${province === 'HI' ? '海南 2025 一分一段录自海南省考试局官方成绩分布表（图片版），已逐锚点交叉核验。' : ''}`
          : `Note: from ${province} 一分一段${data.granularity === 'anchors' ? ' key-anchor (interpolated)' : ''} data; for reference only — verify against official sources.${province === 'HI' ? ' HI 2025 一分一段 is transcribed from the 海南省考试局 official score-distribution images and cross-checked at multiple anchors.' : ''}`}
      </p>
    </div>
  );
};

window.ScoreRankEquivBlock = ScoreRankEquivBlock;
