// ─── Wave 14-C — GaokaoMockExamTrackerBlock ─────────────────────────────────
// "📊 模考追踪" chip + modal: input form + history list + trend SVG line chart.
// Storage: localStorage (client-only, no server round-trip).
// Mirrors the _gaokao-faq-block.jsx chip/modal pattern.

// ── LocalStorage helpers (browser-side clone of lib/gaokao-mock-exam-tracker.js) ──
const _MET_KEY_PREFIX = 'pb:mockExams:';

function _metStorage() {
  try {
    return window.localStorage;
  } catch (_e) {
    return null;
  }
}

function _loadExams(userId) {
  const s = _metStorage();
  if (!s || !userId) return [];
  try {
    const raw = s.getItem(_MET_KEY_PREFIX + userId);
    const arr = JSON.parse(raw || '[]');
    if (!Array.isArray(arr)) return [];
    return arr.slice().sort((a, b) =>
      (a.date || '').localeCompare(b.date || '') ||
      (a.createdAt || '').localeCompare(b.createdAt || '')
    );
  } catch (_e) {
    return [];
  }
}

function _saveExam(userId, exam) {
  const s = _metStorage();
  if (!s || !userId) return null;
  const records = _loadExams(userId);
  const id = Date.now() + '-' + Math.random().toString(36).slice(2, 8);
  const record = {
    id,
    userId,
    score: exam.score,
    rank: exam.rank != null ? Math.round(exam.rank) : null,
    date: exam.date || new Date().toISOString().slice(0, 10),
    subjectCombo: exam.subjectCombo || '',
    createdAt: new Date().toISOString(),
  };
  records.push(record);
  // re-sort
  records.sort((a, b) =>
    (a.date || '').localeCompare(b.date || '') ||
    (a.createdAt || '').localeCompare(b.createdAt || '')
  );
  s.setItem(_MET_KEY_PREFIX + userId, JSON.stringify(records));
  return record;
}

function _computeTrend(exams) {
  const valid = exams.filter(
    (e) => e && typeof e.score === 'number' && Number.isFinite(e.score)
  );
  if (valid.length === 0) return { slope: 0, projection: 0, scoreDelta: 0 };
  if (valid.length === 1) return { slope: 0, projection: valid[0].score, scoreDelta: 0 };
  const n = valid.length;
  const scores = valid.map((e) => e.score);
  const xMean = (n - 1) / 2;
  const yMean = scores.reduce((s, y) => s + y, 0) / n;
  let num = 0, den = 0;
  for (let i = 0; i < n; i++) {
    num += (i - xMean) * (scores[i] - yMean);
    den += (i - xMean) ** 2;
  }
  const slope = den === 0 ? 0 : num / den;
  const intercept = yMean - slope * xMean;
  const projection = Math.round((slope * n + intercept) * 10) / 10;
  const scoreDelta = Math.round((scores[n - 1] - scores[0]) * 10) / 10;
  return { slope: Math.round(slope * 100) / 100, projection, scoreDelta };
}

// ── Mini SVG line chart ──────────────────────────────────────────────────────

function MockExamTrendChart({ exams }) {
  if (!exams || exams.length < 2) return null;

  const W = 340, H = 110, PAD = 28;
  const scores = exams.map((e) => e.score);
  const minY = Math.min(...scores);
  const maxY = Math.max(...scores);
  const rangeY = maxY - minY || 1;

  const pts = scores.map((sc, i) => {
    const x = PAD + (i / (scores.length - 1)) * (W - PAD * 2);
    const y = H - PAD - ((sc - minY) / rangeY) * (H - PAD * 2);
    return [x, y];
  });

  const polyline = pts.map(([x, y]) => `${x},${y}`).join(' ');

  // Trend line (least-squares)
  const n = pts.length;
  const xMean = pts.reduce((s, [x]) => s + x, 0) / n;
  const yMean = pts.reduce((s, [, y]) => s + y, 0) / n;
  let num = 0, den = 0;
  for (const [x, y] of pts) {
    num += (x - xMean) * (y - yMean);
    den += (x - xMean) ** 2;
  }
  const slope = den === 0 ? 0 : num / den;
  const intercept = yMean - slope * xMean;
  const x0 = pts[0][0], x1 = pts[n - 1][0];
  const ty0 = slope * x0 + intercept, ty1 = slope * x1 + intercept;

  return (
    <svg
      viewBox={`0 0 ${W} ${H}`}
      width={W}
      height={H}
      style={{ display: 'block', maxWidth: '100%' }}
      aria-label="模考成绩趋势图"
      data-testid="mock-exam-trend-chart"
    >
      {/* Grid lines */}
      {[0, 0.5, 1].map((t) => {
        const y = H - PAD - t * (H - PAD * 2);
        return (
          <line key={t} x1={PAD} y1={y} x2={W - PAD} y2={y}
            stroke="var(--pb-border)" strokeWidth={1} strokeDasharray="4 4" />
        );
      })}
      {/* Score labels */}
      {[0, 0.5, 1].map((t) => {
        const val = minY + t * rangeY;
        const y = H - PAD - t * (H - PAD * 2);
        return (
          <text key={t} x={PAD - 4} y={y + 4} fontSize={9} fill="var(--pb-fg-muted)" textAnchor="end">
            {Math.round(val)}
          </text>
        );
      })}
      {/* Actual polyline */}
      <polyline
        points={polyline}
        fill="none"
        stroke="var(--pb-gold-500)"
        strokeWidth={2.5}
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      {/* Trend line (dashed) */}
      <line x1={x0} y1={ty0} x2={x1} y2={ty1}
        stroke="var(--pb-accent, #60a5fa)" strokeWidth={1.5} strokeDasharray="5 3" opacity={0.7} />
      {/* Data points */}
      {pts.map(([x, y], i) => (
        <circle key={i} cx={x} cy={y} r={4}
          fill="var(--pb-paper)" stroke="var(--pb-gold-500)" strokeWidth={2} />
      ))}
      {/* Date labels */}
      {exams.map((e, i) => {
        const [x] = pts[i];
        const label = (e.date || '').slice(5); // MM-DD
        return (
          <text key={i} x={x} y={H - 4} fontSize={9} fill="var(--pb-fg-muted)" textAnchor="middle">
            {label}
          </text>
        );
      })}
    </svg>
  );
}

// ── Main block ───────────────────────────────────────────────────────────────

const GaokaoMockExamTrackerBlock = ({ lang, userId: propUserId }) => {
  const [open, setOpen] = React.useState(false);
  const [exams, setExams] = React.useState([]);
  const [score, setScore] = React.useState('');
  const [rank, setRank] = React.useState('');
  const [date, setDate] = React.useState(new Date().toISOString().slice(0, 10));
  const [subjectCombo, setSubjectCombo] = React.useState('');
  const [error, setError] = React.useState('');
  const [saving, setSaving] = React.useState(false);

  const fallbackZh = (window.I18N && window.I18N.zh && window.I18N.zh.gk && window.I18N.zh.gk.mockExam) || {};
  const dict = (window.I18N && window.I18N[lang] && window.I18N[lang].gk && window.I18N[lang].gk.mockExam)
    || fallbackZh
    || {};

  // Resolve userId: from prop, auth context, or anonymous stub
  const userId = propUserId
    || (typeof useAuth === 'function' ? (useAuth().user || {}).id : null)
    || 'anon';

  const reload = React.useCallback(() => {
    setExams(_loadExams(userId));
  }, [userId]);

  React.useEffect(() => {
    if (open) reload();
  }, [open, reload]);

  // Lock body scroll
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, [open]);

  // Close on ESC
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    const onHash = () => setOpen(false); // #323(1) — close on route change
    window.addEventListener('keydown', onKey);
    window.addEventListener('hashchange', onHash);
    return () => { window.removeEventListener('keydown', onKey); window.removeEventListener('hashchange', onHash); };
  }, [open]);

  const trend = React.useMemo(() => _computeTrend(exams), [exams]);

  const handleAdd = (e) => {
    e.preventDefault();
    setError('');
    const sc = parseFloat(score);
    if (!Number.isFinite(sc) || sc < 0 || sc > 1000) {
      setError(dict.errorScore || '请输入有效分数 (0–1000)');
      return;
    }
    const rk = rank.trim() ? parseInt(rank, 10) : null;
    if (rk !== null && (!Number.isInteger(rk) || rk < 1)) {
      setError(dict.errorRank || '位次须为正整数');
      return;
    }
    setSaving(true);
    try {
      _saveExam(userId, { score: sc, rank: rk, date, subjectCombo });
      setScore('');
      setRank('');
      setDate(new Date().toISOString().slice(0, 10));
      setSubjectCombo('');
      reload();
    } finally {
      setSaving(false);
    }
  };

  const slopeLabel = trend.slope > 0.1
    ? (dict.trending || '进步趋势 ↑')
    : trend.slope < -0.1
      ? (dict.declining || '下降趋势 ↓')
      : (dict.stable || '成绩稳定 →');

  const slopeColor = trend.slope > 0.1
    ? 'var(--pb-success)'
    : trend.slope < -0.1
      ? 'var(--pb-danger, #dc2626)'
      : 'var(--pb-fg-muted)';

  return (
    <React.Fragment>
      {/* ── Chip ── */}
      <button
        type="button"
        data-testid="mock-exam-tracker-chip"
        onClick={() => setOpen(true)}
        style={{
          display: 'inline-flex',
          alignItems: 'center',
          gap: 6,
          padding: '6px 14px',
          fontSize: 12,
          fontWeight: 600,
          letterSpacing: '0.04em',
          textTransform: 'uppercase',
          borderRadius: 100,
          border: '1px solid var(--pb-gold-500)',
          background: 'transparent',
          color: 'var(--pb-gold-700)',
          cursor: 'pointer',
          transition: 'background 0.15s, color 0.15s',
        }}
        onMouseEnter={(e) => {
          e.currentTarget.style.background = 'var(--pb-gold-500)';
          e.currentTarget.style.color = 'var(--pb-navy-900)';
        }}
        onMouseLeave={(e) => {
          e.currentTarget.style.background = 'transparent';
          e.currentTarget.style.color = 'var(--pb-gold-700)';
        }}
      >
        <span aria-hidden="true" style={{ fontSize: 14, lineHeight: 1 }}>📊</span>
        {dict.chip || '模考追踪'}
      </button>

      {/* ── Modal — portalled to body to escape sticky stacking context (#258) ── */}
      {open && ReactDOM.createPortal(
        <div
          data-testid="mock-exam-modal"
          role="dialog"
          aria-modal="true"
          aria-labelledby="mock-exam-modal-title"
          style={{
            position: 'fixed',
            inset: 0,
            // Unified top modal layer — above auth-gate (2147483640) & tweaks panel (2147483646) so the popover is never occluded (#258/#218).
            zIndex: 2147483647,
            background: 'rgba(15, 22, 36, 0.55)',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            padding: 24,
          }}
          onClick={(e) => { if (e.target === e.currentTarget) setOpen(false); }}
        >
          <div
            className="pb-card"
            style={{
              maxWidth: 680,
              width: '100%',
              maxHeight: '88vh',
              overflow: 'auto',
              padding: 0,
              boxShadow: '0 20px 60px rgba(0,0,0,0.35)',
            }}
          >
            {/* Header */}
            <div
              style={{
                padding: '20px 28px',
                borderBottom: '1px solid var(--pb-border)',
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'flex-start',
                gap: 16,
                position: 'sticky',
                top: 0,
                background: 'var(--pb-paper)',
                zIndex: 1,
              }}
            >
              <div>
                <div className="pb-eyebrow" style={{ color: 'var(--pb-gold-700)', marginBottom: 8 }}>
                  {dict.eyebrow || '模考成绩追踪 · Mock Exam Tracker'}
                </div>
                <h3
                  id="mock-exam-modal-title"
                  style={{ fontFamily: 'var(--pb-serif)', fontSize: 20, fontWeight: 600, margin: 0 }}
                >
                  {dict.title || '模考成绩追踪'}
                </h3>
              </div>
              <button
                type="button"
                data-testid="mock-exam-modal-close"
                aria-label={dict.close || '关闭'}
                onClick={() => setOpen(false)}
                style={{
                  background: 'transparent',
                  border: '1px solid var(--pb-border)',
                  borderRadius: 8,
                  padding: '4px 10px',
                  fontSize: 16,
                  lineHeight: 1,
                  cursor: 'pointer',
                  color: 'var(--pb-fg-muted)',
                }}
              >
                ×
              </button>
            </div>

            {/* Body */}
            <div style={{ padding: '20px 28px' }}>
              {/* Add form */}
              <form onSubmit={handleAdd} data-testid="mock-exam-form" style={{ marginBottom: 24 }}>
                <div style={{ fontWeight: 600, fontSize: 13, marginBottom: 12, color: 'var(--pb-fg)' }}>
                  {dict.addTitle || '添加模考记录'}
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10, marginBottom: 10 }}>
                  <div>
                    <label style={{ fontSize: 11, color: 'var(--pb-fg-muted)', display: 'block', marginBottom: 4 }}>
                      {dict.labelScore || '分数 *'}
                    </label>
                    <input
                      className="pb-input"
                      type="number"
                      value={score}
                      onChange={(e) => setScore(e.target.value)}
                      placeholder="660"
                      min="0"
                      max="1000"
                      required
                      data-testid="mock-exam-score-input"
                      style={{ fontFamily: 'var(--pb-mono)', fontWeight: 600 }}
                    />
                  </div>
                  <div>
                    <label style={{ fontSize: 11, color: 'var(--pb-fg-muted)', display: 'block', marginBottom: 4 }}>
                      {dict.labelRank || '位次'}
                    </label>
                    <input
                      className="pb-input"
                      type="number"
                      value={rank}
                      onChange={(e) => setRank(e.target.value)}
                      placeholder="5000"
                      min="1"
                      data-testid="mock-exam-rank-input"
                      style={{ fontFamily: 'var(--pb-mono)' }}
                    />
                  </div>
                  <div>
                    <label style={{ fontSize: 11, color: 'var(--pb-fg-muted)', display: 'block', marginBottom: 4 }}>
                      {dict.labelDate || '考试日期'}
                    </label>
                    <input
                      className="pb-input"
                      type="date"
                      value={date}
                      onChange={(e) => setDate(e.target.value)}
                      data-testid="mock-exam-date-input"
                    />
                  </div>
                </div>
                <div style={{ marginBottom: 12 }}>
                  <label style={{ fontSize: 11, color: 'var(--pb-fg-muted)', display: 'block', marginBottom: 4 }}>
                    {dict.labelSubjectCombo || '选科组合（可选）'}
                  </label>
                  <input
                    className="pb-input"
                    type="text"
                    value={subjectCombo}
                    onChange={(e) => setSubjectCombo(e.target.value)}
                    placeholder="物化生"
                    data-testid="mock-exam-subject-input"
                    style={{ maxWidth: 200 }}
                  />
                </div>
                {error && (
                  <div data-testid="mock-exam-error" style={{ color: 'var(--pb-danger, #dc2626)', fontSize: 12, marginBottom: 8 }}>
                    {error}
                  </div>
                )}
                <button
                  type="submit"
                  className="pb-btn pb-btn-primary pb-btn-sm"
                  disabled={saving}
                  data-testid="mock-exam-submit"
                >
                  {saving ? (dict.saving || '保存…') : (dict.add || '添加')}
                </button>
              </form>

              {/* Trend summary + chart */}
              {exams.length >= 2 && (
                <div
                  data-testid="mock-exam-trend-section"
                  style={{
                    background: 'var(--pb-surface-alt)',
                    borderRadius: 10,
                    padding: '16px 20px',
                    marginBottom: 20,
                  }}
                >
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
                    <span style={{ fontWeight: 600, fontSize: 13 }}>{dict.trendTitle || '成绩趋势'}</span>
                    <span style={{ fontSize: 12, fontWeight: 600, color: slopeColor }}>{slopeLabel}</span>
                  </div>
                  <div style={{ display: 'flex', gap: 20, fontSize: 12, color: 'var(--pb-fg-muted)', marginBottom: 12 }}>
                    <span>
                      {dict.projection || '预测下次'}: <strong style={{ fontFamily: 'var(--pb-mono)', color: 'var(--pb-fg)' }}>{trend.projection}</strong>
                    </span>
                    <span>
                      {dict.delta || '首末差值'}: <strong style={{ fontFamily: 'var(--pb-mono)', color: slopeColor }}>
                        {trend.scoreDelta >= 0 ? '+' : ''}{trend.scoreDelta}
                      </strong>
                    </span>
                  </div>
                  <MockExamTrendChart exams={exams} />
                </div>
              )}

              {/* History list */}
              <div>
                <div style={{ fontWeight: 600, fontSize: 13, marginBottom: 12 }}>
                  {dict.historyTitle || '历史记录'}{exams.length > 0 ? ` (${exams.length})` : ''}
                </div>
                {exams.length === 0 ? (
                  <div data-testid="mock-exam-empty" style={{ fontSize: 12, color: 'var(--pb-fg-muted)', padding: '12px 0' }}>
                    {dict.empty || '暂无记录，请添加第一次模考成绩。'}
                  </div>
                ) : (
                  <div style={{ overflowX: 'auto' }}>
                    <table style={{ width: '100%', fontSize: 12, borderCollapse: 'collapse' }}>
                      <thead>
                        <tr style={{ borderBottom: '2px solid var(--pb-border)' }}>
                          {[dict.colDate || '日期', dict.colScore || '分数', dict.colRank || '位次', dict.colSubject || '选科'].map((h) => (
                            <th key={h} style={{ padding: '6px 8px', textAlign: 'left', fontWeight: 600, color: 'var(--pb-fg-muted)', fontSize: 11 }}>
                              {h}
                            </th>
                          ))}
                        </tr>
                      </thead>
                      <tbody>
                        {exams.map((ex, idx) => (
                          <tr
                            key={ex.id || idx}
                            data-testid={`mock-exam-row-${idx}`}
                            style={{ borderBottom: '1px solid var(--pb-border)' }}
                          >
                            <td style={{ padding: '8px 8px', fontFamily: 'var(--pb-mono)', color: 'var(--pb-fg-muted)' }}>{ex.date || '—'}</td>
                            <td style={{ padding: '8px 8px', fontFamily: 'var(--pb-mono)', fontWeight: 700, color: 'var(--pb-fg)' }}>{ex.score}</td>
                            <td style={{ padding: '8px 8px', fontFamily: 'var(--pb-mono)', color: 'var(--pb-fg-muted)' }}>{ex.rank != null ? ex.rank.toLocaleString() : '—'}</td>
                            <td style={{ padding: '8px 8px', color: 'var(--pb-fg-muted)' }}>{ex.subjectCombo || '—'}</td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                )}
              </div>
            </div>

            {/* Footer */}
            <div style={{
              padding: '12px 28px',
              borderTop: '1px solid var(--pb-border)',
              background: 'var(--pb-surface-alt)',
              fontSize: 11,
              color: 'var(--pb-fg-muted)',
            }}>
              {dict.footer || '数据存储在本地浏览器，清除缓存后数据将丢失。'}
            </div>
          </div>
        </div>
      , document.body)}
    </React.Fragment>
  );
};

window.GaokaoMockExamTrackerBlock = GaokaoMockExamTrackerBlock;
