// idea-10-focus-rooms.jsx - Focus Rooms MVP.
// Presence-first study rooms: no chat, no video, no DMs.

const FR_FALLBACK_ROOMS = [
  { id: 'quiet-revision', name: 'Quiet revision', subject: 'mixed', year_group: null, module_label: 'Open study', room_type: 'quiet', sort_order: 10 },
  { id: 'bio-y12-mod7', name: 'Bio Mod 7 grind', subject: 'biology', year_group: 12, module_label: 'Module 7', room_type: 'subject', sort_order: 20 },
  { id: 'chem-y12-practice', name: 'Chem practice block', subject: 'chemistry', year_group: 12, module_label: 'HSC practice', room_type: 'subject', sort_order: 30 },
  { id: 'maths-standard', name: 'Maths Standard room', subject: 'maths-standard', year_group: 12, module_label: 'Mixed topics', room_type: 'subject', sort_order: 40 },
];

const FR_GOAL_SUGGESTIONS = [
  'Finish 10 practice questions',
  'Rewrite one weak answer',
  'Review today\'s lesson notes',
  'Complete one timed section',
];

// Generous idle tolerance: the real work is handwritten in the book, so the
// student will not be clicking constantly. Only pause the focus timer after a
// prolonged idle stretch or when they leave the tab (STUDY_ROUTINE_DESIGN.md —
// Focus Room: "presence-on-a-lesson-page counts", "generous idle tolerance").
const FR_IDLE_LIMIT_MS = 4 * 60 * 1000; // 4 minutes of no activity → pause

function FocusRooms() {
  const db = window.HSC && window.HSC.supabase;
  const auth = window.HSC && window.HSC.auth;
  const [user, setUser] = React.useState(null);
  const [profile, setProfile] = React.useState(null);
  const [authReady, setAuthReady] = React.useState(!auth);
  const [rooms, setRooms] = React.useState(FR_FALLBACK_ROOMS);
  const [activeRoomId, setActiveRoomId] = React.useState(FR_FALLBACK_ROOMS[0].id);
  const [joinedRoom, setJoinedRoom] = React.useState(null);
  const [goalText, setGoalText] = React.useState('');
  const [goalDone, setGoalDone] = React.useState(false);
  const [joinedAt, setJoinedAt] = React.useState(null);
  const [elapsedSec, setElapsedSec] = React.useState(0);
  const [participants, setParticipants] = React.useState([]);
  const [presenceRows, setPresenceRows] = React.useState([]);
  const [roomCounts, setRoomCounts] = React.useState({});
  const [channel, setChannel] = React.useState(null);
  const [saveState, setSaveState] = React.useState('');
  const [recentSessions, setRecentSessions] = React.useState([]);
  const [reportForm, setReportForm] = React.useState({ category: 'safety', detail: '' });
  const [reportSaveState, setReportSaveState] = React.useState('');
  const [activeSec, setActiveSec] = React.useState(0);
  const [isPaused, setIsPaused] = React.useState(false);
  const [rewards, setRewards] = React.useState(null);
  const [stats, setStats] = React.useState(null);
  const heartbeatRef = React.useRef(null);
  const lastActivityRef = React.useRef(Date.now());
  const activeSecRef = React.useRef(0);
  const leavingRef = React.useRef(false);

  const rewardsApi = window.HSC && window.HSC.focusRewards;
  function refreshStats() {
    if (rewardsApi) setStats(rewardsApi.getStats());
  }

  const activeRoom = rooms.find(r => r.id === activeRoomId) || rooms[0];
  const displayName = getSafeDisplayName(user, profile);
  const signedIn = Boolean(user);

  React.useEffect(() => {
    let alive = true;
    async function initAuth() {
      if (!auth) {
        setAuthReady(true);
        return;
      }
      const resolvedUser = await auth.ready;
      if (!alive) return;
      setUser(resolvedUser || null);
      setProfile(auth.getProfile ? auth.getProfile() : null);
      if (resolvedUser && db) {
        const result = await db.from('user_profiles')
          .select('codename, display_name')
          .eq('user_id', resolvedUser.id)
          .maybeSingle();
        if (alive && result.data) setProfile(result.data);
      }
      setAuthReady(true);
    }
    initAuth();
    return () => { alive = false; };
  }, [db]);

  React.useEffect(() => {
    let alive = true;
    async function loadRooms() {
      if (!db) return;
      const result = await db.from('focus_rooms')
        .select('id, name, subject, year_group, module_label, room_type, sort_order')
        .eq('is_active', true)
        .order('sort_order', { ascending: true });
      if (!alive || result.error || !result.data || !result.data.length) return;
      setRooms(result.data);
      if (!result.data.some(r => r.id === activeRoomId)) setActiveRoomId(result.data[0].id);
    }
    loadRooms();
    return () => { alive = false; };
  }, [db]);

  React.useEffect(() => {
    let alive = true;
    async function syncAndLoad() {
      if (user && db) await migrateLocalFocusSessions(user.id, db);
      if (alive) loadRecentSessions();
      if (alive && user && db) loadRoomCounts();
      if (alive && user && db) await reconcileStreakFromCloud();
    }
    syncAndLoad();
    return () => { alive = false; };
  }, [user, db]);

  // Rebuild the study-day set from cloud-durable evidence so the streak survives
  // a device switch or cleared cache: qualifying focus sessions + completed
  // lessons in Supabase → local hsc_study_days → getStats() recomputes the same
  // streak on any device.
  async function reconcileStreakFromCloud() {
    if (!db || !user || !rewardsApi) return;
    const isoDates = [];
    const minMin = rewardsApi.MIN_QUALIFY_MIN || 10;
    const sessions = await db.from('focus_room_sessions')
      .select('joined_at, focus_minutes')
      .eq('user_id', user.id)
      .gte('focus_minutes', minMin)
      .order('joined_at', { ascending: false })
      .limit(400);
    if (sessions.data) sessions.data.forEach(row => { if (row.joined_at) isoDates.push(row.joined_at); });
    const lessons = await db.from('lesson_progress')
      .select('completed_at')
      .eq('user_id', user.id)
      .eq('completed', true)
      .not('completed_at', 'is', null)
      .limit(600);
    if (lessons.data) lessons.data.forEach(row => { if (row.completed_at) isoDates.push(row.completed_at); });
    if (isoDates.length) rewardsApi.mergeStudyDays(isoDates);
    refreshStats();
  }

  React.useEffect(() => {
    if (!user || !db) return undefined;
    loadRoomCounts();
    const timer = window.setInterval(loadRoomCounts, 30000);
    return () => window.clearInterval(timer);
  }, [user && user.id, db]);

  // Idle-gated timer: elapsedSec is wall-clock since join; activeSec only
  // advances while the student is present (tab visible + activity within the
  // generous idle window). Rewards use activeSec so idle time is not paid for.
  React.useEffect(() => {
    if (!joinedAt) {
      setActiveSec(0);
      activeSecRef.current = 0;
      setIsPaused(false);
      return undefined;
    }
    lastActivityRef.current = Date.now();
    const timer = window.setInterval(() => {
      const now = Date.now();
      const hidden = typeof document !== 'undefined' && document.hidden;
      const idleMs = now - lastActivityRef.current;
      const paused = hidden || idleMs > FR_IDLE_LIMIT_MS;
      setIsPaused(paused);
      setElapsedSec(Math.max(0, Math.floor((now - joinedAt.getTime()) / 1000)));
      if (!paused) {
        activeSecRef.current += 1;
        setActiveSec(activeSecRef.current);
      }
    }, 1000);
    return () => window.clearInterval(timer);
  }, [joinedAt]);

  // Track user activity site-wide so brief pauses (reading, handwriting notes)
  // do not stop the timer — only a prolonged idle stretch does.
  React.useEffect(() => {
    const mark = () => { lastActivityRef.current = Date.now(); };
    const events = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll', 'pointerdown'];
    events.forEach(ev => window.addEventListener(ev, mark, { passive: true }));
    const onVis = () => { if (!document.hidden) lastActivityRef.current = Date.now(); };
    document.addEventListener('visibilitychange', onVis);
    return () => {
      events.forEach(ev => window.removeEventListener(ev, mark));
      document.removeEventListener('visibilitychange', onVis);
    };
  }, []);

  React.useEffect(() => { refreshStats(); }, [user]);

  React.useEffect(() => {
    if (!joinedRoom || !db || !user) {
      setParticipants([]);
      setPresenceRows([]);
      return undefined;
    }

    const nextChannel = db.channel('focus-room:' + joinedRoom.id, {
      config: { presence: { key: user.id } }
    });

    nextChannel
      .on('presence', { event: 'sync' }, () => {
        const state = nextChannel.presenceState();
        setParticipants(flattenPresenceState(state));
      })
      .subscribe(async status => {
        if (status !== 'SUBSCRIBED') return;
        await nextChannel.track({
          user_id: user.id,
          display_name: displayName,
          char_id: pickCharId(user.id),
          goal: cleanGoal(goalText),
          status: goalDone ? 'goal done' : 'focused',
          joined_at: new Date().toISOString()
        });
      });

    setChannel(nextChannel);
    return () => {
      nextChannel.untrack();
      db.removeChannel(nextChannel);
      setChannel(null);
    };
  }, [joinedRoom && joinedRoom.id, user && user.id]);

  React.useEffect(() => {
    if (!joinedRoom || !db || !user) {
      if (heartbeatRef.current) window.clearInterval(heartbeatRef.current);
      heartbeatRef.current = null;
      return undefined;
    }

    writePresenceHeartbeat(joinedRoom);
    loadPresenceRows(joinedRoom.id);
    heartbeatRef.current = window.setInterval(() => {
      writePresenceHeartbeat(joinedRoom);
      loadPresenceRows(joinedRoom.id);
      loadRoomCounts();
    }, 20000);

    return () => {
      if (heartbeatRef.current) window.clearInterval(heartbeatRef.current);
      heartbeatRef.current = null;
    };
  }, [joinedRoom && joinedRoom.id, user && user.id, db]);

  React.useEffect(() => {
    if (!channel || !joinedRoom || !user) return;
    channel.track({
      user_id: user.id,
      display_name: displayName,
      char_id: pickCharId(user.id),
      goal: cleanGoal(goalText),
      status: currentPresenceStatus(),
      joined_at: joinedAt ? joinedAt.toISOString() : new Date().toISOString()
    });
    writePresenceHeartbeat(joinedRoom);
    // isPaused in deps so a pause/resume promptly re-pushes 'away'/'focused'
    // to both realtime presence and the DB heartbeat, refreshing the counts.
  }, [goalDone, goalText, channel, displayName, isPaused]);

  // Presence status mirrors the reward-timer pause: a student who is hidden or
  // idle past the threshold is 'away', not 'focused', so they are not counted as
  // "in room". Computed from the live ref + document.hidden (not React state) so
  // the 20s heartbeat interval never reports a stale status.
  function currentPresenceStatus() {
    const hidden = typeof document !== 'undefined' && document.hidden;
    const idleMs = Date.now() - lastActivityRef.current;
    if (hidden || idleMs > FR_IDLE_LIMIT_MS) return 'away';
    return goalDone ? 'goal done' : 'focused';
  }

  async function loadRoomCounts() {
    if (!db || !user) return;
    // Exclude 'away' rows: an idle/hidden student should not inflate the "in
    // room" counts even though their presence row is kept fresh.
    const result = await db.from('focus_room_presence')
      .select('room_id')
      .neq('status', 'away')
      .gt('last_seen_at', new Date(Date.now() - 2 * 60 * 1000).toISOString());
    if (result.error || !result.data) return;
    const counts = {};
    result.data.forEach(row => {
      counts[row.room_id] = (counts[row.room_id] || 0) + 1;
    });
    setRoomCounts(counts);
  }

  async function loadPresenceRows(roomId) {
    if (!db || !user || !roomId) return;
    const result = await db.from('focus_room_presence')
      .select('room_id, user_id, display_name, char_id, goal_text, status, joined_at, last_seen_at')
      .eq('room_id', roomId)
      .gt('last_seen_at', new Date(Date.now() - 2 * 60 * 1000).toISOString())
      .order('joined_at', { ascending: true });
    if (result.error || !result.data) return;
    setPresenceRows(result.data.map(row => ({
      user_id: row.user_id,
      display_name: row.display_name,
      char_id: row.char_id,
      goal: row.goal_text,
      status: row.status,
      joined_at: row.joined_at,
      last_seen_at: row.last_seen_at
    })));
  }

  async function writePresenceHeartbeat(room) {
    if (!db || !user || !room) return false;
    const payload = {
      room_id: room.id,
      user_id: user.id,
      display_name: displayName,
      char_id: pickCharId(user.id),
      goal_text: cleanGoal(goalText),
      status: currentPresenceStatus(),
      joined_at: joinedAt ? joinedAt.toISOString() : new Date().toISOString(),
      last_seen_at: new Date().toISOString()
    };
    const result = await db.from('focus_room_presence')
      .upsert(payload, { onConflict: 'room_id,user_id' });
    return !result.error;
  }

  async function clearPresence(roomId) {
    if (!db || !user || !roomId) return;
    await db.from('focus_room_presence')
      .delete()
      .eq('room_id', roomId)
      .eq('user_id', user.id);
    setPresenceRows([]);
    loadRoomCounts();
  }

  async function submitSafetyReport() {
    if (!db || !user) return;
    const detail = cleanReportText(reportForm.detail);
    if (!detail) return;
    const payload = {
      reporter_id: user.id,
      room_id: (joinedRoom || activeRoom || {}).id || null,
      category: reportForm.category,
      detail: detail
    };

    setReportSaveState('saving');
    const result = await db.from('focus_room_safety_reports').insert(payload);
    if (result.error) {
      setReportSaveState('error');
      return;
    }
    setReportSaveState('saved');
    setReportForm({ category: reportForm.category, detail: '' });
  }

  function loadRecentSessions() {
    if (user && db) {
      db.from('focus_room_sessions')
        .select('client_session_id, room_id, goal_text, goal_completed, focus_minutes, joined_at')
        .eq('user_id', user.id)
        .order('joined_at', { ascending: false })
        .limit(5)
        .then(result => {
          if (result.data) setRecentSessions(result.data);
          else setRecentSessions(loadLocalSessions(user.id));
        });
      return;
    }
    setRecentSessions(user ? loadLocalSessions(user.id) : []);
  }

  function joinRoom(room) {
    if (!signedIn) return;
    const cleaned = cleanGoal(goalText) || FR_GOAL_SUGGESTIONS[0];
    setGoalText(cleaned);
    setGoalDone(false);
    setJoinedRoom(room);
    setActiveRoomId(room.id);
    setJoinedAt(new Date());
    setElapsedSec(0);
    setSaveState('');
  }

  async function leaveRoom() {
    // Guard against double-invocation (both leave buttons, rapid double-click):
    // firing recordSession twice would inflate session count / same-day XP.
    if (leavingRef.current) return;
    leavingRef.current = true;
    try {
      const leavingRoomId = joinedRoom && joinedRoom.id;
      if (!joinedRoom || !joinedAt || !user) {
        setJoinedRoom(null);
        if (leavingRoomId) clearPresence(leavingRoomId);
        return;
      }

      // Save the honest ACTIVE minutes (idle time excluded), floored — a
      // sub-minute session logs 0, never inflated to 1.
      const activeMinutes = Math.floor(activeSecRef.current / 60);
      const payload = {
        user_id: user.id,
        client_session_id: createFocusSessionId(user.id, joinedAt),
        room_id: joinedRoom.id,
        goal_text: cleanGoal(goalText) || 'Focused study',
        goal_completed: goalDone,
        focus_minutes: activeMinutes,
        joined_at: joinedAt.toISOString(),
        left_at: new Date().toISOString()
      };

      setSaveState('saving');
      let savedToCloud = false;
      if (db) {
        const result = await db.from('focus_room_sessions')
          .upsert(payload, { onConflict: 'user_id,client_session_id' });
        savedToCloud = !result.error;
      }
      if (!savedToCloud) saveLocalSession(user.id, payload);

      // Rewards: consistency-weighted XP, badges, and a grace-day streak. Only
      // credit sessions with at least a full active minute — trivial joins must
      // not inflate the session count or badges. The module further gates XP /
      // streaks on MIN_QUALIFY_MIN.
      if (rewardsApi && activeMinutes >= 1) {
        const result = rewardsApi.recordSession({ activeMinutes: activeMinutes, goalCompleted: goalDone });
        setRewards(result);
        refreshStats();
      }

      setRecentSessions([payload].concat(recentSessions).slice(0, 5));
      setSaveState(savedToCloud ? 'saved' : 'saved-local');
      await clearPresence(leavingRoomId);
      setJoinedRoom(null);
      setParticipants([]);
      setPresenceRows([]);
      setJoinedAt(null);
      setElapsedSec(0);
    } finally {
      leavingRef.current = false;
    }
  }

  const roomParticipants = joinedRoom ? mergePresence(participants, presenceRows) : [];
  const visibleParticipants = roomParticipants.length
    ? roomParticipants
    : signedIn && joinedRoom
      ? [{ user_id: user.id, display_name: displayName, char_id: pickCharId(user.id), goal: cleanGoal(goalText), status: goalDone ? 'goal done' : 'focused' }]
      : [];

  return (
    <div className="fr-wrap">
      <aside className="fr-list">
        <div className="fr-list-head">
          <h3>Focus rooms</h3>
          <em>{joinedRoom ? visibleParticipants.length : rooms.length} LIVE</em>
        </div>

        {rooms.map(room => {
          const isActive = activeRoomId === room.id;
          const tag = room.year_group ? 'Yr ' + room.year_group : 'Mixed';
          return (
            <button key={room.id} className={`fr-room ${isActive ? 'active' : ''}`} onClick={() => setActiveRoomId(room.id)}>
              <div className="fr-room-row1">
                <div className="fr-room-name">{room.name}</div>
                <div className="fr-room-phase focus">{room.room_type === 'quiet' ? 'quiet' : 'focus'}</div>
              </div>
              <div className="fr-room-row2">
                <Icon name="users"/>
                <strong>{joinedRoom && joinedRoom.id === room.id ? visibleParticipants.length : roomCounts[room.id] || 0}</strong> in
                <span className="fr-room-tag">{tag}</span>
              </div>
              <div className="fr-room-bar"><i style={{ width: `${isActive ? 68 : 24}%` }}/></div>
            </button>
          );
        })}

        <div className="fr-create">
          <Icon name="flag"/> Chat/video disabled for MVP
        </div>
      </aside>

      <div className="fr-main">
        <div className="fr-room-head">
          <div>
            <h2><Icon name="target"/> {joinedRoom ? joinedRoom.name : activeRoom.name}</h2>
            <div className="fr-room-head-sub">
              {joinedRoom
                ? `${visibleParticipants.length} in room · ${formatElapsed(activeSec)} focused${isPaused ? ' · paused (idle)' : ''} · presence only`
                : `${roomLabel(activeRoom)} · set a goal, study quietly, log the block`}
            </div>
          </div>
          {joinedRoom ? (
            <button className="fr-leave" onClick={leaveRoom} disabled={saveState === 'saving'}>Leave room</button>
          ) : (
            <a className="fr-leave" href="/index.html">Home</a>
          )}
        </div>

        <div className="fr-room-body">
          <div className="fr-stage">
            <div className="fr-timer-card">
              <div className="fr-timer-ring">
                <FocusRing elapsedSec={activeSec} active={Boolean(joinedRoom)} paused={isPaused}/>
              </div>
              <div className="fr-timer-info">
                <div className="fr-timer-info-eye">FOCUS ROOM · PRESENCE ONLY</div>
                <h3>{joinedRoom ? (isPaused ? 'Timer paused — welcome back when you are ready' : 'You are in a quiet study block') : 'Set one goal before you join'}</h3>
                <p>
                  {joinedRoom
                    ? `The timer counts active study time only. Reading or writing in your book is fine — it pauses after about ${Math.round(FR_IDLE_LIMIT_MS / 60000)} minutes idle or if you leave the tab.`
                    : 'Students appear as avatars and status only. No chat, video, direct messages, school names, or public profiles.'}
                </p>

                {!authReady && <div className="fr-notice">Checking sign-in...</div>}
                {authReady && !signedIn && (
                  <div className="fr-notice">
                    Sign in to join a room and save your focus session.
                    <a href="/auth/login.html?next=/focus.html"> Sign in</a>
                  </div>
                )}

                <label className="fr-goal-label" htmlFor="focus-goal">Session goal</label>
                <textarea
                  id="focus-goal"
                  className="fr-goal-input"
                  maxLength="180"
                  value={goalText}
                  disabled={Boolean(joinedRoom)}
                  placeholder="Example: Finish 10 Module 7 practice questions"
                  onChange={e => setGoalText(e.target.value)}
                />
                <div className="fr-goal-help">Keep it study-related. Do not include school, phone, socials, or private details.</div>

                {!joinedRoom && (
                  <div className="fr-suggestions">
                    {FR_GOAL_SUGGESTIONS.map(s => (
                      <button key={s} type="button" onClick={() => setGoalText(s)}>{s}</button>
                    ))}
                  </div>
                )}

                <div className="fr-timer-controls">
                  {!joinedRoom ? (
                    <button className="fr-tc primary" disabled={!signedIn || !cleanGoal(goalText)} onClick={() => joinRoom(activeRoom)}>
                      <Icon name="play"/> Join room
                    </button>
                  ) : (
                    <>
                      <button className={`fr-tc ${goalDone ? 'primary' : ''}`} onClick={() => setGoalDone(!goalDone)}>
                        <Icon name="check"/> {goalDone ? 'Goal marked done' : 'Mark goal done'}
                      </button>
                      <button className="fr-tc" onClick={leaveRoom} disabled={saveState === 'saving'}><Icon name="x"/> Save and leave</button>
                    </>
                  )}
                </div>
                {saveState === 'saved' && <div className="fr-save-state">Focus session saved.</div>}
                {saveState === 'saved-local' && <div className="fr-save-state">Saved on this device. It will sync when cloud saving is available.</div>}
                {rewards && (
                  <div className="fr-reward-card">
                    {rewards.qualified ? (
                      <React.Fragment>
                        <div className="fr-reward-row">
                          {rewards.xpAwarded > 0 && <span className="fr-reward-xp">+{rewards.xpAwarded} XP</span>}
                          <span className="fr-reward-streak"><Icon name="flame"/> {rewards.streak}-day streak</span>
                          {rewards.studyDayCounted && <span className="fr-reward-tag">Day counted</span>}
                        </div>
                        {rewards.newBadges && rewards.newBadges.length > 0 && (
                          <div className="fr-reward-badges">
                            {rewards.newBadges.map(b => (
                              <span key={b.id} className="fr-badge-new">{b.icon} {b.title}</span>
                            ))}
                          </div>
                        )}
                      </React.Fragment>
                    ) : (
                      <div className="fr-reward-row muted">
                        Study a little longer next time — {rewards.minQualifyMin}+ active minutes counts the day toward your streak.
                      </div>
                    )}
                  </div>
                )}
              </div>
            </div>

            <div className="fr-grid">
              {visibleParticipants.map((p, i) => (
                <div key={(p.user_id || p.display_name || 'p') + i} className={`fr-tile ${p.user_id === (user && user.id) ? 'you' : ''}`}>
                  <div className="fr-tile-av"><CharAv charId={p.char_id || 'leaf'} size={56}/></div>
                  <div className="fr-tile-name">{p.display_name || 'Student'}</div>
                  <div className={`fr-tile-status ${p.status === 'goal done' ? 'done' : ''}`}>
                    <Icon name={p.status === 'goal done' ? 'check' : 'bolt'}/> {p.status || 'focused'}
                  </div>
                </div>
              ))}
              {!joinedRoom && (
                <div className="fr-tile fr-empty-tile">
                  <Icon name="timer"/>
                  <span>Join to appear in the room</span>
                </div>
              )}
            </div>
          </div>

          <aside className="fr-rail">
            {signedIn && stats && (
              <div className="fr-consistency">
                <div className="fr-consistency-grid">
                  <div className="fr-stat">
                    <strong><Icon name="flame"/> {stats.streak}</strong>
                    <span>day streak</span>
                  </div>
                  <div className="fr-stat">
                    <strong>{stats.graceRemaining}/{stats.graceDays}</strong>
                    <span>grace days left</span>
                  </div>
                  <div className="fr-stat">
                    <strong>{stats.xp.toLocaleString()}</strong>
                    <span>total XP</span>
                  </div>
                  <div className="fr-stat">
                    <strong>{stats.sessionCount}</strong>
                    <span>focus blocks</span>
                  </div>
                </div>
                <p className="fr-consistency-note">
                  Consistency beats marathons — one {stats.minQualifyMin}-minute block a day keeps your streak alive. Grace days cover the odd day you miss.
                </p>
                {stats.badges && stats.badges.length > 0 && (
                  <div className="fr-badge-row">
                    {stats.badges.map(b => (
                      <span key={b.id} className="fr-badge" title={b.title}>{b.icon}</span>
                    ))}
                  </div>
                )}
              </div>
            )}

            <h4>Your recent focus logs</h4>
            <div className="fr-session-list">
              {recentSessions.length ? recentSessions.map((s, i) => (
                <div key={i} className="fr-session-row">
                  <strong>{s.focus_minutes || 0}m</strong>
                  <span>{s.goal_text || 'Focused study'}</span>
                  <em>{s.goal_completed ? 'done' : 'logged'}</em>
                </div>
              )) : (
                <div className="fr-session-empty">Your saved focus sessions will appear here.</div>
              )}
            </div>

            <div className="fr-guardrail">
              <h4>Room rules</h4>
              <p>Study goals only. No personal details. No chat or video in this first version.</p>
            </div>

            {signedIn && (
              <div className="fr-report-box">
                <h4>Report an issue</h4>
                <select className="fr-report-select" value={reportForm.category} onChange={e => setReportForm(prev => Object.assign({}, prev, { category: e.target.value }))}>
                  <option value="safety">Safety concern</option>
                  <option value="privacy">Privacy concern</option>
                  <option value="content">Goal/content issue</option>
                  <option value="technical">Technical problem</option>
                  <option value="other">Other</option>
                </select>
                <textarea
                  className="fr-report-input"
                  rows="4"
                  maxLength="1000"
                  value={reportForm.detail}
                  onChange={e => setReportForm(prev => Object.assign({}, prev, { detail: e.target.value }))}
                  placeholder="Briefly describe what needs attention."
                />
                <button className="fr-report-action" type="button" disabled={reportSaveState === 'saving' || cleanReportText(reportForm.detail).length < 5} onClick={submitSafetyReport}>
                  <Icon name="flag"/> {reportSaveState === 'saving' ? 'Sending...' : 'Send report'}
                </button>
                {reportSaveState === 'saved' && <div className="fr-report-state">Report sent.</div>}
                {reportSaveState === 'error' && <div className="fr-report-state error">Could not send that report yet.</div>}
              </div>
            )}
          </aside>
        </div>

        <div className="fr-foot">
          <Icon name="bulb"/>
          <strong>Rewards you for:</strong>
          <span className="fr-ambient active">Showing up daily</span>
          <span className="fr-ambient active">Active focus time</span>
          <span className="fr-ambient active">Grace-day streaks</span>
          <a href="squads.html" style={{ marginLeft: 'auto', fontFamily: 'DM Mono', fontWeight: 700, color: 'rgba(255,255,255,0.7)', textDecoration: 'none' }}>
            Ready to be tested by peers? Join a Study Squad →
          </a>
        </div>
      </div>
    </div>
  );
}

function FocusRing({ elapsedSec, active, paused }) {
  const r = 56;
  const c = 2 * Math.PI * r;
  const pct = active ? Math.min(1, elapsedSec / (25 * 60)) : 0;
  const ringColor = paused ? '#9aa0a6' : '#d6a85f';
  return (
    <svg viewBox="0 0 130 130" width="130" height="130">
      <circle cx="65" cy="65" r={r} fill="none" stroke="rgba(255,255,255,0.12)" strokeWidth="10"/>
      <circle cx="65" cy="65" r={r} fill="none" stroke={ringColor} strokeWidth="10" strokeLinecap="round"
        strokeDasharray={`${c*pct} ${c}`} transform="rotate(-90 65 65)"/>
      <text x="65" y="68" textAnchor="middle" fontSize="24" fontWeight="800" fill="#fff" fontFamily="Outfit, sans-serif">{formatElapsed(elapsedSec)}</text>
      <text x="65" y="86" textAnchor="middle" fontSize="9" fontWeight="800" letterSpacing="1.8" fill="rgba(255,255,255,0.5)" fontFamily="Outfit, sans-serif">FOCUS</text>
    </svg>
  );
}

function flattenPresenceState(state) {
  return Object.keys(state || {}).flatMap(key => state[key]).filter(Boolean);
}

function mergePresence(realtimeRows, databaseRows) {
  const byUser = new Map();
  (databaseRows || []).forEach(row => {
    if (!row || !row.user_id) return;
    byUser.set(row.user_id, row);
  });
  (realtimeRows || []).forEach(row => {
    if (!row || !row.user_id) return;
    byUser.set(row.user_id, Object.assign({}, byUser.get(row.user_id) || {}, row));
  });
  return Array.from(byUser.values()).sort((a, b) => {
    return new Date(a.joined_at || 0).getTime() - new Date(b.joined_at || 0).getTime();
  });
}

function cleanGoal(goal) {
  return String(goal || '').replace(/\s+/g, ' ').trim().slice(0, 180);
}

function cleanReportText(value) {
  return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 1000);
}

function getSafeDisplayName(user, profile) {
  if (profile && profile.codename) return cleanDisplayName(profile.codename);
  if (profile && profile.display_name) return cleanDisplayName(profile.display_name);
  if (!user) return 'Student';
  return 'Student ' + shortStudentCode(user.id);
}

function cleanDisplayName(name) {
  return String(name || '').replace(/[^a-zA-Z0-9_. -]/g, '').replace(/\s+/g, ' ').trim().slice(0, 24) || 'Student';
}

function shortStudentCode(seed) {
  const s = String(seed || 'student');
  let n = 0;
  for (let i = 0; i < s.length; i++) n = (n * 31 + s.charCodeAt(i)) % 46656;
  return n.toString(36).toUpperCase().padStart(3, '0').slice(-3);
}

function pickCharId(seed) {
  const ids = ['leaf', 'cell', 'beaker', 'volt', 'mole', 'prism', 'ph', 'atom', 'wave', 'sigma'];
  const s = String(seed || 'student');
  let n = 0;
  for (let i = 0; i < s.length; i++) n = (n + s.charCodeAt(i)) % ids.length;
  return ids[n];
}

function formatElapsed(sec) {
  const total = Math.max(0, Number(sec) || 0);
  const mins = Math.floor(total / 60);
  const secs = total % 60;
  return String(mins).padStart(2, '0') + ':' + String(secs).padStart(2, '0');
}

function roomLabel(room) {
  if (!room) return 'Open study';
  const bits = [];
  if (room.year_group) bits.push('Year ' + room.year_group);
  if (room.subject) bits.push(String(room.subject).replace('-', ' '));
  if (room.module_label) bits.push(room.module_label);
  return bits.join(' · ') || 'Open study';
}

function localSessionKey(userId) {
  return 'hsc_focus_sessions_' + userId;
}

function loadLocalSessions(userId) {
  try {
    return JSON.parse(localStorage.getItem(localSessionKey(userId)) || '[]');
  } catch (e) {
    return [];
  }
}

function saveLocalSession(userId, payload) {
  const existing = loadLocalSessions(userId);
  localStorage.setItem(localSessionKey(userId), JSON.stringify([payload].concat(existing).slice(0, 20)));
}

async function migrateLocalFocusSessions(userId, db) {
  const localRows = loadLocalSessions(userId);
  if (!localRows.length || !db) return false;

  const rows = localRows.map(row => ({
    user_id: userId,
    client_session_id: row.client_session_id || createFocusSessionId(userId, row.joined_at || row.created_at || new Date()),
    room_id: row.room_id || 'quiet-revision',
    goal_text: cleanGoal(row.goal_text) || 'Focused study',
    goal_completed: Boolean(row.goal_completed),
    focus_minutes: Math.max(1, Math.min(720, Number(row.focus_minutes) || 1)),
    joined_at: row.joined_at || new Date().toISOString(),
    left_at: row.left_at || row.joined_at || new Date().toISOString()
  }));

  const result = await db.from('focus_room_sessions')
    .upsert(rows, { onConflict: 'user_id,client_session_id' });
  if (result.error) return false;
  localStorage.removeItem(localSessionKey(userId));
  return true;
}

function createFocusSessionId(userId, joinedAt) {
  const base = String(userId || 'student') + '|' + new Date(joinedAt || Date.now()).toISOString();
  let n = 0;
  for (let i = 0; i < base.length; i++) n = (n * 33 + base.charCodeAt(i)) >>> 0;
  return 'fr_' + n.toString(36) + '_' + Math.floor(new Date(joinedAt || Date.now()).getTime() / 1000).toString(36);
}

function Idea10() {
  return (
    <IdeaShell
      num="10"
      title="Focus"
      titleEm="Rooms"
      tagline={<><strong>Live group study, no Zoom call.</strong> Drop into a quiet study room, set one goal, and log the block. The MVP is presence-only by design: no chat, no video, no DMs.</>}
      pills={[
        { kind: 'rose',  icon: 'flame', label: 'MVP · focus logs' },
        { kind: 'green', icon: 'users', label: 'Presence only' },
      ]}
      stats={[
        { num: '25m', label: 'Default focus block', sub: 'The first loop is habit formation: join, study, mark goal, leave.' },
        { num: '0', label: 'Chat surfaces', sub: 'Communication waits until reporting, moderation, and retention rules exist.', dark: true },
      ]}
      pros={[
        '<strong>Solves the "I can\'t make myself sit down"</strong> problem without adding a public social feed.',
        'Avatar presence gives accountability while avoiding video, DMs, and open chat in the first release.',
        'Focus logs create the data foundation for future Study Squads and weak-topic sessions.',
        'Graceful local fallback means the page can be tested before the Supabase migration is approved.',
      ]}
    >
      <FocusRooms/>
    </IdeaShell>
  );
}

window.Idea10 = Idea10;
window.FocusRooms = FocusRooms;
