/**
 * Nexus Connect — Twitter/X Content Script
 * Floating panel on twitter.com/x.com pages.
 * All actions use the bot's HTTP API (no DOM automation).
 * Account is selected once on the main menu, not per-action.
 */

// ── Humanization helpers ─────────────────────────────────────────────
// humanDelay / sessionWarmup / maybeFatiguePause live in content-common.js,
// prepended to this content_scripts entry in manifest.json (shared MV3 world).

// ── Extension context guard ──────────────────────────────────────────
let _contextDead = false;
function isAlive() {
  try { return !_contextDead && !!chrome.runtime?.id; }
  catch { _contextDead = true; return false; }
}

// ── CRM sync (fire-and-forget) ───────────────────────────────────────
function crmSync(data) {
  if (!isAlive()) return;
  try { chrome.runtime.sendMessage({ action: "crm-sync", ...data }); } catch { _contextDead = true; }
}

const PANEL_ID = "nexus-twitter-panel";
let panelLog = [];
let panelStatus = "Ready";
let panelMinimized = false;
let selectedAccounts = []; // array of usernames currently selected
let availableAccounts = []; // loaded from bot API

// ── State persistence ─────────────────────────────────────────────────

function savePanelState() {
  if (!isAlive()) return;
  try { chrome.storage.session.set({ _nxTwLog: panelLog.slice(0, 20), _nxTwStatus: panelStatus, _nxTwMin: panelMinimized, _nxTwAccounts: selectedAccounts }); } catch {}
}

async function loadPanelState() {
  if (!isAlive()) return;
  try {
    const d = await chrome.storage.session.get(["_nxTwLog", "_nxTwStatus", "_nxTwMin", "_nxTwAccounts"]);
    if (d._nxTwLog) panelLog = d._nxTwLog;
    if (d._nxTwStatus) panelStatus = d._nxTwStatus;
    if (d._nxTwMin !== undefined) panelMinimized = d._nxTwMin;
    if (d._nxTwAccounts) selectedAccounts = d._nxTwAccounts;
  } catch {}
}

const LOG_ICONS = { "V": "\u2713", "X": "\u2717", "S": "\u25f7", "-": "\u2139", "!": "\u26a0" };

// Background-driven batch state. When the dashboard's "Run from here" fires an
// X action, background.js spawns a hidden x.com worker tab and dispatches the
// action HERE. In that mode this tab is the invisible worker \u2014 there's no panel
// the user is looking at, so addLog/setStatus relay their text back to the
// originating dashboard tab (`panelTabId`) as nx-batch-progress messages, the
// same channel content-linkedin.js uses. `null` for normal in-page panel runs.
let _NX_TW_BG_BATCH = null; // { panelTabId: number } | null
// Set while a background-driven ("Run from here") action runs, so addLog /
// setStatus breadcrumbs can be persisted (not just shown in the panel) and
// stitched to the run via runId. This is what makes a stuck run debuggable.
let _NX_TW_RUN_ID = null;
let _NX_TW_RUN_ACTION = null;
let _NX_TW_RUN_ACCT = null;

// Persist a panel breadcrumb to twitter_action_log + x-action-trace.jsonl via
// background's tw-log relay. Only fires for driven runs (where we have a runId
// context); normal in-panel use is unaffected. Best-effort.
function _twRelayLog(status, text, errorMessage = null, url = null) {
  if (!_NX_TW_RUN_ID && !_NX_TW_BG_BATCH) return;
  try {
    chrome.runtime.sendMessage({
      type: "tw-log",
      payload: {
        source: "extension",
        action: _NX_TW_RUN_ACTION || "x-action",
        status,
        errorMessage,
        accountIdentifier: _NX_TW_RUN_ACCT || null,
        runId: _NX_TW_RUN_ID || null,
        // Persist the post/message link so the X Activity history (and not just
        // the live drawer) keeps a clickable record of every action.
        result: (text || url) ? { msg: text || null, url: url || null } : null,
      },
    }).catch(() => {});
  } catch { /* ignore */ }
}

// Per-step screenshot: ask the SW to capture the worker tab for the visual
// replay. Step index increments per logged breadcrumb; reset at run start.
let _twShotStep = 0;
function _twShot(stepName, status) {
  if (!_NX_TW_RUN_ID) return;
  try {
    chrome.runtime.sendMessage({
      type: "tw-shot",
      runId: _NX_TW_RUN_ID,
      stepIndex: _twShotStep++,
      stepName: stepName || null,
      status: status || "info",
    }).catch(() => {});
  } catch { /* ignore */ }
}

function addLog(icon, text, url = null) {
  const displayIcon = LOG_ICONS[icon] || icon;
  panelLog.unshift({ icon: displayIcon, text, url, time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) });
  if (panelLog.length > 50) panelLog.length = 50;
  updateLogUI(); savePanelState();
  if (_NX_TW_BG_BATCH) {
    try {
      chrome.runtime.sendMessage({
        type: "nx-batch-progress",
        panelTabId: _NX_TW_BG_BATCH.panelTabId,
        kind: "log", icon, text, url,
      }).catch(() => {});
    } catch { /* ignore */ }
    // Error icon → a 'fail' row; every other breadcrumb is an 'info' step.
    _twRelayLog(icon === "X" ? "fail" : "info", text, icon === "X" ? text : null, url);
  }
  // Capture a screenshot per logged step when a run is active (best-effort).
  if (_NX_TW_RUN_ID) _twShot(text, icon === "X" ? "fail" : "info");
}

function tweetUrl(authorUsername, tweetId) {
  if (!tweetId) return null;
  return `https://x.com/${authorUsername || "i"}/status/${tweetId}`;
}

let _showFullHistory = false;

function updateLogUI() {
  const el = document.getElementById(PANEL_ID)?.shadowRoot?.getElementById("nx-log");
  if (!el) return;
  const items = _showFullHistory ? panelLog : panelLog.slice(0, 8);
  if (items.length === 0) {
    el.innerHTML = '<div class="nx-empty">No activity yet</div>';
    return;
  }
  el.innerHTML = items.map((l, i) => {
    const clickable = l.url ? ' clickable' : '';
    const textContent = l.url
      ? `<span class="nx-log-link" data-url="${l.url}">${l.text}</span>`
      : l.text;
    return `<div class="nx-log-item${clickable}" data-idx="${i}"><span class="nx-log-icon">${l.icon}</span><span class="nx-log-text">${textContent}</span><span class="nx-log-time">${l.time}</span></div>`;
  }).join("")
    + (panelLog.length > 8 ? `<div class="nx-log-toggle" id="nx-log-toggle">${_showFullHistory ? "Show less" : `Show all (${panelLog.length})`}</div>` : '');

  // Click handlers for links and toggle
  el.querySelectorAll(".nx-log-link").forEach(link => {
    link.addEventListener("click", (e) => { e.stopPropagation(); window.open(link.dataset.url, "_blank"); });
  });
  const toggle = el.querySelector("#nx-log-toggle");
  if (toggle) toggle.addEventListener("click", () => { _showFullHistory = !_showFullHistory; updateLogUI(); });
}

function setStatus(text) {
  panelStatus = text;
  const el = document.getElementById(PANEL_ID)?.shadowRoot?.getElementById("nx-status");
  if (el) el.textContent = text;
  savePanelState();
  if (_NX_TW_BG_BATCH) {
    try {
      chrome.runtime.sendMessage({
        type: "nx-batch-progress",
        panelTabId: _NX_TW_BG_BATCH.panelTabId,
        kind: "status", text,
      }).catch(() => {});
    } catch { /* ignore */ }
    _twRelayLog("info", text);
  }
}

// ── Background-driven action runner ───────────────────────────────────
// Reuses the EXISTING in-panel action logic (ACTION_CONFIGS[id].start) without
// any panel/user being present. The panel's start() reads its config from the
// shadow DOM via `shadow.getElementById("cfg-x").value`; here we hand it a tiny
// fake shadow backed by the `config` object the dashboard collected, so the same
// battle-tested loop runs unchanged. Also sets `selectedAccounts` (the module
// var every action iterates) from the batch config or the first synced account.
// Dashboard ids are `x_like_feed`; panel ACTION_CONFIGS keys are `like-feed`.
// The rule is: drop the `x_` prefix, swap underscores → hyphens. A few don't
// follow that (the dashboard split one panel action into AI/non-AI variants),
// so those get an explicit alias.
const _TW_ACTION_ID_ALIASES = {
  x_engage_feed_ai: "engage-feed",
};
function _twActionKeyFor(actionId) {
  if (ACTION_CONFIGS[actionId]) return actionId;                 // already a panel key
  if (_TW_ACTION_ID_ALIASES[actionId]) return _TW_ACTION_ID_ALIASES[actionId];
  const derived = String(actionId).replace(/^x_/, "").replace(/_/g, "-");
  return ACTION_CONFIGS[derived] ? derived : actionId;
}

async function runDrivenTwitterAction({ actionId, config = {}, panelTabId, account, runId = null } = {}) {
  const cfg = config || {};
  // Turn on the progress relay FIRST so every step below (including the account
  // lookup and any early error) is visible in the dashboard drawer — this is the
  // breadcrumb that tells us exactly how far a stuck run got.
  _NX_TW_BG_BATCH = (typeof panelTabId === "number") ? { panelTabId } : null;
  _NX_TW_RUN_ID = runId || null;
  _NX_TW_RUN_ACCT = account || null;
  _twShotStep = 0; // fresh screenshot sequence for this run

  const key = _twActionKeyFor(actionId);
  const action = ACTION_CONFIGS[key];
  _NX_TW_RUN_ACTION = key;
  if (!action || typeof action.start !== "function") {
    _NX_TW_BG_BATCH = null;
    _NX_TW_RUN_ID = null;
    return { error: `Unknown X action: ${actionId}` };
  }
  addLog("S", `Reached x.com — preparing "${action.title || key}"…`);

  // Choose which account(s) to run as. Prefer an explicit account, else the
  // first synced X account. Without one there's nobody to act as.
  let acct = account;
  if (!acct) {
    setStatus("Looking up your X account…");
    try {
      const res = await twitterCmd("twitter:accounts", {});
      const list = (res.accounts || []).map(a => typeof a === "string" ? a : (a.username || a.handle || "")).filter(Boolean);
      acct = list[0];
    } catch (e) {
      addLog("X", `Couldn't load X accounts: ${e?.message || e}`);
    }
  }
  if (!acct) {
    addLog("X", "No X account synced — open the extension popup and Sync your X session first.");
    _NX_TW_BG_BATCH = null;
    return { error: "No X account synced — sync an X session first" };
  }
  addLog("V", `Running as @${acct}`);
  selectedAccounts = [acct];
  _NX_TW_RUN_ACCT = acct;

  // Fake shadow backing the panel action's config reads. Actions read fields as
  // `.value` (text/number/select) OR `.checked` (boolean, e.g. cfg-preview), so
  // we expose both off the same config object. Booleans come through as real
  // bools; `.value` coerces to string the way a DOM input would.
  const fakeShadow = {
    getElementById: (id) => {
      const v = cfg[id];
      return {
        get value() { return v != null ? String(v) : ""; },
        set value(_x) { /* writes (e.g. autopilot) are no-ops headlessly */ },
        get checked() { return v === true || v === "true" || v === "on"; },
        set checked(_x) { /* no-op */ },
      };
    },
  };

  // (_NX_TW_BG_BATCH was already enabled at the top so the steps above relay.)
  // Bound the action with a timeout so a hung start() (e.g. a deep await that
  // never resolves) surfaces as an error instead of leaving the dashboard on
  // "Auto-engaging…" forever. Sits under the background dispatch timeout (105s).
  try {
    await Promise.race([
      action.start(fakeShadow),
      new Promise((_, reject) => setTimeout(() => reject(new Error("action timed out after 95s")), 95_000)),
    ]);
    return { ok: true, account: acct };
  } catch (err) {
    return { error: err?.message || String(err) };
  } finally {
    _NX_TW_BG_BATCH = null;
    _NX_TW_RUN_ID = null;
    _NX_TW_RUN_ACTION = null;
    _NX_TW_RUN_ACCT = null;
  }
}

function getSelectedUsername() {
  return selectedAccounts[0] || null;
}

// ── Send Twitter command via background → bot API ─────────────────────

function friendlyError(msg) {
  if (!msg) return "Unknown error";
  if (msg.includes("Failed to fetch") || msg.includes("NetworkError")) return "Bot server offline — start the server and retry";
  if (msg.includes("context invalidated")) return "Extension reloaded — refresh this page";
  if (msg.includes("session expired")) return "Session expired — open the platform and log in to re-sync";
  if (msg.includes("rate limit") || msg.includes("429")) return "Rate limited — wait a few minutes and try again";
  if (msg.includes("timed out")) return "Request timed out — bot may be busy, try again";
  if (msg.includes("Not authenticated")) return "Not paired — click the extension icon to set up";
  return msg;
}

function twitterCmd(type, params = {}) {
  // Auto-thread the current run's correlation id to the bot/worker so the
  // server-side and worker-side trace rows stitch to this same run. Call sites
  // (the panel actions) don't need to know about runId.
  if (_NX_TW_RUN_ID && params && params.runId == null) params = { ...params, runId: _NX_TW_RUN_ID };
  return new Promise((resolve, reject) => {
    if (!isAlive()) return reject(new Error("Extension reloaded — refresh this page"));
    const attempt = (retryLeft) => {
      if (!isAlive()) return reject(new Error("Extension reloaded — refresh this page"));
      try {
        chrome.runtime.sendMessage({ action: "twitter-command", type, params }, (res) => {
          const err = chrome.runtime.lastError;
          if (err) {
            if (err.message?.includes("context invalidated")) { _contextDead = true; return reject(new Error("Extension reloaded — refresh this page")); }
            if (retryLeft > 0 && err.message?.includes("establish connection")) { setTimeout(() => attempt(retryLeft - 1), 1000); return; }
            return reject(new Error(friendlyError(err.message)));
          }
          if (res?.error) return reject(new Error(friendlyError(res.error)));
          resolve(res);
        });
      } catch { _contextDead = true; reject(new Error("Extension reloaded — refresh this page")); }
    };
    attempt(2);
  });
}

// Keep service worker alive while panel is active
let _keepaliveInterval = null;
function startKeepalive() {
  if (_keepaliveInterval) return;
  _keepaliveInterval = setInterval(() => {
    if (!isAlive()) { clearInterval(_keepaliveInterval); _keepaliveInterval = null; return; }
    try { chrome.runtime.sendMessage({ action: "keepalive" }).catch(() => {}); }
    catch { _contextDead = true; clearInterval(_keepaliveInterval); _keepaliveInterval = null; }
  }, 25000);
}
function stopKeepalive() {
  if (_keepaliveInterval) { clearInterval(_keepaliveInterval); _keepaliveInterval = null; }
}

// ── Action configs (no username field — uses menu-level selection) ─────

const ACTION_CONFIGS = {
  "like-feed": {
    title: "Like Posts from Feed",
    html: `
      <label class="nx-label">Posts to like</label>
      <input class="nx-input nx-small" id="cfg-count" type="number" value="20" min="1" max="100">
      <p class="nx-hint">Likes posts from your home timeline.</p>`,
    async start(shadow) {
      const count = parseInt(shadow.getElementById("cfg-count").value) || 20;
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Liking ${count} posts...`);
        addLog("...", `@${username}: Liking ${count} feed posts`);
        try {
          const feed = await twitterCmd("twitter:http-home-feed", { username, count: count * 3 });
          let liked = 0, skipped = 0;
          for (const tweet of (feed.tweets || [])) {
            if (liked >= count) break;
            const tweetId = tweet.id || tweet.rest_id;
            if (!tweetId) continue;
            // Skip already-liked tweets
            if (tweet.favorited || tweet.liked) { skipped++; continue; }
            const author = tweet.author?.username || tweet.user?.screen_name || "i";
            try {
              await twitterCmd("twitter:http-like", { username, tweetId });
              liked++;
              const preview = (tweet.text || "").slice(0, 45).replace(/\n/g, " ");
              addLog("H", `@${username} liked: ${preview}...`, tweetUrl(author, tweetId));
            } catch (err) {
              if (err.message?.includes("already favorited") || err.message?.includes("already liked")) {
                skipped++;
                continue;
              }
              addLog("-", `@${username}: skip — ${err.message?.slice(0, 40)}`);
            }
            await maybeFatiguePause(liked);
            await humanDelay(3000 * sessionWarmup(liked));
          }
          addLog("V", `@${username}: Liked ${liked}${skipped ? `, skipped ${skipped} already liked` : ""}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "reply-feed": {
    title: "Reply to Posts",
    html: `
      <label class="nx-label">Reply text (empty = generic)</label>
      <textarea class="nx-input nx-textarea" id="cfg-text" rows="2" placeholder="AI-generated if empty"></textarea>
      <label class="nx-label">Posts to reply to</label>
      <input class="nx-input nx-small" id="cfg-count" type="number" value="5" min="1" max="20">`,
    async start(shadow) {
      const text = shadow.getElementById("cfg-text").value.trim();
      const count = parseInt(shadow.getElementById("cfg-count").value) || 5;
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Replying to ${count} posts...`);
        addLog("...", `@${username}: Replying to ${count} posts`);
        try {
          const feed = await twitterCmd("twitter:http-home-feed", { username, count: count * 3 });
          let replied = 0;
          const tweets = (feed.tweets || []).filter(t => !t.favorited && !t.liked); // skip already-engaged
          for (const tweet of tweets) {
            if (replied >= count) break;
            const tweetId = tweet.id || tweet.rest_id;
            const author = tweet.author?.username || tweet.user?.screen_name || "i";
            const tweetText = tweet.text || tweet.full_text || "";
            // Per-reply voice-aware AI generation. Captures meta so we
            // can log voice profile telemetry post-send.
            let replyText = (text || "").trim();
            let replyMeta = null;
            if (!replyText && tweetText) {
              try {
                const r = await chrome.runtime.sendMessage({
                  action: "ai-reply",
                  body: {
                    platform: "twitter",
                    postText: tweetText,
                    author,
                    replyingAccount: username,
                  },
                });
                if (r?.success && r?.text) {
                  replyText = r.text.trim();
                  replyMeta = r.meta || null;
                }
              } catch {}
            }
            if (!replyText) {
              addLog("S", `@${username}: skipped reply to @${author} (no AI text)`);
              continue;
            }
            let ourReplyPostId = null;
            try {
              const replyResult = await twitterCmd("twitter:http-reply", { username, text: replyText, replyToTweetId: tweetId });
              ourReplyPostId = replyResult?.tweetId ? String(replyResult.tweetId) : null;
              replied++;
              addLog("R", `@${username} replied to @${author}: "${replyText.slice(0, 30)}..."`, tweetUrl(author, tweetId));
              // Log this reply for voice-profile A/B telemetry. Fire-
              // and-forget — telemetry never blocks the action loop.
              if (replyMeta) {
                chrome.runtime.sendMessage({
                  action: "ai-reply-log",
                  body: {
                    platform: "twitter",
                    replyingAccount: username,
                    voiceSource: replyMeta.voiceSource,
                    voiceHash: replyMeta.voiceHash,
                    lengthBucket: replyMeta.lengthBucket,
                    targetHandle: author,
                    targetPostId: String(tweetId),
                    replyText: replyText.slice(0, 280),
                    ourReplyPostId,
                  },
                }).catch(() => {});
              }
            } catch {}
            await humanDelay(5000 * sessionWarmup(replied));
          }
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "retweet": {
    title: "Retweet Posts",
    html: `
      <label class="nx-label">Posts to retweet</label>
      <input class="nx-input nx-small" id="cfg-count" type="number" value="5" min="1" max="20">`,
    async start(shadow) {
      const count = parseInt(shadow.getElementById("cfg-count").value) || 5;
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Retweeting ${count} posts...`);
        try {
          const feed = await twitterCmd("twitter:http-home-feed", { username, count: count * 2 });
          let rt = 0;
          for (const tweet of (feed.tweets || []).slice(0, count)) {
            const tweetId = tweet.id || tweet.rest_id;
            const author = tweet.author?.username || tweet.user?.screen_name || "i";
            try {
              await twitterCmd("twitter:http-retweet", { username, tweetId });
              rt++;
              const preview = (tweet.text || "").slice(0, 40).replace(/\n/g, " ");
              addLog("T", `@${username} RT'd @${author}: ${preview}...`, tweetUrl(author, tweetId));
            } catch {}
            await humanDelay(3500 * sessionWarmup(rt));
          }
          addLog("V", `@${username}: Retweeted ${rt}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "quote-tweet": {
    title: "Quote Tweet",
    html: `
      <label class="nx-label">Tweet URL or ID</label>
      <input class="nx-input" id="cfg-tweet-id" type="text" placeholder="https://x.com/user/status/123... or just the ID">
      <label class="nx-label">Your comment</label>
      <textarea class="nx-input nx-textarea" id="cfg-text" rows="2" placeholder="Great thread!"></textarea>`,
    async start(shadow) {
      let tweetId = shadow.getElementById("cfg-tweet-id").value.trim();
      const text = shadow.getElementById("cfg-text").value.trim();
      // Extract ID from URL if pasted
      const urlMatch = tweetId.match(/status\/(\d+)/);
      if (urlMatch) tweetId = urlMatch[1];
      if (!tweetId) { setStatus("Enter a tweet URL or ID"); return; }
      for (const username of selectedAccounts) {
        try {
          const qr = await twitterCmd("twitter:http-quote-tweet", { username, quotedTweetId: tweetId, text });
          const qid = qr?.id || qr?.tweetId || qr?.rest_id;
          addLog("V", `@${username}: Quoted tweet`, qid ? tweetUrl(username, qid) : tweetUrl(null, tweetId));
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "post-tweet": {
    title: "Post Tweet",
    html: `
      <label class="nx-label">Tweet text</label>
      <textarea class="nx-input nx-textarea" id="cfg-text" rows="3" placeholder="What's happening?"></textarea>`,
    async start(shadow) {
      const text = shadow.getElementById("cfg-text").value.trim();
      if (!text) { setStatus("Enter tweet text"); return; }
      for (const username of selectedAccounts) {
        try {
          const pr = await twitterCmd("twitter:http-post", { username, text });
          const pid = pr?.id || pr?.tweetId || pr?.rest_id;
          addLog("V", `@${username}: Posted tweet`, pid ? tweetUrl(username, pid) : null);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "follow-users": {
    title: "Follow Users",
    html: `
      <label class="nx-label">Usernames to follow (comma-separated)</label>
      <textarea class="nx-input nx-textarea" id="cfg-targets" rows="2" placeholder="@user1, @user2, @user3"></textarea>`,
    async start(shadow) {
      const targets = shadow.getElementById("cfg-targets").value.split(",").map(s => s.trim().replace(/^@/, "")).filter(Boolean);
      if (!targets.length) { setStatus("Enter usernames"); return; }
      for (const username of selectedAccounts) {
        let followed = 0;
        for (const target of targets) {
          try { await twitterCmd("twitter:http-follow-by-username", { username, targetUsername: target }); followed++; addLog("V", `@${username} followed @${target}`, `https://x.com/${target}`); } catch {}
          await humanDelay(2500 * sessionWarmup(followed));
        }
        addLog("V", `@${username}: Followed ${followed}/${targets.length}`);
      }
      setStatus("Done!");
    },
  },
  "follower-sniper": {
    title: "Follower Sniper",
    html: `
      <label class="nx-label">Target account (steal their followers)</label>
      <input class="nx-input" id="cfg-target" type="text" placeholder="@competitor">
      <label class="nx-label">Max follows</label>
      <input class="nx-input nx-small" id="cfg-count" type="number" value="10" min="1" max="50">`,
    async start(shadow) {
      const target = shadow.getElementById("cfg-target").value.trim().replace(/^@/, "");
      const maxFollows = parseInt(shadow.getElementById("cfg-count").value) || 10;
      if (!target) { setStatus("Enter target account"); return; }
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Sniping @${target}'s followers...`);
        addLog("...", `@${username}: Sniper → @${target}`);
        try {
          await twitterCmd("twitter:http-follower-sniper", { username, targetHandle: target, maxFollows });
          addLog("V", `@${username}: Sniped from @${target}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "unfollow-cleanup": {
    title: "Unfollow Non-Followers",
    html: `
      <label class="nx-label">Max to unfollow</label>
      <input class="nx-input nx-small" id="cfg-count" type="number" value="15" min="1" max="50">`,
    async start(shadow) {
      const max = parseInt(shadow.getElementById("cfg-count").value) || 15;
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Cleaning up...`);
        try {
          await twitterCmd("twitter:http-unfollow-cleanup", { username, maxUnfollows: max });
          addLog("V", `@${username}: Unfollow cleanup done`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "engage-hashtag": {
    title: "Engage Hashtag",
    html: `
      <label class="nx-label">Hashtag</label>
      <input class="nx-input" id="cfg-hashtag" type="text" placeholder="#web3">`,
    async start(shadow) {
      const hashtag = shadow.getElementById("cfg-hashtag").value.trim().replace(/^#/, "");
      if (!hashtag) { setStatus("Enter a hashtag"); return; }
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Engaging #${hashtag}...`);
        try {
          await twitterCmd("twitter:http-engage-hashtag", { username, hashtag });
          addLog("V", `@${username}: Engaged #${hashtag}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "engage-feed": {
    title: "Auto-Engage Feed",
    html: `<p class="nx-hint">AI-driven engagement: likes, replies, and retweets based on feed analysis.</p>`,
    async start() {
      for (const username of selectedAccounts) {
        setStatus(`@${username}: Auto-engaging...`);
        addLog("...", `@${username}: Auto-engage feed`);
        try {
          await twitterCmd("twitter:http-engage-feed", { username });
          addLog("V", `@${username}: Feed engaged`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "crm-dashboard": {
    title: "Open CRM Dashboard",
    html: `<p class="nx-hint">View the full CRM dashboard in a new tab — funnel, top leads, account health, weekly trends, and cross-platform stats live here.</p>
      <p class="nx-hint" style="opacity:0.7;">Press <b>Start</b> below to open it.</p>`,
    async start() {
      try {
        const url = chrome.runtime.getURL("dashboard.html#crm");
        window.open(url, "_blank", "noopener");
        setStatus("Opening dashboard…");
        addLog("V", "Opened CRM dashboard in new tab");
      } catch (err) {
        setStatus("Error: " + err.message);
        addLog("X", err.message);
      }
    },
  },
  "view-stats": {
    title: "Open Twitter Monitor",
    html: `<p class="nx-hint">View today's engagement, account health, and historical stats in the dashboard's Twitter Monitor — much richer than what we can fit in this panel.</p>
      <p class="nx-hint" style="opacity:0.7;">Press <b>Start</b> below to open it.</p>`,
    async start() {
      try {
        const url = chrome.runtime.getURL("dashboard.html#x/monitor");
        window.open(url, "_blank", "noopener");
        setStatus("Opening Twitter monitor…");
        addLog("V", "Opened Twitter monitor in new tab");
      } catch (err) {
        setStatus("Error: " + err.message);
        addLog("X", err.message);
      }
    },
  },
  "competitor-siphon": {
    title: "Competitor Siphon",
    html: `
      <label class="nx-label">Target account</label>
      <input class="nx-input" id="cfg-target" type="text" placeholder="@competitor">
      <label class="nx-label">Depth</label>
      <select class="nx-input" id="cfg-depth">
        <option value="light">Light (like their tweets)</option>
        <option value="medium" selected>Medium (like + follow)</option>
        <option value="heavy">Heavy (like + follow + reply)</option>
      </select>
      <label class="nx-label">Max followers to engage</label>
      <input class="nx-input nx-small" id="cfg-max" type="number" value="15" min="5" max="50">
      <p class="nx-hint">Fetches a competitor's followers, likes their recent tweets, then follows them. Steals their audience.</p>`,
    async start(shadow) {
      const target = shadow.getElementById("cfg-target").value.trim().replace(/^@/, "");
      const depth = shadow.getElementById("cfg-depth").value;
      const maxFollowers = parseInt(shadow.getElementById("cfg-max").value) || 15;
      if (!target) { setStatus("Enter a target account"); return; }

      for (const username of selectedAccounts) {
        addLog("S", `@${username}: Siphoning @${target}'s audience`);
        let actions = 0;

        try {
          // Step 1: Get competitor's followers
          setStatus(`@${username}: Fetching @${target}'s followers...`);
          const followersResult = await twitterCmd("twitter:http-followers", { username, targetUsername: target, count: maxFollowers * 2 });
          const followers = followersResult.users || followersResult.followers || [];
          if (followers.length === 0) { addLog("-", `@${username}: no followers found for @${target}`); continue; }
          addLog("V", `@${username}: Found ${followers.length} followers of @${target}`);

          let liked = 0, followed = 0, replied = 0;

          for (const follower of followers.slice(0, maxFollowers)) {
            if (actions >= maxFollowers) break;
            const fHandle = follower.screen_name || follower.username || follower.legacy?.screen_name;
            const fId = follower.id || follower.rest_id || follower.id_str;
            if (!fHandle && !fId) continue;
            const profileUrl = fHandle ? `https://x.com/${fHandle}` : null;

            setStatus(`@${username}: (${actions + 1}/${maxFollowers}) Engaging @${fHandle || fId}...`);

            // Like their recent tweets
            try {
              const tweetsResult = await twitterCmd("twitter:http-user-tweets", { username, targetUserId: fId, count: 3 });
              const tweets = tweetsResult.tweets || [];
              for (const tweet of tweets.slice(0, 2)) {
                const tweetId = tweet.id || tweet.rest_id;
                if (!tweetId) continue;
                try {
                  await twitterCmd("twitter:http-like", { username, tweetId });
                  liked++;
                  const preview = (tweet.text || "").slice(0, 40).replace(/\n/g, " ");
                  addLog("H", `@${username} liked @${fHandle}: ${preview}...`, tweetUrl(fHandle, tweetId));
                } catch {}
                await humanDelay(1500);
              }
            } catch {}

            // Follow (medium + heavy)
            if (depth !== "light" && fHandle) {
              try {
                await twitterCmd("twitter:http-follow-by-username", { username, targetUsername: fHandle });
                followed++;
                addLog("F", `@${username} followed @${fHandle}`, profileUrl);
                crmSync({ platform: "twitter", identifier: fHandle, displayName: fHandle, source: `siphon:${target}`, action: "followed" });
              } catch {}
              await humanDelay(2000);
            }

            // Reply to latest tweet (heavy only) — voice-aware AI gen.
            if (depth === "heavy") {
              try {
                const tweetsResult = await twitterCmd("twitter:http-user-tweets", { username, targetUserId: fId, count: 1 });
                const tweet = (tweetsResult.tweets || [])[0];
                const tweetText = tweet?.text || tweet?.full_text || "";
                if (tweet && tweetText) {
                  let replyText = "";
                  let replyMeta = null;
                  try {
                    const r = await chrome.runtime.sendMessage({
                      action: "ai-reply",
                      body: { platform: "twitter", postText: tweetText, author: fHandle, replyingAccount: username },
                    });
                    if (r?.success && r?.text) { replyText = r.text.trim(); replyMeta = r.meta || null; }
                  } catch {}
                  if (replyText) {
                    const tweetId = tweet.id || tweet.rest_id;
                    const replyResult2 = await twitterCmd("twitter:http-reply", { username, text: replyText, replyToTweetId: tweetId });
                    const ourReplyPostId = replyResult2?.tweetId ? String(replyResult2.tweetId) : null;
                    replied++;
                    addLog("R", `@${username} replied to @${fHandle}`, tweetUrl(fHandle, tweetId));
                    if (replyMeta) {
                      chrome.runtime.sendMessage({
                        action: "ai-reply-log",
                        body: {
                          platform: "twitter", replyingAccount: username,
                          voiceSource: replyMeta.voiceSource, voiceHash: replyMeta.voiceHash,
                          lengthBucket: replyMeta.lengthBucket,
                          targetHandle: fHandle, targetPostId: String(tweetId),
                          replyText: replyText.slice(0, 280),
                          ourReplyPostId,
                        },
                      }).catch(() => {});
                    }
                  }
                }
              } catch {}
              await humanDelay(3000);
            }

            actions++;
            await maybeFatiguePause(actions);
            await humanDelay(3000 * sessionWarmup(actions));
          }

          addLog("V", `@${username}: Siphon done — liked ${liked}, followed ${followed}${replied ? `, replied ${replied}` : ""}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "smart-unfollow": {
    title: "Smart Unfollow",
    html: `
      <label class="nx-label">Keep accounts matching keyword</label>
      <input class="nx-input" id="cfg-keyword" type="text" placeholder="crypto, web3, defi">
      <label class="nx-label">Max to scan</label>
      <input class="nx-input nx-small" id="cfg-max" type="number" value="50" min="10" max="200">
      <label class="nx-checkbox" style="margin:8px 0;"><input type="checkbox" id="cfg-preview" checked> Preview before unfollowing</label>
      <p class="nx-hint">Scans who you follow and unfollows people whose bio doesn't match your keywords. Uses synonym matching.</p>`,
    async start(shadow) {
      const rawKeyword = shadow.getElementById("cfg-keyword").value.trim();
      const maxScan = parseInt(shadow.getElementById("cfg-max").value) || 50;
      const preview = shadow.getElementById("cfg-preview").checked;
      if (!rawKeyword) { setStatus("Enter a keyword"); return; }

      const SYNONYM_MAP = {
        "crypto": ["crypto", "cryptocurrency", "blockchain", "web3", "defi", "nft", "token", "solidity", "ethereum", "bitcoin", "digital asset", "dao"],
        "ai": ["ai", "artificial intelligence", "machine learning", "ml", "deep learning", "llm", "gpt", "neural"],
        "fintech": ["fintech", "financial technology", "payments", "banking", "neobank"],
        "marketing": ["marketing", "growth", "seo", "content", "branding", "social media"],
      };

      const userKeywords = rawKeyword.toLowerCase().split(",").map(s => s.trim()).filter(Boolean);
      const matchTerms = new Set();
      for (const kw of userKeywords) {
        matchTerms.add(kw);
        for (const [root, synonyms] of Object.entries(SYNONYM_MAP)) {
          if (kw === root || synonyms.includes(kw)) { for (const s of synonyms) matchTerms.add(s); matchTerms.add(root); }
        }
      }

      for (const username of selectedAccounts) {
        addLog("S", `@${username}: Scanning following — keeping "${rawKeyword}" (${matchTerms.size} terms)`);

        try {
          setStatus(`@${username}: Loading following list...`);
          const followingResult = await twitterCmd("twitter:http-following", { username, count: maxScan });
          const following = followingResult.users || followingResult.following || [];
          if (following.length === 0) { addLog("-", `@${username}: no following data`); continue; }

          const nonMatching = [];
          const matching = [];
          for (const user of following) {
            const bio = (user.description || user.legacy?.description || "").toLowerCase();
            const name = (user.name || user.legacy?.name || "").toLowerCase();
            const combined = bio + " " + name;
            const matches = [...matchTerms].some(term => combined.includes(term));
            if (matches) matching.push(user);
            else nonMatching.push(user);
          }

          addLog("V", `@${username}: ${matching.length} match, ${nonMatching.length} don't`);

          if (nonMatching.length === 0) { setStatus("All following match — nothing to unfollow"); continue; }

          if (preview) {
            setStatus(`@${username}: ${nonMatching.length} non-matching. Review log, then run with preview OFF.`);
            for (const u of nonMatching.slice(0, 15)) {
              const handle = u.screen_name || u.legacy?.screen_name || "?";
              const bio = (u.description || u.legacy?.description || "no bio").slice(0, 50);
              addLog("-", `@${handle}: "${bio}"`, `https://x.com/${handle}`);
            }
            if (nonMatching.length > 15) addLog("-", `...and ${nonMatching.length - 15} more`);
            continue;
          }

          let unfollowed = 0;
          for (const user of nonMatching) {
            const handle = user.screen_name || user.legacy?.screen_name;
            if (!handle) continue;
            setStatus(`@${username}: (${unfollowed + 1}/${nonMatching.length}) Unfollowing @${handle}...`);
            try {
              await twitterCmd("twitter:http-unfollow-by-username", { username, targetUsername: handle });
              unfollowed++;
              addLog("X", `@${username} unfollowed @${handle}`, `https://x.com/${handle}`);
            } catch {}
            await humanDelay(2000 * sessionWarmup(unfollowed));
          }
          addLog("V", `@${username}: Unfollowed ${unfollowed}, kept ${matching.length}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "engagement-campaign": {
    title: "Engagement Campaign",
    html: `
      <label class="nx-label">Target account</label>
      <input class="nx-input" id="cfg-target" type="text" placeholder="@influencer">
      <label class="nx-label">Depth</label>
      <select class="nx-input" id="cfg-depth">
        <option value="light">Light (like 3 tweets)</option>
        <option value="medium" selected>Medium (like 3 + reply 1 + follow)</option>
        <option value="heavy">Heavy (like 5 + reply 2 + retweet 1 + follow)</option>
      </select>
      <p class="nx-hint">Coordinated engagement on a specific account's content. Makes you visible to them and their audience.</p>`,
    async start(shadow) {
      const target = shadow.getElementById("cfg-target").value.trim().replace(/^@/, "");
      const depth = shadow.getElementById("cfg-depth").value;
      if (!target) { setStatus("Enter a target account"); return; }

      for (const username of selectedAccounts) {
        addLog("S", `@${username}: Engaging @${target} (${depth})`);
        try {
          // Get their tweets
          setStatus(`@${username}: Fetching @${target}'s tweets...`);
          const lookup = await twitterCmd("twitter:http-user-lookup", { username, targetUsername: target });
          const userId = lookup.id || lookup.rest_id || lookup.data?.user?.result?.rest_id;
          if (!userId) { addLog("X", `@${username}: Could not resolve @${target}`); continue; }

          const tweetsResult = await twitterCmd("twitter:http-user-tweets", { username, targetUserId: userId, count: 10 });
          const tweets = tweetsResult.tweets || [];
          if (tweets.length === 0) { addLog("-", `@${username}: No tweets from @${target}`); continue; }

          const likeCount = depth === "heavy" ? 5 : 3;
          const replyCount = depth === "light" ? 0 : depth === "medium" ? 1 : 2;
          const doRetweet = depth === "heavy";

          // Like tweets
          let liked = 0;
          for (const tweet of tweets.slice(0, likeCount)) {
            const tweetId = tweet.id || tweet.rest_id;
            if (!tweetId) continue;
            try {
              await twitterCmd("twitter:http-like", { username, tweetId });
              liked++;
              addLog("H", `@${username} liked @${target}`, tweetUrl(target, tweetId));
            } catch {}
            await humanDelay(2000 * sessionWarmup(liked));
          }

          // Reply to tweets — voice-aware AI gen.
          let replied = 0;
          for (const tweet of tweets.slice(0, replyCount)) {
            const tweetId = tweet.id || tweet.rest_id;
            if (!tweetId) continue;
            const tweetText = tweet.text || tweet.full_text || "";
            let replyText = "";
            let replyMeta = null;
            if (tweetText) {
              try {
                const r = await chrome.runtime.sendMessage({
                  action: "ai-reply",
                  body: { platform: "twitter", postText: tweetText, author: target, replyingAccount: username },
                });
                if (r?.success && r?.text) { replyText = r.text.trim(); replyMeta = r.meta || null; }
              } catch {}
            }
            if (!replyText) continue;
            let ourReplyPostId = null;
            try {
              const replyResult = await twitterCmd("twitter:http-reply", { username, text: replyText, replyToTweetId: tweetId });
              ourReplyPostId = replyResult?.tweetId ? String(replyResult.tweetId) : null;
              replied++;
              addLog("R", `@${username} replied to @${target}: "${replyText.slice(0, 30)}..."`, tweetUrl(target, tweetId));
              if (replyMeta) {
                chrome.runtime.sendMessage({
                  action: "ai-reply-log",
                  body: {
                    platform: "twitter", replyingAccount: username,
                    voiceSource: replyMeta.voiceSource, voiceHash: replyMeta.voiceHash,
                    lengthBucket: replyMeta.lengthBucket,
                    targetHandle: target, targetPostId: String(tweetId),
                    replyText: replyText.slice(0, 280),
                    ourReplyPostId,
                  },
                }).catch(() => {});
              }
            } catch {}
            await humanDelay(4000);
          }

          // Retweet
          if (doRetweet && tweets[0]) {
            const tweetId = tweets[0].id || tweets[0].rest_id;
            try {
              await twitterCmd("twitter:http-retweet", { username, tweetId });
              addLog("T", `@${username} RT'd @${target}`, tweetUrl(target, tweetId));
            } catch {}
            await humanDelay(2000);
          }

          // Follow
          if (depth !== "light") {
            try {
              await twitterCmd("twitter:http-follow-by-username", { username, targetUsername: target });
              addLog("F", `@${username} followed @${target}`, `https://x.com/${target}`);
              crmSync({ platform: "twitter", identifier: target, source: "engagement_campaign", action: "followed" });
            } catch {}
          }

          addLog("V", `@${username}: Campaign on @${target} — liked ${liked}, replied ${replied}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "retweet-campaign": {
    title: "Retweet Campaign",
    html: `
      <label class="nx-label">Target account to retweet</label>
      <input class="nx-input" id="cfg-target" type="text" placeholder="@account">
      <label class="nx-label">Tweets to retweet</label>
      <input class="nx-input nx-small" id="cfg-count" type="number" value="5" min="1" max="20">
      <p class="nx-hint">Retweets recent posts from a specific account to amplify their content.</p>`,
    async start(shadow) {
      const target = shadow.getElementById("cfg-target").value.trim().replace(/^@/, "");
      const count = parseInt(shadow.getElementById("cfg-count").value) || 5;
      if (!target) { setStatus("Enter a target account"); return; }

      for (const username of selectedAccounts) {
        setStatus(`@${username}: Retweeting @${target}...`);
        try {
          await twitterCmd("twitter:http-retweet-campaign", { username, targetHandle: target, count });
          addLog("V", `@${username}: Retweeted ${count} from @${target}`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "audience-overlap": {
    title: "Audience Overlap",
    html: `
      <label class="nx-label">Competitor accounts (2-3)</label>
      <input class="nx-input" id="cfg-targets" type="text" placeholder="@competitor1, @competitor2, @competitor3">
      <label class="nx-label">Max followers to scan per account</label>
      <input class="nx-input nx-small" id="cfg-max" type="number" value="50" min="20" max="200">
      <p class="nx-hint">Finds followers who follow 2+ of these accounts — the highest-value targets in your space.</p>`,
    async start(shadow) {
      const targets = shadow.getElementById("cfg-targets").value.split(",").map(s => s.trim().replace(/^@/, "")).filter(Boolean);
      const maxPer = parseInt(shadow.getElementById("cfg-max").value) || 50;
      if (targets.length < 2) { setStatus("Enter at least 2 accounts"); return; }

      for (const username of selectedAccounts) {
        addLog("S", `@${username}: Finding overlap between @${targets.join(", @")}`);

        try {
          // Fetch followers for each target
          const followerSets = {};
          for (const target of targets) {
            setStatus(`@${username}: Fetching @${target}'s followers...`);
            try {
              const result = await twitterCmd("twitter:http-followers", { username, targetUsername: target, count: maxPer });
              const users = result.users || result.followers || [];
              followerSets[target] = new Map();
              for (const u of users) {
                const handle = u.screen_name || u.legacy?.screen_name || u.username;
                if (handle) followerSets[target].set(handle.toLowerCase(), u);
              }
              addLog("V", `@${target}: ${followerSets[target].size} followers loaded`);
            } catch (err) {
              addLog("X", `@${target}: ${err.message}`);
              followerSets[target] = new Map();
            }
            await humanDelay(2000);
          }

          // Find overlap — accounts following 2+ targets
          const allHandles = new Map(); // handle → count of targets they follow
          for (const [target, followers] of Object.entries(followerSets)) {
            for (const [handle, user] of followers) {
              if (!allHandles.has(handle)) allHandles.set(handle, { count: 0, user, targets: [] });
              const entry = allHandles.get(handle);
              entry.count++;
              entry.targets.push(target);
            }
          }

          const overlap = [...allHandles.entries()]
            .filter(([, data]) => data.count >= 2)
            .sort((a, b) => b[1].count - a[1].count);

          if (overlap.length === 0) {
            setStatus("No overlap found — try scanning more followers");
            addLog("-", "No shared followers between these accounts");
            continue;
          }

          setStatus(`Found ${overlap.length} accounts following ${targets.length >= 3 ? "2+" : "both"} competitors`);
          addLog("V", `${overlap.length} overlap followers found`);

          // Show top results
          for (const [handle, data] of overlap.slice(0, 20)) {
            const bio = (data.user.description || data.user.legacy?.description || "").slice(0, 50);
            addLog("-", `@${handle} (follows ${data.count}/${targets.length}): ${bio}`, `https://x.com/${handle}`);
          }
          if (overlap.length > 20) addLog("-", `...and ${overlap.length - 20} more`);
        } catch (err) { addLog("X", `@${username}: ${err.message}`); }
      }
      setStatus("Done!");
    },
  },
  "autopilot": {
    title: "Autopilot",
    html: `
      <label class="nx-checkbox" style="margin:8px 0;"><input type="checkbox" id="cfg-enabled"> Enable Twitter Autopilot</label>
      <label class="nx-label">Active hours</label>
      <div class="nx-input-row">
        <div><label class="nx-label">From</label><input class="nx-input nx-small" id="cfg-start" type="number" value="9" min="0" max="23"></div>
        <div><label class="nx-label">To</label><input class="nx-input nx-small" id="cfg-end" type="number" value="20" min="0" max="23"></div>
      </div>
      <label class="nx-checkbox" style="margin:6px 0;"><input type="checkbox" id="cfg-likes" checked> Daily feed likes</label>
      <div class="nx-input-row">
        <div><label class="nx-label">Likes/day</label><input class="nx-input nx-small" id="cfg-like-count" type="number" value="15" min="1" max="50"></div>
      </div>
      <p class="nx-hint">Runs in the background every hour. Likes posts from feed, maintains account warmth.</p>`,
    async init(shadow) {
      try {
        if (!isAlive()) return;
        const data = await chrome.storage.local.get(["twAutopilot"]);
        const config = data.twAutopilot || {};
        shadow.getElementById("cfg-enabled").checked = !!config.enabled;
        shadow.getElementById("cfg-start").value = config.startHour ?? 9;
        shadow.getElementById("cfg-end").value = config.endHour ?? 20;
        shadow.getElementById("cfg-likes").checked = config.dailyLikes !== false;
        shadow.getElementById("cfg-like-count").value = config.likesPerDay || 15;
      } catch {}
    },
    async start(shadow) {
      const config = {
        enabled: shadow.getElementById("cfg-enabled").checked,
        startHour: parseInt(shadow.getElementById("cfg-start").value) || 9,
        endHour: parseInt(shadow.getElementById("cfg-end").value) || 20,
        dailyLikes: shadow.getElementById("cfg-likes").checked,
        likesPerDay: parseInt(shadow.getElementById("cfg-like-count").value) || 15,
      };
      if (!isAlive()) return;
      await chrome.storage.local.set({ twAutopilot: config });
      if (config.enabled) {
        setStatus("Autopilot ON — runs every hour");
        addLog("V", `Autopilot enabled: ${config.startHour}:00-${config.endHour}:00, ${config.likesPerDay} likes/day`);
      } else {
        setStatus("Autopilot OFF");
        addLog("-", "Autopilot disabled");
      }
    },
  },
};

const MENU_ITEMS = [
  // ── Intelligence ──
  { id: "competitor-siphon", icon: "S", label: "Competitor Siphon", hint: "Engage followers of a target account" },
  { id: "audience-overlap", icon: "O", label: "Audience Overlap", hint: "Find users following multiple targets" },
  { id: "engagement-campaign", icon: "E", label: "Engagement Campaign", hint: "Systematically engage one account's content" },
  // ── Engage ──
  { id: "like-feed", icon: "H", label: "Engage Feed", hint: "Like posts from your home timeline" },
  { id: "reply-feed", icon: "R", label: "Reply to Posts", hint: "Auto-reply to feed posts with AI" },
  { id: "retweet", icon: "T", label: "Retweet Posts", hint: "Retweet posts from your feed" },
  { id: "engage-hashtag", icon: "#", label: "Engage Hashtag", hint: "Like and reply to posts under a hashtag" },
  { id: "engage-feed", icon: "Z", label: "Auto-Engage (AI)", hint: "AI-driven mix of likes, replies, and retweets" },
  // ── Grow ──
  { id: "follow-users", icon: "F", label: "Follow Users", hint: "Follow a list of usernames" },
  { id: "follower-sniper", icon: "G", label: "Follower Sniper", hint: "Follow people who follow a target account" },
  { id: "retweet-campaign", icon: "C", label: "Retweet Campaign", hint: "Retweet specific accounts on schedule" },
  { id: "post-tweet", icon: "P", label: "Post Tweet", hint: "Compose and post a tweet" },
  { id: "quote-tweet", icon: "Q", label: "Quote Tweet", hint: "Quote a tweet with your commentary" },
  // ── Manage ──
  { id: "smart-unfollow", icon: "X", label: "Clean Up Following", hint: "Unfollow accounts that don't follow back" },
  { id: "autopilot", icon: "A", label: "Autopilot", hint: "Continuous automated engagement" },
];

// ── Panel injection ───────────────────────────────────────────────────

function injectPanel() {
  if (document.getElementById(PANEL_ID)) return;
  const host = document.createElement("div");
  host.id = PANEL_ID;
  host.style.cssText = "position:fixed;top:80px;right:16px;z-index:2147483647;";
  const shadow = host.attachShadow({ mode: "open" });
  shadow.innerHTML = buildPanelHTML();
  document.body.appendChild(host);
  setupPanelBehavior(host, shadow);
  startKeepalive();
}

function buildPanelHTML() {
  const menuHTML = MENU_ITEMS.map(m =>
    `<div class="nx-menu-item" data-action="${m.id}"><span class="nx-menu-icon">${m.icon}</span><div style="flex:1;overflow:hidden;"><span class="nx-menu-label">${m.label}</span>${m.hint ? `<span class="nx-menu-hint">${m.hint}</span>` : ""}</div><span class="nx-menu-arrow">&rsaquo;</span></div>`
  ).join("");

  return `
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    :host { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 13px; }
    .nx-panel { width: 300px; background: #0f0f11; border: 1px solid #2a2a38; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); overflow: hidden; color: #e0e0e8; }
    .nx-panel.minimized .nx-body { display: none; }
    .nx-panel.minimized { width: auto; border-radius: 10px; }
    .nx-header { display: flex; align-items: center; padding: 10px 14px; background: #0a1628; border-bottom: 1px solid #1a2a44; cursor: move; -webkit-user-select: none; user-select: none; }
    .nx-logo { font-size: 16px; margin-right: 8px; color: #4ADE80; font-weight: 700; }
    .nx-title { font-size: 13px; font-weight: 600; color: #fff; flex: 1; }
    .nx-header-btn { background: none; border: none; color: #666; cursor: pointer; font-size: 16px; padding: 2px 4px; line-height: 1; }
    .nx-header-btn:hover { color: #ccc; }
    .nx-body { padding: 0; max-height: 520px; overflow-y: auto; }
    .nx-status { font-size: 11px; color: #4ADE80; padding: 8px 12px 4px; min-height: 20px; }
    .nx-account-bar { padding: 8px 12px; border-bottom: 1px solid #1a1a22; }
    .nx-account-bar-title { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
    .nx-account-list { display: flex; flex-wrap: wrap; gap: 4px; }
    .nx-account-chip {
      font-size: 11px; padding: 3px 8px; background: #1a1a22; border: 1px solid #2a2a38;
      border-radius: 12px; color: #888; cursor: pointer; transition: all 0.12s;
    }
    .nx-account-chip:hover { border-color: #4ADE80; color: #ccc; }
    .nx-account-chip.selected { background: #0a1628; border-color: #4ADE80; color: #4ADE80; }
    .nx-account-chip.selected::before { content: "\\2713 "; }
    .nx-section-title { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px; margin: 0; padding: 8px 12px 4px; border-top: 1px solid #1a1a22; }
    .nx-menu-item { display: flex; align-items: center; padding: 9px 12px; cursor: pointer; border-bottom: 1px solid #0d0d10; transition: background 0.12s; }
    .nx-menu-item:hover { background: #1a1a22; }
    .nx-menu-item:active { background: #222230; }
    .nx-menu-item.disabled { opacity: 0.3; pointer-events: none; }
    .nx-menu-icon { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; background: #0a1628; border-radius: 4px; font-size: 11px; font-weight: 700; color: #4ADE80; margin-right: 10px; flex-shrink: 0; }
    .nx-menu-label { font-size: 12px; color: #e0e0e8; display: block; }
    .nx-menu-hint { font-size: 10px; color: #555; display: block; line-height: 1.3; }
    .nx-menu-arrow { color: #444; font-size: 16px; }
    .nx-config-page { padding: 12px; }
    .nx-back-row { display: flex; align-items: center; padding: 0 0 8px; margin-bottom: 8px; border-bottom: 1px solid #1a1a22; }
    .nx-back-btn { background: none; border: none; color: #4ADE80; cursor: pointer; font-size: 13px; padding: 2px 8px 2px 0; font-weight: 500; }
    .nx-back-btn:hover { color: #4db8ff; }
    .nx-config-title { font-size: 13px; font-weight: 600; color: #fff; flex: 1; }
    .nx-label { display: block; font-size: 11px; color: #888; margin: 8px 0 4px; }
    .nx-label:first-child { margin-top: 0; }
    .nx-hint { font-size: 11px; color: #555; margin-top: 6px; line-height: 1.4; }
    .nx-input { width: 100%; background: #1a1a22; border: 1px solid #2a2a38; border-radius: 6px; color: #e0e0e8; padding: 7px 10px; font-size: 12px; outline: none; }
    .nx-input:focus { border-color: #4ADE80; }
    .nx-input::placeholder { color: #555; }
    .nx-input-row { display: flex; gap: 6px; margin-bottom: 6px; }
    .nx-small { width: 70px; flex: 0 0 70px; text-align: center; }
    .nx-textarea { resize: vertical; min-height: 36px; font-family: inherit; line-height: 1.4; padding: 6px 10px; }
    .nx-start-btn { display: block; width: 100%; padding: 10px; margin-top: 12px; background: #4ADE80; border: none; border-radius: 20px; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer; }
    .nx-start-btn:hover { background: #1a8cd8; }
    .nx-start-btn:disabled { opacity: 0.4; cursor: not-allowed; }
    .nx-log-section { padding: 0 12px 8px; }
    .nx-log { max-height: 240px; overflow-y: auto; }
    .nx-log-item { display: flex; align-items: center; padding: 3px 0; font-size: 11px; border-bottom: 1px solid #0d0d10; }
    .nx-log-icon { margin-right: 6px; font-size: 12px; flex-shrink: 0; }
    .nx-log-text { flex: 1; color: #ccc; word-break: break-word; white-space: normal; line-height: 1.4; }
    .nx-log-time { color: #555; font-size: 10px; margin-left: 6px; flex-shrink: 0; }
    .nx-empty { color: #444; font-size: 11px; text-align: center; padding: 12px 0; }
    .nx-log-item.clickable { cursor: pointer; }
    .nx-log-item.clickable:hover { background: #1a1a22; }
    .nx-log-link { color: #4ADE80; cursor: pointer; text-decoration: none; }
    .nx-log-link:hover { text-decoration: underline; }
    .nx-log-toggle { text-align: center; padding: 6px 0; font-size: 10px; color: #4ADE80; cursor: pointer; border-top: 1px solid #1a1a22; }
    .nx-log-toggle:hover { color: #4db8ff; }
    .nx-badge { display: inline-block; font-size: 10px; padding: 1px 6px; border-radius: 8px; margin-left: 6px; font-weight: 500; }
    .nx-badge.on { background: #1a3a1a; color: #4ade80; }
    .nx-badge.off { background: #3a1a1a; color: #f87171; }
    .nx-no-accounts { text-align: center; padding: 16px 12px; color: #555; font-size: 12px; }
  </style>
  <div class="nx-panel" id="nx-root">
    <div class="nx-header" id="nx-drag">
      <span class="nx-logo">N</span>
      <span class="nx-title">Nexus</span>
      <span class="nx-badge on" id="nx-conn-badge">connected</span>
      <button class="nx-header-btn" id="nx-minimize" title="Minimize">_</button>
      <button class="nx-header-btn" id="nx-close" title="Hide">x</button>
    </div>
    <div class="nx-body">
      <div id="nx-status" class="nx-status">Ready</div>

      <!-- Account selector (always visible on menu screen) -->
      <div class="nx-account-bar" id="nx-account-bar">
        <div class="nx-account-bar-title">Accounts</div>
        <div class="nx-account-list" id="nx-account-list">
          <span class="nx-empty">Loading accounts...</span>
        </div>
      </div>

      <!-- Menu screen -->
      <div id="nx-menu">
        ${menuHTML}
      </div>

      <!-- Config screen -->
      <div id="nx-config" style="display:none">
        <div class="nx-config-page">
          <div class="nx-back-row">
            <button class="nx-back-btn" id="nx-back">&lsaquo; Back</button>
            <span class="nx-config-title" id="nx-config-title"></span>
          </div>
          <div id="nx-config-form"></div>
          <button class="nx-start-btn" id="nx-start">Start</button>
        </div>
      </div>

      <div class="nx-log-section">
        <div class="nx-section-title" style="border-top:none;padding-left:0;">Activity Log</div>
        <div class="nx-log" id="nx-log"><div class="nx-empty">No activity yet</div></div>
      </div>
    </div>
  </div>`;
}

function setupPanelBehavior(host, shadow) {
  const root = shadow.getElementById("nx-root");
  let currentAction = null;

  // Minimize
  shadow.getElementById("nx-minimize").addEventListener("click", () => {
    panelMinimized = !panelMinimized;
    root.classList.toggle("minimized", panelMinimized);
    savePanelState();
  });

  // Close
  shadow.getElementById("nx-close").addEventListener("click", () => { host.style.display = "none"; });

  // Drag
  let isDragging = false, dragX = 0, dragY = 0;
  shadow.getElementById("nx-drag").addEventListener("mousedown", (e) => {
    if (e.target.closest(".nx-header-btn")) return;
    isDragging = true;
    dragX = e.clientX - host.getBoundingClientRect().left;
    dragY = e.clientY - host.getBoundingClientRect().top;
    e.preventDefault();
  });
  document.addEventListener("mousemove", (e) => { if (!isDragging) return; host.style.left = (e.clientX - dragX) + "px"; host.style.top = (e.clientY - dragY) + "px"; host.style.right = "auto"; });
  document.addEventListener("mouseup", () => { isDragging = false; });

  // ── Load accounts + routine status from bot API ──
  loadAccounts(shadow);
  loadRoutineStatus();

  // ── Menu navigation ──
  function showMenu() {
    shadow.getElementById("nx-menu").style.display = "block";
    shadow.getElementById("nx-config").style.display = "none";
    shadow.getElementById("nx-account-bar").style.display = "block";
    currentAction = null;
    updateMenuState(shadow);
  }

  function showConfig(actionId) {
    const config = ACTION_CONFIGS[actionId];
    if (!config) return;
    currentAction = actionId;
    shadow.getElementById("nx-menu").style.display = "none";
    shadow.getElementById("nx-config").style.display = "block";
    shadow.getElementById("nx-account-bar").style.display = "none";
    shadow.getElementById("nx-config-title").textContent = config.title;
    shadow.getElementById("nx-config-form").innerHTML = config.html;
    if (config.init) config.init(shadow);
  }

  shadow.querySelectorAll(".nx-menu-item").forEach(item => {
    item.addEventListener("click", () => {
      if (selectedAccounts.length === 0) { setStatus("Select at least one account first"); return; }
      showConfig(item.dataset.action);
    });
  });

  shadow.getElementById("nx-back").addEventListener("click", showMenu);

  shadow.getElementById("nx-start").addEventListener("click", async () => {
    if (!currentAction) return;
    if (selectedAccounts.length === 0) { setStatus("No account selected"); return; }
    const config = ACTION_CONFIGS[currentAction];
    if (!config) return;
    const btn = shadow.getElementById("nx-start");
    btn.disabled = true; btn.textContent = "Running...";
    try { await config.start(shadow); } catch (err) { setStatus("Error: " + err.message); addLog("X", err.message); }
    btn.disabled = false; btn.textContent = "Start";
  });

  // Restore state
  loadPanelState().then(() => {
    updateLogUI();
    const statusEl = shadow.getElementById("nx-status");
    if (statusEl && panelStatus) statusEl.textContent = panelStatus;
    if (panelMinimized) root.classList.add("minimized");
    updateAccountChips(shadow);
  });

  // Connection badge
  if (isAlive()) {
    try {
      chrome.storage.local.get(["token"], ({ token }) => {
        if (chrome.runtime.lastError) return;
        const badge = shadow.getElementById("nx-conn-badge");
        if (badge) {
          badge.textContent = token ? "connected" : "no token";
          badge.className = token ? "nx-badge on" : "nx-badge off";
        }
      });
    } catch {}
  }
}

// ── Account management ────────────────────────────────────────────────

async function loadRoutineStatus() {
  try {
    const result = await twitterCmd("twitter:live-ops-snapshot", {});
    if (result.snapshot) {
      const running = result.snapshot.routineRunning || false;
      setStatus(running ? "X Routine: Running" : "X Routine: Stopped");
    }
  } catch {
    // Non-critical — just show "Ready"
  }
}

async function loadAccounts(shadow) {
  try {
    const result = await twitterCmd("twitter:accounts", {});
    availableAccounts = (result.accounts || []).map(a => typeof a === "string" ? a : a.username || a.handle || "").filter(Boolean);
    // Auto-select all if none were selected
    if (selectedAccounts.length === 0 && availableAccounts.length > 0) {
      selectedAccounts = [availableAccounts[0]]; // select first by default
    }
    updateAccountChips(shadow);
    updateMenuState(shadow);
  } catch (err) {
    const list = shadow.getElementById("nx-account-list");
    const msg = (err.message.includes("Failed to fetch") || err.message.includes("NetworkError"))
      ? "Bot API offline — start the server"
      : err.message.includes("context invalidated")
        ? "Extension reloaded — refresh this page"
        : `Failed to load: ${err.message}`;
    if (list) list.innerHTML = `<span class="nx-empty">${msg}</span>`;
  }
}

function updateAccountChips(shadow) {
  const list = shadow.getElementById("nx-account-list");
  if (!list) return;
  if (availableAccounts.length === 0) {
    list.innerHTML = '<span class="nx-empty">No accounts — add one via Telegram /x</span>';
    return;
  }
  list.innerHTML = availableAccounts.map(username =>
    `<span class="nx-account-chip ${selectedAccounts.includes(username) ? "selected" : ""}" data-username="${username}">@${username}</span>`
  ).join("");

  // Click to toggle selection
  list.querySelectorAll(".nx-account-chip").forEach(chip => {
    chip.addEventListener("click", () => {
      const username = chip.dataset.username;
      if (selectedAccounts.includes(username)) {
        selectedAccounts = selectedAccounts.filter(u => u !== username);
      } else {
        selectedAccounts.push(username);
      }
      updateAccountChips(shadow);
      updateMenuState(shadow);
      savePanelState();
    });
  });
}

function updateMenuState(shadow) {
  // Disable menu items if no account selected
  shadow.querySelectorAll(".nx-menu-item").forEach(item => {
    item.classList.toggle("disabled", selectedAccounts.length === 0);
  });
}

// ── Message listener for panel notifications + driven commands ────────
if (isAlive()) {
  try {
    chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
      if (!isAlive()) return;
      try {
        if (msg?.nexusPanel) {
          if (msg.type === "status") setStatus(msg.data);
          if (msg.type === "log") addLog(msg.data.icon, msg.data.text);
          return;
        }
        // Background-driven "Run from here": the hidden worker tab receives
        // { nexusCommand, action:"tw:run-action", params } and executes the
        // matching in-panel action headlessly, returning the result.
        if (msg?.nexusCommand && msg.action === "tw:run-action") {
          runDrivenTwitterAction(msg.params || {})
            .then((r) => sendResponse(r))
            .catch((e) => sendResponse({ error: e?.message || String(e) }));
          return true; // keep the channel open for the async response
        }
      } catch (e) {
        try { sendResponse({ error: e?.message || String(e) }); } catch {}
      }
    });
  } catch {}
}

// ── CRM Context: detect Twitter profile pages ─────────────────────────

function detectProfileAndShowCRM() {
  const url = window.location.href;
  // Twitter profile: x.com/username (not /home, /search, /notifications, etc.)
  const match = url.match(/(?:twitter\.com|x\.com)\/([A-Za-z0-9_]{1,15})(?:\?|$|\/(?:$|followers|following))/);
  if (!match) return;
  const handle = match[1];
  // Skip non-profile pages
  if (["home", "search", "explore", "notifications", "messages", "settings", "i", "compose"].includes(handle)) return;

  if (!isAlive()) return;
  try {
  chrome.runtime.sendMessage({ action: "crm-intelligence", platform: "twitter", identifier: handle }, (data) => {
    if (chrome.runtime.lastError) return;
    if (!data || data.error) return;
    const host = document.getElementById(PANEL_ID);
    if (!host) return;
    const shadow = host.shadowRoot;
    let existing = shadow.getElementById("nx-crm-context");
    if (existing) existing.remove();

    // Intelligence API returns flat fields; basic API returns nested { contact, interactions, outreach }
    const contact = data.contact || data;
    const interactions = data.interactions || [];
    const outreach = data.outreach || data.activeCampaign;
    const relationship = data.relationship;
    if (!contact || (!contact.name && !contact.leadScore && !contact.conversationStage)) return;

    const stage = contact.stage || contact.conversationStage || "unknown";
    const leadScore = contact.leadScore || 0;

    let html = `<div class="nx-section-title" style="border-top:1px solid #1a1a22;padding-left:12px;">CRM</div><div style="padding:4px 12px 8px;font-size:11px;">`;
    html += `<div style="margin-bottom:3px;"><b style="color:#fff;">@${handle}</b>`;
    if (contact.company) html += ` <span style="color:#888;">@ ${contact.company}</span>`;
    html += `</div>`;
    if (contact.role) html += `<div style="margin-bottom:3px;color:#888;">${contact.role}</div>`;
    if (leadScore > 0) {
      const sColor = leadScore >= 60 ? "#ef4444" : leadScore >= 30 ? "#fbbf24" : "#60a5fa";
      html += `<div style="margin-bottom:3px;">Score: <span style="color:${sColor};font-weight:700;">${leadScore}</span>`;
      if (data.engagementVelocity === "up") html += ` <span style="color:#4ade80;">&#8593;</span>`;
      else if (data.engagementVelocity === "down") html += ` <span style="color:#ef4444;">&#8595;</span>`;
      html += `</div>`;
    }
    html += `<div style="color:#4ADE80;font-weight:600;margin-bottom:3px;">Stage: ${stage}</div>`;
    if (contact.reachableOn?.length > 0) {
      const icons = { twitter: "X", linkedin: "LI", telegram: "TG", email: "Em" };
      html += `<div style="margin-bottom:3px;">${contact.reachableOn.map(p => `<span style="background:#1a1a22;padding:1px 4px;border-radius:3px;font-size:9px;color:#888;">${icons[p] || p}</span>`).join(" ")}</div>`;
    }

    // Deal info (from intelligence API)
    if (data.deal) {
      const d = data.deal;
      const dColors = { prospect: "#fbbf24", qualified: "#60a5fa", proposal: "#4ADE80", negotiation: "#f97316", won: "#4ade80" };
      html += `<div style="margin-bottom:3px;padding:2px 5px;background:#1a1a22;border-radius:3px;">`;
      html += `<span style="color:#888;font-size:10px;">Deal:</span> <b style="color:${dColors[d.stage] || "#888"};">${d.stage}</b>`;
      if (d.value_usd) html += ` <span style="color:#4ade80;">$${Number(d.value_usd).toLocaleString()}</span>`;
      html += `</div>`;
    }

    // Warm intro
    if (data.warmIntro) {
      html += `<div style="margin-bottom:3px;font-size:10px;color:#4ade80;">Intro via ${data.warmIntro.introducerName || "?"} (${Math.round((data.warmIntro.strength || 0) * 100)}%)</div>`;
    }

    // Reply intent
    if (data.replyIntent) {
      const ri = data.replyIntent;
      const emojis = { hot_interest: "\u{1f525}", warm_interest: "\u{1f31f}", question: "\u2753", soft_no: "\u{1f44b}", hard_no: "\u{1f6d1}" };
      const labels = { hot_interest: "Hot", warm_interest: "Warm", question: "Question", soft_no: "Soft No", hard_no: "Hard No" };
      html += `<div style="margin-bottom:3px;font-size:10px;">${emojis[ri.intent] || ""} <b>${labels[ri.intent] || ri.intent}</b> (${Math.round(ri.confidence * 100)}%)</div>`;
    }

    // Follow-up
    if (data.followUp) {
      const fu = data.followUp;
      let fuText = `Step ${(fu.current_step || 0) + 1}`;
      if (fu.next_follow_up_at) {
        const h = Math.max(0, Math.round((new Date(fu.next_follow_up_at) - Date.now()) / 3600000));
        fuText += h < 24 ? ` \u2022 ${h}h` : ` \u2022 ${Math.round(h / 24)}d`;
      }
      html += `<div style="margin-bottom:3px;font-size:10px;color:#60a5fa;">\u{1f504} ${fuText}</div>`;
    }

    if (relationship) html += `<div style="color:#888;">${relationship.interactions} interactions | ${Math.round(relationship.strength * 100)}% strength</div>`;
    if (outreach) {
      const cs = (s) => !s || s === "not_started" ? "-" : s;
      html += `<div style="color:#888;font-size:10px;">LI:${cs(outreach.linkedin_status || outreach.linkedin)} X:${cs(outreach.x_status || outreach.twitter)} TG:${cs(outreach.telegram_status || outreach.telegram)} Em:${cs(outreach.email_status || outreach.email)}</div>`;
    }
    if (contact.telegram) {
      html += `<div style="margin-top:4px;"><button id="nx-tg-dm-btn" style="font-size:10px;padding:3px 8px;background:#0088cc;color:#fff;border:none;border-radius:4px;cursor:pointer;">Send TG DM to @${contact.telegram}</button></div>`;
    }
    if (interactions.length > 0) {
      html += `<div style="margin-top:3px;color:#555;font-size:10px;">Last: ${interactions[0].type} -- ${new Date(interactions[0].date).toLocaleDateString()}</div>`;
    }
    html += `</div>`;

    const div = document.createElement("div");
    div.id = "nx-crm-context";
    div.innerHTML = html;
    const logSection = shadow.querySelector(".nx-log-section");
    if (logSection) logSection.parentNode.insertBefore(div, logSection);

    // TG DM button handler
    const tgBtn = div.querySelector("#nx-tg-dm-btn");
    if (tgBtn && contact.telegram) {
      tgBtn.addEventListener("click", () => {
        const msg = prompt(`Send Telegram DM to @${contact.telegram}:`, `Hey ${contact.name?.split(" ")[0] || ""}!`);
        if (msg) {
          if (!isAlive()) { addLog("X", "Extension disconnected"); return; }
          try { chrome.runtime.sendMessage({ action: "telegram-dm", params: { recipientUsername: contact.telegram, message: msg } }, (res) => {
            if (chrome.runtime.lastError) { addLog("X", "Extension disconnected"); return; }
            addLog(res?.error ? "X" : "V", res?.error ? `TG DM failed: ${res.error}` : `TG DM sent to @${contact.telegram}`);
          }); } catch { addLog("X", "Extension disconnected"); }
          tgBtn.textContent = "Sent!";
          tgBtn.disabled = true;
        }
      });
    }
  });
  } catch { _contextDead = true; }
}

detectProfileAndShowCRM();
let _lastTwUrl = window.location.href;
const _twUrlWatcher = setInterval(() => {
  if (!isAlive()) { clearInterval(_twUrlWatcher); return; }
  if (window.location.href !== _lastTwUrl) {
    _lastTwUrl = window.location.href;
    detectProfileAndShowCRM();
  }
}, 2000);

// Inject
if (document.body) injectPanel();
else document.addEventListener("DOMContentLoaded", injectPanel);

console.log("[Nexus Connect] Twitter content script active on", window.location.href);
