/* results.jsx — 大会タブ（進出状況・予選ランキング・出場数・メダル・決勝結果） */
// チーム別 進出状況：公式結果の Qualify（→準決勝 / →順位決定 / →決勝）と着順から、
// 各クルーの「いま、どこまで進んだか」を1校ずつ数える。
// 1日目はメダルが決まらないので、この表が実質「今日の勢い」ランキングになる。
//
// この大会の進み方（全種目 24クルー固定・第37回の実施どおり）
//   予選 4組    … 上位3艇が準決へ／4位以下は敗者戦へ
//   敗者戦 2組  … 上位3艇が準決へ／4位以下はここで終わり
//   準決 3組    … 上位2艇がA決勝へ／3・4位はB決勝（順位決定・7〜12位）へ／5・6位はここで終わり
//   B決勝（順位決定）・A決勝（1〜6位）
// 準々決勝や、C決勝より下の順位別レースはこの大会には無い。

// 各バケット（現在地）の定義。round は発艇時刻を引くためのラウンド名。
const PROG_BUCKETS = [
  // A決勝の結果が出たクルーは着順で分ける
  { key: "gold", label: "金メダル", cls: "prog-gold", round: "A決勝" },
  { key: "silver", label: "銀メダル", cls: "prog-silver", round: "A決勝" },
  { key: "bronze", label: "銅メダル", cls: "prog-bronze", round: "A決勝" },
  { key: "fourth", label: "4位", cls: "prog-fourth", round: "A決勝" },
  { key: "prize", label: "入賞", cls: "prog-fourth", round: "A決勝" },   // A決勝の5位・6位
  // A決勝に進んだが、まだ漕いでいない（大会中だけ出るチップ）
  { key: "afinal", label: "決勝", cls: "prog-afinal", round: "A決勝" },
  { key: "bfinal", label: "順位決定", cls: "prog-bfinal", round: "B決勝" }, // 7〜12位
  { key: "semi", label: "準決", cls: "prog-semi", round: "準決" },
  { key: "rep", label: "敗者戦", cls: "prog-cr", round: "敗者戦" },
  // まだ初戦（予選）を漕いでいないクルー
  { key: "waiting", label: "予選前", cls: "prog-wait", round: "予選" },
  { key: "dns", label: "棄権・除外", cls: "pgt-out", round: null },
];

// 公式のラウンド表記 → サイトの表記（敗復→敗者戦・決勝→A決勝）
const roundLabelOf = (r) => r === "敗復" ? "敗者戦" : r === "決勝" ? "A決勝" : r;
const noSpace = (s) => String(s || "").replace(/[\s　]/g, "");
// 「→Ａ決勝」のように全角で来ることがあるので半角に直してから判定する
const toHan = (s) => String(s || "").replace(/[Ａ-Ｚａ-ｚ０-９]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xFEE0));
const timeToSec = (t) => { const m = String(t || "").match(/(\d+):([\d.]+)/); return m ? (+m[1]) * 60 + parseFloat(m[2]) : null; };
// そのラウンドで上に進める艇数。公式の「3上り」を使い、無ければこの大会の固定表で補う。
const advanceOf = (kr, round) => kr.advance || (round === "準決" ? 2 : 3);

// シングルスカルだけ、公式結果に載っている氏名を返す（ダブル・クォドは常に null）。
// 氏名は公式結果ページのレーン欄そのまま。config.js の showAthleteNames が false のときは
// engine.js 側ですでに消えているので、ここで判定する必要はない。
function scullerName(evKey, teamLabel) {
  const RW = window.RW;
  const ev = RW.evOf[evKey];
  if (!ev || !/1[×xX]/.test(ev.code)) return null;
  const races = RW.realRaces(evKey);
  if (!races) return null;
  const key = noSpace(teamLabel);
  for (const r of races) {
    for (const l of (r.lanes || [])) {
      if (l.name && noSpace(l.team) === key) return l.name;
    }
  }
  return null;
}

// 予選ランキング：種目ごとに「予選の全4組」をゴールタイム順に並べた一覧。
// 行き先は Qualify欄（→準決勝）を優先し、書かれていない行は着順と「◯上り」から補う。
function buildHeatRankings() {
  const RW = window.RW;
  const out = [];
  RW.EVENTS.forEach((ev) => {
    const races = RW.realRaces(ev.key);
    if (!races) return;
    const rows = [];
    races.forEach((r) => {
      const round = roundLabelOf(r.round);
      if (round !== "予選") return;
      const up = advanceOf(r, round);
      (r.lanes || []).forEach((l) => {
        const sec = timeToSec(l.t2000);
        if (sec == null || !l.team) return;
        const q = toHan(l.qualify);
        let dest = null;
        if (/準決/.test(q)) dest = "準決";
        else if (/敗復|敗者/.test(q)) dest = "敗者戦";
        else if (!/DNS|DNF|DSQ|棄権|失格/i.test(q) && l.rank != null) dest = l.rank <= up ? "準決" : "敗者戦";
        rows.push({ team: l.team, name: l.name || null, sec, t: l.t2000, grp: r.grp, rank: l.rank, dest });
      });
    });
    if (!rows.length) return;
    rows.sort((a, b) => a.sec - b.sec);
    out.push({ evKey: ev.key, code: ev.code, name: ev.name, cat: ev.cat, rows });
  });
  return out;
}

function buildProgressTable() {
  const RW = window.RW;
  const roundOf = {}; PROG_BUCKETS.forEach((b) => { roundOf[b.key] = b.round; });

  // 予選全体のタイム順位（ドリルに「予選全体 24クルー中3位」と出すため）。
  // 種目の予選4組をまとめてゴールタイムで並べる。タイムの無い艇（棄権など）は分母に入れない。
  const heatRankOf = {}; // evKey -> { 学校名: {r, n} }
  RW.EVENTS.forEach((ev) => {
    const races = RW.realRaces(ev.key);
    if (!races) return;
    const rows = [];
    races.forEach((kr) => {
      if (roundLabelOf(kr.round) !== "予選") return;
      (kr.lanes || []).forEach((l) => {
        const sec = timeToSec(l.t2000);
        if (sec != null && l.team) rows.push({ k: noSpace(l.team), sec });
      });
    });
    rows.sort((a, b) => a.sec - b.sec);
    const m = {};
    rows.forEach((x, i) => { if (!(x.k in m)) m[x.k] = { r: i + 1, n: rows.length }; });
    heatRankOf[ev.key] = m;
  });

  // クルー単位で到達状況をフラグにまとめる。
  // ・そのラウンドの発艇表（レーン）に載っている＝そこへ到達（着順がまだでも到達扱い）。
  // ・Qualify欄の矢印は「次にどこへ行くか」。まだ次の発艇表が出ていない間はこちらで拾う。
  // ・棄権・途中棄権・失格は進出の根拠にしない。
  const crews = {};
  RW.EVENTS.forEach((ev) => {
    const races = RW.realRaces(ev.key);
    if (!races) return;
    races.forEach((kr) => {
      const round = roundLabelOf(kr.round);
      const up = advanceOf(kr, round);
      (kr.lanes || []).forEach((l) => {
        const team = l.team;
        if (!team) return;
        const key = ev.key + "|" + noSpace(team);
        if (!crews[key]) crews[key] = { evKey: ev.key, evName: ev.name, teamLabel: team,
          inA: false, inB: false, inSemi: false, inRep: false, aRank: null,
          qA: false, qB: false, qS: false, qR: false, dns: false };
        const c = crews[key];
        const q = toHan(l.qualify);
        const isOut = /DNS|DNF|DSQ|棄権|失格|除外/i.test(q + toHan(l.status));
        if (isOut) c.dns = true;
        if (round === "A決勝") { c.inA = true; if (l.rank != null) c.aRank = l.rank; }
        else if (round === "B決勝") c.inB = true;
        else if (round === "準決") c.inSemi = true;
        else if (round === "敗者戦") c.inRep = true;
        if (isOut) return;
        // 「→準決勝」にも"決勝"の2文字が入るので、A決勝→B決勝→準決→敗者戦 の順に見る。
        if (/A決勝/.test(q)) c.qA = true;
        else if (/B決勝|順位決定/.test(q)) c.qB = true;
        else if (/準決/.test(q)) c.qS = true;
        else if (/敗復|敗者/.test(q)) c.qR = true;
        else if (l.rank != null) {
          // Qualify欄が空の行は、この大会の固定の進み方で補う。
          if (round === "予選") { if (l.rank <= up) c.qS = true; else c.qR = true; }
          else if (round === "敗者戦") { if (l.rank <= up) c.qS = true; }
          else if (round === "準決") { if (l.rank <= up) c.qA = true; else if (l.rank <= up + 2) c.qB = true; }
        }
      });
    });
  });

  const teams = {};
  Object.keys(crews).forEach((key) => {
    const c = crews[key];
    // 上（A決勝の着順）から順に当てはめる。当てはまった時点でそれがそのクルーの現在地。
    let bucket;
    if (c.aRank != null) bucket = c.aRank === 1 ? "gold" : c.aRank === 2 ? "silver" : c.aRank === 3 ? "bronze" : c.aRank === 4 ? "fourth" : "prize";
    else if (c.inA || c.qA) bucket = "afinal";
    else if (c.inB || c.qB) bucket = "bfinal";
    else if (c.inSemi || c.qS) bucket = "semi";
    else if (c.inRep || c.qR) bucket = "rep";
    else if (c.dns) bucket = "dns";
    else bucket = "waiting";
    const base = RW.baseTeam(c.teamLabel);
    if (!teams[base]) { teams[base] = { team: base, total: 0, crews: {} };
      PROG_BUCKETS.forEach((b) => { teams[base][b.key] = 0; teams[base].crews[b.key] = []; }); }
    teams[base][bucket] += 1; teams[base].total += 1;
    const dr = roundOf[bucket];
    const race = dr ? RW.FLAT.find((r) => r.evKey === c.evKey && r.round === dr) : null;
    const suffix = (c.teamLabel && c.teamLabel !== base) ? c.teamLabel.slice(base.length).replace(/^[\s　]+/, "") : "";
    const hr = (heatRankOf[c.evKey] || {})[noSpace(c.teamLabel)];
    const rankText = hr ? "予選全体 " + hr.n + "クルー中" + hr.r + "位" : null;
    teams[base].crews[bucket].push({ evKey: c.evKey, evName: c.evName, teamLabel: c.teamLabel, suffix,
      name: scullerName(c.evKey, c.teamLabel), time: race ? race.t : "", bucket, rankText });
  });
  // 各バケット内は発艇時刻順（時刻なしは後ろ）。"9:50"と"11:02"は桁数が違うので分に直して比べる。
  const tmin = (s) => { if (!s) return 99999; const p = String(s).split(":"); return (+p[0]) * 60 + (+p[1] || 0); };
  Object.values(teams).forEach((t) => {
    Object.keys(t.crews).forEach((bk) => {
      t.crews[bk].sort((a, b) => tmin(a.time) - tmin(b.time) || a.evName.localeCompare(b.evName, "ja"));
    });
    // 学校名タップ用：全バケットのクルーをまとめて発艇時刻順に並べた一覧。
    t.crews.all = PROG_BUCKETS.flatMap((b) => t.crews[b.key]).sort((a, b) => tmin(a.time) - tmin(b.time) || a.evName.localeCompare(b.evName, "ja"));
  });
  return Object.values(teams).sort((a, b) =>
    b.gold - a.gold || b.silver - a.silver || b.bronze - a.bronze || b.fourth - a.fourth || b.prize - a.prize ||
    b.afinal - a.afinal || b.bfinal - a.bfinal || b.semi - a.semi || b.rep - a.rep ||
    b.total - a.total || a.team.localeCompare(b.team, "ja"));
}

// ある学校が、確定した決勝で取ったメダルの内訳 [{evName, rank}]（金→銀→銅の順）。
function teamMedals(teamBase, div, ord) {
  const RW = window.RW;
  const out = [];
  RW.EVENTS.forEach((ev) => {
    if (div && ev.div !== div) return;
    if (!ev.finalA || ord < ev.finalA.endOrd || !RW.realDecided(ev.key)) return;
    (RW.PODIUMS[ev.key] || []).forEach((p) => {
      if (p.team === teamBase) out.push({ evKey: ev.key, evName: ev.name, rank: p.rank, rawTeam: p.rawTeam || p.label });
    });
  });
  return out.sort((a, b) => a.rank - b.rank || a.evName.localeCompare(b.evName, "ja"));
}

// ---- 都道府県別 ---------------------------------------------------------
// 学校 → 都道府県は data.js の PREF（クルー登録の学校コードから引いたもの）。
// 引けない学校は「その他」に入れる（第37回では0校だが、名簿が変わっても落ちないように用意）。
const PREF_OTHER = "その他";
// 公式結果のレーンの team は「氏名（学校名）」の形で来ることがある（シングルスカル）。
// 学校名だけを取り出してから baseTeam でクルーの枝番（A/B）や後ろの添え名を落とす。
const prefSchoolOf = (rawTeam) => {
  const s = String(rawTeam || "").trim();
  const m = s.match(/[（(]([^）)]+)[）)][\s　]*$/);
  return window.RW.baseTeam(m ? m[1] : s);
};
const prefOfTeam = (base) => ((window.KANSAI && window.KANSAI.PREF) || {})[base] || PREF_OTHER;

// 都道府県ごとのまとめ。エントリー（学校数・クルー数）＋公式結果（A決勝・B決勝）から描画時に作る。
// ・金/銀/銅  … A決勝（内部名）のレーンの着順1・2・3
// ・決勝進出  … A決勝に並んだ艇数／順位決定 … B決勝に並んだ艇数
// 並びは 金→銀→銅→決勝進出→順位決定→クルー数。結果がまだ無い間は 学校数→クルー数 の順。
function buildPrefTable() {
  const RW = window.RW;
  const map = {};
  const getPref = (p) => {
    if (!map[p]) map[p] = { pref: p, g: 0, s: 0, b: 0, afinal: 0, bfinal: 0, crews: 0, schoolMap: {} };
    return map[p];
  };
  const getSchool = (base) => {
    const p = getPref(prefOfTeam(base));
    if (!p.schoolMap[base]) p.schoolMap[base] = { team: base, g: 0, s: 0, b: 0, afinal: 0, bfinal: 0, crews: 0 };
    return { p, s: p.schoolMap[base] };
  };
  // ① エントリー（結果が無くても出る）
  RW.EVENTS.forEach((ev) => {
    (ev.entries || []).forEach((en) => {
      const { p, s } = getSchool(RW.baseTeam(en.team));
      s.crews += 1; p.crews += 1;
    });
  });
  // ② 公式結果（A決勝＝金銀銅と決勝進出／B決勝＝順位決定）
  RW.EVENTS.forEach((ev) => {
    const races = RW.realRaces(ev.key);
    if (!races) return;
    races.forEach((kr) => {
      const round = roundLabelOf(kr.round);   // 内部名で判定（表示名の「決勝」では判定しない）
      if (round !== "A決勝" && round !== "B決勝") return;
      (kr.lanes || []).forEach((l) => {
        if (!l.team) return;
        const { p, s } = getSchool(prefSchoolOf(l.team));
        if (round === "B決勝") { s.bfinal += 1; p.bfinal += 1; return; }
        s.afinal += 1; p.afinal += 1;
        if (l.rank === 1) { s.g += 1; p.g += 1; }
        else if (l.rank === 2) { s.s += 1; p.s += 1; }
        else if (l.rank === 3) { s.b += 1; p.b += 1; }
      });
    });
  });
  const rows = Object.values(map);
  const hasResult = rows.some((r) => r.afinal > 0 || r.bfinal > 0);
  // 結果順（金→銀→銅→決勝進出→順位決定→クルー数）。大会前は学校数→クルー数。
  const byResult = (a, b) => b.g - a.g || b.s - a.s || b.b - a.b || b.afinal - a.afinal || b.bfinal - a.bfinal || b.crews - a.crews;
  rows.forEach((r) => {
    r.schools = Object.values(r.schoolMap).sort((a, b) => byResult(a, b) || a.team.localeCompare(b.team, "ja"));
    delete r.schoolMap;
  });
  return rows.sort((a, b) => {
    if (a.pref === PREF_OTHER || b.pref === PREF_OTHER) return a.pref === PREF_OTHER ? 1 : -1;  // 「その他」は最後
    return (hasResult ? byResult(a, b) : 0) || b.schools.length - a.schools.length || b.crews - a.crews
      || a.pref.localeCompare(b.pref, "ja");
  });
}
// 同順位（「=」を出すか）の判定。並べ替えに使ったキーが前の行と全部同じなら同順位。
const prefTie = (a, b, hasResult) => !!a && !!b && (!hasResult || (a.g === b.g && a.s === b.s && a.b === b.b && a.afinal === b.afinal && a.bfinal === b.bfinal))
  && a.crews === b.crews && a.schools.length === b.schools.length;

function ResultsView({ ord, phase, onOpenTeam, goTab }) {
  const RW = window.RW;
  // 「時間が過ぎた」だけでは確定扱いにしない。公式結果が入った決勝だけ「決定済み」。
  const decided = RW.FLAT.filter((r) => r.isFinalA && ord >= r.endOrd && RW.realDecided(r.ev.key));
  const awaitingFinals = RW.FLAT.filter((r) => r.isFinalA && ord >= r.endOrd && !RW.realDecided(r.ev.key));
  // メダルが出てからは「メダルランキング」を既定表示にする（並び順は固定）。
  const hasMedals = decided.length > 0;
  const tabOrder = ["prog", "heatrank", "entries", "medals", "prefs", "finals"];
  const TAB_LABEL = { prog: "チーム別 進出状況", heatrank: "予選ランキング", entries: "学校別 出場数", medals: "メダルランキング", prefs: "都道府県別", finals: "決勝結果" };
  const [tab, setTab] = React.useState(hasMedals ? "medals" : "prog");
  const [entryMode, setEntryMode] = React.useState("heads"); // "heads"=人数(延べ) / "crews"=クルー数
  const [openBucket, setOpenBucket] = React.useState(null);  // "学校|バケット"
  const [medalMode, setMedalMode] = React.useState("count"); // "count" | "people"
  const [openMedalTeam, setOpenMedalTeam] = React.useState(null); // "区分|学校"（メダル内訳の展開）
  const [qrEv, setQrEv] = React.useState("all");             // 予選ランキングの種目絞り込み
  const [openPref, setOpenPref] = React.useState(null);      // 都道府県別で開いている県
  const progRows = RW.hasReal() ? buildProgressTable() : [];
  const heatRanks = RW.hasReal() ? buildHeatRankings() : [];
  const toggleBucket = (team, bk) => {
    const k = team + "|" + bk;
    setOpenBucket((prev) => (prev === k ? null : k));
  };
  const SRC = "出典：静岡県ローイング協会 公式結果（rowing-szo.com/senbatsu/37/result.htm）";

  return (
    <section className="view">
      <div className="shead"><span className="tag"><span>REGATTA</span></span><span className="jp">大会データ・結果</span><span className="sp" /><span className="meta">{phase === "before" ? (RW.TEAM_NAMES.length + "校") : (decided.length + "/" + RW.COUNTS.finals + " 決勝")}</span></div>
      <div className="chips">
        {tabOrder.map((k) => (
          <button key={k} className={"chip" + (tab === k ? " on" : "")} onClick={() => setTab(k)}>{TAB_LABEL[k]}</button>
        ))}
      </div>

      {tab === "entries" && (() => {
        // 学校ごとの出場数。上のトグルで「人数／クルー数」を切り替える。
        // 人数は艇の定員×クルー数（延べ）。1人が複数種目に乗るため、実人数より多くなる。
        const all = RW.TEAM_NAMES.map((team) => {
          const g = { m: { heads: 0, crews: 0 }, w: { heads: 0, crews: 0 } };
          (RW.TEAMS[team].crews || []).forEach((c) => {
            const ev = RW.evOf[c.evKey]; if (!ev) return;
            const k = ev.cat === "w" ? "w" : "m";
            g[k].heads += RW.seatsOf(ev); g[k].crews += 1;
          });
          return { team, g, heads: g.m.heads + g.w.heads, crews: g.m.crews + g.w.crews };
        });
        const M = entryMode;                                   // "heads" | "crews"
        const unit = M === "heads" ? "名 延べ" : "クルー";
        const value = (r) => r[M];
        const rows = all.filter((r) => value(r) > 0)
          .sort((a, b) => value(b) - value(a) || a.team.localeCompare(b.team, "ja"));
        const max = Math.max(1, ...rows.map(value));
        return (
          <div>
            <div className="mtoggle">
              <button type="button" className={M === "heads" ? "on" : ""} onClick={() => setEntryMode("heads")}>人数順</button>
              <button type="button" className={M === "crews" ? "on" : ""} onClick={() => setEntryMode("crews")}>クルー数順</button>
            </div>
            <p className="pgt-hint" style={{ margin: "10px 0 6px" }}>学校名を押すと、その学校のページが開きます。</p>
            <div className="entlegend">
              <span className="entlg"><i className="entsw gm" />男子</span>
              <span className="entlg"><i className="entsw gw" />女子</span>
            </div>
            <div className="mbt-sec">
              <div className="mbt-chart">
                {rows.map((r, i) => {
                  const v = value(r);
                  const tie = i > 0 && v === value(rows[i - 1]);
                  const pm = r.g.m[M], pw = r.g.w[M];
                  return (
                    <div className="mbt-item" key={r.team}>
                      <div className="mbt-row entval-row" style={{ cursor: "pointer" }} onClick={() => onOpenTeam(r.team)}>
                        <span className="mbt-rk">{tie ? "=" : (i + 1)}</span>
                        <span className="mbt-tm">{r.team}</span>
                        <span className="mbt-barwrap">
                          <span className="mbt-bar">
                            <span className="mbt-fill" style={{ width: (v / max * 100) + "%" }}>
                              {pm > 0 && <span className="mbt-seg gm" style={{ width: (pm / v * 100) + "%" }} />}
                              {pw > 0 && <span className="mbt-seg gw" style={{ width: (pw / v * 100) + "%" }} />}
                            </span>
                          </span>
                          {/* 数の列を縦に揃える（合計＋男女の固定幅セル） */}
                          <span className="mbt-val entval gen">
                            <span className="entcell tot"><b>{v}</b><span className="entu">{unit}</span></span>
                            {["m", "w"].map((k) => (
                              <span className="entcell" key={k}>
                                {r.g[k][M] > 0 ? <><i className={"entsw g" + k} />{r.g[k][M]}</> : <span className="zero">–</span>}
                              </span>
                            ))}
                          </span>
                        </span>
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
            <p className="evnote" style={{ margin: "14px 4px 0", fontSize: 11, lineHeight: 1.9, color: "var(--mut)" }}>
              「名 延べ」は<b>艇の定員×クルー数</b>。1人が複数の種目に乗ることがあるので、実際の人数より多くなります。　{SRC}
            </p>
          </div>
        );
      })()}

      {tab === "heatrank" && (heatRanks.length ? (
        <div>
          <div className="pgt-hint">種目ごとに、<b>予選4組の全クルーをゴールタイムで並べた順位</b>です（組の中の着順ではなく、タイムの順）。緑は準決勝へ進んだクルー。行を押すと、その学校のページが開きます。</div>
          <div className="chips entscope">
            <button type="button" className={"chip" + (qrEv === "all" ? " on" : "")} onClick={() => setQrEv("all")}>全種目</button>
            {heatRanks.map((e) => (
              <button type="button" key={e.evKey} className={"chip" + (qrEv === e.evKey ? " on" : "")} onClick={() => setQrEv(e.evKey)}>{e.code}</button>
            ))}
          </div>
          {heatRanks.filter((e) => qrEv === "all" || e.evKey === qrEv).map((e) => (
            <div className="qr-sec" key={e.evKey}>
              <div className="qr-hd"><span className={"qr-code" + (e.cat === "w" ? " w" : "")}>{e.code}</span><b>{e.name}</b><span className="qr-n">{e.rows.length}クルー</span></div>
              <div className="qr-list">
                {e.rows.map((r, i) => (
                  <div className="qr-row" key={i} onClick={() => onOpenTeam(RW.baseTeam(r.team))}>
                    <span className="qr-rk">{i + 1}</span>
                    <span className="qr-nm">{r.name || r.team}{r.name && <i>{r.team}</i>}</span>
                    <span className="qr-meta">{r.grp || ""}{r.rank ? "・" + r.rank + "着" : ""}</span>
                    <span className="qr-t">{r.t}</span>
                    {r.dest && <span className={"qr-dest" + (r.dest === "敗者戦" ? " low" : "")}>→{r.dest}</span>}
                  </div>
                ))}
              </div>
            </div>
          ))}
          <p className="navsrc" style={{ marginTop: 6 }}>{SRC}</p>
        </div>
      ) : <div className="empty">予選ランキングは、予選の結果が入り次第ここに表示されます。</div>)}

      {tab === "prog" && (progRows.length ? (
        <div>
          <div className="pgt-hint">
            各校のクルーが<b>いまどこまで進んだか</b>を数えたものです。<b>学校名を押す</b>と出場している種目の一覧、<b>チップを押す</b>とその行き先のクルーだけが並びます。
            「入賞」は決勝の5位・6位、「順位決定」は7〜12位を決めるレースです。
          </div>
          <div className="pgt">
            {progRows.map((t, i) => {
              const allKey = t.team + "|all";
              const allOpen = openBucket === allKey;
              return (
                <div className="pgt-item" key={t.team}>
                  <div className="pgt-row">
                    <span className="pgt-rk">{i + 1}</span>
                    <span className={"pgt-tm" + (allOpen ? " on" : "")} onClick={() => toggleBucket(t.team, "all")}>{t.team}<i>{t.total}クルー</i></span>
                    <span className="pgt-chips">
                      {PROG_BUCKETS.map((b) => t[b.key] > 0 ? (
                        <button type="button" key={b.key}
                          className={"progchip " + b.cls + (openBucket === t.team + "|" + b.key ? " on" : "")}
                          onClick={() => toggleBucket(t.team, b.key)}>{b.label} {t[b.key]}</button>
                      ) : null)}
                    </span>
                  </div>
                  {allOpen && (
                    <div className="pgt-drill">
                      {t.crews.all.map((c, ci) => {
                        const bDef = PROG_BUCKETS.find((b) => b.key === c.bucket);
                        return (
                          <div className="pgt-crew" key={ci}>
                            <div className="pgt-crewhd" style={{ cursor: "default" }}>
                              {bDef && <span className={"pgt-btag " + bDef.cls}>{bDef.label}</span>}
                              <span className="pgt-cev">{c.evName}{(c.name || c.suffix) ? <i>{c.name || c.suffix}</i> : null}</span>
                              {c.rankText ? <span className="pgt-crank">{c.rankText}</span> : (c.time ? <span className="pgt-ctime">{c.time}</span> : null)}
                            </div>
                          </div>
                        );
                      })}
                      <button type="button" className="pgt-detlink" onClick={() => onOpenTeam(t.team)}>この学校の詳細を見る ›</button>
                    </div>
                  )}
                  {PROG_BUCKETS.map((b) => (openBucket === t.team + "|" + b.key && t.crews[b.key].length) ? (
                    <div className="pgt-drill" key={b.key}>
                      {t.crews[b.key].map((c, ci) => (
                        <div className="pgt-crew" key={ci}>
                          <div className="pgt-crewhd" style={{ cursor: "default" }}>
                            <span className="pgt-cev">{c.evName}{(c.name || c.suffix) ? <i>{c.name || c.suffix}</i> : null}</span>
                            {c.rankText ? <span className="pgt-crank">{c.rankText}</span> : (c.time ? <span className="pgt-ctime">{c.time}</span> : null)}
                          </div>
                        </div>
                      ))}
                    </div>
                  ) : null)}
                </div>
              );
            })}
          </div>
          <p className="navsrc" style={{ marginTop: 8 }}>{SRC}</p>
        </div>
      ) : <div className="empty">進出状況は、予選の結果が入り次第ここに表示されます。</div>)}

      {tab === "medals" && (() => {
        // 学校別のメダル数を横棒グラフで。区分は data.js の DIV から自動生成（この大会は1区分）。
        const DIVS = Object.keys(RW.DIV)
          .sort((a, b) => RW.DIV[a].order - RW.DIV[b].order)
          .map((k) => ({ key: k, label: RW.DIV[k].label, tag: RW.DIV[k].tag || "" }));
        const secs = DIVS.map((d) => ({ d, rows: RW.medalTable(ord, d.key) })).filter((s) => s.rows.length);
        if (!secs.length) return <div className="empty">まだメダルは決まっていません。</div>;
        const pick = (t) => medalMode === "people" ? [t.gp, t.sp, t.bp] : [t.g, t.s, t.b];
        return (
          <div>
            <div className="mtoggle">
              <button type="button" className={medalMode === "count" ? "on" : ""} onClick={() => setMedalMode("count")}>メダル数</button>
              <button type="button" className={medalMode === "people" ? "on" : ""} onClick={() => setMedalMode("people")}>人数</button>
            </div>
            <div className="mbt-all">
              {secs.map(({ d, rows }) => {
                const keyed = rows.map((t) => { const [g, s, b] = pick(t); return { team: t.team, g, s, b, tot: g + s + b }; })
                  .sort((a, b) => b.g - a.g || b.s - a.s || b.b - a.b || b.tot - a.tot || a.team.localeCompare(b.team, "ja"));
                const max = Math.max(1, ...keyed.map((t) => t.tot));
                return (
                  <div className="mbt-sec" key={d.key}>
                    <div className="mbt-divhd">
                      {DIVS.length > 1 && <span className={"mbt-divtag" + (d.tag ? " " + d.tag : "")}>{d.label}</span>}
                      <span className="n">{keyed.length}校</span>
                    </div>
                    <div className="mbt-chart">
                      {keyed.map((t, i) => {
                        const tie = i > 0 && t.g === keyed[i - 1].g && t.s === keyed[i - 1].s && t.b === keyed[i - 1].b;
                        const mkey = d.key + "|" + t.team;
                        const open = openMedalTeam === mkey;
                        const medals = open ? teamMedals(t.team, d.key, ord) : [];
                        return (
                          <div className={"mbt-item" + (open ? " open" : "")} key={t.team}>
                            <div className="mbt-row" onClick={() => setOpenMedalTeam((prev) => (prev === mkey ? null : mkey))}>
                              <span className="mbt-rk">{tie ? "=" : (i + 1)}</span>
                              <span className="mbt-tm">{t.team}</span>
                              <span className="mbt-barwrap">
                                <span className="mbt-bar">
                                  {/* 色帯全体を mbt-fill で包み、1本のシャインが金→銀→銅の順に走り抜ける */}
                                  <span className="mbt-fill" style={{ width: (t.tot / max * 100) + "%" }}>
                                    {t.g > 0 && <span className="mbt-seg g" style={{ width: (t.g / t.tot * 100) + "%" }} />}
                                    {t.s > 0 && <span className="mbt-seg s" style={{ width: (t.s / t.tot * 100) + "%" }} />}
                                    {t.b > 0 && <span className="mbt-seg b" style={{ width: (t.b / t.tot * 100) + "%" }} />}
                                  </span>
                                </span>
                                <span className="mbt-val">
                                  {t.g > 0 && <span className="mc g"><i />{t.g}</span>}
                                  {t.s > 0 && <span className="mc s"><i />{t.s}</span>}
                                  {t.b > 0 && <span className="mc b"><i />{t.b}</span>}
                                </span>
                              </span>
                            </div>
                            {open && (
                              <div className="mbt-detail">
                                {medals.length ? medals.map((m, mi) => {
                                  const nm = scullerName(m.evKey, m.rawTeam);
                                  return (
                                    <div className="mbt-medal-wrap" key={mi}>
                                      <div className="mbt-medal" style={{ cursor: "default" }}>
                                        <span className={"mbt-md " + (m.rank === 1 ? "g" : m.rank === 2 ? "s" : "b")} />
                                        <span className="mbt-mev">{m.evName}{nm ? "　" + nm : ""}</span>
                                        <span className="mbt-mrk">{m.rank === 1 ? "金" : m.rank === 2 ? "銀" : "銅"}</span>
                                      </div>
                                    </div>
                                  );
                                }) : <div className="mbt-medal" style={{ cursor: "default" }}><span className="mbt-mev" style={{ color: "var(--mut)" }}>内訳は準備中です</span></div>}
                                <button type="button" className="mbt-detlink" onClick={(e) => { e.stopPropagation(); onOpenTeam(t.team); }}>この学校の詳細を見る ›</button>
                              </div>
                            )}
                          </div>
                        );
                      })}
                    </div>
                  </div>
                );
              })}
            </div>
            <p className="navsrc" style={{ marginTop: 6 }}>{medalMode === "people" ? "人数＝金・銀・銅を取ったクルーの乗艇人数（舵手を含む）。" : "金・銀・銅のメダル数。"}バーはいちばん多い学校を基準にした長さです。　{SRC}</p>
          </div>
        );
      })()}

      {tab === "prefs" && (() => {
        // 都道府県別。エントリー（出場校・クルー数）は大会前から出る。
        // 結果が入ると 金→銀→銅→決勝進出→順位決定→クルー数 の順に並び替わる。
        const rows = buildPrefTable();
        if (!rows.length) return <div className="empty">エントリーはまだ入っていません。</div>;
        const hasResult = rows.some((r) => r.afinal > 0 || r.bfinal > 0);
        const hasMedal = rows.some((r) => r.g + r.s + r.b > 0);
        // バーの長さの基準：メダル数（金銀銅の合計）→（まだ0なら）決勝進出数 →（大会前は）出場校数。
        // メダルがある間はメダルランキングと同じ「金→銀→銅の積み上げ」にする。
        // 金が無い県のバーが空になってしまうのを避けるため（愛媛＝銀1銅2 など）。
        const barKey = hasMedal ? "med" : (rows.some((r) => r.afinal > 0) ? "afinal"
          : (rows.some((r) => r.bfinal > 0) ? "bfinal" : "n"));
        const barOf = (r) => barKey === "med" ? (r.g + r.s + r.b) : (barKey === "n" ? r.schools.length : r[barKey]);
        const max = Math.max(1, ...rows.map(barOf));
        const nSchools = rows.reduce((s, r) => s + r.schools.length, 0);
        const nCrews = rows.reduce((s, r) => s + r.crews, 0);
        // 学校ごとの結果チップは、進出状況タブと同じ集計を使い回す
        const progBy = {}; progRows.forEach((t) => { progBy[t.team] = t; });
        return (
          <div>
            <div className="pgt-hint">
              出場校を<b>都道府県ごと</b>にまとめたものです。<b>県名を押す</b>とその県の学校が開き、<b>学校名を押す</b>とその学校のページが開きます。
            </div>
            <div className="mbt-sec">
              <div className="mbt-divhd"><span className="n">{rows.length}都道府県・{nSchools}校・{nCrews}クルー</span></div>
              <div className="mbt-chart">
                {rows.map((r, i) => {
                  const tie = i > 0 && prefTie(rows[i - 1], r, hasResult);
                  const open = openPref === r.pref;
                  return (
                    <div className={"mbt-item" + (open ? " open" : "")} key={r.pref}>
                      <div className="mbt-row" onClick={() => setOpenPref((prev) => (prev === r.pref ? null : r.pref))}>
                        <span className="mbt-rk">{tie ? "=" : (i + 1)}</span>
                        {/* 大会前は右の欄にクルー数を出すので、ここは校数だけにして重複を避ける */}
                        <span className="mbt-tm pf-tm">{r.pref}<i>{r.schools.length}校{hasResult ? "・" + r.crews + "クルー" : ""}</i></span>
                        <span className="mbt-barwrap">
                          <span className="mbt-bar">
                            <span className="mbt-fill" style={{ width: (barOf(r) / max * 100) + "%" }}>
                              {barKey === "med" ? (() => { const tot = r.g + r.s + r.b; return (
                                <React.Fragment>
                                  {r.g > 0 && <span className="mbt-seg g" style={{ width: (r.g / tot * 100) + "%" }} />}
                                  {r.s > 0 && <span className="mbt-seg s" style={{ width: (r.s / tot * 100) + "%" }} />}
                                  {r.b > 0 && <span className="mbt-seg b" style={{ width: (r.b / tot * 100) + "%" }} />}
                                </React.Fragment>); })()
                                : <span className="mbt-seg pf-seg" style={{ width: "100%" }} />}
                            </span>
                          </span>
                          <span className="mbt-val">
                            {hasResult ? (
                              (r.g + r.s + r.b) > 0 ? (
                                <>
                                  {r.g > 0 && <span className="mc g"><i />{r.g}</span>}
                                  {r.s > 0 && <span className="mc s"><i />{r.s}</span>}
                                  {r.b > 0 && <span className="mc b"><i />{r.b}</span>}
                                </>
                              ) : <span className="pf-fin">{r.afinal > 0 ? "決勝 " + r.afinal : (r.bfinal > 0 ? "順位決定 " + r.bfinal : "")}</span>
                            ) : <span className="pf-fin">{r.crews}クルー</span>}
                          </span>
                        </span>
                      </div>
                      {open && (
                        <div className="mbt-detail pf-detail">
                          {hasResult && (
                            <div className="pf-sum">
                              決勝進出 <b>{r.afinal}</b>クルー　順位決定 <b>{r.bfinal}</b>クルー
                            </div>
                          )}
                          {r.schools.map((s) => {
                            const pt = progBy[s.team];
                            return (
                              <div className="pf-school" key={s.team}>
                                <button type="button" className="pf-name"
                                  onClick={(e) => { e.stopPropagation(); onOpenTeam(s.team); }}>
                                  {s.team}<i>{s.crews}クルー</i>
                                </button>
                                {pt && (
                                  <span className="pf-chips">
                                    {PROG_BUCKETS.map((b) => pt[b.key] > 0 ? (
                                      <span className={"progchip " + b.cls} key={b.key}>{b.label} {pt[b.key]}</span>
                                    ) : null)}
                                  </span>
                                )}
                              </div>
                            );
                          })}
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            </div>
            <p className="navsrc" style={{ marginTop: 6 }}>
              {hasResult
                ? "メダルの数で並べています。同じ数のときは決勝に残ったクルー数の順。"
                : "出場している学校の数で並べています。同じ数のときはクルー数の順。"}
              県は各校のエントリー登録によります。　{SRC}
            </p>
          </div>
        );
      })()}

      {tab === "finals" && (
        <div>
          {decided.map((fb) => (
            <div className="medalcard" key={fb.ev.key}>
              <div className="mh"><Code ev={fb.ev} /><span className="nm2">{fb.ev.name}</span><span className="when">{fb.dow} {fb.t}</span></div>
              <PodiumRows podium={RW.PODIUMS[fb.ev.key]} onOpenTeam={onOpenTeam} evKey={fb.ev.key} />
            </div>
          ))}
          {awaitingFinals.map((fb) => (
            <div className="medalcard" key={fb.ev.key}>
              <div className="mh"><Code ev={fb.ev} /><span className="nm2">{fb.ev.name}</span><span className="when">{fb.dow} {fb.t}</span></div>
              <div className="rc-res-wait">結果待ち（公式ページに載るまで数分かかることがあります）</div>
            </div>
          ))}
          {!decided.length && !awaitingFinals.length && <div className="empty">終わった決勝はまだありません。</div>}
          {phase === "during" && (
            <div style={{ textAlign: "center", marginTop: 12, fontSize: 11.5, color: "var(--mut)" }}>※ これからの決勝は、レースが終わり次第ここに出ます</div>
          )}
          {(decided.length > 0 || awaitingFinals.length > 0) && <p className="navsrc" style={{ marginTop: 8 }}>{SRC}</p>}
        </div>
      )}
    </section>
  );
}
Object.assign(window, { ResultsView });
