/*
 * FOCUSFORGE - Complete Study Planner App with Google Docs Integration
 *
 * SETUP INSTRUCTIONS:
 * 1. Go to https://console.firebase.google.com
 * 2. Create a new project
 * 3. Enable Authentication > Sign-in method:
 *    - Enable "Google" provider
 *    - Enable "Email/Password" provider
 * 4. Create a Firestore database (start in test mode)
 * 5. Copy your Firebase config from Project Settings
 * 6. Replace the firebaseConfig object below with your credentials
 * 7. Serve via: python3 -m http.server 3000
 * 8. Open http://localhost:3000/FocusForge.html
 */

const firebaseConfig = {
  apiKey: "AIzaSyBcCieJinM9vYOckUDn4jiykvMOeupPtEw",
  authDomain: "focus-forge-87bb8.firebaseapp.com",
  projectId: "focus-forge-87bb8",
  storageBucket: "focus-forge-87bb8.firebasestorage.app",
  messagingSenderId: "270801670251",
  appId: "1:270801670251:web:4e175ef077b205f94f612e"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const db = firebase.firestore();

// Enable offline-first persistence (IndexedDB cache). Multi-tab safe.
// Must run BEFORE any Firestore reads/writes — we do it immediately after db init.
db.enablePersistence({ synchronizeTabs: true }).catch((err) => {
  if (err.code === 'failed-precondition') {
    console.warn('Firestore persistence: multiple tabs open — only one can enable offline cache.');
  } else if (err.code === 'unimplemented') {
    console.warn('Firestore persistence: not supported in this browser.');
  }
});

// Patch onSnapshot to always include an error handler (prevents Firestore from
// entering a tight internal retry loop on queries needing a missing composite index)
(function patchOnSnapshot() {
  const patchProto = (proto) => {
    const orig = proto.onSnapshot;
    if (!orig) return;
    proto.onSnapshot = function(...args) {
      // If no error handler provided, add one
      if (args.length === 1 && typeof args[0] === 'function') {
        return orig.call(this, args[0], (err) => console.warn('onSnapshot error (handled):', err.message));
      }
      if (args.length === 1 && typeof args[0] === 'object' && args[0].next && !args[0].error) {
        args[0].error = (err) => console.warn('onSnapshot error (handled):', err.message);
      }
      return orig.apply(this, args);
    };
  };
  patchProto(firebase.firestore.Query.prototype);
  patchProto(firebase.firestore.DocumentReference.prototype);
  patchProto(firebase.firestore.CollectionReference.prototype);
})();

// ============= UTILITY FUNCTIONS =============
const getLevel = (points) => Math.floor(points / 100) + 1;

// Streak updater — call on any successful study activity.
// Returns a promise so callers can chain if they need the new streak.
const updateStreak = async (uid, currentUserData) => {
  if (!uid) return;
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const todayKey = today.toISOString().slice(0, 10);
  const last = currentUserData?.lastStudyDate || null;
  if (last === todayKey) return; // already counted today

  const y = new Date(today.getTime() - 86400000);
  const yesterdayKey = y.toISOString().slice(0, 10);
  const prevStreak = currentUserData?.currentStreak || 0;
  const newStreak = last === yesterdayKey ? prevStreak + 1 : 1;

  await db.collection('users').doc(uid).update({
    currentStreak: newStreak,
    lastStudyDate: todayKey,
    longestStreak: Math.max(currentUserData?.longestStreak || 0, newStreak)
  });
};

const getLevelProgress = (points) => {
  const currentLevel = getLevel(points);
  const pointsForCurrentLevel = (currentLevel - 1) * 100;
  const pointsForNextLevel = currentLevel * 100;
  const currentProgress = points - pointsForCurrentLevel;
  const neededForNext = pointsForNextLevel - pointsForCurrentLevel;
  return {
    current: currentLevel,
    needed: neededForNext,
    progress: Math.min(100, Math.floor((currentProgress / neededForNext) * 100))
  };
};

const formatTime = (seconds) => {
  const mins = Math.floor(seconds / 60);
  const secs = seconds % 60;
  return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};

// Shared timer state so the topbar can show a running indicator
const TimerContext = React.createContext({ isRunning: false, seconds: 0, isBreak: false });
const useTimerBroadcast = () => React.useContext(TimerContext);

const formatMinutes = (totalMinutes) => {
  const hours = Math.floor(totalMinutes / 60);
  const mins = totalMinutes % 60;
  if (hours === 0) return `${mins}m`;
  return `${hours}h ${mins}m`;
};

// ============= XP MULTIPLIERS =============
// Computes total multiplier based on context: streak, focus mode, daily goal exceeded
const computeMultiplier = (userData, ctx = {}) => {
  let mult = 1;
  const reasons = [];
  const streak = userData?.currentStreak || 0;
  if (streak >= 7) { mult *= 2; reasons.push(`${streak}-day streak ×2`); }
  else if (streak >= 3) { mult *= 1.5; reasons.push(`${streak}-day streak ×1.5`); }
  if (ctx.focusMode) { mult *= 1.5; reasons.push('Focus Mode ×1.5'); }
  if (ctx.dailyGoalBeaten) { mult *= 1.25; reasons.push('Daily goal beaten ×1.25'); }
  return { multiplier: mult, reasons };
};

// Award XP with multiplier + fire an event so UI can animate +N
const awardXP = async (uid, basePoints, ctx = {}) => {
  if (!uid) return { total: basePoints, multiplier: 1, reasons: [] };
  const userDoc = await db.collection('users').doc(uid).get();
  const userData = userDoc.data();
  const { multiplier, reasons } = computeMultiplier(userData, ctx);
  const total = Math.round(basePoints * multiplier);
  await db.collection('users').doc(uid).update({
    points: firebase.firestore.FieldValue.increment(total)
  });
  // Dispatch UI event so PointsFloat / animation can show it
  window.dispatchEvent(new CustomEvent('xp-awarded', {
    detail: { base: basePoints, total, multiplier, reasons }
  }));
  return { total, multiplier, reasons };
};

const playSound = (frequency = 800, duration = 200) => {
  try {
    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
    // Some browsers start the context suspended until a user gesture — resume so we
    // don't silently drop the alarm if audio was auto-suspended.
    if (audioContext.state === 'suspended' && audioContext.resume) audioContext.resume();
    const oscillator = audioContext.createOscillator();
    const gainNode = audioContext.createGain();

    oscillator.connect(gainNode);
    gainNode.connect(audioContext.destination);

    oscillator.frequency.value = frequency;
    oscillator.type = 'sine';

    gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
    gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration / 1000);

    oscillator.start(audioContext.currentTime);
    oscillator.stop(audioContext.currentTime + duration / 1000);
  } catch (e) {
    console.log('Audio context not available');
  }
};

// Sound packs (unlocked in shop swap the alarm melody)
const SOUND_PACKS = {
  default: [
    { freq: 880, delay: 0 }, { freq: 880, delay: 300 }, { freq: 880, delay: 600 },
    { freq: 1100, delay: 1000 }, { freq: 1100, delay: 1300 }, { freq: 1100, delay: 1600 },
  ],
  'sound-bell': [
    // Gentle zen-style bell cascade
    { freq: 523, delay: 0 }, { freq: 659, delay: 250 }, { freq: 783, delay: 500 }, { freq: 1046, delay: 900 }
  ],
  'sound-epic': [
    // Fanfare — ta-da!
    { freq: 523, delay: 0 }, { freq: 659, delay: 180 }, { freq: 783, delay: 360 },
    { freq: 1046, delay: 540 }, { freq: 1318, delay: 720 }, { freq: 1046, delay: 1000 }, { freq: 1568, delay: 1200 }
  ]
};

const playAlarm = () => {
  const pack = window.FOCUSFORGE_ACTIVE_SOUND || 'default';
  const beeps = SOUND_PACKS[pack] || SOUND_PACKS.default;
  beeps.forEach(({ freq, delay }) => {
    setTimeout(() => playSound(freq, pack === 'sound-bell' ? 400 : 250), delay);
  });
};

const isOverdue = (dueDate) => {
  const due = new Date(dueDate);
  const now = new Date();
  return due < now && due.toDateString() !== now.toDateString();
};

// Notification utilities
const sendNotification = (title, body) => {
  try {
    if (typeof Notification === 'undefined') return;
    if (Notification.permission !== 'granted') return;
    new Notification(title, { body, icon: '⚡' });
  } catch (e) {
    // Permission could have been revoked between check and construct; swallow.
    console.debug('[FocusForge] Notification suppressed:', e?.message || e);
  }
};

const requestNotificationPermission = async () => {
  if ('Notification' in window && Notification.permission === 'default') {
    await Notification.requestPermission();
  }
};

// Scheduled-notification runner — fires when app loads or window regains focus.
// Once per day: checks homework due soon + streak at risk. Dedup via localStorage.
const runScheduledChecks = async (uid, userData) => {
  if (!uid || !userData) return;
  if (Notification.permission !== 'granted') return;
  const prefs = userData.notificationPrefs || {};

  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const todayKey = today.toISOString().slice(0, 10);
  const dedupKey = 'ff-last-notify-' + todayKey;
  if (localStorage.getItem(dedupKey) === '1') return;  // already ran today

  try {
    // 1. Homework due tomorrow/today reminder
    if (prefs.homeworkDue !== false) {
      const hwSnap = await db.collection('users').doc(uid).collection('homework')
        .where('completed', '==', false).get();
      const tomorrow = new Date(today.getTime() + 86400000);
      const tomorrowKey = tomorrow.toISOString().slice(0, 10);
      const dueSoon = hwSnap.docs
        .map(d => ({ id: d.id, ...d.data() }))
        .filter(h => h.dueDate === todayKey || h.dueDate === tomorrowKey);
      if (dueSoon.length > 0) {
        const first = dueSoon[0];
        sendNotification(
          `📚 ${dueSoon.length} homework${dueSoon.length === 1 ? '' : ' items'} due soon`,
          dueSoon.length === 1
            ? `${first.title} is due ${first.dueDate === todayKey ? 'today' : 'tomorrow'}`
            : `Check your homework page — ${dueSoon.length} assignments need attention`
        );
      }
    }

    // 2. Streak-at-risk warning — user has an active streak but hasn't studied today
    if (prefs.streakWarning !== false) {
      const streak = userData.currentStreak || 0;
      const last = userData.lastStudyDate;
      const yesterday = new Date(today.getTime() - 86400000).toISOString().slice(0, 10);
      // If you studied yesterday but not today, and it's already evening, warn
      const hour = new Date().getHours();
      if (streak >= 3 && last === yesterday && hour >= 17) {
        sendNotification(
          `🔥 Your ${streak}-day streak is at risk`,
          `Study at least once today to keep it alive!`
        );
      }
    }

    localStorage.setItem(dedupKey, '1');
    // Housekeeping: purge old ff-last-notify-* keys (older than 7 days) so localStorage doesn't grow forever
    const cutoff = new Date(today.getTime() - 7 * 86400000).toISOString().slice(0, 10);
    for (let i = localStorage.length - 1; i >= 0; i--) {
      const k = localStorage.key(i);
      if (k && k.startsWith('ff-last-notify-') && k.slice('ff-last-notify-'.length) < cutoff) {
        localStorage.removeItem(k);
      }
    }
  } catch (e) {
    console.warn('runScheduledChecks error:', e.message);
  }
};

// Extract document ID from Google Docs URL
const extractDocId = (url) => {
  const match = url.match(/\/document\/d\/([a-zA-Z0-9-_]+)/);
  return match ? match[1] : null;
};

// Get the current day of the week
const getDayName = () => {
  const days = ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'];
  return days[new Date().getDay()];
};

// Fetch Google Docs - tries Docs API first, falls back to Drive API for uploaded .docx files
const fetchGoogleDocs = async (docId, accessToken) => {
  const authHeaders = { 'Authorization': 'Bearer ' + accessToken };

  // Helper to parse error responses
  const getErrorMessage = async (resp) => {
    try {
      const body = await resp.json();
      return body.error ? (body.error.message || body.error.status || JSON.stringify(body.error)) : JSON.stringify(body);
    } catch(e) {
      return 'HTTP ' + resp.status;
    }
  };

  // Step 1: Try native Google Docs API
  console.log('[Sync] Trying Google Docs API for docId:', docId);
  const docsResp = await fetch('https://docs.googleapis.com/v1/documents/' + docId, { headers: authHeaders });

  if (docsResp.ok) {
    console.log('[Sync] Google Docs API succeeded');
    return await docsResp.json();
  }

  // Only clear token on actual 401 (invalid/expired token)
  if (docsResp.status === 401) {
    localStorage.removeItem('googleAccessToken');
    throw new Error('Your Google token has expired. Click Reconnect Google to get a fresh token.');
  }

  console.log('[Sync] Google Docs API returned', docsResp.status, '- trying Drive API fallbacks...');
  const docsError = await getErrorMessage(docsResp);
  console.log('[Sync] Docs API error detail:', docsError);

  // Step 2: Try Drive API export (works for native Google Docs opened from uploaded files)
  console.log('[Sync] Trying Drive API export...');
  const exportResp = await fetch(
    'https://www.googleapis.com/drive/v3/files/' + docId + '/export?mimeType=text/html',
    { headers: authHeaders }
  );

  if (exportResp.ok) {
    console.log('[Sync] Drive API export succeeded');
    const html = await exportResp.text();
    return parseHtmlToDocsFormat(html);
  }

  if (exportResp.status === 401) {
    localStorage.removeItem('googleAccessToken');
    throw new Error('Your Google token has expired. Click Reconnect Google to get a fresh token.');
  }

  const exportError = await getErrorMessage(exportResp);
  console.log('[Sync] Drive export returned', exportResp.status, ':', exportError);

  // Step 3: Try Drive API direct download (works for uploaded .docx files)
  console.log('[Sync] Trying Drive API direct download...');
  const downloadResp = await fetch(
    'https://www.googleapis.com/drive/v3/files/' + docId + '?alt=media',
    { headers: authHeaders }
  );

  if (downloadResp.ok) {
    console.log('[Sync] Drive API download succeeded');
    const blob = await downloadResp.blob();
    const arrayBuf = await blob.arrayBuffer();
    const bytes = new Uint8Array(arrayBuf);

    // Check if it's a ZIP/DOCX file (starts with PK)
    if (bytes[0] === 0x50 && bytes[1] === 0x4B) {
      console.log('[Sync] Detected .docx file, parsing with JSZip...');
      return await parseDocxToDocsFormat(arrayBuf);
    }

    // Otherwise try as text
    const content = new TextDecoder().decode(bytes);
    if (content.trim().startsWith('<')) {
      return parseHtmlToDocsFormat(content);
    }
    return { body: { content: [{ paragraph: { elements: [{ textRun: { content: content } }] } }] } };
  }

  if (downloadResp.status === 401) {
    localStorage.removeItem('googleAccessToken');
    throw new Error('Your Google token has expired. Click Reconnect Google to get a fresh token.');
  }

  const downloadError = await getErrorMessage(downloadResp);
  console.log('[Sync] Drive download returned', downloadResp.status, ':', downloadError);

  // Step 4: If 403 on all attempts, it's likely an API enablement or permissions issue
  if (docsResp.status === 403 || exportResp.status === 403 || downloadResp.status === 403) {
    throw new Error(
      'Google API returned 403 Forbidden. This usually means:\n' +
      '1. The Google Docs API or Drive API is not enabled in your Firebase project.\n' +
      '2. Go to console.cloud.google.com → APIs & Services → Enable "Google Docs API" and "Google Drive API".\n' +
      'Detail: ' + (docsError || exportError || downloadError)
    );
  }

  throw new Error('Could not fetch document. Docs API: ' + docsError + ', Drive export: ' + exportError + ', Drive download: ' + downloadError);
};

// Parse .docx (ZIP) file and convert to docs-compatible format
const parseDocxToDocsFormat = async (arrayBuffer) => {
  const zip = await JSZip.loadAsync(arrayBuffer);
  const docXml = await zip.file('word/document.xml').async('string');
  const parser = new DOMParser();
  const xmlDoc = parser.parseFromString(docXml, 'application/xml');

  const content = [];
  const ns = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';

  // Helper to get all text from a node
  const getNodeText = (node) => {
    let text = '';
    const tNodes = node.getElementsByTagNameNS(ns, 't');
    for (const t of tNodes) {
      text += t.textContent || '';
    }
    return text;
  };

  const body = xmlDoc.getElementsByTagNameNS(ns, 'body')[0];
  if (!body) return { body: { content: [] } };

  for (const child of body.children) {
    const localName = child.localName;

    if (localName === 'tbl') {
      // Parse table
      const tableRows = [];
      const trNodes = Array.from(child.children).filter(c => c.localName === 'tr');
      for (const tr of trNodes) {
        const tableCells = [];
        const tcNodes = Array.from(tr.children).filter(c => c.localName === 'tc');
        for (const tc of tcNodes) {
          const cellText = getNodeText(tc);
          tableCells.push({
            content: [{
              paragraph: {
                elements: [{ textRun: { content: cellText } }]
              }
            }]
          });
        }
        tableRows.push({ tableCells });
      }
      content.push({ table: { tableRows } });
    } else if (localName === 'p') {
      // Parse paragraph
      const text = getNodeText(child);
      if (text.trim()) {
        content.push({
          paragraph: {
            elements: [{ textRun: { content: text } }]
          }
        });
      }
    }
  }

  console.log('[Sync] Parsed docx: found', content.filter(c => c.table).length, 'tables,', content.filter(c => c.paragraph).length, 'paragraphs');
  return { body: { content } };
};

// Convert HTML table content to a format compatible with parseGoogleDocsForHomework
const parseHtmlToDocsFormat = (html) => {
  const parser = new DOMParser();
  const doc = parser.parseFromString(html, 'text/html');
  const content = [];

  // Process all tables in the document
  const tables = doc.querySelectorAll('table');
  tables.forEach(table => {
    const tableRows = [];
    const rows = table.querySelectorAll('tr');
    rows.forEach(row => {
      const tableCells = [];
      const cells = row.querySelectorAll('td, th');
      cells.forEach(cell => {
        tableCells.push({
          content: [{
            paragraph: {
              elements: [{
                textRun: { content: cell.textContent.trim() }
              }]
            }
          }]
        });
      });
      tableRows.push({ tableCells });
    });
    content.push({ table: { tableRows } });
  });

  // Also include non-table text as paragraphs
  const allElements = doc.body.children;
  for (const el of allElements) {
    if (el.tagName !== 'TABLE') {
      content.push({
        paragraph: {
          elements: [{
            textRun: { content: el.textContent.trim() }
          }]
        }
      });
    }
  }

  return { body: { content } };
};

// Extract text from a table cell
const getCellText = (cell) => {
  if (!cell || !cell.content) return '';
  let text = '';
  cell.content.forEach(para => {
    if (para.paragraph && para.paragraph.elements) {
      para.paragraph.elements.forEach(elem => {
        if (elem.textRun && elem.textRun.content) {
          text += elem.textRun.content;
        }
      });
    }
  });
  return text.trim();
};

// Parse Google Docs tables for homework
const parseGoogleDocsForHomework = (docContent, section, dayName) => {
  const homework = [];

  if (!docContent.body || !docContent.body.content) return homework;

  const content = docContent.body.content;

  console.log('[Parser] Looking for section:', section, 'day:', dayName);

  for (let i = 0; i < content.length; i++) {
    const element = content[i];

    if (element.table) {
      const rows = element.table.tableRows;
      if (!rows || rows.length === 0) continue;

      // Check if this table belongs to the target section
      const firstRowText = rows[0] && rows[0].tableCells ? getCellText(rows[0].tableCells[0]) : '';
      const sectionUpper = section.toUpperCase().replace(/\s+/g, '');
      const firstRowUpper = firstRowText.toUpperCase().replace(/\s+/g, '');

      if (!firstRowUpper.includes(sectionUpper)) continue;

      console.log('[Parser] Found matching table section:', firstRowText.substring(0, 60));

      // Scan rows for the target day
      for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
        const row = rows[rowIdx];
        if (!row.tableCells || row.tableCells.length === 0) continue;

        const firstCell = getCellText(row.tableCells[0]).toUpperCase().trim();

        if (firstCell.includes(dayName)) {
          console.log('[Parser] Found day row at index', rowIdx, ':', firstCell);

          // Structure: day row has subjects in cols 1+
          // Then TOPIC row, then H/w row, then Date of Submission row
          const subjects = [];
          for (let colIdx = 1; colIdx < row.tableCells.length; colIdx++) {
            const subjectName = getCellText(row.tableCells[colIdx]).trim();
            if (subjectName && subjectName.toUpperCase() !== 'NIL' && subjectName !== '') {
              subjects.push({ name: subjectName, colIdx });
            }
          }

          console.log('[Parser] Found subjects:', subjects.map(s => s.name));

          // Find the H/w row (look for a row starting with "H/w" or "H/W" or "HW" within next few rows)
          let hwRow = null;
          let dateRow = null;
          for (let offset = 1; offset <= 4 && rowIdx + offset < rows.length; offset++) {
            const checkRow = rows[rowIdx + offset];
            if (!checkRow.tableCells) continue;
            const label = getCellText(checkRow.tableCells[0]).toUpperCase().trim();
            if (label.includes('H/W') || label.includes('HW') || label === 'HOMEWORK') {
              hwRow = checkRow;
            }
            if (label.includes('DATE') || label.includes('SUBMISSION')) {
              dateRow = checkRow;
            }
          }

          if (!hwRow) {
            // Fallback: assume H/w is 2 rows after day row (skip TOPIC)
            hwRow = rowIdx + 2 < rows.length ? rows[rowIdx + 2] : null;
            dateRow = rowIdx + 3 < rows.length ? rows[rowIdx + 3] : null;
          }

          if (hwRow) {
            subjects.forEach(subject => {
              const hwCell = hwRow.tableCells && hwRow.tableCells[subject.colIdx];
              const hwText = getCellText(hwCell).trim();

              const dateCell = dateRow && dateRow.tableCells && dateRow.tableCells[subject.colIdx];
              let dateText = getCellText(dateCell).trim();

              // Try to normalize date format
              if (dateText && !dateText.match(/^\d{4}-/)) {
                // Try parsing dates like "7.04.2026" or "7/4/26"
                const parsed = new Date(dateText.replace(/\./g, '/'));
                if (!isNaN(parsed)) {
                  dateText = parsed.toISOString().split('T')[0];
                }
              }

              if (hwText && hwText.toUpperCase() !== 'NIL' && hwText !== '' && hwText !== '-') {
                homework.push({
                  subject: subject.name,
                  task: hwText,
                  dueDate: dateText || new Date().toISOString().split('T')[0]
                });
              }
            });
          }

          console.log('[Parser] Found', homework.length, 'homework items so far');
        }
      }
    }
  }

  console.log('[Parser] Total homework items found:', homework.length);
  return homework;
};

// Google Auth SVG
const GoogleLogo = () => (
  <svg width="18" height="18" viewBox="0 0 24 24">
    <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/>
    <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
    <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
    <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
  </svg>
);

// Zap icon for logo
const ZapIcon = () => (
  <svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor">
    <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
  </svg>
);

// ============= MARKDOWN HELPER (XSS-safe) =============
// Any content rendered with dangerouslySetInnerHTML MUST go through this.
// Falls back to plain-text line breaks if libraries haven't loaded yet.
const renderMarkdown = (raw) => {
  const text = raw || '';
  const hasMarked = typeof marked !== 'undefined' && marked.parse;
  const hasPurify = typeof DOMPurify !== 'undefined' && DOMPurify.sanitize;
  if (!hasMarked) return text.replace(/\n/g, '<br>');
  const html = marked.parse(text);
  return hasPurify ? DOMPurify.sanitize(html) : html;
};

// ============= FIREBASE HELPERS =============
// `overrideDisplayName` lets the signup flow pass the name entered on the LoginPage
// (which Firebase itself doesn't know about during email-link auth).
const createUserDoc = async (user, overrideDisplayName) => {
  const docRef = db.collection('users').doc(user.uid);
  const doc = await docRef.get();
  const fromOverride = (overrideDisplayName || '').trim();
  const fromAuth = (user.displayName || '').trim();
  const resolvedName = fromOverride || fromAuth || 'User';

  if (!doc.exists) {
    await docRef.set({
      displayName: resolvedName,
      email: user.email,
      photoURL: user.photoURL || '',
      points: 0,
      totalStudyMinutes: 0,
      totalPomodoros: 0,
      currentStreak: 0,
      lastStudyDate: null,
      createdAt: new Date()
    });
  } else {
    // Back-fill: if existing doc has the stub "User" name but we now know a real name
    // (from signup override OR from Firebase Auth — e.g. Google OAuth doc created
    // before displayName was saved), update it so the greeting personalises.
    const stored = (doc.data()?.displayName || '').trim();
    const realName = fromOverride || fromAuth;
    if (realName && (stored === 'User' || stored === '')) {
      await docRef.update({ displayName: realName });
    }
  }
  // Also update Firebase Auth profile so other surfaces that read `user.displayName` work.
  if (fromOverride && !user.displayName) {
    try { await user.updateProfile({ displayName: fromOverride }); } catch {}
  }
  return docRef;
};

// ============= COMPONENTS =============

// =====================================================================
//  AUTH FLOW (Firebase only — no third-party services)
//  • Sign-up   → enter email → Firebase magic link → click → set password
//  • Sign-in   → email + password (instant, no link)
//  • Forgot PW → enter email → Firebase magic link → click → set new password
// =====================================================================

const EMAIL_FOR_SIGNIN_KEY     = 'ff-email-for-signin';
const PENDING_PW_INTENT        = 'ff-pending-pw-intent';   // 'signup' | 'reset'
const SIGNUP_DISPLAY_NAME_KEY  = 'ff-signup-display-name'; // captures name entered during signup so we can apply it after magic-link return

// `intent` is 'signup' or 'reset' — encoded into the return URL so we know which copy
// to show after Firebase signs the user back in.
const sendEmailLink = async (email, intent) => {
  const actionCodeSettings = {
    url: window.location.origin + window.location.pathname + '?intent=' + intent,
    handleCodeInApp: true,
  };
  await auth.sendSignInLinkToEmail(email, actionCodeSettings);
  localStorage.setItem(EMAIL_FOR_SIGNIN_KEY, email);
  localStorage.setItem(PENDING_PW_INTENT, intent);
};

// Login Page — Google + Email
//   • Sign-up   → enter email, Firebase mails a magic link, user clicks it, sets password
//   • Sign-in   → email + password (no link, instant)
//   • Forgot PW → enter email, Firebase mails a magic link, user clicks it, sets new password
const LoginPage = () => {
  // 'choose' | 'signin' | 'signup' | 'forgot' | 'link-sent'
  const [mode, setMode] = React.useState('choose');
  const [intent, setIntent] = React.useState('signup'); // tracks which flow is on the link-sent screen
  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [displayName, setDisplayName] = React.useState(''); // captured on signup, applied post-magic-link
  const [error, setError] = React.useState('');
  const [info, setInfo] = React.useState('');
  const [loading, setLoading] = React.useState(false);

  const reset = () => {
    setMode('choose');
    setEmail(''); setPassword(''); setDisplayName('');
    setError(''); setInfo('');
  };

  const handleGoogleLogin = async () => {
    setError('');
    try {
      if (firebaseConfig.apiKey === 'YOUR_API_KEY') {
        setError('Firebase is not configured yet. Replace the firebaseConfig values with your project credentials.');
        return;
      }
      const provider = new firebase.auth.GoogleAuthProvider();
      const result = await auth.signInWithPopup(provider);
      if (result && result.user) await createUserDoc(result.user);
    } catch (err) {
      if (err.code === 'auth/popup-blocked') setError('Popup was blocked. Please allow popups and try again.');
      else if (err.code === 'auth/popup-closed-by-user') setError('Sign-in was cancelled.');
      else setError(err.message);
    }
  };

  // ---------- Sign in (email + password — no link) ----------
  const handleSignIn = async (e) => {
    e?.preventDefault();
    if (!email || !password) return;
    setError(''); setLoading(true);
    try {
      const result = await auth.signInWithEmailAndPassword(email.trim(), password);
      if (result?.user) await createUserDoc(result.user);
    } catch (err) {
      const msg = err.code === 'auth/user-not-found' ? 'No account with that email. Try signing up.'
        : err.code === 'auth/wrong-password' || err.code === 'auth/invalid-credential' ? 'Incorrect password.'
        : err.code === 'auth/invalid-email' ? 'Invalid email address.'
        : err.message;
      setError(msg);
    } finally { setLoading(false); }
  };

  // ---------- Sign up — Firebase magic link ----------
  const handleSendSignupLink = async (e) => {
    e?.preventDefault();
    setError(''); setInfo('');
    const trimmedName = displayName.trim();
    const trimmed = email.trim().toLowerCase();
    if (!trimmedName) { setError('Please enter your name.'); return; }
    if (!trimmed || !trimmed.includes('@')) { setError('Enter a valid email address.'); return; }
    setLoading(true);
    try {
      const methods = await auth.fetchSignInMethodsForEmail(trimmed);
      if (methods.length > 0) {
        setError('An account with this email already exists. Try signing in instead.');
        setLoading(false); return;
      }
      await sendEmailLink(trimmed, 'signup');
      // Persist the name so it survives the out-of-app email round-trip.
      localStorage.setItem(SIGNUP_DISPLAY_NAME_KEY, trimmedName);
      setIntent('signup');
      setMode('link-sent');
      setInfo(`✓ Sign-up link sent to ${trimmed}. Click it from your inbox to set your password.`);
    } catch (err) {
      setError(mapAuthLinkError(err));
    } finally { setLoading(false); }
  };

  // ---------- Forgot password — Firebase magic link ----------
  const handleForgotSendLink = async (e) => {
    e?.preventDefault();
    setError(''); setInfo('');
    const trimmed = email.trim().toLowerCase();
    if (!trimmed || !trimmed.includes('@')) { setError('Enter a valid email address.'); return; }
    setLoading(true);
    try {
      // Check account exists AND has a password credential before sending.
      // Prevents email-enumeration and avoids sending reset links to Google-only users.
      const methods = await auth.fetchSignInMethodsForEmail(trimmed);
      if (methods.length === 0) {
        setError('No account with that email. Try signing up instead.');
        setLoading(false); return;
      }
      if (!methods.includes('password') && !methods.includes('emailLink')) {
        setError('This account signs in with ' + methods[0] + '. Use that provider instead.');
        setLoading(false); return;
      }
      await sendEmailLink(trimmed, 'reset');
      setIntent('reset');
      setMode('link-sent');
      setInfo(`✓ Reset link sent to ${trimmed}. Click it from your inbox to set a new password.`);
    } catch (err) {
      setError(mapAuthLinkError(err));
    } finally { setLoading(false); }
  };

  // Resend the same kind of link from link-sent screen
  const resendLink = async () => {
    setError(''); setInfo(''); setLoading(true);
    try {
      await sendEmailLink(email.trim().toLowerCase(), intent);
      setInfo('✓ New link sent.');
    } catch (err) { setError(mapAuthLinkError(err)); }
    finally { setLoading(false); }
  };

  return (
    <div className="login-page">
      <div className="login-card">
        <div className="login-logo">
          <div style={{ color: '#06b6d4' }}><ZapIcon /></div>
          <div className="sidebar-logo-text">FocusForge</div>
        </div>
        <p className="login-tagline">Level up your focus. Master your mind.</p>

        {error && <div className="login-error" style={{ marginBottom: '12px' }}>{error}</div>}
        {info && (
          <div style={{
            marginBottom: '12px', padding: '10px 12px', borderRadius: '8px',
            background: 'rgba(6,182,212,0.08)', color: '#06b6d4',
            fontSize: '13px', border: '1px solid rgba(6,182,212,0.2)'
          }}>{info}</div>
        )}

        {/* ───── INITIAL CHOICE ───── */}
        {mode === 'choose' && (
          <>
            <button className="google-btn" onClick={handleGoogleLogin}>
              <GoogleLogo />
              Continue with Google
            </button>

            <div className="login-divider"><span>or</span></div>

            <button
              className="btn btn-secondary"
              style={{ width: '100%', padding: '12px', marginBottom: '8px' }}
              onClick={() => { setError(''); setMode('signin'); }}
            >
              Sign in with email
            </button>
            <button
              className="btn"
              style={{
                width: '100%', padding: '12px',
                background: 'transparent', color: '#06b6d4',
                border: '1px solid rgba(6,182,212,0.3)'
              }}
              onClick={() => { setError(''); setMode('signup'); }}
            >
              Sign up with email
            </button>

            <p style={{ fontSize: '12px', color: 'var(--text-subtle)', marginTop: '16px' }}>
              Your data is private and syncs across devices
            </p>
          </>
        )}

        {/* ───── SIGN IN (email + password — no link required) ───── */}
        {mode === 'signin' && (
          <form onSubmit={handleSignIn} className="email-form">
            <div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '12px', textAlign: 'left' }}>Sign in to your account</div>
            <input type="email" placeholder="Email address" value={email} onChange={e => setEmail(e.target.value)} required />
            <input type="password" placeholder="Password" value={password} onChange={e => setPassword(e.target.value)} required />
            <button type="submit" className="email-submit-btn" disabled={loading}>{loading ? '...' : 'Sign In'}</button>
            <button
              type="button"
              className="login-toggle"
              style={{ color: '#06b6d4' }}
              onClick={() => { setError(''); setInfo(''); setMode('forgot'); }}
            >
              Forgot password?
            </button>
            <button type="button" className="login-toggle" onClick={reset}>← Back</button>
          </form>
        )}

        {/* ───── SIGN UP — enter name + email, Firebase mails a magic link ───── */}
        {mode === 'signup' && (
          <form onSubmit={handleSendSignupLink} className="email-form">
            <div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '12px', textAlign: 'left' }}>
              Step 1 / 2 — Tell us about you
            </div>
            <input
              type="text"
              placeholder="Your name"
              value={displayName}
              onChange={e => setDisplayName(e.target.value)}
              required autoFocus maxLength={40}
              autoComplete="given-name"
            />
            <input
              type="email"
              placeholder="Email address"
              value={email}
              onChange={e => setEmail(e.target.value)}
              required
              autoComplete="email"
            />
            <div style={{ fontSize: '11px', color: 'var(--text-muted)', textAlign: 'left', lineHeight: 1.5 }}>
              We'll email you a secure sign-up link. Click it to verify your address, then you'll set a password.
            </div>
            <button
              type="submit"
              className="email-submit-btn"
              disabled={loading || !displayName.trim() || !email.trim()}
            >
              {loading ? 'Sending link…' : 'Send sign-up link'}
            </button>
            <button type="button" className="login-toggle" onClick={reset}>← Back</button>
          </form>
        )}

        {/* ───── FORGOT PASSWORD — enter email ───── */}
        {mode === 'forgot' && (
          <form onSubmit={handleForgotSendLink} className="email-form">
            <div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '12px', textAlign: 'left' }}>
              Reset your password — enter the email on your account
            </div>
            <input
              type="email"
              placeholder="Email address"
              value={email}
              onChange={e => setEmail(e.target.value)}
              required autoFocus
            />
            <div style={{ fontSize: '11px', color: 'var(--text-muted)', textAlign: 'left', lineHeight: 1.5 }}>
              We'll email you a secure link. Click it from your inbox to set a new password.
            </div>
            <button type="submit" className="email-submit-btn" disabled={loading || !email.trim()}>
              {loading ? 'Sending link…' : 'Send reset link'}
            </button>
            <button type="button" className="login-toggle" onClick={() => { setError(''); setInfo(''); setMode('signin'); }}>← Back to sign in</button>
          </form>
        )}

        {/* ───── LINK SENT — used for both signup and forgot ───── */}
        {mode === 'link-sent' && (
          <div className="email-form">
            <div style={{ textAlign: 'center', padding: '16px 0' }}>
              <div style={{ fontSize: '44px', marginBottom: '8px' }}>📧</div>
              <div style={{ fontSize: '15px', fontWeight: 600, marginBottom: '6px' }}>Check your inbox</div>
              <div style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: 1.55 }}>
                We sent {intent === 'reset' ? 'a password-reset link' : 'a sign-up link'} to<br />
                <strong style={{ color: 'var(--text-primary)' }}>{email}</strong><br />
                <span style={{ fontSize: '11px' }}>
                  Click the link in the email — it will sign you in and prompt you to {intent === 'reset' ? 'set a new password' : 'create your password'}.
                </span>
              </div>
            </div>
            <button type="button" className="login-toggle" onClick={reset}>← Use a different email</button>
            <button
              type="button"
              className="login-toggle"
              onClick={resendLink}
              disabled={loading}
            >{loading ? 'Resending…' : 'Resend link'}</button>
          </div>
        )}
      </div>
    </div>
  );
};

// Friendly error mapper for sendSignInLinkToEmail failures
const mapAuthLinkError = (err) => (
  err.code === 'auth/invalid-email' ? 'Invalid email address.'
  : err.code === 'auth/missing-android-pkg-name' || err.code === 'auth/missing-continue-uri' ? 'Firebase config issue. Check console.'
  : err.code === 'auth/unauthorized-continue-uri' ? 'Your domain must be whitelisted in Firebase → Authentication → Settings → Authorized domains.'
  : err.code === 'auth/operation-not-allowed' ? 'Email-link sign-in is disabled. Enable it in Firebase → Authentication → Sign-in method → Email/Password → Email link (passwordless).'
  : (err.message || 'Could not send link. Try again.')
);

// ============= POST-LINK PASSWORD GATE =============
// Shown after a user completes magic-link sign-in (signup OR forgot-password).
// They MUST set a password before continuing. Copy adapts to `intent`.
const PasswordCreationGate = ({ intent = 'signup', onDone }) => {
  const isReset = intent === 'reset';
  const [password, setPassword] = React.useState('');
  const [confirmPassword, setConfirmPassword] = React.useState('');
  const [error, setError] = React.useState('');
  const [loading, setLoading] = React.useState(false);

  const submit = async (e) => {
    e?.preventDefault();
    setError('');
    if (!password || !confirmPassword) { setError('Both password fields are required.'); return; }
    if (password.length < 6) { setError('Password must be at least 6 characters.'); return; }
    if (password !== confirmPassword) { setError('Passwords do not match.'); return; }
    setLoading(true);
    try {
      const user = auth.currentUser;
      if (!user) { setError('Session expired. Please sign in again.'); setLoading(false); return; }
      // Fresh account → linkWithCredential adds a password. Existing account → updatePassword.
      const credential = firebase.auth.EmailAuthProvider.credential(user.email, password);
      try {
        await user.linkWithCredential(credential);
      } catch (e) {
        if (e.code === 'auth/provider-already-linked' || e.code === 'auth/credential-already-in-use' || e.code === 'auth/email-already-in-use') {
          await user.updatePassword(password);
        } else { throw e; }
      }
      onDone();
    } catch (err) {
      const msg = err.code === 'auth/requires-recent-login'
        ? 'For security, please request a new link and try again.'
        : err.message;
      setError(msg);
    } finally { setLoading(false); }
  };

  return (
    <div className="login-page">
      <div className="login-card">
        <div className="login-logo">
          <div style={{ color: '#06b6d4' }}><ZapIcon /></div>
          <div className="sidebar-logo-text">FocusForge</div>
        </div>
        <p className="login-tagline">{isReset ? 'Set a new password' : 'One more step — set your password'}</p>

        {error && <div className="login-error" style={{ marginBottom: '12px' }}>{error}</div>}

        <form onSubmit={submit} className="email-form">
          <div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '12px', textAlign: 'left' }}>
            {isReset ? 'Resetting password for' : 'Step 2 / 2 — Create password for'} <strong style={{ color: 'var(--text-primary)' }}>{auth.currentUser?.email}</strong>
          </div>
          <input
            type="password"
            placeholder={(isReset ? 'New password' : 'Password') + ' (min 6 characters)'}
            value={password}
            onChange={e => setPassword(e.target.value)}
            required minLength={6} autoFocus
          />
          <input
            type="password"
            placeholder={isReset ? 'Confirm new password' : 'Confirm password'}
            value={confirmPassword}
            onChange={e => setConfirmPassword(e.target.value)}
            required minLength={6}
            style={confirmPassword && password !== confirmPassword ? { borderColor: '#ef4444' } : {}}
          />
          {confirmPassword && password !== confirmPassword && (
            <div style={{ fontSize: '11px', color: '#ef4444', textAlign: 'left' }}>Passwords don't match</div>
          )}
          <button
            type="submit"
            className="email-submit-btn"
            disabled={loading || !password || !confirmPassword || password !== confirmPassword || password.length < 6}
          >
            {loading ? 'Saving…' : (isReset ? 'Update password & continue' : 'Save password & continue')}
          </button>
        </form>
      </div>
    </div>
  );
};

// Add Homework Modal
const AddHomeworkModal = ({ isOpen, onClose, onAdd, uid }) => {
  const [title, setTitle] = React.useState('');
  const [subject, setSubject] = React.useState('');
  const [dueDate, setDueDate] = React.useState('');
  const [googleDocLink, setGoogleDocLink] = React.useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!title || !subject || !dueDate) {
      alert('Please fill in all required fields');
      return;
    }

    try {
      await db.collection('users').doc(uid).collection('homework').add({
        title,
        subject,
        dueDate,
        googleDocLink,
        completed: false,
        completedAt: null,
        createdAt: new Date()
      });
      setTitle('');
      setSubject('');
      setDueDate('');
      setGoogleDocLink('');
      onAdd();
      onClose();
    } catch (error) {
      alert('Error adding homework: ' + error.message);
    }
  };

  if (!isOpen) return null;

  return (
    <div className="modal-overlay">
      <div className="modal-content">
        <div className="modal-header">
          Add Homework
          <button className="modal-close" onClick={onClose}>&times;</button>
        </div>

        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label className="form-label">Title *</label>
            <input
              type="text"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
              placeholder="e.g., Math Project"
            />
          </div>

          <div className="form-group">
            <label className="form-label">Subject *</label>
            <select value={subject} onChange={(e) => setSubject(e.target.value)}>
              <option value="">Select Subject</option>
              <option value="Math">Math</option>
              <option value="Science">Science (Chemistry)</option>
              <option value="Science (Biology)">Science (Biology)</option>
              <option value="Science (Physics)">Science (Physics)</option>
              <option value="English">English</option>
              <option value="SST">SST</option>
              <option value="SST (History/Civics)">SST (History/Civics)</option>
              <option value="SST (Geography)">SST (Geography)</option>
              <option value="IT">IT</option>
              <option value="II Lang">II Language</option>
              <option value="Other">Other</option>
            </select>
          </div>

          <div className="form-group">
            <label className="form-label">Due Date *</label>
            <input
              type="date"
              value={dueDate}
              onChange={(e) => setDueDate(e.target.value)}
            />
          </div>

          <div className="form-group">
            <label className="form-label">Google Doc Link</label>
            <input
              type="url"
              value={googleDocLink}
              onChange={(e) => setGoogleDocLink(e.target.value)}
              placeholder="https://docs.google.com/..."
            />
          </div>

          <div className="form-group" style={{ marginTop: '24px' }}>
            <button type="submit" className="btn btn-primary" style={{ width: '100%' }}>
              Add Homework
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

// Google Docs Settings Component
const GoogleDocsSettings = ({ uid, googleSettings, onSettingsChange }) => {
  const [isExpanded, setIsExpanded] = React.useState(false);
  const [docLink, setDocLink] = React.useState(googleSettings?.docLink || '');
  const [classSection, setClassSection] = React.useState(googleSettings?.classSection || '9 A');
  const [fetchTime, setFetchTime] = React.useState(googleSettings?.fetchTime || '16:00');
  const [isSyncing, setIsSyncing] = React.useState(false);
  const [syncMessage, setSyncMessage] = React.useState('');
  const [hasToken, setHasToken] = React.useState(!!localStorage.getItem('googleAccessToken'));

  // Sync state when Firestore settings load after initial render
  React.useEffect(() => {
    if (googleSettings?.docLink) setDocLink(googleSettings.docLink);
    if (googleSettings?.classSection) setClassSection(googleSettings.classSection);
    if (googleSettings?.fetchTime) setFetchTime(googleSettings.fetchTime);
  }, [googleSettings]);

  const handleSaveSettings = async () => {
    if (!docLink) {
      alert('Please enter a Google Docs link');
      return;
    }

    try {
      await db.collection('users').doc(uid).collection('settings').doc('googleDocs').set({
        docLink,
        classSection,
        fetchTime,
        updatedAt: new Date()
      }, { merge: true });

      onSettingsChange({ docLink, classSection, fetchTime });
      alert('Settings saved!');
    } catch (error) {
      alert('Error saving settings: ' + error.message);
    }
  };

  const handleSyncNow = async () => {
    if (!docLink) {
      alert('Please configure Google Docs link first');
      return;
    }

    setIsSyncing(true);
    setSyncMessage('Syncing...');

    try {
      const accessToken = localStorage.getItem('googleAccessToken');
      if (!accessToken) {
        setSyncMessage('No Google Docs access token found. Please go to Profile → Connect Google Account to grant access.');
        setIsSyncing(false);
        return;
      }

      const docId = extractDocId(docLink);
      if (!docId) {
        setSyncMessage('Invalid Google Docs URL');
        setIsSyncing(false);
        return;
      }

      const docContent = await fetchGoogleDocs(docId, accessToken);
      // Try today first, if no items found, try all weekdays (useful on weekends)
      let homeworkItems = parseGoogleDocsForHomework(docContent, `GRADE ${classSection}`, getDayName());
      if (homeworkItems.length === 0) {
        console.log('[Sync] No homework found for', getDayName(), '- fetching all weekdays');
        const weekdays = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY'];
        for (const day of weekdays) {
          const dayItems = parseGoogleDocsForHomework(docContent, `GRADE ${classSection}`, day);
          homeworkItems = homeworkItems.concat(dayItems);
        }
      }

      let addedCount = 0;
      for (const item of homeworkItems) {
        const existingQuery = await db.collection('users').doc(uid).collection('homework')
          .where('subject', '==', item.subject)
          .where('title', '==', item.task)
          .where('dueDate', '==', item.dueDate)
          .limit(1)
          .get();

        if (existingQuery.empty) {
          await db.collection('users').doc(uid).collection('homework').add({
            title: item.task,
            subject: item.subject,
            dueDate: item.dueDate,
            completed: false,
            source: 'google-docs',
            createdAt: new Date()
          });
          addedCount++;
        }
      }

      await db.collection('users').doc(uid).collection('settings').doc('googleDocs').set({
        lastFetchDate: new Date().toDateString()
      }, { merge: true });

      setSyncMessage(`Synced! Added ${addedCount} homework items`);
    } catch (error) {
      console.error('Sync error:', error);
      // Only update token state if fetchGoogleDocs already removed it (on 401)
      if (!localStorage.getItem('googleAccessToken')) {
        setHasToken(false);
      }
      setSyncMessage('Error: ' + error.message);
    } finally {
      setIsSyncing(false);
    }
  };

  const handleReconnectGoogle = async () => {
    setIsSyncing(true);
    setSyncMessage('Reconnecting...');
    const provider = new firebase.auth.GoogleAuthProvider();
    provider.addScope('https://www.googleapis.com/auth/documents.readonly');
    provider.addScope('https://www.googleapis.com/auth/drive.readonly');
    provider.setCustomParameters({ prompt: 'consent' });
    try {
      // Use signInWithPopup — it always returns the OAuth access token
      const result = await auth.signInWithPopup(provider);
      const accessToken = result.credential ? result.credential.accessToken : null;

      if (accessToken) {
        localStorage.setItem('googleAccessToken', accessToken);
        setHasToken(true);
        setSyncMessage('Reconnected! You can now click Sync Now.');
      } else {
        setSyncMessage('Could not get API token. Please try again.');
      }
    } catch (error) {
      if (error.code === 'auth/popup-closed-by-user') {
        setSyncMessage('Sign-in was cancelled.');
      } else {
        setSyncMessage('Error: ' + error.message);
      }
    }
    setIsSyncing(false);
  };

  return (
    <div className="google-docs-settings glass-card">
      <div className="settings-header" onClick={() => setIsExpanded(!isExpanded)}>
        <div className="settings-title">
          <span>{isExpanded ? '▼' : '▶'}</span>
          📄 Google Docs Integration
          {googleSettings?.docLink && hasToken && <div className="google-docs-badge">Connected</div>}
          {googleSettings?.docLink && !hasToken && <div className="google-docs-badge" style={{ background: 'rgba(239,68,68,0.2)', borderColor: 'rgba(239,68,68,0.4)', color: '#ef4444' }}>Token Expired</div>}
        </div>
      </div>

      {isExpanded && (
        <div className="settings-content">
          <div className="form-group">
            <label className="form-label">Google Docs Link</label>
            <input
              type="url"
              value={docLink}
              onChange={(e) => setDocLink(e.target.value)}
              placeholder="https://docs.google.com/document/d/..."
            />
          </div>

          <div className="settings-row">
            <div className="form-group">
              <label className="form-label">Class Section</label>
              <select value={classSection} onChange={(e) => setClassSection(e.target.value)}>
                <option value="9 A">Grade 9 A</option>
                <option value="9 B">Grade 9 B</option>
                <option value="9 C">Grade 9 C</option>
                <option value="9 D">Grade 9 D</option>
                <option value="9 E">Grade 9 E</option>
                <option value="9 F">Grade 9 F</option>
                <option value="9 G">Grade 9 G</option>
                <option value="9 H">Grade 9 H</option>
                <option value="9 I">Grade 9 I</option>
              </select>
            </div>

            <div className="form-group">
              <label className="form-label">Daily Fetch Time</label>
              <input
                type="time"
                value={fetchTime}
                onChange={(e) => setFetchTime(e.target.value)}
              />
            </div>
          </div>

          <div className="settings-info">
            Homework will be automatically fetched daily at the specified time. You can also manually sync anytime.
          </div>

          <div style={{ display: 'flex', gap: '12px', marginTop: '16px' }}>
            <button
              className="btn btn-primary"
              onClick={handleSaveSettings}
              disabled={isSyncing}
            >
              Save Settings
            </button>
            <button
              className="btn btn-secondary"
              onClick={handleSyncNow}
              disabled={isSyncing}
            >
              {isSyncing ? 'Syncing...' : 'Sync Now'}
            </button>
          </div>

          {syncMessage && (
            <div className="sync-status" style={{ whiteSpace: 'pre-wrap' }}>
              {syncMessage}
              {(syncMessage.includes('expired') || syncMessage.includes('No Google Docs access token') || syncMessage.includes('403')) ? (
                <button
                  className="btn btn-primary btn-small"
                  onClick={handleReconnectGoogle}
                  disabled={isSyncing}
                  style={{ marginLeft: '12px', marginTop: '8px' }}
                >
                  Reconnect Google
                </button>
              ) : null}
            </div>
          )}

          {!hasToken && !syncMessage && (
            <div className="sync-status" style={{ color: '#ef4444' }}>
              No Google access token found.
              <button
                className="btn btn-primary btn-small"
                onClick={handleReconnectGoogle}
                disabled={isSyncing}
                style={{ marginLeft: '12px' }}
              >
                Connect Google
              </button>
            </div>
          )}

          {googleSettings?.lastFetchDate && (
            <div className="sync-status">
              Last sync: {googleSettings.lastFetchDate}
            </div>
          )}
        </div>
      )}
    </div>
  );
};

// Circular Timer SVG
const CircularTimer = ({ seconds, duration, isBreak }) => {
  const circumference = 2 * Math.PI * 90;
  const progress = (seconds / duration) * circumference;
  const offset = circumference - progress;
  const color = isBreak ? '#22c55e' : '#06b6d4';

  return (
    <svg className="timer-svg" viewBox="0 0 200 200">
      <circle
        cx="100"
        cy="100"
        r="90"
        fill="none"
        stroke="#27272a"
        strokeWidth="3"
      />
      <circle
        cx="100"
        cy="100"
        r="90"
        fill="none"
        stroke={color}
        strokeWidth="3"
        strokeDasharray={circumference}
        strokeDashoffset={offset}
        strokeLinecap="round"
        style={{ transition: 'stroke-dashoffset 1s linear', transform: 'rotate(-90deg)', transformOrigin: '100px 100px' }}
      />
    </svg>
  );
};

// Smart break suggestions — rotates every break
const BREAK_SUGGESTIONS = [
  { icon: '🧘', title: 'Stretch', text: 'Stand up and stretch your neck, shoulders, and back for 30 seconds.' },
  { icon: '💧', title: 'Hydrate', text: 'Drink a glass of water. Hydration directly affects focus and memory.' },
  { icon: '👀', title: '20-20-20 rule', text: 'Look at something 20 feet away for 20 seconds to rest your eyes.' },
  { icon: '🚶', title: 'Walk', text: 'Take a quick walk. Even 2 minutes of movement boosts circulation.' },
  { icon: '🌬️', title: 'Deep breaths', text: 'Try 4-7-8 breathing: inhale 4s, hold 7s, exhale 8s. Repeat 3 times.' },
  { icon: '✨', title: 'Mindful minute', text: 'Close your eyes and notice 5 sounds around you. Return refreshed.' },
  { icon: '🍎', title: 'Healthy snack', text: 'Grab nuts, fruit, or yogurt. Avoid sugar crashes from junk food.' },
  { icon: '🎵', title: 'Favorite song', text: 'Play your favorite upbeat song and dance like no one\'s watching.' },
  { icon: '📱', title: 'No phone', text: 'Resist scrolling. Let your brain actually rest — it\'s working hard!' },
  { icon: '☀️', title: 'Natural light', text: 'Step outside or look out a window. Natural light resets your focus.' }
];

// Pomodoro Timer Tab — pure display component. State lives in App so it survives navigation.
const PomodoroTimer = ({ controls }) => {
  const { seconds, isRunning, isBreak, sessionCount, todayCount, toggle, reset } = controls;
  const cycleSeconds = isBreak ? 300 : 1500;
  const [focusMode, setFocusMode] = React.useState(false);
  const [breakSuggestion, setBreakSuggestion] = React.useState(null);

  // Pick a new break suggestion each time we enter break
  React.useEffect(() => {
    if (isBreak) {
      const s = BREAK_SUGGESTIONS[Math.floor(Math.random() * BREAK_SUGGESTIONS.length)];
      setBreakSuggestion(s);
    }
  }, [isBreak]);

  // Reflection prompt
  const [showReflection, setShowReflection] = React.useState(false);
  const [reflectionText, setReflectionText] = React.useState('');
  const [reflectionMood, setReflectionMood] = React.useState(3);
  const [reflectionSaving, setReflectionSaving] = React.useState(false);
  const prevIsBreakRef = React.useRef(isBreak);

  React.useEffect(() => {
    // Show reflection only when entering break (after a focus session completes)
    if (!prevIsBreakRef.current && isBreak) {
      setShowReflection(true);
      setReflectionText('');
      setReflectionMood(3);
    }
    prevIsBreakRef.current = isBreak;
  }, [isBreak]);

  const saveReflection = async () => {
    if (!reflectionText.trim()) { setShowReflection(false); return; }
    setReflectionSaving(true);
    const uid = firebase.auth().currentUser?.uid;
    if (uid) {
      try {
        await db.collection('users').doc(uid).collection('journal').add({
          content: reflectionText.trim(),
          mood: reflectionMood,
          createdAt: new Date(),
          source: 'pomodoro-reflection'
        });
      } catch (e) { /* ignore */ }
    }
    setReflectionSaving(false);
    setShowReflection(false);
  };

  const skipReflection = () => setShowReflection(false);

  const moodEmojis = ['😞', '😕', '😐', '🙂', '😊'];

  // Exit focus mode on Escape key
  React.useEffect(() => {
    if (!focusMode) return;
    const onKey = (e) => { if (e.key === 'Escape') setFocusMode(false); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [focusMode]);

  return (
    <div>
      <div className="timer-display">
        <CircularTimer seconds={seconds} duration={cycleSeconds} isBreak={isBreak} />
      </div>

      <div className="timer-time">{formatTime(seconds)}</div>

      <div className="timer-status">
        <span className={`status-badge ${isBreak ? 'break' : 'focus'}`}>
          {isBreak ? 'Break Time' : 'Focus Time'}
        </span>
      </div>

      <div className="timer-controls">
        <button
          className={`btn ${isRunning ? 'btn-secondary' : 'btn-primary'}`}
          onClick={toggle}
        >
          {isRunning ? 'Pause' : 'Start'}
        </button>
        <button className="btn btn-secondary" onClick={reset}>
          Reset
        </button>
        <button className="btn btn-secondary" onClick={() => setFocusMode(true)} title="Distraction-free full-screen timer">
          🎯 Focus Mode
        </button>
      </div>

      <div className="timer-info">
        <div className="timer-info-row">
          <div className="timer-info-item">
            <div className="timer-info-label">This Session</div>
            <div className="timer-info-value">{sessionCount}</div>
          </div>
          <div className="timer-info-item">
            <div className="timer-info-label">Today's Total</div>
            <div className="timer-info-value">{todayCount}</div>
          </div>
        </div>
      </div>

      {showReflection && (
        <div className="glass-card" style={{
          marginTop: '16px',
          background: 'linear-gradient(135deg, rgba(139,92,246,0.08), rgba(6,182,212,0.05))',
          border: '1px solid rgba(139,92,246,0.22)'
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' }}>
            <div>
              <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>✍️ 30-second reflection</div>
              <div className="ach-desc" style={{ fontSize: '12px' }}>What did you focus on? Any wins or struggles?</div>
            </div>
            <div style={{ display: 'flex', gap: '4px' }}>
              {moodEmojis.map((em, i) => (
                <button key={i}
                  onClick={() => setReflectionMood(i + 1)}
                  style={{
                    width: '28px', height: '28px', border: 'none', borderRadius: '6px',
                    cursor: 'pointer', fontSize: '16px', fontFamily: 'inherit',
                    background: reflectionMood === i + 1 ? 'rgba(139,92,246,0.2)' : 'transparent',
                    opacity: reflectionMood === i + 1 ? 1 : 0.45
                  }}>{em}</button>
              ))}
            </div>
          </div>
          <textarea
            value={reflectionText}
            onChange={e => setReflectionText(e.target.value)}
            placeholder="Just a sentence or two — feed your future weekly AI summary."
            style={{ width: '100%', minHeight: '60px', resize: 'vertical' }}
          />
          <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '10px' }}>
            <button className="btn btn-secondary btn-small" onClick={skipReflection}>Skip</button>
            <button className="btn btn-primary btn-small" onClick={saveReflection} disabled={reflectionSaving}>
              {reflectionSaving ? 'Saving…' : 'Save reflection'}
            </button>
          </div>
        </div>
      )}

      {isBreak && breakSuggestion && (
        <div className="glass-card" style={{
          marginTop: '16px',
          background: 'linear-gradient(135deg, rgba(34,197,94,0.08), rgba(20,184,166,0.05))',
          border: '1px solid rgba(34,197,94,0.2)'
        }}>
          <div style={{ display: 'flex', alignItems: 'flex-start', gap: '14px' }}>
            <div style={{ fontSize: '32px' }}>{breakSuggestion.icon}</div>
            <div style={{ flex: 1 }}>
              <div className="ach-name" style={{ fontSize: '13px', fontWeight: 600, marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '0.04em', color: '#22c55e' }}>
                Break tip · {breakSuggestion.title}
              </div>
              <div className="ach-name" style={{ fontSize: '14px', lineHeight: 1.5 }}>{breakSuggestion.text}</div>
            </div>
            <button
              onClick={() => setBreakSuggestion(BREAK_SUGGESTIONS[Math.floor(Math.random() * BREAK_SUGGESTIONS.length)])}
              style={{ background: 'none', border: 'none', color: '#71717a', cursor: 'pointer', fontSize: '18px', padding: '4px', fontFamily: 'inherit' }}
              title="Another suggestion"
            >⟳</button>
          </div>
        </div>
      )}

      {focusMode && (
        <div className="focus-mode-overlay" onClick={(e) => { if (e.target.classList.contains('focus-mode-overlay')) setFocusMode(false); }}>
          <button className="focus-mode-exit" onClick={() => setFocusMode(false)} title="Exit focus mode (Esc)">
            ✕
          </button>
          <div className="focus-mode-content">
            <div className="focus-mode-badge">
              {isBreak ? 'Break' : 'Focus'}
            </div>
            <div className="focus-mode-timer">{formatTime(seconds)}</div>
            <div className="focus-mode-progress">
              <svg viewBox="0 0 200 200" style={{ width: '100%', maxWidth: '420px' }}>
                <circle cx="100" cy="100" r="90" fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="2" />
                <circle
                  cx="100" cy="100" r="90" fill="none"
                  stroke={isBreak ? '#22c55e' : '#06b6d4'}
                  strokeWidth="2" strokeLinecap="round"
                  strokeDasharray={2 * Math.PI * 90}
                  strokeDashoffset={2 * Math.PI * 90 * (1 - seconds / cycleSeconds)}
                  style={{ transition: 'stroke-dashoffset 1s linear', transform: 'rotate(-90deg)', transformOrigin: '100px 100px' }}
                />
              </svg>
            </div>
            <div className="focus-mode-controls">
              <button className={`btn ${isRunning ? 'btn-secondary' : 'btn-primary'}`} onClick={toggle}>
                {isRunning ? 'Pause' : 'Start'}
              </button>
              <button className="btn btn-secondary" onClick={reset}>Reset</button>
            </div>
            <div className="focus-mode-hint">Press Esc to exit</div>
          </div>
        </div>
      )}
    </div>
  );
};

// Custom Timer Tab
const CustomTimer = ({ uid, onPointsEarned, onRefresh, studySubject }) => {
  const [inputMinutes, setInputMinutes] = React.useState(50);
  const [minutes, setMinutes] = React.useState(50);
  const [seconds, setSeconds] = React.useState(0);
  const [isRunning, setIsRunning] = React.useState(false);
  const [totalSeconds, setTotalSeconds] = React.useState(50 * 60);
  const [isComplete, setIsComplete] = React.useState(false);

  const handleMinutesChange = (val) => {
    const num = Math.max(1, Math.min(180, parseInt(val) || 1));
    setInputMinutes(num);
    setMinutes(num);
    setTotalSeconds(num * 60);
    setSeconds(0);
    setIsRunning(false);
    setIsComplete(false);
  };

  React.useEffect(() => {
    let interval;
    if (isRunning && (minutes > 0 || seconds > 0)) {
      interval = setInterval(() => {
        setSeconds((prevSec) => {
          if (prevSec === 0) {
            setMinutes((prevMin) => prevMin - 1);
            return 59;
          }
          return prevSec - 1;
        });
      }, 1000);
    } else if (isRunning && minutes === 0 && seconds === 0) {
      // Timer reached zero
      playAlarm();
      setIsRunning(false);
      setIsComplete(true);
    }
    return () => clearInterval(interval);
  }, [isRunning, minutes, seconds]);

  const handleSaveSession = async () => {
    const elapsedSeconds = totalSeconds - (minutes * 60 + seconds);
    const minutesStudied = elapsedSeconds / 60;
    const pointsEarned = Math.max(5, Math.floor(minutesStudied / 5) * 5);

    if (uid && minutesStudied > 0) {
      await db.collection('users').doc(uid).update({
        points: firebase.firestore.FieldValue.increment(pointsEarned),
        totalStudyMinutes: firebase.firestore.FieldValue.increment(Math.floor(minutesStudied))
      });
      await db.collection('users').doc(uid).collection('sessions').add({
        type: 'custom',
        minutes: Math.floor(minutesStudied),
        completedAt: new Date(),
        subject: studySubject || ''
      });
      // Update streak after custom timer session saves
      const fresh = (await db.collection('users').doc(uid).get()).data();
      updateStreak(uid, fresh);
      onPointsEarned(pointsEarned);
      onRefresh();
      alert(`Saved! +${pointsEarned} points`);
      setMinutes(inputMinutes);
      setSeconds(0);
      setTotalSeconds(inputMinutes * 60);
      setIsComplete(false);
    }
  };

  return (
    <div>
      <div style={{ marginBottom: '24px', textAlign: 'center' }}>
        <label className="form-label">Duration (minutes)</label>
        <input
          type="number"
          min="1"
          max="180"
          value={inputMinutes}
          onChange={(e) => handleMinutesChange(e.target.value)}
          disabled={isRunning}
          style={{ width: '120px', textAlign: 'center', marginTop: '8px' }}
        />
      </div>

      <div className="timer-display">
        <CircularTimer seconds={minutes * 60 + seconds} duration={totalSeconds} isBreak={false} />
      </div>

      <div className="timer-time">{formatTime(minutes * 60 + seconds)}</div>

      <div className="timer-controls">
        <button
          className={`btn ${isRunning ? 'btn-secondary' : 'btn-primary'}`}
          onClick={() => setIsRunning(!isRunning)}
          disabled={isComplete}
        >
          {isRunning ? 'Pause' : 'Start'}
        </button>
        <button
          className="btn btn-secondary"
          onClick={() => {
            setIsRunning(false);
            setMinutes(inputMinutes);
            setSeconds(0);
            setTotalSeconds(inputMinutes * 60);
            setIsComplete(false);
          }}
        >
          Reset
        </button>
        {isComplete && (
          <button className="btn btn-success" onClick={handleSaveSession}>
            Save Session
          </button>
        )}
      </div>
    </div>
  );
};

// Points Float Animation Component
const PointsFloat = ({ points }) => {
  return (
    <div className="points-float" style={{ top: '40%', left: '50%' }}>
      +{points}
    </div>
  );
};

// Confetti Burst Component
// Confetti emoji sets — default + shop-unlocked variants
const CONFETTI_SETS = {
  default: ['🎉', '✨', '🌟', '⭐', '💫', '🎊', '🔥', '💪'],
  'confetti-stars': ['⭐', '🌟', '✨', '💫', '🌠'],
  'confetti-hearts': ['💖', '💕', '💗', '💓', '💝', '❤️'],
  'confetti-fire': ['🔥', '💥', '✨', '⚡', '🌋'],
  'confetti-diamond': ['💎', '✨', '💠', '🔷', '🔹']
};

// Reads shop unlocks from window cache (populated by App) to pick the active set.
// Respects the user's reduced-motion accessibility preference by skipping animation.
const ConfettiBurst = ({ setId }) => {
  // Respect reduced-motion: accessibility toggle OR OS-level prefers-reduced-motion
  const reducedMotion =
    (typeof document !== 'undefined' && document.documentElement.classList.contains('reduced-motion'))
    || (typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  if (reducedMotion) return null;
  const id = setId || (typeof window !== 'undefined' ? window.FOCUSFORGE_ACTIVE_CONFETTI : '') || 'default';
  const emojis = CONFETTI_SETS[id] || CONFETTI_SETS.default;
  const particles = [];
  for (let i = 0; i < 24; i++) {
    particles.push(
      <div
        key={i}
        className="confetti"
        style={{
          left: `${15 + Math.random() * 70}%`,
          top: `${25 + Math.random() * 35}%`,
          fontSize: `${20 + Math.random() * 22}px`,
          animation: `confettiFall ${2 + Math.random() * 1.5}s ease-out forwards`,
          animationDelay: `${Math.random() * 0.5}s`,
        }}
      >
        {emojis[Math.floor(Math.random() * emojis.length)]}
      </div>
    );
  }
  return <>{particles}</>;
};

// Study Heatmap Component
const StudyHeatmap = ({ uid }) => {
  const [cells, setCells] = React.useState([]);

  React.useEffect(() => {
    if (!uid) return;
    const now = new Date();
    const startDate = new Date(now);
    startDate.setDate(startDate.getDate() - 27);
    startDate.setHours(0, 0, 0, 0);

    const unsubscribe = db.collection('users').doc(uid).collection('sessions')
      .where('completedAt', '>=', startDate)
      .onSnapshot((snapshot) => {
        const dayMinutes = {};
        snapshot.docs.forEach((doc) => {
          const data = doc.data();
          const date = data.completedAt?.toDate ? data.completedAt.toDate() : new Date(data.completedAt);
          const key = date.toISOString().split('T')[0];
          dayMinutes[key] = (dayMinutes[key] || 0) + (data.minutes || 0);
        });

        const grid = [];
        for (let i = 0; i < 28; i++) {
          const d = new Date(startDate);
          d.setDate(d.getDate() + i);
          const key = d.toISOString().split('T')[0];
          const mins = dayMinutes[key] || 0;
          let level = 0;
          if (mins > 0 && mins <= 15) level = 1;
          else if (mins <= 45) level = 2;
          else if (mins <= 90) level = 3;
          else if (mins > 90) level = 4;
          grid.push({ date: key, level, minutes: mins });
        }
        setCells(grid);
      });
    return unsubscribe;
  }, [uid]);

  return (
    <div className="heatmap-container">
      <h3 style={{ fontSize: '15px', fontWeight: 600, marginBottom: '12px', color: 'var(--text-primary)' }}>Study Activity (Last 28 Days)</h3>
      <div style={{ display: 'flex', gap: '16px', fontSize: '11px', color: 'var(--text-muted)', marginBottom: '8px' }}>
        <span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span>
      </div>
      <div className="heatmap-grid">
        {cells.map((cell, i) => (
          <div key={i} className={`heatmap-cell ${cell.level > 0 ? `level-${cell.level}` : ''}`} title={`${cell.date}: ${cell.minutes} min`} />
        ))}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginTop: '8px', fontSize: '11px', color: 'var(--text-muted)' }}>
        <span>Less</span>
        <div className="heatmap-cell" style={{ width: '12px', height: '12px' }} />
        <div className="heatmap-cell level-1" style={{ width: '12px', height: '12px' }} />
        <div className="heatmap-cell level-2" style={{ width: '12px', height: '12px' }} />
        <div className="heatmap-cell level-3" style={{ width: '12px', height: '12px' }} />
        <div className="heatmap-cell level-4" style={{ width: '12px', height: '12px' }} />
        <span>More</span>
      </div>
    </div>
  );
};

// Dashboard Page
// Energy/Mood Tracker — daily check-in card
const MoodTracker = ({ uid }) => {
  const [todayEntry, setTodayEntry] = React.useState(null);
  const [mood, setMood] = React.useState(0);
  const [energy, setEnergy] = React.useState(0);
  const [saved, setSaved] = React.useState(false);
  const today = new Date().toISOString().slice(0, 10);

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('moodEntries').doc(today)
      .onSnapshot(doc => {
        if (doc.exists) {
          setTodayEntry(doc.data());
          setMood(doc.data().mood);
          setEnergy(doc.data().energy);
        }
      });
    return unsub;
  }, [uid, today]);

  const save = async () => {
    if (!uid || mood === 0 || energy === 0) return;
    await db.collection('users').doc(uid).collection('moodEntries').doc(today).set({
      mood, energy, date: today, recordedAt: new Date()
    });
    setSaved(true);
    setTimeout(() => setSaved(false), 1800);
  };

  const moodEmojis = ['😞', '😕', '😐', '🙂', '😊'];
  const moodLabels = ['Bad', 'Low', 'OK', 'Good', 'Great'];

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px' }}>
        <h3 style={{ fontSize: '15px', fontWeight: 600 }}>
          {todayEntry ? '✓ Today\'s check-in' : 'How are you today?'}
        </h3>
        {saved && <span style={{ fontSize: '12px', color: '#22c55e' }}>Saved ✓</span>}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
        <div>
          <div style={{ fontSize: '11px', color: '#71717a', marginBottom: '8px', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 500 }}>Mood</div>
          <div style={{ display: 'flex', gap: '6px' }}>
            {moodEmojis.map((e, i) => (
              <button
                key={i}
                onClick={() => setMood(i + 1)}
                title={moodLabels[i]}
                style={{
                  flex: 1, padding: '8px 0', border: 'none', borderRadius: '8px', cursor: 'pointer',
                  background: mood === i + 1 ? 'rgba(6,182,212,0.15)' : 'rgba(255,255,255,0.04)',
                  outline: mood === i + 1 ? '1px solid rgba(6,182,212,0.4)' : 'none',
                  fontSize: '22px', transition: 'all 0.15s', fontFamily: 'inherit',
                  transform: mood === i + 1 ? 'scale(1.08)' : 'scale(1)'
                }}>{e}</button>
            ))}
          </div>
        </div>
        <div>
          <div style={{ fontSize: '11px', color: '#71717a', marginBottom: '8px', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 500 }}>Energy</div>
          <div style={{ display: 'flex', gap: '6px' }}>
            {[1,2,3,4,5].map(i => (
              <button
                key={i}
                onClick={() => setEnergy(i)}
                title={`Level ${i}`}
                style={{
                  flex: 1, padding: '10px 0', border: 'none', borderRadius: '8px', cursor: 'pointer',
                  background: energy >= i ? '#06b6d4' : 'rgba(255,255,255,0.04)',
                  color: energy >= i ? '#09090b' : '#71717a',
                  fontSize: '13px', fontWeight: 600, transition: 'all 0.15s', fontFamily: 'inherit'
                }}>⚡</button>
            ))}
          </div>
        </div>
      </div>
      {(mood > 0 && energy > 0) && (!todayEntry || todayEntry.mood !== mood || todayEntry.energy !== energy) && (
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '14px' }}>
          <button className="btn btn-primary btn-small" onClick={save}>
            {todayEntry ? 'Update' : 'Save'}
          </button>
        </div>
      )}
    </div>
  );
};

const Dashboard = ({ userData, uid }) => {
  const hour = new Date().getHours();
  let greeting = 'Good morning';
  if (hour >= 12 && hour < 18) greeting = 'Good afternoon';
  if (hour >= 18) greeting = 'Good evening';

  const levelData = getLevelProgress(userData?.points || 0);

  return (
    <div>
      <h1 className="page-title">{greeting}, {(userData?.displayName || '').trim().split(' ')[0] || 'friend'}!</h1>
      <p className="page-subtitle">Ready to level up today?</p>

      {uid && <MoodTracker uid={uid} />}

      <div className="grid-2" style={{ marginBottom: '32px' }}>
        <div className="level-progress-card">
          <div className="level-display">
            <div className="level-number">{levelData.current}</div>
            <div className="level-stats">
              <div className="level-stats-item">
                <strong>{userData?.points || 0}</strong> points
              </div>
              <div className="level-stats-item">
                {levelData.progress}% to next level
              </div>
            </div>
          </div>
          <div className="progress-bar">
            <div className="progress-fill" style={{ width: `${levelData.progress}%` }}></div>
          </div>
          <div className="progress-text">
            {levelData.needed - ((userData?.points || 0) % 100)} points to Level {levelData.current + 1}
          </div>
        </div>

        <div className="glass-card">
          <div style={{ textAlign: 'center' }}>
            <h3 style={{ marginBottom: '12px' }}>Quick Tips</h3>
            <ul style={{ fontSize: '12px', color: 'var(--text-secondary)', textAlign: 'left' }}>
              <li>Use the Pomodoro timer for focused sessions</li>
              <li>Track your homework to stay organized</li>
              <li>Build your streak with daily study</li>
              <li>Earn points to unlock achievements</li>
            </ul>
          </div>
        </div>
      </div>

      <div className="grid-4">
        <div className="stat-card cyan">
          <div className="stat-value">{userData?.totalPomodoros || 0}</div>
          <div className="stat-label">Total Pomodoros</div>
        </div>
        <div className="stat-card blue">
          <div className="stat-value">{formatMinutes(userData?.totalStudyMinutes || 0)}</div>
          <div className="stat-label">Total Study Time</div>
        </div>
        <div className="stat-card orange">
          <div className="stat-value">{userData?.currentStreak || 0}</div>
          <div className="stat-label">Current Streak</div>
        </div>
        <div className="stat-card yellow">
          <div className="stat-value">{Math.min(100, Math.round(((userData?.totalStudyMinutes || 0) % (userData?.dailyGoalMinutes || 60)) / (userData?.dailyGoalMinutes || 60) * 100))}%</div>
          <div className="stat-label">Daily Goal</div>
        </div>
      </div>

      {uid && <StudyHeatmap uid={uid} />}
    </div>
  );
};

// Study Timer Page
const StudyTimer = ({ uid, onRefresh, pomoControls, userData }) => {
  const [activeTab, setActiveTab] = React.useState('pomodoro');
  const [, setPointsEarned] = React.useState(0);

  // AI Motivational Coach
  const [coachMessage, setCoachMessage] = React.useState('');
  const [coachLoading, setCoachLoading] = React.useState(false);
  const [coachError, setCoachError] = React.useState('');

  const quotes = [
    { text: "The secret of getting ahead is getting started.", author: "Mark Twain" },
    { text: "It always seems impossible until it's done.", author: "Nelson Mandela" },
    { text: "Don't watch the clock; do what it does. Keep going.", author: "Sam Levenson" },
    { text: "Success is the sum of small efforts repeated day in and day out.", author: "Robert Collier" },
    { text: "The only way to do great work is to love what you do.", author: "Steve Jobs" },
    { text: "Education is the most powerful weapon.", author: "Nelson Mandela" },
    { text: "The mind is not a vessel to be filled, but a fire to be kindled.", author: "Plutarch" },
    { text: "Study hard what interests you the most.", author: "Richard Feynman" },
  ];

  const [quote] = React.useState(() => quotes[Math.floor(Math.random() * quotes.length)]);

  const handlePointsEarned = React.useCallback((pts) => {
    setPointsEarned(pts);
  }, []);

  const getPepTalk = async () => {
    setCoachLoading(true);
    setCoachError('');
    try {
      const name = userData?.displayName?.split(' ')[0] || 'friend';
      const streak = userData?.currentStreak || 0;
      const level = userData ? Math.floor((userData.points || 0) / 100) + 1 : 1;
      const totalPomo = userData?.totalPomodoros || 0;
      const todayPomo = pomoControls?.todayCount || 0;
      const subject = pomoControls?.subject || '';
      const isRunning = pomoControls?.isRunning || false;
      const isBreak = pomoControls?.isBreak || false;

      const system = `You are a warm, encouraging study coach. Give a SHORT personalized pep talk (2-3 sentences max, under 50 words total). Be genuine and specific, not cliché. Reference the user's actual stats when relevant. Don't start with "Hey" or the user's name — just get right to the message.`;

      const prompt = `Student context:
- Name: ${name}
- Current streak: ${streak} days
- Level: ${level}
- Total pomodoros ever: ${totalPomo}
- Pomodoros done today: ${todayPomo}
- Subject being studied: ${subject || 'none specified'}
- Currently ${isBreak ? 'on break' : isRunning ? 'in a focus session' : 'about to start a session'}

Give them a short, energizing pep talk for right now.`;

      const text = await callAI(prompt, system);
      setCoachMessage(text.trim());
      if (uid) {
        db.collection('users').doc(uid).update({
          aiPepTalksReceived: firebase.firestore.FieldValue.increment(1)
        }).catch(() => {});
      }
    } catch (e) {
      setCoachError(e.message);
    } finally {
      setCoachLoading(false);
    }
  };

  return (
    <div>
      <h1 className="page-title">Study Timer</h1>
      <p className="page-subtitle">Focus on what matters</p>

      <div className="glass-card quote-card" style={{ marginBottom: '16px', padding: '16px 20px' }}>
        <p className="quote-text" style={{ fontStyle: 'italic', fontSize: '14px', marginBottom: '4px' }}>"{quote.text}"</p>
        <p style={{ fontSize: '12px', color: '#06b6d4' }}>-- {quote.author}</p>
      </div>

      {/* AI Motivational Coach */}
      <div className="ai-coach-card">
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px', minWidth: '160px' }}>
            <span style={{ fontSize: '22px' }}>💬</span>
            <div>
              <div className="ai-coach-title">AI Coach</div>
              <div className="ai-coach-subtitle">Personalized pep talk, powered by AI</div>
            </div>
          </div>
          <button className="btn btn-primary btn-small" onClick={getPepTalk} disabled={coachLoading}>
            {coachLoading ? 'Thinking…' : coachMessage ? '✨ New pep talk' : '✨ Get pep talk'}
          </button>
        </div>
        {coachMessage && (
          <div className="ai-coach-message">{coachMessage}</div>
        )}
        {coachError && (
          <div style={{ marginTop: '12px', padding: '8px 12px', borderRadius: '8px', background: 'rgba(239,68,68,0.08)', color: '#ef4444', fontSize: '12px' }}>
            {coachError}
          </div>
        )}
      </div>

      <div style={{ marginBottom: '16px' }}>
        <label style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '6px', display: 'block' }}>Study Subject (optional)</label>
        <input type="text" placeholder="e.g., Math, Science..." value={pomoControls.subject} onChange={e => pomoControls.setSubject(e.target.value)} style={{ width: '100%', maxWidth: '300px' }} />
      </div>

      <div className="tabs">
        <button
          className={`tab ${activeTab === 'pomodoro' ? 'active' : ''}`}
          onClick={() => setActiveTab('pomodoro')}
        >
          Pomodoro (25min)
        </button>
        <button
          className={`tab ${activeTab === 'custom' ? 'active' : ''}`}
          onClick={() => setActiveTab('custom')}
        >
          Custom Timer
        </button>
      </div>

      <div className="tab-content">
        {activeTab === 'pomodoro' && (
          <PomodoroTimer controls={pomoControls} />
        )}
        {activeTab === 'custom' && (
          <CustomTimer uid={uid} onPointsEarned={handlePointsEarned} onRefresh={onRefresh} studySubject={pomoControls.subject} />
        )}
      </div>
    </div>
  );
};

// Homework Page
const HomeworkPage = ({ uid }) => {
  const [homeworks, setHomeworks] = React.useState([]);
  const [showModal, setShowModal] = React.useState(false);
  const [activeTab, setActiveTab] = React.useState('pending');
  const [refresh, setRefresh] = React.useState(0);
  const [googleSettings, setGoogleSettings] = React.useState(null);
  const [aiRanking, setAiRanking] = React.useState(null); // { [id]: { score, reason } }
  const [aiLoading, setAiLoading] = React.useState(false);
  const [aiError, setAiError] = React.useState('');

  React.useEffect(() => {
    if (uid) {
      const unsubscribe = db.collection('users').doc(uid).collection('homework')
        .orderBy('createdAt', 'desc')
        .onSnapshot((snapshot) => {
          setHomeworks(snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })));
        });
      return unsubscribe;
    }
  }, [uid]);

  React.useEffect(() => {
    if (uid) {
      const unsubscribe = db.collection('users').doc(uid).collection('settings').doc('googleDocs')
        .onSnapshot((doc) => {
          if (doc.exists) {
            setGoogleSettings(doc.data());
          }
        });
      return unsubscribe;
    }
  }, [uid]);

  const handleComplete = async (id) => {
    if (uid) {
      // Schedule first review in 1 day (SRS) — normalise to midnight FIRST so DST
      // transitions can't shave off an hour and land us on the previous day.
      const nextReview = new Date();
      nextReview.setHours(0, 0, 0, 0);
      nextReview.setDate(nextReview.getDate() + 1);

      await db.collection('users').doc(uid).collection('homework').doc(id).update({
        completed: true,
        completedAt: new Date(),
        reviewStage: 0,        // 0=1d, 1=3d, 2=7d, 3=14d, 4=30d (done)
        nextReviewAt: nextReview
      });
      await db.collection('users').doc(uid).update({
        totalHomeworkCompleted: firebase.firestore.FieldValue.increment(1)
      });
      // Fetch fresh userData for streak check then update
      const fresh = (await db.collection('users').doc(uid).get()).data();
      await updateStreak(uid, fresh);
      await awardXP(uid, 5, {});
      setRefresh((r) => r + 1);
    }
  };

  // SRS review handler — rates "Easy" (advance 2), "Good" (advance 1), "Hard" (stay)
  const handleReview = async (id, rating) => {
    if (!uid) return;
    const hw = homeworks.find(h => h.id === id);
    if (!hw) return;

    const intervals = [1, 3, 7, 14, 30]; // days
    let stage = (hw.reviewStage ?? 0);
    if (rating === 'easy') stage += 2;
    else if (rating === 'good') stage += 1;
    // 'hard' keeps stage
    // Clamp to prevent out-of-bounds index when 'easy' at the last valid stage
    stage = Math.min(stage, intervals.length);

    if (stage >= intervals.length) {
      // Fully mastered — stop SRS
      await db.collection('users').doc(uid).collection('homework').doc(id).update({
        reviewStage: intervals.length,
        nextReviewAt: null,
        masteredAt: new Date()
      });
      await db.collection('users').doc(uid).update({
        points: firebase.firestore.FieldValue.increment(10) // bonus for mastery
      });
    } else {
      // Normalise to midnight before shifting so DST can't add/drop an hour
      const next = new Date();
      next.setHours(0, 0, 0, 0);
      next.setDate(next.getDate() + intervals[stage]);
      await db.collection('users').doc(uid).collection('homework').doc(id).update({
        reviewStage: stage,
        nextReviewAt: next,
        lastReviewedAt: new Date()
      });
      await db.collection('users').doc(uid).update({
        points: firebase.firestore.FieldValue.increment(2)
      });
    }
  };

  // Items due for review today or earlier
  const dueForReview = React.useMemo(() => {
    const now = Date.now();
    return homeworks.filter(h => {
      if (!h.completed) return false;
      if (!h.nextReviewAt) return false;
      const t = h.nextReviewAt?.seconds ? h.nextReviewAt.seconds * 1000 : new Date(h.nextReviewAt).getTime();
      return t <= now && (h.reviewStage ?? 0) < 5;
    });
  }, [homeworks]);

  const handleDelete = async (id) => {
    if (uid && window.confirm('Delete this homework?')) {
      await db.collection('users').doc(uid).collection('homework').doc(id).delete();
    }
  };

  // AI Smart Task Prioritization
  const aiPrioritize = async () => {
    setAiLoading(true);
    setAiError('');
    try {
      const pendingItems = homeworks.filter(h => !h.completed);
      if (pendingItems.length === 0) {
        setAiError('No pending homework to prioritize.');
        setAiLoading(false);
        return;
      }

      const today = new Date().toISOString().slice(0, 10);
      const items = pendingItems.map(h => ({
        id: h.id,
        title: h.title,
        subject: h.subject || 'Unknown',
        dueDate: h.dueDate || null,
        daysUntilDue: h.dueDate ? Math.ceil((new Date(h.dueDate) - new Date()) / 86400000) : null
      }));

      const system = `You are a homework prioritization expert. Given a list of pending homework, rank them by urgency and importance.
Return ONLY valid JSON in this exact format (no other text, no markdown, no code blocks):
{"rankings":[{"id":"<id>","score":<1-100>,"reason":"<short reason in 1 sentence>"}]}

Scoring rules:
- Overdue items: 85-100 (highest priority)
- Due today/tomorrow: 70-90
- Due in 2-3 days: 55-75
- Due in 4-7 days: 35-55
- Due later or no date: 20-45
- Harder subjects (Math, Physics, Chemistry) get a slight boost when deadlines are close
- Keep reasons concise and actionable.`;

      const prompt = `Today is ${today}.
Pending homework:
${JSON.stringify(items, null, 2)}

Rank them with focus scores.`;

      const text = await callAI(prompt, system);

      // Extract JSON from response (may have code fences)
      let jsonText = text.trim();
      const codeBlockMatch = jsonText.match(/```(?:json)?\s*([\s\S]*?)```/);
      if (codeBlockMatch) jsonText = codeBlockMatch[1].trim();
      const objMatch = jsonText.match(/\{[\s\S]*\}/);
      if (objMatch) jsonText = objMatch[0];

      const parsed = JSON.parse(jsonText);
      const map = {};
      (parsed.rankings || []).forEach(r => {
        map[r.id] = { score: Number(r.score) || 0, reason: r.reason || '' };
      });
      setAiRanking(map);
      if (uid) {
        db.collection('users').doc(uid).update({
          aiPrioritizationsUsed: firebase.firestore.FieldValue.increment(1)
        }).catch(() => {});
      }
    } catch (e) {
      setAiError('AI ranking failed: ' + e.message);
    } finally {
      setAiLoading(false);
    }
  };

  const clearAiRanking = () => { setAiRanking(null); setAiError(''); };

  const [subjectFilter, setSubjectFilter] = React.useState('All');
  const allSubjects = React.useMemo(() => {
    // Dedupe case-insensitively (so "Math" and "MATH" collapse into one chip) while
    // keeping the first-seen casing for display. Sort alphabetically (case-insensitive).
    const bucket = new Map();
    homeworks.forEach(h => {
      const raw = (h.subject || '').trim();
      if (!raw) return;
      const key = raw.toLowerCase();
      if (!bucket.has(key)) bucket.set(key, raw);
    });
    const list = Array.from(bucket.values()).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
    return ['All', ...list];
  }, [homeworks]);

  const filteredHomeworks = subjectFilter === 'All'
    ? homeworks
    : homeworks.filter(h => (h.subject || '').trim().toLowerCase() === subjectFilter.trim().toLowerCase());
  const pending = filteredHomeworks.filter((h) => !h.completed);
  const completed = filteredHomeworks.filter((h) => h.completed);
  let displayItems = activeTab === 'pending' ? pending : activeTab === 'completed' ? completed : filteredHomeworks;

  // When AI ranking is active, sort pending items by score (highest first)
  if (aiRanking && activeTab === 'pending') {
    displayItems = [...displayItems].sort((a, b) => {
      const sa = aiRanking[a.id]?.score ?? 0;
      const sb = aiRanking[b.id]?.score ?? 0;
      return sb - sa;
    });
  }

  // Group homework by subject for summary
  const subjectGroups = {};
  homeworks.forEach(hw => {
    const subj = hw.subject || 'Other';
    if (!subjectGroups[subj]) subjectGroups[subj] = { total: 0, completed: 0, pending: 0, items: [] };
    subjectGroups[subj].total++;
    if (hw.completed) subjectGroups[subj].completed++;
    else subjectGroups[subj].pending++;
    subjectGroups[subj].items.push(hw);
  });

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', gap: '12px', flexWrap: 'wrap' }}>
        <div>
          <h1 className="page-title">Homework</h1>
          <p className="page-subtitle">Stay on top of assignments</p>
        </div>
        <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
          {aiRanking ? (
            <button className="btn btn-secondary" onClick={clearAiRanking}>Clear AI ranking</button>
          ) : (
            <button className="btn btn-secondary" onClick={aiPrioritize} disabled={aiLoading || pending.length === 0}>
              {aiLoading ? '✨ Ranking…' : '✨ AI Prioritize'}
            </button>
          )}
          <button className="btn btn-primary" onClick={() => setShowModal(true)}>
            + Add Homework
          </button>
        </div>
      </div>

      {aiError && (
        <div style={{ marginBottom: '16px', padding: '10px 14px', borderRadius: '8px', background: 'rgba(239,68,68,0.08)', color: '#ef4444', fontSize: '13px', border: '1px solid rgba(239,68,68,0.2)' }}>
          {aiError}
        </div>
      )}

      {aiRanking && !aiError && (
        <div style={{ marginBottom: '16px', padding: '10px 14px', borderRadius: '8px', background: 'rgba(6,182,212,0.08)', color: '#06b6d4', fontSize: '13px', border: '1px solid rgba(6,182,212,0.2)', display: 'flex', alignItems: 'center', gap: '8px' }}>
          <span>✨</span>
          <span>AI-ranked by urgency. Higher focus score means more urgent. Tackle top items first.</span>
        </div>
      )}

      {/* Quick stats bar */}
      <div style={{ display: 'flex', gap: '12px', marginBottom: '20px', flexWrap: 'wrap' }}>
        <div className="glass-card" style={{ flex: 1, minWidth: '100px', padding: '12px 16px', textAlign: 'center' }}>
          <div style={{ fontSize: '24px', fontWeight: '700', color: '#00d4ff' }}>{homeworks.length}</div>
          <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Total</div>
        </div>
        <div className="glass-card" style={{ flex: 1, minWidth: '100px', padding: '12px 16px', textAlign: 'center' }}>
          <div style={{ fontSize: '24px', fontWeight: '700', color: '#ffa502' }}>{pending.length}</div>
          <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Pending</div>
        </div>
        <div className="glass-card" style={{ flex: 1, minWidth: '100px', padding: '12px 16px', textAlign: 'center' }}>
          <div style={{ fontSize: '24px', fontWeight: '700', color: '#2ed573' }}>{completed.length}</div>
          <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Completed</div>
        </div>
        <div className="glass-card" style={{ flex: 1, minWidth: '100px', padding: '12px 16px', textAlign: 'center' }}>
          <div style={{ fontSize: '24px', fontWeight: '700', color: '#ff4757' }}>{pending.filter(h => isOverdue(h.dueDate)).length}</div>
          <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Overdue</div>
        </div>
      </div>

      {allSubjects.length > 1 && (
        <div className="subject-filter-bar">
          {allSubjects.map(s => (
            <span key={s} className={`subject-chip ${subjectFilter === s ? 'active' : ''}`} onClick={() => setSubjectFilter(s)}>{s}</span>
          ))}
        </div>
      )}

      <GoogleDocsSettings
        uid={uid}
        googleSettings={googleSettings}
        onSettingsChange={(settings) => setGoogleSettings(settings)}
      />

      {dueForReview.length > 0 && (
        <div className="glass-card" style={{ marginBottom: '20px', background: 'linear-gradient(135deg, rgba(139,92,246,0.08), rgba(6,182,212,0.05))', border: '1px solid rgba(139,92,246,0.25)' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px', flexWrap: 'wrap', gap: '8px' }}>
            <div>
              <div className="ach-name" style={{ fontSize: '15px', fontWeight: 600 }}>🔁 Due for Review ({dueForReview.length})</div>
              <div className="ach-desc" style={{ fontSize: '12px' }}>Spaced repetition — reinforce long-term memory</div>
            </div>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            {dueForReview.map(hw => {
              const stage = hw.reviewStage ?? 0;
              const stageLabels = ['1 day', '3 day', '7 day', '14 day', '30 day'];
              return (
                <div key={hw.id} style={{
                  padding: '12px 14px', borderRadius: '10px',
                  background: 'rgba(255,255,255,0.03)',
                  border: '1px solid rgba(255,255,255,0.05)'
                }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px', marginBottom: '8px' }}>
                    <div>
                      <div className="ach-name" style={{ fontSize: '13px', fontWeight: 600 }}>{hw.title}</div>
                      <div className="ach-desc" style={{ fontSize: '11px' }}>
                        {hw.subject} · Review {stage + 1}/5 ({stageLabels[stage]})
                      </div>
                    </div>
                  </div>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <button className="btn btn-danger btn-small" style={{ flex: 1 }} onClick={() => handleReview(hw.id, 'hard')}>😓 Hard</button>
                    <button className="btn btn-secondary btn-small" style={{ flex: 1 }} onClick={() => handleReview(hw.id, 'good')}>🙂 Good</button>
                    <button className="btn btn-success btn-small" style={{ flex: 1 }} onClick={() => handleReview(hw.id, 'easy')}>😎 Easy</button>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      <div className="tabs-header">
        <button
          className={`tab ${activeTab === 'pending' ? 'active' : ''}`}
          onClick={() => setActiveTab('pending')}
        >
          Pending ({pending.length})
        </button>
        <button
          className={`tab ${activeTab === 'completed' ? 'active' : ''}`}
          onClick={() => setActiveTab('completed')}
        >
          Completed ({completed.length})
        </button>
        <button
          className={`tab ${activeTab === 'summary' ? 'active' : ''}`}
          onClick={() => setActiveTab('summary')}
        >
          Summary
        </button>
      </div>

      {activeTab === 'summary' ? (
        <div>
          {Object.keys(subjectGroups).length === 0 ? (
            <div className="empty-state">
              <div className="empty-state-icon">&#128202;</div>
              <p>No homework yet. Add some to see the summary!</p>
            </div>
          ) : (
            Object.entries(subjectGroups).sort((a, b) => b[1].pending - a[1].pending).map(([subject, data]) => (
              <div key={subject} className="glass-card" style={{ marginBottom: '16px', padding: '16px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
                  <h3 style={{ fontSize: '16px', fontWeight: '600' }}>{subject}</h3>
                  <div style={{ display: 'flex', gap: '8px' }}>
                    <span className="badge badge-subject" style={{ background: 'rgba(0,212,255,0.15)', color: '#00d4ff' }}>{data.total} total</span>
                    {data.pending > 0 && <span className="badge" style={{ background: 'rgba(255,165,2,0.15)', color: '#ffa502' }}>{data.pending} pending</span>}
                    {data.completed > 0 && <span className="badge" style={{ background: 'rgba(46,213,115,0.15)', color: '#2ed573' }}>{data.completed} done</span>}
                  </div>
                </div>
                {data.items.map(hw => (
                  <div key={hw.id} style={{
                    padding: '10px 12px',
                    borderLeft: hw.completed ? '3px solid #2ed573' : isOverdue(hw.dueDate) ? '3px solid #ff4757' : '3px solid #ffa502',
                    marginBottom: '8px',
                    background: 'rgba(255,255,255,0.02)',
                    borderRadius: '0 8px 8px 0',
                    display: 'flex',
                    justifyContent: 'space-between',
                    alignItems: 'center'
                  }}>
                    <div>
                      <div style={{ fontSize: '14px', fontWeight: '500', textDecoration: hw.completed ? 'line-through' : 'none', color: hw.completed ? 'rgba(255,255,255,0.4)' : 'inherit' }}>
                        {hw.title}
                      </div>
                      <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '4px' }}>
                        Due: {new Date(hw.dueDate).toLocaleDateString()}
                        {hw.completed && hw.completedAt && ` • Completed: ${new Date(hw.completedAt.seconds ? hw.completedAt.seconds * 1000 : hw.completedAt).toLocaleDateString()}`}
                      </div>
                    </div>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                      {hw.googleDocLink && (
                        <a href={hw.googleDocLink} target="_blank" rel="noopener noreferrer" style={{ fontSize: '12px', color: '#00d4ff' }}>Doc</a>
                      )}
                      {hw.completed ? (
                        <span style={{ color: '#2ed573', fontSize: '14px' }}>✓</span>
                      ) : (
                        <button className="btn btn-success btn-small" style={{ padding: '4px 10px', fontSize: '12px' }} onClick={() => handleComplete(hw.id)}>Done</button>
                      )}
                    </div>
                  </div>
                ))}
              </div>
            ))
          )}
        </div>
      ) : displayItems.length === 0 ? (
        <div className="empty-state">
          <div className="empty-state-icon">{activeTab === 'pending' ? '✨' : '🎉'}</div>
          <p>
            {activeTab === 'pending'
              ? "You're all caught up! No pending homework."
              : 'No completed homework yet.'}
          </p>
        </div>
      ) : (
        <div className="homework-grid">
          {displayItems.map((hw) => {
            const rank = aiRanking?.[hw.id];
            const scoreColor = rank ? (rank.score >= 80 ? '#ef4444' : rank.score >= 60 ? '#f59e0b' : rank.score >= 40 ? '#06b6d4' : '#71717a') : null;
            return (
            <div
              key={hw.id}
              className={`homework-card ${hw.completed ? 'completed' : ''}`}
              style={rank ? { borderLeft: `3px solid ${scoreColor}` } : {}}
            >
              {rank && (
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
                  <span style={{
                    fontSize: '11px', fontWeight: 600, padding: '3px 8px',
                    borderRadius: '6px', background: `${scoreColor}22`, color: scoreColor,
                    textTransform: 'uppercase', letterSpacing: '0.04em'
                  }}>
                    Focus {rank.score}
                  </span>
                </div>
              )}
              <h3 className="homework-title">{hw.title}</h3>
              {rank && rank.reason && (
                <div style={{ fontSize: '12px', color: '#a1a1aa', fontStyle: 'italic', marginBottom: '8px', lineHeight: 1.4 }}>
                  "{rank.reason}"
                </div>
              )}

              <div className="homework-meta">
                <span className="badge badge-subject">{hw.subject}</span>
                {hw.source === 'google-docs' && (
                  <span className="badge badge-source">Google Docs</span>
                )}
                {!hw.completed && isOverdue(hw.dueDate) && (
                  <span className="badge badge-overdue">Overdue</span>
                )}
                {hw.completed && <span className="badge badge-points">+5 pts</span>}
              </div>

              <div className="homework-date">
                {new Date(hw.dueDate).toLocaleDateString()}
              </div>

              {hw.googleDocLink && (
                <a
                  href={hw.googleDocLink}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="homework-link"
                >
                  View Document
                </a>
              )}

              <div className="homework-actions">
                {!hw.completed && (
                  <button
                    className="btn btn-success btn-small"
                    onClick={() => handleComplete(hw.id)}
                  >
                    Done
                  </button>
                )}
                <button
                  className="btn btn-danger btn-small"
                  onClick={() => handleDelete(hw.id)}
                >
                  Delete
                </button>
              </div>
            </div>
            );
          })}
        </div>
      )}

      <AddHomeworkModal
        isOpen={showModal}
        onClose={() => setShowModal(false)}
        onAdd={() => setRefresh((r) => r + 1)}
        uid={uid}
      />
    </div>
  );
};

// Profile Page
const ProfilePage = ({ userData, uid, onLogout }) => {
  const levelData = getLevelProgress(userData?.points || 0);
  const [googleLinked, setGoogleLinked] = React.useState(false);
  const [linkingGoogle, setLinkingGoogle] = React.useState(false);
  const [linkMessage, setLinkMessage] = React.useState('');
  const [completionRate, setCompletionRate] = React.useState('--');
  // Inline display-name editor (used so the dashboard greeting can be personalised)
  const [editingName, setEditingName] = React.useState(false);
  const [nameDraft, setNameDraft] = React.useState('');
  const [nameSaving, setNameSaving] = React.useState(false);

  React.useEffect(() => { setNameDraft(userData?.displayName || ''); }, [userData?.displayName]);

  const saveName = async () => {
    const trimmed = nameDraft.trim();
    if (!trimmed || !uid) return;
    setNameSaving(true);
    try {
      await db.collection('users').doc(uid).update({ displayName: trimmed });
      try { await auth.currentUser?.updateProfile({ displayName: trimmed }); } catch {}
      setEditingName(false);
    } catch (e) {
      console.warn('[FocusForge] saveName failed:', e);
    } finally {
      setNameSaving(false);
    }
  };

  // Calculate homework completion rate
  React.useEffect(() => {
    if (uid) {
      db.collection('users').doc(uid).collection('homework').get().then((snapshot) => {
        const total = snapshot.size;
        if (total === 0) { setCompletionRate('--'); return; }
        const completed = snapshot.docs.filter(d => d.data().completed).length;
        setCompletionRate(Math.round((completed / total) * 100) + '%');
      });
    }
  }, [uid]);

  React.useEffect(() => {
    const user = auth.currentUser;
    if (user) {
      const hasToken = !!localStorage.getItem('googleAccessToken');
      setGoogleLinked(hasToken);
      if (localStorage.getItem('pendingGoogleLink') && hasToken) {
        setLinkMessage('Google account connected successfully!');
        localStorage.removeItem('pendingGoogleLink');
      }
    }
  }, []);

  const handleConnectGoogle = async () => {
    setLinkingGoogle(true);
    setLinkMessage('');
    const provider = new firebase.auth.GoogleAuthProvider();
    provider.addScope('https://www.googleapis.com/auth/documents.readonly');
    provider.addScope('https://www.googleapis.com/auth/drive.readonly');
    provider.setCustomParameters({ prompt: 'consent' });

    try {
      // Use signInWithPopup — it always returns the OAuth access token
      const result = await auth.signInWithPopup(provider);
      const accessToken = result.credential ? result.credential.accessToken : null;

      if (accessToken) {
        localStorage.setItem('googleAccessToken', accessToken);
        setLinkMessage('Google account connected successfully!');
        setGoogleLinked(true);
      } else {
        setLinkMessage('Connected but could not get API token. Please try again.');
      }
    } catch (error) {
      console.error('Google link error:', error);
      if (error.code === 'auth/popup-closed-by-user') {
        setLinkMessage('Sign-in was cancelled.');
      } else if (error.code === 'auth/credential-already-in-use') {
        setLinkMessage('This Google account is already linked to another account.');
      } else {
        setLinkMessage('Error: ' + error.message);
      }
    }
    setLinkingGoogle(false);
  };

  const handleDisconnectGoogle = async () => {
    setLinkingGoogle(true);
    setLinkMessage('');
    try {
      const user = auth.currentUser;
      const hasPassword = user.providerData.some(p => p.providerId === 'password');
      if (!hasPassword && user.providerData.length <= 1) {
        setLinkMessage('Cannot disconnect Google — it is your only sign-in method. Add a password first.');
        setLinkingGoogle(false);
        return;
      }
      await user.unlink('google.com');
      localStorage.removeItem('googleAccessToken');
      setGoogleLinked(false);
      setLinkMessage('Google account disconnected.');
    } catch (error) {
      console.error('Google unlink error:', error);
      setLinkMessage('Error: ' + error.message);
    }
    setLinkingGoogle(false);
  };

  const milestones = [
    { name: 'First Pomodoro', icon: '🍅', achieved: (userData?.totalPomodoros || 0) >= 1 },
    { name: '10 Pomodoros', icon: '🔟', achieved: (userData?.totalPomodoros || 0) >= 10 },
    { name: 'Study 10 Hours', icon: '⏰', achieved: (userData?.totalStudyMinutes || 0) >= 600 },
    { name: 'Reach Level 5', icon: '🏆', achieved: levelData.current >= 5 },
    { name: '7-Day Streak', icon: '🔥', achieved: (userData?.currentStreak || 0) >= 7 },
  ];

  const GoogleIcon = () => (
    <svg width="18" height="18" viewBox="0 0 48 48" style={{ verticalAlign: 'middle', marginRight: '8px' }}>
      <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>
      <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/>
      <path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/>
      <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/>
    </svg>
  );

  return (
    <div>
      <h1 className="page-title">Profile</h1>

      <div className="profile-header">
        <div className="profile-avatar-large">
          {userData?.photoURL ? (
            <img src={userData.photoURL} alt="User" />
          ) : (
            <span style={{ fontSize: '48px' }}>👤</span>
          )}
        </div>

        <div className="profile-info">
          {editingName ? (
            <div style={{ display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginBottom: '4px' }}>
              <input
                type="text"
                value={nameDraft}
                onChange={e => setNameDraft(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter') saveName(); if (e.key === 'Escape') { setEditingName(false); setNameDraft(userData?.displayName || ''); } }}
                placeholder="Your name"
                maxLength={40}
                autoFocus
                style={{ fontSize: '20px', fontWeight: 600, padding: '6px 10px', minWidth: '180px' }}
              />
              <button className="btn btn-primary btn-small" onClick={saveName} disabled={nameSaving || !nameDraft.trim()}>
                {nameSaving ? 'Saving…' : 'Save'}
              </button>
              <button className="btn btn-secondary btn-small" onClick={() => { setEditingName(false); setNameDraft(userData?.displayName || ''); }}>
                Cancel
              </button>
            </div>
          ) : (
            <h2 className="profile-name" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              {userData?.displayName || 'Add your name'}
              <button
                onClick={() => setEditingName(true)}
                title="Edit name"
                style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: '16px', padding: '4px 8px', color: 'var(--text-muted)' }}
              >✏️</button>
            </h2>
          )}
          <p className="profile-email">{userData?.email}</p>

          <div className="profile-level-badge">
            <span>Level {levelData.current}</span>
          </div>

          <div className="profile-progress">
            <div className="profile-progress-label">Level Progress</div>
            <div className="progress-bar">
              <div className="progress-fill" style={{ width: `${levelData.progress}%` }}></div>
            </div>
            <p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '8px' }}>
              {userData?.points || 0} / {levelData.current * 100} points
            </p>
          </div>

          <button className="btn btn-danger" onClick={onLogout} style={{ marginTop: '20px' }}>
            Sign Out
          </button>
        </div>
      </div>

      {/* Google Account Connection */}
      <div className="glass-card" style={{ marginBottom: '24px' }}>
        <h3 style={{ marginBottom: '16px', fontSize: '18px', fontWeight: '600' }}>
          <GoogleIcon />
          Google Account
        </h3>

        {googleLinked ? (
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '12px' }}>
              <span style={{ color: '#34A853', fontSize: '20px' }}>✓</span>
              <span style={{ color: '#34A853', fontWeight: '600' }}>Google Account Connected</span>
            </div>
            <p style={{ fontSize: '13px', color: 'var(--text-muted)', marginBottom: '12px' }}>
              Your Google account is linked. Homework sync from Google Docs is available.
            </p>
            <button
              className="btn"
              onClick={handleDisconnectGoogle}
              disabled={linkingGoogle}
              style={{
                fontSize: '13px',
                background: 'rgba(234, 67, 53, 0.15)',
                color: '#EA4335',
                border: '1px solid rgba(234, 67, 53, 0.3)',
              }}
            >
              {linkingGoogle ? 'Disconnecting...' : 'Disconnect Google Account'}
            </button>
          </div>
        ) : (
          <div>
            <p style={{ fontSize: '13px', color: 'var(--text-muted)', marginBottom: '16px' }}>
              Connect your Google account to enable automatic homework sync from Google Docs.
              This is required for the Google Docs integration in the Homework tab.
            </p>
            <button
              className="btn"
              onClick={handleConnectGoogle}
              disabled={linkingGoogle}
              style={{
                background: '#ffffff',
                color: '#1f1f1f',
                fontWeight: '600',
                display: 'flex',
                alignItems: 'center',
                gap: '10px',
                padding: '10px 20px'
              }}
            >
              <GoogleIcon />
              {linkingGoogle ? 'Connecting...' : 'Connect Google Account'}
            </button>
          </div>
        )}

        {linkMessage && (
          <p style={{
            marginTop: '12px',
            fontSize: '13px',
            color: linkMessage.includes('Error') || linkMessage.includes('Cannot') || linkMessage.includes('cancelled') ? '#ff6b6b' : '#34A853'
          }}>
            {linkMessage}
          </p>
        )}
      </div>

      {/* Notification Settings */}
      <NotificationSettings uid={uid} userData={userData} />
      <AccessibilitySection />
      <PrivacyControlsSection uid={uid} />
      <ThemesSection uid={uid} />
      <ShareProgressCard userData={userData} />
      {/* AI Features section hidden from Profile — component + localStorage key still work.
          Re-render with <AISettings uid={uid} userData={userData} /> if you want it back. */}

      <div className="grid-2" style={{ marginBottom: '24px' }}>
        <div className="stat-card cyan">
          <div className="stat-value">{formatMinutes(userData?.totalStudyMinutes || 0)}</div>
          <div className="stat-label">Total Study Time</div>
        </div>
        <div className="stat-card blue">
          <div className="stat-value">{userData?.totalPomodoros || 0}</div>
          <div className="stat-label">Total Pomodoros</div>
        </div>
        <div className="stat-card orange">
          <div className="stat-value">{userData?.currentStreak || 0}</div>
          <div className="stat-label">Day Streak</div>
        </div>
        <div className="stat-card yellow">
          <div className="stat-value">{completionRate}</div>
          <div className="stat-label">Completion Rate</div>
        </div>
      </div>

      <div style={{ marginBottom: '32px' }}>
        <h3 style={{ marginBottom: '16px', fontSize: '18px', fontWeight: '600' }}>Milestones</h3>
        <div className="milestones-grid">
          {milestones.map((milestone) => (
            <div
              key={milestone.name}
              className={`milestone-card ${milestone.achieved ? 'unlocked' : 'locked'}`}
            >
              <div className="milestone-icon">{milestone.icon}</div>
              <div className="milestone-name">{milestone.name}</div>
              <div className={`milestone-status ${milestone.achieved ? 'unlocked' : ''}`}>
                {milestone.achieved ? 'Unlocked' : 'Locked'}
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Clear All History */}
      <ClearHistorySection uid={uid} />
    </div>
  );
};

// Clear History Section Component
const ClearHistorySection = ({ uid }) => {
  const [clearing, setClearing] = React.useState(false);
  const [clearMessage, setClearMessage] = React.useState('');
  const [confirmOpen, setConfirmOpen] = React.useState(false);
  const [selectedItems, setSelectedItems] = React.useState({
    homework: true,
    todos: true,
    studySessions: true,
    stats: false,
    googleDocs: false,
  });

  const toggleItem = (key) => {
    setSelectedItems(prev => ({ ...prev, [key]: !prev[key] }));
  };

  const deleteCollection = async (collectionRef) => {
    const snapshot = await collectionRef.get();
    const batch = db.batch();
    let count = 0;
    snapshot.docs.forEach(doc => {
      batch.delete(doc.ref);
      count++;
    });
    if (count > 0) await batch.commit();
    return count;
  };

  const handleClearHistory = async () => {
    setClearing(true);
    setClearMessage('Clearing...');
    let totalCleared = 0;

    try {
      const userRef = db.collection('users').doc(uid);

      if (selectedItems.homework) {
        const count = await deleteCollection(userRef.collection('homework'));
        totalCleared += count;
      }

      if (selectedItems.todos) {
        const count = await deleteCollection(userRef.collection('todos'));
        totalCleared += count;
      }

      if (selectedItems.studySessions) {
        const count = await deleteCollection(userRef.collection('studySessions'));
        totalCleared += count;
      }

      if (selectedItems.stats) {
        await userRef.update({
          points: 0,
          totalStudyMinutes: 0,
          totalPomodoros: 0,
          currentStreak: 0,
          lastStudyDate: null,
        });
      }

      if (selectedItems.googleDocs) {
        await deleteCollection(userRef.collection('settings'));
        localStorage.removeItem('googleAccessToken');
      }

      setClearMessage(`Done! Cleared ${totalCleared} items${selectedItems.stats ? ' and reset stats' : ''}.`);
      setConfirmOpen(false);
    } catch (error) {
      console.error('Clear history error:', error);
      setClearMessage('Error: ' + error.message);
    }

    setClearing(false);
  };

  const checkboxStyle = (checked) => ({
    display: 'flex',
    alignItems: 'center',
    gap: '10px',
    padding: '10px 14px',
    borderRadius: '8px',
    cursor: 'pointer',
    background: checked ? 'rgba(255, 107, 107, 0.1)' : 'rgba(255,255,255,0.03)',
    border: checked ? '1px solid rgba(255, 107, 107, 0.3)' : '1px solid rgba(255,255,255,0.08)',
    transition: 'all 0.2s',
    marginBottom: '8px',
  });

  return (
    <div className="glass-card" style={{ marginBottom: '24px', borderColor: 'rgba(255, 107, 107, 0.2)' }}>
      <h3 style={{ marginBottom: '6px', fontSize: '18px', fontWeight: '600', color: '#ff6b6b' }}>
        🗑️ Clear History
      </h3>
      <p style={{ fontSize: '13px', color: 'var(--text-muted)', marginBottom: '16px' }}>
        Select what you want to clear. This action cannot be undone.
      </p>

      <div style={{ marginBottom: '16px' }}>
        <div style={checkboxStyle(selectedItems.homework)} onClick={() => toggleItem('homework')}>
          <input type="checkbox" checked={selectedItems.homework} readOnly style={{ accentColor: '#ff6b6b' }} />
          <div>
            <div style={{ fontWeight: '500' }}>Homework</div>
            <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>All pending and completed homework items</div>
          </div>
        </div>

        <div style={checkboxStyle(selectedItems.todos)} onClick={() => toggleItem('todos')}>
          <input type="checkbox" checked={selectedItems.todos} readOnly style={{ accentColor: '#ff6b6b' }} />
          <div>
            <div style={{ fontWeight: '500' }}>To-Do List</div>
            <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>All tasks in your to-do list</div>
          </div>
        </div>

        <div style={checkboxStyle(selectedItems.studySessions)} onClick={() => toggleItem('studySessions')}>
          <input type="checkbox" checked={selectedItems.studySessions} readOnly style={{ accentColor: '#ff6b6b' }} />
          <div>
            <div style={{ fontWeight: '500' }}>Study Sessions</div>
            <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>All recorded pomodoro and study session history</div>
          </div>
        </div>

        <div style={checkboxStyle(selectedItems.stats)} onClick={() => toggleItem('stats')}>
          <input type="checkbox" checked={selectedItems.stats} readOnly style={{ accentColor: '#ff6b6b' }} />
          <div>
            <div style={{ fontWeight: '500' }}>Stats & Progress</div>
            <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>Reset points, study time, pomodoros, and streak to zero</div>
          </div>
        </div>

        <div style={checkboxStyle(selectedItems.googleDocs)} onClick={() => toggleItem('googleDocs')}>
          <input type="checkbox" checked={selectedItems.googleDocs} readOnly style={{ accentColor: '#ff6b6b' }} />
          <div>
            <div style={{ fontWeight: '500' }}>Google Docs Settings</div>
            <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>Remove saved doc link, class section, and disconnect token</div>
          </div>
        </div>
      </div>

      {!confirmOpen ? (
        <button
          className="btn btn-danger"
          onClick={() => {
            const anySelected = Object.values(selectedItems).some(v => v);
            if (!anySelected) { setClearMessage('Please select at least one item to clear.'); return; }
            setConfirmOpen(true);
            setClearMessage('');
          }}
          style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
        >
          🗑️ Clear Selected Data
        </button>
      ) : (
        <div style={{
          background: 'rgba(255, 107, 107, 0.1)',
          border: '1px solid rgba(255, 107, 107, 0.3)',
          borderRadius: '10px',
          padding: '16px',
        }}>
          <p style={{ fontWeight: '600', marginBottom: '12px', color: '#ff6b6b' }}>
            ⚠️ Are you sure? This cannot be undone.
          </p>
          <div style={{ display: 'flex', gap: '10px' }}>
            <button
              className="btn btn-danger"
              onClick={handleClearHistory}
              disabled={clearing}
            >
              {clearing ? 'Clearing...' : 'Yes, Clear Everything'}
            </button>
            <button
              className="btn"
              onClick={() => setConfirmOpen(false)}
              disabled={clearing}
              style={{ background: 'rgba(255,255,255,0.1)' }}
            >
              Cancel
            </button>
          </div>
        </div>
      )}

      {clearMessage && (
        <p style={{
          marginTop: '12px',
          fontSize: '13px',
          color: clearMessage.includes('Error') || clearMessage.includes('Please') ? '#ff6b6b' : '#34A853'
        }}>
          {clearMessage}
        </p>
      )}
    </div>
  );
};

// To-Do List (shows in floating panel)
const TodoList = ({ uid }) => {
  const [todos, setTodos] = React.useState([]);
  const [showForm, setShowForm] = React.useState(false);
  const [isExpanded, setIsExpanded] = React.useState(true);
  const [taskName, setTaskName] = React.useState('');
  const [dueDate, setDueDate] = React.useState('');
  const [priority, setPriority] = React.useState('Medium');
  const [dragId, setDragId] = React.useState(null);
  const [dragOverId, setDragOverId] = React.useState(null);

  React.useEffect(() => {
    if (uid) {
      const unsubscribe = db.collection('users').doc(uid).collection('todos')
        .orderBy('order', 'asc')
        .onSnapshot((snapshot) => {
          setTodos(snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })));
        });
      return unsubscribe;
    }
  }, [uid]);

  const handleAddTask = async (e) => {
    e.preventDefault();
    if (!taskName) return;

    if (uid) {
      await db.collection('users').doc(uid).collection('todos').add({
        title: taskName,
        dueDate,
        priority,
        completed: false,
        order: Date.now(),
        createdAt: new Date()
      });
      setTaskName('');
      setDueDate('');
      setPriority('Medium');
      setShowForm(false);
    }
  };

  const handleToggle = async (id, completed) => {
    if (uid) {
      await db.collection('users').doc(uid).collection('todos').doc(id).update({
        completed: !completed
      });
    }
  };

  const handleDelete = async (id) => {
    if (uid) {
      await db.collection('users').doc(uid).collection('todos').doc(id).delete();
    }
  };

  // Drag-and-drop handlers
  const handleDragStart = (e, id) => {
    setDragId(id);
    e.dataTransfer.effectAllowed = 'move';
    e.target.style.opacity = '0.4';
  };

  const handleDragEnd = (e) => {
    e.target.style.opacity = '1';
    setDragId(null);
    setDragOverId(null);
  };

  const handleDragOver = (e, id) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    if (id !== dragOverId) setDragOverId(id);
  };

  const handleDrop = async (e, targetId) => {
    e.preventDefault();
    if (!dragId || dragId === targetId || !uid) return;

    const dragIndex = todos.findIndex(t => t.id === dragId);
    const targetIndex = todos.findIndex(t => t.id === targetId);
    if (dragIndex === -1 || targetIndex === -1) { setDragId(null); setDragOverId(null); return; }

    const reordered = [...todos];
    const [moved] = reordered.splice(dragIndex, 1);
    reordered.splice(targetIndex, 0, moved);

    // Only rewrite items whose index actually changed. Reduces writes from O(n) to O(Δ)
    // AND makes partial-failure states less disruptive.
    const batch = db.batch();
    let writes = 0;
    reordered.forEach((todo, idx) => {
      if (todo.order !== idx) {
        batch.update(db.collection('users').doc(uid).collection('todos').doc(todo.id), { order: idx });
        writes++;
      }
    });
    try {
      if (writes > 0) await batch.commit();
    } catch (err) {
      console.warn('[FocusForge] Reorder failed:', err);
      // Firestore offline persistence will retry. Surface only truly unrecoverable errors.
      if (err.code === 'permission-denied') alert('Reorder failed: ' + err.message);
    } finally {
      setDragId(null);
      setDragOverId(null);
    }
  };

  const pendingCount = todos.filter((t) => !t.completed).length;

  return (
    <div className="todo-section glass-card">
      <div className="todo-header" onClick={() => setIsExpanded(!isExpanded)}>
        <div className="todo-title">
          <span>{isExpanded ? '▼' : '▶'}</span>
          To-Do List
          {pendingCount > 0 && <span className="todo-badge">{pendingCount}</span>}
        </div>
      </div>

      {isExpanded && (
        <div className="todo-content">
          {showForm ? (
            <form onSubmit={handleAddTask} className="todo-form">
              <input
                type="text"
                value={taskName}
                onChange={(e) => setTaskName(e.target.value)}
                placeholder="Add a task..."
                autoFocus
              />
              <div className="todo-input-row">
                <input
                  type="date"
                  value={dueDate}
                  onChange={(e) => setDueDate(e.target.value)}
                />
                <select value={priority} onChange={(e) => setPriority(e.target.value)}>
                  <option>Low</option>
                  <option>Medium</option>
                  <option>High</option>
                </select>
                <button type="submit" className="btn btn-primary btn-small" style={{ width: '100%' }}>
                  Add
                </button>
              </div>
            </form>
          ) : (
            <button
              className="btn btn-secondary btn-small"
              onClick={() => setShowForm(true)}
              style={{ width: '100%', marginBottom: '12px' }}
            >
              + Add Task
            </button>
          )}

          {todos.map((todo) => (
            <div
              key={todo.id}
              className="todo-item"
              draggable
              onDragStart={(e) => handleDragStart(e, todo.id)}
              onDragEnd={handleDragEnd}
              onDragOver={(e) => handleDragOver(e, todo.id)}
              onDrop={(e) => handleDrop(e, todo.id)}
              style={{
                borderTop: dragOverId === todo.id && dragId !== todo.id ? '2px solid #00d4ff' : '2px solid transparent',
                cursor: 'grab'
              }}
            >
              <div style={{ cursor: 'grab', marginRight: '6px', color: 'var(--text-subtle)', fontSize: '14px' }}>⠿</div>
              <div
                className={`todo-checkbox ${todo.completed ? 'checked' : ''}`}
                onClick={() => handleToggle(todo.id, todo.completed)}
              />
              <div className="todo-text" style={{
                textDecoration: todo.completed ? 'line-through' : 'none',
                color: todo.completed ? 'rgba(255,255,255,0.4)' : 'inherit'
              }}>
                {todo.title}
                {todo.dueDate && (
                  <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '4px' }}>
                    Due: {new Date(todo.dueDate).toLocaleDateString()}
                  </div>
                )}
              </div>
              {todo.priority && (
                <span className={`badge badge-priority-${todo.priority.toLowerCase()}`}>
                  {todo.priority}
                </span>
              )}
              <button className="todo-delete" onClick={() => handleDelete(todo.id)}>
                ✕
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

// Floating To-Do Panel (visible on all pages)
// ============= AMBIENT FOCUS SOUNDS =============
// Procedural audio generator using Web Audio API — no external files needed
const createAmbientAudio = () => {
  let ctx = null;
  let currentSource = null;
  let gainNode = null;

  const ensureCtx = () => {
    if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)();
    if (ctx.state === 'suspended') ctx.resume();
    return ctx;
  };

  const makeNoiseBuffer = (type) => {
    const buffer = ctx.createBuffer(2, ctx.sampleRate * 4, ctx.sampleRate);
    for (let channel = 0; channel < 2; channel++) {
      const data = buffer.getChannelData(channel);
      if (type === 'white') {
        for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
      } else if (type === 'pink') {
        // Paul Kellet's pink noise algorithm
        let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
        for (let i = 0; i < data.length; i++) {
          const white = Math.random() * 2 - 1;
          b0 = 0.99886 * b0 + white * 0.0555179;
          b1 = 0.99332 * b1 + white * 0.0750759;
          b2 = 0.96900 * b2 + white * 0.1538520;
          b3 = 0.86650 * b3 + white * 0.3104856;
          b4 = 0.55000 * b4 + white * 0.5329522;
          b5 = -0.7616 * b5 - white * 0.0168980;
          data[i] = (b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362) * 0.11;
          b6 = white * 0.115926;
        }
      } else if (type === 'brown') {
        let lastOut = 0;
        for (let i = 0; i < data.length; i++) {
          const white = Math.random() * 2 - 1;
          data[i] = (lastOut + (0.02 * white)) / 1.02;
          lastOut = data[i];
          data[i] *= 3.5;
        }
      }
    }
    return buffer;
  };

  const stop = () => {
    if (currentSource) {
      try { currentSource.stop(); } catch (e) {}
      currentSource.disconnect();
      currentSource = null;
    }
    if (gainNode) {
      gainNode.disconnect();
      gainNode = null;
    }
  };

  const play = (soundType, volume = 0.3) => {
    stop();
    ensureCtx();
    gainNode = ctx.createGain();
    gainNode.gain.value = volume;
    gainNode.connect(ctx.destination);

    if (soundType === 'white' || soundType === 'pink' || soundType === 'brown') {
      const buffer = makeNoiseBuffer(soundType);
      currentSource = ctx.createBufferSource();
      currentSource.buffer = buffer;
      currentSource.loop = true;
      currentSource.connect(gainNode);
      currentSource.start();
    } else if (soundType === 'rain') {
      // Filtered pink noise + occasional drops
      const buffer = makeNoiseBuffer('pink');
      currentSource = ctx.createBufferSource();
      currentSource.buffer = buffer;
      currentSource.loop = true;
      const filter = ctx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.value = 2200;
      currentSource.connect(filter);
      filter.connect(gainNode);
      currentSource.start();
    } else if (soundType === 'ocean') {
      // Brown noise with slow LFO modulation for wave-like feel
      const buffer = makeNoiseBuffer('brown');
      currentSource = ctx.createBufferSource();
      currentSource.buffer = buffer;
      currentSource.loop = true;
      const lfo = ctx.createOscillator();
      const lfoGain = ctx.createGain();
      lfo.frequency.value = 0.13;
      lfoGain.gain.value = volume * 0.6;
      lfo.connect(lfoGain.gain);
      const waveGain = ctx.createGain();
      waveGain.gain.value = volume;
      currentSource.connect(waveGain);
      waveGain.connect(gainNode);
      lfo.start();
      currentSource.start();
    } else if (soundType === 'forest') {
      // Filtered pink noise + subtle wind
      const buffer = makeNoiseBuffer('pink');
      currentSource = ctx.createBufferSource();
      currentSource.buffer = buffer;
      currentSource.loop = true;
      const filter = ctx.createBiquadFilter();
      filter.type = 'bandpass';
      filter.frequency.value = 800;
      filter.Q.value = 0.6;
      currentSource.connect(filter);
      filter.connect(gainNode);
      currentSource.start();
    }
  };

  const setVolume = (vol) => {
    if (gainNode) gainNode.gain.value = vol;
  };

  return { play, stop, setVolume };
};

const ambientAudio = createAmbientAudio();

// ============= VOICE COMMANDS =============
// Uses Web Speech API to recognize commands like "start timer", "go to homework"
const VoiceCommandsPanel = ({ onNavigate, onTimerToggle, onTimerReset }) => {
  const [listening, setListening] = React.useState(false);
  const [transcript, setTranscript] = React.useState('');
  const [status, setStatus] = React.useState(''); // 'ok' | 'error' | 'heard'
  const [supported, setSupported] = React.useState(true);
  const recognitionRef = React.useRef(null);

  React.useEffect(() => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) { setSupported(false); return; }
    const rec = new SR();
    rec.continuous = false;
    rec.interimResults = true;
    rec.lang = 'en-US';
    rec.onresult = (e) => {
      const result = e.results[e.results.length - 1];
      const alt = result[0];
      const text = alt.transcript.trim().toLowerCase();
      setTranscript(text);
      if (result.isFinal) {
        // Only fire the command when the engine is reasonably confident.
        // Some browsers don't populate .confidence (reports 0) — fall back to accepting in that case.
        const conf = typeof alt.confidence === 'number' ? alt.confidence : 1;
        if (conf === 0 || conf >= 0.6) {
          handleCommand(text);
        } else {
          setTranscript(text + ' (low confidence — try again)');
        }
      }
    };
    rec.onerror = (e) => {
      setStatus('error');
      setTranscript(`Error: ${e.error}`);
      setListening(false);
    };
    rec.onend = () => { setListening(false); };
    recognitionRef.current = rec;
  }, []);

  const handleCommand = (text) => {
    // Navigation
    const pages = ['dashboard', 'timer', 'homework', 'calendar', 'analytics', 'goals', 'notes', 'profile', 'journal', 'achievements', 'aiplan'];
    const pageAliases = {
      'dashboard': 'dashboard', 'home': 'dashboard',
      'timer': 'timer', 'study timer': 'timer', 'pomodoro': 'timer',
      'homework': 'homework', 'assignments': 'homework',
      'calendar': 'calendar',
      'analytics': 'analytics', 'stats': 'analytics', 'statistics': 'analytics',
      'goals': 'goals',
      'notes': 'notes',
      'profile': 'profile', 'settings': 'profile',
      'journal': 'journal',
      'achievements': 'achievements', 'badges': 'achievements',
      'ai plan': 'aiplan', 'study plan': 'aiplan', 'plan': 'aiplan'
    };

    let matched = false;
    // Go to / navigate
    if (/\b(?:go to|open|show|navigate to?)\s+(.+)/i.test(text)) {
      const m = text.match(/\b(?:go to|open|show|navigate to?)\s+(.+)/i);
      const key = Object.keys(pageAliases).find(k => m[1].includes(k));
      if (key) {
        onNavigate(pageAliases[key]);
        setStatus('ok');
        setTranscript(`Navigating to ${pageAliases[key]}`);
        matched = true;
      }
    }
    // Start / pause timer
    if (!matched && /\b(start|pause|stop|resume|toggle)\s*(?:the\s*)?(?:timer|pomodoro)\b/i.test(text)) {
      onTimerToggle();
      setStatus('ok');
      setTranscript('Timer toggled');
      matched = true;
    }
    // Reset
    if (!matched && /\breset\s*(?:the\s*)?(?:timer|pomodoro)\b/i.test(text)) {
      onTimerReset();
      setStatus('ok');
      setTranscript('Timer reset');
      matched = true;
    }

    if (!matched) {
      setStatus('error');
      setTranscript(`Didn't understand: "${text}"`);
    }
    setTimeout(() => setStatus(''), 2500);
  };

  const startListening = () => {
    if (!recognitionRef.current) return;
    setStatus('');
    setTranscript('Listening…');
    setListening(true);
    try { recognitionRef.current.start(); } catch (e) { setListening(false); }
  };

  if (!supported) return null;

  return (
    <>
      <button
        onClick={startListening}
        title={listening ? 'Listening…' : 'Voice commands — click and speak'}
        style={{
          position: 'fixed', bottom: '172px', right: '24px', zIndex: 99,
          width: '56px', height: '56px', borderRadius: '50%',
          background: listening ? '#ef4444' : '#18181b',
          border: '1px solid rgba(255,255,255,0.1)', cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: '22px', boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
          transition: 'all 0.15s',
          animation: listening ? 'voicePulse 1.2s ease-in-out infinite' : 'none'
        }}
      >🎤</button>

      {(listening || status) && (
        <div style={{
          position: 'fixed', bottom: '240px', right: '24px', zIndex: 99,
          maxWidth: '320px', padding: '10px 14px', borderRadius: '10px',
          background: status === 'error' ? '#18181b' : '#18181b',
          border: `1px solid ${status === 'error' ? 'rgba(239,68,68,0.3)' : 'rgba(6,182,212,0.3)'}`,
          boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
          fontSize: '12px', color: status === 'error' ? '#ef4444' : '#a1a1aa'
        }}>
          {transcript || 'Listening…'}
        </div>
      )}
    </>
  );
};

const AmbientSoundsPanel = () => {
  const [isOpen, setIsOpen] = React.useState(false);
  const [currentSound, setCurrentSound] = React.useState(null);
  const [volume, setVolume] = React.useState(() => parseFloat(localStorage.getItem('ambient-volume') || '0.3'));

  const sounds = [
    { id: 'rain', label: 'Rain', icon: '🌧️' },
    { id: 'ocean', label: 'Ocean', icon: '🌊' },
    { id: 'forest', label: 'Forest', icon: '🌲' },
    { id: 'white', label: 'White Noise', icon: '🌫️' },
    { id: 'pink', label: 'Pink Noise', icon: '🌸' },
    { id: 'brown', label: 'Brown Noise', icon: '🍂' },
  ];

  // Guard against rapid-fire clicks: if we're already transitioning, ignore further clicks.
  const switchingRef = React.useRef(false);
  const togglePlay = (soundId) => {
    if (switchingRef.current) return;
    switchingRef.current = true;
    try {
      if (currentSound === soundId) {
        ambientAudio.stop();
        setCurrentSound(null);
      } else {
        // Always stop any currently playing sound before starting a new one — prevents
        // two sound sources overlapping if the user switches quickly.
        if (currentSound) ambientAudio.stop();
        ambientAudio.play(soundId, volume);
        setCurrentSound(soundId);
      }
    } finally {
      // Small delay so the Web Audio graph has time to settle before the next click
      setTimeout(() => { switchingRef.current = false; }, 150);
    }
  };

  // Stop ambient sound when the panel itself unmounts (app reload / sign-out)
  React.useEffect(() => () => { try { ambientAudio.stop(); } catch {} }, []);

  const onVolumeChange = (v) => {
    setVolume(v);
    ambientAudio.setVolume(v);
    localStorage.setItem('ambient-volume', String(v));
  };

  return (
    <>
      <button
        className="ambient-fab"
        onClick={() => setIsOpen(o => !o)}
        title="Ambient sounds"
        style={{
          position: 'fixed', bottom: '100px', right: '24px', zIndex: 99,
          width: '56px', height: '56px', borderRadius: '50%',
          background: currentSound ? '#06b6d4' : '#18181b',
          border: '1px solid rgba(255,255,255,0.1)', cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: '22px', boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
          transition: 'all 0.15s'
        }}
      >
        {currentSound ? '🎵' : '🔇'}
      </button>

      {isOpen && (
        <div className="ambient-panel" style={{
          position: 'fixed', bottom: '170px', right: '24px', zIndex: 99,
          width: '280px', background: '#18181b',
          border: '1px solid rgba(255,255,255,0.08)', borderRadius: '16px',
          padding: '16px', boxShadow: '0 8px 24px rgba(0,0,0,0.3)'
        }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px' }}>
            <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>🎵 Ambient Sounds</div>
            <button
              onClick={() => setIsOpen(false)}
              style={{ background: 'none', border: 'none', color: '#71717a', cursor: 'pointer', fontSize: '16px', fontFamily: 'inherit' }}
            >×</button>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '14px' }}>
            {sounds.map(s => (
              <button
                key={s.id}
                onClick={() => togglePlay(s.id)}
                style={{
                  padding: '10px 8px', borderRadius: '10px', cursor: 'pointer',
                  background: currentSound === s.id ? 'rgba(6,182,212,0.15)' : '#27272a',
                  border: `1px solid ${currentSound === s.id ? 'rgba(6,182,212,0.4)' : 'rgba(255,255,255,0.05)'}`,
                  color: currentSound === s.id ? '#06b6d4' : '#a1a1aa',
                  fontSize: '12px', fontWeight: 500, display: 'flex',
                  flexDirection: 'column', alignItems: 'center', gap: '4px',
                  transition: 'all 0.15s', fontFamily: 'inherit'
                }}
              >
                <span style={{ fontSize: '20px' }}>{s.icon}</span>
                <span>{s.label}</span>
              </button>
            ))}
          </div>

          <div>
            <div className="ach-desc" style={{ fontSize: '11px', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 500 }}>
              Volume {Math.round(volume * 100)}%
            </div>
            <input
              type="range" min="0" max="1" step="0.05"
              value={volume}
              onChange={e => onVolumeChange(parseFloat(e.target.value))}
              style={{ width: '100%', accentColor: '#06b6d4' }}
            />
          </div>

          {currentSound && (
            <button
              onClick={() => togglePlay(currentSound)}
              className="btn btn-secondary btn-small"
              style={{ width: '100%', marginTop: '12px' }}
            >
              ⏸ Stop
            </button>
          )}
        </div>
      )}
    </>
  );
};

const TodoFloatingPanel = ({ uid }) => {
  const [isOpen, setIsOpen] = React.useState(false);
  const [pendingCount, setPendingCount] = React.useState(0);

  React.useEffect(() => {
    if (uid) {
      const unsubscribe = db.collection('users').doc(uid).collection('todos')
        .where('completed', '==', false)
        .onSnapshot((snapshot) => {
          setPendingCount(snapshot.size);
        });
      return unsubscribe;
    }
  }, [uid]);

  return (
    <React.Fragment>
      <button className="todo-floating-btn" onClick={() => setIsOpen(!isOpen)}>
        {isOpen ? '✕' : '📝'}
        {!isOpen && pendingCount > 0 && (
          <span className="badge-count">{pendingCount}</span>
        )}
      </button>
      {isOpen && (
        <div className="todo-panel">
          <TodoList uid={uid} />
        </div>
      )}
    </React.Fragment>
  );
};

// ============= NOTIFICATION SETTINGS =============
const NotificationSettings = ({ uid, userData }) => {
  const defaults = { timer: true, homeworkDue: true, streakWarning: true };
  const prefs = userData?.notificationPrefs || defaults;

  const togglePref = async (key) => {
    if (!uid) return;
    const updated = { ...defaults, ...prefs, [key]: !prefs[key] };
    await db.collection('users').doc(uid).update({ notificationPrefs: updated });
    if (updated[key]) requestNotificationPermission();
  };

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <h3 style={{ marginBottom: '16px', fontSize: '18px', fontWeight: '600' }}>Notification Settings</h3>
      <div className="notif-toggle-row">
        <div>
          <div style={{ fontWeight: '500' }}>Timer Notifications</div>
          <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>Get notified when a pomodoro or custom timer completes</div>
        </div>
        <div className={`toggle-switch ${prefs.timer !== false ? 'on' : ''}`} onClick={() => togglePref('timer')} />
      </div>
      <div className="notif-toggle-row">
        <div>
          <div style={{ fontWeight: '500' }}>Homework Due Reminders</div>
          <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>Remind about upcoming homework deadlines</div>
        </div>
        <div className={`toggle-switch ${prefs.homeworkDue !== false ? 'on' : ''}`} onClick={() => togglePref('homeworkDue')} />
      </div>
      <div className="notif-toggle-row">
        <div>
          <div style={{ fontWeight: '500' }}>Streak Warning</div>
          <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>Warn when your study streak is about to break</div>
        </div>
        <div className={`toggle-switch ${prefs.streakWarning !== false ? 'on' : ''}`} onClick={() => togglePref('streakWarning')} />
      </div>
    </div>
  );
};

// ============= SHARE PROGRESS CARD =============
const ShareProgressCard = ({ userData }) => {
  const canvasRef = React.useRef(null);
  const [imgUrl, setImgUrl] = React.useState(null);
  const [achievementCount, setAchievementCount] = React.useState(0);

  React.useEffect(() => {
    const uid = firebase.auth().currentUser?.uid;
    if (!uid) return;
    db.collection('users').doc(uid).collection('achievements').get().then(snap => {
      setAchievementCount(snap.size);
    });
  }, []);

  const level = Math.floor((userData?.points || 0) / 100) + 1;
  const points = userData?.points || 0;
  const streak = userData?.currentStreak || 0;
  const totalMin = userData?.totalStudyMinutes || 0;
  const totalPomo = userData?.totalPomodoros || 0;
  const name = userData?.displayName?.split(' ')[0] || 'Student';

  const draw = async () => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    // Wait for Inter to load — canvas 2D falls back to serif without this, making
    // the card look blocky/misaligned. Safe to skip if document.fonts unavailable.
    if (document.fonts && document.fonts.load) {
      try {
        await Promise.all([
          document.fonts.load('700 36px Inter'),
          document.fonts.load('500 16px Inter'),
        ]);
      } catch {}
    }
    const W = 1200, H = 630; // 1.91:1 OG image ratio
    canvas.width = W; canvas.height = H;
    const ctx = canvas.getContext('2d');

    // Background gradient
    const bg = ctx.createLinearGradient(0, 0, W, H);
    bg.addColorStop(0, '#0a0a0a');
    bg.addColorStop(0.5, '#0f0f1a');
    bg.addColorStop(1, '#0a0a0a');
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, W, H);

    // Decorative orbs
    const orb1 = ctx.createRadialGradient(W * 0.8, H * 0.2, 0, W * 0.8, H * 0.2, 400);
    orb1.addColorStop(0, 'rgba(6, 182, 212, 0.25)');
    orb1.addColorStop(1, 'rgba(6, 182, 212, 0)');
    ctx.fillStyle = orb1; ctx.fillRect(0, 0, W, H);
    const orb2 = ctx.createRadialGradient(W * 0.2, H * 0.8, 0, W * 0.2, H * 0.8, 350);
    orb2.addColorStop(0, 'rgba(139, 92, 246, 0.2)');
    orb2.addColorStop(1, 'rgba(139, 92, 246, 0)');
    ctx.fillStyle = orb2; ctx.fillRect(0, 0, W, H);

    // Brand
    ctx.fillStyle = '#06b6d4';
    ctx.font = '700 36px Inter, -apple-system, sans-serif';
    ctx.fillText('⚡ FocusForge', 60, 80);

    // Name
    ctx.fillStyle = '#fafafa';
    ctx.font = '700 64px Inter, -apple-system, sans-serif';
    ctx.fillText(`${name}'s Progress`, 60, 180);

    // Level badge
    ctx.fillStyle = 'rgba(139, 92, 246, 0.2)';
    ctx.strokeStyle = 'rgba(139, 92, 246, 0.5)';
    ctx.lineWidth = 2;
    const lx = 60, ly = 220, lw = 200, lh = 70;
    ctx.beginPath();
    ctx.roundRect(lx, ly, lw, lh, 35);
    ctx.fill(); ctx.stroke();
    ctx.fillStyle = '#a78bfa';
    ctx.font = '700 32px Inter, -apple-system, sans-serif';
    ctx.fillText(`🏆 Level ${level}`, lx + 30, ly + 46);

    // Stats grid — 2x2
    const stats = [
      { label: 'Study Time', value: formatMinutes(totalMin), color: '#06b6d4' },
      { label: 'Pomodoros', value: `${totalPomo}`, color: '#22c55e' },
      { label: 'Day Streak', value: `${streak} 🔥`, color: '#f97316' },
      { label: 'Achievements', value: `${achievementCount}/${ACHIEVEMENTS.length} 🏆`, color: '#eab308' }
    ];
    const gx = 60, gy = 340, gw = (W - 120 - 20) / 2, gh = 120;
    stats.forEach((s, i) => {
      const col = i % 2, row = Math.floor(i / 2);
      const x = gx + col * (gw + 20), y = gy + row * (gh + 20);
      ctx.fillStyle = 'rgba(255, 255, 255, 0.04)';
      ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
      ctx.beginPath();
      ctx.roundRect(x, y, gw, gh, 16);
      ctx.fill(); ctx.stroke();
      ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
      ctx.font = '500 16px Inter, -apple-system, sans-serif';
      ctx.fillText(s.label.toUpperCase(), x + 24, y + 36);
      ctx.fillStyle = s.color;
      ctx.font = '700 42px Inter, -apple-system, sans-serif';
      ctx.fillText(s.value, x + 24, y + 86);
    });

    // Points
    ctx.fillStyle = '#fafafa';
    ctx.font = '500 20px Inter, -apple-system, sans-serif';
    ctx.fillText(`${points.toLocaleString()} XP earned`, 60, H - 40);

    // URL
    ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
    ctx.font = '500 16px Inter, -apple-system, sans-serif';
    ctx.textAlign = 'right';
    ctx.fillText('Generated with FocusForge', W - 60, H - 40);
    ctx.textAlign = 'left';

    setImgUrl(canvas.toDataURL('image/png'));
  };

  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

  const download = async () => {
    if (!canvasRef.current) return;
    const filename = `focusforge-progress-${new Date().toISOString().slice(0, 10)}.png`;
    // iOS Safari: `a.download` is ignored + large data URIs are rejected. Open in a
    // new tab so the user can long-press → Save Image instead.
    if (isIOS) {
      const dataUrl = canvasRef.current.toDataURL('image/png');
      const win = window.open();
      if (win) {
        win.document.write(
          `<title>${filename}</title>
           <style>body{margin:0;background:#09090b;display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;color:#fff}p{position:fixed;top:12px;left:12px;right:12px;background:rgba(6,182,212,0.2);padding:10px;border-radius:8px;font-size:14px;text-align:center}img{max-width:100%;max-height:100vh}</style>
           <p>Long-press the image below → Save Image</p>
           <img src="${dataUrl}">`
        );
      } else {
        alert('Please allow popups to save the image.');
      }
      return;
    }
    // Desktop / Android: Blob URL is more reliable than data URI for large canvases
    canvasRef.current.toBlob((blob) => {
      if (!blob) return;
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      setTimeout(() => URL.revokeObjectURL(url), 1000);
    }, 'image/png');
  };

  const canCopyImage = typeof navigator !== 'undefined' && !!navigator.clipboard?.write && typeof ClipboardItem !== 'undefined';

  const copyImage = async () => {
    if (!canvasRef.current) return;
    if (!canCopyImage) {
      alert('Copy isn\'t supported in this browser — use Download instead.');
      return;
    }
    try {
      const blob = await new Promise(r => canvasRef.current.toBlob(r, 'image/png'));
      await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
      alert('Image copied to clipboard!');
    } catch (e) {
      alert('Copy failed: ' + e.message + '\nTry downloading instead.');
    }
  };

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <h3 style={{ marginBottom: '8px', fontSize: '18px', fontWeight: '600' }}>📤 Share Your Progress</h3>
      <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '16px' }}>
        Generate a shareable image of your stats. Perfect for social media or WhatsApp.
      </p>
      <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '16px' }}>
        <button className="btn btn-primary btn-small" onClick={draw}>
          {imgUrl ? '🔄 Regenerate' : '✨ Generate card'}
        </button>
        {imgUrl && (
          <>
            <button className="btn btn-secondary btn-small" onClick={download}>⬇️ Download PNG</button>
            {canCopyImage && (
              <button className="btn btn-secondary btn-small" onClick={copyImage}>📋 Copy image</button>
            )}
          </>
        )}
      </div>
      <canvas ref={canvasRef} style={{
        width: '100%', maxWidth: '600px', borderRadius: '12px',
        display: imgUrl ? 'block' : 'none',
        border: '1px solid rgba(255,255,255,0.06)'
      }} />
    </div>
  );
};

// ============= PRIVACY CONTROLS =============
const PrivacyControlsSection = ({ uid }) => {
  const [incognito, setIncognito] = React.useState(() => localStorage.getItem('focusforge-incognito') === '1');
  const [exporting, setExporting] = React.useState(false);
  const [message, setMessage] = React.useState('');

  const toggleIncognito = (on) => {
    setIncognito(on);
  };
  // Keep window flag in lockstep with state on every change — previous code only
  // synced once on mount, so toggling in this component could drift from the real flag.
  React.useEffect(() => {
    window.FOCUSFORGE_INCOGNITO = !!incognito;
    localStorage.setItem('focusforge-incognito', incognito ? '1' : '0');
  }, [incognito]);

  const exportData = async (format) => {
    if (!uid) return;
    setExporting(true);
    setMessage('Gathering your data…');
    try {
      const [userDoc, sessions, homework, todos, notes, journal, achievements, rewards, aiPlans, moodEntries] =
        await Promise.all([
          db.collection('users').doc(uid).get(),
          db.collection('users').doc(uid).collection('sessions').get(),
          db.collection('users').doc(uid).collection('homework').get(),
          db.collection('users').doc(uid).collection('todos').get(),
          db.collection('users').doc(uid).collection('notes').get(),
          db.collection('users').doc(uid).collection('journal').get(),
          db.collection('users').doc(uid).collection('achievements').get(),
          db.collection('users').doc(uid).collection('unlockedRewards').get(),
          db.collection('users').doc(uid).collection('aiPlans').get(),
          db.collection('users').doc(uid).collection('moodEntries').get(),
        ]);

      const snapToArray = (snap) => snap.docs.map(d => {
        const data = d.data();
        // Convert Firestore timestamps to ISO strings
        Object.keys(data).forEach(k => {
          if (data[k]?.toDate) data[k] = data[k].toDate().toISOString();
        });
        return { id: d.id, ...data };
      });

      const payload = {
        exportedAt: new Date().toISOString(),
        user: userDoc.data(),
        sessions: snapToArray(sessions),
        homework: snapToArray(homework),
        todos: snapToArray(todos),
        notes: snapToArray(notes),
        journal: snapToArray(journal),
        achievements: snapToArray(achievements),
        unlockedRewards: snapToArray(rewards),
        aiPlans: snapToArray(aiPlans),
        moodEntries: snapToArray(moodEntries)
      };

      // Warn the user if we're about to export a fundamentally empty archive so they
      // don't think the export failed silently.
      const totalRecords = payload.sessions.length + payload.homework.length
        + payload.todos.length + payload.notes.length + payload.journal.length;
      if (totalRecords === 0) {
        const proceed = window.confirm('Your account has no data yet. Download an empty archive anyway?');
        if (!proceed) { setExporting(false); setMessage(''); return; }
      }

      const fileBase = `focusforge-data-${new Date().toISOString().slice(0, 10)}`;
      if (format === 'json') {
        const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = `${fileBase}.json`;
        a.click();
        URL.revokeObjectURL(a.href);
      } else if (format === 'csv') {
        // Simple CSV: just sessions (the most useful for users)
        const headers = ['type', 'minutes', 'subject', 'completedAt'];
        const rows = payload.sessions.map(s =>
          [s.type || '', s.minutes || 0, (s.subject || '').replace(/,/g, ';'), s.completedAt || ''].join(',')
        );
        const csv = [headers.join(','), ...rows].join('\n');
        const blob = new Blob([csv], { type: 'text/csv' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = `${fileBase}-sessions.csv`;
        a.click();
        URL.revokeObjectURL(a.href);
      }
      setMessage('✓ Exported successfully');
      setTimeout(() => setMessage(''), 2500);
    } catch (e) {
      setMessage('Export failed: ' + e.message);
    } finally {
      setExporting(false);
    }
  };

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <h3 style={{ marginBottom: '12px', fontSize: '18px', fontWeight: '600' }}>🔒 Privacy & Data</h3>

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,0.04)', gap: '12px' }}>
        <div>
          <div className="ach-name" style={{ fontSize: '14px', fontWeight: 500 }}>Incognito Study Mode</div>
          <div className="ach-desc" style={{ fontSize: '12px' }}>Pomodoros won't be saved as sessions (no stats recorded)</div>
        </div>
        <div className={`toggle-switch ${incognito ? 'on' : ''}`} onClick={() => toggleIncognito(!incognito)} />
      </div>

      {incognito && (
        <div style={{ margin: '12px 0', padding: '10px 12px', borderRadius: '8px', background: 'rgba(245,158,11,0.08)', color: '#f59e0b', fontSize: '12px', border: '1px solid rgba(245,158,11,0.2)' }}>
          ⚠️ Incognito mode is active — pomodoro sessions won't count towards stats, points, or achievements.
        </div>
      )}

      <div style={{ padding: '14px 0', borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
        <div className="ach-name" style={{ fontSize: '14px', fontWeight: 500, marginBottom: '4px' }}>Data export</div>
        <div className="ach-desc" style={{ fontSize: '12px', marginBottom: '12px' }}>Download all your FocusForge data</div>
        <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
          <button className="btn btn-secondary btn-small" onClick={() => exportData('json')} disabled={exporting}>
            ⬇️ Export all (JSON)
          </button>
          <button className="btn btn-secondary btn-small" onClick={() => exportData('csv')} disabled={exporting}>
            📊 Sessions CSV
          </button>
        </div>
      </div>

      <div style={{ padding: '12px 0 4px', fontSize: '12px', color: '#71717a', lineHeight: 1.6 }}>
        Your data is stored in Firebase Firestore and synced across devices. API keys and preferences live only in this browser (localStorage).
      </div>

      {message && (
        <div style={{ marginTop: '12px', padding: '8px 12px', borderRadius: '8px', background: message.startsWith('✓') ? 'rgba(34,197,94,0.08)' : 'rgba(245,158,11,0.08)', color: message.startsWith('✓') ? '#22c55e' : '#f59e0b', fontSize: '12px' }}>
          {message}
        </div>
      )}
    </div>
  );
};

// ============= ACCESSIBILITY SETTINGS =============
const applyAccessibility = (settings) => {
  const root = document.documentElement;
  const sizes = { s: '13px', m: '14px', l: '16px', xl: '18px' };
  root.style.fontSize = sizes[settings.fontSize || 'm'];
  root.classList.toggle('reduced-motion', !!settings.reducedMotion);
  root.classList.toggle('high-contrast', !!settings.highContrast);
  root.classList.toggle('dyslexia-font', !!settings.dyslexiaFont);
};

// Apply saved settings on load
(() => {
  try {
    const saved = JSON.parse(localStorage.getItem('focusforge-a11y') || '{}');
    applyAccessibility(saved);
  } catch (e) {}
})();

const AccessibilitySection = () => {
  const [settings, setSettings] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('focusforge-a11y') || '{}'); }
    catch (e) { return {}; }
  });

  const update = (patch) => {
    const next = { ...settings, ...patch };
    setSettings(next);
    localStorage.setItem('focusforge-a11y', JSON.stringify(next));
    applyAccessibility(next);
  };

  const Row = ({ label, desc, control }) => (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,0.04)', gap: '12px' }}>
      <div>
        <div className="ach-name" style={{ fontSize: '14px', fontWeight: 500 }}>{label}</div>
        <div className="ach-desc" style={{ fontSize: '12px' }}>{desc}</div>
      </div>
      {control}
    </div>
  );

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <h3 style={{ marginBottom: '12px', fontSize: '18px', fontWeight: '600' }}>♿ Accessibility</h3>

      <Row
        label="Font size"
        desc="Adjust the text size across the whole app"
        control={
          <div style={{ display: 'flex', gap: '4px', background: '#27272a', borderRadius: '8px', padding: '3px' }}>
            {[{k:'s',l:'S'},{k:'m',l:'M'},{k:'l',l:'L'},{k:'xl',l:'XL'}].map(opt => (
              <button key={opt.k} onClick={() => update({ fontSize: opt.k })}
                style={{
                  padding: '4px 12px', borderRadius: '6px', border: 'none', cursor: 'pointer',
                  background: (settings.fontSize || 'm') === opt.k ? '#06b6d4' : 'transparent',
                  color: (settings.fontSize || 'm') === opt.k ? '#09090b' : '#a1a1aa',
                  fontWeight: 600, fontFamily: 'inherit', fontSize: '12px'
                }}>{opt.l}</button>
            ))}
          </div>
        }
      />

      <Row
        label="Reduced motion"
        desc="Disable animations and transitions for less visual movement"
        control={
          <div className={`toggle-switch ${settings.reducedMotion ? 'on' : ''}`}
            onClick={() => update({ reducedMotion: !settings.reducedMotion })} />
        }
      />

      <Row
        label="High contrast"
        desc="Stronger borders and text contrast for better readability"
        control={
          <div className={`toggle-switch ${settings.highContrast ? 'on' : ''}`}
            onClick={() => update({ highContrast: !settings.highContrast })} />
        }
      />

      <Row
        label="Dyslexia-friendly font"
        desc="Use OpenDyslexic for easier reading"
        control={
          <div className={`toggle-switch ${settings.dyslexiaFont ? 'on' : ''}`}
            onClick={() => update({ dyslexiaFont: !settings.dyslexiaFont })} />
        }
      />
    </div>
  );
};

// ============= THEMES PICKER =============
const ThemesSection = ({ uid }) => {
  const [current, setCurrent] = React.useState(() => localStorage.getItem('focusforge-accent') || 'default');
  const [unlockedPremium, setUnlockedPremium] = React.useState({});

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('unlockedRewards')
      .onSnapshot(snap => {
        const map = {};
        snap.docs.forEach(d => {
          const data = d.data();
          if (data.type === 'theme') map[d.id.replace('theme-', '')] = true;
        });
        setUnlockedPremium(map);
      });
    return unsub;
  }, [uid]);

  const pick = (id) => {
    // Whitelist prevents random IDs pasted into localStorage from breaking CSS
    const valid = ['default', 'nature', 'sunset', 'royal', 'rose', 'amber', 'crimson',
                   'aurora', 'ember', 'forest', 'cosmic'];
    if (!valid.includes(id)) id = 'default';
    setCurrent(id);
    valid.filter(v => v !== 'default').forEach(t => document.documentElement.classList.remove(`theme-${t}`));
    if (id !== 'default') document.documentElement.classList.add(`theme-${id}`);
    localStorage.setItem('focusforge-accent', id);
  };

  const presets = [
    { id: 'default', name: 'Cyber', color: '#06b6d4', bg: 'linear-gradient(135deg, #06b6d4, #0891b2)', premium: false },
    { id: 'nature', name: 'Nature', color: '#22c55e', bg: 'linear-gradient(135deg, #22c55e, #16a34a)', premium: false },
    { id: 'sunset', name: 'Sunset', color: '#f97316', bg: 'linear-gradient(135deg, #f97316, #ea580c)', premium: false },
    { id: 'royal', name: 'Royal', color: '#8b5cf6', bg: 'linear-gradient(135deg, #8b5cf6, #7c3aed)', premium: false },
    { id: 'rose', name: 'Rose', color: '#ec4899', bg: 'linear-gradient(135deg, #ec4899, #db2777)', premium: false },
    { id: 'amber', name: 'Amber', color: '#f59e0b', bg: 'linear-gradient(135deg, #f59e0b, #d97706)', premium: false },
    { id: 'crimson', name: 'Crimson', color: '#ef4444', bg: 'linear-gradient(135deg, #ef4444, #dc2626)', premium: false },
    // Premium (from shop)
    { id: 'aurora', name: 'Aurora', color: '#a78bfa', bg: 'linear-gradient(135deg, #06b6d4, #a78bfa)', premium: true },
    { id: 'ember', name: 'Ember', color: '#f87171', bg: 'linear-gradient(135deg, #ef4444, #f97316)', premium: true },
    { id: 'forest', name: 'Deep Forest', color: '#15803d', bg: 'linear-gradient(135deg, #15803d, #d4af37)', premium: true },
    { id: 'cosmic', name: 'Cosmic', color: '#8b5cf6', bg: 'linear-gradient(135deg, #0ea5e9, #8b5cf6, #ec4899)', premium: true }
  ];

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <h3 style={{ marginBottom: '8px', fontSize: '18px', fontWeight: '600' }}>🎨 Theme</h3>
      <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '16px' }}>
        Personalize your accent color. Premium themes unlock in the Rewards Shop.
      </p>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))', gap: '10px' }}>
        {presets.map(p => {
          const locked = p.premium && !unlockedPremium[p.id];
          return (
            <button key={p.id} onClick={() => !locked && pick(p.id)} disabled={locked}
              title={locked ? 'Unlock in Rewards Shop' : p.name}
              style={{
                padding: '14px 10px', borderRadius: '12px', cursor: locked ? 'not-allowed' : 'pointer',
                background: current === p.id ? 'rgba(255,255,255,0.06)' : 'transparent',
                border: `2px solid ${current === p.id ? p.color : 'rgba(255,255,255,0.08)'}`,
                display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px',
                transition: 'all 0.15s', fontFamily: 'inherit', position: 'relative',
                opacity: locked ? 0.4 : 1
              }}>
              <div style={{
                width: '40px', height: '40px', borderRadius: '50%', background: p.bg,
                boxShadow: current === p.id ? `0 0 16px ${p.color}66` : 'none'
              }}></div>
              <div className="ach-name" style={{ fontSize: '12px', fontWeight: 600 }}>{p.name}</div>
              {p.premium && (
                <span style={{
                  fontSize: '9px', padding: '2px 6px', borderRadius: '4px',
                  background: locked ? 'rgba(255,255,255,0.05)' : 'rgba(139,92,246,0.2)',
                  color: locked ? '#71717a' : '#a78bfa', fontWeight: 600
                }}>{locked ? '🔒 LOCKED' : '✨ PREMIUM'}</span>
              )}
            </button>
          );
        })}
      </div>
    </div>
  );
};

// ============= AI SETTINGS (API key) =============
const AI_KEY_STORAGE = 'focusforge-ai-key';

// Transparent migration from legacy key name — anyone who already pasted a key
// continues to work without needing to re-enter it.
const getAIKey = () => {
  try {
    const legacy = localStorage.getItem('gemini-api-key');
    if (legacy) {
      localStorage.setItem(AI_KEY_STORAGE, legacy);
      localStorage.removeItem('gemini-api-key');
    }
  } catch {}
  return localStorage.getItem(AI_KEY_STORAGE) || '';
};

const AISettings = ({ uid, userData }) => {
  const [apiKey, setApiKey] = React.useState('');
  const [saved, setSaved] = React.useState(false);
  const [testing, setTesting] = React.useState(false);
  const [testResult, setTestResult] = React.useState('');

  React.useEffect(() => {
    const stored = getAIKey();
    if (stored) setApiKey(stored);
  }, []);

  const saveKey = () => {
    localStorage.setItem(AI_KEY_STORAGE, apiKey.trim());
    setSaved(true);
    setTestResult('');
    setTimeout(() => setSaved(false), 2000);
  };

  const testKey = async () => {
    setTesting(true);
    setTestResult('');
    try {
      const resp = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey.trim()}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ contents: [{ parts: [{ text: 'Reply with the single word: ok' }] }] })
      });
      const data = await resp.json();
      if (resp.ok && data.candidates) {
        setTestResult('✓ Connected — AI is ready');
      } else {
        setTestResult(`✗ ${data.error?.message || 'Invalid key'}`);
      }
    } catch (e) {
      setTestResult('✗ Network error: ' + e.message);
    } finally {
      setTesting(false);
    }
  };

  return (
    <div className="glass-card" style={{ marginBottom: '24px' }}>
      <h3 style={{ marginBottom: '8px', fontSize: '18px', fontWeight: '600' }}>AI Features</h3>
      <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '16px', lineHeight: 1.5 }}>
        Unlock AI-powered study plans, smart prioritization, and coaching. Get an API key at{' '}
        <a href="https://aistudio.google.com/app/apikey" target="_blank" rel="noopener" style={{ color: '#06b6d4' }}>aistudio.google.com/app/apikey</a>.
        Key is stored locally in your browser only.
      </p>

      <div style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' }}>
        <input
          type="password"
          value={apiKey}
          onChange={e => setApiKey(e.target.value)}
          placeholder="Paste your AI API key (starts with AIza...)"
          style={{ flex: 1, minWidth: '240px' }}
        />
        <button className="btn btn-primary btn-small" onClick={saveKey} disabled={!apiKey.trim()}>
          {saved ? '✓ Saved' : 'Save'}
        </button>
        <button className="btn btn-secondary btn-small" onClick={testKey} disabled={!apiKey.trim() || testing}>
          {testing ? 'Testing…' : 'Test'}
        </button>
      </div>

      {testResult && (
        <div style={{
          fontSize: '13px',
          color: testResult.startsWith('✓') ? '#22c55e' : '#ef4444',
          padding: '8px 12px',
          background: testResult.startsWith('✓') ? 'rgba(34,197,94,0.08)' : 'rgba(239,68,68,0.08)',
          borderRadius: '8px'
        }}>
          {testResult}
        </div>
      )}
    </div>
  );
};

// Shared AI API helper — used by AI features
const callAI = async (prompt, systemInstruction) => {
  const key = getAIKey();
  if (!key) throw new Error('No AI API key set. Add one in Profile → AI Features.');

  // Some models don't accept systemInstruction — combine into user prompt instead
  const makeBody = (model) => {
    const body = { generationConfig: { temperature: 0.7, maxOutputTokens: 2048 } };
    if (model.startsWith('gemma') && systemInstruction) {
      body.contents = [{ parts: [{ text: systemInstruction + '\n\n' + prompt }] }];
    } else {
      body.contents = [{ parts: [{ text: prompt }] }];
      if (systemInstruction) body.systemInstruction = { parts: [{ text: systemInstruction }] };
    }
    return body;
  };

  // Fallback chain — tries each model, skips on quota/overload errors.
  // Each request has a 30s timeout so the UI never hangs indefinitely.
  const models = ['gemini-2.5-flash', 'gemma-3-27b-it', 'gemma-3-12b-it', 'gemma-3-4b-it'];
  let lastError = null;
  for (const model of models) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 30000);
    try {
      const resp = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(makeBody(model)),
        signal: controller.signal
      });
      const data = await resp.json();
      if (!resp.ok) {
        lastError = new Error(data.error?.message || `HTTP ${resp.status}`);
        const msg = (data.error?.message || '').toLowerCase();
        const shouldRetry = msg.includes('high demand') || msg.includes('quota') || msg.includes('overload') || resp.status === 429 || resp.status === 503;
        if (!shouldRetry) throw lastError;
        continue;
      }
      const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
      if (text) return text;
      // Empty response body — try next model rather than returning blank to user
      lastError = new Error('Empty response from model.');
    } catch (e) {
      lastError = e.name === 'AbortError' ? new Error('Request timed out.') : e;
    } finally {
      clearTimeout(timeoutId);
    }
  }
  throw lastError || new Error('AI request failed. Try again.');
};

// ============= CALENDAR PAGE =============
const CalendarPage = ({ uid }) => {
  const [currentMonth, setCurrentMonth] = React.useState(new Date());
  const [selectedDate, setSelectedDate] = React.useState(null);
  const [homework, setHomework] = React.useState([]);
  const [todos, setTodos] = React.useState([]);
  const [view, setView] = React.useState('month');  // 'month' | 'week'

  const subjectColors = { Math: '#06b6d4', Science: '#34A853', English: '#FF9800', SST: '#9C27B0', Hindi: '#E91E63' };
  const getColor = (subj) => subjectColors[subj] || '#607D8B';

  React.useEffect(() => {
    if (!uid) return;
    const unsub1 = db.collection('users').doc(uid).collection('homework')
      .onSnapshot(snap => setHomework(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    const unsub2 = db.collection('users').doc(uid).collection('todos')
      .onSnapshot(snap => setTodos(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    return () => { unsub1(); unsub2(); };
  }, [uid]);

  const year = currentMonth.getFullYear();
  const month = currentMonth.getMonth();
  const firstDay = new Date(year, month, 1).getDay();
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const today = new Date();
  const monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December'];

  const getTasksForDate = (dateStr) => {
    const hw = homework.filter(h => h.dueDate === dateStr);
    const td = todos.filter(t => t.dueDate === dateStr);
    return { hw, td };
  };

  const cells = [];
  for (let i = 0; i < firstDay; i++) {
    const d = new Date(year, month, -firstDay + i + 1);
    cells.push({ day: d.getDate(), date: d, otherMonth: true });
  }
  for (let d = 1; d <= daysInMonth; d++) {
    cells.push({ day: d, date: new Date(year, month, d), otherMonth: false });
  }
  const remaining = 7 - (cells.length % 7);
  if (remaining < 7) {
    for (let i = 1; i <= remaining; i++) {
      cells.push({ day: i, date: new Date(year, month + 1, i), otherMonth: true });
    }
  }

  const formatDateStr = (d) => d.toISOString().split('T')[0];
  const selStr = selectedDate ? formatDateStr(selectedDate) : null;
  const selTasks = selectedDate ? getTasksForDate(formatDateStr(selectedDate)) : null;

  // Weekly view: 7 consecutive days starting from currentMonth's Sunday
  const weekStart = React.useMemo(() => {
    const d = new Date(currentMonth);
    d.setDate(d.getDate() - d.getDay());
    d.setHours(0, 0, 0, 0);
    return d;
  }, [currentMonth]);
  const weekDays = React.useMemo(() =>
    Array.from({ length: 7 }, (_, i) => {
      const d = new Date(weekStart);
      d.setDate(d.getDate() + i);
      return d;
    }), [weekStart]);

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: '12px' }}>
        <div>
          <h1 className="page-title">Calendar</h1>
          <p className="page-subtitle">View your schedule at a glance</p>
        </div>
        {/* View toggle */}
        <div style={{ display: 'flex', gap: '0', background: '#27272a', borderRadius: '10px', padding: '3px' }}>
          <button
            onClick={() => setView('month')}
            style={{
              padding: '6px 16px', borderRadius: '8px', border: 'none', cursor: 'pointer',
              background: view === 'month' ? '#06b6d4' : 'transparent',
              color: view === 'month' ? '#09090b' : '#a1a1aa',
              fontWeight: 600, fontSize: '13px', fontFamily: 'inherit'
            }}
          >Month</button>
          <button
            onClick={() => setView('week')}
            style={{
              padding: '6px 16px', borderRadius: '8px', border: 'none', cursor: 'pointer',
              background: view === 'week' ? '#06b6d4' : 'transparent',
              color: view === 'week' ? '#09090b' : '#a1a1aa',
              fontWeight: 600, fontSize: '13px', fontFamily: 'inherit'
            }}
          >Week</button>
        </div>
      </div>

      <div className="glass-card" style={{ marginBottom: '20px', marginTop: '16px' }}>
        {view === 'month' ? (
          <>
            <div className="calendar-nav">
              <button onClick={() => { setSelectedDate(null); setCurrentMonth(new Date(year, month - 1, 1)); }}>Prev</button>
              <h2>{monthNames[month]} {year}</h2>
              <button onClick={() => { setSelectedDate(null); setCurrentMonth(new Date(year, month + 1, 1)); }}>Next</button>
            </div>

            <div className="calendar-grid">
              {['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].map(d => (
                <div key={d} className="calendar-header-cell">{d}</div>
              ))}
              {cells.map((cell, i) => {
                const ds = formatDateStr(cell.date);
                const tasks = getTasksForDate(ds);
                const isToday = !cell.otherMonth && cell.date.toDateString() === today.toDateString();
                const isSel = selStr === ds;
                return (
                  <div key={i}
                    className={`calendar-cell ${cell.otherMonth ? 'other-month' : ''} ${isToday ? 'today' : ''} ${isSel ? 'selected' : ''}`}
                    onClick={() => !cell.otherMonth && setSelectedDate(cell.date)}
                  >
                    {cell.day}
                    {(tasks.hw.length > 0 || tasks.td.length > 0) && (
                      <div className="calendar-dots">
                        {tasks.hw.slice(0, 3).map((h, j) => (
                          <div key={j} className="calendar-dot" style={{ background: getColor(h.subject) }} />
                        ))}
                        {tasks.td.slice(0, 2).map((t, j) => (
                          <div key={'t'+j} className="calendar-dot" style={{ background: '#ffa502' }} />
                        ))}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </>
        ) : (
          <>
            {/* WEEK VIEW */}
            <div className="calendar-nav">
              <button onClick={() => { setSelectedDate(null); const d = new Date(currentMonth); d.setDate(d.getDate() - 7); setCurrentMonth(d); }}>Prev week</button>
              <h2>
                {weekDays[0].toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – {weekDays[6].toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
              </h2>
              <button onClick={() => { setSelectedDate(null); const d = new Date(currentMonth); d.setDate(d.getDate() + 7); setCurrentMonth(d); }}>Next week</button>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '8px', marginTop: '16px' }}>
              {weekDays.map((d, i) => {
                const ds = formatDateStr(d);
                const tasks = getTasksForDate(ds);
                const isToday = d.toDateString() === today.toDateString();
                const isSel = selStr === ds;
                const dayName = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()];
                return (
                  <div
                    key={i}
                    onClick={() => setSelectedDate(d)}
                    style={{
                      padding: '14px 10px',
                      minHeight: '180px',
                      borderRadius: '12px',
                      background: isToday ? 'rgba(6,182,212,0.08)' : 'rgba(255,255,255,0.02)',
                      border: `1px solid ${isSel ? '#06b6d4' : isToday ? 'rgba(6,182,212,0.3)' : 'rgba(255,255,255,0.05)'}`,
                      cursor: 'pointer',
                      transition: 'all 0.15s'
                    }}
                  >
                    <div style={{ fontSize: '11px', color: '#71717a', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 500 }}>{dayName}</div>
                    <div className="ach-name" style={{ fontSize: '20px', fontWeight: 700, color: isToday ? '#06b6d4' : '#fafafa', marginBottom: '8px' }}>{d.getDate()}</div>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
                      {tasks.hw.slice(0, 4).map(h => (
                        <div key={h.id} style={{
                          fontSize: '10px', padding: '3px 6px', borderRadius: '4px',
                          background: `${getColor(h.subject)}22`, color: getColor(h.subject),
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                          textDecoration: h.completed ? 'line-through' : 'none'
                        }}>
                          {h.title}
                        </div>
                      ))}
                      {tasks.td.slice(0, 3).map(t => (
                        <div key={t.id} style={{
                          fontSize: '10px', padding: '3px 6px', borderRadius: '4px',
                          background: 'rgba(255,165,2,0.15)', color: '#ffa502',
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                          textDecoration: t.completed ? 'line-through' : 'none'
                        }}>
                          ✓ {t.title}
                        </div>
                      ))}
                      {(tasks.hw.length + tasks.td.length) > 7 && (
                        <div style={{ fontSize: '10px', color: '#71717a' }}>+{(tasks.hw.length + tasks.td.length) - 7} more</div>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          </>
        )}
      </div>

      {selectedDate && selTasks && (
        <div className="calendar-detail-panel">
          <h3 style={{ marginBottom: '12px' }}>{selectedDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}</h3>
          {selTasks.hw.length === 0 && selTasks.td.length === 0 ? (
            <p style={{ color: 'var(--text-muted)' }}>No tasks on this date.</p>
          ) : (
            <div>
              {selTasks.hw.map(h => (
                <div key={h.id} style={{ padding: '8px 12px', marginBottom: '6px', borderLeft: `3px solid ${getColor(h.subject)}`, background: 'rgba(255,255,255,0.02)', borderRadius: '0 8px 8px 0' }}>
                  <div style={{ fontWeight: '500', fontSize: '14px' }}>{h.title}</div>
                  <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{h.subject} {h.completed ? '- Done' : ''}</div>
                </div>
              ))}
              {selTasks.td.map(t => (
                <div key={t.id} style={{ padding: '8px 12px', marginBottom: '6px', borderLeft: '3px solid #ffa502', background: 'rgba(255,255,255,0.02)', borderRadius: '0 8px 8px 0' }}>
                  <div style={{ fontWeight: '500', fontSize: '14px' }}>{t.title}</div>
                  <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>To-Do {t.completed ? '- Done' : ''}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
};

// ============= ANALYTICS UTILITIES =============
const toDate = (s) => s.completedAt?.seconds ? new Date(s.completedAt.seconds * 1000) : new Date(s.completedAt);
const DOW_NAMES = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];

const filterSessionsByRange = (sessions, days) => {
  if (days === 0) return sessions;
  const cutoff = new Date(Date.now() - days * 86400000);
  return sessions.filter(s => toDate(s) >= cutoff);
};

const sessionsInRange = (sessions, startDate, endDate) => sessions.filter(s => {
  const d = toDate(s);
  return d >= startDate && d < endDate;
});

const computeInsights = (sessions, days) => {
  const current = filterSessionsByRange(sessions, days);
  const now = Date.now();
  // filterSessionsByRange uses [now - days*86400000, now] inclusive.
  // So previous period is [now - 2*days*86400000, now - days*86400000) — exclusive
  // on the upper bound to avoid overlap with the current period.
  const prevStart = new Date(now - (days * 2) * 86400000);
  const prevEnd = new Date(now - days * 86400000);
  const prev = days === 0 ? [] : sessionsInRange(sessions, prevStart, prevEnd);

  const sum = arr => arr.reduce((a, s) => a + (s.minutes || 0), 0);
  const curMinutes = sum(current);
  const prevMinutes = sum(prev);
  const curPomodoros = current.filter(s => s.type === 'pomodoro').length;
  const prevPomodoros = prev.filter(s => s.type === 'pomodoro').length;

  const pctChange = (cur, pr) => pr === 0 ? (cur > 0 ? 100 : 0) : Math.round(((cur - pr) / pr) * 100);

  // Best hour
  const hourTotals = Array(24).fill(0);
  current.forEach(s => { hourTotals[toDate(s).getHours()] += (s.minutes || 0); });
  const totalHourMinutes = hourTotals.reduce((a, b) => a + b, 0);
  let bestHour = 0, bestHourMinutes = 0;
  hourTotals.forEach((m, i) => { if (m > bestHourMinutes) { bestHour = i; bestHourMinutes = m; } });
  const bestHourPct = totalHourMinutes > 0 ? Math.round((bestHourMinutes / totalHourMinutes) * 100) : 0;

  // Best day of week
  const dowTotals = Array(7).fill(0);
  const dowCounts = Array(7).fill(0);
  current.forEach(s => { const d = toDate(s); dowTotals[d.getDay()] += (s.minutes || 0); dowCounts[d.getDay()]++; });
  let bestDow = 0, bestDowAvg = 0;
  dowTotals.forEach((m, i) => {
    const avg = dowCounts[i] > 0 ? m / dowCounts[i] : 0;
    if (avg > bestDowAvg) { bestDowAvg = avg; bestDow = i; }
  });

  // Consistency score (% of days in range with at least one session)
  let consistency = 0;
  if (days > 0) {
    const dayKeys = new Set(current.map(s => toDate(s).toDateString()));
    consistency = Math.round((dayKeys.size / days) * 100);
  } else if (current.length > 0) {
    const uniqueDays = new Set(current.map(s => toDate(s).toDateString())).size;
    const oldest = current.reduce((min, s) => Math.min(min, toDate(s).getTime()), Date.now());
    const totalDays = Math.max(1, Math.round((Date.now() - oldest) / 86400000));
    consistency = Math.round((uniqueDays / totalDays) * 100);
  }

  // Current streak (consecutive days up to today with a session)
  const dayMap = new Set(sessions.map(s => toDate(s).toDateString()));
  let streak = 0;
  const d = new Date(); d.setHours(0,0,0,0);
  for (;;) {
    if (dayMap.has(d.toDateString())) { streak++; d.setDate(d.getDate() - 1); }
    else break;
  }

  // Subject leader
  const subjects = {};
  current.forEach(s => {
    const k = (s.subject || '').trim() || 'Unspecified';
    subjects[k] = (subjects[k] || 0) + (s.minutes || 0);
  });
  const subjectEntries = Object.entries(subjects).sort((a, b) => b[1] - a[1]);
  const topSubject = subjectEntries[0] || null;

  // Average session length
  const avgSessionLen = current.length > 0 ? Math.round(curMinutes / current.length) : 0;

  return {
    curMinutes, prevMinutes, minutesDelta: pctChange(curMinutes, prevMinutes),
    curPomodoros, prevPomodoros, pomodorosDelta: pctChange(curPomodoros, prevPomodoros),
    curSessions: current.length, prevSessions: prev.length, sessionsDelta: pctChange(current.length, prev.length),
    dailyAvg: days > 0 ? Math.round(curMinutes / days) : Math.round(curMinutes / Math.max(1, new Set(current.map(s => toDate(s).toDateString())).size)),
    prevDailyAvg: days > 0 ? Math.round(prevMinutes / days) : 0,
    bestHour, bestHourMinutes, bestHourPct,
    bestDow, bestDowAvg,
    consistency,
    streak,
    topSubject,
    avgSessionLen,
    hourTotals,
    dowTotals,
    sessionsData: current
  };
};

// Hour × Day-of-week heatmap data (returns 7 rows × 24 cols with minute values)
const computeHourDowHeatmap = (sessions) => {
  const grid = Array.from({ length: 7 }, () => Array(24).fill(0));
  sessions.forEach(s => {
    const d = toDate(s);
    grid[d.getDay()][d.getHours()] += (s.minutes || 0);
  });
  const max = grid.flat().reduce((m, v) => Math.max(m, v), 0);
  return { grid, max };
};

// Full-year streak calendar (371 days back in 7-day columns)
const computeYearCalendar = (sessions) => {
  const map = {};
  sessions.forEach(s => {
    const d = toDate(s);
    const k = d.toISOString().slice(0, 10);
    map[k] = (map[k] || 0) + (s.minutes || 0);
  });
  // Use calendar-day arithmetic (setDate) rather than fixed ms offsets — DST
  // transitions can add/remove an hour per day, which compounds to day-drift over 365 cells.
  const today = new Date(); today.setHours(0,0,0,0);
  const cells = [];
  for (let i = 364; i >= 0; i--) {
    const d = new Date(today);
    d.setDate(today.getDate() - i);
    // Convert to local YYYY-MM-DD (toISOString would shift by timezone offset)
    const k = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
    const mins = map[k] || 0;
    let level = 0;
    if (mins > 0 && mins <= 15) level = 1;
    else if (mins <= 45) level = 2;
    else if (mins <= 90) level = 3;
    else if (mins > 90) level = 4;
    cells.push({ date: k, mins, level });
  }
  return cells;
};

// Forecast: based on the last 7 days, project how many days to complete homework / reach goal
const computeForecast = (sessions, homeworks, dailyGoalMinutes) => {
  const last7 = filterSessionsByRange(sessions, 7);
  const last7Min = last7.reduce((a, s) => a + (s.minutes || 0), 0);
  const dailyRate = last7Min / 7; // minutes per day

  const pendingHw = (homeworks || []).filter(h => !h.completed);
  const avgHwMinutes = 30; // estimate
  const remainingMinutes = pendingHw.length * avgHwMinutes;
  const daysToComplete = dailyRate > 0 ? Math.ceil(remainingMinutes / dailyRate) : null;

  // Monthly target projection
  const daysIntoMonth = new Date().getDate();
  const monthEnd = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();
  const monthTarget = dailyGoalMinutes * monthEnd;
  const monthSoFar = filterSessionsByRange(sessions, daysIntoMonth).reduce((a, s) => a + (s.minutes || 0), 0);
  const monthProjection = dailyRate * monthEnd;

  return {
    dailyRate: Math.round(dailyRate),
    daysToCompleteHomework: daysToComplete,
    pendingHomeworkCount: pendingHw.length,
    monthTarget,
    monthSoFar,
    monthProjection: Math.round(monthProjection),
    onTrack: monthProjection >= monthTarget
  };
};

// ============= ANALYTICS PAGE =============
const AnalyticsPage = ({ uid }) => {
  const [sessions, setSessions] = React.useState([]);
  const [homeworks, setHomeworks] = React.useState([]);
  const [userData, setUserData] = React.useState(null);
  const [range, setRange] = React.useState(7);
  const [exporting, setExporting] = React.useState(false);
  const dailyRef = React.useRef(null);
  const pomodoroRef = React.useRef(null);
  const subjectRef = React.useRef(null);
  const masteryRef = React.useRef(null);
  const reportRef = React.useRef(null);
  const chartInstances = React.useRef({});

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('sessions')
      .orderBy('completedAt', 'desc')
      .onSnapshot(snap => {
        setSessions(snap.docs.map(d => ({ id: d.id, ...d.data() })));
      });
    const unsubHw = db.collection('users').doc(uid).collection('homework')
      .onSnapshot(snap => setHomeworks(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    const unsubUser = db.collection('users').doc(uid)
      .onSnapshot(doc => setUserData(doc.data()));
    return () => { unsub(); unsubHw(); unsubUser(); };
  }, [uid]);

  // Compute all insights
  const insights = React.useMemo(() => computeInsights(sessions, range), [sessions, range]);
  const heatmap = React.useMemo(() => computeHourDowHeatmap(insights.sessionsData), [insights.sessionsData]);
  const yearCalendar = React.useMemo(() => computeYearCalendar(sessions), [sessions]);
  const forecast = React.useMemo(
    () => computeForecast(sessions, homeworks, userData?.dailyGoalMinutes || 60),
    [sessions, homeworks, userData]
  );

  // Render charts whenever filtered data changes
  React.useEffect(() => {
    const filtered = insights.sessionsData;
    Object.values(chartInstances.current).forEach(c => c.destroy());
    chartInstances.current = {};

    const isLight = document.documentElement.classList.contains('light-mode');
    const textCol = isLight ? 'rgba(0,0,0,0.55)' : 'rgba(255,255,255,0.55)';
    const gridCol = isLight ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.05)';

    const baseOpts = {
      responsive: true, maintainAspectRatio: false,
      plugins: { legend: { labels: { color: textCol } }, tooltip: { backgroundColor: '#27272a', titleColor: '#fafafa', bodyColor: '#a1a1aa', borderColor: 'rgba(255,255,255,0.08)', borderWidth: 1 } },
      scales: {
        x: { ticks: { color: textCol, font: { size: 11 } }, grid: { color: gridCol, drawBorder: false } },
        y: { ticks: { color: textCol, font: { size: 11 } }, grid: { color: gridCol, drawBorder: false }, beginAtZero: true }
      }
    };

    // Daily minutes with 7-day moving average overlay
    if (dailyRef.current) {
      const daily = {};
      filtered.forEach(s => {
        const d = toDate(s);
        const key = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
        daily[key] = (daily[key] || 0) + (s.minutes || 0);
      });
      const labels = Object.keys(daily);
      const values = Object.values(daily);
      const ma = values.map((_, i) => {
        const slice = values.slice(Math.max(0, i - 6), i + 1);
        return Math.round(slice.reduce((a, b) => a + b, 0) / slice.length);
      });
      chartInstances.current.daily = new Chart(dailyRef.current, {
        data: {
          labels,
          datasets: [
            { type: 'bar', label: 'Minutes', data: values, backgroundColor: 'rgba(6, 182, 212, 0.65)', borderRadius: 6, order: 2 },
            { type: 'line', label: '7-day avg', data: ma, borderColor: '#8b5cf6', backgroundColor: 'transparent', borderWidth: 2, tension: 0.35, pointRadius: 0, order: 1 }
          ]
        },
        options: baseOpts
      });
    }

    // Pomodoros per day
    if (pomodoroRef.current) {
      const pomDaily = {};
      filtered.filter(s => s.type === 'pomodoro').forEach(s => {
        const d = toDate(s);
        const key = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
        pomDaily[key] = (pomDaily[key] || 0) + 1;
      });
      chartInstances.current.pom = new Chart(pomodoroRef.current, {
        type: 'line',
        data: { labels: Object.keys(pomDaily), datasets: [{ label: 'Pomodoros', data: Object.values(pomDaily), borderColor: '#06b6d4', backgroundColor: 'rgba(6,182,212,0.12)', fill: true, tension: 0.35, pointRadius: 3, pointBackgroundColor: '#06b6d4', borderWidth: 2 }] },
        options: { ...baseOpts, plugins: { ...baseOpts.plugins, legend: { display: false } } }
      });
    }

    // Subject doughnut
    if (subjectRef.current) {
      const subj = {};
      filtered.forEach(s => {
        const key = (s.subject || '').trim() || 'Unspecified';
        subj[key] = (subj[key] || 0) + (s.minutes || 0);
      });
      const colors = ['#06b6d4', '#8b5cf6', '#22c55e', '#f59e0b', '#ef4444', '#ec4899', '#14b8a6', '#f97316'];
      chartInstances.current.subj = new Chart(subjectRef.current, {
        type: 'doughnut',
        data: {
          labels: Object.keys(subj),
          datasets: [{ data: Object.values(subj), backgroundColor: colors.slice(0, Math.max(1, Object.keys(subj).length)), borderWidth: 0 }]
        },
        options: {
          responsive: true, maintainAspectRatio: false, cutout: '65%',
          plugins: { legend: { position: 'right', labels: { color: textCol, padding: 12, boxWidth: 12, font: { size: 12 } } } }
        }
      });
    }

    // Subject Mastery Radar — shows mastery % per subject (capped at 10 hours = 100%)
    if (masteryRef.current) {
      const subj = {};
      filtered.forEach(s => {
        const key = (s.subject || '').trim() || 'Other';
        subj[key] = (subj[key] || 0) + (s.minutes || 0);
      });
      const entries = Object.entries(subj).sort((a, b) => b[1] - a[1]).slice(0, 8);
      if (entries.length > 0) {
        // Mastery scaled: 600 minutes (10 hours) = 100%
        const masteryData = entries.map(([, mins]) => Math.min(100, Math.round((mins / 600) * 100)));
        chartInstances.current.mastery = new Chart(masteryRef.current, {
          type: 'radar',
          data: {
            labels: entries.map(([name]) => name),
            datasets: [{
              label: 'Mastery %',
              data: masteryData,
              backgroundColor: 'rgba(6, 182, 212, 0.18)',
              borderColor: '#06b6d4',
              borderWidth: 2,
              pointBackgroundColor: '#06b6d4',
              pointRadius: 4
            }]
          },
          options: {
            responsive: true, maintainAspectRatio: false,
            plugins: { legend: { display: false }, tooltip: { callbacks: { label: (ctx) => `${ctx.parsed.r}% mastery` } } },
            scales: {
              r: {
                beginAtZero: true, max: 100,
                ticks: { color: textCol, backdropColor: 'transparent', stepSize: 25, font: { size: 10 } },
                grid: { color: gridCol },
                angleLines: { color: gridCol },
                pointLabels: { color: textCol, font: { size: 12, weight: 500 } }
              }
            }
          }
        });
      }
    }

    return () => { Object.values(chartInstances.current).forEach(c => c.destroy()); };
  }, [insights.sessionsData]);

  // ===== PDF Export =====
  const exportPDF = async () => {
    if (!reportRef.current || !window.jspdf) return;
    setExporting(true);
    try {
      // Chart.js paints on next animation frame — wait two RAFs + microtask so
      // charts and any font loading are committed before html2canvas snapshots.
      if (document.fonts && document.fonts.ready) { try { await document.fonts.ready; } catch {} }
      await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
      const canvas = await html2canvas(reportRef.current, {
        backgroundColor: document.documentElement.classList.contains('light-mode') ? '#ffffff' : '#09090b',
        scale: 2,
        useCORS: true,
        logging: false
      });
      const imgData = canvas.toDataURL('image/jpeg', 0.92);
      const { jsPDF } = window.jspdf;
      const pdf = new jsPDF({ unit: 'pt', format: 'a4' });
      const pageWidth = pdf.internal.pageSize.getWidth();
      const pageHeight = pdf.internal.pageSize.getHeight();
      const imgWidth = pageWidth - 40;
      const imgHeight = (canvas.height * imgWidth) / canvas.width;

      // Title
      pdf.setFontSize(18); pdf.setFont('helvetica', 'bold');
      pdf.text('FocusForge Study Report', 20, 30);
      pdf.setFontSize(10); pdf.setFont('helvetica', 'normal'); pdf.setTextColor(100);
      const rangeLabel = range === 0 ? 'All Time' : `Last ${range} days`;
      pdf.text(`${rangeLabel}  •  Generated ${new Date().toLocaleDateString()}`, 20, 46);

      // Image with pagination
      let y = 60;
      if (imgHeight <= pageHeight - y - 20) {
        pdf.addImage(imgData, 'JPEG', 20, y, imgWidth, imgHeight);
      } else {
        // Paginate tall content
        const pxPerPt = canvas.width / imgWidth;
        const availablePt = pageHeight - y - 20;
        const sliceHeightPx = availablePt * pxPerPt;
        let offset = 0;
        while (offset < canvas.height) {
          const c2 = document.createElement('canvas');
          c2.width = canvas.width;
          c2.height = Math.min(sliceHeightPx, canvas.height - offset);
          const ctx = c2.getContext('2d');
          ctx.drawImage(canvas, 0, offset, c2.width, c2.height, 0, 0, c2.width, c2.height);
          const sliceData = c2.toDataURL('image/jpeg', 0.92);
          const sliceImgHeight = (c2.height * imgWidth) / canvas.width;
          pdf.addImage(sliceData, 'JPEG', 20, y, imgWidth, sliceImgHeight);
          offset += sliceHeightPx;
          if (offset < canvas.height) { pdf.addPage(); y = 30; }
        }
      }

      pdf.save(`focusforge-report-${new Date().toISOString().slice(0,10)}.pdf`);
    } catch (err) {
      console.error('PDF export failed:', err);
      alert('PDF export failed. See console for details.');
    } finally {
      setExporting(false);
    }
  };

  // Helper for delta display
  const renderDelta = (delta) => {
    if (delta > 0) return <span className="stat-tile-delta up">▲ {delta}% vs previous</span>;
    if (delta < 0) return <span className="stat-tile-delta down">▼ {Math.abs(delta)}% vs previous</span>;
    return <span className="stat-tile-delta flat">— no change</span>;
  };

  const hourLabel = (h) => `${h % 12 === 0 ? 12 : h % 12}${h < 12 ? 'am' : 'pm'}`;

  return (
    <div>
      <div className="analytics-header">
        <div>
          <h1 className="page-title" style={{ marginBottom: '4px' }}>Analytics</h1>
          <p className="page-subtitle" style={{ marginBottom: 0 }}>Deep insights into your study habits</p>
        </div>
        <div className="analytics-actions">
          <button className="btn-export" onClick={exportPDF} disabled={exporting || sessions.length === 0}>
            {exporting ? 'Exporting…' : '📄 Export PDF'}
          </button>
        </div>
      </div>

      <div className="time-range-bar">
        {[{l:'7 Days',v:7},{l:'30 Days',v:30},{l:'90 Days',v:90},{l:'1 Year',v:365},{l:'All Time',v:0}].map(r => (
          <button key={r.v} className={range === r.v ? 'active' : ''} onClick={() => setRange(r.v)}>{r.l}</button>
        ))}
      </div>

      <div ref={reportRef}>
        {/* Stat tiles with deltas */}
        <div className="stat-grid-4">
          <div className="stat-tile">
            <div className="stat-tile-label">Study Time</div>
            <div className="stat-tile-value">{formatMinutes(insights.curMinutes)}</div>
            {range > 0 && renderDelta(insights.minutesDelta)}
          </div>
          <div className="stat-tile">
            <div className="stat-tile-label">Pomodoros</div>
            <div className="stat-tile-value">{insights.curPomodoros}</div>
            {range > 0 && renderDelta(insights.pomodorosDelta)}
          </div>
          <div className="stat-tile">
            <div className="stat-tile-label">Sessions</div>
            <div className="stat-tile-value">{insights.curSessions}</div>
            {range > 0 && renderDelta(insights.sessionsDelta)}
          </div>
          <div className="stat-tile">
            <div className="stat-tile-label">Daily Average</div>
            <div className="stat-tile-value">{formatMinutes(insights.dailyAvg)}</div>
            <span className="stat-tile-delta flat">Avg session {formatMinutes(insights.avgSessionLen)}</span>
          </div>
        </div>

        {/* AI-style insights */}
        <div className="insights-grid">
          {insights.bestHourMinutes > 0 && (
            <div className="insight-card">
              <div className="insight-icon cyan">🕒</div>
              <div className="insight-body">
                <div className="insight-title">Peak hour</div>
                <div className="insight-text">You study best at <strong>{hourLabel(insights.bestHour)}</strong></div>
                <div className="insight-sub">{insights.bestHourPct}% of your study time happens in this hour</div>
              </div>
            </div>
          )}
          {insights.bestDowAvg > 0 && (
            <div className="insight-card">
              <div className="insight-icon violet">📅</div>
              <div className="insight-body">
                <div className="insight-title">Best day</div>
                <div className="insight-text"><strong>{DOW_NAMES[insights.bestDow]}</strong> is your strongest day</div>
                <div className="insight-sub">Avg {formatMinutes(Math.round(insights.bestDowAvg))} per session</div>
              </div>
            </div>
          )}
          <div className="insight-card">
            <div className="insight-icon green">🔥</div>
            <div className="insight-body">
              <div className="insight-title">Current streak</div>
              <div className="insight-text"><strong>{insights.streak} {insights.streak === 1 ? 'day' : 'days'}</strong> in a row</div>
              <div className="insight-sub">{insights.consistency}% consistency in this range</div>
            </div>
          </div>
          {insights.topSubject && (
            <div className="insight-card">
              <div className="insight-icon amber">📚</div>
              <div className="insight-body">
                <div className="insight-title">Top subject</div>
                <div className="insight-text"><strong>{insights.topSubject[0]}</strong> — {formatMinutes(insights.topSubject[1])}</div>
                <div className="insight-sub">Most-studied subject in this range</div>
              </div>
            </div>
          )}
        </div>

        {/* Predictive forecast */}
        <div className="forecast-card">
          <h3>🔮 Predictive Forecast</h3>
          <div className="forecast-list">
            <div className="forecast-item">
              <span>Current pace</span>
              <strong>{formatMinutes(forecast.dailyRate)} / day</strong>
            </div>
            {forecast.pendingHomeworkCount > 0 && forecast.daysToCompleteHomework !== null && (
              <div className="forecast-item">
                <span>At this pace, {forecast.pendingHomeworkCount} pending {forecast.pendingHomeworkCount === 1 ? 'homework' : 'assignments'} done in</span>
                <strong>~{forecast.daysToCompleteHomework} {forecast.daysToCompleteHomework === 1 ? 'day' : 'days'}</strong>
              </div>
            )}
            <div className="forecast-item">
              <span>Monthly projection {forecast.onTrack ? '— on track ✓' : '— falling short ⚠️'}</span>
              <strong>{formatMinutes(forecast.monthProjection)} / {formatMinutes(forecast.monthTarget)}</strong>
            </div>
          </div>
        </div>

        {/* Charts */}
        <div className="analytics-grid">
          <div className="chart-card" style={{ gridColumn: 'span 2' }}>
            <h3>Daily study time</h3>
            <div style={{ height: '260px', position: 'relative' }}><canvas ref={dailyRef} /></div>
          </div>

          <div className="chart-card" style={{ gridColumn: 'span 2' }}>
            <h3>Productivity heatmap — when you study</h3>
            <div className="productivity-heatmap">
              {heatmap.grid.map((row, dow) => (
                <React.Fragment key={dow}>
                  <div className="ph-label">{DOW_NAMES[dow]}</div>
                  {row.map((v, h) => {
                    let cls = '';
                    if (heatmap.max > 0) {
                      const ratio = v / heatmap.max;
                      if (ratio > 0.75) cls = 'l4';
                      else if (ratio > 0.5) cls = 'l3';
                      else if (ratio > 0.25) cls = 'l2';
                      else if (ratio > 0) cls = 'l1';
                    }
                    return <div key={h} className={`ph-cell ${cls}`} title={`${DOW_NAMES[dow]} ${hourLabel(h)} — ${Math.round(v)} min`} />;
                  })}
                </React.Fragment>
              ))}
            </div>
            <div className="heatmap-hour-labels">
              <div></div>
              {Array.from({ length: 24 }, (_, i) => <div key={i}>{i % 3 === 0 ? hourLabel(i) : ''}</div>)}
            </div>
          </div>

          <div className="chart-card">
            <h3>Pomodoros per day</h3>
            <div style={{ height: '220px', position: 'relative' }}><canvas ref={pomodoroRef} /></div>
          </div>

          <div className="chart-card">
            <h3>Subject breakdown</h3>
            <div style={{ height: '220px', position: 'relative' }}><canvas ref={subjectRef} /></div>
          </div>

          <div className="chart-card" style={{ gridColumn: 'span 2' }}>
            <h3>Subject mastery — 10 hours = 100%</h3>
            <div style={{ height: '320px', position: 'relative' }}><canvas ref={masteryRef} /></div>
          </div>

          <div className="chart-card" style={{ gridColumn: 'span 2' }}>
            <h3>Year activity — {yearCalendar.filter(c => c.mins > 0).length} active days</h3>
            <div className="streak-calendar">
              {yearCalendar.map((c, i) => (
                <div key={i} className={`sc-cell ${c.level ? `l${c.level}` : ''}`} title={`${c.date}: ${c.mins} min`} />
              ))}
            </div>
            <div className="streak-legend">
              <span>Less</span>
              <div className="ll" style={{ background: 'rgba(255,255,255,0.04)' }}></div>
              <div className="ll" style={{ background: 'rgba(6,182,212,0.2)' }}></div>
              <div className="ll" style={{ background: 'rgba(6,182,212,0.4)' }}></div>
              <div className="ll" style={{ background: 'rgba(6,182,212,0.65)' }}></div>
              <div className="ll" style={{ background: 'rgba(6,182,212,0.9)' }}></div>
              <span>More</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

// ============= GOALS PAGE =============
const GoalsPage = ({ uid, userData }) => {
  const [dailyGoal, setDailyGoal] = React.useState(60);
  const [weeklyGoal, setWeeklyGoal] = React.useState(10);
  const [todayMinutes, setTodayMinutes] = React.useState(0);
  const [weekPomodoros, setWeekPomodoros] = React.useState(0);
  const [editing, setEditing] = React.useState(false);

  React.useEffect(() => {
    if (userData?.dailyGoalMinutes) setDailyGoal(userData.dailyGoalMinutes);
    if (userData?.weeklyGoalPomodoros) setWeeklyGoal(userData.weeklyGoalPomodoros);
  }, [userData]);

  React.useEffect(() => {
    if (!uid) return;
    const today = new Date(); today.setHours(0,0,0,0);
    const weekAgo = new Date(today.getTime() - 7 * 86400000);

    const unsub1 = db.collection('users').doc(uid).collection('sessions')
      .where('completedAt', '>=', today)
      .onSnapshot(snap => {
        setTodayMinutes(snap.docs.reduce((a, d) => a + (d.data().minutes || 0), 0));
      });
    const unsub2 = db.collection('users').doc(uid).collection('sessions')
      .where('completedAt', '>=', weekAgo)
      .onSnapshot(snap => {
        const count = snap.docs.filter(d => d.data().type === 'pomodoro').length;
        setWeekPomodoros(count);
      });
    return () => { unsub1(); unsub2(); };
  }, [uid]);

  const saveGoals = async () => {
    if (!uid) return;
    await db.collection('users').doc(uid).update({ dailyGoalMinutes: dailyGoal, weeklyGoalPomodoros: weeklyGoal });
    setEditing(false);
  };

  // Safe percentage — guard against 0/negative goals that would yield Infinity/NaN
  const safePct = (v, max) => (max > 0 ? Math.min(100, Math.round((v / max) * 100)) : 0);
  const dailyPct = safePct(todayMinutes, dailyGoal);
  const weeklyPct = safePct(weekPomodoros, weeklyGoal);

  // Celebration fires ONCE per crossing, not every render while >= 100
  const celebratedRef = React.useRef({ daily: false, weekly: false });
  const [celebrate, setCelebrate] = React.useState({ daily: false, weekly: false });
  React.useEffect(() => {
    if (dailyPct >= 100 && !celebratedRef.current.daily) {
      celebratedRef.current.daily = true;
      setCelebrate(c => ({ ...c, daily: true }));
    } else if (dailyPct < 100 && celebratedRef.current.daily) {
      celebratedRef.current.daily = false; // re-arm after dropping
      setCelebrate(c => ({ ...c, daily: false }));
    }
    if (weeklyPct >= 100 && !celebratedRef.current.weekly) {
      celebratedRef.current.weekly = true;
      setCelebrate(c => ({ ...c, weekly: true }));
    } else if (weeklyPct < 100 && celebratedRef.current.weekly) {
      celebratedRef.current.weekly = false;
      setCelebrate(c => ({ ...c, weekly: false }));
    }
  }, [dailyPct, weeklyPct]);

  const Ring = ({ pct, color, size, label, value, max }) => {
    const r = (size - 12) / 2;
    const circ = 2 * Math.PI * r;
    const offset = circ - (pct / 100) * circ;
    return (
      <div className="goal-ring">
        <svg width={size} height={size}>
          <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--ring-track, rgba(255,255,255,0.08))" strokeWidth="10" />
          <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth="10"
            strokeDasharray={circ} strokeDashoffset={offset} strokeLinecap="round" style={{ transition: 'stroke-dashoffset 0.6s ease' }} />
          <text x={size/2} y={size/2 - 8} textAnchor="middle" fill="var(--text-primary)" fontSize="28" fontWeight="700">{pct}%</text>
          <text x={size/2} y={size/2 + 16} textAnchor="middle" fill="var(--text-muted)" fontSize="12">{value}/{max}</text>
        </svg>
        <div className="ring-label">{label}</div>
      </div>
    );
  };

  return (
    <div>
      <h1 className="page-title">Goals</h1>
      <p className="page-subtitle">Set targets and track your progress</p>

      <div className="glass-card">
        <div className="goal-ring-container">
          <Ring pct={dailyPct} color="#06b6d4" size={200} label="Daily Study (minutes)" value={todayMinutes} max={dailyGoal} />
          <Ring pct={weeklyPct} color="#FF9800" size={200} label="Weekly Pomodoros" value={weekPomodoros} max={weeklyGoal} />
        </div>

        {(celebrate.daily || celebrate.weekly) && (
          <div className="celebration-msg">
            {celebrate.daily && celebrate.weekly ? 'Both goals reached! Amazing!' : celebrate.daily ? 'Daily goal reached! Keep it up!' : 'Weekly pomodoro goal reached!'}
          </div>
        )}

        <div className="goal-edit-row">
          {editing ? (
            <React.Fragment>
              <div>
                <label style={{ fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '4px' }}>Daily (min)</label>
                <input type="number" value={dailyGoal} onChange={e => setDailyGoal(Math.max(1, parseInt(e.target.value) || 1))} style={{ width: '80px' }} />
              </div>
              <div>
                <label style={{ fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '4px' }}>Weekly Pomodoros</label>
                <input type="number" value={weeklyGoal} onChange={e => setWeeklyGoal(Math.max(1, parseInt(e.target.value) || 1))} style={{ width: '80px' }} />
              </div>
              <button className="btn btn-primary" onClick={saveGoals}>Save</button>
              <button className="btn btn-secondary" onClick={() => setEditing(false)}>Cancel</button>
            </React.Fragment>
          ) : (
            <button className="btn btn-secondary" onClick={() => setEditing(true)}>Edit Goals</button>
          )}
        </div>
      </div>
    </div>
  );
};

// ============= ACHIEVEMENTS =============
const ACHIEVEMENTS = [
  // Pomodoro milestones
  { id: 'pomo-1', icon: '🍅', name: 'First Pomodoro', desc: 'Complete your first pomodoro session', tier: 'bronze', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 1 },
  { id: 'pomo-10', icon: '🍅', name: 'Getting Started', desc: 'Complete 10 pomodoros', tier: 'bronze', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 10 },
  { id: 'pomo-50', icon: '🍅', name: 'Dedicated Learner', desc: 'Complete 50 pomodoros', tier: 'silver', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 50 },
  { id: 'pomo-100', icon: '🏆', name: 'Century Club', desc: 'Complete 100 pomodoros', tier: 'silver', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 100 },
  { id: 'pomo-500', icon: '🏆', name: 'Pomodoro Master', desc: 'Complete 500 pomodoros', tier: 'gold', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 500 },
  { id: 'pomo-1000', icon: '👑', name: 'Pomodoro Legend', desc: 'Complete 1000 pomodoros', tier: 'legendary', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 1000 },

  // Study time milestones
  { id: 'time-1h', icon: '⏰', name: 'First Hour', desc: 'Study for 1 hour total', tier: 'bronze', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 60 },
  { id: 'time-10h', icon: '⏰', name: '10 Hour Hero', desc: 'Study for 10 hours total', tier: 'bronze', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 600 },
  { id: 'time-50h', icon: '⏰', name: 'Half-Century', desc: 'Study for 50 hours total', tier: 'silver', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 3000 },
  { id: 'time-100h', icon: '🎓', name: 'Scholar', desc: 'Study for 100 hours total', tier: 'silver', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 6000 },
  { id: 'time-500h', icon: '🎓', name: 'Sage', desc: 'Study for 500 hours total', tier: 'gold', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 30000 },
  { id: 'time-1000h', icon: '🧠', name: 'Brain Titan', desc: 'Study for 1000 hours total', tier: 'legendary', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 60000 },

  // Streaks
  { id: 'streak-3', icon: '🔥', name: 'On Fire', desc: '3-day study streak', tier: 'bronze', category: 'Streaks', check: (d) => d.currentStreak >= 3 },
  { id: 'streak-7', icon: '🔥', name: 'Week Warrior', desc: '7-day study streak', tier: 'silver', category: 'Streaks', check: (d) => d.currentStreak >= 7 },
  { id: 'streak-14', icon: '🔥', name: 'Two-Week Wonder', desc: '14-day study streak', tier: 'silver', category: 'Streaks', check: (d) => d.currentStreak >= 14 },
  { id: 'streak-30', icon: '💫', name: 'Monthly Habit', desc: '30-day study streak', tier: 'gold', category: 'Streaks', check: (d) => d.currentStreak >= 30 },
  { id: 'streak-60', icon: '💫', name: 'Unstoppable', desc: '60-day study streak', tier: 'gold', category: 'Streaks', check: (d) => d.currentStreak >= 60 },
  { id: 'streak-100', icon: '⚡', name: 'Centurion', desc: '100-day study streak', tier: 'legendary', category: 'Streaks', check: (d) => d.currentStreak >= 100 },
  { id: 'streak-365', icon: '🌟', name: 'Year-Long Devotee', desc: '365-day study streak', tier: 'legendary', category: 'Streaks', check: (d) => d.currentStreak >= 365 },

  // Level milestones
  { id: 'level-5', icon: '⭐', name: 'Level 5', desc: 'Reach level 5', tier: 'bronze', category: 'Levels', check: (d) => Math.floor((d.points || 0) / 100) + 1 >= 5 },
  { id: 'level-10', icon: '⭐', name: 'Level 10', desc: 'Reach level 10', tier: 'silver', category: 'Levels', check: (d) => Math.floor((d.points || 0) / 100) + 1 >= 10 },
  { id: 'level-25', icon: '🌟', name: 'Level 25', desc: 'Reach level 25', tier: 'gold', category: 'Levels', check: (d) => Math.floor((d.points || 0) / 100) + 1 >= 25 },
  { id: 'level-50', icon: '👑', name: 'Level 50', desc: 'Reach level 50 — prestige!', tier: 'legendary', category: 'Levels', check: (d) => Math.floor((d.points || 0) / 100) + 1 >= 50 },

  // Homework
  { id: 'hw-1', icon: '📚', name: 'First Assignment', desc: 'Complete your first homework', tier: 'bronze', category: 'Homework', check: (d) => (d.totalHomeworkCompleted || 0) >= 1 },
  { id: 'hw-10', icon: '📚', name: 'Homework Hero', desc: 'Complete 10 homework items', tier: 'bronze', category: 'Homework', check: (d) => (d.totalHomeworkCompleted || 0) >= 10 },
  { id: 'hw-50', icon: '📖', name: 'Assignment Ace', desc: 'Complete 50 homework items', tier: 'silver', category: 'Homework', check: (d) => (d.totalHomeworkCompleted || 0) >= 50 },
  { id: 'hw-100', icon: '📖', name: 'Homework Champion', desc: 'Complete 100 homework items', tier: 'gold', category: 'Homework', check: (d) => (d.totalHomeworkCompleted || 0) >= 100 },

  // Points
  { id: 'points-500', icon: '💎', name: 'Point Collector', desc: 'Earn 500 points total', tier: 'bronze', category: 'Points', check: (d) => (d.points || 0) >= 500 },
  { id: 'points-2500', icon: '💎', name: 'Point Hoarder', desc: 'Earn 2,500 points total', tier: 'silver', category: 'Points', check: (d) => (d.points || 0) >= 2500 },
  { id: 'points-10k', icon: '💰', name: 'Treasury', desc: 'Earn 10,000 points total', tier: 'gold', category: 'Points', check: (d) => (d.points || 0) >= 10000 },

  // Special time-of-day badges
  { id: 'early-bird', icon: '🌅', name: 'Early Bird', desc: 'Study before 6am', tier: 'silver', category: 'Special', check: (d, extras) => extras?.hasEarlySession },
  { id: 'night-owl', icon: '🦉', name: 'Night Owl', desc: 'Study after 11pm', tier: 'silver', category: 'Special', check: (d, extras) => extras?.hasLateSession },
  { id: 'weekend-warrior', icon: '🎯', name: 'Weekend Warrior', desc: 'Study on both Saturday and Sunday', tier: 'silver', category: 'Special', check: (d, extras) => extras?.hasWeekendStudy },
  { id: 'marathoner', icon: '🏃', name: 'Marathoner', desc: 'Study for 3+ hours in a single day', tier: 'gold', category: 'Special', check: (d, extras) => extras?.maxDayMinutes >= 180 },

  // AI feature usage
  { id: 'ai-plan-1', icon: '✨', name: 'Future Planner', desc: 'Generate your first AI study plan', tier: 'bronze', category: 'AI Features', check: (d) => (d.aiPlansGenerated || 0) >= 1 },
  { id: 'ai-coach-1', icon: '💬', name: 'First Pep Talk', desc: 'Get your first AI pep talk', tier: 'bronze', category: 'AI Features', check: (d) => (d.aiPepTalksReceived || 0) >= 1 },
  { id: 'ai-prioritize-1', icon: '🎯', name: 'Smart Planner', desc: 'Use AI prioritization for the first time', tier: 'bronze', category: 'AI Features', check: (d) => (d.aiPrioritizationsUsed || 0) >= 1 },
  { id: 'ai-plan-5', icon: '🗓️', name: 'Strategic Thinker', desc: 'Generate 5 AI study plans', tier: 'silver', category: 'AI Features', check: (d) => (d.aiPlansGenerated || 0) >= 5 },
  { id: 'ai-coach-10', icon: '🎙️', name: 'Coach\'s Favorite', desc: 'Get 10 AI pep talks', tier: 'silver', category: 'AI Features', check: (d) => (d.aiPepTalksReceived || 0) >= 10 },

  // More Pomodoro milestones
  { id: 'pomo-25', icon: '🍅', name: 'Quarter Century', desc: 'Complete 25 pomodoros', tier: 'bronze', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 25 },
  { id: 'pomo-250', icon: '🏆', name: 'Pomodoro Pro', desc: 'Complete 250 pomodoros', tier: 'gold', category: 'Pomodoros', check: (d) => d.totalPomodoros >= 250 },

  // Time milestones
  { id: 'time-25h', icon: '⏳', name: 'Committed', desc: 'Study for 25 hours total', tier: 'bronze', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 1500 },
  { id: 'time-250h', icon: '🎓', name: 'Graduate', desc: 'Study for 250 hours total', tier: 'gold', category: 'Study Time', check: (d) => d.totalStudyMinutes >= 15000 },

  // Streaks
  { id: 'streak-200', icon: '⚡', name: 'Elite', desc: '200-day study streak', tier: 'legendary', category: 'Streaks', check: (d) => d.currentStreak >= 200 },

  // More levels
  { id: 'level-15', icon: '⭐', name: 'Level 15', desc: 'Reach level 15', tier: 'silver', category: 'Levels', check: (d) => Math.floor((d.points || 0) / 100) + 1 >= 15 },
  { id: 'level-100', icon: '👑', name: 'Level 100', desc: 'Reach level 100 — mythic', tier: 'legendary', category: 'Levels', check: (d) => Math.floor((d.points || 0) / 100) + 1 >= 100 },

  // Homework
  { id: 'hw-500', icon: '📘', name: 'Homework Grandmaster', desc: 'Complete 500 homework items', tier: 'legendary', category: 'Homework', check: (d) => (d.totalHomeworkCompleted || 0) >= 500 },
  { id: 'hw-mastery-10', icon: '🧩', name: 'Memory Champion', desc: 'Master 10 homework items via SRS', tier: 'gold', category: 'Homework', check: (d) => (d.masteredItems || 0) >= 10 },

  // Points
  { id: 'points-50k', icon: '👑', name: 'Point Emperor', desc: 'Earn 50,000 points total', tier: 'legendary', category: 'Points', check: (d) => (d.points || 0) >= 50000 },

  // Special
  { id: 'focus-mode-10', icon: '🎯', name: 'Deep Focus', desc: 'Use Focus Mode 10 times', tier: 'silver', category: 'Special', check: (d) => (d.focusModeUsed || 0) >= 10 },
  { id: 'journal-7', icon: '📔', name: 'Reflective Week', desc: 'Journal 7 days in a row', tier: 'silver', category: 'Special', check: (d, extras) => extras?.journalStreak >= 7 },
  { id: 'journal-30', icon: '📖', name: 'Journal Keeper', desc: 'Write 30 journal entries', tier: 'gold', category: 'Special', check: (d, extras) => extras?.journalCount >= 30 },

  // Shop
  { id: 'shop-first', icon: '🛒', name: 'First Purchase', desc: 'Unlock your first reward', tier: 'bronze', category: 'Shop', check: (d, extras) => extras?.rewardsUnlocked >= 1 },
  { id: 'shop-5', icon: '🎁', name: 'Collector', desc: 'Unlock 5 rewards', tier: 'silver', category: 'Shop', check: (d, extras) => extras?.rewardsUnlocked >= 5 },
  { id: 'shop-all', icon: '💎', name: 'Completionist', desc: 'Unlock every reward in the shop', tier: 'legendary', category: 'Shop', check: (d, extras) => extras?.rewardsUnlocked >= 12 },
];

const TIER_COLORS = {
  bronze: { bg: 'rgba(180, 83, 9, 0.15)', border: 'rgba(180, 83, 9, 0.4)', text: '#d97706' },
  silver: { bg: 'rgba(148, 163, 184, 0.15)', border: 'rgba(148, 163, 184, 0.4)', text: '#cbd5e1' },
  gold: { bg: 'rgba(234, 179, 8, 0.15)', border: 'rgba(234, 179, 8, 0.4)', text: '#eab308' },
  legendary: { bg: 'rgba(139, 92, 246, 0.18)', border: 'rgba(139, 92, 246, 0.5)', text: '#a78bfa' }
};

// Compute derived stats from sessions for achievements that need more than userData
const computeAchievementExtras = (sessions, journals = [], rewards = []) => {
  const extras = { hasEarlySession: false, hasLateSession: false, hasWeekendStudy: false, maxDayMinutes: 0, journalCount: 0, journalStreak: 0, rewardsUnlocked: 0 };
  const dayTotals = {};
  const weekendDays = new Set();
  sessions.forEach(s => {
    const d = s.completedAt?.seconds ? new Date(s.completedAt.seconds * 1000) : new Date(s.completedAt);
    const hour = d.getHours();
    if (hour < 6) extras.hasEarlySession = true;
    if (hour >= 23) extras.hasLateSession = true;
    const dow = d.getDay();
    if (dow === 0 || dow === 6) weekendDays.add(dow);
    const key = d.toDateString();
    dayTotals[key] = (dayTotals[key] || 0) + (s.minutes || 0);
  });
  extras.hasWeekendStudy = weekendDays.has(0) && weekendDays.has(6);
  extras.maxDayMinutes = Math.max(0, ...Object.values(dayTotals));

  extras.journalCount = journals.length;
  // Journal streak (consecutive days up to today with a journal entry)
  const jDays = new Set(journals.map(j => {
    const d = j.createdAt?.seconds ? new Date(j.createdAt.seconds * 1000) : new Date(j.createdAt);
    return d.toDateString();
  }));
  let streak = 0;
  const today = new Date(); today.setHours(0,0,0,0);
  const check = new Date(today);
  while (jDays.has(check.toDateString())) {
    streak++;
    check.setDate(check.getDate() - 1);
  }
  extras.journalStreak = streak;
  extras.rewardsUnlocked = rewards.length;
  return extras;
};

// Hook that watches for new achievement unlocks and dispatches a toast event
const useAchievementWatcher = (uid, userData) => {
  const [sessions, setSessions] = React.useState([]);
  const [journals, setJournals] = React.useState([]);
  const [rewards, setRewards] = React.useState([]);
  const unlockedRef = React.useRef(new Set());
  const loadedRef = React.useRef(false);

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('achievements')
      .onSnapshot(snap => {
        // Merge server state into the existing set rather than replacing it — this
        // preserves locally-added IDs whose Firestore writes haven't landed yet,
        // preventing a race that would re-fire the same toast.
        snap.docs.forEach(d => unlockedRef.current.add(d.id));
        loadedRef.current = true;
      });
    const unsubSess = db.collection('users').doc(uid).collection('sessions')
      .onSnapshot(snap => setSessions(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    const unsubJ = db.collection('users').doc(uid).collection('journal')
      .onSnapshot(snap => setJournals(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    const unsubR = db.collection('users').doc(uid).collection('unlockedRewards')
      .onSnapshot(snap => setRewards(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    return () => { unsub(); unsubSess(); unsubJ(); unsubR(); };
  }, [uid]);

  React.useEffect(() => {
    if (!uid || !userData || !loadedRef.current) return;
    const extras = computeAchievementExtras(sessions, journals, rewards);
    const newlyUnlocked = [];
    ACHIEVEMENTS.forEach(a => {
      if (unlockedRef.current.has(a.id)) return;
      try {
        if (a.check(userData, extras)) {
          newlyUnlocked.push(a);
          unlockedRef.current.add(a.id);
        }
      } catch (e) { /* ignore broken check */ }
    });

    newlyUnlocked.forEach(a => {
      // Persist to Firestore
      db.collection('users').doc(uid).collection('achievements').doc(a.id).set({
        unlockedAt: new Date()
      });
      // Dispatch toast event
      window.dispatchEvent(new CustomEvent('achievement-unlocked', { detail: a }));
    });
  }, [userData, sessions, journals, rewards, uid]);
};

// Achievement unlock toast (floating)
// XP award toast
const XPToasts = () => {
  const [toasts, setToasts] = React.useState([]);
  React.useEffect(() => {
    const handler = (e) => {
      const id = Math.random().toString(36).slice(2);
      setToasts(prev => [...prev, { id, ...e.detail }]);
      setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 3200);
    };
    window.addEventListener('xp-awarded', handler);
    return () => window.removeEventListener('xp-awarded', handler);
  }, []);

  return (
    <div style={{ position: 'fixed', top: '100px', left: '50%', transform: 'translateX(-50%)', zIndex: 9998, pointerEvents: 'none', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
      {toasts.map(t => (
        <div key={t.id} style={{
          animation: 'xpFloat 3s ease-out forwards',
          background: '#06b6d4', color: '#09090b',
          padding: '12px 20px', borderRadius: '9999px',
          fontWeight: 700, fontSize: '18px',
          boxShadow: '0 4px 16px rgba(6,182,212,0.4)',
          display: 'flex', alignItems: 'center', gap: '10px',
          fontVariantNumeric: 'tabular-nums'
        }}>
          <span>+{t.total} XP</span>
          {t.multiplier > 1 && (
            <span style={{
              background: 'rgba(0,0,0,0.15)', padding: '2px 8px', borderRadius: '9999px',
              fontSize: '12px', fontWeight: 600
            }}>×{t.multiplier.toFixed(t.multiplier % 1 === 0 ? 0 : 2).replace(/\.?0+$/, '')}</span>
          )}
        </div>
      ))}
    </div>
  );
};

// Mobile bottom navigation
const MobileBottomNav = ({ currentPage, setCurrentPage }) => {
  const items = [
    { id: 'dashboard', label: 'Home', icon: '📊' },
    { id: 'timer', label: 'Timer', icon: '⏱️' },
    { id: 'homework', label: 'HW', icon: '📚' },
    { id: 'achievements', label: 'Badges', icon: '🏆' },
    { id: 'profile', label: 'Me', icon: '👤' },
  ];
  return (
    <nav className="mobile-bottom-nav">
      {items.map(i => (
        <button
          key={i.id}
          className={currentPage === i.id ? 'active' : ''}
          onClick={() => setCurrentPage(i.id)}
        >
          <span style={{ fontSize: '20px' }}>{i.icon}</span>
          <span>{i.label}</span>
        </button>
      ))}
    </nav>
  );
};

const AchievementToasts = () => {
  const [toasts, setToasts] = React.useState([]);
  React.useEffect(() => {
    const handler = (e) => {
      const a = e.detail;
      const id = Math.random().toString(36).slice(2);
      setToasts(prev => [...prev, { id, achievement: a }]);
      setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5500);
    };
    window.addEventListener('achievement-unlocked', handler);
    return () => window.removeEventListener('achievement-unlocked', handler);
  }, []);

  return (
    <div style={{ position: 'fixed', top: '80px', right: '24px', zIndex: 9999, display: 'flex', flexDirection: 'column', gap: '10px', pointerEvents: 'none' }}>
      {toasts.map(t => {
        const tc = TIER_COLORS[t.achievement.tier];
        return (
          <div key={t.id} className="achievement-toast" style={{
            background: '#18181b', border: `1px solid ${tc.border}`, borderRadius: '12px',
            padding: '14px 18px', minWidth: '280px', display: 'flex', gap: '12px', alignItems: 'center',
            boxShadow: '0 8px 24px rgba(0,0,0,0.35)', pointerEvents: 'auto',
            animation: 'achievementSlide 0.4s ease-out'
          }}>
            <div style={{
              width: '44px', height: '44px', borderRadius: '10px', background: tc.bg,
              display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '24px', flexShrink: 0
            }}>{t.achievement.icon}</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: '11px', fontWeight: 600, color: tc.text, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '2px' }}>
                Achievement unlocked
              </div>
              <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>{t.achievement.name}</div>
              <div className="ach-desc" style={{ fontSize: '12px' }}>{t.achievement.desc}</div>
            </div>
          </div>
        );
      })}
    </div>
  );
};

// Achievements Gallery Page
const AchievementsPage = ({ uid }) => {
  const [unlocked, setUnlocked] = React.useState({});
  const [filter, setFilter] = React.useState('All');

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('achievements')
      .onSnapshot(snap => {
        const map = {};
        snap.docs.forEach(d => { map[d.id] = d.data(); });
        setUnlocked(map);
      });
    return unsub;
  }, [uid]);

  const categories = ['All', ...Array.from(new Set(ACHIEVEMENTS.map(a => a.category)))];
  // Fall back to 'Other' for any achievement missing a category so it remains visible.
  const visible = filter === 'All' ? ACHIEVEMENTS : ACHIEVEMENTS.filter(a => (a.category || 'Other') === filter);
  const unlockedCount = Object.keys(unlocked).length;

  return (
    <div>
      <h1 className="page-title">Achievements</h1>
      <p className="page-subtitle">{unlockedCount} of {ACHIEVEMENTS.length} unlocked</p>

      <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '24px' }}>
        {categories.map(c => (
          <button key={c} onClick={() => setFilter(c)} style={{
            padding: '6px 14px', borderRadius: '8px', fontSize: '13px', fontWeight: 500, cursor: 'pointer',
            background: filter === c ? 'rgba(6, 182, 212, 0.1)' : '#27272a',
            border: `1px solid ${filter === c ? 'rgba(6, 182, 212, 0.2)' : 'rgba(255,255,255,0.06)'}`,
            color: filter === c ? '#06b6d4' : '#a1a1aa', fontFamily: 'inherit'
          }}>{c}</button>
        ))}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: '16px' }}>
        {visible.map(a => {
          const isUnlocked = !!unlocked[a.id];
          const tc = TIER_COLORS[a.tier];
          return (
            <div key={a.id} className="achievement-card" style={{
              background: '#18181b',
              border: `1px solid ${isUnlocked ? tc.border : 'rgba(255,255,255,0.06)'}`,
              borderRadius: '16px', padding: '18px',
              opacity: isUnlocked ? 1 : 0.55,
              filter: isUnlocked ? 'none' : 'grayscale(0.6)',
              transition: 'all 0.15s ease',
              position: 'relative'
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '10px' }}>
                <div style={{
                  width: '44px', height: '44px', borderRadius: '10px',
                  background: isUnlocked ? tc.bg : 'rgba(255,255,255,0.04)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: '24px', filter: isUnlocked ? 'none' : 'grayscale(1)'
                }}>{a.icon}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>{a.name}</div>
                  <div style={{ fontSize: '10px', fontWeight: 600, color: isUnlocked ? tc.text : '#71717a', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
                    {a.tier}
                  </div>
                </div>
              </div>
              <div className="ach-desc" style={{ fontSize: '12px', lineHeight: 1.4 }}>{a.desc}</div>
              {isUnlocked && unlocked[a.id]?.unlockedAt && (
                <div style={{ fontSize: '11px', color: '#71717a', marginTop: '8px' }}>
                  🗓 {new Date(unlocked[a.id].unlockedAt.seconds * 1000).toLocaleDateString()}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

// ============= LEADERBOARD =============
const LeaderboardPage = ({ uid, userData }) => {
  const [optedIn, setOptedIn] = React.useState(!!userData?.leaderboardOptIn);
  const [entries, setEntries] = React.useState([]);
  const [friends, setFriends] = React.useState([]);          // [{ id, email, displayName, addedAt }]
  const [friendEntries, setFriendEntries] = React.useState([]);
  const [mode, setMode] = React.useState('global');           // 'global' | 'friends'
  const [metric, setMetric] = React.useState('points');
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState('');
  const [addFriendEmail, setAddFriendEmail] = React.useState('');
  const [addFriendMsg, setAddFriendMsg] = React.useState('');
  const [addingFriend, setAddingFriend] = React.useState(false);
  // Incoming requests: docs at users/{me}/friendRequests/{senderUid}
  const [incomingRequests, setIncomingRequests] = React.useState([]);
  // Track which uids the current user has already sent a request to, so we can show
  // "Request sent" instead of letting them spam another send.
  const [outgoingRequestUids, setOutgoingRequestUids] = React.useState(new Set());

  React.useEffect(() => { setOptedIn(!!userData?.leaderboardOptIn); }, [userData]);

  // Subscribe to friends list
  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('friends')
      .onSnapshot(snap => setFriends(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    return unsub;
  }, [uid]);

  // Subscribe to incoming friend requests (realtime)
  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('friendRequests')
      .onSnapshot(snap => {
        setIncomingRequests(snap.docs.map(d => ({ id: d.id, ...d.data() })));
      });
    return unsub;
  }, [uid]);

  const loadGlobal = React.useCallback(async () => {
    setLoading(true);
    setError('');
    try {
      const snap = await db.collection('users')
        .where('leaderboardOptIn', '==', true)
        .orderBy(metric, 'desc')
        .limit(50)
        .get();
      setEntries(snap.docs.map(d => ({ id: d.id, ...d.data() })));
    } catch (e) {
      setError('Could not load global leaderboard (Firestore rules may block public reads). Your data is safe.');
    } finally { setLoading(false); }
  }, [metric]);

  const loadFriends = React.useCallback(async () => {
    if (friends.length === 0) { setFriendEntries([]); return; }
    setLoading(true);
    setError('');
    try {
      // Fetch each friend's user doc in parallel (plus self)
      const ids = [...new Set([uid, ...friends.map(f => f.id)])];
      const snaps = await Promise.all(ids.map(id => db.collection('users').doc(id).get()));
      const rows = snaps
        .filter(s => s.exists)
        .map(s => ({ id: s.id, ...s.data() }))
        .sort((a, b) => (b[metric] || 0) - (a[metric] || 0));
      setFriendEntries(rows);
    } catch (e) {
      setError('Could not load friends leaderboard: ' + e.message);
    } finally { setLoading(false); }
  }, [uid, friends, metric]);

  React.useEffect(() => {
    if (mode === 'global' && optedIn) loadGlobal();
    if (mode === 'friends') loadFriends();
  }, [mode, optedIn, loadGlobal, loadFriends]);

  const toggleOptIn = async () => {
    if (!uid) return;
    const next = !optedIn;
    setOptedIn(next);
    await db.collection('users').doc(uid).update({
      leaderboardOptIn: next,
      leaderboardDisplayName: next ? (userData?.displayName || 'Student') : firebase.firestore.FieldValue.delete()
    });
    if (next && mode === 'global') loadGlobal();
  };

  // ============= FRIEND REQUESTS =============
  // Outgoing-request dedup: persisted per-user so a page refresh doesn't let the user
  // spam Send again. Key is scoped by uid so multi-account sign-in on the same browser
  // doesn't leak state between users.
  const OUTGOING_KEY = uid ? `ff-outgoing-requests-${uid}` : null;
  React.useEffect(() => {
    if (!OUTGOING_KEY) return;
    try {
      const raw = localStorage.getItem(OUTGOING_KEY);
      if (raw) setOutgoingRequestUids(new Set(JSON.parse(raw)));
    } catch {}
  }, [OUTGOING_KEY]);
  // When a friendship is established (someone accepted), clear that outgoing entry.
  React.useEffect(() => {
    if (!OUTGOING_KEY) return;
    const friendIds = new Set(friends.map(f => f.id));
    setOutgoingRequestUids(prev => {
      const next = new Set([...prev].filter(id => !friendIds.has(id)));
      if (next.size !== prev.size) {
        try { localStorage.setItem(OUTGOING_KEY, JSON.stringify([...next])); } catch {}
      }
      return next;
    });
  }, [friends, OUTGOING_KEY]);

  // Send a friend request to someone by email. Writes to THEIR friendRequests
  // subcollection so they can accept/reject. We validate against already-friends
  // and already-sent cases to avoid spamming.
  const sendFriendRequest = async () => {
    setAddFriendMsg('');
    if (!uid || !userData) { setAddFriendMsg('Please sign in first.'); return; }
    const email = addFriendEmail.trim().toLowerCase();
    if (!email || !email.includes('@')) { setAddFriendMsg('Enter a valid email'); return; }
    if (email === (userData?.email || '').toLowerCase()) { setAddFriendMsg("You can't send a request to yourself"); return; }
    setAddingFriend(true);
    try {
      const snap = await db.collection('users').where('email', '==', email).limit(1).get();
      if (snap.empty) { setAddFriendMsg('No FocusForge user with that email'); setAddingFriend(false); return; }
      const targetDoc = snap.docs[0];
      const targetUid = targetDoc.id;
      const targetData = targetDoc.data();

      // Block if already friends
      if (friends.find(f => f.id === targetUid)) {
        setAddFriendMsg('You\'re already friends!');
        setAddingFriend(false); return;
      }
      // Block if they already sent YOU a request — tell the user to accept theirs instead
      if (incomingRequests.find(r => r.id === targetUid)) {
        setAddFriendMsg(`${targetData.displayName || 'They'} already sent you a request — accept it below.`);
        setAddingFriend(false); return;
      }
      // Block if we already have an outgoing request pending (persisted across reloads)
      if (outgoingRequestUids.has(targetUid)) {
        setAddFriendMsg('You already sent them a request. Waiting for them to accept.');
        setAddingFriend(false); return;
      }

      // Write request to target's inbox. Doc ID = sender's UID for easy lookup + dedup.
      await db.collection('users').doc(targetUid).collection('friendRequests').doc(uid).set({
        fromUid: uid,
        fromEmail: userData.email || '',
        fromDisplayName: userData.displayName || 'Student',
        fromPhotoURL: userData.photoURL || '',
        sentAt: new Date()
      });

      setOutgoingRequestUids(prev => {
        const next = new Set([...prev, targetUid]);
        try { if (OUTGOING_KEY) localStorage.setItem(OUTGOING_KEY, JSON.stringify([...next])); } catch {}
        return next;
      });
      setAddFriendMsg(`✓ Request sent to ${targetData.displayName || targetData.email}`);
      setAddFriendEmail('');
      setTimeout(() => setAddFriendMsg(''), 3000);
    } catch (e) {
      setAddFriendMsg('Error: ' + (e.message || 'Could not send request'));
    } finally { setAddingFriend(false); }
  };

  // Accept an incoming request — bilateral write so we appear in each other's friend lists.
  // Firestore rules must allow the friend (identified in doc) to write into our counterpart.
  const acceptFriendRequest = async (req) => {
    if (!uid || !userData) { setAddFriendMsg('Please sign in first.'); return; }
    try {
      // The batch is atomic — all three writes succeed together or none do. The batch is
      // evaluated against pre-batch Firestore state, so the reciprocal-write's `exists()`
      // rule check sees the request doc even though the same batch deletes it.
      const batch = db.batch();
      batch.set(
        db.collection('users').doc(uid).collection('friends').doc(req.fromUid),
        {
          email: req.fromEmail || '',
          displayName: req.fromDisplayName || 'Unknown',
          photoURL: req.fromPhotoURL || '',
          addedAt: new Date()
        }
      );
      batch.set(
        db.collection('users').doc(req.fromUid).collection('friends').doc(uid),
        {
          email: userData.email || '',
          displayName: userData.displayName || 'Unknown',
          photoURL: userData.photoURL || '',
          addedAt: new Date()
        }
      );
      // Delete the request we just accepted
      batch.delete(db.collection('users').doc(uid).collection('friendRequests').doc(req.fromUid));
      await batch.commit();
      setAddFriendMsg(`✓ You and ${req.fromDisplayName || 'your friend'} are now connected`);
      setTimeout(() => setAddFriendMsg(''), 2500);
    } catch (e) {
      // Request stays in the inbox on failure so user can retry. Show a specific message
      // when the rules reject the reciprocal write so they know what to fix.
      const msg = e.code === 'permission-denied'
        ? 'Permission denied — update your Firestore rules (see README section 1 step 5) and retry.'
        : (e.message || 'unknown error');
      setAddFriendMsg('Could not accept: ' + msg);
    }
  };

  const rejectFriendRequest = async (req) => {
    if (!uid) return;
    try {
      await db.collection('users').doc(uid).collection('friendRequests').doc(req.fromUid).delete();
    } catch (e) {
      setAddFriendMsg('Could not reject: ' + (e.message || 'unknown error'));
    }
  };

  const removeFriend = async (friendId) => {
    if (!uid) return;
    // Delete both sides so the friendship disappears for them too (allowed because we wrote
    // their side under the reciprocal rule — but if rules reject the remote delete, we silently
    // fall back to only removing our local view).
    try {
      await db.collection('users').doc(uid).collection('friends').doc(friendId).delete();
      db.collection('users').doc(friendId).collection('friends').doc(uid).delete().catch(() => {});
    } catch (e) {
      console.warn('[FocusForge] removeFriend failed:', e);
    }
  };

  const metricLabels = {
    points: 'XP',
    totalStudyMinutes: 'Study Time',
    totalPomodoros: 'Pomodoros',
    currentStreak: 'Streak'
  };

  const formatVal = (val, m) => {
    if (m === 'totalStudyMinutes') return formatMinutes(val || 0);
    if (m === 'currentStreak') return `${val || 0} days`;
    return (val || 0).toLocaleString();
  };

  const board = mode === 'global' ? entries : friendEntries;
  const yourRank = board.findIndex(e => e.id === uid) + 1;

  const renderRow = (e, i) => {
    const isYou = e.id === uid;
    const rankColor = i === 0 ? '#eab308' : i === 1 ? '#d4d4d8' : i === 2 ? '#c88147' : '#71717a';
    return (
      <div key={e.id} style={{
        display: 'flex', alignItems: 'center', gap: '14px',
        padding: '14px 18px',
        background: isYou ? 'rgba(6,182,212,0.08)' : 'transparent',
        borderBottom: '1px solid rgba(255,255,255,0.04)'
      }}>
        <div style={{ width: '32px', textAlign: 'center', fontWeight: 700, color: rankColor, fontSize: i < 3 ? '18px' : '14px', fontVariantNumeric: 'tabular-nums' }}>
          {i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `#${i + 1}`}
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>
            {e.leaderboardDisplayName || e.displayName || 'Anonymous'}
            {isYou && <span style={{ marginLeft: '8px', fontSize: '11px', color: '#06b6d4', fontWeight: 500 }}>(you)</span>}
          </div>
          <div className="ach-desc" style={{ fontSize: '11px' }}>Level {Math.floor((e.points || 0) / 100) + 1}</div>
        </div>
        <div className="ach-name" style={{ fontSize: '15px', fontWeight: 700, color: '#06b6d4', fontVariantNumeric: 'tabular-nums' }}>
          {formatVal(e[metric], metric)}
        </div>
      </div>
    );
  };

  return (
    <div>
      <h1 className="page-title">Leaderboard</h1>
      <p className="page-subtitle">See how you stack up against other FocusForge users</p>

      {/* Mode switch: Global / Friends */}
      <div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
        <button onClick={() => setMode('global')} style={{
          flex: 1, padding: '10px 14px', borderRadius: '10px', fontSize: '13px', fontWeight: 600, cursor: 'pointer',
          background: mode === 'global' ? 'rgba(6,182,212,0.1)' : '#27272a',
          border: `1px solid ${mode === 'global' ? 'rgba(6,182,212,0.3)' : 'rgba(255,255,255,0.06)'}`,
          color: mode === 'global' ? '#06b6d4' : '#a1a1aa', fontFamily: 'inherit'
        }}>🌍 Global</button>
        <button onClick={() => setMode('friends')} style={{
          flex: 1, padding: '10px 14px', borderRadius: '10px', fontSize: '13px', fontWeight: 600, cursor: 'pointer',
          background: mode === 'friends' ? 'rgba(6,182,212,0.1)' : '#27272a',
          border: `1px solid ${mode === 'friends' ? 'rgba(6,182,212,0.3)' : 'rgba(255,255,255,0.06)'}`,
          color: mode === 'friends' ? '#06b6d4' : '#a1a1aa', fontFamily: 'inherit'
        }}>👥 Friends ({friends.length})</button>
      </div>

      {/* GLOBAL MODE — requires opt-in */}
      {mode === 'global' && (
        <>
          <div className="glass-card" style={{ marginBottom: '20px' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>
              <div>
                <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>
                  {optedIn ? '🌍 You\'re on the global leaderboard' : '🔒 Opt in to compete globally'}
                </div>
                <div className="ach-desc" style={{ fontSize: '12px' }}>
                  {optedIn
                    ? 'Your name and stats are visible to other opted-in users.'
                    : 'Your stats stay private until you opt in.'}
                </div>
              </div>
              <div className={`toggle-switch ${optedIn ? 'on' : ''}`} onClick={toggleOptIn} />
            </div>
          </div>
        </>
      )}

      {/* FRIENDS MODE — send requests, show inbox, list accepted friends */}
      {mode === 'friends' && (
        <>
          {/* Incoming friend requests — shown first so they're the highest priority */}
          {incomingRequests.length > 0 && (
            <div className="glass-card" style={{ marginBottom: '16px', border: '1px solid rgba(6,182,212,0.25)' }}>
              <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600, marginBottom: '10px' }}>
                📬 {incomingRequests.length} pending friend {incomingRequests.length === 1 ? 'request' : 'requests'}
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
                {incomingRequests.map(req => (
                  <div key={req.id} style={{
                    display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '10px',
                    padding: '10px 12px', borderRadius: '8px', background: 'rgba(6,182,212,0.05)',
                    flexWrap: 'wrap'
                  }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '10px', minWidth: 0, flex: 1 }}>
                      {req.fromPhotoURL ? (
                        <img src={req.fromPhotoURL} alt="" style={{ width: '36px', height: '36px', borderRadius: '50%' }} />
                      ) : (
                        <div style={{
                          width: '36px', height: '36px', borderRadius: '50%',
                          background: '#27272a', display: 'flex', alignItems: 'center', justifyContent: 'center',
                          color: '#a1a1aa', fontWeight: 600
                        }}>{(req.fromDisplayName || '?').slice(0, 1).toUpperCase()}</div>
                      )}
                      <div style={{ minWidth: 0 }}>
                        <div className="ach-name" style={{ fontSize: '13px', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                          {req.fromDisplayName || 'Unknown user'}
                        </div>
                        <div className="ach-desc" style={{ fontSize: '11px' }}>{req.fromEmail || ''}</div>
                      </div>
                    </div>
                    <div style={{ display: 'flex', gap: '6px' }}>
                      <button
                        className="btn btn-primary btn-small"
                        onClick={() => acceptFriendRequest(req)}
                        style={{ padding: '6px 12px' }}
                      >✓ Accept</button>
                      <button
                        className="btn btn-secondary btn-small"
                        onClick={() => rejectFriendRequest(req)}
                        style={{ padding: '6px 12px' }}
                      >Reject</button>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          )}

          {/* Send a new friend request */}
          <div className="glass-card" style={{ marginBottom: '20px' }}>
            <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600, marginBottom: '4px' }}>👥 Your friends</div>
            <div className="ach-desc" style={{ fontSize: '12px', marginBottom: '12px' }}>
              Send a friend request by email. Friends leaderboards work without needing to opt into the global leaderboard.
            </div>
            <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
              <input
                type="email"
                placeholder="friend@example.com"
                value={addFriendEmail}
                onChange={e => setAddFriendEmail(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && sendFriendRequest()}
                style={{ flex: 1, minWidth: '200px' }}
              />
              <button className="btn btn-primary btn-small" onClick={sendFriendRequest} disabled={addingFriend || !addFriendEmail.trim()}>
                {addingFriend ? 'Sending…' : 'Send request'}
              </button>
            </div>
            {addFriendMsg && (
              <div style={{
                marginTop: '10px', padding: '8px 12px', borderRadius: '8px', fontSize: '12px',
                background: addFriendMsg.startsWith('✓') ? 'rgba(34,197,94,0.08)' : 'rgba(239,68,68,0.08)',
                color: addFriendMsg.startsWith('✓') ? '#22c55e' : '#ef4444',
              }}>{addFriendMsg}</div>
            )}

            {friends.length > 0 && (
              <div style={{ marginTop: '14px' }}>
                <div className="ach-desc" style={{ fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 600, marginBottom: '8px' }}>
                  Connected ({friends.length})
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
                  {friends.map(f => (
                    <div key={f.id} style={{
                      display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                      padding: '8px 12px', borderRadius: '8px', background: 'rgba(255,255,255,0.02)'
                    }}>
                      <div>
                        <div className="ach-name" style={{ fontSize: '13px', fontWeight: 500 }}>{f.displayName}</div>
                        <div className="ach-desc" style={{ fontSize: '11px' }}>{f.email}</div>
                      </div>
                      <button onClick={() => removeFriend(f.id)} style={{ background: 'none', border: 'none', color: '#71717a', cursor: 'pointer', fontSize: '12px', fontFamily: 'inherit' }}>Remove</button>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {friends.length === 0 && incomingRequests.length === 0 && (
              <div className="ach-desc" style={{ fontSize: '12px', marginTop: '12px', fontStyle: 'italic' }}>
                No friends yet. Send a request to get started!
              </div>
            )}
          </div>
        </>
      )}

      {/* Metric chips — shown when we have something to rank */}
      {((mode === 'global' && optedIn) || (mode === 'friends' && friends.length > 0)) && (
        <div style={{ display: 'flex', gap: '8px', marginBottom: '16px', flexWrap: 'wrap' }}>
          {Object.entries(metricLabels).map(([k, l]) => (
            <button key={k} onClick={() => setMetric(k)} style={{
              padding: '6px 14px', borderRadius: '8px', fontSize: '13px', fontWeight: 500, cursor: 'pointer',
              background: metric === k ? 'rgba(6, 182, 212, 0.1)' : '#27272a',
              border: `1px solid ${metric === k ? 'rgba(6, 182, 212, 0.3)' : 'rgba(255,255,255,0.06)'}`,
              color: metric === k ? '#06b6d4' : '#a1a1aa', fontFamily: 'inherit'
            }}>{l}</button>
          ))}
        </div>
      )}

      {yourRank > 0 && (
        <div style={{
          marginBottom: '20px', padding: '12px 16px', borderRadius: '12px',
          background: 'linear-gradient(135deg, rgba(6,182,212,0.08), rgba(139,92,246,0.06))',
          border: '1px solid rgba(6,182,212,0.22)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between'
        }}>
          <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>
            Your rank: <span style={{ color: '#06b6d4' }}>#{yourRank}</span>
            <span className="ach-desc" style={{ fontSize: '11px', marginLeft: '8px' }}>
              ({mode === 'friends' ? `among ${friends.length + 1}` : 'globally'})
            </span>
          </div>
          <div className="ach-desc" style={{ fontSize: '12px' }}>
            {formatVal(userData?.[metric], metric)} {metricLabels[metric]}
          </div>
        </div>
      )}

      {loading && <div style={{ padding: '20px', textAlign: 'center', color: '#71717a' }}>Loading leaderboard…</div>}
      {error && (
        <div style={{ padding: '14px 16px', borderRadius: '12px', background: 'rgba(239,68,68,0.08)', color: '#ef4444', border: '1px solid rgba(239,68,68,0.2)', fontSize: '13px' }}>
          {error}
        </div>
      )}
      {!loading && !error && board.length === 0 && (
        <div className="glass-card" style={{ textAlign: 'center', padding: '40px', color: '#71717a' }}>
          {mode === 'global'
            ? (optedIn ? 'No one else on the leaderboard yet. Be the first!' : 'Turn on the opt-in switch above to see the global leaderboard.')
            : 'No friends yet — add one above to compare stats.'}
        </div>
      )}
      {!loading && !error && board.length > 0 && (
        <div className="glass-card" style={{ padding: 0, overflow: 'hidden' }}>
          {board.map(renderRow)}
        </div>
      )}
    </div>
  );
};

// ============= REWARDS SHOP =============
const SHOP_ITEMS = [
  // Premium themes (unlocked with points)
  { id: 'theme-aurora', type: 'theme', name: 'Aurora', icon: '🌌', price: 500, desc: 'Cyan→violet gradient accent', rare: false },
  { id: 'theme-ember', type: 'theme', name: 'Ember', icon: '🔥', price: 800, desc: 'Fiery red-orange gradient', rare: false },
  { id: 'theme-forest', type: 'theme', name: 'Deep Forest', icon: '🌲', price: 1000, desc: 'Emerald + gold premium', rare: true },
  { id: 'theme-cosmic', type: 'theme', name: 'Cosmic', icon: '✨', price: 2500, desc: 'Animated star gradient', rare: true },
  // Confetti styles
  { id: 'confetti-stars', type: 'confetti', name: 'Star Shower', icon: '⭐', price: 300, desc: 'Stars fall on completion', rare: false },
  { id: 'confetti-hearts', type: 'confetti', name: 'Heart Rain', icon: '💖', price: 400, desc: 'Hearts on completion', rare: false },
  { id: 'confetti-fire', type: 'confetti', name: 'Fire Burst', icon: '🔥', price: 600, desc: 'Fire explosion', rare: true },
  { id: 'confetti-diamond', type: 'confetti', name: 'Diamond Fall', icon: '💎', price: 1500, desc: 'Rare diamond burst', rare: true },
  // Avatar frames
  { id: 'frame-gold', type: 'frame', name: 'Gold Frame', icon: '🏅', price: 750, desc: 'Golden avatar border', rare: false },
  { id: 'frame-pulse', type: 'frame', name: 'Pulse Frame', icon: '💫', price: 1200, desc: 'Animated pulsing glow', rare: true },
  // Sound packs
  { id: 'sound-bell', type: 'sound', name: 'Zen Bell', icon: '🔔', price: 300, desc: 'Calming bell for timer end', rare: false },
  { id: 'sound-epic', type: 'sound', name: 'Epic Victory', icon: '🎺', price: 800, desc: 'Epic fanfare on completion', rare: true },
];

const RewardsShopPage = ({ uid, userData, activeConfetti, setActiveConfetti, activeSound, setActiveSound }) => {
  const [unlocked, setUnlocked] = React.useState({});
  const [filter, setFilter] = React.useState('all');
  const [message, setMessage] = React.useState('');
  // Tracks in-flight purchases so a double-click can't fire two writes
  const [purchasing, setPurchasing] = React.useState({});

  const equip = (item) => {
    if (item.type === 'confetti') { setActiveConfetti(item.id); setMessage(`🎉 Equipped ${item.name}`); }
    else if (item.type === 'sound') { setActiveSound(item.id); setMessage(`🔔 Equipped ${item.name}`); }
    else return;
    setTimeout(() => setMessage(''), 2000);
  };

  const unequip = (type) => {
    if (type === 'confetti') { setActiveConfetti('default'); setMessage('Reverted confetti to default'); }
    else if (type === 'sound') { setActiveSound('default'); setMessage('Reverted sound to default'); }
    setTimeout(() => setMessage(''), 1500);
  };

  const isEquipped = (item) => (item.type === 'confetti' && activeConfetti === item.id) || (item.type === 'sound' && activeSound === item.id);

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('unlockedRewards')
      .onSnapshot(snap => {
        const map = {};
        snap.docs.forEach(d => map[d.id] = d.data());
        setUnlocked(map);
      });
    return unsub;
  }, [uid]);

  const points = userData?.points || 0;
  const typeLabels = { all: 'All', theme: 'Themes', confetti: 'Confetti', frame: 'Avatar Frames', sound: 'Sounds' };
  const visible = filter === 'all' ? SHOP_ITEMS : SHOP_ITEMS.filter(i => i.type === filter);

  const unlock = async (item) => {
    if (!uid) return;
    if (purchasing[item.id]) return; // already in flight — ignore double-click
    if (unlocked[item.id]) { setMessage('Already unlocked!'); setTimeout(() => setMessage(''), 1500); return; }
    if (points < item.price) {
      setMessage(`Need ${item.price - points} more XP`);
      setTimeout(() => setMessage(''), 2000);
      return;
    }
    setPurchasing(p => ({ ...p, [item.id]: true }));
    try {
      // Atomic transaction: check ownership + price and commit both writes together.
      // Without this, a double-click could race past the client-side guard and charge twice.
      await db.runTransaction(async (tx) => {
        const userRef = db.collection('users').doc(uid);
        const rewardRef = userRef.collection('unlockedRewards').doc(item.id);
        const rewardDoc = await tx.get(rewardRef);
        if (rewardDoc.exists) throw new Error('Already unlocked.');
        const userDoc = await tx.get(userRef);
        const current = userDoc.data()?.points || 0;
        if (current < item.price) throw new Error('Not enough XP.');
        tx.set(rewardRef, { ...item, unlockedAt: new Date() });
        tx.update(userRef, { points: firebase.firestore.FieldValue.increment(-item.price) });
      });
      setMessage(`✨ Unlocked ${item.name}!`);
    } catch (err) {
      setMessage('⚠ ' + (err.message || 'Purchase failed'));
    } finally {
      setPurchasing(p => { const n = { ...p }; delete n[item.id]; return n; });
      setTimeout(() => setMessage(''), 2500);
    }
  };

  const unlockedCount = Object.keys(unlocked).length;

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px', marginBottom: '20px' }}>
        <div>
          <h1 className="page-title" style={{ marginBottom: '4px' }}>Rewards Shop</h1>
          <p className="page-subtitle" style={{ marginBottom: 0 }}>Spend your XP on cosmetic unlocks</p>
        </div>
        <div style={{
          padding: '10px 18px', borderRadius: '9999px',
          background: 'rgba(245, 158, 11, 0.12)', border: '1px solid rgba(245, 158, 11, 0.3)',
          color: '#f59e0b', fontWeight: 700, fontSize: '16px', fontVariantNumeric: 'tabular-nums'
        }}>💎 {points.toLocaleString()} XP</div>
      </div>

      <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '20px' }}>
        {Object.entries(typeLabels).map(([k, l]) => (
          <button key={k} onClick={() => setFilter(k)} style={{
            padding: '6px 14px', borderRadius: '8px', fontSize: '13px', fontWeight: 500, cursor: 'pointer',
            background: filter === k ? 'rgba(6, 182, 212, 0.1)' : '#27272a',
            border: `1px solid ${filter === k ? 'rgba(6, 182, 212, 0.3)' : 'rgba(255,255,255,0.06)'}`,
            color: filter === k ? '#06b6d4' : '#a1a1aa', fontFamily: 'inherit'
          }}>{l}</button>
        ))}
      </div>

      {message && (
        <div style={{
          marginBottom: '20px', padding: '10px 14px', borderRadius: '8px',
          background: message.includes('✨') ? 'rgba(34,197,94,0.1)' : 'rgba(245,158,11,0.1)',
          border: `1px solid ${message.includes('✨') ? 'rgba(34,197,94,0.3)' : 'rgba(245,158,11,0.3)'}`,
          color: message.includes('✨') ? '#22c55e' : '#f59e0b',
          fontSize: '13px', fontWeight: 500
        }}>{message}</div>
      )}

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: '16px' }}>
        {visible.map(item => {
          const isUnlocked = !!unlocked[item.id];
          const canAfford = points >= item.price;
          return (
            <div key={item.id} className="glass-card" style={{
              padding: '18px',
              border: isUnlocked ? '1px solid rgba(34, 197, 94, 0.35)' : item.rare ? '1px solid rgba(139, 92, 246, 0.3)' : '1px solid rgba(255,255,255,0.06)',
              background: isUnlocked ? 'rgba(34, 197, 94, 0.04)' : item.rare ? 'linear-gradient(135deg, rgba(139,92,246,0.05), rgba(6,182,212,0.03))' : '#18181b'
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '10px' }}>
                <div style={{
                  width: '44px', height: '44px', borderRadius: '10px',
                  background: 'rgba(255,255,255,0.04)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: '24px'
                }}>{item.icon}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '6px' }}>
                    {item.name}
                    {item.rare && <span style={{ fontSize: '10px', padding: '2px 6px', borderRadius: '4px', background: 'rgba(139,92,246,0.18)', color: '#a78bfa', fontWeight: 600 }}>RARE</span>}
                  </div>
                  <div className="ach-desc" style={{ fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 500, marginTop: '2px' }}>
                    {item.type}
                  </div>
                </div>
              </div>
              <div className="ach-desc" style={{ fontSize: '12px', marginBottom: '12px', lineHeight: 1.4 }}>{item.desc}</div>

              {isUnlocked ? (
                (item.type === 'confetti' || item.type === 'sound') ? (
                  isEquipped(item) ? (
                    <button className="btn btn-secondary btn-small" onClick={() => unequip(item.type)} style={{ width: '100%' }}>
                      ✓ Equipped — click to unequip
                    </button>
                  ) : (
                    <button className="btn btn-success btn-small" onClick={() => equip(item)} style={{ width: '100%' }}>
                      Equip
                    </button>
                  )
                ) : item.type === 'theme' ? (
                  <button className="btn btn-success btn-small" disabled style={{ width: '100%' }}>
                    ✓ Unlocked — pick in Profile
                  </button>
                ) : (
                  <button className="btn btn-success btn-small" disabled style={{ width: '100%' }}>✓ Unlocked</button>
                )
              ) : (
                <button
                  className={`btn ${canAfford ? 'btn-primary' : 'btn-secondary'} btn-small`}
                  onClick={() => unlock(item)}
                  disabled={!canAfford}
                  style={{ width: '100%' }}
                >
                  💎 {item.price.toLocaleString()} XP
                </button>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

// ============= AI STUDY PLAN PAGE =============
// ============= JOURNAL PAGE =============
const JournalPage = ({ uid }) => {
  const [entries, setEntries] = React.useState([]);
  const [currentEntry, setCurrentEntry] = React.useState('');
  const [mood, setMood] = React.useState(3);
  const [saving, setSaving] = React.useState(false);
  const [summary, setSummary] = React.useState('');
  const [summaryLoading, setSummaryLoading] = React.useState(false);
  const [summaryError, setSummaryError] = React.useState('');
  const [editingId, setEditingId] = React.useState(null);

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('journal')
      .orderBy('createdAt', 'desc').limit(50)
      .onSnapshot(snap => setEntries(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    return unsub;
  }, [uid]);

  const saveEntry = async () => {
    if (!currentEntry.trim() || !uid) return;
    setSaving(true);
    try {
      if (editingId) {
        await db.collection('users').doc(uid).collection('journal').doc(editingId).update({
          content: currentEntry.trim(), mood, updatedAt: new Date()
        });
        setEditingId(null);
      } else {
        await db.collection('users').doc(uid).collection('journal').add({
          content: currentEntry.trim(), mood, createdAt: new Date()
        });
      }
      setCurrentEntry('');
      setMood(3);
    } finally {
      setSaving(false);
    }
  };

  const editEntry = (entry) => {
    setEditingId(entry.id);
    setCurrentEntry(entry.content);
    setMood(entry.mood || 3);
  };

  const cancelEdit = () => {
    setEditingId(null);
    setCurrentEntry('');
    setMood(3);
  };

  const deleteEntry = async (id) => {
    if (!uid || !window.confirm('Delete this entry?')) return;
    await db.collection('users').doc(uid).collection('journal').doc(id).delete();
  };

  const generateSummary = async () => {
    setSummaryLoading(true);
    setSummaryError('');
    try {
      const weekAgo = Date.now() - 7 * 86400000;
      const recentEntries = entries.filter(e => {
        const t = e.createdAt?.seconds ? e.createdAt.seconds * 1000 : 0;
        return t >= weekAgo;
      });
      if (recentEntries.length === 0) {
        setSummaryError('No journal entries in the last 7 days to summarize.');
        setSummaryLoading(false);
        return;
      }

      const compiledText = recentEntries.map(e => {
        const d = new Date(e.createdAt.seconds * 1000).toLocaleDateString();
        return `[${d}, mood ${e.mood}/5] ${e.content}`;
      }).join('\n\n');

      const system = `You are a thoughtful journal analyst. Given a week of study journal entries, produce a warm, insightful summary.
Format:
- 2-3 sentence overall summary of the week's themes and emotional arc
- 3-4 bullet points of observations or patterns (study habits, moods, focus wins, struggles)
- One concrete suggestion for the coming week
Keep it under 180 words total. Be specific, reference actual entry content. Use "you" — address the student directly.`;

      const prompt = `Here are the journal entries from this week:\n\n${compiledText}\n\nProvide your summary now.`;
      const text = await callAI(prompt, system);
      setSummary(text);
    } catch (e) {
      setSummaryError(e.message);
    } finally {
      setSummaryLoading(false);
    }
  };

  const hasApiKey = !!getAIKey();
  const moodEmojis = ['😞', '😕', '😐', '🙂', '😊'];

  return (
    <div>
      <h1 className="page-title">Journal</h1>
      <p className="page-subtitle">Reflect on your study sessions and moods</p>

      <div className="glass-card" style={{ marginBottom: '20px' }}>
        <div style={{ marginBottom: '12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div className="ach-name" style={{ fontSize: '15px', fontWeight: 600 }}>
            {editingId ? 'Edit entry' : 'New entry'}
          </div>
          <div style={{ display: 'flex', gap: '6px' }}>
            {moodEmojis.map((e, i) => (
              <button key={i}
                onClick={() => setMood(i + 1)}
                title={`Mood ${i + 1}/5`}
                style={{
                  width: '32px', height: '32px', border: 'none', borderRadius: '8px',
                  cursor: 'pointer', fontSize: '18px', fontFamily: 'inherit',
                  background: mood === i + 1 ? 'rgba(6,182,212,0.15)' : 'transparent',
                  outline: mood === i + 1 ? '1px solid rgba(6,182,212,0.4)' : 'none',
                  opacity: mood === i + 1 ? 1 : 0.5, transition: 'all 0.15s'
                }}>{e}</button>
            ))}
          </div>
        </div>
        <textarea
          value={currentEntry}
          onChange={e => setCurrentEntry(e.target.value)}
          placeholder="What did you study today? How did it go? What did you learn?"
          style={{ width: '100%', minHeight: '100px', resize: 'vertical' }}
        />
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '12px' }}>
          {editingId && (
            <button className="btn btn-secondary btn-small" onClick={cancelEdit}>Cancel</button>
          )}
          <button className="btn btn-primary btn-small" onClick={saveEntry} disabled={!currentEntry.trim() || saving}>
            {saving ? 'Saving…' : editingId ? 'Update entry' : 'Save entry'}
          </button>
        </div>
      </div>

      <div className="glass-card" style={{ marginBottom: '20px', background: 'linear-gradient(135deg, rgba(6,182,212,0.08), rgba(139,92,246,0.06))', border: '1px solid rgba(6,182,212,0.22)' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', flexWrap: 'wrap' }}>
          <div>
            <div className="ach-name" style={{ fontSize: '14px', fontWeight: 600 }}>🧠 Weekly AI Summary</div>
            <div className="ach-desc" style={{ fontSize: '12px' }}>AI analyzes your last 7 days of entries</div>
          </div>
          <button className="btn btn-primary btn-small" onClick={generateSummary} disabled={summaryLoading || !hasApiKey || entries.length === 0}>
            {summaryLoading ? 'Analyzing…' : '✨ Generate summary'}
          </button>
        </div>
        {!hasApiKey && (
          <div style={{ marginTop: '10px', fontSize: '12px', color: '#f59e0b' }}>
            Add an AI API key in Profile → AI Features to use this.
          </div>
        )}
        {summaryError && (
          <div style={{ marginTop: '12px', padding: '8px 12px', borderRadius: '8px', background: 'rgba(239,68,68,0.08)', color: '#ef4444', fontSize: '12px' }}>
            {summaryError}
          </div>
        )}
        {summary && (
          <div
            className="ai-plan-markdown"
            style={{ marginTop: '14px', padding: '14px 16px', background: 'rgba(255,255,255,0.04)', borderRadius: '10px', fontSize: '13px' }}
            dangerouslySetInnerHTML={{ __html: renderMarkdown(summary) }}
          />
        )}
      </div>

      <div>
        <h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: '#a1a1aa', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
          Recent entries ({entries.length})
        </h3>
        {entries.length === 0 ? (
          <div className="glass-card" style={{ textAlign: 'center', padding: '32px', color: '#71717a' }}>
            No entries yet. Start by writing your first reflection above.
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            {entries.map(e => (
              <div key={e.id} className="glass-card" style={{ padding: '14px 16px' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
                    <span style={{ fontSize: '18px' }}>{moodEmojis[(e.mood || 3) - 1]}</span>
                    <span style={{ fontSize: '12px', color: '#71717a' }}>
                      {e.createdAt?.seconds ? new Date(e.createdAt.seconds * 1000).toLocaleString() : 'Just now'}
                    </span>
                  </div>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <button onClick={() => editEntry(e)} style={{ background: 'none', border: 'none', color: '#71717a', cursor: 'pointer', fontSize: '12px', fontFamily: 'inherit' }}>Edit</button>
                    <button onClick={() => deleteEntry(e.id)} style={{ background: 'none', border: 'none', color: '#71717a', cursor: 'pointer', fontSize: '12px', fontFamily: 'inherit' }}>Delete</button>
                  </div>
                </div>
                <div className="ach-name" style={{ fontSize: '13px', whiteSpace: 'pre-wrap', lineHeight: 1.5 }}>{e.content}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

const AIPlanPage = ({ uid, userData }) => {
  const [goal, setGoal] = React.useState('');
  const [days, setDays] = React.useState(7);
  const [hoursPerDay, setHoursPerDay] = React.useState(2);
  const [plan, setPlan] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState('');
  const [savedPlans, setSavedPlans] = React.useState([]);
  const [viewingSaved, setViewingSaved] = React.useState(null);

  const examples = [
    'Prepare for Math exam in 10 days',
    'Complete chapter 5 of Physics',
    'Learn basics of Python in 2 weeks',
    'Revise entire Science syllabus',
  ];

  React.useEffect(() => {
    if (!uid) return;
    const unsub = db.collection('users').doc(uid).collection('aiPlans')
      .orderBy('createdAt', 'desc').limit(10)
      .onSnapshot(snap => setSavedPlans(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    return unsub;
  }, [uid]);

  const parsePlan = (text) => {
    // Try to find day-by-day structure
    const dayPattern = /(?:^|\n)(?:#+\s*)?(?:\*\*)?(?:Day\s+\d+|Week\s+\d+)(?:\*\*)?[:\s]*/gi;
    const matches = [...text.matchAll(dayPattern)];
    if (matches.length < 2) return { raw: text, days: null };

    const blocks = [];
    for (let i = 0; i < matches.length; i++) {
      const start = matches[i].index;
      const end = i + 1 < matches.length ? matches[i + 1].index : text.length;
      const block = text.slice(start, end).trim();
      const firstLineEnd = block.indexOf('\n');
      const title = (firstLineEnd === -1 ? block : block.slice(0, firstLineEnd)).replace(/[#*:]/g, '').trim();
      const body = firstLineEnd === -1 ? '' : block.slice(firstLineEnd + 1).trim();
      // Extract bullet-style tasks
      const tasks = body.split(/\n/).map(l => l.replace(/^[\s\-*•]+/, '').replace(/^\*\*|\*\*$/g, '').trim()).filter(Boolean);
      blocks.push({ title, tasks });
    }
    return { raw: text, days: blocks };
  };

  const generate = async () => {
    if (!goal.trim()) { setError('Describe your study goal first'); return; }
    setError('');
    setLoading(true);
    setPlan(null);
    setViewingSaved(null);

    const system = `You are an expert study planner. When given a user goal, create a detailed day-by-day study schedule.
Format rules (follow STRICTLY):
- Start with one short opening line (no more than 1 sentence).
- Then list each day as "Day 1: <topic>", "Day 2: <topic>", etc.
- Under each day, give 3-5 concrete bullet-point tasks starting with "-".
- Each task should reference specific topics, exercises, or review points.
- Include Pomodoro blocks (25-min focused study) where appropriate.
- Include review/practice sessions.
- Keep it realistic for the given hours per day.
- End with 1-2 motivational closing sentences.`;

    const prompt = `Create a ${days}-day study plan.

Goal: ${goal.trim()}
Hours available per day: ${hoursPerDay}

Produce the plan now.`;

    try {
      const text = await callAI(prompt, system);
      const parsed = parsePlan(text);
      setPlan(parsed);
      // Save to Firestore + increment AI usage counter
      if (uid) {
        await db.collection('users').doc(uid).collection('aiPlans').add({
          goal: goal.trim(),
          days, hoursPerDay,
          text, createdAt: new Date()
        });
        await db.collection('users').doc(uid).update({
          aiPlansGenerated: firebase.firestore.FieldValue.increment(1)
        });
      }
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  const loadSaved = (saved) => {
    setViewingSaved(saved.id);
    setPlan(parsePlan(saved.text));
    setGoal(saved.goal);
    setDays(saved.days);
    setHoursPerDay(saved.hoursPerDay);
  };

  const deleteSaved = async (id) => {
    if (!uid) return;
    await db.collection('users').doc(uid).collection('aiPlans').doc(id).delete();
    if (viewingSaved === id) { setPlan(null); setViewingSaved(null); }
  };

  const hasApiKey = !!getAIKey();

  return (
    <div>
      <h1 className="page-title">AI Study Plan</h1>
      <p className="page-subtitle">Generate a personalized study schedule in seconds</p>

      {!hasApiKey && (
        <div className="glass-card" style={{ marginBottom: '20px', borderColor: 'rgba(245, 158, 11, 0.3)', background: 'rgba(245, 158, 11, 0.05)' }}>
          <div style={{ fontSize: '14px', color: '#f59e0b', fontWeight: 500, marginBottom: '4px' }}>⚠️ AI API key required</div>
          <div style={{ fontSize: '13px', color: '#a1a1aa' }}>
            Add your free AI API key in <strong>Profile → AI Features</strong> to use AI Study Plan.
          </div>
        </div>
      )}

      <div className="ai-plan-hero">
        <h2>🎯 Describe your study goal</h2>
        <p>Tell the AI what you want to achieve. It'll create a realistic day-by-day plan tailored to your schedule.</p>

        <div className="ai-plan-form">
          <textarea
            value={goal}
            onChange={e => setGoal(e.target.value)}
            placeholder="e.g., Prepare for Math exam in 10 days, focus on algebra and geometry..."
          />
          <div>
            <label style={{ fontSize: '12px', color: '#a1a1aa', display: 'block', marginBottom: '6px', textTransform: 'uppercase', fontWeight: 500, letterSpacing: '0.04em' }}>Days</label>
            <input type="number" min="1" max="60" value={days} onChange={e => setDays(Math.max(1, Math.min(60, parseInt(e.target.value) || 1)))} style={{ width: '100%' }} />
          </div>
          <div>
            <label style={{ fontSize: '12px', color: '#a1a1aa', display: 'block', marginBottom: '6px', textTransform: 'uppercase', fontWeight: 500, letterSpacing: '0.04em' }}>Hours / day</label>
            <input type="number" min="0.5" max="12" step="0.5" value={hoursPerDay} onChange={e => setHoursPerDay(Math.max(0.5, Math.min(12, parseFloat(e.target.value) || 1)))} style={{ width: '100%' }} />
          </div>
        </div>

        <div className="ai-example-chips">
          <span style={{ fontSize: '12px', color: '#71717a', alignSelf: 'center' }}>Try:</span>
          {examples.map(ex => (
            <button key={ex} className="ai-example-chip" onClick={() => setGoal(ex)}>{ex}</button>
          ))}
        </div>

        <div className="ai-plan-generate-row">
          <button className="btn btn-primary" onClick={generate} disabled={loading || !hasApiKey}>
            {loading ? '✨ Generating…' : '✨ Generate plan'}
          </button>
        </div>

        {error && (
          <div style={{ marginTop: '12px', padding: '10px 14px', borderRadius: '8px', background: 'rgba(239,68,68,0.08)', color: '#ef4444', fontSize: '13px' }}>
            {error}
          </div>
        )}
      </div>

      {loading && (
        <div className="ai-plan-output">
          <h3>Thinking…</h3>
          <div className="ai-loading-shimmer"></div>
          <div className="ai-loading-shimmer" style={{ width: '80%' }}></div>
          <div className="ai-loading-shimmer" style={{ width: '90%' }}></div>
        </div>
      )}

      {plan && !loading && (
        <div className="ai-plan-output">
          <h3>📋 Your Plan {viewingSaved && <span style={{ fontSize: '12px', color: '#71717a', fontWeight: 400, marginLeft: '6px' }}>(saved)</span>}</h3>
          {plan.days ? (
            <div>
              {plan.days.map((d, i) => (
                <div key={i} className="ai-plan-day">
                  <div className="day-title">{d.title}</div>
                  <div className="day-tasks">
                    {d.tasks.length > 0 ? (
                      d.tasks.map((t, j) => <div key={j} className="day-task">{t}</div>)
                    ) : (
                      <div className="day-task" style={{ color: '#71717a' }}>(no tasks listed)</div>
                    )}
                  </div>
                </div>
              ))}
            </div>
          ) : (
            <div
              className="ai-plan-markdown"
              dangerouslySetInnerHTML={{
                __html: renderMarkdown(plan.raw)
              }}
            />
          )}
        </div>
      )}

      {!plan && !loading && (
        <div className="ai-plan-output">
          <div className="ai-empty-state">
            <div className="ai-empty-state-icon">✨</div>
            <div style={{ fontSize: '15px', color: '#a1a1aa', marginBottom: '4px' }}>Your plan will appear here</div>
            <div style={{ fontSize: '13px', color: '#71717a' }}>Describe a goal above and click Generate.</div>
          </div>
        </div>
      )}

      {savedPlans.length > 0 && (
        <div style={{ marginTop: '24px' }}>
          <h3 style={{ fontSize: '14px', fontWeight: 600, color: '#a1a1aa', marginBottom: '12px', textTransform: 'uppercase', letterSpacing: '0.04em' }}>Recent plans</h3>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            {savedPlans.map(p => (
              <div
                key={p.id}
                className="glass-card"
                style={{ padding: '12px 16px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer' }}
                onClick={() => loadSaved(p)}
              >
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: '14px', color: 'var(--text-primary)', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {p.goal}
                  </div>
                  <div style={{ fontSize: '12px', color: '#71717a', marginTop: '2px' }}>
                    {p.days} days · {p.hoursPerDay}h/day · {p.createdAt?.seconds ? new Date(p.createdAt.seconds * 1000).toLocaleDateString() : 'now'}
                  </div>
                </div>
                <button
                  onClick={(e) => { e.stopPropagation(); deleteSaved(p.id); }}
                  style={{ background: 'none', border: 'none', color: '#71717a', cursor: 'pointer', fontSize: '18px', padding: '4px 8px' }}
                  title="Delete"
                >
                  ×
                </button>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

// ============= NOTES PAGE =============
const NotesPage = ({ uid, onNavigate }) => {
  const [notes, setNotes] = React.useState([]);
  const [selectedNote, setSelectedNote] = React.useState(null);
  const [title, setTitle] = React.useState('');
  const [content, setContent] = React.useState('');
  const [linkType, setLinkType] = React.useState('none');   // 'none' | 'homework' | 'todo'
  const [linkId, setLinkId] = React.useState('');
  const [searchQuery, setSearchQuery] = React.useState('');
  const [showPreview, setShowPreview] = React.useState(false);

  // For linking picker
  const [homework, setHomework] = React.useState([]);
  const [todos, setTodos] = React.useState([]);

  React.useEffect(() => {
    if (!uid) return;
    const unsubNotes = db.collection('users').doc(uid).collection('notes')
      .orderBy('updatedAt', 'desc')
      .onSnapshot(snap => setNotes(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    const unsubHw = db.collection('users').doc(uid).collection('homework')
      .onSnapshot(snap => setHomework(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    const unsubTodo = db.collection('users').doc(uid).collection('todos')
      .onSnapshot(snap => setTodos(snap.docs.map(d => ({ id: d.id, ...d.data() }))));
    return () => { unsubNotes(); unsubHw(); unsubTodo(); };
  }, [uid]);

  const createNote = async () => {
    if (!uid) return;
    const doc = await db.collection('users').doc(uid).collection('notes').add({
      title: 'Untitled Note',
      content: '',
      linkType: 'none',
      linkId: '',
      createdAt: new Date(),
      updatedAt: new Date()
    });
    setSelectedNote(doc.id);
    setTitle('Untitled Note');
    setContent('');
    setLinkType('none');
    setLinkId('');
  };

  // Debounced save — prevents out-of-order writes when the user types fast,
  // and coalesces rapid edits into one Firestore update.
  const saveTimerRef = React.useRef(null);
  const pendingSaveRef = React.useRef(null);
  const saveNote = () => {
    if (!uid || !selectedNote) return;
    pendingSaveRef.current = { id: selectedNote, title, content, linkType, linkId };
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    saveTimerRef.current = setTimeout(async () => {
      const snap = pendingSaveRef.current;
      if (!snap) return;
      try {
        await db.collection('users').doc(uid).collection('notes').doc(snap.id).update({
          title: snap.title, content: snap.content, linkType: snap.linkType, linkId: snap.linkId,
          updatedAt: new Date()
        });
      } catch (err) { console.warn('[FocusForge] Note save failed:', err); }
    }, 500);
  };
  // Flush on unmount/navigation so a pending save isn't lost
  React.useEffect(() => () => {
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    const snap = pendingSaveRef.current;
    if (snap && uid) {
      db.collection('users').doc(uid).collection('notes').doc(snap.id).update({
        title: snap.title, content: snap.content, linkType: snap.linkType, linkId: snap.linkId,
        updatedAt: new Date()
      }).catch(() => {});
    }
  }, [uid]);

  const deleteNote = async (id) => {
    if (!uid || !window.confirm('Delete this note?')) return;
    await db.collection('users').doc(uid).collection('notes').doc(id).delete();
    if (selectedNote === id) { setSelectedNote(null); setTitle(''); setContent(''); setLinkType('none'); setLinkId(''); }
  };

  const selectNote = (note) => {
    setSelectedNote(note.id);
    setTitle(note.title);
    setContent(note.content || '');
    setLinkType(note.linkType || 'none');
    setLinkId(note.linkId || '');
    setShowPreview(false);
  };

  // Look up linked item title for display
  const getLinkedItem = (note) => {
    if (!note.linkType || note.linkType === 'none' || !note.linkId) return null;
    if (note.linkType === 'homework') return homework.find(h => h.id === note.linkId);
    if (note.linkType === 'todo') return todos.find(t => t.id === note.linkId);
    return null;
  };

  const filteredNotes = searchQuery ? notes.filter(n => n.title.toLowerCase().includes(searchQuery.toLowerCase())) : notes;

  return (
    <div>
      <h1 className="page-title">Notes</h1>
      <p className="page-subtitle">Capture your thoughts with markdown</p>

      <div className="notes-layout">
        <div className="notes-list">
          <div style={{ display: 'flex', gap: '8px', marginBottom: '12px' }}>
            <input type="text" placeholder="Search notes..." value={searchQuery} onChange={e => setSearchQuery(e.target.value)} style={{ flex: 1, padding: '8px 10px', fontSize: '13px' }} />
            <button className="btn btn-primary btn-small" onClick={createNote}>+</button>
          </div>
          {filteredNotes.map(note => {
            const linkedItem = getLinkedItem(note);
            return (
              <div key={note.id} className={`note-item ${selectedNote === note.id ? 'active' : ''}`} onClick={() => selectNote(note)}>
                <h4>{note.title || 'Untitled'}</h4>
                <p>{note.updatedAt?.seconds ? new Date(note.updatedAt.seconds * 1000).toLocaleDateString() : 'Just now'}</p>
                {linkedItem && (
                  <div style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '10px', padding: '2px 6px', borderRadius: '4px', background: 'rgba(6,182,212,0.12)', color: '#06b6d4', marginTop: '4px' }}>
                    🔗 {note.linkType === 'homework' ? '📚' : '✓'} {linkedItem.title}
                  </div>
                )}
                {note.linkType && note.linkType !== 'none' && !linkedItem && (
                  <div style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '10px', padding: '2px 6px', borderRadius: '4px', background: 'rgba(239,68,68,0.08)', color: '#a1a1aa', marginTop: '4px', fontStyle: 'italic' }}
                    title="The linked item was deleted">
                    🔗 (linked item deleted)
                  </div>
                )}
                <button onClick={(e) => { e.stopPropagation(); deleteNote(note.id); }} style={{ float: 'right', background: 'none', border: 'none', color: 'var(--text-subtle)', cursor: 'pointer', fontSize: '12px', marginTop: '-28px' }}>Delete</button>
              </div>
            );
          })}
          {filteredNotes.length === 0 && <p style={{ textAlign: 'center', color: 'var(--text-subtle)', padding: '20px', fontSize: '13px' }}>No notes yet</p>}
        </div>

        <div className="note-editor">
          {selectedNote ? (
            <React.Fragment>
              <input type="text" value={title} onChange={e => setTitle(e.target.value)} onBlur={saveNote} placeholder="Note title" style={{ marginBottom: '12px', fontSize: '18px', fontWeight: '600', background: 'transparent', border: 'none', borderBottom: '1px solid rgba(255,255,255,0.1)', borderRadius: 0, padding: '8px 0' }} />

              {/* Link picker — attach note to a homework item or todo */}
              <div style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap', alignItems: 'center' }}>
                <span style={{ fontSize: '11px', color: '#71717a', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 500 }}>🔗 Link to:</span>
                <select
                  value={linkType}
                  onChange={e => { setLinkType(e.target.value); setLinkId(''); }}
                  onBlur={saveNote}
                  style={{ padding: '4px 10px', fontSize: '12px', maxWidth: '140px' }}
                >
                  <option value="none">Nothing</option>
                  <option value="homework">Homework</option>
                  <option value="todo">To-do</option>
                </select>
                {linkType === 'homework' && (
                  <select value={linkId} onChange={e => setLinkId(e.target.value)} onBlur={saveNote} style={{ padding: '4px 10px', fontSize: '12px', flex: 1, minWidth: '160px' }}>
                    <option value="">— pick a homework —</option>
                    {homework.map(h => (
                      <option key={h.id} value={h.id}>{h.title} {h.completed ? '(done)' : ''}</option>
                    ))}
                  </select>
                )}
                {linkType === 'todo' && (
                  <select value={linkId} onChange={e => setLinkId(e.target.value)} onBlur={saveNote} style={{ padding: '4px 10px', fontSize: '12px', flex: 1, minWidth: '160px' }}>
                    <option value="">— pick a todo —</option>
                    {todos.map(t => (
                      <option key={t.id} value={t.id}>{t.title} {t.completed ? '(done)' : ''}</option>
                    ))}
                  </select>
                )}
                {linkType !== 'none' && linkId && onNavigate && (
                  <button
                    onClick={() => onNavigate(linkType === 'homework' ? 'homework' : 'todo')}
                    style={{ background: 'none', border: 'none', color: '#06b6d4', cursor: 'pointer', fontSize: '12px', fontFamily: 'inherit', textDecoration: 'underline' }}
                  >Open →</button>
                )}
              </div>

              <div style={{ display: 'flex', gap: '8px', marginBottom: '12px' }}>
                <button className={`btn btn-small ${!showPreview ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setShowPreview(false)}>Edit</button>
                <button className={`btn btn-small ${showPreview ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setShowPreview(true)}>Preview</button>
                <button className="btn btn-small btn-secondary" onClick={saveNote} style={{ marginLeft: 'auto' }}>Save</button>
              </div>
              {showPreview ? (
                <div className="markdown-preview" dangerouslySetInnerHTML={{ __html: renderMarkdown(content) }} />
              ) : (
                <textarea value={content} onChange={e => setContent(e.target.value)} onBlur={saveNote} placeholder="Write in markdown..." />
              )}
            </React.Fragment>
          ) : (
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: 'var(--text-subtle)' }}>
              <div style={{ textAlign: 'center' }}>
                <div style={{ fontSize: '48px', marginBottom: '12px' }}>📝</div>
                <p>Select a note or create a new one</p>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

// Level Up Celebration
const LevelUpCelebration = ({ newLevel, onDismiss }) => {
  React.useEffect(() => {
    const timer = setTimeout(onDismiss, 3000);
    return () => clearTimeout(timer);
  }, [onDismiss]);

  const createConfetti = () => {
    const confetti = [];
    const items = ['🌟', '✨', '🎉', '🎊', '🏆'];
    for (let i = 0; i < 20; i++) {
      confetti.push(
        <div
          key={i}
          className="confetti"
          style={{
            left: Math.random() * 100 + '%',
            top: '50%',
            animation: `confettiFall ${2 + Math.random() * 1}s ease-out forwards`,
            animationDelay: Math.random() * 0.3 + 's'
          }}
        >
          {items[Math.floor(Math.random() * items.length)]}
        </div>
      );
    }
    return confetti;
  };

  return (
    <div className="celebration-overlay" onClick={onDismiss}>
      {createConfetti()}
      <div className="celebration-modal">
        <div className="celebration-icon">🏆</div>
        <div className="celebration-text">Level Up!</div>
        <div className="celebration-level">Level {newLevel}</div>
      </div>
    </div>
  );
};

// Main App Component
const App = () => {
  const [currentUser, setCurrentUser] = React.useState(null);
  const [userData, setUserData] = React.useState(null);
  const [currentPage, setCurrentPage] = React.useState('dashboard');
  const [sidebarOpen, setSidebarOpen] = React.useState(false);
  // Desktop-only collapse — separate from `sidebarOpen` which is the mobile drawer.
  // Persisted so the user's preference survives reload.
  const [sidebarCollapsed, setSidebarCollapsed] = React.useState(
    () => localStorage.getItem('ff-sidebar-collapsed') === '1'
  );
  React.useEffect(() => {
    localStorage.setItem('ff-sidebar-collapsed', sidebarCollapsed ? '1' : '0');
  }, [sidebarCollapsed]);
  const [showLevelUp, setShowLevelUp] = React.useState(false);
  const [newLevel, setNewLevel] = React.useState(0);
  const [prevLevel, setPrevLevel] = React.useState(0);
  const [theme, setTheme] = React.useState(() => localStorage.getItem('focusforge-theme') || 'dark');
  const [accentTheme, setAccentTheme] = React.useState(() => localStorage.getItem('focusforge-accent') || 'default');
  // Persist these so a refresh mid-flow can't bypass the password-set gate.
  const [passwordPending, setPasswordPending] = React.useState(() => localStorage.getItem('ff-password-pending') === '1');
  const [pendingIntent, setPendingIntent] = React.useState(() => localStorage.getItem('ff-password-pending-intent') || 'signup');
  const [linkProcessing, setLinkProcessing] = React.useState(false);
  const [linkError, setLinkError] = React.useState('');

  // Detect Firebase magic-link return: sign in, then prompt for password.
  // Intent ('signup' or 'reset') comes from the URL ?intent=… encoded by sendEmailLink.
  React.useEffect(() => {
    if (auth.isSignInWithEmailLink(window.location.href)) {
      setLinkProcessing(true);
      setLinkError(''); // clear any stale error from a previous attempt
      const params = new URLSearchParams(window.location.search);
      const intent = params.get('intent') || localStorage.getItem(PENDING_PW_INTENT) || 'signup';
      let email = localStorage.getItem(EMAIL_FOR_SIGNIN_KEY);
      if (!email) email = window.prompt('Please confirm the email address you used:') || '';
      if (!email) { setLinkProcessing(false); return; }
      auth.signInWithEmailLink(email, window.location.href)
        .then(async (result) => {
          localStorage.removeItem(EMAIL_FOR_SIGNIN_KEY);
          localStorage.removeItem(PENDING_PW_INTENT);
          // Pick up the name the user typed on the signup form (signup flow only).
          // Consumed now so a later magic-link cycle can't apply a stale value.
          const savedName = intent === 'signup'
            ? localStorage.getItem(SIGNUP_DISPLAY_NAME_KEY)
            : null;
          if (savedName) localStorage.removeItem(SIGNUP_DISPLAY_NAME_KEY);
          if (result?.user) await createUserDoc(result.user, savedName);
          setPendingIntent(intent);
          setPasswordPending(true);
          // Persist so refresh during gate keeps the requirement
          localStorage.setItem('ff-password-pending', '1');
          localStorage.setItem('ff-password-pending-intent', intent);
          window.history.replaceState({}, document.title, window.location.pathname);
        })
        .catch((err) => {
          setLinkError(err.message || 'Link sign-in failed.');
        })
        .finally(() => setLinkProcessing(false));
    }
  }, []);

  React.useEffect(() => {
    // Valid accent IDs. Keep in sync with ThemesSection.presets.
    const valid = ['default', 'nature', 'sunset', 'royal', 'rose', 'amber', 'crimson',
                   'aurora', 'ember', 'forest', 'cosmic'];
    // Defensively coerce an unknown stored value back to default so we don't
    // leave a dangling `theme-garbage` class on the root element.
    const safeAccent = valid.includes(accentTheme) ? accentTheme : 'default';
    if (safeAccent !== accentTheme) { setAccentTheme('default'); return; }
    valid.filter(v => v !== 'default').forEach(t => document.documentElement.classList.remove(`theme-${t}`));
    if (safeAccent !== 'default') document.documentElement.classList.add(`theme-${safeAccent}`);
    localStorage.setItem('focusforge-accent', safeAccent);
  }, [accentTheme]);

  // ============ POMODORO TIMER (lifted to App so it survives page navigation) ============
  const [pomoSeconds, setPomoSeconds] = React.useState(1500);
  const [pomoRunning, setPomoRunning] = React.useState(false);
  const [pomoBreak, setPomoBreak] = React.useState(false);
  const [pomoSessionCount, setPomoSessionCount] = React.useState(0);
  const [pomoTodayCount, setPomoTodayCount] = React.useState(0);
  const [pomoTodayMinutes, setPomoTodayMinutes] = React.useState(0);
  // Ref so completion handler reads the freshest value without re-registering
  const pomoTodayMinutesRef = React.useRef(0);
  React.useEffect(() => { pomoTodayMinutesRef.current = pomoTodayMinutes; }, [pomoTodayMinutes]);
  // Cancellable confetti timeout — prevents stuck-true state or unmount warnings
  const pomoConfettiTimeoutRef = React.useRef(null);
  React.useEffect(() => () => {
    if (pomoConfettiTimeoutRef.current) clearTimeout(pomoConfettiTimeoutRef.current);
  }, []);
  const [pomoSubject, setPomoSubject] = React.useState('');
  const [pomoConfetti, setPomoConfetti] = React.useState(false);
  const [activeConfetti, setActiveConfetti] = React.useState(() => localStorage.getItem('ff-active-confetti') || 'default');
  const [activeSound, setActiveSound] = React.useState(() => localStorage.getItem('ff-active-sound') || 'default');

  // Publish active cosmetic IDs to window so ConfettiBurst + playAlarm can read them
  React.useEffect(() => {
    window.FOCUSFORGE_ACTIVE_CONFETTI = activeConfetti;
    localStorage.setItem('ff-active-confetti', activeConfetti);
  }, [activeConfetti]);
  React.useEffect(() => {
    window.FOCUSFORGE_ACTIVE_SOUND = activeSound;
    localStorage.setItem('ff-active-sound', activeSound);
  }, [activeSound]);

  // Subscribe to the user's unlocked rewards — ensure equipped items are still owned
  React.useEffect(() => {
    if (!currentUser?.uid) return;
    const unsub = db.collection('users').doc(currentUser.uid).collection('unlockedRewards')
      .onSnapshot(snap => {
        const owned = new Set(snap.docs.map(d => d.id));
        // If equipped item isn't owned (e.g., after reset), fall back to default
        if (activeConfetti !== 'default' && !owned.has(activeConfetti)) setActiveConfetti('default');
        if (activeSound !== 'default' && !owned.has(activeSound)) setActiveSound('default');
      });
    return unsub;
  }, [currentUser?.uid]);

  // The interval lives in App and keeps ticking regardless of which page is shown
  React.useEffect(() => {
    if (!pomoRunning) return;
    const iv = setInterval(() => {
      setPomoSeconds(s => Math.max(0, s - 1));
    }, 1000);
    return () => clearInterval(iv);
  }, [pomoRunning]);

  // Handle completion when seconds hits 0
  const pomoCompletedRef = React.useRef(false);
  React.useEffect(() => {
    if (pomoSeconds !== 0 || !pomoRunning) {
      pomoCompletedRef.current = false;
      return;
    }
    if (pomoCompletedRef.current) return;
    pomoCompletedRef.current = true;

    playAlarm();
    if (pomoBreak) {
      // Break ended → back to focus
      setPomoBreak(false);
      setPomoSeconds(1500);
      setPomoRunning(false);
    } else {
      // Focus session completed
      setPomoBreak(true);
      setPomoSeconds(300);
      setPomoSessionCount(c => c + 1);
      setPomoTodayCount(c => c + 1);
      if (currentUser?.uid) {
        if (!window.FOCUSFORGE_INCOGNITO) {
          // Wrap writes so offline / permission errors surface instead of silently losing data
          (async () => {
            try {
              await db.collection('users').doc(currentUser.uid).update({
                totalPomodoros: firebase.firestore.FieldValue.increment(1),
                totalStudyMinutes: firebase.firestore.FieldValue.increment(25)
              });
              await db.collection('users').doc(currentUser.uid).collection('sessions').add({
                type: 'pomodoro',
                minutes: 25,
                completedAt: new Date(),
                subject: pomoSubject || ''
              });
              updateStreak(currentUser.uid, userData);
              const dailyGoalMinutes = userData?.dailyGoalMinutes || 60;
              // Use the LIVE today-minutes ref, not the non-existent userData.todayMinutes
              const todayMinutesAfter = (pomoTodayMinutesRef.current || 0) + 25;
              awardXP(currentUser.uid, 10, {
                dailyGoalBeaten: todayMinutesAfter >= dailyGoalMinutes
              });
            } catch (err) {
              console.warn('[FocusForge] Session write failed — will retry when online.', err);
              // Firestore has offline persistence, so writes queue automatically.
              // Surface a toast if truly unrecoverable (permission/quota).
              if (err.code === 'permission-denied' || err.code === 'resource-exhausted') {
                alert('Could not save your session: ' + err.message);
              }
            }
          })();
          // 🎉 Confetti on focus session completion — timeout tracked so we can cancel on unmount
          setPomoConfetti(true);
          if (pomoConfettiTimeoutRef.current) clearTimeout(pomoConfettiTimeoutRef.current);
          // 4000ms > longest particle (2s duration + 0.5s delay + safety margin) so no particle is cut off mid-fall
          pomoConfettiTimeoutRef.current = setTimeout(() => setPomoConfetti(false), 4000);
        }
        sendNotification('Pomodoro Complete!', 'Great work! Take a break.');
      }
      setPomoRunning(false);
    }
  }, [pomoSeconds, pomoRunning, pomoBreak, currentUser, pomoSubject, userData]);

  // Load today's pomodoro count from Firestore
  React.useEffect(() => {
    if (!currentUser?.uid) return;
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const unsubscribe = db.collection('users').doc(currentUser.uid).collection('sessions')
      .where('completedAt', '>=', today)
      .onSnapshot((snapshot) => {
        const pomoDocs = snapshot.docs.filter(d => d.data().type === 'pomodoro');
        setPomoTodayCount(pomoDocs.length);
        const mins = snapshot.docs.reduce((a, d) => a + (d.data().minutes || 0), 0);
        setPomoTodayMinutes(mins);
      });
    return unsubscribe;
  }, [currentUser?.uid]);

  const pomoControls = React.useMemo(() => ({
    seconds: pomoSeconds,
    isRunning: pomoRunning,
    isBreak: pomoBreak,
    sessionCount: pomoSessionCount,
    todayCount: pomoTodayCount,
    subject: pomoSubject,
    setSubject: setPomoSubject,
    toggle: () => setPomoRunning(r => !r),
    reset: () => {
      setPomoRunning(false);
      setPomoSeconds(pomoBreak ? 300 : 1500);
    }
  }), [pomoSeconds, pomoRunning, pomoBreak, pomoSessionCount, pomoTodayCount, pomoSubject]);

  // Watch for achievement unlocks based on userData changes
  useAchievementWatcher(currentUser?.uid, userData);

  // Run scheduled-notification checks on load + on window focus
  React.useEffect(() => {
    if (!currentUser?.uid || !userData) return;
    // Run once on mount
    runScheduledChecks(currentUser.uid, userData);
    // And whenever the tab regains focus
    const handler = () => runScheduledChecks(currentUser.uid, userData);
    window.addEventListener('focus', handler);
    return () => window.removeEventListener('focus', handler);
  }, [currentUser?.uid, userData]);

  // Keyboard shortcuts
  const [showShortcuts, setShowShortcuts] = React.useState(false);
  React.useEffect(() => {
    const onKey = (e) => {
      // Ignore if user is typing in an input/textarea/select, or inside any
      // contentEditable region — walk up the parent chain since the target
      // may be a text node inside a deeply nested editor.
      const tag = e.target?.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
      let node = e.target;
      while (node) {
        if (node.isContentEditable === true || node.getAttribute?.('contenteditable') === 'true') return;
        node = node.parentElement;
      }
      if (e.metaKey || e.ctrlKey || e.altKey) return; // avoid clashing with browser shortcuts

      // Single-key shortcuts
      const key = e.key.toLowerCase();
      if (key === '?') { e.preventDefault(); setShowShortcuts(s => !s); return; }
      if (key === 'd') { e.preventDefault(); setCurrentPage('dashboard'); return; }
      if (key === 't') { e.preventDefault(); setCurrentPage('timer'); return; }
      if (key === 'h') { e.preventDefault(); setCurrentPage('homework'); return; }
      if (key === 'c') { e.preventDefault(); setCurrentPage('calendar'); return; }
      if (key === 'a') { e.preventDefault(); setCurrentPage('analytics'); return; }
      if (key === 'g') { e.preventDefault(); setCurrentPage('goals'); return; }
      if (key === 'n') { e.preventDefault(); setCurrentPage('notes'); return; }
      if (key === 'p') { e.preventDefault(); setCurrentPage('aiplan'); return; }
      if (key === 'b') { e.preventDefault(); setCurrentPage('achievements'); return; }
      if (key === 'j') { e.preventDefault(); setCurrentPage('journal'); return; }
      if (key === 's') { e.preventDefault(); setCurrentPage('shop'); return; }
      if (key === ' ') {
        // Space toggles timer from anywhere — except when focus is on a button,
        // checkbox, link, or similar element where Space is the native activation.
        const focusTag = document.activeElement?.tagName;
        const activeRole = document.activeElement?.getAttribute('role');
        if (focusTag === 'BUTTON' || focusTag === 'A' || focusTag === 'LABEL'
            || activeRole === 'button' || activeRole === 'checkbox' || activeRole === 'switch') return;
        e.preventDefault();
        setPomoRunning(r => !r);
        return;
      }
      if (key === 'l') { e.preventDefault(); toggleTheme(); return; }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  React.useEffect(() => {
    document.documentElement.classList.toggle('light-mode', theme === 'light');
    document.body.classList.toggle('light-mode', theme === 'light');
    localStorage.setItem('focusforge-theme', theme);
  }, [theme]);

  const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark');

  // Stable callback for child components
  const handleRefresh = React.useCallback(() => {
    // Firestore onSnapshot handles real-time updates automatically
  }, []);

  // Handle Google redirect result on page load
  React.useEffect(() => {
    auth.getRedirectResult().then(async (result) => {
      if (result && result.user) {
        const credential = firebase.auth.GoogleAuthProvider.credentialFromResult(result);
        if (credential && credential.accessToken) {
          localStorage.setItem('googleAccessToken', credential.accessToken);
        }
        await createUserDoc(result.user);
        if (localStorage.getItem('pendingGoogleLink')) {
          localStorage.removeItem('pendingGoogleLink');
        }
      }
    }).catch((error) => {
      console.error('Redirect result error:', error);
      if (error.code === 'auth/credential-already-in-use') {
        const provider = new firebase.auth.GoogleAuthProvider();
        auth.signInWithPopup(provider);
      }
    });
  }, []);

  React.useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(async (user) => {
      if (user) {
        setCurrentUser(user);
        await createUserDoc(user);
      } else {
        setCurrentUser(null);
        setUserData(null);
      }
    });
    return unsubscribe;
  }, []);

  // Track whether we've seen the first snapshot for this session — prevents fake
  // level-up celebrations on page load AND still fires on the actual first level-up.
  const levelUpFirstSnapshotRef = React.useRef(true);
  React.useEffect(() => { levelUpFirstSnapshotRef.current = true; }, [currentUser?.uid]);

  React.useEffect(() => {
    if (currentUser?.uid) {
      const unsubscribe = db.collection('users').doc(currentUser.uid)
        .onSnapshot((doc) => {
          if (doc.exists) {
            const data = doc.data();
            setUserData(data);
            const newLvl = getLevel(data.points);
            if (levelUpFirstSnapshotRef.current) {
              // Initial load — just seed prevLevel, don't celebrate
              levelUpFirstSnapshotRef.current = false;
            } else if (newLvl > prevLevel) {
              setNewLevel(newLvl);
              setShowLevelUp(true);
            }
            setPrevLevel(newLvl);
          }
        });
      return unsubscribe;
    }
  }, [currentUser?.uid, prevLevel]);

  const handleLogout = async () => {
    await auth.signOut();
    setCurrentUser(null);
    setUserData(null);
    setCurrentPage('dashboard');
    // Clear any pending password-gate flags — signing out resets the auth flow
    localStorage.removeItem('ff-password-pending');
    localStorage.removeItem('ff-password-pending-intent');
    setPasswordPending(false);
  };

  // Determine sidebar visibility based on screen width — reactive to resize so the
  // close-button behaviour switches between mobile-drawer and desktop-collapse correctly.
  const [isMobile, setIsMobile] = React.useState(() =>
    typeof window !== 'undefined' && window.innerWidth <= 768
  );
  React.useEffect(() => {
    const onResize = () => setIsMobile(window.innerWidth <= 768);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  // Show loading state while we resolve a magic-link sign-in
  if (linkProcessing) {
    return (
      <div className="login-page">
        <div className="login-card" style={{ textAlign: 'center' }}>
          <div className="login-logo">
            <div style={{ color: '#06b6d4' }}><ZapIcon /></div>
            <div className="sidebar-logo-text">FocusForge</div>
          </div>
          <div style={{ margin: '20px 0', fontSize: '14px', color: 'var(--text-secondary)' }}>
            Verifying your email…
          </div>
          {linkError && <div className="login-error">{linkError}</div>}
        </div>
      </div>
    );
  }

  if (!currentUser) {
    return <LoginPage />;
  }

  // After magic-link sign-in, user must create or reset their password before using the app.
  // Persistent flag means a mid-flow refresh still enforces the gate.
  if (passwordPending) {
    return <PasswordCreationGate intent={pendingIntent} onDone={() => {
      localStorage.removeItem('ff-password-pending');
      localStorage.removeItem('ff-password-pending-intent');
      setPasswordPending(false);
    }} />;
  }

  return (
    <div className={`app-container ${theme === 'light' ? 'light-mode' : ''}`}>
      <div className="bg-orb-teal"></div>
      <aside className={`sidebar ${isMobile ? (sidebarOpen ? 'mobile-open' : '') : ''} ${!isMobile && sidebarCollapsed ? 'collapsed' : ''}`}>
        <div className="sidebar-logo">
          <div style={{ color: '#06b6d4' }}>
            <ZapIcon />
          </div>
          <span className="sidebar-logo-text">FocusForge</span>
          <button
            className="sidebar-close"
            onClick={() => {
              if (isMobile) setSidebarOpen(false);
              else setSidebarCollapsed(true);
            }}
            title="Hide sidebar"
            aria-label="Hide sidebar"
          >✕</button>
        </div>

        <nav className="sidebar-nav">
          <button
            className={`nav-link ${currentPage === 'dashboard' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('dashboard');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>📊</span> Dashboard
          </button>
          <button
            className={`nav-link ${currentPage === 'timer' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('timer');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>⏱️</span> Study Timer
          </button>
          <button
            className={`nav-link ${currentPage === 'homework' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('homework');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>📚</span> Homework
          </button>
          <button
            className={`nav-link ${currentPage === 'calendar' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('calendar');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>📅</span> Calendar
          </button>
          <button
            className={`nav-link ${currentPage === 'analytics' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('analytics');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>📈</span> Analytics
          </button>
          <button
            className={`nav-link ${currentPage === 'goals' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('goals');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>🎯</span> Goals
          </button>
          <button
            className={`nav-link ${currentPage === 'notes' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('notes');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>📝</span> Notes
          </button>
          <button
            className={`nav-link ${currentPage === 'aiplan' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('aiplan');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>✨</span> AI Plan
          </button>
          <button
            className={`nav-link ${currentPage === 'achievements' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('achievements');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>🏆</span> Achievements
          </button>
          <button
            className={`nav-link ${currentPage === 'journal' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('journal');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>📔</span> Journal
          </button>
          <button
            className={`nav-link ${currentPage === 'shop' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('shop');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>🛒</span> Rewards Shop
          </button>
          <button
            className={`nav-link ${currentPage === 'leaderboard' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('leaderboard');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>🌍</span> Leaderboard
          </button>
          <button
            className={`nav-link ${currentPage === 'profile' ? 'active' : ''}`}
            onClick={() => {
              setCurrentPage('profile');
              if (isMobile) setSidebarOpen(false);
            }}
          >
            <span>👤</span> Profile
          </button>
        </nav>

        <div className="sidebar-bottom">
          Built with focus. v2.0.0
        </div>
      </aside>

      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        <div className="topbar">
          <div className="topbar-left">
            <button
              className={`hamburger ${!isMobile && sidebarCollapsed ? 'show-desktop' : ''}`}
              onClick={() => {
                if (isMobile) setSidebarOpen(!sidebarOpen);
                else setSidebarCollapsed(c => !c);
              }}
              title={isMobile ? 'Menu' : (sidebarCollapsed ? 'Show sidebar' : 'Hide sidebar')}
              aria-label="Toggle sidebar"
            >
              <span></span>
              <span></span>
              <span></span>
            </button>
            <div className="streak-counter">
              <span>🔥</span>
              <span>{userData?.currentStreak || 0}</span>
            </div>
            {pomoRunning && (
              <div
                onClick={() => setCurrentPage('timer')}
                title="Timer is running — click to view"
                style={{
                  display: 'flex', alignItems: 'center', gap: '8px',
                  fontSize: '13px', fontWeight: 600, fontVariantNumeric: 'tabular-nums',
                  background: 'rgba(6, 182, 212, 0.1)', padding: '6px 12px',
                  borderRadius: '8px', color: '#06b6d4', cursor: 'pointer'
                }}
              >
                <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: '#06b6d4', animation: 'timerBlink 1.5s ease-in-out infinite' }}></span>
                <span>{formatTime(pomoSeconds)}</span>
              </div>
            )}
          </div>

          <div className="topbar-right">
            <div className="theme-toggle-wrapper">
              <span>{theme === 'dark' ? '🌙' : '☀️'}</span>
              <button className={`theme-toggle ${theme}`} onClick={toggleTheme} title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}></button>
            </div>
            <div className="level-badge">
              <span>🏆</span>
              <span className="level-badge-number">Lvl {getLevel(userData?.points || 0)}</span>
              <div className="mini-progress-bar">
                <div
                  className="mini-progress-fill"
                  style={{ width: `${getLevelProgress(userData?.points || 0).progress}%` }}
                ></div>
              </div>
              <span style={{ fontSize: '12px' }}>{userData?.points || 0}pts</span>
            </div>
            <div className="avatar">
              {userData?.photoURL ? (
                <img src={userData.photoURL} alt="User" />
              ) : (
                <span>👤</span>
              )}
            </div>
          </div>
        </div>

        <div className={`main-content page-${currentPage}`}>
          {currentPage === 'dashboard' && <Dashboard userData={userData} uid={currentUser?.uid} />}
          {currentPage === 'timer' && (
            <StudyTimer uid={currentUser?.uid} onRefresh={handleRefresh} pomoControls={pomoControls} userData={userData} />
          )}
          {currentPage === 'homework' && <HomeworkPage uid={currentUser?.uid} />}
          {currentPage === 'calendar' && <CalendarPage uid={currentUser?.uid} />}
          {currentPage === 'analytics' && <AnalyticsPage uid={currentUser?.uid} />}
          {currentPage === 'goals' && <GoalsPage uid={currentUser?.uid} userData={userData} />}
          {currentPage === 'notes' && <NotesPage uid={currentUser?.uid} onNavigate={setCurrentPage} />}
          {currentPage === 'aiplan' && <AIPlanPage uid={currentUser?.uid} userData={userData} />}
          {currentPage === 'achievements' && <AchievementsPage uid={currentUser?.uid} />}
          {currentPage === 'journal' && <JournalPage uid={currentUser?.uid} />}
          {currentPage === 'shop' && <RewardsShopPage
            uid={currentUser?.uid}
            userData={userData}
            activeConfetti={activeConfetti}
            setActiveConfetti={setActiveConfetti}
            activeSound={activeSound}
            setActiveSound={setActiveSound}
          />}
          {currentPage === 'leaderboard' && <LeaderboardPage uid={currentUser?.uid} userData={userData} />}
          {currentPage === 'profile' && (
            <ProfilePage userData={userData} uid={currentUser?.uid} onLogout={handleLogout} />
          )}
        </div>
      </div>

      <TodoFloatingPanel uid={currentUser?.uid} />
      <AmbientSoundsPanel />
      <VoiceCommandsPanel
        onNavigate={(page) => setCurrentPage(page)}
        onTimerToggle={() => setPomoRunning(r => !r)}
        onTimerReset={() => { setPomoRunning(false); setPomoSeconds(pomoBreak ? 300 : 1500); }}
      />
      <AchievementToasts />
      <XPToasts />
      <MobileBottomNav currentPage={currentPage} setCurrentPage={setCurrentPage} />
      {pomoConfetti && <ConfettiBurst setId={activeConfetti} />}

      {showLevelUp && (
        <LevelUpCelebration newLevel={newLevel} onDismiss={() => setShowLevelUp(false)} />
      )}

      {showShortcuts && (
        <div className="modal-overlay" onClick={() => setShowShortcuts(false)}>
          <div className="modal-content" onClick={e => e.stopPropagation()} style={{ maxWidth: '440px' }}>
            <div className="modal-header">
              <span>⌨️ Keyboard Shortcuts</span>
              <button className="modal-close" onClick={() => setShowShortcuts(false)}>✕</button>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '13px' }}>
              {[
                { k: 'D', a: 'Dashboard' },
                { k: 'T', a: 'Study Timer' },
                { k: 'H', a: 'Homework' },
                { k: 'C', a: 'Calendar' },
                { k: 'A', a: 'Analytics' },
                { k: 'G', a: 'Goals' },
                { k: 'N', a: 'Notes' },
                { k: 'P', a: 'AI Plan' },
                { k: 'B', a: 'Achievements' },
                { k: 'J', a: 'Journal' },
                { k: 'S', a: 'Rewards Shop' },
                { k: 'L', a: 'Toggle light/dark' },
                { k: 'Space', a: 'Start / pause Pomodoro' },
                { k: '?', a: 'Show this dialog' }
              ].map(s => (
                <div key={s.k} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', borderRadius: '8px', background: 'rgba(255,255,255,0.03)' }}>
                  <span style={{ color: '#a1a1aa' }}>{s.a}</span>
                  <kbd style={{
                    padding: '3px 10px', borderRadius: '6px',
                    background: '#27272a', border: '1px solid rgba(255,255,255,0.1)',
                    color: 'var(--text-primary)', fontFamily: 'SF Mono, Monaco, monospace', fontSize: '12px'
                  }}>{s.k}</kbd>
                </div>
              ))}
            </div>
            <div style={{ fontSize: '11px', color: '#71717a', marginTop: '16px', textAlign: 'center' }}>
              Shortcuts are disabled while typing in inputs
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
