// ─── Grid Search: Model Mode UI ───
window.MC = window.MC || {};

MC.GridSearch = function GridSearch({ coin, liveData, advanced, isUsd, poolFee, excludeCapex, priceGrowth, onOpenCalculator, initialFilters, onFiltersChange }) {
  var useState = React.useState;
  var useMemo = React.useMemo;
  var useEffect = React.useEffect;
  var useRef = React.useRef;
  var mobile = MC.useIsMobile();

  var livePrice = (liveData && liveData[coin.id] && liveData[coin.id].price) || null;
  var defaultPrice = livePrice || coin.defaultPrice;
  var netHash = (liveData && liveData[coin.id] && liveData[coin.id].netHash) || coin.defaultNetworkHash;

  var [projMonths, setProjMonths] = useState((initialFilters && initialFilters.projMonths) || 24);
  var [coinPrice, setCoinPrice] = useState(
    (initialFilters && initialFilters.coinPrice) || defaultPrice
  );
  var [filters, setFilters] = useState({
    hashGrowth: (initialFilters && initialFilters.hashGrowth) || "Base",
    minerCount: (initialFilters && initialFilters.minerCount) || "150",
    maxPayback: (initialFilters && initialFilters.maxPayback) || "24",
  });
  var [sortCol, setSortCol] = useState("roi");
  var [sortDir, setSortDir] = useState("desc");
  var [showAll, setShowAll] = useState(false);

  var cf = function(zarVal) { return MC.currency.fmtFull(zarVal, isUsd, advanced.zarUsd); };

  // Notify parent of filter/control changes for permalink
  useEffect(function() {
    if (onFiltersChange) {
      onFiltersChange({
        projMonths: projMonths,
        coinPrice: coinPrice,
        hashGrowth: filters.hashGrowth,
        minerCount: filters.minerCount,
        maxPayback: filters.maxPayback,
      });
    }
  }, [projMonths, coinPrice, filters.hashGrowth, filters.minerCount, filters.maxPayback]);

  // Update price when live data arrives (only once per coin)
  var hasSetLivePrice = useRef({});
  useEffect(function() {
    if (livePrice && !hasSetLivePrice.current[coin.id]) {
      hasSetLivePrice.current[coin.id] = true;
      setCoinPrice(livePrice);
    }
  }, [livePrice, coin.id]);

  // Reset filters when coin changes
  var prevCoinRef = useRef(coin.id);
  useEffect(function() {
    if (prevCoinRef.current !== coin.id) {
      prevCoinRef.current = coin.id;
      var newPrice = (liveData && liveData[coin.id] && liveData[coin.id].price) || coin.defaultPrice;
      setCoinPrice(newPrice);
      setFilters({ hashGrowth: "Base", minerCount: "150", maxPayback: "24" });
      setSortCol("roi");
      setSortDir("desc");
      setShowAll(false);
    }
  }, [coin.id]);

  // Reactive search — re-runs automatically when inputs change
  var searchResult = useMemo(function() {
    return MC.gridSearch.run({
      coin: coin,
      projMonths: projMonths,
      poolFee: poolFee / 100,
      advanced: advanced,
      netHash: netHash,
      coinPrice: coinPrice,
      excludeCapex: excludeCapex,
      priceGrowth: priceGrowth,
    });
  }, [coin.id, projMonths, poolFee, advanced, netHash, coinPrice, excludeCapex, priceGrowth]);

  var results = searchResult.results;
  var meta = searchResult.meta;

  // Filter results (hash growth + miner count only — all rows always visible)
  var filtered = useMemo(function() {
    if (!results) return [];
    return results.filter(function(r) {
      if (filters.hashGrowth !== "all" && r.hashGrowthLabel !== filters.hashGrowth) return false;
      if (filters.minerCount !== "all" && r.minerCount !== Number(filters.minerCount)) return false;
      return true;
    });
  }, [results, filters.hashGrowth, filters.minerCount]);

  // Sort filtered results
  var sorted = useMemo(function() {
    var arr = filtered.slice();
    arr.sort(function(a, b) {
      var av = a[sortCol];
      var bv = b[sortCol];
      if (sortCol === "paybackMonth" || sortCol === "effectiveCostPerCoin") {
        if (av === null) av = 9999;
        if (bv === null) bv = 9999;
      }
      if (av === null || av === undefined) av = -Infinity;
      if (bv === null || bv === undefined) bv = -Infinity;
      if (sortDir === "asc") return av > bv ? 1 : av < bv ? -1 : 0;
      return av < bv ? 1 : av > bv ? -1 : 0;
    });
    return arr;
  }, [filtered, sortCol, sortDir]);

  // Top picks from filtered (respects max payback)
  var maxPb = filters.maxPayback !== "" ? parseInt(filters.maxPayback, 10) : null;
  var picks = useMemo(function() {
    if (filtered.length === 0) return null;
    var viable = maxPb ? filtered.filter(function(r) {
      return r.paybackMonth !== null && r.paybackMonth <= maxPb;
    }) : filtered.filter(function(r) { return r.paybackMonth !== null; });
    if (viable.length === 0) return { bestROI: null, fastestPayback: null, lowestCost: null, bestNet: null, viableCount: 0 };
    var bestROI = null, fastestPayback = null, lowestCost = null, bestNet = null;
    for (var i = 0; i < viable.length; i++) {
      var r = viable[i];
      if (bestROI === null || r.roi > bestROI.roi) bestROI = r;
      if (fastestPayback === null || r.paybackMonth < fastestPayback.paybackMonth) fastestPayback = r;
      if (lowestCost === null || r.totalCostAtEnd < lowestCost.totalCostAtEnd) lowestCost = r;
      if (bestNet === null || r.netAtEnd > bestNet.netAtEnd) bestNet = r;
    }
    return { bestROI: bestROI, fastestPayback: fastestPayback, lowestCost: lowestCost, bestNet: bestNet, viableCount: viable.length };
  }, [filtered, maxPb]);

  var displayRows = showAll ? sorted : sorted.slice(0, 100);

  // Miner comparison: best scenario per miner, sorted by cost-per-coin
  var minerSummary = useMemo(function() {
    if (filtered.length === 0) return [];
    var byMiner = {};
    for (var i = 0; i < filtered.length; i++) {
      var r = filtered[i];
      if (!byMiner[r.minerKey] || (r.effectiveCostPerCoin !== null &&
          (byMiner[r.minerKey].effectiveCostPerCoin === null ||
           r.effectiveCostPerCoin < byMiner[r.minerKey].effectiveCostPerCoin))) {
        byMiner[r.minerKey] = r;
      }
    }
    var arr = Object.keys(byMiner).map(function(k) { return byMiner[k]; });
    arr.sort(function(a, b) {
      var ac = a.effectiveCostPerCoin === null ? Infinity : a.effectiveCostPerCoin;
      var bc = b.effectiveCostPerCoin === null ? Infinity : b.effectiveCostPerCoin;
      return ac - bc;
    });
    return arr;
  }, [filtered]);

  var [showDetailedTable, setShowDetailedTable] = useState(false);

  // Check if any results are viable after applying current filters
  function hasViableFiltered(searchResult) {
    var rows = searchResult.results;
    for (var i = 0; i < rows.length; i++) {
      var r = rows[i];
      if (filters.hashGrowth !== "all" && r.hashGrowthLabel !== filters.hashGrowth) continue;
      if (filters.minerCount !== "all" && r.minerCount !== Number(filters.minerCount)) continue;
      if (r.paybackMonth !== null) return true;
    }
    return false;
  }

  // Find minimum viable price via binary search
  function findMinViablePrice() {
    var lo = coin.priceRange.min;
    var hi = coin.priceRange.max;
    var step = coin.priceRange.step;
    var found = null;

    for (var i = 0; i < 50; i++) {
      var mid = Math.round((lo + hi) / 2 / step) * step;
      if (mid <= lo) mid = lo + step;
      if (mid >= hi) mid = hi - step;

      var result = MC.gridSearch.run({
        coin: coin, projMonths: projMonths, poolFee: poolFee / 100,
        advanced: advanced, netHash: netHash, coinPrice: mid, excludeCapex: excludeCapex, priceGrowth: priceGrowth,
      });
      if (hasViableFiltered(result)) {
        found = mid;
        hi = mid;
      } else {
        lo = mid;
      }
      if (hi - lo <= step) break;
    }
    // Check the boundary
    if (found === null) {
      var boundResult = MC.gridSearch.run({
        coin: coin, projMonths: projMonths, poolFee: poolFee / 100,
        advanced: advanced, netHash: netHash, coinPrice: hi, excludeCapex: excludeCapex, priceGrowth: priceGrowth,
      });
      if (hasViableFiltered(boundResult)) found = hi;
    }
    if (found !== null) setCoinPrice(found);
  }

  // Find minimum viable months via linear search
  function findMinViableMonths() {
    for (var m = 6; m <= 60; m++) {
      var result = MC.gridSearch.run({
        coin: coin, projMonths: m, poolFee: poolFee / 100,
        advanced: advanced, netHash: netHash, coinPrice: coinPrice, excludeCapex: excludeCapex, priceGrowth: priceGrowth,
      });
      if (hasViableFiltered(result)) {
        setProjMonths(m);
        return;
      }
    }
  }

  // Reset all model controls to defaults
  function handleReset() {
    var resetPrice = (liveData && liveData[coin.id] && liveData[coin.id].price) || coin.defaultPrice;
    setCoinPrice(resetPrice);
    setProjMonths(24);
    setFilters({ hashGrowth: "Base", minerCount: "150", maxPayback: "24" });
    setSortCol("roi");
    setSortDir("desc");
    setShowAll(false);
  }

  function handleSort(col) {
    if (sortCol === col) {
      setSortDir(sortDir === "asc" ? "desc" : "asc");
    } else {
      setSortCol(col);
      setSortDir(col === "paybackMonth" ? "asc" : "desc");
    }
  }

  function sortIndicator(col) {
    if (sortCol !== col) return "";
    return sortDir === "asc" ? " \u25B2" : " \u25BC";
  }

  var selectStyle = {
    padding: "6px 10px", background: "#1a1a16", border: "1px solid #3a3a30",
    borderRadius: 6, color: "#e8dcc8", fontSize: 11, fontFamily: "var(--mono)", cursor: "pointer",
  };
  var inputStyle = {
    padding: "6px 8px", background: "#1a1a16", border: "1px solid #3a3a30",
    borderRadius: 6, color: "#e8dcc8", fontSize: 11, fontFamily: "var(--mono)", textAlign: "center",
  };
  var actionBtnStyle = {
    padding: "4px 10px", background: "transparent", border: "1px solid #3a3a30",
    borderRadius: 5, cursor: "pointer", color: "#8a8a7a",
    fontSize: 10, fontFamily: "var(--mono)", transition: "all 0.15s", whiteSpace: "nowrap",
  };

  var baseGrowth = coin.defaultHashGrowth;
  var growthScenarios = [
    { label: "Optimistic", desc: "Slow network growth (" + (baseGrowth * 0.5).toFixed(1) + "%/mo)" },
    { label: "Base", desc: "Default growth (" + baseGrowth.toFixed(1) + "%/mo)" },
    { label: "Pessimistic", desc: "Fast network growth (" + (baseGrowth * 1.5).toFixed(1) + "%/mo)" },
  ];
  var minerCounts = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160];

  function handleRowClick(r) {
    if (onOpenCalculator) {
      onOpenCalculator({
        minerKey: r.minerKey,
        minerCount: r.minerCount,
        coinPrice: r.coinPrice,
        hashGrowthPct: r.hashGrowthPct,
        projMonths: projMonths,
      });
    }
  }

  return (
    <div style={{ marginTop: 16 }}>

      {/* Controls */}
      <div id="sec-gs-controls" />
      <MC.SectionHeader icon={"\u25C8"}>Grid Search Controls</MC.SectionHeader>
      <div style={{
        display: "flex", flexWrap: "wrap", gap: 12, alignItems: "flex-end",
        padding: "16px 20px", background: "#1a1a16", border: "1px solid #2a2a24", borderRadius: 10, marginBottom: 16,
      }}>
        <div style={{ flex: "1 1 200px", minWidth: 180 }}>
          <MC.Slider label="Projection Months" value={projMonths} onChange={setProjMonths}
            min={6} max={60} step={1} format={function(v) { return v + " mo"; }} color={coin.color} />
          <button onClick={findMinViableMonths} style={Object.assign({}, actionBtnStyle, { marginTop: 6 })}>
            Min viable months
          </button>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 10, alignSelf: "center" }}>
          <span style={{ fontSize: 11, color: "#8a8a7a", fontFamily: "var(--mono)" }}>
            {meta.totalCombos.toLocaleString()} combos {"\u00B7"} {meta.viableCount.toLocaleString()} viable
          </span>
          <button onClick={handleReset} style={actionBtnStyle}>Reset</button>
        </div>
      </div>

          {/* Filters */}
          <div style={{
            padding: "16px 20px", background: "#1a1a16", border: "1px solid #2a2a24", borderRadius: 10, marginBottom: 16,
          }}>
            <div style={{ maxWidth: 400, marginBottom: 8 }}>
              <MC.Slider
                label={coin.ticker + " Price (USD)" + (livePrice ? "  \u00B7  live: " + MC.currency.fmtPrice(livePrice, coin) : "")}
                value={coinPrice}
                onChange={setCoinPrice}
                min={coin.priceRange.min} max={coin.priceRange.max} step={coin.priceRange.step}
                format={function(v) { return MC.currency.fmtPrice(v, coin); }}
                color={coin.color}
              />
              <button onClick={findMinViablePrice} style={Object.assign({}, actionBtnStyle, { marginTop: 6 })}>
                Min viable price
              </button>
            </div>
            <div style={{
              display: "flex", flexWrap: "wrap", gap: 12, alignItems: "center",
            }}>
              <select value={filters.hashGrowth} onChange={function(e) { setFilters(Object.assign({}, filters, { hashGrowth: e.target.value })); }} style={selectStyle}>
                <option value="all">All hash growth</option>
                {growthScenarios.map(function(s) {
                  return <option key={s.label} value={s.label}>{s.desc}</option>;
                })}
              </select>
              <select value={filters.minerCount} onChange={function(e) { setFilters(Object.assign({}, filters, { minerCount: e.target.value })); }} style={selectStyle}>
                <option value="all">All counts</option>
                {minerCounts.map(function(c) {
                  return <option key={c} value={c}>{c} miners</option>;
                })}
              </select>
              <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
                <span style={{ fontSize: 10, color: "#6a6a5a", fontFamily: "var(--mono)" }}>Max payback:</span>
                <input type="number" value={filters.maxPayback}
                  onChange={function(e) { setFilters(Object.assign({}, filters, { maxPayback: e.target.value })); }}
                  placeholder="mo" min={1} max={60}
                  style={Object.assign({}, inputStyle, { width: 56 })}
                />
              </div>
              <span style={{ fontSize: 10, color: "#6a6a5a", fontFamily: "var(--mono)" }}>
                {filtered.length.toLocaleString()} results
              </span>
            </div>
          </div>

          {/* No viable banner */}
          {picks && !picks.fastestPayback && (
            <div style={{
              padding: "14px 20px", marginBottom: 16,
              background: "rgba(212, 100, 74, 0.1)", border: "1px solid rgba(212, 100, 74, 0.3)",
              borderRadius: 10, fontSize: 12, color: "#d4644a",
              fontFamily: "var(--mono)", fontWeight: 600, textAlign: "center",
            }}>
              {maxPb
                ? "No configurations achieve payback within " + maxPb + " months at " + MC.currency.fmtPrice(coinPrice, coin) + "."
                : "No configurations achieve payback within " + projMonths + " months at " + MC.currency.fmtPrice(coinPrice, coin) + "."
              }
            </div>
          )}

          {/* Top Picks */}
          {picks && picks.fastestPayback && (
            <React.Fragment>
              <div id="sec-gs-picks" />
              <MC.SectionHeader icon={"\u2605"}>Top Picks</MC.SectionHeader>
              <div style={{ display: "flex", flexWrap: "wrap", gap: mobile ? 8 : 12, marginBottom: 16 }}>
                {picks.bestROI && (
                  <MC.MetricCard label="Best ROI" small
                    value={picks.bestROI.roi.toFixed(0) + "%"}
                    sub={picks.bestROI.minerName + " \u00D7" + picks.bestROI.minerCount}
                    accent="#6abf5e" />
                )}
                {picks.fastestPayback && (
                  <MC.MetricCard label="Fastest Payback" small
                    value={picks.fastestPayback.paybackMonth + " mo"}
                    sub={picks.fastestPayback.minerName + " \u00D7" + picks.fastestPayback.minerCount}
                    accent="#8a8aff" />
                )}
                {picks.lowestCost && (
                  <MC.MetricCard label="Total Cost" small
                    value={cf(picks.lowestCost.totalCostAtEnd)}
                    sub={picks.lowestCost.minerName + " \u00D7" + picks.lowestCost.minerCount}
                    accent="#c9a84c" />
                )}
                {picks.bestNet && (
                  <MC.MetricCard label="Best Net Position" small
                    value={cf(picks.bestNet.netAtEnd)}
                    sub={picks.bestNet.minerName + " \u00D7" + picks.bestNet.minerCount}
                    accent={picks.bestNet.netAtEnd >= 0 ? "#6abf5e" : "#d4644a"} />
                )}
              </div>
            </React.Fragment>
          )}

          {/* Miner Comparison Cards */}
          {minerSummary.length > 0 && (
            <React.Fragment>
              <div id="sec-gs-comparison" />
              <MC.SectionHeader icon={"\u25C6"}>Miner Comparison</MC.SectionHeader>
              <div style={{ display: "grid", gridTemplateColumns: mobile ? "1fr" : "repeat(auto-fill, minmax(280px, 1fr))", gap: 12, marginBottom: 16 }}>
                {minerSummary.map(function(r, i) {
                  var isRecommended = i === 0 && r.effectiveCostPerCoin !== null;
                  return (
                    <div key={r.minerKey} onClick={function() { handleRowClick(r); }}
                      style={{
                        background: "linear-gradient(135deg, #1e1e1a, #252520)",
                        border: isRecommended ? "2px solid " + coin.color : "1px solid #3a3a30",
                        borderRadius: 10, padding: mobile ? "14px" : "18px 20px", cursor: "pointer",
                        position: "relative", transition: "border-color 0.15s",
                      }}>
                      {isRecommended && (
                        <div style={{ position: "absolute", top: -10, right: 16, background: coin.color, color: "#141410", padding: "2px 10px", borderRadius: 10, fontSize: 9, fontWeight: 700, fontFamily: "var(--mono)", textTransform: "uppercase", letterSpacing: 1 }}>
                          Recommended
                        </div>
                      )}
                      <div style={{ fontSize: 14, fontWeight: 700, color: "#e8dcc8", marginBottom: 12 }}>{r.minerName}</div>
                      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
                        <div>
                          <div style={{ fontSize: 9, textTransform: "uppercase", letterSpacing: 1, color: "#6a6a5a", fontFamily: "var(--mono)" }}>Cost/Coin</div>
                          <div style={{ fontSize: 16, fontWeight: 700, color: "#c9a84c", fontFamily: "var(--sans)" }}>
                            {r.effectiveCostPerCoin !== null ? cf(r.effectiveCostPerCoin) : "\u2014"}
                          </div>
                        </div>
                        <div>
                          <div style={{ fontSize: 9, textTransform: "uppercase", letterSpacing: 1, color: "#6a6a5a", fontFamily: "var(--mono)" }}>Payback</div>
                          <div style={{ fontSize: 16, fontWeight: 700, color: r.paybackMonth ? "#6abf5e" : "#d4644a", fontFamily: "var(--sans)" }}>
                            {r.paybackMonth ? r.paybackMonth + " mo" : "Never"}
                          </div>
                        </div>
                        <div>
                          <div style={{ fontSize: 9, textTransform: "uppercase", letterSpacing: 1, color: "#6a6a5a", fontFamily: "var(--mono)" }}>{coin.ticker} Mined</div>
                          <div style={{ fontSize: 14, fontWeight: 600, color: coin.color, fontFamily: "var(--sans)" }}>
                            {MC.currency.fmtCoinAmount(r.totalCoinAtEnd)}
                          </div>
                        </div>
                        <div>
                          <div style={{ fontSize: 9, textTransform: "uppercase", letterSpacing: 1, color: "#6a6a5a", fontFamily: "var(--mono)" }}>Mo.1 Margin</div>
                          <div style={{ fontSize: 14, fontWeight: 600, color: r.m1Margin >= 0 ? "#6abf5e" : "#d4644a", fontFamily: "var(--sans)" }}>
                            {cf(r.m1Margin)}
                          </div>
                        </div>
                      </div>
                      <div style={{ fontSize: 10, color: "#6a6a5a", fontFamily: "var(--mono)", marginTop: 8 }}>
                        {r.minerCount + " miners \u00B7 " + r.hashGrowthLabel + " \u00B7 Net: " + cf(r.netAtEnd)}
                      </div>
                    </div>
                  );
                })}
              </div>
            </React.Fragment>
          )}

          {/* Collapsible Detailed Results */}
          <div style={{ marginBottom: 16 }}>
            <button onClick={function() { setShowDetailedTable(!showDetailedTable); }} style={{
              width: "100%", padding: "10px 20px", background: "#1a1a16", border: "1px solid #2a2a24",
              borderRadius: 10, cursor: "pointer", color: "#8a8a7a", fontSize: 11,
              fontFamily: "var(--mono)", display: "flex", justifyContent: "space-between", alignItems: "center",
            }}>
              <span>{"\u25A6"} Detailed Results ({sorted.length.toLocaleString()} rows)</span>
              <span style={{ transform: showDetailedTable ? "rotate(180deg)" : "rotate(0)", transition: "transform 0.2s" }}>{"\u25BC"}</span>
            </button>
            {showDetailedTable && (
              <React.Fragment>
                <div id="sec-gs-results" style={{ marginTop: 12 }} />
                <div style={{
                  overflowX: "auto", WebkitOverflowScrolling: "touch",
                  border: "1px solid #2a2a24", borderRadius: 10, background: "#1a1a16",
                }}>
                  <table className="grid-search-table" style={{
                    width: "100%", borderCollapse: "collapse", fontSize: mobile ? 10 : 11,
                    fontFamily: "var(--mono)", whiteSpace: "nowrap",
                  }}>
                    <thead>
                      <tr style={{ borderBottom: "1px solid #2a2a24" }}>
                        {[
                          { col: "minerName", label: "Miner" },
                          { col: "minerCount", label: "Count" },
                          { col: "hashGrowthLabel", label: "Growth" },
                          { col: "totalUpfront", label: "Upfront" },
                          { col: "paybackMonth", label: "Payback" },
                          { col: "netAtEnd", label: "Net Position" },
                          { col: "roi", label: "ROI%" },
                          { col: "totalCostAtEnd", label: "Total Cost" },
                          { col: "effectiveCostPerCoin", label: "Cost/Coin" },
                          { col: "beatsAlt", label: "vs APY" },
                        ].map(function(h) {
                          return (
                            <th key={h.col} onClick={function() { handleSort(h.col); }}
                              className="grid-search-th"
                              style={{
                                padding: mobile ? "8px 6px" : "10px 12px", textAlign: "right",
                                color: sortCol === h.col ? coin.color : "#6a6a5a",
                                fontWeight: 600, fontSize: 10, textTransform: "uppercase", letterSpacing: 0.5,
                                cursor: "pointer", userSelect: "none", position: "sticky", top: 0,
                                background: "#1a1a16",
                              }}>
                              {h.label}{sortIndicator(h.col)}
                            </th>
                          );
                        })}
                      </tr>
                    </thead>
                    <tbody>
                      {displayRows.map(function(r, i) {
                        var positive = r.netAtEnd >= 0;
                        var rowBg = positive ? "rgba(106, 191, 94, 0.04)" : "rgba(212, 100, 74, 0.04)";
                        return (
                          <tr key={i} className="grid-search-row" onClick={function() { handleRowClick(r); }}
                            title="Click to open in Calculator"
                            style={{ borderBottom: "1px solid #222218", background: rowBg, cursor: "pointer" }}>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "left", color: "#e8dcc8" }}>{r.minerName}</td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: "#8a8a7a" }}>{r.minerCount}</td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: "#8a8a7a" }}>{r.hashGrowthLabel}</td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: "#8a8a7a" }}>{cf(r.totalUpfront)}</td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: r.paybackMonth !== null ? "#6abf5e" : "#d4644a" }}>
                              {r.paybackMonth !== null ? r.paybackMonth + " mo" : "Never"}
                            </td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: positive ? "#6abf5e" : "#d4644a", fontWeight: 600 }}>
                              {cf(r.netAtEnd)}
                            </td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: r.roi >= 0 ? "#6abf5e" : "#d4644a" }}>
                              {r.roi.toFixed(0)}%
                            </td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: "#c9a84c" }}>
                              {cf(r.totalCostAtEnd)}
                            </td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "right", color: "#c9a84c" }}>
                              {r.effectiveCostPerCoin !== null ? cf(r.effectiveCostPerCoin) : "\u2014"}
                            </td>
                            <td style={{ padding: mobile ? "6px" : "8px 12px", textAlign: "center", color: r.beatsAlt ? "#6abf5e" : "#d4644a" }}>
                              {r.beatsAlt ? "\u2713" : "\u2717"}
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
                {!showAll && sorted.length > 100 && (
                  <div style={{ textAlign: "center", marginTop: 12 }}>
                    <button onClick={function() { setShowAll(true); }} style={{
                      padding: "8px 20px", background: "transparent", border: "1px solid #3a3a30",
                      borderRadius: 6, cursor: "pointer", color: "#8a8a7a",
                      fontSize: 11, fontFamily: "var(--mono)", transition: "all 0.15s",
                    }}>
                      Show all {sorted.length.toLocaleString()} results
                    </button>
                  </div>
                )}
              </React.Fragment>
            )}
          </div>
    </div>
  );
};
