// entry.jsx  v1  —  数据录入模态框：手动录入运动员带日期的指标测量值，前值预填充
// 职责：表单 UI + 写入 athlete.seasons，无自有持久化（由 app.jsx 接收回调后经持久化边界保存）
// 依赖：components.jsx · data.js（METRIC_GROUPS）
// for any athlete; previous values are pre-filled so only deltas need typing.

const { useState: eUseState, useMemo: eUseMemo, useRef: eUseRef, useEffect: eUseEffect } = React;

function DataEntryModal({ open, onClose, athletes, onAthletesChange, groups, seasons, onSeasonsChange, defaultAthleteId, defaultDate }) {
  const [athleteId, setAthleteId] = eUseState(defaultAthleteId || athletes[0]?.id || null);
  const [date, setDate] = eUseState(defaultDate || todayISO());
  const [values, setValues] = eUseState({});
  const [filledFromId, setFilledFromId] = eUseState(null);
  const [saved, setSaved] = eUseState(false);
  const [validationError, setValidationError] = eUseState(null); // user-facing validation message

  const athlete = athletes.find(a => a.id === athleteId) || athletes[0] || null;
  const editableMetricIds = eUseMemo(() => new Set(
    groups.flatMap(g => g.metrics.filter(m => !m.computed).map(m => m.id))
  ), [groups]);

  // Re-read launch context every time the modal opens. This keeps calendar /
  // athlete-page shortcuts from inheriting a stale athlete or date from the
  // previous entry session.
  eUseEffect(() => {
    if (!open) return;
    const nextAthleteId = defaultAthleteId && athletes.some(a => a.id === defaultAthleteId)
      ? defaultAthleteId
      : athletes[0]?.id || null;
    setAthleteId(nextAthleteId);
    setDate(defaultDate || todayISO());
  }, [open, defaultAthleteId, defaultDate, athletes.length]);

  // Pre-fill with most recent values for this athlete (sorted dates, descending)
  eUseEffect(() => {
    if (!open || !athlete) return;
    const sortedDates = Object.keys(athlete.seasons || {}).sort((a, b) => new Date(b) - new Date(a));
    let mostRecent = null;
    for (const d of sortedDates) {
      if (athlete.seasons[d] && Object.keys(athlete.seasons[d]).length) {
        mostRecent = athlete.seasons[d]; break;
      }
    }
    const nextValues = {};
    Object.entries(mostRecent || {}).forEach(([key, value]) => {
      if (editableMetricIds.has(key)) nextValues[key] = value;
    });
    setValues(nextValues);
    setFilledFromId(athlete.id);
    setSaved(false);
  }, [athleteId, open, editableMetricIds]);

  if (!open) return null;
  if (!athlete) {
    return (
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(5,8,12,.7)', backdropFilter: 'blur(4px)',
        zIndex: 100, display: 'grid', placeItems: 'center', padding: 20,
      }}>
        <div onClick={(e) => e.stopPropagation()} style={{
          background: 'var(--panel)', border: '1px solid var(--border-strong)',
          borderRadius: 12, width: 420, maxWidth: '100%', padding: 18,
          boxShadow: '0 20px 60px rgba(0,0,0,.5)',
        }}>
          <h3 style={{ margin: '0 0 8px', fontSize: 16 }}>No athlete available</h3>
          <div style={{ color: 'var(--muted)', fontSize: 12, lineHeight: 1.5 }}>
            Add an athlete before recording measurements.
          </div>
          <div style={{ marginTop: 14, display: 'flex', justifyContent: 'flex-end' }}>
            <button className="btn" onClick={onClose}>Close</button>
          </div>
        </div>
      </div>
    );
  }

  const setVal = (mid, raw) => {
    setValues(v => ({ ...v, [mid]: raw === '' ? '' : (isNaN(+raw) ? raw : +raw) }));
    if (validationError) setValidationError(null);
  };

  const hasDateEntry = athlete.seasons[date] && Object.keys(athlete.seasons[date]).length > 0;

  const save = () => {
    setValidationError(null);
    // Validate date format
    if (!date || isNaN(new Date(date).getTime())) {
      setValidationError('Please enter a valid date.');
      return;
    }
    // Coerce values to numbers; drop empty
    const clean = {};
    Object.entries(values).forEach(([k, v]) => {
      if (!editableMetricIds.has(k)) return;
      if (v === '' || v == null) return;
      const n = +v;
      if (!isNaN(n)) clean[k] = n;
    });
    if (!Object.keys(clean).length) {
      setValidationError('No metrics filled in. Enter at least one value before saving.');
      return;
    }

    // Merge with existing if any
    const existing = athlete.seasons[date] || {};
    const merged = { ...existing, ...clean };

    const savedAt = new Date().toISOString();
    const nextAthletes = athletes.map(a => {
      if (a.id !== athleteId) return a;
      const provenance = { ...(a.provenance || {}) };
      const byMetric = { ...(provenance[date] || {}) };
      ['sRPE', 'duration_min'].forEach(metricId => {
        if (!Object.prototype.hasOwnProperty.call(clean, metricId)) return;
        byMetric[metricId] = {
          source: 'manual_entry',
          metricId,
          value: clean[metricId],
          savedAt,
        };
      });
      if (Object.keys(byMetric).length) provenance[date] = byMetric;
      return window.DASHBOARD_DATA.recomputeLoadForAthlete({
        ...a,
        seasons: { ...a.seasons, [date]: merged },
        provenance,
      });
    });
    onAthletesChange(nextAthletes);

    if (!seasons.includes(date)) {
      // Sort chronologically as actual dates, newest first.
      // String-sort fails for non-zero-padded dates like '2025-1-5' vs '2025-10-1'.
      onSeasonsChange([...seasons, date].sort((a, b) => new Date(b) - new Date(a)));
    }
    setSaved(true);
    setTimeout(() => setSaved(false), 1800);
  };

  const totalMetrics = groups.reduce((s, g) => s + g.metrics.filter(m => !m.computed).length, 0);
  const filledCount = Object.entries(values).filter(([key, v]) => editableMetricIds.has(key) && v !== '' && v != null && !isNaN(+v)).length;

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(5,8,12,.7)', backdropFilter: 'blur(4px)',
      zIndex: 100, display: 'grid', placeItems: 'center', padding: 20,
      animation: 'fade .2s ease both',
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: 'var(--panel)', border: '1px solid var(--border-strong)',
        borderRadius: 12, width: 960, maxWidth: '100%', maxHeight: '92vh',
        boxShadow: '0 20px 60px rgba(0,0,0,.5)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
        animation: 'fadeUp .25s ease both',
      }}>
        {/* header */}
        <div style={{
          padding: '14px 18px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          borderBottom: '1px solid var(--border)',
        }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.1em' }}>
              Data Entry
            </div>
            <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>Record measurement</h3>
          </div>
          <button onClick={onClose} className="btn ghost" style={{ padding: 6 }}><Icon name="x" size={14}/></button>
        </div>

        {/* meta strip */}
        <div style={{
          padding: '14px 18px',
          borderBottom: '1px solid var(--border)',
          background: 'var(--panel-2)',
          display: 'grid', gridTemplateColumns: '2fr 1.2fr 1fr', gap: 14, alignItems: 'flex-end',
        }}>
          <div>
            <Label>Athlete</Label>
            <AthletePicker athletes={athletes} value={athleteId} onChange={setAthleteId}/>
          </div>
          <div>
            <Label>Date of measurement</Label>
            <input
              type="date"
              value={date}
              onChange={(e) => setDate(e.target.value)}
              style={{
                width: '100%',
                background: 'var(--panel)', border: '1px solid var(--border)',
                borderRadius: 6, padding: '8px 10px',
                color: 'var(--text)', fontSize: 13, fontFamily: 'var(--font-mono)',
                outline: 'none', colorScheme: 'dark',
              }}
              onFocus={(e) => e.target.style.borderColor = 'var(--accent)'}
              onBlur={(e) => e.target.style.borderColor = 'var(--border)'}
            />
            {hasDateEntry && (
              <div style={{ fontSize: 10, color: 'var(--warn)', marginTop: 4 }}>
                Existing entry on this date will be merged.
              </div>
            )}
          </div>
          <div style={{ textAlign: 'right' }}>
            <Label>Progress</Label>
            <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'flex-end', gap: 4 }}>
              <span className="mono" style={{ fontSize: 22, fontWeight: 600, color: 'var(--text)' }}>{filledCount}</span>
              <span style={{ fontSize: 12, color: 'var(--muted)' }}>/ {totalMetrics}</span>
            </div>
            <div style={{ fontSize: 10, color: 'var(--muted)' }}>
              Pre-filled with most recent values
            </div>
          </div>
        </div>

        {/* metric form */}
        <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '18px' }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            {groups.map(g => {
              // Hide metrics flagged computed:true (session_load, acwr) — they
              // are derived from sRPE × duration, not directly entered. If a
              // group has nothing left to enter after filtering, hide it too.
              const inputMetrics = g.metrics.filter(m => !m.computed);
              if (!inputMetrics.length) return null;
              return (
                <GroupEntrySection
                  key={g.id}
                  group={{ ...g, metrics: inputMetrics }}
                  values={values} setVal={setVal}
                  reference={athlete.seasons}
                />
              );
            })}
          </div>
        </div>

        {/* footer */}
        <div style={{
          padding: '12px 18px',
          borderTop: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          background: 'var(--panel)',
        }}>
          <div style={{ fontSize: 11, color: validationError ? 'var(--neg)' : 'var(--muted)', display: 'flex', alignItems: 'center', gap: 6 }}>
            {validationError ? (
              <>
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                  <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
                </svg>
                <span>{validationError}</span>
              </>
            ) : (
              <span>Saving records this measurement under <span className="mono" style={{ color: 'var(--text-2)' }}>{date}</span> for {athlete.name}.</span>
            )}
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            {saved && (
              <span style={{
                fontSize: 12, color: 'var(--pos)',
                display: 'inline-flex', alignItems: 'center', gap: 6,
                animation: 'fade .2s ease both',
              }}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M20 6 9 17l-5-5"/>
                </svg>
                Saved
              </span>
            )}
            <button className="btn" onClick={onClose}>Close</button>
            <button className="btn primary" onClick={save}>
              <Icon name="plus" size={12}/> Save measurement
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function todayISO() {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}

const Label = ({ children }) => (
  <div style={{
    fontSize: 10, textTransform: 'uppercase', letterSpacing: '.1em',
    color: 'var(--muted)', marginBottom: 6,
  }}>{children}</div>
);

// ──────────────────────────────────────────────────────────────────────────
function AthletePicker({ athletes, value, onChange }) {
  const [open, setOpen] = eUseState(false);
  const ref = eUseRef(null);
  eUseEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, []);
  const current = athletes.find(a => a.id === value) || athletes[0];
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button onClick={() => setOpen(o => !o)} style={{
        width: '100%',
        display: 'flex', alignItems: 'center', gap: 10, justifyContent: 'space-between',
        background: 'var(--panel)', border: '1px solid var(--border)',
        borderRadius: 6, padding: '7px 10px',
        color: 'var(--text)', cursor: 'pointer',
        font: 'inherit',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
          <AvatarMini name={current.name} accent="var(--accent)" size={26}/>
          <div style={{ textAlign: 'left', minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text)' }}>{current.name}</div>
            <div style={{ fontSize: 11, color: 'var(--muted)' }}>{current.position} · #{String(current.jersey).padStart(2,'0')}</div>
          </div>
        </div>
        <Icon name="chevDown" size={12}/>
      </button>
      {open && (
        <div className="fade" style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0,
          background: 'var(--panel-2)', border: '1px solid var(--border-strong)',
          borderRadius: 6, padding: 4, zIndex: 50,
          boxShadow: '0 8px 24px rgba(0,0,0,.4)',
          maxHeight: 280, overflowY: 'auto',
        }}>
          {athletes.map(a => (
            <button key={a.id} onClick={() => { onChange(a.id); setOpen(false); }}
              style={{
                width: '100%', textAlign: 'left',
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '6px 8px', borderRadius: 4,
                background: a.id === value ? 'var(--accent-soft)' : 'transparent',
                color: 'inherit', border: 0, cursor: 'pointer', font: 'inherit',
              }}
              onMouseEnter={(e) => { if (a.id !== value) e.currentTarget.style.background = 'var(--panel-hi)'; }}
              onMouseLeave={(e) => { if (a.id !== value) e.currentTarget.style.background = 'transparent'; }}
            >
              <AvatarMini name={a.name} accent={a.id === value ? 'var(--accent)' : '#eaeef3'} size={22}/>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 12, color: a.id === value ? 'var(--accent-2)' : 'var(--text)' }}>{a.name}</div>
                <div style={{ fontSize: 10, color: 'var(--muted)' }}>{a.position} · #{String(a.jersey).padStart(2,'0')}</div>
              </div>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────────────────────
function GroupEntrySection({ group, values, setVal, reference }) {
  // Find most recent prior value for diff hint
  const sortedDates = Object.keys(reference || {}).sort((a, b) => new Date(b) - new Date(a));
  const recent = sortedDates[0] ? reference[sortedDates[0]] : {};

  return (
    <section style={{
      border: '1px solid var(--border)', borderRadius: 8,
      background: 'var(--panel-2)', overflow: 'hidden',
    }}>
      <div style={{
        padding: '10px 14px',
        display: 'flex', alignItems: 'center', gap: 10,
        borderBottom: '1px solid var(--border)',
        background: 'var(--panel)',
      }}>
        <div style={{ width: 4, height: 16, borderRadius: 2, background: group.accent, boxShadow: `0 0 8px ${group.accent}66` }}/>
        <div style={{ fontSize: 12, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '.08em' }}>{group.label}</div>
        <div style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--muted)' }}>{group.metrics.length} metrics</div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0,1fr))', gap: 1, background: 'var(--border)' }}>
        {group.metrics.map(m => {
          const cur = values[m.id];
          const ref = recent[m.id];
          const hasDiff = ref != null && cur != null && cur !== '' && !isNaN(+cur) && +cur !== ref;
          const delta = hasDiff ? (+cur - ref) : 0;
          const improved = hasDiff
            ? (m.dir === 'lower' ? delta < 0 : m.dir === 'higher' ? delta > 0 : false)
            : false;
          const arrow = !hasDiff ? '' : delta > 0 ? '▲' : '▼';
          const color = !hasDiff ? 'var(--muted)' : improved ? 'var(--pos)' : 'var(--neg)';
          return (
            <div key={m.id} style={{
              background: 'var(--panel-2)',
              padding: '10px 12px',
              display: 'flex', flexDirection: 'column', gap: 4,
            }}>
              <div style={{
                fontSize: 11, color: 'var(--text-2)',
                display: 'flex', alignItems: 'baseline', gap: 6, justifyContent: 'space-between',
                minWidth: 0,
              }}>
                <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }} title={m.label}>{m.label}</span>
                <span style={{ fontSize: 10, color: 'var(--muted)', fontFamily: 'var(--font-mono)', flexShrink: 0 }}>{m.unit}</span>
              </div>
              <input
                type="number"
                value={cur ?? ''}
                step="any"
                onChange={(e) => setVal(m.id, e.target.value)}
                placeholder={ref != null ? String(ref) : '—'}
                style={{
                  background: 'var(--panel)',
                  border: '1px solid var(--border)',
                  borderRadius: 4,
                  padding: '6px 8px',
                  color: 'var(--text)',
                  fontSize: 13, fontFamily: 'var(--font-mono)',
                  outline: 'none',
                  textAlign: 'right',
                }}
                onFocus={(e) => e.target.style.borderColor = group.accent}
                onBlur={(e) => e.target.style.borderColor = 'var(--border)'}
              />
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, fontFamily: 'var(--font-mono)' }}>
                <span style={{ color: 'var(--muted)' }}>
                  prev {ref != null ? ref : '—'}
                </span>
                {hasDiff && (
                  <span style={{ color, display: 'inline-flex', alignItems: 'center', gap: 3 }}>
                    <span style={{ fontSize: 8 }}>{arrow}</span>
                    {Math.abs(delta).toFixed(Math.abs(delta) >= 10 ? 1 : 2)}
                  </span>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </section>
  );
}

Object.assign(window, { DataEntryModal });
