// 그란데 독서타임 — 로그인 (Google Identity Services)
//
// 흐름: GIS 버튼 → id_token → Apps Script 가 검증 → 세션 토큰(HMAC 서명, 30일)
//   - 검증 관문 3개: 도메인 화이트리스트 → 구성원 시트 등재 → 재직/휴직 상태
//   - 세션 토큰에 서명이 있어 localStorage 를 손으로 고쳐도 서버에서 튕긴다
//   - 화면에 뿌리는 이름은 표시 전용. 권한 판정은 전부 Apps Script 에서 한다
//
// GIS 버튼은 반드시 #scaler 밖(document.body portal)에 그려야 한다.
// scaler 의 transform: scale() 이 버튼 내부 px 과 position:fixed 기준을 둘 다 어긋나게 만든다.

const GIS_CLIENT_ID = '1096551149192-5n5kr5r6ufio1uk4vg5n0g1268mia58o.apps.googleusercontent.com';
const AUTH_ENDPOINT = 'https://script.google.com/macros/s/AKfycbwRygO5DaybmUOnVTGm78vO2MgV24xHnkG6w7y-j4MWOn9jimwCII_kQnwXD0AY07Fo/exec';
const SESSION_KEY = 'grandeReadingTime.session.v1';

// 저장된 세션 — { session, user, exp }. 만료는 서버가 최종 판정하고, 여기선 조기 정리만.
function readSession() {
  try {
    const raw = localStorage.getItem(SESSION_KEY);
    if (!raw) return null;
    const s = JSON.parse(raw);
    if (!s || !s.session || !s.user) return null;
    if (s.exp && Date.now() > s.exp) { localStorage.removeItem(SESSION_KEY); return null; }
    return s;
  } catch (e) { return null; }
}

function writeSession(s) {
  try {
    if (s) localStorage.setItem(SESSION_KEY, JSON.stringify(s));
    else localStorage.removeItem(SESSION_KEY);
  } catch (e) {}
}

// GIS 스크립트가 로드될 때까지 대기 (index.html 에서 async 로 붙는다)
function waitForGis() {
  return new Promise((resolve, reject) => {
    if (window.google && window.google.accounts && window.google.accounts.id) { resolve(); return; }
    let tries = 0;
    const timer = setInterval(() => {
      if (window.google && window.google.accounts && window.google.accounts.id) {
        clearInterval(timer); resolve();
      } else if (++tries > 100) {          // 10초
        clearInterval(timer); reject(new Error('구글 로그인 스크립트를 불러오지 못했어요.'));
      }
    }, 100);
  });
}

async function exchangeToken(idToken) {
  const res = await fetch(AUTH_ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain;charset=utf-8' },
    body: JSON.stringify({ action: 'login', idToken }),
    redirect: 'follow',
  });
  const text = await res.text();
  try { return JSON.parse(text); }
  catch (e) { return { ok: false, error: '서버 응답을 읽지 못했어요.' }; }
}

const listeners = new Set();
let current = readSession();

function setCurrent(s) {
  current = s;
  writeSession(s);
  listeners.forEach(fn => { try { fn(s); } catch (e) {} });
}

// ---------- 숨긴 GIS 버튼 ----------
// 우리 '로그인' 글자를 누르면 곧바로 구글 계정 선택 창이 뜨게 하려고,
// 구글 버튼을 화면 밖에 미리 그려두고 프로그램으로 클릭한다.
// (One Tap 은 사용자가 몇 번 닫으면 한동안 안 뜨는 쿨다운이 있어 쓰지 않는다)
let hiddenBox = null;
let hiddenReady = null;
let pendingResolve = null;

function ensureHiddenButton() {
  if (hiddenReady) return hiddenReady;
  hiddenReady = (async () => {
    await waitForGis();
    hiddenBox = document.createElement('div');
    hiddenBox.setAttribute('aria-hidden', 'true');
    hiddenBox.style.cssText = 'position:fixed;left:-9999px;top:-9999px;width:280px;opacity:0;pointer-events:none;';
    document.body.appendChild(hiddenBox);
    window.google.accounts.id.initialize({
      client_id: GIS_CLIENT_ID,
      callback: async (resp) => {
        const r = await exchangeToken(resp && resp.credential).catch(() => null);
        if (r && r.ok && r.session) {
          setCurrent({ session: r.session, user: r.user, exp: Date.now() + 30 * 86400000 });
          if (pendingResolve) { pendingResolve(null); pendingResolve = null; }
        } else if (pendingResolve) {
          pendingResolve((r && r.error) || '로그인에 실패했어요.');
          pendingResolve = null;
        }
      },
    });
    window.google.accounts.id.renderButton(hiddenBox, {
      theme: 'outline', size: 'large', text: 'signin_with', locale: 'ko', width: 280,
    });
    // 버튼 내부 DOM 이 실제로 생길 때까지 잠깐 대기
    for (let i = 0; i < 40 && !hiddenBox.querySelector('div[role="button"]'); i++) {
      await new Promise(r => setTimeout(r, 50));
    }
    return hiddenBox.querySelector('div[role="button"]');
  })();
  return hiddenReady;
}

window.authStore = {
  CLIENT_ID: GIS_CLIENT_ID,
  get session() { return current ? current.session : ''; },
  get user() { return current ? current.user : null; },
  isAdmin() { return !!(current && current.user && current.user.role === 'admin'); },
  subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
  logout() {
    try {
      if (window.google && window.google.accounts && window.google.accounts.id) {
        window.google.accounts.id.disableAutoSelect();
      }
    } catch (e) {}
    setCurrent(null);
  },
  // '로그인' 클릭 → 구글 계정 선택 창 바로 띄우기.
  // 숨긴 버튼을 못 찾으면 false 를 돌려주고, 호출측이 폴백 모달을 연다.
  async login() {
    try {
      const btn = await ensureHiddenButton();
      if (!btn) return false;
      return await new Promise((resolve) => {
        pendingResolve = (err) => resolve(err ? { error: err } : true);
        btn.click();
        // 사용자가 구글 창을 그냥 닫으면 콜백이 안 온다 → 영원히 대기하지 않게 정리
        setTimeout(() => { if (pendingResolve) { pendingResolve = null; resolve(true); } }, 120000);
      });
    } catch (e) {
      return false;
    }
  },
  // GIS 버튼을 el 안에 그린다. 로그인 성공 시 onDone(null), 실패 시 onDone(에러메시지)
  async renderButton(el, onDone) {
    await waitForGis();
    // initialize 는 전역 콜백을 하나만 가진다. 여기서 다시 부르면 숨긴 버튼의 콜백이 덮이므로,
    // 다음 '로그인' 클릭 때 숨긴 버튼을 새로 만들게 한다.
    if (hiddenBox) { hiddenBox.remove(); hiddenBox = null; }
    hiddenReady = null;
    window.google.accounts.id.initialize({
      client_id: GIS_CLIENT_ID,
      callback: async (resp) => {
        const r = await exchangeToken(resp && resp.credential).catch(() => null);
        if (r && r.ok && r.session) {
          setCurrent({ session: r.session, user: r.user, exp: Date.now() + 30 * 86400000 });
          onDone(null);
        } else {
          onDone((r && r.error) || '로그인에 실패했어요.');
        }
      },
    });
    window.google.accounts.id.renderButton(el, {
      theme: 'outline', size: 'large', text: 'signin_with',
      shape: 'rectangular', locale: 'ko', width: 280,
    });
  },
};

// ---------- 로그인 모달 ----------
// nav 의 '로그인' 을 누르면 열린다. body 로 portal 해서 scaler 영향을 받지 않는다.
// initialError: 바로 뜨는 구글 창에서 로그인이 거부됐을 때(명단에 없음 등) 그 사유를 보여준다.
const LoginModal = ({ open, onClose, initialError = '' }) => {
  const t = window.tokens;
  const boxRef = React.useRef(null);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    if (!open) { setErr(''); return; }
    let alive = true;
    setErr(initialError);
    window.authStore.renderButton(boxRef.current, (e) => {
      if (!alive) return;
      if (e) setErr(e); else onClose();
    }).catch(e => { if (alive) setErr(e.message || '구글 로그인을 불러오지 못했어요.'); });
    return () => { alive = false; };
  }, [open]);

  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = prev; };
  }, [open]);

  if (!open) return null;

  return ReactDOM.createPortal(
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, zIndex: 10000,
        background: 'rgba(10,10,10,0.55)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontFamily: t.font.sans, padding: 20,
      }}
    >
      <div onClick={(e) => e.stopPropagation()} style={{
        background: '#fff', width: 380, maxWidth: '100%', borderRadius: 14,
        padding: '34px 34px 30px', position: 'relative',
      }}>
        <div onClick={onClose} style={{
          position: 'absolute', top: 14, right: 18, fontSize: 24,
          color: t.color.mutedLight, cursor: 'pointer', lineHeight: 1,
        }}>×</div>

        <div style={{ fontSize: 15, color: t.color.muted, fontWeight: 500 }}>그란데 독서타임</div>
        <div style={{ fontSize: 26, fontWeight: 800, letterSpacing: '-0.03em', marginTop: 4 }}>로그인</div>
        <div style={{ fontSize: 14, color: t.color.muted, lineHeight: 1.6, marginTop: 12 }}>
          회사 구글 계정으로 로그인하면 책을 등록하고<br />내 기록을 관리할 수 있어요.
        </div>

        <div ref={boxRef} style={{ marginTop: 24, display: 'flex', justifyContent: 'center', minHeight: 44 }} />

        {err && (
          <div style={{ fontSize: 13, color: '#C53030', marginTop: 16, lineHeight: 1.5 }}>{err}</div>
        )}
      </div>
    </div>,
    document.body
  );
};

window.LoginModal = LoginModal;

// ---------- 마이페이지 ----------
// nav 의 이름을 누르면 열린다. 내 서재(내가 올린 책) + 로그아웃.
// 현재는 이름으로 내 책을 찾는다. ownerEmail 컬럼이 생기면 그 키로 바꾼다.
const MyPageModal = ({ open, onClose, books, me, onEditBook, onAddBook }) => {
  const t = window.tokens;
  const store = window.bookStore;

  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = prev; };
  }, [open]);

  if (!open || !me) return null;

  // 소유자는 ownerEmail 이 원천. 아직 안 채워진 행은 이름으로 폴백한다.
  const mine = (books || []).filter(b => (
    b.ownerEmail ? b.ownerEmail === me.email
                 : (store.readersOf(b) || []).indexOf(me.name) !== -1
  ));
  const pages = mine.reduce((s, b) => s + store.readPagesOf(b), 0);
  const cm = store.pagesToCm(pages);
  const doneCount = mine.filter(b => b.done).length;

  const Stat = ({ label, value, unit }) => (
    <div>
      <div style={{ fontSize: 12, color: t.color.muted, fontWeight: 500 }}>{label}</div>
      <div style={{ fontSize: 26, fontWeight: 800, letterSpacing: '-0.03em', marginTop: 3 }}>
        {value}<span style={{ fontSize: 13, color: t.color.muted, fontWeight: 600, marginLeft: 2 }}>{unit}</span>
      </div>
    </div>
  );

  return ReactDOM.createPortal(
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, zIndex: 10000,
        background: 'rgba(10,10,10,0.55)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontFamily: t.font.sans, padding: 20,
      }}
    >
      <div onClick={(e) => e.stopPropagation()} style={{
        background: '#fff', width: 560, maxWidth: '100%', maxHeight: '86vh',
        borderRadius: 14, position: 'relative', display: 'flex', flexDirection: 'column',
      }}>
        <div onClick={onClose} style={{
          position: 'absolute', top: 14, right: 18, fontSize: 24,
          color: t.color.mutedLight, cursor: 'pointer', lineHeight: 1, zIndex: 2,
        }}>×</div>

        {/* 머리 */}
        <div style={{ padding: '30px 30px 22px', borderBottom: `1px solid ${t.color.ruleSoft}` }}>
          <div style={{ fontSize: 26, fontWeight: 800, letterSpacing: '-0.03em' }}>{me.name}</div>
          <div style={{ fontSize: 13, color: t.color.muted, marginTop: 5 }}>
            {[me.org, me.team].filter(Boolean).join(' · ') || me.email}
          </div>

          <div style={{ display: 'flex', gap: 34, marginTop: 22 }}>
            <Stat label="읽은 책" value={mine.length} unit="권" />
            <Stat label="완독" value={doneCount} unit="권" />
            <Stat label="누적" value={pages.toLocaleString()} unit="쪽" />
            <Stat label="책탑" value={cm} unit="cm" />
          </div>
        </div>

        {/* 내 서재 */}
        <div style={{ padding: '20px 30px 8px', display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
          <div style={{ fontSize: 15, fontWeight: 700, letterSpacing: '-0.02em' }}>내 서재</div>
          <div onClick={onAddBook} style={{ fontSize: 13, color: t.color.blue, fontWeight: 600, cursor: 'pointer' }}>
            + 책 추가
          </div>
        </div>

        <div style={{ overflowY: 'auto', padding: '0 30px', flex: 1 }}>
          {mine.length === 0 ? (
            <div style={{ fontSize: 13, color: t.color.muted, padding: '18px 0 26px', lineHeight: 1.6 }}>
              아직 올린 책이 없어요. 위 <b style={{ color: t.color.blue }}>+ 책 추가</b>로 첫 책을 남겨보세요.
            </div>
          ) : mine.map(b => {
            const total = b.pages || 0;
            const read = store.readPagesOf(b);
            const pct = total > 0 ? Math.round((read / total) * 100) : 0;
            return (
              <div key={b.id} style={{
                display: 'flex', alignItems: 'center', gap: 14,
                padding: '13px 0', borderTop: `1px solid ${t.color.ruleSoft}`,
              }}>
                <div style={{ flex: '0 0 34px', height: 50, background: t.color.cream, overflow: 'hidden' }}>
                  {b.thumbnail && <img src={b.thumbnail} alt="" loading="lazy"
                    style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 14, fontWeight: 700, letterSpacing: '-0.02em', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{b.title}</div>
                  <div style={{ fontSize: 12, color: t.color.muted, marginTop: 2 }}>
                    {b.author || '저자 미상'}
                    <span style={{ color: t.color.mutedLight, margin: '0 6px' }}>·</span>
                    {b.done ? <b style={{ color: t.color.blue, fontWeight: 700 }}>완독</b>
                            : (store.hasProgress(b) && total > 0 ? `${pct}%` : '읽는 중')}
                  </div>
                </div>
                <div onClick={() => onEditBook(b)} style={{
                  fontSize: 12, color: t.color.muted, cursor: 'pointer', fontWeight: 600,
                  padding: '5px 11px', border: `1px solid ${t.color.ruleSoft}`,
                }}>편집</div>
              </div>
            );
          })}
        </div>

        {/* 발 */}
        <div style={{
          padding: '16px 30px 22px', borderTop: `1px solid ${t.color.ruleSoft}`,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        }}>
          <div style={{ fontSize: 12, color: t.color.mutedLight }}>{me.email}</div>
          <div
            onClick={() => { window.authStore.logout(); onClose(); }}
            style={{
              fontSize: 13, fontWeight: 600, color: t.color.muted, cursor: 'pointer',
              padding: '9px 18px', border: `1px solid ${t.color.ruleSoft}`, background: '#fff',
            }}
            onMouseEnter={e => { e.currentTarget.style.color = '#C53030'; }}
            onMouseLeave={e => { e.currentTarget.style.color = t.color.muted; }}
          >로그아웃</div>
        </div>
      </div>
    </div>,
    document.body
  );
};

window.MyPageModal = MyPageModal;
