// dashboard-campaigns-view.js — extracted verbatim from dashboard.js (pure move).

// ── Campaigns view ────────────────────────────────────────────────

let campaignTab = "dm"; // "dm" | "email" | "outreach" | "workflows"
let campaignFilter = { status: "", q: "" };

// Map a contextAccount.platform → the campaign tab it most naturally belongs in.
// Used to auto-switch the active tab when the account picker changes.
function campaignTabForPlatform(platform) {
  if (platform === "telegram") return "dm";
  if (platform === "smtp") return "email";
  if (platform === "twitter" || platform === "linkedin") return "outreach";
  return null;
}

// Filter a campaign row against the active contextAccount. Returns true if the
// row should be shown. Per-tab behavior:
//   - dm / email: row must reference the selected account_id directly
//   - outreach:   row's platform-specific status (x_status / linkedin_status /
//                 telegram_status / email_status) must be a non-empty value
//   - workflows:  no filter (workflows aren't account-scoped yet)
//
// Tries common field names so we don't have to hardcode against any one
// schema variant. Falls open (returns true) if no match candidate exists —
// the picker should narrow, never disappear data the user owns.
function rowMatchesContextAccount(row, tab) {
  const ctx = dashboardState.contextAccount;
  if (!ctx) return true;
  if (tab === "workflows") return true;

  if (tab === "outreach") {
    // Outreach rows are multi-channel; only the active channel's status field matters.
    const channelKey = {
      twitter: "x_status",
      linkedin: "linkedin_status",
      telegram: "telegram_status",
      smtp: "email_status",
    }[ctx.platform];
    if (!channelKey) return true;
    const v = row[channelKey];
    // Hide ONLY rows where the channel was explicitly skipped (orchestrator's
    // smart channel queueing decided this contact has no reachable channel
    // for that platform). Everything else — null, "", "not_started", "queued",
    // "sent", "replied" — is in-flight or attempted and should stay visible.
    // Previously not_started also hid the row, which made freshly-spawned
    // outreach campaigns invisible to users with the account picker set.
    return v !== "skipped";
  }

  // dm / email — single account per row
  const candidates = [
    "account_id",
    "dispatch_account_id",
    "tg_account_id",
    "telegram_account_id",
    "from_account_id",
    "smtp_account_id",
    "email_account_id",
  ];
  let sawAnyId = false;
  for (const f of candidates) {
    if (row[f] != null) {
      sawAnyId = true;
      if (String(row[f]) === String(ctx.id)) return true;
    }
  }
  // No account_id field on the row at all → fall open
  return !sawAnyId;
}

async function renderCampaigns() {
  // If the user picked an account in the top bar, auto-switch the tab to the
  // one that matches that account's platform — but only if they haven't
  // already manually picked a tab in this session via the URL hash. (Manual
  // tab selection happens via tabBtn onclick, which sets campaignTab and
  // re-enters renderCampaigns; that flow is preserved.)
  const ctx = dashboardState.contextAccount;
  if (ctx) {
    const target = campaignTabForPlatform(ctx.platform);
    if (target && campaignTab !== target && campaignTab !== "workflows") {
      campaignTab = target;
    }
  }
  const content = el("div", {}, [
    el("div", { class: "page-header" }, [
      el("div", {}, [
        el("div", { class: "page-title" }, "Campaigns"),
        el(
          "div",
          { class: "page-subtitle" },
          "Manage all running campaigns across DM, email, and multi-channel outreach",
        ),
      ]),
    ]),
    // Run-hub pill nav — switch between Active / Campaigns / Queue
    _runHubNav("campaigns"),
    // Outcomes funnel — 7-day reply-rate at a glance. Placeholder div
    // gets populated async once /api/ops/outcomes returns. Hidden when
    // the user has zero events in the window.
    el("div", { id: "campaigns-outcomes-strip" }, []),
    el("div", { class: "tabs" }, [
      tabBtn("dm", "Telegram DM", () => {
        campaignTab = "dm";
        renderCampaigns();
      }),
      tabBtn("email", "Email", () => {
        campaignTab = "email";
        renderCampaigns();
      }),
      tabBtn("outreach", "Outreach", () => {
        campaignTab = "outreach";
        renderCampaigns();
      }),
      tabBtn("workflows", "Workflows", () => {
        campaignTab = "workflows";
        renderCampaigns();
      }),
    ]),
    el("div", { class: "filter-row" }, [
      el("input", {
        class: "filter-input",
        placeholder: "Search by name or subject…",
        value: campaignFilter.q,
        oninput: (e) => {
          campaignFilter.q = e.target.value;
          loadCampaigns();
        },
      }),
      el(
        "select",
        {
          class: "filter-select",
          onchange: (e) => {
            campaignFilter.status = e.target.value;
            loadCampaigns();
          },
        },
        [
          el("option", { value: "" }, "All statuses"),
          el("option", { value: "running" }, "Running"),
          el("option", { value: "paused" }, "Paused"),
          el("option", { value: "completed" }, "Completed"),
          el("option", { value: "draft" }, "Draft"),
        ],
      ),
    ]),
    el("div", { id: "campaigns-table-wrap" }, [
      el("div", { class: "loading" }, "Loading campaigns…"),
    ]),
  ]);
  setContentNode(content);
  // Highlight active tab
  $$(".tab").forEach((t) => t.classList.toggle("active", t.dataset.tab === campaignTab));
  // Lazy-load the outcomes strip in parallel with campaign rows. Failure
  // is silent — strip just doesn't render. Single 7-day window for now;
  // user can drill in via the per-campaign detail later.
  loadOutcomesStrip();
  await loadCampaigns();
}

// Loads the funnel + scoreboard summary, renders into #campaigns-outcomes-strip.
async function loadOutcomesStrip() {
  try {
    const [outcomes, scoreboard] = await Promise.all([
      api("ops-outcomes", { queryString: "?days=7" }),
      api("ops-account-scoreboard", { queryString: "?days=7" }),
    ]);
    const host = document.getElementById("campaigns-outcomes-strip");
    if (!host) return;
    host.innerHTML = "";
    const f = outcomes?.funnel || {};
    const totalSent = f.sent || 0;
    if (totalSent === 0) return; // nothing to show — keep page clean
    host.appendChild(buildOutcomesFunnelCard(outcomes, scoreboard));
  } catch (err) {
    // Don't surface — outcomes are nice-to-have. Log to console for ops debug.
    console.warn("[outcomes-strip] failed:", err.message || err);
  }
}

// Renders the funnel + per-platform breakdown + per-account scoreboard
// in one card. Compact enough to live above the campaigns tabs.
function buildOutcomesFunnelCard(outcomes, scoreboard) {
  const f = outcomes?.funnel || {};
  const r = outcomes?.rates || {};
  const sb = (scoreboard?.accounts || []).slice(0, 5); // top 5 accounts

  // 5-stage horizontal funnel: each stage is a wedge whose width scales
  // with its count relative to "sent" (the leftmost = 100%). Channels
  // aren't normalized per-row because the user wants absolute throughput
  // visible — 100 sent → 5 replied is more useful than just "5% rate."
  const stages = [
    { key: "sent", label: "Sent", color: "#3b82f6" },
    { key: "accepted", label: "Accepted", color: "#8b5cf6" },
    { key: "opened", label: "Opened", color: "#06b6d4" },
    { key: "replied", label: "Replied", color: "#22c55e" },
    { key: "converted", label: "Converted", color: "#fbbf24" },
  ];
  const maxN = Math.max(1, ...stages.map((s) => f[s.key] || 0));
  const stageBars = stages.map((s) => {
    const n = f[s.key] || 0;
    const pctOfMax = (n / maxN) * 100;
    return el(
      "div",
      {
        style: "flex:1;display:flex;flex-direction:column;gap:4px;min-width:90px;",
        title: `${n} ${s.label.toLowerCase()} in last 7d`,
      },
      [
        el(
          "div",
          {
            style:
              "font-size:10px;color:var(--text-4);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;",
          },
          s.label,
        ),
        el(
          "div",
          {
            style: `font-size:20px;font-weight:700;color:${s.color};font-family:ui-monospace,monospace;`,
          },
          String(n),
        ),
        el(
          "div",
          { style: "height:4px;background:var(--bg-3);border-radius:2px;overflow:hidden;" },
          [el("div", { style: `height:100%;width:${pctOfMax}%;background:${s.color};` })],
        ),
      ],
    );
  });

  // Per-platform mini-table — shows which channel is producing
  const perPlatform = outcomes?.perPlatform || {};
  const platRows = Object.entries(perPlatform)
    .filter(([, v]) => v.sent > 0)
    .map(([plat, v]) => {
      const rate = v.sent > 0 ? Math.round((v.replied / v.sent) * 100) : 0;
      return el(
        "div",
        {
          style: "display:flex;gap:10px;font-size:11px;align-items:center;padding:3px 0;",
        },
        [
          el(
            "span",
            { style: "min-width:64px;color:var(--text-1);text-transform:capitalize;" },
            plat,
          ),
          el("span", { class: "muted mono" }, `${v.sent} sent`),
          el("span", { class: "muted mono" }, `${v.replied} replied`),
          el(
            "span",
            {
              class: "mono",
              style: `font-weight:600;color:${rate >= 10 ? "#a7f3d0" : rate >= 3 ? "#fde68a" : "var(--red)"};`,
            },
            `${rate}%`,
          ),
        ],
      );
    });

  // Per-account scoreboard — clickable ids open campaign filter
  const acctRows = sb.map((a) => {
    const rate = Math.round((a.replyRate || 0) * 100);
    return el(
      "div",
      {
        style: "display:flex;gap:10px;font-size:11px;align-items:center;padding:3px 0;",
        title: `${a.account_used} on ${a.platform}: ${a.sent} sent · ${a.replied} replied`,
      },
      [
        el(
          "span",
          {
            class: "mono",
            style:
              "min-width:200px;color:var(--text-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",
          },
          `${a.account_used} (${a.platform})`,
        ),
        el("span", { class: "muted mono" }, `${a.sent}→${a.replied}`),
        el(
          "span",
          {
            class: "mono",
            style: `font-weight:600;color:${rate >= 10 ? "#a7f3d0" : rate >= 3 ? "#fde68a" : "var(--red)"};`,
          },
          `${rate}%`,
        ),
      ],
    );
  });

  return el(
    "div",
    {
      class: "card",
      style: "padding:14px 16px;margin-bottom:14px;border-left:3px solid #3b82f6;",
    },
    [
      // Headline row
      el(
        "div",
        {
          style: "display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin-bottom:12px;",
        },
        [
          el(
            "div",
            { style: "font-size:13px;font-weight:600;color:var(--text-1);" },
            "Outcomes · last 7 days",
          ),
          el(
            "div",
            { class: "muted small mono" },
            `reply rate ${(r.replyRate * 100).toFixed(1)}%${r.conversionRate > 0 ? ` · conversion ${(r.conversionRate * 100).toFixed(1)}%` : ""}`,
          ),
        ],
      ),
      // Funnel bars
      el("div", { style: "display:flex;gap:14px;margin-bottom:14px;" }, stageBars),
      // Two-column: per-platform + per-account
      el(
        "div",
        {
          style:
            "display:grid;grid-template-columns:1fr 2fr;gap:18px;border-top:1px solid var(--border);padding-top:12px;",
        },
        [
          el("div", {}, [
            el(
              "div",
              {
                style:
                  "font-size:10px;color:var(--text-4);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-bottom:6px;",
              },
              "By platform",
            ),
            ...(platRows.length > 0
              ? platRows
              : [el("div", { class: "muted small" }, "No platform breakdown yet")]),
          ]),
          el("div", {}, [
            el(
              "div",
              {
                style:
                  "font-size:10px;color:var(--text-4);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-bottom:6px;",
              },
              `Top accounts (${sb.length})`,
            ),
            ...(acctRows.length > 0
              ? acctRows
              : [el("div", { class: "muted small" }, "No account-level activity yet")]),
          ]),
        ],
      ),
    ],
  );
}

function tabBtn(id, label, onclick) {
  const b = el("button", { class: "tab", "data-tab": id, onclick }, label);
  return b;
}

async function loadCampaigns() {
  const wrap = $("#campaigns-table-wrap");
  if (!wrap) return;
  wrap.innerHTML = `<div class="loading">Loading ${campaignTab} campaigns…</div>`;

  // Workflows tab has different data source
  if (campaignTab === "workflows") {
    const data = await api("workflows-list").catch(() => ({}));
    if (data?.error) {
      wrap.innerHTML = `<div class="empty-state"><div class="empty-state-title">Error</div><div class="empty-state-hint">${escapeHtml(data.error)}</div></div>`;
      return;
    }
    const workflows = data?.workflows || [];
    if (workflows.length === 0) {
      wrap.innerHTML = `<div class="empty-state"><div class="empty-state-icon">\ud83d\udce3</div><div class="empty-state-title">No workflows</div><div class="empty-state-hint">Create one from the New Campaign menu</div></div>`;
      return;
    }
    wrap.innerHTML = "";
    const tbl = el("table", { class: "table" });
    tbl.appendChild(
      el("thead", {}, [
        el("tr", {}, [
          el("th", {}, "Name"),
          el("th", {}, "Type"),
          el("th", {}, "Status"),
          el("th", {}, "Targets"),
          el("th", {}, "Campaigns"),
          el("th", {}, "Created"),
          el("th", {}, ""),
        ]),
      ]),
    );
    const tbody = el("tbody", {});
    for (const w of workflows) {
      const typeDef = WORKFLOW_TYPES[w.workflow_type];
      // event_promotion rows have a rich drawer UI (approval queue, TG
      // groups picker, start/pause/stop buttons). Other types still use
      // the simple toggle/delete inline buttons.
      const isEvent = w.workflow_type === "event_promotion";
      tbody.appendChild(
        el(
          "tr",
          {
            style: isEvent ? "cursor: pointer;" : undefined,
            onclick: isEvent ? () => showEventCampaignDrawer(w.id) : undefined,
            title: isEvent ? "Click to manage event campaign (approval queue + groups)" : undefined,
          },
          [
            el("td", {}, [el("span", { class: "primary-cell" }, w.name)]),
            el("td", {}, [
              el(
                "span",
                { class: "badge badge-purple" },
                typeDef?.icon ? `${typeDef.icon} ${w.workflow_type}` : w.workflow_type,
              ),
            ]),
            el("td", {}, [statusDot(w.status)]),
            el("td", { class: "mono" }, String(w.targets_found || 0)),
            el("td", { class: "mono" }, String(w.campaigns_created || 0)),
            el("td", { class: "secondary-cell mono" }, fmtDate(w.created_at)),
            el("td", {}, [
              el(
                "button",
                {
                  class: "btn btn-tiny btn-secondary row-actions",
                  type: "button",
                  onclick: async (e) => {
                    e.stopPropagation();
                    await apiMutate("workflow-toggle", {
                      workflowId: w.id,
                      body: { enabled: w.status !== "active" },
                    });
                    toast(w.status === "active" ? "Paused" : "Resumed", "ok");
                    loadCampaigns();
                  },
                },
                w.status === "active" ? "\u23f8" : "\u25b6",
              ),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-danger row-actions",
                  type: "button",
                  onclick: async (e) => {
                    e.stopPropagation();
                    await apiMutate("workflow-delete", { workflowId: w.id });
                    toast("Deleted", "ok");
                    loadCampaigns();
                  },
                },
                "\ud83d\uddd1",
              ),
            ]),
          ],
        ),
      );
    }
    tbl.appendChild(tbody);
    wrap.appendChild(el("div", { class: "table-wrap" }, [tbl]));
    return;
  }

  const action =
    campaignTab === "dm"
      ? "campaigns-dm"
      : campaignTab === "email"
        ? "campaigns-email"
        : "campaigns-outreach";

  const queryString = `?${new URLSearchParams({
    ...(campaignFilter.status && { status: campaignFilter.status }),
    ...(campaignFilter.q && { q: campaignFilter.q }),
    limit: "100",
  }).toString()}`;

  const data = await api(action, { queryString });
  if (data?.error) {
    wrap.innerHTML = `<div class="empty-state"><div class="empty-state-title">Error</div><div class="empty-state-hint">${escapeHtml(data.error)}</div></div>`;
    return;
  }
  const allRows = data?.campaigns || [];
  // Apply contextAccount filter (no-op when the picker is on "All accounts").
  // We filter client-side so the empty-state correctly reflects "0 of N"
  // matching rather than "0 returned by the API".
  const rows = allRows.filter((r) => rowMatchesContextAccount(r, campaignTab));
  const filteredOut = allRows.length - rows.length;

  if (rows.length === 0) {
    const ctx = dashboardState.contextAccount;
    const filterMsg = ctx
      ? `No ${campaignTab} campaigns for ${escapeHtml(ctx.label)}. ${filteredOut > 0 ? `${filteredOut} hidden by the account filter \u2014 clear it from the top bar to see all.` : "Try a different account or clear the filter."}`
      : "Create one from the New Campaign menu";
    wrap.innerHTML = `<div class="empty-state"><div class="empty-state-icon">\ud83d\udce3</div><div class="empty-state-title">No ${campaignTab} campaigns</div><div class="empty-state-hint">${filterMsg}</div></div>`;
    return;
  }

  wrap.innerHTML = "";
  wrap.appendChild(renderCampaignsTable(rows, campaignTab));
  const paginationLabel =
    filteredOut > 0
      ? `${rows.length} of ${data.total} campaigns \u00b7 ${filteredOut} hidden by account filter`
      : `${rows.length} of ${data.total} campaigns`;
  wrap.appendChild(el("div", { class: "pagination" }, [el("div", {}, paginationLabel)]));
}

function renderCampaignsTable(rows, type) {
  const ss = dashboardState.sort.campaigns;
  const sorted = sortRows(rows, ss.key, ss.dir);

  const table = el("div", { class: "table-wrap" });
  const tbl = el("table", { class: "table" });

  const columns =
    type === "outreach"
      ? [
          { label: "Contact", key: "contact_name" },
          { label: "Company", key: "company_name" },
          { label: "LI" },
          { label: "X" },
          { label: "TG" },
          { label: "Email" },
          { label: "Status", key: "overall_status" },
          { label: "Reply rate", key: "event_replied" },
          { label: "Updated", key: "updated_at" },
          { label: "" },
        ]
      : [
          { label: "Name", key: "name" },
          { label: "Status", key: "status" },
          { label: "Sent", key: "total_sent" },
          { label: "Failed", key: "total_failed" },
          { label: "Limit", key: "daily_limit" },
          { label: "Created", key: "created_at" },
          { label: "" },
        ];

  tbl.appendChild(makeSortableHeader(columns, ss, () => loadCampaigns()));

  const tbody = el("tbody", {});
  for (const c of sorted) {
    let cells;
    if (type === "outreach") {
      // Reply-rate cell: small "5/100 \u00b7 5%" badge color-coded by performance.
      // Hidden when no events fired yet \u2014 not "0/0 \u00b7 0%" which reads as "broken."
      const sent = Number(c.event_sent) || 0;
      const replied = Number(c.event_replied) || 0;
      const rate = sent > 0 ? Math.round((replied / sent) * 100) : null;
      const rateColor =
        rate == null
          ? "muted"
          : rate >= 10
            ? "badge-green"
            : rate >= 3
              ? "badge-yellow"
              : "badge-red";
      const rateCell =
        sent > 0
          ? el("td", { title: `${replied} replied \u00b7 ${sent} sent (last all-time)` }, [
              el(
                "span",
                { class: `badge ${rateColor}`, style: "font-family:ui-monospace,monospace;" },
                `${replied}/${sent} \u00b7 ${rate}%`,
              ),
            ])
          : el("td", { class: "muted small" }, "\u2014");

      cells = [
        el("td", {}, [el("span", { class: "primary-cell" }, c.contact_name || "\u2014")]),
        el("td", {}, c.company_name || "\u2014"),
        el("td", {}, [statusDot(c.linkedin_status)]),
        el("td", {}, [statusDot(c.x_status)]),
        el("td", {}, [statusDot(c.telegram_status)]),
        el("td", {}, [statusDot(c.email_status)]),
        el("td", {}, [statusDot(c.overall_status)]),
        rateCell,
        el("td", { class: "secondary-cell" }, fmtDate(c.updated_at)),
        el(
          "td",
          {},
          campaignActions("outreach", c.id, c.overall_status === "paused" ? "paused" : "active"),
        ),
      ];
    } else {
      cells = [
        el("td", {}, [
          el("span", { class: "primary-cell" }, c.name || c.subject || `Campaign #${c.id}`),
        ]),
        el("td", {}, [statusDot(c.status)]),
        el("td", { class: "mono" }, String(c.total_sent || 0)),
        el("td", {}, String(c.total_failed || 0)),
        el("td", {}, String(c.daily_limit || "—")),
        el("td", { class: "secondary-cell" }, fmtDate(c.created_at)),
        el("td", {}, campaignActions(type, c.id, c.status)),
      ];
    }

    const tr = el(
      "tr",
      {
        onclick: (e) => {
          if (e.target.tagName === "BUTTON") return;
          navigate(`campaigns/${type}/${c.id}`);
        },
      },
      cells,
    );
    tbody.appendChild(tr);
  }
  tbl.appendChild(tbody);
  table.appendChild(tbl);
  return table;
}

function statusClass(s) {
  if (s === "running" || s === "active") return "badge-green";
  if (s === "paused") return "badge-yellow";
  if (s === "completed" || s === "converted") return "badge-blue";
  if (s === "failed" || s === "dead") return "badge-red";
  return "badge-gray";
}

/** Linear-style status dot + label */
function statusDot(s) {
  const dotClass =
    s === "running" || s === "active"
      ? "green"
      : s === "paused"
        ? "yellow"
        : s === "completed" || s === "converted"
          ? "blue"
          : s === "failed" || s === "dead"
            ? "red"
            : "gray";
  return el("span", { class: "status" }, [
    el("span", { class: `status-dot-inline ${dotClass}` }),
    s || "\u2014",
  ]);
}

function campaignActions(type, id, status) {
  const wrap = el("div", { class: "row-actions" });
  if (status === "paused" || status === "draft") {
    wrap.appendChild(
      el(
        "button",
        {
          class: "btn btn-tiny btn-primary",
          title: "Resume",
          onclick: async (e) => {
            e.stopPropagation();
            await campaignAction(type, id, "resume");
          },
        },
        "\u25b6",
      ),
    );
  } else if (status === "running" || status === "active") {
    wrap.appendChild(
      el(
        "button",
        {
          class: "btn btn-tiny btn-secondary",
          title: "Pause",
          onclick: async (e) => {
            e.stopPropagation();
            await campaignAction(type, id, "pause");
          },
        },
        "\u23f8",
      ),
    );
  }
  wrap.appendChild(
    el(
      "button",
      {
        class: "btn btn-tiny btn-danger",
        title: "Delete",
        onclick: async (e) => {
          e.stopPropagation();
          if (!confirm("Delete this campaign? This cannot be undone.")) return;
          await campaignAction(type, id, "delete");
        },
      },
      "\ud83d\uddd1",
    ),
  );
  return wrap;
}

async function campaignAction(type, id, action) {
  const result = await api("campaign-action", { type, id, operation: action });
  if (result?.ok) {
    toast(`Campaign ${action}d`, "ok");
    await loadCampaigns();
  } else {
    toast(`Error: ${result?.error || "action failed"}`, "err");
  }
}
