// App shell — auth-gated routing

const COGNITO_CLIENT = '7nffjheavp985k7b4bgug45jf4';

const RAW_PAGES = {
  landing:   { url: 'getfossyl.dev',                     gate: 'public'  },
  login:     { url: 'app.getfossyl.dev/signin',           gate: 'guest'   },
  signup:    { url: 'app.getfossyl.dev/signup',           gate: 'guest'   },
  pricing:   { url: 'getfossyl.dev/pricing',              gate: 'pricing' },
  upload:    { url: 'app.getfossyl.dev',                  gate: 'authed'  },
  report:    { url: 'app.getfossyl.dev/report',           gate: 'authed'  },
  reports:   { url: 'app.getfossyl.dev/reports',          gate: 'authed'  },
  billing:   { url: 'app.getfossyl.dev/settings/billing', gate: 'authed'  },
  account:   { url: 'app.getfossyl.dev/settings/account', gate: 'authed'  },
};

function resolveRoute(target, { authed, paid }) {
  const def = RAW_PAGES[target] || RAW_PAGES.landing;
  switch (def.gate) {
    case 'guest':   return authed ? 'upload' : target;
    case 'pricing': return !authed ? 'login' : (paid ? 'upload' : target);
    case 'authed':  return authed ? target : 'login';
    case 'paid':    return !authed ? 'login' : (paid ? target : 'pricing');
    default:        return target;
  }
}

// ── Token helpers ─────────────────────────────────────────────────────────────
function getCognitoIdToken() {
  const last = localStorage.getItem(`CognitoIdentityServiceProvider.${COGNITO_CLIENT}.LastAuthUser`);
  if (!last) return null;
  return localStorage.getItem(`CognitoIdentityServiceProvider.${COGNITO_CLIENT}.${last}.idToken`);
}

function isTokenFresh(token) {
  if (!token) return false;
  try {
    const { exp } = JSON.parse(atob(token.split('.')[1]));
    return exp * 1000 > Date.now() + 30_000; // 30s buffer
  } catch { return false; }
}

// Global getter used by all API helpers across pages
window.getIdToken = () => getCognitoIdToken() || '';

// Single source of truth for the signatures-only payload description.
// Must enumerate exactly the field types emitted by the extractor allow-list
// in fossyl/analysis/intent_inferrer.py. The copy-drift validator in graph.jsx
// asserts this string contains all required tokens and no forbidden ones.
window.FOSSYL_SIGNATURES_PAYLOAD_DESC =
  "## Zope header directives (Parameters, Returns, Bind), " +
  "import and from-import module names, " +
  "function and class signatures with parameter names and type annotations " +
  "(no default values or bodies), " +
  "and METAL macro/slot names";

// ── Mount ─────────────────────────────────────────────────────────────────────
function Mount() {
  const [authed, setAuthed] = useState(() => localStorage.getItem('fossyl_authed') === '1');
  const [paid,   setPaid]   = useState(() => localStorage.getItem('fossyl_paid')   === '1');
  const [hasRun, setHasRun] = useState(() => localStorage.getItem('fossyl_run')    === '1');
  const [plan,   setPlan]   = useState(() => localStorage.getItem('fossyl_plan')   || 'team');
  const [role,   setRole]   = useState(() => localStorage.getItem('fossyl_role')   || 'Owner');
  const [page,   setPage]   = useState(() => localStorage.getItem('fossyl_page')   || 'landing');
  const [sessionExpired, setSessionExpired] = useState(false);
  const [ghAuthError,    setGhAuthError]    = useState(null);
  const [pendingInvite,    setPendingInvite]    = useState(null);
  const [acceptingInvite,  setAcceptingInvite]  = useState(false);
  const [pendingTransfer,  setPendingTransfer]  = useState(null);
  const [acceptingTransfer,setAcceptingTransfer]= useState(false);

  // ── Stripe Checkout redirect ───────────────────────────────────────────────
  useEffect(() => {
    const search = window.location.search;
    if (!search) return;
    const params = new URLSearchParams(search);

    if (params.get('checkout_cancel')) {
      window.history.replaceState(null, '', '/');
      setPage('billing');
      return;
    }

    if (params.get('checkout_success') && params.get('session_id')) {
      const sessionId = params.get('session_id');
      window.history.replaceState(null, '', '/');
      const token = localStorage.getItem(
        `CognitoIdentityServiceProvider.${COGNITO_CLIENT}.` +
        `${localStorage.getItem(`CognitoIdentityServiceProvider.${COGNITO_CLIENT}.LastAuthUser`)}.idToken`
      );
      fetch(
        `https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod/billing/verify?session_id=${encodeURIComponent(sessionId)}`,
        { headers: token ? { Authorization: `Bearer ${token}` } : {} }
      )
        .then(r => r.json())
        .then(data => {
          if (data.status === 'active') {
            setAuthed(true);
            setPaid(true);
            setPlan(data.plan || 'team');
            setSessionExpired(false);
            setPage('upload');
          } else {
            setPage('billing');
          }
        })
        .catch(() => setPage('billing'));
    }
  }, []); // eslint-disable-line

  // ── GitHub OAuth callback ──────────────────────────────────────────────────
  useEffect(() => {
    const hash = window.location.hash.slice(1);
    if (!hash) return;
    const params = new URLSearchParams(hash);

    if (params.get('gh_error')) {
      window.history.replaceState(null, '', '/');
      setGhAuthError(decodeURIComponent(params.get('gh_error')));
      setPage('login');
      return;
    }

    const idToken      = params.get('gh_id_token');
    const accessToken  = params.get('gh_access_token');
    const refreshToken = params.get('gh_refresh_token');
    const email        = params.get('gh_email');
    const expiry       = params.get('gh_expiry');
    if (!idToken || !email) return;

    // Store in Cognito SDK format so getCognitoIdToken() picks it up
    const prefix = `CognitoIdentityServiceProvider.${COGNITO_CLIENT}`;
    localStorage.setItem(`${prefix}.LastAuthUser`,           email);
    localStorage.setItem(`${prefix}.${email}.idToken`,       idToken);
    localStorage.setItem(`${prefix}.${email}.accessToken`,   accessToken);
    localStorage.setItem(`${prefix}.${email}.refreshToken`,  refreshToken);

    // Also store in fossyl_ keys for any consumers that use them
    localStorage.setItem('fossyl_id_token',      idToken);
    localStorage.setItem('fossyl_access_token',  accessToken);
    localStorage.setItem('fossyl_refresh_token', refreshToken);
    localStorage.setItem('fossyl_email',         email);
    if (expiry) localStorage.setItem('fossyl_token_expiry', expiry);

    window.history.replaceState(null, '', '/');
    setAuthed(true);
    setSessionExpired(false);
    // subscription check useEffect will set paid/plan once authed flips to true
  }, []); // eslint-disable-line

  // ── Token check on mount and tab focus ────────────────────────────────────
  useEffect(() => {
    function checkToken() {
      const token = getCognitoIdToken();

      if (isTokenFresh(token)) {
        // Valid token — ensure state is consistent; subscription check handles paid/plan
        if (!authed) { setAuthed(true); }
        setSessionExpired(false);
        return;
      }

      // No valid token
      if (authed) {
        // Was logged in but token expired — clear state and show expiry notice
        setAuthed(false);
        setPaid(false);
        setSessionExpired(true);
      }
    }

    // Only run the check if we think we're logged in
    // (avoids false positives on landing page before any login)
    if (localStorage.getItem('fossyl_authed') === '1') {
      checkToken();
    }

    // Guard the focus handler: after an explicit sign-out fossyl_authed is '0',
    // so we must not re-authenticate even if a stale Cognito token is still readable.
    function checkTokenIfAuthed() {
      if (localStorage.getItem('fossyl_authed') === '1') checkToken();
    }
    window.addEventListener('focus', checkTokenIfAuthed);
    return () => window.removeEventListener('focus', checkTokenIfAuthed);
  }, []); // eslint-disable-line

  // ── Subscription + role check — runs on login and on mount if already authed ─
  useEffect(() => {
    if (!authed) return;
    const token = getCognitoIdToken();
    if (!isTokenFresh(token)) return;   // stale localStorage authed flag — skip, token check will correct it
    const h = { Authorization: `Bearer ${token}` };
    const base = 'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod';
    Promise.all([
      fetch(`${base}/billing/subscription`, { headers: h }).then(r => r.json()).catch(() => ({})),
      fetch(`${base}/account/profile`,      { headers: h }).then(r => r.json()).catch(() => ({})),
    ]).then(([sub, prof]) => {
      setPaid(!!sub.paid);
      if (sub.plan)  setPlan(sub.plan);
      if (prof.role) setRole(prof.role);
    });
  }, [authed]); // eslint-disable-line

  // ── Pending invitation + transfer check ──────────────────────────────────
  useEffect(() => {
    if (!authed) return;
    const token = getCognitoIdToken();
    if (!isTokenFresh(token)) return;   // stale localStorage authed flag — skip
    const base = 'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod';
    const h    = { Authorization: `Bearer ${token}` };
    fetch(`${base}/account/invite`, { headers: h })
      .then(r => r.json())
      .then(data => { if (data.invite)    setPendingInvite(data.invite); })
      .catch(() => {});
    fetch(`${base}/account/workspace/transfer`, { headers: h })
      .then(r => r.json())
      .then(data => { if (data.transfer)  setPendingTransfer(data.transfer); })
      .catch(() => {});
  }, [authed]); // eslint-disable-line

  // ── Persist ───────────────────────────────────────────────────────────────
  useEffect(() => { localStorage.setItem('fossyl_authed', authed ? '1' : '0'); }, [authed]);
  useEffect(() => { localStorage.setItem('fossyl_paid',   paid   ? '1' : '0'); }, [paid]);
  useEffect(() => { localStorage.setItem('fossyl_run',    hasRun ? '1' : '0'); }, [hasRun]);
  useEffect(() => { localStorage.setItem('fossyl_plan',   plan);               }, [plan]);
  useEffect(() => { localStorage.setItem('fossyl_role',   role);               }, [role]);
  useEffect(() => { localStorage.setItem('fossyl_page',   page);               }, [page]);

  // ── Routing ───────────────────────────────────────────────────────────────
  useEffect(() => {
    const resolved = resolveRoute(page, { authed, paid });
    if (resolved !== page) setPage(resolved);
  }, [page, authed, paid]);

  const nav = useMemo(() => ({
    current: page,
    go: (id) => {
      const resolved = resolveRoute(id, { authed, paid });
      setPage(resolved);
      window.scrollTo(0, 0);
    },
  }), [page, authed, paid]);

  const auth = useMemo(() => ({
    authed, paid, hasRun, plan, role,
    signIn:  () => { setAuthed(true); setSessionExpired(false); },
    signUp:  () => { setAuthed(true); setSessionExpired(false); },
    signOut: () => {
      if (window.clearTokens) window.clearTokens();
      setAuthed(false); setPaid(false); setHasRun(false); setRole('Owner'); setSessionExpired(false);
    },
    setPaid, setHasRun, setPlan, setRole,
  }), [authed, paid, hasRun, plan, role]);

  const handleInviteAccept = async () => {
    setAcceptingInvite(true);
    try {
      const token = getCognitoIdToken();
      const res = await fetch(
        'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod/account/invite',
        { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'accept' }) }
      );
      const data = await res.json();
      if (data.accepted) {
        setRole(data.role || 'Member');
        setPendingInvite(null);
        // Re-check subscription since workspace/plan may have changed
        const sub = await fetch(
          'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod/billing/subscription',
          { headers: { Authorization: `Bearer ${token}` } }
        ).then(r => r.json()).catch(() => ({}));
        setPaid(!!sub.paid);
        if (sub.plan) setPlan(sub.plan);
      }
    } catch {}
    setAcceptingInvite(false);
  };

  const handleInviteDecline = async () => {
    try {
      const token = getCognitoIdToken();
      await fetch(
        'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod/account/invite',
        { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'decline' }) }
      );
    } catch {}
    setPendingInvite(null);
  };

  const handleTransferAccept = async () => {
    setAcceptingTransfer(true);
    try {
      const token = getCognitoIdToken();
      const res  = await fetch(
        'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod/account/workspace/transfer/accept',
        { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: '{}' }
      );
      const data = await res.json();
      if (data.accepted) {
        setRole('Owner');
        setPendingTransfer(null);
      }
    } catch {}
    setAcceptingTransfer(false);
  };

  const handleTransferDecline = async () => {
    try {
      const token = getCognitoIdToken();
      await fetch(
        'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod/account/workspace/transfer/decline',
        { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: '{}' }
      );
    } catch {}
    setPendingTransfer(null);
  };

  return (
    <ToastProvider>
      <AuthContext.Provider value={auth}>
        <NavContext.Provider value={nav}>
          {pendingInvite && (
            <WorkspaceJoinModal
              invite={pendingInvite}
              accepting={acceptingInvite}
              onAccept={handleInviteAccept}
              onDecline={handleInviteDecline}
            />
          )}
          {pendingTransfer && (
            <TransferOwnershipModal
              transfer={pendingTransfer}
              accepting={acceptingTransfer}
              onAccept={handleTransferAccept}
              onDecline={handleTransferDecline}
            />
          )}

          {/* GitHub auth error banner */}
          {ghAuthError && (
            <div style={{
              position: 'fixed', top: 0, left: 0, right: 0, zIndex: 300,
              background: 'var(--rust-dim)', borderBottom: '1px solid var(--rust)',
              padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 16,
              fontFamily: 'var(--mono)', fontSize: 12,
            }}>
              <span style={{ color: 'var(--rust)' }}>◈</span>
              <span style={{ color: 'var(--ink)', flex: 1 }}>GitHub sign-in failed — {ghAuthError}</span>
              <button className="btn btn-sm" onClick={() => setGhAuthError(null)}>Dismiss</button>
            </div>
          )}

          {/* Session expiry banner — shown on any authed page when token has expired */}
          {sessionExpired && page !== 'login' && (
            <div style={{
              position: 'fixed', top: 0, left: 0, right: 0, zIndex: 300,
              background: 'var(--rust-dim)', borderBottom: '1px solid var(--rust)',
              padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 16,
              fontFamily: 'var(--mono)', fontSize: 12,
            }}>
              <span style={{ color: 'var(--rust)' }}>◈</span>
              <span style={{ color: 'var(--ink)', flex: 1 }}>Session expired — please sign in again to continue.</span>
              <button className="btn btn-sm" onClick={() => { nav.go('login'); setSessionExpired(false); }}>
                Sign in →
              </button>
            </div>
          )}

          {page === 'landing'  && <Landing/>}
          {page === 'login'    && <AuthPage mode="signin"/>}
          {page === 'signup'   && <AuthPage mode="signup"/>}
          {page === 'pricing'  && <PricingPage/>}
          {page === 'upload'   && <UploadPage/>}
          {page === 'report'   && <Dashboard/>}
          {page === 'reports'  && <ReportPage/>}
          {page === 'billing'  && <BillingPage/>}
          {page === 'account'  && <AccountPage/>}
        </NavContext.Provider>
      </AuthContext.Provider>
    </ToastProvider>
  );
}

const rootEl = document.getElementById('root');
rootEl.removeAttribute('data-babel-unloaded');
ReactDOM.createRoot(rootEl).render(<Mount/>);

window.__graph = window.__graph || 'force';