/* ============================================================
   IDA Student Dashboard — app shell
   Welcome / role picker + sidebar + module routing.
   ============================================================ */

// useState/useEffect/useRef already declared as globals via destructuring in signature.jsx
// (babel-standalone runs all scripts in one global scope; redeclaring would crash).
const { useMemo, useCallback } = React;

/* ---------- Tiny utils ---------- */
const fmtDate = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d)) return iso;
  return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
};
const fmtDateTime = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  return d.toLocaleString('en-IN', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
};
const ago = (iso) => {
  if (!iso) return '';
  const ms = Date.now() - new Date(iso).getTime();
  const m = Math.round(ms / 60000);
  if (m < 1) return 'just now';
  if (m < 60) return `${m}m ago`;
  const h = Math.round(m / 60);
  if (h < 24) return `${h}h ago`;
  const d = Math.round(h / 24);
  return `${d}d ago`;
};
const initials = (name) => name?.split(/\s+/).map(w => w[0]).slice(0, 2).join('').toUpperCase() || '?';
const cls = (...parts) => parts.filter(Boolean).join(' ');
window.UI = { fmtDate, fmtDateTime, ago, initials, cls };

/* ---------- Inline SVG icons (data-viz / nav) ---------- */
const NavIcons = {
  home: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M2 7l6-5 6 5v6.5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"/></svg>,
  reg:  <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="currentColor" strokeWidth="1.4"/><path d="M5.5 5.5h5M5.5 8h5M5.5 10.5h3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
  docs: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M4 1.5h5l3 3v9.5a.5.5 0 0 1-.5.5h-7.5a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z" stroke="currentColor" strokeWidth="1.4"/><path d="M9 1.5v3h3" stroke="currentColor" strokeWidth="1.4"/></svg>,
  med:  <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M8 2v12M2 8h12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/><rect x="3.5" y="3.5" width="9" height="9" rx="2" stroke="currentColor" strokeWidth="1.2" opacity=".4"/></svg>,
  test: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M2 13l3-4 3 2.5L13 4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/><circle cx="13" cy="4" r="1.5" fill="currentColor"/></svg>,
  att:  <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><rect x="2" y="3" width="12" height="11" rx="1" stroke="currentColor" strokeWidth="1.4"/><path d="M5 1.5v3M11 1.5v3M2.5 6.5h11" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
  theo: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M2.5 3.5h6a2 2 0 0 1 2 2v8a2 2 0 0 0-2-2h-6V3.5zM13.5 3.5h-3a2 2 0 0 0-2 2v8a2 2 0 0 1 2-2h3V3.5z" stroke="currentColor" strokeWidth="1.3"/></svg>,
  soc:  <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><circle cx="4" cy="8" r="2" stroke="currentColor" strokeWidth="1.4"/><circle cx="12" cy="4" r="2" stroke="currentColor" strokeWidth="1.4"/><circle cx="12" cy="12" r="2" stroke="currentColor" strokeWidth="1.4"/><path d="M5.6 7l4.8-2M5.6 9l4.8 2" stroke="currentColor" strokeWidth="1.4"/></svg>,
  ann:  <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M2 6v4l9 3.5V2.5L2 6z" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"/><path d="M11 5.5c1.5 0 1.5 5 0 5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
  cert: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><circle cx="8" cy="6.5" r="3.5" stroke="currentColor" strokeWidth="1.4"/><path d="M6 9l-1.5 4.5L8 12l3.5 1.5L10 9" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"/></svg>,
  roster: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><circle cx="5.5" cy="6" r="2" stroke="currentColor" strokeWidth="1.4"/><path d="M1.5 13c0-2.5 2-4 4-4s4 1.5 4 4" stroke="currentColor" strokeWidth="1.4"/><circle cx="11.5" cy="5" r="1.5" stroke="currentColor" strokeWidth="1.3"/><path d="M11.5 8.5c2 0 3 1 3 3" stroke="currentColor" strokeWidth="1.3"/></svg>,
  verify: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M8 1.5l5.5 2v5.5c0 3-2.5 4.5-5.5 5.5-3-1-5.5-2.5-5.5-5.5V3.5L8 1.5z" stroke="currentColor" strokeWidth="1.4"/><path d="M5.5 8L7 9.5l3.5-3.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  search: <svg viewBox="0 0 16 16" width="14" height="14" fill="none"><circle cx="7" cy="7" r="4.5" stroke="currentColor" strokeWidth="1.4"/><path d="M10.5 10.5L14 14" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
  signout: <svg viewBox="0 0 16 16" width="13" height="13" fill="none"><path d="M9.5 4V2.5a.5.5 0 0 0-.5-.5h-6a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h6a.5.5 0 0 0 .5-.5V12" stroke="currentColor" strokeWidth="1.3"/><path d="M6 8h8M11.5 5.5L14 8l-2.5 2.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>,
  download: <svg viewBox="0 0 16 16" width="13" height="13" fill="none"><path d="M8 2v9M4 7.5l4 4 4-4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/><path d="M2.5 13.5h11" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
  upload: <svg viewBox="0 0 16 16" width="13" height="13" fill="none"><path d="M8 11V2M4 6.5l4-4 4 4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/><path d="M2.5 13.5h11" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
  plus: <svg viewBox="0 0 16 16" width="12" height="12" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>,
  ext:  <svg viewBox="0 0 12 12" width="10" height="10" fill="none"><path d="M3 9l6-6M9 3H5M9 3v4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  trash: <svg viewBox="0 0 16 16" width="11" height="11" fill="none"><path d="M3 4h10M6 4V2.5h4V4M5 4l.5 9h5l.5-9" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>,
};
window.NavIcons = NavIcons;

/* ---------- Password input with show/hide toggle ---------- */
function PasswordInput({ value, onChange, shown, onToggle, autoComplete, placeholder }) {
  return (
    <div className="pw-wrap">
      <input className="input pw-input"
        type={shown ? 'text' : 'password'}
        autoComplete={autoComplete}
        value={value} onChange={e=>onChange(e.target.value)}
        placeholder={placeholder}/>
      <button type="button" className="pw-toggle"
        onClick={onToggle}
        aria-label={shown ? 'Hide password' : 'Show password'}
        title={shown ? 'Hide password' : 'Show password'}>
        {shown
          // eye-off
          ? <svg width="16" height="16" viewBox="0 0 20 20" fill="none">
              <path d="M3 3l14 14" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
              <path d="M9 5.5c.33-.03.67-.05 1-.05 4 0 7 4.5 7 4.5a13.5 13.5 0 0 1-2.05 2.6M6.5 6.8C4.5 8.2 3 10 3 10s3 4.5 7 4.5c1.1 0 2.1-.27 3-.7"
                stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
              <path d="M8.5 8.5a2 2 0 0 0 2.7 2.7" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
            </svg>
          // eye
          : <svg width="16" height="16" viewBox="0 0 20 20" fill="none">
              <path d="M3 10s3-5 7-5 7 5 7 5-3 5-7 5-7-5-7-5z"
                stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
              <circle cx="10" cy="10" r="2" stroke="currentColor" strokeWidth="1.6"/>
            </svg>}
      </button>
    </div>
  );
}
window.PasswordInput = PasswordInput;

/* ---------- Sign in / Create account (Firebase email/password) ---------- */
function SignIn({ error }) {
  // 'choose' is the new landing screen — two big options instead of
  // dropping the user straight into a sign-in form that hides the
  // sign-up affordance. Existing accounts (incl. staff) jump straight
  // to 'signin' from the chooser; new students go to 'signup'.
  const [mode, setMode] = useState('choose');
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [pw, setPw] = useState('');
  const [pw2, setPw2] = useState('');
  const [busy, setBusy] = useState(false);
  const [localErr, setLocalErr] = useState('');
  const [notice, setNotice] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [showPw2, setShowPw2] = useState(false);
  const isSignup = mode === 'signup';
  const isChooser = mode === 'choose';
  // If the parent surfaces an auth-state error (deactivated /
  // not-provisioned / inactive), pop the sign-in form so the banner
  // is visible — the chooser screen would otherwise hide it.
  useEffect(() => {
    if (error && mode === 'choose') setMode('signin');
  }, [error]);

  const friendly = (code) => ({
    'auth/invalid-email': 'That email address is not valid.',
    'auth/user-disabled': 'This account has been disabled. Contact the IDA office.',
    'auth/user-not-found': 'No account found for that email.',
    'auth/wrong-password': 'Incorrect email or password.',
    'auth/invalid-credential': 'Incorrect email or password.',
    'auth/too-many-requests': 'Too many attempts. Try again in a few minutes.',
    'auth/network-request-failed': 'Network error — check your connection.',
    'auth/email-already-in-use': 'An account with this email already exists — sign in instead.',
    'auth/weak-password': 'Password must be at least 6 characters.',
  }[code] || null);

  const swap = (m) => { setMode(m); setLocalErr(''); setNotice(''); setPw(''); setPw2(''); };

  const submit = async (e) => {
    e.preventDefault();
    if (busy) return;
    setLocalErr(''); setNotice('');
    if (isSignup) {
      if (!name.trim()) { setLocalErr('Enter your full name.'); return; }
      if (!email.trim()) { setLocalErr('Enter your email.'); return; }
      if (pw.length < 6) { setLocalErr('Password must be at least 6 characters.'); return; }
      if (pw !== pw2) { setLocalErr('Passwords do not match.'); return; }
      setBusy(true);
      try {
        await window.Auth.signUp(name, email, pw);
        // onAuthStateChanged drives the rest (lands on onboarding)
      } catch (err) {
        setLocalErr(friendly(err.code) || err.message || 'Could not create account.');
        setBusy(false);
      }
      return;
    }
    if (!email.trim() || !pw) { setLocalErr('Enter your email and password.'); return; }
    setBusy(true);
    try {
      await window.Auth.signIn(email, pw);
    } catch (err) {
      setLocalErr(friendly(err.code) || err.message || 'Sign-in failed.');
      setBusy(false);
    }
  };

  const reset = async () => {
    setLocalErr(''); setNotice('');
    if (!email.trim()) { setLocalErr('Enter your email first, then tap “Forgot password”.'); return; }
    try {
      await window.Auth.sendReset(email);
      setNotice('Password reset link sent — check your inbox.');
    } catch (err) {
      setLocalErr(friendly(err.code) || err.message || 'Could not send reset email.');
    }
  };

  const shownErr = localErr || error;

  const Brand = ({ large }) => (
    <div className={cls("stack-sm", large && "brand-large")}
      style={{alignItems: large ? 'center' : 'flex-start', gap: large ? 14 : 8,
              textAlign: large ? 'center' : 'left',
              marginBottom: large ? 6 : 0}}>
      <img src="assets/ida-logo.png" alt="India Drone Academy"
        style={{height: large ? 'clamp(64px, 18vw, 96px)' : 48,
                width:'auto', maxWidth:'100%', display:'block'}}/>
      <div style={{
        fontSize: large ? 'clamp(13px, 3.4vw, 16px)' : 12,
        fontWeight: 600,
        color: large ? 'var(--accent)' : 'var(--muted)',
        letterSpacing:'.08em',
        textTransform:'uppercase',
      }}>
        RPTO Student Dashboard
      </div>
    </div>
  );

  // ---------- Chooser screen ----------
  if (isChooser) {
    const IconExisting = (
      // Curved arrow — "sign in / return"
      <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
        strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
        <polyline points="10 17 15 12 10 7"/>
        <line x1="15" y1="12" x2="3" y2="12"/>
      </svg>
    );
    const IconNew = (
      // User plus — "register"
      <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
        strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
        <circle cx="9" cy="7" r="4"/>
        <line x1="19" y1="8" x2="19" y2="14"/>
        <line x1="22" y1="11" x2="16" y2="11"/>
      </svg>
    );
    const IconArrow = (
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
        strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <line x1="5" y1="12" x2="19" y2="12"/>
        <polyline points="12 5 19 12 12 19"/>
      </svg>
    );

    return (
      <div className="welcome">
        <div className="welcome-card welcome-chooser">
          <Brand large/>
          <h1 className="chooser-h1" style={{textAlign:'center'}}>Welcome to India Drone Academy</h1>
          <p className="lead chooser-lead" style={{textAlign:'center',margin:'0 auto 26px',maxWidth:440}}>
            How would you like to continue? Existing students and staff sign in with their email. New students register here.
          </p>

          <div className="stack-sm" style={{gap:12}}>
            <button type="button" className="login-choice login-choice-primary"
              onClick={()=>swap('signin')}>
              <div className="login-choice-icon">{IconExisting}</div>
              <div className="login-choice-body">
                <div className="login-choice-title">I already have an account</div>
                <div className="login-choice-sub">Sign in to continue your training, or as an instructor / manager.</div>
              </div>
              <div className="login-choice-arrow">{IconArrow}</div>
            </button>

            <button type="button" className="login-choice"
              onClick={()=>swap('signup')}>
              <div className="login-choice-icon">{IconNew}</div>
              <div className="login-choice-body">
                <div className="login-choice-title">I'm a new student</div>
                <div className="login-choice-sub">Create your student account. We'll guide you through the registration form and document uploads.</div>
              </div>
              <div className="login-choice-arrow">{IconArrow}</div>
            </button>
          </div>

          <div className="tiny muted" style={{marginTop:22,textAlign:'center',lineHeight:1.6}}>
            New students verify their email before the dashboard opens. Instructors and managers are provisioned by the Accountable Manager.
          </div>
        </div>
      </div>
    );
  }

  // ---------- Sign-in / sign-up form ----------
  return (
    <div className="welcome">
      <form className="welcome-card" onSubmit={submit}>
        <div className="row-wrap" style={{justifyContent:'space-between',alignItems:'flex-start',gap:10}}>
          <Brand/>
          <button type="button" className="btn btn-sm" onClick={()=>swap('choose')}
            title="Back to the chooser screen">
            ← Back
          </button>
        </div>
        <h1 style={{marginTop:14}}>{isSignup ? 'Create your student account' : 'Sign in'}</h1>
        <p className="lead">
          {isSignup
            ? 'Register as a new student. You’ll be guided through your registration form and document uploads.'
            : 'Sign in with your email and password. Students, instructors and the accountable manager each see their own view.'}
        </p>

        {shownErr && (
          <div className="banner bad" style={{marginBottom:14}}>
            <div className="body">{shownErr}</div>
          </div>
        )}
        {notice && (
          <div className="banner good" style={{marginBottom:14}}>
            <div className="body">{notice}</div>
          </div>
        )}

        {isSignup && (
          <div className="field" style={{marginBottom:12}}>
            <label>Full name</label>
            <input className="input" type="text" autoComplete="name"
              value={name} onChange={e=>setName(e.target.value)}
              placeholder="As it should appear on your certificate" />
          </div>
        )}
        <div className="field" style={{marginBottom:12}}>
          <label>Email</label>
          <input className="input" type="email" autoComplete="username"
            value={email} onChange={e=>setEmail(e.target.value)}
            placeholder="you@example.com" />
        </div>
        <div className="field" style={{marginBottom: isSignup ? 12 : 14}}>
          <label>Password</label>
          <PasswordInput value={pw} onChange={setPw}
            shown={showPw} onToggle={()=>setShowPw(v=>!v)}
            autoComplete={isSignup ? 'new-password' : 'current-password'}
            placeholder={isSignup ? 'At least 6 characters' : '••••••••'} />
        </div>
        {isSignup && (
          <div className="field" style={{marginBottom:14}}>
            <label>Confirm password</label>
            <PasswordInput value={pw2} onChange={setPw2}
              shown={showPw2} onToggle={()=>setShowPw2(v=>!v)}
              autoComplete="new-password"
              placeholder="Re-enter password" />
          </div>
        )}

        <div className="row-wrap signin-actions" style={{justifyContent:'space-between',alignItems:'center',gap:12}}>
          {isSignup
            ? <button type="button" className="btn-link tiny" onClick={()=>swap('signin')}>
                Have an account? Sign in
              </button>
            : <button type="button" className="btn-link tiny" onClick={reset}>Forgot password?</button>}
          <button className="btn btn-primary signin-submit" type="submit" disabled={busy}>
            {busy ? (isSignup ? 'Creating…' : 'Signing in…') : (isSignup ? 'Create account →' : 'Sign in →')}
          </button>
        </div>

        {!isSignup && (
          <div style={{marginTop:16,paddingTop:14,borderTop:'1px solid var(--line)',
                       textAlign:'center',fontSize:12.5,color:'var(--muted)'}}>
            New student?{' '}
            <button type="button" className="btn-link" style={{display:'inline'}}
              onClick={()=>swap('signup')}>Create an account</button>
          </div>
        )}
      </form>
    </div>
  );
}

/* ---------- Sidebar ---------- */
/* Segmented English / తెలుగు switch. Reused in the sidebar footer
   and the Account page. */
function LangToggle({ lang, setLang, block }) {
  const langs = window.IDA_LANGS || [{id:'en',native:'English'},{id:'te',native:'తెలుగు'}];
  return (
    <div className={cls('lang-toggle', block && 'lang-toggle-block')} role="group" aria-label="Language">
      {langs.map(l => (
        <button key={l.id}
          className={cls('lang-opt', (lang || 'en') === l.id && 'active')}
          onClick={()=> setLang(l.id)}
          aria-pressed={(lang || 'en') === l.id}>
          {l.native}
        </button>
      ))}
    </div>
  );
}
window.LangToggle = LangToggle;

function Sidebar({ session, route, setRoute, store, onSignOut, paletteTweak, lang, setLang }) {
  const isStudent    = session.role === 'student';
  const isInstructor = session.role === 'instructor';
  const isManager    = session.role === 'manager';
  const rec = isStudent ? store.students[session.userId] : null;
  const prog = rec ? window.calcProgress(rec) : null;

  // For instructor/manager, derive queue counts. Mirror OfficerQueue:
  // only count uploaded docs whose id still lives in the current
  // DOCUMENTS list (legacy schema entries like 'aadhar', 'govt-id',
  // 'logbook', 'basic-knowledge' would otherwise inflate the badge).
  // Use listStudents() so former-students / orphans don't count.
  let pendingVerify = 0;
  const activeStudents = (isInstructor || isManager)
    ? window.Store.listStudents()
    : [];
  if (isInstructor || isManager) {
    const DOC_IDS = new Set((window.DOCUMENTS || []).map(d => d.id));
    activeStudents.forEach(s => {
      Object.entries(s.documents || {}).forEach(([id, d]) => {
        if (DOC_IDS.has(id) && d && d.status === 'uploaded') pendingVerify++;
      });
      if (s.medical?.status === 'endorsed') pendingVerify++;
      if (s.registration?.submitted && !s.registration?.verifiedByOfficer) pendingVerify++;
    });
  }
  const activeStudentCount = activeStudents.length;

  const studentNav = [
    { id:'home',         label:'Home',                    icon:NavIcons.home, pct: prog?.overall },
    { id:'onboarding',   label:'Get started',             icon:NavIcons.verify,
      badge: prog && !prog.registrationDone ? '!' : null },
    { id:'registration', label:'Registration',            icon:NavIcons.reg,  pct: prog?.registrationPct },
    { id:'documents',    label:'Documents',               icon:NavIcons.docs, badge: prog ? `${prog.docDone}/${prog.docTotal}` : null },
    { id:'medical',      label:'Medical fitness',         icon:NavIcons.med,  pct: prog?.medicalPct },
    { id:'tests',        label:'Progress & skill',        icon:NavIcons.test, pct: prog?.testsPct },
    { id:'attendance',   label:'Attendance',              icon:NavIcons.att,  pct: prog?.attendancePct },
    { id:'online-classes', label:'Online Classes',        icon:NavIcons.theo, pct: prog?.theoryPct },
    { id:'social',       label:'Social media',            icon:NavIcons.soc,  pct: prog?.socialPct },
  ];
  const annCount = Object.keys(store.announcements || {}).length;
  const studentNav2 = [
    { id:'announcements', label:'Announcements', icon:NavIcons.ann,
      badge: annCount ? String(annCount) : null },
    { id:'certificate',   label:'Final certificate', icon:NavIcons.cert,
      pct: rec?.issuance?.cleared ? 100 : null },
    { id:'account',       label:'Account', icon:NavIcons.verify },
  ];

  // Batches now live INSIDE the Students tab (top-level grid → drill
  // into a batch → student list + file). The standalone Batches route
  // still resolves, but it points at the same component.
  const officerNav = [
    { id:'overview',     label:'Cohort overview', icon:NavIcons.home },
    { id:'roster',       label:'Students',        icon:NavIcons.roster,
      badge: activeStudentCount ? String(activeStudentCount) : null },
    { id:'queue',        label:'Verification queue', icon:NavIcons.verify, badge: pendingVerify ? String(pendingVerify) : null },
    { id:'tests-fill',   label:'Test reports',    icon:NavIcons.test },
    { id:'attendance-mark', label:'Mark attendance', icon:NavIcons.att },
    { id:'announcements-post', label:'Post notice', icon:NavIcons.ann },
    { id:'account', label:'Account', icon:NavIcons.verify },
  ];
  const adminNav = [
    { id:'overview',  label:'Cohort overview', icon:NavIcons.home },
    { id:'roster',    label:'All students',    icon:NavIcons.roster,
      badge: activeStudentCount ? String(activeStudentCount) : null },
    { id:'instructors', label:'Instructors',   icon:NavIcons.verify },
    { id:'issuance',  label:'Certificate issuance', icon:NavIcons.cert,
      badge: (() => {
        const T = window.Store.getSettings().certThreshold;
        const n = activeStudents
          .filter(s => !s.issuance?.cleared && (window.calcProgress(s)?.overall || 0) >= T).length;
        return n ? String(n) : null;
      })() },
    { id:'users',     label:'User management', icon:NavIcons.roster },
    { id:'audit',     label:'Activity log',    icon:NavIcons.verify },
    { id:'announcements-post', label:'Post notice', icon:NavIcons.ann },
    { id:'account',   label:'Account',         icon:NavIcons.verify },
    { id:'settings',  label:'Settings',         icon:NavIcons.verify },
  ];

  const NavBtn = ({ item }) => (
    <button className={cls('nav-item', route === item.id && 'active')} onClick={()=>setRoute(item.id)}>
      <span className="ico">{item.icon}</span>
      <span>{isStudent ? tr(item.label) : item.label}</span>
      {typeof item.pct === 'number' && <span className="pct">{item.pct}%</span>}
      {item.badge && <span className="badge">{item.badge}</span>}
    </button>
  );

  return (
    <aside className="sb">
      <div className="sb-brand">
        <img src="assets/ida-logo.png" alt="India Drone Academy" className="sb-logo"/>
        <div className="sub">{isStudent ? tr('Student portal') : isInstructor ? 'Instructor console' : 'Accountable Manager'}</div>
      </div>

      <div className="sb-who">
        <div className="av">{initials(session.userName)}</div>
        <div style={{minWidth:0,flex:1}}>
          <div className="nm" style={{overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{session.userName}</div>
          <div className="rl">{isStudent ? (rec?.profile?.rollNo || tr('Student')) : isInstructor ? (session.officerRole || 'Instructor') : 'Accountable Manager · IDA HQ'}</div>
        </div>
      </div>

      <nav className="sb-nav">
        {isStudent && <>
          <div className="sb-cap">{tr('Course modules')}</div>
          {studentNav.map(i => <NavBtn key={i.id} item={i} />)}
          <div className="sb-cap">{tr('Communications')}</div>
          {studentNav2.map(i => <NavBtn key={i.id} item={i} />)}
        </>}
        {isInstructor && <>
          <div className="sb-cap">Console</div>
          {officerNav.map(i => <NavBtn key={i.id} item={i} />)}
        </>}
        {isManager && <>
          <div className="sb-cap">Accountable Manager</div>
          {adminNav.map(i => <NavBtn key={i.id} item={i} />)}
        </>}
      </nav>

      {/* Language toggle — student-facing i18n (English ⇄ Telugu). */}
      {isStudent && setLang && (
        <div className="sb-lang">
          <LangToggle lang={lang} setLang={setLang}/>
        </div>
      )}

      <div className="sb-foot">
        <div className="meta">
          <div>India Drone Academy</div>
          <div>{tr('Saved to your account')}</div>
        </div>
        <button className="btn-link" onClick={onSignOut} title={tr('Sign out')}>
          {NavIcons.signout}
        </button>
      </div>
    </aside>
  );
}

/* ---------- Module Header ---------- */
function ModuleHeader({ crumbs, title, sub, right }) {
  return (
    <div className="main-hd">
      <div>
        {crumbs && <div className="crumbs">{crumbs}</div>}
        <h1>{title}</h1>
        {sub && <p>{sub}</p>}
      </div>
      {right}
    </div>
  );
}
window.ModuleHeader = ModuleHeader;

/* ---------- Self-service account (all roles) ---------- */
function AccountPage({ ctx }) {
  const s = ctx.session;
  const roleLabel = s.role === 'student' ? 'Student'
    : s.role === 'instructor' ? (s.officerRole || 'Instructor')
    : 'Accountable Manager';
  const [name, setName] = useState(s.userName || '');
  const [savingName, setSavingName] = useState(false);
  const [resetSent, setResetSent] = useState(false);

  const saveName = async () => {
    const nm = name.trim();
    if (!nm) { ctx.setToast('Name cannot be empty'); return; }
    setSavingName(true);
    try {
      await window.Store.setOwnName(nm);
      ctx.setToast('Name updated — sign in again to refresh it everywhere');
    } catch (e) {
      ctx.setToast(e.message || 'Could not update name');
    } finally { setSavingName(false); }
  };
  const emailReset = async () => {
    try {
      await window.Auth.sendReset(s.email);
      setResetSent(true);
      ctx.setToast('Password reset email sent');
    } catch (e) { ctx.setToast(e.message || 'Could not send reset email'); }
  };

  const isStaff = s.role === 'instructor' || s.role === 'manager';
  const meUser = isStaff ? (window.Store.getUser ? window.Store.getUser(s.uid) : null) : null;
  const existingSignature = meUser && meUser.signature;
  const [sigDraft, setSigDraft] = useState(existingSignature || '');
  const [editingSig, setEditingSig] = useState(!existingSignature);
  useEffect(() => {
    setSigDraft(existingSignature || '');
    setEditingSig(!existingSignature);
  }, [existingSignature]);

  const saveSignature = async () => {
    if (!sigDraft) { ctx.setToast('Sign in the box first'); return; }
    try {
      await window.Store.setOwnSignature(sigDraft);
      setEditingSig(false);
      ctx.setToast('Signature saved');
      window.Store.logAudit('Signature updated', s.userName, '');
    } catch (e) { ctx.setToast(e.message || 'Could not save signature'); }
  };
  const clearSignature = async () => {
    if (!confirm('Remove your saved signature?')) return;
    try {
      await window.Store.setOwnSignature('');
      setSigDraft('');
      setEditingSig(true);
      ctx.setToast('Signature removed');
    } catch (e) { ctx.setToast(e.message || 'Could not remove signature'); }
  };

  return (
    <div className="module">
      <ModuleHeader
        crumbs="ACCOUNT"
        title={tr("Your account")}
        sub="Your profile and sign-in security."
      />
      {s.role === 'student' && ctx.setLang && (
        <div className="card" style={{marginBottom:14}}>
          <div className="card-hd"><div>
            <h3>{tr('Language')}</h3>
            <div className="sub">{tr('Choose the language for your dashboard.')}</div>
          </div></div>
          <div className="card-body">
            <LangToggle lang={ctx.lang} setLang={ctx.setLang} block/>
          </div>
        </div>
      )}
      <div className="grid g-2">
        <div className="card">
          <div className="card-hd"><h3>Profile</h3></div>
          <div className="card-body stack-sm">
            <div className="field">
              <label>Display name</label>
              <input className="input" value={name} onChange={e=>setName(e.target.value)}/>
            </div>
            <div className="field">
              <label>Email</label>
              <input className="input" value={s.email} readOnly/>
            </div>
            <div className="field">
              <label>Role</label>
              <input className="input" value={roleLabel} readOnly/>
            </div>
            <div className="row">
              <button className="btn btn-primary btn-sm" disabled={savingName} onClick={saveName}>
                {savingName ? 'Saving…' : 'Save name'}
              </button>
            </div>
          </div>
        </div>
        <div className="card">
          <div className="card-hd"><h3>Security</h3></div>
          <div className="card-body">
            <p className="small muted" style={{margin:'0 0 12px',lineHeight:1.6}}>
              To change your password we email a secure reset link to <b>{s.email}</b>.
            </p>
            <div className="row" style={{gap:8,flexWrap:'wrap'}}>
              <button className="btn btn-sm" disabled={resetSent} onClick={emailReset}>
                {resetSent ? 'Reset email sent' : 'Email me a password reset'}
              </button>
              <button className="btn btn-sm btn-bad" onClick={()=>window.Auth.signOut()}>Sign out</button>
            </div>
          </div>
        </div>
      </div>

      {/* Instructor / manager signature — applied automatically to
          DGCA progress reports, attendance and packet exports. */}
      {isStaff && (
        <div className="card" style={{marginTop:18}}>
          <div className="card-hd">
            <div>
              <h3>Your signature</h3>
              <div className="sub">Saved once. Applied automatically to progress reports, attendance and the DGCA submission packet.</div>
            </div>
            {existingSignature && !editingSig && (
              <div className="row" style={{gap:8}}>
                <button className="btn btn-sm" onClick={()=>setEditingSig(true)}>Replace</button>
                <button className="btn btn-sm btn-bad" onClick={clearSignature}>Remove</button>
              </div>
            )}
          </div>
          <div className="card-body">
            {existingSignature && !editingSig ? (
              <div style={{maxWidth:360}}>
                <img src={existingSignature} alt="Your signature"
                  style={{maxWidth:'100%',height:'auto',border:'1px solid var(--line)',
                          borderRadius:8,background:'var(--surface)',padding:6}}/>
                {meUser && meUser.signatureAt && (
                  <div className="tiny muted" style={{marginTop:6}}>
                    Saved {UI.fmtDateTime(meUser.signatureAt)}
                  </div>
                )}
              </div>
            ) : (
              <div style={{maxWidth:360}}>
                <SignaturePad label="Sign in the box below" role={s.userName}
                  value={sigDraft} onChange={setSigDraft}/>
                <div className="row" style={{gap:8,marginTop:10}}>
                  <button className="btn btn-primary btn-sm"
                    disabled={!sigDraft}
                    onClick={saveSignature}>Save signature</button>
                  {existingSignature && (
                    <button className="btn btn-sm" onClick={()=>{ setSigDraft(existingSignature); setEditingSig(false); }}>
                      Cancel
                    </button>
                  )}
                </div>
              </div>
            )}
          </div>
        </div>
      )}

      {/* Per-instructor activity — derived from the audit log so it
          reflects exactly what was recorded against this actor. The
          audit collection is capped at the latest 200 entries so the
          "all time" column is honest about that. */}
      {isStaff && <ActivityWidget ctx={ctx}/>}

      <PushNotificationCard ctx={ctx}/>
    </div>
  );
}
window.AccountPage = AccountPage;

/* ---------- Push notifications opt-in ---------- */
function PushNotificationCard({ ctx }) {
  // Re-read on each render so the chip state matches what's persisted.
  const me = (window.Store.getUser && window.Store.getUser(ctx.session.uid)) || null;
  const enrolled = !!(me && me.fcmToken);
  const [busy, setBusy] = useState(false);
  const [perm, setPerm] = useState(
    'Notification' in window ? Notification.permission : 'unsupported'
  );
  useEffect(() => {
    if (!('Notification' in window)) return;
    setPerm(Notification.permission);
  }, [enrolled]);

  const enable = async () => {
    setBusy(true);
    try {
      await window.Store.enablePushNotifications();
      setPerm(Notification.permission);
      window.Store.logAudit('Push notifications enabled', ctx.session.userName, '');
      ctx.setToast('Push notifications enabled');
    } catch (e) {
      ctx.setToast(e.message || 'Could not enable notifications');
    } finally { setBusy(false); }
  };
  const disable = async () => {
    if (!confirm('Stop receiving push notifications on this device?')) return;
    setBusy(true);
    try {
      await window.Store.disablePushNotifications();
      window.Store.logAudit('Push notifications disabled', ctx.session.userName, '');
      ctx.setToast('Push notifications disabled on this device');
    } catch (e) {
      ctx.setToast(e.message || 'Could not disable notifications');
    } finally { setBusy(false); }
  };

  const supported = perm !== 'unsupported';
  return (
    <div className="card" style={{marginTop:18}}>
      <div className="card-hd">
        <div>
          <h3>Push notifications</h3>
          <div className="sub">Get instant updates when a document is verified, an announcement is posted, or your theory test is unlocked.</div>
        </div>
        {enrolled
          ? <span className="pill pill-good"><i className="dotty"/>On for this device</span>
          : <span className="pill pill-ghost"><i className="dotty"/>Off</span>}
      </div>
      <div className="card-body">
        {!supported && (
          <div className="banner warn">
            <div className="body">This browser doesn't support web push notifications. Try a recent Chrome / Edge / Firefox / Safari.</div>
          </div>
        )}
        {supported && perm === 'denied' && (
          <div className="banner bad" style={{marginBottom:10}}>
            <div className="body">Notifications are blocked for this site in your browser. Allow them from the browser's site settings, then try again.</div>
          </div>
        )}
        {supported && (
          <div className="row" style={{gap:8,flexWrap:'wrap'}}>
            {enrolled ? (
              <button className="btn btn-sm btn-bad" disabled={busy} onClick={disable}>
                {busy ? 'Working…' : 'Disable on this device'}
              </button>
            ) : (
              <button className="btn btn-primary btn-sm" disabled={busy || perm === 'denied'} onClick={enable}>
                {busy ? 'Enabling…' : 'Enable push notifications'}
              </button>
            )}
            <span className="tiny muted" style={{alignSelf:'center'}}>
              The browser will ask for permission once. You can turn this off any time.
            </span>
          </div>
        )}
      </div>
    </div>
  );
}
window.PushNotificationCard = PushNotificationCard;

/* ---------- First-login push-notification prompt ----------
   On the user's first sign-in we proactively ask whether they want
   browser push notifications, so they don't have to discover the
   Account page toggle on their own. Shows once per account; the
   choice is sticky (localStorage flag). Always available later
   under Account → Push notifications.
   --------------------------------------------------------- */
function PushPromptOnFirstLogin({ ctx }) {
  const uid = ctx.session?.uid;
  const supported = typeof Notification !== 'undefined';
  const me = (uid && window.Store.getUser) ? window.Store.getUser(uid) : null;
  const alreadyEnabled = !!(me && me.fcmToken);
  const askedKey = uid ? `push-prompt-asked-${uid}` : null;
  const dismissed = askedKey ? !!localStorage.getItem(askedKey) : true;
  const hasVapid = !!(window.FCM_VAPID_KEY && window.FCM_VAPID_KEY.length > 20);

  const [show, setShow] = useState(false);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    if (!uid) return undefined;
    if (!supported) return undefined;
    if (!hasVapid) return undefined;
    if (alreadyEnabled || dismissed) return undefined;
    // Don't re-prompt if the browser has already DENIED permission —
    // the OS popup won't appear again and we'd just be nagging. If the
    // user re-grants permission in browser settings later, the Account
    // page toggle is still available. Permission === 'default' and
    // 'granted' both allow proceeding (granted lets us silently fetch
    // the FCM token without a second OS prompt).
    if (Notification.permission === 'denied') return undefined;
    // Defer a moment so the dashboard renders first, then the modal
    // pops — feels less like the app is shouting before showing
    // itself.
    const t = setTimeout(() => setShow(true), 1000);
    return () => clearTimeout(t);
  }, [uid, alreadyEnabled, dismissed]);

  const remember = () => { if (askedKey) localStorage.setItem(askedKey, '1'); };
  const enable = async () => {
    setBusy(true);
    try {
      await window.Store.enablePushNotifications();
      window.Store.logAudit('Push notifications enabled', ctx.session.userName, 'first-login prompt');
      remember();
      setShow(false);
      ctx.setToast('Push notifications enabled');
    } catch (e) {
      ctx.setToast(e.message || 'Could not enable notifications');
    } finally { setBusy(false); }
  };
  const later = () => {
    remember();
    setShow(false);
  };

  if (!show) return null;

  return ReactDOM.createPortal(
    <div className="cmdk-backdrop" onClick={later}>
      <div className="welcome-card" onClick={e => e.stopPropagation()}
        style={{maxWidth:480,width:'100%'}}>
        <div className="stack-sm" style={{alignItems:'flex-start',gap:8,marginBottom:8}}>
          <div style={{fontSize:32}}>🔔</div>
          <h1 style={{margin:0,fontSize:22}}>Stay in the loop</h1>
        </div>
        <p className="lead" style={{margin:'4px 0 12px'}}>
          Get a desktop notification — instantly — when:
        </p>
        <ul style={{margin:'0 0 16px 18px',padding:0,fontSize:13,lineHeight:1.7,color:'var(--ink-2)'}}>
          <li>A document of yours is verified or needs attention</li>
          <li>Your DGCA theory test is unlocked</li>
          <li>The Accountable Manager posts an announcement</li>
        </ul>
        <p className="small muted" style={{margin:'0 0 18px',lineHeight:1.5}}>
          Your browser will ask for permission once. You can toggle this any time under
          {' '}<b>Account → Push notifications</b>.
        </p>
        <div className="row" style={{justifyContent:'flex-end',gap:8,flexWrap:'wrap'}}>
          <button className="btn btn-sm" disabled={busy} onClick={later}>
            Maybe later
          </button>
          <button className="btn btn-primary" disabled={busy} onClick={enable}>
            {busy ? 'Enabling…' : 'Enable notifications'}
          </button>
        </div>
      </div>
    </div>,
    document.body);
}
window.PushPromptOnFirstLogin = PushPromptOnFirstLogin;

function ActivityWidget({ ctx }) {
  const all = (window.Store.listAudit ? window.Store.listAudit() : []) || [];
  const myUid = ctx.session.uid;
  const myName = ctx.session.userName;
  const mine = all.filter(r =>
    (r.actorUid && r.actorUid === myUid) ||
    (!r.actorUid && r.actorName === myName));

  // Bucket by category + by time window.
  const WEEK_MS = 7 * 24 * 3600 * 1000;
  const now = Date.now();
  const bucketOf = (action) => {
    const a = (action || '').toLowerCase();
    if (a.includes('verified')) return 'verified';
    if (a.includes('reject'))   return 'rejected';
    if (a.includes('finalised'))return 'tests';
    if (a.includes('attendance'))return 'attendance';
    if (a.includes('notice') || a.includes('posted')) return 'notices';
    if (a.includes('issued'))   return 'issued';
    if (a.includes('packet') || a.includes('export')) return 'packets';
    if (a.includes('theory'))   return 'theory';
    if (a.includes('batch'))    return 'batches';
    return null;
  };

  const init = () => ({
    verified:0, rejected:0, tests:0, attendance:0, notices:0,
    issued:0, packets:0, theory:0, batches:0,
  });
  const week = init();
  const total = init();
  mine.forEach(r => {
    const b = bucketOf(r.action);
    if (!b) return;
    total[b]++;
    if (r.ts && (now - new Date(r.ts).getTime()) < WEEK_MS) week[b]++;
  });

  const rows = [
    { id:'verified',   label:'Documents verified',     accent:'good' },
    { id:'rejected',   label:'Documents rejected',     accent:'bad'  },
    { id:'tests',      label:'Tests finalised',        accent:'accent' },
    { id:'attendance', label:'Attendance entries',     accent:'accent' },
    { id:'theory',     label:'Theory unlock toggles',  accent:'accent' },
    { id:'notices',    label:'Notices posted',         accent:'accent' },
    { id:'batches',    label:'Batch actions',          accent:'accent' },
    { id:'packets',    label:'Packets / exports',      accent:'accent' },
    { id:'issued',     label:'Certificates issued',    accent:'good' },
  ];
  const totalThisWeek = Object.values(week).reduce((s,x)=>s+x,0);
  const totalAll = Object.values(total).reduce((s,x)=>s+x,0);

  return (
    <div className="card" style={{marginTop:18}}>
      <div className="card-hd">
        <div>
          <h3>Your activity</h3>
          <div className="sub">From the audit log (the most recent 200 entries are kept).</div>
        </div>
        <div className="row" style={{gap:8}}>
          <span className="pill pill-warn"><i className="dotty"/>{totalThisWeek} this week</span>
          <span className="pill pill-ghost"><i className="dotty"/>{totalAll} total</span>
        </div>
      </div>
      <table className="tbl">
        <thead>
          <tr>
            <th>Action</th>
            <th style={{width:120,textAlign:'right'}}>This week</th>
            <th style={{width:120,textAlign:'right'}}>All-time (200)</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(r => {
            const w = week[r.id], t = total[r.id];
            const colorVar = r.accent === 'good' ? 'var(--good)'
              : r.accent === 'bad' ? 'var(--bad)'
              : 'var(--accent)';
            return (
              <tr key={r.id} style={!t ? { opacity: 0.55 } : undefined}>
                <td className="small">{r.label}</td>
                <td className="mono small" style={{textAlign:'right',color: w ? colorVar : 'var(--muted)'}}>{w}</td>
                <td className="mono small" style={{textAlign:'right'}}>{t}</td>
              </tr>
            );
          })}
          {totalAll === 0 && (
            <tr><td colSpan={3} className="small muted" style={{padding:'18px',textAlign:'center'}}>
              Nothing recorded for your account yet. As you verify docs, mark attendance and post notices, totals will land here.
            </td></tr>
          )}
        </tbody>
      </table>
    </div>
  );
}
window.ActivityWidget = ActivityWidget;

/* ---------- Status badge helper ---------- */
function StatusPill({ status }) {
  const map = {
    'not-started': { c:'pill-ghost', l:'Not started' },
    'downloaded':  { c:'pill-info',  l:'Downloaded' },
    'endorsed':    { c:'pill-warn',  l:'Endorsed' },
    'uploaded':    { c:'pill-warn',  l:'Awaiting verification' },
    'verified':    { c:'pill-good',  l:'Verified' },
    'rejected':    { c:'pill-bad',   l:'Rejected' },
    'pending':     { c:'pill-ghost', l:'Pending' },
    'present':     { c:'pill-good',  l:'Present' },
    'absent':      { c:'pill-bad',   l:'Absent' },
    'draft':       { c:'pill-ghost', l:'Draft' },
    'submitted':   { c:'pill-warn',  l:'Submitted' },
  };
  const m = map[status] || { c:'pill-ghost', l: status || '—' };
  return <span className={cls('pill', m.c)}><i className="dotty"/>{tr(m.l)}</span>;
}
window.StatusPill = StatusPill;

/* ---------- Donut ring ---------- */
function Donut({ value, size = 120, stroke = 10, label, sub, color }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const off = c - (value / 100) * c;
  return (
    <svg className="donut" viewBox={`0 0 ${size} ${size}`} width={size} height={size}>
      <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--bg-2)" strokeWidth={stroke}/>
      <circle cx={size/2} cy={size/2} r={r} fill="none"
        stroke={color || 'var(--accent)'} strokeWidth={stroke}
        strokeLinecap="round" strokeDasharray={c} strokeDashoffset={off}
        transform={`rotate(-90 ${size/2} ${size/2})`}/>
      <text x={size/2} y={size/2 - 2} textAnchor="middle" fontSize={size*0.22} fill="var(--ink)" dominantBaseline="middle">
        {value}<tspan fontSize={size*0.12} dx="1">%</tspan>
      </text>
      {sub && <text x={size/2} y={size/2 + size*0.18} textAnchor="middle" fontSize={size*0.085}
                    fill="var(--muted)" fontFamily="var(--font-sans)" fontWeight="600"
                    letterSpacing=".08em">{String(sub).toUpperCase()}</text>}
    </svg>
  );
}
window.Donut = Donut;

/* ---------- File upload helper ----------
   Uploads real bytes to Cloud Storage under
   students/{studentId}/{docId}/… then hands the resulting metadata
   ({name,size,path,url,uploadedAt}) to onFiles. accept/type/size are
   also enforced by storage.rules (pdf/png/jpeg, <2MB). */
const ACCEPT_RE = /\.(pdf|png|jpe?g)$/i;
const MAX_FILE_BYTES = 2 * 1024 * 1024;
function FileDrop({ onFiles, studentId, docId, accept = '.pdf,.png,.jpg,.jpeg', multiple = false, hint }) {
  const inp = useRef(null);
  const [over, setOver] = useState(false);
  const [busy, setBusy] = useState(false);
  const [pct, setPct] = useState(0);
  const [err, setErr] = useState('');

  const handle = async (fileList) => {
    if (busy) return;
    const files = Array.from(fileList || []);
    if (!files.length) return;
    setErr('');
    if (!studentId || !docId) { setErr('Upload target missing — please reload.'); return; }
    const bad = files.find(f => !ACCEPT_RE.test(f.name) || f.size >= MAX_FILE_BYTES);
    if (bad) {
      setErr(bad.size >= MAX_FILE_BYTES
        ? `${bad.name} is over 2 MB — please compress and try again.`
        : `${bad.name} must be a PDF, JPG or PNG.`);
      return;
    }
    setBusy(true); setPct(0);
    try {
      const out = [];
      for (let i = 0; i < files.length; i++) {
        const meta = await window.Store.uploadFile(
          studentId, docId, files[i],
          (p) => setPct(Math.round(((i + p) / files.length) * 100)));
        out.push(meta);
      }
      await onFiles(out);
    } catch (e) {
      setErr(e.message || 'Upload failed.');
    } finally {
      setBusy(false); setPct(0);
      if (inp.current) inp.current.value = '';
    }
  };

  return (
    <>
      <div className={cls('dropzone', over && 'over', busy && 'busy')}
        onClick={() => !busy && inp.current.click()}
        onDragOver={(e)=>{e.preventDefault();if(!busy)setOver(true)}}
        onDragLeave={()=>setOver(false)}
        onDrop={(e)=>{e.preventDefault();setOver(false);handle(e.dataTransfer.files)}}>
        {busy ? (
          <>
            <div className="big">Uploading… {pct}%</div>
            <div className="small">Saving securely to your file — don’t close this tab.</div>
          </>
        ) : (
          <>
            <div className="big">Drop file{multiple?'s':''} here or click to browse</div>
            <div className="small">{hint || 'PDF, JPG or PNG · up to 2 MB each'}</div>
          </>
        )}
        <input ref={inp} type="file" accept={accept} multiple={multiple}
          style={{display:'none'}} onChange={(e)=>handle(e.target.files)}/>
      </div>
      {err && <div className="tiny" style={{color:'var(--bad)',marginTop:6}}>{err}</div>}
    </>
  );
}
window.FileDrop = FileDrop;

function FileChip({ file, onRemove }) {
  const open = (e) => {
    e.stopPropagation();
    if (file.url) window.open(file.url, '_blank', 'noopener');
  };
  return (
    <span className="file-chip">
      <span className="icon">
        <svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M4 1.5h5l3 3v9.5a.5.5 0 0 1-.5.5h-7.5a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z" stroke="currentColor" strokeWidth="1.3"/><path d="M9 1.5v3h3" stroke="currentColor" strokeWidth="1.3"/></svg>
      </span>
      {file.url
        ? <button type="button" className="nm" onClick={open}
            style={{background:'none',border:0,padding:0,cursor:'pointer',
                    color:'var(--accent)',textDecoration:'underline',font:'inherit'}}
            title="Open / download">{file.name}</button>
        : <span className="nm">{file.name}</span>}
      <span className="sz">{Math.round((file.size||0)/1024)} KB</span>
      {onRemove && <button onClick={(e)=>{e.stopPropagation();onRemove();}}>{NavIcons.trash}</button>}
    </span>
  );
}
window.FileChip = FileChip;

/* ---------- Toast / inline confirm bar ---------- */
function Toast({ msg, onDone }) {
  useEffect(() => {
    if (!msg) return;
    const t = setTimeout(onDone, 2400);
    return () => clearTimeout(t);
  }, [msg]);
  if (!msg) return null;
  return (
    <div style={{
      position:'fixed',bottom:24,left:'50%',transform:'translateX(-50%)',zIndex:99,
      background:'var(--ink)',color:'#fff',padding:'8px 14px',
      borderRadius:8,fontSize:12.5,boxShadow:'var(--shadow-md)',fontWeight:500,
      // Mobile: don't overflow the viewport on long messages.
      maxWidth:'calc(100vw - 32px)', whiteSpace:'normal', textAlign:'center',
      lineHeight:1.4,
    }}>{msg}</div>
  );
}
window.Toast = Toast;

/* ---------- Tweaks defaults ---------- */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "kraft",
  "density": "regular",
  "showAdvanced": false
}/*EDITMODE-END*/;

/* ---------- Email verification gate (students only) ---------- */
function VerifyEmailGate({ info }) {
  const [busy, setBusy] = useState(false);
  const [resentAt, setResentAt] = useState(null);
  const [err, setErr] = useState('');
  const [info2, setInfo2] = useState(null);   // success/notice after a refresh

  const resend = async () => {
    setBusy(true); setErr(''); setInfo2(null);
    try {
      await window.Auth.resendVerification();
      setResentAt(new Date());
    } catch (e) {
      const code = e && e.code;
      setErr(
        code === 'auth/too-many-requests'
          ? 'Too many requests — please wait a minute, then try again.'
          : (e.message || 'Could not resend verification email.'));
    } finally { setBusy(false); }
  };

  const refresh = async () => {
    setBusy(true); setErr(''); setInfo2(null);
    try {
      const ok = await window.Auth.refreshVerificationStatus();
      if (!ok) {
        setInfo2('Not yet verified — open the link in the email we sent you, then click Continue.');
      }
      // If ok=true, onAuthStateChanged hasn't re-fired (Firebase doesn't
      // emit on emailVerified flips). Force a hard reload so the gate
      // re-evaluates against the fresh user.
      if (ok) window.location.reload();
    } catch (e) {
      setErr(e.message || 'Could not refresh status.');
    } finally { setBusy(false); }
  };

  return (
    <div className="welcome">
      <div className="welcome-card" style={{textAlign:'left'}}>
        <div className="stack-sm" style={{alignItems:'flex-start',gap:8,marginBottom:14}}>
          <img src="assets/ida-logo.png" alt="India Drone Academy"
            style={{height:48,width:'auto',maxWidth:'100%',display:'block'}}/>
          <div style={{fontSize:12,fontWeight:600,color:'var(--muted)',letterSpacing:'.04em'}}>
            RPTO Student Dashboard
          </div>
        </div>
        <h1>Verify your email</h1>
        <p className="lead">
          {info && info.name ? <>Hi {info.name.split(' ')[0]}, we've</> : <>We've</>} sent a verification link to <b>{info.email}</b>.
          Click the link in that email to activate your student account, then come back here and tap <b>Continue</b>.
        </p>
        <div className="banner" style={{margin:'14px 0'}}>
          <div className="body">
            <b>Didn't get it?</b> Check your spam / promotions folder. You can resend the email below. The link expires after a few hours.
          </div>
        </div>
        {err && (
          <div className="banner bad" style={{marginBottom:14}}>
            <div className="body">{err}</div>
          </div>
        )}
        {info2 && (
          <div className="banner warn" style={{marginBottom:14}}>
            <div className="body">{info2}</div>
          </div>
        )}
        {resentAt && (
          <div className="tiny muted" style={{marginBottom:14}}>
            Verification email re-sent at {resentAt.toLocaleTimeString()}.
          </div>
        )}
        <div className="row" style={{gap:8,flexWrap:'wrap'}}>
          <button className="btn btn-primary" disabled={busy} onClick={refresh}>
            {busy ? 'Checking…' : "I've verified — continue"}
          </button>
          <button className="btn btn-sm" disabled={busy} onClick={resend}>
            Resend verification email
          </button>
          <button className="btn btn-sm btn-bad" disabled={busy} onClick={()=>window.Auth.signOut()}>
            Sign out
          </button>
        </div>
      </div>
    </div>
  );
}
window.VerifyEmailGate = VerifyEmailGate;

/* ---------- Splash while auth/data resolves ---------- */
function Splash({ label }) {
  return (
    <div className="welcome">
      <div className="welcome-card" style={{textAlign:'center',padding:'48px 40px'}}>
        <img src="assets/ida-logo.png" alt="India Drone Academy"
          style={{height:56,width:'auto',maxWidth:'80%',display:'block',margin:'0 auto 18px'}}/>
        <div className="spinner" aria-hidden="true" />
        <div style={{marginTop:14,fontSize:13,color:'var(--muted)',fontWeight:500}}>
          {label || 'Connecting to India Drone Academy…'}
        </div>
      </div>
    </div>
  );
}

/* ---------- Root App ---------- */
function App() {
  const [authReady, setAuthReady] = useState(false);
  const [session, setSession] = useState(null);
  const [authError, setAuthError] = useState(null);
  const [needsVerification, setNeedsVerification] = useState(null);
  const [store, setStore] = useState(() => window.Store.get());
  const [route, setRoute] = useState('home');
  const [toast, setToast] = useState('');
  const [navOpen, setNavOpen] = useState(false);
  const [paletteOpen, setPaletteOpen] = useState(false);
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  // UI language ('en' | 'te'). Initialised from the localStorage cache
  // (window.getLang reads it) so the first paint is already correct.
  const [lang, setLangState] = useState(() => (window.getLang ? window.getLang() : 'en'));

  // Change language: update the i18n global FIRST (so tr() returns the
  // new strings) then setState to re-render the whole tree, then
  // persist to the user's account so it follows them across devices.
  const setLang = (l) => {
    const next = (window.setLangGlobal ? window.setLangGlobal(l) : l);
    setLangState(next);
    if (session && window.Store.setOwnLang) window.Store.setOwnLang(next);
  };

  // Cmd+K / Ctrl+K — open the command palette anywhere the user is.
  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) {
        e.preventDefault();
        setPaletteOpen(v => !v);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  // Sync palette into <html data-palette="...">
  useEffect(() => {
    document.documentElement.setAttribute('data-palette', tweaks.palette || 'kraft');
  }, [tweaks.palette]);

  // Surface silent Firestore persist failures (rules glitch, offline,
  // etc.) as a real toast instead of letting the prior "saved" toast
  // mislead the user. patchStudent's local cache update remains
  // optimistic — the snapshot listener will reconcile on next round
  // trip — but at least the user gets a heads-up.
  useEffect(() => {
    if (!window.Store?.onPersistError) return;
    return window.Store.onPersistError((err) => {
      const msg = (err && (err.message || err.code)) || 'unknown error';
      setToast('Save failed — please retry. (' + msg + ')');
    });
  }, []);

  // Live Firestore data
  useEffect(() => window.Store.subscribe((s) => setStore(s)), []);

  // Firebase Auth drives the session
  useEffect(() => window.Auth.onReady(({ ready, session, error, needsVerification }) => {
    setAuthReady(ready);
    setSession(session || null);
    setAuthError(error || null);
    setNeedsVerification(needsVerification || null);
    if (session) setRoute(session.role === 'student' ? 'home' : 'overview');
    // Apply the account's saved language on login (overrides the local
    // cache so the choice follows the user across devices). Only when a
    // value is present — otherwise keep whatever the device had.
    if (session && session.lang && session.lang !== (window.getLang ? window.getLang() : 'en')) {
      const applied = window.setLangGlobal ? window.setLangGlobal(session.lang) : session.lang;
      setLangState(applied);
    }
  }), []);

  // First-run: land an incomplete student on the guided onboarding
  // once (per account), without trapping them there afterwards.
  const landedFor = useRef(null);
  useEffect(() => {
    if (!session || session.role !== 'student') return;
    const r = store.students[session.userId];
    if (!r || landedFor.current === session.uid) return;
    landedFor.current = session.uid;
    if (!r.registration?.submitted) setRoute('onboarding');
  }, [session, store]);

  const handleSignOut = () => window.Auth.signOut();

  if (!authReady) return <Splash />;

  if (needsVerification) {
    return <VerifyEmailGate info={needsVerification}/>;
  }

  if (!session) {
    return <>
      <SignIn error={authError} />
      <TweaksPanel title="Tweaks">
        <PaletteTweaks tweaks={tweaks} setTweak={setTweak} />
      </TweaksPanel>
    </>;
  }

  const ctx = { session, store, setRoute, setToast, lang, setLang };

  let view = null;
  if (session.role === 'student') {
    const rec = store.students[session.userId];
    if (!rec) {
      view = <Splash label="Loading your training record…" />;
    } else {
      view = <StudentRouter route={route} ctx={ctx} rec={rec} />;
    }
  } else if (session.role === 'instructor') {
    view = <OfficerRouter route={route} ctx={ctx} />;
  } else {
    view = <AdminRouter route={route} ctx={ctx} />;
  }

  return (
    <div className={cls('app', navOpen && 'nav-open')}>
      <button className="nav-burger" aria-label="Menu" onClick={()=>setNavOpen(v=>!v)}>
        <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
          <path d="M2 4.5h14M2 9h14M2 13.5h14" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/>
        </svg>
      </button>
      <div className="nav-scrim" onClick={()=>setNavOpen(false)}/>
      <Sidebar session={session} route={route}
               setRoute={(r)=>{ setRoute(r); setNavOpen(false); }}
               store={store} onSignOut={handleSignOut}
               lang={lang} setLang={setLang} />
      <main className="main">{view}</main>
      <Toast msg={toast} onDone={()=>setToast('')}/>
      {window.CommandPalette && (
        <window.CommandPalette ctx={ctx} isOpen={paletteOpen} onClose={()=>setPaletteOpen(false)}/>
      )}
      <PushPromptOnFirstLogin ctx={ctx}/>
      <TweaksPanel title="Tweaks">
        <PaletteTweaks tweaks={tweaks} setTweak={setTweak} />
        <TweakSection label="Account">
          <TweakButton label="Sign out" secondary onClick={handleSignOut}/>
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

function PaletteTweaks({ tweaks, setTweak }) {
  return (
    <TweakSection label="Brand palette">
      <TweakColor label="Accent"
        value={tweaks.palette === 'blue' ? '#2B5FAB' : tweaks.palette === 'green' ? '#2C8770' : tweaks.palette === 'slate' ? '#3F4A5C' : '#D97757'}
        options={['#D97757','#2B5FAB','#2C8770','#3F4A5C']}
        onChange={(c) => {
          const map = { '#D97757':'kraft', '#2B5FAB':'blue', '#2C8770':'green', '#3F4A5C':'slate' };
          setTweak('palette', map[c] || 'kraft');
        }}/>
      <TweakRadio label="Density" value={tweaks.density}
        options={['compact','regular','comfy']}
        onChange={(v) => setTweak('density', v)}/>
    </TweakSection>
  );
}

/* ---------- Routers — student/officer/admin ---------- */
function StudentRouter({ route, ctx, rec }) {
  switch (route) {
    case 'home':         return <StudentHome ctx={ctx} rec={rec} />;
    case 'onboarding':   return <StudentOnboarding ctx={ctx} rec={rec} />;
    case 'registration': return <StudentRegistration ctx={ctx} rec={rec} />;
    case 'documents':    return <StudentDocuments ctx={ctx} rec={rec} />;
    case 'medical':      return <StudentMedical ctx={ctx} rec={rec} />;
    case 'tests':        return <StudentTests ctx={ctx} rec={rec} />;
    case 'attendance':   return <StudentAttendance ctx={ctx} rec={rec} />;
    case 'online-classes': return <StudentLearn ctx={ctx} rec={rec} />;
    // Legacy aliases — old deep links to /learn or /theory still work.
    case 'learn':        return <StudentLearn ctx={ctx} rec={rec} />;
    case 'theory':       return <StudentLearn ctx={ctx} rec={rec} />;
    case 'social':       return <StudentSocial ctx={ctx} rec={rec} />;
    case 'announcements':return <StudentAnnouncements ctx={ctx} rec={rec} />;
    case 'certificate':  return <StudentCertificate ctx={ctx} rec={rec} />;
    case 'account':      return <AccountPage ctx={ctx} />;
    default: return <div>Module not found · <code>{route}</code></div>;
  }
}
function OfficerRouter({ route, ctx }) {
  switch (route) {
    case 'overview': return <OfficerOverview ctx={ctx}/>;
    case 'roster':   return <OfficerRoster ctx={ctx}/>;
    case 'queue':    return <OfficerQueue ctx={ctx}/>;
    case 'batches':  return <OfficerRoster ctx={ctx}/>;   // legacy alias
    case 'tests-fill': return <OfficerTests ctx={ctx}/>;
    case 'attendance-mark': return <OfficerAttendance ctx={ctx}/>;
    case 'announcements-post': return <OfficerNotices ctx={ctx}/>;
    case 'account': return <AccountPage ctx={ctx}/>;
    default: return <OfficerOverview ctx={ctx}/>;
  }
}
function AdminRouter({ route, ctx }) {
  switch (route) {
    case 'overview':    return <AdminOverview ctx={ctx}/>;
    case 'roster':      return <AdminRoster ctx={ctx}/>;
    case 'batches':     return <OfficerRoster ctx={ctx}/>;  // legacy alias — Students covers it
    case 'instructors': return <ManagerInstructors ctx={ctx}/>;
    case 'issuance':    return <AdminIssuance ctx={ctx}/>;
    case 'users':       return <ManagerUsers ctx={ctx}/>;
    case 'audit':       return <ManagerAudit ctx={ctx}/>;
    case 'announcements-post': return <OfficerNotices ctx={ctx}/>;
    case 'account':     return <AccountPage ctx={ctx}/>;
    case 'settings':    return <AdminSettings ctx={ctx}/>;
    default: return <AdminOverview ctx={ctx}/>;
  }
}

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