// NO PROD JSON API (bucket B — feed is a DataTables render payload or
//   {"html":...}, not data) — mock until backend (see PROD_API_INVENTORY.md)
// Represents: GET /bonus/programs/{create,{id}/edit} · Admin/BonusProgramController::create|edit — see docs/ISYSTEM_REFERENCE.md
/* Traced Aug 2026 (architecture item 2). Upstream these are two full pages
   (admin.bonus-programs.create / .edit), not a modal; the prototype renders
   the wizard from BonusPrograms.jsx. Provider and game pickers come from
   PromotionsBonusController. Not routed on its own — see index.html load
   order; it is load-bearing, not dead. */
/* NO DATA: this file is a CHILD COMPONENT, not a screen. It is rendered by
   BonusPrograms.jsx, which does the reading — skins, providers and the row
   being edited all arrive as props, and its onCreate goes to that screen's
   save_bonus_program call. A fetch of its own would be a second query for data
   the parent already holds.

   What is left at module level is the product's own vocabulary: the four bonus
   types, the five trigger types, the calculation methods, the four statuses.
   Those are not data about the platform — they are the set of things a bonus
   CAN be, and the RPC validates against the same set. Fetching them would mean
   a table whose rows are the names of the branches in this file.

   The one list that WAS data is gone: the freespin and allowed-provider pickers
   read a hardcoded array from cms-shared.jsx, and those decide where a bonus may
   be played — a name that is not a real provider makes a bonus playable
   nowhere. Providers now come from the parent's feed. */
/* Bonus Program Wizard — "Create New Bonus Program" (Bonus Programs → New Bonus Program).
   Seven-step configuration form + a persistent Trigger Configuration side
   panel. Several fields reveal extra, trigger/type-specific configuration
   once a value is picked (Trigger Type, Bonus Type = Freespin, Calculation
   Method, Allowed Games Filter) — these render inside a `.bw-dynamic-panel`
   so it's visually clear they're conditional. "Key Terms" on Step 2 is
   computed live from the rest of the form instead of being decorative. */

const { useState: useStateBW, useEffect: useEffectBW, useRef: useRefBW } = React;

const bwToast = (m, isErr) => window.PAYBO?.emitToast && window.PAYBO.emitToast({
  id: `bw-${Date.now()}`, tx_id: m, amount: 0, currency: isErr ? "ERR" : "HOST",
  player: "Bonus Program", reason: isErr ? "Fix this before continuing." : "Prototype state only \u2014 not persisted.",
});

/* THE PROVIDER LIST WAS A HARDCODED ARRAY in src/cms-shared.jsx, and it is the
   one list on this form that decides where money can be spent: the freespin
   providers and the allowed-provider filter are what a bonus may be played on.
   A name that is not a real provider produces a bonus playable nowhere; a real
   provider missing from the list cannot be selected at all.

   Fetched now, and passed down — the wizard already receives `skins` the same
   way, because a component cannot hold a hook it only sometimes mounts.

   `bwSkins()` is gone with it: it read a window global whose first entry was
   the string "1xway - test skin", which is a NAME where the form needs an id. */
const BW_PRODUCT_TYPES = ["Casino", "Sport"];
const BW_BONUS_TYPES = [
  { value: "wagering", label: "Bonus Money / Wagering Bonus", listLabel: "Wagering bonus" },
  { value: "freespin", label: "Freespin", listLabel: "Freespin" },
  { value: "cash", label: "Cash Bonus (Cashback/Rakeback)", listLabel: "Cash bonus" },
  { value: "nodeposit", label: "No Deposit Bonus", listLabel: "No deposit bonus" },
];
const BW_CAMPAIGN_END = ["Stop issuing + allow active bonuses to continue", "Stop issuing + force forfeit active bonuses"];
const BW_STATUSES = ["Draft", "Active", "Paused", "Archived"];
const BW_TRIGGER_TYPES = ["Signup / Registration Trigger", "Deposit Approved % Match Trigger", "Bet-Based Bonus Calculation Trigger", "Net-loss Cashback Trigger", "Manual / CRM Assignment Trigger"];
const BW_KYC = ["None", "Basic", "Full"];
const BW_CALC_METHODS = ["Fixed Amount", "Deposit % Match", "Wager Rate-Based"];
const BW_GAMES_FILTER = ["All", "Provider Allowlist", "Game Allowlist", "Denylist"];
const BW_COMPLETION_OUTCOME = ["Convert bonus to cash", "Transfer winnings to bonus balance"];
const BW_CONSUMPTION_ORDER = ["Cash first", "Bonus first"];
const BW_UNMET_DEPOSIT_BEHAVIOR = ["Keep balance locked indefinitely", "Forfeit bonus-derived winnings after window expires"];
const BW_STEPS = [
  "Step 1: Main Bonus Configuration",
  "Step 2: Visibility & Player-Facing Content",
  "Step 3: Eligibility Rules",
  "Step 4: Reward Definition",
  "Step 5: Wagering / Usage Requirements",
  "Step 6: Wallet / Balance Behavior & Forfeiture",
  "Step 7: Limits & Budget Controls",
];
// Each step gets its own URL (e.g. /bonus-campaigns/create/eligibility-rules)
// via useUrlTab (src/routes.jsx) — id is the step index STEP_RENDERERS
// already indexes by; slug drops the redundant "Step N:" prefix.
const BW_STEP_ROUTES = BW_STEPS.map((label, i) => [i, label, i === 0 ? "" : window.slugifyTab(label.replace(/^Step \d+:\s*/, ""))]);

/* ------------------------------------------------------------------ *
 * A stored programme -> the wizard's form. The inverse of bpRpcArgs.
 *
 * Needed because the wizard is the EDIT form too now, and an edit form that
 * opens blank is worse than no edit form: the operator fixes a typo in the
 * name, presses Save, and the wagering multiplier they never saw goes to
 * whatever the blank default was. Every field the RPC can write is read back
 * here, from the child rows embedded in the list query.
 *
 * `?? ""` throughout rather than `|| ""`, because 0 is a real value for a
 * contribution percentage and a minimum bet.
 * ------------------------------------------------------------------ */
const bwOne = (v) => (Array.isArray(v) ? (v[0] || null) : (v || null));
const bwStr = (v) => (v === null || v === undefined ? "" : String(v));

const BW_PRODUCT_FROM_DB = { casino: "Casino", sports: "Sport", poker: "Casino", lottery: "Casino" };
const BW_BONUS_FROM_DB = { wagering_bonus: "wagering", freespin: "freespin", cash_bonus: "cash", no_deposit: "nodeposit", freebet: "wagering", golden_chip: "wagering" };
const BW_STATUS_FROM_DB = { draft: "Draft", active: "Active", paused: "Paused", archived: "Archived" };
const BW_TRIGGER_FROM_DB = {
  signup: "Signup / Registration Trigger", deposit: "Deposit Approved % Match Trigger",
  bet_based: "Bet-Based Bonus Calculation Trigger", cashback: "Net-loss Cashback Trigger",
  manual: "Manual / CRM Assignment Trigger",
};
const BW_CALC_FROM_DB = { fixed_amount: "Fixed Amount", deposit_match: "Deposit % Match", wager_rate_based: "Wager Rate-Based" };
const BW_GAMES_FROM_DB = { all: "All", provider_allowlist: "Provider Allowlist", game_allowlist: "Game Allowlist", denylist: "Denylist" };
const BW_OUTCOME_FROM_DB = { convert_bonus_to_cash: "Convert bonus to cash", transfer_winnings_to_bonus: "Transfer winnings to bonus balance" };
const BW_CONSUMPTION_FROM_DB = { cash_first: "Cash first", bonus_first: "Bonus first" };
const BW_KYC_FROM_DB = { none: "None", basic: "Basic", full: "Full" };
const BW_UNMET_FROM_DB = { lock_indefinitely: "Keep balance locked indefinitely", forfeit_on_expiry: "Forfeit bonus-derived winnings after window expires" };
const BW_CAMPAIGN_END_FROM_DB = {
  continue_active: "Stop issuing + allow active bonuses to continue",
  force_forfeit: "Stop issuing + force forfeit active bonuses",
  stop_issuing: "Stop issuing + allow active bonuses to continue",
};

const bwFormFromRow = (row) => {
  const d = (row && row._db) || {};
  const t = bwOne(d.terms) || {};
  const w = bwOne(d.wagering) || {};
  const g = bwOne(d.depositGate) || {};
  const e = bwOne(d.eligibility) || {};
  const b = bwOne(d.betBased) || {};
  const c = bwOne(d.cashback) || {};
  const local = (iso) => (iso ? String(iso).slice(0, 16) : "");
  return {
    skin_id: bwStr(d.skin_id),
    programName: bwStr(d.name),
    displayName: bwStr(d.display_name),
    shortDescription: bwStr(d.short_description),
    terms: bwStr(d.terms_conditions),
    keyTerms: bwStr(d.key_terms),
    productType: BW_PRODUCT_FROM_DB[d.product_type] || "Casino",
    bonusType: BW_BONUS_FROM_DB[d.bonus_type] || "wagering",
    triggerType: BW_TRIGGER_FROM_DB[d.trigger_type] || "",
    status: BW_STATUS_FROM_DB[d.status] || "Draft",
    priority: bwStr(d.priority),
    startDate: local(d.starts_at),
    endDate: local(d.ends_at),
    campaignEnd: BW_CAMPAIGN_END_FROM_DB[d.campaign_end_handling] || BW_CAMPAIGN_END[0],

    calcMethod: BW_CALC_FROM_DB[t.calculation_method] || "",
    fixedAmount: bwStr(t.fixed_bonus_amount),
    matchPct: bwStr(t.match_percentage),
    minBonusAmount: bwStr(t.minimum_bonus_amount),
    maxBonusAmount: bwStr(t.maximum_bonus_amount),
    wagerRateAmount: bwStr(t.wager_rate_amount),
    wagerUnitSize: bwStr(t.wager_unit_size),
    maxCashout: bwStr(t.max_cashout_winnings_cap),
    completionOutcome: BW_OUTCOME_FROM_DB[t.completion_outcome] || "Convert bonus to cash",
    manualForfeit: !!t.manual_forfeit_allowed,
    freespinCount: bwStr(t.number_of_freespins),
    freespinCoinValue: bwStr(t.freespin_bet_amount),

    wageringMultiplier: bwStr(w.wagering_multiplier),
    wageringTimeLimit: bwStr(w.wagering_time_limit_hours),
    bonusFundsExpiry: bwStr(w.bonus_funds_expiry_hours),
    slotsContribution: bwStr(w.slots_contribution),
    liveContribution: bwStr(w.live_casino_contribution),
    sportsContribution: bwStr(w.sports_contribution),
    defaultContribution: bwStr(w.default_contribution),
    minBet: bwStr(w.min_bet_while_active),
    maxBet: bwStr(w.max_bet_while_active),
    consumptionOrder: BW_CONSUMPTION_FROM_DB[w.balance_consumption_order] || "",
    gamesFilter: BW_GAMES_FROM_DB[w.allowed_games_filter] || "",

    depositGateEnabled: !!g.enabled,
    requiredDepositPct: bwStr(g.required_percent),
    depositWindowValue: bwStr(g.window_value),
    depositWindowUnit: g.window_unit === "days" ? "Days" : "Hours",
    unmetDepositBehavior: BW_UNMET_FROM_DB[g.unmet_behavior] || BW_UNMET_DEPOSIT_BEHAVIOR[0],
    minDepositAmount: bwStr(g.min_deposit_amount),

    newPlayersOnly: !!e.new_players_only,
    kyc: BW_KYC_FROM_DB[e.kyc_requirement] || "None",
    fraudBlock: !!e.fraud_risk_block,
    perPlayerMaxClaims: bwStr(e.per_player_max_claims),
    maxActivePerCategory: bwStr(e.max_active_bonuses_per_player),
    globalMaxCount: bwStr(e.global_max_issued_count),
    globalMaxValue: bwStr(d.budget_amount != null ? d.budget_amount : e.global_max_issued_value),
    globalMaxConverted: bwStr(e.global_max_converted_value),

    minBetCount: bwStr(b.min_bet_count),
    evalWindowHours: bwStr(b.window_hours),
    netLossPeriod: c.periodicity ? (c.periodicity.charAt(0).toUpperCase() + c.periodicity.slice(1)) : "Daily",
    cashbackPct: bwStr(c.cashback_rate),
  };
};

const bwDefaultForm = (skins) => ({
  /* skin_id, not a brand NAME. `bwSkins()` read a window global whose first
     entry was the literal string "1xway - test skin (N/A)" — a placeholder
     brand that exists nowhere, offered as the default for every new programme. */
  skin_id: (skins && skins[0]) ? String(skins[0].id) : "",
  programName: "", productType: "Casino", bonusType: "wagering",
  startDate: "", endDate: "", campaignEnd: BW_CAMPAIGN_END[0], status: "Draft", priority: 100,
  freespinCount: "", freespinCoinValue: "", freespinProviders: [],

  displayName: "", shortDescription: "", terms: "", keyTerms: "",

  newPlayersOnly: false, kyc: "Basic", fraudBlock: false,

  calcMethod: "", maxBonusAmount: "", minBonusAmount: "0.00",
  fixedAmount: "", matchPct: "", wagerRateAmount: "", wagerUnitSize: "",

  wageringMultiplier: "35.00", wageringTimeLimit: "", bonusFundsExpiry: "",
  slotsContribution: "", liveContribution: "", sportsContribution: "", defaultContribution: "100.00",
  gamesFilter: "", allowedProviders: [], minBet: "0.00", maxBet: "0.00", maxCashout: "0.00",
  winningsCap: "0", completionOutcome: "Convert bonus to cash",

  consumptionOrder: "", manualForfeit: false,
  depositGateEnabled: false, requiredDepositPct: "", depositWindowValue: "", depositWindowUnit: "Hours",
  unmetDepositBehavior: BW_UNMET_DEPOSIT_BEHAVIOR[0],

  perPlayerMaxClaims: "0", maxActivePerCategory: "1", globalMaxCount: "0",
  globalMaxValue: "0.00", globalMaxConverted: "0.00",

  triggerType: "", depositNumber: "Any", minDepositAmount: "",
  minBetCount: "", evalWindowHours: "", netLossPeriod: "Daily", cashbackPct: "",
});

/* Live "Key Terms" summary — reads back the rest of the form so Step 2
   shows real derived copy instead of a static placeholder. */
const bwKeyTermsSummary = (f) => {
  const parts = [];
  if (f.wageringMultiplier) parts.push(`${f.wageringMultiplier}x wagering`);
  if (f.wageringTimeLimit) parts.push(`Complete within ${f.wageringTimeLimit}h`);
  if (f.bonusFundsExpiry) parts.push(`Funds expire in ${f.bonusFundsExpiry}h`);
  if (f.maxBet && Number(f.maxBet) > 0) parts.push(`Max bet €${f.maxBet} while active`);
  if (f.maxCashout && Number(f.maxCashout) > 0) parts.push(`Max cashout €${f.maxCashout}`);
  if (f.gamesFilter && f.gamesFilter !== "All") parts.push(`${f.gamesFilter}${f.allowedProviders.length ? ` (${f.allowedProviders.length} providers)` : ""}`);
  if (f.maxBonusAmount) parts.push(`Max bonus €${f.maxBonusAmount}`);
  return parts.join(" · ");
};

const BwRow = ({ label, required, disabled, children }) => (
  <div className="bw-row">
    <div className="bw-row-label">
      {label}{required && <span style={{ color: "#e2011a" }}> *</span>}
      {disabled && <span className="bw-disabled-badge">Disabled</span>}
    </div>
    <div className="bw-row-field">{children}</div>
  </div>
);
const BwHelp = ({ children }) => <div className="bw-help">{children}</div>;
const BwFieldLabel = ({ children }) => <label className="form-label" style={{ display: "block", marginBottom: 6 }}>{children}</label>;

/* Chip-style multi-select popover — used for Allowed Providers / freespin providers. */
const BwMultiPicker = ({ value, onChange, options, placeholder }) => {
  const [open, setOpen] = useStateBW(false);
  const ref = useRefBW(null);
  useEffectBW(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const toggle = (opt) => onChange(value.includes(opt) ? value.filter(v => v !== opt) : [...value, opt]);
  return (
    <div ref={ref} style={{ position: "relative" }}>
      <div className="bw-chip-input" onClick={() => setOpen(o => !o)}>
        {value.length === 0 && <span className="bw-chip-placeholder">{placeholder}</span>}
        {value.map(v => (
          <span key={v} className="bw-chip">
            {v}
            <button onClick={(e) => { e.stopPropagation(); toggle(v); }}><Icon name="x" size={10} /></button>
          </span>
        ))}
      </div>
      {open && (
        <div className="bw-popover">
          {options.map(o => (
            <label key={o}>
              <input type="checkbox" checked={value.includes(o)} onChange={() => toggle(o)} />
              {o}
            </label>
          ))}
        </div>
      )}
    </div>
  );
};

const BonusProgramWizard = ({ skins, providers, row, busy, onBack, onCreate }) => {
  const providerNames = (providers || []).map(p => p.name);
  window.useLocale && window.useLocale();
  const [step, setStep] = window.useUrlTab("/bonus-campaigns/create", BW_STEP_ROUTES, 0);
  /* Editing prefills from the stored programme; creating starts blank. The old
     comment said reopening the wizard prefilled "needs the stored program
     record, which only the backend has" — it does, and now it is read. */
  const [f, setF] = useStateBW(() => Object.assign(bwDefaultForm(skins), row ? bwFormFromRow(row) : {}));
  const set = (patch) => setF(d => ({ ...d, ...patch }));

  const validate = () => {
    if (!f.skin_id) return "Skin is required";
    if (!f.programName.trim()) return "Program Name is required";
    if (!f.productType) return "Product Type is required";
    if (!f.bonusType) return "Bonus Type is required";
    if (!f.campaignEnd) return "Campaign End Handling is required";
    if (!f.status) return "Status is required";
    if (f.priority === "" || f.priority == null) return "Priority is required";
    if (!f.triggerType) return "Trigger Type is required";
    if (f.bonusType === "nodeposit" && f.depositGateEnabled) {
      if (f.requiredDepositPct === "" || f.requiredDepositPct == null) return "Required Deposit % is required when the deposit gate is enabled";
      const pct = Number(f.requiredDepositPct);
      if (isNaN(pct) || pct < 0 || pct > 100) return "Required Deposit % must be between 0 and 100";
      if (f.depositWindowValue === "" || f.depositWindowValue == null) return "Deposit Window is required when the deposit gate is enabled";
      const win = Number(f.depositWindowValue);
      if (isNaN(win) || win <= 0) return "Deposit Window must be greater than 0";
      if (!f.unmetDepositBehavior) return "Unmet Deposit Behavior is required when the deposit gate is enabled";
    }
    return null;
  };

  const handleCreate = () => {
    const err = validate();
    if (err) { bwToast(err, true); return; }
    /* THE WHOLE FORM. This used to build a nine-field summary — name, skin,
       product, bonus type, status, priority, two dates, budget — and drop
       everything steps 3 to 7 collected: the calculation method, the wagering
       multiplier and its per-vertical contributions, the deposit gate, the
       eligibility caps, the forfeiture behaviour. Seven steps of form, two
       steps' worth of payload, and no sign on screen that the rest went
       nowhere. bpRpcArgs maps every field onto save_bonus_program. */
    onCreate(f);
  };

  const renderStep1 = () => (
    <>
      <BwRow label="Skin" required>
        <select className="select" style={{ width: "100%" }} value={f.skin_id} onChange={e => set({ skin_id: e.target.value })}>
          <option value="">Select skin</option>
          {(skins || []).map(k => <option key={k.id} value={k.id}>{k.name}</option>)}
        </select>
      </BwRow>
      <BwRow label="Program Name" required>
        <input className="input" style={{ width: "100%" }} value={f.programName} onChange={e => set({ programName: e.target.value })} />
      </BwRow>
      <BwRow label="Product Type" required>
        <select className="select" style={{ width: "100%" }} value={f.productType} onChange={e => set({ productType: e.target.value })}>
          {BW_PRODUCT_TYPES.map(p => <option key={p}>{p}</option>)}
        </select>
      </BwRow>
      <BwRow label="Bonus Type" required>
        <select className="select" style={{ width: "100%" }} value={f.bonusType} onChange={e => {
          const val = e.target.value;
          // No Deposit Bonus has no deposit to base a % on — force the only
          // valid Calculation Method so Step 4 doesn't show a stale/invalid pick.
          set(val === "nodeposit" ? { bonusType: val, calcMethod: "Fixed Amount" } : { bonusType: val });
        }}>
          {BW_BONUS_TYPES.map(b => <option key={b.value} value={b.value}>{b.label}</option>)}
        </select>
        {f.bonusType === "freespin" && (
          <div className="bw-dynamic-panel">
            <div className="bw-dynamic-title">Freespin configuration</div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <div><BwFieldLabel>Number of Free Spins</BwFieldLabel>
                <input type="number" className="input" style={{ width: "100%" }} value={f.freespinCount} onChange={e => set({ freespinCount: e.target.value })} /></div>
              <div><BwFieldLabel>Spin Coin Value</BwFieldLabel>
                <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.freespinCoinValue} onChange={e => set({ freespinCoinValue: e.target.value })} /></div>
            </div>
            <div style={{ marginTop: 14 }}>
              <BwFieldLabel>Spins Valid On (Providers)</BwFieldLabel>
              <BwMultiPicker value={f.freespinProviders} onChange={v => set({ freespinProviders: v })} options={providerNames} placeholder="Select providers where spins are playable" />
            </div>
          </div>
        )}
      </BwRow>
      <BwRow label="Start Date (UTC+0)">
        <input type="datetime-local" className="input" style={{ width: "100%" }} value={f.startDate} onChange={e => set({ startDate: e.target.value })} />
      </BwRow>
      <BwRow label="End Date (UTC+0)">
        <input type="datetime-local" className="input" style={{ width: "100%" }} value={f.endDate} onChange={e => set({ endDate: e.target.value })} />
      </BwRow>
      <BwRow label="Campaign End Handling" required>
        <select className="select" style={{ width: "100%" }} value={f.campaignEnd} onChange={e => set({ campaignEnd: e.target.value })}>
          {BW_CAMPAIGN_END.map(c => <option key={c}>{c}</option>)}
        </select>
      </BwRow>
      <BwRow label="Status" required>
        <select className="select" style={{ width: "100%" }} value={f.status} onChange={e => set({ status: e.target.value })}>
          {BW_STATUSES.map(s => <option key={s}>{s}</option>)}
        </select>
      </BwRow>
      <BwRow label="Priority" required>
        <input type="number" className="input" style={{ width: "100%" }} value={f.priority} onChange={e => set({ priority: e.target.value })} />
      </BwRow>
      <BwRow label="Stacking Group" disabled>
        <input className="input" style={{ width: "100%" }} disabled placeholder="Not available in this phase" />
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
    </>
  );

  const renderStep2 = () => (
    <>
      <BwRow label="Bonus Display Name">
        <input className="input" style={{ width: "100%" }} placeholder="e.g., Welcome Bonus" value={f.displayName} onChange={e => set({ displayName: e.target.value })} />
        <BwHelp>Shown to player in promos/wallet. Alphanumeric + punctuation allowed.</BwHelp>
      </BwRow>
      <BwRow label="Short Description">
        <textarea className="input" style={{ width: "100%", minHeight: 70, resize: "vertical" }} placeholder="e.g., 100% up to €200, 35x wagering" value={f.shortDescription} onChange={e => set({ shortDescription: e.target.value })} />
        <BwHelp>Promo summary (0-200 characters recommended)</BwHelp>
      </BwRow>
      <BwRow label="Terms & Conditions">
        <textarea className="input" style={{ width: "100%", minHeight: 150, resize: "vertical" }} placeholder="Legal/marketing copy. Must reflect actual rules." value={f.terms} onChange={e => set({ terms: e.target.value })} />
      </BwRow>
      <BwRow label="Key Terms (Structured Summary)">
        <textarea className="input" style={{ width: "100%", minHeight: 70, resize: "vertical", background: "#f6f7f9", color: "#6b7280" }} readOnly value={bwKeyTermsSummary(f)} placeholder="Auto-generated from configuration. Will be populated after saving." />
        <BwHelp>System derived (read-only). Derived from config to prevent mismatch: wagering x, expiry, max bet, max cashout, eligible games, etc.</BwHelp>
      </BwRow>
    </>
  );

  const renderStep3 = () => (
    <>
      <BwRow label="New Players Only">
        <label className="bw-checkbox-row"><input type="checkbox" checked={f.newPlayersOnly} onChange={e => set({ newPlayersOnly: e.target.checked })} /> Restrict to new players</label>
        <BwHelp>Restricts to new players (definition may be "no deposit history" or "account age &lt; X days" later)</BwHelp>
      </BwRow>
      <BwRow label="KYC Requirement">
        <select className="select" style={{ width: "100%" }} value={f.kyc} onChange={e => set({ kyc: e.target.value })}>
          {BW_KYC.map(k => <option key={k}>{k}</option>)}
        </select>
      </BwRow>
      <BwRow label="Allowed Countries" disabled>
        <input className="input" style={{ width: "100%" }} disabled />
        <BwHelp>Compliance allowlist. Currently disabled - not checked during registration.</BwHelp>
      </BwRow>
      <BwRow label="Blocked Countries" disabled>
        <input className="input" style={{ width: "100%" }} disabled />
        <BwHelp>Compliance blocklist. Currently disabled - not checked during registration.</BwHelp>
      </BwRow>
      <BwRow label="Allowed Currencies" disabled>
        <input className="input" style={{ width: "100%" }} disabled />
        <BwHelp>Currently disabled - not checked during registration</BwHelp>
      </BwRow>
      <BwRow label="Responsible Gaming Block Rules" disabled>
        <div style={{ display: "flex", gap: 18, flexWrap: "wrap" }}>
          <label className="bw-checkbox-row"><input type="checkbox" disabled /> Self-exclusion</label>
          <label className="bw-checkbox-row"><input type="checkbox" disabled /> Timeout/Cool-off</label>
          <label className="bw-checkbox-row"><input type="checkbox" disabled /> Limits reached</label>
        </div>
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
      <BwRow label="Fraud/Risk Block">
        <label className="bw-checkbox-row"><input type="checkbox" checked={f.fraudBlock} onChange={e => set({ fraudBlock: e.target.checked })} /> Enable fraud/risk block</label>
        <BwHelp>If player/device flagged, block issuance (MVP = toggle; Phase 2+ thresholds/actions)</BwHelp>
      </BwRow>
    </>
  );

  const renderStep4 = () => (
    <>
      <BwRow label="Calculation Method" required>
        {f.bonusType === "nodeposit" ? (
          <>
            <select className="select" style={{ width: "100%" }} value="Fixed Amount" disabled><option>Fixed Amount</option></select>
            <BwHelp>No Deposit Bonus has no deposit to calculate a percentage from — Calculation Method is fixed to "Fixed Amount".</BwHelp>
          </>
        ) : (
          <select className="select" style={{ width: "100%" }} value={f.calcMethod} onChange={e => set({ calcMethod: e.target.value })}>
            <option value="">Select calculation method</option>
            {BW_CALC_METHODS.map(c => <option key={c}>{c}</option>)}
          </select>
        )}
        {f.calcMethod === "Fixed Amount" && (
          <div className="bw-dynamic-panel">
            <div className="bw-dynamic-title">Fixed amount configuration</div>
            <BwFieldLabel>Fixed Bonus Amount</BwFieldLabel>
            <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.fixedAmount} onChange={e => set({ fixedAmount: e.target.value })} />
          </div>
        )}
        {f.bonusType !== "nodeposit" && f.calcMethod === "Deposit % Match" && (
          <div className="bw-dynamic-panel">
            <div className="bw-dynamic-title">Deposit % match configuration</div>
            <BwFieldLabel>Match Percentage (%)</BwFieldLabel>
            <input type="number" className="input" style={{ width: "100%" }} value={f.matchPct} onChange={e => set({ matchPct: e.target.value })} />
          </div>
        )}
        {f.bonusType !== "nodeposit" && f.calcMethod === "Wager Rate-Based" && (
          <div className="bw-dynamic-panel">
            <div className="bw-dynamic-title">Wager rate configuration</div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <div><BwFieldLabel>Bonus per Unit Wagered</BwFieldLabel>
                <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.wagerRateAmount} onChange={e => set({ wagerRateAmount: e.target.value })} /></div>
              <div><BwFieldLabel>Wager Unit Size</BwFieldLabel>
                <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.wagerUnitSize} onChange={e => set({ wagerUnitSize: e.target.value })} /></div>
            </div>
          </div>
        )}
      </BwRow>
      <BwRow label="Maximum Bonus Amount">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.maxBonusAmount} onChange={e => set({ maxBonusAmount: e.target.value })} />
        <BwHelp>Hard cap: finalBonus = min(rawBonus, maxBonusAmount)</BwHelp>
      </BwRow>
      <BwRow label="Minimum Bonus Amount">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.minBonusAmount} onChange={e => set({ minBonusAmount: e.target.value })} />
        <BwHelp>Guarantees minimum award after eligibility met (optional)</BwHelp>
      </BwRow>
      <BwRow label="Rounding Rule" disabled>
        <select className="select" style={{ width: "100%" }} disabled><option>Select rounding rule</option></select>
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
      <BwRow label="Issue Currency Rule" disabled>
        <select className="select" style={{ width: "100%" }} disabled><option>Select currency rule</option></select>
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
    </>
  );

  const renderStep5 = () => (
    <>
      <BwRow label="Wagering Multiplier">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.wageringMultiplier} onChange={e => set({ wageringMultiplier: e.target.value })} />
        <BwHelp>requiredTurnover = wageringBaseAmount * multiplier (e.g., 35 for 35x)</BwHelp>
      </BwRow>
      <BwRow label="Wagering Base">
        <select className="select" style={{ width: "100%" }} value="Bonus only" disabled><option>Bonus only</option></select>
        <BwHelp>Only "Bonus Only" option available</BwHelp>
      </BwRow>
      <BwRow label="Wagering Time Limit">
        <div className="bw-unit-wrap"><input type="number" className="input" style={{ flex: 1 }} value={f.wageringTimeLimit} onChange={e => set({ wageringTimeLimit: e.target.value })} /><span className="bw-unit">Hours</span></div>
        <BwHelp>Time allowed to complete wagering once active (hours only)</BwHelp>
      </BwRow>
      <BwRow label="Bonus Funds Expiry">
        <div className="bw-unit-wrap"><input type="number" className="input" style={{ flex: 1 }} value={f.bonusFundsExpiry} onChange={e => set({ bonusFundsExpiry: e.target.value })} /><span className="bw-unit">Hours</span></div>
        <BwHelp>Time limit to use bonus funds (hours only)</BwHelp>
      </BwRow>
      <BwRow label="Slots Game Contribution">
        <input type="number" min="0" max="100" className="input" style={{ width: "100%" }} placeholder="Leave empty for default" value={f.slotsContribution} onChange={e => set({ slotsContribution: e.target.value })} />
        <BwHelp>Optional. Applies to casino/slots category wagers (0-100%). Empty uses Default Game Contribution.</BwHelp>
      </BwRow>
      <BwRow label="Live Casino Game Contribution">
        <input type="number" min="0" max="100" className="input" style={{ width: "100%" }} placeholder="Leave empty for default" value={f.liveContribution} onChange={e => set({ liveContribution: e.target.value })} />
        <BwHelp>Optional. Applies to live casino category wagers (0-100%). Empty uses Default Game Contribution.</BwHelp>
      </BwRow>
      <BwRow label="Sports Game Contribution">
        <input type="number" min="0" max="100" className="input" style={{ width: "100%" }} placeholder="Leave empty for default" value={f.sportsContribution} onChange={e => set({ sportsContribution: e.target.value })} />
        <BwHelp>Optional. Applies to sports category wagers (0-100%). Empty uses Default Game Contribution.</BwHelp>
      </BwRow>
      <BwRow label="Default Game Contribution">
        <input type="number" min="0" max="100" className="input" style={{ width: "100%" }} value={f.defaultContribution} onChange={e => set({ defaultContribution: e.target.value })} />
        <BwHelp>Fallback when no category-specific rate is set. turnoverCredit = stake × contributionPercent (0-100%). "Other" games (virtual, poker, etc.) use this unless overridden via API/JSON.</BwHelp>
      </BwRow>
      <BwRow label="Allowed Games Filter">
        <select className="select" style={{ width: "100%" }} value={f.gamesFilter} onChange={e => set({ gamesFilter: e.target.value })}>
          <option value="">Select filter type</option>
          {BW_GAMES_FILTER.map(g => <option key={g}>{g}</option>)}
        </select>
        <BwHelp>Which games/providers count toward wagering after freespin winnings (or general bonus wagering). For Freespin type, use the fields under Bonus Type for where spins are played.</BwHelp>
        {f.gamesFilter && f.gamesFilter !== "All" && (
          <div className="bw-dynamic-panel">
            <div className="bw-dynamic-title">{f.gamesFilter}</div>
            <BwFieldLabel>Allowed Providers</BwFieldLabel>
            <BwMultiPicker value={f.allowedProviders} onChange={v => set({ allowedProviders: v })} options={providerNames} placeholder="Select providers to allow" />
          </div>
        )}
      </BwRow>
      <BwRow label="Min Bet While Bonus Active">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.minBet} onChange={e => set({ minBet: e.target.value })} />
        <BwHelp>Per-spin/round lower cap while a bonus is active</BwHelp>
      </BwRow>
      <BwRow label="Max Bet While Bonus Active">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.maxBet} onChange={e => set({ maxBet: e.target.value })} />
        <BwHelp>Per-spin/round upper cap while a bonus is active</BwHelp>
      </BwRow>
      <BwRow label="Max Cashout">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.maxCashout} onChange={e => set({ maxCashout: e.target.value })} />
        <BwHelp>Maximum amount converted to withdrawable cash when wagering is complete (optional)</BwHelp>
      </BwRow>
      <BwRow label="Winnings Cap">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.winningsCap} onChange={e => set({ winningsCap: e.target.value })} />
        <BwHelp>Caps total winnings generated from bonus play before conversion (0 = unlimited)</BwHelp>
      </BwRow>
      <BwRow label="Completion Outcome">
        <select className="select" style={{ width: "100%" }} value={f.completionOutcome} onChange={e => set({ completionOutcome: e.target.value })}>
          <option value="">Select completion outcome</option>
          {BW_COMPLETION_OUTCOME.map(c => <option key={c}>{c}</option>)}
        </select>
      </BwRow>
    </>
  );

  const renderStep6 = () => (
    <>
      <BwRow key="consumption-order" label="Balance Consumption Order">
        <select className="select" style={{ width: "100%" }} value={f.consumptionOrder} onChange={e => set({ consumptionOrder: e.target.value })}>
          <option value="">Select consumption order</option>
          {BW_CONSUMPTION_ORDER.map(c => <option key={c}>{c}</option>)}
        </select>
        <BwHelp>Determines which balance is debited first for wagers while a bonus is active</BwHelp>
      </BwRow>
      <BwRow key="withdrawals" label="Withdrawals While Bonus Active">
        <select className="select" style={{ width: "100%" }} value="Block withdrawal" disabled><option>Block withdrawal</option></select>
        <BwHelp>Only "Block withdrawal" option available</BwHelp>
      </BwRow>
      {f.bonusType === "nodeposit" && (
        <BwRow key="deposit-gate" label="Deposit Gate">
          <label className="bw-checkbox-row"><input type="checkbox" checked={f.depositGateEnabled} onChange={e => set({ depositGateEnabled: e.target.checked })} /> Enable deposit gate</label>
          <BwHelp>Blocks withdrawal of bonus-derived winnings until the player deposits the required percentage.</BwHelp>
          {f.depositGateEnabled && (
            <div className="bw-dynamic-panel">
              <div className="bw-dynamic-title">Deposit gate configuration</div>
              <div style={{ marginBottom: 14 }}>
                <BwFieldLabel>Required Deposit %</BwFieldLabel>
                <input type="number" min="0" max="100" className="input" style={{ width: "100%" }}
                  value={f.requiredDepositPct} onChange={e => set({ requiredDepositPct: e.target.value })} />
                <BwHelp>How much the player must deposit, as % of their bonus-derived balance, before they can withdraw</BwHelp>
              </div>
              <div style={{ marginBottom: 14 }}>
                <BwFieldLabel>Deposit Window</BwFieldLabel>
                <div className="bw-unit-wrap">
                  <input type="number" className="input" style={{ flex: 1 }}
                    value={f.depositWindowValue} onChange={e => set({ depositWindowValue: e.target.value })} />
                  <select className="select" style={{ width: 100 }} value={f.depositWindowUnit} onChange={e => set({ depositWindowUnit: e.target.value })}>
                    <option>Hours</option>
                    <option>Days</option>
                  </select>
                </div>
                <BwHelp>Time allowed to make that deposit after wagering is complete</BwHelp>
              </div>
              <div>
                <BwFieldLabel>Unmet Deposit Behavior</BwFieldLabel>
                <select className="select" style={{ width: "100%" }} value={f.unmetDepositBehavior} onChange={e => set({ unmetDepositBehavior: e.target.value })}>
                  {BW_UNMET_DEPOSIT_BEHAVIOR.map(o => <option key={o}>{o}</option>)}
                </select>
                <BwHelp>Applied automatically if the deposit window expires without the required deposit</BwHelp>
              </div>
            </div>
          )}
        </BwRow>
      )}
      <BwRow key="manual-forfeit" label="Manual Forfeit Allowed">
        <label className="bw-checkbox-row"><input type="checkbox" checked={f.manualForfeit} onChange={e => set({ manualForfeit: e.target.checked })} /> Allow player to forfeit bonus manually</label>
        <BwHelp>When enabled, players can forfeit this program's bonus from the app.</BwHelp>
      </BwRow>
      <BwRow key="forfeit-amount" label="Forfeit Amount" disabled>
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value="" disabled onChange={() => {}} />
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
      <BwRow key="forfeit-expiry" label="Forfeit on Expiry" disabled>
        <label className="bw-checkbox-row"><input type="checkbox" disabled /> Forfeit bonus when time expires</label>
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
    </>
  );

  const renderStep7 = () => (
    <>
      <BwRow label="Per Player Max Claims">
        <input type="number" className="input" style={{ width: "100%" }} value={f.perPlayerMaxClaims} onChange={e => set({ perPlayerMaxClaims: e.target.value })} />
        <BwHelp>Limits number of awards per player</BwHelp>
      </BwRow>
      <BwRow label="Max Active Bonuses Per Player in This Category">
        <input type="number" className="input" style={{ width: "100%" }} value={f.maxActivePerCategory} onChange={e => set({ maxActivePerCategory: e.target.value })} />
        <BwHelp>If the bonus type matches the category of the other bonus, user can not claim it (MVP recommended: 1)</BwHelp>
      </BwRow>
      <BwRow label="Global Max Issued Count">
        <input type="number" className="input" style={{ width: "100%" }} value={f.globalMaxCount} onChange={e => set({ globalMaxCount: e.target.value })} />
        <BwHelp>Stops issuance after count reached (0 = Unlimited by default)</BwHelp>
      </BwRow>
      <BwRow label="Global Max Issued Bonus Value">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.globalMaxValue} onChange={e => set({ globalMaxValue: e.target.value })} />
        <BwHelp>Stops issuance after total issued bonus value budget reached</BwHelp>
      </BwRow>
      <BwRow label="Global Max Issued Converted Bonus Value">
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.globalMaxConverted} onChange={e => set({ globalMaxConverted: e.target.value })} />
        <BwHelp>Stops issuance after converted bonus to real balance value budget reached</BwHelp>
      </BwRow>
      <BwRow label="Daily Issuance Cap (Count)" disabled>
        <input type="number" className="input" style={{ width: "100%" }} disabled />
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
      <BwRow label="Daily Issuance Cap (Value)" disabled>
        <input type="number" step="0.01" className="input" style={{ width: "100%" }} disabled />
        <BwHelp>Currently disabled</BwHelp>
      </BwRow>
    </>
  );

  const STEP_RENDERERS = [renderStep1, renderStep2, renderStep3, renderStep4, renderStep5, renderStep6, renderStep7];

  return (
    <div className="page report-page bonus-programs bw-wizard">
      <div className="page__title" style={{ color: "var(--p-700)" }}>Create New Bonus Program</div>
      <button className="btn btn--ghost btn--sm" style={{ marginTop: 6, marginBottom: 18 }} onClick={onBack}>
        <Icon name="chevron_left" size={13} /> Back to List
      </button>

      <div className="bw-layout">
        <div className="bw-main panel">
          <div className="bw-tabs">
            {BW_STEPS.map((s, i) => (
              <button key={s} className={`bw-tab ${step === i ? "active" : ""}`} onClick={() => setStep(i)}>{s}</button>
            ))}
          </div>
          <div className="bw-body">{STEP_RENDERERS[step]()}</div>
          <div className="bw-foot">
            <button className="btn btn--ghost" disabled={step === 0} onClick={() => setStep(Math.max(0, step - 1))}>Back</button>
            <button className="btn btn--ghost" disabled={step === BW_STEPS.length - 1} onClick={() => setStep(Math.min(BW_STEPS.length - 1, step + 1))}>Next</button>
            {/* One label for three modes. A read-only open renders no save at
                all rather than a disabled one — a greyed Save invites a click
                and then explains itself, which is a worse answer than not being
                there. */}
            {row && row._readOnly
              ? <span className="bw-readonly-note">Viewing a stored programme — reopen with Edit to change it.</span>
              : <button className="btn btn--primary" onClick={handleCreate} disabled={busy}>
                  {busy ? "Saving…" : (row ? "Save changes" : "Create Bonus Program")}
                </button>}
            <button className="btn btn--ghost" onClick={onBack} disabled={busy}>Cancel</button>
          </div>
        </div>

        <div className="panel bw-side">
          <div className="bw-side-title">Trigger Configuration</div>
          <BwRow label="Trigger Type" required>
            <select className="select" style={{ width: "100%" }} value={f.triggerType} onChange={e => set({ triggerType: e.target.value })}>
              <option value="">Select trigger type</option>
              {BW_TRIGGER_TYPES.map(t => <option key={t}>{t}</option>)}
            </select>
            <BwHelp>Select the trigger type for this bonus program</BwHelp>
          </BwRow>

          {f.triggerType === "Signup / Registration Trigger" && (
            <div className="bw-dynamic-panel">
              <div className="bw-dynamic-title">Signup trigger</div>
              <div style={{ fontSize: 12, color: "#7E8299", lineHeight: 1.6 }}>Fires once when a new player completes registration on the selected skin. No additional configuration required.</div>
            </div>
          )}
          {f.triggerType === "Deposit Approved % Match Trigger" && (
            <div className="bw-dynamic-panel">
              <div className="bw-dynamic-title">Deposit trigger configuration</div>
              <BwFieldLabel>Applies to Deposit #</BwFieldLabel>
              <select className="select" style={{ width: "100%", marginBottom: 14 }} value={f.depositNumber} onChange={e => set({ depositNumber: e.target.value })}>
                {["Any", "1st", "2nd", "3rd", "4th+"].map(d => <option key={d}>{d}</option>)}
              </select>
              <BwFieldLabel>Minimum Deposit Amount</BwFieldLabel>
              <input type="number" step="0.01" className="input" style={{ width: "100%" }} value={f.minDepositAmount} onChange={e => set({ minDepositAmount: e.target.value })} />
            </div>
          )}
          {f.triggerType === "Bet-Based Bonus Calculation Trigger" && (
            <div className="bw-dynamic-panel">
              <div className="bw-dynamic-title">Bet-based trigger configuration</div>
              <BwFieldLabel>Minimum Bet Count</BwFieldLabel>
              <input type="number" className="input" style={{ width: "100%", marginBottom: 14 }} value={f.minBetCount} onChange={e => set({ minBetCount: e.target.value })} />
              <BwFieldLabel>Evaluation Window (Hours)</BwFieldLabel>
              <input type="number" className="input" style={{ width: "100%" }} value={f.evalWindowHours} onChange={e => set({ evalWindowHours: e.target.value })} />
            </div>
          )}
          {f.triggerType === "Net-loss Cashback Trigger" && (
            <div className="bw-dynamic-panel">
              <div className="bw-dynamic-title">Net-loss cashback configuration</div>
              <BwFieldLabel>Evaluation Period</BwFieldLabel>
              <select className="select" style={{ width: "100%", marginBottom: 14 }} value={f.netLossPeriod} onChange={e => set({ netLossPeriod: e.target.value })}>
                {["Daily", "Weekly", "Monthly"].map(p => <option key={p}>{p}</option>)}
              </select>
              <BwFieldLabel>Cashback Percentage (%)</BwFieldLabel>
              <input type="number" className="input" style={{ width: "100%" }} value={f.cashbackPct} onChange={e => set({ cashbackPct: e.target.value })} />
            </div>
          )}
          {f.triggerType === "Manual / CRM Assignment Trigger" && (
            <div className="bw-dynamic-panel">
              <div className="bw-dynamic-title">Manual / CRM trigger</div>
              <div style={{ fontSize: 12, color: "#7E8299", lineHeight: 1.6 }}>
                No automatic in-app condition — eligibility is decided externally (CRM, support, affiliate tools) and the bonus is granted through the assignment API. Typically used for one-off or discretionary promos like No Deposit Bonus.
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

window.BonusProgramWizard = BonusProgramWizard;
