// 方案 ③ —— 缩放同一条河（语义缩放：拉远=月走向 → 拉近=周频道+事 → 再拉近=日呼吸景深）
function RiverZoom() {
  const boxRef = useRef(null);
  const [dim, setDim] = useState({ W: 400, H: 600 });
  const [lvl, setLvl] = useState(0);              // 0=月 1=周 2=日（可连续）
  const [selWeek, setSelWeek] = useState(window.WEEK_OF[window.TODAY]);
  const [selDay, setSelDay] = useState(window.TODAY);
  const rafRef = useRef(null);
  const draggingRef = useRef(false);

  // 测量容器
  useEffect(() => {
    const el = boxRef.current; if (!el) return;
    const ro = new ResizeObserver(() => {
      const r = el.getBoundingClientRect();
      setDim({ W: r.width, H: r.height });
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const cc = (id) => (window.CATS[id] || {}).color || '#9DB4BF';
  const { W, H } = dim;
  const N = 30;
  const riverY = H * 0.52;

  // —— 缩放几何（左右留白：右侧给滑杆让位，避免裁切）——
  const PAD_L = 12, PAD_R = 56;
  const usableW = Math.max(60, W - PAD_L - PAD_R);
  const S0 = 1, S1 = N / 7, S2 = N / 1.7;
  const lerp = (a, b, t) => a + (b - a) * t;
  const dayX1 = (d) => PAD_L + ((d - 0.5) / N) * usableW;
  const monthCenter = PAD_L + usableW / 2;
  const weekX1 = (wi) => {
    const ds = window.WEEKS[wi].filter(Boolean);
    return dayX1(ds.reduce((s, x) => s + x.d, 0) / ds.length);
  };
  const scaleAt = (L) => (L <= 1 ? lerp(S0, S1, L) : lerp(S1, S2, L - 1));
  const focusAt = (L) => (L <= 1
    ? lerp(monthCenter, weekX1(selWeek), L)
    : lerp(weekX1(selWeek), dayX1(selDay), L - 1));
  const S = scaleAt(lvl);
  const tx = monthCenter - focusAt(lvl) * S;
  const screenX = (d) => dayX1(d) * S + tx;

  // —— 平滑过渡 ——
  const tweenTo = (target, opts = {}) => {
    if (opts.selDay !== undefined) setSelDay(opts.selDay);
    if (opts.selWeek !== undefined) setSelWeek(opts.selWeek);
    cancelAnimationFrame(rafRef.current);
    const start = lvl, t0 = performance.now(), dur = 520;
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    const step = (now) => {
      const t = Math.min(1, (now - t0) / dur);
      setLvl(start + (target - start) * ease(t));
      if (t < 1) rafRef.current = requestAnimationFrame(step);
    };
    rafRef.current = requestAnimationFrame(step);
  };

  // 给触摸手势用：始终拿到最新的 lvl / tweenTo，避免重绑监听
  const lvlRef = useRef(lvl); lvlRef.current = lvl;
  const tweenToRef = useRef(tweenTo); tweenToRef.current = tweenTo;

  // —— 河流光带（屏幕坐标重算，缩放不失真）——
  const buildStrand = (baseFrac, amp, k, phase) => {
    const pts = [];
    for (let sx = -10; sx <= W + 10; sx += 7) {
      const frac = (((sx - tx) / S) - PAD_L) / usableW;   // 回到月进度
      const y = riverY + (baseFrac * H) + amp * Math.sin(frac * Math.PI * k + phase);
      pts.push([sx, y]);
    }
    return 'M' + pts.map(p => `${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' L ');
  };

  // 详情/层显隐
  const wkDetail = Math.max(0, Math.min(1, (lvl - 0.4) / 0.55));  // 周详情淡入
  const chipShow = Math.max(0, Math.min(1, (lvl - 0.6) / 0.4));   // 关键事 chips（周停留点即清晰）
  const dayShow = Math.max(0, Math.min(1, (lvl - 1.5) / 0.5));    // 日视图覆盖
  const monthShow = Math.max(0, Math.min(1, (0.7 - lvl) / 0.5));  // 月走向氛围

  // 标尺密度
  const daySpacing = (W / N) * S;
  const labelEvery = daySpacing > 30 ? 1 : daySpacing > 14 ? 2 : 5;

  // 当前可见的天
  const visDays = window.MONTH.filter(day => { const x = screenX(day.d); return x > -40 && x < W + 40; });

  // —— 滑杆缩放 ——
  const onSliderPointer = (e) => {
    const track = e.currentTarget.getBoundingClientRect();
    const move = (clientY) => {
      const t = Math.max(0, Math.min(1, (clientY - track.top) / track.height));
      setLvl((1 - t) * 2); // 顶=日(2) 底=月(0)
    };
    draggingRef.current = true;
    cancelAnimationFrame(rafRef.current);
    move(e.clientY);
    const mv = (ev) => move(ev.clientY);
    const up = () => {
      draggingRef.current = false;
      window.removeEventListener('pointermove', mv);
      window.removeEventListener('pointerup', up);
      setLvl(L => { const snap = Math.round(L); tweenTo(snap); return L; });
    };
    window.addEventListener('pointermove', mv);
    window.addEventListener('pointerup', up);
  };

  // 滚轮缩放
  useEffect(() => {
    const el = boxRef.current; if (!el) return;
    let acc = 0;
    const onWheel = (e) => {
      if (lvl >= 1.5) return; // 进入日视图后交给日视图滚动
      e.preventDefault();
      acc += e.deltaY;
      if (Math.abs(acc) > 60) {
        const dir = acc > 0 ? -1 : 1; // 上滚=拉近
        tweenTo(Math.max(0, Math.min(2, Math.round(lvl) + dir)));
        acc = 0;
      }
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, [lvl]);

  // 触摸缩放（手机）：双指捏合 = 缩放；单指竖向拖 = 缩放（上滑=拉近）。绑定一次，靠 ref 取最新值
  useEffect(() => {
    const el = boxRef.current; if (!el) return;
    let mode = null, startDist = 0, startY = 0, startLvl = 0, moved = false;
    const dist = (t) => Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY);
    const onStart = (e) => {
      if (e.target.closest && e.target.closest('[data-slider]')) { mode = null; return; }
      cancelAnimationFrame(rafRef.current);
      startLvl = lvlRef.current; moved = false;
      if (e.touches.length >= 2) { mode = 'pinch'; startDist = dist(e.touches); }
      else if (e.touches.length === 1 && lvlRef.current < 1.5) { mode = 'pan'; startY = e.touches[0].clientY; }
      else mode = null;
    };
    const onMove = (e) => {
      if (mode === 'pinch' && e.touches.length >= 2) {
        e.preventDefault(); moved = true;
        const ratio = dist(e.touches) / (startDist || 1);
        setLvl(Math.max(0, Math.min(2, startLvl + (ratio - 1) * 2.2)));
      } else if (mode === 'pan' && e.touches.length === 1) {
        const dy = startY - e.touches[0].clientY;            // 上滑=拉近
        if (!moved && Math.abs(dy) < 10) return;             // 阈值内当作点按，放过去给按钮
        moved = true; e.preventDefault();
        setLvl(Math.max(0, Math.min(2, startLvl + dy / 150)));
      }
    };
    const onEnd = (e) => {
      if (e.touches && e.touches.length > 0) return;
      if (moved) tweenToRef.current(Math.round(lvlRef.current));  // 松手吸附到最近层
      mode = null; moved = false;
    };
    el.addEventListener('touchstart', onStart, { passive: false });
    el.addEventListener('touchmove', onMove, { passive: false });
    el.addEventListener('touchend', onEnd);
    el.addEventListener('touchcancel', onEnd);
    return () => {
      el.removeEventListener('touchstart', onStart);
      el.removeEventListener('touchmove', onMove);
      el.removeEventListener('touchend', onEnd);
      el.removeEventListener('touchcancel', onEnd);
    };
  }, []);

  const headTxt = lvl < 0.5 ? '六月 · 顺流看走向'
    : lvl < 1.5 ? `第 ${selWeek + 1} 周 · 频道与关键事`
    : `${selDay} 日 · 周${window.MONTH[selDay - 1].weekday}`;

  const knobTop = (1 - lvl / 2) * 100;

  return (
    <div ref={boxRef} style={rzStyles.root}>
      {/* 顶部状态 */}
      <div style={rzStyles.head}>
        <div style={rzStyles.headRow}>
          {lvl > 0.05 && (
            <button style={rzStyles.zoomOut} onClick={() => tweenTo(lvl > 1.5 ? 1 : 0)}>‹ 拉远</button>
          )}
          <div style={rzStyles.title}>{headTxt}</div>
        </div>
        <div style={{ ...rzStyles.threads, opacity: monthShow, display: monthShow < 0.02 ? 'none' : 'flex' }}>
          {Object.values(window.THREADS).map(t => (
            <span key={t.id} style={rzStyles.threadTag}>
              <span style={{ ...rzStyles.threadLine, background: t.color }} />{t.name}
            </span>
          ))}
        </div>
      </div>

      {/* 河面 SVG */}
      <svg width={W} height={H} style={rzStyles.svg}>
        <defs>
          <linearGradient id="rzFlow" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0%" stopColor="#BFE9DA" /><stop offset="50%" stopColor="#BBD8FF" /><stop offset="100%" stopColor="#FFD3E8" />
          </linearGradient>
          <filter id="rzGlow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="6" /></filter>
        </defs>
        {/* 主河带 */}
        <path d={buildStrand(0, 14, 5, 0)} fill="none" stroke="url(#rzFlow)" strokeWidth={18} strokeLinecap="round" opacity="0.9" />
        <path d={buildStrand(-0.04, 18, 4, 1.2)} fill="none" stroke={window.THREADS.T1.color} strokeWidth={5} strokeLinecap="round" opacity={0.5 + 0.2 * monthShow} />
        <path d={buildStrand(0.05, 22, 6, 2.4)} fill="none" stroke={window.THREADS.T2.color} strokeWidth={4} strokeLinecap="round" opacity={0.45 + 0.2 * monthShow} />
        <path d={buildStrand(0.02, 16, 7, 4)} fill="none" stroke={window.THREADS.T3.color} strokeWidth={3.5} strokeLinecap="round" opacity={0.4 + 0.2 * monthShow} />
        {/* 流动高光 */}
        <path d={buildStrand(0, 14, 5, 0)} fill="none" stroke="#fff" strokeWidth={2} strokeLinecap="round" opacity="0.55"
          strokeDasharray="2 16">
          <animate attributeName="stroke-dashoffset" from="0" to="-36" dur="2.2s" repeatCount="indefinite" />
        </path>

        {/* 每天的色点 */}
        {visDays.map(day => {
          const x = screenX(day.d);
          const isToday = day.d === window.TODAY;
          const c = day.rest ? '#FFD3E8' : cc((day.key[0] || {}).cat);
          const r = day.rest ? 3 : 5 + 2 * chipShow;
          return (
            <g key={day.d}>
              {isToday && <circle cx={x} cy={riverY} r={12 + 6 * dayShow} fill="#fff" opacity="0.5" filter="url(#rzGlow)" />}
              <circle cx={x} cy={riverY} r={isToday ? r + 1.5 : r} fill={isToday ? '#fff' : c}
                opacity={day.rest ? 0.6 : 0.95} stroke={isToday ? c : 'none'} strokeWidth={isToday ? 2 : 0} />
            </g>
          );
        })}
      </svg>

      {/* 周/日详情：非缩放覆盖层，文字始终清晰 */}
      <div style={rzStyles.overlayLayer}>
        {wkDetail > 0.02 && visDays.map(day => {
          const x = screenX(day.d);
          if (x < 6 || x > W - PAD_R + 4) return null;
          const ch = window.CHANNELS[day.channel];
          const inSel = window.WEEK_OF[day.d] === selWeek;
          const op = wkDetail * (inSel ? 1 : 0.32);
          return (
            <div key={day.d} style={{ ...rzStyles.dayCol, left: x, opacity: op }}>
              {/* 关键事 = 小色点（周缩放下保持清爽，文字详情在「日」里）*/}
              <div style={{ ...rzStyles.colDots, opacity: chipShow, transform: `translateY(${(1 - chipShow) * 6}px)` }}>
                {day.rest
                  ? <span style={rzStyles.restChip}>留白</span>
                  : day.key.map((it, i) => (
                      <span key={i} style={{ ...rzStyles.colDot, background: cc(it.cat) }} title={it.t} />
                    ))}
              </div>
              <button style={{ ...rzStyles.colHead, background: ch.tint + 'ee', boxShadow: inSel ? '0 10px 22px -12px rgba(54,84,99,.5)' : 'none' }}
                onClick={() => tweenTo(2, { selDay: day.d, selWeek: window.WEEK_OF[day.d] })}>
                <span style={rzStyles.colCh}><span style={{ ...rzStyles.chDot, background: ch.glow }} />{ch.name}</span>
                <span style={rzStyles.colDate}>{day.d}</span>
                <span style={rzStyles.colWk}>周{day.weekday}</span>
              </button>
            </div>
          );
        })}
      </div>

      {/* 月级日期标尺（§12：③ 必须解决的"定位某天"）—— tap 任意刻度 = snap 到那天 */}
      <div style={{ ...rzStyles.ruler, opacity: 1 - dayShow }}>
        {window.MONTH.map(day => {
          const x = screenX(day.d);
          if (x < 6 || x > W - 6) return null;
          const showLabel = day.d % labelEvery === 0 || day.d === 1 || day.d === window.TODAY;
          const isToday = day.d === window.TODAY;
          return (
            <button key={day.d} style={{ ...rzStyles.tick, left: x }}
              onClick={() => tweenTo(2, { selDay: day.d, selWeek: window.WEEK_OF[day.d] })}>
              <span style={{ ...rzStyles.tickMark, background: isToday ? '#6FB4FF' : 'rgba(110,140,155,.4)', height: isToday ? 12 : 7 }} />
              {showLabel && <span style={{ ...rzStyles.tickNum, color: isToday ? '#365463' : 'var(--ink-faint)', fontWeight: isToday ? 700 : 500 }}>{day.d}</span>}
            </button>
          );
        })}
        {/* 周分隔标签 */}
        {monthShow > 0.1 && window.WEEKS.map((w, wi) => {
          const ds = w.filter(Boolean);
          const cx = screenX(ds[Math.floor(ds.length / 2)].d);
          return (
            <button key={wi} style={{ ...rzStyles.weekTab, left: cx, opacity: monthShow }}
              onClick={() => tweenTo(1, { selWeek: wi })}>W{wi + 1}</button>
          );
        })}
      </div>

      {/* 缩放滑杆 */}
      <div style={rzStyles.slider}>
        <div style={rzStyles.sliderLabelTop}>日</div>
        <div data-slider style={rzStyles.track} onPointerDown={onSliderPointer}>
          <div style={rzStyles.trackFill} />
          <div style={{ ...rzStyles.knob, top: `calc(${knobTop}% - 13px)` }}>
            <span style={rzStyles.knobIcon}>{lvl > 1.5 ? '日' : lvl > 0.5 ? '周' : '月'}</span>
          </div>
        </div>
        <div style={rzStyles.sliderLabelBot}>月</div>
      </div>

      {/* 日视图覆盖（呼吸景深）—— 底部小河即这条河拉到最近 */}
      {dayShow > 0.02 && (
        <div style={{ ...rzStyles.dayOverlay, opacity: dayShow, pointerEvents: dayShow > 0.6 ? 'auto' : 'none' }}>
          <div style={rzStyles.dayHead}>
            <button style={rzStyles.backBtn} onClick={() => tweenTo(1)}>‹ 拉回这一周</button>
            <div style={rzStyles.dayHeadDate}>{selDay} 日 · 周{window.MONTH[selDay - 1].weekday}</div>
          </div>
          <TodayView dayNum={selDay} embedded />
        </div>
      )}

      {/* 引导文案 */}
      <div style={{ ...rzStyles.hint, opacity: lvl < 0.5 ? 1 : 0 }}>
        双指捏合 / 上下滑 / 拖滑杆 = 缩放这条河 · 点标尺某天 = 直达那天
      </div>
    </div>
  );
}

const rzStyles = {
  root: { flex: 1, minHeight: 0, position: 'relative', overflow: 'hidden', touchAction: 'none' },
  head: { position: 'absolute', top: 0, left: 0, right: 0, padding: '16px 20px 0', zIndex: 6, pointerEvents: 'none' },
  headRow: { display: 'flex', alignItems: 'center', gap: 10 },
  title: { fontSize: 21, fontWeight: 700, letterSpacing: '-0.4px' },
  threads: { gap: 12, marginTop: 8, flexWrap: 'wrap', transition: 'opacity .3s' },
  threadTag: { fontSize: 11.5, fontWeight: 600, color: 'var(--ink-soft)', display: 'flex', alignItems: 'center', gap: 5 },
  threadLine: { width: 16, height: 3, borderRadius: 2 },
  svg: { position: 'absolute', inset: 0 },

  overlayLayer: { position: 'absolute', inset: 0, zIndex: 4, pointerEvents: 'none' },
  dayCol: { position: 'absolute', top: 0, bottom: 0, transform: 'translateX(-50%)', width: 54, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10, pointerEvents: 'auto' },
  colDots: { display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center', position: 'absolute', bottom: '62%', transition: 'opacity .2s' },
  colDot: { width: 9, height: 9, borderRadius: 999, boxShadow: '0 2px 6px -2px rgba(54,84,99,.4)' },
  chip: { fontSize: 11.5, fontWeight: 600, color: 'var(--ink)', background: 'rgba(255,255,255,0.82)', borderRadius: 999, padding: '4px 9px', border: '1px solid', display: 'inline-flex', alignItems: 'center', gap: 4, whiteSpace: 'nowrap', boxShadow: '0 4px 12px -8px rgba(54,84,99,.4)' },
  chipDot: { width: 6, height: 6, borderRadius: 999 },
  restChip: { fontSize: 10.5, color: 'var(--ink-faint)', fontWeight: 600, fontStyle: 'italic' },
  colHead: { position: 'absolute', top: '54%', border: '1px solid rgba(255,255,255,0.75)', borderRadius: 13, padding: '6px 4px 7px', width: 48, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, cursor: 'pointer', fontFamily: 'var(--font)' },
  colCh: { fontSize: 9.5, fontWeight: 700, color: 'var(--ink)', display: 'flex', alignItems: 'center', gap: 3, whiteSpace: 'nowrap' },
  chDot: { width: 5, height: 5, borderRadius: 999 },
  colDate: { fontSize: 18, fontWeight: 800, color: 'var(--ink)', lineHeight: 1.05 },
  colWk: { fontSize: 9.5, fontWeight: 600, color: 'var(--ink-soft)' },

  ruler: { position: 'absolute', left: 0, right: 0, bottom: 64, height: 40, zIndex: 5, transition: 'opacity .3s' },
  tick: { position: 'absolute', bottom: 16, transform: 'translateX(-50%)', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3, border: 'none', background: 'none', cursor: 'pointer', padding: 0 },
  tickMark: { width: 2, borderRadius: 2, display: 'block' },
  tickNum: { fontSize: 10 },
  weekTab: { position: 'absolute', bottom: 0, transform: 'translateX(-50%)', fontSize: 11, fontWeight: 700, color: 'var(--ink-soft)', background: 'rgba(255,255,255,0.7)', border: '1px solid rgba(255,255,255,0.7)', borderRadius: 999, padding: '2px 10px', cursor: 'pointer', fontFamily: 'var(--font)' },

  slider: { position: 'absolute', right: 14, top: '50%', transform: 'translateY(-50%)', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, zIndex: 7 },
  sliderLabelTop: { fontSize: 11, fontWeight: 700, color: 'var(--ink-soft)' },
  sliderLabelBot: { fontSize: 11, fontWeight: 700, color: 'var(--ink-soft)' },
  track: { width: 30, height: 200, borderRadius: 999, background: 'rgba(255,255,255,0.5)', border: '1px solid rgba(255,255,255,0.7)', position: 'relative', cursor: 'pointer', boxShadow: 'inset 0 2px 8px rgba(54,84,99,.12)', touchAction: 'none' },
  trackFill: { position: 'absolute', inset: 3, borderRadius: 999, background: 'linear-gradient(#BBD8FF,#BFE9DA)', opacity: .5 },
  knob: { position: 'absolute', left: '50%', transform: 'translateX(-50%)', width: 26, height: 26, borderRadius: 999, background: '#fff', boxShadow: '0 6px 16px -4px rgba(54,84,99,.5)', display: 'flex', alignItems: 'center', justifyContent: 'center' },
  knobIcon: { fontSize: 12, fontWeight: 700, color: 'var(--ink)' },

  zoomOut: { border: 'none', background: 'rgba(255,255,255,0.78)', color: 'var(--ink)', fontSize: 13, fontWeight: 600, padding: '6px 12px', borderRadius: 999, cursor: 'pointer', fontFamily: 'var(--font)', boxShadow: '0 6px 16px -8px rgba(54,84,99,.4)', pointerEvents: 'auto', flexShrink: 0 },

  dayOverlay: { position: 'absolute', inset: 0, zIndex: 8, background: 'linear-gradient(165deg,#EAF6F1 0%,#DEEDFF 52%,#F3E8F5 100%)', display: 'flex', flexDirection: 'column' },
  dayHead: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 18px 2px', flexShrink: 0 },
  backBtn: { border: 'none', background: 'rgba(255,255,255,0.7)', color: 'var(--ink)', fontSize: 13, fontWeight: 600, padding: '7px 13px', borderRadius: 999, cursor: 'pointer', fontFamily: 'var(--font)', boxShadow: '0 6px 16px -8px rgba(54,84,99,.4)' },
  dayHeadDate: { fontSize: 14, fontWeight: 700, color: 'var(--ink-soft)' },

  hint: { position: 'absolute', left: 0, right: 56, bottom: 16, textAlign: 'center', fontSize: 12, color: 'var(--ink-faint)', fontWeight: 500, transition: 'opacity .3s', zIndex: 5, pointerEvents: 'none', padding: '0 12px' },
};

window.RiverZoom = RiverZoom;
