// =================================================================
// pw-screens-campaigns.jsx — Campaign listing + creative preview
//
//   A campaign list that shows every status, shared by the publisher
//   and advertiser portals, plus a preview that renders a creative
//   exactly as the recommendation widget draws it.
// =================================================================

// "Approved" is the label, `active` is the stored value — publisher approval sets a campaign to
// active, and that is the state operators and advertisers think of as approved.
const CAMPAIGN_STATUS_META = {
  active:         { label: "Approved", tone: "good" },
  pending_review: { label: "Pending review", tone: "warn" },
  draft:          { label: "Draft", tone: "neutral" },
  paused:         { label: "Paused", tone: "warn" },
  rejected:       { label: "Rejected", tone: "accent" },
  ended:          { label: "Ended", tone: "neutral" },
};

const campaignStatusMeta = (status) =>
  CAMPAIGN_STATUS_META[String(status || "").toLowerCase()] || { label: String(status || "unknown"), tone: "neutral" };

const CampaignStatusPill = ({ status }) => {
  const meta = campaignStatusMeta(status);
  return <Pill tone={meta.tone}>{meta.label}</Pill>;
};

const CAMPAIGN_FILTERS = [
  { label: "All statuses", value: "all" },
  { label: "Approved", value: "active" },
  { label: "Pending review", value: "pending_review" },
  { label: "Draft", value: "draft" },
  { label: "Paused", value: "paused" },
  { label: "Rejected", value: "rejected" },
  { label: "Ended", value: "ended" },
];

// advName() falls back to the raw id, which in the publisher portal means a bare UUID: the
// advertisers collection is operator-scoped and never loaded there. Resolve to a real name or
// nothing at all rather than showing an identifier to a publisher.
const resolvedAdvertiserName = (data, id) => {
  const match = (data.advertisers || []).find(a => a.advertiser_id === id);
  return match?.name || null;
};

const creativeForCampaign = (data, campaign) =>
  (data.adCreatives || []).find(c => (campaign?.creative_ids || []).includes(c.creative_id)) || null;

/**
 * Shared campaign table. Both portals list the same records and route into the same detail screen;
 * only the destination route and the visible columns differ.
 */
const CampaignList = ({ data, campaigns, detailRoute, showAdvertiser }) => {
  const [statusFilter, setStatusFilter] = React.useState("all");
  const [search, setSearch] = React.useState("");

  const rows = (campaigns || []).filter(c => {
    if (statusFilter !== "all" && c.status !== statusFilter) return false;
    const q = search.trim().toLowerCase();
    if (!q) return true;
    const creative = creativeForCampaign(data, c);
    return [c.name, creative?.title, c.campaign_id].some(v => String(v || "").toLowerCase().includes(q));
  });

  const countFor = (status) => (campaigns || []).filter(c => c.status === status).length;

  return (
    <>
      <KpiGrid>
        <StatCard label="Approved" value={fmt.number(countFor("active"))} sub="Serving now"/>
        <StatCard label="Pending review" value={fmt.number(countFor("pending_review"))} sub="Awaiting a decision"/>
        <StatCard label="Draft" value={fmt.number(countFor("draft"))}/>
        <StatCard label="Rejected" value={fmt.number(countFor("rejected"))}/>
      </KpiGrid>

      <Card padded={false}>
        <div style={{ padding: "14px var(--pad)" }}>
          <Toolbar search={search} onSearchChange={setSearch} searchPlaceholder="Search campaign or headline">
            <Select value={statusFilter} onChange={setStatusFilter} options={CAMPAIGN_FILTERS} style={{ minWidth: 180 }}/>
          </Toolbar>
        </div>

        {rows.length === 0 ? (
          <EmptyState icon={<IconAd size={26}/>}
            title={(campaigns || []).length ? "Nothing in that status" : "No campaigns yet"}
            description={(campaigns || []).length
              ? "Try a different status or search."
              : "Campaigns appear here once an advertiser submits one."}/>
        ) : (
          <DataTable rows={rows} keyField="campaign_id"
            onRowClick={(r) => window.__nav(detailRoute, r.campaign_id)}
            columns={[
              { key: "name", label: "Campaign", render: r => {
                const cr = creativeForCampaign(data, r);
                return (
                  <div style={{ display: "flex", gap: 12, alignItems: "center", minWidth: 240 }}>
                    {cr?.image_url
                      ? <img src={cr.image_url} alt="" style={{ width: 64, height: 40, objectFit: "cover", borderRadius: 6, border: "1px solid var(--line)" }}/>
                      : <div style={{ width: 64, height: 40, borderRadius: 6, border: "1px solid var(--line)", background: "var(--panel-2)" }}/>}
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontWeight: 600 }}>{cr?.title || r.name}</div>
                      <div style={{ fontSize: 12, color: "var(--muted)" }}>
                        {[
                          showAdvertiser ? resolvedAdvertiserName(data, r.advertiser_id) : null,
                          (r.target_placements || []).join(", ") || "all placements",
                        ].filter(Boolean).join(" · ")}
                      </div>
                    </div>
                  </div>
                );
              } },
              { key: "pricing_model", label: "Bid", render: r => `${String(r.pricing_model || "cpc").toUpperCase()} $${Number(r.bid_amount || 0).toFixed(2)}` },
              { key: "ctr", label: "CTR", align: "right", render: r => (campaignStats(r).ctr * 100).toFixed(2) + "%" },
              { key: "spend", label: "Spend", align: "right", render: r => fmt.currency(campaignStats(r).spend) },
              { key: "status", label: "Status", render: r => <CampaignStatusPill status={r.status}/> },
            ]}/>
        )}
      </Card>
    </>
  );
};

// ---------------------------------------------------------------- publisher list

const PublisherCampaignsScreen = ({ data, setData }) => {
  const [status, setStatus] = React.useState("");

  const refresh = React.useCallback(async () => {
    setStatus("Loading campaigns…");
    try {
      // Creatives come from a separate publisher-scoped endpoint; without them the list, detail and
      // preview screens can name a campaign but not show the ad it runs.
      const [campaigns, creatives] = await Promise.all([
        window.__api.read("listPublisherAdCampaigns"),
        window.__api.read("listPublisherAdCreatives").catch(() => null),
      ]);
      setData(d => ({
        ...d,
        adCampaigns: campaigns ? (Array.isArray(campaigns) ? campaigns : (campaigns.campaigns || [])) : d.adCampaigns,
        adCreatives: creatives ? (Array.isArray(creatives) ? creatives : (creatives.creatives || [])) : d.adCreatives,
      }));
      setStatus("");
    } catch (e) {
      setStatus(e.message || "Could not load campaigns.");
    }
  }, [setData]);

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

  return (
    <Screen>
      <PageHead icon={IconAd} title="Campaigns"
        subtitle="Every advertiser campaign on your inventory, in any state — not just the ones awaiting a decision."
        actions={<Btn kind="secondary" onClick={refresh}>Refresh</Btn>}/>
      {status && <div style={{ fontSize: 12, color: "var(--muted)", marginBottom: 8 }}>{status}</div>}
      <CampaignList data={data} campaigns={data.adCampaigns || []} detailRoute="ad-detail" showAdvertiser/>
    </Screen>
  );
};

// ---------------------------------------------------------------- preview

// Mirrors assetForPlacement() in the API: prefer an asset for this exact placement, fall back through
// the aliases the server uses, then to the creative's default image.
const PLACEMENT_ALIASES = {
  below_article: ["below_article", "below_post"],
  below_post: ["below_post", "below_article"],
  sidebar: ["sidebar", "right_rail"],
  right_rail: ["right_rail", "sidebar"],
  left_rail: ["left_rail", "sidebar"],
  in_post: ["in_post"],
};

const assetForPlacement = (creative, placement) => {
  const assets = Array.isArray(creative?.image_assets) ? creative.image_assets : [];
  for (const alias of PLACEMENT_ALIASES[placement] || [placement]) {
    const hit = assets.find(a => a.placement === alias);
    if (hit) return hit;
  }
  return assets[0] || null;
};

/** The widget's own card markup and styles, reproduced so the preview matches what readers see. */
const WidgetCard = ({ creative, campaign, placement, sponsoredLabel = "Sponsored" }) => {
  const asset = assetForPlacement(creative, placement);
  const image = asset?.image_url || creative?.image_url || "";
  const brand = campaign?.branding_text || creative?.brand_name || "";
  return (
    <div className="pw-prev-card">
      {image ? <img className="pw-prev-img" src={image} alt=""/> : <div className="pw-prev-img pw-prev-img-empty"/>}
      <span className="pw-prev-meta">
        <span className="pw-prev-label">{sponsoredLabel}</span>
        {brand && <span className="pw-prev-brand">{brand}</span>}
      </span>
      <strong className="pw-prev-title">{creative?.title || campaign?.name || "Untitled"}</strong>
      {creative?.description && <p className="pw-prev-desc">{creative.description}</p>}
      {creative?.cta && <span className="pw-prev-cta">{creative.cta}</span>}
    </div>
  );
};

const PREVIEW_PLACEMENTS = [
  { code: "below_article", label: "Below article", width: 260 },
  { code: "in_post", label: "In post", width: 260 },
  { code: "right_rail", label: "Right rail", width: 220 },
  { code: "left_rail", label: "Left rail", width: 220 },
];

const AdPreviewScreen = ({ data, campaignId, mode = "publisher" }) => {
  const campaign = (data.adCampaigns || []).find(c => c.campaign_id === campaignId)
    || (data.adApprovals || []).find(c => c.campaign_id === campaignId);
  const detailRoute = mode === "advertiser" ? "advertiser-ad-detail" : "ad-detail";

  if (!campaign) {
    return (
      <Screen>
        <PageHead icon={IconAd} title="Preview"/>
        <EmptyState icon={<IconAd size={30}/>} title="Campaign not found"
          description="Open a campaign from the list to preview it."/>
      </Screen>
    );
  }

  const creative = creativeForCampaign(data, campaign);
  // A campaign with no placement targeting is eligible everywhere, so preview every variant.
  const targeted = (campaign.target_placements || []).length
    ? PREVIEW_PLACEMENTS.filter(p => campaign.target_placements.includes(p.code))
    : PREVIEW_PLACEMENTS;
  const shown = targeted.length ? targeted : PREVIEW_PLACEMENTS;

  return (
    <Screen>
      <PageHead icon={IconAd} title={`Preview · ${creative?.title || campaign.name}`}
        subtitle="How this ad renders in the recommendation widget, per placement it targets."
        actions={<Btn kind="secondary" onClick={() => window.__nav(detailRoute, campaign.campaign_id)}>Back to details</Btn>}/>

      <Card title="Placements" subtitle="Each card uses the image asset uploaded for that placement, falling back to the default creative image.">
        <div className="pw-prev-grid">
          {shown.map(p => (
            <div key={p.code} className="pw-prev-slot">
              <div className="pw-prev-slot-head">
                <strong>{p.label}</strong>
                <code>{p.code}</code>
                {!assetForPlacement(creative, p.code) && <Pill tone="warn">No asset</Pill>}
              </div>
              <div style={{ width: p.width, maxWidth: "100%" }}>
                <WidgetCard creative={creative} campaign={campaign} placement={p.code}/>
              </div>
            </div>
          ))}
        </div>
      </Card>

      <Card title="What readers can click">
        <div style={{ display: "grid", gap: 12 }}>
          <MiniStat label="Destination" value={creative?.target_url || "Not set"}/>
          <MiniStat label="Disclosure" value="Labelled “Sponsored” — set per placement by the publisher"/>
          <MiniStat label="Click tracking"
            value="Clicks route through a signed redirect, so only served impressions can be billed"/>
        </div>
      </Card>

      <style>{`
        .pw-prev-grid { display: flex; flex-wrap: wrap; gap: 24px; }
        .pw-prev-slot { display: flex; flex-direction: column; gap: 10px; }
        .pw-prev-slot-head { display: flex; align-items: center; gap: 8px; font-size: 13px; }
        .pw-prev-slot-head code { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
        .pw-prev-card { position: relative; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel); }
        .pw-prev-img { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; }
        .pw-prev-img-empty { background: var(--panel-2); }
        .pw-prev-meta { display: flex; gap: 6px; align-items: center; padding: 10px 12px 0; font-size: 11px; color: var(--muted); text-transform: uppercase; }
        .pw-prev-label { font-weight: 700; }
        .pw-prev-title { display: block; padding: 6px 12px 0; font-size: 14px; line-height: 1.35; }
        .pw-prev-desc { padding: 6px 12px 0; margin: 0; color: var(--muted); font-size: 13px; line-height: 1.4; }
        .pw-prev-cta { display: block; padding: 10px 12px 12px; color: var(--accent); font-weight: 600; font-size: 13px; }
      `}</style>
    </Screen>
  );
};

Object.assign(window, {
  CampaignList, CampaignStatusPill, campaignStatusMeta, creativeForCampaign, resolvedAdvertiserName,
  PublisherCampaignsScreen, AdPreviewScreen, WidgetCard,
});
