/* race-card.jsx — 発艇表ビッグカード（紺地×白の高コントラスト全画面）。
   2モード：
   ・raceId … 1レース（発艇時刻ドン＋エントリー）。レーダー/日程から。
   ・eventKey … 1種目（予選〜決勝のラウンド一覧＋エントリー＋クルー艇図）。種目タブから。
   レーン・組分けは捏造しない（当日発表と明記）。 */

// karal.jp 実結果（RW.realRaces / RW.realRacesFor）をラウンド別にグルーピングして表示するための補助
const KARAL_ROUND_ORDER = ["予選", "敗者戦", "準々決勝", "準決", "C決勝", "順位決定", "B決勝", "A決勝", "オープン", "レース"];
const karalRoundLabel = (r) => r === "敗復" ? "敗者戦" : r === "決勝" ? "A決勝" : r;
function groupKaralByRound(races) {
  const byRound = {};
  (races || []).forEach((r) => { const l = karalRoundLabel(r.round); (byRound[l] = byRound[l] || []).push(r); });
  return Object.keys(byRound)
    .sort((a, b) => KARAL_ROUND_ORDER.indexOf(a) - KARAL_ROUND_ORDER.indexOf(b))
    .map((round) => ({ round, races: byRound[round] }));
}
// 表示用の「通過」欄には、埋まっている区間のうち最も後ろのもの（1500→1000→500の順）を優先して出す
// （500mが計測できていないレースが多いため。無ければ空欄のまま）
function karalPickMid(l) { return l.t1500 || l.t1000 || l.t500 || ""; }

function KaralLaneRows({ races, favTeams, onOpenTeam, cat, entries, ros }) {
  const RW = window.RW;
  const norm = (s) => String(s || "").replace(/[\s　]/g, "");
  const [openKey, setOpenKey] = React.useState(null);
  return races.map((kr, gi) => {
    const lanes = kr.lanes.slice().sort((a, b) => {
      if (a.rank == null && b.rank == null) return (a.lane || 0) - (b.lane || 0);
      if (a.rank == null) return 1;
      if (b.rank == null) return -1;
      return a.rank - b.rank;
    });
    return (
      <div className="rc-res-grp" key={gi}>
        <div className="rc-res-grphd">{kr.grp || ""}{kr.time ? "　" + kr.time : ""}{kr.raceNo ? <b className="rc-raceno">レースNo.{kr.raceNo}</b> : null}</div>
        {lanes.map((l, i) => {
          const isFav = !!(favTeams && l.team && favTeams[RW.baseTeam(l.team)]);
          const num = l.rank != null ? l.rank : (l.status || (l.lane != null ? l.lane : "―"));
          // 高校（未成年）のクルーは個人名・プロフィールを表示しない（チーム名のみ）。
          const minor = RW.isMinorTeam && RW.isMinorTeam(l.team);
          const showName = l.name && !minor;
          const matchedEntry = !l.name && l.team && entries && !minor ? entries.find((en) => norm(en.team) === norm(l.team)) : null;
          const roster = matchedEntry ? ((ros && ros[matchedEntry.n]) || []) : [];
          const key = gi + "-" + (l.team || i);
          const expandable = roster.length > 0;
          // 個人名の行（スカル）は行タップでチーム欄へ飛ばさない（名前タップ＝プロフィールのみ）。
          // クルー行のみ、展開 or チーム欄へ遷移させる。
          const navTeam = !l.name && !!l.team;
          const open = openKey === key;
          return (
            <React.Fragment key={i}>
              <div className={"rc-row" + (expandable || navTeam ? " clk" : "") + (cat === "w" ? " w" : "") + (isFav ? " fav" : "")}
                   onClick={expandable ? () => setOpenKey(open ? null : key) : (navTeam ? () => onOpenTeam(l.team) : undefined)}>
                <span className="rc-n">{num}</span>
                <span className="rc-ident">
                  <span className="rc-team">{showName ? <RowerName name={l.name} light /> : (l.team || "")}{isFav && <Star size={11} color="var(--gold-2)" />}</span>
                  {showName && <span className="rc-sub">{l.team}</span>}
                </span>
                <span className="rc-res-t">{karalPickMid(l)}</span>
                <span className="rc-res-t">{l.t2000 || ""}</span>
                {l.qualify && <span className="rc-res-q">{l.qualify}</span>}
                {expandable && <IconChevron dir={open ? "up" : "down"} size={12} />}
              </div>
              {open && (
                <div className="rc-roster">
                  {roster.map((m, si) => (
                    <div className="rc-seat" key={si}>
                      <span className="rc-s">{m[0]}</span>
                      <span className="rc-nm"><RowerName name={m[1]} kana={m[2]} light hideKana /></span>
                      {m[2] && <span className="rc-k">{m[2]}</span>}
                    </div>
                  ))}
                  <button type="button" className="rc-teambtn" onClick={() => onOpenTeam(l.team)}>チーム詳細 →</button>
                </div>
              )}
            </React.Fragment>
          );
        })}
      </div>
    );
  });
}

// ペース比較（ヒートマップ／レース再現）用の補助。
// karal.jpの実データは500m/1000m/1500m/2000mの4区間（2025年度の実データで確認済み）。
// ただし計測できていない区間はしばしば空欄になる（先頭の500mが特に多い）ため、欠測は前提として扱う。
function karalTimeToSec(t) {
  const m = String(t || "").match(/(\d+):(\d{2}(?:\.\d+)?)/);
  return m ? (+m[1]) * 60 + parseFloat(m[2]) : null;
}
function karalSecToTime(x) {
  if (x == null) return "";
  const mm = Math.floor(x / 60), ss = x - mm * 60;
  return mm + ":" + ss.toFixed(2).padStart(5, "0");
}
function karalLapStr(x) { const mm = Math.floor(x / 60); return mm + ":" + (x - mm * 60).toFixed(1).padStart(4, "0"); }
function karalPaceColor(frac) {
  const a = [40, 116, 240], b = [255, 72, 88];
  const c = a.map((v, i) => Math.round(v + (b[i] - v) * frac));
  return "rgba(" + c[0] + "," + c[1] + "," + c[2] + ",0.28)";
}
// 距離0mを起点に、実測できている通過点だけを繋いだ折れ線（区間欠測は前後の実測点から補間する）
function karalWaypoints(cum) {
  const wp = [{ d: 0, t: 0 }];
  for (let i = 0; i < 4; i++) { if (cum[i] != null) wp.push({ d: (i + 1) * 500, t: cum[i] }); }
  return wp;
}
function karalDistAt(wp, tt) {
  const last = wp[wp.length - 1];
  if (tt <= 0) return 0;
  if (tt >= last.t) return last.d;
  for (let i = 1; i < wp.length; i++) {
    if (tt < wp[i].t) { const a = wp[i - 1], b = wp[i]; return a.d + (tt - a.t) / (b.t - a.t) * (b.d - a.d); }
  }
  return last.d;
}
function karalTimeAtDist(wp, d) {
  const last = wp[wp.length - 1];
  if (d <= 0) return 0;
  if (d >= last.d) return last.t;
  for (let i = 1; i < wp.length; i++) {
    if (d < wp[i].d) { const a = wp[i - 1], b = wp[i]; return a.t + (d - a.d) / (b.d - a.d) * (b.t - a.t); }
  }
  return last.t;
}
// karalList（種目の全レース）から、2000mゴールタイムがある艇だけを取り出す（DNS/DNF等は除外）
const KARAL_RND_ORD = { "予選": 0, "敗者戦": 1, "準々決勝": 2, "準決": 3, "B決勝": 4, "A決勝": 5 };
function karalPaceRows(karalList) {
  const out = [];
  const zen = (s) => String(s || "").replace(/[Ａ-Ｚａ-ｚ０-９]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xFEE0));
  const nrm = (s) => String(s || "").replace(/[\s　]/g, "");
  // ラウンドごとの出場クルー集合（チーム＋氏名）。「後のラウンドの発艇表に載っている＝進出」を判定するため。
  // karalは各組1着にしか「→Ａ決勝」を付けず、タイム上がりの矢印は無いので、掲載ベースで補完する。
  const inRound = {};
  (karalList || []).forEach((race) => {
    const R = karalRoundLabel(race.round);
    (race.lanes || []).forEach((l) => { if (l.team) (inRound[R] = inRound[R] || new Set()).add(nrm(l.team) + "|" + nrm(l.name)); });
  });
  const destOf = (R, l) => {
    const q = zen(l.qualify);
    if (/DNS|棄権/.test(q)) return null;
    if (/A決勝/.test(q)) return "A決勝";
    if (/B決勝/.test(q)) return "B決勝";
    if (/準決/.test(q)) return "準決";
    if (/敗者|敗復/.test(q)) return "敗者戦";
    // 矢印なし＝タイム上がり等の可能性。直後のラウンドから順に、発艇表への掲載を探す
    const key = nrm(l.team) + "|" + nrm(l.name);
    let best = null;
    Object.keys(inRound).forEach((R2) => {
      if ((KARAL_RND_ORD[R2] ?? 9) > (KARAL_RND_ORD[R] ?? 9) && inRound[R2].has(key)) {
        if (best == null || (KARAL_RND_ORD[R2] ?? 9) < (KARAL_RND_ORD[best] ?? 9)) best = R2;
      }
    });
    return best;
  };
  (karalList || []).forEach((race) => {
    const round = karalRoundLabel(race.round);
    (race.lanes || []).forEach((l) => {
      const cum = [karalTimeToSec(l.t500), karalTimeToSec(l.t1000), karalTimeToSec(l.t1500), karalTimeToSec(l.t2000)];
      if (cum[3] == null) return;
      const laps = [0, 1, 2, 3].map((i) => (cum[i] != null && (i === 0 || cum[i - 1] != null)) ? cum[i] - (i === 0 ? 0 : cum[i - 1]) : null);
      out.push({
        name: l.name, team: l.team, heat: race.grp || round, round, next: destOf(round, l), rank: l.rank, lane: l.lane,
        cum, laps, total: cum[3], wp: karalWaypoints(cum),
      });
    });
  });
  return out;
}

// 全組横断ペース・ヒートマップ（500mごとの速さを色で。欠測区間は「－」）
function KaralPaceHeatmap({ rows }) {
  if (!rows || !rows.length) return null;
  const labels = ["0–500m", "500–1000m", "1000–1500m", "1500–2000m"];
  const colMM = [0, 1, 2, 3].map((i) => {
    const v = rows.map((r) => r.laps[i]).filter((x) => x != null);
    return v.length ? [Math.min(...v), Math.max(...v)] : [0, 0];
  });
  const tMM = [Math.min(...rows.map((r) => r.total)), Math.max(...rows.map((r) => r.total))];
  const frac = (x, mn, mx) => (mx > mn ? (x - mn) / (mx - mn) : 0);
  const sorted = [...rows].sort((a, b) => a.total - b.total);
  return (
    <div className="pcwrap">
      <div className="pchint">500mごとのラップを色分け（<b style={{ color: "#2b8cff" }}>青＝速い</b>／<b style={{ color: "#ff4858" }}>赤＝遅い</b>、区間内で比較）。計測できていない区間は「－」。左が青→右が赤なら前半飛ばして後半失速。</div>
      <div className="hmscroll">
        <div className="hmtable">
          <div className="hmrow hmhead"><span className="hmnm" /><span className="hmtot">2000m</span>{labels.map((l, i) => <span className="hmc" key={i}>{l}</span>)}</div>
          {sorted.map((r, ri) => (
            <div className="hmrow" key={ri}>
              <span className="hmnm"><b>{r.name || r.team}</b>{r.name && <i className="hmteam">{r.team}</i>}<span className="hmsub">{r.heat}{r.rank ? "・" + r.rank + "着" : ""}</span></span>
              <span className="hmtot" style={{ background: karalPaceColor(frac(r.total, tMM[0], tMM[1])) }}>{karalSecToTime(r.total)}</span>
              {r.laps.map((lp, i) => (
                <span className="hmc" key={i} style={lp != null ? { background: karalPaceColor(frac(lp, colMM[i][0], colMM[i][1])) } : undefined}>{lp != null ? karalLapStr(lp) : "－"}</span>
              ))}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// レース再現（ヨーイドン）：2000mゴールタイムがある艇を、一斉スタートに見立てて左→右へ進める
// 欠測区間があっても前後の実測通過点から距離を補間するので、500m/1000m/1500mのどれが欠けていても動く
function KaralRaceReplay({ rows, initialRound, lockRound }) {
  const all = (rows || []).filter((r) => r.total != null);
  // ラウンド（予選/敗者戦/準決/…）で分ける。「全組」は同じラウンド内の全組だけ（予選と決勝を混ぜて一斉スタートしない）
  // initialRound＝親（種目カードのラウンドタブ）から指定された初期ラウンド。lockRound時は内部のラウンド切替を出さない。
  const rounds = [...new Set(all.map((r) => r.round))].sort((a, b) => (KARAL_RND_ORD[a] ?? 9) - (KARAL_RND_ORD[b] ?? 9));
  const [roundSel, setRoundSel] = React.useState(initialRound || null);
  const curRound = (roundSel && rounds.indexOf(roundSel) >= 0) ? roundSel : rounds[rounds.length - 1]; // 既定＝最新ラウンド
  const inRound = all.filter((r) => r.round === curRound);
  const heats = [...new Set(inRound.map((r) => r.heat))];
  const [heatSel, setHeatSel] = React.useState(null);
  const curHeat = (heatSel && heats.indexOf(heatSel) >= 0) ? heatSel : null;
  const [t, setT] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);
  const [speed, setSpeed] = React.useState(2);
  const PALETTE = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#a855f7", "#06b6d4"];
  const crews = (curHeat ? inRound.filter((r) => r.heat === curHeat) : inRound).slice().sort((a, b) => a.total - b.total);
  const maxT = crews.length ? Math.max(...crews.map((c) => c.total)) : 0;
  const singleHeat = heats.length <= 1;
  const heatColor = (h) => PALETTE[Math.max(0, heats.indexOf(h)) % PALETTE.length];
  const colorOf = (c, idx) => ((curHeat || singleHeat) ? PALETTE[idx % PALETTE.length] : heatColor(c.heat));
  // 開始時のみ約1秒スタートラインで静止（ヨーイ…ドン）。組/ラウンド切替でリセット。
  const startHold = React.useRef(1);
  React.useEffect(() => { startHold.current = 1; }, [curRound, curHeat]);
  React.useEffect(() => {
    if (!playing) return;
    let raf, last = null;
    const step = (ts) => {
      if (last == null) last = ts;
      const dt = (ts - last) / 1000; last = ts;
      if (startHold.current > 0) { startHold.current -= dt; raf = requestAnimationFrame(step); return; } // ヨーイ…（1秒静止）
      setT((prev) => { const nx = prev + dt * speed * 18; if (nx >= maxT) { setPlaying(false); return maxT; } return nx; });
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [playing, speed, maxT]);
  if (!crews.length) return null;
  const lead = Math.max(...crews.map((c) => karalDistAt(c.wp, t)));
  const xPct = (d) => 5 + (d / 2000) * 93; // 左=スタート(5%)→右=ゴール(98%)。関西はコースが左→右に流れる
  const ROWH = 23;
  const order = crews.map((c, i) => ({ i, d: karalDistAt(c.wp, t) }))
    .sort((a, b) => (b.d - a.d) || (crews[a.i].total - crews[b.i].total));
  const rankOf = {}; order.forEach((o, pos) => { rankOf[o.i] = pos; });
  const leadI = order[0].i;
  const leaderWp = crews[leadI].wp;
  const bowOrder = crews.map((c, i) => ({ i, b: +c.lane || 99 })).sort((a, b) => a.b - b.b);
  const bowPos = {}; bowOrder.forEach((o, p) => { bowPos[o.i] = p; });
  return (
    <div className="pcwrap">
      <div className="pchint">{curHeat
        ? (<>{curRound}{curHeat !== curRound ? " " + curHeat : ""}の実際のレース。行は<b>レーン番号順</b>に固定。左＝スタート→右＝ゴール。</>)
        : (<>{curRound}の全組を「一斉スタート」に見立てた再現。左＝スタート→右＝ゴール。<b>順位が変わると行も入れ替わります</b>。ゴール後は<b>進出先</b>（→準決・→A決勝など）を表示。</>)}</div>
      {!lockRound && rounds.length > 1 && (
        <div className="rrheats rrrounds">
          {rounds.map((rd) => <button key={rd} className={"rrhb" + (curRound === rd ? " on" : "")} onClick={() => { setRoundSel(rd); setHeatSel(null); setT(0); setPlaying(true); }}>{rd}</button>)}
        </div>
      )}
      {heats.length > 1 && (
        <div className="rrheats">
          <button className={"rrhb" + (curHeat === null ? " on" : "")} onClick={() => { setHeatSel(null); setT(0); setPlaying(true); }}>全組</button>
          {heats.map((h) => <button key={h} className={"rrhb" + (curHeat === h ? " on" : "")} onClick={() => { setHeatSel(h); setT(0); setPlaying(true); }}>{h}</button>)}
        </div>
      )}
      {!curHeat && heats.length > 1 && <div className="rrlegend">{heats.map((h) => <span className="rrlg" key={h}><i style={{ background: heatColor(h) }} />{h}</span>)}</div>}
      <div className="rrctrl">
        <button className="rrplay" onClick={() => { if (t >= maxT) setT(0); setPlaying((p) => !p); }}>{playing ? "⏸" : "▶"}</button>
        <input className="rrscrub" type="range" min="0" max={maxT || 1} step="0.05" value={t} onChange={(e) => { setPlaying(false); setT(+e.target.value); }} />
        <span className="rrtime">{karalSecToTime(t)}</span>
        {[1, 2, 4].map((s) => <button key={s} className={"rrspd" + (speed === s ? " on" : "")} onClick={() => setSpeed(s)}>{s}x</button>)}
      </div>
      <div className="rrtrack" style={{ height: 16 + crews.length * ROWH }}>
        <div className="rrgrid">
          {[0, 500, 1000, 1500, 2000].map((d) => <span className={"rrgl" + (d === 2000 ? " goal" : "") + (d === 0 ? " start" : "")} key={d} style={{ left: xPct(d) + "%" }}><i>{d === 2000 ? "GOAL" : d === 0 ? "START" : d}</i></span>)}
          <span className="rrlead" style={{ left: xPct(lead) + "%" }} />
        </div>
        {crews.map((c, i) => {
          const di = karalDistAt(c.wp, t); const x = xPct(di);
          const fin = t >= c.total;
          const pos = curHeat ? bowPos[i] : rankOf[i];
          const isLead = i === leadI;
          const gap = isLead ? 0 : (t - karalTimeAtDist(leaderWp, di));
          const col = colorOf(c, i);
          return (
            <div className="rrlane" key={i} style={{ top: 16 + pos * ROWH, zIndex: 100 - pos }}>
              <span className={"rrrank" + (isLead ? " top" : "")}>{curHeat ? (c.lane || "") : pos + 1}</span>
              <span className="rrnm" style={{ color: col }}>{c.name || c.team}</span>
              <span className="rrln">
                <span className={"rrboat" + (fin ? " fin" : "")} style={{ left: x + "%", color: col }}>
                  <svg className="rrhull" viewBox="0 0 48 18" aria-hidden="true">
                    <g opacity="0.85">
                      <g>
                        <animateTransform attributeName="transform" type="rotate" dur="0.9s" repeatCount="indefinite" calcMode="spline" keyTimes="0;0.5;1" keySplines="0.4 0 0.6 1;0.4 0 0.6 1" values="-19 17 7.3;19 17 7.3;-19 17 7.3" />
                        <line x1="17" y1="7.3" x2="17" y2="3" stroke="currentColor" strokeWidth="1" strokeLinecap="round" /><ellipse cx="17" cy="2.4" rx="1.3" ry="2" fill="currentColor" />
                      </g>
                      <g>
                        <animateTransform attributeName="transform" type="rotate" dur="0.9s" repeatCount="indefinite" calcMode="spline" keyTimes="0;0.5;1" keySplines="0.4 0 0.6 1;0.4 0 0.6 1" values="-19 29 7.3;19 29 7.3;-19 29 7.3" />
                        <line x1="29" y1="7.3" x2="29" y2="3" stroke="currentColor" strokeWidth="1" strokeLinecap="round" /><ellipse cx="29" cy="2.4" rx="1.3" ry="2" fill="currentColor" />
                      </g>
                      <g>
                        <animateTransform attributeName="transform" type="rotate" dur="0.9s" repeatCount="indefinite" calcMode="spline" keyTimes="0;0.5;1" keySplines="0.4 0 0.6 1;0.4 0 0.6 1" values="19 17 10.7;-19 17 10.7;19 17 10.7" />
                        <line x1="17" y1="10.7" x2="17" y2="15" stroke="currentColor" strokeWidth="1" strokeLinecap="round" /><ellipse cx="17" cy="15.6" rx="1.3" ry="2" fill="currentColor" />
                      </g>
                      <g>
                        <animateTransform attributeName="transform" type="rotate" dur="0.9s" repeatCount="indefinite" calcMode="spline" keyTimes="0;0.5;1" keySplines="0.4 0 0.6 1;0.4 0 0.6 1" values="19 29 10.7;-19 29 10.7;19 29 10.7" />
                        <line x1="29" y1="10.7" x2="29" y2="15" stroke="currentColor" strokeWidth="1" strokeLinecap="round" /><ellipse cx="29" cy="15.6" rx="1.3" ry="2" fill="currentColor" />
                      </g>
                    </g>
                    <path d="M3 9 C16 6.4 32 6.4 44 8.2 C44.9 8.45 44.9 9.55 44 9.8 C32 11.6 16 11.6 3 9 Z" fill="currentColor" />
                    <circle cx="17.5" cy="9" r="1.05" fill="#fff" opacity="0.5" /><circle cx="28.5" cy="9" r="1.05" fill="#fff" opacity="0.5" />
                  </svg>
                  {fin ? (
                    <i className="rrfintime">
                      {pos + 1}着 {karalSecToTime(c.total)}
                      {c.next && <em className={"rrq " + (c.next === "A決勝" ? "af" : c.next === "B決勝" ? "bf" : c.next === "準決" ? "sf" : "cr")}>→{c.next}</em>}
                    </i>
                  ) : (gap > 0.05 && <i className={"rrgap" + (di < 300 ? " ahead" : "")}>+{gap.toFixed(1)}</i>)}
                </span>
              </span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function RaceCardOverlay({ raceId, eventKey, ord, onClose, onOpenTeam, onOpenRace, onOpenEvent, favTeams }) {
  const RW = window.RW;
  const isEvent = !!eventKey;
  const r = !isEvent ? RW.FLAT.find((x) => x.id === raceId) : null;
  const ev = isEvent ? RW.evOf[eventKey] : (r ? r.ev : null);
  const [openResRound, setOpenResRound] = React.useState(null);
  const [openN, setOpenN] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const [resMode, setResMode] = React.useState("結果");
  const [selRound, setSelRound] = React.useState("A決勝"); // 種目モードのラウンドタブ（既定＝A決勝）
  // 種目カード（isEvent）を開いた時の初期表示：予選（最初のラウンド）のレース再現を自動スタート。個別レースは従来どおり結果。
  React.useEffect(() => {
    setOpenResRound(null); setOpenN(null);
    if (eventKey) {
      const evRounds = RW.FLAT.filter((x) => x.ev.key === eventKey).sort((a, b) => a.ord - b.ord);
      setSelRound(evRounds[0] ? (evRounds[0].isFinalA ? "A決勝" : evRounds[0].round) : "A決勝");
      setResMode("レース");
    } else {
      setSelRound("A決勝"); setResMode("結果");
    }
  }, [raceId, eventKey]);
  if (!ev) return null;
  const ros = (window.KANSAI.ROSTERS || {})[ev.key] || {};
  const st = r ? RW.statusOf(r, ord) : null;
  const finalPassed = ev.finalA && ord >= ev.finalA.endOrd;
  const podium = finalPassed && RW.realDecided(ev.key) ? RW.PODIUMS[ev.key] : null;
  const podiumWaiting = finalPassed && !RW.realDecided(ev.key);
  const multi = r ? (r.n || 1) > 1 : true;
  const openTeam = (team) => { onClose(); onOpenTeam(RW.baseTeam(team)); };
  const shareUrl = location.origin + location.pathname + (isEvent ? "#event=" + encodeURIComponent(ev.key) : "#race=" + r.id);
  const shareTitle = (isEvent ? ev.code + " " + ev.name : r.ev.code + " " + r.ev.name + " " + r.round) + "｜関西選手権2026";
  const onShare = () => {
    if (navigator.share) navigator.share({ title: shareTitle, url: shareUrl }).catch(() => {});
    else { navigator.clipboard.writeText(shareUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); }
  };
  const rounds = isEvent ? RW.FLAT.filter((x) => x.ev.key === ev.key).sort((a, b) => a.ord - b.ord) : null;
  const karalList = isEvent ? (RW.realRaces(ev.key) || []) : RW.realRacesFor(r);
  // 種目モードのヒートマップ／レース再現は、上のラウンドタブで選んだ段階（予選/準決/…）の全組だけに絞る
  const resGroups = groupKaralByRound(karalList);
  const awaitingResult = !isEvent && st === "done" && karalList.length === 0;
  const paceRows = karalPaceRows(karalList);
  const canViz = paceRows.length >= 2;
  const heatRows = isEvent ? paceRows.filter((pr) => pr.round === selRound) : paceRows;
  const effMode = canViz ? resMode : "結果"; // 可視化データが無い種目は既定がレースでも結果を出す

  // 前後ナビ：種目モード＝前後の種目 / レースモード＝前後のレース。遷移先の種目コード＋部門カラーも補足表示。
  let prevNav = null, nextNav = null, prevHint = null, nextHint = null, prevCat = null, nextCat = null, navLbl = isEvent ? "種目" : "レース";
  if (isEvent) {
    const i = RW.EVENTS.findIndex((e) => e.key === ev.key);
    const p = RW.EVENTS[i - 1], n = RW.EVENTS[i + 1];
    if (p) { prevNav = () => onOpenEvent(p.key); prevHint = p.code; prevCat = p.cat; }
    if (n) { nextNav = () => onOpenEvent(n.key); nextHint = n.code; nextCat = n.cat; }
  } else {
    const i = RW.FLAT.indexOf(r);
    const p = RW.FLAT[i - 1], n = RW.FLAT[i + 1];
    if (p) { prevNav = () => onOpenRace(p.id); prevHint = p.ev.code; prevCat = p.ev.cat; }
    if (n) { nextNav = () => onOpenRace(n.id); nextHint = n.ev.code; nextCat = n.ev.cat; }
  }

  return (
    <div className="rcwrap" onClick={onClose}>
      <div className={"rcard" + (isEvent ? " evmode" : "")} onClick={(e) => e.stopPropagation()}>
        <span className="lanes" />
        {copied && <span className="rc-share-toast">コピーしました</span>}
        <button type="button" className="rc-share" aria-label="共有" onClick={onShare}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M12 15V4" /><path d="M7 9l5-5 5 5" /><path d="M6 13v5a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-5" />
          </svg>
        </button>
        <button type="button" className="rc-x" aria-label="閉じる" onClick={onClose}>
          <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><path d="M6 6l12 12M18 6L6 18" /></svg>
        </button>
        <div className="rc-hd">
          <div className="rc-badges">
            <DivChip div={ev.div} />
            {ev.open && <span className="opchip">OPEN</span>}
            {r && r.isFinalA && <span className="finchip">A決勝</span>}
            {!isEvent && (st === "live" ? <span className="livetag">LIVE</span> : st === "done" ? <span className="rc-st done">終了</span> : <span className="rc-st">NEXT</span>)}
          </div>
          <div className={"rc-codeband" + (ev.div === "hamadera" ? " hm" : ev.div === "masters" ? " ms" : (ev.cat === "w" ? " w" : ""))}>
            <span className="rc-codebig">{evCodeLabel(ev)}</span>
            <span className="rc-name">{ev.name}</span>
          </div>
          {isEvent ? (
            <div className="rc-rounds">
              {/* ラウンドタブ：タップでそのラウンドのレース再現が即スタート（既定＝A決勝） */}
              {rounds.map((rd) => {
                const lbl = rd.isFinalA ? "A決勝" : rd.round;
                return (
                  <button type="button" key={rd.id} className={"rc-rnd" + (lbl === selRound ? " fin" : "")}
                          onClick={() => { setSelRound(lbl); setResMode("レース"); setOpenResRound(lbl); }}>
                    <b>{lbl}</b>
                    <i>{rd.dow} {rd.t}</i>
                  </button>
                );
              })}
            </div>
          ) : (
            <React.Fragment>
              <div className="rc-round">{r.round}{r.grp ? "・" + r.grp : ""}</div>
              <div className="rc-time">{r.dow} {r.t}{r.tEnd && r.tEnd !== r.t ? "–" + r.tEnd : ""}</div>
            </React.Fragment>
          )}
        </div>

        <div className="rc-scroll">
          {/* 種目モードではラウンドタブ＋結果一覧で完結するため、表彰台ブロックは出さない（レースモードのみ表示） */}
          {!isEvent && podium && (
            <div className="rc-blk">
              <div className="rc-sec">RESULT・A決勝</div>
              <div className="rc-podium"><PodiumRows podium={podium} onOpenTeam={openTeam} /></div>
            </div>
          )}

          {!isEvent && podiumWaiting && (
            <div className="rc-blk">
              <div className="rc-sec">RESULT・A決勝</div>
              <div className="rc-res-wait">結果待ち（速報反映まで数分かかることがあります）</div>
            </div>
          )}

          {resGroups.length > 0 && (
            <div className="rc-blk">
              <div className="rc-sec">結果</div>
              {canViz && (
                <div className="pcmode">
                  {["結果", "レース", "ヒートマップ"].map((m) => (
                    <button type="button" key={m} className={"pcmbtn" + (resMode === m ? " on" : "")} onClick={() => setResMode(m)}>
                      {m === "レース" ? "レース🏁" : m}
                    </button>
                  ))}
                </div>
              )}
              {effMode === "ヒートマップ" ? (heatRows.length ? <KaralPaceHeatmap rows={heatRows} key={"hm" + selRound} /> : <div className="rc-res-wait">{selRound}は計測データがありません</div>) : effMode === "レース" ? <KaralRaceReplay rows={paceRows} key={"rr" + selRound} initialRound={isEvent ? selRound : null} lockRound={isEvent} /> : (
                <React.Fragment>
                  <div className="rc-res-note">着順・クルー・通過（計測できた区間）・ゴール</div>
                  <div className="rc-res">
                    {/* 種目モード：上のラウンドタブで選んだラウンドだけを直接表示（予選/B決勝…の折りたたみ見出しは出さない） */}
                    {resGroups.filter((g) => !isEvent || g.round === selRound).map((g) => (
                      <div className="rc-res-rnd" key={g.round}>
                        <KaralLaneRows races={g.races} favTeams={favTeams} onOpenTeam={openTeam} cat={ev.cat} entries={ev.entries} ros={ros} />
                      </div>
                    ))}
                  </div>
                </React.Fragment>
              )}
            </div>
          )}

          {awaitingResult && (
            <div className="rc-blk">
              <div className="rc-res-wait">結果待ち（速報反映まで数分かかることがあります）</div>
            </div>
          )}

          {/* 組み合わせ（karal速報）がまだ来ていない種目だけ、エントリー一覧をフォールバック表示。
              速報が来た種目は上の「結果/組別」表示があるので二重表示にしない。 */}
          {resGroups.length === 0 && (
          <div className="rc-blk">
            <div className="rc-sec">{multi ? "エントリー全クルー（組分け・レーンは当日発表）" : "エントリー"}</div>
            <div className={"rc-list" + (ev.cat === "w" ? " w" : "")}>
              {ev.entries.map((en, i) => {
                const roster = ros[en.n] || [];
                const isFav = !!(favTeams && favTeams[RW.baseTeam(en.team)]);
                const rowCls = (i % 2 ? " alt" : "") + (isFav ? " fav" : "");
                // 高校（未成年）は個人名・プロフィール・編成を出さない（チーム名のみ）。
                const minor = RW.isMinorTeam && RW.isMinorTeam(en.team);
                if (ev.type === "scull") {
                  const nm = minor ? "" : ((roster[0] && roster[0][1]) || en.name);
                  const nmKana = roster[0] && roster[0][2];
                  return (
                    <div className={"rc-row single" + rowCls + (en.x ? " scratch" : "")} key={en.n}>
                      <span className="rc-n">{en.n}</span>
                      <span className="rc-team">{nm ? <RowerName name={nm} kana={nmKana} light /> : en.team}</span>
                      {en.x && <span className="rc-dnf">棄権</span>}
                      {isFav && !en.x && <Star size={11} color="var(--gold-2)" />}
                      {nm && <span className="rc-sub">{en.team}</span>}
                    </div>
                  );
                }
                const op = openN === en.n;
                const tappable = roster.length > 0 && !minor && !en.x;
                return (
                  <React.Fragment key={en.n}>
                    <div className={"rc-row" + (tappable ? " clk" : "") + rowCls + (en.x ? " scratch" : "")}
                         onClick={tappable ? () => setOpenN(op ? null : en.n) : undefined}>
                      <span className="rc-n">{en.n}</span>
                      <span className="rc-team">{en.name || en.team}</span>
                      {en.x && <span className="rc-dnf">棄権</span>}
                      {isFav && !en.x && <Star size={11} color="var(--gold-2)" />}
                      {en.name && <span className="rc-sub">{en.team}</span>}
                      {tappable && <IconChevron dir={op ? "up" : "down"} size={12} />}
                    </div>
                    {op && !en.x && (
                      <div className="rc-roster">
                        {roster.map((m, i) => (
                          <div className="rc-seat" key={i}>
                            <span className="rc-s">{m[0]}</span>
                            <span className="rc-nm"><RowerName name={m[1]} kana={m[2]} light hideKana /></span>
                            {m[2] && <span className="rc-k">{m[2]}</span>}
                          </div>
                        ))}
                        <button type="button" className="rc-teambtn" onClick={() => openTeam(en.team)}>チーム詳細 →</button>
                      </div>
                    )}
                  </React.Fragment>
                );
              })}
            </div>
          </div>
          )}

        </div>

        <div className="rc-nav">
          <button type="button" className={prevCat === "w" ? "w" : "m"} disabled={!prevNav} onClick={() => prevNav && prevNav()}>‹ 前の{navLbl}{prevHint && <em className="rc-navhint">{prevHint}</em>}</button>
          <button type="button" className={nextCat === "w" ? "w" : "m"} disabled={!nextNav} onClick={() => nextNav && nextNav()}>次の{navLbl}{nextHint && <em className="rc-navhint">{nextHint}</em>} ›</button>
        </div>
      </div>
    </div>
  );
}
// 予選の進出条件（プログレッションシステム）。主催者資料より。SF=準決勝。
// 表に無い種目（女子舵手つきフォア・男子舵手なしフォア・浜寺舵手つきフォア/クォド・女子ペアOPEN等）は
// 直接決勝や組数の都合で条件表示なし。
const KARAL_PROGRESSION = {
  "g-w1x": "1〜4着 → 準決勝",
  "g-m1x": "1〜4着 → 準決勝",
  "h-m1x": "1〜2着 → A決勝 ／ 3〜4着 → B決勝",
  "g-m2-": "1〜2着 → A決勝 ／ 3〜4着 → B決勝",
  "g-w2x": "1〜2着 → A決勝 ／ 3〜4着 → B決勝",
  "g-m2x": "1着＋2着のタイム上位2艇 → A決勝 ／ 2着タイム上位3位以下＋3着 → B決勝",
  "g-w4x": "1〜3着 → A決勝 ／ 4〜5着 → B決勝",
  "g-m4+": "1着＋2着のタイム上位2艇 → A決勝 ／ 2着タイム上位3位以下＋3着 → B決勝",
  "g-m4x": "1〜3着 → A決勝 ／ 4〜6着 → B決勝",
  "h-m8+": "1〜3着 → A決勝 ／ 4〜5着 → B決勝",
  "g-m8+": "1〜2着 → A決勝 ／ 3〜4着 → B決勝",
};

// 日程タブ用インライン展開：1レース(=予選など1ラウンド)を、その場でダークパネルに展開して表示する。
// 結果が来たら KaralLaneRows が組ごとに着順(1位順)へ自動整列。結果前は組み合わせ／エントリー一覧を表示。
function RaceInline({ r, favTeams, onOpenTeam }) {
  const RW = window.RW;
  const ev = r.ev;
  const ros = (window.KANSAI.ROSTERS || {})[ev.key] || {};
  const [resMode, setResMode] = React.useState("結果");
  const karalList = RW.realRacesFor(r);
  const resGroups = groupKaralByRound(karalList);
  const paceRows = karalPaceRows(karalList);
  const canViz = paceRows.length >= 2;
  const multi = (r.n || 1) > 1;
  const openTeam = onOpenTeam || (() => {});
  const prog = KARAL_PROGRESSION[ev.key];
  return (
    <div className="rc-inline">
      {prog && /予選/.test(r.round) && <div className="rc-prog"><b>進出条件</b>{prog}</div>}
      {resGroups.length > 0 ? (
        <React.Fragment>
          {canViz && (
            <div className="pcmode">
              {["結果", "レース", "ヒートマップ"].map((m) => (
                <button type="button" key={m} className={"pcmbtn" + (resMode === m ? " on" : "")} onClick={() => setResMode(m)}>
                  {m === "レース" ? "レース🏁" : m}
                </button>
              ))}
            </div>
          )}
          {resMode === "ヒートマップ" ? <KaralPaceHeatmap rows={paceRows} /> : resMode === "レース" ? <KaralRaceReplay rows={paceRows} /> : (
            <React.Fragment>
              <div className="rc-res-note">着順・クルー・通過（計測できた区間）・ゴール</div>
              <div className="rc-res">
                {resGroups.map((g) => (
                  <KaralLaneRows key={g.round} races={g.races} favTeams={favTeams} onOpenTeam={openTeam} cat={ev.cat} entries={ev.entries} ros={ros} />
                ))}
              </div>
            </React.Fragment>
          )}
        </React.Fragment>
      ) : (
        <React.Fragment>
          <div className="rc-res-note">{multi ? "エントリー全クルー（組分け・レーンは当日発表）" : "エントリー（着順は結果反映後に1位順で表示）"}</div>
          <div className={"rc-list" + (ev.cat === "w" ? " w" : "")}>
            {ev.entries.map((en) => {
              const roster = ros[en.n] || [];
              const minor = RW.isMinorTeam && RW.isMinorTeam(en.team);
              const nm = ev.type === "scull" ? (minor ? "" : ((roster[0] && roster[0][1]) || en.name)) : (en.name || null);
              const nmKana = roster[0] && roster[0][2];
              return (
                <div className={"rc-row" + (ev.type === "scull" ? " single" : "") + (en.x ? " scratch" : "")} key={en.n}>
                  <span className="rc-n">{en.n}</span>
                  <span className="rc-team">{nm ? <RowerName name={nm} kana={nmKana} light /> : en.team}</span>
                  {en.x && <span className="rc-dnf">棄権</span>}
                  {nm && <span className="rc-sub">{en.team}</span>}
                </div>
              );
            })}
          </div>
        </React.Fragment>
      )}
    </div>
  );
}
Object.assign(window, { RaceCardOverlay, RaceInline });
