// checkin.jsx — Gamification check-in: QR scanner, celebrate, leaderboard, badges. Global.

function parseCheckinToken(text) {
  if (!text) return null;
  const m = text.match(/checkin\/([0-9a-fA-F-]{36})/) ;
  if (m) return m[1];
  const u = text.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);
  return u ? u[0] : null;
}

function getGeo(timeout = 6000) {
  return new Promise((resolve) => {
    if (!navigator.geolocation) return resolve(null);
    navigator.geolocation.getCurrentPosition(
      p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }),
      () => resolve(null),
      { enableHighAccuracy: true, timeout: timeout, maximumAge: 30000 }
    );
  });
}

function loadJsQR() {
  return new Promise((resolve) => {
    if (window.jsQR) return resolve(window.jsQR);
    const s = document.createElement('script');
    s.src = 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js';
    s.onload = () => resolve(window.jsQR || null);
    s.onerror = () => resolve(null);
    document.body.appendChild(s);
  });
}

// ---------- QR scanner (camera) ----------
function CheckinScanner({ onClose, onToken }) {
  const videoRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const streamRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const doneRef = React.useRef(false);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    let detector = null;
    let jsqr = null;
    let cancelled = false;

    async function start() {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } }, audio: false });
        if (cancelled) { stream.getTracks().forEach(t => t.stop()); return; }
        streamRef.current = stream;
        const v = videoRef.current;
        if (!v) return;
        v.srcObject = stream; v.setAttribute('playsinline', 'true'); v.muted = true;
        await v.play().catch(() => {});
        if ('BarcodeDetector' in window) {
          try { detector = new window.BarcodeDetector({ formats: ['qr_code'] }); } catch (e) { detector = null; }
        }
        if (!detector) jsqr = await loadJsQR();
        scan();
      } catch (e) {
        setErr('Tidak bisa akses kamera. Izinkan kamera atau tempel link QR di aplikasi kamera HP.');
      }
    }

    async function scan() {
      if (cancelled || doneRef.current) return;
      const v = videoRef.current;
      if (v && v.readyState === 4) {
        try {
          if (detector) {
            const codes = await detector.detect(v);
            if (codes && codes.length) return hit(codes[0].rawValue);
          } else if (jsqr) {
            const cv = canvasRef.current;
            cv.width = v.videoWidth; cv.height = v.videoHeight;
            const ctx = cv.getContext('2d');
            ctx.drawImage(v, 0, 0, cv.width, cv.height);
            const img = ctx.getImageData(0, 0, cv.width, cv.height);
            const res = jsqr(img.data, cv.width, cv.height, { inversionAttempts: 'dontInvert' });
            if (res && res.data) return hit(res.data);
          }
        } catch (e) {}
      }
      rafRef.current = requestAnimationFrame(scan);
    }

    function hit(raw) {
      const tok = parseCheckinToken(raw);
      if (!tok) { rafRef.current = requestAnimationFrame(scan); return; }
      doneRef.current = true;
      stop();
      onToken(tok);
    }

    function stop() {
      cancelAnimationFrame(rafRef.current);
      if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; }
    }

    start();
    return () => { cancelled = true; stop(); };
  }, []);

  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 100, background: '#000', display: 'flex', flexDirection: 'column' }}>
      <video ref={videoRef} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
      <canvas ref={canvasRef} style={{ display: 'none' }} />
      <div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.5)' }} />
      {/* viewfinder */}
      <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }}>
        <div style={{ position: 'relative', width: 250, height: 250, borderRadius: 28, boxShadow: '0 0 0 2000px rgba(0,0,0,0.45)' }}>
          {[['top', 'left'], ['top', 'right'], ['bottom', 'left'], ['bottom', 'right']].map(([v, h], i) => (
            <span key={i} style={{ position: 'absolute', [v]: -2, [h]: -2, width: 40, height: 40,
              [`border${v[0].toUpperCase() + v.slice(1)}`]: '4px solid var(--coral)',
              [`border${h[0].toUpperCase() + h.slice(1)}`]: '4px solid var(--coral)',
              [`border${v === 'top' ? 'TopLeftRadius' : 'BottomLeftRadius'}`]: h === 'left' ? 28 : 0,
              borderRadius: 6 }} />
          ))}
        </div>
      </div>
      <div style={{ position: 'absolute', top: 'max(16px, env(safe-area-inset-top))', left: 0, right: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 16px' }}>
        <button onClick={onClose} className="gs-press" style={{ width: 40, height: 40, borderRadius: '50%', border: 'none', background: 'rgba(255,255,255,0.16)', backdropFilter: 'blur(8px)', display: 'grid', placeItems: 'center', cursor: 'pointer' }}>
          <Icon name="x" size={20} color="#fff" />
        </button>
        <span style={{ color: '#fff', fontWeight: 800, fontSize: 15, textShadow: '0 1px 6px rgba(0,0,0,0.5)' }}>Scan QR Check-in</span>
        <span style={{ width: 40 }} />
      </div>
      <div style={{ position: 'absolute', left: 24, right: 24, bottom: 'max(40px, env(safe-area-inset-bottom))', textAlign: 'center' }}>
        {err
          ? <div style={{ padding: '14px 16px', borderRadius: 'var(--r-card)', background: 'rgba(255,90,95,0.92)', color: '#fff', fontSize: 13.5, fontWeight: 600, lineHeight: 1.5 }}>{err}</div>
          : <div style={{ color: 'rgba(255,255,255,0.92)', fontSize: 14, fontWeight: 600, textShadow: '0 1px 6px rgba(0,0,0,0.6)' }}>Arahkan kamera ke QR tempat untuk check-in & kumpulkan poin</div>}
      </div>
    </div>
  );
}

// ---------- confetti ----------
function Confetti({ n = 44 }) {
  const cols = ['#1B4FD8', '#FF5A5F', '#22C55E', '#F59E0B', '#7C3AED', '#0EA5E9', '#EC4899'];
  const bits = React.useMemo(() => Array.from({ length: n }, (_, i) => ({
    left: Math.random() * 100, delay: Math.random() * 0.5, dur: 1.6 + Math.random() * 1.4,
    col: cols[i % cols.length], size: 6 + Math.random() * 7, round: Math.random() > 0.5,
  })), [n]);
  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none', zIndex: 1 }}>
      {bits.map((b, i) => (
        <span key={i} style={{ position: 'absolute', top: '-6vh', left: b.left + '%', width: b.size, height: b.size,
          background: b.col, borderRadius: b.round ? '50%' : 2, animation: `gsConfetti ${b.dur}s var(--ease) ${b.delay}s forwards` }} />
      ))}
    </div>
  );
}

// ---------- celebrate / result ----------
function CheckinResult({ state, result, error, placeName, onClose, onViewPlace, onRetry, onRewards }) {
  const ok = state === 'success';
  const pts = result ? (result.points_awarded || 0) : 0;
  const badges = (result && result.new_badges) || [];
  const notes = (result && result.bonus_notes) || [];
  const streak = result ? (result.streak || 0) : 0;

  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 100, background: ok ? 'var(--grad-hero)' : 'var(--surface)', display: 'flex', flexDirection: 'column', animation: 'gsFade 200ms var(--ease)' }}>
      {ok && <Confetti />}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: 30, position: 'relative', zIndex: 2 }}>
        {state === 'processing' ? (
          <React.Fragment>
            <div style={{ width: 76, height: 76, borderRadius: '50%', border: '4px solid var(--border)', borderTopColor: 'var(--primary)', animation: 'spin 0.8s linear infinite' }} />
            <style>{'@keyframes spin{to{transform:rotate(360deg)}}'}</style>
            <p style={{ marginTop: 20, fontSize: 15, fontWeight: 700, color: 'var(--text-2)' }}>Memproses check-in...</p>
          </React.Fragment>
        ) : ok ? (
          <React.Fragment>
            <div style={{ width: 96, height: 96, borderRadius: '50%', background: 'rgba(255,255,255,0.2)', display: 'grid', placeItems: 'center', animation: 'gsPop 420ms var(--ease)' }}>
              <span style={{ width: 68, height: 68, borderRadius: '50%', background: '#fff', display: 'grid', placeItems: 'center' }}><Icon name="check" size={40} color="var(--lime)" sw={3} /></span>
            </div>
            <h2 style={{ fontSize: 25, fontWeight: 800, color: '#fff', marginTop: 22, letterSpacing: '-0.02em' }}>Check-in berhasil!</h2>
            <p style={{ fontSize: 15, color: 'rgba(255,255,255,0.9)', marginTop: 4 }}>{result.place_name}</p>
            <div style={{ margin: '20px 0 6px', display: 'flex', alignItems: 'baseline', gap: 6, color: '#fff' }}>
              <span style={{ fontSize: 20, fontWeight: 800 }}>+</span>
              <CountUp value={pts} style={{ fontSize: 56, fontWeight: 800, lineHeight: 1, fontFamily: 'var(--mono)' }} />
              <span style={{ fontSize: 20, fontWeight: 800 }}>poin</span>
            </div>
            {notes.length > 0 && (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, justifyContent: 'center', marginTop: 6, maxWidth: 300 }}>
                {notes.map((nt, i) => <span key={i} style={{ padding: '4px 11px', borderRadius: 'var(--r-chip)', background: 'rgba(255,255,255,0.2)', color: '#fff', fontSize: 12, fontWeight: 700 }}>{nt}</span>)}
              </div>
            )}
            {streak >= 2 && <div style={{ marginTop: 14, display: 'inline-flex', alignItems: 'center', gap: 7, padding: '8px 16px', borderRadius: 'var(--r-chip)', background: 'rgba(255,255,255,0.16)', color: '#fff', fontSize: 15, fontWeight: 800 }}><span style={{ fontSize: 20 }}>🔥</span> Streak {streak} hari!</div>}

            {badges.length > 0 && (
              <div style={{ marginTop: 22, width: '100%', maxWidth: 340 }}>
                <div style={{ fontSize: 12.5, fontWeight: 800, color: 'rgba(255,255,255,0.9)', letterSpacing: '0.08em', marginBottom: 10 }}>🎉 BADGE BARU</div>
                <div style={{ display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap' }}>
                  {badges.map((b, i) => (
                    <div key={b.code || i} style={{ background: 'var(--surface)', borderRadius: 'var(--r-card)', padding: '14px 16px', minWidth: 120, boxShadow: 'var(--sh-2)', animation: `gsBadgeUnlock 600ms var(--ease) ${i * 180}ms both` }}>
                      <div style={{ fontSize: 38 }}>{b.icon || '🏅'}</div>
                      <div style={{ fontSize: 14, fontWeight: 800, color: 'var(--text-1)', marginTop: 4 }}>{b.name}</div>
                      <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2, lineHeight: 1.35 }}>{b.descr}</div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {onRewards && (
              <button onClick={onRewards} className="gs-press" style={{ marginTop: 22, display: 'inline-flex', alignItems: 'center', gap: 8, padding: '12px 20px', borderRadius: 'var(--r-chip)', border: 'none', cursor: 'pointer', background: 'rgba(255,255,255,0.94)', color: 'var(--primary)', fontFamily: 'var(--font)', fontWeight: 800, fontSize: 14.5, boxShadow: 'var(--sh-2)' }}>
                <span style={{ fontSize: 17 }}>🎁</span> Tukar poinmu
              </button>
            )}
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div style={{ width: 84, height: 84, borderRadius: '50%', background: 'var(--coral-50)', display: 'grid', placeItems: 'center' }}>
              <Icon name={/segera/i.test(error || '') ? 'clock' : 'shield'} size={40} color="var(--coral-700)" />
            </div>
            <h2 style={{ fontSize: 21, fontWeight: 800, color: 'var(--text-1)', marginTop: 20 }}>{placeName || 'Check-in belum berhasil'}</h2>
            <p style={{ fontSize: 14.5, color: 'var(--text-2)', marginTop: 8, maxWidth: 290, lineHeight: 1.5 }}>{error || 'Terjadi kesalahan. Coba lagi.'}</p>
          </React.Fragment>
        )}
      </div>

      {state !== 'processing' && (
        <div style={{ padding: '16px 20px', paddingBottom: 'max(24px, env(safe-area-inset-bottom))', position: 'relative', zIndex: 2, display: 'flex', gap: 10 }}>
          {ok && result.place_id && onViewPlace && (
            <button onClick={() => onViewPlace(result.place_id)} className="gs-press" style={{ flex: 1, padding: 15, borderRadius: 'var(--r-chip)', border: 'none', background: 'rgba(255,255,255,0.2)', color: '#fff', fontWeight: 800, fontSize: 15, cursor: 'pointer', fontFamily: 'var(--font)' }}>Lihat tempat</button>
          )}
          {!ok && onRetry && <button onClick={onRetry} className="gs-press" style={{ flex: 1, padding: 15, borderRadius: 'var(--r-chip)', border: '1px solid var(--border)', background: 'var(--surface)', color: 'var(--primary)', fontWeight: 800, fontSize: 15, cursor: 'pointer', fontFamily: 'var(--font)' }}>Scan lagi</button>}
          <button onClick={onClose} className="gs-press" style={{ flex: 1, padding: 15, borderRadius: 'var(--r-chip)', border: 'none', background: ok ? '#fff' : 'var(--primary)', color: ok ? 'var(--primary)' : '#fff', fontWeight: 800, fontSize: 15, cursor: 'pointer', fontFamily: 'var(--font)' }}>{ok ? 'Selesai' : 'Tutup'}</button>
        </div>
      )}
    </div>
  );
}

// ---------- check-in flow controller ----------
function CheckinFlow({ initialToken, authed, onRequireAuth, onClose, onDone, onViewPlace, onRewards }) {
  const [state, setState] = React.useState(initialToken ? 'processing' : 'scan');
  const [result, setResult] = React.useState(null);
  const [error, setError] = React.useState('');
  const [placeName, setPlaceName] = React.useState('');

  const process = React.useCallback(async (token) => {
    setState('processing'); setError('');
    if (!authed) { onRequireAuth && onRequireAuth(); onClose(); return; }
    const geo = await getGeo();
    const res = await gsData.doCheckin(token, geo ? geo.lat : null, geo ? geo.lng : null);
    if (res.notReady) { setError('Fitur check-in segera aktif. Coba lagi sebentar lagi ya.'); setState('error'); return; }
    if (res.error) { setError(res.error); setPlaceName(res.place_name || ''); setState('error'); return; }
    setResult(res.data); setState('success');
    if (onDone) onDone(res.data);
  }, [authed]);

  React.useEffect(() => { if (initialToken) process(initialToken); }, []);

  if (state === 'scan') return <CheckinScanner onClose={onClose} onToken={process} />;
  return <CheckinResult state={state} result={result} error={error} placeName={placeName}
    onClose={onClose} onViewPlace={onViewPlace} onRewards={onRewards} onRetry={() => { setResult(null); setState('scan'); }} />;
}

// ---------- leaderboard ----------
function LeaderboardScreen({ profile, onClose }) {
  const [scope, setScope] = React.useState('week');
  const [rows, setRows] = React.useState(null);
  const [notReady, setNotReady] = React.useState(false);

  React.useEffect(() => {
    setRows(null);
    gsData.loadLeaderboard(scope).then(r => { if (r.notReady) { setNotReady(true); setRows([]); } else setRows(r.rows); });
  }, [scope]);

  const myId = profile && profile.id;
  const medal = ['🥇', '🥈', '🥉'];

  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 92, background: 'var(--bg)', display: 'flex', flexDirection: 'column', animation: 'gsFade 180ms var(--ease)' }}>
      <div style={{ paddingTop: 'max(12px, env(safe-area-inset-top))', background: 'var(--grad-hero)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '6px 16px 14px' }}>
          <button onClick={onClose} className="gs-press" style={{ width: 36, height: 36, borderRadius: '50%', border: 'none', background: 'rgba(255,255,255,0.2)', display: 'grid', placeItems: 'center', cursor: 'pointer' }}><Icon name="chevL" size={20} color="#fff" /></button>
          <h2 style={{ fontSize: 20, fontWeight: 800, color: '#fff', letterSpacing: '-0.02em' }}>Top Warga</h2>
          <span style={{ marginLeft: 'auto', fontSize: 26 }}>🏆</span>
        </div>
        <div style={{ display: 'flex', gap: 8, padding: '0 16px 14px' }}>
          {[['week', 'Minggu ini'], ['all', 'Semua waktu']].map(([k, l]) => (
            <button key={k} onClick={() => setScope(k)} className="gs-press" style={{ flex: 1, padding: '10px', borderRadius: 'var(--r-chip)', cursor: 'pointer', fontFamily: 'var(--font)', fontWeight: 800, fontSize: 13.5, border: 'none',
              background: scope === k ? '#fff' : 'rgba(255,255,255,0.18)', color: scope === k ? 'var(--primary)' : '#fff' }}>{l}</button>
          ))}
        </div>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: '16px 16px 30px', scrollbarWidth: 'none' }}>
        {rows === null ? (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>{[0, 1, 2, 3, 4].map(i => <Skeleton key={i} h={64} r={14} />)}</div>
        ) : notReady ? (
          <EmptyState icon="trophy" grad="var(--grad-amber)" title="Papan peringkat segera aktif" sub="Fitur check-in sedang disiapkan. Cek lagi sebentar lagi ya." />
        ) : rows.length === 0 ? (
          <EmptyState icon="trophy" grad="var(--grad-amber)" title="Belum ada yang check-in" sub="Jadi warga pertama yang check-in minggu ini!" />
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
            {rows.map((r, i) => {
              const me = myId && r.user_id === myId;
              return (
                <div key={r.user_id} style={{ display: 'flex', alignItems: 'center', gap: 13, padding: '12px 14px', borderRadius: 'var(--r-card)',
                  background: me ? 'var(--primary-soft)' : 'var(--surface)', border: me ? '1.5px solid var(--primary)' : '1px solid var(--border)', boxShadow: 'var(--sh-1)' }}>
                  <span style={{ width: 30, textAlign: 'center', fontSize: i < 3 ? 22 : 15, fontWeight: 800, color: 'var(--text-3)' }} className={i < 3 ? '' : 'mono'}>{i < 3 ? medal[i] : (i + 1)}</span>
                  <span style={{ width: 40, height: 40, borderRadius: '50%', overflow: 'hidden', background: 'var(--grad-blue)', display: 'grid', placeItems: 'center', color: '#fff', fontWeight: 800, fontSize: 15, flexShrink: 0 }}>
                    {r.avatar_url ? <img src={r.avatar_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : (r.name || 'W')[0].toUpperCase()}
                  </span>
                  <span style={{ flex: 1, minWidth: 0 }}>
                    <span style={{ display: 'block', fontSize: 14.5, fontWeight: 800, color: 'var(--text-1)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name}{me ? ' (kamu)' : ''}</span>
                    <span style={{ fontSize: 12, color: 'var(--text-3)' }}>{r.checkins} check-in</span>
                  </span>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontWeight: 800, color: 'var(--primary)', fontSize: 15 }}>
                    <Icon name="bolt" size={15} color="var(--amber)" /> <span className="mono">{r.points}</span>
                  </span>
                </div>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

// ---------- badges grid (profile tab) ----------
function BadgesGrid() {
  const [data, setData] = React.useState(null);
  const [notReady, setNotReady] = React.useState(false);
  React.useEffect(() => { gsData.loadBadges().then(d => { if (d.notReady) { setNotReady(true); setData({ badges: [], earned: {} }); } else setData(d); }); }, []);

  if (notReady) return <div style={{ padding: '16px 4px' }}><EmptyState icon="award" grad="var(--grad-violet)" title="Badge segera aktif" sub="Mulai check-in di tempat untuk mengumpulkan badge." /></div>;
  if (data === null) return <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, padding: '4px 0' }}>{[0, 1, 2, 3].map(i => <Skeleton key={i} h={120} r={16} />)}</div>;

  const earnedCount = Object.keys(data.earned).length;
  return (
    <div style={{ padding: '4px 0 0' }}>
      <div style={{ fontSize: 12.5, color: 'var(--text-3)', fontWeight: 600, marginBottom: 12 }}>{earnedCount} dari {data.badges.length} badge terkumpul</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        {data.badges.map(b => {
          const got = !!data.earned[b.code];
          return (
            <div key={b.code} style={{ padding: '16px 14px', borderRadius: 'var(--r-card)', textAlign: 'center', border: '1px solid var(--border)',
              background: got ? 'var(--surface)' : 'var(--surface-2)', boxShadow: got ? 'var(--sh-1)' : 'none', opacity: got ? 1 : 0.7 }}>
              <div style={{ fontSize: 40, filter: got ? 'none' : 'grayscale(1)', opacity: got ? 1 : 0.5 }}>{b.icon || '🏅'}</div>
              <div style={{ fontSize: 14, fontWeight: 800, color: 'var(--text-1)', marginTop: 6 }}>{b.name}</div>
              <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 3, lineHeight: 1.4 }}>{b.descr}</div>
              {got
                ? <div style={{ marginTop: 8, display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11, fontWeight: 800, color: 'var(--lime-700)' }}><Icon name="check" size={13} color="var(--lime-700)" sw={2.6} /> Didapat</div>
                : <div style={{ marginTop: 8, fontSize: 11, fontWeight: 700, color: 'var(--text-3)' }}>🔒 Terkunci</div>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Object.assign(window, { CheckinFlow, CheckinScanner, CheckinResult, LeaderboardScreen, BadgesGrid, Confetti, parseCheckinToken, getGeo });
