// ============================================
// Reliq — Scan Results (full product surface)
// ============================================

// ── Scan-results view template ──────────────────────────────────────────────
// Live data (score, verdict, app, findings) is fetched from GET /v1/scans/:scanId
// (+ /findings) and overlaid below. The mobile-extraction tabs (permissions, SDK
// inventory, privacy declarations, store listing) have NO public backend endpoint
// in the generalized API, so they render from this structural template.
const SCAN_TEMPLATE = {
  app: {
    name: "Roamly",
    letters: "R",
    grad: "linear-gradient(135deg,#5B9DFF,#A78BFA)",
    version: "1.4.0",
    build: "142",
    bundle: "com.roamly.app",
    file: "Roamly-1.4.0-release.aab · 47.3 MB",
    category: "Travel",
    rating: "Everyone",
  },
  score: 78,
  verdict: "needs_fixes",
  ranAt: "Today, 14:32",
  scanId: "scn_2k8x",    // scan identifier — scn_ prefix only
  reportId: "rep_2k8x",  // linked report identifier — rep_ prefix only; used for share URLs and exports
  duration: "2 min 14 sec",
  byStore: {
    play: { score: 72, crit: 1, warn: 4, info: 3, pass: 12 },
    app:  { score: 84, crit: 0, warn: 3, info: 2, pass: 13 },
  },
  breakdown: [
    { name: "Permissions",    score: 70, level: "warn" },
    { name: "Privacy",        score: 55, level: "bad"  },
    { name: "Security",       score: 90, level: "ok"   },
    { name: "Content Policy", score: 82, level: "ok"   },
    { name: "Metadata",       score: 88, level: "ok"   },
    { name: "Store Listing",  score: 92, level: "ok"   },
  ],
  categories: {
    play: [
      {
        id: "privacy", name: "Privacy", icon: "eye", critCount: 1, warnCount: 2,
        findings: [
          { sev: "critical", name: "DATA_SAFETY_COMPLETENESS",
            desc: "Data Safety form declares no third-party data sharing, but 3 tracker SDKs were detected in the binary.",
            rec: "Open Play Console → Store presence → Data Safety. Under 'Data shared with third parties', add entries for Facebook SDK, AppsFlyer, and Adjust. Mismatch is the #1 Google Play rejection reason in 2024–25.",
            code: { file: "AndroidManifest.xml", lines: [
              { ln: 12, html: <><span className="tag">&lt;meta-data</span> <span className="at">android:name=</span><span className="str">"com.facebook.sdk.ApplicationId"</span><span className="tag">/&gt;</span></>, hl: true },
              { ln: 13, html: <><span className="tag">&lt;meta-data</span> <span className="at">android:name=</span><span className="str">"com.appsflyer.devkey"</span><span className="tag">/&gt;</span></>, hl: true },
              { ln: 14, html: <><span className="tag">&lt;meta-data</span> <span className="at">android:name=</span><span className="str">"com.adjust.sdk.token"</span><span className="tag">/&gt;</span></>, hl: true },
            ]},
            policy: "Google Play Policy · Data Safety", policyUrl: "#",
          },
          { sev: "warning", name: "PRIVACY_POLICY_URL_CHECK",
            desc: "Privacy policy URL declared, but returns HTTP 301 → redirects to a non-HTTPS endpoint.",
            rec: "Ensure your privacy policy URL is reachable via HTTPS and does not redirect to HTTP. Google Play's automated crawler treats redirected or non-HTTPS URLs as missing.",
          },
          { sev: "warning", name: "ADVERTISING_ID_WITHOUT_DECLARATION",
            desc: "App requests AD_ID permission but Data Safety section doesn't mention advertising ID usage.",
            rec: "Declare advertising ID collection under Data Safety → Device or other IDs, with 'used for advertising' purpose checked.",
          },
        ]
      },
      {
        id: "permissions", name: "Permissions", icon: "shield", critCount: 0, warnCount: 1, infoCount: 2,
        findings: [
          { sev: "warning", name: "DANGEROUS_PERMISSIONS_AUDIT",
            desc: "READ_CONTACTS is requested with no foreground UI that justifies it. Travel apps rarely qualify under Google Play policy.",
            rec: "Remove READ_CONTACTS from your manifest, or add a clear in-app flow that explains its use and gates the permission request behind it. File a Permissions Declaration form if you keep it.",
          },
          { sev: "info", name: "BACKGROUND_LOCATION",
            desc: "ACCESS_BACKGROUND_LOCATION not requested. Foreground location only — passes Google Play's background location policy.",
          },
          { sev: "info", name: "MANAGE_EXTERNAL_STORAGE",
            desc: "Permission not present. Scoped storage compliance is intact.",
          },
        ]
      },
      {
        id: "security", name: "Security", icon: "lock", critCount: 0, warnCount: 1, infoCount: 0,
        findings: [
          { sev: "warning", name: "TARGET_SDK_OUTDATED",
            desc: "targetSdkVersion = 33. Google Play requires API 34 (Android 14) for new submissions since 31 Aug 2024.",
            rec: "Bump targetSdkVersion to 34 in build.gradle. Test scoped storage, photo picker, and foreground service type changes before re-submitting.",
            code: { file: "app/build.gradle", lines: [
              { ln: 23, html: <><span className="at">compileSdk</span> 34</> },
              { ln: 24, html: <><span className="at">targetSdkVersion</span> <span className="str">33</span> <span style={{color:"var(--red)"}}>// must be 34</span></>, hl: true },
              { ln: 25, html: <><span className="at">minSdkVersion</span> <span className="str">24</span></> },
            ]},
          },
          { sev: "pass", name: "CLEARTEXT_TRAFFIC", desc: "usesCleartextTraffic=false. Network security config locked down." },
          { sev: "pass", name: "DEBUGGABLE_BUILD", desc: "android:debuggable not present in release manifest." },
          { sev: "pass", name: "HARDCODED_SECRETS_SCAN", desc: "No high-confidence secret patterns found in decompiled sources." },
        ]
      },
      {
        id: "content", name: "Content Policy", icon: "sparkles", critCount: 0, warnCount: 0, infoCount: 1,
        findings: [
          { sev: "info", name: "MISLEADING_METADATA_CHECK",
            desc: "AI policy review: description is clear, makes no get-rich-quick claims, doesn't impersonate other brands. Confidence: 0.94",
          },
          { sev: "pass", name: "CLONED_APP_FINGERPRINT", desc: "App structure shows no significant overlap with top 1000 apps (max similarity 18%)." },
          { sev: "pass", name: "CONTENT_RATING_CONSISTENCY", desc: "Declared Everyone rating is consistent with detected content signals." },
        ]
      },
      {
        id: "metadata", name: "Metadata & Listing", icon: "package", critCount: 0, warnCount: 0, infoCount: 0,
        findings: [
          { sev: "pass", name: "TITLE_LENGTH", desc: "App title is 6 chars (Google Play max: 30)." },
          { sev: "pass", name: "DESCRIPTION_LENGTH", desc: "Long description 612 chars; short description 71 chars. Within limits." },
          { sev: "pass", name: "ICON_REQUIREMENTS", desc: "512×512 PNG, no alpha issues, no pre-rounded corners detected." },
          { sev: "pass", name: "FEATURE_GRAPHIC_REQUIRED", desc: "1024×500 feature graphic detected in store_listing/." },
        ]
      },
    ],
    app: [
      {
        id: "privacy-ios", name: "Privacy", icon: "eye", critCount: 0, warnCount: 2, infoCount: 1,
        findings: [
          { sev: "warning", name: "PRIVACY_MANIFEST_INCOMPLETE",
            desc: "Privacy manifest (PrivacyInfo.xcprivacy) is present but missing required API reason entries for NSUserDefaults and NSFileManager.",
            rec: "Add NSPrivacyAccessedAPITypeReasons for NSUserDefaults (CA92.1 or 1C8F.1) and NSFileManager (C617.1 or 3B52.1) to PrivacyInfo.xcprivacy.",
          },
          { sev: "warning", name: "NUTRITION_LABEL_MISMATCH",
            desc: "App Store Connect Privacy Nutrition Label doesn't declare 'Precise Location' despite CoreLocation usage with kCLLocationAccuracyBest.",
            rec: "Add 'Precise Location' under Location in your Privacy Nutrition Label and specify the purpose (App Functionality).",
          },
          { sev: "info", name: "ATT_PROMPT_DETECTED",
            desc: "AppTrackingTransparency framework present. ATT usage description string detected in InfoPlist.strings.",
          },
        ]
      },
      {
        id: "entitlements", name: "Entitlements", icon: "shield", critCount: 0, warnCount: 1, infoCount: 0,
        findings: [
          { sev: "warning", name: "UNUSED_ENTITLEMENTS",
            desc: "com.apple.developer.healthkit entitlement present but no HealthKit API calls detected in binary.",
            rec: "Remove the HealthKit entitlement from your provisioning profile if you're not using HealthKit — Apple App Review may reject for unused entitlements.",
          },
          { sev: "pass", name: "ATS_CONFIGURATION", desc: "NSAppTransportSecurity not present; ATS defaults apply (all connections require HTTPS)." },
          { sev: "pass", name: "PUSH_NOTIFICATIONS", desc: "Push notification entitlement matches distribution certificate." },
        ]
      },
      {
        id: "metadata-ios", name: "Metadata & Listing", icon: "package", critCount: 0, warnCount: 0, infoCount: 1,
        findings: [
          { sev: "info", name: "KEYWORD_REPETITION",
            desc: "Long description contains 'trip plan' 8× and 'adventure' 6×. App Store Review may flag for keyword stuffing.",
          },
          { sev: "pass", name: "TITLE_LENGTH_IOS", desc: "App title is 6 chars (Apple App Store max: 30)." },
          { sev: "pass", name: "SUBTITLE_LENGTH", desc: "Subtitle is 28 chars (max 30). Within limit." },
          { sev: "pass", name: "SCREENSHOT_REQUIREMENTS", desc: "6.7\" and 5.5\" screenshots present. iPad screenshots detected." },
        ]
      },
    ],
  },
  fixPlan: [
    { id:"fp1", code:"DATA_SAFETY_COMPLETENESS", title:"Declare tracker SDKs in Google Play Data Safety form",
      severity:"critical", platform:"Google Play", blocking:true,
      why:"Data Safety form declares no third-party data sharing, but Facebook SDK, AppsFlyer, and Adjust were detected in the binary. Google Play's automated review flags this and can result in removal.",
      rec:"Open Play Console → Store presence → Data Safety. Under 'Data shared with third parties', add entries for Facebook SDK (App interactions, Device ID), AppsFlyer (App interactions, Device ID, Crash logs), and Adjust (Device ID). Re-submit the Data Safety form and allow 24–48 h for review.",
      effort:"1–2 hours", policyRef:"Google Play Policy · Data Safety", rescanRequired:true },
    { id:"fp2", code:"TARGET_SDK_OUTDATED", title:"Bump targetSdkVersion to API 34 (Android 14)",
      severity:"warning", platform:"Google Play", blocking:false,
      why:"Google Play requires targetSdkVersion ≥ 34 for all new and updated apps since 31 Aug 2024. Submissions targeting API 33 are rejected at upload.",
      rec:"In app/build.gradle, change targetSdkVersion 33 → 34. Test scoped storage (MediaStore APIs), the system photo picker, foreground service types, and notification permission (POST_NOTIFICATIONS) on Android 14 before submitting.",
      effort:"4–8 hours", policyRef:"Google Play Policy · Target API Level Requirements", rescanRequired:true },
    { id:"fp3", code:"PRIVACY_MANIFEST_INCOMPLETE", title:"Add missing API reason entries to Privacy Manifest",
      severity:"warning", platform:"Apple App Store", blocking:false,
      why:"Apple requires required reason API entries for NSUserDefaults and NSFileManager in PrivacyInfo.xcprivacy. Missing entries cause App Review rejection.",
      rec:"Open PrivacyInfo.xcprivacy and add NSUserDefaults → CA92.1 (accessing defaults from same process) and NSFileManager → C617.1 (file timestamp). Validate with Xcode's privacy report.",
      effort:"30 minutes", policyRef:"Apple App Review Guidelines · Privacy Manifest", rescanRequired:true },
    { id:"fp4", code:"DANGEROUS_PERMISSIONS_AUDIT", title:"Justify or remove READ_CONTACTS permission",
      severity:"warning", platform:"Google Play", blocking:false,
      why:"READ_CONTACTS is a dangerous permission. Travel apps are not a permitted use-case under Google Play policy unless there is a clear user-visible feature backed by it.",
      rec:"Option A: Remove <uses-permission android:name='android.permission.READ_CONTACTS'/> from AndroidManifest.xml. Option B: Implement a contacts import feature and complete a Permissions Declaration form in Play Console.",
      effort:"2–4 hours", policyRef:"Google Play Policy · Permissions", rescanRequired:true },
    { id:"fp5", code:"PRIVACY_POLICY_URL_CHECK", title:"Fix privacy policy URL redirect chain",
      severity:"warning", platform:"Google Play", blocking:false,
      why:"Privacy policy URL returns HTTP 301 redirect to a non-HTTPS endpoint. Google Play's crawler treats this as a missing policy URL.",
      rec:"Update the policy URL in Play Console to the final HTTPS destination (e.g. https://roamly.app/privacy). Verify the URL resolves without redirects and returns HTTP 200.",
      effort:"15 minutes", policyRef:"Google Play Policy · Privacy Policy", rescanRequired:false },
    { id:"fp6", code:"NUTRITION_LABEL_MISMATCH", title:"Add Precise Location to App Store Privacy Nutrition Label",
      severity:"warning", platform:"Apple App Store", blocking:false,
      why:"The app uses kCLLocationAccuracyBest (precise location) but the App Store Connect Nutrition Label only declares 'Approximate Location'. This mismatch can cause App Review rejection.",
      rec:"In App Store Connect → App Privacy, under Location, check 'Precise Location' in addition to 'Approximate Location'. Set purpose to 'App Functionality'.",
      effort:"15 minutes", policyRef:"Apple App Review Guidelines · Privacy Nutrition Label", rescanRequired:false },
    { id:"fp7", code:"UNUSED_ENTITLEMENTS", title:"Remove unused HealthKit entitlement",
      severity:"warning", platform:"Apple App Store", blocking:false,
      why:"com.apple.developer.healthkit is in the entitlements file but no HealthKit API calls are present in the binary. App Review rejects apps for unused sensitive entitlements.",
      rec:"Remove HealthKit from your provisioning profile in the Apple Developer Portal and from your Xcode project Entitlements file.",
      effort:"30 minutes", policyRef:"Apple App Review Guidelines · Entitlements", rescanRequired:false },
  ],
  sdks: [
    { name:"Facebook SDK", version:"16.2.0", category:"Analytics / Advertising",
      purpose:"User attribution and remarketing", dataCollected:"Device ID, IP address, App events, Crash data",
      privacyImpact:"high", storeRisk:"high",
      requiredDisclosure:"Data Safety: Device IDs, App interactions; Privacy Label: Device ID, Usage Data",
      action:"Declare in Data Safety & Privacy Nutrition Label, or remove if unused" },
    { name:"AppsFlyer", version:"6.14.2", category:"Attribution",
      purpose:"Install attribution and marketing analytics", dataCollected:"Device ID, IP, App events, Install source",
      privacyImpact:"high", storeRisk:"high",
      requiredDisclosure:"Data Safety: Device IDs, App interactions, Crash logs",
      action:"Declare in Data Safety form under 'Data shared with third parties'" },
    { name:"Adjust", version:"4.38.2", category:"Attribution",
      purpose:"Campaign measurement", dataCollected:"Device ID, IP address",
      privacyImpact:"medium", storeRisk:"high",
      requiredDisclosure:"Data Safety: Device IDs",
      action:"Declare in Data Safety form" },
    { name:"Firebase Analytics", version:"21.5.0", category:"Analytics",
      purpose:"App usage analytics and crash reporting", dataCollected:"Device ID, App events, Crash data, Performance data",
      privacyImpact:"medium", storeRisk:"medium",
      requiredDisclosure:"Data Safety: App interactions, Crash logs, Diagnostics",
      action:"Declared in Data Safety — verify accuracy" },
    { name:"Firebase Crashlytics", version:"18.6.2", category:"Crash Reporting",
      purpose:"Crash and ANR reporting", dataCollected:"Crash logs, Device state, Stack traces",
      privacyImpact:"low", storeRisk:"low",
      requiredDisclosure:"Data Safety: Crash logs",
      action:"Declared — no action required" },
    { name:"Google Maps SDK", version:"18.2.0", category:"Mapping",
      purpose:"In-app maps and location services", dataCollected:"Precise location (when in use), IP address",
      privacyImpact:"medium", storeRisk:"low",
      requiredDisclosure:"Data Safety: Precise location (user-facing)",
      action:"Ensure location purpose is declared — appears correct" },
    { name:"Stripe SDK", version:"23.27.3", category:"Payments",
      purpose:"In-app payment processing", dataCollected:"Payment info (PCI-DSS scoped), Device ID",
      privacyImpact:"high", storeRisk:"low",
      requiredDisclosure:"Data Safety: Financial info (payment info)",
      action:"Verify Data Safety financial info declaration is present" },
  ],
  permissions: [
    { permission:"READ_CONTACTS", platform:"Google Play", riskLevel:"high",
      declaredPurpose:"Not declared", issue:"Dangerous permission with no foreground UI justification for travel category",
      recommendation:"Remove permission or file Permissions Declaration form in Play Console" },
    { permission:"ACCESS_FINE_LOCATION", platform:"Google Play", riskLevel:"medium",
      declaredPurpose:"Trip mapping and navigation", issue:"None — purpose is clear and declared",
      recommendation:"No action required" },
    { permission:"ACCESS_COARSE_LOCATION", platform:"Google Play", riskLevel:"low",
      declaredPurpose:"Trip mapping fallback", issue:"None",
      recommendation:"No action required" },
    { permission:"CAMERA", platform:"Google Play", riskLevel:"medium",
      declaredPurpose:"Photo capture for itinerary items", issue:"None — common for travel apps",
      recommendation:"No action required" },
    { permission:"READ_MEDIA_IMAGES", platform:"Google Play", riskLevel:"low",
      declaredPurpose:"Selecting photos for trip memories", issue:"None — correct scoped media permission",
      recommendation:"No action required" },
    { permission:"POST_NOTIFICATIONS", platform:"Google Play", riskLevel:"low",
      declaredPurpose:"Trip reminders and updates", issue:"None — Android 13+ runtime permission",
      recommendation:"No action required" },
    { permission:"RECEIVE_BOOT_COMPLETED", platform:"Google Play", riskLevel:"low",
      declaredPurpose:"Reschedule trip alarms after reboot", issue:"Declared — low risk",
      recommendation:"No action required" },
    { permission:"NSLocationWhenInUseUsageDescription", platform:"Apple App Store", riskLevel:"medium",
      declaredPurpose:"Show your location on trip maps", issue:"None — Privacy Nutrition Label declares Precise Location",
      recommendation:"No action required" },
    { permission:"NSCameraUsageDescription", platform:"Apple App Store", riskLevel:"medium",
      declaredPurpose:"Capture photos for your trip", issue:"None",
      recommendation:"No action required" },
    { permission:"NSPhotoLibraryUsageDescription", platform:"Apple App Store", riskLevel:"low",
      declaredPurpose:"Select photos from your library", issue:"None",
      recommendation:"No action required" },
  ],
  privacyDeclarations: [
    { dataType:"Contact info (email)", collected:false, linkedToUser:false, tracking:false,
      status:"mismatch", mismatch:"READ_CONTACTS permission detected — suggests contact data may be accessed but not declared",
      fix:"Remove READ_CONTACTS or add 'Contact info → Email address' to Data Safety and Privacy Nutrition Label" },
    { dataType:"Device ID / Advertising ID", collected:true, linkedToUser:true, tracking:true,
      status:"missing", mismatch:"AD_ID permission present; 3 SDKs collect Device ID — not declared in Data Safety",
      fix:"Declare 'Device or other identifiers' in Data Safety form under 'Data shared with third parties'" },
    { dataType:"Precise location", collected:true, linkedToUser:false, tracking:false,
      status:"partial", mismatch:"Declared in Google Play Data Safety, not in Apple App Store Nutrition Label",
      fix:"Add 'Precise Location' to App Store Connect Privacy settings under Location" },
    { dataType:"App interactions (events)", collected:true, linkedToUser:true, tracking:false,
      status:"declared", mismatch:"",
      fix:"" },
    { dataType:"Crash logs", collected:true, linkedToUser:false, tracking:false,
      status:"declared", mismatch:"",
      fix:"" },
    { dataType:"Financial info (payment)", collected:true, linkedToUser:true, tracking:false,
      status:"declared", mismatch:"",
      fix:"" },
    { dataType:"Photos / videos", collected:false, linkedToUser:false, tracking:false,
      status:"declared", mismatch:"",
      fix:"" },
    { dataType:"Audio files", collected:false, linkedToUser:false, tracking:false,
      status:"declared", mismatch:"",
      fix:"" },
  ],
  storeListing: {
    score: 92,
    items: [
      { field:"App title", value:"Roamly: Travel & Trip Planner", status:"pass", detail:"32 chars — within Google Play (50) and Apple App Store (30) limits" },
      { field:"Short description", value:"Plan trips with AI-powered itineraries and offline maps.", status:"pass", detail:"57 chars — Google Play max 80, Apple App Store (subtitle) max 30" },
      { field:"Long description", value:"612 chars", status:"info", detail:"'trip plan' appears 8× — may trigger keyword-spam classifier. Vary phrasing." },
      { field:"App icon", value:"512×512 PNG", status:"pass", detail:"No pre-rounded corners, no alpha issues. Correct dimensions for both stores." },
      { field:"Screenshots", value:"8 screenshots (phone + tablet)", status:"pass", detail:"6.7\" and 5.5\" for iOS; phone, 7-inch, 10-inch for Google Play. Meets minimums." },
      { field:"Feature graphic", value:"1024×500 PNG", status:"pass", detail:"Present and correct dimensions (Google Play only)" },
    ],
    issues: [
      { sev:"info", field:"Long description", issue:"'trip plan' appears 8×, 'adventure' 6×. Google Play and Apple App Store may flag for keyword repetition.", rec:"Rephrase with natural language variation." },
    ],
  },
  policyRefs: [
    { store:"play", name:"Data Safety Requirements", desc:"Mandatory disclosure of data collected, shared, and its purpose", severity:"critical", url:"#" },
    { store:"play", name:"Target API Level Requirements", desc:"All new/updated apps must target API 34 (Android 14) since Aug 31, 2024", severity:"warning", url:"#" },
    { store:"play", name:"Permissions Policy", desc:"Dangerous permissions must have a justifiable use case for the app category", severity:"warning", url:"#" },
    { store:"play", name:"Privacy Policy Requirements", desc:"Valid, accessible HTTPS privacy policy URL required", severity:"warning", url:"#" },
    { store:"play", name:"Deceptive Behavior Policy", desc:"App descriptions must not contain misleading claims or keyword spam", severity:"info", url:"#" },
    { store:"play", name:"Advertising Policy", desc:"Apps using advertising ID must declare usage and comply with Families Policy", severity:"info", url:"#" },
    { store:"app",  name:"App Review Guidelines · Privacy Manifest", desc:"Required reason APIs must be declared in PrivacyInfo.xcprivacy", severity:"warning", url:"#" },
    { store:"app",  name:"App Review Guidelines · Entitlements", desc:"Sensitive entitlements must be actively used by the app", severity:"warning", url:"#" },
    { store:"app",  name:"Privacy Nutrition Label", desc:"App Store Connect nutrition label must accurately reflect data practices", severity:"warning", url:"#" },
    { store:"app",  name:"App Tracking Transparency", desc:"ATT framework required before accessing IDFA for tracking", severity:"info", url:"#" },
  ],
  scanHistory: [
    { v:"1.4.0 (build 142)", s:78, when:"Now", cur:true },
    { v:"1.4.0 (build 141)", s:74, when:"2h ago" },
    { v:"1.3.9 (build 138)", s:81, when:"Yesterday" },
    { v:"1.3.8 (build 130)", s:65, when:"3 days ago" },
    { v:"1.3.7 (build 124)", s:72, when:"5 days ago" },
  ],
};

// ── Finding row (shared between Findings + Security tabs) ──
const FindingRow = ({ f, open, onClick }) => (
  <>
    <div className={`finding sev-${f.sev} ${open ? "open" : ""}`} onClick={onClick}>
      <div style={{display:"flex"}}><Severity level={f.sev} /></div>
      <div className="body">
        <div className="name">
          <span className="mono" style={{fontSize:12, color:"var(--fg-3)", marginRight:8}}>{f.name}</span>
        </div>
        <div className="desc">{f.desc}</div>
      </div>
      <Icon name="chevron" size={15} className="chev" />
    </div>
    <div className="finding-expand">
      {f.rec && (
        <div className="rec"><h5>Recommendation</h5>{f.rec}</div>
      )}
      {f.code && (
        <div className="code-block">
          <div className="filename">{f.code.file}</div>
          {f.code.lines.map((l, i) => (
            <div key={i} className={l.hl ? "hl" : ""}><span className="ln">{l.ln}</span>{l.html}</div>
          ))}
        </div>
      )}
      <div style={{marginTop:12}}>
        {f.policy && (
          <a className="policy-link"><Icon name="book" size={12} /> {f.policy} <Icon name="external-link" size={11} /></a>
        )}
        {f.sev !== "pass" && (
          <button className="btn btn-sm" style={{marginRight:8, marginTop:f.policy?8:0}}>
            <Icon name="check" size={12} /> Mark acknowledged
          </button>
        )}
        {f.sev !== "pass" && (
          <button className="btn btn-sm btn-ghost"><Icon name="copy" size={12} /> Copy as task</button>
        )}
      </div>
    </div>
  </>
);

const CategoryBlock = ({ cat }) => {
  const [openCat, setOpenCat] = React.useState(cat.critCount > 0 || cat.warnCount > 0);
  const [openIdx, setOpenIdx] = React.useState(0);
  return (
    <div style={{marginBottom:14}}>
      <div className={`cat-head ${!openCat ? "closed" : ""}`} onClick={() => setOpenCat(!openCat)}>
        <Icon name={cat.icon} size={16} className="ic" />
        <div className="title">{cat.name}</div>
        <div className="meta">
          {cat.critCount > 0 && <span className="mono" style={{color:"var(--red)", fontSize:12}}>● {cat.critCount} critical</span>}
          {cat.warnCount > 0 && <span className="mono" style={{color:"var(--amber)", fontSize:12}}>● {cat.warnCount} warning{cat.warnCount!==1?"s":""}</span>}
          {(cat.infoCount||0) > 0 && <span className="mono" style={{color:"var(--blue)", fontSize:12}}>● {cat.infoCount} info</span>}
          {cat.critCount===0 && cat.warnCount===0 && (cat.infoCount||0)===0 && (
            <span className="mono" style={{color:"var(--teal)", fontSize:12}}>● all pass</span>
          )}
          <span className="mono" style={{color:"var(--fg-3)", fontSize:12}}>{cat.findings.length} checks</span>
          <Icon name="chevron-down" size={15} className="chev" />
        </div>
      </div>
      {openCat && (
        <div className="findings">
          {cat.findings.map((f, i) => (
            <FindingRow key={i} f={f} open={openIdx===i} onClick={() => setOpenIdx(openIdx===i ? null : i)} />
          ))}
        </div>
      )}
    </div>
  );
};

// ── Tab: Overview ──
const OverviewTab = ({ d, go }) => (
  <div className="two-col" style={{alignItems:"flex-start"}}>
    <div>
      {/* Store score comparison */}
      <div className="card" style={{marginBottom:16}}>
        <div className="card-head">
          <div className="card-title">Score by store</div>
        </div>
        <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", borderTop:"1px solid var(--line-1)"}}>
          {[["play","Google Play","android"], ["app","Apple App Store","apple"]].map(([k, lbl, ic]) => {
            const s = d.byStore[k];
            const col = s.score >= 85 ? "var(--teal)" : s.score >= 60 ? "var(--amber)" : "var(--red)";
            return (
              <div key={k} style={{padding:"18px 20px", borderRight: k==="play" ? "1px solid var(--line-1)" : "none"}}>
                <div style={{display:"flex", alignItems:"center", gap:8, marginBottom:12}}>
                  <Icon name={ic} size={14} style={{color:"var(--fg-2)"}} />
                  <span style={{fontSize:12, color:"var(--fg-2)", fontFamily:"var(--font-mono)"}}>{lbl}</span>
                </div>
                <div style={{fontFamily:"var(--font-pixel-square)", fontSize:36, color:col, lineHeight:1}}>{s.score}</div>
                <div style={{fontFamily:"var(--font-mono)", fontSize:10.5, color:"var(--fg-3)", marginTop:4, letterSpacing:"0.08em"}}>/100</div>
                <div style={{display:"flex", gap:14, marginTop:12, fontFamily:"var(--font-mono)", fontSize:12}}>
                  {s.crit > 0 && <span style={{color:"var(--red)"}}>● {s.crit} critical</span>}
                  {s.warn > 0 && <span style={{color:"var(--amber)"}}>● {s.warn} warning{s.warn!==1?"s":""}</span>}
                  {s.info > 0 && <span style={{color:"var(--blue)"}}>● {s.info} info</span>}
                  <span style={{color:"var(--teal)"}}>● {s.pass} pass</span>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* Top issues */}
      <div className="card">
        <div className="card-head">
          <div className="card-title">Top issues to fix</div>
          <span className="card-sub">Ordered by impact</span>
        </div>
        {d.fixPlan.slice(0, 4).map((f, i) => (
          <div key={f.id} style={{
            display:"flex", alignItems:"flex-start", gap:14,
            padding:"14px 18px", borderTop:"1px solid var(--line-1)"
          }}>
            <Severity level={f.severity} />
            <div style={{flex:1}}>
              <div style={{fontSize:13.5, fontWeight:500}}>{f.title}</div>
              <div style={{fontSize:12, color:"var(--fg-2)", marginTop:3, display:"flex", gap:10, alignItems:"center"}}>
                <StoreChip store={f.platform === "Google Play" ? "play" : "app"} />
                {f.blocking && <span style={{fontFamily:"var(--font-mono)", fontSize:10, color:"var(--red)", background:"var(--red-bg)", border:"1px solid rgba(239,68,68,0.25)", borderRadius:4, padding:"2px 6px", letterSpacing:"0.06em", textTransform:"uppercase"}}>Blocking</span>}
                <span style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-3)"}}>~{f.effort}</span>
              </div>
            </div>
            <Icon name="chevron" size={15} style={{color:"var(--fg-3)", marginTop:2}} />
          </div>
        ))}
        <div style={{padding:"12px 18px", borderTop:"1px solid var(--line-1)"}}>
          <span style={{fontSize:12.5, color:"var(--fg-2)"}}>{d.fixPlan.length - 4} more issues — </span>
          <span style={{fontSize:12.5, color:"var(--teal)", cursor:"pointer"}}>view full Fix Plan →</span>
        </div>
      </div>
    </div>

    <div style={{display:"flex", flexDirection:"column", gap:16, position:"sticky", top:72}}>
      {/* Score breakdown */}
      <div className="card">
        <div className="card-head">
          <div className="card-title">Score breakdown</div>
        </div>
        <div className="card-body">
          <div className="cat-breakdown">
            {d.breakdown.map(b => (
              <div className={`cat-row ${b.level === "bad" ? "bad" : b.level === "warn" ? "warn" : ""}`} key={b.name}>
                <div className="name">{b.name}</div>
                <div className="bar-track"><div className="bar" style={{width:b.score+"%"}}></div></div>
                <div className="pct">{b.score}</div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Next steps */}
      <div className="card">
        <div className="card-head"><div className="card-title">Next steps</div></div>
        <div className="card-body" style={{display:"flex", flexDirection:"column", gap:10}}>
          <button className="btn btn-primary" style={{justifyContent:"flex-start"}}>
            <Icon name="refresh" size={13} /> Fix & re-scan
          </button>
          <button className="btn" style={{justifyContent:"flex-start"}}>
            <Icon name="download" size={13} /> Export PDF report
          </button>
          <button className="btn" style={{justifyContent:"flex-start"}}>
            <Icon name="share" size={13} /> Share read-only link
          </button>
          <button className="btn" style={{justifyContent:"flex-start"}}>
            <Icon name="webhook" size={13} /> Send to Slack / Linear
          </button>
          <div style={{marginTop:4, padding:"10px 14px", background:"var(--bg-2)", border:"1px solid var(--line-1)", borderRadius:"var(--r-sm)"}}>
            <div className="mono" style={{fontSize:10, fontWeight:600, letterSpacing:"0.1em", color:"var(--fg-3)", marginBottom:5}}>TIP</div>
            <div style={{fontSize:12.5, color:"var(--fg-2)", lineHeight:1.5}}>Add the Reliq GitHub Action to re-scan on every push, then display the badge in your repo.</div>
          </div>
        </div>
      </div>

      {/* Scan history */}
      <div className="card">
        <div className="card-head"><div className="card-title">Scan history</div></div>
        <div style={{padding:"6px 0"}}>
          {d.scanHistory.map((h, i) => (
            <div key={i} style={{
              display:"flex", alignItems:"center", gap:10,
              padding:"9px 18px", borderTop:i===0?"0":"1px solid var(--line-1)",
              background:h.cur?"var(--bg-2)":"transparent"
            }}>
              <Icon name="git-branch" size={12} />
              <div className="mono" style={{fontSize:12.5, color:h.cur?"var(--fg-0)":"var(--fg-1)"}}>{h.v}</div>
              <div style={{marginLeft:"auto", fontFamily:"var(--font-mono)", fontSize:12, fontWeight:600,
                color:h.s>=85?"var(--teal)":h.s>=60?"var(--amber)":"var(--red)"}}>{h.s}</div>
              <div className="mono" style={{fontSize:11, color:"var(--fg-3)", width:64, textAlign:"right"}}>{h.when}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  </div>
);

// ── Tab: Findings ──
const FindingsTab = ({ d }) => {
  const [storeTab, setStoreTab] = React.useState("play");
  const cats = d.categories[storeTab] || [];
  const agg = d.byStore[storeTab];
  return (
    <div>
      <div className="tabs" style={{marginBottom:18}}>
        <div className={`tab ${storeTab==="play"?"active":""}`} onClick={() => setStoreTab("play")}>
          <Icon name="android" size={14} /> Google Play
          <span className="ct">{d.byStore.play.crit}C · {d.byStore.play.warn}W</span>
        </div>
        <div className={`tab ${storeTab==="app"?"active":""}`} onClick={() => setStoreTab("app")}>
          <Icon name="apple" size={14} /> Apple App Store
          <span className="ct">{d.byStore.app.warn}W</span>
        </div>
        <div style={{marginLeft:"auto", display:"flex", alignItems:"center", gap:8}}>
          <button className="btn btn-sm"><Icon name="sliders" size={12} /> Filter</button>
          <button className="btn btn-sm btn-ghost"><Icon name="chevron-down" size={12} /> Expand all</button>
        </div>
      </div>
      {cats.map(cat => <CategoryBlock key={cat.id} cat={cat} />)}
    </div>
  );
};

// ── Tab: Fix Plan ──
const FixPlanTab = ({ d }) => {
  const [openId, setOpenId] = React.useState("fp1");
  const resolved = 0;
  return (
    <div>
      <div style={{display:"flex", alignItems:"center", gap:16, marginBottom:20, padding:"14px 18px", background:"var(--bg-1)", border:"1px solid var(--line-1)", borderRadius:"var(--r-lg)"}}>
        <div style={{flex:1}}>
          <div style={{fontSize:12, color:"var(--fg-2)", fontFamily:"var(--font-mono)", marginBottom:6}}>{resolved} of {d.fixPlan.length} issues resolved</div>
          <div style={{height:6, background:"var(--bg-3)", borderRadius:3, overflow:"hidden"}}>
            <div style={{height:"100%", width:(resolved/d.fixPlan.length*100)+"%", background:"var(--teal)", borderRadius:3, transition:"width .4s"}}></div>
          </div>
        </div>
        <button className="btn btn-sm"><Icon name="download" size={12} /> Export Fix Plan</button>
        <button className="btn btn-sm btn-ghost"><Icon name="webhook" size={12} /> Send to Linear</button>
      </div>

      {d.fixPlan.map(f => (
        <div key={f.id} className="fix-ticket" style={{
          marginBottom:10, background:"var(--bg-1)", border:`1px solid var(--line-1)`,
          borderLeft:`3px solid ${f.severity==="critical"?"var(--red)":f.severity==="warning"?"var(--amber)":"var(--blue)"}`,
          borderRadius:"var(--r-md)", overflow:"hidden"
        }}>
          <div style={{display:"flex", alignItems:"center", gap:14, padding:"14px 18px", cursor:"pointer"}}
               onClick={() => setOpenId(openId===f.id ? null : f.id)}>
            <Severity level={f.severity} />
            <div style={{flex:1}}>
              <div style={{fontWeight:500, fontSize:13.5}}>{f.title}</div>
              <div style={{display:"flex", gap:10, alignItems:"center", marginTop:4}}>
                <StoreChip store={f.platform==="Google Play"?"play":"app"} />
                {f.blocking && (
                  <span style={{fontFamily:"var(--font-mono)", fontSize:10, color:"var(--red)", background:"var(--red-bg)", border:"1px solid rgba(239,68,68,0.25)", borderRadius:4, padding:"2px 6px", letterSpacing:"0.06em", textTransform:"uppercase"}}>Blocking submission</span>
                )}
                <span style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-3)"}}>Est. {f.effort}</span>
                {f.rescanRequired && <span style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--blue)"}}>Re-scan required</span>}
              </div>
            </div>
            <div style={{display:"flex", gap:8}}>
              <button className="btn btn-sm" onClick={e => e.stopPropagation()}><Icon name="check" size={12} /> Mark fixed</button>
              <button className="btn btn-sm btn-ghost" title="Copy fix ticket" onClick={e => e.stopPropagation()}><Icon name="copy" size={12} /></button>
            </div>
            <Icon name="chevron-down" size={15} style={{color:"var(--fg-3)", transform:openId===f.id?"rotate(180deg)":"none", transition:"transform .15s"}} />
          </div>
          {openId===f.id && (
            <div style={{padding:"0 18px 18px", borderTop:"1px solid var(--line-1)", background:"var(--bg-2)"}}>
              <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:12, marginTop:14}}>
                <div style={{background:"var(--bg-1)", border:"1px solid var(--line-1)", borderRadius:"var(--r-md)", padding:"14px 16px"}}>
                  <div style={{fontFamily:"var(--font-mono)", fontSize:10.5, letterSpacing:"0.12em", textTransform:"uppercase", color:"var(--fg-3)", marginBottom:8}}>Why it matters</div>
                  <div style={{fontSize:13, color:"var(--fg-1)", lineHeight:1.6}}>{f.why}</div>
                </div>
                <div style={{background:"var(--bg-1)", border:"1px solid var(--line-1)", borderRadius:"var(--r-md)", padding:"14px 16px"}}>
                  <div style={{fontFamily:"var(--font-mono)", fontSize:10.5, letterSpacing:"0.12em", textTransform:"uppercase", color:"var(--teal)", marginBottom:8}}>Recommendation</div>
                  <div style={{fontSize:13, color:"var(--fg-1)", lineHeight:1.6}}>{f.rec}</div>
                </div>
              </div>
              <div style={{display:"flex", gap:8, marginTop:12, flexWrap:"wrap"}}>
                <a className="policy-link" style={{marginTop:0}}>
                  <Icon name="book" size={12} /> {f.policyRef} <Icon name="external-link" size={11} />
                </a>
              </div>
            </div>
          )}
        </div>
      ))}
    </div>
  );
};

// ── Tab: Permissions ──
const PermissionsTab = ({ d }) => {
  const riskColor = { high:"var(--red)", medium:"var(--amber)", low:"var(--teal)" };
  const [permSearch, setPermSearch]   = React.useState("");
  const [permRisk,   setPermRisk]     = React.useState("all");
  const [permPlat,   setPermPlat]     = React.useState("all");

  const filteredPerms = d.permissions.filter(p => {
    if (permSearch && !p.permission.toLowerCase().includes(permSearch.toLowerCase()) &&
        !p.declaredPurpose.toLowerCase().includes(permSearch.toLowerCase())) return false;
    if (permRisk !== "all" && p.riskLevel !== permRisk) return false;
    if (permPlat !== "all") {
      const store = p.platform === "Google Play" ? "play" : "app";
      if (store !== permPlat) return false;
    }
    return true;
  });
  const hasFilters = permSearch || permRisk !== "all" || permPlat !== "all";

  const selSm = { height:30, background:"var(--bg-2)", border:"1px solid var(--line-2)", borderRadius:"var(--r-sm)", padding:"0 8px", fontSize:12, color:"var(--fg-0)", outline:"none", cursor:"pointer" };

  return (
    <div className="card">
      <div className="card-head">
        <div className="card-title">Permissions audit</div>
        <span className="card-sub" style={{marginRight:"auto"}}>{d.permissions.length} permissions detected</span>
        <div style={{position:"relative"}}>
          <Icon name="search" size={12} style={{position:"absolute", left:8, top:"50%", transform:"translateY(-50%)", color:"var(--fg-3)", pointerEvents:"none"}} />
          <input
            value={permSearch}
            onChange={e => setPermSearch(e.target.value)}
            placeholder="Search permissions…"
            style={{height:30, width:170, background:"var(--bg-2)", border:"1px solid var(--line-2)", borderRadius:"var(--r-sm)", padding:"0 8px 0 26px", fontSize:12, color:"var(--fg-0)", outline:"none", boxSizing:"border-box"}}
          />
        </div>
        <select value={permRisk} onChange={e => setPermRisk(e.target.value)} style={selSm}>
          <option value="all">All risk</option>
          <option value="high">High</option>
          <option value="medium">Medium</option>
          <option value="low">Low</option>
        </select>
        <select value={permPlat} onChange={e => setPermPlat(e.target.value)} style={selSm}>
          <option value="all">All platforms</option>
          <option value="play">Google Play</option>
          <option value="app">App Store</option>
        </select>
        {hasFilters && (
          <button className="btn btn-sm btn-ghost" onClick={() => { setPermSearch(""); setPermRisk("all"); setPermPlat("all"); }}>
            <Icon name="x" size={11} />
          </button>
        )}
      </div>
      <div className="tbl-scroll">
      <table className="tbl">
        <thead>
          <tr>
            <th>Permission</th>
            <th>Platform</th>
            <th>Risk</th>
            <th>Declared purpose</th>
            <th>Issue</th>
            <th>Recommendation</th>
          </tr>
        </thead>
        <tbody>
          {filteredPerms.length === 0 && (
            <tr><td colSpan={6} style={{textAlign:"center", padding:"36px 20px", color:"var(--fg-3)"}}>
              No permissions match your filters.
            </td></tr>
          )}
          {filteredPerms.map((p, i) => (
            <tr key={i}>
              <td><span className="mono" style={{fontSize:12}}>{p.permission}</span></td>
              <td><StoreChip store={p.platform==="Google Play"?"play":"app"} /></td>
              <td>
                <span style={{
                  fontFamily:"var(--font-mono)", fontSize:10.5, fontWeight:600, letterSpacing:"0.06em",
                  textTransform:"uppercase", padding:"2px 8px", borderRadius:4,
                  color:riskColor[p.riskLevel],
                  background:p.riskLevel==="high"?"var(--red-bg)":p.riskLevel==="medium"?"var(--amber-bg)":"var(--teal-bg)",
                  border:`1px solid ${riskColor[p.riskLevel]}40`
                }}>{p.riskLevel}</span>
              </td>
              <td style={{fontSize:12.5, color:"var(--fg-1)", maxWidth:160}}>{p.declaredPurpose}</td>
              <td style={{fontSize:12.5, color:p.issue==="None"||p.issue.startsWith("None")?"var(--fg-3)":"var(--amber)", maxWidth:200}}>{p.issue}</td>
              <td style={{fontSize:12, color:"var(--fg-2)", maxWidth:200}}>{p.recommendation}</td>
            </tr>
          ))}
        </tbody>
      </table>
      </div>
    </div>
  );
};

// ── Tab: SDK Inventory ──
const SDKInventoryTab = ({ d }) => {
  const impactColor = { high:"var(--red)", medium:"var(--amber)", low:"var(--teal)" };
  const [sdkSearch,  setSdkSearch]  = React.useState("");
  const [sdkImpact,  setSdkImpact]  = React.useState("all");
  const [sdkCat,     setSdkCat]     = React.useState("all");

  const categories = [...new Set(d.sdks.map(s => s.category))];
  const filteredSdks = d.sdks.filter(s => {
    if (sdkSearch && !s.name.toLowerCase().includes(sdkSearch.toLowerCase()) &&
        !s.category.toLowerCase().includes(sdkSearch.toLowerCase())) return false;
    if (sdkImpact !== "all" && s.privacyImpact !== sdkImpact) return false;
    if (sdkCat !== "all" && s.category !== sdkCat) return false;
    return true;
  });
  const hasFilters = sdkSearch || sdkImpact !== "all" || sdkCat !== "all";
  const selSm = { height:30, background:"var(--bg-2)", border:"1px solid var(--line-2)", borderRadius:"var(--r-sm)", padding:"0 8px", fontSize:12, color:"var(--fg-0)", outline:"none", cursor:"pointer" };

  return (
    <div className="card">
      <div className="card-head">
        <div className="card-title">SDK Inventory</div>
        <span className="card-sub" style={{marginRight:"auto"}}>{d.sdks.length} SDKs detected</span>
        <div style={{position:"relative"}}>
          <Icon name="search" size={12} style={{position:"absolute", left:8, top:"50%", transform:"translateY(-50%)", color:"var(--fg-3)", pointerEvents:"none"}} />
          <input
            value={sdkSearch}
            onChange={e => setSdkSearch(e.target.value)}
            placeholder="Search SDKs…"
            style={{height:30, width:150, background:"var(--bg-2)", border:"1px solid var(--line-2)", borderRadius:"var(--r-sm)", padding:"0 8px 0 26px", fontSize:12, color:"var(--fg-0)", outline:"none", boxSizing:"border-box"}}
          />
        </div>
        <select value={sdkImpact} onChange={e => setSdkImpact(e.target.value)} style={selSm}>
          <option value="all">All impact</option>
          <option value="high">High</option>
          <option value="medium">Medium</option>
          <option value="low">Low</option>
        </select>
        <select value={sdkCat} onChange={e => setSdkCat(e.target.value)} style={selSm}>
          <option value="all">All categories</option>
          {categories.map(c => <option key={c} value={c}>{c}</option>)}
        </select>
        {hasFilters && (
          <button className="btn btn-sm btn-ghost" onClick={() => { setSdkSearch(""); setSdkImpact("all"); setSdkCat("all"); }}>
            <Icon name="x" size={11} />
          </button>
        )}
        <button className="btn btn-sm"><Icon name="download" size={12} /> Export</button>
      </div>
      <div className="tbl-scroll">
      <table className="tbl">
        <thead>
          <tr>
            <th>SDK</th>
            <th>Category</th>
            <th>Data collected</th>
            <th>Privacy impact</th>
            <th>Store risk</th>
            <th>Required disclosure</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>
          {filteredSdks.length === 0 && (
            <tr><td colSpan={7} style={{textAlign:"center", padding:"36px 20px", color:"var(--fg-3)"}}>
              No SDKs match your filters.
            </td></tr>
          )}
          {filteredSdks.map((s, i) => (
            <tr key={i}>
              <td>
                <div style={{fontWeight:500, fontSize:13.5}}>{s.name}</div>
                <div className="mono" style={{fontSize:11, color:"var(--fg-3)", marginTop:2}}>v{s.version}</div>
              </td>
              <td style={{fontSize:12.5, color:"var(--fg-1)"}}>{s.category}</td>
              <td style={{fontSize:12, color:"var(--fg-2)", maxWidth:160}}>{s.dataCollected}</td>
              <td>
                <span style={{
                  fontFamily:"var(--font-mono)", fontSize:10.5, fontWeight:600, letterSpacing:"0.06em",
                  textTransform:"uppercase", padding:"2px 8px", borderRadius:4,
                  color:impactColor[s.privacyImpact],
                  background:s.privacyImpact==="high"?"var(--red-bg)":s.privacyImpact==="medium"?"var(--amber-bg)":"var(--teal-bg)",
                  border:`1px solid ${impactColor[s.privacyImpact]}40`
                }}>{s.privacyImpact}</span>
              </td>
              <td>
                <span style={{
                  fontFamily:"var(--font-mono)", fontSize:10.5, fontWeight:600, letterSpacing:"0.06em",
                  textTransform:"uppercase", padding:"2px 8px", borderRadius:4,
                  color:impactColor[s.storeRisk],
                  background:s.storeRisk==="high"?"var(--red-bg)":s.storeRisk==="medium"?"var(--amber-bg)":"var(--teal-bg)",
                  border:`1px solid ${impactColor[s.storeRisk]}40`
                }}>{s.storeRisk}</span>
              </td>
              <td style={{fontSize:12, color:"var(--fg-2)", maxWidth:180}}>{s.requiredDisclosure}</td>
              <td style={{fontSize:12, color:s.action.includes("no action")||s.action.includes("Declared")?"var(--fg-3)":"var(--amber)", maxWidth:180}}>{s.action}</td>
            </tr>
          ))}
        </tbody>
      </table>
      </div>
    </div>
  );
};

// ── Tab: Privacy Declarations ──
const PrivacyTab = ({ d }) => {
  const statusMeta = {
    declared: { label:"Declared", color:"var(--teal)", bg:"var(--teal-bg)" },
    missing:  { label:"Missing",  color:"var(--red)",   bg:"var(--red-bg)" },
    partial:  { label:"Partial",  color:"var(--amber)", bg:"var(--amber-bg)" },
    mismatch: { label:"Mismatch", color:"var(--amber)", bg:"var(--amber-bg)" },
  };
  const [privStatus, setPrivStatus] = React.useState("all");
  const [privSearch, setPrivSearch] = React.useState("");

  const filteredDecls = d.privacyDeclarations.filter(p => {
    if (privSearch && !p.dataType.toLowerCase().includes(privSearch.toLowerCase())) return false;
    if (privStatus !== "all" && p.status !== privStatus) return false;
    return true;
  });
  const hasFilters = privSearch || privStatus !== "all";
  const selSm = { height:30, background:"var(--bg-2)", border:"1px solid var(--line-2)", borderRadius:"var(--r-sm)", padding:"0 8px", fontSize:12, color:"var(--fg-0)", outline:"none", cursor:"pointer" };

  return (
    <div>
      <div style={{display:"flex", gap:12, marginBottom:16, padding:"14px 18px", background:"var(--bg-1)", border:"1px solid var(--line-1)", borderRadius:"var(--r-lg)"}}>
        {[
          {key:"missing",  label:"Missing declarations", color:"var(--red)"},
          {key:"mismatch", label:"Mismatches",           color:"var(--amber)"},
          {key:"partial",  label:"Partial",              color:"var(--amber)"},
          {key:"declared", label:"Declared correctly",   color:"var(--teal)"},
        ].map(s => (
          <button
            key={s.key}
            onClick={() => setPrivStatus(prev => prev === s.key ? "all" : s.key)}
            style={{flex:1, textAlign:"center", background:"transparent", border:"none", cursor:"pointer", padding:"6px 4px",
              borderRadius:6, outline: privStatus === s.key ? `2px solid ${s.color}` : "none",
              opacity: privStatus !== "all" && privStatus !== s.key ? 0.45 : 1
            }}
          >
            <div style={{fontFamily:"var(--font-display)", fontSize:28, fontWeight:600, color:s.color, lineHeight:1}}>
              {d.privacyDeclarations.filter(p=>p.status===s.key).length}
            </div>
            <div style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-2)", marginTop:4}}>{s.label}</div>
          </button>
        ))}
      </div>
      <div className="card">
        <div className="card-head">
          <div className="card-title">Privacy declarations status</div>
          <span className="card-sub" style={{marginRight:"auto"}}>Data Safety · Privacy Nutrition Label</span>
          <div style={{position:"relative"}}>
            <Icon name="search" size={12} style={{position:"absolute", left:8, top:"50%", transform:"translateY(-50%)", color:"var(--fg-3)", pointerEvents:"none"}} />
            <input
              value={privSearch}
              onChange={e => setPrivSearch(e.target.value)}
              placeholder="Search data types…"
              style={{height:30, width:160, background:"var(--bg-2)", border:"1px solid var(--line-2)", borderRadius:"var(--r-sm)", padding:"0 8px 0 26px", fontSize:12, color:"var(--fg-0)", outline:"none", boxSizing:"border-box"}}
            />
          </div>
          <select value={privStatus} onChange={e => setPrivStatus(e.target.value)} style={selSm}>
            <option value="all">All statuses</option>
            <option value="declared">Declared</option>
            <option value="missing">Missing</option>
            <option value="mismatch">Mismatch</option>
            <option value="partial">Partial</option>
          </select>
          {hasFilters && (
            <button className="btn btn-sm btn-ghost" onClick={() => { setPrivSearch(""); setPrivStatus("all"); }}>
              <Icon name="x" size={11} />
            </button>
          )}
        </div>
        <div className="tbl-scroll">
        <table className="tbl">
          <thead>
            <tr>
              <th>Data type</th>
              <th>Collected</th>
              <th>Linked to user</th>
              <th>For tracking</th>
              <th>Status</th>
              <th>Issue / fix</th>
            </tr>
          </thead>
          <tbody>
            {filteredDecls.length === 0 && (
              <tr><td colSpan={6} style={{textAlign:"center", padding:"36px 20px", color:"var(--fg-3)"}}>
                No declarations match your filters.
              </td></tr>
            )}
            {filteredDecls.map((p, i) => {
              const sm = statusMeta[p.status];
              return (
                <tr key={i}>
                  <td style={{fontWeight:500, fontSize:13.5}}>{p.dataType}</td>
                  <td>
                    <span style={{color:p.collected?"var(--amber)":"var(--teal)", fontFamily:"var(--font-mono)", fontSize:12}}>
                      {p.collected ? "Yes" : "No"}
                    </span>
                  </td>
                  <td>
                    <span style={{color:p.linkedToUser?"var(--amber)":"var(--fg-3)", fontFamily:"var(--font-mono)", fontSize:12}}>
                      {p.linkedToUser ? "Yes" : "No"}
                    </span>
                  </td>
                  <td>
                    <span style={{color:p.tracking?"var(--red)":"var(--fg-3)", fontFamily:"var(--font-mono)", fontSize:12}}>
                      {p.tracking ? "Yes" : "No"}
                    </span>
                  </td>
                  <td>
                    <span style={{
                      fontFamily:"var(--font-mono)", fontSize:10.5, fontWeight:600, letterSpacing:"0.06em",
                      textTransform:"uppercase", padding:"2px 8px", borderRadius:4,
                      color:sm.color, background:sm.bg, border:`1px solid ${sm.color}40`
                    }}>{sm.label}</span>
                  </td>
                  <td style={{fontSize:12, color:"var(--fg-2)", maxWidth:240}}>
                    {p.mismatch || p.fix || <span style={{color:"var(--fg-3)"}}>—</span>}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
        </div>
      </div>
    </div>
  );
};

// ── Tab: Store Listing ──
const StoreListingTab = ({ d }) => {
  const sl = d.storeListing;
  const statusIcon = { pass: "check-circle", info: "info", warning: "alert" };
  const statusColor = { pass:"var(--teal)", info:"var(--blue)", warning:"var(--amber)" };
  return (
    <div>
      {sl.issues.length > 0 && (
        <div style={{marginBottom:16, padding:"14px 18px", background:"var(--amber-bg)", border:"1px solid rgba(245,158,11,0.25)", borderRadius:"var(--r-md)", display:"flex", gap:12}}>
          <Icon name="alert" size={16} style={{color:"var(--amber)", flexShrink:0, marginTop:2}} />
          <div>
            {sl.issues.map((is, i) => (
              <div key={i} style={{fontSize:13, color:"var(--fg-1)", lineHeight:1.6}}>
                <strong style={{color:"var(--amber)"}}>{is.field}:</strong> {is.issue}{is.rec && <span style={{color:"var(--fg-2)"}}> — {is.rec}</span>}
              </div>
            ))}
          </div>
        </div>
      )}
      <div className="card">
        <div className="card-head">
          <div className="card-title">Store listing review</div>
          <span className="card-sub" style={{marginLeft:"auto", color:"var(--teal)"}}>Score: {sl.score}/100</span>
        </div>
        {sl.items.map((item, i) => (
          <div key={i} style={{
            display:"flex", alignItems:"flex-start", gap:16,
            padding:"16px 18px", borderTop:i===0?"1px solid var(--line-1)":"1px solid var(--line-1)"
          }}>
            <Icon name={statusIcon[item.status]||"check-circle"} size={16}
              style={{color:statusColor[item.status]||"var(--teal)", flexShrink:0, marginTop:2}} />
            <div style={{flex:1}}>
              <div style={{display:"flex", alignItems:"center", gap:10, marginBottom:4}}>
                <span style={{fontWeight:500, fontSize:13.5}}>{item.field}</span>
                <span className="chip" style={{fontSize:11}}>{item.value}</span>
              </div>
              <div style={{fontSize:12.5, color:"var(--fg-2)"}}>{item.detail}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

// ── Tab: Security ──
const SecurityTab = ({ d }) => {
  const secFindings = d.categories.play.find(c => c.id==="security");
  const [openIdx, setOpenIdx] = React.useState(null);
  if (!secFindings) return null;
  const extra = [
    { sev:"pass", name:"CERTIFICATE_PINNING", desc:"Certificate pinning detected via OkHttp CertificatePinner for api.roamly.app. MITM resistance confirmed." },
    { sev:"pass", name:"PROGUARD_ENABLED", desc:"ProGuard/R8 obfuscation is active on release build. Class and method names are stripped." },
    { sev:"pass", name:"ROOT_DETECTION", desc:"Root detection heuristics detected. App checks for Magisk, SuperSU, and custom recovery before sensitive operations." },
    { sev:"info", name:"EXPORTED_ACTIVITIES", desc:"2 Activities are exported (android:exported=true) — ShareActivity, DeepLinkActivity. Both appear intentional. Verify they require appropriate permissions." },
  ];
  const allFindings = [...secFindings.findings, ...extra];
  return (
    <div>
      <div style={{display:"grid", gridTemplateColumns:"repeat(4, 1fr)", gap:12, marginBottom:16}}>
        {[
          { label:"Critical", count:0, color:"var(--red)" },
          { label:"Warnings", count:1, color:"var(--amber)" },
          { label:"Info", count:1, color:"var(--blue)" },
          { label:"Passing", count:6, color:"var(--teal)" },
        ].map(s => (
          <div key={s.label} style={{background:"var(--bg-1)", border:"1px solid var(--line-1)", borderRadius:"var(--r-md)", padding:"14px 16px", textAlign:"center"}}>
            <div style={{fontFamily:"var(--font-display)", fontSize:28, fontWeight:600, color:s.color, lineHeight:1}}>{s.count}</div>
            <div style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-2)", marginTop:4}}>{s.label}</div>
          </div>
        ))}
      </div>
      <div style={{marginBottom:14}}>
        <div className="cat-head" style={{borderRadius:"var(--r-md) var(--r-md) 0 0", cursor:"default"}}>
          <Icon name="lock" size={16} className="ic" />
          <div className="title">Security findings</div>
          <div className="meta">
            <span className="mono" style={{color:"var(--amber)", fontSize:12}}>● 1 warning</span>
            <span className="mono" style={{color:"var(--fg-3)", fontSize:12}}>{allFindings.length} checks</span>
          </div>
        </div>
        <div className="findings">
          {allFindings.map((f, i) => (
            <FindingRow key={i} f={f} open={openIdx===i} onClick={() => setOpenIdx(openIdx===i ? null : i)} />
          ))}
        </div>
      </div>
    </div>
  );
};

// ── Tab: Policy References ──
const PolicyRefsTab = ({ d }) => {
  const sevColor = { critical:"var(--red)", warning:"var(--amber)", info:"var(--blue)", advisory:"var(--fg-2)" };
  const sevBg    = { critical:"var(--red-bg)", warning:"var(--amber-bg)", info:"var(--blue-bg)", advisory:"var(--bg-3)" };
  return (
    <div>
      <div className="card">
        <div className="card-head">
          <div className="card-title">Applicable policies</div>
          <span className="card-sub">{d.policyRefs.length} policies triggered</span>
        </div>
        {d.policyRefs.map((p, i) => (
          <div key={i} style={{
            display:"flex", alignItems:"center", gap:16,
            padding:"14px 18px", borderTop:i===0?"1px solid var(--line-1)":"1px solid var(--line-1)"
          }}>
            <StoreChip store={p.store} />
            <div style={{flex:1}}>
              <div style={{fontWeight:500, fontSize:13.5}}>{p.name}</div>
              <div style={{fontSize:12.5, color:"var(--fg-2)", marginTop:3}}>{p.desc}</div>
            </div>
            <span style={{
              fontFamily:"var(--font-mono)", fontSize:10.5, fontWeight:600, letterSpacing:"0.06em",
              textTransform:"uppercase", padding:"2px 8px", borderRadius:4, flexShrink:0,
              color:sevColor[p.severity]||"var(--fg-2)",
              background:sevBg[p.severity]||"var(--bg-3)",
              border:`1px solid ${sevColor[p.severity]||"var(--fg-3)"}40`
            }}>{p.severity}</span>
            <a className="btn btn-sm btn-ghost" href={p.url} target="_blank">
              <Icon name="external-link" size={12} /> View policy
            </a>
          </div>
        ))}
      </div>
    </div>
  );
};

// ── Tab: Export ──
const ExportTab = ({ d, go }) => {
  const [copied,      setCopied]      = React.useState(false);
  const [sharePublic, setSharePublic] = React.useState(false);

  const canExport = window.reliqGates ? window.reliqGates.canExportReport() : true;
  const canShare  = window.reliqGates ? window.reliqGates.canShareReport()  : true;

  // Share URL uses the report ID (rep_*) — NEVER the scan ID (scn_*).
  const shareUrl = window.reliqIds
    ? window.reliqIds.makeShareReportUrl(d.reportId)
    : "https://reliq.dev/r/" + d.reportId;

  const copyShareLink = () => {
    if (!canShare) {
      window.reliqGatePrompt?.show({ title:"Upgrade to share reports", description:"Share read-only report links with Starter plan or above.", planRequired:"starter", go });
      return;
    }
    try { navigator.clipboard.writeText(shareUrl).catch(() => {}); } catch (_) {}
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
    window.reliqToast?.success("Share link copied to clipboard.");
  };

  // PDF download handler — gated; filename uses report ID
  const handlePdfDownload = (variant) => {
    if (!canExport) {
      window.reliqGatePrompt?.show({ title:"Export requires Starter plan", description:"Upgrade to export full or summary PDF reports.", planRequired:"starter", go });
      return;
    }
    const suffix = variant === "summary" ? "-summary" : variant === "fixplan" ? "-fixplan" : "";
    const filename = "reliq-report-" + d.reportId + suffix + ".pdf";
    // Mock export — in production, call the report generation API
    const content = [
      "RELIQ COMPLIANCE REPORT",
      "Report ID: " + d.reportId,
      "Scan ID:   " + d.scanId,
      "App:       " + d.app.name + " v" + d.app.version + " (build " + d.app.build + ")",
      "Bundle:    " + d.app.bundle,
      "Score:     " + d.score + "/100",
      "Verdict:   " + d.verdict,
      "Scanned:   " + d.ranAt,
      "Variant:   " + (variant || "full"),
      "",
      "This is a mock PDF. Wire up real PDF generation in production.",
    ].join("\n");
    const blob = new Blob([content], { type:"application/pdf" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = filename;
    a.click();
    window.reliqToast?.success("Downloading " + filename);
  };

  return (
    <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:16}}>
      <div>
        <div className="card" style={{marginBottom:16}}>
          <div className="card-head"><div className="card-title">Export PDF report</div></div>
          <div className="card-body" style={{display:"flex", flexDirection:"column", gap:12}}>
            {[
              { label:"Full report",          sub:"All findings, fix plan, SDK inventory, privacy declarations · ~12 pages", variant:"full" },
              { label:"Summary report",       sub:"Overview, verdict, top issues only · ~3 pages",                          variant:"summary" },
              { label:"Developer fix plan",   sub:"Fix tickets only — formatted for dev handoff · ~4 pages",                variant:"fixplan" },
            ].map(opt => (
              <div key={opt.label} style={{display:"flex", alignItems:"center", gap:14, padding:"12px 14px", background:"var(--bg-2)", border:"1px solid var(--line-1)", borderRadius:"var(--r-md)"}}>
                <Icon name="file-text" size={16} style={{color:"var(--fg-2)"}} />
                <div style={{flex:1}}>
                  <div style={{fontSize:13.5, fontWeight:500}}>{opt.label}</div>
                  <div style={{fontSize:12, color:"var(--fg-2)", marginTop:2}}>{opt.sub}</div>
                  {/* Filename preview — uses report ID, never scan ID */}
                  <div className="mono" style={{fontSize:10.5, color:"var(--fg-3)", marginTop:3}}>
                    reliq-report-{d.reportId}{opt.variant !== "full" ? "-" + opt.variant : ""}.pdf
                  </div>
                </div>
                <button className="btn btn-sm" onClick={() => handlePdfDownload(opt.variant)}
                  title={canExport ? "Download PDF" : "Upgrade to download PDF"}>
                  <Icon name="download" size={12} /> Download
                  {!canExport && <Icon name="lock" size={10} style={{marginLeft:4, opacity:.6}} />}
                </button>
              </div>
            ))}
          </div>
        </div>

        <div className="card">
          <div className="card-head"><div className="card-title">White-label report</div><span className="chip" style={{marginLeft:"auto"}}>Business plan</span></div>
          <div className="card-body">
            <p style={{fontSize:13, color:"var(--fg-2)", margin:"0 0 14px", lineHeight:1.55}}>Export a co-branded or unbranded PDF with your company's logo, colours, and domain. Share directly with clients.</p>
            <button className="btn btn-sm" style={{opacity:0.5, cursor:"not-allowed"}} disabled><Icon name="sparkles" size={12} /> Configure white-label</button>
          </div>
        </div>
      </div>

      <div>
        <div className="card" style={{marginBottom:16}}>
          <div className="card-head"><div className="card-title">Share report</div></div>
          <div className="card-body">
            <div style={{display:"flex", alignItems:"center", gap:10, marginBottom:14}}>
              <span style={{flex:1, fontSize:13.5}}>Make report publicly accessible</span>
              <div className={`toggle ${sharePublic?"on":""}`} onClick={() => setSharePublic(!sharePublic)}></div>
            </div>
            {sharePublic && (
              <div style={{animation:"fadeUp .25s ease-out"}}>
                <div style={{display:"flex", gap:8, alignItems:"center", padding:"10px 12px", background:"var(--bg-2)", border:"1px solid var(--line-1)", borderRadius:"var(--r-sm)", marginBottom:12}}>
                  <Icon name="link" size={13} style={{color:"var(--fg-3)"}} />
                  {/* Share URL uses the report ID (rep_*) — never the scan ID (scn_*) */}
                  <span className="mono" style={{fontSize:12, color:"var(--fg-2)", flex:1, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>{shareUrl}</span>
                  <button className="btn btn-sm" onClick={copyShareLink}
                    title={canShare ? "Copy share link" : "Upgrade to share reports"}>
                    {copied ? <><Icon name="check" size={12} /> Copied</> : <><Icon name="copy" size={12} /> Copy</>}
                    {!canShare && <Icon name="lock" size={10} style={{marginLeft:4, opacity:.6}} />}
                  </button>
                </div>
                <button className="btn btn-sm btn-ghost" style={{color:"var(--red)"}}><Icon name="x" size={12} /> Revoke link</button>
              </div>
            )}
            <div style={{marginTop:14, padding:12, background:"var(--bg-2)", border:"1px solid var(--line-1)", borderRadius:"var(--r-sm)", fontSize:12, color:"var(--fg-2)", lineHeight:1.55}}>
              Shared reports are read-only. Viewers see findings, score, and fix recommendations. Your account details, API keys, and workspace information are never exposed.
            </div>
          </div>
        </div>

        <div className="card">
          <div className="card-head"><div className="card-title">Integrations</div></div>
          <div className="card-body" style={{display:"flex", flexDirection:"column", gap:10}}>
            {[
              {label:"Post to Slack", sub:"#releases channel", icon:"webhook"},
              {label:"Create Linear issues", sub:"Auto-create one ticket per finding", icon:"git-branch"},
              {label:"Jira integration", sub:"Push fix plan to project", icon:"box"},
            ].map(int => (
              <button key={int.label} className="btn" style={{justifyContent:"flex-start", gap:12}}>
                <Icon name={int.icon} size={14} />
                <div style={{textAlign:"left"}}>
                  <div style={{fontSize:13}}>{int.label}</div>
                  <div style={{fontSize:11.5, color:"var(--fg-3)", fontFamily:"var(--font-mono)"}}>{int.sub}</div>
                </div>
              </button>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

// Overlay live backend scan + findings onto the structural template.
const SCAN_VERDICT_MAP = { PASS: "likely_pass", WARNING: "needs_fixes", FAIL: "will_reject" };
function buildScanView(scan, findings) {
  if (!scan) return SCAN_TEMPLATE;
  const crit = findings.filter(f => f.severity === "CRITICAL").length;
  const warn = findings.filter(f => f.severity === "WARNING").length;
  const info = findings.filter(f => f.severity === "INFO").length;
  // Map the real findings into one category group so the Findings/Overview tabs
  // render backend findings (mobile per-store extraction has no backend endpoint).
  const realCat = findings.length ? [{
    id: "findings", name: "Findings", icon: "alert", critCount: crit, warnCount: warn,
    findings: findings.map(f => ({
      sev: (f.severity || "INFO").toLowerCase(), name: f.title || f.category,
      store: "all", desc: f.description || "", policy: f.policyRef || null,
      fix: f.fixRecommendation || null, category: f.category,
    })),
  }] : SCAN_TEMPLATE.categories.play;
  return {
    ...SCAN_TEMPLATE,
    scanId: scan.id || SCAN_TEMPLATE.scanId,
    score: scan.score != null ? scan.score : (scan.overallScore != null ? scan.overallScore : SCAN_TEMPLATE.score),
    verdict: SCAN_VERDICT_MAP[scan.verdict] || SCAN_TEMPLATE.verdict,
    app: { ...SCAN_TEMPLATE.app, name: (scan.app && scan.app.name) || (scan.targetUrl || SCAN_TEMPLATE.app.name) },
    byStore: {
      play: { ...SCAN_TEMPLATE.byStore.play, crit, warn, info, score: scan.score ?? SCAN_TEMPLATE.byStore.play.score },
      app:  { ...SCAN_TEMPLATE.byStore.app,  crit: 0, warn: 0, info: 0 },
    },
    categories: { play: realCat, app: [] },
    _findings: findings,
  };
}

// ── Main ScanResults component ──
const ScanResults = ({ go }) => {
  // scanId from the route (#/scan/:id) — falls back to the template scan id.
  const routeRaw = window.location.hash.replace(/^#\/?/, "");
  const scanId = routeRaw.startsWith("scan/") ? routeRaw.slice(5).split("?")[0].trim() : "";
  const wantFetch = !!scanId && scanId.startsWith("scn_");

  const [scan, setScan] = React.useState(null);
  const [findings, setFindings] = React.useState([]);
  const [loading, setLoading] = React.useState(wantFetch);
  const [error, setError] = React.useState(null);
  const [mainTab, setMainTab] = React.useState("overview");

  React.useEffect(() => {
    if (!wantFetch) { setLoading(false); return; }
    let cancelled = false;
    setLoading(true); setError(null);
    Promise.all([
      window.reliqReportsApi.getScan(scanId),
      window.reliqReportsApi.getScanFindings(scanId).catch(() => ({ findings: [] })),
    ])
      .then(([s, f]) => { if (!cancelled) { setScan(s); setFindings(f.findings || (Array.isArray(f) ? f : [])); } })
      .catch((e) => { if (!cancelled) setError(e); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [scanId]);

  const d = React.useMemo(() => buildScanView(scan, findings), [scan, findings]);
  const v = verdictMeta[d.verdict] || verdictMeta.needs_fixes;

  // ── Feature-gate checks (reads auth + subscription state) ──────────────────
  const canShare    = window.reliqGates ? reliqGates.canShareReport()  : true;
  const canExport   = window.reliqGates ? reliqGates.canExportReport() : true;
  const canRescan   = window.reliqGates ? reliqGates.canRescan()       : true;

  const gatedTabs = new Set(["sdk","privacy","listing","security","export"]);
  const canViewTab  = (id) => !gatedTabs.has(id) || (window.reliqGates ? reliqGates.canAccessTab(id) : true);

  // ── Gated TopBar action handlers ───────────────────────────────────────────
  const handleShare = () => {
    if (!canShare) {
      window.reliqGatePrompt?.show({ title: "Upgrade to share reports", description: "Share read-only report links with Starter plan or above.", planRequired: "starter", go });
      return;
    }
    // Share URL always uses the report ID (rep_*) — never the scan ID (scn_*)
    const shareUrl = window.reliqIds
      ? window.reliqIds.makeShareReportUrl(d.reportId)
      : "https://reliq.dev/r/" + d.reportId;
    try { navigator.clipboard.writeText(shareUrl).catch(() => {}); } catch (_) {}
    window.reliqToast?.success("Share link copied: " + shareUrl);
  };

  const handlePdf = () => {
    if (!canExport) {
      window.reliqGatePrompt?.show({ title: "Upgrade to export PDF", description: "Export full or summary PDF reports with Starter plan or above.", planRequired: "starter", go });
      return;
    }
    window.reliqToast?.info("Preparing PDF — this may take a moment.");
  };

  const handleRescan = () => {
    if (!canRescan) {
      window.reliqGatePrompt?.show({ title: "Upgrade to re-scan", description: "Re-run a scan after applying fixes with Starter plan or above.", planRequired: "starter", go });
      return;
    }
    window.reliqToast?.success("Re-scan queued. Results will appear in a few minutes.");
  };

  const tabs = [
    { id:"overview",    label:"Overview",     icon:"home"     },
    { id:"findings",    label:"Findings",     icon:"alert"    },
    { id:"fix-plan",    label:"Fix Plan",     icon:"tool"     },
    { id:"permissions", label:"Permissions",  icon:"shield"   },
    { id:"sdk",         label:"SDKs",         icon:"cpu",      gated:true },
    { id:"privacy",     label:"Privacy",      icon:"eye",      gated:true },
    { id:"listing",     label:"Listing",      icon:"package",  gated:true },
    { id:"security",    label:"Security",     icon:"lock",     gated:true },
    { id:"policy",      label:"Policies",     icon:"book"     },
    { id:"export",      label:"Export",       icon:"download", gated:true },
  ];

  const totalIssues = d.byStore.play.crit + d.byStore.play.warn + d.byStore.app.crit + d.byStore.app.warn;

  // ── Loading / error states (backend fetch) ────────────────────────────────
  if (loading) {
    return (
      <AuthLayout route="scans" go={go} crumbs={["Workspace", "All Scans", "Loading…"]}>
        <div style={{padding:"60px 20px", textAlign:"center", color:"var(--fg-3)", fontSize:13}}>Loading scan results…</div>
      </AuthLayout>
    );
  }
  if (error) {
    const notFound = error.status === 404;
    return (
      <AuthLayout route="scans" go={go} crumbs={["Workspace", "All Scans", notFound ? "Not found" : "Error"]}>
        <div style={{padding:"60px 20px", textAlign:"center", color:"var(--fg-3)", fontSize:13}}>
          <div style={{color:"var(--red)", fontWeight:600, marginBottom:8}}>{notFound ? "Scan not found." : `Couldn’t load this scan (${error.code || "error"}).`}</div>
          <button className="btn btn-sm" onClick={() => go("scans")}>Back to scans</button>
        </div>
      </AuthLayout>
    );
  }

  return (
    <AuthLayout
      route="scans"
      go={go}
      crumbs={["Workspace", "All Scans", d.app.name + " · v" + d.app.version]}
      actions={
        <>
          <button
            className="btn btn-sm"
            onClick={handleShare}
            aria-label="Share report"
            title={canShare ? "Share read-only link" : "Upgrade to share reports"}
          >
            <Icon name="share" size={13} /> Share
            {!canShare && <Icon name="lock" size={10} style={{marginLeft:4, opacity:.6}} />}
          </button>
          <button
            className="btn btn-sm"
            onClick={handlePdf}
            aria-label="Export PDF"
            title={canExport ? "Export PDF report" : "Upgrade to export PDF"}
          >
            <Icon name="download" size={13} /> PDF
            {!canExport && <Icon name="lock" size={10} style={{marginLeft:4, opacity:.6}} />}
          </button>
          <button
            className="btn btn-sm btn-primary"
            onClick={handleRescan}
            aria-label="Re-scan this build"
            title={canRescan ? "Re-scan after applying fixes" : "Upgrade to re-scan"}
          >
            <Icon name="refresh" size={13} /> Re-scan
          </button>
        </>
      }
    >
          {/* App header */}
          <div className="card" style={{marginBottom:14}}>
            <div className="card-body" style={{display:"flex", alignItems:"center", gap:20, flexWrap:"wrap"}}>
              <AppIcon letters={d.app.letters} size={64} gradient={d.app.grad} />
              <div style={{flex:1, minWidth:0}}>
                <div style={{display:"flex", alignItems:"center", gap:10, flexWrap:"wrap"}}>
                  <h1 style={{margin:0, fontFamily:"var(--font-display)", fontSize:22, fontWeight:600, letterSpacing:"-0.01em"}}>{d.app.name}</h1>
                  <span className="chip">v{d.app.version} · build {d.app.build}</span>
                  <span className="chip mono">{d.app.bundle}</span>
                  <span className="chip">{d.app.category}</span>
                </div>
                <div className="mono" style={{fontSize:12, color:"var(--fg-2)", marginTop:6}}>
                  {d.app.file} · scanned {d.ranAt} · took {d.duration}
                  {" · "}<span style={{color:"var(--fg-3)"}}>Scan: {d.scanId}</span>
                  {d.reportId && <>{" · "}<span style={{color:"var(--fg-3)"}}>Report: {d.reportId}</span></>}
                </div>
              </div>
              <div className="scan-header-ring">
                <ScoreRing value={d.score} />
              </div>
            </div>
          </div>

          {/* Verdict banner */}
          <div className={`verdict v-${d.verdict==="pass"?"pass":d.verdict==="likely_pass"?"likely":d.verdict==="will_reject"?"reject":"needs"}`} style={{marginBottom:18}}>
            <div className="icon-wrap">
              <Icon name={d.verdict==="pass"?"check-circle":d.verdict==="will_reject"?"x-circle":"alert"} size={26} />
            </div>
            <div>
              <div className="label">VERDICT</div>
              <div className="title" style={{color:v.color}}>{v.label}</div>
              <div style={{color:"var(--fg-2)", fontSize:13, marginTop:4}}>
                {totalIssues} issue{totalIssues!==1?"s":""} found — fix 1 critical and 4 warnings to reach a passing score.
              </div>
            </div>
            <div className="stamp">
              <div className="mono" style={{fontSize:11}}>{d.scanId}</div>
              {d.reportId && <div className="mono" style={{fontSize:10, color:"var(--fg-3)", marginTop:1}}>{d.reportId}</div>}
              <div className="mono" style={{fontSize:11, marginTop:4}}>{d.ranAt}</div>
            </div>
          </div>

          {/* Main tab nav */}
          <div className="tabs" role="tablist" aria-label="Scan detail sections" style={{marginBottom:20, overflowX:"auto"}}>
            {tabs.map(t => {
              const locked = t.gated && !canViewTab(t.id);
              return (
                <div
                  key={t.id}
                  role="tab"
                  aria-selected={mainTab === t.id}
                  aria-label={locked ? `${t.label} — upgrade required` : t.label}
                  tabIndex={0}
                  className={`tab ${mainTab===t.id?"active":""} ${locked?"tab-locked":""}`}
                  onClick={() => setMainTab(t.id)}
                  onKeyDown={e => (e.key === "Enter" || e.key === " ") && setMainTab(t.id)}
                  style={{whiteSpace:"nowrap", cursor:"pointer"}}
                >
                  <Icon name={t.icon} size={13} />
                  {t.label}
                  {t.id==="findings"    && <span className="ct">{d.byStore.play.crit+d.byStore.app.crit}C · {d.byStore.play.warn+d.byStore.app.warn}W</span>}
                  {t.id==="fix-plan"    && <span className="ct">{d.fixPlan.length}</span>}
                  {t.id==="sdk"         && <span className="ct">{d.sdks.length}</span>}
                  {t.id==="permissions" && <span className="ct">{d.permissions.length}</span>}
                  {locked && <Icon name="lock" size={10} style={{opacity:.5, marginLeft:2}} aria-hidden="true" />}
                </div>
              );
            })}
          </div>

          {/* Tab content — gated tabs show LockedTabOverlay for unpaid users */}
          <div role="tabpanel">
            {mainTab==="overview"    && <OverviewTab d={d} go={go} />}
            {mainTab==="findings"    && <FindingsTab d={d} />}
            {mainTab==="fix-plan"    && <FixPlanTab d={d} />}
            {mainTab==="permissions" && <PermissionsTab d={d} />}
            {mainTab==="sdk"         && (canViewTab("sdk")      ? <SDKInventoryTab d={d} />    : <LockedTabOverlay planRequired="starter" featureName="SDK Inventory"    go={go} />)}
            {mainTab==="privacy"     && (canViewTab("privacy")  ? <PrivacyTab d={d} />         : <LockedTabOverlay planRequired="starter" featureName="Privacy Analysis"  go={go} />)}
            {mainTab==="listing"     && (canViewTab("listing")  ? <StoreListingTab d={d} />    : <LockedTabOverlay planRequired="starter" featureName="Store Listing"     go={go} />)}
            {mainTab==="security"    && (canViewTab("security") ? <SecurityTab d={d} />        : <LockedTabOverlay planRequired="starter" featureName="Security Audit"    go={go} />)}
            {mainTab==="policy"      && <PolicyRefsTab d={d} />}
            {mainTab==="export"      && (canViewTab("export")   ? <ExportTab d={d} go={go} /> : <LockedTabOverlay planRequired="starter" featureName="Export & Share"    go={go} />)}
          </div>
    </AuthLayout>
  );
};

Object.assign(window, { ScanResults });
