// =================================================================
// pw-screens-marketplace.jsx — Publisher Admin · Marketplace (PKG-14)
//
//   PRD §8. Publishers sell each other's inventory. DropCap publishes
//   the directory and clears the transaction; it never sets a price.
//
//   Four things this file exists to say out loud, because every one of
//   them is money and every one is easy to render wrongly:
//
//     1. BOOKING SPENDS MONEY NOW. The seller pays 100% in advance, in
//        full, before anything runs. A button that reads "Book" without
//        the amount next to it is a button somebody clicks by accident.
//     2. A PLACEMENT HAS A QUANTITY. Exclusive (one slot) and run-of-site
//        (no limit) are different products at different prices, and a
//        buyer choosing between them has to be able to see which is which.
//     3. `declined` IS NOT `refunded`. A declined booking has money OWED
//        until somebody sends it. Showing them as one state hides every
//        unpaid refund, which is the whole reason the queue exists.
//     4. A REFUND CAN BE LESS THAN WHAT WAS PAID, and that is correct.
//        The seller is rebated 15% at T+3; a refund after that is net of
//        it (PRD §8 Terms). Unexplained, ₹1,700 back on a ₹2,000 booking
//        reads as being short-changed.
// =================================================================

const MKT_STATUS = {
  pending_payment: { label: "Awaiting payment", tone: "warn" },
  paid: { label: "Paid — awaiting the host", tone: "info" },
  accepted: { label: "Accepted", tone: "good" },
  live: { label: "Live", tone: "good" },
  completed: { label: "Completed", tone: "neutral" },
  declined: { label: "Declined", tone: "warn" },
  cancelled: { label: "Cancelled", tone: "neutral" },
  refunded: { label: "Refunded", tone: "neutral" },
};

const mktMoney = (n, currency) => {
  try {
    return new Intl.NumberFormat(undefined, {
      style: "currency",
      currency: currency || "USD",
      maximumFractionDigits: 2,
    }).format(Number(n || 0));
  } catch {
    return `${currency || ""} ${Number(n || 0).toFixed(2)}`.trim();
  }
};

/**
 * How much of a placement is for sale (C22).
 *
 * -1 means the host has said this placement does not run out — a run-of-site unit carrying any
 * number of advertisers at once. 1 means exclusive. Neither is a default worth hiding: they are
 * different products, and the price only makes sense next to the answer.
 */
const CapacityNote = ({ capacity }) => {
  const n = Number(capacity ?? 1);
  if (n === -1) return <Pill tone="neutral">Runs alongside others</Pill>;
  if (n === 1) return <Pill tone="accent">Exclusive — one slot</Pill>;
  return <Pill tone="accent">{n} slots</Pill>;
};

// =================================================================
// Browse and book
// =================================================================
const BookingDialog = ({ entry, campaigns, onClose, onBooked, notify }) => {
  const [campaignId, setCampaignId] = React.useState(campaigns[0]?.campaign_id || "");
  const [startsOn, setStartsOn] = React.useState("");
  const [busy, setBusy] = React.useState(false);

  const endsOn = React.useMemo(() => {
    if (!startsOn) return null;
    const ms = Date.parse(`${startsOn}T00:00:00.000Z`);
    if (!Number.isFinite(ms)) return null;
    // The card's own duration decides the end, inclusive of the first day: a 30-day run starting on
    // the 1st ends on the 30th. Shown rather than computed silently — the seller is committing to
    // a specific fortnight on somebody else's site.
    return new Date(ms + (Number(entry.duration_days || 1) - 1) * 86400000).toISOString().slice(0, 10);
  }, [startsOn, entry.duration_days]);

  const submit = async () => {
    if (!campaignId || !startsOn) return;
    setBusy(true);
    try {
      const res = await window.__api.write("createBooking", {
        body: {
          host_publisher_id: entry.publisher_id,
          rate_card_id: entry.rate_card_id,
          campaign_id: campaignId,
          starts_on: startsOn,
          // Echoed back so the server can refuse a quote that moved while this dialog sat open. The
          // host sets the price, and booking at a stale figure would be booking at a price nobody
          // agreed to.
          quoted_price: entry.price,
          quoted_currency: entry.currency,
        },
      });
      onBooked(res);
    } catch (e) {
      notify(e?.message || "The booking could not be made.", "bad");
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="hx-pal-overlay" onClick={onClose}>
      <div className="hx-card" style={{ maxWidth: 520, margin: "10vh auto", padding: 20 }} onClick={(e) => e.stopPropagation()}>
        <div style={{ fontSize: 16, fontWeight: 600 }}>
          Book {entry.placement_code} on {entry.property_name}
        </div>
        <div style={{ fontSize: 13, opacity: 0.7, marginTop: 4 }}>{entry.publisher_name}</div>

        <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 16 }}>
          <label style={{ fontSize: 13 }}>
            <div style={{ marginBottom: 4, opacity: 0.75 }}>Your campaign</div>
            <select
              value={campaignId}
              onChange={(e) => setCampaignId(e.target.value)}
              style={{ width: "100%", height: 36, borderRadius: 8, padding: "0 8px" }}
            >
              {campaigns.length === 0 && <option value="">You have no campaigns yet</option>}
              {campaigns.map((c) => (
                <option key={c.campaign_id} value={c.campaign_id}>{c.name}</option>
              ))}
            </select>
          </label>

          <label style={{ fontSize: 13 }}>
            <div style={{ marginBottom: 4, opacity: 0.75 }}>Starts on</div>
            <input
              type="date"
              value={startsOn}
              onChange={(e) => setStartsOn(e.target.value)}
              style={{ width: "100%", height: 36, borderRadius: 8, padding: "0 8px" }}
            />
          </label>

          {endsOn && (
            <div style={{ fontSize: 13, opacity: 0.8 }}>
              Runs {startsOn} → <strong>{endsOn}</strong> ({entry.duration_days} days).
            </div>
          )}

          {/* The sentence that stops a click being an accident. */}
          <div style={{ padding: "12px 14px", borderRadius: 8, background: "var(--warn-soft)", fontSize: 13, lineHeight: 1.5 }}>
            You pay <strong>{mktMoney(entry.price, entry.currency)}</strong> now, in full and in
            advance. That is what the host is paid from, and it is what makes your own 15% a rebate
            rather than an invoice — it comes back three days after payment clears.
            <br />
            <span style={{ opacity: 0.8 }}>
              If the host declines, you are refunded — net of any rebate you have already had.
            </span>
          </div>

          <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
            <Btn kind="secondary" onClick={onClose}>Cancel</Btn>
            <Btn onClick={submit} disabled={busy || !campaignId || !startsOn}>
              {busy ? "Booking…" : `Pay ${mktMoney(entry.price, entry.currency)} and book`}
            </Btn>
          </div>
        </div>
      </div>
    </div>
  );
};

const DirectoryRow = ({ entry, onBook }) => (
  <div style={{ padding: "14px 16px", borderTop: "1px solid var(--line)", display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap" }}>
    <div style={{ flex: "1 1 240px", minWidth: 0 }}>
      <div style={{ fontWeight: 600 }}>{entry.property_name}</div>
      <div style={{ fontSize: 12, opacity: 0.7 }}>
        {entry.publisher_name} · {entry.property_type}
      </div>
      {entry.notes && <div style={{ fontSize: 12, opacity: 0.7, marginTop: 6 }}>{entry.notes}</div>}
    </div>
    <div style={{ flex: "0 0 auto", display: "flex", flexDirection: "column", gap: 6 }}>
      <div style={{ fontSize: 13 }}>
        {entry.placement_code} · {entry.creative_type}
      </div>
      <CapacityNote capacity={entry.max_concurrent_bookings} />
    </div>
    <div style={{ flex: "0 0 auto", textAlign: "right" }}>
      <div style={{ fontSize: 18, fontWeight: 600 }}>{mktMoney(entry.price, entry.currency)}</div>
      <div style={{ fontSize: 12, opacity: 0.7 }}>for {entry.duration_days} days</div>
    </div>
    <Btn size="sm" onClick={() => onBook(entry)}>Book</Btn>
  </div>
);

const MarketplaceScreen = ({ data, setData }) => {
  const notify = (msg, tone) => (window.__toast ? window.__toast(msg, tone) : null);
  const [booking, setBooking] = React.useState(null);
  const [handoff, setHandoff] = React.useState(null);

  const dir = data?.marketplaceDirectory || {};
  const entries = dir.entries || [];
  const campaigns = data?.adCampaigns || [];

  const onBooked = (res) => {
    setBooking(null);
    // The response carries a checkout, and nothing is paid until the seller completes it. Showing
    // the URL rather than redirecting keeps the fixture path identical to the live one.
    setHandoff(res || null);
    if (window.__api?.read) {
      window.__api.read("listBookings").then((b) => setData((d) => ({ ...d, marketplaceBookings: b })));
    }
    notify("Booking created. It is not confirmed until the payment clears.", "warn");
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      {/* The terms belong here, not only inside the dialog a buyer has already decided to open.
          Somebody comparing prices is deciding whether to spend money, and the fact that they pay
          all of it up front changes what the prices mean. */}
      <Card title="How buying here works">
        <div style={{ fontSize: 13, lineHeight: 1.6 }}>
          You pay the host's price <strong>in full and in advance</strong>, to DropCap, before
          anything runs. <strong>15% comes back to you three days later</strong> — that is your
          share as the seller, and it is a rebate on money you have already paid rather than an
          invoice you send. If the host declines, you are refunded net of any rebate you have
          already had.
        </div>
      </Card>

      <Card
        title="Inventory for sale"
        subtitle="Other publishers' placements. Your own never appears here — you cannot broker to yourself."
        padded={false}
      >
        {entries.length === 0 ? (
          <div style={{ padding: 20, opacity: 0.6, fontSize: 13 }}>
            Nothing listed yet. DropCap seeds no prices — every line here is a publisher's own.
          </div>
        ) : (
          entries.map((e) => <DirectoryRow key={e.rate_card_id} entry={e} onBook={setBooking} />)
        )}
        {dir.has_more && (
          <div style={{ padding: "12px 16px", borderTop: "1px solid var(--line)", fontSize: 12, opacity: 0.7 }}>
            There is more inventory than shown. Narrow by format or price rather than assuming this
            is the whole network.
          </div>
        )}
      </Card>

      {handoff && (
        <Card title="Finish paying" subtitle="Your booking holds the slot for about thirty minutes.">
          <div style={{ fontSize: 13, lineHeight: 1.6 }}>
            Booking <code>{handoff.booking_id}</code> is <strong>awaiting payment</strong>. It holds
            the host's slot while you complete the checkout and then releases it, so an abandoned
            checkout does not take their inventory off the market.
            <div style={{ marginTop: 10 }}>
              <a href={handoff.checkout_url} target="_blank" rel="noreferrer">{handoff.checkout_url}</a>
            </div>
            <div style={{ marginTop: 10, opacity: 0.75 }}>
              It becomes confirmed only when the gateway tells us the money arrived — not when your
              browser comes back.
            </div>
          </div>
        </Card>
      )}

      {booking && (
        <BookingDialog
          entry={booking}
          campaigns={campaigns}
          onClose={() => setBooking(null)}
          onBooked={onBooked}
          notify={notify}
        />
      )}
    </div>
  );
};

// =================================================================
// Bookings, both sides
// =================================================================
const RefundNote = ({ refund }) => {
  if (!refund) return null;
  const netted = Number(refund.rebate_already_paid || 0) > 0;
  return (
    <div style={{ marginTop: 8, padding: "10px 12px", borderRadius: 8, background: "var(--info-soft)", fontSize: 12.5, lineHeight: 1.5 }}>
      <strong>
        {refund.status === "paid"
          ? `Refunded ${mktMoney(refund.refund_amount, refund.currency)}`
          : `Refund owed: ${mktMoney(refund.refund_amount, refund.currency)}`}
      </strong>
      {netted && (
        // Without this sentence a ₹1,700 refund on a ₹2,000 booking reads as being short-changed.
        <div style={{ marginTop: 4, opacity: 0.85 }}>
          You paid {mktMoney(refund.gross_amount, refund.currency)} and had already been rebated{" "}
          {mktMoney(refund.rebate_already_paid, refund.currency)} of it, so this returns the rest.
          You end up exactly where you started.
        </div>
      )}
      {refund.status === "owed" && (
        <div style={{ marginTop: 4, opacity: 0.85 }}>Not sent yet.</div>
      )}
    </div>
  );
};

const BookingRow = ({ booking, onAction, busyId }) => {
  const s = MKT_STATUS[booking.status] || { label: booking.status, tone: "neutral" };
  const busy = busyId === booking.booking_id;
  const isHost = booking.role === "host";
  // A host answers a booking that has been paid for. Nothing reaches them before that.
  const canAnswer = isHost && booking.status === "paid";
  // A seller may back out only before the run starts: from the first day the host has held the slot
  // off the market and is delivering against it.
  const canCancel =
    !isHost &&
    ["paid", "accepted"].includes(booking.status) &&
    booking.starts_on > new Date().toISOString().slice(0, 10);

  return (
    <div style={{ padding: "14px 16px", borderTop: "1px solid var(--line)" }}>
      <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
        <Pill tone={booking.role === "host" ? "accent" : "neutral"}>
          {isHost ? "On your inventory" : "You are selling"}
        </Pill>
        <div style={{ flex: "1 1 200px", minWidth: 0 }}>
          <div style={{ fontWeight: 600 }}>{booking.counterparty_name}</div>
          <div style={{ fontSize: 12, opacity: 0.7 }}>
            {booking.starts_on} → {booking.ends_on}
          </div>
        </div>
        <div style={{ fontSize: 15, fontWeight: 600 }}>{mktMoney(booking.amount, booking.currency)}</div>
        <Pill tone={s.tone}>{s.label}</Pill>
      </div>

      {booking.declined_reason && (
        <div style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85 }}>
          Reason given: {booking.declined_reason}
        </div>
      )}

      <RefundNote refund={booking.refund} />

      {(canAnswer || canCancel) && (
        <div style={{ marginTop: 10, display: "flex", gap: 8 }}>
          {canAnswer && (
            <>
              <Btn size="sm" disabled={busy} onClick={() => onAction(booking, "accept")}>Accept</Btn>
              <Btn size="sm" kind="danger" disabled={busy} onClick={() => onAction(booking, "decline")}>
                Decline
              </Btn>
            </>
          )}
          {canCancel && (
            <Btn size="sm" kind="danger" disabled={busy} onClick={() => onAction(booking, "cancel")}>
              Cancel booking
            </Btn>
          )}
        </div>
      )}
    </div>
  );
};

const BookingsScreen = ({ data, setData }) => {
  const notify = (msg, tone) => (window.__toast ? window.__toast(msg, tone) : null);
  const [busyId, setBusyId] = React.useState(null);
  const bookings = data?.marketplaceBookings || [];

  const reload = () =>
    window.__api.read("listBookings").then((b) => setData((d) => ({ ...d, marketplaceBookings: b })));

  const onAction = async (booking, action) => {
    let reason = "";
    if (action !== "accept") {
      reason = window.prompt(
        action === "decline"
          ? "Why are you declining? The seller has paid in full and will be told."
          : "Why are you cancelling?",
        "",
      );
      // A blank reason is a decline the seller cannot act on. `BrokeredBookingDecisionRequest`
      // requires one; asking again here saves a round trip and an unexplained 400.
      if (!reason) return;
    }
    setBusyId(booking.booking_id);
    try {
      const endpoint = action === "accept" ? "acceptBooking" : action === "decline" ? "declineBooking" : "cancelBooking";
      await window.__api.write(endpoint, {
        params: { booking_id: booking.booking_id },
        ...(action === "accept" ? {} : { body: { reason } }),
      });
      await reload();
      notify(
        action === "accept"
          ? "Accepted."
          : "Recorded. The seller's refund is owed until it has actually been sent.",
        action === "accept" ? "good" : "warn",
      );
    } catch (e) {
      notify(e?.message || "That could not be done.", "bad");
    } finally {
      setBusyId(null);
    }
  };

  const awaitingMe = bookings.filter((b) => b.role === "host" && b.status === "paid").length;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      {awaitingMe > 0 && (
        <Card title="Waiting on you" subtitle="Paid bookings on your own inventory that you have not answered.">
          <div style={{ fontSize: 14 }}>
            <strong>{awaitingMe}</strong> {awaitingMe === 1 ? "booking is" : "bookings are"} paid for
            and waiting for your decision. Declining is free until your share is released; after that
            it cannot be reversed here.
          </div>
        </Card>
      )}

      <Card
        title="Bookings"
        subtitle="Both sides of the network — what you have sold, and what has been booked on you."
        padded={false}
      >
        {bookings.length === 0 ? (
          <div style={{ padding: 20, opacity: 0.6, fontSize: 13 }}>Nothing booked yet.</div>
        ) : (
          bookings.map((b) => (
            <BookingRow key={b.booking_id} booking={b} onAction={onAction} busyId={busyId} />
          ))
        )}
      </Card>

      <div style={{ fontSize: 12, opacity: 0.65, lineHeight: 1.5 }}>
        A declined booking still owes the seller money until it has been sent — that is why it reads
        <strong> Declined</strong> with a refund owed rather than <strong>Refunded</strong>.
      </div>
    </div>
  );
};

// =================================================================
// My inventory — properties, prices, and who you will not take
// =================================================================
const RateCardRow = ({ card, onSave, busy }) => {
  const [edit, setEdit] = React.useState(false);
  const [price, setPrice] = React.useState(String(card.price ?? ""));
  const [capacity, setCapacity] = React.useState(String(card.max_concurrent_bookings ?? 1));

  const save = () => {
    onSave(card, {
      placement_code: card.placement_code,
      creative_type: card.creative_type,
      price: Number(price),
      currency: card.currency,
      duration_days: card.duration_days,
      max_concurrent_bookings: Number(capacity),
      status: card.status,
    });
    setEdit(false);
  };

  return (
    <div style={{ padding: "14px 16px", borderTop: "1px solid var(--line)" }}>
      <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
        <div style={{ flex: "1 1 200px" }}>
          <div style={{ fontWeight: 600 }}>{card.placement_code}</div>
          <div style={{ fontSize: 12, opacity: 0.7 }}>{card.creative_type} · {card.duration_days} days</div>
        </div>
        {!edit && <CapacityNote capacity={card.max_concurrent_bookings} />}
        {!edit && <div style={{ fontSize: 15, fontWeight: 600 }}>{mktMoney(card.price, card.currency)}</div>}
        <Pill tone={card.status === "active" ? "good" : "neutral"}>
          {card.status === "active" ? "Listed" : "Paused"}
        </Pill>
        {!edit && <Btn size="sm" kind="secondary" onClick={() => setEdit(true)}>Edit</Btn>}
      </div>

      {edit && (
        <div style={{ marginTop: 12, display: "flex", gap: 10, alignItems: "flex-end", flexWrap: "wrap" }}>
          <label style={{ fontSize: 12 }}>
            <div style={{ opacity: 0.75, marginBottom: 4 }}>Price ({card.currency})</div>
            <input value={price} onChange={(e) => setPrice(e.target.value)} style={{ height: 32, borderRadius: 8, padding: "0 8px", width: 120 }} />
          </label>
          <label style={{ fontSize: 12 }}>
            <div style={{ opacity: 0.75, marginBottom: 4 }}>How many at once</div>
            <select value={capacity} onChange={(e) => setCapacity(e.target.value)} style={{ height: 32, borderRadius: 8, padding: "0 8px" }}>
              <option value="1">Exclusive — one at a time</option>
              <option value="2">2 at a time</option>
              <option value="3">3 at a time</option>
              <option value="5">5 at a time</option>
              {/* -1 is the host saying this placement does not run out. Zero is deliberately absent:
                  a placement nobody may book is a paused card, and two ways to say one thing drift. */}
              <option value="-1">No limit — runs alongside others</option>
            </select>
          </label>
          <Btn size="sm" disabled={busy} onClick={save}>Save</Btn>
          <Btn size="sm" kind="ghost" onClick={() => setEdit(false)}>Discard</Btn>
        </div>
      )}

      {card.notes && <div style={{ fontSize: 12, opacity: 0.7, marginTop: 8 }}>{card.notes}</div>}
    </div>
  );
};

const InventoryRulesCard = ({ rules, onAdd, onRemove, busy }) => {
  const [type, setType] = React.useState("category");
  const [value, setValue] = React.useState("");

  return (
    <Card
      title="Business you will not take"
      subtitle="Checked before a booking is made — the one point where a decline costs nobody anything."
      padded={false}
    >
      {rules.length === 0 ? (
        <div style={{ padding: "16px", fontSize: 13, opacity: 0.7 }}>Nothing declined yet.</div>
      ) : (
        rules.map((r) => (
          <div key={r.rule_id} style={{ padding: "12px 16px", borderTop: "1px solid var(--line)", display: "flex", gap: 12, alignItems: "center" }}>
            <Pill tone="neutral">{r.rule_type}</Pill>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontWeight: 500 }}>{r.rule_value}</div>
              {r.note && <div style={{ fontSize: 12, opacity: 0.7 }}>{r.note}</div>}
            </div>
            <Btn size="sm" kind="ghost" disabled={busy} onClick={() => onRemove(r)}>Remove</Btn>
          </div>
        ))
      )}
      <div style={{ padding: "12px 16px", borderTop: "1px solid var(--line)", display: "flex", gap: 8, alignItems: "flex-end", flexWrap: "wrap" }}>
        <label style={{ fontSize: 12 }}>
          <div style={{ opacity: 0.75, marginBottom: 4 }}>Decline by</div>
          <select value={type} onChange={(e) => setType(e.target.value)} style={{ height: 32, borderRadius: 8, padding: "0 8px" }}>
            <option value="category">Category</option>
            <option value="advertiser">Advertiser</option>
            <option value="campaign">Campaign</option>
          </select>
        </label>
        <label style={{ fontSize: 12, flex: "1 1 200px" }}>
          <div style={{ opacity: 0.75, marginBottom: 4 }}>Value</div>
          <input value={value} onChange={(e) => setValue(e.target.value)} placeholder="gambling" style={{ height: 32, borderRadius: 8, padding: "0 8px", width: "100%" }} />
        </label>
        <Btn size="sm" disabled={busy || !value.trim()} onClick={() => { onAdd(type, value.trim()); setValue(""); }}>
          Decline it
        </Btn>
      </div>
      {/* True whether or not any rules exist, so it cannot live in the empty state. A host reading
          a list of two rules still needs to know that everything absent from it is accepted. */}
      <div style={{ padding: "12px 16px", borderTop: "1px solid var(--line)", fontSize: 12, opacity: 0.7, lineHeight: 1.5 }}>
        Anything not listed here is bookable: a host who has expressed no preference{" "}
        <strong>has not declined</strong>. You can still turn down an individual booking afterwards,
        but by then the seller has paid and it becomes a refund.
      </div>
    </Card>
  );
};

const MyInventoryScreen = ({ data, setData }) => {
  const notify = (msg, tone) => (window.__toast ? window.__toast(msg, tone) : null);
  const [busy, setBusy] = React.useState(false);
  const properties = data?.myProperties || [];
  const [selected, setSelected] = React.useState(properties[0]?.property_id || null);

  const propertyId = selected || properties[0]?.property_id || null;
  const cards = (data?.myRateCards || []).filter((c) => !propertyId || c.property_id === propertyId);
  const rules = (data?.inventoryRules || []).filter((r) => !propertyId || r.property_id === propertyId);

  const reload = async () => {
    if (!propertyId) return;
    const [cardList, ruleList] = await Promise.all([
      window.__api.read("listRateCards", { params: { property_id: propertyId } }),
      window.__api.read("listInventoryRules", { params: { property_id: propertyId } }),
    ]);
    setData((d) => ({ ...d, myRateCards: cardList, inventoryRules: ruleList }));
  };

  const saveCard = async (card, body) => {
    setBusy(true);
    try {
      await window.__api.write("updateRateCard", { params: { rate_card_id: card.rate_card_id }, body });
      await reload();
      notify("Saved.", "good");
    } catch (e) {
      notify(e?.message || "That price could not be saved.", "bad");
    } finally {
      setBusy(false);
    }
  };

  const addRule = async (rule_type, rule_value) => {
    setBusy(true);
    try {
      await window.__api.write("createInventoryRule", { params: { property_id: propertyId }, body: { rule_type, rule_value } });
      await reload();
      notify("Declined in advance.", "good");
    } catch (e) {
      notify(e?.message || "That rule could not be saved.", "bad");
    } finally {
      setBusy(false);
    }
  };

  const removeRule = async (rule) => {
    setBusy(true);
    try {
      await window.__api.write("deleteInventoryRule", { params: { rule_id: rule.rule_id } });
      await reload();
      notify("Removed.", "good");
    } catch (e) {
      notify(e?.message || "That rule could not be removed.", "bad");
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <Card title="Your properties" subtitle="A rate card belongs to a property, not to you — so a newsletter can price differently from a site." padded={false}>
        {properties.length === 0 ? (
          <div style={{ padding: 20, opacity: 0.6, fontSize: 13 }}>No properties yet.</div>
        ) : (
          properties.map((p) => (
            <button
              key={p.property_id}
              onClick={() => setSelected(p.property_id)}
              style={{
                display: "flex", width: "100%", gap: 12, alignItems: "center", textAlign: "left",
                padding: "12px 16px", borderTop: "1px solid var(--line)", background: p.property_id === propertyId ? "var(--hover)" : "transparent",
                border: "none", borderTopStyle: "solid", cursor: "pointer", font: "inherit", color: "inherit",
              }}
            >
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 600 }}>{p.name}</div>
                <div style={{ fontSize: 12, opacity: 0.7 }}>{p.property_type}</div>
              </div>
              <Pill tone={p.status === "active" ? "good" : "neutral"}>
                {p.status === "active" ? "Listed" : "Hidden"}
              </Pill>
            </button>
          ))
        )}
      </Card>

      <Card
        title="What you charge"
        subtitle="DropCap never sets a price. Every figure here is yours, and it is what a buyer pays in full, in advance."
        padded={false}
      >
        {cards.length === 0 ? (
          <div style={{ padding: 20, opacity: 0.6, fontSize: 13 }}>
            No prices on this property yet — so none of it is for sale.
          </div>
        ) : (
          cards.map((c) => <RateCardRow key={c.rate_card_id} card={c} onSave={saveCard} busy={busy} />)
        )}
      </Card>

      <InventoryRulesCard rules={rules} onAdd={addRule} onRemove={removeRule} busy={busy} />

      <div style={{ fontSize: 12, opacity: 0.65, lineHeight: 1.5 }}>
        Pausing a card is how you stop selling a slot; there is no delete, because a price a booking
        was made against has to survive for that booking to be explainable.
      </div>
    </div>
  );
};
