// ============================================
// Reliq — Free preview scan funnel
// Public route: /free-scan
// Phases: entry → progress → result
// ============================================

// ── Analytics placeholders ────────────────────────────────────────────────────
const trackEvent = (event, props) => {
  // TODO: wire to analytics (PostHog, Amplitude, Segment, etc.)
  console.log("[Reliq Analytics]", event, props || {});
};
// Named exports for readability at call-sites:
const track = {
  clickedFreeScanCta:        ()      => trackEvent("clicked_free_scan_cta"),
  uploadedPreviewBuild:      (name)  => trackEvent("uploaded_preview_build", { file: name }),
  startedDemoScan:           ()      => trackEvent("started_demo_scan"),
  completedPreviewScan:      (score) => trackEvent("completed_preview_scan", { score }),
  clickedUnlockFullReport:   (via)   => trackEvent("clicked_unlock_full_report", { via }),
  createdAccountFromPreview: ()      => trackEvent("created_account_from_preview_scan"),
};

// ── Demo scan data ────────────────────────────────────────────────────────────
const DEMO_RESULT = {
  appName:   "Roamly",
  letters:   "R",
  grad:      "linear-gradient(135deg,#5B9DFF,#A78BFA)",
  bundle:    "com.roamly.app",
  version:   "1.4.0",
  file:      "Roamly-1.4.0-release.aab",
  score:     64,
  verdict:   "needs_fixes",
  critCount: 1,
  warnCount: 2,
  infoCount: 3,
  stores:    ["play", "app"],
  topFindings: [
    {
      sev:  "critical",
      code: "DATA_SAFETY_COMPLETENESS",
      desc: "Data Safety form declares no third-party data sharing, but 3 tracker SDKs were detected in the binary (Facebook SDK, AppsFlyer, Adjust).",
      store: "play",
    },
    {
      sev:  "warning",
      code: "CONTACTS_PERMISSION_AUDIT",
      desc: "READ_CONTACTS is declared in the manifest with no visible foreground UI that justifies it. Travel apps rarely qualify under Google Play policy.",
      store: "play",
    },
    {
      sev:  "warning",
      code: "PRIVACY_POLICY_REDIRECT",
      desc: "Privacy policy URL returns HTTP 301 → redirects to a non-HTTPS endpoint. Automated store crawlers treat this as a missing policy.",
      store: "both",
    },
  ],
  policyRef: "Google Play Policy · Data Safety (Aug 2025)",
  fixPreview: "Open Play Console → Store presence → Data Safety. Under 'Data shared with third parties', add entries for Facebook SDK, AppsFlyer, and Adjust. Data Safety mismatch is the #1 Google Play rejection reason in 2024–25.",
};

const SCAN_STEPS = [
  { key: "extract", label: "Extracting build metadata",       sub: "APK/AAB decompile, binary analysis"            },
  { key: "perms",   label: "Reading permissions",             sub: "Manifest audit, dangerous permission scan"      },
  { key: "sdks",    label: "Detecting SDKs",                  sub: "Third-party library fingerprinting"            },
  { key: "privacy", label: "Checking privacy declarations",   sub: "Data Safety, privacy manifests, policy URL"     },
  { key: "risk",    label: "Reviewing store listing risk",    sub: "Metadata, keywords, content rating"             },
  { key: "score",   label: "Generating readiness score",      sub: "Weighted scoring across all categories"         },
];

// Step durations (ms). Total ≈ 4.2 s — short enough to feel snappy.
const STEP_DURATIONS = { extract: 500, perms: 600, sdks: 800, privacy: 900, risk: 700, score: 700 };

// ── VALID file extensions + size limit ───────────────────────────────────────
const VALID_EXTS    = [".apk", ".aab", ".ipa"];
const MAX_FILE_BYTES = 250 * 1024 * 1024; // 250 MB — enforced frontend-side before upload
const fileIsValid   = (name) => VALID_EXTS.some(ext => name.toLowerCase().endsWith(ext));

// ── Severity colors ───────────────────────────────────────────────────────────
const SEV_COLOR = { critical: "var(--red)", warning: "var(--amber)", info: "var(--blue)", pass: "var(--teal)" };
const SEV_BG    = { critical: "var(--red-bg)", warning: "var(--amber-bg)", info: "var(--blue-bg)", pass: "var(--teal-bg)" };
const SEV_BORDER = { critical: "rgba(239,68,68,0.25)", warning: "rgba(245,158,11,0.25)", info: "rgba(91,157,255,0.25)", pass: "var(--line-3)" };

// ── Phase 1: Entry ────────────────────────────────────────────────────────────
const FreeScanEntry = ({ onUpload, onDemo }) => {
  const [drag, setDrag]     = React.useState(false);
  const [error, setError]   = React.useState(null);
  const inputRef            = React.useRef(null);

  const handleFile = (f) => {
    if (!f) return;
    if (!fileIsValid(f.name)) {
      setError(`"${f.name}" isn't a valid build file. Please upload an .apk, .aab, or .ipa.`);
      return;
    }
    if (f.size > MAX_FILE_BYTES) {
      setError(`"${f.name}" is too large. Maximum file size is 250 MB.`);
      return;
    }
    setError(null);
    onUpload(f.name);
  };

  // Reset the input value after every selection so the user can re-select the
  // same file after correcting it (e.g. after a size or type error).
  const onInputChange = (e) => {
    handleFile(e.target.files?.[0]);
    e.target.value = "";
  };

  const onDrop = (e) => {
    e.preventDefault();
    setDrag(false);
    handleFile(e.dataTransfer.files?.[0]);
  };

  return (
    <div className="fade-up" style={{maxWidth:640, margin:"0 auto"}}>
      {/* Upload zone */}
      <div
        className={`uploader ${drag ? "drag" : ""}`}
        style={{cursor:"pointer"}}
        onDragOver={e => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={onDrop}
        onClick={() => inputRef.current?.click()}
      >
        <input
          ref={inputRef}
          type="file"
          accept=".apk,.aab,.ipa"
          style={{display:"none"}}
          onChange={onInputChange}
        />
        <div className="ic-big"><Icon name="upload" size={22} /></div>
        <h3>Drop your build here</h3>
        <p>APK · AAB · IPA — Reliq runs 38 store-policy checks in under 3 minutes.</p>
        <button className="btn btn-primary" onClick={e => { e.stopPropagation(); inputRef.current?.click(); }}>
          Choose file
        </button>
        <div className="types">
          <span>.APK</span><span>.AAB</span><span>.IPA</span>
          <span style={{borderColor:"transparent", color:"var(--fg-3)"}}>· max 250 MB</span>
        </div>
      </div>

      {error && (
        <div style={{
          marginTop:12, padding:"10px 14px", borderRadius:8,
          background:"var(--red-bg)", border:"1px solid rgba(239,68,68,0.25)",
          color:"var(--red)", fontSize:13, display:"flex", alignItems:"center", gap:8,
        }}>
          <Icon name="alert" size={14} /> {error}
        </div>
      )}

      {/* Divider */}
      <div style={{
        display:"flex", alignItems:"center", gap:16,
        margin:"28px 0 20px", color:"var(--fg-3)",
        fontFamily:"var(--font-mono)", fontSize:11, letterSpacing:"0.1em", textTransform:"uppercase",
      }}>
        <div style={{flex:1, height:1, background:"var(--line-1)"}} />
        don't have a build ready?
        <div style={{flex:1, height:1, background:"var(--line-1)"}} />
      </div>

      {/* Demo scan option */}
      <div
        className="card"
        style={{
          display:"flex", alignItems:"center", gap:16, padding:"18px 20px",
          cursor:"pointer", transition:"border-color .15s",
        }}
        onClick={onDemo}
        onMouseEnter={e => e.currentTarget.style.borderColor = "var(--line-3)"}
        onMouseLeave={e => e.currentTarget.style.borderColor = ""}
      >
        <div style={{
          width:44, height:44, borderRadius:10, flexShrink:0,
          background:"var(--teal-bg)", border:"1px solid var(--line-3)",
          display:"grid", placeItems:"center", color:"var(--teal)",
        }}>
          <Icon name="play" size={18} />
        </div>
        <div style={{flex:1}}>
          <div style={{fontWeight:600, fontSize:14}}>Try demo scan</div>
          <div style={{fontSize:12.5, color:"var(--fg-2)", marginTop:2}}>
            See a realistic scan of Roamly — a sample travel app with known issues.
          </div>
        </div>
        <Icon name="arrow-right" size={15} style={{color:"var(--fg-3)", flexShrink:0}} />
      </div>

      {/* Trust note */}
      <div style={{
        marginTop:20, textAlign:"center",
        fontFamily:"var(--font-mono)", fontSize:12, color:"var(--fg-3)",
        display:"flex", alignItems:"center", justifyContent:"center", gap:16,
      }}>
        <span><Icon name="lock" size={11} style={{display:"inline", verticalAlign:"middle", marginRight:4}} />No credit card required</span>
        <span style={{color:"var(--line-2)"}}>·</span>
        <span>Results in under 3 minutes</span>
        <span style={{color:"var(--line-2)"}}>·</span>
        <span>Android + iOS</span>
      </div>
    </div>
  );
};

// ── Phase 2: Progress ─────────────────────────────────────────────────────────
const FreeScanProgress = ({ fileName, onComplete }) => {
  const [doneKeys, setDoneKeys] = React.useState([]);

  React.useEffect(() => {
    // Build cumulative fire times: initial delay 300ms, then each step fires
    // after the previous step's duration has elapsed.
    let t = 300;
    const timers = SCAN_STEPS.map((s) => {
      const fireAt = t;
      t += STEP_DURATIONS[s.key];
      return setTimeout(() => setDoneKeys(prev => [...prev, s.key]), fireAt);
    });
    // t now equals the time the last step was marked + its own duration.
    // Fire onComplete 350ms after the last step mark (t - last_duration + 350).
    const lastDuration = STEP_DURATIONS[SCAN_STEPS[SCAN_STEPS.length - 1].key];
    const done = setTimeout(onComplete, t - lastDuration + 350);
    return () => { timers.forEach(clearTimeout); clearTimeout(done); };
  }, []);

  const activeIdx = SCAN_STEPS.findIndex(s => !doneKeys.includes(s.key));

  return (
    <div className="fade-up" style={{maxWidth:560, margin:"0 auto"}}>
      <div className="card">
        <div className="card-head">
          <div style={{display:"flex", alignItems:"center", gap:10}}>
            <svg width="14" height="14" viewBox="0 0 24 24"
                 style={{animation:"spin 1s linear infinite", color:"var(--teal)", flexShrink:0}}>
              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2.5"
                      fill="none" strokeDasharray="40 60" strokeLinecap="round" />
            </svg>
            <div className="card-title">Running preview scan…</div>
          </div>
          <span className="card-sub" style={{marginLeft:"auto"}}>
            {fileName ? fileName : "Demo · Roamly 1.4.0"}
          </span>
        </div>

        <div className="card-body" style={{display:"flex", flexDirection:"column", gap:18}}>
          {SCAN_STEPS.map((s, i) => {
            const done   = doneKeys.includes(s.key);
            const active = i === activeIdx;
            return (
              <div key={s.key} style={{display:"flex", alignItems:"flex-start", gap:14}}>
                {/* Step indicator */}
                <div style={{
                  width:24, height:24, borderRadius:"50%", flexShrink:0, marginTop:1,
                  display:"grid", placeItems:"center",
                  background: done   ? "var(--teal-bg)" : "var(--bg-3)",
                  border:     `1px solid ${done ? "var(--teal)" : "var(--line-2)"}`,
                  color:      done   ? "var(--teal)"   : "var(--fg-3)",
                  transition: "background .3s, border-color .3s",
                }}>
                  {done
                    ? <Icon name="check" size={11} stroke={2.5} />
                    : active
                      ? <svg width="10" height="10" viewBox="0 0 24 24"
                             style={{animation:"spin 1s linear infinite"}}>
                          <circle cx="12" cy="12" r="10" stroke="currentColor"
                                  strokeWidth="3" fill="none"
                                  strokeDasharray="40 60" strokeLinecap="round" />
                        </svg>
                      : <span style={{fontFamily:"var(--font-mono)", fontSize:10}}>{i + 1}</span>
                  }
                </div>

                {/* Label */}
                <div style={{flex:1}}>
                  <div style={{
                    fontSize:13.5, fontWeight:500,
                    color: done ? "var(--fg-1)" : active ? "var(--fg-0)" : "var(--fg-3)",
                    transition:"color .3s",
                  }}>
                    {s.label}
                  </div>
                  <div style={{
                    fontSize:11.5, color:"var(--fg-3)",
                    fontFamily:"var(--font-mono)", marginTop:2,
                  }}>
                    {s.sub}
                  </div>
                  {active && (
                    <div style={{
                      height:3, background:"var(--bg-3)", borderRadius:2,
                      marginTop:8, overflow:"hidden",
                    }}>
                      <div style={{
                        height:"100%", width:"60%",
                        background:"var(--teal)", borderRadius:2,
                        animation:"sweep 1.2s ease-in-out infinite",
                      }} />
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </div>

      <div style={{
        marginTop:16, textAlign:"center",
        fontFamily:"var(--font-mono)", fontSize:12, color:"var(--fg-3)",
      }}>
        Checking store policies · This takes about 30 seconds on the free tier
      </div>
    </div>
  );
};

// ── Scan context helpers ──────────────────────────────────────────────────────
// Delegates to window.reliqSubscription (reliq-state.jsx) — the canonical store
// for all preview context. Local fallbacks guard against load-order edge cases.

const LS_SCAN_ID          = "reliq_preview_scan_id";       // preview scan ID — unchanged
const LS_PREVIEW_REDIRECT = "reliq_preview_pending_redirect"; // new canonical preview redirect key
const LS_REDIRECT_LEGACY  = "reliq_pending_redirect";       // DEPRECATED — read-only fallback, never write

// Write preview context so signup can redirect back to the result page after auth.
// Redirects to the demo scan result (scn_2k8x), not the generated previewScanId.
// The previewScanId signals that a preview exists; it is not itself the route.
// Both scanId (scn_preview_*) and reportId (rep_preview_*) are persisted so that
// signup.jsx can restore the full preview context after authentication.
const preservePreviewContext = (scanId, reportId) => {
  if (window.reliqSubscription) {
    // setVisitorPreview stores both IDs and writes to reliq_preview_pending_redirect.
    window.reliqSubscription.setVisitorPreview(scanId, reportId || null, "free-scan", "scan/scn_2k8x");
  } else {
    // Local fallback — should never fire in production (reliq-state.jsx loads first).
    try {
      localStorage.setItem(LS_SCAN_ID,                       scanId);
      if (reportId) localStorage.setItem("reliq_preview_report_id", reportId);
      localStorage.setItem("reliq_signup_source",            "free-scan");
      localStorage.setItem(LS_PREVIEW_REDIRECT,              "scan/scn_2k8x"); // new key only
    } catch (_) {}
  }
};

// Returns the post-auth redirect destination (no leading slash).
// Reads the new preview redirect key first; falls back to the legacy shared key
// for sessions created before the Step 3 redirect key migration.
const readPendingRedirect = () => {
  if (window.reliqSubscription) {
    return window.reliqSubscription.getBestPreviewRedirect() || "scan/scn_2k8x";
  }
  try {
    return localStorage.getItem(LS_PREVIEW_REDIRECT)
        || localStorage.getItem(LS_REDIRECT_LEGACY)
        || "scan/scn_2k8x";
  } catch (_) { return "scan/scn_2k8x"; }
};

// Clear preview context when the user signs in directly from the free-scan page.
const clearPreviewContext = () => {
  if (window.reliqSubscription) {
    window.reliqSubscription.clearPreviewContext();
  } else {
    // Local fallback — clears both the new key and the legacy key.
    try {
      localStorage.removeItem(LS_SCAN_ID);
      localStorage.removeItem("reliq_signup_source");
      localStorage.removeItem(LS_PREVIEW_REDIRECT);
      localStorage.removeItem(LS_REDIRECT_LEGACY); // legacy — clear for backwards compat
    } catch (_) {}
  }
};

// ── Phase 3: Preview Result ───────────────────────────────────────────────────
const FreeScanResult = ({ result, go, scanId, reportId }) => {
  const { score, verdict, critCount, warnCount, infoCount, stores,
          appName, letters, grad, bundle, version, file,
          topFindings, policyRef, fixPreview } = result;

  const [githubLoading, setGithubLoading] = React.useState(false);
  const [githubError,   setGithubError]   = React.useState(null);

  const verdictLabel = verdict === "needs_fixes" ? "Needs Fixes" : verdict === "pass" ? "Likely to Pass" : "At Risk";
  const verdictCls   = verdict === "needs_fixes" ? "v-needs" : verdict === "pass" ? "v-pass" : "v-reject";
  const verdictIcon  = verdict === "needs_fixes" ? "alert" : verdict === "pass" ? "check-circle" : "x-circle";

  // ── "Create an account" CTA ───────────────────────────────────────────────
  const goToSignup = () => {
    track.clickedUnlockFullReport("create_account");
    preservePreviewContext(scanId, reportId);
    go("/signup");
  };

  // ── "Continue with GitHub" mock OAuth flow ────────────────────────────────
  const doGithubAuth = () => {
    if (githubLoading) return;
    setGithubError(null);
    setGithubLoading(true);
    track.clickedUnlockFullReport("github");
    preservePreviewContext(scanId, reportId);

    // Simulate GitHub OAuth round-trip (~1.6 s)
    // Swap this block for a real window.open OAuth flow when auth is wired up.
    setTimeout(() => {
      setGithubLoading(false);
      // Mock: always succeeds. On real failure, call setGithubError() instead.
      track.createdAccountFromPreview();
      clearPreviewContext();
      go(readPendingRedirect());
    }, 1600);
  };

  return (
    <div className="fade-up" style={{maxWidth:700, margin:"0 auto"}}>

      {/* App + Score header */}
      <div className="card" style={{marginBottom:16}}>
        <div className="card-body" style={{display:"flex", alignItems:"center", gap:20}}>
          <AppIcon letters={letters} size={52} gradient={grad} />
          <div style={{flex:1, minWidth:0}}>
            <div style={{fontWeight:600, fontSize:16, letterSpacing:"-0.01em"}}>{appName}</div>
            <div style={{fontFamily:"var(--font-mono)", fontSize:11.5, color:"var(--fg-3)", marginTop:2}}>
              {bundle} · v{version}
            </div>
            <div style={{display:"flex", gap:8, marginTop:8}}>
              {stores.map(s => <StoreChip key={s} store={s} />)}
            </div>
          </div>
          {/* Compact score ring */}
          <div className="score-ring-sm" style={{textAlign:"center", flexShrink:0}}>
            <ScoreRing value={score} size={96} stroke={7} />
          </div>
        </div>

        {/* Verdict banner */}
        <div style={{borderTop:"1px solid var(--line-1)", padding:"14px 18px"}}>
          <div className={`verdict ${verdictCls}`} style={{padding:"14px 18px"}}>
            <div className="icon-wrap">
              <Icon name={verdictIcon} size={22} />
            </div>
            <div>
              <div className="label">Preview verdict</div>
              <div className="title" style={{fontSize:18}}>{verdictLabel}</div>
            </div>
            <div className="stamp">
              <div style={{fontSize:18, fontWeight:700, fontFamily:"var(--font-pixel-square)", letterSpacing:0}}>
                {critCount} critical
              </div>
              <div style={{marginTop:3}}>
                {warnCount} warnings · {infoCount} info
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Top 3 findings */}
      <div style={{marginBottom:16}}>
        <div style={{
          fontFamily:"var(--font-mono)", fontSize:10.5,
          letterSpacing:"0.14em", textTransform:"uppercase",
          color:"var(--fg-3)", marginBottom:10,
          display:"flex", alignItems:"center", justifyContent:"space-between",
        }}>
          <span>Top findings</span>
          <span>Showing 3 of {critCount + warnCount + infoCount + 4} · <span style={{color:"var(--fg-3)"}}>full list locked</span></span>
        </div>

        <div style={{
          background:"var(--bg-1)", border:"1px solid var(--line-1)",
          borderRadius:"var(--r-lg)", overflow:"hidden",
        }}>
          {topFindings.map((f, i) => (
            <div key={f.code} style={{
              padding:"14px 18px",
              borderTop: i > 0 ? "1px solid var(--line-1)" : "none",
              borderLeft:`3px solid ${SEV_COLOR[f.sev]}`,
              display:"grid", gridTemplateColumns:"108px 1fr",
              gap:14, alignItems:"flex-start",
            }}>
              <div>
                <span className={`sev sev-${f.sev}`}>{f.sev}</span>
              </div>
              <div>
                <div style={{fontWeight:500, fontSize:13, marginBottom:4, fontFamily:"var(--font-mono)"}}>
                  {f.code}
                </div>
                <div style={{fontSize:12.5, color:"var(--fg-2)", lineHeight:1.5}}>{f.desc}</div>
                {f.store !== "both" && (
                  <div style={{marginTop:6}}>
                    <StoreChip store={f.store} />
                  </div>
                )}
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Fix preview (1 of N) */}
      <div style={{marginBottom:20}}>
        <div style={{
          fontFamily:"var(--font-mono)", fontSize:10.5,
          letterSpacing:"0.14em", textTransform:"uppercase",
          color:"var(--fg-3)", marginBottom:10,
          display:"flex", alignItems:"center", justifyContent:"space-between",
        }}>
          <span>Fix preview</span>
          <span>1 of {critCount + warnCount} · <span style={{color:"var(--fg-3)"}}>rest locked</span></span>
        </div>

        <div className="card">
          <div className="card-body">
            <div style={{
              display:"inline-flex", alignItems:"center", gap:6,
              fontFamily:"var(--font-mono)", fontSize:11.5, color:"var(--blue)",
              padding:"4px 8px", borderRadius:4,
              border:"1px solid rgba(91,157,255,0.25)", background:"var(--blue-bg)",
              marginBottom:10,
            }}>
              <Icon name="book" size={11} /> {policyRef}
            </div>
            <p style={{fontSize:13.5, color:"var(--fg-1)", lineHeight:1.65, margin:0}}>
              {fixPreview}
            </p>
          </div>
        </div>
      </div>

      {/* Lock panel */}
      <div style={{
        border:"1px solid var(--line-2)", borderRadius:"var(--r-xl)",
        background:"radial-gradient(ellipse 80% 60% at 50% 0%, var(--teal-tint), transparent 60%), var(--bg-1)",
        padding:"40px 36px 36px",
        textAlign:"center",
        position:"relative", overflow:"hidden",
      }}>
        {/* Lock icon */}
        <div style={{
          width:48, height:48, borderRadius:"var(--r-md)",
          background:"var(--bg-3)", border:"1px solid var(--line-2)",
          display:"inline-grid", placeItems:"center", color:"var(--fg-2)",
          marginBottom:16,
        }}>
          <Icon name="lock" size={22} />
        </div>

        <h3 style={{
          fontFamily:"var(--font-display)", fontSize:20, fontWeight:600,
          letterSpacing:"-0.015em", margin:"0 0 10px",
        }}>
          Unlock the full report
        </h3>
        <p style={{color:"var(--fg-2)", fontSize:14, lineHeight:1.6, maxWidth:480, margin:"0 auto 24px"}}>
          Create an account to unlock the full report, fix plan,
          PDF export, and re-scan history.
        </p>

        {/* Locked feature list */}
        <div style={{
          display:"flex", flexWrap:"wrap", gap:8, justifyContent:"center",
          marginBottom:28,
        }}>
          {["All findings", "Full fix plan", "PDF export", "Shareable link",
            "Dev tickets", "Re-scan history", "CI/CD setup"].map(f => (
            <span key={f} style={{
              display:"inline-flex", alignItems:"center", gap:5,
              padding:"4px 10px", borderRadius:999,
              border:"1px solid var(--line-2)", background:"var(--bg-2)",
              fontSize:12, color:"var(--fg-2)", fontFamily:"var(--font-mono)",
            }}>
              <Icon name="lock" size={10} /> {f}
            </span>
          ))}
        </div>

        {/* CTAs */}
        <div style={{display:"flex", gap:10, justifyContent:"center", flexWrap:"wrap"}}>
          <button
            className="btn btn-primary btn-lg"
            onClick={goToSignup}
          >
            Create an account <Icon name="arrow-right" size={14} />
          </button>
          <button
            className="btn btn-lg"
            onClick={doGithubAuth}
            disabled={githubLoading}
            style={{
              display:"flex", alignItems:"center", gap:8,
              opacity: githubLoading ? 0.7 : 1,
              cursor:  githubLoading ? "not-allowed" : "pointer",
              minWidth: 220,
              justifyContent: "center",
            }}
          >
            {githubLoading
              ? <>
                  <svg width="14" height="14" viewBox="0 0 24 24"
                       style={{animation:"spin 1s linear infinite", flexShrink:0}}>
                    <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2.5"
                            fill="none" strokeDasharray="40 60" strokeLinecap="round" />
                  </svg>
                  Connecting GitHub…
                </>
              : <><Icon name="github" size={15} /> Continue with GitHub</>
            }
          </button>
        </div>

        {/* GitHub error message */}
        {githubError && (
          <div style={{
            marginTop:14, padding:"9px 14px", borderRadius:8,
            background:"var(--red-bg)", border:"1px solid rgba(239,68,68,0.25)",
            color:"var(--red)", fontSize:13,
            display:"inline-flex", alignItems:"center", gap:8,
          }}>
            <Icon name="alert" size={13} /> {githubError}
          </div>
        )}

        <div style={{
          marginTop:18, fontFamily:"var(--font-mono)", fontSize:11.5, color:"var(--fg-3)",
        }}>
          No credit card required · your scan is preserved
        </div>
      </div>
    </div>
  );
};

// ── Root component ─────────────────────────────────────────────────────────────
const FreeScan = ({ go }) => {
  const [phase, setPhase]       = React.useState("entry");   // "entry" | "progress" | "result"
  const [fileName, setFileName] = React.useState(null);      // real upload name, or null for demo
  const [isDemo, setIsDemo]     = React.useState(false);
  const resultRef               = React.useRef(DEMO_RESULT);

  // Stable preview scan/report IDs — generated once per component mount.
  // Used to preserve context for signup/auth redirect.
  // scanId  (scn_preview_*): signals a preview exists and identifies this session's scan.
  // reportId (rep_preview_*): identifies the preview report for post-signup deep-linking.
  const previewScanId   = React.useRef(
    "scn_preview_" + Math.random().toString(36).slice(2, 7)
  );
  const previewReportId = React.useRef(
    "rep_preview_" + Math.random().toString(36).slice(2, 7)
  );

  // Track landing on this page (CTA was clicked)
  React.useEffect(() => {
    track.clickedFreeScanCta();
  }, []);

  // Track completion + persist context when result is ready.
  // Pre-seeds localStorage so the preview scan+report are stored even before the user
  // clicks a CTA — the CTAs re-write the same values, so this is always idempotent.
  React.useEffect(() => {
    if (phase === "result") {
      track.completedPreviewScan(resultRef.current.score);
      preservePreviewContext(previewScanId.current, previewReportId.current);
    }
  }, [phase]);

  const handleUpload = (name) => {
    track.uploadedPreviewBuild(name);
    setFileName(name);
    setIsDemo(false);
    // Overlay the result with user's file name for realism
    resultRef.current = {
      ...DEMO_RESULT,
      file:    name,
      appName: name.replace(/[-_].*/, "").replace(/\.(apk|aab|ipa)$/i, "") || DEMO_RESULT.appName,
    };
    setPhase("progress");
  };

  const handleDemo = () => {
    track.startedDemoScan();
    setIsDemo(true);
    setFileName(null);
    resultRef.current = DEMO_RESULT;
    setPhase("progress");
  };

  const handleProgressComplete = () => {
    setPhase("result");
  };

  // Headline + subtext vary by phase
  const headings = {
    entry: {
      eyebrow: "Free preview · no account needed",
      h1:      "Run a free store-readiness preview.",
      sub:     "Upload an APK, AAB, or IPA. Reliq checks store-policy risk before you submit.",
    },
    progress: {
      eyebrow: isDemo ? "Demo scan · Roamly 1.4.0" : `Scanning · ${fileName}`,
      h1:      "Checking your build…",
      sub:     "Running 38 store-policy checks across permissions, privacy, security, and metadata.",
    },
    result: {
      eyebrow: isDemo ? "Demo results · Roamly 1.4.0" : `Preview results · ${fileName}`,
      h1:      "Here's your store-readiness preview.",
      sub:     "Showing 3 of " + (DEMO_RESULT.critCount + DEMO_RESULT.warnCount + DEMO_RESULT.infoCount + 4) + " findings. Create an account to see the full report and fix plan.",
    },
  };

  const { eyebrow, h1, sub } = headings[phase];

  return (
    <LandingLayout go={go} pageTitle="Free Scan Preview">
      {/* Section heading */}
      <div style={{textAlign:"center", marginBottom:44}}>
        <div style={{
          fontFamily:"var(--font-mono)", fontSize:11, letterSpacing:"0.14em",
          textTransform:"uppercase", color:"var(--teal)", marginBottom:12,
          display:"flex", alignItems:"center", justifyContent:"center", gap:8,
        }}>
          {phase === "progress" && (
            <svg width="8" height="8" viewBox="0 0 24 24"
                 style={{animation:"spin 1s linear infinite", display:"inline-block"}}>
              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"
                      fill="none" strokeDasharray="40 60" strokeLinecap="round" />
            </svg>
          )}
          {eyebrow}
        </div>
        <h1 style={{
          fontFamily:"var(--font-display)", fontSize:"clamp(26px, 4vw, 40px)",
          fontWeight:600, letterSpacing:"-0.02em", margin:"0 0 14px", lineHeight:1.1,
        }}>{h1}</h1>
        <p style={{
          color:"var(--fg-2)", fontSize:15.5, lineHeight:1.6,
          maxWidth:520, margin:"0 auto",
        }}>{sub}</p>

        {/* Phase progress dots */}
        <div style={{
          display:"flex", alignItems:"center", justifyContent:"center",
          gap:8, marginTop:24,
        }}>
          {["entry","progress","result"].map((p, i) => (
            <div key={p} style={{
              width: phase === p ? 20 : 7,
              height:7, borderRadius:999,
              background: phase === p
                ? "var(--teal)"
                : ["entry","progress","result"].indexOf(phase) > i
                  ? "var(--teal-bg)"
                  : "var(--bg-4)",
              border: phase !== p && ["entry","progress","result"].indexOf(phase) > i
                ? "1px solid var(--line-3)" : "none",
              transition:"all .25s",
            }} />
          ))}
        </div>
      </div>

      {/* Phase content */}
      {phase === "entry"    && <FreeScanEntry onUpload={handleUpload} onDemo={handleDemo} />}
      {phase === "progress" && <FreeScanProgress fileName={fileName} onComplete={handleProgressComplete} />}
      {phase === "result"   && <FreeScanResult result={resultRef.current} go={go} scanId={previewScanId.current} reportId={previewReportId.current} />}

      {/* Back link (entry only) */}
      {phase === "entry" && (
        <div style={{textAlign:"center", marginTop:32}}>
          <button
            className="btn btn-ghost btn-sm"
            onClick={() => go("/")}
          >
            ← Back to home
          </button>
        </div>
      )}
    </LandingLayout>
  );
};

Object.assign(window, { FreeScan });
