// =================================================================
// pw-screens-payouts.jsx — Publisher Admin · Payouts
//
//   What this publisher has earned from advertiser spend on its own
//   inventory, and the statements settling it. The revenue share is
//   frozen per event when the spend is recorded, so a later rate change
//   never restates past earnings — statements are reproducible.
// =================================================================

const STATEMENT_TONE = { pending: "warn", approved: "info", paid: "good", void: "neutral" };

const bpsToPct = (bps) => `${(Number(bps || 0) / 100).toFixed(Number(bps || 0) % 100 === 0 ? 0 : 2)}%`;

// PRD §5. The three rows differ by who found the advertiser, which is what decides the rate — the
// labels say that rather than naming the internal context codes.
const RATE_ROWS = [
  { context: "direct", label: "Advertisers DropCap brought you" },
  { context: "brokered", label: "Sold by another publisher onto your site" },
  { context: "own", label: "Your own advertisers, your own site" },
];
const rateFor = (earnings, context) =>
  (earnings?.commission_rates || []).find((r) => r && r.context === context) || null;

const PayoutMethodCard = ({ config, onSaved, notify }) => {
  const [method, setMethod] = React.useState(config?.payout_method || "");
  const [details, setDetails] = React.useState("");
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => { setMethod(config?.payout_method || ""); }, [config]);

  const save = async () => {
    setSaving(true);
    try {
      const body = {
        // The rate is an operator decision — the API ignores what is sent here and keeps the
        // configured share. It is included only because the endpoint's schema requires it.
        revenue_share_bps: config?.revenue_share_bps ?? 5000,
        payout_method: method || null,
        minimum_payout: config?.minimum_payout,
      };
      if (details.trim()) {
        body.payout_details = details.trim().startsWith("{")
          ? JSON.parse(details)
          : { account: details.trim() };
      }
      await window.__api.write("savePayoutConfig", null, { body });
      setDetails("");
      notify("Payout method saved.");
      onSaved();
    } catch (e) {
      notify(e.message || "Could not save payout details.", "bad");
    } finally {
      setSaving(false);
    }
  };

  return (
    <Card title="Payout method" subtitle="Where we send your earnings.">
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div>
            <label className="pw-lbl">Method</label>
            <Select value={method} onChange={setMethod} placeholder="Choose a method"
              options={[
                { label: "Bank transfer", value: "bank" },
                { label: "PayPal", value: "paypal" },
                { label: "Stripe Connect", value: "stripe" },
              ]}/>
          </div>
          <div>
            <label className="pw-lbl">Account details</label>
            <Input value={details} type="password" placeholder={config?.payout_details_set ? "•••••••• (saved)" : "Account or email"}
              onChange={(e) => setDetails(e.target.value)}/>
          </div>
        </div>
        <div style={{ fontSize: 12, color: "var(--muted)" }}>
          Stored details are never shown again — leave the field blank to keep what is already saved.
        </div>
        <div style={{ display: "flex", justifyContent: "flex-end" }}>
          <Btn onClick={save} disabled={saving || !method}>{saving ? "Saving…" : "Save"}</Btn>
        </div>
      </div>
    </Card>
  );
};

const PayoutsScreen = () => {
  const [earnings, setEarnings] = React.useState(null);
  const [config, setConfig] = React.useState(null);
  const [statements, setStatements] = React.useState([]);
  const [verification, setVerification] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [toast, setToast] = React.useState(null);

  const notify = (message, tone = "good") => {
    setToast({ message, tone });
    setTimeout(() => setToast(null), 4000);
  };

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const [e, c, s, v] = await Promise.all([
        window.__api.read("getPayoutEarnings"),
        window.__api.read("getPayoutConfig"),
        window.__api.read("listPayoutStatements"),
        window.__api.read("getVerification"),
      ]);
      setEarnings(e || {});
      setConfig(c?.config || c || {});
      setStatements(s?.statements || (Array.isArray(s) ? s : []));
      setVerification(v || null);
    } catch (err) {
      notify(err.message || "Could not load payouts.", "bad");
    } finally {
      setLoading(false);
    }
  }, []);

  React.useEffect(() => { load(); }, [load]);

  const currency = earnings?.currency || config?.currency || "USD";
  const money = (n) => fmt.currency(Number(n || 0), currency);
  const bal = earnings?.balance || null;

  if (loading) {
    return (
      <Screen>
        <PageHead icon={IconReceipt} title="Payouts"/>
        <Card><div style={{ padding: 24, textAlign: "center", color: "var(--muted)" }}>Loading…</div></Card>
      </Screen>
    );
  }

  return (
    <Screen>
      <PageHead icon={IconReceipt} title="Payouts"
        subtitle="Your share of what advertisers spent on your inventory. Earnings accrue daily and are settled by statement."/>

      {/* PRD §7. Rendered above the balance and never in place of it: the reminder must never hide
          what a publisher has earned. The stage comes from the server, so how hard we press is a
          policy decision rather than a per-screen one. */}
      <VerificationReminder reminder={verification?.reminder} currency={currency}
        onVerify={() => {
          const el = document.getElementById("pw-verify-card");
          if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
        }}
        onAcknowledged={load} notify={notify}/>

      <KpiGrid>
        {/* PRD §7: earnings are visible the moment they accrue; the hold governs withdrawal only.
            available + held is always exactly unpaid_earnings — one number, shown two ways — so the
            older total is the fallback when the breakdown is unavailable rather than a rival figure. */}
        <StatCard label="Available now" value={money(bal ? bal.available : earnings?.unpaid_earnings)}
          sub={bal && bal.held > 0
            ? `${money(bal.held)} still held`
            : "Earned, not yet on a statement"}/>
        <StatCard label="This month" value={money(earnings?.month_earnings)}/>
        <StatCard label="Lifetime earnings" value={money(earnings?.lifetime_earnings)}
          sub="Your share of advertiser spend"/>
        <StatCard label="Gross advertiser spend" value={money(earnings?.lifetime_gross)}
          sub={`${fmt.number(earnings?.clicks || 0)} clicks · ${fmt.number(earnings?.impressions || 0)} impressions`}/>
      </KpiGrid>

      <Card title="How this is calculated">
        {/* What a publisher keeps depends on who found the advertiser, so there is no single share to
            quote. Rendered from `commission_rates` on the response, never hardcoded: the rates are
            effective-dated rows in the database and a number written into this screen would drift
            away from the one actually being paid. */}
        {RATE_ROWS.some(r => rateFor(earnings, r.context)) ? (
          <div style={{ display: "grid", gap: 6, marginBottom: 12 }}>
            {RATE_ROWS.map(r => {
              const rate = rateFor(earnings, r.context);
              if (!rate) return null;
              return (
                <div key={r.context} style={{ display: "flex", justifyContent: "space-between", gap: 12,
                  fontSize: 13, padding: "6px 0", borderBottom: "1px solid var(--line)" }}>
                  <span style={{ color: "var(--ink-2)" }}>{r.label}</span>
                  <strong style={{ whiteSpace: "nowrap" }}>you keep {bpsToPct(rate.host_bps)}</strong>
                </div>
              );
            })}
          </div>
        ) : null}
        <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.6 }}>
          What you keep depends on who brought the advertiser. Your share is worked out at the moment
          each click or impression is billed and stored with it, so changing a rate later never
          rewrites what you have already earned.
          Statements sweep whole days only — the current day keeps accruing until it closes.
          {bal && bal.held > 0 ? (
            <> <strong>{money(bal.held)}</strong> of your balance is still inside the payout hold
            {bal.next_release_at ? <> — the next part of it releases on <strong>{bal.next_release_at}</strong></> : null}.
            Verifying your account shortens the hold, and it is free.</>
          ) : null}
          {config?.minimum_payout ? (
            <> A statement is issued once the balance clears <strong>{money(config.minimum_payout)}</strong>; below
            that it rolls into the next period.</>
          ) : null}
        </div>
      </Card>

      <div id="pw-verify-card">
        <VerificationCard verification={verification?.verification} onSaved={load} notify={notify}/>
      </div>

      <PayoutMethodCard config={config} onSaved={load} notify={notify}/>

      <Card title="Statements" subtitle="Each one settles a closed period." padded={false}>
        {statements.length === 0 ? (
          <EmptyState icon={<IconReceipt size={26}/>} title="No statements yet"
            description="Once your unpaid balance clears the minimum, a statement is issued for the period."/>
        ) : (
          <div className="pw-stmt-list">
            <div className="pw-stmt-row head">
              <span>Period</span><span>Impressions</span><span>Clicks</span>
              <span>Gross</span><span>Your earnings</span><span>Status</span>
            </div>
            {statements.map(s => (
              <div className="pw-stmt-row" key={s.statement_id}>
                <span className="pw-stmt-period">{s.period_start} → {s.period_end}</span>
                <span className="pw-stmt-num">{fmt.number(s.impressions)}</span>
                <span className="pw-stmt-num">{fmt.number(s.clicks)}</span>
                <span className="pw-stmt-num">{fmt.currency(s.gross_revenue, s.currency)}</span>
                <span className="pw-stmt-num strong">{fmt.currency(s.publisher_earnings, s.currency)}</span>
                <span>
                  <Pill tone={STATEMENT_TONE[s.status] || "neutral"}>{s.status}</Pill>
                  {s.reference && <div className="pw-stmt-ref">{s.reference}</div>}
                </span>
              </div>
            ))}
          </div>
        )}
      </Card>

      {toast && <div className={`pw-toast ${toast.tone}`} role="status">{toast.message}</div>}

      <style>{`
        .pw-stmt-list { display: flex; flex-direction: column; }
        .pw-stmt-row { display: grid; grid-template-columns: 1.6fr 1fr 0.8fr 1fr 1fr 1fr; gap: 12px; align-items: center;
          padding: 11px var(--pad); border-top: 1px solid var(--line-2); font-size: 13px; }
        .pw-stmt-row.head { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
          font-weight: 600; border-top: 0; }
        .pw-stmt-period { font-family: var(--mono); font-size: 12px; }
        .pw-stmt-num { font-family: var(--mono); font-size: 12.5px; }
        .pw-stmt-num.strong { font-weight: 600; color: var(--ink); }
        .pw-stmt-ref { font-size: 11px; color: var(--muted); margin-top: 3px; font-family: var(--mono); }
        @media (max-width: 900px) {
          .pw-stmt-row { grid-template-columns: 1fr 1fr; }
          .pw-stmt-row.head { display: none; }
        }
      `}</style>
    </Screen>
  );
};

Object.assign(window, { PayoutsScreen });
