// NO PROD JSON API (bucket C) — mock until backend (see PROD_API_INVENTORY.md)
// Represents: GET /withdraw/ · WithdrawController::index — see docs/ISYSTEM_REFERENCE.md §Batch 9.4
/* Withdraw — the operator-initiated payout, and the counterpart of /deposit
   (built as HostDeposit.jsx). NEW SCREEN (Batch 9). Reached from a player row,
   not the sidebar.

   The index action is two lines:
       $methods = array();
       return view('admin.withdraw.index', array("methods" => $methods));
   It passes an empty array the blade ignores — the blade calls
   WithdrawMethodsController::getWithdrawMethods('', Auth::user()->skin_id, 1)
   itself and renders one card per enabled method, each with a "Withdraw now"
   link to /withdraw/{method_code}/.

   showWithdrawForm($method) then hard-switches on THREE method codes:
       bank    -> withdraw/forms/bank
       crypto  -> withdraw/forms/crypto   (+ counts non-empty skins.cryptoio_addresses
                  against CryptoIo::cryptoio_coins(); zero configured -> die("method not enabled"))
       pix     -> withdraw/forms/pix
   Anything else, or a method not enabled for the skin, also hits
   die("method not enabled") — a bare string, not a 404.

   REAL DEFECT, surfaced on screen: the index renders cards from
   getWithdrawMethods, but only those three codes have a template. A skin with
   a fourth method enabled shows a card whose "Withdraw now" link dies.
   <!-- SUGGESTION: filter the index to the three implemented codes, or return a real 404 instead of die(). -->

   THIS MOVES MONEY. Per the standing rule in docs/REAL_VS_MOCK.md §4 —
   "anything that moves money or writes an audit row is never simulated" — the
   forms are built to the field level and the submit is DISABLED behind
   NoBackend, naming POST /withdraw/doWithdraw/. Everything else on the screen
   (method choice, validation, the balance read) is real behaviour.

   Deliberately NOT added: a withdrawal history (that is /withdraw/myrequests/,
   already built as HostReportMyWithdrawals.jsx), an approval queue (that is
   /withdrawrequests/, HostWithdraws.jsx), or a cancel action. */

/* THE FOUR METHODS WERE TRANSCRIBED, and one of them was invented to
   demonstrate a defect. That was defensible as documentation and indefensible
   as a screen: an operator opening this page saw a "Cash voucher" card their
   brand may not have, and did not see the methods it does.

   The real list is `skin_payment_methods` inner-joined to `payment_methods`
   where flow = 'withdrawal' and the row is enabled, for the SIGNED-IN
   operator's skin — which is exactly what getWithdrawMethods('', skin_id, 1)
   does. The defect is still surfaced, but only when it is real: a method whose
   code has no form in showWithdrawForm.

   THE PAYOUT FIELDS STAY DECLARED HERE. `withdrawal_requests.payout_details` is
   jsonb — the schema does not say what a bank payout needs and neither does the
   platform; the three field sets below are the three blade templates
   (withdraw/forms/{bank,crypto,pix}), which IS where that shape is defined
   upstream. Keyed by method CODE, so a brand whose bank method is called
   something else still gets the bank form. */
const HWT_FORMS = {
  bank: {
    blurb: "Payout to a bank account. The operator supplies the account details.",
    fields: [
      { key: "iban", label: "IBAN / Account number", required: true, placeholder: "BR15 0000 0000 0000 0000 0000 000" },
      { key: "holder", label: "Account holder", required: true },
      { key: "bank_name", label: "Bank name", required: true },
      { key: "branch", label: "Branch / Agency" },
      { key: "swift", label: "SWIFT / BIC" },
      { key: "document", label: "Holder document (CPF/CUIT)", required: true },
    ] },
  crypto: {
    blurb: "Payout to a wallet address. Only coins with an address configured on the skin are offered.",
    fields: [
      { key: "coin", label: "Coin", required: true, type: "select", options: [] },
      { key: "address", label: "Wallet address", required: true, placeholder: "T…" },
      { key: "network_memo", label: "Memo / Tag (if the network needs one)" },
    ] },
  pix: {
    blurb: "Instant Brazilian payout against a PIX key.",
    fields: [
      { key: "pix_key_type", label: "Key type", required: true, type: "select",
        options: ["CPF", "CNPJ", "Email", "Phone", "Random key"] },
      { key: "pix_key", label: "PIX key", required: true },
      { key: "holder", label: "Key holder", required: true },
    ] },
};

/* `HWT_CRYPTO_CONFIGURED` was three hardcoded coin names standing in for
   skins.cryptoio_addresses — a column this schema does not have. There is
   therefore NOTHING to read, and the honest thing is to say so on the screen
   rather than to offer five coins nobody configured.
   UNCLEAR-19: which coins a skin can pay out to has no home in this schema.
   <!-- SUGGESTION: crypto payout addresses need a table — one row per (skin,
        coin, address) — before the crypto form can offer a coin list that means
        anything. Until then the coin is a free-text field and the operator is
        told why. --> */

const hwtToast = (t, d) => window.hrsToast && window.hrsToast(t, d);

const HwtMethodCard = ({ m, onPick }) => (
  <div className={`panel hwt-card${m.template ? "" : " hwt-card--broken"}`}>
    <div className="hwt-card__name">{m.name}</div>
    <div className="hwt-card__blurb">{m.blurb}</div>
    {/* The configured bounds, from skin_payment_methods. Absent means absent —
        a method with no max is not a method with a max of nothing. */}
    <div className="hwt-card__lims">
      {m.min == null && m.max == null
        ? <>No amount limits configured</>
        : <>{m.min != null ? `min ${Number(m.min).toFixed(2)}` : "no minimum"}
            {" · "}
            {m.max != null ? `max ${Number(m.max).toFixed(2)}` : "no maximum"}</>}
    </div>
    {m.template ? (
      <button className="hrs-btn hrs-btn--filters hwt-card__go" onClick={() => onPick(m)}>
        Withdraw now <Icon name="chevron_right" size={13}/>
      </button>
    ) : (
      <>
        <NoBackend
          need={`a showWithdrawForm case for "${m.code}"`}
          what="Withdraw now"
          className="hrs-btn hwt-card__go">
          Withdraw now <Icon name="chevron_right" size={13}/>
        </NoBackend>
        <div className="hwt-card__warn">
          <Icon name="alert" size={12}/> <b>This method is enabled with no payout form.</b> Upstream the
          index lists every enabled method but <code>showWithdrawForm</code> implements only
          <code> bank</code>, <code>crypto</code> and <code>pix</code> — so this card's link answers the
          bare string <code>method not enabled</code>. Disabled here rather than reproduced.
        </div>
      </>
    )}
  </div>
);

const HwtForm = ({ method, onBack, me, wallet, onDone }) => {
  const [vals, setVals] = React.useState({});
  const [amount, setAmount] = React.useState("");
  const [errors, setErrors] = React.useState([]);
  const hwtSave = useHrsSave([]);

  const form = HWT_FORMS[method.code] || { fields: [] };
  /* THE COIN LIST HAS NOTHING BEHIND IT. `skins.cryptoio_addresses` does not
     exist in this schema, so a select would be a list of coins nobody
     configured. Free text, with the reason on screen. */
  const fields = form.fields.map(f => (f.key === "coin" ? { ...f, type: "text", options: null } : f));

  /* AVAILABLE, NOT BALANCE. `balance_withdrawable` is what may leave; `balance`
     is a column nothing writes (UNCLEAR-12). A screen that offered `balance`
     would let an operator raise a request the RPC then refuses. */
  const available = wallet ? Number(wallet.balance_withdrawable) : null;
  const currency = (wallet && wallet.users && wallet.users.currency) || (me && me.currency) || "";
  const min = method.min == null ? null : Number(method.min);
  const max = method.max == null ? null : Number(method.max);

  const validate = () => {
    const out = [];
    const amt = parseFloat(amount);
    if (!amount) out.push("Amount is required.");
    else if (isNaN(amt) || amt <= 0) out.push("Amount must be a positive number.");
    /* A LIMIT THAT IS NOT CONFIGURED IS NOT A LIMIT OF ZERO. min_amount and
       max_amount are nullable on skin_payment_methods; treating a null max as 0
       would refuse every withdrawal and read like a balance problem. */
    else if (min != null && amt < min) out.push(`Minimum withdrawal is ${min.toFixed(2)} ${currency}.`);
    else if (max != null && amt > max) out.push(`Maximum withdrawal is ${max.toFixed(2)} ${currency}.`);
    else if (available != null && amt > available) out.push("Amount exceeds the withdrawable balance.");
    fields.filter(f => f.required).forEach(f => {
      if (!vals[f.key]) out.push(`${f.label} is required.`);
    });
    setErrors(out);
    return out.length === 0;
  };

  /* THE SUBMIT IS REAL NOW, and it is worth saying why it was not.

     This screen was written before supabase/035. The rule it was obeying —
     "anything that moves money is never simulated" — is still the rule; what
     changed is that there is now a real path. `request_withdrawal` creates the
     request and DEBITS in the same transaction, which is the decision 035
     records: the money leaves when the request is raised, not when it is
     approved, because between the two there is a queue and a balance the holder
     can otherwise spend down twice.

     Everything but the amount is read or enforced server-side — skin, currency,
     whether the balance covers it — and a refusal rolls the request back with
     the debit. */
  const submit = () => {
    if (!validate()) return;
    if (!me || !me.id) return;
    hwtSave.run(() => window.sb.requestWithdrawal({
      userId: me.id,
      amount: parseFloat(amount),
      methodId: method.id,
      payoutDetails: vals,
      recipientName: vals.holder || vals.pix_key || null,
    }), {
      done: "Withdrawal requested",
      fail: "The withdrawal was not requested",
    }).then(res => { if (res && res.ok) { setAmount(""); setVals({}); onDone && onDone(); } });
  };

  return (
    <>
      <div className="hwt-formhead">
        <button className="hrs-btn" onClick={onBack}><Icon name="chevron_left" size={13}/> Back to methods</button>
        <div className="hwt-formhead__t">{method.name}</div>
        <code className="hwt-formhead__r">GET /withdraw/{method.code}/</code>
      </div>

      {method.code === "crypto" && (
        <div className="hwt-cryptonote">
          <Icon name="alert" size={13}/>
          <span>
            Upstream this form lists only coins with a non-empty address in
            <code> skins.cryptoio_addresses</code>, and calls <code>die("method not enabled")</code>
            when none is configured. <b>This schema has no home for those addresses</b>, so the coin is
            a free-text field rather than a list of coins nobody set up.
          </span>
        </div>
      )}

      {errors.length > 0 && (
        <div className="hma-errs" role="alert">
          <div className="hma-errs__h"><Icon name="alert" size={13}/> Please correct the following errors</div>
          <ul>{errors.map((e, i) => <li key={i}>{e}</li>)}</ul>
        </div>
      )}

      <HrsSection title="Amount" sub={available == null
        ? "Balance not loaded — the amount cannot be checked against it"
        : `Withdrawable: ${available.toLocaleString("en-US", { minimumFractionDigits: 2 })} ${currency}`}>
        <div className="panel hma-card">
          <div className="hma-field">
            <label className="hma-label"><span className="hma-req">*</span> Amount ({currency || "—"})</label>
            <input className="input hma-input" inputMode="decimal" value={amount}
              placeholder={min != null || max != null
                ? `${min != null ? min.toFixed(2) : "no minimum"} – ${max != null ? max.toFixed(2) : "no maximum"}`
                : "No limits configured for this method"}
              onChange={(e) => setAmount(e.target.value)} />
            <div className="hma-hint">
              {/* NOT "limits come from the configuration" AS A PROMISE. Either
                  they are configured and shown, or they are not and that is
                  said — an unconfigured limit is not a limit of zero. */}
              {min == null && max == null
                ? <>This method has no <code>min_amount</code> or <code>max_amount</code> on
                    <code> skin_payment_methods</code>, so only the balance bounds it.</>
                : <>Limits come from this skin's configuration of the method, not from this screen.</>}
            </div>
          </div>
        </div>
      </HrsSection>

      <HrsSection title="Payout details">
        <div className="panel hma-card">
          {fields.length === 0 && (
            <div className="hma-hint">
              <code>showWithdrawForm</code> has no template for <code>{method.code}</code>, so there are no
              payout fields to collect. The details go to <code>withdrawal_requests.payout_details</code>,
              which is jsonb — the shape of a payout is defined by the form, and this method has none.
            </div>
          )}
          {fields.map(f => (
            <div className="hma-field" key={f.key}>
              <label className="hma-label">{f.required && <span className="hma-req">*</span>} {f.label}</label>
              {f.type === "select" ? (
                <select className="select hma-input" value={vals[f.key] || ""}
                  onChange={(e) => setVals(v => ({ ...v, [f.key]: e.target.value }))}>
                  <option value="">Select an option</option>
                  {f.options.map(o => <option key={o} value={o}>{o}</option>)}
                </select>
              ) : (
                <input className="input hma-input" placeholder={f.placeholder || ""}
                  value={vals[f.key] || ""}
                  onChange={(e) => setVals(v => ({ ...v, [f.key]: e.target.value }))} />
              )}
            </div>
          ))}
        </div>
      </HrsSection>

      <div className="hma-actions">
        <button className="hrs-btn hrs-btn--filters hma-save" onClick={submit}
          disabled={hwtSave.busy || !me || !me.id || fields.length === 0}>
          <Icon name="check" size={14}/> {hwtSave.busy ? "Requesting…" : "Submit withdrawal"}
        </button>
        <button className="hrs-btn" onClick={() => {
          if (validate()) hwtToast("Validation passed", "Every required field is present and the amount is inside the configured limits and the withdrawable balance.");
        }}>
          <Icon name="check" size={13}/> Check this form
        </button>
        <span className="hma-hint">
          {/* THE DEBIT IS THE HEADLINE. An operator pressing this should know
              the money leaves now, not on approval. */}
          Submitting calls <code>request_withdrawal</code>, which creates the request <b>and debits the
          balance in the same transaction</b> — the money leaves when the request is raised, not when it
          is approved. A refusal rolls back both.
        </span>
      </div>
    </>
  );
};

const HostWithdrawTo = () => {
  const [picked, setPicked] = React.useState(null);

  /* WHOSE withdrawal this is. The real index reads Auth::user()->skin_id and
     the payout comes off the signed-in operator's own balance — this screen is
     the shop cashing out, not an operator paying a player. */
  const [hwtMe, setHwtMe] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    Promise.resolve(window.sb.me()).then(r => { if (alive && r && r.ok) setHwtMe(r.data); });
    return () => { alive = false; };
  }, []);

  const hwtMethodFeed = useHrsFetch(
    () => (hwtMe && hwtMe.skin_id
      ? window.sb.list("skinPaymentMethods", {
          limit: 200, filters: { skin: hwtMe.skin_id, flow: "withdrawal", enabled: true } })
      : Promise.resolve({ ok: true, data: [] })), [hwtMe && hwtMe.skin_id]);

  const hwtWalletFeed = useHrsFetch(
    () => (hwtMe && hwtMe.id
      ? window.sb.list("balances", { limit: 1, filters: { user: hwtMe.id } })
      : Promise.resolve({ ok: true, data: [] })), [hwtMe && hwtMe.id]);
  const wallet = (hwtWalletFeed.data || [])[0] || null;

  /* Requests already in the queue, so the operator can see what is pending
     against the same balance before raising another. */
  const hwtPendingFeed = useHrsFetch(
    () => (hwtMe && hwtMe.id
      ? window.sb.list("withdrawalRequests", { limit: 50, filters: { user: hwtMe.id, status: 0 } })
      : Promise.resolve({ ok: true, data: [] })), [hwtMe && hwtMe.id]);
  const pendingCount = (hwtPendingFeed.data || []).length;

  /* THE DEFECT, SURFACED ONLY WHEN IT IS REAL. Upstream the index lists every
     enabled method and showWithdrawForm implements three codes; a fourth
     enabled method renders a card whose link answers the bare string
     "method not enabled". The prototype used to ship an invented fourth method
     to demonstrate that. Now it flags whichever real methods have no form. */
  const methods = React.useMemo(() => (hwtMethodFeed.data || []).map(r => ({
    id: r.method_id,
    code: r.method ? r.method.code : String(r.method_id),
    name: r.method ? r.method.name : `Method ${r.method_id}`,
    min: r.min_amount, max: r.max_amount,
    template: r.method && HWT_FORMS[r.method.code] ? r.method.code : null,
    blurb: (r.method && HWT_FORMS[r.method.code] && HWT_FORMS[r.method.code].blurb)
      || "Enabled for this skin. showWithdrawForm has no case for this code, so upstream its link fails.",
  })), [hwtMethodFeed.data]);

  const busy = hwtMethodFeed.loading || hwtWalletFeed.loading;
  const err = hwtMethodFeed.error || hwtWalletFeed.error;

  return (
    <HrsShell
      title="Withdraw"
      subtitle="Operator-initiated payout — the counterpart of Deposit."
      gate={["auth", "admin", "2fa", "g2fa"]}
      gateNote={<> The index applies no role check of its own. Which methods appear is decided entirely by <code>getWithdrawMethods('', skin_id, 1)</code> — the enabled-only flag on the signed-in operator's skin.</>}
      explainer={{
        title: "What this screen is, in plain English",
        bullets: [
          "Step one is a method chooser, nothing more. The real index action is two lines and passes an empty array the blade ignores.",
          "Only bank, crypto and pix have a form upstream. A method enabled without one gets a card whose link fails — flagged here when it happens.",
          "The payout comes off YOUR balance, not a player's. This screen is the shop cashing out.",
          "Submitting debits the balance in the same transaction as it creates the request. The money leaves when the request is raised, not when it is approved.",
        ],
      }}
    >
      {busy && <HrsSkeleton rows={3} cols={3} />}
      {!busy && err && <HrsError error={err} onRetry={() => { hwtMethodFeed.retry(); hwtWalletFeed.retry(); }} />}

      {!busy && !err && !picked && (
        <HrsSection title="Select a withdrawal method"
          sub={`${methods.length} method(s) enabled for this skin${pendingCount ? ` · ${pendingCount} request(s) already pending against this balance` : ""}`}>
          {methods.length === 0 ? (
            <HrsEmpty>
              No withdrawal method is enabled for this skin. Enable one on the skin's Withdrawal methods
              tab — a row in <code>skin_payment_methods</code> is what makes a method available, and
              without one there is nothing for this screen to offer.
            </HrsEmpty>
          ) : (
            <div className="hwt-grid">
              {methods.map(m => <HwtMethodCard key={m.id} m={m} onPick={setPicked} />)}
            </div>
          )}
        </HrsSection>
      )}

      {!busy && !err && picked && (
        <HwtForm method={picked} me={hwtMe} wallet={wallet}
          onBack={() => setPicked(null)}
          onDone={() => { hwtWalletFeed.retry(); hwtPendingFeed.retry(); setPicked(null); }} />
      )}
    </HrsShell>
  );
};

window.HostWithdrawTo = HostWithdrawTo;
