/* app.jsx — root: phase state, bottom nav, tweaks */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "clock": 2,
  "timetable": "list",
  "dark": true
}/*EDITMODE-END*/;

const FAV_KEY = "kansai2026_fav_v1";

// representative clock positions for quick phase jump
const PHASE_POS = { before: 2, during: 88, after: 99 };

// URL の ?phase= で任意フェーズを固定表示・共有できる（編集・確認・SNS共有用）。
//   ?phase=pre  / before        → 大会前ページ
//   ?phase=now  / during / live → 大会中ページ（代表的な進行状況）
//   ?phase=post / after         → 大会後ページ（全結果・メダル）
// 指定が無ければ従来どおり実時刻で自動判定（当日に 前→中→後 が自動で切替）。
const PHASE_ALIAS = {
  pre: "before", before: "before",
  now: "during", during: "during", live: "during",
  post: "after", after: "after",
};
function urlPhaseOverride() {
  try {
    const v = (new URLSearchParams(window.location.search).get("phase") || "").toLowerCase().trim();
    return PHASE_ALIAS[v] || null;
  } catch (e) { return null; }
}

// 大会日（JST）。設定は config.js（window.KANSAI.eventDates）に集約。data.js の META.days と一致させること。
const EVENT_YMD = (window.KANSAI_CONFIG && window.KANSAI_CONFIG.eventDates) || [20260704, 20260705];   // d1, d2
// 現在の日本時間を {ymd, minutes} で返す（端末TZに依らず Asia/Tokyo で判定）
function jstNow() {
  const p = new Intl.DateTimeFormat("en-US", {
    timeZone: "Asia/Tokyo", year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", hour12: false,
  }).formatToParts(new Date());
  const g = (t) => +(p.find((x) => x.type === t) || {}).value;
  let hh = g("hour"); if (hh === 24) hh = 0;
  return { ymd: g("year") * 10000 + g("month") * 100 + g("day"), minutes: hh * 60 + g("minute") };
}
// 実時刻 → ord（engine の dayIndex*1440+分 スケール）。大会前後は境界外に倒す。
function realOrd(RW) {
  const { ymd, minutes } = jstNow();
  if (ymd < EVENT_YMD[0]) return RW.FIRST - 600;      // 大会前
  if (ymd === EVENT_YMD[0]) return 0 * 1440 + minutes; // 1日目
  if (ymd === EVENT_YMD[1]) return 1 * 1440 + minutes; // 2日目
  return RW.LAST + 600;                                 // 大会後
}

// jstNow()の{ymd,minutes} → 実カレンダー時刻(ms)。EVENT_YMDに依らず任意の日付を正しく扱う。
function jstPartsToMs(ymd, minutes) {
  const y = Math.floor(ymd / 10000), mo = Math.floor((ymd % 10000) / 100), d = ymd % 100;
  const hh = Math.floor(minutes / 60), mm = minutes % 60;
  return new Date(`${y}-${String(mo).padStart(2, "0")}-${String(d).padStart(2, "0")}T${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}:00+09:00`).getTime();
}
const jstNowMs = () => { const { ymd, minutes } = jstNow(); return jstPartsToMs(ymd, minutes); };
// ord（0=大会1日目, 1440=大会2日目...）→ 実カレンダー時刻(ms)。大会期間中の値専用（前後の境界丸め値は渡さない）。
function ordToRealMs(RW, ord) {
  const dayIndex = Math.max(0, Math.min(EVENT_YMD.length - 1, Math.floor(ord / 1440)));
  const minutesOfDay = ((ord % 1440) + 1440) % 1440;
  return jstPartsToMs(EVENT_YMD[dayIndex], minutesOfDay);
}
const DAY_MS = 24 * 60 * 60 * 1000;

// ホーム最上部モジュールの出し分け用サブフェーズ。
// before/during/after の3分岐はそのまま維持しつつ、時間帯でさらに細かく判定する。
// beforeフェーズはrealOrdが「大会何日前でも同じ値」に丸めてしまうため、実カレンダー時刻で判定する
// （デモ時はordが常に有効な日時を指すので ord ベースで代用）。
function subPhaseOf(RW, phase, ord, liveMode) {
  if (phase === "during") {
    const day1LastEnd = Math.max(...RW.FLAT.filter((r) => r.dayIndex === 0).map((r) => r.endOrd));
    const day2First = Math.min(...RW.FLAT.filter((r) => r.dayIndex === 1).map((r) => r.ord));
    return (ord >= day1LastEnd && ord < day2First) ? "interday" : "racing";
  }
  if (phase === "before") {
    const nowMs = liveMode ? jstNowMs() : ordToRealMs(RW, ord);
    const firstMs = ordToRealMs(RW, RW.FIRST);
    return (firstMs - nowMs) <= DAY_MS ? "eve" : "countdown";
  }
  // after
  const nowMs = liveMode ? jstNowMs() : ordToRealMs(RW, ord);
  const lastMs = ordToRealMs(RW, RW.LAST);
  return (nowMs - lastMs) <= DAY_MS ? "justEnded" : "archive";
}

// アンケート（Googleフォーム埋め込み・匿名）。回答は主催者への提案資料の集計に使う。
function SurveyView() {
  const FORM = (window.KANSAI_CONFIG && window.KANSAI_CONFIG.surveyForm) || "https://docs.google.com/forms/d/e/1FAIpQLScrtdu7mIyIjU5BcLO5N-dWe425b3hFPiyTI_z_2i19vWIvYw/viewform";
  return (
    <section className="view">
      <div className="shead"><span className="tag"><span>SURVEY</span></span><span className="jp">アンケート</span></div>
      <div className="survey-intro">
        今回の観戦サイトのご感想・改善のご要望をお聞かせください。<b>所要1分・匿名</b>です。ご協力ありがとうございます。
        <a className="survey-open" href={FORM} target="_blank" rel="noopener noreferrer">うまく表示されない場合は別のタブで開く ↗</a>
      </div>
      <div className="survey-frame">
        <iframe src={FORM + "?embedded=true"} title="観戦サイト アンケート" loading="lazy">読み込んでいます…</iframe>
      </div>
    </section>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const RW = window.RW;
  const [tab, setTab] = React.useState("schedule");
  const [teamQ, setTeamQ] = React.useState(""); // チーム検索語。ホームの検索欄とチームタブで共有
  const [selectedTeam, setSelectedTeam] = React.useState(null);
  const [cardRace, setCardRace] = React.useState(null);   // 発艇表ビッグカード：1レース
  const [cardEvent, setCardEvent] = React.useState(null); // 発艇表ビッグカード：1種目
  const [favTeams, setFavTeams] = React.useState(() => {
    try { const s = localStorage.getItem(FAV_KEY); if (s) return JSON.parse(s); } catch (e) {}
    return { "同志社大学": true, "京都大学": true };
  });
  React.useEffect(() => { try { localStorage.setItem(FAV_KEY, JSON.stringify(favTeams)); } catch (e) {} }, [favTeams]);

  // karal.jp 実結果の自動取り込み（大会期間中/後に本物へ差し替わる。関西配信時のみ有効化）
  const [, setKaralV] = React.useState(0);
  // 速報取得の生死監視：フッターの「最終更新」表示・停止インジケータ用
  const [karalStatus, setKaralStatus] = React.useState({ lastOkAt: null, failing: false });
  React.useEffect(() => {
    let alive = true;
    const load = async () => {
      for (const u of ["/api/karal.json", "./src/karal.json"]) {
        try {
          const r = await fetch(u, { cache: "no-store" });
          if (!r.ok) continue;
          const j = await r.json();
          if (!alive) return;
          if (window.RW.applyKaral(j)) setKaralV((v) => v + 1);
          setKaralStatus({ lastOkAt: Date.now(), failing: false });
          return;
        } catch (e) { /* try next */ }
      }
      // 全URL失敗：lastOkAtは維持し、3分（60秒ポーリング3回分）途絶えたら停止扱いにする
      if (!alive) return;
      setKaralStatus((s) => {
        if (s.lastOkAt == null) return s;
        const failing = Date.now() - s.lastOkAt > 180000;
        return failing === s.failing ? s : { ...s, failing };
      });
    };
    load();
    const id = setInterval(load, 60000);   // 競技中は60秒ごとに更新
    return () => { alive = false; clearInterval(id); };
  }, []);

  // 浜寺水路の天気（Open-Meteo経由）。取得できない間はカード自体を出さない（エラー表示はしない）。
  const [weather, setWeather] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    const load = async () => {
      try {
        const r = await fetch("/api/weather.json", { cache: "no-store" });
        if (!r.ok) return;
        const j = await r.json();
        if (!alive || !j || !j.current) return;
        setWeather(j);
      } catch (e) { /* カード非表示のまま */ }
    };
    load();
    const id = setInterval(load, 600000);  // 10分ごとに更新
    return () => { alive = false; clearInterval(id); };
  }, []);
  const toggleFav = (team) => setFavTeams((f) => { const n = { ...f }; if (n[team]) delete n[team]; else n[team] = true; return n; });

  // 既定は実時刻（当日に自動で 大会前→大会中→大会後 が切り替わる）。
  // Tweaks のスライダー/フェーズボタンを触るとデモ上書きに切替（liveMode=false）。
  const [liveMode, setLiveMode] = React.useState(true);
  const [, setTick] = React.useState(0);
  React.useEffect(() => {
    if (!liveMode) return;
    const id = setInterval(() => setTick((x) => x + 1), 30000); // 30秒ごとに時刻再評価
    return () => clearInterval(id);
  }, [liveMode]);

  // ?phase= 指定があれば実時刻より優先（1つのURLで任意フェーズを確認・共有できる）。
  const forcedPhase = urlPhaseOverride();
  const effLive = liveMode && !forcedPhase;
  const ord = forcedPhase ? RW.pctToOrd(PHASE_POS[forcedPhase])
    : liveMode ? realOrd(RW) : RW.pctToOrd(t.clock);
  const phase = RW.phaseOf(ord);
  const subPhase = subPhaseOf(RW, phase, ord, effLive);
  const clockLabel = phase === "before" ? "開幕前" : phase === "after" ? "大会終了" : RW.ordToLabel(ord);
  const defaultDay = ord >= 1440 ? "d2" : "d1";
  const hasLive = RW.liveAndNext(ord).live.length > 0;

  const go = (v) => { setTab(v); window.scrollTo({ top: 0, behavior: "smooth" }); };
  const openTeam = (team) => { setSelectedTeam(team); setTab("teams"); window.scrollTo({ top: 0, behavior: "smooth" }); };
  // 種目はどこから開いても「発艇表ビッグカード（種目モード）」
  // カードが閉じている状態から開く時は履歴を1段積む（pushState）。既にカードが開いていて
  // 別のカードへ移る時（前後ナビ・ラウンドボタン等）は積み増さず今の段を置き換える（replaceState）。
  const openEvent = (key) => {
    const h = "#event=" + encodeURIComponent(key);
    if (cardRace || cardEvent) history.replaceState(null, "", h); else history.pushState(null, "", h);
    setCardRace(null); setCardEvent(key);
  };
  const openRace = (id) => {
    const h = "#race=" + id;
    if (cardRace || cardEvent) history.replaceState(null, "", h); else history.pushState(null, "", h);
    setCardEvent(null); setCardRace(id);
  };
  // ×ボタン・背景タップも「戻る」と同じ経路（popstate）でカードを閉じる
  const closeCard = () => { history.back(); };

  // ハッシュ ⇄ カードstate 同期（スマホの戻るでカードだけ閉じる／共有リンクで直接開く）
  React.useEffect(() => {
    const applyHash = (h) => {
      if (h.indexOf("#race=") === 0) {
        const id = decodeURIComponent(h.slice(6));
        if (RW.FLAT.find((x) => x.id === id)) { setCardRace(id); setCardEvent(null); return true; }
      } else if (h.indexOf("#event=") === 0) {
        const key = decodeURIComponent(h.slice(7));
        if (RW.evOf[key]) { setCardEvent(key); setCardRace(null); return true; }
      }
      return false;
    };
    // 初回ロードでハッシュ付き（共有リンク）だった場合：素のURL（カード無しホーム）を
    // 1段下に敷いてからカードのハッシュを積み直す。これで×やバックでホームに着地する
    // （敷かないと直前の履歴がLINE等になり、サイトごと離脱してしまう）。
    const initialHash = location.hash;
    if (initialHash) {
      history.replaceState(null, "", location.pathname);
      if (applyHash(initialHash)) history.pushState(null, "", initialHash);
    }
    const onPop = () => {
      const h = location.hash;
      if (!h) { setCardRace(null); setCardEvent(null); return; }
      if (!applyHash(h)) { history.replaceState(null, "", location.pathname); setCardRace(null); setCardEvent(null); }
    };
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
  }, []);

  const counts = RW.COUNTS;
  const tabs = [
    { id: "schedule", label: "ホーム", Icon: IconHome, live: true },
    { id: "teams", label: "チーム", Icon: IconTeams },
    { id: "results", label: "結果", Icon: IconMedal },
    { id: "rules", label: "大会", Icon: IconRule },
    { id: "survey", label: "アンケート", Icon: IconSurvey },
  ];

  return (
    <div className="app" data-phase={phase} data-dark={t.dark ? "1" : "0"}>
      <Hero counts={counts} />
      <div className="body">
        {subPhase === "archive" ? (
          <div className="archivebar">大会アーカイブ　·　2026/7/4–5</div>
        ) : (
          <LiveStrip ord={ord} phase={phase} onOpen={() => go("schedule")} onOpenRace={openRace} onOpenResults={() => go("results")} />
        )}

        {tab === "home" && <HomeView ord={ord} phase={phase} subPhase={subPhase} weather={weather} clockLabel={clockLabel} favTeams={favTeams} onOpenTeam={openTeam} onOpenEvent={openEvent} onOpenRace={openRace} goTab={go} teamQ={teamQ} setTeamQ={setTeamQ} toggleFav={toggleFav} />}
        {/* 大会後：タイムテーブルより「種目カード→結果」の方が役立つので、ホームタブは大会後ビューに切り替える */}
        {tab === "schedule" && (phase === "after"
          ? <HomeView ord={ord} phase={phase} subPhase={subPhase} weather={weather} clockLabel={clockLabel} favTeams={favTeams} onOpenTeam={openTeam} onOpenEvent={openEvent} onOpenRace={openRace} goTab={go} teamQ={teamQ} setTeamQ={setTeamQ} toggleFav={toggleFav} />
          : <ScheduleView ord={ord} ttLayout={t.timetable} defaultDay={defaultDay} onOpenRace={openRace} onOpenTeam={openTeam} favTeams={favTeams} weather={weather} />)}
        {tab === "results" && <ResultsView ord={ord} phase={phase} onOpenTeam={openTeam} goTab={go} />}
        {tab === "teams" && <TeamsView ord={ord} favTeams={favTeams} toggleFav={toggleFav} selectedTeam={selectedTeam} setSelectedTeam={setSelectedTeam} onOpenEvent={openEvent} q={teamQ} setQ={setTeamQ} />}
        {tab === "rules" && <RulesView />}
        {tab === "survey" && <SurveyView />}

        <footer>
          <div><b>A決勝</b>＝メダル決定レース　「—」＝その日はレースなし　／　{RW.hasReal()
            ? <>結果・タイムは<b>karal.jp（関西ローイング連盟 速報）より自動取得</b>（60秒ごと更新）</>
            : <>結果・タイムは、確定結果が届くまでは<b>参考用の自動生成値</b>を表示することがあります。大会中は<b>karal.jp（関西ローイング連盟 速報）</b>の結果に自動的に切り替わります（60秒ごと更新）</>}
            {RW.hasReal() && karalStatus.lastOkAt && (karalStatus.failing
              ? <span style={{ color: "#a3670a" }}>　⚠ 速報の更新が一時停止しています（最終更新 {new Date(karalStatus.lastOkAt).toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit" })}）</span>
              : <>（最終更新 {new Date(karalStatus.lastOkAt).toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit" })}）</>)}
          </div>
          これは非公式の観戦ガイドページです。日程・エントリーは主催者発表に基づきますが、変更となる場合があります。<b>各クルーの選手名・編成は6月上旬時点のエントリー名簿をもとに作成しているため、その後の変更・誤りが含まれる場合があります。</b>正式情報は主催・関西ローイング連盟の公式発表をご確認ください。
        </footer>
      </div>

      {(cardRace || cardEvent) && (
        <RaceCardOverlay raceId={cardRace} eventKey={cardEvent} ord={ord} onClose={closeCard} favTeams={favTeams}
                         onOpenTeam={openTeam} onOpenRace={openRace} onOpenEvent={openEvent} />
      )}

      <nav className="bnav">
        {tabs.map(({ id, label, Icon, live }) => (
          <button key={id} className={(tab === id ? "on" : "") + (id === "survey" ? " nav-survey" : "")} onClick={() => go(id)}>
            <span className={live ? "nav-live" : ""}><Icon size={21} />{live && hasLive && <span className="ld" />}</span>
            <span className="bl">{label}</span>
          </button>
        ))}
      </nav>

      <TweaksPanel title="Tweaks">
        <TweakSection label="時刻モード" />
        <TweakRow label="モード" value={forcedPhase ? `URL固定：${forcedPhase}（?phase=）` : liveMode ? "実時刻（ライブ）" : "デモ上書き"}>
          <div style={{ display: "flex", gap: 6 }}>
            {[[true, "実時刻"], [false, "デモ"]].map(([v, l]) => {
              const on = liveMode === v;
              return (
                <button key={String(v)} type="button" onClick={() => setLiveMode(v)}
                        style={{ flex: 1, padding: "9px 4px", borderRadius: 8, cursor: "pointer", fontWeight: 800, fontSize: 12,
                          fontFamily: "inherit", border: on ? "2px solid rgba(0,0,0,.8)" : ".5px solid rgba(0,0,0,.18)",
                          background: on ? "#0b1f44" : "#fff", color: on ? "#fff" : "#333" }}>{l}</button>
              );
            })}
          </div>
        </TweakRow>

        <TweakSection label="デモ：大会フェーズ（手動上書き）" />
        <TweakRow label="時期" value={clockLabel}>
          <div style={{ display: "flex", gap: 6 }}>
            {[["before", "大会前"], ["during", "大会中"], ["after", "大会後"]].map(([p, l]) => {
              const on = !liveMode && phase === p;
              return (
                <button key={p} type="button" onClick={() => { setLiveMode(false); setTweak("clock", PHASE_POS[p]); }}
                        style={{ flex: 1, padding: "9px 4px", borderRadius: 8, cursor: "pointer", fontWeight: 800, fontSize: 12,
                          fontFamily: "inherit", border: on ? "2px solid rgba(0,0,0,.8)" : ".5px solid rgba(0,0,0,.18)",
                          background: on ? "#0b1f44" : "#fff", color: on ? "#fff" : "#333" }}>{l}</button>
              );
            })}
          </div>
        </TweakRow>
        <TweakRow label="会場時刻" value={clockLabel}>
          <input type="range" className="twk-slider" min={0} max={100} step={1} value={t.clock} onChange={(e) => { setLiveMode(false); setTweak("clock", Number(e.target.value)); }} />
        </TweakRow>

        <TweakSection label="配色" />
        <TweakRadio label="テーマ" value={t.dark ? "dark" : "light"}
                    options={[{ value: "dark", label: "ダーク" }, { value: "light", label: "ライト" }]}
                    onChange={(v) => setTweak("dark", v === "dark")} />

        <TweakSection label="タイムテーブル" />
        <TweakRadio label="レイアウト" value={t.timetable}
                    options={[{ value: "list", label: "リスト" }, { value: "compact", label: "コンパクト" }]}
                    onChange={(v) => setTweak("timetable", v)} />

        <TweakButton label="お気に入りをリセット" secondary onClick={() => setFavTeams({})} />
      </TweaksPanel>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
