// ─────────────────────────────────────────────────────────────
// GITWRAP — the three-step wizard. One shared coordinate stage;
// elements morph between phases. No scroll, no nav.
// ─────────────────────────────────────────────────────────────

const Y = {
  input:   { eyebrow: -86, hero: 0,    hint: 70 },
  config:  { hero: -150, pills: -22, pat: 36, gen: 118 },
  card:    { hero: -190, download: 200, restart: 252 },
};

function Centered({ y = 0, children, style = {}, ...rest }) {
  return (
    <div style={{
      position: 'absolute', left: '50%', top: '50%',
      transform: `translate(-50%, calc(-50% + ${y}px))`,
      transition: 'transform 520ms var(--ease-in), opacity 360ms ease',
      ...style,
    }} {...rest}>{children}</div>
  );
}

const DEMO = new URLSearchParams(location.search).get('demo');
const DEMO_CARD = DEMO === 'card' || DEMO === 'pat';

function App() {
  const [phase, setPhase] = useState(DEMO_CARD ? 'reveal' : (DEMO === 'loading' ? 'loading' : (DEMO === 'error' ? 'error' : 'input')));
  const [username, setUsername] = useState(DEMO ? 'torvalds' : '');
  const [range, setRange] = useState(DEMO ? 'monthly' : null);
  const [patOpen, setPatOpen] = useState(DEMO === 'pat');
  const [pat, setPat] = useState(DEMO === 'pat' ? 'ghp_demo' : '');
  const [stats, setStats] = useState(DEMO_CARD ? buildStats('torvalds', 'monthly', null) : null);
  const [error, setError] = useState(DEMO === 'error' ? { title: 'user not found', sub: 'check the username and try again' } : null);
  const [slowFetch, setSlowFetch] = useState(false);
  const [scale, setScale] = useState(1);
  const [pulseKey, setPulseKey] = useState(0);
  const [hintIn, setHintIn] = useState(false);
  const inputRef = useRef(null);
  const exportRef = useRef(null);       // card format
  const overlayExportRef = useRef(null); // transparent overlay format

  const patUnlocked = !!pat.trim() && phase !== 'input';
  const onCard = phase === 'loading' || phase === 'reveal' || phase === 'error';

  useEffect(() => {
    const fit = () => {
      const vw = window.innerWidth, vh = window.innerHeight;
      setScale(Math.min(1, (vw - 32) / 660, (vh - 40) / 560));
    };
    fit();
    window.addEventListener('resize', fit);
    return () => window.removeEventListener('resize', fit);
  }, []);

  useEffect(() => { if (phase === 'input') { inputRef.current?.focus(); } }, [phase]);
  useEffect(() => { const t = setTimeout(() => setHintIn(true), 600); return () => clearTimeout(t); }, []);

  useEffect(() => {
    if (phase !== 'loading') { setSlowFetch(false); return; }
    const t = setTimeout(() => setSlowFetch(true), 3000);
    return () => clearTimeout(t);
  }, [phase]);

  const goConfig = () => {
    if (!username.trim()) { inputRef.current?.focus(); return; }
    if (!range) setRange('monthly');
    setPhase('config');
  };

  const generate = async () => {
    if (!range) return;
    setError(null);
    setPhase('loading');
    const token = pat.trim();
    const [res, realStats] = await Promise.all([
      lookupProfile(username),
      token ? fetchRealStats(username, range, token) : Promise.resolve(null),
      new Promise((r) => setTimeout(r, 950)),
    ]);
    if (res.status === 'notfound') {
      setError({ title: 'user not found', sub: 'check the username and try again' });
      setPhase('error');
      return;
    }
    const profile = res.status === 'ok' ? res.profile : null;
    setStats(buildStats(username, range, profile, realStats));
    setPhase('reveal');
  };

  const startOver = () => {
    setPhase('config');
    setTimeout(() => { setPhase('input'); setStats(null); setError(null); }, 480);
  };
  const tryAgain = () => { setError(null); setStats(null); setPhase('config'); };

  const onKeyInput = (e) => { if (e.key === 'Enter') goConfig(); };
  const onChange = (e) => { setUsername(e.target.value); setPulseKey((k) => k + 1); };

  const heroPhase = phase === 'input' ? 'input' : (phase === 'config' ? 'config' : 'card');
  const heroY = heroPhase === 'input' ? Y.input.hero : (heroPhase === 'config' ? Y.config.hero : Y.card.hero);
  const heroFont = heroPhase === 'input' ? 36 : 14;
  const heroColor = heroPhase === 'input' ? 'var(--white)' : 'var(--green)';
  const heroOpacity = onCard ? 0 : 1;

  return (
    <div style={{ position: 'fixed', inset: 0 }}>
      {/* persistent wordmark */}
      <div className="mono" style={{
        position: 'fixed', top: 18, left: 20, zIndex: 50, userSelect: 'none',
        display: 'flex', alignItems: 'center', gap: 8,
      }}>
        <MarkWrapCell s={26} />
        <span style={{ fontSize: 14, fontWeight: 600, color: 'var(--green)', letterSpacing: '0.02em' }}>
          GITWRAP
        </span>
      </div>

      {/* buy me a coffee */}
      <a href="https://buymeacoffee.com/danultimate" target="_blank" rel="noopener noreferrer"
        className="mono" style={{
          position: 'fixed', bottom: 20, right: 22, zIndex: 50,
          fontSize: 12, color: 'var(--muted)', letterSpacing: '0.03em',
          textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6,
          transition: 'color 180ms ease',
        }}
        onMouseEnter={(e) => e.currentTarget.style.color = 'var(--white)'}
        onMouseLeave={(e) => e.currentTarget.style.color = 'var(--muted)'}
      >
        <span style={{ fontSize: 14 }}>☕</span> buy me a coffee
      </a>

      {/* ── hidden overlay card (rendered off-screen for export) ── */}
      {phase === 'reveal' && stats && (
        <div style={{ position: 'fixed', left: -9999, top: 0, zIndex: -100 }}>
          <OverlayCard stats={stats} patUnlocked={patUnlocked} exportRef={overlayExportRef} />
        </div>
      )}

      {/* scaled stage */}
      <div style={{
        position: 'absolute', inset: 0,
        transform: `scale(${scale})`, transformOrigin: 'center center',
      }}>
        {/* ambient glow behind card */}
        <Centered y={0} style={{
          width: 760, height: 480, pointerEvents: 'none', zIndex: 0,
          background: 'radial-gradient(closest-side, var(--glow), transparent 72%)',
          opacity: onCard ? 1 : 0,
          transition: 'opacity 700ms ease',
          animation: phase === 'reveal' ? 'gw-glow-pulse 3.4s ease-in-out infinite' : 'none',
        }} />

        {/* STEP 1 eyebrow */}
        <Centered y={Y.input.eyebrow} style={{ opacity: phase === 'input' ? 1 : 0, pointerEvents: 'none', zIndex: 10 }}>
          <div style={{ fontSize: 12, fontWeight: 500, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--muted)', whiteSpace: 'nowrap' }}>
            Your GitHub Username
          </div>
        </Centered>

        {/* HERO username (morphs) */}
        <Centered y={heroY} style={{ zIndex: 20, opacity: heroOpacity, width: 'min(480px, 90vw)' }}>
          <div style={{ position: 'relative', textAlign: 'center' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10 }}>
              <input
                ref={inputRef}
                value={username}
                onChange={onChange}
                onKeyDown={onKeyInput}
                readOnly={phase !== 'input'}
                spellCheck={false}
                autoCapitalize="off"
                autoComplete="off"
                placeholder="torvalds"
                className="mono"
                style={{
                  width: phase === 'input' ? '100%' : 'auto',
                  textAlign: 'center',
                  fontSize: heroFont, fontWeight: phase === 'input' ? 400 : 500,
                  color: heroColor, caretColor: 'var(--green)',
                  padding: phase === 'input' ? '6px 4px' : '0',
                  transition: 'font-size 480ms var(--ease-in), color 320ms ease, font-weight 200ms ease',
                  pointerEvents: phase === 'input' ? 'auto' : 'none',
                }}
              />
              <button onClick={goConfig} aria-label="continue" style={{
                width: 34, height: 34, borderRadius: '50%', flexShrink: 0,
                border: '1px solid var(--border)', background: 'transparent', color: 'var(--green)',
                fontSize: 16, lineHeight: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
                opacity: phase === 'input' && username.trim() ? 1 : 0,
                transform: phase === 'input' && username.trim() ? 'scale(1)' : 'scale(0.7)',
                transition: 'opacity 240ms ease, transform 240ms var(--ease-out), border-color 200ms, background 200ms',
                pointerEvents: phase === 'input' && username.trim() ? 'auto' : 'none',
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = 'var(--green)'; e.currentTarget.style.background = 'rgba(63,185,80,0.10)'; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.background = 'transparent'; }}
              >→</button>
            </div>
            <div key={pulseKey} style={{
              height: 1, marginTop: 4,
              background: 'var(--border)', borderRadius: 2,
              opacity: phase === 'input' ? 1 : 0,
              transition: 'opacity 300ms ease',
              animation: phase === 'input' && pulseKey ? 'gw-border-pulse 220ms ease' : 'none',
            }} className="gw-underline" />
          </div>
        </Centered>

        {/* STEP 1 hint */}
        <Centered y={Y.input.hint} style={{ opacity: phase === 'input' && hintIn ? 1 : 0, pointerEvents: 'none', zIndex: 10 }}>
          <div style={{ fontSize: 12, color: 'var(--muted)', letterSpacing: '0.02em' }}>press enter to continue</div>
        </Centered>

        {/* STEP 2 range pills */}
        <Centered y={Y.config.pills} style={{ opacity: phase === 'config' ? 1 : 0, pointerEvents: phase === 'config' ? 'auto' : 'none', zIndex: 15 }}>
          <div style={{ display: 'flex', gap: 12 }}>
            {Object.values(RANGES).map((r) => (
              <RangePill key={r.key} r={r} active={range === r.key} onClick={() => setRange(r.key)} />
            ))}
          </div>
        </Centered>

        {/* STEP 2 PAT toggle */}
        <Centered y={Y.config.pat} style={{ opacity: phase === 'config' ? 1 : 0, pointerEvents: phase === 'config' ? 'auto' : 'none', zIndex: 15, width: 320 }}>
          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
            {!patOpen ? (
              <button onClick={() => setPatOpen(true)} className="gw-link" style={{ background: 'none', border: 'none', color: 'var(--muted)', fontSize: 12, padding: '6px', letterSpacing: '0.02em' }}>
                + use real stats
              </button>
            ) : (
              <div style={{ width: '100%', textAlign: 'center' }}>
                <input
                  value={pat}
                  onChange={(e) => setPat(e.target.value)}
                  type="password"
                  placeholder="paste your GitHub PAT"
                  className="mono"
                  style={{ width: '100%', textAlign: 'center', fontSize: 14, color: 'var(--white)', padding: '8px 4px', borderBottom: '1px solid var(--border)', caretColor: 'var(--green)' }}
                  onFocus={(e) => e.currentTarget.style.borderBottomColor = 'var(--green)'}
                  onBlur={(e) => e.currentTarget.style.borderBottomColor = 'var(--border)'}
                />
                <div style={{ marginTop: 8, fontSize: 11, color: 'var(--muted)' }}>
                  <a href="https://github.com/settings/tokens" target="_blank" rel="noopener noreferrer" className="gw-link" style={{ color: 'var(--muted)' }}>what's this?</a>
                  <span style={{ opacity: 0.4 }}> · optional · never stored</span>
                </div>
              </div>
            )}
          </div>
        </Centered>

        {/* SHELL: generate button ⟶ card */}
        <div style={{
          position: 'absolute', left: '50%', top: '50%', zIndex: 30,
          width: onCard ? CARD_W : 152,
          height: onCard ? CARD_H : 42,
          transform: `translate(-50%, calc(-50% + ${onCard ? 0 : Y.config.gen}px))`,
          borderRadius: 14, overflow: 'hidden',
          border: onCard ? 'none' : `1px solid ${range ? 'var(--green)' : 'var(--border)'}`,
          boxShadow: onCard ? '0 24px 80px -20px rgba(0,0,0,0.8), 0 0 0 1px var(--border)' : 'none',
          opacity: (phase === 'config' || onCard) ? 1 : 0,
          pointerEvents: (phase === 'config' && range) || phase === 'reveal' || phase === 'error' ? 'auto' : 'none',
          transition: 'width 600ms var(--ease-out), height 600ms var(--ease-out), transform 600ms var(--ease-out), border-color 300ms ease, box-shadow 500ms ease, opacity 300ms ease',
        }}>
          <button onClick={generate} disabled={!range} style={{
            position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
            gap: 8, background: 'transparent', border: 'none',
            color: range ? 'var(--green)' : 'var(--muted)',
            opacity: phase === 'config' ? 1 : 0,
            transition: 'opacity 200ms ease, background 200ms ease',
            pointerEvents: phase === 'config' ? 'auto' : 'none',
          }} className="mono gw-generate"
          onMouseEnter={(e) => { if (range) e.currentTarget.style.background = 'rgba(63,185,80,0.12)'; }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>
            <span style={{ fontSize: 13, fontWeight: 500, letterSpacing: '0.03em' }}>generate</span>
            <span style={{ fontSize: 14 }}>→</span>
          </button>

          {onCard && stats !== null || phase === 'loading' || phase === 'error' ? (
            <div style={{
              position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%, -50%)',
              opacity: onCard ? 1 : 0, transition: 'opacity 300ms ease 120ms',
            }}>
              <Card stats={stats} phase={phase} patUnlocked={patUnlocked} error={error} exportRef={exportRef} />
            </div>
          ) : null}
        </div>

        {/* STEP 3 — download buttons (two formats) */}
        <Centered y={Y.card.download} style={{
          opacity: phase === 'reveal' ? 1 : 0,
          transform: `translate(-50%, calc(-50% + ${phase === 'reveal' ? Y.card.download : Y.card.download + 12}px))`,
          pointerEvents: phase === 'reveal' ? 'auto' : 'none', zIndex: 25,
          transitionDelay: phase === 'reveal' ? '1500ms' : '0ms',
        }}>
          <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
            <DownloadButton
              exportRef={exportRef}
              label="Card"
              filename={`gitwrap-card-${stats?.username || 'card'}.png`}
              pixelRatio={2}
              bgColor="#0d1117"
              primary
            />
            <DownloadButton
              exportRef={overlayExportRef}
              label="Overlay"
              filename={`gitwrap-overlay-${stats?.username || 'overlay'}.png`}
              pixelRatio={3}
              bgColor={null}
              primary={false}
            />
          </div>
        </Centered>

        {/* start over / try again */}
        <Centered y={Y.card.restart} style={{
          opacity: (phase === 'reveal' || phase === 'error') ? 1 : 0,
          pointerEvents: (phase === 'reveal' || phase === 'error') ? 'auto' : 'none', zIndex: 25,
          transitionDelay: phase === 'reveal' ? '1700ms' : '0ms',
        }}>
          <button onClick={phase === 'error' ? tryAgain : startOver} className="gw-link" style={{
            background: 'none', border: 'none', color: 'var(--muted)', fontSize: 12, letterSpacing: '0.02em', whiteSpace: 'nowrap',
          }}>← {phase === 'error' ? 'try again' : 'start over'}</button>
        </Centered>

        {/* slow-fetch hint */}
        <Centered y={Y.card.download} style={{ opacity: slowFetch && phase === 'loading' ? 1 : 0, pointerEvents: 'none', zIndex: 25 }}>
          <div style={{ fontSize: 12, color: 'var(--muted)' }}>fetching your stats…</div>
        </Centered>
      </div>
    </div>
  );
}

function RangePill({ r, active, onClick }) {
  const [bump, setBump] = useState(0);
  const click = () => { onClick(); setBump((b) => b + 1); };
  return (
    <button key={bump} onClick={click} className="mono" style={{
      padding: '9px 20px', borderRadius: 999, fontSize: 13, letterSpacing: '0.02em',
      border: `1px solid ${active ? 'var(--green)' : 'var(--border)'}`,
      background: active ? 'var(--green-dim)' : 'transparent',
      color: active ? 'var(--white)' : 'var(--muted)',
      transition: 'border-color 180ms ease, background 220ms ease, color 180ms ease',
      animation: active && bump ? 'gw-pill-bump 320ms cubic-bezier(0.34,1.56,0.64,1)' : 'none',
    }}
    onMouseEnter={(e) => { if (!active) e.currentTarget.style.borderColor = 'rgba(63,185,80,0.4)'; }}
    onMouseLeave={(e) => { if (!active) e.currentTarget.style.borderColor = 'var(--border)'; }}
    >{r.label}</button>
  );
}

function DownloadButton({ exportRef, label, filename, pixelRatio = 2, bgColor = '#0d1117', primary = true }) {
  const [stamp, setStamp] = useState(false);
  const [busy, setBusy] = useState(false);

  const download = async () => {
    setStamp(true); setTimeout(() => setStamp(false), 220);
    if (!exportRef.current || !window.htmlToImage) return;
    setBusy(true);
    try {
      const opts = { pixelRatio, cacheBust: true };
      if (bgColor) opts.backgroundColor = bgColor;
      const url = await window.htmlToImage.toPng(exportRef.current, opts);
      const a = document.createElement('a');
      a.href = url; a.download = filename; a.click();
    } catch (e) { console.warn('export failed', e); }
    setBusy(false);
  };

  if (primary) {
    return (
      <button onClick={download} className="mono" style={{
        display: 'flex', alignItems: 'center', gap: 8,
        background: 'var(--green)', color: '#090c10', fontWeight: 600, fontSize: 13,
        padding: '11px 20px', borderRadius: 6, border: 'none', whiteSpace: 'nowrap',
        transform: stamp ? 'scale(0.96)' : 'scale(1)',
        transition: 'transform 140ms ease, filter 160ms ease',
        filter: busy ? 'brightness(0.85)' : 'brightness(1)',
      }}
      onMouseEnter={(e) => { if (!busy) { e.currentTarget.style.filter = 'brightness(1.1)'; e.currentTarget.style.transform = 'scale(1.02)'; } }}
      onMouseLeave={(e) => { e.currentTarget.style.filter = 'brightness(1)'; e.currentTarget.style.transform = 'scale(1)'; }}>
        <span>{busy ? 'rendering…' : `↓ ${label}`}</span>
      </button>
    );
  }

  return (
    <button onClick={download} className="mono" style={{
      display: 'flex', alignItems: 'center', gap: 8,
      background: 'transparent', color: 'var(--green)', fontWeight: 500, fontSize: 13,
      padding: '10px 20px', borderRadius: 6,
      border: '1px solid rgba(63,185,80,0.35)',
      whiteSpace: 'nowrap',
      transform: stamp ? 'scale(0.96)' : 'scale(1)',
      transition: 'transform 140ms ease, border-color 160ms, background 160ms',
      filter: busy ? 'opacity(0.6)' : 'opacity(1)',
    }}
    onMouseEnter={(e) => { if (!busy) { e.currentTarget.style.borderColor = 'var(--green)'; e.currentTarget.style.background = 'rgba(63,185,80,0.08)'; } }}
    onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'rgba(63,185,80,0.35)'; e.currentTarget.style.background = 'transparent'; }}>
      <span>{busy ? 'rendering…' : `↓ ${label}`}</span>
    </button>
  );
}

const styleEl = document.createElement('style');
styleEl.textContent = `
  .gw-link { cursor: pointer; transition: color 160ms ease; }
  .gw-link:hover { color: var(--white) !important; text-decoration: underline; text-underline-offset: 3px; }
  .gw-generate:disabled { cursor: default; }
  @keyframes gw-pill-bump { 0%{transform:scale(1)} 45%{transform:scale(1.05)} 100%{transform:scale(1)} }
`;
document.head.appendChild(styleEl);

document.addEventListener('focusin', (e) => {
  if (e.target.tagName === 'INPUT') {
    const u = e.target.closest('div')?.parentElement?.querySelector('.gw-underline');
    if (u) { u.style.background = 'var(--green)'; u.style.boxShadow = '0 2px 12px var(--glow)'; }
  }
});
document.addEventListener('focusout', (e) => {
  if (e.target.tagName === 'INPUT') {
    const u = e.target.closest('div')?.parentElement?.querySelector('.gw-underline');
    if (u) { u.style.background = 'var(--border)'; u.style.boxShadow = 'none'; }
  }
});

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