// dashboard-telegram-pages.js — extracted verbatim from dashboard.js (classic script,
// shared global scope). Loaded by dashboard.html before dashboard.js so its
// top-level declarations are defined when init() runs. Pure move, no logic change.
// ── Telegram feature pages (dashboard-native) ──────────────────

function tgAccountPicker(accounts, selectedId, onchange) {
  return el(
    "select",
    {
      class: "wizard-input",
      style: "max-width: 300px;",
      onchange: (e) => onchange(e.target.value),
    },
    [
      el("option", { value: "" }, "Select account\u2026"),
      ...accounts.map((a) => {
        const o = el("option", { value: String(a.id) }, a.label || a.phone || `Account #${a.id}`);
        if (String(a.id) === String(selectedId)) o.selected = true;
        return o;
      }),
    ],
  );
}

function tgGroupPicker(groups, selectedIds, onChange) {
  const selected = new Set(selectedIds || []);
  const wrap = el("div", {
    style:
      "max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-md); padding: 4px;",
  });
  for (const g of groups) {
    const id = String(g.chat_id);
    wrap.appendChild(
      el(
        "label",
        {
          style:
            "display: flex; align-items: center; gap: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer;",
        },
        [
          el("input", {
            type: "checkbox",
            checked: selected.has(id),
            onchange: (e) => {
              if (e.target.checked) selected.add(id);
              else selected.delete(id);
              onChange([...selected]);
            },
          }),
          el("span", {}, g.group_name || id),
          g.member_count
            ? el("span", { class: "muted", style: "font-size: 10px;" }, `(${g.member_count})`)
            : null,
        ],
      ),
    );
  }
  if (groups.length === 0)
    wrap.appendChild(
      el(
        "div",
        { class: "muted small", style: "padding: 8px;" },
        "No groups found. Scrape groups first.",
      ),
    );
  return wrap;
}

async function renderAutoReply() {
  showLoading("Loading auto-reply configs\u2026");
  const [data, accts] = await Promise.all([
    api("tg-autoreply-list").catch(() => ({})),
    Promise.resolve(dashboardState.cache.accounts?.telegram || []),
  ]);
  if (data?.error) {
    showError(data.error, renderAutoReply);
    return;
  }
  const configs = data.configs || [];

  let formAccountId = "",
    formPrompt = "",
    formContext = "";

  const content = el("div", {}, [
    pageHeader("Auto Reply", "AI-powered automatic responses to DMs"),

    // Existing configs
    configs.length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Active configs (${configs.length})`),
          ...configs.map((c) =>
            el("div", { class: "channel-row", style: "align-items: flex-start;" }, [
              el("div", { class: "channel-icon" }, c.is_active ? "\u2705" : "\u23f8"),
              el("div", { style: "flex: 1; min-width: 0;" }, [
                el(
                  "div",
                  { class: "primary-cell" },
                  c.account_label || c.account_phone || `Account #${c.account_id}`,
                ),
                el(
                  "div",
                  { class: "muted small", style: "margin-top: 2px;" },
                  (c.system_prompt || "No prompt set").slice(0, 80),
                ),
              ]),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-secondary",
                  type: "button",
                  onclick: async () => {
                    await api("tg-autoreply-toggle", {
                      body: { accountId: c.account_id, enabled: !c.is_active },
                    });
                    toast(c.is_active ? "Disabled" : "Enabled", "ok");
                    renderAutoReply();
                  },
                },
                c.is_active ? "\u23f8 Pause" : "\u25b6 Enable",
              ),
            ]),
          ),
        ])
      : null,

    // Add new config
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "Add / Update Auto-Reply"),
      el("div", { class: "wizard-label" }, "Account"),
      tgAccountPicker(accts, formAccountId, (v) => {
        formAccountId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "System prompt"),
      el("textarea", {
        class: "wizard-textarea",
        placeholder: "You are a friendly crypto professional. Reply naturally to messages\u2026",
        style: "min-height: 100px;",
        oninput: (e) => {
          formPrompt = e.target.value;
        },
      }),
      el(
        "div",
        { class: "wizard-label", style: "margin-top: 12px;" },
        "Context (about you/your project)",
      ),
      el("textarea", {
        class: "wizard-textarea",
        placeholder: "I run Nexus, a CRM and outreach tool for web3 teams\u2026",
        style: "min-height: 60px;",
        oninput: (e) => {
          formContext = e.target.value;
        },
      }),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 12px;",
          type: "button",
          onclick: async () => {
            if (!formAccountId) {
              toast("Select an account", "err");
              return;
            }
            await api("tg-autoreply-save", {
              body: {
                accountId: parseInt(formAccountId),
                prompt: formPrompt,
                context: formContext,
              },
            });
            toast("\u2705 Auto-reply config saved", "ok");
            renderAutoReply();
          },
        },
        "Save Config",
      ),
    ]),
  ]);
  setContentNode(content);
}

async function renderBroadcast() {
  showLoading("Loading broadcasts\u2026");
  const [history, groupsData, accts] = await Promise.all([
    api("tg-broadcast-history").catch(() => ({})),
    api("tg-groups").catch(() => ({})),
    Promise.resolve(dashboardState.cache.accounts?.telegram || []),
  ]);

  let formAccountId = "",
    formMessage = "",
    formChatIds = [];

  const content = el("div", {}, [
    pageHeader("Broadcast", "Send a message to multiple Telegram groups"),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "New Broadcast"),
      el("div", { class: "wizard-label" }, "Sending account"),
      tgAccountPicker(accts, formAccountId, (v) => {
        formAccountId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Message"),
      el("textarea", {
        class: "wizard-textarea",
        style: "min-height: 120px;",
        placeholder: "Your broadcast message\u2026",
        oninput: (e) => {
          formMessage = e.target.value;
        },
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Target groups"),
      tgGroupPicker(groupsData.groups || [], formChatIds, (ids) => {
        formChatIds = ids;
      }),
      el(
        "div",
        { class: "muted small", style: "margin-top: 6px;" },
        `${formChatIds.length} groups selected`,
      ),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 12px;",
          type: "button",
          onclick: async () => {
            if (!formMessage || !formChatIds.length) {
              toast("Message and at least 1 group required", "err");
              return;
            }
            const r = await api("tg-broadcast-send", {
              body: {
                accountId: formAccountId ? parseInt(formAccountId) : null,
                message: formMessage,
                chatIds: formChatIds,
              },
            });
            if (r?.ok) {
              toast(`\u2705 Broadcast #${r.broadcastId} queued`, "ok");
              renderBroadcast();
            } else toast(`\u274c ${r?.error || "Failed"}`, "err");
          },
        },
        "\ud83d\udce2 Send Broadcast",
      ),
    ]),
    (history.broadcasts || []).length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, "Recent Broadcasts"),
          ...history.broadcasts
            .slice(0, 10)
            .map((b) =>
              el("div", { class: "channel-row" }, [
                el(
                  "div",
                  { class: "channel-icon" },
                  b.status === "completed"
                    ? "\u2705"
                    : b.status === "sending"
                      ? "\u23f3"
                      : "\u274c",
                ),
                el("div", { style: "flex: 1;" }, [
                  el("div", { class: "small" }, (b.message_text || "").slice(0, 60)),
                  el(
                    "div",
                    { class: "muted", style: "font-size: 10px;" },
                    `${b.sent_count || 0} sent, ${b.failed_count || 0} failed \u00b7 ${fmtDate(b.created_at)}`,
                  ),
                ]),
              ]),
            ),
        ])
      : null,
  ]);
  setContentNode(content);
}

async function renderMassInvite() {
  showLoading("Loading invite jobs\u2026");
  const [data, groupsData, accts] = await Promise.all([
    api("tg-invite-list").catch(() => ({})),
    api("tg-groups").catch(() => ({})),
    Promise.resolve(dashboardState.cache.accounts?.telegram || []),
  ]);

  let formAccountId = "",
    formSourceId = "",
    formTargetId = "",
    formLimit = 20;
  const groups = groupsData.groups || [];

  const groupSelect = (label, value, onChange) =>
    el(
      "select",
      {
        class: "wizard-input",
        style: "max-width: 300px;",
        onchange: (e) => onChange(e.target.value),
      },
      [
        el("option", { value: "" }, `Select ${label}\u2026`),
        ...groups.map((g) => {
          const o = el("option", { value: g.chat_id }, g.group_name || g.chat_id);
          if (g.chat_id === value) o.selected = true;
          return o;
        }),
      ],
    );

  const content = el("div", {}, [
    pageHeader("Mass Invite", "Invite members from one group to another"),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "New Invite Job"),
      el("div", { class: "wizard-label" }, "Account"),
      tgAccountPicker(accts, formAccountId, (v) => {
        formAccountId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Source group"),
      groupSelect("source", formSourceId, (v) => {
        formSourceId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Target group"),
      groupSelect("target", formTargetId, (v) => {
        formTargetId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Daily limit"),
      el("input", {
        class: "wizard-input",
        type: "number",
        value: "20",
        style: "width: 100px;",
        oninput: (e) => {
          formLimit = parseInt(e.target.value) || 20;
        },
      }),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 12px;",
          type: "button",
          onclick: async () => {
            if (!formAccountId || !formSourceId || !formTargetId) {
              toast("Fill all fields", "err");
              return;
            }
            const r = await api("tg-invite-create", {
              body: {
                accountId: parseInt(formAccountId),
                sourceGroupId: formSourceId,
                targetGroupId: formTargetId,
                dailyLimit: formLimit,
              },
            });
            if (r?.ok) {
              toast(`\u2705 Invite job #${r.jobId} started`, "ok");
              renderMassInvite();
            } else toast(`\u274c ${r?.error || "Failed"}`, "err");
          },
        },
        "\ud83d\udce5 Start Inviting",
      ),
    ]),
    (data.jobs || []).length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, "Jobs"),
          ...data.jobs.map((j) =>
            el("div", { class: "channel-row" }, [
              el(
                "div",
                { class: "channel-icon" },
                j.status === "running" ? "\u25b6" : j.status === "completed" ? "\u2705" : "\u23f8",
              ),
              el("div", { style: "flex: 1;" }, [
                el(
                  "div",
                  { class: "small" },
                  `${j.source_group_name || j.source_group_id} \u2192 ${j.target_group_name || j.target_group_id}`,
                ),
                el(
                  "div",
                  { class: "muted", style: "font-size: 10px;" },
                  `${j.total_invited || 0} invited, ${j.total_failed || 0} failed \u00b7 ${j.status}`,
                ),
              ]),
              j.status === "running"
                ? el(
                    "button",
                    {
                      class: "btn btn-tiny btn-danger",
                      type: "button",
                      onclick: (ev) =>
                        mutateAction(
                          ev,
                          "tg-invite-stop",
                          { jobId: j.id },
                          {
                            confirm: "Stop this invite job?",
                            success: "Stopped",
                            after: renderMassInvite,
                          },
                        ),
                    },
                    "Stop",
                  )
                : null,
            ]),
          ),
        ])
      : null,
  ]);
  setContentNode(content);
}

async function renderForward() {
  showLoading("Loading forward rules\u2026");
  const [data, groupsData, accts] = await Promise.all([
    api("tg-forward-list").catch(() => ({})),
    api("tg-groups").catch(() => ({})),
    Promise.resolve(dashboardState.cache.accounts?.telegram || []),
  ]);
  const groups = groupsData.groups || [];
  let formSourceId = "",
    formTargetId = "",
    formFilterType = "all",
    formFilterValue = "",
    formStrip = false;

  const groupSelect = (label, onChange) =>
    el(
      "select",
      {
        class: "wizard-input",
        style: "max-width: 300px;",
        onchange: (e) => onChange(e.target.value),
      },
      [
        el("option", { value: "" }, `Select ${label}\u2026`),
        ...groups.map((g) => el("option", { value: g.chat_id }, g.group_name || g.chat_id)),
      ],
    );

  const content = el("div", {}, [
    el("div", { class: "page-header" }, [
      el("div", {}, [
        el("div", { class: "page-title" }, "Forward Rules"),
        el("div", { class: "page-subtitle" }, "Auto-forward messages between groups"),
      ]),
    ]),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "New Rule"),
      el("div", { class: "wizard-label" }, "Source group"),
      groupSelect("source", (v) => {
        formSourceId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Target group"),
      groupSelect("target", (v) => {
        formTargetId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Filter"),
      el(
        "select",
        {
          class: "wizard-input",
          style: "max-width: 200px;",
          onchange: (e) => {
            formFilterType = e.target.value;
          },
        },
        [
          el("option", { value: "all" }, "All messages"),
          el("option", { value: "keywords" }, "Keywords only"),
          el("option", { value: "regex" }, "Regex match"),
        ],
      ),
      formFilterType !== "all"
        ? el("input", {
            class: "wizard-input",
            style: "margin-top: 6px;",
            placeholder: formFilterType === "keywords" ? "keyword1, keyword2" : "pattern",
            oninput: (e) => {
              formFilterValue = e.target.value;
            },
          })
        : null,
      el(
        "label",
        {
          style:
            "display: flex; align-items: center; gap: 6px; margin-top: 8px; font-size: 12px; color: var(--text-2); cursor: pointer;",
        },
        [
          el("input", {
            type: "checkbox",
            onchange: (e) => {
              formStrip = e.target.checked;
            },
          }),
          "Strip forward tag",
        ],
      ),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 12px;",
          type: "button",
          onclick: async () => {
            if (!formSourceId || !formTargetId) {
              toast("Select source and target", "err");
              return;
            }
            const r = await api("tg-forward-create", {
              body: {
                sourceChatId: formSourceId,
                targetChatId: formTargetId,
                filterType: formFilterType,
                filterValue: formFilterValue,
                stripForwardTag: formStrip,
              },
            });
            if (r?.ok) {
              toast("\u2705 Rule created", "ok");
              renderForward();
            } else toast(`\u274c ${r?.error || "Failed"}`, "err");
          },
        },
        "\u27a1 Create Rule",
      ),
    ]),
    (data.rules || []).length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Rules (${data.rules.length})`),
          ...data.rules.map((r) =>
            el("div", { class: "channel-row" }, [
              el("div", { class: "channel-icon" }, r.is_active ? "\u25b6" : "\u23f8"),
              el("div", { style: "flex: 1;" }, [
                el(
                  "div",
                  { class: "small" },
                  `${r.name || "Rule"}: ${r.source_chat_id} \u2192 ${r.target_chat_id}`,
                ),
                el(
                  "div",
                  { class: "muted", style: "font-size: 10px;" },
                  `${r.filter_type} \u00b7 ${r.messages_forwarded || 0} forwarded`,
                ),
              ]),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-secondary",
                  type: "button",
                  onclick: (ev) =>
                    mutateAction(
                      ev,
                      "tg-forward-toggle",
                      { ruleId: r.id, body: { enabled: !r.is_active } },
                      { after: renderForward },
                    ),
                },
                r.is_active ? "\u23f8" : "\u25b6",
              ),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-danger",
                  type: "button",
                  onclick: (ev) =>
                    mutateAction(
                      ev,
                      "tg-forward-delete",
                      { ruleId: r.id },
                      {
                        confirm: "Delete this forward rule? This can't be undone.",
                        success: "Deleted",
                        after: renderForward,
                      },
                    ),
                },
                "\ud83d\uddd1",
              ),
            ]),
          ),
        ])
      : null,
  ]);
  setContentNode(content);
}

async function renderActiveMember() {
  showLoading("Loading active member configs\u2026");
  const [data, groupsData, accts] = await Promise.all([
    api("tg-activemember-list").catch(() => ({})),
    api("tg-groups").catch(() => ({})),
    Promise.resolve(dashboardState.cache.accounts?.telegram || []),
  ]);
  const groups = groupsData.groups || [];
  let formAccountId = "",
    formGroupId = "",
    formObjective = "",
    formPersona = "",
    formProbability = 0.3,
    formMaxReplies = 10;
  // v75 behavior toggles + info sources
  let formTypingIndicator = true;
  let formReactToMessages = false;
  let formReplyToOthers = true;
  let formInfoSources = []; // [{ type, url, handle }]

  const content = el("div", {}, [
    el("div", { class: "page-header" }, [
      el("div", {}, [
        el("div", { class: "page-title" }, "Active Member"),
        el("div", { class: "page-subtitle" }, "AI-powered participation in Telegram groups"),
      ]),
    ]),
    (data.configs || []).length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Configs (${data.configs.length})`),
          ...data.configs.map((c) =>
            el("div", { class: "channel-row", style: "align-items: flex-start;" }, [
              el("div", { class: "channel-icon" }, c.is_active ? "\u2705" : "\u23f8"),
              el("div", { style: "flex: 1;" }, [
                el("div", { class: "primary-cell" }, c.group_name || c.group_chat_id),
                el(
                  "div",
                  { class: "muted", style: "font-size: 10px;" },
                  `${c.account_label || c.account_phone} \u00b7 ${c.reply_probability * 100}% \u00b7 ${c.replies_today || 0}/${c.max_replies_per_day} today`,
                ),
              ]),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-secondary",
                  type: "button",
                  onclick: (ev) =>
                    mutateAction(
                      ev,
                      "tg-activemember-toggle",
                      { configId: c.id, body: { enabled: !c.is_active } },
                      { after: renderActiveMember },
                    ),
                },
                c.is_active ? "\u23f8" : "\u25b6",
              ),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-danger",
                  type: "button",
                  onclick: (ev) =>
                    mutateAction(
                      ev,
                      "tg-activemember-delete",
                      { configId: c.id },
                      {
                        confirm: "Delete this active-member config? This can't be undone.",
                        success: "Deleted",
                        after: renderActiveMember,
                      },
                    ),
                },
                "\ud83d\uddd1",
              ),
            ]),
          ),
        ])
      : null,
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "Add Config"),
      el("div", { class: "wizard-label" }, "Account"),
      tgAccountPicker(accts, formAccountId, (v) => {
        formAccountId = v;
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Group"),
      el(
        "select",
        {
          class: "wizard-input",
          id: "am-group-select",
          style: "max-width: 300px;",
          onchange: (e) => {
            formGroupId = e.target.value;
          },
        },
        [
          el("option", { value: "" }, "Select group\u2026"),
          ...groups.map((g) => {
            const label = g.group_name
              ? g.group_name
              : g.username
                ? `@${g.username}`
                : `[unnamed] ${g.chat_id}`;
            const suffix = g.member_count
              ? ` \u00b7 ${Number(g.member_count).toLocaleString()} members`
              : "";
            return el("option", { value: g.chat_id }, label + suffix);
          }),
        ],
      ),
      // Invite-link join. Lets the user paste https://t.me/+abc123 \u2192
      // the bot's MTProto account for the active member config joins
      // the group via ImportChatInvite, registers it in
      // passive_listen_groups, and pre-selects it in the dropdown.
      el("div", { style: "margin-top: 8px; display: flex; gap: 6px; align-items: center;" }, [
        el("input", {
          id: "am-invite-link",
          class: "wizard-input",
          type: "text",
          placeholder: "Or paste an invite link: https://t.me/+abc123\u2026",
          style: "flex: 1; min-width: 220px; font-size: 12px;",
        }),
        el(
          "button",
          {
            class: "btn btn-secondary",
            type: "button",
            title: "Join the group via invite link using the selected account, then auto-select it",
            onclick: async (ev) => {
              const btn = ev.currentTarget;
              const link = (document.getElementById("am-invite-link")?.value || "").trim();
              if (!link) {
                toast("Paste an invite link first", "err");
                return;
              }
              if (!/t\.me\/(\+|joinchat\/)/i.test(link)) {
                toast("Not a Telegram invite link", "err");
                return;
              }
              if (!formAccountId) {
                toast("Pick the account that will join first", "err");
                return;
              }
              btn.disabled = true;
              btn.textContent = "Joining\u2026";
              // Hard 60s client-side ceiling so the button can never get
              // stuck if the API hangs (server-side already times out at
              // ~30s per MTProto call, but a hung fetch would otherwise
              // leave us spinning).
              const apiPromise = api("tg-join-by-invite", {
                body: { inviteLink: link, accountId: parseInt(formAccountId) },
              }).catch((e) => ({ error: e?.message }));
              const timeoutPromise = new Promise((resolve) =>
                setTimeout(
                  () =>
                    resolve({
                      error:
                        "Timed out after 60s. The Telegram account may be offline \u2014 try again or pick a different account.",
                      _timeout: true,
                    }),
                  60000,
                ),
              );
              const r = await Promise.race([apiPromise, timeoutPromise]);
              btn.disabled = false;
              btn.textContent = "Join via invite";
              if (r?.error) {
                toast(`Join failed: ${r.error}`, "err", 5500);
                return;
              }
              const label = r.groupName || r.chatId;
              toast(
                r.alreadyMember ? `Already a member of ${label}` : `\u2713 Joined ${label}`,
                "ok",
                2500,
              );
              logTelemetry("active_member", "join_by_invite", {
                chatId: r.chatId,
                alreadyMember: !!r.alreadyMember,
              });
              // Add the new group to the dropdown if not already present, then select it.
              const select = document.getElementById("am-group-select");
              if (select) {
                let opt = [...select.options].find((o) => o.value === r.chatId);
                if (!opt) {
                  opt = el("option", { value: r.chatId }, label);
                  select.appendChild(opt);
                }
                select.value = r.chatId;
                formGroupId = r.chatId;
                // Stash resolved name so the create handler uses it
                groups.push({ chat_id: r.chatId, group_name: r.groupName });
              }
            },
          },
          "Join via invite",
        ),
      ]),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Objective"),
      el("textarea", {
        class: "wizard-textarea",
        placeholder: "Be a helpful community member. Share insights about crypto\u2026",
        style: "min-height: 60px;",
        oninput: (e) => {
          formObjective = e.target.value;
        },
      }),
      el(
        "div",
        { class: "wizard-label", style: "margin-top: 12px;" },
        "Reply probability (0-100%)",
      ),
      el("input", {
        class: "wizard-input",
        type: "number",
        value: "30",
        style: "width: 80px;",
        min: "1",
        max: "100",
        oninput: (e) => {
          formProbability = (parseInt(e.target.value) || 30) / 100;
        },
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Max replies per day"),
      el("input", {
        class: "wizard-input",
        type: "number",
        value: "10",
        style: "width: 80px;",
        oninput: (e) => {
          formMaxReplies = parseInt(e.target.value) || 10;
        },
      }),

      // v75 Behavior toggles ──────────────────────────────────────────
      el("div", { class: "wizard-label", style: "margin-top: 18px;" }, "Behavior"),
      el(
        "div",
        {
          style:
            "display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; background: rgba(255,255,255,0.02); border: 1px solid var(--border); border-radius: 6px;",
        },
        [
          // Typing indicator
          el(
            "label",
            {
              style:
                "display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-1); cursor: pointer;",
            },
            [
              el("input", {
                type: "checkbox",
                checked: "checked",
                onchange: (e) => {
                  formTypingIndicator = e.target.checked;
                },
              }),
              el("span", {}, "Show 'typing…' before sending"),
              el("span", { class: "muted small", style: "margin-left: auto;" }, "humanizes pace"),
            ],
          ),
          // React to messages
          el(
            "label",
            {
              style:
                "display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-1); cursor: pointer;",
            },
            [
              el("input", {
                type: "checkbox",
                onchange: (e) => {
                  formReactToMessages = e.target.checked;
                },
              }),
              el("span", {}, "React 👍 to messages I reply to"),
              el("span", { class: "muted small", style: "margin-left: auto;" }, "looks more human"),
            ],
          ),
          // Reply to others
          el(
            "label",
            {
              style:
                "display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-1); cursor: pointer;",
            },
            [
              el("input", {
                type: "checkbox",
                checked: "checked",
                onchange: (e) => {
                  formReplyToOthers = e.target.checked;
                },
              }),
              el("span", {}, "Reply to other members' messages"),
              el(
                "span",
                { class: "muted small", style: "margin-left: auto;" },
                "off = mentions only",
              ),
            ],
          ),
        ],
      ),

      // v75 Info sources (X handles to scrape + use as topic seeds) ──
      el(
        "div",
        {
          class: "wizard-label",
          style: "margin-top: 18px; display: flex; align-items: center; gap: 8px;",
        },
        [
          el("span", {}, "Info sources (optional)"),
          el(
            "span",
            {
              class: "muted small",
              style: "font-weight: 400; text-transform: none; letter-spacing: 0;",
            },
            "scraped + AI-evaluated for relevance to your objective",
          ),
        ],
      ),
      (() => {
        const wrap = el("div", { style: "display: flex; flex-direction: column; gap: 6px;" });
        const list = el("div", {
          id: "am-info-sources-list",
          style: "display: flex; flex-wrap: wrap; gap: 6px;",
        });
        wrap.appendChild(list);
        const renderSources = () => {
          list.innerHTML = "";
          if (formInfoSources.length === 0) {
            list.appendChild(el("span", { class: "muted small" }, "(none yet)"));
            return;
          }
          for (let i = 0; i < formInfoSources.length; i++) {
            const s = formInfoSources[i];
            list.appendChild(
              el(
                "span",
                {
                  style:
                    "display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; background: rgba(99,102,241,0.12); border: 1px solid rgba(99,102,241,0.35); border-radius: 12px; font-size: 11px; color: var(--text-1); font-family: ui-monospace, monospace;",
                },
                [
                  el("span", {}, `@${s.handle}`),
                  el(
                    "span",
                    {
                      style: "color: var(--text-4); cursor: pointer; padding-left: 3px;",
                      title: "Remove",
                      onclick: () => {
                        formInfoSources.splice(i, 1);
                        renderSources();
                      },
                    },
                    "✕",
                  ),
                ],
              ),
            );
          }
        };
        renderSources();
        const addInput = el("input", {
          class: "wizard-input",
          type: "text",
          placeholder: "https://x.com/BRICSinfo or @arkham",
          style: "flex: 1; font-size: 12px;",
        });
        const addBtn = el(
          "button",
          {
            class: "btn btn-secondary",
            type: "button",
            onclick: () => {
              const value = (addInput.value || "").trim();
              if (!value) return;
              const m =
                value.match(/(?:https?:\/\/)?(?:x|twitter)\.com\/([A-Za-z0-9_]{1,30})/i) ||
                value.match(/^@?([A-Za-z0-9_]{1,30})$/);
              if (!m) {
                toast("Not a recognized X handle or URL", "err");
                return;
              }
              const handle = m[1];
              if (formInfoSources.some((s) => s.handle.toLowerCase() === handle.toLowerCase())) {
                toast("Already added", "info");
                return;
              }
              if (formInfoSources.length >= 10) {
                toast("Max 10 sources", "err");
                return;
              }
              formInfoSources.push({ type: "x", handle, url: `https://x.com/${handle}` });
              addInput.value = "";
              renderSources();
            },
          },
          "+ Add",
        );
        wrap.appendChild(
          el("div", { style: "display: flex; gap: 6px; align-items: center; margin-top: 4px;" }, [
            addInput,
            addBtn,
          ]),
        );
        return wrap;
      })(),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 12px;",
          type: "button",
          onclick: async () => {
            if (!formAccountId || !formGroupId) {
              toast("Select account and group", "err");
              return;
            }
            // Resolve the best name we know for this chat_id at create time,
            // so the configs list shows a friendly label even if the group's
            // metadata changes later. Falls back: name > @username > chat_id.
            const g = groups.find((g) => g.chat_id === formGroupId);
            const resolvedName = g?.group_name || (g?.username ? `@${g.username}` : formGroupId);
            const r = await api("tg-activemember-create", {
              body: {
                accountId: parseInt(formAccountId),
                groupChatId: formGroupId,
                groupName: resolvedName,
                objective: formObjective,
                persona: formPersona,
                replyProbability: formProbability,
                maxRepliesPerDay: formMaxReplies,
                // v75 behavior + info sources
                typingIndicator: formTypingIndicator,
                reactToMessages: formReactToMessages,
                replyToOthers: formReplyToOthers,
                infoSources: formInfoSources,
              },
            });
            if (r?.ok) {
              toast("\u2705 Config created", "ok");
              renderActiveMember();
            } else toast(`\u274c ${r?.error || "Failed"}`, "err");
          },
        },
        "\ud83d\udcac Create Config",
      ),
    ]),
  ]);
  setContentNode(content);
}

async function renderPassiveListener() {
  showLoading("Loading passive listener\u2026");
  const data = await api("tg-passive-groups").catch(() => ({}));
  if (data?.error) {
    showError(data.error, renderPassiveListener);
    return;
  }
  let formChatId = "",
    formGroupName = "";

  const content = el("div", {}, [
    el("div", { class: "page-header" }, [
      el("div", {}, [
        el("div", { class: "page-title" }, "Passive Listener"),
        el("div", { class: "page-subtitle" }, "Silently capture contacts from Telegram groups"),
      ]),
    ]),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "Add Group"),
      el("div", { style: "display: flex; gap: 8px; align-items: flex-end;" }, [
        el("div", { style: "flex: 1;" }, [
          el("div", { class: "wizard-label" }, "Chat ID or group username"),
          el("input", {
            class: "wizard-input",
            placeholder: "-1001234567890 or @groupname",
            oninput: (e) => {
              formChatId = e.target.value.trim();
            },
          }),
        ]),
        el(
          "button",
          {
            class: "btn btn-primary",
            type: "button",
            onclick: async () => {
              if (!formChatId) {
                toast("Enter a chat ID", "err");
                return;
              }
              const r = await api("tg-passive-add", {
                body: { chatId: formChatId, groupName: formGroupName || null },
              });
              if (r?.ok) {
                toast("\u2705 Group added", "ok");
                renderPassiveListener();
              } else toast(`\u274c ${r?.error || "Failed"}`, "err");
            },
          },
          "Add",
        ),
      ]),
    ]),
    (data.groups || []).length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Whitelisted groups (${data.groups.length})`),
          ...data.groups.map((g) =>
            el("div", { class: "channel-row" }, [
              el("div", { class: "channel-icon" }, g.enabled ? "\ud83d\udc41" : "\u23f8"),
              el("div", { style: "flex: 1;" }, [
                el("div", { class: "primary-cell" }, g.group_name || g.chat_id),
                el(
                  "div",
                  { class: "muted", style: "font-size: 10px;" },
                  `${g.messages_captured || 0} messages \u00b7 ${g.unique_senders || 0} contacts \u00b7 last: ${g.last_message_at ? fmtDate(g.last_message_at) : "never"}`,
                ),
              ]),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-danger",
                  type: "button",
                  onclick: (ev) =>
                    mutateAction(
                      ev,
                      "tg-passive-remove",
                      { body: { chatId: g.chat_id } },
                      {
                        confirm: "Stop listening to this group?",
                        success: "Removed",
                        after: renderPassiveListener,
                      },
                    ),
                },
                "\ud83d\uddd1",
              ),
            ]),
          ),
        ])
      : el("div", { class: "empty-state" }, [
          el("div", { class: "empty-state-icon" }, "\ud83d\udc41"),
          el("div", { class: "empty-state-title" }, "No groups whitelisted"),
          el(
            "div",
            { class: "empty-state-hint" },
            "Add a group above to start capturing contacts.",
          ),
        ]),
  ]);
  setContentNode(content);
}

async function renderStories() {
  showLoading("Loading stories config\u2026");
  const [data, accts] = await Promise.all([
    api("tg-stories-get").catch(() => ({})),
    Promise.resolve(dashboardState.cache.accounts?.telegram || []),
  ]);
  const config = data.config || {};
  const media = data.media || [];

  const content = el("div", {}, [
    el("div", { class: "page-header" }, [
      el("div", {}, [
        el("div", { class: "page-title" }, "Telegram Stories"),
        el("div", { class: "page-subtitle" }, "Auto-post photos to Telegram Stories on schedule"),
      ]),
    ]),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "Configuration"),
      el("div", { class: "wizard-label" }, "Account"),
      tgAccountPicker(accts, config.premium_account_id || "", () => {}),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Post hour (0-23)"),
      el("input", {
        class: "wizard-input",
        type: "number",
        value: String(config.post_hour ?? 10),
        style: "width: 80px;",
        min: "0",
        max: "23",
        id: "stories-hour",
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Privacy"),
      el("select", { class: "wizard-input", style: "max-width: 200px;", id: "stories-privacy" }, [
        ...["contacts", "everyone", "close_friends"].map((v) => {
          const o = el("option", { value: v }, v);
          if (config.privacy === v) o.selected = true;
          return o;
        }),
      ]),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Default caption"),
      el("input", {
        class: "wizard-input",
        value: config.default_caption || "",
        placeholder: "Caption for all stories\u2026",
        id: "stories-caption",
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Rotation"),
      el("select", { class: "wizard-input", style: "max-width: 200px;", id: "stories-rotation" }, [
        ...["sequential", "random"].map((v) => {
          const o = el("option", { value: v }, v);
          if (config.rotation_mode === v) o.selected = true;
          return o;
        }),
      ]),
      el("div", { style: "display: flex; gap: 8px; margin-top: 12px;" }, [
        el(
          "button",
          {
            class: "btn btn-primary",
            type: "button",
            onclick: async () => {
              const r = await api("tg-stories-config", {
                body: {
                  postHour: parseInt(document.getElementById("stories-hour")?.value) || 10,
                  privacy: document.getElementById("stories-privacy")?.value || "contacts",
                  caption: document.getElementById("stories-caption")?.value || "",
                  rotationMode: document.getElementById("stories-rotation")?.value || "sequential",
                },
              });
              if (r?.ok) toast("\u2705 Config saved", "ok");
              else toast(`\u274c ${r?.error || "Failed"}`, "err");
            },
          },
          "Save Config",
        ),
        el(
          "button",
          {
            class: `btn ${config.is_active ? "btn-danger" : "btn-secondary"}`,
            type: "button",
            onclick: async () => {
              await api("tg-stories-toggle", { body: { enabled: !config.is_active } });
              toast(config.is_active ? "Stories paused" : "Stories enabled", "ok");
              renderStories();
            },
          },
          config.is_active ? "\u23f8 Pause Stories" : "\u25b6 Enable Stories",
        ),
      ]),
    ]),
    media.length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Media (${media.length} photos)`),
          el(
            "div",
            { class: "muted small" },
            "Photos are managed via Telegram (/stories) \u2014 send photos to the bot to add them.",
          ),
        ])
      : el("div", { class: "card" }, [
          el("div", { class: "card-title" }, "Media"),
          el(
            "div",
            { class: "muted small" },
            "No photos added yet. Send photos to the Telegram bot with /stories to add them.",
          ),
        ]),
  ]);
  setContentNode(content);
}

async function renderSmartScrape() {
  showLoading("Loading\u2026");
  const accts = dashboardState.cache.accounts?.telegram || [];
  let formAccountId = "",
    formTarget = "";

  const content = el("div", {}, [
    el("div", { class: "page-header" }, [
      el("div", {}, [
        el("div", { class: "page-title" }, "Smart Scrape"),
        el("div", { class: "page-subtitle" }, "Extract and enrich members from Telegram groups"),
      ]),
    ]),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "New Scrape"),
      el("div", { class: "wizard-label" }, "Account"),
      tgAccountPicker(accts, formAccountId, (v) => {
        formAccountId = v;
      }),
      el(
        "div",
        { class: "wizard-label", style: "margin-top: 12px;" },
        "Target (group URL, @username, or invite link)",
      ),
      el("input", {
        class: "wizard-input",
        placeholder: "https://t.me/groupname or @groupname",
        oninput: (e) => {
          formTarget = e.target.value.trim();
        },
      }),
      el(
        "div",
        { class: "muted small", style: "margin-top: 12px;" },
        "Scraped members will be added to your CRM with tags from the source group.",
      ),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 12px;",
          type: "button",
          onclick: async () => {
            if (!formAccountId || !formTarget) {
              toast("Select account and enter target", "err");
              return;
            }
            toast("Scrape job started\u2026 This runs in the background.", "info");
            // Use the existing Telegram command flow via the bot
            try {
              const { token, apiBase } = await chrome.storage.local.get(["token", "apiBase"]);
              const base = (apiBase || "http://localhost:9091").replace(/\/$/, "");
              await fetch(`${base}/api/telegram/dm`, {
                method: "POST",
                headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
                body: JSON.stringify({
                  text: `/scrape ${formTarget}`,
                  accountId: parseInt(formAccountId),
                }),
              });
              toast("\u2705 Scrape command sent to bot. Check CRM for results.", "ok");
            } catch (err) {
              toast(`\u274c ${err.message}`, "err");
            }
          },
        },
        "\ud83e\uddf2 Start Scrape",
      ),
    ]),
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "Tips"),
      el("div", { class: "muted small", style: "line-height: 1.8;" }, [
        "\u2022 Paste a Telegram group invite link or @username",
        el("br", {}),
        "\u2022 The bot scrapes all visible members and enriches their profiles",
        el("br", {}),
        "\u2022 Results appear in the CRM tagged with the source group",
        el("br", {}),
        "\u2022 Use Passive Listener for continuous monitoring instead of one-off scrapes",
      ]),
    ]),
  ]);
  setContentNode(content);
}

// Trigger the extension to scrape Luma cookies + push to the bot. Backed by
// the existing "sync-luma" message in background.js. Resolves true on success.
async function syncLumaSession() {
  return new Promise((resolve) => {
    try {
      chrome.runtime.sendMessage({ action: "sync-luma" }, (resp) => {
        if (chrome.runtime.lastError) {
          resolve({ error: chrome.runtime.lastError.message });
          return;
        }
        resolve(resp || {});
      });
    } catch (err) {
      resolve({ error: err.message });
    }
  });
}

async function renderLuma() {
  showLoading("Loading Luma subscriptions\u2026");
  const data = await api("luma-subs").catch(() => ({}));
  if (data?.error) {
    showError(data.error, renderLuma);
    return;
  }
  const subs = data.subscriptions || [];
  const rsvps = data.rsvps || [];
  const hasSession = !!data.has_session;
  // Two creation paths: paste a Luma URL (primary) OR set keyword/city
  // filters (advanced). The URL path is the simplest UX \u2014 paste once,
  // every event under that calendar/profile gets auto-RSVP'd.
  let formFeedUrl = "",
    formName = "",
    formCategories = "",
    formCities = "",
    formKeywords = "";
  let showAdvanced = false;

  const header = el("div", { class: "page-header" }, [
    el("div", {}, [
      el("div", { class: "page-title" }, "Luma Auto-Subscribe"),
      el("div", { class: "page-subtitle" }, "Automatically RSVP to events matching your criteria"),
    ]),
    el("div", { style: "display:flex;align-items:center;gap:8px;" }, [
      el(
        "span",
        {
          style: hasSession
            ? "font-size:11px;color:#10b981;background:rgba(16,185,129,0.12);padding:3px 9px;border-radius:10px;border:1px solid rgba(16,185,129,0.4);font-weight:600;letter-spacing:0.04em;"
            : "font-size:11px;color:#fde68a;background:rgba(234,179,8,0.12);padding:3px 9px;border-radius:10px;border:1px solid rgba(234,179,8,0.4);font-weight:600;letter-spacing:0.04em;",
        },
        hasSession ? "\u25cf CONNECTED" : "NOT CONNECTED",
      ),
      el(
        "button",
        {
          class: "btn btn-secondary btn-tiny",
          type: "button",
          title: "Re-scrape your Luma cookies (open lu.ma in a tab if not logged in)",
          onclick: async (ev) => {
            ev.currentTarget.disabled = true;
            ev.currentTarget.textContent = "Syncing\u2026";
            const r = await syncLumaSession();
            if (r?.error) toast(`Sync failed: ${r.error}`, "err");
            else toast("\u2713 Synced", "ok", 1500);
            renderLuma();
          },
        },
        hasSession ? "\u21bb Re-sync" : "\ud83d\udd0c Sync from lu.ma",
      ),
    ]),
  ]);

  // No session \u2192 show a clear setup path BEFORE the form, so users don't
  // fill out filters and hit a 400. The CTA opens lu.ma in a new tab; the
  // background script's syncLuma() picks up the cookies on next interaction.
  if (!hasSession) {
    setContentNode(
      el("div", {}, [
        header,
        el(
          "div",
          { class: "empty-state", style: "padding: 40px 20px; max-width: 520px; margin: 0 auto;" },
          [
            el("div", { class: "empty-state-icon" }, "\ud83d\udd0c"),
            el("div", { class: "empty-state-title" }, "Connect your Luma account first"),
            el(
              "div",
              { class: "empty-state-hint", style: "line-height:1.6; margin-bottom: 20px;" },
              "We use your browser's lu.ma session to discover and RSVP to events on your behalf. " +
                "Log in at lu.ma in any tab, then come back and click Sync \u2014 the extension reads " +
                "the auth cookie automatically.",
            ),
            el("div", { style: "display:flex;justify-content:center;gap:10px;flex-wrap:wrap;" }, [
              el(
                "button",
                {
                  class: "btn btn-primary",
                  type: "button",
                  onclick: () => {
                    chrome.tabs.create({ url: "https://lu.ma" });
                  },
                },
                "\u2197 Open lu.ma",
              ),
              el(
                "button",
                {
                  class: "btn btn-secondary",
                  type: "button",
                  onclick: async (ev) => {
                    ev.currentTarget.disabled = true;
                    ev.currentTarget.textContent = "Syncing\u2026";
                    const r = await syncLumaSession();
                    if (r?.error) toast(`Sync failed: ${r.error}`, "err");
                    else toast("\u2713 Synced", "ok", 1500);
                    renderLuma();
                  },
                },
                "\ud83d\udd0c I'm logged in \u2014 sync now",
              ),
            ]),
          ],
        ),
      ]),
    );
    return;
  }

  // Lazy refs so the toggle handler can swap visibility without a full render
  let advancedSection = null;
  let advancedToggleBtn = null;

  const content = el("div", {}, [
    header,
    el("div", { class: "card" }, [
      el("div", { class: "card-title" }, "Auto-apply to a Luma calendar or host"),
      el(
        "div",
        { class: "muted small", style: "margin-bottom: 12px;" },
        "Paste any Luma calendar URL (e.g. lu.ma/crypto) or host profile (e.g. lu.ma/superteam). " +
          "We'll RSVP to every upcoming event under it, watch for approvals every ~24h, and once you're in, scrape the attendees into your CRM tagged by event.",
      ),
      el("div", { class: "wizard-label" }, "Luma URL"),
      el("input", {
        class: "wizard-input",
        placeholder: "https://lu.ma/crypto  or  lu.ma/superteam",
        oninput: (e) => {
          formFeedUrl = e.target.value;
        },
      }),
      el("div", { class: "wizard-label", style: "margin-top: 12px;" }, "Label (optional)"),
      el("input", {
        class: "wizard-input",
        placeholder: "e.g. Crypto calendar, Superteam events",
        oninput: (e) => {
          formName = e.target.value;
        },
      }),
      el(
        "button",
        {
          class: "btn btn-primary",
          style: "margin-top: 14px;",
          type: "button",
          onclick: async (ev) => {
            if (!formFeedUrl.trim()) {
              toast("Paste a Luma URL first (e.g. lu.ma/crypto)", "err");
              return;
            }
            await withProgress(ev.currentTarget, async () => {
              const r = await api("luma-sub-create", {
                body: {
                  name: formName.trim() || null,
                  feedUrl: formFeedUrl.trim(),
                  autoApply: true,
                },
              });
              if (r?.ok) {
                toast(
                  "\u2705 Feed created \u2014 discovery runs at the next cycle (or use Run-now)",
                  "ok",
                  5000,
                );
                renderLuma();
              } else toast(`\u274c ${r?.error || "Failed"}`, "err");
            });
          },
        },
        "\ud83c\udfab Create feed",
      ),

      // Advanced: keyword / city filters for users who want to cast a wider
      // net than a single calendar. Hidden by default to keep the primary
      // flow clean \u2014 the URL approach covers 80%+ of cases. The toggle
      // mutates display directly (no full re-render) so input values survive.
      (advancedToggleBtn = el(
        "button",
        {
          class: "btn btn-tiny btn-secondary",
          type: "button",
          style: "margin-top: 10px; background: transparent;",
          onclick: () => {
            showAdvanced = !showAdvanced;
            if (advancedSection) advancedSection.style.display = showAdvanced ? "block" : "none";
            advancedToggleBtn.textContent = showAdvanced
              ? "\u25be Hide advanced filters"
              : "\u25b8 Advanced: keyword / city filters";
          },
        },
        "\u25b8 Advanced: keyword / city filters",
      )),

      (advancedSection = el(
        "div",
        {
          style:
            "display: none; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);",
        },
        [
          el(
            "div",
            { class: "muted small", style: "margin-bottom: 10px;" },
            "Search Luma's global Discover feed by category / city / keyword. Use this when you don't have a specific calendar URL in mind.",
          ),
          el("div", { class: "wizard-label" }, "Categories (comma-separated)"),
          el("input", {
            class: "wizard-input",
            placeholder: "crypto, web3, defi, networking",
            oninput: (e) => {
              formCategories = e.target.value;
            },
          }),
          el(
            "div",
            { class: "wizard-label", style: "margin-top: 10px;" },
            "Cities (comma-separated)",
          ),
          el("input", {
            class: "wizard-input",
            placeholder: "Lisbon, Dubai, Singapore",
            oninput: (e) => {
              formCities = e.target.value;
            },
          }),
          el(
            "div",
            { class: "wizard-label", style: "margin-top: 10px;" },
            "Keywords (comma-separated)",
          ),
          el("input", {
            class: "wizard-input",
            placeholder: "hiring, fundraise, launch",
            oninput: (e) => {
              formKeywords = e.target.value;
            },
          }),
          el(
            "button",
            {
              class: "btn btn-primary btn-tiny",
              style: "margin-top: 10px;",
              type: "button",
              onclick: async (ev) => {
                if (!formName.trim()) {
                  toast("Label is required for filter-based feeds", "err");
                  return;
                }
                await withProgress(ev.currentTarget, async () => {
                  const r = await api("luma-sub-create", {
                    body: {
                      name: formName.trim(),
                      categories: formCategories,
                      cities: formCities,
                      keywords: formKeywords,
                    },
                  });
                  if (r?.ok) {
                    toast("\u2705 Filter feed created", "ok");
                    renderLuma();
                  } else toast(`\u274c ${r?.error || "Failed"}`, "err");
                });
              },
            },
            "Create filter feed",
          ),
        ],
      )),
    ]),

    subs.length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Feeds (${subs.length})`),
          ...subs.map((s) =>
            el("div", { class: "channel-row" }, [
              el("div", { class: "channel-icon" }, s.status === "active" ? "\u2705" : "\u23f8"),
              el("div", { style: "flex: 1;" }, [
                el("div", { class: "primary-cell" }, s.name || "Subscription"),
                el(
                  "div",
                  { class: "muted", style: "font-size: 10px;" },
                  // Show feed_url if URL-based, otherwise the legacy filter chips
                  s.feed_url
                    ? `\ud83d\udd17 ${s.feed_url}`
                    : [s.categories, s.cities, s.keywords].filter(Boolean).join(" \u00b7 ") ||
                        "No filters",
                ),
              ]),
              el(
                "button",
                {
                  class: "btn btn-tiny btn-danger",
                  type: "button",
                  onclick: async () => {
                    if (!confirm(`Delete feed "${s.name || "Subscription"}"?`)) return;
                    const r = await api("luma-sub-delete", { subId: s.id });
                    if (r?.ok) {
                      toast("Deleted", "ok");
                      renderLuma();
                    } else toast(`Delete failed: ${r?.error || "unknown"}`, "err");
                  },
                },
                "\ud83d\uddd1",
              ),
            ]),
          ),
        ])
      : null,
    rsvps.length > 0
      ? el("div", { class: "card" }, [
          el("div", { class: "card-title" }, `Recent RSVPs (${rsvps.length})`),
          ...rsvps
            .slice(0, 10)
            .map((r) =>
              el("div", { class: "channel-row" }, [
                el(
                  "div",
                  { class: "channel-icon" },
                  r.rsvp_status === "going"
                    ? "\u2705"
                    : r.rsvp_status === "pending"
                      ? "\u23f3"
                      : "\u274c",
                ),
                el("div", { style: "flex: 1;" }, [
                  el("div", { class: "small" }, r.event_name || r.event_slug),
                  el(
                    "div",
                    { class: "muted", style: "font-size: 10px;" },
                    `${r.event_city || ""} ${r.event_date ? fmtDate(r.event_date) : ""} \u00b7 ${r.rsvp_status}`,
                  ),
                ]),
              ]),
            ),
        ])
      : null,
  ]);
  setContentNode(content);
}
