// Main App — wires onboarding → room → viewer → admin.
// V2: HttpOnly session cookies via /api/invite/redeem + /api/session.

const TWEAK_DEFAULTS = {
  logoVariant: "gold-mono",
  density: "comfortable",
  showBrackets: true,
  dark: false,
  q1Title: "What best describes you?",
  q1Sub: "We tailor what you see in the room to your investor profile.",
  q2Title: "Anticipated allocation size?",
  q2Sub: "Used for routing — no commitment.",
  q3Title: "When do you expect to make a decision?",
  q3Sub: "Helps us schedule deeper diligence sessions.",
  cta: "Enter the room",
};

// Minimal useState wrapper. Production has no tweak panel UI; defaults apply.
function useTweaks(defaults) {
  const [values, setValues] = React.useState(defaults);
  const setTweak = React.useCallback((keyOrEdits, val) => {
    const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null
      ? keyOrEdits : { [keyOrEdits]: val };
    setValues(prev => ({ ...prev, ...edits }));
  }, []);
  return [values, setTweak];
}

// Track viewport-is-narrow. Drives responsive layout changes in tokens.css.
function useIsMobile(breakpoint = 768) {
  const [isMobile, setIsMobile] = React.useState(() =>
    typeof window !== 'undefined' && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
  );
  React.useEffect(() => {
    if (typeof window === 'undefined') return;
    const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
    const handler = e => setIsMobile(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [breakpoint]);
  return isMobile;
}

// URL-persisted view: ?view=admin keeps you on admin across reloads.
function readInitialView() {
  if (typeof window === 'undefined') return 'onboarding';
  const v = new URLSearchParams(window.location.search).get('view');
  return ['admin', 'room'].includes(v) ? v : 'onboarding';
}

function writeViewToUrl(view) {
  if (typeof window === 'undefined') return;
  const url = new URL(window.location.href);
  if (['admin', 'room'].includes(view)) {
    url.searchParams.set('view', view);
  } else if (view === 'onboarding') {
    url.searchParams.delete('view');
  }
  // 'viewer' is transient — not persisted to URL.
  window.history.replaceState(null, '', url.toString());
}

function App({ initialAuth }) {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const isMobile = useIsMobile();
  const auth = initialAuth || { hasSession: false, investor: null, returning: false };
  const investor = auth.investor;

  // Initial view: URL param wins; else, if session has all answers → room; else onboarding.
  const initialView = (() => {
    const urlView = readInitialView();
    if (urlView !== 'onboarding') return urlView;
    if (auth.hasSession && investor?.profile && investor?.allocation && investor?.timeline) {
      return 'room';
    }
    return 'onboarding';
  })();

  const [view, setView] = React.useState(initialView);
  const [openFileId, setOpenFileId] = React.useState(null);
  const [returning] = React.useState(!!auth.returning);
  const [identity, setIdentity] = React.useState({
    email: investor?.email || '',
    name:  investor?.name  || '',
    firm:  investor?.firm  || '',
    a1: investor?.profile    || 'Fund / RIA',
    a2: investor?.allocation || '$500K – $2M',
    a3: investor?.timeline   || '3–6 months',
  });

  // Apply theme/density/bracket attrs to <html>.
  React.useEffect(() => {
    document.documentElement.setAttribute('data-theme', t.dark ? 'dark' : 'light');
    document.documentElement.setAttribute('data-density', t.density);
    document.documentElement.setAttribute('data-brackets', t.showBrackets ? 'on' : 'off');
  }, [t.dark, t.density, t.showBrackets]);

  React.useEffect(() => { writeViewToUrl(view); }, [view]);

  const completeOnboarding = async (data) => {
    setIdentity(data);
    try {
      await fetch('/api/onboarding', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({
          profile: data.a1,
          allocation: data.a2,
          timeline: data.a3,
          name: data.name,
          firm: data.firm,
        }),
      });
    } catch (e) {
      console.warn('[onboarding] save failed:', e.message);
    }
    setView('room');
  };

  const handleLogout = async () => {
    // Remember the email so the sign-in form can pre-fill on return —
    // removes friction for the same-investor-on-same-device case.
    try {
      if (identity?.email) localStorage.setItem('arbcap_last_email', identity.email);
    } catch (e) { /* private mode / quota */ }
    try { await fetch('/api/session', { method: 'DELETE', credentials: 'include' }); } catch (e) {}
    window.location.href = window.location.pathname;
  };

  const openFile = (id) => {
    const f = ROOM_FILES.find(x => x.id === id);
    if (f && f.external) {
      window.open(f.url, '_blank', 'noopener,noreferrer');
      return;
    }
    setOpenFileId(id);
    setView('viewer');
  };

  return (
    <div style={{ minHeight: '100vh', background: 'var(--bg)', position: 'relative', overflow: 'hidden' }}>
      {view === 'onboarding' && (
        <Onboarding tweaks={t} identity={identity} onComplete={completeOnboarding} dark={t.dark} identityKnown={!!investor?.email} />
      )}
      {view === 'room' && (
        <Room tweaks={t} identity={identity} returning={returning} onOpenFile={openFile} onLogout={handleLogout} />
      )}
      {view === 'viewer' && (
        <div style={{ position: 'relative', minHeight: isMobile ? 'calc(100vh - 56px)' : '100vh' }}>
          <Viewer fileId={openFileId} identity={identity} onClose={() => setView('room')} />
        </div>
      )}
      {view === 'admin' && (
        <Admin tweaks={t} onLogout={() => setView('room')} />
      )}
    </div>
  );
}

// ─── Bootstrap: load manifest from R2, then auth, before mounting React ───
// Manifest: edit manifest.json in arcdeck → push → green CI → reflects on reload.
// Auth: ?inv= in URL → POST /api/invite/redeem (sets HttpOnly cookie) → fall through
//       to GET /api/session for returning investors with existing cookies.

function kindFromPath(path) {
  const ext = (path.split('?')[0].split('#')[0].split('.').pop() || '').toLowerCase();
  return ['pdf','html','xlsx','xls','csv','docx','doc','sol'].includes(ext) ? ext : 'link';
}

function manifestEntryToFile(d, manifestUpdated) {
  const isExternal = !!d.external_url;
  const url = isExternal ? d.external_url : `${R2_DECK_BASE}/${d.file}`;
  const displayName = isExternal ? new URL(d.external_url).hostname : d.file;
  return {
    id: d.slug,
    name: displayName,
    title: d.title,
    kind: kindFromPath(isExternal ? d.external_url : d.file),
    size: d.size || '—',
    pages: d.pages || 1,
    section: (d.section || '').toUpperCase(),
    updated: manifestUpdated || '—',
    description: d.description,
    badge: d.badge,
    avgTime: '—',
    url,
    external: isExternal,
  };
}

async function loadManifest() {
  try {
    const res = await fetch(`${R2_DECK_BASE}/manifest.json`, { cache: 'no-store' });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const m = await res.json();

    if (Array.isArray(m.sections) && m.sections.length > 0) {
      SECTIONS.length = 0;
      SECTIONS.push(...m.sections.map(s => s.toUpperCase()));
    }

    const live = (m.documents || []).filter(d => d.visibility === 'live');
    ROOM_FILES.length = 0;
    ROOM_FILES.push(...live.map(d => manifestEntryToFile(d, m.updated)));

    console.log(`[manifest] loaded ${ROOM_FILES.length} live doc(s) in ${SECTIONS.length} section(s)`);
  } catch (e) {
    console.error('[manifest] failed to load — room will be empty:', e);
  }
}

async function bootstrapAuth() {
  const url = new URL(window.location.href);
  const inviteToken = url.searchParams.get('inv');
  const magicToken  = url.searchParams.get('magic');

  // Cache the invite token + email for one-click sign-back-in after sign-out.
  const cacheDeviceCredentials = (token, email) => {
    try {
      if (token) localStorage.setItem('arbcap_invite_token', token);
      if (email) localStorage.setItem('arbcap_last_email', email);
    } catch (e) { /* private mode / quota */ }
  };

  // Magic-link redemption first — these are short-lived (30min) and single-use.
  if (magicToken) {
    try {
      const r = await fetch('/api/auth/redeem-magic-link', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ token: magicToken }),
      });
      if (r.ok) {
        const data = await r.json();
        url.searchParams.delete('magic');
        window.history.replaceState(null, '', url.toString());
        cacheDeviceCredentials(data.inviteToken, data.investor?.email);
        console.log(`[auth] magic-link redeemed (${data.returning ? 'returning' : 'first visit'})`);
        return { hasSession: true, investor: data.investor, returning: !!data.returning, visitCount: data.visitCount };
      }
      console.warn(`[auth] magic-link redeem returned ${r.status}`);
    } catch (e) {
      console.warn('[auth] magic-link unreachable:', e.message);
    }
  }

  // Invite-link redemption.
  if (inviteToken) {
    try {
      const r = await fetch('/api/invite/redeem', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ token: inviteToken }),
      });
      if (r.ok) {
        const data = await r.json();
        url.searchParams.delete('inv');
        window.history.replaceState(null, '', url.toString());
        cacheDeviceCredentials(inviteToken, data.investor?.email);
        console.log(`[auth] invite redeemed (${data.returning ? 'returning' : 'first visit'})`);
        return { hasSession: true, investor: data.investor, returning: !!data.returning, visitCount: data.visitCount };
      }
      console.warn(`[auth] invite redeem returned ${r.status}`);
    } catch (e) {
      console.warn('[auth] redeem unreachable — backend not connected:', e.message);
    }
  }

  // Resume cookie-bound session.
  try {
    const r = await fetch('/api/session', { credentials: 'include' });
    if (r.ok) {
      const data = await r.json();
      console.log('[auth] resumed existing session');
      return { hasSession: true, investor: data.investor, returning: true, visitCount: null };
    }
  } catch (e) {
    console.warn('[auth] /api/session unreachable:', e.message);
  }

  return { hasSession: false, investor: null, returning: false, visitCount: null };
}

Promise.all([loadManifest(), bootstrapAuth()]).then(([_, auth]) => {
  ReactDOM.createRoot(document.getElementById('root')).render(<App initialAuth={auth} />);
});
