/**
 * Automation registry — the platform-agnostic single source of truth for
 * "what can this product do?" It generalizes the two older, platform-specific
 * registries into ONE taxonomy of Action × Source × Account(s):
 *
 *   - chrome-extension/linkedin-action-registry.js  (window.LI_ACTION_REGISTRY)
 *       runnable LinkedIn batch actions with config-form schemas + moduleMaps.
 *   - chrome-extension/platform-actions.js          (window.PLATFORM_ACTIONS)
 *       the broader catalog of in-page platform actions (LI/X/TG) + X run-from-here.
 *
 * This file does NOT replace those — it REFERENCES them (via `runRef`) so a
 * capability can never claim to be runnable without a real handler behind it,
 * and the in-product Capability Catalog (Part D) + the unified Composer (Part C)
 * read everything from here. See the plan:
 *   C:\Users\Gamer\.claude\plans\now-on-the-product-eager-hinton.md
 *
 * Pure data + helpers. No DOM, no chrome.*, no network. Loads as a browser
 * global (window.AUTOMATION_REGISTRY) AND as a CommonJS module (for node tests
 * and a future server-side catalog API), so the same source of truth can drive
 * both the extension UI and `/api/catalog`.
 *
 * Phase 0 (this file): data only — nothing consumes it yet, so adding it is a
 * zero-behavior-change addition. Phase 1 wires the Catalog + Composer to it.
 */

(function () {
  "use strict";

  // ── Capability maturity. Keeps the catalog honest. ──────────────────────
  const STATE = Object.freeze({
    BUILT: "built", // ships today, end-to-end
    PARTIAL: "partial", // plumbing exists, the surface/selector is the gap
    SOON: "coming_soon", // designed, not built
  });

  // ── Platforms + how their actions actually execute. ─────────────────────
  const EXEC_ROUTES = Object.freeze({
    EXTENSION_DOM: "extension_dom", // content script in the user's real browser
    SERVER_WORKER: "server_worker", // social-autoposter Puppeteer fleet (per-account profile)
    SERVER_HTTP: "server_http", // X GraphQL / LinkedIn Voyager, no browser
    MTPROTO: "mtproto", // Telegram premium_accounts, server-side
    EMAIL: "email", // SMTP campaigns (nodemailer), server-side
  });

  const PLATFORMS = Object.freeze([
    {
      id: "linkedin",
      label: "LinkedIn",
      routes: [EXEC_ROUTES.EXTENSION_DOM, EXEC_ROUTES.SERVER_WORKER, EXEC_ROUTES.SERVER_HTTP],
    },
    { id: "twitter", label: "X", routes: [EXEC_ROUTES.SERVER_HTTP, EXEC_ROUTES.EXTENSION_DOM] },
    { id: "telegram", label: "Telegram", routes: [EXEC_ROUTES.MTPROTO] },
    { id: "email", label: "Email", routes: [EXEC_ROUTES.EMAIL] },
  ]);

  // ── ACTIONS (the verb). Grouped by outcome so the Composer can group them. ─
  const VERB_GROUPS = Object.freeze(["Grow", "Engage", "Outreach", "Maintain", "Intelligence"]);
  const ACTIONS = Object.freeze([
    {
      id: "like",
      label: "Like",
      group: "Engage",
      description: "Like posts to create an engagement + visibility signal.",
    },
    {
      id: "comment",
      label: "Comment / Reply",
      group: "Engage",
      description: "Reply to posts (AI-drafted or custom), optionally CRM-aware.",
    },
    {
      id: "react",
      label: "React",
      group: "Engage",
      description: "Add an emoji reaction (Telegram groups).",
    },
    {
      id: "dm",
      label: "Message / DM",
      group: "Outreach",
      description: "Send a direct message (consent-gated, AI variations).",
    },
    {
      id: "connect",
      label: "Connect",
      group: "Outreach",
      description: "Send connection requests with a personalized note.",
    },
    {
      id: "accept",
      label: "Accept invites",
      group: "Maintain",
      description: "Accept pending connection requests.",
    },
    {
      id: "accept_and_dm",
      label: "Accept & Message",
      group: "Outreach",
      description: "Accept invites, then send each a personalized DM.",
    },
    {
      id: "follow",
      label: "Follow",
      group: "Grow",
      description: "Follow accounts / suggested people.",
    },
    {
      id: "unfollow",
      label: "Unfollow / clean up",
      group: "Maintain",
      description: "Unfollow or disconnect low-value accounts.",
    },
    {
      id: "view",
      label: "View profiles",
      group: "Grow",
      description: "Visit profiles so you surface in their 'who viewed you'.",
    },
    {
      id: "share",
      label: "Repost / Retweet",
      group: "Grow",
      description: "Repost content to your audience.",
    },
    {
      id: "post",
      label: "Post",
      group: "Grow",
      description: "Publish a post / promo (feed, thread, or group message).",
    },
    { id: "join", label: "Join", group: "Grow", description: "Join target groups/communities." },
    {
      id: "scrape",
      label: "Collect audience",
      group: "Intelligence",
      description: "Extract members/followers/engagers into the CRM.",
    },
    {
      id: "withdraw",
      label: "Withdraw invites",
      group: "Maintain",
      description: "Withdraw stale sent invites to free up budget.",
    },
  ]);

  // ── SOURCES (where the targets come from). ──────────────────────────────
  const SOURCES = Object.freeze([
    { id: "feed", label: "My feed", needsTarget: false, description: "Your home timeline / feed." },
    {
      id: "hashtag",
      label: "Hashtag / topic",
      needsTarget: true,
      targetLabel: "Hashtag or topic",
      description: "Posts under a hashtag or topic search.",
    },
    {
      id: "search",
      label: "Keyword search",
      needsTarget: true,
      targetLabel: "Search keywords",
      description: "People/posts matching a keyword search.",
    },
    {
      id: "crm_list",
      label: "CRM segment",
      needsTarget: true,
      targetLabel: "CRM segment",
      description: "A filtered list you pick from your CRM (shared_contacts).",
    },
    {
      id: "competitor",
      label: "Competitor audience",
      needsTarget: true,
      targetLabel: "Competitor handle/URL",
      description: "Followers / likers / commenters of a competitor.",
    },
    {
      id: "group",
      label: "Group members",
      needsTarget: true,
      targetLabel: "Group link/handle",
      description: "Members of a Telegram group.",
    },
    {
      id: "group_live",
      label: "Live group messages",
      needsTarget: true,
      targetLabel: "Group link/handle",
      description: "Messages happening live in a group (for replies/reactions).",
    },
    {
      id: "invites",
      label: "Pending invites",
      needsTarget: false,
      description: "Your pending connection requests.",
    },
    {
      id: "list",
      label: "Pasted list",
      needsTarget: true,
      targetLabel: "Handles/URLs",
      description: "A list of handles or profile URLs you paste in.",
    },
    {
      id: "suggested",
      label: "Platform suggestions",
      needsTarget: false,
      description: "People the platform suggests to you.",
    },
    {
      id: "event",
      label: "Event attendees",
      needsTarget: true,
      targetLabel: "Event URL",
      description: "Attendees of a Luma/event page.",
    },
    {
      id: "geo",
      label: "Geo-targeted",
      needsTarget: true,
      targetLabel: "Location",
      description:
        "Location-filtered search/CRM (composes with proxy geo). A modifier on search/crm_list/event.",
    },
  ]);

  // ── ACCOUNT MODES (how many accounts + how targets split across them). ───
  const ACCOUNT_MODES = Object.freeze([
    { id: "single", label: "One account", description: "Run on a single selected account." },
    {
      id: "fleet_shard",
      label: "Fleet — split targets",
      description: "Split the target list across the selected accounts (safer, default).",
    },
    {
      id: "fleet_broadcast",
      label: "Fleet — same targets",
      description:
        "Every selected account hits the same targets (only for distinct goals; riskier).",
    },
  ]);

  /**
   * CAPABILITIES — the authoritative inventory (Part A of the plan).
   * Each entry:
   *   id            stable key
   *   platform      linkedin | twitter | telegram
   *   action        an ACTIONS id
   *   sources[]     SOURCES ids this capability can target
   *   accountModes[] ACCOUNT_MODES ids supported today
   *   route         EXEC_ROUTES value (primary execution path today)
   *   state         STATE value
   *   title, description  catalog copy
   *   limitsCategory   (LinkedIn) /api/linkedin/limits-check category, if any
   *   runRef        how the Composer launches it (resolved against the existing
   *                 registries) — see resolveRunnable(). null = not yet runnable.
   *   safety        one-line safety note for the catalog
   */
  const CAPABILITIES = Object.freeze([
    // ── LinkedIn ──────────────────────────────────────────────────────────
    {
      id: "li_like_feed",
      platform: "linkedin",
      action: "like",
      sources: ["feed"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Like your feed",
      limitsCategory: "like",
      description:
        "Scroll your LinkedIn feed and like posts (optional keyword filter, optional comment every N).",
      runRef: { kind: "li_registry", id: "like-posts" },
      safety: "Daily like cap + human-paced timing.",
    },
    {
      id: "li_comment_feed",
      platform: "linkedin",
      action: "comment",
      sources: ["feed", "competitor", "search"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Comment on posts",
      limitsCategory: "comment",
      description:
        "Comment on feed / competitor / search posts. AI-drafted with CRM context, or custom text.",
      runRef: { kind: "platform_action", id: "li_engage_feed" },
      safety: "Per-contact 7-day comment cap; DOM-first with Voyager fallback.",
    },
    {
      id: "li_accept_and_dm",
      platform: "linkedin",
      action: "accept_and_dm",
      sources: ["invites"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Accept & Message",
      limitsCategory: "message",
      description:
        "Accept pending invites, optionally like their latest post, then send an AI-personalized DM (12 variations + per-recipient hook).",
      runRef: { kind: "li_registry", id: "accept-and-dm" },
      safety: "Per-recipient 90-day DM cooldown; account-age warmup; 429 backoff.",
    },
    {
      id: "li_accept_invites",
      platform: "linkedin",
      action: "accept",
      sources: ["invites"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Accept invites",
      limitsCategory: "connect_request",
      description: "Bulk-accept pending invitations with no follow-up DM.",
      runRef: { kind: "li_registry", id: "accept-invites" },
      safety: "Daily accept cap.",
    },
    {
      id: "li_add_connections",
      platform: "linkedin",
      action: "connect",
      sources: ["search"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Add connections",
      limitsCategory: "connect_request",
      description: "Search by keyword and bulk-send connection requests with a personalized note.",
      runRef: { kind: "li_registry", id: "add-connections" },
      safety: "Weekly connect budget; headline-match filter; warmup-scaled.",
    },
    {
      id: "li_message_connections",
      platform: "linkedin",
      action: "dm",
      sources: ["crm_list"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Message connections",
      limitsCategory: "message",
      description: "Bulk-DM your 1st-degree connections with a template + AI variations.",
      runRef: { kind: "li_registry", id: "message-connections" },
      safety: "Same variation pool + per-recipient context as Accept & Message.",
    },
    {
      id: "li_view_profiles",
      platform: "linkedin",
      action: "view",
      sources: ["search"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "View profiles",
      limitsCategory: "profile_view",
      description:
        "Visit N profiles so your name lands in their 'who viewed you' — soft-touch outbound.",
      runRef: { kind: "li_registry", id: "view-profiles" },
      safety: "Daily view cap.",
    },
    {
      id: "li_follow_suggested",
      platform: "linkedin",
      action: "follow",
      sources: ["suggested"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Follow suggested",
      limitsCategory: "profile_view",
      description: "Follow suggested connections — low-touch warmup signal.",
      runRef: { kind: "li_registry", id: "follow-connections" },
      safety: "Daily cap; good for warming a new account.",
    },
    {
      id: "li_withdraw_invites",
      platform: "linkedin",
      action: "withdraw",
      sources: ["invites"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Withdraw sent invites",
      limitsCategory: "profile_view",
      description:
        "Bulk-withdraw stale pending invites nobody accepted; frees your weekly connect budget.",
      runRef: { kind: "li_registry", id: "withdraw-invites" },
      safety: "Read-mostly; no outbound contact.",
    },
    {
      id: "li_competitor_siphon",
      platform: "linkedin",
      action: "scrape",
      sources: ["competitor"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Competitor siphon",
      limitsCategory: null,
      description:
        "Extract followers/engagers of a competitor's LinkedIn account into the CRM for outreach.",
      runRef: { kind: "platform_action", id: "li_competitor_siphon" },
      safety: "Collection only; feeds shared_contacts.",
    },
    {
      id: "li_connect_commenters",
      platform: "linkedin",
      action: "connect",
      sources: ["competitor"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EXTENSION_DOM,
      state: STATE.BUILT,
      title: "Connect with commenters",
      limitsCategory: "connect_request",
      description: "Pull people who commented on a target post and send them connect requests.",
      runRef: { kind: "platform_action", id: "li_connect_commenters" },
      safety: "Weekly connect budget applies.",
    },
    // Gap-fills (Part F.1 + Part E fleet)
    {
      id: "li_like_crm",
      platform: "linkedin",
      action: "like",
      sources: ["crm_list"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_WORKER,
      state: STATE.PARTIAL,
      title: "Like a CRM list",
      limitsCategory: "like",
      description:
        "Like the latest post of each person in a CRM segment you pick. Plumbing exists; needs the CRM source picker + bulk loop.",
      runRef: null,
      safety: "Daily like cap, per-account.",
    },
    {
      id: "li_comment_crm",
      platform: "linkedin",
      action: "comment",
      sources: ["crm_list"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_WORKER,
      state: STATE.PARTIAL,
      title: "Comment on a CRM list",
      limitsCategory: "comment",
      description:
        "AI-comment on the latest post of each person in a CRM segment. Reuses ai-comment.js CRM context; needs the bulk loop.",
      runRef: null,
      safety: "Per-contact 7-day comment cap, per-account.",
    },

    // ── X / Twitter ───────────────────────────────────────────────────────
    {
      id: "x_like_feed",
      platform: "twitter",
      action: "like",
      sources: ["feed"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Engage your feed",
      limitsCategory: null,
      description:
        "Like (and AI-reply to) posts from your X home timeline; skips your own + already-engaged posts.",
      runRef: { kind: "platform_action", id: "x_like_feed" },
      safety: "Daily like cap + replay guard.",
    },
    {
      id: "x_reply_feed",
      platform: "twitter",
      action: "comment",
      sources: ["feed"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Reply to posts",
      limitsCategory: null,
      description: "AI-draft and post replies to feed posts, gated by a relevance score.",
      runRef: { kind: "platform_action", id: "x_reply_feed" },
      safety: "Daily reply cap.",
    },
    {
      id: "x_engage_hashtag",
      platform: "twitter",
      action: "like",
      sources: ["hashtag"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Engage a hashtag",
      limitsCategory: null,
      description: "Like + reply to recent posts under a hashtag/topic.",
      runRef: { kind: "platform_action", id: "x_engage_hashtag" },
      safety: "Daily caps; AI like/reply/skip per post.",
    },
    {
      id: "x_follower_sniper",
      platform: "twitter",
      action: "follow",
      sources: ["competitor"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Follower sniper",
      limitsCategory: null,
      description:
        "Follow people who follow a target account — siphon a competitor's audience one tier deep.",
      runRef: { kind: "platform_action", id: "x_follower_sniper" },
      safety: "Daily follow cap.",
    },
    {
      id: "x_competitor_siphon",
      platform: "twitter",
      action: "scrape",
      sources: ["competitor"],
      accountModes: ["single"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Competitor siphon",
      limitsCategory: null,
      description: "Extract a competitor's followers and follow / DM / save them to the CRM.",
      runRef: { kind: "platform_action", id: "x_competitor_siphon" },
      safety: "Collection + optional follow.",
    },
    {
      id: "x_audience_overlap",
      platform: "twitter",
      action: "scrape",
      sources: ["competitor"],
      accountModes: ["single"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Audience overlap",
      limitsCategory: null,
      description:
        "Find users who follow MULTIPLE target accounts — highest-intent prospects in a niche.",
      runRef: { kind: "platform_action", id: "x_audience_overlap" },
      safety: "Collection only.",
    },
    {
      id: "x_follow_users",
      platform: "twitter",
      action: "follow",
      sources: ["list"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Follow a list",
      limitsCategory: null,
      description: "Follow a list of usernames you paste in.",
      runRef: { kind: "platform_action", id: "x_follow_users" },
      safety: "Daily follow cap.",
    },
    {
      id: "x_retweet",
      platform: "twitter",
      action: "share",
      sources: ["feed"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Repost posts",
      limitsCategory: null,
      description: "Retweet posts from your feed (AI-selected for topic fit).",
      runRef: { kind: "platform_action", id: "x_retweet" },
      safety: "Daily repost cap.",
    },
    {
      id: "x_raid_competitors",
      platform: "twitter",
      action: "comment",
      sources: ["competitor"],
      accountModes: ["fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.BUILT,
      title: "Raid competitor sections",
      limitsCategory: null,
      description:
        "Post promo replies in competitors' comment sections and engage their likers (scheduled engine).",
      runRef: { kind: "engine", preset: "raid_army" },
      safety: "Daily comment caps; runs as a scheduled campaign.",
    },
    {
      id: "x_like_crm",
      platform: "twitter",
      action: "like",
      sources: ["crm_list"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.SERVER_HTTP,
      state: STATE.PARTIAL,
      title: "Like a CRM list",
      limitsCategory: null,
      description:
        "Like the latest tweet of each person in a CRM segment. Single-like-by-id exists; needs the CRM source loop.",
      runRef: null,
      safety: "Daily like cap, per-account.",
    },

    // ── Telegram ──────────────────────────────────────────────────────────
    {
      id: "tg_scrape_members",
      platform: "telegram",
      action: "scrape",
      sources: ["group"],
      accountModes: ["single"],
      route: EXEC_ROUTES.MTPROTO,
      state: STATE.BUILT,
      title: "Scrape group members",
      limitsCategory: null,
      description:
        "Extract members of a Telegram group (filters: premium / active-24h / posted-90d / admins) into the CRM.",
      runRef: { kind: "platform_action", id: "tg_group_scraper" },
      safety: "FLOOD_WAIT-aware; auto-evicts stale channels.",
    },
    {
      id: "tg_mass_dm",
      platform: "telegram",
      action: "dm",
      sources: ["crm_list", "group"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.MTPROTO,
      state: STATE.BUILT,
      title: "Mass DM",
      limitsCategory: null,
      description:
        "DM a CRM segment or scraped group members via the account pool. Consent-gated, account-rotated.",
      runRef: { kind: "platform_action", id: "tg_bulk_dm" },
      safety: "Opt-out tombstones + 72h/2-per-week caps + STOP footer.",
    },

    // ── Email ─────────────────────────────────────────────────────────────
    {
      id: "email_crm_dm",
      platform: "email",
      action: "dm",
      sources: ["crm_list"],
      accountModes: ["single"],
      route: EXEC_ROUTES.EMAIL,
      state: STATE.BUILT,
      title: "Email a CRM list",
      limitsCategory: null,
      description:
        "Send a templated email campaign to a CRM segment via your configured SMTP. Open/click tracking, bounce handling, and unsubscribe gating apply.",
      runRef: { kind: "engine", preset: "email_campaign" },
      safety: "Unsubscribed contacts excluded; throttled sends (daily cap + jitter).",
    },

    {
      id: "tg_group_post",
      platform: "telegram",
      action: "post",
      sources: ["group"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.MTPROTO,
      state: STATE.BUILT,
      title: "Post in groups",
      limitsCategory: null,
      description: "Post AI-drafted promos into target groups, capped per account per day.",
      runRef: { kind: "engine", preset: "group_amplification" },
      safety: "Daily posts/account cap; dedupe per group.",
    },
    {
      id: "tg_group_reply",
      platform: "telegram",
      action: "comment",
      sources: ["group_live"],
      accountModes: ["single"],
      route: EXEC_ROUTES.MTPROTO,
      state: STATE.BUILT,
      title: "Reply live in groups",
      limitsCategory: null,
      description:
        "Listen to a group and post relevance-gated AI replies — active-member presence.",
      runRef: { kind: "engine", preset: "active_member_listener" },
      safety: "Relevance + health gates; daily reply quota.",
    },
    {
      id: "tg_join_groups",
      platform: "telegram",
      action: "join",
      sources: ["list"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.MTPROTO,
      state: STATE.BUILT,
      title: "Join groups",
      limitsCategory: null,
      description: "Join a list of target groups across the account pool.",
      runRef: { kind: "engine", preset: "group_join" },
      safety: "Daily join cap (premium-aware).",
    },
    {
      id: "tg_react",
      platform: "telegram",
      action: "react",
      sources: ["group_live"],
      accountModes: ["single", "fleet_shard"],
      route: EXEC_ROUTES.MTPROTO,
      state: STATE.BUILT,
      title: "React in groups",
      limitsCategory: null,
      description:
        "Add emoji reactions to recent messages in target groups — deduped (never twice per message) and rate-gated per account. Runs as a scheduled engine.",
      runRef: { kind: "engine", preset: "group_reactions" },
      safety: "1 reaction per message per account; daily group_react cap; skips your own messages.",
    },
  ]);

  /**
   * USE_CASES — outcome-first recipes that compose capabilities into a goal
   * (Part D.2). Each step references a capability id. The Composer can walk a
   * recipe step-by-step; the catalog renders it as a named playbook.
   */
  const USE_CASES = Object.freeze([
    {
      id: "uc_event_geo",
      title: "Promote an event, geo-targeted",
      goal: "Drive RSVPs/attendance from the right region across every channel.",
      steps: [
        {
          capability: "li_message_connections",
          note: "DM a geo-filtered CRM segment on LinkedIn.",
        },
        {
          capability: "x_reply_feed",
          note: "Reply to relevant feed chatter to surface the event.",
        },
        { capability: "tg_group_post", note: "Post the event in regional Telegram groups." },
      ],
      sourcesHint: ["geo", "crm_list", "event"],
    },
    {
      id: "uc_lead_gen",
      title: "Lead generation",
      goal: "Fill the CRM with qualified prospects and open conversations.",
      steps: [
        { capability: "x_follower_sniper", note: "Siphon a competitor's audience on X." },
        { capability: "li_add_connections", note: "Keyword-search + connect on LinkedIn." },
        { capability: "li_accept_and_dm", note: "Convert accepted invites into DMs." },
      ],
      sourcesHint: ["competitor", "search", "invites"],
    },
    {
      id: "uc_competitor_siphon",
      title: "Competitor audience siphoning",
      goal: "Capture and engage a competitor's audience across platforms.",
      steps: [
        { capability: "x_follower_sniper", note: "Follow their followers on X." },
        { capability: "x_raid_competitors", note: "Reply in their comment sections." },
        { capability: "li_comment_feed", note: "Comment on the competitor's LinkedIn posts." },
        { capability: "li_competitor_siphon", note: "Pull their engagers into the CRM." },
      ],
      sourcesHint: ["competitor"],
    },
    {
      id: "uc_community_activation",
      title: "Community activation",
      goal: "Turn a Telegram community into active, engaged participants.",
      steps: [
        { capability: "tg_scrape_members", note: "Collect the group's members." },
        { capability: "tg_mass_dm", note: "Consent-gated DM to invite/activate." },
        { capability: "tg_group_post", note: "Seed value posts into the group." },
        { capability: "tg_group_reply", note: "Maintain live presence with replies." },
        { capability: "tg_react", note: "Warm reactions on members' recent messages." },
      ],
      sourcesHint: ["group", "group_live", "crm_list"],
    },
    {
      id: "uc_network_warming",
      title: "Network warming",
      goal: "Warm up accounts and relationships with low-risk touches.",
      steps: [
        { capability: "li_accept_and_dm", note: "Accept + personally greet new invites." },
        { capability: "li_view_profiles", note: "Soft-touch profile views." },
        { capability: "li_follow_suggested", note: "Follow suggested people." },
        { capability: "x_like_feed", note: "Light, natural feed engagement on X." },
      ],
      sourcesHint: ["invites", "search", "feed", "suggested"],
    },
  ]);

  // ── Helpers ─────────────────────────────────────────────────────────────

  function getCapability(id) {
    return CAPABILITIES.find((c) => c.id === id) || null;
  }

  /**
   * Filtered capability list. All filters optional + AND-combined.
   *   { platform, action, source, state }
   */
  function getCapabilities(filter) {
    const f = filter || {};
    return CAPABILITIES.filter((c) => {
      if (f.platform && c.platform !== f.platform) return false;
      if (f.action && c.action !== f.action) return false;
      if (f.state && c.state !== f.state) return false;
      if (f.source && !c.sources.includes(f.source)) return false;
      return true;
    });
  }

  function getUseCases() {
    return USE_CASES.slice();
  }

  function getAction(id) {
    return ACTIONS.find((a) => a.id === id) || null;
  }

  function getSource(id) {
    return SOURCES.find((s) => s.id === id) || null;
  }

  /**
   * Resolve how a capability actually runs. Returns the linked entry from the
   * underlying registry so the Composer can launch it without re-declaring the
   * config schema. This is what guarantees the catalog can't claim a runnable
   * capability that has no handler (the no-drift test asserts every non-null,
   * non-engine runRef resolves here).
   *
   * In the browser, reads window.LI_ACTION_REGISTRY / window.PLATFORM_ACTIONS.
   * For tests/server, pass { liRegistry, platformActions } explicitly.
   *
   * Returns:
   *   { kind, id|preset, entry }  on success (entry is null for engine refs)
   *   null                        if the capability isn't runnable yet
   *   { ...ref, entry: null, missing: true }  if a ref exists but doesn't resolve
   */
  function resolveRunnable(capOrId, registries) {
    const cap = typeof capOrId === "string" ? getCapability(capOrId) : capOrId;
    if (!cap || !cap.runRef) return null;
    const ref = cap.runRef;

    const reg = registries || {};
    const liRegistry =
      reg.liRegistry || (typeof window !== "undefined" ? window.LI_ACTION_REGISTRY : null) || [];
    const platformActions =
      reg.platformActions || (typeof window !== "undefined" ? window.PLATFORM_ACTIONS : null) || [];

    if (ref.kind === "li_registry") {
      const entry = liRegistry.find((e) => e.id === ref.id) || null;
      return entry ? { ...ref, entry } : { ...ref, entry: null, missing: true };
    }
    if (ref.kind === "platform_action") {
      const entry = platformActions.find((e) => e.id === ref.id) || null;
      return entry ? { ...ref, entry } : { ...ref, entry: null, missing: true };
    }
    if (ref.kind === "engine") {
      // Server-side preset/engine — no browser registry entry. The Composer
      // routes these to /api/workflows or the relevant run-now endpoint.
      return { ...ref, entry: null };
    }
    return { ...ref, entry: null, missing: true };
  }

  const API = Object.freeze({
    STATE,
    EXEC_ROUTES,
    PLATFORMS,
    VERB_GROUPS,
    ACTIONS,
    SOURCES,
    ACCOUNT_MODES,
    CAPABILITIES,
    USE_CASES,
    getCapability,
    getCapabilities,
    getUseCases,
    getAction,
    getSource,
    resolveRunnable,
  });

  if (typeof window !== "undefined") window.AUTOMATION_REGISTRY = API;
  if (typeof module !== "undefined" && module.exports) module.exports = API;
})();
