// App shell: EmailGate, Sidebar, TopBar, BottomTabBar, Dashboard composition.

const { useState, useMemo, useEffect, useRef } = React;

// ============================================================
// Email Gate
// ============================================================
function EmailGate({ onSubmit, onBack }) {
  const [email, setEmail] = useState("");
  const [error, setError] = useState("");
  const [submitting, setSubmitting] = useState(false);
  const [sent, setSent] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const v = email.trim();
    if (!v) { setError("Enter the email used at checkout."); return; }
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v)) { setError("That doesn't look like a valid email."); return; }
    setError("");
    setSubmitting(true);
    try {
      await onSubmit(v);            // sends the Supabase magic-link OTP
      setSent(true);
    } catch (err) {
      setError(err && err.message ? err.message : "Couldn't send the link. Try again.");
    } finally {
      setSubmitting(false);
    }
  };

  // Confirmation state — link sent, waiting for the user to click it in their inbox.
  if (sent) {
    return (
      <div className="gate" data-screen-label="01 Email Gate (sent)">
        <div className="gate__logo">
          <img src="assets/sweepify-logo.svg" alt="Sweepify" />
        </div>
        <div className="gate__panel">
          <h1 className="gate__title">Check your email.</h1>
          <p className="gate__sub">
            We sent a secure sign-in link to <strong>{email}</strong>. Click it to open your dashboard.
          </p>
          <div className="gate__hint">
            <span className="pulse" />
            Waiting for you to click the link…
          </div>
          <button className="sw-link" style={{ marginTop: 18 }}
                  onClick={() => { setSent(false); setEmail(""); }}>
            Use a different email
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="gate" data-screen-label="01 Email Gate">
      {onBack && (
        <button className="gate__back" onClick={onBack} aria-label="Open dashboard preview">
          <IconArrowRight size={14} />
          Dashboard preview
        </button>
      )}

      <div className="gate__logo">
        <img src="assets/sweepify-logo.svg" alt="Sweepify" />
      </div>

      <div className="gate__panel">
        <h1 className="gate__title">Access your report.</h1>
        <p className="gate__sub">Enter the email you used at checkout.</p>

        <form className="gate__form" onSubmit={handleSubmit} noValidate>
          <input
            className="sw-input"
            type="email"
            autoFocus
            placeholder="you@example.com"
            value={email}
            onChange={(e) => { setEmail(e.target.value); if (error) setError(""); }}
            aria-invalid={!!error}
            aria-label="Email used at checkout"
          />
          {error && <div className="gate__error">{error}</div>}
          <button className="sw-btn gate__cta" type="submit" disabled={submitting}>
            {submitting ? "Sending link…" : "Email me a sign-in link"}
            {!submitting && <IconArrowRight size={16} />}
          </button>
        </form>

        <div className="gate__hint">
          <span className="pulse" />
          Your results are ready and waiting.
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Sidebar
// ============================================================
function Sidebar({ route, onNav, email, onLogout, scanOpen, setScanOpen, unreadNotifs }) {
  const isScanRoute = route.startsWith("scan/");
  const items = [
    { id: "dashboard", label: "Dashboard", Icon: IconHome },
    { id: "new-scan",  label: "New Scan",  Icon: IconScan, hasChildren: true },
    { id: "privacy",   label: "Privacy Shield", Icon: IconShieldCheck, badge: "New" },
    { id: "topup",     label: "Top Up",   Icon: IconPlus }
  ];

  return (
    <aside className="sidebar">
      <div className="sidebar__logo">
        <img src="assets/sweepify-logo.svg" alt="Sweepify" />
      </div>

      <nav className="nav" aria-label="Primary">
        {items.map(({ id, label, Icon, hasChildren, badge }) => {
          const active = (id === "new-scan" && isScanRoute) || (id === route);
          if (hasChildren) {
            return (
              <div key={id} className="nav__group">
                <button
                  className={"nav__item" + (active ? " nav__item--active" : "")}
                  onClick={() => setScanOpen(v => !v)}
                  aria-expanded={scanOpen}
                >
                  <Icon size={18} />
                  <span>{label}</span>
                  <span className="nav__chev" aria-hidden style={{ marginLeft: "auto", transform: scanOpen ? "rotate(180deg)" : "none", transition: "transform 220ms var(--sw-ease)" }}>
                    <IconChevronDown size={14} />
                  </span>
                </button>
                {scanOpen && (
                  <div className="nav__children">
                    {SCAN_TYPES.map(s => {
                      const isActive = route === ("scan/" + s.id);
                      return (
                        <button
                          key={s.id}
                          className={"nav__subitem" + (isActive ? " nav__subitem--active" : "")}
                          onClick={() => onNav("scan/" + s.id)}
                        >
                          <span className="nav__bullet" style={isActive ? undefined : { background: s.color }} />
                          <span className="nav__subitem-label">{s.name}</span>
                        </button>
                      );
                    })}
                  </div>
                )}
              </div>
            );
          }
          return (
            <button
              key={id}
              className={"nav__item" + (active ? " nav__item--active" : "")}
              onClick={() => onNav(id)}
            >
              <Icon size={18} />
              <span>{label}</span>
              {badge && <span className="nav__chip">{badge}</span>}
            </button>
          );
        })}
      </nav>

      <div className="sidebar__bottom">
        <button
          className={"nav__item nav__item--bottom" + (route === "notifications" ? " nav__item--active" : "")}
          onClick={() => onNav("notifications")}
        >
          <IconBell size={18} />
          <span>Notifications</span>
          {unreadNotifs > 0 && <span className="nav__chip nav__chip--alert">{unreadNotifs}</span>}
        </button>
        <button
          className={"nav__item nav__item--bottom" + (route === "support" ? " nav__item--active" : "")}
          onClick={() => onNav("support")}
        >
          <IconLifeBuoy size={18} />
          <span>Support</span>
        </button>
        <button
          className={"nav__item nav__item--bottom" + (route === "settings" ? " nav__item--active" : "")}
          onClick={() => onNav("settings")}
        >
          <IconSettings size={18} />
          <span>Settings</span>
        </button>
      </div>

      <div className="sidebar__account">
        <div className="sidebar__email" title={email}>{email}</div>
        <button className="icon-btn" aria-label="Log out" onClick={onLogout}>
          <IconLogout size={16} />
        </button>
      </div>
    </aside>
  );
}

// ============================================================
// Bottom tab bar (mobile)
// ============================================================
function BottomTabBar({ route, onNav, onScan, onMore, unreadNotifs }) {
  const items = [
    { id: "dashboard", label: "Home", Icon: IconHome },
    { id: "__scan",    label: "Scan", Icon: IconScan },
    { id: "privacy",   label: "Shield", Icon: IconShieldCheck },
    { id: "__more",    label: "More", Icon: IconMoreH, badge: unreadNotifs }
  ];
  return (
    <nav className="bottombar" aria-label="Primary">
      {items.map(({ id, label, Icon, badge }) => {
        const active =
          id === "__more" || id === "__scan" ? (id === "__scan" && route.startsWith("scan/")) :
          id === route;
        return (
          <button
            key={id}
            className={"bottombar__item" + (active ? " bottombar__item--active" : "")}
            onClick={() => id === "__more" ? onMore() : id === "__scan" ? onScan() : onNav(id)}
          >
            <span className="bottombar__ico">
              <Icon size={20} />
              {badge > 0 && <span className="bottombar__badge">{badge}</span>}
            </span>
            <span>{label}</span>
          </button>
        );
      })}
    </nav>
  );
}

// ============================================================
// Mobile scan picker drawer — choose a scan (mobile)
// ============================================================
function ScanPickerSheet({ open, onClose, onNav }) {
  if (!open) return null;
  const go = (id) => { onClose(); onNav("scan/" + id); };
  return (
    <div className="msheet" role="dialog" aria-modal="true" aria-label="New scan" onClick={onClose}>
      <div className="msheet__panel" onClick={(e) => e.stopPropagation()}>
        <div className="msheet__grip" aria-hidden />
        <div className="msheet__title">Run a new scan</div>
        <div className="msheet__list">
          {SCAN_TYPES.map(s => {
            const Icon = window[s.icon] || IconScan;
            return (
              <button key={s.id} className="msheet__row" onClick={() => go(s.id)}>
                <span className="msheet__ico" style={{ background: "color-mix(in srgb, " + s.color + " 15%, transparent)", color: s.color }}>
                  <Icon size={18} />
                </span>
                <span className="msheet__row-body">
                  <span className="msheet__row-title">{s.name}</span>
                  <span className="msheet__row-sub">{formatTokens(s.cost)} tokens</span>
                </span>
                <IconArrowRight size={15} />
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Mobile "More" sheet — access to everything not on the tab bar
// ============================================================
function MobileMoreSheet({ open, onClose, onNav, onLogout, unreadNotifs }) {
  if (!open) return null;
  const go = (route) => { onClose(); onNav(route); };
  const links = [
    { id: "topup",         label: "Top Up",        sub: "Add more tokens",            Icon: IconPlus },
    { id: "notifications", label: "Notifications", sub: "Reports, alerts & updates",  Icon: IconBell, badge: unreadNotifs },
    { id: "support",       label: "Support",       sub: "FAQs and live chat",         Icon: IconLifeBuoy },
    { id: "settings",      label: "Settings",      sub: "Shield, data & account",     Icon: IconSettings }
  ];
  return (
    <div className="msheet" role="dialog" aria-modal="true" aria-label="More" onClick={onClose}>
      <div className="msheet__panel" onClick={(e) => e.stopPropagation()}>
        <div className="msheet__grip" aria-hidden />
        <div className="msheet__title">More</div>
        <div className="msheet__list">
          {links.map(({ id, label, sub, Icon, badge }) => (
            <button key={id} className="msheet__row" onClick={() => go(id)}>
              <span className="msheet__ico"><Icon size={18} /></span>
              <span className="msheet__row-body">
                <span className="msheet__row-title">{label}</span>
                <span className="msheet__row-sub">{sub}</span>
              </span>
              {badge > 0 && <span className="msheet__badge">{badge}</span>}
              <IconArrowRight size={15} />
            </button>
          ))}
        </div>
        <button className="msheet__logout" onClick={() => { onClose(); onLogout(); }}>
          <IconLogout size={15} /> Log out
        </button>
      </div>
    </div>
  );
}

// ============================================================
// Animated token balance — counts to target when it changes
// ============================================================
function useAnimatedNumber(target) {
  const [val, setVal] = useState(target);
  const prev = useRef(target);
  useEffect(() => {
    if (target === prev.current) return;
    const from = prev.current;
    const to = target;
    const start = performance.now();
    const dur = 700;
    let raf;
    const tick = (t) => {
      const k = Math.min(1, (t - start) / dur);
      const eased = 1 - Math.pow(1 - k, 3);
      setVal(Math.round(from + (to - from) * eased));
      if (k < 1) raf = requestAnimationFrame(tick);
      else prev.current = to;
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target]);
  return val;
}

// ============================================================
// Top bar
// ============================================================
function TopBar({ title, tokens, onTopUp, lowBalance, unreadNotifs, onNotifications }) {
  const [tooltip, setTooltip] = useState(false);
  const animated = useAnimatedNumber(tokens);
  return (
    <header className="topbar">
      <h1 className="topbar__title">{title}</h1>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <button
          className="topbar-bell"
          aria-label={`Notifications${unreadNotifs ? ` (${unreadNotifs} unread)` : ""}`}
          onClick={onNotifications}
        >
          <IconBell size={18} />
          {unreadNotifs > 0 && <span className="topbar-bell__badge">{unreadNotifs}</span>}
        </button>
        <div className="balance-wrap"
             onMouseEnter={() => setTooltip(true)}
             onMouseLeave={() => setTooltip(false)}>
          <button className={"sw-chip-balance" + (lowBalance ? " is-low" : "")}
                  onClick={onTopUp}
                  aria-label={`Balance ${tokens} tokens. Top up.`}>
            <span className="sw-chip-balance__dot" aria-hidden />
            <IconChip size={14} />
            <span className="balance-num">{formatTokens(animated)} tokens</span>
            {lowBalance && <span className="balance-red" aria-hidden />}
            <span className="sw-chip-balance__plus" aria-hidden>
              <IconPlus size={14} />
            </span>
          </button>
          {lowBalance && tooltip && (
            <div className="balance-tooltip">
              Low balance — Top Up to keep scanning
              <button className="sw-link" onClick={onTopUp}>Top up <IconArrowRight size={11} /></button>
            </div>
          )}
        </div>
      </div>
    </header>
  );
}

// ============================================================
// Stub
// ============================================================
function StubPage({ title, sub }) {
  return (
    <div className="stub">
      <div className="stub__icon"><IconShieldCheck size={26} /></div>
      <h2 className="stub__title">{title}</h2>
      <p className="stub__sub">{sub}</p>
    </div>
  );
}

// ============================================================
// Dashboard composition (handles routing)
// ============================================================
function Dashboard({ email, onLogout, initialRoute = "dashboard", data }) {
  // `data` (when present) is real backend state from SweepifyBackend.load().
  // Falling back to the prototype mocks keeps this component runnable standalone.
  const BE = window.SweepifyBackend;
  const [route, setRoute] = useState(initialRoute);
  const [balance, setBalance] = useState(data ? data.balance : 6500);
  const [searches, setSearches] = useState(data ? data.searches : SEARCHES);
  const [scanOpen, setScanOpen] = useState(initialRoute.startsWith("scan/"));
  const [toast, setToast] = useState(null);
  const [loadingScan, setLoadingScan] = useState(null); // { scan, subject }
  const [insuffScan, setInsuffScan] = useState(null); // scan obj for modal
  const [notifs, setNotifs] = useState(data ? data.notifications : NOTIFICATIONS);
  const [faceResult, setFaceResult] = useState(null);
  const [topupPack, setTopupPack] = useState(null); // pack obj -> Stripe modal
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  const [scanMenuOpen, setScanMenuOpen] = useState(false);
  const unreadNotifs = notifs.filter(n => n.unread).length;

  const [shield, setShield] = useState((data && data.shield) ? data.shield : {
    active: false,
    paused: false,
    plan: "—",
    expires: "—",
    remaining: "—",
    autorenew: false
  });

  // Re-pull live state from Supabase (after a scan, top-up, etc.).
  const refresh = async () => {
    if (!BE) return;
    try {
      const d = await BE.load();
      setBalance(d.balance);
      setSearches(d.searches);
      setNotifs(d.notifications);
      if (d.shield) setShield(d.shield);
    } catch (e) { console.error("refresh failed", e); }
  };
  const scheduleRefresh = (ms = 1600) => { if (BE) setTimeout(refresh, ms); };

  useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => setToast(null), 2400);
    return () => clearTimeout(t);
  }, [toast]);

  const minScanCost = Math.min(...SCAN_TYPES.map(s => s.cost));
  const lowBalance = balance < minScanCost;

  const handleRun = (scan) => {
    setRoute("scan/" + scan.id);
    setScanOpen(true);
  };
  const handlePromo = (target) => {
    if (target.startsWith("scan/")) setScanOpen(true);
    setRoute(target);
  };
  const handleTopUp = () => setRoute("topup");
  // Scans with no backend workflow yet — show "coming soon", never charge.
  const UNWIRED_SCANS = new Set(["tinder-sonar", "relationship"]);
  const handleScanSubmit = (scan, form) => {
    if (UNWIRED_SCANS.has(scan.id)) {
      setToast(`${scan.name} is coming soon — no tokens charged.`);
      setRoute("dashboard");
      return;
    }
    const subject =
      form.name ||
      (form.first || form.last ? [form.first, form.last].filter(Boolean).join(" ") : null) ||
      form.email || form.phone || form.photo?.name || null;

    // Optimistic balance debit + the existing loading animation.
    setBalance(b => b - scan.cost);
    setLoadingScan({ scan, subject, photoUrl: form.photoUrl || null });
    setRoute("scanning");

    // Kick off the real scan against the Worker (no-op when running standalone).
    if (BE) {
      const isFace = scan.id === "face";
      const call = isFace
        ? BE.api.faceScan({
            subject_name: subject,
            image_url: form.photoUrl || undefined,
            image_base64: form.photoBase64 || undefined,
          })
        : BE.api.scan({
            scan_type: scan.id,
            subject_name: subject,
            subject_identifier: form.email || form.phone || form.handle || null,
            input_data: { ...form, photo: undefined, photoUrl: undefined },
            platforms_selected: form.platforms || null,
          });
      call
        .then((res) => { if (res && typeof res.balance === "number") setBalance(res.balance); scheduleRefresh(); })
        .catch((err) => {
          // Refund is handled server-side; restore the optimistic debit and surface the error.
          refresh();
          setLoadingScan(null);
          setRoute(scan.id ? "scan/" + scan.id : "dashboard");
          setToast(err.status === 402 ? "Not enough tokens for that scan." : (err.message || "Scan failed to start."));
        });
    }
  };
  const handleScanDone = () => {
    const { scan, subject, photoUrl } = loadingScan || {};
    if (scan && scan.id === "face") {
      setFaceResult({ scan, subject, photoUrl });
      setLoadingScan(null);
      setRoute("face-results");
      scheduleRefresh();
      return;
    }
    setLoadingScan(null);
    setRoute("dashboard");
    setToast(`${scan?.name || "Scan"} complete${subject ? ` for ${subject}` : ""}. Open in My Searches.`);
    scheduleRefresh();
  };
  // Called by StripeTopUpModal AFTER a successful payment. The webhook credits
  // the authoritative amount server-side; we optimistically reflect it, then
  // reconcile via refresh().
  const handlePurchase = (pack) => {
    setBalance(b => b + pack.total);
    setToast(`${pack.name} added — ${formatTokens(pack.total)} tokens credited (+${pack.cashback}% cashback).`);
    scheduleRefresh(2500);
  };

  const title = useMemo(() => {
    if (route === "dashboard") return "Dashboard";
    if (route === "searches")  return "My Searches";
    if (route === "privacy")   return "Privacy Shield";
    if (route === "topup")     return "Top Up";
    if (route === "new-scan")  return "New Scan";
    if (route === "settings")  return "Settings";
    if (route === "notifications") return "Notifications";
    if (route === "support") return "Support";
    if (route === "face-results") return "Face Scan Results";
    if (route === "scanning")  return "Scanning…";
    if (route.startsWith("scan/")) return "New Scan";
    return "Dashboard";
  }, [route]);

  const activeScan = route.startsWith("scan/")
    ? SCAN_TYPES.find(s => s.id === route.split("/")[1])
    : null;

  // Reset the insufficient-tokens modal when navigating away or when balance covers the active scan.
  // (We no longer auto-open it on landing — only when the user clicks Start Scan underfunded.)
  useEffect(() => {
    if (!activeScan || balance >= activeScan.cost) {
      setInsuffScan(null);
    }
  }, [route, activeScan, balance]);

  const scanInputProps = activeScan && {
    scan: activeScan, balance,
    onBack: () => setRoute("dashboard"),
    onSubmit: handleScanSubmit,
    onTopUp: () => setInsuffScan(activeScan)
  };

  return (
    <div className="app-shell" data-screen-label="02 Dashboard">
      <Sidebar
        route={route}
        onNav={setRoute}
        email={email}
        onLogout={onLogout}
        scanOpen={scanOpen}
        setScanOpen={setScanOpen}
        unreadNotifs={unreadNotifs}
      />
      <div className="content">
        <TopBar
          title={title}
          tokens={balance}
          onTopUp={handleTopUp}
          lowBalance={lowBalance}
          unreadNotifs={unreadNotifs}
          onNotifications={() => setRoute("notifications")}
        />

        <div className="main">
          {route === "dashboard" && (
            <>
              <PromoCarousel onOpen={handlePromo} />
              <MySearchesSection rows={searches} />
              <ScanImageGrid onOpen={handlePromo} />
              <PrivacyBanner onClick={() => setRoute("privacy")} />
            </>
          )}
          {route === "searches" && <MySearchesSection rows={searches} />}
          {route === "new-scan" && (
            <ScansSection balance={balance} onRun={handleRun} onTopUp={handleTopUp} />
          )}
          {route === "privacy" && <PrivacyShieldPage />}
          {route === "topup" && (
            <TopUpPage balance={balance} onBack={() => setRoute("dashboard")} onPurchase={(p) => setTopupPack(p)} />
          )}
          {route === "settings" && (
            <SettingsPage
              shield={shield}
              onShieldChange={setShield}
              onBack={() => setRoute("dashboard")}
              onActivateShield={() => setRoute("privacy")}
              onExport={() => setToast(`Preparing ${searches.length} reports for export…`)}
              onDelete={() => setToast("Account deletion requested. We'll email you to confirm.")}
              reportCount={searches.length}
            />
          )}
          {route === "notifications" && (
            <NotificationsPage
              onBack={() => setRoute("dashboard")}
              notifications={notifs}
              onMarkAllRead={() => {
                setNotifs(ns => ns.map(n => ({ ...n, unread: false })));
                setToast("All notifications marked as read.");
                if (BE) notifs.filter(n => n.unread).forEach(n => BE.markNotificationRead(n.id));
              }}
              onItem={(n) => {
                setNotifs(ns => ns.map(x => x.id === n.id ? { ...x, unread: false } : x));
                if (BE && n.unread) BE.markNotificationRead(n.id);
                if (n.type === "report") setRoute("dashboard");
              }}
            />
          )}
          {route === "support" && (
            <SupportPage
              onBack={() => setRoute("dashboard")}
              onContact={() => setToast("Connecting you to a support agent…")}
            />
          )}
          {route === "scanning" && loadingScan && (
            <ScanLoadingPage
              scan={loadingScan.scan}
              subject={loadingScan.subject}
              photoUrl={loadingScan.photoUrl}
              onComplete={handleScanDone}
              onCancel={() => { setLoadingScan(null); setRoute("dashboard"); }}
            />
          )}
          {route === "face-results" && faceResult && (
            <FaceResultsPage
              scan={faceResult.scan}
              subject={faceResult.subject}
              photoUrl={faceResult.photoUrl}
              onBack={() => setRoute("dashboard")}
              onUpgrade={() => setRoute("topup")}
            />
          )}
          {activeScan && route !== "scanning" && (() => {
            const map = {
              "dating-app":   DatingAppScanInput,
              "face":         FaceScanInput,
              "new-person":   NewPersonCheckInput,
              "relationship": RelationshipStatusInput,
              "tinder-sonar": TinderSonarInput
            };
            const Cmp = map[activeScan.id] || DatingAppScanInput;
            return <Cmp {...scanInputProps} />;
          })()}
        </div>
      </div>
      <BottomTabBar
        route={route}
        onNav={setRoute}
        onScan={() => setScanMenuOpen(true)}
        onMore={() => setMobileMenuOpen(true)}
        unreadNotifs={unreadNotifs}
      />

      <ActivityTicker />

      <ScanPickerSheet
        open={scanMenuOpen}
        onClose={() => setScanMenuOpen(false)}
        onNav={setRoute}
      />

      <MobileMoreSheet
        open={mobileMenuOpen}
        onClose={() => setMobileMenuOpen(false)}
        onNav={setRoute}
        onLogout={onLogout}
        unreadNotifs={unreadNotifs}
      />

      {toast && (
        <div className="toast" role="status">
          <IconCheck size={14} />
          {toast}
        </div>
      )}

      <InsufficientTokensModal
        open={!!insuffScan}
        onClose={() => setInsuffScan(null)}
        scan={insuffScan}
        balance={balance}
        onTopUp={() => { setInsuffScan(null); setRoute("topup"); }}
      />

      {window.StripeTopUpModal && (
        <StripeTopUpModal
          open={!!topupPack}
          pack={topupPack}
          onClose={() => setTopupPack(null)}
          onSuccess={handlePurchase}
        />
      )}
    </div>
  );
}

Object.assign(window, { EmailGate, Dashboard, TopBar, Sidebar, BottomTabBar, MobileMoreSheet, ScanPickerSheet, StubPage });
