// Main app — orchestrates tier feeds, live stream, detail drawer, rule builder
//
// DATA SOURCE: window.DATA_MODE === "mock" (default) | "live"  (set in api.jsx)
//   mock -> generated SEED_ITEMS + simulated stream (unchanged original behavior)
//   live -> fetches the real API via window.KS and refreshes by polling
// Append ?data=mock to the URL for generated local data.

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

const DATA_MODE = window.DATA_MODE || "mock";
const LIVE = DATA_MODE === "live";
const LIVE_REFRESH_MS = 45 * 1000;
const DEFAULT_FILTERS = { karat: [], type: [] };
const defaultFilters = () => ({
  karat: [...DEFAULT_FILTERS.karat],
  type: [...DEFAULT_FILTERS.type],
});

// ---- loading / error states (live mode) -----------------------------------
const LoadingGrid = () => (
  <div className="grid">
    {Array.from({ length: 8 }).map((_, i) => (
      <div key={i} className="skeleton skel-card" />
    ))}
  </div>
);

const ErrorState = ({ error, onRetry }) => {
  const limited = error && error.status === 429;
  return (
    <div className="feed-state">
      <div className="icon-wrap"><I.x size={20} /></div>
      <h3>{limited ? "Slow down a moment" : "Couldn't load this feed"}</h3>
      <p>
        {limited
          ? `Rate limited by the API — try again in ${error.retryAfter || 30}s.`
          : (error && error.message) || "The API returned an error."}
        {error && error.requestId ? ` · ref ${error.requestId}` : ""}
      </p>
      <button className="btn accent sm" onClick={onRetry}><I.spark size={12} /> Try again</button>
    </div>
  );
};

function App() {
  const [tweaks, setTweaks] = useState(window.__TWEAKS__);
  const signedIn = tweaks.signedIn;

  const [liveOn, setLiveOn] = useState(false);
  const [active, setActive] = useState("deals");
  const [feed, setFeed] = useState("offer");
  const [nearTier, setNearTier] = useState("deal");
  const [includeReview, setIncludeReview] = useState(true);
  const [view, setView] = useState(tweaks.feedLayout || "grid");
  const [sort, setSort] = useState("newest");
  const [tweaksOpen, setTweaksOpen] = useState(false);
  const [detailItem, setDetailItem] = useState(null);
  const [items, setItems] = useState(LIVE ? [] : SEED_ITEMS);
  const [loading, setLoading] = useState(LIVE);
  const [error, setError] = useState(null);
  const [reload, setReload] = useState(0);
  const [searchTerm, setSearchTerm] = useState("");
  const [searchMeta, setSearchMeta] = useState(null);
  const [comingSoon, setComingSoon] = useState(null);
  const [justInIds, setJustInIds] = useState(new Set());
  const [filters, setFilters] = useState(defaultFilters);
  const [lastMatch, setLastMatch] = useState(LIVE ? "—" : "12s");
  const [alerts, setAlerts] = useState(ALERTS);
  const [spot, setSpot] = useState(GOLD_SPOT);

  const showComingSoon = (feature, detail) => setComingSoon({
    feature,
    detail: detail || "Accounts, alerts, saved items, and live push are staged behind this public MVP while anonymous deal browsing stays live.",
  });
  const closeComingSoon = () => setComingSoon(null);

  const changeFeed = (next) => {
    setSearchTerm("");
    setSearchMeta(null);
    setFeed(next);
  };

  const runSearch = (query) => {
    const q = (query || "").trim();
    setSearchMeta(null);
    if (!q) {
      setSearchTerm("");
      return;
    }
    setActive("browse");
    setFeed("all");
    setSearchTerm(q);
  };

  const clearSearch = () => {
    setSearchTerm("");
    setSearchMeta(null);
  };
  const applyFilters = () => setReload(n => n + 1);
  const resetFilters = () => {
    setFilters(defaultFilters());
    setSearchTerm("");
    setSearchMeta(null);
  };
  const clearFilters = () => {
    setFilters({ karat: [], type: [] });
    setSearchTerm("");
    setSearchMeta(null);
  };

  // theme / accent / density on root
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", tweaks.theme);
    document.documentElement.setAttribute("data-accent", tweaks.accent);
    document.documentElement.setAttribute("data-density", tweaks.density);
  }, [tweaks]);
  useEffect(() => { setView(tweaks.feedLayout); }, [tweaks.feedLayout]);

  // edit mode bridge
  useEffect(() => {
    const onMsg = (e) => {
      if (!e.data) return;
      if (e.data.type === "__activate_edit_mode") { setTweaksOpen(true); }
      if (e.data.type === "__deactivate_edit_mode") { setTweaksOpen(false); }
    };
    window.addEventListener("message", onMsg);
    window.parent.postMessage({type:"__edit_mode_available"}, "*");
    return () => window.removeEventListener("message", onMsg);
  }, []);

  // ====================== LIVE DATA ======================
  // Load the active feed/search from the real API whenever the query changes.
  useEffect(() => {
    if (!LIVE) return;
    let alive = true;
    setLoading(true); setError(null);
    const request = searchTerm
      ? KS.search(searchTerm, { filters: { karat: filters.karat, item_type: filters.type } })
      : KS.getFeed(feed, { nearTier, includeReview, filters, sort });
    request
      .then(result => {
        if (!alive) return;
        if (searchTerm) {
          setItems(result.items || []);
          setSearchMeta({ totalCount: result.totalCount, nextPage: result.nextPage });
        } else {
          setItems(result);
          setSearchMeta(null);
        }
        setLoading(false);
      })
      .catch(err => { if (alive) { setError(err); setLoading(false); } });
    return () => { alive = false; };
  }, [feed, nearTier, includeReview, filters, sort, reload, searchTerm]);

  // Anonymous freshness: re-fetch the current feed/search without opening the auth-only SSE stream.
  useEffect(() => {
    if (!LIVE) return;
    let id = null;
    const stop = () => { if (id) { clearInterval(id); id = null; } };
    const refresh = () => {
      if (document.hidden) return;
      setReload(n => n + 1);
      setLastMatch("refreshed");
    };
    const start = () => {
      stop();
      if (!document.hidden) id = setInterval(refresh, LIVE_REFRESH_MS);
    };
    const onVisibility = () => {
      if (document.hidden) stop();
      else { refresh(); start(); }
    };
    start();
    document.addEventListener("visibilitychange", onVisibility);
    return () => { stop(); document.removeEventListener("visibilitychange", onVisibility); };
  }, [feed, nearTier, includeReview, filters, sort, searchTerm]);

  // Live gold spot (refreshes every 5 min, matching the API's cache window).
  useEffect(() => {
    if (!LIVE) return;
    let alive = true;
    const load = () => KS.getSpot().then(s => { if (alive) setSpot(s); }).catch(() => {});
    load();
    const id = setInterval(load, 5 * 60 * 1000);
    return () => { alive = false; clearInterval(id); };
  }, []);

  // Live alert rules (signed-in only).
  // Account-only alert rules are Coming soon for the public MVP.

  // Live SSE stream — prepend newly analyzed items.
  // The live push EventSource is auth-only, so anonymous freshness uses polling above.

  // ====================== MOCK DATA (original behavior) ======================
  // Simulated stream — only when signed in + on.
  useEffect(() => {
    if (LIVE || !liveOn || !signedIn) return;
    const tick = () => {
      const r = Math.random();
      const opts = r < 0.4 ? { ratio: +rand(0.55, 0.88).toFixed(2), ageMin: 0 }
                 : r < 0.7 ? { ratio: +rand(0.88, 1.12).toFixed(2), ageMin: 0 }
                 : r < 0.85 ? { needsWeight: true, ageMin: 0 }
                 : { ageMin: 0 };
      const newItem = makeItem(opts);
      setItems(prev => [newItem, ...prev].slice(0, 48));
      setJustInIds(prev => new Set([...prev, newItem.id]));
      setLastMatch("just now");
      setTimeout(() => setJustInIds(prev => { const n = new Set(prev); n.delete(newItem.id); return n; }), 5200);
    };
    const id = setInterval(tick, 5000);
    return () => clearInterval(id);
  }, [liveOn, signedIn]);

  // age bump (mock only — live ages come from real timestamps)
  useEffect(() => {
    if (LIVE) return;
    const id = setInterval(() => setItems(prev => prev.map(it => ({...it, ageMin: it.ageMin + 10/60}))), 10000);
    return () => clearInterval(id);
  }, []);

  const toggleAlert = (id) => {
    showComingSoon("Alert rules", "Rule creation and delivery history are account features staged for the next rollout.");
  };

  const onSave = (item) => {
    showComingSoon("Saved items", "Personal watchlists need accounts, so saving is gated while the public live feed remains anonymous.");
  };

  // Open detail — in live mode fetch the full item (seller, gallery, evidence).
  const openDetail = (item) => {
    if (!item) { setDetailItem(null); return; }
    setDetailItem(item);
    if (LIVE) {
      KS.getItem(item.id)
        .then(full => setDetailItem(d => (d && d.id === item.id ? { ...d, ...full } : d)))
        .catch(() => {});
    }
  };

  const openRuleBuilder = () => {
    showComingSoon("Alert rules", "Advanced rule creation, saved searches, and notification delivery are account features for a later phase.");
  };
  const openAdvancedFilter = () => {
    showComingSoon("Advanced filters", "The public MVP keeps the core feed filters live; saved searches and advanced criteria come next.");
  };

  // feed filtering
  const feedMatch = (it, key) => {
    switch (key) {
      case "offer": return it.ratio != null && it.ratio <= TIER.offer.max;
      case "deal":  return it.ratio != null && it.ratio <= TIER.deal.max;
      case "steal": return it.ratio != null && it.ratio <= TIER.steal.max;
      case "near": { const th = TIER[nearTier].max; return it.ratio != null && it.ratio > th && it.ratio <= th * 1.05; }
      case "needs": return it.ratio == null;
      case "all":   return true;
      default: return true;
    }
  };

  const counts = useMemo(() => {
    // Live deal feeds carry no total count — show the active feed's loaded
    // size and leave the rest as "—" (the tier tabs already handle null).
    if (LIVE) {
      const c = { [feed]: items.length };
      c.alerts = alerts.filter(a => a.enabled).length;
      return c;
    }
    const c = {};
    ["offer","deal","steal","near","needs","all"].forEach(k => { c[k] = items.filter(it => feedMatch(it, k)).length; });
    c.alerts = alerts.filter(a => a.enabled).length;
    return c;
  }, [items, nearTier, alerts, feed]);

  const visibleItems = useMemo(() => {
    let arr = searchTerm
      ? [...items]
      : items.filter(it => feedMatch(it, feed));
    if (!LIVE && searchTerm) {
      const q = searchTerm.toLowerCase();
      arr = arr.filter(it =>
        [it.title, it.itemType, it.seller, it.primaryMetal]
          .filter(Boolean)
          .some(v => String(v).toLowerCase().includes(q))
      );
    }
    // review-required excluded only when the feed toggle is off
    if (!searchTerm && !includeReview && ["offer","deal","steal","near"].includes(feed)) {
      arr = arr.filter(it => !it.requiresReview);
    }
    const activeKarats = filters.karat || [];
    const activeTypes = filters.type || [];
    if (activeKarats.length) {
      arr = arr.filter(it => activeKarats.includes(Number(it.karat)));
    }
    if (activeTypes.length) {
      arr = arr.filter(it => activeTypes.includes(it.itemType));
    }
    arr = [...arr];
    if (sort === "newest") arr.sort((a,b) => a.ageMin - b.ageMin);
    else if (sort === "price_to_melt_asc") arr.sort((a,b) => (a.ratio ?? 99) - (b.ratio ?? 99));
    else if (sort === "deal_score_desc") arr.sort((a,b) => (b.dealScore ?? -1) - (a.dealScore ?? -1));
    else if (sort === "listing_time_desc") arr.sort((a,b) => a.ageMin - b.ageMin);
    return arr;
  }, [items, feed, nearTier, includeReview, sort, searchTerm, filters]);

  const related = useMemo(() => {
    if (!detailItem) return [];
    return items.filter(it => it.id !== detailItem.id && it.itemType === detailItem.itemType).slice(0, 5);
  }, [detailItem, items]);

  const onNav = (k) => {
    if (k === "deals") { setActive(k); changeFeed("offer"); }
    else if (k === "browse") { setActive(k); changeFeed("all"); }
    else if (k === "alerts") showComingSoon("Alerts", "Alert rules, channels, saved searches, and alert history are account features coming after launch.");
    else if (k === "saved") showComingSoon("Saved items", "Saved items and saved searches need accounts, so they are paused for the anonymous MVP.");
    else if (k === "live") showComingSoon("Live push", "Anonymous visitors stay fresh through periodic re-fetching. The auth-only SSE stream returns after the account rollout.");
  };

  const showAlerts = tweaks.showAlertsRail;

  return (
    <div className="app">
      <Header
        liveOn={liveOn} active={active} setActive={onNav}
        onOpenTweaks={() => setTweaksOpen(v => !v)}
        signedIn={signedIn} onComingSoon={showComingSoon}
        spot={spot} counts={counts}
        searchTerm={searchTerm} onSearch={runSearch} onClearSearch={clearSearch}
        searching={loading && !!searchTerm}
      />
      <div style={{display:"flex", minHeight: 0, minWidth: 0, overflow: "hidden"}}>
        <FilterRail
          liveOn={liveOn} setLiveOn={setLiveOn}
          onOpenRuleBuilder={openAdvancedFilter}
          lastMatch={lastMatch} filters={filters} setFilters={setFilters}
          signedIn={signedIn} onComingSoon={showComingSoon}
          searchTerm={searchTerm} onSearch={runSearch} onClearSearch={clearSearch}
          onApplyFilters={applyFilters} onResetFilters={resetFilters}
        />

        <main className="feed-wrap">
          <Ticker/>
          <TierBar
            feed={feed} setFeed={changeFeed} counts={counts}
            includeReview={includeReview} setIncludeReview={setIncludeReview}
            nearTier={nearTier} setNearTier={setNearTier}
          />
          <FilterStrip
            onOpenRuleBuilder={openAdvancedFilter}
            signedIn={signedIn}
            filters={filters}
            setFilters={setFilters}
            searchTerm={searchTerm}
            onClearSearch={clearSearch}
            onClearFilters={clearFilters}
          />
          <FeedHeader feed={feed} sort={sort} setSort={setSort} count={visibleItems.length} liveOn={liveOn} view={view} setView={setView} searchTerm={searchTerm} searchMeta={searchMeta}/>
          <div className="feed-body">
            {error ? (
              <ErrorState error={error} onRetry={() => setReload(n => n + 1)}/>
            ) : loading ? (
              <LoadingGrid/>
            ) : visibleItems.length === 0 ? (
              <EmptyState feed={feed}/>
            ) : view === "grid" ? (
              <div className="grid">
                {visibleItems.map(it => (
                  <ItemCard key={it.id} item={it} justIn={justInIds.has(it.id)} onOpen={openDetail} signedIn={signedIn} onSave={onSave}/>
                ))}
              </div>
            ) : (
              <ListView items={visibleItems} justInIds={justInIds} onOpen={openDetail} signedIn={signedIn}/>
            )}
            {!loading && !error && visibleItems.length > 0 && (
              <div className="feed-end">
                <div className="rule"/>
                <span className="mono" style={{fontSize:11}}>{visibleItems.length} shown · refreshes automatically</span>
                <div className="rule"/>
              </div>
            )}
          </div>
        </main>

        {showAlerts && <RightRail alerts={alerts} onToggle={toggleAlert} signedIn={signedIn} onCreateRule={openRuleBuilder} onComingSoon={showComingSoon}/>}
      </div>

      {detailItem && <DetailDrawer item={detailItem} onClose={() => setDetailItem(null)} onOpen={openDetail} signedIn={signedIn} onSave={onSave} related={related} onComingSoon={showComingSoon}/>}
      <TweaksPanel visible={tweaksOpen} onClose={() => setTweaksOpen(false)} tweaks={tweaks} setTweaks={setTweaks}/>
      <ComingSoon
        open={!!comingSoon}
        feature={comingSoon && comingSoon.feature}
        detail={comingSoon && comingSoon.detail}
        onClose={closeComingSoon}
      />
    </div>
  );
}

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