// =================================================================
// pw-screens-op-payouts.jsx — Platform Operator · Publisher Payouts
//
//   Two operator-only jobs the publisher cannot do for itself:
//   setting each publisher's revenue share, and settling the
//   statements that share produces.
// =================================================================

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

// Statuses a statement can move to from where it is now. Paid and void are terminal: a settled
// statement is a financial record, so it is never edited back into an open state.
const NEXT_STATUSES = {
  pending: ["approved", "void"],
  approved: ["paid", "void"],
  paid: [],
  void: [],
};

const pctFromBps = (bps) => Number(bps || 0) / 100;
const bpsFromPct = (pct) => Math.round(Number(pct || 0) * 100);

// ---------------------------------------------------------------- settle

const SettleDialog = ({ statement, nextStatus, publisherName, onClose, onDone, notify }) => {
  const [reference, setReference] = React.useState("");
  const [notes, setNotes] = React.useState("");
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => { setReference(""); setNotes(""); }, [statement, nextStatus]);

  const confirm = async () => {
    setBusy(true);
    try {
      await window.__api.write("setPayoutStatementStatus", null, {
        params: { statement_id: statement.statement_id },
        body: { status: nextStatus, reference: reference.trim() || null, notes: notes.trim() || null },
      });
      notify(`Statement marked ${nextStatus}.`);
      onDone();
      onClose();
    } catch (e) {
      notify(e.message || "Could not update the statement.", "bad");
    } finally {
      setBusy(false);
    }
  };

  const isPayment = nextStatus === "paid";

  return (
    <Modal open={!!statement} onClose={onClose} title={`Mark ${nextStatus}`}>
      {statement && (
        <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
            <MiniStat label="Publisher" value={publisherName || statement.publisher_id}/>
            <MiniStat label="Period" value={`${statement.period_start} → ${statement.period_end}`}/>
            <MiniStat label="Amount owed" value={fmt.currency(statement.publisher_earnings, statement.currency)}/>
            <MiniStat label="Gross spend" value={fmt.currency(statement.gross_revenue, statement.currency)}/>
          </div>

          {isPayment && (
            <div style={{ fontSize: 13, color: "var(--ink-2)" }}>
              Record this only once the transfer has actually left — <strong>paid</strong> is terminal and
              cannot be reversed from here.
            </div>
          )}
          {nextStatus === "void" && (
            <div style={{ fontSize: 13, color: "var(--ink-2)" }}>
              Voiding does not return these earnings to the unpaid balance — the days it swept stay
              sealed. Use it for a statement raised in error, and note why.
            </div>
          )}

          <div>
            <label className="pw-lbl">{isPayment ? "Payment reference" : "Reference"}</label>
            <Input value={reference} placeholder={isPayment ? "wire-2026-07" : "Optional"}
              onChange={(e) => setReference(e.target.value)}/>
          </div>
          <div>
            <label className="pw-lbl">Notes</label>
            <Textarea rows={2} value={notes} placeholder="Optional" onChange={(e) => setNotes(e.target.value)}/>
          </div>

          <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
            <Btn kind="ghost" onClick={onClose}>Cancel</Btn>
            <Btn onClick={confirm} disabled={busy || (isPayment && !reference.trim())}>
              {busy ? "Saving…" : `Mark ${nextStatus}`}
            </Btn>
          </div>
          {isPayment && !reference.trim() && (
            <div style={{ fontSize: 12, color: "var(--muted)", textAlign: "right" }}>
              A payment reference is required.
            </div>
          )}
        </div>
      )}
    </Modal>
  );
};

// ---------------------------------------------------------------- revenue share

const RevenueShareCard = ({ publishers, notify, onChanged }) => {
  const [publisherId, setPublisherId] = React.useState("");
  const [config, setConfig] = React.useState(null);
  const [earnings, setEarnings] = React.useState(null);
  const [sharePct, setSharePct] = React.useState("");
  const [minimum, setMinimum] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [generating, setGenerating] = React.useState(false);

  const loadPublisher = React.useCallback(async (id) => {
    if (!id) { setConfig(null); setEarnings(null); return; }
    setLoading(true);
    try {
      const [c, e] = await Promise.all([
        window.__api.read("adminGetPayoutConfig", { params: { publisher_id: id } }),
        window.__api.read("adminGetPayoutEarnings", { params: { publisher_id: id } }),
      ]);
      const cfg = c?.config || c || {};
      setConfig(cfg);
      setEarnings(e || {});
      setSharePct(String(pctFromBps(cfg.revenue_share_bps)));
      setMinimum(String(cfg.minimum_payout ?? 50));
    } catch (err) {
      notify(err.message || "Could not load that publisher's payout settings.", "bad");
    } finally {
      setLoading(false);
    }
  }, [notify]);

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

  const save = async () => {
    setSaving(true);
    try {
      await window.__api.write("adminSavePayoutConfig", null, {
        params: { publisher_id: publisherId },
        body: {
          revenue_share_bps: bpsFromPct(sharePct),
          minimum_payout: Number(minimum),
          currency: config?.currency || "USD",
        },
      });
      notify("Revenue share updated. It applies to earnings from now on.");
      loadPublisher(publisherId);
    } catch (e) {
      notify(e.message || "Could not save the revenue share.", "bad");
    } finally {
      setSaving(false);
    }
  };

  const generate = async () => {
    setGenerating(true);
    try {
      const res = await window.__api.write("adminGeneratePayoutStatement", null, {
        params: { publisher_id: publisherId },
        body: {},
      });
      if (res?.statement) {
        notify(`Statement raised for ${fmt.currency(res.statement.publisher_earnings, res.statement.currency)}.`);
      } else if (res?.skipped_reason === "below_minimum") {
        notify("Balance is under the minimum — it rolls into the next period.", "bad");
      } else {
        notify("Nothing to bill for the closed period.", "bad");
      }
      onChanged();
      loadPublisher(publisherId);
    } catch (e) {
      notify(e.message || "Could not raise a statement.", "bad");
    } finally {
      setGenerating(false);
    }
  };

  const pctValid = sharePct !== "" && Number(sharePct) >= 0 && Number(sharePct) <= 100;
  const changed = config && (bpsFromPct(sharePct) !== config.revenue_share_bps || Number(minimum) !== Number(config.minimum_payout));

  return (
    <Card title="Revenue share" subtitle="The publisher's cut of advertiser spend on their inventory.">
      <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        <div>
          <label className="pw-lbl">Publisher</label>
          <Select value={publisherId} onChange={setPublisherId} placeholder="Choose a publisher"
            options={publishers.map(p => ({ label: p.name, value: p.publisher_id }))}/>
        </div>

        {loading && <div style={{ fontSize: 13, color: "var(--muted)" }}>Loading…</div>}

        {config && !loading && (
          <>
            <KpiGrid>
              <StatCard label="Unpaid balance"
                value={fmt.currency(earnings?.unpaid_earnings || 0, config.currency)}
                sub="Not yet on a statement"/>
              <StatCard label="Lifetime paid out"
                value={fmt.currency((earnings?.lifetime_earnings || 0) - (earnings?.unpaid_earnings || 0), config.currency)}/>
              <StatCard label="Gross spend"
                value={fmt.currency(earnings?.lifetime_gross || 0, config.currency)}
                sub="Charged to advertisers"/>
            </KpiGrid>

            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
              <div>
                <label className="pw-lbl">Publisher share</label>
                <Input type="number" step="0.5" min="0" max="100" value={sharePct} suffix="%"
                  onChange={(e) => setSharePct(e.target.value)}/>
              </div>
              <div>
                <label className="pw-lbl">Minimum payout</label>
                <Input type="number" step="1" min="0" value={minimum} prefix="$"
                  onChange={(e) => setMinimum(e.target.value)}/>
              </div>
            </div>

            <div style={{ fontSize: 12.5, color: "var(--muted)", lineHeight: 1.55 }}>
              Each click and impression stores the share that applied when it was billed, so changing
              this rate affects future earnings only — it never restates a statement already raised.
            </div>

            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", flexWrap: "wrap" }}>
              <Btn kind="secondary" onClick={generate} disabled={generating || !(earnings?.unpaid_earnings > 0)}>
                {generating ? "Raising…" : "Raise statement"}
              </Btn>
              <Btn onClick={save} disabled={saving || !pctValid || !changed}>{saving ? "Saving…" : "Save share"}</Btn>
            </div>
            {!(earnings?.unpaid_earnings > 0) && (
              <div style={{ fontSize: 12, color: "var(--muted)", textAlign: "right", marginTop: -8 }}>
                Nothing unpaid to raise a statement for.
              </div>
            )}
          </>
        )}
      </div>
    </Card>
  );
};

// ---------------------------------------------------------------- screen

const OpPayoutsScreen = ({ data }) => {
  const [statements, setStatements] = React.useState([]);
  const [publishers, setPublishers] = React.useState([]);
  const [statusFilter, setStatusFilter] = React.useState("all");
  const [loading, setLoading] = React.useState(true);
  const [settling, setSettling] = React.useState(null);
  const [toast, setToast] = React.useState(null);

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

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const [s, p] = await Promise.all([
        window.__api.read("adminListPayoutStatements"),
        window.__api.read("listPublishers"),
      ]);
      setStatements(s?.statements || (Array.isArray(s) ? s : []));
      setPublishers(p?.publishers || (Array.isArray(p) ? p : (data?.publishers || [])));
    } catch (e) {
      notify(e.message || "Could not load payouts.", "bad");
    } finally {
      setLoading(false);
    }
  }, [data]);

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

  const publisherName = (id) => publishers.find(p => p.publisher_id === id)?.name || id;

  const filtered = statusFilter === "all" ? statements : statements.filter(s => s.status === statusFilter);

  const owed = statements
    .filter(s => s.status === "pending" || s.status === "approved")
    .reduce((n, s) => n + Number(s.publisher_earnings || 0), 0);
  const paidOut = statements
    .filter(s => s.status === "paid")
    .reduce((n, s) => n + Number(s.publisher_earnings || 0), 0);
  const currency = statements[0]?.currency || "USD";

  return (
    <Screen>
      <PageHead icon={IconReceipt} title="Publisher Payouts"
        subtitle="Set each publisher's revenue share and settle the statements it produces."/>

      <KpiGrid>
        <StatCard label="Awaiting settlement" value={fmt.currency(owed, currency)}
          sub={`${statements.filter(s => s.status === "pending").length} pending · ${statements.filter(s => s.status === "approved").length} approved`}/>
        <StatCard label="Paid to date" value={fmt.currency(paidOut, currency)}/>
        <StatCard label="Statements" value={fmt.number(statements.length)}/>
        <StatCard label="Publishers" value={fmt.number(publishers.length)}/>
      </KpiGrid>

      <RevenueShareCard publishers={publishers} notify={notify} onChanged={load}/>

      <Card title="Settlement queue" subtitle="Approve, then mark paid once the transfer has left." padded={false}>
        <div style={{ padding: "14px var(--pad)" }}>
          <Toolbar>
            <Select value={statusFilter} onChange={setStatusFilter} style={{ minWidth: 180 }}
              options={[
                { label: "All statuses", value: "all" },
                { label: "Pending", value: "pending" },
                { label: "Approved", value: "approved" },
                { label: "Paid", value: "paid" },
                { label: "Void", value: "void" },
              ]}/>
          </Toolbar>
        </div>

        {loading ? (
          <div style={{ padding: 32, textAlign: "center", color: "var(--muted)" }}>Loading…</div>
        ) : filtered.length === 0 ? (
          <EmptyState icon={<IconReceipt size={26}/>}
            title={statements.length ? "Nothing in that status" : "No statements yet"}
            description={statements.length
              ? "Try a different filter."
              : "Raise one above once a publisher has an unpaid balance over their minimum."}/>
        ) : (
          <div className="pw-opstmt-list">
            <div className="pw-opstmt-row head">
              <span>Publisher</span><span>Period</span><span>Gross</span>
              <span>Owed</span><span>Status</span><span>Actions</span>
            </div>
            {filtered.map(s => (
              <div className="pw-opstmt-row" key={s.statement_id}>
                <span className="pw-opstmt-pub">{publisherName(s.publisher_id)}</span>
                <span className="pw-opstmt-period">{s.period_start} → {s.period_end}</span>
                <span className="pw-opstmt-num">{fmt.currency(s.gross_revenue, s.currency)}</span>
                <span className="pw-opstmt-num strong">{fmt.currency(s.publisher_earnings, s.currency)}</span>
                <span>
                  <Pill tone={OP_STATEMENT_TONE[s.status] || "neutral"}>{s.status}</Pill>
                  {s.reference && <div className="pw-opstmt-ref">{s.reference}</div>}
                </span>
                <span className="pw-opstmt-actions">
                  {(NEXT_STATUSES[s.status] || []).map(next => (
                    <Btn key={next} size="sm" kind={next === "paid" ? "primary" : next === "void" ? "ghost" : "secondary"}
                      onClick={() => setSettling({ statement: s, next })}>
                      {next === "approved" ? "Approve" : next === "paid" ? "Mark paid" : "Void"}
                    </Btn>
                  ))}
                  {(NEXT_STATUSES[s.status] || []).length === 0 && (
                    <span style={{ fontSize: 12, color: "var(--muted)" }}>Settled</span>
                  )}
                </span>
              </div>
            ))}
          </div>
        )}
      </Card>

      <SettleDialog statement={settling?.statement} nextStatus={settling?.next}
        publisherName={settling ? publisherName(settling.statement.publisher_id) : ""}
        onClose={() => setSettling(null)} onDone={load} notify={notify}/>

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

      <style>{`
        .pw-opstmt-list { display: flex; flex-direction: column; }
        .pw-opstmt-row { display: grid; grid-template-columns: 1.2fr 1.5fr 1fr 1fr 1fr 1.4fr; gap: 12px;
          align-items: center; padding: 11px var(--pad); border-top: 1px solid var(--line-2); font-size: 13px; }
        .pw-opstmt-row.head { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em;
          color: var(--muted); font-weight: 600; border-top: 0; }
        .pw-opstmt-pub { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .pw-opstmt-period { font-family: var(--mono); font-size: 12px; color: var(--muted); }
        .pw-opstmt-num { font-family: var(--mono); font-size: 12.5px; }
        .pw-opstmt-num.strong { font-weight: 600; color: var(--ink); }
        .pw-opstmt-ref { font-size: 11px; color: var(--muted); margin-top: 3px; font-family: var(--mono); }
        .pw-opstmt-actions { display: flex; gap: 6px; flex-wrap: wrap; }
        @media (max-width: 1000px) {
          .pw-opstmt-row { grid-template-columns: 1fr 1fr; }
          .pw-opstmt-row.head { display: none; }
        }
      `}</style>
    </Screen>
  );
};

Object.assign(window, { OpPayoutsScreen });
