// =================================================================
// Native recommendation ads: operator creatives/campaigns + publisher placements.
// =================================================================

// What an advertiser can buy. 'native' is the original headline-plus-image unit; the other three are
// content the publisher hosts. Pricing, approval and billing are identical for all four — only the
// submission form, the review view and the card template differ.
const CREATIVE_TYPES = [
  { value: "native", label: "Native ad", hint: "Headline, image and a link to your own landing page." },
  { value: "press_release", label: "Press release", hint: "A full announcement published on the publisher's site." },
  { value: "advertorial", label: "Advertorial", hint: "Long-form sponsored editorial, labelled as sponsored." },
  { value: "classified", label: "Classified", hint: "A short listing with a price, location and contact details." },
];
const CREATIVE_TYPE_LABEL = CREATIVE_TYPES.reduce((m, t) => ({ ...m, [t.value]: t.label }), {});
const CREATIVE_TYPE_TONE = { native: "neutral", press_release: "info", advertorial: "warn", classified: "good" };
const isArticleType = (t) => t === "press_release" || t === "advertorial";
const CLASSIFIED_PRICE_TYPES = [
  { label: "Fixed price", value: "fixed" },
  { label: "Negotiable", value: "negotiable" },
  { label: "On request", value: "on_request" },
  { label: "Free", value: "free" },
];
const CLASSIFIED_CONDITIONS = [
  { label: "Not specified", value: "" },
  { label: "New", value: "new" },
  { label: "Like new", value: "like_new" },
  { label: "Used", value: "used" },
  { label: "Refurbished", value: "refurbished" },
];
const CreativeTypeBadge = ({ type }) => {
  const t = type || "native";
  return <Pill tone={CREATIVE_TYPE_TONE[t] || "neutral"}>{CREATIVE_TYPE_LABEL[t] || t}</Pill>;
};

const advName = (data, id) => (data.advertisers.find(a => a.advertiser_id === id) || {}).name || id || "Brand";
const creativeName = (data, id) => (data.adCreatives.find(c => c.creative_id === id) || {}).title || id;
const DEMO_PUBLISHER_ID = "550e8400-e29b-41d4-a716-446655440010";
const isUuid = (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(String(v || ""));
const campaignStats = (c) => {
  const impressions = Number(c.impressions || 0);
  const clicks = Number(c.clicks || 0);
  const spend = Number(c.spend || 0);
  return { impressions, clicks, spend, ctr: impressions ? clicks / impressions : 0, ecpm: impressions ? spend / impressions * 1000 : 0 };
};
const metricTrend = (seed = 10) => Array.from({ length: 12 }, (_, i) => Math.max(1, Math.round(seed * (0.45 + i / 14 + ((i % 3) * 0.08)))));
const reportToStats = (campaign, report) => {
  const base = campaignStats(campaign || {});
  if (!report) return base;
  return {
    impressions: Number(report.impressions ?? base.impressions),
    clicks: Number(report.clicks ?? base.clicks),
    spend: Number(report.spend ?? base.spend),
    ctr: Number(report.ctr ?? base.ctr),
    ecpm: Number(report.ecpm ?? base.ecpm),
  };
};

const CreativeDrawer = ({ open, onClose, creative, data, onSave }) => {
  const blank = {
    advertiser_id: data.advertisers[0]?.advertiser_id || "", publisher_id: "", creative_type: "native",
    title: "", description: "", image_url: "", target_url: "", cta: "Learn more", brand_name: "", status: "draft",
    article: null, classified: null,
  };
  const [f, setF] = React.useState(blank);
  React.useEffect(() => { if (open) setF(creative ? { ...blank, ...creative } : blank); }, [open, creative]);
  const set = (p) => setF(x => ({ ...x, ...p }));
  const setArticle = (p) => setF(x => ({ ...x, article: { ...(x.article || {}), ...p } }));
  const setClassified = (p) => setF(x => ({ ...x, classified: { ...(x.classified || {}), ...p } }));
  const type = f.creative_type || "native";
  const hosted = isArticleType(type);
  // Hosted formats derive their own target_url from the publisher's article base, so the operator
  // supplies a publisher instead of a destination.
  const canSave = f.advertiser_id && f.title && f.image_url
    && (hosted ? (f.publisher_id && wordCount(f.article?.body_html) >= 40) : f.target_url)
    && (type !== "classified" || f.classified?.category);
  return <Drawer open={open} onClose={onClose} title={creative ? "Edit creative" : "New creative"} width={620}
    footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!canSave} onClick={() => { onSave(f, !!creative); onClose(); }}>Save creative</Btn></>}>
    <div style={{ display: "grid", gap: 16 }}>
      <FieldRow label="Format"><Select value={type} onChange={v => set({ creative_type: v })} options={CREATIVE_TYPES.map(t => ({ label: t.label, value: t.value }))}/></FieldRow>
      <FieldRow label="Advertiser"><Select value={f.advertiser_id} onChange={v => set({ advertiser_id: v, brand_name: advName(data, v) })} options={data.advertisers.map(a => ({ label: a.name, value: a.advertiser_id }))}/></FieldRow>
      {(hosted || type === "classified") && <FieldRow label="Publisher">
        <Select value={f.publisher_id || ""} onChange={v => set({ publisher_id: v })}
          options={[{ label: "— select —", value: "" }, ...(data.publishers || []).map(p => ({ label: p.name, value: p.publisher_id }))]}/>
      </FieldRow>}
      <FieldRow label="Title"><Input value={f.title} onChange={e => set({ title: e.target.value })}/></FieldRow>
      <FieldRow label="Description"><Input value={f.description || ""} onChange={e => set({ description: e.target.value })}/></FieldRow>
      <FieldRow label="Image URL"><Input value={f.image_url} onChange={e => set({ image_url: e.target.value })}/></FieldRow>
      {hosted
        ? <Banner tone="info" icon={<IconInfo size={15}/>}>The destination is generated from the publisher's article page URL and this headline's slug, so there is nothing to enter here.</Banner>
        : <FieldRow label="Target URL"><Input value={f.target_url} onChange={e => set({ target_url: e.target.value })}/></FieldRow>}
      {hosted && <>
        <FieldGroup cols={2}>
          <div className="pw-ff"><label className="pw-lbl">Byline</label><Input value={f.article?.byline || ""} onChange={e => setArticle({ byline: e.target.value })}/></div>
          <div className="pw-ff"><label className="pw-lbl">Source organisation</label><Input value={f.article?.source_org || ""} onChange={e => setArticle({ source_org: e.target.value })}/></div>
        </FieldGroup>
        <div className="pw-ff"><label className="pw-lbl">Article body</label>
          <RichTextEditor value={f.article?.body_html || ""} minWords={40} onChange={v => setArticle({ body_html: v })}/>
        </div>
      </>}
      {type === "classified" && <>
        <FieldGroup cols={2}>
          <div className="pw-ff"><label className="pw-lbl">Category</label><Input value={f.classified?.category || ""} onChange={e => setClassified({ category: e.target.value })}/></div>
          <div className="pw-ff"><label className="pw-lbl">Location</label><Input value={f.classified?.location || ""} onChange={e => setClassified({ location: e.target.value })}/></div>
        </FieldGroup>
        <FieldGroup cols={3}>
          <div className="pw-ff"><label className="pw-lbl">Price type</label><Select value={f.classified?.price_type || "fixed"} onChange={v => setClassified({ price_type: v })} options={CLASSIFIED_PRICE_TYPES}/></div>
          <div className="pw-ff"><label className="pw-lbl">Price</label><Input type="number" value={f.classified?.price ?? ""} onChange={e => setClassified({ price: e.target.value === "" ? null : Number(e.target.value) })}/></div>
          <div className="pw-ff"><label className="pw-lbl">Expires</label><Input type="date" value={f.classified?.valid_until || ""} onChange={e => setClassified({ valid_until: e.target.value || null })}/></div>
        </FieldGroup>
      </>}
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">CTA</label><Input value={f.cta || ""} onChange={e => set({ cta: e.target.value })}/></div>
        <div><label className="pw-lbl">Brand label</label><Input value={f.brand_name || ""} onChange={e => set({ brand_name: e.target.value })}/></div>
      </FieldGroup>
      <FieldRow label="Review status"><SegmentedControl value={f.status} onChange={v => set({ status: v })} options={[{ label: "Draft", value: "draft" }, { label: "Review", value: "pending_review" }, { label: "Approved", value: "approved" }, { label: "Paused", value: "paused" }]}/></FieldRow>
    </div>
  </Drawer>;
};

const AdCreativesScreen = ({ data, setData }) => {
  const [drawer, setDrawer] = React.useState(false);
  const [editing, setEditing] = React.useState(null);
  // The drawer keeps blank strings and nulls for fields the chosen format does not use; the API
  // contract rejects those rather than ignoring them, so strip them before sending.
  const payloadOf = (f) => {
    const type = f.creative_type || "native";
    const body = {
      advertiser_id: f.advertiser_id,
      creative_type: type,
      title: f.title,
      description: f.description || null,
      image_url: f.image_url,
      cta: f.cta || null,
      brand_name: f.brand_name || null,
      status: f.status || "draft",
    };
    if (f.publisher_id) body.publisher_id = f.publisher_id;
    // A hosted article's destination is derived server-side from the publisher's article base URL.
    if (!isArticleType(type) && f.target_url) body.target_url = f.target_url;
    if (isArticleType(type) && f.article?.body_html) body.article = f.article;
    if (type === "classified" && f.classified?.category) body.classified = f.classified;
    return body;
  };
  const save = (f, isEdit) => {
    const body = payloadOf(f);
    if (isEdit) {
      window.__api.write("updateAdCreative", d => ({ ...d, adCreatives: d.adCreatives.map(c => c.creative_id === f.creative_id ? { ...c, ...f } : c) }), { params: { creative_id: f.creative_id }, body, ok: "Creative updated", err: "Could not update creative" });
    } else {
      const creative_id = "cr_" + Math.random().toString(36).slice(2, 8);
      window.__api.write("createAdCreative", d => ({ ...d, adCreatives: [{ ...f, creative_id, created_at: new Date().toISOString() }, ...d.adCreatives] }), { body, ok: "Creative created", err: "Could not create creative" });
    }
  };
  return <Screen>
    <PageHead icon={IconAd} title="Ad Creatives" subtitle="Native recommendation cards approved for campaign use." actions={<Btn kind="primary" icon={<IconPlus size={15}/>} onClick={() => { setEditing(null); setDrawer(true); }}>New creative</Btn>}/>
    <Card padded={false}>
      <DataTable rows={data.adCreatives || []} keyField="creative_id" onRowClick={(r) => { setEditing(r); setDrawer(true); }} columns={[
        { key: "title", label: "Creative", render: r => <div><div style={{ fontWeight: 600 }}>{r.title}</div><div style={{ fontSize: 12, color: "var(--muted)" }}>{r.target_url}</div></div> },
        { key: "advertiser_id", label: "Advertiser", render: r => advName(data, r.advertiser_id) },
        { key: "status", label: "Status", render: r => <StatusPill status={r.status}/> },
      ]}/>
    </Card>
    <CreativeDrawer open={drawer} onClose={() => setDrawer(false)} creative={editing} data={data} onSave={save}/>
  </Screen>;
};

const CampaignDrawer = ({ open, onClose, campaign, data, onSave }) => {
  const blank = { advertiser_id: data.advertisers[0]?.advertiser_id || "", name: "", pricing_model: "cpc", bid_amount: 1, currency: "USD", daily_budget: 50, total_budget: 1000, target_publishers: [], target_placements: ["below_article"], target_segments: [], blocked_publishers: [], blocked_categories: [], creative_ids: [], status: "active" };
  const [f, setF] = React.useState(blank);
  React.useEffect(() => { if (open) setF(campaign ? { ...campaign } : blank); }, [open, campaign]);
  const set = (p) => setF(x => ({ ...x, ...p }));
  const toggleCreative = (id) => set({ creative_ids: f.creative_ids.includes(id) ? f.creative_ids.filter(x => x !== id) : [...f.creative_ids, id] });
  const canSave = f.advertiser_id && f.name && f.bid_amount > 0 && f.creative_ids.length;
  return <Drawer open={open} onClose={onClose} title={campaign ? "Edit campaign" : "New campaign"} width={680}
    footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!canSave} onClick={() => { onSave(f, !!campaign); onClose(); }}>Save campaign</Btn></>}>
    <div style={{ display: "grid", gap: 16 }}>
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Advertiser</label><Select value={f.advertiser_id} onChange={v => set({ advertiser_id: v })} options={data.advertisers.map(a => ({ label: a.name, value: a.advertiser_id }))}/></div>
        <div><label className="pw-lbl">Name</label><Input value={f.name} onChange={e => set({ name: e.target.value })}/></div>
      </FieldGroup>
      <FieldGroup cols={3}>
        <div><label className="pw-lbl">Pricing</label><SegmentedControl value={f.pricing_model} onChange={v => set({ pricing_model: v })} options={[{ label: "CPC", value: "cpc" }, { label: "CPM", value: "cpm" }]}/></div>
        <div><label className="pw-lbl">Bid</label><Input prefix="$" type="number" value={f.bid_amount} onChange={e => set({ bid_amount: Number(e.target.value) || 0 })}/></div>
        <div><label className="pw-lbl">Daily budget</label><Input prefix="$" type="number" value={f.daily_budget || ""} onChange={e => set({ daily_budget: Number(e.target.value) || null })}/></div>
      </FieldGroup>
      <FieldRow label="Placements" hint="Comma-separated placement codes."><Input value={(f.target_placements || []).join(", ")} onChange={e => set({ target_placements: e.target.value.split(",").map(x => x.trim()).filter(Boolean) })}/></FieldRow>
      <FieldRow label="Creatives">
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>{data.adCreatives.map(c => <button key={c.creative_id} className={`pw-chip ${f.creative_ids.includes(c.creative_id) ? "on" : ""}`} onClick={() => toggleCreative(c.creative_id)}>{creativeName(data, c.creative_id)}</button>)}</div>
      </FieldRow>
      <FieldRow label="Status"><SegmentedControl value={f.status} onChange={v => set({ status: v })} options={[{ label: "Active", value: "active" }, { label: "Paused", value: "paused" }, { label: "Draft", value: "draft" }, { label: "Ended", value: "ended" }]}/></FieldRow>
    </div>
  </Drawer>;
};

const AdCampaignsScreen = ({ data, setData }) => {
  const [drawer, setDrawer] = React.useState(false);
  const [editing, setEditing] = React.useState(null);
  const save = (f, isEdit) => {
    if (isEdit) {
      window.__api.write("updateAdCampaign", d => ({ ...d, adCampaigns: d.adCampaigns.map(c => c.campaign_id === f.campaign_id ? { ...c, ...f } : c) }), { params: { campaign_id: f.campaign_id }, body: f, ok: "Campaign updated", err: "Could not update campaign" });
    } else {
      const campaign_id = "camp_" + Math.random().toString(36).slice(2, 8);
      window.__api.write("createAdCampaign", d => ({ ...d, adCampaigns: [{ ...f, campaign_id, impressions: 0, clicks: 0, spend: 0 }, ...d.adCampaigns] }), { body: { ...f, campaign_id }, ok: "Campaign created", err: "Could not create campaign" });
    }
  };
  return <Screen>
    <PageHead icon={IconActivity} title="Ad Campaigns" subtitle="CPC and CPM native recommendation campaigns." actions={<Btn kind="primary" icon={<IconPlus size={15}/>} onClick={() => { setEditing(null); setDrawer(true); }}>New campaign</Btn>}/>
    <KpiGrid>
      <StatCard label="Campaigns" value={(data.adCampaigns || []).length} sub="all statuses" icon={<IconAd size={16}/>}/>
      <StatCard label="Impressions" value={fmt.compact((data.adCampaigns || []).reduce((s, c) => s + campaignStats(c).impressions, 0))} sub="fixture/reporting" icon={<IconChart size={16}/>}/>
      <StatCard label="Clicks" value={fmt.compact((data.adCampaigns || []).reduce((s, c) => s + campaignStats(c).clicks, 0))} sub="tracked" icon={<IconActivity size={16}/>}/>
    </KpiGrid>
    <Card padded={false}>
      <DataTable rows={data.adCampaigns || []} keyField="campaign_id" onRowClick={(r) => { setEditing(r); setDrawer(true); }} columns={[
        { key: "name", label: "Campaign", render: r => <div><div style={{ fontWeight: 600 }}>{r.name}</div><div style={{ fontSize: 12, color: "var(--muted)" }}>{advName(data, r.advertiser_id)}</div></div> },
        { key: "pricing_model", label: "Model", render: r => String(r.pricing_model).toUpperCase() + " $" + r.bid_amount },
        { 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 => <StatusPill status={r.status}/> },
      ]}/>
    </Card>
    <CampaignDrawer open={drawer} onClose={() => setDrawer(false)} campaign={editing} data={data} onSave={save}/>
  </Screen>;
};

const PlacementDrawer = ({ open, onClose, placement, onSave }) => {
  const blank = { placement_code: "", name: "", layout: "grid", max_items: 4, sponsored_label: "Sponsored", organic_fallback_enabled: false, status: "active" };
  const [f, setF] = React.useState(blank);
  React.useEffect(() => { if (open) setF(placement ? { ...placement } : blank); }, [open, placement]);
  const set = (p) => setF(x => ({ ...x, ...p }));
  const canSave = f.placement_code && f.name;
  return <Drawer open={open} onClose={onClose} title={placement ? "Edit placement" : "New placement"} width={560}
    footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!canSave} onClick={() => { onSave(f, !!placement); onClose(); }}>Save placement</Btn></>}>
    <div style={{ display: "grid", gap: 16 }}>
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Code</label><Input disabled={!!placement} value={f.placement_code} onChange={e => set({ placement_code: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_") })}/></div>
        <div><label className="pw-lbl">Name</label><Input value={f.name} onChange={e => set({ name: e.target.value })}/></div>
      </FieldGroup>
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Layout</label><Select value={f.layout} onChange={v => set({ layout: v })} options={[{ label: "Grid", value: "grid" }, { label: "List", value: "list" }, { label: "Carousel", value: "carousel" }]}/></div>
        <div><label className="pw-lbl">Max cards</label><Input type="number" value={f.max_items} onChange={e => set({ max_items: Number(e.target.value) || 1 })}/></div>
      </FieldGroup>
      <FieldRow label="Sponsored label"><Input value={f.sponsored_label} onChange={e => set({ sponsored_label: e.target.value })}/></FieldRow>
      <label style={{ display: "flex", gap: 8, alignItems: "center", fontSize: 13 }}><input type="checkbox" checked={!!f.organic_fallback_enabled} onChange={e => set({ organic_fallback_enabled: e.target.checked })}/> Allow organic fallback for no-ads readers</label>
      <FieldRow label="Status"><SegmentedControl value={f.status} onChange={v => set({ status: v })} options={[{ label: "Active", value: "active" }, { label: "Paused", value: "paused" }]}/></FieldRow>
    </div>
  </Drawer>;
};

const AdPlacementsScreen = ({ data, setData }) => {
  const [drawer, setDrawer] = React.useState(false);
  const [editing, setEditing] = React.useState(null);
  const connStatus = window.useConnStatus ? window.useConnStatus() : { state: "mock" };
  const liveLoading = !!window.__api?.getConfig?.().live && ["unknown", "connecting"].includes(connStatus.state);
  const save = (f, isEdit) => {
    if (isEdit) {
      window.__api.write("updateAdPlacement", d => ({ ...d, adPlacements: d.adPlacements.map(p => p.placement_code === f.placement_code ? { ...p, ...f } : p) }), { params: { placement_code: f.placement_code }, body: f, ok: "Placement updated", err: "Could not update placement" });
    } else {
      window.__api.write("createAdPlacement", d => ({ ...d, adPlacements: [{ ...f, publisher_id: data.publishers[0]?.publisher_id || "pub_we72" }, ...d.adPlacements] }), { body: f, ok: "Placement created", err: "Could not create placement" });
    }
  };
  const snippet = `Paywall.init({\n  apiBase,\n  getAccessToken,\n  getPublisherToken,\n  recommendations: { enabled: true, autoMount: true }\n});\n\n<div data-paywall-recommendations data-paywall-placement="below_article" data-paywall-content="article-123"></div>`;
  const approvalMap = new Map();
  (data.adApprovals || []).forEach(c => approvalMap.set(c.campaign_id, c));
  (data.adCampaigns || []).filter(c => c.status === "pending_review").forEach(c => {
    approvalMap.set(c.campaign_id, { ...(approvalMap.get(c.campaign_id) || {}), ...c });
  });
  const approvals = Array.from(approvalMap.values());
  const creativeFor = (campaign) => (data.adCreatives || []).find(c => (campaign.creative_ids || []).includes(c.creative_id));
  const review = (campaign, status) => {
    window.__api.write("approvePublisherAd",
      d => ({
        ...d,
        adApprovals: (d.adApprovals || []).filter(c => c.campaign_id !== campaign.campaign_id),
        adCampaigns: d.adCampaigns.map(c => c.campaign_id === campaign.campaign_id ? { ...c, status: status === "approved" ? "active" : "rejected" } : c),
        adCreatives: d.adCreatives.map(c => (campaign.creative_ids || []).includes(c.creative_id) ? { ...c, status } : c),
      }),
      { params: { campaign_id: campaign.campaign_id }, body: { status }, ok: `Campaign ${status}`, err: "Could not review campaign" });
  };
  return <Screen>
    <PageHead icon={IconList} title="Ad Approvals" subtitle="Review client-submitted recommendation ads and manage placement snippets." actions={<Btn kind="primary" icon={<IconPlus size={15}/>} onClick={() => { setEditing(null); setDrawer(true); }}>New placement</Btn>}/>
    <Card title="SDK opt-in snippet" subtitle="Recommendations stay disabled unless the publisher enables this extension."><pre style={{ margin: 0, whiteSpace: "pre-wrap", fontFamily: "var(--mono)", fontSize: 12 }}>{snippet}</pre></Card>
    <Card title="Approval queue" subtitle="Advertiser-submitted campaigns must be approved before serving." padded={false}>
      {liveLoading ? <EmptyState icon={<IconActivity size={30}/>} title="Loading ad approvals" description="Fetching live publisher submissions."/> : approvals.length ? <DataTable rows={approvals} keyField="campaign_id" columns={[
        { key: "name", label: "Ad submitted", render: r => {
          const cr = creativeFor(r);
          return <div style={{ display: "flex", gap: 12, alignItems: "center", minWidth: 260 }}>
            {cr?.image_url && <img src={cr.image_url} alt="" style={{ width: 72, height: 44, objectFit: "cover", borderRadius: 6, border: "1px solid var(--border)" }}/>}
            <div><div style={{ fontWeight: 600 }}>{cr?.title || r.name}</div><div style={{ fontSize: 12, color: "var(--muted)" }}>{advName(data, r.advertiser_id)} · {cr?.business_category || r.business_category || r.marketing_objective || "native ad"}</div></div>
          </div>;
        } },
        // Press releases and advertorials must be read before they are approved, so the row leads to
        // the detail view rather than inviting a decision from the queue alone.
        { key: "creative_type", label: "Format", render: r => <CreativeTypeBadge type={creativeFor(r)?.creative_type}/> },
        { key: "bid_amount", label: "CPC", align: "right", render: r => "$" + Number(r.bid_amount || 0).toFixed(2) },
        { key: "geo_targets", label: "Geo", render: r => (r.geo_targets || []).join(", ") || "Any" },
        { key: "actions", label: "", align: "right", sortable: false, render: r => {
          const type = creativeFor(r)?.creative_type;
          // An article has to be read to be reviewed. Approving straight from the queue stays
          // available for native ads and classifieds, where the row already shows the whole ad.
          if (isArticleType(type)) {
            return <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <Btn kind="primary" size="sm" onClick={() => window.__nav("ad-detail", r.campaign_id)}>Read &amp; review</Btn>
            </div>;
          }
          return <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}><Btn kind="secondary" size="sm" onClick={() => window.__nav("ad-detail", r.campaign_id)}>Details</Btn><Btn kind="secondary" size="sm" onClick={() => review(r, "rejected")}>Disapprove</Btn><Btn kind="primary" size="sm" onClick={() => review(r, "approved")}>Approve</Btn></div>;
        } },
      ]}/> : <EmptyState icon={<IconCheck size={30}/>} title="No ads waiting for approval" description="Client submissions will appear here as pending review."/>}
    </Card>
    <Card title="All publisher ads" subtitle="Open an ad to review details and performance." padded={false}>
      {liveLoading ? <EmptyState icon={<IconActivity size={30}/>} title="Loading publisher ads" description="Fetching live campaign data."/> : <DataTable rows={data.adCampaigns || []} keyField="campaign_id" onRowClick={(r) => window.__nav("ad-detail", r.campaign_id)} columns={[
        { key: "name", label: "Ad", render: r => <div><div style={{ fontWeight: 600 }}>{r.name}</div><div style={{ fontSize: 12, color: "var(--muted)" }}>{advName(data, r.advertiser_id)} · {(r.target_placements || []).join(", ") || "all placements"}</div></div> },
        { key: "pricing_model", label: "Bid", render: r => String(r.pricing_model || "cpc").toUpperCase() + " $" + Number(r.bid_amount || 0).toFixed(2) },
        { key: "impressions", label: "Impr.", align: "right", render: r => fmt.compact(campaignStats(r).impressions) },
        { key: "clicks", label: "Clicks", align: "right", render: r => fmt.compact(campaignStats(r).clicks) },
        { key: "status", label: "Status", render: r => <StatusPill status={r.status}/> },
      ]}/>}
    </Card>
    <Card padded={false}>
      {liveLoading ? <EmptyState icon={<IconActivity size={30}/>} title="Loading placements" description="Fetching live placement snippets."/> : <DataTable rows={data.adPlacements || []} keyField="placement_code" onRowClick={(r) => { setEditing(r); setDrawer(true); }} columns={[
        { key: "placement_code", label: "Placement", render: r => <div><div style={{ fontWeight: 600 }}>{r.name}</div><div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--muted)" }}>{r.placement_code}</div></div> },
        { key: "layout", label: "Layout" },
        { key: "max_items", label: "Cards", align: "right" },
        { key: "organic_fallback_enabled", label: "No-ads fallback", render: r => r.organic_fallback_enabled ? "Organic allowed" : "Hidden" },
        { key: "status", label: "Status", render: r => <StatusPill status={r.status}/> },
      ]}/>}
    </Card>
    <PlacementDrawer open={drawer} onClose={() => setDrawer(false)} placement={editing} onSave={save}/>
  </Screen>;
};

Object.assign(window, { AdCreativesScreen, AdCampaignsScreen, AdPlacementsScreen });

// =================================================================
// Advertiser/client portal
// =================================================================

const AD_ASSET_SPECS = [
  { placement: "below_post", label: "Below post", size: "1200x628 or 16:9" },
  { placement: "in_post", label: "In between post", size: "1000x600 or 5:3" },
  { placement: "right_rail", label: "Right of post", size: "600x500 or 6:5" },
  { placement: "left_rail", label: "Left of post", size: "600x500 or 6:5" },
];
const blankAdAssets = (url = "") => AD_ASSET_SPECS.map(s => ({ placement: s.placement, label: s.label, image_url: url, mime_type: url.toLowerCase().includes(".gif") ? "image/gif" : "image/webp" }));

const ADV_TOKEN_KEY = "dropcap.advertiser.token.v1";
const ADV_PROFILE_KEY = "dropcap.advertiser.profile.v1";

// In-memory fallback: keeps the advertiser session alive within the SPA even
// when localStorage is unavailable (sandboxed preview, strict privacy mode).
let ADV_MEM = { token: "", profile: null };
const loadAdvProfile = () => {
  try {
    const p = JSON.parse(localStorage.getItem(ADV_PROFILE_KEY) || "null");
    if (p) return p;
  } catch (e) {}
  return ADV_MEM.profile;
};
const loadAdvToken = () => {
  try {
    const t = localStorage.getItem(ADV_TOKEN_KEY);
    if (t) return t;
  } catch (e) {}
  return ADV_MEM.token || "";
};
const saveAdvSession = (token, profile) => {
  ADV_MEM = { token: token || "", profile: profile || null };
  try {
    localStorage.setItem(ADV_TOKEN_KEY, token || "");
    localStorage.setItem(ADV_PROFILE_KEY, JSON.stringify(profile || null));
  } catch (e) {}
};
const advBase = () => window.__api?.getConfig?.().baseUrl || "";
const advLive = () => !!window.__api?.getConfig?.().live;
const advCall = async (path, { method = "POST", body, token, raw } = {}) => {
  const headers = {};
  if (!raw && method !== "GET") headers["Content-Type"] = "application/json";
  if (method !== "GET") headers["Idempotency-Key"] = crypto.randomUUID ? crypto.randomUUID() : String(Date.now());
  if (token) headers.Authorization = `Bearer ${token}`;
  const res = await fetch(advBase() + path, { method, headers, body: method === "GET" ? undefined : (raw ? body : JSON.stringify(body || {})) });
  const json = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(json?.error?.message || "Request failed");
  return json;
};

const AdvertiserLoginScreen = ({ data, setData }) => {
  const [mode, setMode] = React.useState("login");
  const [profile, setProfile] = React.useState(loadAdvProfile());
  const [f, setF] = React.useState({ publisher_id: isUuid(data.publishers[0]?.publisher_id) ? data.publishers[0].publisher_id : DEMO_PUBLISHER_ID, advertiser_name: "", email: "advertiser@brand.example", password: "demo-pass", business_category: "Retail" });
  const [status, setStatus] = React.useState("");
  const set = (p) => setF(x => ({ ...x, ...p }));
  const submit = async () => {
    setStatus("Signing in...");
    try {
      if (!advLive()) {
        const fake = { advertiser_id: "adv_demo", advertiser_user_id: "adu_demo", publisher_id: f.publisher_id, name: f.advertiser_name || "Demo Advertiser", email: f.email, business_category: f.business_category };
        saveAdvSession("mock-advertiser-token", fake);
        setProfile(fake);
        setStatus("Mock advertiser session active.");
        return;
      }
      const path = mode === "register" ? "/advertiser/v1/register" : "/advertiser/v1/login";
      const publisher_id = isUuid(f.publisher_id) ? f.publisher_id : DEMO_PUBLISHER_ID;
      const payload = mode === "register"
        ? { publisher_id, advertiser_name: f.advertiser_name || "Advertiser Demo", email: f.email, password: f.password, business_category: f.business_category }
        : { publisher_id, email: f.email, password: f.password };
      const out = await advCall(path, { body: payload });
      saveAdvSession(out.access_token, out.advertiser);
      setProfile(out.advertiser);
      try {
        const campaigns = await advCall("/advertiser/v1/campaigns", { method: "GET", token: out.access_token });
        setData(d => ({ ...d, adCampaigns: campaigns.campaigns || d.adCampaigns }));
      } catch (e) {}
      setStatus("Advertiser session active.");
    } catch (e) {
      setStatus(e.message || "Login failed");
    }
  };
  return <Screen>
    <PageHead icon={IconStore} title="Advertiser Login" subtitle="Clients create campaigns and submit creatives for publisher approval."/>
    <Card>
      <div style={{ display: "grid", gap: 16 }}>
        <Banner tone="info" icon={<IconInfo size={15}/>}>Dummy client login: advertiser / advertiser@brand.example / demo-pass</Banner>
        {profile && <Banner tone="info" icon={<IconInfo size={15}/>}>Signed in as {profile.name} · {profile.business_category}</Banner>}
        <SegmentedControl value={mode} onChange={setMode} options={[{ label: "Login", value: "login" }, { label: "Register", value: "register" }]}/>
        <FieldRow label="Publisher"><Select value={f.publisher_id} onChange={v => set({ publisher_id: v })} options={[{ label: "The Lede demo live", value: DEMO_PUBLISHER_ID }, ...data.publishers.map(p => ({ label: p.name, value: p.publisher_id }))]}/></FieldRow>
        {mode === "register" && <FieldRow label="Advertiser name"><Input value={f.advertiser_name} onChange={e => set({ advertiser_name: e.target.value })}/></FieldRow>}
        <FieldGroup cols={2}>
          <div><label className="pw-lbl">Email</label><Input value={f.email} onChange={e => set({ email: e.target.value })}/></div>
          <div><label className="pw-lbl">Password</label><Input type="password" value={f.password} onChange={e => set({ password: e.target.value })}/></div>
        </FieldGroup>
        {mode === "register" && <FieldRow label="Business category"><Input value={f.business_category} onChange={e => set({ business_category: e.target.value })}/></FieldRow>}
        <div style={{ display: "flex", gap: 10, alignItems: "center" }}><Btn kind="primary" onClick={submit}>{mode === "register" ? "Create advertiser login" : "Login"}</Btn><span style={{ fontSize: 12, color: "var(--muted)" }}>{status}</span></div>
      </div>
    </Card>
  </Screen>;
};

const AdvertiserCampaignScreen = ({ data, setData }) => {
  const profile = loadAdvProfile();
  const [f, setF] = React.useState({
    creative_type: "native",
    url: "https://example.com", name: "Summer Sale - US", marketing_objective: "leads", branding_text: "Sponsored by Brand",
    daily_budget: 50, bid_amount: 0.45, geo_targets: "US, CA", device_targets: ["desktop", "mobile"],
    headline: "", description: "", image_url: "", image_assets: blankAdAssets(), cta: "Read More",
    business_category: profile?.business_category || "Retail",
    // press_release / advertorial
    body_html: "", slug: "", byline: "", dateline: "", source_org: "", seo_title: "", seo_description: "",
    // classified
    category: "", price: "", price_currency: "USD", price_type: "fixed", item_condition: "",
    location: "", contact_name: "", contact_email: "", contact_phone: "", valid_until: "",
  });
  const [status, setStatus] = React.useState("");
  const set = (p) => setF(x => ({ ...x, ...p }));
  const token = loadAdvToken;
  const preview = async () => {
    setStatus("Fetching URL metadata...");
    try {
      if (!advLive()) {
        const fallbackImage = "https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=1200&q=80";
        // Demo mode has no backend, so fetch the page directly from the browser.
        // Works when the target site allows cross-origin reads; otherwise fall back.
        try {
          const res = await fetch(f.url, { mode: "cors" });
          const html = await res.text();
          const doc = new DOMParser().parseFromString(html, "text/html");
          const og = (p) => doc.querySelector(`meta[property="og:${p}"], meta[name="og:${p}"]`)?.getAttribute("content") || "";
          const title = og("title") || doc.querySelector("title")?.textContent || "";
          const description = og("description") || doc.querySelector('meta[name="description"]')?.getAttribute("content") || "";
          const image = og("image") || fallbackImage;
          if (title || description) {
            set({ headline: title.trim().slice(0, 60), description: description.trim(), image_url: image, image_assets: blankAdAssets(image) });
            setStatus("Metadata loaded from page.");
            return;
          }
        } catch (e) {}
        let host = "your landing page";
        try { host = new URL(f.url).hostname.replace(/^www\./, ""); } catch (e) {}
        set({ headline: `Discover ${host}`.slice(0, 60), description: `Auto-generated demo creative for ${host}.`, image_url: fallbackImage, image_assets: blankAdAssets(fallbackImage) });
        setStatus("Site blocks direct metadata reads (CORS) — used demo placeholder. Connect the live API for full previews.");
        return;
      }
      const out = await advCall("/advertiser/v1/url-preview", { body: { url: f.url }, token: token() });
      const decodeEntities = (s) => { const el = document.createElement("textarea"); el.innerHTML = s || ""; return el.value; };
      set({ headline: decodeEntities(out.title).slice(0, 60), description: decodeEntities(out.description), image_url: out.image_url || "", image_assets: blankAdAssets(out.image_url || "") });
      setStatus("Metadata loaded.");
    } catch (e) { setStatus(e.message || "Preview failed"); }
  };
  const setAsset = (placement, patch) => set({ image_assets: f.image_assets.map(a => a.placement === placement ? { ...a, ...patch } : a) });
  const uploadAsset = async (placement, file) => {
    if (!file) return;
    if (advLive()) {
      try {
        setStatus(`Uploading ${placement} asset...`);
        const body = new FormData();
        body.append("placement", placement);
        body.append("file", file);
        const headers = {
          Authorization: `Bearer ${token()}`,
          "Idempotency-Key": crypto.randomUUID ? crypto.randomUUID() : String(Date.now()),
        };
        const res = await fetch(advBase() + "/advertiser/v1/ad-assets", { method: "POST", headers, body });
        const json = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(json?.error?.message || "Upload failed");
        setAsset(placement, { image_url: json.image_url, mime_type: json.mime_type });
        setStatus(`${placement} asset uploaded.`);
      } catch (e) {
        setStatus(e.message || "Upload failed");
      }
      return;
    }
    const reader = new FileReader();
    reader.onload = () => setAsset(placement, { image_url: String(reader.result || ""), mime_type: file.type || "image/webp" });
    reader.readAsDataURL(file);
  };
  const submit = async () => {
    setStatus("Submitting for publisher review...");
    const type = f.creative_type;
    // Hosted articles carry no destination: the server derives it from the publisher's article base
    // URL and the slug, so sending one would give two sources of truth for where a billed click lands.
    const assets = type === "classified" ? f.image_assets.filter(a => a.image_url) : f.image_assets;
    const payload = {
      campaign: {
        name: f.name,
        marketing_objective: f.marketing_objective,
        branding_text: f.branding_text,
        daily_budget: Number(f.daily_budget),
        bid_amount: Number(f.bid_amount),
        geo_targets: f.geo_targets.split(",").map(x => x.trim()).filter(Boolean),
        device_targets: f.device_targets,
        target_placements: ["below_article"],
      },
      creative: {
        creative_type: type,
        ...(isArticleType(type) ? {} : { destination_url: f.url }),
        headline: f.headline,
        description: f.description,
        image_url: assets[0]?.image_url || f.image_url || undefined,
        image_assets: assets,
        cta: f.cta,
        business_category: f.business_category,
        ...(isArticleType(type) ? {
          article: {
            body_html: f.body_html,
            ...(f.slug ? { slug: f.slug } : {}),
            byline: f.byline || null,
            dateline: f.dateline || null,
            source_org: f.source_org || null,
            seo_title: f.seo_title || null,
            seo_description: f.seo_description || null,
          },
        } : {}),
        ...(type === "classified" ? {
          classified: {
            category: f.category,
            price: f.price === "" ? null : Number(f.price),
            price_currency: f.price_currency,
            price_type: f.price_type,
            item_condition: f.item_condition || null,
            location: f.location || null,
            contact_name: f.contact_name || null,
            contact_email: f.contact_email || null,
            contact_phone: f.contact_phone || null,
            valid_until: f.valid_until || null,
          },
        } : {}),
      },
    };
    try {
      if (!advLive()) {
        const creative_id = "cr_pending_" + Date.now();
        const campaign_id = "camp_pending_" + Date.now();
        setData(d => ({
          ...d,
          adCreatives: [{ creative_id, advertiser_id: profile?.advertiser_id || "adv_demo", publisher_id: profile?.publisher_id || "pub_we72", creative_type: type, title: f.headline, description: f.description, image_url: payload.creative.image_url || "", image_assets: assets, target_url: isArticleType(type) ? `https://example.com/sponsored/${f.slug || "demo-article"}` : f.url, cta: f.cta, brand_name: f.branding_text, business_category: f.business_category, status: "pending_review", article: payload.creative.article || null, classified: payload.creative.classified || null }, ...d.adCreatives],
          adCampaigns: [{ campaign_id, advertiser_id: profile?.advertiser_id || "adv_demo", publisher_id: profile?.publisher_id || "pub_we72", name: f.name, marketing_objective: f.marketing_objective, branding_text: f.branding_text, pricing_model: "cpc", bid_amount: Number(f.bid_amount), daily_budget: Number(f.daily_budget), geo_targets: payload.campaign.geo_targets, device_targets: f.device_targets, creative_ids: [creative_id], status: "pending_review", impressions: 0, clicks: 0, spend: 0 }, ...d.adCampaigns],
          adApprovals: [{ campaign_id, advertiser_id: profile?.advertiser_id || "adv_demo", publisher_id: profile?.publisher_id || "pub_we72", name: f.name, marketing_objective: f.marketing_objective, branding_text: f.branding_text, business_category: f.business_category, pricing_model: "cpc", bid_amount: Number(f.bid_amount), daily_budget: Number(f.daily_budget), geo_targets: payload.campaign.geo_targets, device_targets: f.device_targets, creative_ids: [creative_id], status: "pending_review" }, ...(d.adApprovals || [])],
        }));
        setStatus("Submitted to publisher approval queue.");
        return;
      }
      const out = await advCall("/advertiser/v1/campaigns", { body: payload, token: token() });
      setStatus(out?.article_url
        ? `Submitted for review. Once approved it will publish at ${out.article_url}`
        : "Submitted to publisher approval queue.");
    } catch (e) { setStatus(e.message || "Submit failed"); }
  };

  // Mirrors the server's per-type rules, deliberately no looser: a form that submits something the
  // API will reject just turns a validation message into a round trip. The article word minimum is
  // stricter than the server's character minimum, which is the safe direction.
  const canSubmit = (() => {
    if (!f.headline) return false;
    if (f.creative_type === "native") return !!f.url && !f.image_assets.some(a => !a.image_url);
    if (isArticleType(f.creative_type)) {
      return wordCount(f.body_html) >= 40 && f.image_assets.some(a => a.image_url);
    }
    return !!f.category;
  })();

  return <Screen>
    <PageHead icon={IconAd} title="Create Campaign" subtitle="URL-first workflow: paste a landing page, then tweak the generated creative."/>
    <Card>
      <div className="pw-adv-form" style={{ display: "grid", gap: 20 }}>
        {!profile && <Banner tone="warn" icon={<IconInfo size={15}/>}>Login first so submissions are tied to an advertiser.</Banner>}

        <FormSection title="Format" description="What you are buying. Pricing and approval are the same for all four — only the content differs.">
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(190px, 1fr))", gap: 10 }}>
            {CREATIVE_TYPES.map(t => {
              const active = f.creative_type === t.value;
              return <button key={t.value} type="button" onClick={() => set({ creative_type: t.value })}
                style={{ textAlign: "left", padding: "12px 14px", borderRadius: "var(--r-md)", cursor: "pointer",
                  border: `1px solid ${active ? "var(--accent)" : "var(--border)"}`,
                  background: active ? "var(--accent-soft)" : "var(--panel)",
                  color: active ? "var(--accent-strong)" : "var(--ink)" }}>
                <div style={{ fontWeight: 600, fontSize: 13.5 }}>{t.label}</div>
                <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 3, lineHeight: 1.4 }}>{t.hint}</div>
              </button>;
            })}
          </div>
        </FormSection>

        {isArticleType(f.creative_type)
          ? <Banner tone="info" icon={<IconInfo size={15}/>}>
              This article will be published on the publisher's own site. They set the URL, so there is no
              destination to enter — readers who click the card land on the full piece.
            </Banner>
          : <FormSection title="Landing page" description="Paste a destination URL, then auto-fill to generate the creative.">
              <div className="pw-ff"><label className="pw-lbl">Destination URL</label>
                <div style={{ display: "flex", gap: 8 }}><Input value={f.url} onChange={e => set({ url: e.target.value })}/><Btn kind="secondary" onClick={preview}>Auto-fill</Btn></div>
                {status && <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 6 }}>{status}</div>}
              </div>
            </FormSection>}

        <FormSection title="Campaign">
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Campaign name</label><Input value={f.name} onChange={e => set({ name: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Objective</label><Select value={f.marketing_objective} onChange={v => set({ marketing_objective: v })} options={[{ label: "Brand awareness", value: "brand_awareness" }, { label: "Leads", value: "leads" }, { label: "Purchases", value: "purchases" }]}/></div>
          </FieldGroup>
          <FieldGroup cols={3}>
            <div className="pw-ff"><label className="pw-lbl">Daily budget</label><Input prefix="$" type="number" value={f.daily_budget} onChange={e => set({ daily_budget: Number(e.target.value) || 0 })}/></div>
            <div className="pw-ff"><label className="pw-lbl">CPC bid</label><Input prefix="$" type="number" value={f.bid_amount} onChange={e => set({ bid_amount: Number(e.target.value) || 0 })}/></div>
            <div className="pw-ff"><label className="pw-lbl">CTA</label><Select value={f.cta} onChange={v => set({ cta: v })} options={["Read More","Sign Up","Shop Now","Learn More","Read Release","View Listing","Contact Seller"].map(x => ({ label: x, value: x }))}/></div>
          </FieldGroup>
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Geo targeting</label><Input value={f.geo_targets} onChange={e => set({ geo_targets: e.target.value.toUpperCase() })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Branding text</label><Input value={f.branding_text} onChange={e => set({ branding_text: e.target.value })}/></div>
          </FieldGroup>
          <div className="pw-ff"><label className="pw-lbl">Devices</label>
            <div style={{ display: "flex", gap: 16, flexWrap: "wrap", marginTop: 2 }}>{["desktop","mobile","tablet"].map(dev => <label key={dev} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13 }}><input type="checkbox" checked={f.device_targets.includes(dev)} onChange={e => set({ device_targets: e.target.checked ? [...f.device_targets, dev] : f.device_targets.filter(x => x !== dev) })}/> {dev}</label>)}</div>
          </div>
        </FormSection>

        <FormSection title="Creative">
          <div className="pw-ff"><label className="pw-lbl">Headline</label><Input value={f.headline} maxLength={60} onChange={e => set({ headline: e.target.value })}/></div>
          <div className="pw-ff"><label className="pw-lbl">Image / GIF assets</label>
            <div style={{ fontSize: 12, color: "var(--muted)", margin: "2px 0 8px" }}>
              {f.creative_type === "native"
                ? "One asset per placement, so the ad can render below, inside, left, and right of posts."
                : f.creative_type === "classified"
                  ? "Optional for a classified — plenty of listings run without a photo."
                  : "At least one image, used for the card and as the article's lead photo."}
            </div>
            <div style={{ display: "grid", gap: 10 }}>
              {AD_ASSET_SPECS.map(spec => {
                const asset = f.image_assets.find(a => a.placement === spec.placement) || {};
                return <div key={spec.placement} style={{ display: "grid", gridTemplateColumns: "150px minmax(0,1fr) 170px", gap: 8, alignItems: "center" }}>
                  <div><div style={{ fontWeight: 600, fontSize: 13 }}>{spec.label}</div><div style={{ fontSize: 11, color: "var(--muted)" }}>{spec.size}</div></div>
                  <Input value={asset.image_url || ""} placeholder="Image/GIF URL" onChange={e => setAsset(spec.placement, { image_url: e.target.value, mime_type: e.target.value.toLowerCase().includes(".gif") ? "image/gif" : asset.mime_type })}/>
                  <input type="file" accept="image/png,image/jpeg,image/webp,image/gif" onChange={e => uploadAsset(spec.placement, e.target.files?.[0])}/>
                </div>;
              })}
            </div>
          </div>
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Description</label><Input value={f.description} onChange={e => set({ description: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Business category</label><Input value={f.business_category} onChange={e => set({ business_category: e.target.value })}/></div>
          </FieldGroup>
        </FormSection>

        {isArticleType(f.creative_type) && <FormSection
          title={f.creative_type === "press_release" ? "Press release" : "Advertorial"}
          description="Published on the publisher's site under a sponsored label. Formatting outside the toolbar is removed on publish.">
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Byline</label><Input value={f.byline} placeholder="Jane Doe" onChange={e => set({ byline: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Source organisation</label><Input value={f.source_org} placeholder="Acme Corp" onChange={e => set({ source_org: e.target.value })}/></div>
          </FieldGroup>
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Dateline</label><Input value={f.dateline} placeholder="LONDON, 12 March" onChange={e => set({ dateline: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">URL slug (optional)</label>
              <Input value={f.slug} placeholder="auto-generated from the headline" onChange={e => set({ slug: e.target.value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+/, "") })}/>
              <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 4 }}>Leave blank and we derive one from the headline. If it is taken we add a suffix.</div>
            </div>
          </FieldGroup>
          <div className="pw-ff"><label className="pw-lbl">Article body</label>
            <RichTextEditor value={f.body_html} minWords={40} onChange={v => set({ body_html: v })}
              placeholder={f.creative_type === "press_release" ? "FOR IMMEDIATE RELEASE…" : "Write the advertorial…"}/>
          </div>
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">SEO title (optional)</label><Input value={f.seo_title} onChange={e => set({ seo_title: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">SEO description (optional)</label><Input value={f.seo_description} onChange={e => set({ seo_description: e.target.value })}/></div>
          </FieldGroup>
        </FormSection>}

        {f.creative_type === "classified" && <FormSection title="Listing details" description="Shown on the card and on the listing page. Contact details are only revealed on the listing itself, never in the ad payload.">
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Category</label><Input value={f.category} placeholder="Vehicles, Property, Jobs…" onChange={e => set({ category: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Location</label><Input value={f.location} placeholder="Leeds, UK" onChange={e => set({ location: e.target.value })}/></div>
          </FieldGroup>
          <FieldGroup cols={3}>
            <div className="pw-ff"><label className="pw-lbl">Price type</label><Select value={f.price_type} onChange={v => set({ price_type: v })} options={CLASSIFIED_PRICE_TYPES}/></div>
            <div className="pw-ff"><label className="pw-lbl">Price</label><Input type="number" value={f.price} disabled={["free", "on_request"].includes(f.price_type)} onChange={e => set({ price: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Currency</label><Input value={f.price_currency} maxLength={3} onChange={e => set({ price_currency: e.target.value.toUpperCase() })}/></div>
          </FieldGroup>
          <FieldGroup cols={2}>
            <div className="pw-ff"><label className="pw-lbl">Condition</label><Select value={f.item_condition} onChange={v => set({ item_condition: v })} options={CLASSIFIED_CONDITIONS}/></div>
            <div className="pw-ff"><label className="pw-lbl">Listing expires</label>
              <Input type="date" value={f.valid_until} onChange={e => set({ valid_until: e.target.value })}/>
              <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 4 }}>After this date the listing stops serving, even if the campaign still has budget.</div>
            </div>
          </FieldGroup>
          <FieldGroup cols={3}>
            <div className="pw-ff"><label className="pw-lbl">Contact name</label><Input value={f.contact_name} onChange={e => set({ contact_name: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Contact email</label><Input type="email" value={f.contact_email} onChange={e => set({ contact_email: e.target.value })}/></div>
            <div className="pw-ff"><label className="pw-lbl">Contact phone</label><Input value={f.contact_phone} onChange={e => set({ contact_phone: e.target.value })}/></div>
          </FieldGroup>
        </FormSection>}

        <div style={{ display: "flex", gap: 10, alignItems: "center" }}><Btn kind="primary" disabled={!profile || !canSubmit} onClick={submit}>Submit for approval</Btn><span style={{ fontSize: 12, color: "var(--muted)" }}>{status}</span></div>
      </div>
      {/* pw-lbl is otherwise an unstyled class, so labels fell back to browser defaults (16px inline).
          Scope real label styling to this form's fields so every field reads consistently. */}
      <style>{`
        .pw-ff { display: flex; flex-direction: column; gap: 6px; }
        .pw-ff > .pw-lbl { font-size: 13px; font-weight: 500; color: var(--ink-2); }
        /* FormSection bodies have no row gap by default; space the fields within each section. */
        .pw-adv-form .hx-fs-body { gap: 16px; }
      `}</style>
    </Card>
  </Screen>;
};

const AdvertiserBulkUploadScreen = () => {
  const [csv, setCsv] = React.useState("Campaign_ID,Landing_Page_URL,Headline,Image_URL\ncamp_123,https://example.com,My headline,https://example.com/image.jpg");
  const [status, setStatus] = React.useState("");
  const token = loadAdvToken;
  const upload = async () => {
    setStatus("Uploading...");
    try {
      if (!advLive()) { setStatus("Mock upload accepted. Rows will enter Pending Review in live mode."); return; }
      const out = await advCall("/advertiser/v1/creatives/bulk", { body: csv, token: token(), raw: true });
      setStatus(`Inserted ${out.inserted}, rejected ${out.rejected}`);
    } catch (e) { setStatus(e.message || "Upload failed"); }
  };
  return <Screen>
    <PageHead icon={IconUpload} title="Bulk Upload" subtitle="Create many pending-review creatives from CSV."/>
    <Card>
      <FieldRow label="CSV"><textarea className="pw-ta" rows={10} value={csv} onChange={e => setCsv(e.target.value)}/></FieldRow>
      <div style={{ display: "flex", gap: 10, alignItems: "center" }}><Btn kind="primary" onClick={upload}>Upload CSV</Btn><span style={{ fontSize: 12, color: "var(--muted)" }}>{status}</span></div>
    </Card>
  </Screen>;
};

const AdDetailScreen = ({ data, setData, campaignId, mode = "publisher" }) => {
  const campaign = (data.adCampaigns || []).find(c => c.campaign_id === campaignId) || (data.adApprovals || []).find(c => c.campaign_id === campaignId) || (data.adCampaigns || [])[0];
  const [report, setReport] = React.useState(null);
  const [detail, setDetail] = React.useState(null);
  const [status, setStatus] = React.useState("");
  const token = loadAdvToken;

  // The list endpoints omit article bodies and listing fields — they would bloat every approval
  // queue — so the review view fetches the full submission separately.
  React.useEffect(() => {
    let alive = true;
    setDetail(null);
    (async () => {
      if (!campaign || !advLive()) return;
      try {
        // Same shape either way; the advertiser route is scoped to their own submissions so a
        // client can re-read the article they sent while it is still in review.
        const out = mode === "publisher"
          ? await window.__api.call("getAdApprovalDetail", { params: { campaign_id: campaign.campaign_id } })
          : await advCall(`/advertiser/v1/campaigns/${encodeURIComponent(campaign.campaign_id)}`, { method: "GET", token: token() });
        if (alive) setDetail(out);
      } catch (e) {}
    })();
    return () => { alive = false; };
  }, [campaign?.campaign_id, mode]);

  React.useEffect(() => {
    let alive = true;
    (async () => {
      if (!campaign) return;
      try {
        if (advLive() && mode === "publisher") {
          const out = await window.__api.call("publisherAdCampaignReport", { params: { campaign_id: campaign.campaign_id } });
          if (alive) setReport(out);
        } else if (advLive() && mode === "advertiser") {
          const out = await advCall(`/advertiser/v1/campaigns/${encodeURIComponent(campaign.campaign_id)}/report`, { method: "GET", token: token() });
          if (alive) setReport(out);
        }
      } catch (e) {}
    })();
    return () => { alive = false; };
  }, [campaign?.campaign_id, mode]);
  if (!campaign) return <Screen><EmptyState icon={<IconAd size={30}/>} title="Ad not found" description="Select an ad from the campaign list."/></Screen>;
  // Prefer the detail fetch, which carries the article/classified payload; fall back to the cached
  // list row (and to demo-mode fixtures, which already inline both).
  const creative = (detail?.creatives || []).find(c => (campaign.creative_ids || []).includes(c.creative_id))
    || (detail?.creatives || [])[0]
    || (data.adCreatives || []).find(c => (campaign.creative_ids || []).includes(c.creative_id));
  const creativeType = creative?.creative_type || "native";
  const stats = reportToStats(campaign, report);
  const action = async (next) => {
    setStatus("Updating...");
    try {
      if (mode === "publisher") {
        await window.__api.write("approvePublisherAd",
          d => ({ ...d, adApprovals: (d.adApprovals || []).filter(c => c.campaign_id !== campaign.campaign_id), adCampaigns: (d.adCampaigns || []).map(c => c.campaign_id === campaign.campaign_id ? { ...c, status: next === "approved" ? "active" : "rejected" } : c) }),
          { params: { campaign_id: campaign.campaign_id }, body: { status: next }, ok: `Ad ${next}`, err: "Could not update ad" });
      } else {
        const nextStatus = next === "start" ? "active" : "paused";
        if (advLive()) await advCall(`/advertiser/v1/campaigns/${encodeURIComponent(campaign.campaign_id)}/status`, { method: "PUT", token: token(), body: { status: nextStatus } });
        setData(d => ({ ...d, adCampaigns: (d.adCampaigns || []).map(c => c.campaign_id === campaign.campaign_id ? { ...c, status: nextStatus } : c) }));
      }
      setStatus("Updated.");
    } catch (e) { setStatus(e.message || "Update failed"); }
  };
  return <Screen>
    <PageHead icon={IconAd} title={campaign.name || "Ad details"} subtitle={`${advName(data, campaign.advertiser_id)} · ${String(campaign.pricing_model || "cpc").toUpperCase()} $${Number(campaign.bid_amount || 0).toFixed(2)}`}
      actions={<div style={{ display: "flex", gap: 8 }}>{mode === "publisher" ? <><Btn kind="secondary" onClick={() => action("rejected")}>Disapprove</Btn><Btn kind="primary" onClick={() => action("approved")}>Approve</Btn></> : <><Btn kind="secondary" onClick={() => action("stop")}>Stop ad</Btn><Btn kind="primary" onClick={() => action("start")}>Start ad</Btn></>}<span style={{ fontSize: 12, color: "var(--muted)", alignSelf: "center" }}>{status}</span></div>}/>
    <KpiGrid cols={6}>
      <StatCard label="Impressions" value={fmt.compact(stats.impressions)} sub="served" icon={<IconChart size={16}/>} trend={metricTrend(stats.impressions || 10)}/>
      <StatCard label="Clicks" value={fmt.compact(stats.clicks)} sub="tracked" icon={<IconActivity size={16}/>} trend={metricTrend(stats.clicks || 4)}/>
      <StatCard label="CTR" value={(stats.ctr * 100).toFixed(2) + "%"} sub="click rate" icon={<IconGauge size={16}/>} trend={metricTrend((stats.ctr || 0.02) * 1000)}/>
      <StatCard label="Spend" value={fmt.currency(stats.spend)} sub="estimated" icon={<IconReceipt size={16}/>} trend={metricTrend(stats.spend || 6)}/>
      <StatCard label="eCPM" value={fmt.currency(stats.ecpm)} sub="effective" icon={<IconCoin size={16}/>} trend={metricTrend(stats.ecpm || 8)}/>
      <StatCard label="Daily budget" value={fmt.currency(Number(campaign.daily_budget || 0))} sub={campaign.status} icon={<IconAd size={16}/>} trend={metricTrend(Number(campaign.daily_budget || 12))}/>
    </KpiGrid>
    <Card title="Ad details">
      <div style={{ display: "grid", gridTemplateColumns: "minmax(220px, 360px) minmax(0,1fr)", gap: 20, alignItems: "start" }}>
        <div>{creative?.image_url ? <img src={creative.image_url} alt="" style={{ width: "100%", borderRadius: 8, border: "1px solid var(--border)", objectFit: "cover" }}/> : <EmptyState icon={<IconAd size={24}/>} title="No creative image" description="Creative asset will appear here."/>}</div>
        <div style={{ display: "grid", gap: 12 }}>
          <MiniStat label="Format" value={<CreativeTypeBadge type={creativeType}/>}/>
          <MiniStat label="Headline" value={creative?.title || campaign.name}/>
          <MiniStat label="Branding text" value={campaign.branding_text || creative?.brand_name || "Sponsored"}/>
          <MiniStat label="Business category" value={creative?.business_category || campaign.business_category || "Not set"}/>
          <MiniStat label="Placements" value={(campaign.target_placements || []).join(", ") || "All placements"}/>
          <MiniStat label="Geo / devices" value={`${(campaign.geo_targets || []).join(", ") || "Any"} · ${(campaign.device_targets || []).join(", ") || "All devices"}`}/>
          <MiniStat label={isArticleType(creativeType) ? "Article page" : "Destination"} value={creative?.target_url || "Not set"}/>
        </div>
      </div>
    </Card>

    {isArticleType(creativeType) && <Card
      title={creativeType === "press_release" ? "Press release" : "Advertorial"}
      subtitle={mode === "publisher"
        ? "Read the full submission before approving — this is what will publish on your site."
        : "The article you submitted, exactly as the publisher is reviewing it."}>
      {creative?.article ? <>
        <div className="pw-detail-grid" style={{ marginBottom: 16 }}>
          <MiniStat label="Byline" value={creative.article.byline || "Not set"}/>
          <MiniStat label="Source" value={creative.article.source_org || creative.brand_name || "Not set"}/>
          <MiniStat label="Dateline" value={creative.article.dateline || "Not set"}/>
          <MiniStat label="Slug" value={creative.article.slug}/>
          <MiniStat label="Reading time" value={creative.article.reading_minutes ? `${creative.article.reading_minutes} min` : "—"}/>
        </div>
        {/* The body is sanitized server-side on write and again on read, against a strict tag
            allowlist. That is the only reason it can be set as HTML here. */}
        <div className="pw-article-review" dangerouslySetInnerHTML={{ __html: creative.article.body_html || "" }}/>
        <style>{`
          .pw-article-review { font-size: 15.5px; line-height: 1.7; max-width: 68ch; }
          .pw-article-review h2 { font-size: 20px; margin: 22px 0 8px; }
          .pw-article-review h3 { font-size: 17px; margin: 18px 0 6px; }
          .pw-article-review p { margin: 0 0 14px; }
          .pw-article-review img { max-width: 100%; height: auto; border-radius: 6px; }
          .pw-article-review blockquote { margin: 16px 0; padding-left: 14px; border-left: 3px solid var(--border); color: var(--muted); }
          .pw-article-review a { color: var(--accent); }
        `}</style>
      </> : <EmptyState icon={<IconList size={24}/>} title="Article body not loaded" description="Connect the live API to read the submitted article."/>}
    </Card>}

    {creativeType === "classified" && <Card title="Listing details" subtitle={mode === "publisher"
      ? "Contact details are shown to you for review; they are never included in the public ad payload."
      : "Your listing as submitted. Contact details appear on the listing page, never in the ad itself."}>
      {creative?.classified ? <div className="pw-detail-grid">
        <MiniStat label="Category" value={creative.classified.category}/>
        <MiniStat label="Price" value={
          creative.classified.price_type === "free" ? "Free"
            : creative.classified.price_type === "on_request" || creative.classified.price == null ? "On request"
              : `${creative.classified.price_currency} ${creative.classified.price}${creative.classified.price_type === "negotiable" ? " (negotiable)" : ""}`
        }/>
        <MiniStat label="Location" value={creative.classified.location || "Not set"}/>
        <MiniStat label="Condition" value={creative.classified.item_condition || "Not specified"}/>
        <MiniStat label="Expires" value={creative.classified.valid_until || "No expiry"}/>
        <MiniStat label="Contact" value={[creative.classified.contact_name, creative.classified.contact_email, creative.classified.contact_phone].filter(Boolean).join(" · ") || "Not provided"}/>
      </div> : <EmptyState icon={<IconList size={24}/>} title="Listing not loaded" description="Connect the live API to read the submitted listing."/>}
    </Card>}

    <Card title="Preview" subtitle="See this ad rendered as readers see it, for each placement it targets.">
      <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
        <Btn kind="secondary" icon={<IconAd size={14}/>}
          onClick={() => window.__nav(mode === "advertiser" ? "advertiser-ad-preview" : "ad-preview", campaign.campaign_id)}>
          Open preview
        </Btn>
        <span style={{ fontSize: 12.5, color: "var(--muted)" }}>
          Renders the widget card per placement, using the asset uploaded for each.
        </span>
      </div>
    </Card>

    <Card title="Other details">
      <div className="pw-detail-grid">
        <MiniStat label="Status" value={<CampaignStatusPill status={campaign.status}/>}/>
        <MiniStat label="Pricing model" value={String(campaign.pricing_model || "cpc").toUpperCase()}/>
        <MiniStat label="Bid" value={fmt.currency(Number(campaign.bid_amount || 0))}/>
        <MiniStat label="Daily budget" value={campaign.daily_budget == null ? "No cap" : fmt.currency(Number(campaign.daily_budget))}/>
        <MiniStat label="Total budget" value={campaign.total_budget == null ? "No cap" : fmt.currency(Number(campaign.total_budget))}/>
        <MiniStat label="Objective" value={campaign.marketing_objective || "Not set"}/>
        <MiniStat label="Runs from" value={campaign.starts_at ? String(campaign.starts_at).slice(0, 10) : "Immediately"}/>
        <MiniStat label="Runs until" value={campaign.ends_at ? String(campaign.ends_at).slice(0, 10) : "No end date"}/>
        <MiniStat label="Created" value={campaign.created_at ? String(campaign.created_at).slice(0, 10) : "—"}/>
        <MiniStat label="Campaign ID" value={<code style={{ fontFamily: "var(--mono)", fontSize: 11.5 }}>{campaign.campaign_id}</code>}/>
        <MiniStat label="Creative ID" value={<code style={{ fontFamily: "var(--mono)", fontSize: 11.5 }}>{creative?.creative_id || "—"}</code>}/>
        <MiniStat label="Blocked categories" value={(campaign.blocked_categories || []).join(", ") || "None"}/>
      </div>
      <style>{`
        .pw-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; }
      `}</style>
    </Card>
  </Screen>;
};

const AdvertiserAdsScreen = ({ data, setData }) => {
  const [status, setStatus] = React.useState("");
  const token = loadAdvToken;
  const refresh = async () => {
    setStatus("Loading ads...");
    try {
      if (advLive()) {
        const out = await advCall("/advertiser/v1/campaigns", { method: "GET", token: token() });
        setData(d => ({ ...d, adCampaigns: out.campaigns || [] }));
      }
      setStatus("");
    } catch (e) { setStatus(e.message || "Could not load ads"); }
  };
  React.useEffect(() => { refresh(); }, []);
  return <Screen>
    <PageHead icon={IconActivity} title="My Ads" subtitle="Every campaign you have submitted, in any state." 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="advertiser-ad-detail"/>
  </Screen>;
};

Object.assign(window, { AdvertiserLoginScreen, AdvertiserAdsScreen, AdDetailScreen, AdvertiserCampaignScreen, AdvertiserBulkUploadScreen });
