/* global React, ReactDOM, SAMPLES, LIBRARY, useTweaks, TweaksPanel,
   TweakSection, TweakRadio, TweakSlider, TweakButton */
const { useState, useEffect, useRef } = React;

// ─── Icons ─────────────────────────────────────────────────────
const SvgIcon = ({ d, size = 18, stroke = 2 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
       stroke="currentColor" strokeWidth={stroke}
       strokeLinecap="round" strokeLinejoin="round">
    {Array.isArray(d) ? d.map((p, i) => <path key={i} d={p} />) : <path d={d} />}
  </svg>
);
const I = {
  upload:   "M12 15V3 M7 8l5-5 5 5 M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2",
  image:    "M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z M3 15l5-5 4 4 3-3 6 6",
  sparkles: "M12 3v4 M12 17v4 M3 12h4 M17 12h4 M5.6 5.6l2.8 2.8 M15.6 15.6l2.8 2.8 M18.4 5.6l-2.8 2.8 M8.4 15.6l-2.8 2.8",
  home:     "M3 11l9-8 9 8 M5 10v10h14V10",
  library:  "M4 4h6v6H4z M14 4h6v6h-6z M4 14h6v6H4z M14 14h6v6h-6z",
  user:     "M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z M4 20c1.5-4 4.5-6 8-6s6.5 2 8 6",
  download: "M12 3v12 M7 10l5 5 5-5 M4 21h16",
  share:    "M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7 M16 6l-4-4-4 4 M12 2v13",
  close:    "M6 6l12 12 M18 6l-6 6-6 6",
  check:    "M5 12l5 5 9-11",
  copy:     "M9 4h9a2 2 0 0 1 2 2v12 M16 8H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2Z",
  heart:    "M12 21s-8-4.5-8-11a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 6.5-8 11-8 11h-2Z",
  gift:     "M20 12v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8 M2 8h20v4H2z M12 22V8 M12 8a3 3 0 1 0-3-3 3 3 0 0 0 3 3Z M12 8a3 3 0 1 1 3-3 3 3 0 0 1-3 3Z",
  wand:     "M15 4V2 M15 10V8 M11 6h2 M17 6h2 M4 17l10-10 M6 19l1-1 M19 9l2 2-10 10-2-2z",
  slider:   "M4 6h14 M4 12h7 M4 18h16 M18 4v4 M11 10v4 M20 16v4",
  eye:      "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8Z M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z",
  split:    "M12 3v18 M5 8l3 4-3 4 M19 8l-3 4 3 4",
  print:    "M6 9V3h12v6 M6 17H4a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-2 M6 14h12v7H6z",
  mail:     "M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z M2 6l10 7 10-7",
  play:     "M7 4v16l13-8L7 4Z",
  shield:   "M12 3 4 6v6c0 5 3.5 8.5 8 9 4.5-.5 8-4 8-9V6l-8-3Z",
  bolt:     "M13 2 3 14h7l-1 8 10-12h-7l1-8Z",
  crown:    "M3 7l4 5 5-7 5 7 4-5v12H3z",
};

// ─── R mark ────────────────────────────────────────────────────
function RMark({ size = 26 }) {
  return (
    <div className="dt-brand-mark" style={{ width: size, height: size, borderRadius: size * 0.27 }}>
      <svg width={size * 0.62} height={size * 0.62} viewBox="0 0 32 32" fill="none">
        <path d="M7 4 L7 28" stroke="#FBF6EC" strokeWidth="4" strokeLinecap="square"/>
        <path d="M7 4 L19 4 L24 5.5 L26 9 L26 13 L24 16 L19 17 L7 17"
              stroke="#FBF6EC" strokeWidth="4" fill="none"/>
        <path d="M16 17 L28 30" stroke="#FBF6EC" strokeWidth="4" strokeLinecap="round"/>
        <path d="M22 4 L26 4 L26 8 Z" fill="#FF4A00" stroke="#FBF6EC" strokeWidth="1.2"/>
      </svg>
    </div>
  );
}

// ─── Decorative QR ─────────────────────────────────────────────
function MiniQR() {
  return (
    <div className="gac-qr">
      {Array.from({ length: 441 }).map((_, i) => {
        const row = Math.floor(i / 21), col = i % 21;
        const isFinder = (r, c) =>
          (r < 7 && c < 7) || (r < 7 && c > 13) || (r > 13 && c < 7);
        const finderOn = (r, c) => {
          if (!isFinder(r, c)) return false;
          const rr = r < 7 ? r : r - 14;
          const cc = c < 7 ? c : c - 14;
          const ar = Math.abs(rr - 3), ac = Math.abs(cc - 3);
          if (Math.max(ar, ac) === 3) return true;
          if (Math.max(ar, ac) <= 1) return true;
          return false;
        };
        if (isFinder(row, col)) {
          return <span key={i} className={finderOn(row, col) ? 'qr-on' : 'qr-off'} />;
        }
        const on = ((row * 7 + col * 13 + (row ^ col) * 3) % 5) < 2;
        return <span key={i} className={on ? 'qr-on' : 'qr-off'} />;
      })}
    </div>
  );
}

// ─── Top bar ───────────────────────────────────────────────────
function TopBar({ page, setPage, account, onUpgrade }) {
  const usage =
    account.tier === 'free'
      ? `${Math.max(0, 3 - account.restoreUsed)} free restores left`
      : account.tier === 'family'
        ? 'Family plan · unlimited'
        : 'Pro · unlimited';
  const chipClass =
    account.tier === 'free' && (3 - account.restoreUsed) <= 1
      ? (3 - account.restoreUsed <= 0 ? 'out' : 'warn')
      : '';
  return (
    <div className="dt-topbar">
      <div className="dt-topbar-inner">
        <a className="dt-brand" href="landing.html">
          <RMark size={34} />
          <span className="dt-brand-wm">
            <span className="l1">Restore</span>
            <span className="l2">Old Photos</span>
          </span>
        </a>
        <ul className="dt-nav">
          <li><button className={page === 'home' ? 'active' : ''} onClick={() => setPage('home')}>
            <SvgIcon d={I.home} size={16}/> Restore
          </button></li>
          <li><button className={page === 'library' ? 'active' : ''} onClick={() => setPage('library')}>
            <SvgIcon d={I.library} size={16}/> Library
          </button></li>
          <li><button className={page === 'account' ? 'active' : ''} onClick={() => setPage('account')}>
            <SvgIcon d={I.user} size={16}/> Account
          </button></li>
        </ul>
        <div className="dt-topbar-right">
          <div className={`dt-usage-chip ${chipClass}`}>
            <span className="uc-dot"/>
            {usage}
          </div>
          {account.tier === 'free' && (
            <button className="dt-upgrade-btn" onClick={onUpgrade}>
              <SvgIcon d={I.sparkles} size={14}/> Upgrade
            </button>
          )}
          <div className="dt-avatar" onClick={() => setPage('account')} title={account.email || 'Account'}>
            {(account.email || 'M').charAt(0).toUpperCase()}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Sign-in gate ──────────────────────────────────────────────
// Shown full-page when an unauthenticated visitor hits /desktop. Keeps the
// TopBar + tweaks panel out of the way so no placeholder "Margaret" leaks.
function SignInGate({ onSignedIn }) {
  const [email, setEmail] = useState('');
  const [phase, setPhase] = useState('pick'); // 'pick' | 'sent' | 'sending'
  const [error, setError] = useState(null);

  const submit = async (e) => {
    e && e.preventDefault && e.preventDefault();
    if (!email.includes('@')) return;
    setError(null);
    setPhase('sending');
    try {
      await window.API.requestMagicLink(email);
      setPhase('sent');
    } catch (err) {
      setError(window.API.friendlyError(err));
      setPhase('pick');
    }
  };

  return (
    <div style={{
      minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: '24px', background: 'var(--bg-warm, #fbf3e7)',
    }}>
      <div style={{
        width: '100%', maxWidth: 440, background: '#fff',
        borderRadius: 20, padding: '40px 32px',
        boxShadow: '0 20px 60px rgba(0,0,0,.08)',
        fontFamily: 'var(--font-body)',
      }}>
        <a href="/" style={{ textDecoration: 'none', color: 'inherit', display: 'inline-flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
          <RMark size={32} />
          <span style={{ fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 17, letterSpacing: '-0.01em' }}>
            Restore Old Photos
          </span>
        </a>

        {phase === 'sent' ? (
          <>
            <h1 style={{ fontFamily: 'var(--font-display)', fontSize: 26, fontWeight: 800, margin: '0 0 10px', color: 'var(--ink-1)' }}>
              Check your email
            </h1>
            <p style={{ fontSize: 15, color: 'var(--muted-1)', margin: '0 0 20px', lineHeight: 1.5 }}>
              We sent a sign-in link to <strong style={{ color: 'var(--ink-1)' }}>{email}</strong>. Tap the link to finish signing in. It expires in 15 minutes.
            </p>
            <button className="btn btn-ghost" onClick={() => setPhase('pick')} style={{ width: '100%', justifyContent: 'center' }}>
              Use a different email
            </button>
          </>
        ) : (
          <>
            <h1 style={{ fontFamily: 'var(--font-display)', fontSize: 26, fontWeight: 800, margin: '0 0 10px', color: 'var(--ink-1)' }}>
              Sign in to continue
            </h1>
            <p style={{ fontSize: 15, color: 'var(--muted-1)', margin: '0 0 24px', lineHeight: 1.5 }}>
              Enter your email and we'll send you a magic link. No password needed. First three restorations are free.
            </p>
            <form onSubmit={submit}>
              <input
                type="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@example.com"
                autoFocus
                disabled={phase === 'sending'}
                style={{
                  width: '100%', padding: '14px 16px', fontSize: 15,
                  border: '1.5px solid rgba(0,0,0,.12)', borderRadius: 10,
                  fontFamily: 'inherit', outline: 'none', marginBottom: 12,
                  background: phase === 'sending' ? '#f5f0e7' : '#fff',
                }}
              />
              {error && (
                <div style={{ color: '#b34040', fontSize: 13, marginBottom: 10 }}>{error}</div>
              )}
              <button
                type="submit"
                className="btn btn-primary"
                disabled={!email.includes('@') || phase === 'sending'}
                style={{
                  width: '100%', justifyContent: 'center',
                  opacity: email.includes('@') && phase !== 'sending' ? 1 : 0.55,
                }}>
                {phase === 'sending' ? 'Sending…' : 'Email me a sign-in link'}
              </button>
            </form>
            <p style={{ fontSize: 12, color: 'var(--muted-1)', margin: '20px 0 0', textAlign: 'center' }}>
              By continuing you agree to our <a href="/terms.html" style={{ color: 'inherit' }}>Terms</a> and <a href="/privacy.html" style={{ color: 'inherit' }}>Privacy Policy</a>.
            </p>
          </>
        )}
      </div>
    </div>
  );
}

// ─── Drop zone / Home ──────────────────────────────────────────
function HomePage({ onStart, onUploadFile, account, onUpgrade, aiModel, setAiModel }) {
  const [drag, setDrag] = useState(false);
  const fileRef = useRef(null);
  // Prefer a real display name (populated from SSO in the future). We deliberately
  // don't manufacture one from the email — "Welcome back, Pmf111" reads like spam.
  const name = account && account.name ? account.name : null;
  const greeting = name ? `Welcome back, ${name}.` : 'Welcome back.';

  const pickSample = (id) => onStart(id);

  const handleFiles = (files) => {
    if (!files || files.length === 0) return;
    const f = files[0];
    if (onUploadFile) onUploadFile(f);
    else onStart('bw'); // demo mode
  };

  const onDrop = (e) => {
    e.preventDefault();
    setDrag(false);
    const items = e.dataTransfer && e.dataTransfer.files;
    if (items && items.length > 0) handleFiles(items);
    else onStart('bw');
  };

  return (
    <div className="dt-shell">
      <div className="dt-page-head">
        <div>
          <span className="eyebrow">Web app · beta</span>
          <h1>{greeting}</h1>
          <p className="dt-lede">
            Drop a scanned photo, or upload from your computer — we'll restore it in about 15 seconds. Your first three are on us.
          </p>
        </div>
      </div>

      <div className="dt-home-grid">
        <div className="dt-upload-card">
          <div
            className={`dt-dropzone ${drag ? 'dragover' : ''}`}
            onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
            onDragLeave={() => setDrag(false)}
            onDrop={onDrop}
            onClick={() => fileRef.current?.click()}
          >
            <div className="dz-ic"><SvgIcon d={I.upload} size={34} stroke={2}/></div>
            <h2>Drop a photo here</h2>
            <p className="dz-sub">
              Or click to browse. JPG, PNG, HEIC, or TIFF up to 40MB. We never store your photos after processing.
            </p>
            <div className="dz-btns">
              <button className="btn btn-primary btn-lg" onClick={(e) => { e.stopPropagation(); fileRef.current?.click(); }}>
                <SvgIcon d={I.image} size={16}/> Choose a file
              </button>
              <button className="btn btn-ghost btn-lg" onClick={(e) => { e.stopPropagation(); pickSample('bw'); }}>
                <SvgIcon d={I.sparkles} size={16}/> Try a sample
              </button>
            </div>
            <input ref={fileRef} type="file" style={{ display: 'none' }}
                   accept="image/jpeg,image/png,image/webp,image/heic,image/heif"
                   onChange={(e) => handleFiles(e.target.files)}/>
            {setAiModel && (
              <div onClick={(e) => e.stopPropagation()} style={{
                marginTop: 14,
                display: 'flex', flexWrap: 'wrap', alignItems: 'center',
                gap: 10, justifyContent: 'center',
                fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--muted-1)',
              }}>
                <span>AI model:</span>
                {[
                  { v: 'auto',  label: 'Auto' },
                  { v: 'pro',   label: 'Pro (higher quality)' },
                  { v: 'flash', label: 'Flash (faster)' },
                ].map((opt) => {
                  const active = (aiModel || 'auto') === opt.v;
                  return (
                    <button
                      key={opt.v}
                      onClick={() => setAiModel(opt.v)}
                      style={{
                        background: active ? 'var(--sepia-ink)' : 'transparent',
                        color: active ? '#fff' : 'var(--sepia-ink)',
                        border: active ? 0 : '1px solid var(--sepia-line, rgba(0,0,0,.15))',
                        borderRadius: 999,
                        padding: '6px 12px',
                        fontFamily: 'inherit', fontSize: 12.5,
                        cursor: 'pointer',
                      }}
                    >
                      {opt.label}
                    </button>
                  );
                })}
              </div>
            )}
            <div className="dz-fine">Private · encrypted · you control deletion</div>
          </div>
          <div className="dt-upload-tips">
            <div className="tip">
              <div className="tip-ic"><SvgIcon d={I.bolt} size={16}/></div>
              <div>
                <div className="tip-title">Takes ~15 seconds</div>
                <div className="tip-sub">Even severe damage, scratches, and fading</div>
              </div>
            </div>
            <div className="tip">
              <div className="tip-ic"><SvgIcon d={I.shield} size={16}/></div>
              <div>
                <div className="tip-title">Private by default</div>
                <div className="tip-sub">Never sold, never used for training</div>
              </div>
            </div>
            <div className="tip">
              <div className="tip-ic"><SvgIcon d={I.wand} size={16}/></div>
              <div>
                <div className="tip-title">True to the original</div>
                <div className="tip-sub">Preserves faces, grain, and era</div>
              </div>
            </div>
          </div>
        </div>

        <div className="dt-side">
          {account.tier === 'free' && (
            <div className="dt-side-card dark">
              <span className="dt-side-eye">Upgrade</span>
              <h3>Save in high resolution</h3>
              <p style={{ marginBottom: 14 }}>
                Your free preview is watermarked. A Family subscription unlocks 4× upscaled downloads, no watermarks, and your cloud library across all your devices.
              </p>
              <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center' }} onClick={onUpgrade}>
                View plans — from $4.08/mo
              </button>
            </div>
          )}

          <div className="dt-side-card">
            <div className="dt-getapp-card">
              <MiniQR/>
              <div>
                <h3>On your phone?</h3>
                <p style={{ fontSize: 13 }}>Scan the code to restore straight from the camera. No scanner needed.</p>
                <div className="gac-stores">
                  <span>App Store</span>
                  <span style={{ color: 'var(--brand-orange)' }}>•</span>
                  <span>Google Play</span>
                </div>
              </div>
            </div>
          </div>

          <div className="dt-side-card dark dt-refcard">
            <span className="dt-side-eye">Invite friends</span>
            <h3>Give a free video, get one back</h3>
            <p style={{ color: 'rgba(255,255,255,.7)', fontSize: 13.5 }}>
              When a friend signs up and restores their first photo, you both get a free Living Memory video credit.
            </p>
            <div className="dt-ref-progress">
              <div className="dt-ref-dot on"/>
              <div className="dt-ref-dot on"/>
              <div className="dt-ref-dot"/>
              <div className="dt-ref-dot"/>
              <div className="dt-ref-dot"/>
            </div>
            <div className="dt-ref-stats">
              <span>2 invited · 1 completed</span>
              <span style={{ color: '#FF933A' }}>1 video earned</span>
            </div>
            <div className="dt-ref-link">
              <input readOnly value="restoreoldphoto.app/r/margaret-e"/>
              <button onClick={(e) => {
                const btn = e.currentTarget;
                const original = btn.innerHTML;
                btn.innerHTML = '<span>Copied ✓</span>';
                setTimeout(() => { btn.innerHTML = original; }, 1500);
              }}>
                <SvgIcon d={I.copy} size={13}/> Copy
              </button>
            </div>
          </div>
        </div>
      </div>

      <div className="dt-recent-head">
        <h2>Recent restorations</h2>
        <a onClick={() => {}}>See all →</a>
      </div>
      <div className="dt-gallery-grid">
        {LIBRARY.slice(0, 4).map(item => (
          <div key={item.id} className="dt-gallery-card">
            <div className="gc-img">
              <img src={SAMPLES[item.sample].after} alt=""/>
              <span className="gc-badge">RESTORED</span>
            </div>
            <div className="gc-meta">
              <div className="gc-title">{item.title}</div>
              <div className="gc-date">{item.date}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─── Processing ────────────────────────────────────────────────
function ProcessingPage({ sampleId, onDone, restorationId, onFailure }) {
  const [pct, setPct] = useState(0);
  const [stepIdx, setStepIdx] = useState(0);
  const steps = [
    'Detecting the photograph…',
    'Analyzing damage and fading…',
    'Repairing scratches and tears…',
    'Sharpening facial detail…',
    'Finalizing 4× upscale…',
  ];
  useEffect(() => {
    if (restorationId && window.API) {
      let cancelled = false;
      const start = Date.now();
      const tick = setInterval(() => {
        if (cancelled) return;
        const p = Math.min(0.9, (Date.now() - start) / 90000);
        setPct(p);
        setStepIdx(Math.min(steps.length - 1, Math.floor(p * steps.length)));
      }, 200);
      (async () => {
        try {
          const final = await window.API.pollUntilDone(restorationId, {
            intervalMs: 2000,
            maxMs: 5 * 60 * 1000,
          });
          clearInterval(tick);
          if (cancelled) return;
          if (final.status === 'done') {
            setPct(1);
            setStepIdx(steps.length - 1);
            setTimeout(() => { if (!cancelled) onDone(); }, 400);
          } else {
            onFailure && onFailure(final.error || 'failed');
          }
        } catch (err) {
          clearInterval(tick);
          if (!cancelled) onFailure && onFailure(window.API.friendlyError(err));
        }
      })();
      return () => { cancelled = true; clearInterval(tick); };
    }
    const start = Date.now();
    const total = 4500;
    const id = setInterval(() => {
      const p = Math.min(1, (Date.now() - start) / total);
      setPct(p);
      setStepIdx(Math.min(steps.length - 1, Math.floor(p * steps.length)));
      if (p >= 1) {
        clearInterval(id);
        setTimeout(() => onDone(), 400);
      }
    }, 80);
    return () => clearInterval(id);
  }, [restorationId]);
  const sample = restorationId
    ? { before: window.API.imageUrl(restorationId, 'before') }
    : SAMPLES[sampleId];
  return (
    <div className="dt-shell">
      <div className="dt-processing">
        <div className="dt-proc-img">
          <img src={sample.before} alt=""/>
          <div className="dt-proc-scan"/>
        </div>
        <div className="dt-proc-eye">Restoring · about 15 seconds</div>
        <div className="dt-proc-title">AI at work</div>
        <div className="dt-proc-step">{steps[stepIdx]}</div>
        <div className="dt-proc-bar"><div className="fill" style={{ width: `${pct * 100}%` }}/></div>
      </div>
    </div>
  );
}

// ─── Compare slider ────────────────────────────────────────────
// Pointer Events + setPointerCapture so mousedown -> dragging stays latched
// to the handle through the whole gesture. The previous MouseEvent version
// lost the `mouseup` when the browser's native image-drag kicked in on the
// first click, which left `dragging=true` until the user clicked again.
function CompareSlider({ before, after, initial = 52 }) {
  const [pos, setPos] = useState(initial);
  const ref = useRef(null);

  const moveTo = (clientX) => {
    if (!ref.current) return;
    const r = ref.current.getBoundingClientRect();
    const p = Math.min(100, Math.max(0, ((clientX - r.left) / r.width) * 100));
    setPos(p);
  };

  const onPointerDown = (e) => {
    // Latch the rest of this gesture to the slider — even when the pointer
    // leaves the element or the native image-drag wants to fire.
    e.currentTarget.setPointerCapture(e.pointerId);
    e.preventDefault();
    moveTo(e.clientX);
  };
  const onPointerMove = (e) => {
    // Only track while the button is held (1 = primary).
    if (e.buttons === 0) return;
    moveTo(e.clientX);
  };

  return (
    <div
      ref={ref}
      className="dt-compare-img dt-compare-slider"
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      style={{ touchAction: 'none' }}
    >
      <img src={after} alt="restored" draggable={false} style={{ pointerEvents: 'none', userSelect: 'none' }}/>
      <div className="dt-before-wrap" style={{ width: `${pos}%` }}>
        <img src={before} alt="original" draggable={false}
             style={{ width: `${100 / (pos / 100)}%`, pointerEvents: 'none', userSelect: 'none' }}/>
      </div>
      <span className="dt-compare-label before" style={{ pointerEvents: 'none' }}>BEFORE</span>
      <span className="dt-compare-label after" style={{ pointerEvents: 'none' }}>AFTER</span>
      <div className="dt-handle" style={{ left: `${pos}%`, pointerEvents: 'none' }}/>
    </div>
  );
}

// ─── Results ───────────────────────────────────────────────────
function ResultsPage({ sampleId, onSaveRequest, onAnimate, onShare, onRestoreAnother, account, onUpgrade, restorationId }) {
  const [mode, setMode] = useState('slider');
  const [meta, setMeta] = useState(null);
  useEffect(() => {
    if (!restorationId || !window.API) { setMeta(null); return; }
    window.API.getRestoration(restorationId).then(setMeta).catch(() => setMeta(null));
  }, [restorationId]);
  const sample = restorationId
    ? {
        before: window.API.imageUrl(restorationId, 'before'),
        after: window.API.imageUrl(restorationId, 'after'),
      }
    : SAMPLES[sampleId];

  return (
    <div className="dt-shell">
      <div className="dt-page-head">
        <div>
          <span className="eyebrow">
            {meta && meta.modelUsed
              ? `Restored with ${meta.modelUsed}`
              : 'Restoration complete'}
          </span>
          <h1>Here's how it turned out.</h1>
          <p className="dt-lede">
            Drag the slider to compare before and after. Download it, share it with family, or start a new restoration.
          </p>
        </div>
      </div>

      <div className="dt-results-grid">
        <div className="dt-compare-stage">
          <div className="dt-compare-head">
            <h3>Your photograph</h3>
            <div className="dt-mode-tabs">
              <button className={mode === 'slider' ? 'active' : ''} onClick={() => setMode('slider')}>
                <SvgIcon d={I.split} size={12}/> Slider
              </button>
              <button className={mode === 'sbs' ? 'active' : ''} onClick={() => setMode('sbs')}>
                <SvgIcon d={I.library} size={12}/> Side-by-side
              </button>
              <button className={mode === 'after' ? 'active' : ''} onClick={() => setMode('after')}>
                <SvgIcon d={I.eye} size={12}/> After only
              </button>
            </div>
          </div>
          <div className="dt-compare-body">
            {mode === 'slider' && <CompareSlider before={sample.before} after={sample.after}/>}
            {mode === 'sbs' && (
              <div className="dt-compare-sbs">
                <div className="sbs-cell"><img src={sample.before} alt=""/><span className="sbs-label">BEFORE</span></div>
                <div className="sbs-cell"><img src={sample.after} alt=""/><span className="sbs-label">AFTER</span></div>
              </div>
            )}
            {mode === 'after' && (
              <div className="dt-compare-img"><img src={sample.after} alt=""/></div>
            )}
          </div>
        </div>

        <div>
          <div className="dt-actions">
            <button className="btn btn-primary dt-action-main btn-lg" onClick={onSaveRequest}>
              <SvgIcon d={I.download} size={16}/> Download restored photo
            </button>
            <div className="dt-action-row">
              <button className="btn btn-ghost" onClick={onAnimate}>
                <SvgIcon d={I.sparkles} size={14}/> Generate video
              </button>
              <button className="btn btn-ghost" onClick={onShare}>
                <SvgIcon d={I.share} size={14}/> Share
              </button>
            </div>
            <div className="dt-action-row">
              <button className="btn btn-ghost" onClick={onRestoreAnother}>
                <SvgIcon d={I.upload} size={14}/> New photo
              </button>
            </div>
          </div>

          {account.tier === 'free' && (
            <div className="dt-pro-tip">
              <div className="pt-ic"><SvgIcon d={I.crown} size={16}/></div>
              <div>
                <div className="pt-title">Free download is watermarked</div>
                <div className="pt-sub">Upgrade to Family ($4.08/mo) to save in 4K with no watermark.</div>
                <button className="pt-cta" onClick={onUpgrade}>Upgrade</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Library ───────────────────────────────────────────────────
function LibraryPage({ isLive, onOpen }) {
  const [filter, setFilter] = useState('all');
  const [live, setLive] = useState(null);
  useEffect(() => {
    if (!isLive || !window.API) return;
    window.API.listRestorations(100).then((d) => setLive(d.items || [])).catch(() => setLive([]));
  }, [isLive]);

  const items = isLive && live
    ? live
        .filter((r) => r.status === 'done' && r.afterUrl)
        .map((r) => ({
          id: r.id,
          title: 'Restoration',
          date: new Date((r.createdAt || 0) * 1000).toLocaleDateString(undefined, { month: 'long', year: 'numeric' }),
          thumb: r.afterUrl,
        }))
    : LIBRARY.map((item) => ({
        id: item.id,
        title: item.title,
        date: item.date,
        thumb: SAMPLES[item.sample].after,
      }));

  return (
    <div className="dt-shell">
      <div className="dt-page-head">
        <div>
          <span className="eyebrow">Your library</span>
          <h1>Every photo, safely kept.</h1>
          <p className="dt-lede">
            Your restorations live here, synced across your phone and computer. {items.length} photos restored.
          </p>
        </div>
      </div>
      <div className="dt-lib-toolbar">
        <div className="dt-filters">
          <button className={filter === 'all' ? 'active' : ''} onClick={() => setFilter('all')}>All</button>
          <button className={filter === 'bw' ? 'active' : ''} onClick={() => setFilter('bw')}>Black & white</button>
          <button className={filter === 'color' ? 'active' : ''} onClick={() => setFilter('color')}>Color</button>
          <button className={filter === 'videos' ? 'active' : ''} onClick={() => setFilter('videos')}>Videos</button>
        </div>
        <div style={{ fontFamily: 'var(--font-body)', fontSize: 13, color: 'var(--muted-1)' }}>
          Sorted by most recent
        </div>
      </div>
      <div className="dt-lib-grid">
        {items.length === 0 && <div style={{ gridColumn: '1/-1', textAlign: 'center', color: 'var(--muted-1)', padding: 40 }}>No restorations yet.</div>}
        {items.map(item => (
          <div
            key={item.id}
            className="dt-gallery-card"
            onClick={() => onOpen && onOpen(item.id)}
            role={onOpen ? 'button' : undefined}
            tabIndex={onOpen ? 0 : undefined}
            onKeyDown={(e) => {
              if (!onOpen) return;
              if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(item.id); }
            }}
            style={{ cursor: onOpen ? 'pointer' : 'default' }}
          >
            <div className="gc-img">
              <img src={item.thumb} alt=""/>
              <span className="gc-badge">RESTORED</span>
            </div>
            <div className="gc-meta">
              <div className="gc-title">{item.title}</div>
              <div className="gc-date">{item.date}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─── Account ───────────────────────────────────────────────────
function AccountPage({ account, onUpgrade, onSignOut }) {
  const tierCopy = {
    free: { name: 'Free', sub: '3 free restorations' },
    family: { name: 'Family', sub: 'Active subscription' },
    pro: { name: 'Pro', sub: 'Active subscription' },
  }[account.tier] || { name: 'Free', sub: '3 restorations' };
  const name = account && account.name ? account.name : null;
  const email = account && account.email ? account.email : '';

  return (
    <div className="dt-shell">
      <div className="dt-page-head" style={{ display: 'flex', alignItems: 'flex-start', gap: 24, flexWrap: 'wrap' }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <span className="eyebrow">Your account</span>
          <h1 style={{ wordBreak: name ? 'break-word' : 'break-all' }}>{name || email}</h1>
          {name && email && (
            <p className="dt-lede" style={{ wordBreak: 'break-all' }}>{email}</p>
          )}
        </div>
        {onSignOut && (
          <button className="btn btn-ghost" onClick={onSignOut} style={{ flexShrink: 0, marginTop: 8 }}>
            Sign out
          </button>
        )}
      </div>

      <div className="dt-account-grid">
        <div className="dt-acc-card">
          <h3>Plan & usage</h3>
          <div className="dt-acc-plan">
            <div className="dt-acc-plan-ic"><SvgIcon d={I.crown} size={22}/></div>
            <div>
              <div className="dt-acc-plan-name">{tierCopy.name}</div>
              <div className="dt-acc-plan-sub">{tierCopy.sub}</div>
            </div>
          </div>
          <div className="dt-acc-stat">
            <span className="lbl">Restorations this month</span>
            <span className="val">{account.tier === 'free' ? `${account.restoreUsed} of 3` : '47'}</span>
          </div>
          <div className="dt-acc-stat">
            <span className="lbl">Living Memory videos</span>
            <span className="val">{account.videoCredits} remaining</span>
          </div>
          <div className="dt-acc-stat">
            <span className="lbl">Cloud library</span>
            <span className="val">{LIBRARY.length} photos · 43 MB</span>
          </div>
          <div className="dt-acc-stat">
            <span className="lbl">Devices synced</span>
            <span className="val">{account.tier === 'free' ? '—' : 'iPhone · Web'}</span>
          </div>
          {account.tier === 'free' ? (
            <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center', marginTop: 18 }} onClick={onUpgrade}>
              Upgrade to Family
            </button>
          ) : (
            <button className="btn btn-ghost" style={{ width: '100%', justifyContent: 'center', marginTop: 18 }} onClick={onUpgrade}>
              Manage subscription
            </button>
          )}
        </div>

        <div className="dt-acc-card dark">
          <h3 style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <SvgIcon d={I.gift} size={20}/> Invite friends, earn videos
          </h3>
          <p className="dt-ref-sub">
            Every friend who signs up and restores their first photo earns you a free Living Memory video credit. They get one too — to welcome them.
          </p>
          <div className="dt-ref-progress">
            <div className="dt-ref-dot on"/>
            <div className="dt-ref-dot on"/>
            <div className="dt-ref-dot"/>
            <div className="dt-ref-dot"/>
            <div className="dt-ref-dot"/>
          </div>
          <div className="dt-ref-stats">
            <span>2 friends invited · 1 completed</span>
            <span style={{ color: '#FF933A' }}>1 video earned</span>
          </div>
          <div className="dt-ref-link">
            <input readOnly value="restoreoldphoto.app/r/margaret-e"/>
            <button><SvgIcon d={I.copy} size={13}/> Copy link</button>
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
            <button className="btn btn-ghost" style={{ flex: 1, justifyContent: 'center', background: 'rgba(255,255,255,.08)', color: '#fff', borderColor: 'rgba(255,255,255,.2)' }}>
              <SvgIcon d={I.mail} size={14}/> Email
            </button>
            <button className="btn btn-ghost" style={{ flex: 1, justifyContent: 'center', background: 'rgba(255,255,255,.08)', color: '#fff', borderColor: 'rgba(255,255,255,.2)' }}>
              <SvgIcon d={I.share} size={14}/> More
            </button>
          </div>
        </div>

        <div className="dt-acc-card">
          <h3>Get the mobile app</h3>
          <p style={{ fontFamily: 'var(--font-body)', fontWeight: 300, fontSize: 14, color: 'var(--ink-3)', lineHeight: 1.55, marginBottom: 16 }}>
            Restore straight from your camera — the easiest way to digitize a stack of prints.
          </p>
          <div className="dt-getapp-card">
            <MiniQR/>
            <div>
              <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 14, color: 'var(--sepia-ink)', marginBottom: 4 }}>
                Scan with your phone camera
              </div>
              <div style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--muted-1)' }}>
                We'll detect iPhone or Android automatically.
              </div>
              <div className="gac-stores">
                <span>App Store</span>
                <span style={{ color: 'var(--brand-orange)' }}>•</span>
                <span>Google Play</span>
              </div>
            </div>
          </div>
        </div>

        <div className="dt-acc-card">
          <h3>Privacy & data</h3>
          <p style={{ fontFamily: 'var(--font-body)', fontWeight: 300, fontSize: 14, color: 'var(--ink-3)', lineHeight: 1.55, marginBottom: 16 }}>
            Your photos stay in your library until you delete them, and are encrypted at rest. We never train on them or share them.
          </p>
          <div className="dt-acc-stat">
            <span className="lbl">Two-factor auth</span>
            <span className="val" style={{ color: 'var(--success-soft)' }}>On</span>
          </div>
          <div className="dt-acc-stat">
            <span className="lbl">Family sharing</span>
            <span className="val">{account.tier === 'free' ? 'Off' : 'On'}</span>
          </div>
          <div className="dt-acc-stat">
            <span className="lbl">Data export</span>
            <span className="val" style={{ color: 'var(--brand-orange)' }}>Request</span>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Paywall modal ─────────────────────────────────────────────
function Paywall({ onClose, onSubscribe, trigger }) {
  const [yearly, setYearly] = useState(true);
  const copy = {
    save: { eye: 'Upgrade', title: 'Save in high resolution', sub: 'Your preview is watermarked. Upgrade for 4× upscaled, watermark-free downloads.' },
    animate: { eye: 'Living Memory', title: 'Bring this photo to life', sub: 'Turn any restored photo into an 8-second cinematic video.' },
    limit: { eye: 'Upgrade', title: "You've used your free restorations", sub: 'Keep digitizing your family archive with unlimited restorations.' },
    default: { eye: 'Pricing', title: 'Pick a plan', sub: 'Start free. Upgrade anytime.' },
  }[trigger || 'default'] || { eye: 'Pricing', title: 'Pick a plan', sub: '' };

  // The animate flow needs video generation — only the Pro plan offers it.
  // Showing Free or Family there would be actively misleading (user could
  // "upgrade" to a plan that still can't do the thing they want), so for
  // animate we narrow the modal to a single Pro tier with its full feature
  // list spelled out (no "Everything in Family" hand-wave).
  const proFeaturesStandalone = [
    'Unlimited restorations',
    '4× HD · no watermarks',
    'Cloud library & sync',
    'Family sharing (up to 4 members)',
    '10 Living Memory videos / month',
    'Priority processing',
    'Early access to new features',
  ];

  const allTiers = [
    {
      id: 'free', name: 'Free', desc: 'Try it out',
      price: '$0', per: 'forever', eq: 'No card needed',
      features: ['3 restorations', 'Watermarked previews', 'iPhone, Android & web'],
      cta: 'Current plan',
    },
    {
      id: 'family', name: 'Family', desc: 'For the archivist',
      price: yearly ? '$49' : '$9.99', per: yearly ? '/ year' : '/ month',
      eq: yearly ? '$4.08/mo · save 58%' : 'Billed monthly',
      features: ['Unlimited restorations', '4× HD · no watermarks', 'Cloud library & sync', 'Family sharing (up to 4 members)'],
      cta: 'Choose Family',
      featured: true,
    },
    {
      id: 'pro', name: 'Pro', desc: 'Videos included',
      price: yearly ? '$99' : '$19.99', per: yearly ? '/ year' : '/ month',
      eq: yearly ? '$8.25/mo · save 58%' : 'Billed monthly',
      features: trigger === 'animate' ? proFeaturesStandalone : ['Everything in Family', '10 videos per month', 'Priority processing', 'Early access to new features'],
      cta: 'Choose Pro',
      badge: 'Best value',
      featured: trigger === 'animate',
    },
  ];
  const tiers = trigger === 'animate' ? allTiers.filter(t => t.id === 'pro') : allTiers;

  return (
    <div className="dt-modal-backdrop" onClick={onClose}>
      <div className="dt-modal" onClick={e => e.stopPropagation()}>
        <button className="dt-modal-close" onClick={onClose} aria-label="Close">
          <SvgIcon d={I.close} size={16}/>
        </button>
        <div className="dt-modal-head">
          <span className="pw-eye">{copy.eye}</span>
          <h2>{copy.title}</h2>
          <p>{copy.sub}</p>
          <div className="dt-billing-toggle">
            <button className={!yearly ? 'active' : ''} onClick={() => setYearly(false)}>Monthly</button>
            <button className={yearly ? 'active' : ''} onClick={() => setYearly(true)}>
              Yearly <span className="save-pill">Save 58%</span>
            </button>
          </div>
        </div>
        <div className="dt-modal-body">
          <div
            className="dt-tiers"
            style={tiers.length === 1 ? { gridTemplateColumns: 'minmax(0, 360px)', justifyContent: 'center' } : undefined}
          >
            {tiers.map(t => (
              <div key={t.id} className={`dt-tier ${t.featured ? 'featured' : ''}`}>
                {t.badge && <div className="dt-tier-badge">{t.badge}</div>}
                {t.featured && !t.badge && <div className="dt-tier-badge">Most loved</div>}
                <h4>{t.name}</h4>
                <div className="t-desc">{t.desc}</div>
                <div className="dt-tier-price">
                  <span className="amt">{t.price}</span>
                  <span className="per">{t.per}</span>
                </div>
                <div className="dt-tier-eq">{t.eq}</div>
                <ul>
                  {t.features.map((f, i) => (
                    <li key={i}><SvgIcon d={I.check} size={14} stroke={2.5}/> {f}</li>
                  ))}
                </ul>
                <button
                  className={t.featured ? 'btn btn-primary' : 'btn btn-ghost'}
                  style={{ width: '100%', justifyContent: 'center' }}
                  onClick={() => t.id !== 'free' && onSubscribe({ kind: 'plan', plan: t.id, billing: yearly ? 'yearly' : 'monthly' })}
                  disabled={t.id === 'free'}>
                  {t.cta}
                </button>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── App ───────────────────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accountTier": "guest",
  "restoreUsed": 0,
  "videoCredits": 0,
  "startPage": "home",
  "previewAsGuest": false,
  "aiModel": "auto"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [page, setPage] = useState(t.startPage || 'home');
  const [sampleId, setSampleId] = useState('bw');
  const [paywall, setPaywall] = useState(null);

  // Live session loaded from the API, if a cookie exists.
  const [liveAccount, setLiveAccount] = useState(null);
  const [liveRestorationId, setLiveRestorationId] = useState(null);
  const [uploadError, setUploadError] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [accountLoaded, setAccountLoaded] = useState(false);
  const [lastUpload, setLastUpload] = useState(null); // keep File for retry

  const refreshAccount = async () => {
    if (!window.API) return;
    try {
      const me = await window.API.me();
      setLiveAccount({
        tier: me.tier,
        restoreUsed: me.restoreUsed || 0,
        videoCredits: me.videoCredits || 0,
        email: me.email,
      });
    } catch (err) {
      if (err && err.status === 401) setLiveAccount(null);
    } finally {
      setAccountLoaded(true);
    }
  };

  useEffect(() => { refreshAccount(); }, []);

  // After magic-link or Stripe redirect — refetch account, clean the URL, show a banner.
  const [banner, setBanner] = useState(null);
  useEffect(() => {
    try {
      const p = new URLSearchParams(window.location.search);
      const authParam = p.get('auth');
      const checkoutParam = p.get('checkout');
      if (authParam === 'ok' || checkoutParam === 'success') refreshAccount();
      if (authParam || checkoutParam) {
        const url = new URL(window.location.href);
        url.searchParams.delete('auth');
        url.searchParams.delete('checkout');
        window.history.replaceState({}, '', url.toString());
        const msg =
          authParam === 'ok' ? 'Signed in — welcome.' :
          authParam === 'expired' ? 'That link expired — request a new one.' :
          authParam === 'used' ? 'That link was already used.' :
          authParam === 'invalid' ? 'That link is invalid.' :
          checkoutParam === 'success' ? 'Subscription active — thanks!' :
          checkoutParam === 'canceled' ? 'Checkout cancelled.' :
          null;
        if (msg) {
          setBanner({ kind: (authParam === 'ok' || checkoutParam === 'success') ? 'ok' : 'warn', msg });
          setTimeout(() => setBanner(null), 4000);
        }
      }
    } catch (_) {}
  }, []);

  const isLive = Boolean(liveAccount);
  const account = liveAccount || {
    tier: t.accountTier,
    restoreUsed: t.restoreUsed || 0,
    videoCredits: t.videoCredits || 0,
  };

  const startRestore = (id) => {
    setLiveRestorationId(null);
    setSampleId(id);
    setPage('processing');
  };

  const handleUploadFile = async (file) => {
    if (!file) return;
    if (!isLive) {
      // Unauthenticated: run the demo flow (samples).
      startRestore('bw');
      return;
    }
    setUploadError(null);
    setUploading(true);
    setLastUpload(file);
    try {
      const opts = t.aiModel && t.aiModel !== 'auto' ? { model: t.aiModel } : undefined;
      const r = await window.API.createRestoration(file, opts);
      setLiveRestorationId(r.id);
      setPage('processing');
    } catch (err) {
      if (err.status === 402) setPaywall('limit');
      else setUploadError(window.API.friendlyError(err));
    } finally {
      setUploading(false);
    }
  };

  const retryLastUpload = () => {
    setUploadError(null);
    if (lastUpload) handleUploadFile(lastUpload);
  };

  const handleProcessingDone = () => {
    if (!isLive && account.tier === 'free') setTweak('restoreUsed', account.restoreUsed + 1);
    if (isLive) refreshAccount();
    setPage('results');
  };
  const handleSaveRequest = () => {
    if (account.tier === 'free') { setPaywall('save'); return; }
    if (!liveRestorationId) return;
    // Download from the authenticated image endpoint. Because the request
    // carries the session cookie, the <a download> trick needs a Blob URL.
    (async () => {
      try {
        const r = await fetch(window.API.imageUrl(liveRestorationId, 'after'), { credentials: 'include' });
        if (!r.ok) throw new Error(`download_failed_${r.status}`);
        const blob = await r.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `restored-${liveRestorationId}.jpg`;
        document.body.appendChild(a);
        a.click();
        a.remove();
        setTimeout(() => URL.revokeObjectURL(url), 5000);
      } catch (err) {
        setUploadError(window.API.friendlyError(err));
      }
    })();
  };

  const handleAnimate = () => {
    // Animate is Pro-only (Family no longer includes video credits).
    if (account.tier !== 'pro') {
      setPaywall(account.tier === 'free' ? 'animate' : 'animate-pro-only');
      return;
    }
    // Pro user: the video-generation backend isn't built yet. Surface a
    // proper in-app notice instead of an alert().
    setBanner({ kind: 'warn', msg: 'Living Memory videos are coming soon — not yet available.' });
    setTimeout(() => setBanner(null), 4000);
  };

  const handleShare = async () => {
    if (!liveRestorationId) return;
    const shareUrl = window.API.imageUrl(liveRestorationId, 'after');
    // Prefer the native share sheet where the platform supports it
    // (iOS Safari, Android Chrome, some desktop browsers).
    if (navigator.share) {
      try {
        await navigator.share({ title: 'Restored photo', text: 'Restored with Restore Old Photos', url: shareUrl });
        return;
      } catch (err) {
        // User cancelled — no need to surface anything.
        if (err && err.name === 'AbortError') return;
      }
    }
    // Fallback: copy the image URL to the clipboard.
    try {
      await navigator.clipboard.writeText(shareUrl);
      setBanner({ kind: 'ok', msg: 'Link copied to clipboard.' });
      setTimeout(() => setBanner(null), 3000);
    } catch (_) {
      setBanner({ kind: 'warn', msg: 'Sharing isn\u2019t supported on this browser.' });
      setTimeout(() => setBanner(null), 3000);
    }
  };
  const handleSubscribe = async (choice) => {
    if (!isLive) {
      // Demo mode: flip the tweak tier so preview reflects the click.
      setTweak('accountTier', choice.plan);
      setTweak('videoCredits', choice.plan === 'pro' ? 10 : 0);
      setPaywall(null);
      return;
    }
    try {
      const session = await window.API.createCheckoutSession(choice);
      if (session && session.url) window.location.href = session.url;
      else setUploadError('Checkout is not available right now. Please try again later.');
    } catch (err) {
      console.error('checkout failed', err);
      setUploadError(window.API.friendlyError(err));
    }
  };
  const openUpgrade = () => setPaywall('default');

  // Brief loading splash while /auth/me resolves — avoids a flash of either the
  // sign-in gate (if a session exists) or demo data (if it doesn't).
  if (!accountLoaded && !t.previewAsGuest) {
    return (
      <div style={{
        minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: 'var(--bg-warm, #fbf3e7)',
      }}>
        <div style={{
          width: 24, height: 24, borderRadius: '50%',
          border: '3px solid rgba(0,0,0,.1)', borderTopColor: '#a0521b',
          animation: 'spin 0.8s linear infinite',
        }}/>
        <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
      </div>
    );
  }

  // Show a full-page sign-in gate to any visitor without a real session.
  // The tweaks panel unlocks demo mode via the `previewAsGuest` toggle so QA
  // can still inspect the signed-out UI without actually signing in.
  if (accountLoaded && !isLive && !t.previewAsGuest) {
    return <SignInGate />;
  }

  return (
    <>
      <TopBar page={page} setPage={setPage} account={account} onUpgrade={openUpgrade}/>

      {(banner || uploadError) && (
        <div style={{
          position: 'fixed', top: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 9999,
          background: uploadError ? '#b34040' : banner && banner.kind === 'ok' ? '#2f7a4a' : '#8a5a2a',
          color: '#fff', borderRadius: 10, padding: '10px 18px',
          fontFamily: 'var(--font-display)', fontSize: 14, boxShadow: '0 8px 24px rgba(0,0,0,.25)',
          display: 'flex', gap: 14, alignItems: 'center', maxWidth: 'calc(100vw - 32px)',
        }}>
          <span>{uploadError ? uploadError : banner.msg}</span>
          {uploadError && lastUpload && (
            <button onClick={retryLastUpload} style={{
              background: 'rgba(255,255,255,.2)', color: '#fff', border: 0,
              borderRadius: 6, padding: '6px 12px', fontFamily: 'inherit',
              fontSize: 13, cursor: 'pointer', fontWeight: 600,
            }}>Retry</button>
          )}
          <button onClick={() => { setUploadError(null); setBanner(null); }} style={{
            background: 'transparent', color: 'rgba(255,255,255,.8)', border: 0,
            cursor: 'pointer', fontSize: 18, lineHeight: 1, padding: 0,
          }} aria-label="Dismiss">×</button>
        </div>
      )}

      {uploading && (
        <div style={{
          position: 'fixed', inset: 0, zIndex: 9998,
          background: 'rgba(43, 32, 24, 0.75)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <div style={{
            background: '#fff', borderRadius: 14, padding: '28px 36px',
            fontFamily: 'var(--font-display)', fontSize: 15, color: 'var(--ink-1)',
            display: 'flex', gap: 14, alignItems: 'center',
            boxShadow: '0 20px 60px rgba(0,0,0,.3)',
          }}>
            <div style={{
              width: 22, height: 22, borderRadius: '50%',
              border: '3px solid rgba(0,0,0,.1)', borderTopColor: '#a0521b',
              animation: 'spin 0.8s linear infinite',
            }}/>
            Uploading your photo…
            <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
          </div>
        </div>
      )}

      {page === 'home' && (
        <HomePage
          onStart={startRestore}
          onUploadFile={handleUploadFile}
          account={account}
          onUpgrade={openUpgrade}
          aiModel={t.aiModel || 'auto'}
          setAiModel={(v) => setTweak('aiModel', v)}
        />
      )}
      {page === 'processing' && (
        <ProcessingPage
          sampleId={sampleId}
          restorationId={liveRestorationId}
          onDone={handleProcessingDone}
          onFailure={(e) => { setUploadError(window.API.friendlyError(e)); setPage('home'); }}
        />
      )}
      {page === 'results' && (
        <ResultsPage
          sampleId={sampleId}
          restorationId={liveRestorationId}
          onSaveRequest={handleSaveRequest}
          onAnimate={handleAnimate}
          onShare={handleShare}
          onRestoreAnother={() => { setLiveRestorationId(null); setPage('home'); }}
          account={account}
          onUpgrade={openUpgrade}
        />
      )}
      {page === 'library' && (
        <LibraryPage
          isLive={isLive}
          onOpen={isLive ? (id) => { setLiveRestorationId(id); setPage('results'); } : undefined}
        />
      )}
      {page === 'account' && (
        <AccountPage
          account={account}
          onUpgrade={openUpgrade}
          onSignOut={isLive ? async () => { try { await window.API.logout(); } catch (_) {} window.location.href = '/'; } : null}
        />
      )}

      <footer style={{
        maxWidth: 1200, margin: '40px auto 0', padding: '20px 32px 24px',
        borderTop: '1px solid rgba(0,0,0,.08)',
        display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center',
        fontFamily: 'var(--font-body)', fontSize: 13, color: 'var(--muted-1)',
      }}>
        <span>© 2026</span>
        <span style={{ opacity: 0.4 }}>·</span>
        <a href="/privacy.html" style={{ color: 'inherit' }}>Privacy</a>
        <a href="/terms.html" style={{ color: 'inherit' }}>Terms</a>
        <a href="mailto:hello@obviousbubbles.pt" style={{ color: 'inherit' }}>Contact</a>
        <span style={{ marginLeft: 'auto' }}>
          Made with <span role="img" aria-label="love" style={{ color: '#e53935' }}>♥</span> in Portugal
        </span>
      </footer>

      {paywall && (
        <Paywall
          trigger={paywall}
          onClose={() => setPaywall(null)}
          onSubscribe={handleSubscribe}
        />
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Jump to page"/>
        <TweakButton label="Home / upload" onClick={() => setPage('home')}/>
        <TweakButton label="Processing"    onClick={() => { setSampleId('bw'); setPage('processing'); }}/>
        <TweakButton label="Results"       onClick={() => setPage('results')}/>
        <TweakButton label="Library"       onClick={() => setPage('library')}/>
        <TweakButton label="Account"       onClick={() => setPage('account')}/>

        <TweakSection label="Sample photo"/>
        <TweakRadio
          label="Era" value={sampleId}
          options={[
            { value: 'bw', label: '1940s B&W' },
            { value: 'color70s', label: '1970s color' },
          ]}
          onChange={setSampleId}/>

        <TweakSection label="Account state"/>
        <TweakRadio
          label="Tier" value={t.accountTier}
          options={[
            { value: 'free', label: 'Free' },
            { value: 'family', label: 'Family' },
            { value: 'pro', label: 'Pro' },
          ]}
          onChange={v => setTweak('accountTier', v)}/>
        <TweakSlider label="Free restores used" min={0} max={3} step={1}
          value={t.restoreUsed} onChange={v => setTweak('restoreUsed', v)}/>
        <TweakSlider label="Video credits" min={0} max={10} step={1}
          value={t.videoCredits} onChange={v => setTweak('videoCredits', v)}/>

        <TweakSection label="AI model (restoration)"/>
        <TweakRadio
          label="Model" value={t.aiModel || 'auto'}
          options={[
            { value: 'auto',  label: 'Auto (server default)' },
            { value: 'pro',   label: 'Gemini 3 Pro (slower, higher quality)' },
            { value: 'flash', label: 'Gemini 2.5 Flash (faster, cheaper)' },
          ]}
          onChange={v => setTweak('aiModel', v)}/>

        <TweakSection label="Preview paywalls"/>
        <TweakButton label="Paywall: Save HD"  onClick={() => setPaywall('save')}/>
        <TweakButton label="Paywall: Animate"  onClick={() => setPaywall('animate')}/>
        <TweakButton label="Paywall: Hit limit" onClick={() => setPaywall('limit')}/>
      </TweaksPanel>
    </>
  );
}

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