// swc.jsx  v1  —  SWC 监控组件（个人视图）：Hopkins 最小有意义变化可视化
// 职责：接收指标历史值，计算 SWC = 0.2 × 队伍 SD，展示当前值相对 SWC 的变化方向与幅度
// 依赖：data.js（SWC 常数）· 由 dashboard-views.jsx 引用
//
// Hopkins (Sportscience 2004; Hopkins et al. Med Sci Sports Exerc 2009)
// defines the SWC as the smallest practically meaningful change in a
// performance metric. For between-athlete comparisons in team sports,
// SWC = 0.2 × between-athlete SD (Cohen's "small" effect threshold).
//
// Individual application: compare an athlete's most-recent change in a
// metric against multiples of the squad SD (effect size). Classify
// magnitude using standard Cohen/Hopkins thresholds:
//   < 0.2 σ   → trivial  (within noise / not worthwhile)
//   0.2–0.6 σ → small    (the SWC band)
//   0.6–1.2 σ → moderate
//   1.2–2.0 σ → large
//   > 2.0 σ   → very large
//
// This panel surfaces meaningful changes only, separating improvements
// from declines, so the coach can see what to praise and what to address.

const { useState: swcUseS, useMemo: swcUseM } = React;

const SWC_BANDS = [
  { id: 'trivial',   label: 'Trivial',    min: 0,   max: 0.2, color: 'var(--muted)'  },
  { id: 'small',     label: 'Small',      min: 0.2, max: 0.6, color: 'var(--accent-2)' },
  { id: 'moderate',  label: 'Moderate',   min: 0.6, max: 1.2, color: 'var(--warn)'   },
  { id: 'large',     label: 'Large',      min: 1.2, max: 2.0, color: '#fb923c'       },
  { id: 'veryLarge', label: 'Very large', min: 2.0, max: 999, color: '#f472b6'       },
];

function classifyES(absES) {
  for (const b of SWC_BANDS) if (absES >= b.min && absES < b.max) return b;
  return SWC_BANDS[0];
}

// Build a flat list of {metric, group, prev, curr, deltaRaw, es, magnitude, improved}
function buildSWCRows(athlete, groups, seasons, squadStatsAll) {
  // Sort dates desc — newest first (numeric date sort, not string sort)
  const sortedDates = [...seasons].sort((a, b) => new Date(b) - new Date(a));
  const sv = athlete.seasons || {};
  // Find two most recent dates that have ANY data for this athlete
  const dated = sortedDates.filter(d => sv[d] && Object.keys(sv[d]).length);
  if (dated.length < 1) return { rows: [], currDate: null, prevDate: null };

  const currDate = dated[0];
  const prevDate = dated[1] || null;
  const curr = sv[currDate] || {};
  const prev = prevDate ? (sv[prevDate] || {}) : {};

  const rows = [];
  groups.forEach(g => {
    g.metrics.forEach(m => {
      if (m.dir === 'neutral') return;
      const c = curr[m.id];
      const p = prev[m.id];
      if (c == null || p == null) return;

      const squadVals = (squadStatsAll[currDate] && squadStatsAll[currDate][m.id]?.values) || [];
      if (squadVals.length < 2) return;
      const sd = window.DASHBOARD_DATA.stddev(squadVals);
      if (sd === 0) return;

      const deltaRaw = c - p;
      // Effect size in σ. Sign = direction of improvement (per metric.dir).
      let es = deltaRaw / sd;
      if (m.dir === 'lower') es = -es;
      const absES = Math.abs(es);
      const band = classifyES(absES);
      const improved = es > 0;
      rows.push({
        metricId: m.id,
        label: m.label,
        unit: m.unit,
        group: g.label,
        groupAccent: g.accent,
        prev: p,
        curr: c,
        deltaRaw,
        es,
        absES,
        band,
        improved,
        swc: 0.2 * sd,
        sd,
      });
    });
  });
  // Order by magnitude desc — biggest movers up top
  rows.sort((a, b) => b.absES - a.absES);
  return { rows, currDate, prevDate };
}

// ──────────────────────────────────────────────────────────────────────────
// Readiness score: weighted balance of improvements vs declines (0-100, 50=neutral)
const BAND_WEIGHT = { trivial: 0, small: 1, moderate: 2, large: 3, veryLarge: 4 };
function calcReadiness(improvements, declines) {
  if (!improvements.length && !declines.length) return null;
  const pos = improvements.reduce((s, r) => s + (BAND_WEIGHT[r.band.id] || 0), 0);
  const neg = declines.reduce((s, r) => s + (BAND_WEIGHT[r.band.id] || 0), 0);
  const total = pos + neg || 1;
  return Math.round(50 + ((pos - neg) / total) * 50);
}

// Detect consecutive same-direction changes across all sessions for each metric.
// Returns sorted array of { metricId, label, group, groupAccent, count, improved }.
function detectConsecutiveStreaks(athlete, groups, seasons) {
  const sortedDates = [...seasons].sort((a, b) => new Date(b) - new Date(a)); // newest first
  const sv = athlete.seasons || {};
  const streaks = [];

  groups.forEach(g => {
    g.metrics.forEach(m => {
      if (m.dir === 'neutral') return;

      // First gather only the dated values that actually exist for this metric,
      // skipping sessions where the athlete didn't record this metric. This way
      // a single missed session doesn't artificially break a consistent streak.
      const present = [];
      for (const dt of sortedDates) {
        const v = sv[dt]?.[m.id];
        if (v != null) present.push(v);
      }
      if (present.length < 2) return;

      // Now compute consecutive change directions across present values
      const changes = [];
      for (let i = 0; i < present.length - 1; i++) {
        const delta = present[i] - present[i + 1];
        if (Math.abs(delta) < 1e-9) continue;  // tied value — skip, don't break the streak
        const improving = m.dir === 'lower' ? delta < 0 : delta > 0;
        changes.push(improving ? 1 : -1);
      }
      if (!changes.length) return;
      const firstDir = changes[0];
      let count = 1;
      for (let i = 1; i < changes.length; i++) {
        if (changes[i] === firstDir) count++;
        else break;
      }
      if (count >= 2) {
        streaks.push({
          metricId: m.id, label: m.label,
          group: g.label, groupAccent: g.accent,
          count,
          improved: firstDir > 0,
        });
      }
    });
  });

  return streaks.sort((a, b) => b.count - a.count);
}

function SWCPanel({ athlete, groups, seasons, squadStatsAll }) {
  const { rows, currDate, prevDate } = swcUseM(
    () => buildSWCRows(athlete, groups, seasons, squadStatsAll),
    [athlete, groups, seasons, squadStatsAll]
  );

  const streaks = swcUseM(
    () => detectConsecutiveStreaks(athlete, groups, seasons),
    [athlete, groups, seasons]
  );

  const meaningful   = rows.filter(r => r.band.id !== 'trivial');
  const improvements = meaningful.filter(r => r.improved);
  const declines     = meaningful.filter(r => !r.improved);
  const trivials     = rows.filter(r => r.band.id === 'trivial');
  const readiness    = calcReadiness(improvements, declines);

  // Training priority: group with the most / highest-magnitude declines
  const priorityGroup = (() => {
    if (!declines.length) return null;
    const tally = {};
    declines.forEach(r => {
      tally[r.group] = (tally[r.group] || 0) + (BAND_WEIGHT[r.band.id] || 0);
    });
    return Object.entries(tally).sort((a,b) => b[1]-a[1])[0]?.[0];
  })();

  if (!rows.length || !prevDate) {
    return (
      <div style={{ padding: '14px 12px', fontSize: 12, color: 'var(--muted)', textAlign: 'center' }}>
        Need at least two dated measurements to compute change.
      </div>
    );
  }

  const rdColor = readiness == null ? 'var(--muted)'
    : readiness >= 60 ? 'var(--pos)'
    : readiness >= 40 ? 'var(--warn)'
    : 'var(--neg)';

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>

      {/* ── Compact header + summary row ── */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
        padding: '8px 10px',
        background: 'var(--panel-2)', border: '1px solid var(--border)', borderRadius: 6,
      }}>
        {/* Date range */}
        <div className="mono" style={{ fontSize: 10, color: 'var(--muted)', marginRight: 4 }}>
          {window.formatDate ? window.formatDate(prevDate, 'compact') : prevDate}
          {' → '}
          {window.formatDate ? window.formatDate(currDate, 'compact') : currDate}
        </div>

        {/* Improvement / decline counts */}
        <span style={{ fontSize: 11, color: 'var(--pos)', fontWeight: 600 }}>↑ {improvements.length}</span>
        <span style={{ fontSize: 11, color: 'var(--neg)', fontWeight: 600 }}>↓ {declines.length}</span>
        {trivials.length > 0 && (
          <span style={{ fontSize: 10, color: 'var(--muted)' }}>· {trivials.length} trivial</span>
        )}

        {/* Readiness pill */}
        {readiness != null && (
          <span style={{
            marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: 5,
            padding: '2px 9px', borderRadius: 999,
            background: `${rdColor}18`, border: `1px solid ${rdColor}55`,
            fontSize: 11, color: rdColor, fontFamily: 'var(--font-mono)', fontWeight: 600,
          }}>
            Readiness {readiness}
          </span>
        )}

        {/* Training priority */}
        {priorityGroup && (
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 4,
            padding: '2px 8px', borderRadius: 999,
            background: 'rgba(248,113,113,.1)', border: '1px solid rgba(248,113,113,.25)',
            fontSize: 10, color: 'var(--neg)',
          }}>
            Focus: {priorityGroup}
          </span>
        )}

        {/* Magnitude legend (compact) */}
        <div style={{ display: 'flex', gap: 2, height: 4, width: 80, borderRadius: 2, overflow: 'hidden', marginLeft: priorityGroup ? 0 : 'auto' }}
          title="Trivial · Small · Moderate · Large · Very large">
          {SWC_BANDS.map(b => (
            <div key={b.id}
              style={{ flex: b.max === 999 ? 1 : (b.max - b.min), background: b.color, opacity: .65 }}/>
          ))}
        </div>
      </div>

      {/* ── Consecutive streak alerts ── */}
      {streaks.length > 0 && (
        <div style={{
          display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center',
          padding: '6px 10px',
          background: 'var(--panel-2)', border: '1px solid var(--border)', borderRadius: 6,
        }}>
          <span style={{ fontSize: 9, textTransform: 'uppercase', letterSpacing: '.1em', color: 'var(--muted)', flexShrink: 0 }}>
            Streaks
          </span>
          {streaks.map(s => (
            <span key={s.metricId} style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              padding: '2px 8px', borderRadius: 999, fontSize: 11, fontWeight: 600,
              background: s.improved ? 'rgba(52,211,153,.12)' : 'rgba(248,113,113,.12)',
              border: `1px solid ${s.improved ? 'rgba(52,211,153,.4)' : 'rgba(248,113,113,.4)'}`,
              color: s.improved ? 'var(--pos)' : 'var(--neg)',
            }}>
              <span style={{ fontSize: 9 }}>{s.improved ? '↑' : '↓'}</span>
              {s.label}
              <span className="mono" style={{ fontSize: 9, opacity: .75 }}>×{s.count}</span>
            </span>
          ))}
        </div>
      )}

      {/* ── Improvements ── */}
      {improvements.length > 0 && (
        <SWCGroup title="Improvements" count={improvements.length} accent="var(--pos)" rows={improvements}/>
      )}

      {/* ── Declines ── */}
      {declines.length > 0 && (
        <SWCGroup title="Declines" count={declines.length} accent="var(--neg)" rows={declines}/>
      )}

      {/* ── Trivial footer ── */}
      {trivials.length > 0 && (
        <div style={{
          fontSize: 10, color: 'var(--muted-2)', padding: '4px 8px',
          display: 'flex', justifyContent: 'space-between',
        }}>
          <span>{trivials.length} metric{trivials.length === 1 ? '' : 's'} within SWC</span>
          <span className="mono">|ΔES| &lt; 0.2σ</span>
        </div>
      )}

      {meaningful.length === 0 && (
        <div style={{
          padding: '10px 12px', textAlign: 'center',
          border: '1px dashed var(--border)', borderRadius: 6,
          fontSize: 11, color: 'var(--muted)',
        }}>
          All changes within SWC — no meaningful shifts to flag.
        </div>
      )}
    </div>
  );
}

function SWCGroup({ title, count, accent, rows }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '2px 2px 4px' }}>
        <span style={{ width: 3, height: 10, borderRadius: 1, background: accent }}/>
        <span style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '.08em', color: accent, fontWeight: 600 }}>
          {title}
        </span>
        <span className="mono" style={{ fontSize: 10, color: 'var(--muted)' }}>{count}</span>
      </div>
      {rows.map(r => <SWCRow key={r.metricId} row={r}/>)}
    </div>
  );
}

function SWCRow({ row }) {
  const r = row;
  const cap = 2.5;
  const pct = Math.min(1, r.absES / cap) * 100;
  const swcPct = (0.2 / cap) * 100;
  const fmt = (v) => Math.abs(v) >= 100 ? v.toFixed(0) : Math.abs(v) >= 10 ? v.toFixed(1) : v.toFixed(2);

  return (
    <div style={{
      display: 'grid', gridTemplateColumns: '1fr 56px 1fr 62px',
      gap: 8, alignItems: 'center',
      padding: '5px 8px', borderRadius: 4,
      background: 'var(--panel-2)', border: '1px solid var(--border)',
    }}>
      {/* Label + prev→curr */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
        <span style={{ width: 3, height: 12, borderRadius: 1, background: r.groupAccent, flexShrink: 0 }}/>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 11, color: 'var(--text-2)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{r.label}</div>
          <div className="mono" style={{ fontSize: 9, color: 'var(--muted)' }}>
            {fmt(r.prev)} → {fmt(r.curr)}{r.unit ? ' '+r.unit : ''}
          </div>
        </div>
      </div>

      {/* Delta value */}
      <div className="mono" style={{
        fontSize: 12, fontWeight: 600, textAlign: 'right',
        color: r.improved ? 'var(--pos)' : 'var(--neg)',
      }}>
        {r.deltaRaw > 0 ? '+' : ''}{fmt(r.deltaRaw)}
      </div>

      {/* Effect size bar */}
      <div style={{ position: 'relative', height: 12 }}>
        <div style={{ position: 'absolute', inset: '3px 0', background: 'rgba(15,23,42,.06)', borderRadius: 2 }}/>
        <div style={{ position: 'absolute', left: `${swcPct}%`, top: 0, bottom: 0, width: 1, background: 'rgba(15,23,42,.35)' }}/>
        <div style={{
          position: 'absolute', left: 0, top: 3, bottom: 3,
          width: `${pct}%`,
          background: r.improved
            ? `linear-gradient(90deg, var(--pos)66, ${r.band.color})`
            : `linear-gradient(90deg, var(--neg)66, ${r.band.color})`,
          borderRadius: 2,
        }}/>
      </div>

      {/* Magnitude badge + ES */}
      <div style={{ textAlign: 'right' }}>
        <span style={{
          display: 'inline-block', padding: '1px 6px', borderRadius: 999,
          fontSize: 9, fontWeight: 600,
          background: `${r.band.color}20`, color: r.band.color,
          border: `1px solid ${r.band.color}44`,
          textTransform: 'uppercase', letterSpacing: '.05em',
        }}>{r.band.label}</span>
        <div className="mono" style={{ fontSize: 9, color: 'var(--muted)', marginTop: 1 }}>{r.absES.toFixed(2)} σ</div>
      </div>
    </div>
  );
}

Object.assign(window, { SWCPanel, buildSWCRows }); // buildSWCRows: TEAM-2 swc-movers reuse
