// =================================================================
// pw-campaign-pricing.jsx — campaign type, industry segment, reader countries and the minimums a
// campaign must meet (payments phase 4, migration 0057). Shared by the operator's campaign form and
// the brand's.
//
// The API is the authority: it checks every campaign write against the minimums and refuses with the
// number to meet. This panel shows that number before the round trip, not instead of it. Live mode
// asks GET …/campaign-options; demo mode resolves the fixtures with the same lookup order
// (segment x country → segment → country → default) and the same "strictest country wins" rule as
// campaignPricingService.ts.
// =================================================================

const PW_CAMPAIGN_CURRENCIES = ["USD", "INR", "EUR", "GBP", "AUD", "CAD", "SGD", "AED"];

/** "us, IN ,eu-west" → { countries: ["IN","US"], anywhere: true } — mirrors readerCountries(). */
const pwReaderCountries = (list) => {
  const raw = (list || []).map((g) => String(g).trim().toUpperCase()).filter(Boolean);
  const countries = [...new Set(raw.filter((g) => /^[A-Z]{2}$/.test(g)))].sort();
  return { countries, anywhere: raw.length === 0 || raw.some((g) => !/^[A-Z]{2}$/.test(g)) };
};

/** Demo-mode mirror of campaignPricingService.campaignMinimums(). Null when unpriced. */
const pwResolveMinimums = (rows, { pricing_model, currency, segment_id, geo_targets }) => {
  if (pricing_model !== "cpc" && pricing_model !== "cpa") return { governed: false };
  const now = new Date().toISOString();
  const mine = (rows || []).filter((r) => r.pricing_model === pricing_model && r.currency === currency && String(r.effective_from) <= now);
  const latest = (seg, c) => mine
    .filter((r) => (r.segment_id || null) === seg && (r.country || null) === c)
    .sort((a, b) => String(b.effective_from).localeCompare(String(a.effective_from)))[0] || null;
  const resolve = (c) => {
    const levels = [];
    if (segment_id && c) levels.push([segment_id, c, "segment_country"]);
    if (segment_id) levels.push([segment_id, null, "segment"]);
    if (c) levels.push([null, c, "country"]);
    levels.push([null, null, "default"]);
    for (const [s, cc, source] of levels) { const row = latest(s, cc); if (row) return { row, source }; }
    return null;
  };
  const { countries, anywhere } = pwReaderCountries(geo_targets);
  const targets = [...countries];
  if (anywhere) {
    const specific = [...new Set(mine.filter((r) => r.country && (!r.segment_id || r.segment_id === segment_id)).map((r) => r.country))].sort();
    for (const c of specific) if (!targets.includes(c)) targets.push(c);
    targets.push(null);
  }
  const by_country = [];
  for (const c of targets) {
    const hit = resolve(c);
    if (!hit) return null;
    by_country.push({ country: c, minimum_id: hit.row.minimum_id, source: hit.source, min_bid: Number(hit.row.min_bid), min_campaign_budget: Number(hit.row.min_campaign_budget) });
  }
  return {
    governed: true,
    min_bid: Math.max(...by_country.map((b) => b.min_bid)),
    min_campaign_budget: Math.max(...by_country.map((b) => b.min_campaign_budget)),
    by_country,
  };
};

/**
 * The options a campaign form needs: active segments, the brand's segment, and the minimums for the
 * current type / segment / countries / currency. `source` is "admin" (staff API) or "advertiser"
 * (the brand's own JWT). Re-asks ~250ms after the terms stop changing.
 */
function useCampaignOptions({ source, data, pricing_model, currency, industry_segment_id, geo_targets, advertiser_id }) {
  const [state, setState] = React.useState({ loading: true, error: null, segments: [], brand_segment_id: null, quote: null });
  const key = JSON.stringify({ source, pricing_model, currency, industry_segment_id, geo_targets, advertiser_id });
  React.useEffect(() => {
    let alive = true;
    const t = setTimeout(async () => {
      const countries = pwReaderCountries(geo_targets).countries.join(",");
      const query = { pricing_model, currency };
      if (industry_segment_id) query.industry_segment_id = industry_segment_id;
      if (countries) query.countries = countries;
      const live = source === "advertiser" ? (typeof advLive === "function" && advLive()) : Boolean(window.__api.getConfig().live);
      try {
        let out;
        if (live && source === "advertiser") {
          out = await advCall(`/advertiser/v1/campaign-options?${new URLSearchParams(query).toString()}`, { method: "GET", token: loadAdvToken() });
        } else if (live) {
          if (advertiser_id) query.advertiser_id = advertiser_id;
          out = await window.__api.call("adminCampaignOptions", { query });
        } else {
          const segs = (data.industrySegments || []).filter((s) => s.status === "active");
          const brandSeg = (data.advertisers || []).find((a) => a.advertiser_id === advertiser_id)?.industry_segment_id || null;
          const seg = industry_segment_id || brandSeg;
          const q = pwResolveMinimums(data.campaignMinimums, { pricing_model, currency, segment_id: seg, geo_targets });
          out = {
            segments: segs, brand_segment_id: brandSeg,
            quote: q && { ...q, pricing_model, currency, live: true, not_live_reason: null },
          };
          if (!q) throw new Error(`No ${pricing_model.toUpperCase()} minimums are set for ${currency}.`);
        }
        if (alive) setState({ loading: false, error: null, segments: out.segments || [], brand_segment_id: out.brand_segment_id || null, quote: out.quote || null });
      } catch (e) {
        if (alive) setState((s) => ({ ...s, loading: false, error: (e && e.message) || "Could not load minimums.", quote: null }));
      }
    }, 250);
    return () => { alive = false; clearTimeout(t); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [key, data.industrySegments, data.campaignMinimums]);
  return state;
}

/** True when the bid and budget meet the quote (or the quote does not govern this type). */
const pwMeetsMinimums = (f, quote) => {
  if (!quote || !quote.governed) return true;
  const budget = f.total_budget ? Number(f.total_budget) : f.daily_budget ? Number(f.daily_budget) : null;
  return Number(f.bid_amount) + 1e-9 >= quote.min_bid && (budget === null || budget + 1e-9 >= quote.min_campaign_budget);
};

const pwMinMoney = (n, currency) => `${Number(n).toFixed(Math.abs(n * 100 - Math.round(n * 100)) < 1e-9 ? 2 : 4)} ${currency}`;

/**
 * The type / segment / countries / currency fields plus the minimums panel. `f` holds pricing_model,
 * industry_segment_id, geo_targets (array), currency, bid_amount, daily_budget, total_budget.
 */
const CampaignPricingFields = ({ f, set, options }) => {
  const segs = options.segments || [];
  // A campaign keeps a segment that was retired after it was created; show it rather than blank it.
  const current = f.industry_segment_id && !segs.some((s) => s.segment_id === f.industry_segment_id)
    ? [{ label: "Current segment (retired)", value: f.industry_segment_id }] : [];
  const segOptions = [{ label: options.brand_segment_id ? "Brand's segment" : "— choose a segment —", value: "" },
    ...segs.map((s) => ({ label: s.name, value: s.segment_id })), ...current];
  const types = [{ label: "CPC · per click", value: "cpc" }, { label: "CPA · per conversion", value: "cpa" }];
  // CPM is not offered anywhere (0058: blocked until impressions can be charged).
  const q = options.quote;
  const unit = f.pricing_model === "cpa" ? "per conversion" : f.pricing_model === "cpm" ? "per 1,000 views" : "per click";
  const bidLow = q && q.governed && Number(f.bid_amount) + 1e-9 < q.min_bid;
  const budget = f.total_budget ? Number(f.total_budget) : f.daily_budget ? Number(f.daily_budget) : null;
  const budgetLow = q && q.governed && budget !== null && budget + 1e-9 < q.min_campaign_budget;
  return (
    <div style={{ display: "grid", gap: 14 }}>
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Campaign type</label>
          <SegmentedControl value={f.pricing_model} onChange={(v) => set({ pricing_model: v })} options={types}/></div>
        <div><label className="pw-lbl">Industry segment</label>
          <Select value={f.industry_segment_id || ""} onChange={(v) => set({ industry_segment_id: v || null })} options={segOptions}/></div>
      </FieldGroup>
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Reader countries</label>
          <Input value={(f.geo_targets || []).join(", ")} placeholder="IN, US — empty means anywhere"
            onChange={(e) => set({ geo_targets: e.target.value.toUpperCase().split(",").map((x) => x.trim()).filter(Boolean) })}/></div>
        <div><label className="pw-lbl">Currency</label>
          <Select value={f.currency || "USD"} onChange={(v) => set({ currency: v })} options={PW_CAMPAIGN_CURRENCIES.map((c) => ({ label: c, value: c }))}/></div>
      </FieldGroup>
      {f.pricing_model === "cpa" && <Banner tone="info" icon={<IconInfo size={15}/>}>CPA bills per conversion the brand reports (signed postback, or pixel after review). Clicks are free. It serves once the wallet covers one conversion.</Banner>}
      <div style={{ border: "1px solid var(--line)", borderRadius: 9, padding: "10px 12px", fontSize: 13, lineHeight: 1.55, background: "var(--panel-2, transparent)" }}>
        {options.loading ? <span style={{ color: "var(--muted)" }}>Checking minimums…</span>
          : options.error ? <span style={{ color: "var(--bad, #b42318)" }}>{options.error}</span>
          : !q ? null
          : !q.governed ? <span style={{ color: "var(--muted)" }}>Minimums do not apply to CPM campaigns.</span>
          : <>
              <div><strong>Minimum bid</strong> {pwMinMoney(q.min_bid, f.currency || "USD")} {unit}
                {bidLow && <span style={{ color: "var(--bad, #b42318)" }}> — your bid is below it</span>}</div>
              <div><strong>Minimum campaign budget</strong> {pwMinMoney(q.min_campaign_budget, f.currency || "USD")} (total, or daily when there is no total)
                {budgetLow && <span style={{ color: "var(--bad, #b42318)" }}> — your budget is below it</span>}</div>
              <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 4 }}>
                Set per segment, reader country and type; the strictest of your countries applies.
                {(q.by_country || []).length > 1 && " " + q.by_country.map((b) => `${b.country || "elsewhere"} ${pwMinMoney(b.min_bid, f.currency || "USD")}`).join(" · ")}
              </div>
            </>}
      </div>
    </div>
  );
};

Object.assign(window, { PW_CAMPAIGN_CURRENCIES, pwReaderCountries, pwResolveMinimums, useCampaignOptions, pwMeetsMinimums, CampaignPricingFields });
