// Represents: GET /reports/bonus-retention/ · BonusRetentionReportController::index → admin.reports.bonus_retention.index
/* Traced Aug 2026 (architecture item 2). A real host report with its own
   controller and route; it predated ISYSTEM_REFERENCE. */
/* Bonus Retention report.

   Measures what happens after a player converts a bonus into real balance:
   whether they keep depositing and playing, and whether the promotion paid
   off. Starts from every player who ACTIVATED the selected promotion and
   tracks: activation → redeemed → first withdrawal → deposits/play after,
   observed up to day 60 after the promo ends.

   The KPI box reflects the WHOLE campaign (stored aggregate figures, not
   affected by the filters). The player table is a filterable list of the
   individual journeys behind those KPIs. */

const { useState: useStateBR, useMemo: useMemoBR, useEffect: useEffectBR } = React;

/* it-IT money/number formatting → "99.986,21", "1,20%", "5,27X" */
const brFmt = (n) => Number(n || 0).toLocaleString("it-IT", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const brInt = (n) => Number(n || 0).toLocaleString("it-IT");
const BR_EUR_RATE = 1750; // 1 EUR ≈ 1.750 NGN (demo)

const BR_SKINS = [{ id: "betcamp", name: "Betcamp", currency: "NGN" }];

/* Promotions — each carries its campaign-level KPI aggregates. The default
   promo reproduces the reference screenshot exactly; the others use the
   spec's worked example and a third plausible campaign. */
const BR_PROMOS = [
  { id: "wb300sport", skin: "betcamp", name: "300% Welcome Bonus Sport", start: "2026-05-24", end: "2026-08-24",
    base: { activated: 166, redeemed: 19, retainedPlayers: 2, totalRedeposit: 53466.66, totalGGR: 99986.21, totalCost: 527400, budget: 1000000 } },
  { id: "wb100", skin: "betcamp", name: "100% Welcome Bonus", start: "2026-04-01", end: "2026-06-30",
    base: { activated: 200, redeemed: 120, retainedPlayers: 66, totalRedeposit: 19800, totalGGR: 25000, totalCost: 12000, budget: 50000 } },
  { id: "reload50", skin: "betcamp", name: "50% Reload Bonus Casino", start: "2026-05-01", end: "2026-07-15",
    base: { activated: 312, redeemed: 140, retainedPlayers: 58, totalRedeposit: 14790, totalGGR: 88000, totalCost: 71000, budget: 150000 } },
];

/* Recognisable rows pinned to the top of the default promo so the table
   matches the reference screenshot. */
const BR_FEATURED = {
  wb300sport: [
    { id: "9091818538" },
    { id: "8106030902" },
    { id: "9097686307" },
    { id: "7057617554", ggrSport: 300 },
    { id: "9030828567" },
    { id: "9045823383", withdrawalDate: "2026-06-03 14:50:15", deposits: { count: 3, total: 106100 } },
    { id: "9120458979" },
  ],
};

/* Deterministic PRNG so a promo always renders the same dataset. */
const brHash = (s) => { let h = 2166136261 >>> 0; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; };
const brRng = pbRng;   // was a local copy of mulberry32 — see pbRng in src/data.jsx
const brPad = (n) => String(n).padStart(2, "0");
const brTs = (ts) => { const d = new Date(ts); return `${d.getUTCFullYear()}-${brPad(d.getUTCMonth() + 1)}-${brPad(d.getUTCDate())} ${brPad(d.getUTCHours())}:${brPad(d.getUTCMinutes())}:${brPad(d.getUTCSeconds())}`; };

const brGenerate = (promo) => {
  const rng = brRng(brHash(promo.id));
  const b = promo.base;
  const startTs = Date.parse(promo.start + "T00:00:00Z");
  const endTs = Date.parse(promo.end + "T23:59:59Z");
  const span = Math.max(1, endTs - startTs);
  const N = b.activated;

  const rows = [];
  for (let i = 0; i < N; i++) {
    rows.push({
      id: String(7000000000 + Math.floor(rng() * 2999999999)),
      activation: startTs + Math.floor(rng() * span),
      redeemDate: null, redeemedAmount: 0,
      withdrawalDate: null, deposits: { count: 0, total: 0 },
      ggrCasino: 0, ggrSport: 0, status: "inactive",
    });
  }
  const pick = (count) => { const s = new Set(); const c = Math.min(count, N); while (s.size < c) s.add(Math.floor(rng() * N)); return [...s]; };

  // Redeemers — feed Total Promotion Cost.
  pick(b.redeemed).forEach(i => {
    const r = rows[i];
    r.redeemedAmount = Math.round((b.totalCost / b.redeemed) * (0.6 + rng() * 0.8));
    r.redeemDate = r.activation + Math.floor((1 + rng() * 10) * 86400000);
  });
  // Retained — deposited again after first withdrawal (these are "active").
  pick(b.retainedPlayers).forEach(i => {
    const r = rows[i];
    r.withdrawalDate = r.activation + Math.floor((3 + rng() * 20) * 86400000);
    r.deposits = { count: 1 + Math.floor(rng() * 4), total: Math.round((b.totalRedeposit / b.retainedPlayers) * (0.6 + rng() * 0.8)) };
    r.status = "active";
  });
  // GGR spread across a slice of players.
  const ggrN = Math.max(b.retainedPlayers, Math.round(N * 0.12));
  pick(ggrN).forEach(i => {
    const r = rows[i];
    const tot = (b.totalGGR / ggrN) * (0.4 + rng() * 1.4);
    const sport = rng();
    r.ggrCasino = Math.round(tot * (1 - sport));
    r.ggrSport = Math.round(tot * sport);
    if (rng() < 0.5) r.status = "active";
  });

  // Pin featured rows to the top (default promo) for screenshot fidelity.
  (BR_FEATURED[promo.id] || []).forEach((f, k) => {
    const r = rows[k];
    r.id = f.id;
    r.status = "inactive";
    r.redeemDate = null; r.redeemedAmount = 0;
    r.withdrawalDate = f.withdrawalDate ? Date.parse(f.withdrawalDate.replace(" ", "T") + "Z") : null;
    r.deposits = f.deposits || { count: 0, total: 0 };
    r.ggrCasino = 0; r.ggrSport = f.ggrSport || 0;
  });
  return rows;
};

const BR_DEFAULT_FILTERS = (promo) => ({
  start: promo.start, startTime: "00:00:00", end: promo.end, endTime: "23:59:59",
  minConverted: "", status: "all", minDeposit: "",
});

const BonusRetention = ({ brand }) => {
  window.useLocale && window.useLocale();
  const [skin, setSkin] = useStateBR("betcamp");
  const [promoId, setPromoId] = useStateBR(BR_PROMOS[0].id);
  const promo = BR_PROMOS.find(p => p.id === promoId) || BR_PROMOS[0];

  const [convertEUR, setConvertEUR] = useStateBR(false);
  const [draft, setDraft] = useStateBR(() => BR_DEFAULT_FILTERS(BR_PROMOS[0]));
  const [applied, setApplied] = useStateBR(() => BR_DEFAULT_FILTERS(BR_PROMOS[0]));

  // Re-baseline filters whenever the promotion changes.
  useEffectBR(() => {
    const d = BR_DEFAULT_FILTERS(promo);
    setDraft(d); setApplied(d);
  }, [promoId]);

  const rows = useMemoBR(() => (skin ? brGenerate(promo) : []), [promoId, skin]);

  const cur = convertEUR ? "EUR" : (BR_SKINS.find(s => s.id === skin)?.currency || "NGN");
  const money = (n) => `${brFmt(convertEUR ? n / BR_EUR_RATE : n)} ${cur}`;

  // Apply the (committed) filters to the table only — KPIs stay campaign-wide.
  const filtered = useMemoBR(() => {
    const fromTs = applied.start ? Date.parse(`${applied.start}T${applied.startTime || "00:00:00"}Z`) : -Infinity;
    const toTs = applied.end ? Date.parse(`${applied.end}T${applied.endTime || "23:59:59"}Z`) : Infinity;
    const minConv = parseFloat(applied.minConverted);
    const minDep = parseInt(applied.minDeposit, 10);
    return rows.filter(r => {
      if (applied.status !== "all" && r.status !== applied.status) return false;
      if (r.activation < fromTs || r.activation > toTs) return false;
      if (!isNaN(minConv) && r.redeemedAmount < minConv) return false;
      if (!isNaN(minDep) && r.deposits.count < minDep) return false;
      return true;
    });
  }, [rows, applied, convertEUR]);

  const b = promo.base;
  const retentionPct = (b.activated ? (b.retainedPlayers / b.activated) * 100 : 0);
  const avgRedeposit = (b.retainedPlayers ? b.totalRedeposit / b.retainedPlayers : 0);
  const costGGR = (b.totalGGR ? b.totalCost / b.totalGGR : 0);
  const budgetReachedPct = (b.budget ? (b.totalCost / b.budget) * 100 : 0);

  const setD = (patch) => setDraft(d => ({ ...d, ...patch }));
  const search = () => setApplied(draft);
  const reset = () => { const d = BR_DEFAULT_FILTERS(promo); setDraft(d); setApplied(d); setConvertEUR(false); };

  const exportCSV = () => {
    if (!window.PAYBO) return;
    const stamp = new Date().toISOString().slice(0, 10);
    window.PAYBO.downloadCSV(`bonus-retention-${promo.id}-${stamp}.csv`, filtered, [
      { key: "id", label: "player_id" },
      { key: "promo", label: "promotion", get: () => `${promo.name} (${promo.start}/${promo.end})` },
      { key: "redeemDate", label: "redeem_date", get: r => r.redeemDate ? brTs(r.redeemDate) : "" },
      { key: "redeemedAmount", label: "redeemed_amount", get: r => convertEUR ? (r.redeemedAmount / BR_EUR_RATE).toFixed(2) : r.redeemedAmount },
      { key: "withdrawalDate", label: "first_withdrawal_date", get: r => r.withdrawalDate ? brTs(r.withdrawalDate) : "" },
      { key: "depositsCount", label: "deposits_post_withdrawal_count", get: r => r.deposits.count },
      { key: "depositsTotal", label: "deposits_post_withdrawal_total", get: r => convertEUR ? (r.deposits.total / BR_EUR_RATE).toFixed(2) : r.deposits.total },
      { key: "ggrCasino", label: "ggr_casino", get: r => convertEUR ? (r.ggrCasino / BR_EUR_RATE).toFixed(2) : r.ggrCasino },
      { key: "ggrSport", label: "ggr_sport", get: r => convertEUR ? (r.ggrSport / BR_EUR_RATE).toFixed(2) : r.ggrSport },
      { key: "status", label: "player_status" },
      { key: "currency", label: "currency", get: () => cur },
    ]);
    window.PAYBO.emitToast && window.PAYBO.emitToast({
      id: `export-br-${Date.now()}`, tx_id: "Bonus Retention export",
      amount: 0, currency: "CSV", player: `${filtered.length} rows`,
      reason: "Export queued · recorded in the Export Requests table.",
    });
  };

  const openPlayer = (r) => {
    window.PAYBO?.emitToast && window.PAYBO.emitToast({
      id: `br-open-${r.id}-${Date.now()}`, tx_id: `Player ${r.id}`,
      amount: 0, currency: "HOST", player: "Player profile",
      reason: "Opening the player profile (host).",
    });
  };

  return (
    <div className="page report-page bonus-retention">
      <div className="page__header">
        <div className="page__title">Bonus Retention</div>
      </div>

      {/* ---------- Filters ---------- */}
      <div className="rpt-filters">
        <div className="rpt-field">
          <label>Skin</label>
          <div className="rpt-skin-wrap">
            <select className="select" value={skin} onChange={e => setSkin(e.target.value)}>
              <option value="">- Select skin -</option>
              {BR_SKINS.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
            </select>
            {skin && (
              <button className="rpt-skin-clear" title="Clear skin" onClick={() => setSkin("")}>
                <Icon name="x" size={13} />
              </button>
            )}
          </div>
          <label style={{ marginTop: 12 }}>Promotions</label>
          <select className="select" value={promoId} disabled={!skin} onChange={e => setPromoId(e.target.value)} style={{ maxWidth: 300 }}>
            {BR_PROMOS.filter(p => p.skin === skin).map(p => (
              <option key={p.id} value={p.id}>{p.name} ({p.start}/{p.end})</option>
            ))}
          </select>
        </div>

        <div className="rpt-field">
          <label>Bonus activation date range ( UTC )</label>
          <div className="rpt-daterow">
            <input className="input rpt-date" type="date" value={draft.start} onChange={e => setD({ start: e.target.value })} />
            <input className="input rpt-time" type="time" step="1" value={draft.startTime} onChange={e => setD({ startTime: e.target.value })} />
            <input className="input rpt-date" type="date" value={draft.end} onChange={e => setD({ end: e.target.value })} />
            <input className="input rpt-time" type="time" step="1" value={draft.endTime} onChange={e => setD({ endTime: e.target.value })} />
          </div>
          <div style={{ display: "flex", gap: 18, flexWrap: "wrap", marginTop: 12, alignItems: "flex-end" }}>
            <div className="rpt-field">
              <label>Min. amount converted</label>
              <input className="input" inputMode="decimal" value={draft.minConverted} onChange={e => setD({ minConverted: e.target.value })} placeholder="0" />
            </div>
            <div className="rpt-field">
              <label>Player's status</label>
              <select className="select" value={draft.status} onChange={e => setD({ status: e.target.value })}>
                <option value="all">- All -</option>
                <option value="active">Active</option>
                <option value="inactive">Inactive</option>
              </select>
            </div>
            <div className="rpt-field">
              <label>Min. deposit</label>
              <input className="input" inputMode="numeric" value={draft.minDeposit} onChange={e => setD({ minDeposit: e.target.value })} placeholder="0" />
            </div>
            <div className="rpt-field rpt-toggle-field">
              <label>Convert to EUR</label>
              <Toggle value={convertEUR} onChange={setConvertEUR} onLabel="" offLabel="" />
            </div>
          </div>
          <div className="rpt-note">* Filters only apply to the list below and do not affect the KPI values above.</div>
        </div>

        <div className="rpt-actions">
          <div className="rpt-actions-row">
            <button className="rpt-btn rpt-btn--search" onClick={search}><Icon name="search" size={14} /> Search</button>
            <button className="rpt-btn rpt-btn--export" onClick={exportCSV}><Icon name="download" size={14} /> Export</button>
          </div>
          <div className="rpt-actions-row">
            <button className="rpt-btn rpt-btn--reset" onClick={reset}><Icon name="x" size={14} /> Reset Filters</button>
          </div>
        </div>
      </div>

      {/* ---------- KPI stat widgets ---------- */}
      <div className="br-stats">
        <div className="br-stat br-stat--a"><div className="br-stat-ic"><Icon name="users" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{brInt(b.activated)}</div><div className="br-stat-k">Players with activated bonus</div></div></div>
        <div className="br-stat br-stat--b"><div className="br-stat-ic"><Icon name="check" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{brInt(b.redeemed)}</div><div className="br-stat-k">Players with redeemed bonus</div></div></div>
        <div className="br-stat br-stat--c"><div className="br-stat-ic"><Icon name="percent" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{brFmt(retentionPct)}%</div><div className="br-stat-k">Retention rate</div></div></div>
        <div className="br-stat br-stat--d"><div className="br-stat-ic"><Icon name="wallet" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{money(avgRedeposit)}</div><div className="br-stat-k">Avg. deposit post-withdrawal</div></div></div>
        <div className="br-stat br-stat--e"><div className="br-stat-ic"><Icon name="chart" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{money(b.totalGGR)}</div><div className="br-stat-k">Total GGR</div></div></div>
        <div className="br-stat br-stat--f"><div className="br-stat-ic"><Icon name="credit_card" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{money(b.totalCost)}</div><div className="br-stat-k">Total promotion cost</div></div></div>
        <div className="br-stat br-stat--g"><div className="br-stat-ic"><Icon name="activity" size={18} /></div><div className="br-stat-body"><div className="br-stat-v">{brFmt(costGGR)}X</div><div className="br-stat-k">Cost / GGR ratio</div></div></div>
      </div>

      {/* ---------- Budget progress ---------- */}
      <div className="br-budget">
        <div className="br-budget-head">
          <div><div className="br-budget-k">Total budget of the promotion</div><div className="br-budget-v">{money(b.budget)}</div></div>
          <div style={{ textAlign: "right" }}><div className="br-budget-k">% of budget reached</div><div className="br-budget-v br-budget-v--accent">{brFmt(budgetReachedPct)}%</div></div>
        </div>
        <div className="br-budget-bar"><div className="br-budget-fill" style={{ width: `${Math.min(100, budgetReachedPct)}%` }} /></div>
        <div className="br-budget-sub">{money(b.totalCost)} spent of {money(b.budget)}</div>
      </div>
      <div className="rpt-note" style={{ marginBottom: 16 }}>* KPI values reflect the entire promotion and are not affected by the filters above.</div>

      {/* ---------- Player table ---------- */}
      <div className="panel" style={{ overflow: "hidden" }}>
        <div style={{ maxHeight: "calc(100vh - 460px)", overflow: "auto" }}>
          <table className="data-table">
            <thead>
              <tr>
                <th>Username</th>
                <th>Redeem date</th>
                <th>Redeemed amount</th>
                <th>1st withdrawal date</th>
                <th>Deposits post-withdrawal</th>
                <th>GGR casino</th>
                <th>GGR sport</th>
                <th>Player's status</th>
              </tr>
            </thead>
            <tbody>
              {filtered.length === 0 && (
                <tr><td colSpan={8} style={{ padding: "40px 20px", textAlign: "center", color: "var(--text-tertiary)" }}>
                  {skin ? "No players match your filters." : "Select a skin and promotion to load the report."}
                </td></tr>
              )}
              {filtered.map(r => (
                <tr key={r.id}>
                  <td>
                    <button className="rpt-user" onClick={() => openPlayer(r)} title="Open player profile">
                      {r.id} <Icon name="chevron_right" size={12} className="chev" />
                    </button>
                  </td>
                  <td>{r.redeemDate ? brTs(r.redeemDate) : "N/A"}</td>
                  <td>{money(r.redeemedAmount)}</td>
                  <td>{r.withdrawalDate ? brTs(r.withdrawalDate) : "N/A"}</td>
                  <td>{r.deposits.count > 0 ? `${r.deposits.count} (${money(r.deposits.total)})` : "N/A"}</td>
                  <td>{money(r.ggrCasino)}</td>
                  <td>{money(r.ggrSport)}</td>
                  <td><span style={{ color: r.status === "active" ? "var(--ok-600, #16a34a)" : "var(--text-tertiary)", fontWeight: 500 }}>{r.status}</span></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
};

window.BonusRetention = BonusRetention;
