// â•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گ
// ط·ط±ط§ط­ ظپظ„ظˆغŒ ع©ظ…ظ¾غŒظ† â€" ط¨ظˆظ… ظ†ظˆط¯غŒ
// ظ†ظˆط¯ظ‡ط§غŒ طھط±غŒع¯ط±/ط§ط³ع©ط±غŒظ¾طھ/ظپط§ظ„ظˆط¢ظ¾/ط´ط±ط·/ط§ع©ط´ظ† ط±ط§ ظ…غŒâ€Œع†غŒظ†غŒ ظˆ ظˆطµظ„ ظ…غŒâ€Œع©ظ†غŒطŒ
// ظˆ ع©ظ„ظگ ع¯ط±ط§ظپ = طھط¹ط±غŒظپ غŒع© ع©ظ…ظ¾غŒظ†. (ظپط§ط² غ±: ط·ط±ط§ط­غŒ + ط°ط®غŒط±ظ‡)
// â•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گ

// ابعاد کارت نود معمولی
const FL_W = 264, FL_H = 72;
// ابعاد نود شرط (دایره)
const FL_CD = 64, FL_CR = 32; // قطر و شعاع دایره شرط

// انواع نود
const FL_NODES = {
  trigger:   { label: 'تریگر',    icon: 'zap',        color: '#F59E0B', group: 'ورود',   inPort: false, outPorts: ['out'],      desc: 'نقطهٔ ورود لید به فلو' },
  script:    { label: 'اسکریپت', icon: 'file-text',  color: '#6366F1', group: 'مکالمه', inPort: true,  outPorts: ['out'],      desc: 'چسباندن اسکریپت مکالمه' },
  followup:  { label: 'فالوآپ',  icon: 'repeat-2',   color: '#10B981', group: 'پیگیری', inPort: true,  outPorts: ['out'],      desc: 'اجرای قانون پیگیری' },
  delay:     { label: 'تأخیر',   icon: 'clock',      color: '#0EA5E9', group: 'کنترل',  inPort: true,  outPorts: ['out'],      desc: 'صبر برای مدت مشخص' },
  condition: { label: 'شرط',     icon: 'git-branch', color: '#EC4899', group: 'کنترل',  inPort: true,  outPorts: ['yes','no'], desc: 'شاخه بر اساس وضعیت لید' },
  action:    { label: 'اکشن',    icon: 'sparkles',   color: '#8B5CF6', group: 'اکشن',   inPort: true,  outPorts: ['out'],      desc: 'تغییر وضعیت/تگ/اطلاع' },
  end:       { label: 'پایان',   icon: 'flag',       color: '#64748B', group: 'کنترل',  inPort: true,  outPorts: [],           desc: 'پایان مسیر' },
};

const FL_PORT_LABEL = { yes: 'بله', no: 'خیر', out: '', else: 'بقیه' };

const PORT_DATA_COLOR  = '#10B981';
const PORT_STATE_COLOR = '#F59E0B';

function nodeOutPorts(node) {
  if (node.type === 'script') {
    const branches = (node.config && node.config.branches) || [];
    if (branches.length) {
      return [
        ...branches.map(b => ({ id: b.id, label: b.label || '', color: '#6366F1' })),
        { id: 'else', label: 'بقیه', color: '#64748B' },
      ];
    }
    return [{ id: 'out', label: '', color: FL_NODES.script.color }];
  }
  if (node.type === 'condition') {
    return [{ id: 'yes', label: 'بله', color: '#34D399' }, { id: 'no', label: 'خیر', color: '#F87171' }];
  }
  return (FL_NODES[node.type]?.outPorts || []).map(p => ({ id: p, label: FL_PORT_LABEL[p] || '', color: FL_NODES[node.type].color }));
}

const TRIGGER_EVENTS = [
  { id: 'free_content_sent', label: 'محتوای رایگان ارسال شد' },
  { id: 'script_drop',       label: 'مکالمه رها شد' },
  { id: 'consultation_done', label: 'مشاوره انجام شد' },
  { id: 'payment_expired',   label: 'پرداخت منقضی شد' },
  { id: 'keyword',           label: 'کلیدواژه (تریگر کمپین)' },
  { id: 'manual',            label: 'دستی' },
];
const CONDITION_KINDS = [
  { id: 'replied',        label: 'لید پاسخ داد؟' },
  { id: 'purchased',      label: 'خرید کرد؟' },
  { id: 'has_phone',      label: 'شماره داد؟' },
  { id: 'not_interested', label: 'بی‌علاقه شد؟' },
];
const CONDITION_SOURCES = [
  { id: 'lead',  label: '🧭 وضعیت کلیِ لید' },
  { id: 'field', label: '📋 اطلاعاتی که کاربر داده' },
  { id: 'state', label: '📍 مرحلهٔ مکالمه' },
];
const FIELD_OPS = [
  { id: 'ai',       label: '🤖 بررسی هوشمند (معنایی)' },
  { id: 'contains', label: 'شامل می‌شود' },
  { id: 'exists',   label: 'مقدار دارد' },
  { id: 'empty',    label: 'خالی است' },
  { id: 'eq',       label: 'برابر است با' },
  { id: 'neq',      label: 'برابر نیست با' },
  { id: 'gt',       label: 'بزرگ‌تر از' },
  { id: 'lt',       label: 'کوچک‌تر از' },
];

const flOpLabel = function(op) { return (FIELD_OPS.find(function(o) { return o.id === op; }) || {}).label || 'شامل می‌شود'; };
const flCheckLabel = function(ch) {
  if (!ch) return null;
  if (ch.type === 'field') {
    if (!ch.field) return null;
    var opLabels = { exists: 'پر باشد', empty: 'خالی باشد', eq: '=', neq: '≠', contains: 'شامل', gt: '>', lt: '<', ai: '≈ (هوشمند)' };
    var opL = opLabels[ch.op] || 'پر باشد';
    if (ch.op === 'eq' || ch.op === 'neq' || ch.op === 'contains' || ch.op === 'gt' || ch.op === 'lt')
      return '«' + ch.field + '» ' + opL + ' «' + (ch.value || '...') + '»';
    return '«' + ch.field + '» ' + opL;
  }
  if (ch.type === 'state') {
    var op = ch.op || ch.stateOp || 'is';
    if (op === 'stuck') return 'بیش از ' + (ch.hours || 0) + ' ساعت در «' + (ch.value || '...') + '» مانده';
    if (!ch.value) return null;
    return op === 'neq' ? ('مرحله «' + ch.value + '» نباشد') : ('مرحله «' + ch.value + '» باشد');
  }
  if (ch.type === 'lead' || ch.kind) {
    var kind = ch.kind;
    var m = { has_phone: '📱 شماره داده', purchased: '💰 خرید کرده', replied: '💬 پاسخ داده', not_interested: '🚫 بی‌علاقه' };
    return m[kind] || null;
  }
  return null;
};
const flConditionSentence = function(c) {
  var checks = (Array.isArray(c.checks) && c.checks.length) ? c.checks : flLegacyChecks(c);
  var labels = checks.map(flCheckLabel).filter(Boolean);
  if (!labels.length) return '';
  return labels.join(c.match === 'any' ? ' | ' : ' + ');
};

const flLegacyChecks = function(c) {
  var src = c.source || 'lead';
  if (src === 'field') return (c.field || c.op || c.value) ? [{ type: 'field', field: c.field, op: c.op, value: c.value }] : [];
  if (src === 'state') return c.value ? [{ type: 'state', op: c.op || 'is', value: c.value }] : [];
  if (c.kind) return [{ type: 'lead', kind: c.kind }];
  return [];
};
const ACTION_KINDS = [
  { id: 'send_text',     label: '💬 ارسال متن' },
  { id: 'send_library',  label: '📁 ارسال فایل از کتابخانه' },
  { id: 'send_link',     label: '🔗 ارسال لینک' },
  { id: 'send_audio',    label: '🎵 ارسال صدا از کتابخانه' },
  { id: 'change_status', label: '🔄 تغییر وضعیت' },
  { id: 'add_tag',       label: '🏷 افزودن تگ' },
  { id: 'notify_seller', label: '🔔 اطلاع به فروشنده' },
];
const flUid = () => 'n_' + Math.random().toString(36).slice(2, 8);
const flClamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));

const FlowBuilder = ({ initialCampId, embedded } = {}) => {
  const { Icon, useToast } = window.SB_UI;
  const toast = useToast ? useToast() : { show: () => {} };

  const [nodes, setNodes]   = useState([]);
  const [edges, setEdges]   = useState([]);
  const [selId, setSelId]       = useState(null); // نود انتخابی
  const [selEdgeId, setSelEdgeId] = useState(null); // خط انتخابی
  const [expandedId, setExpandedId] = useState(null); // باز بودن پنل نود
  const [focusId, setFocusId] = useState(null); // focus mode
  const [view, setView]     = useState({ x: 40, y: 40, zoom: 1 });

  const [campaigns, setCampaigns] = useState([]);
  const [campId, setCampId]       = useState('');
  const [campName, setCampName]   = useState('');
  const [editingName, setEditingName] = useState(false);
  const [scripts, setScripts]     = useState([]);
  const [rules, setRules]         = useState([]);
  const [triggers, setTriggers]   = useState([]);

  const [saving, setSaving] = useState(false);
  const [dirty, setDirty]   = useState(false);
  const [connecting, setConnecting] = useState(null); // {from, port}
  const [cursor, setCursor] = useState({ x: 0, y: 0 }); // flow-coords ط¨ط±ط§غŒ ط®ط· ظ…ظˆظ‚طھ
  const [simResult, setSimResult] = useState(null);
  const [meta, setMeta] = useState({ fields: [], states: [] });
  const [libFiles, setLibFiles] = useState([]); // ظپط§غŒظ„â€Œظ‡ط§غŒ ع©طھط§ط¨ط®ط§ظ†ظ‡ ط¨ط±ط§غŒ ط§ظ†طھط®ط§ط¨ ط¯ط± ظ†ظˆط¯ظگ ط§ع©ط´ظ†
  const [activeTab, setActiveTab] = window.useStickyState('tab_flow', 'design'); // design | report
  const [liveData, setLiveData] = useState(null);
  const [liveLoading, setLiveLoading] = useState(false);
  const [liveMode, setLiveMode] = useState(false);
  const livePollRef = useRef(null);
  // particles
  const [particles, setParticles] = useState([]);
  const prevCountsRef = useRef({});
  const particleIdRef = useRef(0);
  // conversation panel
  const [livePanel, setLivePanel] = useState(null); // {nodeId, label}
  const [panelLeads, setPanelLeads] = useState([]);
  const [selConv, setSelConv] = useState(null);   // {conv_id, name}
  const [convMsgs, setConvMsgs] = useState([]);
  const [convLoading, setConvLoading] = useState(false);
  // ctxMenu: {mode:'node'|'canvas', nodeId?, x, y, cx, cy}
  // cx/cy = موقعیت روی کانواس (برای ساخت نود در مکان کلیک)
  const [ctxMenu, setCtxMenu] = useState(null);
  const [switcherOpen, setSwitcherOpen] = useState(false);
  const [switcherQ, setSwitcherQ] = useState('');
  const switcherRef = useRef(null);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const flowRootRef = useRef(null);
  // undo stack — هر بار قبل از عمل، state فعلی push میشه
  const undoStackRef  = useRef([]); // [{nodes,edges}]
  const nodesRef      = useRef([]); // mirror sync
  const edgesRef      = useRef([]); // mirror sync
  const selEdgeIdRef  = useRef(null); // همیشه آخرین مقدار selEdgeId
  const viewRef       = useRef({ x: 40, y: 40, zoom: 1 });

  const canvasRef = useRef(null);
  const dragRef   = useRef(null); // {id, offX, offY}
  const panRef    = useRef(null); // {sx, sy, vx, vy}

  // فول‌اسکرین — Escape برای بستن
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape' && isFullscreen) setIsFullscreen(false); };
    const onFsChange = () => {
      if (!document.fullscreenElement) setIsFullscreen(false);
    };
    document.addEventListener('keydown', onKey);
    document.addEventListener('fullscreenchange', onFsChange);
    return () => {
      document.removeEventListener('keydown', onKey);
      document.removeEventListener('fullscreenchange', onFsChange);
    };
  }, [isFullscreen]);

  const toggleFullscreen = () => setIsFullscreen(f => !f);

  // همگام‌سازی viewRef با view (برای دسترسی synchronous در wheel handler)
  useEffect(() => { viewRef.current = view; }, [view]);

  // ── Wheel: zoom با Ctrl، pan بدون Ctrl ───────────────────────────
  useEffect(() => {
    const el = canvasRef.current;
    if (!el) return;
    const handler = (e) => {
      e.preventDefault();
      const v = viewRef.current;
      if (e.ctrlKey || e.metaKey) {
        // zoom حول مکان cursor
        const rect = el.getBoundingClientRect();
        const mx = e.clientX - rect.left;
        const my = e.clientY - rect.top;
        const delta = e.deltaY < 0 ? 1.12 : 1 / 1.12;
        const nz = flClamp(v.zoom * delta, 0.15, 3);
        const nx = mx - (mx - v.x) * (nz / v.zoom);
        const ny = my - (my - v.y) * (nz / v.zoom);
        setView({ x: nx, y: ny, zoom: nz });
      } else {
        // pan
        setView(prev => ({ ...prev, x: prev.x - e.deltaX, y: prev.y - e.deltaY }));
      }
    };
    el.addEventListener('wheel', handler, { passive: false });
    return () => el.removeEventListener('wheel', handler);
  }, []); // canvasRef.current ثابت است

  // mirror refs — همیشه آخرین مقدار رو sync نگه می‌دارن
  useEffect(() => { nodesRef.current = nodes; }, [nodes]);
  useEffect(() => { edgesRef.current = edges; }, [edges]);
  useEffect(() => { selEdgeIdRef.current = selEdgeId; }, [selEdgeId]);

  // قبل از هر تغییر، state فعلی رو ذخیره کن
  const pushUndo = () => {
    const snap = {
      nodes: JSON.parse(JSON.stringify(nodesRef.current)),
      edges: JSON.parse(JSON.stringify(edgesRef.current)),
    };
    const stack = undoStackRef.current;
    stack.push(snap);
    if (stack.length > 40) stack.shift(); // حداکثر ۴۰ مرحله
  };

  // ── بسته شدن سوییچر با کلیک بیرون
  useEffect(() => {
    if (!switcherOpen) return;
    const handler = (e) => {
      if (switcherRef.current && !switcherRef.current.contains(e.target)) {
        setSwitcherOpen(false);
      }
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [switcherOpen]);

  // â"€â"€ ط¨ط§ط±ع¯ط°ط§ط±غŒ ط¯ط§ط¯ظ‡â€Œظ‡ط§غŒ ظ…ط±ط¬ط¹ â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  useEffect(() => {
    window.api('/campaigns/campaigns').then(d => {
      if (Array.isArray(d)) setCampaigns(d);
    }).catch(() => {});
    window.api('/scripts/').then(d => {
      if (d && d.success && d.data) setScripts(d.data.scripts || []);
    }).catch(() => {});
    window.api('/followup/rules').then(d => {
      if (d && d.success) setRules(d.data || []);
    }).catch(() => {});
    window.api('/scripts/campaigns/triggers').then(d => {
      if (d && d.success && d.data) setTriggers(d.data.triggers || []);
    }).catch(() => {});
    window.api('/campaigns/flow-fields').then(d => {
      if (d && (d.fields || d.states)) setMeta({ fields: d.fields || [], states: d.states || [] });
    }).catch(() => {});
    window.api('/library/picker').then(d => {
      if (d && d.success && d.data) setLibFiles(d.data.groups || []);
    }).catch(() => {});
    // اگه از صفحه کمپین‌ها اومدیم یا embedded هستیم، مستقیم بازش کن
    const openId = initialCampId || window.SB_openCampaign;
    if (openId) { window.SB_openCampaign = null; loadCampaign(openId); }
  }, []);

  // â"€â"€ ط¨ط§ط±ع¯ط°ط§ط±غŒ ظپظ„ظˆغŒ غŒع© ع©ظ…ظ¾غŒظ† â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  const loadCampaign = (id) => {
    setCampId(id);
    setSelId(null);
    setExpandedId(null);
    if (!id) { setNodes([]); setEdges([]); setDirty(false); return; }
    window.api('/campaigns/campaigns/' + id).then(c => {
      setCampName(c.name || '');
      let flow = {};
      try { flow = JSON.parse(c.flow_json || '{}'); } catch {}
      const nodeArr = Array.isArray(flow.nodes)
        ? flow.nodes
        : Object.values(flow.nodes || {});
      const edgeArr = flow.edges || [];
      setNodes(nodeArr);
      setEdges(edgeArr);
      setDirty(false);
      undoStackRef.current = []; // undo stack رو ریست کن
    }).catch(() => { setNodes([]); setEdges([]); });
  };

  // â"€â"€ ط°ط®غŒط±ظ‡ â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  const save = () => {
    if (!campId) { toast.show && toast.show('اول یک کمپین انتخاب کن', 'error'); return Promise.resolve(false); }
    setSaving(true);
    const flow = { nodes, edges };
    return window.api('/campaigns/campaigns/' + campId + '/flow', { method: 'PUT', body: { flow } })
      .then(r => {
        if (r && r.success) { setDirty(false); toast.show && toast.show('فلو ذخیره شد', 'success'); return true; }
        toast.show && toast.show('ذخیره ناموفق بود', 'error'); return false;
      })
      .catch(() => { toast.show && toast.show('خطا در ذخیره', 'error'); return false; })
      .finally(() => setSaving(false));
  };

  // ── BFS level helper — مشترک بین هر دو چیدمان ──────────────────
  const _bfsLevels = () => {
    const outMap = {}, inDeg = {};
    nodes.forEach(n => { outMap[n.id] = []; inDeg[n.id] = 0; });
    edges.forEach(ed => {
      if (outMap[ed.from]) outMap[ed.from].push(ed.to);
      if (inDeg[ed.to] !== undefined) inDeg[ed.to]++;
    });
    const level = {};
    const queue = nodes.filter(n => n.type === 'trigger' || inDeg[n.id] === 0).map(n => n.id);
    queue.forEach(id => { level[id] = 0; });
    let head = 0;
    while (head < queue.length) {
      const id = queue[head++];
      (outMap[id] || []).forEach(nid => {
        const proposed = (level[id] || 0) + 1;
        if (level[nid] === undefined) { level[nid] = proposed; queue.push(nid); }
        else if (proposed > level[nid]) { level[nid] = proposed; }
      });
    }
    nodes.forEach(n => { if (level[n.id] === undefined) level[n.id] = 0; });
    const byLevel = {};
    nodes.forEach(n => { const l = level[n.id]; if (!byLevel[l]) byLevel[l] = []; byLevel[l].push(n.id); });
    return byLevel;
  };

  // ── چینش افقی (چپ → راست) — پیش‌فرض ───────────────────────────
  const autoLayout = () => {
    if (!nodes.length) return;
    const NX = 320, NY = 160, X0 = 60, Y0 = 80;
    const byLevel = _bfsLevels();
    const pos = {};
    Object.entries(byLevel).forEach(([col, ids]) => {
      const c = parseInt(col);
      ids.forEach((id, row) => {
        const totalH = ids.length * NY;
        const startY = Y0 + (row * NY) - (totalH / 2) + NY / 2;
        pos[id] = { x: X0 + c * NX, y: Math.max(Y0, startY) };
      });
    });
    setNodes(ns => ns.map(n => pos[n.id] ? { ...n, x: pos[n.id].x, y: pos[n.id].y } : n));
    setDirty(true); setView({ x: 40, y: 40, zoom: 1 });
    toast.show && toast.show('چینش افقی اعمال شد', 'success');
  };

  // ── چینش عمودی (بالا → پایین) ───────────────────────────────────
  const autoLayoutV = () => {
    if (!nodes.length) return;
    const NY = 180, NX = 300, X0 = 80, Y0 = 60;
    const byLevel = _bfsLevels();
    // عرض کل canvas برای مرکزچینی
    const maxCols = Math.max(...Object.values(byLevel).map(ids => ids.length));
    const totalW = maxCols * NX;
    const pos = {};
    Object.entries(byLevel).forEach(([lv, ids]) => {
      const rowW = ids.length * NX;
      const startX = (totalW - rowW) / 2 + X0;
      ids.forEach((id, col) => {
        pos[id] = { x: startX + col * NX, y: Y0 + parseInt(lv) * NY };
      });
    });
    setNodes(ns => ns.map(n => pos[n.id] ? { ...n, x: pos[n.id].x, y: pos[n.id].y } : n));
    setDirty(true); setView({ x: 40, y: 40, zoom: 1 });
    toast.show && toast.show('چینش عمودی اعمال شد', 'success');
  };

  // ط´ط¨غŒظ‡â€Œط³ط§ط²غŒ ظ…ط³غŒط± ظپظ„ظˆ (ط¨ط¯ظˆظ† ط¹ظˆط§ط±ط¶ ط¬ط§ظ†ط¨غŒ)
  const runSimulate = async () => {
    if (!campId) { toast.show && toast.show('اول یک کمپین انتخاب کن', 'error'); return; }
    if (dirty) { const okSaved = await save(); if (!okSaved) return; }
    try {
      const r = await window.api('/campaigns/campaigns/' + campId + '/flow/simulate', { method: 'POST', body: {} });
      setSimResult(r);
    } catch { toast.show && toast.show('خطا در شبیه‌سازی', 'error'); }
  };

  // ط¨ط§ط±ع¯ط°ط§ط±غŒ ع¯ط²ط§ط±ط´ ط²ظ†ط¯ظ‡
  const openLivePanel = (nodeId, node) => {
    var ns = liveData && liveData.nodeStats && liveData.nodeStats[nodeId];
    if (!ns) return;
    setLivePanel({ nodeId: nodeId, label: (node && node.label) || (FL_NODES[node && node.type] && FL_NODES[node.type].label) || 'نود' });
    setPanelLeads(ns.leads || []);
    setSelConv(null);
    setConvMsgs([]);
  };

  const loadConv = (convId, leadName) => {
    if (!convId) return;
    setSelConv({ conv_id: convId, name: leadName });
    setConvLoading(true);
    setConvMsgs([]);
    window.api('/conversations/' + convId + '/messages')
      .then(function(d) { if (d && d.success && d.data) setConvMsgs(d.data.messages || []); })
      .catch(function() {})
      .finally(function() { setConvLoading(false); });
  };

  const loadLive = () => {
    if (!campId) return;
    setLiveLoading(true);
    window.api('/campaigns/campaigns/' + campId + '/live')
      .then(d => { if (d && d.ok) setLiveData(d); })
      .catch(() => {})
      .finally(() => setLiveLoading(false));
  };
  useEffect(() => { if (activeTab === 'report' && campId) loadLive(); }, [activeTab, campId]);

  // polling زنده در تب طراح
  useEffect(() => {
    if (livePollRef.current) { clearInterval(livePollRef.current); livePollRef.current = null; }
    if (!liveMode || !campId) { if (!liveMode) { setLiveData(null); prevCountsRef.current = {}; setParticles([]); } return; }
    loadLive();
    livePollRef.current = setInterval(loadLive, 5000);
    return () => { if (livePollRef.current) { clearInterval(livePollRef.current); livePollRef.current = null; } };
  }, [liveMode, campId]); // eslint-disable-line

  // particle engine — وقتی liveData آپدیت می‌شه
  useEffect(() => {
    if (!liveData || !liveMode) return;
    const newCounts = {};
    Object.entries(liveData.nodeStats || {}).forEach(function(entry) {
      newCounts[entry[0]] = entry[1].count || 0;
    });
    const prev = prevCountsRef.current;
    const newParticles = [];
    edgesRef.current.forEach(function(edge) {
      const prevFrom = prev[edge.from] || 0;
      const newFrom  = newCounts[edge.from] || 0;
      const prevTo   = prev[edge.to] || 0;
      const newTo    = newCounts[edge.to] || 0;
      if (prevFrom > newFrom && newTo > prevTo) {
        const n = Math.min(prevFrom - newFrom, newTo - prevTo, 5);
        for (var i = 0; i < n; i++) {
          particleIdRef.current++;
          newParticles.push({ id: particleIdRef.current, edgeId: edge.id, delay: i * 260 });
        }
      }
    });
    if (newParticles.length > 0) {
      setParticles(function(p) { return p.concat(newParticles); });
      var maxDelay = newParticles.reduce(function(m, p) { return Math.max(m, p.delay); }, 0);
      var ids = newParticles.map(function(p) { return p.id; });
      setTimeout(function() {
        setParticles(function(p) { return p.filter(function(x) { return ids.indexOf(x.id) === -1; }); });
      }, 2200 + maxDelay);
    }
    prevCountsRef.current = newCounts;
  }, [liveData]); // eslint-disable-line

  // ط³ط§ط®طھ ع©ظ…ظ¾غŒظ† ط¬ط¯غŒط¯ ظ‡ظ…غŒظ†â€Œط¬ط§ (طھط¹ط±غŒظپ ط¯ط³طھغŒ)
  const createCampaign = async () => {
    const name = window.prompt('نام کمپین جدید را وارد کن:');
    if (!name || !name.trim()) return;
    try {
      const r = await window.api('/campaigns/campaigns', { method: 'POST', body: { name: name.trim(), type: 'product' } });
      if (r && r.id) {
        const list = await window.api('/campaigns/campaigns');
        if (Array.isArray(list)) setCampaigns(list);
        loadCampaign(r.id);
        setActiveTab('design');
        toast.show && toast.show('کمپین ساخته شد — راست‌کلیک کن و نود اضافه کن', 'success');
      } else toast.show && toast.show('ساخت کمپین ناموفق بود', 'error');
    } catch { toast.show && toast.show('خطا در ساخت کمپین', 'error'); }
  };

  // â"€â"€ ط§ظپط²ظˆط¯ظ† ظ†ظˆط¯ â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  const addNode = (type, atX, atY) => {
    const id = flUid();
    let x, y;
    if (atX !== undefined && atY !== undefined) {
      x = atX - FL_W / 2;
      y = atY - 36;
    } else {
      const cx = ((canvasRef.current?.clientWidth || 800) / 2 - view.x) / view.zoom - FL_W / 2;
      const cy = ((canvasRef.current?.clientHeight || 600) / 3 - view.y) / view.zoom;
      const offset = nodes.length * 14 % 120;
      x = cx + offset; y = cy + offset;
    }
    const newNode = { id, type, x, y, config: {}, label: '' };
    pushUndo();
    setNodes(n => [...n, newNode]);
    setSelId(id);
    setExpandedId(id);
    setDirty(true);
  };

  // افزودن نود بعدی که خودکار به نود مبدأ وصل می‌شود
  const addConnectedNode = (fromId, port, type) => {
    const from = nodes.find(n => n.id === fromId);
    if (!from) return;
    const id = flUid();
    const fromH = from.type === 'condition' ? FL_CD : FL_H;
    const dx = port === 'no' ? 130 : port === 'yes' ? -130 : 0;
    const nx = from.x + dx, ny = from.y + fromH + 60;
    const newNode = { id, type, x: nx, y: ny, config: {}, label: '' };
    const newEdge = { id: 'e_' + Math.random().toString(36).slice(2, 8), from: fromId, fromPort: port, to: id };
    pushUndo();
    setNodes(n => [...n, newNode]);
    setEdges(e => [...e.filter(ed => !(ed.from === fromId && ed.fromPort === port)), newEdge]);
    setSelId(id);
    setExpandedId(id);
    setDirty(true);
  };


  const updateNode = (id, patch) => {
    pushUndo();
    setNodes(n => n.map(nd => nd.id === id ? { ...nd, ...patch, config: patch.config ? { ...nd.config, ...patch.config } : nd.config } : nd));
    setDirty(true);
  };
  const deleteNode = (id) => {
    pushUndo();
    setNodes(n => n.filter(nd => nd.id !== id));
    setEdges(e => e.filter(ed => ed.from !== id && ed.to !== id));
    if (selId === id) setSelId(null);
    if (expandedId === id) setExpandedId(null);
    setDirty(true);
  };
  const deleteEdge = (eid) => {
    pushUndo();
    setEdges(e => e.filter(ed => ed.id !== eid));
    setDirty(true);
  };

  const addEdge = (from, port, to) => {
    if (from === to) return;
    const cur = edgesRef.current;
    const exists = cur.some(ed => ed.from === from && ed.fromPort === port && ed.to === to);
    if (exists) return;
    pushUndo();
    setEdges(e => [
      ...e.filter(ed => !(ed.from === from && ed.fromPort === port)),
      { id: 'e_' + Math.random().toString(36).slice(2, 8), from, fromPort: port, to },
    ]);
    setDirty(true);
  };

  // â"€â"€ ظ…ط®طھطµط§طھ flow ط§ط² ظ…ط®طھطµط§طھ طµظپط­ظ‡ â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  const toFlow = (clientX, clientY) => {
    const r = canvasRef.current.getBoundingClientRect();
    return {
      x: (clientX - r.left - view.x) / view.zoom,
      y: (clientY - r.top - view.y) / view.zoom,
    };
  };

  // ── محل پورت‌ها ──────────────────────────────────────────────
  const inPortPos  = (nd) => {
    if (nd.type === 'condition') return { x: nd.x + FL_CR, y: nd.y };
    return { x: nd.x + FL_W / 2, y: nd.y };
  };
  const outPortPos = (nd, port) => {
    if (nd.type === 'condition') {
      // دایره: بله چپ، خیر راست
      const cx = nd.x + FL_CR, cy = nd.y + FL_CD;
      return port === 'no' ? { x: cx + FL_CR * 0.7, y: cy } : { x: cx - FL_CR * 0.7, y: cy };
    }
    const outs = nodeOutPorts(nd);
    if (outs.length <= 1) return { x: nd.x + FL_W / 2, y: nd.y + FL_H };
    let idx = outs.findIndex(p => p.id === port);
    if (idx < 0) idx = 0;
    const frac = (idx + 1) / (outs.length + 1);
    return { x: nd.x + FL_W * frac, y: nd.y + FL_H };
  };

  // â"€â"€ ظ…ط§ظˆط³ â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  const onBgMouseDown = (e) => {
    if (e.target === canvasRef.current || e.target.dataset.bg) {
      panRef.current = { sx: e.clientX, sy: e.clientY, vx: view.x, vy: view.y };
      setSelId(null);
      setSelEdgeId(null);
      setExpandedId(null);
    }
  };
  const onMouseMove = (e) => {
    if (dragRef.current) {
      const f = toFlow(e.clientX, e.clientY);
      const { id, offX, offY } = dragRef.current;
      setNodes(n => n.map(nd => nd.id === id ? { ...nd, x: f.x - offX, y: f.y - offY } : nd));
    } else if (panRef.current) {
      const p = panRef.current;
      setView(v => ({ ...v, x: p.vx + (e.clientX - p.sx), y: p.vy + (e.clientY - p.sy) }));
    } else if (connecting) {
      setCursor(toFlow(e.clientX, e.clientY));
    }
  };
  const onMouseUp = () => {
    dragRef.current = null;
    panRef.current  = null;
    // ط§ع¯ط± ط±ظˆغŒ ظ†ظˆط¯ ط±ظ‡ط§ ظ†ط´ط¯طŒ ط§طھطµط§ظ„ ظ„ط؛ظˆ ظ…غŒâ€Œط´ظˆط¯ (ظ†ظˆط¯ ط®ظˆط¯ط´ onMouseUp ط±ط§ ظ…ط¯غŒط±غŒطھ ظ…غŒâ€Œع©ظ†ط¯)
    setConnecting(null);
  };

  const startDrag = (e, nd) => {
    if (connecting) return;
    e.stopPropagation();
    setSelId(nd.id); // فقط highlight — پنل باز نمی‌شه
    if (nd._anchored) return;
    const f = toFlow(e.clientX, e.clientY);
    dragRef.current = { id: nd.id, offX: f.x - nd.x, offY: f.y - nd.y };
  };
  const startConnect = (e, nd, port) => {
    e.stopPropagation();
    setConnecting({ from: nd.id, port });
    setCursor(outPortPos(nd, port));
  };
  const finishConnect = (e, nd) => {
    if (connecting && FL_NODES[nd.type].inPort && connecting.from !== nd.id) {
      e.stopPropagation();
      addEdge(connecting.from, connecting.port, nd.id);
    }
    setConnecting(null);
  };

  // کلیدهای میان‌بر
  useEffect(() => {
    const notInInput = () => !['INPUT','SELECT','TEXTAREA'].includes(document.activeElement?.tagName);
    const h = (e) => {
      // Delete / Backspace — حذف نود یا خط انتخابی
      if ((e.key === 'Delete' || e.key === 'Backspace') && notInInput()) {
        const curEdgeId = selEdgeIdRef.current;
        if (curEdgeId) {
          e.preventDefault();
          deleteEdge(curEdgeId);
          setSelEdgeId(null);
          selEdgeIdRef.current = null;
          return;
        }
        if (selId) {
          deleteNode(selId);
          return;
        }
      }
      // Ctrl+Shift+S — ذخیره
      if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 's') {
        e.preventDefault(); e.stopPropagation();
        save();
        return;
      }
      // Ctrl+Z — undo
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'z' && !e.shiftKey && notInInput()) {
        e.preventDefault(); e.stopPropagation();
        const stack = undoStackRef.current;
        if (stack.length > 0) {
          const snap = stack.pop();
          setNodes(JSON.parse(JSON.stringify(snap.nodes)));
          setEdges(JSON.parse(JSON.stringify(snap.edges)));
          setSelId(null);
          setExpandedId(null);
          setDirty(true);
          toast.show && toast.show(`↩ بازگشت (${stack.length} مرحله باقی)`, 'success');
        } else {
          toast.show && toast.show('چیزی برای بازگشت نیست', 'error');
        }
        return;
      }
    };
    // capture:true = قبل از browser shortcuts مثل Ctrl+S و Ctrl+Z
    document.addEventListener('keydown', h, { capture: true });
    return () => document.removeEventListener('keydown', h, { capture: true });
  }, [selId]); // eslint-disable-line

  const selNode = nodes.find(n => n.id === selId) || null;

  // â"€â"€ ظ…ط³غŒط± ظ…ظ†ط­ظ†غŒ ط§طھطµط§ظ„ â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
  const edgePath = (s, t) => `M ${s.x} ${s.y} C ${s.x} ${s.y + 50}, ${t.x} ${t.y - 50}, ${t.x} ${t.y}`;

  // لنگراندختن نود شرط به والدش
  const layoutNodes = nodes.map(nd => {
    if (nd.type === 'condition') {
      const e = edges.find(ed => ed.to === nd.id);
      const p = e && nodes.find(n => n.id === e.from);
      if (p) {
        const pPort = outPortPos(p, e.fromPort);
        const condType = e.fromPort === 'data' ? 'field' : e.fromPort === 'state' ? 'state' : 'lead';
        return { ...nd, x: Math.round(pPort.x - FL_CR), y: Math.round(p.y + FL_H + 44),
          _anchored: true, _condType: condType, _fromPort: e.fromPort };
      }
      return { ...nd, _orphan: false };
    }
    return nd;
  });
  const findLN = (id) => layoutNodes.find(n => n.id === id);

  // ع¯ط±ظˆظ‡â€Œط¨ظ†ط¯غŒ ظ¾ط§ظ„طھ (ظ†ظˆط¯ظگ ط´ط±ط· ظ…ط³طھظ‚ظ„ ط³ط§ط®طھظ‡ ظ†ظ…غŒâ€Œط´ظˆط¯ط› ظپظ‚ط· ط¨ظ‡â€Œط¹ظ†ظˆط§ظ† ط§ظ†ط´ط¹ط§ط¨)
  const groups = {};
  Object.entries(FL_NODES).forEach(([k, v]) => {
    if (k === 'condition') return;
    (groups[v.group] = groups[v.group] || []).push([k, v]);
  });

  return (
    <div ref={flowRootRef} className="fl-root" style={{
      display: 'flex', flexDirection: 'column', minHeight: 0,
      ...(isFullscreen ? {
        position: 'fixed', inset: 0, zIndex: 9999,
        background: '#0a0814',
      } : { height: '100%' })
    }}>
      <style>{`
        .fl-root select, .fl-root select option {
          background: #1e1e2e !important;
          color: #e2e8f0 !important;
          color-scheme: dark;
        }
        .fl-root select:focus { outline: 1px solid #8B5CF6; }
        .fl-root input, .fl-root textarea {
          background: #1e1e2e !important;
          color: #e2e8f0 !important;
        }
        .fl-root input::placeholder, .fl-root textarea::placeholder { color: #64748b; }
      `}</style>
      {/* â"€â"€ ظ†ظˆط§ط± ط¨ط§ظ„ط§ â"€â"€ */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px',
        borderBottom: '1px solid rgba(255,255,255,0.07)', flexShrink: 0, flexWrap: 'wrap' }}>
        {embedded && (
          <button onClick={() => window.SB_setPage && window.SB_setPage('campaigns')}
            style={{ display:'flex', alignItems:'center', gap:5, padding:'5px 10px', borderRadius:7,
              border:'1px solid rgba(255,255,255,0.1)', background:'transparent',
              color:'var(--text-3)', cursor:'pointer', fontSize:12, fontFamily:'inherit', flexShrink:0 }}>
            ← کمپین‌ها
          </button>
        )}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="workflow" size={18} color="#8B5CF6" />
          <span style={{ fontWeight: 700, fontSize: 15, color: 'var(--text-3)' }}>
            {campName ? campName : 'طراح فلو'}
          </span>
        </div>
        {!embedded && (() => {
          const idx  = campaigns.findIndex(c => c.id === campId);
          const prev = idx > 0 ? campaigns[idx - 1] : null;
          const next = idx >= 0 && idx < campaigns.length - 1 ? campaigns[idx + 1] : null;
          const filtered = campaigns.filter(c =>
            !switcherQ || c.name.toLowerCase().includes(switcherQ.toLowerCase())
          );
          return (
            <div ref={switcherRef} style={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 4 }}>
              {/* دکمه قبلی */}
              <button
                disabled={!prev}
                onClick={() => { if (prev) { if (dirty && !confirm('تغییرات ذخیره نشده — ادامه می‌دی؟')) return; loadCampaign(prev.id); } }}
                title={prev ? '◀ ' + prev.name : ''}
                style={{ ...btnGhost, padding: '6px 8px', opacity: prev ? 1 : 0.3 }}>
                ◀
              </button>

              {/* دکمه اصلی — باز کردن پنل */}
              <button
                onClick={() => { setSwitcherOpen(o => !o); setSwitcherQ(''); }}
                style={{ ...btnGhost, minWidth: 160, maxWidth: 220, textAlign: 'right',
                  display: 'flex', alignItems: 'center', gap: 6,
                  borderColor: switcherOpen ? '#8B5CF6' : 'rgba(255,255,255,0.1)',
                  background: switcherOpen ? 'rgba(139,92,246,0.12)' : 'var(--bg-2)' }}>
                <Icon name="layout-grid" size={13} color="#8B5CF6" />
                <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                  fontSize: 12, color: campId ? 'var(--text-1)' : 'var(--text-3)' }}>
                  {campId ? (campName || 'بدون نام') : 'انتخاب کمپین...'}
                </span>
                <span style={{ fontSize: 10, color: 'var(--text-3)' }}>
                  {campId && idx >= 0 ? `${idx + 1}/${campaigns.length}` : `${campaigns.length}`}
                </span>
                <Icon name="chevron-down" size={12} color="var(--text-3)" />
              </button>

              {/* دکمه بعدی */}
              <button
                disabled={!next}
                onClick={() => { if (next) { if (dirty && !confirm('تغییرات ذخیره نشده — ادامه می‌دی؟')) return; loadCampaign(next.id); } }}
                title={next ? next.name + ' ▶' : ''}
                style={{ ...btnGhost, padding: '6px 8px', opacity: next ? 1 : 0.3 }}>
                ▶
              </button>

              {/* پنل دراپ‌داون */}
              {switcherOpen && (
                <div style={{
                  position: 'absolute', top: '110%', right: 0, zIndex: 999,
                  width: 300, maxHeight: 420,
                  background: '#12111e', border: '1px solid rgba(139,92,246,0.3)',
                  borderRadius: 12, boxShadow: '0 16px 48px rgba(0,0,0,0.6)',
                  display: 'flex', flexDirection: 'column', overflow: 'hidden',
                }}>
                  {/* جستجو */}
                  <div style={{ padding: '10px 12px', borderBottom: '1px solid rgba(255,255,255,0.07)' }}>
                    <input
                      autoFocus
                      value={switcherQ}
                      onChange={e => setSwitcherQ(e.target.value)}
                      placeholder="جستجوی کمپین..."
                      style={{ width: '100%', background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.1)',
                        borderRadius: 7, padding: '6px 10px', color: 'var(--text-1)', fontSize: 12,
                        fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }}
                    />
                  </div>
                  {/* لیست */}
                  <div style={{ overflowY: 'auto', flex: 1 }}>
                    {filtered.length === 0 && (
                      <div style={{ padding: '20px', textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
                        کمپینی پیدا نشد
                      </div>
                    )}
                    {filtered.map((c, i) => {
                      const active  = c.id === campId;
                      const hasFlow = c.flow_json && c.flow_json !== '{}';
                      return (
                        <div key={c.id}
                          onClick={() => {
                            if (!active) {
                              if (dirty && !confirm('تغییرات ذخیره نشده — ادامه می‌دی؟')) return;
                              loadCampaign(c.id);
                            }
                            setSwitcherOpen(false);
                          }}
                          style={{
                            padding: '10px 14px', cursor: 'pointer',
                            display: 'flex', alignItems: 'center', gap: 10,
                            borderBottom: '1px solid rgba(255,255,255,0.04)',
                            background: active ? 'rgba(139,92,246,0.15)' : 'transparent',
                            transition: 'background 0.15s',
                          }}
                          onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
                          onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
                          {/* آیکون فلو/سکانس */}
                          <div style={{ width: 28, height: 28, borderRadius: 7, flexShrink: 0,
                            background: active ? '#8B5CF6' : 'rgba(255,255,255,0.06)',
                            display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                            <Icon name={hasFlow ? 'workflow' : 'list'} size={14}
                              color={active ? '#fff' : (hasFlow ? '#8B5CF6' : 'var(--text-3)')} />
                          </div>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 13, fontWeight: active ? 700 : 400,
                              color: active ? '#e2d9ff' : 'var(--text-1)',
                              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                              {c.name}
                            </div>
                            <div style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 2,
                              display: 'flex', gap: 8 }}>
                              {hasFlow && <span style={{ color: '#8B5CF6' }}>● فلو</span>}
                              {c.sequence_count > 0 && <span style={{ color: '#10B981' }}>● {c.sequence_count} سکانس</span>}
                              {c.is_active ? <span style={{ color: '#34D399' }}>فعال</span> : <span style={{ color: '#F87171' }}>غیرفعال</span>}
                            </div>
                          </div>
                          {active && <Icon name="check" size={14} color="#8B5CF6" />}
                        </div>
                      );
                    })}
                  </div>
                  {/* فوتر — ساخت کمپین جدید */}
                  <div style={{ padding: '8px 12px', borderTop: '1px solid rgba(255,255,255,0.07)' }}>
                    <button
                      onClick={() => {
                        setSwitcherOpen(false);
                        const name = prompt('نام کمپین جدید:');
                        if (!name || !name.trim()) return;
                        if (dirty && !confirm('تغییرات ذخیره نشده — ادامه می‌دی؟')) return;
                        window.api('/campaigns/campaigns', { method: 'POST', body: { name: name.trim() } })
                          .then(r => {
                            if (r && r.id) {
                              window.api('/campaigns/campaigns').then(d => {
                                if (Array.isArray(d)) { setCampaigns(d); loadCampaign(r.id); setActiveTab('design'); }
                              });
                            }
                          });
                      }}
                      style={{ width: '100%', padding: '7px', borderRadius: 7, border: '1px dashed rgba(139,92,246,0.4)',
                        background: 'transparent', color: '#8B5CF6', cursor: 'pointer', fontSize: 12, fontWeight: 700 }}>
                      + کمپین جدید
                    </button>
                  </div>
                </div>
              )}
            </div>
          );
        })()}
        {/* تب طراح */}
        <div style={{ display: 'flex', gap: 3, background: 'var(--bg-2)', borderRadius: 10, padding: 3, border: '1px solid rgba(255,255,255,0.06)' }}>
          {[
            { id: 'design', label: 'طراح', icon: 'pencil' },
          ].map(tab => {
            const active = activeTab === tab.id;
            return (
              <button key={tab.id} onClick={() => setActiveTab(tab.id)}
                style={{ minWidth: 82, padding: '7px 12px', borderRadius: 7, border: 'none', fontWeight: 700, fontSize: 12,
                  cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 7,
                  background: active ? '#8B5CF6' : 'transparent',
                  color: active ? '#fff' : 'var(--text-3)' }}>
                <Icon name={tab.icon} size={14} />
                <span>{tab.label}</span>
              </button>
            );
          })}
        </div>

        <div style={{ flex: 1 }} />
        {activeTab === 'design' && <>
          {selEdgeId && (
            <span style={{
              fontSize: 11, color: '#f87171', background: 'rgba(239,68,68,0.1)',
              border: '1px solid rgba(239,68,68,0.3)', padding: '3px 10px', borderRadius: 6,
              display: 'flex', alignItems: 'center', gap: 5,
            }}>
              <span style={{ width:6, height:6, borderRadius:'50%', background:'#f87171', flexShrink:0 }} />
              خط انتخاب شد — Delete برای حذف، Ctrl+Z برای بازگشت
            </span>
          )}
          {!selEdgeId && dirty && <span style={{ fontSize: 11, color: '#FBBF24' }}>● تغییرات ذخیره‌نشده</span>}
          <button onClick={() => setView({ x: 40, y: 40, zoom: 1 })} style={btnGhost} title="بازنشانی نما">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
            </svg>
          </button>
          <button onClick={toggleFullscreen}
            title={isFullscreen ? 'خروج از فول‌اسکرین (Esc)' : 'فول‌اسکرین'}
            style={{ ...btnGhost, borderColor: isFullscreen ? '#8B5CF6aa' : undefined, color: isFullscreen ? '#A78BFA' : 'var(--text-2)' }}>
            {isFullscreen ? (
              /* minimize — فلش‌های رو به داخل */
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/>
                <line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/>
              </svg>
            ) : (
              /* maximize — فلش‌های رو به بیرون */
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/>
                <line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/>
              </svg>
            )}
          </button>
          <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <button onClick={() => setView(v => ({ ...v, zoom: flClamp(v.zoom / 1.2, 0.3, 2) }))} style={btnGhost}>−</button>
            <span style={{ fontSize: 11, color: 'var(--text-3)', minWidth: 36, textAlign: 'center', fontFamily: 'JetBrains Mono' }}>{Math.round(view.zoom * 100)}٪</span>
            <button onClick={() => setView(v => ({ ...v, zoom: flClamp(v.zoom * 1.2, 0.3, 2) }))} style={btnGhost}>+</button>
          </div>
          <button onClick={autoLayout} disabled={!campId || !nodes.length}
            title="چینش خودکار نودها"
            style={{ ...btnGhost, opacity: (campId && nodes.length) ? 1 : 0.4, borderColor: '#0EA5E955', color: '#0EA5E9' }}>
            ⬡ چینش
          </button>
          <button
            onClick={() => setLiveMode(v => !v)}
            disabled={!campId}
            title={liveMode ? 'خاموش کردن نمایش زنده' : 'نمایش زنده — چه کسانی الان در کدام نود هستند'}
            style={{
              padding: '6px 12px', borderRadius: 8, cursor: campId ? 'pointer' : 'not-allowed',
              fontSize: 13, lineHeight: 1, fontFamily: 'inherit', fontWeight: liveMode ? 700 : 400,
              opacity: campId ? 1 : 0.4,
              border: liveMode ? '1.5px solid #22c55e' : '1px solid rgba(34,197,94,0.3)',
              color: liveMode ? '#fff' : '#4ade80',
              background: liveMode ? 'linear-gradient(135deg,#16a34a,#15803d)' : 'rgba(34,197,94,0.07)',
              boxShadow: liveMode ? '0 0 12px rgba(34,197,94,0.4)' : 'none',
              display: 'flex', alignItems: 'center', gap: 5,
              transition: 'all .2s',
            }}>
            <span style={{
              display: 'inline-block', width: 7, height: 7, borderRadius: '50%',
              background: '#fff',
              boxShadow: liveMode ? '0 0 6px #fff' : 'none',
              animation: liveMode ? 'livePulse 1.5s ease-in-out infinite' : 'none',
            }} />
            {liveMode ? '● زنده' : 'زنده'}
          </button>
        </>}
        <button onClick={save} disabled={saving || !campId}
          style={{ ...btnPrimary, opacity: (saving || !campId) ? 0.5 : 1 }}>
          {saving ? '...' : '💾 ذخیره'}
        </button>
      </div>

      {/* â•گâ•گ طھط¨ظگ ع¯ط²ط§ط±ط´ â•گâ•گ */}
      {/* ── نمای زنده کامل ── */}
      {activeTab === 'design' && liveMode && campId && (
        <FlowLiveView
          campId={campId}
          nodes={nodesRef.current}
          edges={edgesRef.current}
          onClose={function(){ setLiveMode(false); }}
        />
      )}

      {activeTab === 'design' && <div style={{ display: 'flex', flex: 1, minHeight: 0, direction: 'ltr' }}>

        {/* â"€â"€ ط¨ظˆظ… â"€â"€ */}
        <div ref={canvasRef}
          onMouseDown={onBgMouseDown} onMouseMove={onMouseMove} onMouseUp={onMouseUp} onMouseLeave={onMouseUp}
          onContextMenu={(e) => {
            e.preventDefault();
            const rect = canvasRef.current.getBoundingClientRect();
            const cx = (e.clientX - rect.left - view.x) / view.zoom;
            const cy = (e.clientY - rect.top  - view.y) / view.zoom;
            setCtxMenu({ mode: 'canvas', x: e.clientX, y: e.clientY, cx, cy });
          }}
          style={{ flex: 1, position: 'relative', overflow: 'hidden', direction: 'rtl', cursor: panRef.current ? 'grabbing' : 'default',
            background: `radial-gradient(rgba(255,255,255,.05) 1.1px, transparent 1.1px)`,
            backgroundSize: `${28 * view.zoom}px ${28 * view.zoom}px`,
            backgroundPosition: `${view.x}px ${view.y}px` }}>
          <div data-bg="1" style={{ position: 'absolute', inset: 0 }} />

          {/* ظ„ط§غŒظ‡ظ" ظ…طھط­ط±ع© */}
          <div style={{ position: 'absolute', left: 0, top: 0,
            transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`, transformOrigin: '0 0' }}>
            {/* ط§طھطµط§ظ„â€Œظ‡ط§ */}
            <svg style={{ position: 'absolute', left: 0, top: 0, width: 6000, height: 6000, overflow: 'visible', pointerEvents: 'none' }}>
              <defs>
                {edges.map(ed => {
                  const fn = findLN(ed.from);
                  const portDef = fn ? nodeOutPorts(fn).find(p => p.id === ed.fromPort) : null;
                  const col = portDef ? portDef.color : '#8b5cf6';
                  return (
                    <linearGradient key={'g_' + ed.id} id={'g_' + ed.id} x1="0" y1="0" x2="0" y2="1">
                      <stop offset="0" stopColor="#8b5cf6" />
                      <stop offset="1" stopColor={col} />
                    </linearGradient>
                  );
                })}
              </defs>
              {edges.map(ed => {
                const fn = findLN(ed.from), tn = findLN(ed.to);
                if (!fn || !tn) return null;
                const s = outPortPos(fn, ed.fromPort), t = inPortPos(tn);
                const d = edgePath(s, t);
                const isSel = selEdgeId === ed.id;
                // میانه خط برای دکمه حذف
                const mx = (s.x + t.x) / 2;
                const my = (s.y + t.y) / 2;
                return (
                  <g key={ed.id}>
                    {/* glow underlay */}
                    <path d={d} fill="none" stroke={isSel ? '#ef4444' : 'url(#g_' + ed.id + ')'} strokeWidth={isSel ? 10 : 7} strokeLinecap="round" opacity={isSel ? '.35' : '.18'} style={{ filter: isSel ? 'blur(4px)' : 'blur(3px)', pointerEvents: 'none' }} />
                    {/* main line */}
                    <path d={d} fill="none" stroke={isSel ? '#f87171' : 'url(#g_' + ed.id + ')'} strokeWidth={isSel ? 2.8 : 2.4} strokeLinecap="round" strokeDasharray={isSel ? '6 4' : 'none'} style={{ pointerEvents: 'none' }} />
                    {/* animated dashes — فقط وقتی انتخاب نشده */}
                    {!isSel && (
                      <path d={d} fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" opacity=".5" strokeDasharray="1 16" style={{ pointerEvents: 'none' }}>
                        <animate attributeName="strokeDashoffset" from="34" to="0" dur="1.4s" repeatCount="indefinite" />
                      </path>
                    )}
                    {/* ناحیه کلیک برای انتخاب خط — pointer-events:all تا stroke=transparent هم کار کنه */}
                    <path d={d} fill="rgba(0,0,0,0)" stroke="rgba(255,255,255,0.01)" strokeWidth="18"
                      style={{ pointerEvents: 'all', cursor: 'pointer' }}
                      onClick={(e) => { e.stopPropagation(); setSelEdgeId(isSel ? null : ed.id); selEdgeIdRef.current = isSel ? null : ed.id; setSelId(null); }} />
                    {/* دکمه حذف روی خط — وقتی انتخاب شده */}
                    {isSel && (
                      <g style={{ pointerEvents: 'all' }}>
                        <circle cx={mx} cy={my} r={14} fill="#1a0a0a" stroke="#ef4444" strokeWidth="1.5" style={{ cursor:'pointer', filter:'drop-shadow(0 0 6px #ef444488)', pointerEvents: 'all' }}
                          onClick={(e) => { e.stopPropagation(); deleteEdge(ed.id); setSelEdgeId(null); selEdgeIdRef.current = null; }} />
                        <text x={mx} y={my + 5} textAnchor="middle" fill="#f87171" fontSize="14" style={{ pointerEvents:'none', userSelect:'none' }}>✕</text>
                      </g>
                    )}
                  </g>
                );
              })}
              {/* ط®ط· ظ…ظˆظ‚طھ */}
              {connecting && (() => {
                const fn = findLN(connecting.from);
                if (!fn) return null;
                const s = outPortPos(fn, connecting.port);
                return <path d={edgePath(s, cursor)} fill="none" stroke="#8B5CF6" strokeWidth="2" strokeDasharray="5 4" opacity=".7" />;
              })()}

              {/* ── particles — انرژی نوری جابجایی لیدها ── */}
              {liveMode && particles.map(function(p) {
                var edge = edges.find(function(e) { return e.id === p.edgeId; });
                if (!edge) return null;
                var fn = findLN(edge.from), tn = findLN(edge.to);
                if (!fn || !tn) return null;
                var d = edgePath(outPortPos(fn, edge.fromPort), inPortPos(tn));
                var colors = ['#22c55e','#4ade80','#86efac','#34d399','#6ee7b7'];
                var col = colors[p.id % colors.length];
                var dur = '1.9s';
                var beg = p.delay + 'ms';
                return (
                  <g key={p.id} style={{ filter: 'drop-shadow(0 0 5px ' + col + ')' }}>
                    {/* ذره اصلی */}
                    <circle r="5" fill={col} opacity="0">
                      <animateMotion dur={dur} begin={beg} fill="remove" path={d} />
                      <animate attributeName="opacity" values="0;1;1;0.2;0" keyTimes="0;0.08;0.75;0.92;1" dur={dur} begin={beg} fill="remove" />
                      <animate attributeName="r" values="2;6;5;3;1" keyTimes="0;0.08;0.5;0.85;1" dur={dur} begin={beg} fill="remove" />
                    </circle>
                    {/* هاله داخلی سفید */}
                    <circle r="2.5" fill="#fff" opacity="0">
                      <animateMotion dur={dur} begin={beg} fill="remove" path={d} />
                      <animate attributeName="opacity" values="0;0.8;0.6;0;0" keyTimes="0;0.1;0.6;0.85;1" dur={dur} begin={beg} fill="remove" />
                    </circle>
                    {/* ذره دنباله */}
                    <circle r="3" fill={col} opacity="0">
                      <animateMotion dur={dur} begin={(p.delay + 120) + 'ms'} fill="remove" path={d} />
                      <animate attributeName="opacity" values="0;0.5;0.4;0;0" keyTimes="0;0.1;0.6;0.85;1" dur={dur} begin={(p.delay + 120) + 'ms'} fill="remove" />
                    </circle>
                  </g>
                );
              })}
            </svg>

            {/* نودها */}
            {layoutNodes.map(nd => {
              const def = FL_NODES[nd.type];
              if (!def) return null;
              const isDragged = nd.id === selId;
              const isSel     = nd.id === expandedId;
              const nodeColor = def.color;
              const bgGrad = {
                trigger:'linear-gradient(135deg,#f59e0b,#fb923c)', script:'linear-gradient(135deg,#3b82f6,#06b6d4)',
                followup:'linear-gradient(135deg,#f43f5e,#ec4899)', delay:'linear-gradient(135deg,#10b981,#22d3ee)',
                condition:'linear-gradient(135deg,#ec4899,#8b5cf6)', action:'linear-gradient(135deg,#7c3aed,#a78bfa)',
                end:'linear-gradient(135deg,#475569,#64748b)',
              }[nd.type] || `linear-gradient(135deg,${nodeColor},${nodeColor}88)`;

              // ══ نود شرط — دایره کوچک ══
              if (nd.type === 'condition') {
                const ports = nodeOutPorts(nd);
                const summary = nodeSummary(nd, { scripts, rules });
                return (
                  <div key={nd.id}
                    onMouseDown={(e) => startDrag(e, nd)}
                    onMouseUp={(e) => finishConnect(e, nd)}
                    onContextMenu={(e) => {
                      e.preventDefault(); e.stopPropagation(); setSelId(nd.id);
                      const rect = canvasRef.current.getBoundingClientRect();
                      const cx2 = (e.clientX - rect.left - view.x) / view.zoom;
                      const cy2 = (e.clientY - rect.top  - view.y) / view.zoom;
                      setCtxMenu({ mode: 'node', nodeId: nd.id, nodeType: nd.type, x: e.clientX, y: e.clientY, cx: cx2, cy: cy2 });
                    }}
                    style={{ position: 'absolute', left: nd.x, top: nd.y, userSelect: 'none', cursor: 'grab' }}>

                    {/* دایره */}
                    <div
                      onClick={(e) => { e.stopPropagation(); setFocusId(nd.id); }}
                      title="شرط — کلیک برای ویرایش"
                      style={{
                        width: FL_CD, height: FL_CD, borderRadius: '50%',
                        background: bgGrad,
                        border: `3px solid ${isDragged ? '#fff' : isSel ? '#fff' : nodeColor + 'cc'}`,
                        boxShadow: isSel
                          ? `0 0 0 4px ${nodeColor}44, 0 0 24px ${nodeColor}88`
                          : isDragged
                            ? `0 0 0 3px rgba(255,255,255,.2)`
                            : `0 0 0 3px ${nodeColor}22, 0 0 16px ${nodeColor}66, 0 8px 24px -6px rgba(0,0,0,.7)`,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        cursor: 'pointer', transition: 'box-shadow .2s',
                      }}>
                      <Icon name={def.icon} size={20} color="#fff" />
                    </div>

                    {/* live badge روی نود شرط */}
                    {liveMode && liveData && liveData.nodeStats && liveData.nodeStats[nd.id] && liveData.nodeStats[nd.id].count > 0 && (
                      <div
                        onClick={function(e) {
                          e.stopPropagation();
                          var ns = liveData.nodeStats[nd.id];
                          setLivePanel({ nodeId: nd.id, label: nd.label || (FL_NODES[nd.type] && FL_NODES[nd.type].label) || 'نود' });
                          setPanelLeads(ns.leads || []);
                          setSelConv(null);
                          setConvMsgs([]);
                        }}
                        title={liveData.nodeStats[nd.id].count + ' نفر — کلیک برای مکالمات'}
                        style={{
                          position: 'absolute', top: -14, left: '50%',
                          transform: 'translateX(-50%)',
                          display: 'flex', alignItems: 'center', gap: 4,
                          padding: '3px 9px', borderRadius: 999, cursor: 'pointer',
                          background: 'linear-gradient(135deg,#16a34a,#15803d)',
                          border: '1.5px solid #22c55e',
                          fontSize: 10, fontWeight: 800, color: '#fff',
                          boxShadow: '0 0 14px rgba(34,197,94,0.7)',
                          zIndex: 10, whiteSpace: 'nowrap',
                          animation: 'livePop .35s ease',
                        }}>
                        <span style={{ width:6, height:6, borderRadius:'50%', background:'#fff', animation:'livePulse 1.5s ease-in-out infinite', flexShrink:0 }} />
                        {liveData.nodeStats[nd.id].count} نفر
                      </div>
                    )}

                    {/* لیبل */}
                    <div style={{ marginTop: 5, textAlign: 'center', fontSize: 10, fontWeight: 700,
                      color: nodeColor, whiteSpace: 'nowrap', textShadow: `0 0 8px ${nodeColor}88` }}>
                      {nd.label || 'شرط'}
                    </div>
                    {summary && (
                      <div style={{ textAlign: 'center', fontSize: 9, color: 'rgba(232,235,255,.4)',
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: FL_CD + 40, marginLeft: -20 }}>
                        {summary}
                      </div>
                    )}

                    {/* پورت ورودی بالا */}
                    <div style={{ position: 'absolute', top: -7, left: FL_CR - 7,
                      width: 14, height: 14, borderRadius: '50%', background: '#0c0b24',
                      border: '2px solid #60A5FA', zIndex: 3,
                      boxShadow: '0 0 0 3px rgba(96,165,250,.15), 0 0 8px rgba(96,165,250,.5)' }} />

                    {/* ۲ پورت خروجی پایین */}
                    {ports.map(pp => {
                      const pos = outPortPos(nd, pp.id);
                      return (
                        <div key={pp.id}
                          onMouseDown={(e) => { e.stopPropagation(); startConnect(e, nd, pp.id); }}
                          title={pp.label}
                          style={{ position: 'absolute', top: FL_CD - 7, left: pos.x - nd.x - 7,
                            width: 14, height: 14, borderRadius: '50%', background: '#0c0b24',
                            border: `2.5px solid ${pp.color}`, cursor: 'crosshair', zIndex: 3,
                            boxShadow: `0 0 0 3px ${pp.color}22, 0 0 10px ${pp.color}88` }}>
                          <span style={{ position: 'absolute', top: 15, left: '50%', transform: 'translateX(-50%)',
                            fontSize: 9, fontWeight: 700, color: pp.color, whiteSpace: 'nowrap', pointerEvents: 'none' }}>
                            {pp.label}
                          </span>
                        </div>
                      );
                    })}
                  </div>
                );
              }

              // ══ سایر نودها — کارت معمولی ══
              return (
                <div key={nd.id}
                  onMouseDown={(e) => startDrag(e, nd)}
                  onMouseUp={(e) => finishConnect(e, nd)}
                  onContextMenu={(e) => {
                    e.preventDefault(); e.stopPropagation(); setSelId(nd.id);
                    const rect = canvasRef.current.getBoundingClientRect();
                    const cx = (e.clientX - rect.left - view.x) / view.zoom;
                    const cy = (e.clientY - rect.top  - view.y) / view.zoom;
                    setCtxMenu({ mode: 'node', nodeId: nd.id, nodeType: nd.type, x: e.clientX, y: e.clientY, cx, cy });
                  }}
                  style={{ position: 'absolute', left: nd.x, top: nd.y, width: isSel ? 392 : FL_W,
                    borderRadius: 20, transition: 'width .35s cubic-bezier(.22,1,.36,1), border-color .2s, box-shadow .2s',
                    background: 'linear-gradient(158deg,rgba(255,255,255,.085),rgba(255,255,255,.055))',
                    backdropFilter: 'blur(24px) saturate(150%)', WebkitBackdropFilter: 'blur(24px) saturate(150%)',
                    border: `1px solid ${isSel ? nodeColor + 'cc' : isDragged ? 'rgba(255,255,255,.32)' : 'rgba(255,255,255,.13)'}`,
                    boxShadow: isSel
                      ? `0 0 0 2px ${nodeColor}55, 0 30px 80px -22px rgba(40,30,120,.8), inset 0 1px 0 rgba(255,255,255,.2)`
                      : isDragged ? '0 0 0 1px rgba(255,255,255,.2), 0 24px 60px -20px rgba(5,4,30,.9)'
                      : '0 24px 60px -24px rgba(5,4,30,.85), 0 4px 14px -6px rgba(5,4,30,.6), inset 0 1px 0 rgba(255,255,255,.16)',
                    cursor: 'grab', userSelect: 'none' }}>

                  {/* live badge بالای نود */}
                  {liveMode && liveData && liveData.nodeStats && liveData.nodeStats[nd.id] && liveData.nodeStats[nd.id].count > 0 && (
                    <div
                      onClick={function(e) {
                        e.stopPropagation();
                        var ns = liveData.nodeStats[nd.id];
                        setLivePanel({ nodeId: nd.id, label: nd.label || (FL_NODES[nd.type] && FL_NODES[nd.type].label) || 'نود' });
                        setPanelLeads(ns.leads || []);
                        setSelConv(null);
                        setConvMsgs([]);
                      }}
                      title={liveData.nodeStats[nd.id].count + ' نفر — کلیک برای مکالمات'}
                      style={{
                        position: 'absolute', top: -20, left: '50%',
                        transform: 'translateX(-50%)',
                        display: 'flex', alignItems: 'center', gap: 5,
                        padding: '4px 12px', borderRadius: 999, cursor: 'pointer', zIndex: 10,
                        background: 'linear-gradient(135deg,#16a34a,#15803d)',
                        border: '1.5px solid #4ade80',
                        fontSize: 11, fontWeight: 800, color: '#fff',
                        boxShadow: '0 0 16px rgba(34,197,94,0.65), 0 2px 8px rgba(0,0,0,0.4)',
                        whiteSpace: 'nowrap',
                        animation: 'livePop .35s ease',
                      }}>
                      <span style={{ width:7, height:7, borderRadius:'50%', background:'#86efac', animation:'livePulse 1.5s ease-in-out infinite', flexShrink:0, boxShadow:'0 0 6px #86efac' }} />
                      <span style={{ animation: 'liveCountPop .4s ease' }}>{liveData.nodeStats[nd.id].count}</span>
                      <span style={{ opacity:0.8, fontSize:10 }}>نفر</span>
                    </div>
                  )}

                  {/* پورت ورودی */}
                  {def.inPort && (
                    <div style={{ position: 'absolute', top: -8, left: FL_W / 2 - 8,
                      width: 16, height: 16, borderRadius: '50%', background: '#0c0b24',
                      border: '2px solid rgba(96,165,250,.85)', zIndex: 3,
                      boxShadow: '0 0 0 4px rgba(96,165,250,.12), 0 0 12px rgba(96,165,250,.5)' }} />
                  )}
                  {/* rail رنگی سمت چپ */}
                  <div style={{ position: 'absolute', top: 14, bottom: 14, right: 0, width: 3, borderRadius: 3,
                    background: bgGrad }} />
                  {/* header */}
                  <div onClick={(e) => e.stopPropagation()}
                    style={{ padding: '14px 16px 14px 14px', display: 'flex', alignItems: 'center', gap: 10 }}>
                    <div style={{ flexShrink: 0, width: 38, height: 38, borderRadius: 12, display: 'flex',
                      alignItems: 'center', justifyContent: 'center',
                      background: bgGrad,
                      boxShadow: '0 6px 16px -6px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.35)' }}>
                      <Icon name={def.icon} size={17} color="#fff" />
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div onClick={(e) => { e.stopPropagation(); setFocusId(nd.id); }}
                        title="کلیک برای ویرایش کامل"
                        style={{ fontSize: 14, fontWeight: 700, color: '#eef0ff', letterSpacing: '-.2px',
                          cursor: 'pointer', display: 'inline-block' }}
                        onMouseEnter={e => e.currentTarget.style.color = '#c4b5fd'}
                        onMouseLeave={e => e.currentTarget.style.color = '#eef0ff'}>
                        {nd.label || def.label}
                      </div>
                      <div style={{ fontSize: 11, color: 'rgba(232,235,255,.55)', marginTop: 2,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {nodeSummary(nd, { scripts, rules })}
                      </div>
                    </div>
                    {/* chevron */}
                    <div onClick={(e) => { e.stopPropagation(); setExpandedId(expandedId === nd.id ? null : nd.id); }}
                      style={{ flexShrink: 0, width: 24, height: 24, borderRadius: 8, display: 'flex',
                        alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
                        background: isSel ? `${nodeColor}25` : 'rgba(255,255,255,.05)',
                        border: `1px solid ${isSel ? nodeColor + '55' : 'rgba(255,255,255,.08)'}`,
                        color: isSel ? nodeColor : 'rgba(232,235,255,.45)', transition: '.25s' }}>
                      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2"
                        strokeLinecap="round" strokeLinejoin="round"
                        style={{ width: 13, height: 13, transform: isSel ? 'rotate(180deg)' : 'none', transition: 'transform .35s' }}>
                        <path d="m6 9 6 6 6-6"/>
                      </svg>
                    </div>
                  </div>
                  {/* body collapsed */}
                  {!isSel && (
                    <div style={{ padding: '0 16px 15px', display: 'flex', alignItems: 'center', gap: 7,
                      fontSize: '11.5px', color: 'rgba(232,235,255,.40)' }}>
                      <span style={{ flexShrink:0, width:6, height:6, borderRadius:'50%',
                        background: nodeColor, boxShadow: `0 0 8px ${nodeColor}` }} />
                      {nodeSummary(nd, { scripts, rules })}
                    </div>
                  )}
                  {/* body expanded */}
                  <div onMouseDown={e => e.stopPropagation()}
                    style={{ overflow: 'hidden', maxHeight: isSel ? '800px' : '0', opacity: isSel ? 1 : 0,
                      transition: 'max-height .45s cubic-bezier(.22,1,.36,1), opacity .35s',
                      padding: '0 16px', paddingBottom: isSel ? 16 : 0 }}>
                    <div style={{ borderTop: '1px solid rgba(255,255,255,.08)', paddingTop: 14,
                      display: 'flex', flexDirection: 'column', gap: 13 }}>
                      <NodeConfig key={nd.id} node={nd}
                        ctx={{ scripts, rules, triggers, fields: meta.fields, states: meta.states, libFiles }}
                        condType=""
                        upstreamScriptId=""
                        onChange={(patch) => updateNode(nd.id, patch)}
                        onAddNext={(port, type) => addConnectedNode(nd.id, port, type)}
                        onDelete={() => deleteNode(nd.id)} />
                    </div>
                  </div>
                  {/* پورت‌های خروجی */}
                  {nodeOutPorts(nd).map(pp => {
                    const p = outPortPos(nd, pp.id);
                    return (
                      <div key={pp.id}
                        onMouseDown={(e) => { e.stopPropagation(); startConnect(e, nd, pp.id); }}
                        title={pp.label || 'اتصال خروجی'}
                        style={{ position: 'absolute', bottom: -8, left: (p.x - nd.x) - 8,
                          width: 16, height: 16, borderRadius: '50%', background: '#0c0b24',
                          border: `2px solid ${pp.color}`, cursor: 'crosshair', zIndex: 3,
                          boxShadow: `0 0 0 4px ${pp.color}22, 0 0 12px ${pp.color}88` }}>
                        {pp.label && (
                          <span style={{ position: 'absolute', top: 17, left: '50%', transform: 'translateX(-50%)',
                            fontSize: 10, fontWeight: 700, color: pp.color, whiteSpace: 'nowrap' }}>{pp.label}</span>
                        )}
                      </div>
                    );
                  })}
                </div>
              );
            })}
          </div>

          {/* ط±ط§ظ‡ظ†ظ…ط§غŒ ط®ط§ظ„غŒ */}
          {nodes.length === 0 && (
            <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
              alignItems: 'center', justifyContent: 'center', color: 'var(--text-3)', pointerEvents: 'none' }}>
              <Icon name="workflow" size={40} color="var(--text-3)" />
              <div style={{ marginTop: 12, fontSize: 13 }}>یک کمپین انتخاب کن، بعد راست‌کلیک کن تا نود اضافه کنی</div>
              <div style={{ marginTop: 4, fontSize: 11 }}>پورت پایینی نود را بگیر و روی نود بعدی رها کن تا وصل شوند</div>
            </div>
          )}

          {/* ظ¾ظ†ظ„ ظ†طھغŒط¬ظ‡ظ" ط´ط¨غŒظ‡â€Œط³ط§ط²غŒ */}
          {simResult && (
            <div style={{ position: 'absolute', left: 14, bottom: 14, width: 320, maxHeight: '70%',
              background: 'var(--bg-1, #14141c)', border: '1px solid rgba(52,211,153,0.3)', borderRadius: 12,
              boxShadow: '0 8px 32px rgba(0,0,0,0.5)', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
              <div style={{ padding: '10px 14px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                borderBottom: '1px solid rgba(255,255,255,0.07)' }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: '#34D399' }}>مسیر شبیه‌سازی</span>
                <button onClick={() => setSimResult(null)} style={{ background: 'none', border: 'none', color: 'var(--text-3)', cursor: 'pointer', fontSize: 16 }}>أ—</button>
              </div>
              <div style={{ padding: 12, overflowY: 'auto' }}>
                {!simResult.ok ? (
                  <div style={{ color: '#F87171', fontSize: 12 }}>{simResult.error || 'خطا'}</div>
                ) : (
                  <>
                    <div style={{ fontSize: 11, color: 'var(--text-3)', marginBottom: 10 }}>
                      لید: <b style={{ color: 'var(--text-1)' }}>{simResult.lead?.name}</b> ({simResult.lead?.status})
                    </div>
                    {(simResult.path || []).map((step, i) => (
                      <div key={i} style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
                        <div style={{ width: 22, height: 22, borderRadius: '50%', background: 'rgba(52,211,153,0.15)',
                          color: '#34D399', fontSize: 11, fontWeight: 700, display: 'flex', alignItems: 'center',
                          justifyContent: 'center', flexShrink: 0, fontFamily: 'JetBrains Mono' }}>{i + 1}</div>
                        <div style={{ flex: 1 }}>
                          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-1)' }}>{step.label || step.type}</div>
                          {step.note && <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 1 }}>{step.note}</div>}
                        </div>
                      </div>
                    ))}
                    {(!simResult.path || simResult.path.length === 0) && (
                      <div style={{ fontSize: 12, color: 'var(--text-3)' }}>مسیری پیدا نشد؛ تریگر به جایی وصل نیست.</div>
                    )}
                  </>
                )}
              </div>
            </div>
          )}
        </div>

      </div>}  {/* end design tab */}

      {/* ── Focus Mode — Full Screen ── */}
      {focusId && (() => {
        const fn = nodes.find(n => n.id === focusId);
        if (!fn) return null;
        const def = FL_NODES[fn.type] || {};
        const nodeColor = def.color || '#818CF8';
        const gradMap = {
          trigger:  'linear-gradient(135deg,#f59e0b,#fb923c)',
          script:   'linear-gradient(135deg,#3b82f6,#06b6d4)',
          followup: 'linear-gradient(135deg,#f43f5e,#ec4899)',
          delay:    'linear-gradient(135deg,#10b981,#22d3ee)',
          condition:'linear-gradient(135deg,#6366f1,#8b5cf6)',
          action:   'linear-gradient(135deg,#7c3aed,#a78bfa)',
          end:      'linear-gradient(135deg,#475569,#64748b)',
        };
        const grad = gradMap[fn.type] || `linear-gradient(135deg,${nodeColor},${nodeColor}88)`;
        const summary = nodeSummary(fn, { scripts, rules });
        const connectedEdges = edges.filter(e => e.from === fn.id || e.to === fn.id);

        return (
          <>
            {/* backdrop */}
            <div onClick={() => setFocusId(null)}
              style={{ position:'fixed', inset:0, zIndex:1000,
                background:'rgba(4,3,20,.82)', backdropFilter:'blur(8px)', WebkitBackdropFilter:'blur(8px)' }} />

            {/* full-screen panel */}
            <div style={{ position:'fixed', inset:'20px', zIndex:1001, display:'flex', flexDirection:'column',
              borderRadius:24,
              background:'linear-gradient(158deg,rgba(22,18,55,.97),rgba(12,10,34,.98))',
              border:'1px solid rgba(255,255,255,.12)',
              boxShadow:'0 40px 120px -30px rgba(4,3,20,.98), 0 0 0 1px '+nodeColor+'22',
              overflow:'hidden' }}>

              {/* ── top bar ── */}
              <div style={{ display:'flex', alignItems:'center', gap:14, padding:'16px 24px',
                borderBottom:'1px solid rgba(255,255,255,.08)', flexShrink:0,
                background:'rgba(255,255,255,.025)' }}>
                {/* آیکون نود */}
                <div style={{ flexShrink:0, width:48, height:48, borderRadius:16, display:'flex',
                  alignItems:'center', justifyContent:'center', color:'#fff',
                  background: grad,
                  boxShadow:'0 10px 28px -10px rgba(0,0,0,.7), inset 0 1px 0 rgba(255,255,255,.4)' }}>
                  <Icon name={def.icon} size={22} color="#fff" />
                </div>
                {/* عنوان */}
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ display:'flex', alignItems:'center', gap:10 }}>
                    <span style={{ fontSize:18, fontWeight:700, color:'#eef0ff' }}>{fn.label || def.label}</span>
                    <span style={{ fontSize:11, padding:'2px 10px', borderRadius:20,
                      background:nodeColor+'22', color:nodeColor, border:'1px solid '+nodeColor+'44', fontWeight:600 }}>
                      {def.label}
                    </span>
                  </div>
                  <div style={{ fontSize:12, color:'rgba(232,235,255,.5)', marginTop:3 }}>{def.desc}</div>
                </div>
                {/* وضعیت */}
                <div style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 16px', borderRadius:12,
                  background:'rgba(255,255,255,.04)', border:'1px solid rgba(255,255,255,.08)' }}>
                  <span style={{ width:7, height:7, borderRadius:'50%',
                    background: summary && !summary.includes('نشده') ? '#34d399' : '#fbbf24',
                    boxShadow: '0 0 8px currentcolor', flexShrink:0 }} />
                  <span style={{ fontSize:12, color:'rgba(232,235,255,.7)' }}>{summary || 'تنظیم نشده'}</span>
                </div>
                {/* بستن */}
                <button onClick={() => setFocusId(null)}
                  style={{ flexShrink:0, width:36, height:36, borderRadius:12,
                    border:'1px solid rgba(255,255,255,.12)',
                    background:'rgba(255,255,255,.06)', color:'rgba(232,235,255,.6)', cursor:'pointer', fontSize:18,
                    display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'inherit',
                    transition:'.2s' }}
                  onMouseEnter={e => { e.currentTarget.style.background='rgba(248,113,113,.2)'; e.currentTarget.style.color='#fb7185'; }}
                  onMouseLeave={e => { e.currentTarget.style.background='rgba(255,255,255,.06)'; e.currentTarget.style.color='rgba(232,235,255,.6)'; }}>
                  ✕
                </button>
              </div>

              {/* ── body: دوستونه ── */}
              <div style={{ flex:1, display:'flex', minHeight:0, direction:'rtl' }}>

                {/* ستون راست — فرم تنظیمات */}
                <div style={{ flex:1, overflowY:'auto', padding:'28px 32px',
                  borderLeft:'1px solid rgba(255,255,255,.06)' }}>
                  <div style={{ maxWidth:560, margin:'0 auto' }}>
                    <NodeConfig key={fn.id + '_focus'} node={fn}
                      ctx={{ scripts, rules, triggers, fields: meta.fields, states: meta.states, libFiles }}
                      condType={(layoutNodes.find(n=>n.id===fn.id)||{})._condType || ''}
                      upstreamScriptId={(() => {
                        if (fn.type !== 'condition') return '';
                        for (const e of edges.filter(ed => ed.to === fn.id)) {
                          const src = nodes.find(n => n.id === e.from);
                          if (src && src.type === 'script' && src.config && src.config.script_id) return src.config.script_id;
                        }
                        return '';
                      })()}
                      onChange={(patch) => updateNode(fn.id, patch)}
                      onAddNext={(port, type) => { addConnectedNode(fn.id, port, type); setFocusId(null); }}
                      onDelete={() => { deleteNode(fn.id); setFocusId(null); }} />
                  </div>
                </div>

                {/* ستون چپ — اطلاعات + اتصالات */}
                <div style={{ width:280, flexShrink:0, overflowY:'auto', padding:'24px 20px',
                  background:'rgba(0,0,0,.2)', display:'flex', flexDirection:'column', gap:16 }}>

                  {/* نود ID */}
                  <div style={{ padding:'12px 14px', borderRadius:12, background:'rgba(255,255,255,.03)', border:'1px solid rgba(255,255,255,.07)' }}>
                    <div style={{ fontSize:10, color:'rgba(232,235,255,.4)', marginBottom:4, fontWeight:600 }}>شناسه نود</div>
                    <div style={{ fontSize:11, color:'rgba(232,235,255,.5)', fontFamily:'monospace', wordBreak:'break-all' }}>{fn.id}</div>
                  </div>

                  {/* موقعیت */}
                  <div style={{ padding:'12px 14px', borderRadius:12, background:'rgba(255,255,255,.03)', border:'1px solid rgba(255,255,255,.07)' }}>
                    <div style={{ fontSize:10, color:'rgba(232,235,255,.4)', marginBottom:8, fontWeight:600 }}>موقعیت روی کانواس</div>
                    <div style={{ display:'none', gap:10 }}>
                      {[['X', Math.round(fn.x)], ['Y', Math.round(fn.y)]].map(([k, v]) => (
                        <div key={k} style={{ flex:1, textAlign:'center', padding:'8px', borderRadius:8, background:'rgba(255,255,255,.04)' }}>
                          <div style={{ fontSize:10, color:'rgba(232,235,255,.4)' }}>{k}</div>
                          <div style={{ fontSize:14, fontWeight:700, color:'rgba(232,235,255,.8)', fontFamily:'monospace' }}>{v}</div>
                        </div>
                      ))}
                    </div>
                  </div>

                  {/* اتصالات */}
                  {connectedEdges.length > 0 && (
                    <div style={{ padding:'12px 14px', borderRadius:12, background:'rgba(255,255,255,.03)', border:'1px solid rgba(255,255,255,.07)' }}>
                      <div style={{ fontSize:10, color:'rgba(232,235,255,.4)', marginBottom:10, fontWeight:600 }}>
                        اتصالات ({connectedEdges.length})
                      </div>
                      <div style={{ display:'flex', flexDirection:'column', gap:6 }}>
                        {connectedEdges.map(ed => {
                          const isOut = ed.from === fn.id;
                          const otherId = isOut ? ed.to : ed.from;
                          const other = nodes.find(n => n.id === otherId);
                          const otherDef = other ? FL_NODES[other.type] : null;
                          const portColor = ed.fromPort === 'yes' ? '#34d399' : ed.fromPort === 'no' ? '#fb7185' : nodeColor;
                          return (
                            <div key={ed.id} style={{ display:'flex', alignItems:'center', gap:8, padding:'7px 10px',
                              borderRadius:9, background:'rgba(255,255,255,.03)', border:'1px solid rgba(255,255,255,.06)' }}>
                              <span style={{ fontSize:10, padding:'1px 7px', borderRadius:8, fontWeight:700,
                                background: (isOut ? nodeColor : (otherDef?.color||'#818cf8')) + '22',
                                color: isOut ? nodeColor : (otherDef?.color||'#818cf8') }}>
                                {isOut ? '← خروج' : '→ ورود'}
                              </span>
                              {ed.fromPort && ed.fromPort !== 'out' && (
                                <span style={{ fontSize:10, fontWeight:700, color:portColor }}>{ed.fromPort==='yes'?'بله':ed.fromPort==='no'?'خیر':ed.fromPort}</span>
                              )}
                              <span style={{ flex:1, fontSize:11, color:'rgba(232,235,255,.6)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                                {other ? (other.label || otherDef?.label || other.type) : otherId}
                              </span>
                            </div>
                          );
                        })}
                      </div>
                    </div>
                  )}

                  {/* راهنما */}
                  <div style={{ padding:'12px 14px', borderRadius:12, background:nodeColor+'08', border:'1px solid '+nodeColor+'20' }}>
                    <div style={{ fontSize:10, color:nodeColor, fontWeight:600, marginBottom:6 }}>💡 راهنما</div>
                    <div style={{ fontSize:11, color:'rgba(232,235,255,.5)', lineHeight:1.8 }}>
                      {def.desc}<br/>
                      {fn.type === 'condition' && 'شرط‌ها از بالا به پایین بررسی می‌شوند.'}
                      {fn.type === 'script'    && 'اسکریپت پس از اتمام، به پورت خروجی می‌رود.'}
                      {fn.type === 'delay'     && 'مکث قبل از اجرای نود بعدی.'}
                      {fn.type === 'action'    && 'اکشن فوری اجرا می‌شود.'}
                      {fn.type === 'trigger'   && 'نقطه شروع فلو — فقط یکی لازم است.'}
                      {fn.type === 'followup'  && 'پیام پیگیری خودکار ارسال می‌شود.'}
                    </div>
                  </div>

                  {/* دکمه بستن */}
                  <button onClick={() => setFocusId(null)}
                    style={{ width:'100%', padding:'12px', borderRadius:12, border:'1px solid rgba(255,255,255,.1)',
                      background:'rgba(255,255,255,.05)', color:'rgba(232,235,255,.7)', cursor:'pointer',
                      fontFamily:'inherit', fontSize:13, fontWeight:600, transition:'.2s' }}
                    onMouseEnter={e => e.currentTarget.style.background='rgba(255,255,255,.1)'}
                    onMouseLeave={e => e.currentTarget.style.background='rgba(255,255,255,.05)'}>
                    ← بازگشت به کانواس
                  </button>
                </div>
              </div>
            </div>
          </>
        );
      })()}

      {/* منوی راست‌کلیک */}
      {ctxMenu && (() => {
        const close = () => setCtxMenu(null);
        // تنظیم موقعیت (جلوگیری از رفتن خارج از صفحه)
        const menuW = 210, menuMaxH = 400;
        const left = Math.min(ctxMenu.x, window.innerWidth  - menuW - 8);
        const top  = Math.min(ctxMenu.y, window.innerHeight - menuMaxH - 8);

        const NodeRow = ({ type }) => {
          const v = FL_NODES[type];
          if (!v) return null;
          return (
            <div
              onClick={() => {
                if (ctxMenu.mode === 'canvas') {
                  addNode(type, ctxMenu.cx, ctxMenu.cy);
                } else {
                  addConnectedNode(ctxMenu.nodeId, 'out', type);
                }
                close();
              }}
              style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
                cursor: 'pointer', borderRadius: 7, direction: 'rtl' }}
              onMouseEnter={e => e.currentTarget.style.background = `${v.color}22`}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              <span style={{ width: 24, height: 24, borderRadius: 6, background: v.color, flexShrink: 0,
                display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: `0 0 6px ${v.color}88` }}>
                <Icon name={v.icon} size={13} color="#fff" />
              </span>
              <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-1)' }}>{v.label}</span>
              <span style={{ fontSize: 10, color: 'var(--text-3)', flex: 1, textAlign: 'left', marginLeft: 4 }}>{v.desc}</span>
            </div>
          );
        };

        return (
          <>
            <div onClick={close} onContextMenu={e => { e.preventDefault(); close(); }}
              style={{ position: 'fixed', inset: 0, zIndex: 998 }} />
            <div style={{ position: 'fixed', top, left, zIndex: 999, width: menuW,
              background: '#12111e', border: '1px solid rgba(139,92,246,0.25)', borderRadius: 12,
              boxShadow: '0 16px 48px rgba(0,0,0,0.7)', overflow: 'hidden', direction: 'rtl', padding: 6 }}>

              {/* ── چیدمان — همیشه نمایش داده می‌شود ── */}
              <div style={{ padding: '6px 10px 4px', fontSize: 10, color: 'var(--text-3)',
                borderBottom: '1px solid rgba(255,255,255,0.07)', marginBottom: 4,
                display: 'flex', alignItems: 'center', gap: 6 }}>
                <Icon name="layout-grid" size={11} color="var(--text-3)" />
                چیدمان
              </div>
              {[
                { icon: 'arrow-right', label: 'افقی (چپ به راست)', desc: 'ترتیب طبیعی فلو', fn: autoLayout },
                { icon: 'arrow-down',  label: 'عمودی (بالا به پایین)', desc: 'مناسب فانل‌های بلند', fn: autoLayoutV },
              ].map(item => (
                <div key={item.label}
                  onClick={() => { if (nodes.length) { item.fn(); } close(); }}
                  style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '7px 12px',
                    cursor: nodes.length ? 'pointer' : 'not-allowed',
                    opacity: nodes.length ? 1 : 0.4, borderRadius: 7, direction: 'rtl' }}
                  onMouseEnter={e => { if (nodes.length) e.currentTarget.style.background = 'rgba(139,92,246,0.12)'; }}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                  <span style={{ width: 24, height: 24, borderRadius: 6, background: 'rgba(139,92,246,0.18)', flexShrink: 0,
                    display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <Icon name={item.icon} size={12} color="#8B5CF6" />
                  </span>
                  <span style={{ flex: 1 }}>
                    <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-1)' }}>{item.label}</div>
                    <div style={{ fontSize: 10, color: 'var(--text-3)' }}>{item.desc}</div>
                  </span>
                </div>
              ))}

              {/* ── افزودن نود (فقط وقتی کمپین انتخاب شده) ── */}
              {campId && <>
                <div style={{ padding: '6px 10px 4px', fontSize: 10, color: 'var(--text-3)',
                  borderTop: '1px solid rgba(255,255,255,0.07)', borderBottom: '1px solid rgba(255,255,255,0.07)',
                  marginTop: 4, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
                  <Icon name="plus" size={11} color="var(--text-3)" />
                  {ctxMenu.mode === 'canvas' ? 'افزودن نود' : 'نود بعدی'}
                </div>
                {Object.entries(groups).map(([gname, items]) => (
                  <div key={gname} style={{ marginBottom: 4 }}>
                    <div style={{ padding: '3px 10px', fontSize: 9, fontWeight: 700,
                      color: 'var(--text-3)', letterSpacing: 0.5 }}>{gname}</div>
                    {items.map(([k]) => <NodeRow key={k} type={k} />)}
                  </div>
                ))}
              </>}

              {/* ── حذف نود (فقط حالت نود) ── */}
              {ctxMenu.mode === 'node' && (
                <>
                  <div style={{ borderTop: '1px solid rgba(255,255,255,0.07)', margin: '4px 0' }} />
                  <div
                    onClick={() => { deleteNode(ctxMenu.nodeId); close(); }}
                    style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px',
                      cursor: 'pointer', borderRadius: 7, color: '#F87171', direction: 'rtl' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'rgba(248,113,113,0.12)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                    <Icon name="trash-2" size={13} color="#F87171" />
                    <span style={{ fontSize: 12, fontWeight: 600 }}>حذف نود</span>
                  </div>
                </>
              )}
            </div>
          </>
        );
      })()}
    </div>
  );
};

// خلاصهٔ نمایش روی کارت نود
function nodeSummary(nd, ctx) {
  const c = nd.config || {};
  switch (nd.type) {
    case 'trigger':   return (TRIGGER_EVENTS.find(e => e.id === c.event)?.label) || 'تنظیم نشده';
    case 'script':
      return (ctx.scripts.find(s => s.id === c.script_id)?.name) || 'اسکریپت انتخاب نشده';
    case 'followup':  return (ctx.rules.find(r => r.id === c.rule_id)?.name) || 'قانون انتخاب نشده';
    case 'delay':     return c.hours ? `${c.hours} ساعت صبر` : 'مدت تنظیم نشده';
    case 'condition': {
      const t = nd._condType || c.condType;
      if (t === 'field') return c.field ? `«${c.field}» ${c.op === 'empty' ? 'نداده' : 'داده'}؟` : '📋 کدام فیلد؟';
      if (t === 'state') {
        if (c.stateOp === 'stuck') return `⏳ ${c.hours || '?'}ساعت در «${c.value || '...'}»`;
        return c.value ? `📍 ${c.stateOp === 'neq' ? 'نه ' : ''}«${c.value}»` : '📍 کدام مرحله؟';
      }
      const s = flConditionSentence(c);
      return s || 'شرط تنظیم نشده';
    }
    case 'action':    return (ACTION_KINDS.find(a => a.id === c.kind)?.label) || 'اکشن تنظیم نشده';
    case 'end':       return 'پایان فلو';
    default:          return '';
  }
}

// ظ¾ظ†ظ„ طھظ†ط¸غŒظ…ط§طھ غŒع© ظ†ظˆط¯
const NodeConfig = ({ node, ctx, condType, upstreamScriptId, onChange, onAddNext, onDelete }) => {
  const { Icon } = window.SB_UI;
  const def = FL_NODES[node.type];
  const c = node.config || {};
  const setCfg = (patch) => onChange({ config: patch });
  const parseFields = (raw) => { try { const a = JSON.parse(raw || '[]'); return Array.isArray(a) ? a : []; } catch { return []; } };
  const scopeScriptId = node.type === 'script' ? c.script_id : (node.type === 'condition' ? upstreamScriptId : '');
  const [scriptStates, setScriptStates] = useState([]);
  useEffect(() => {
    if (scopeScriptId) {
      window.api('/scripts/' + scopeScriptId + '/map').then(d => {
        if (d && d.success && d.data) setScriptStates(d.data.states || []);
        else setScriptStates([]);
      }).catch(() => setScriptStates([]));
    } else setScriptStates([]);
  }, [scopeScriptId]);
  const scoped = node.type === 'condition' && scriptStates.length > 0;
  const scopedScriptName = scoped ? (ctx.scripts.find(s => s.id === scopeScriptId)?.name || '') : '';
  const fieldOptions = scoped ? [...new Set(scriptStates.flatMap(s => parseFields(s.data_collect)))] : (ctx.fields || []);
  const stateOptions = scoped ? scriptStates.map(s => s.name) : (ctx.states || []);
  const SUCCESSORS = ['script', 'followup', 'delay', 'condition', 'action', 'end'];

  return (
    <div>
      {/* ─── برچسب ─── */}
      <label style={lblStyle}>برچسب (اختیاری)</label>
      <input value={node.label || ''} onChange={e => onChange({ label: e.target.value })}
        placeholder="نام دلخواه برای این نود" style={inpStyle} />

      {/* ─── تریگر ─── */}
      {node.type === 'trigger' && (<>
        <label style={{...lblStyle, marginTop:13}}>رویداد شروع</label>
        <select value={c.event||''} onChange={e => setCfg({ event: e.target.value })} style={inpStyle}>
          <option value="">— انتخاب —</option>
          {TRIGGER_EVENTS.map(ev => <option key={ev.id} value={ev.id}>{ev.label}</option>)}
        </select>
        {c.event === 'keyword' && (<>
          <label style={{...lblStyle, marginTop:13}}>کلیدواژه</label>
          <select value={c.keyword||''} onChange={e => setCfg({ keyword: e.target.value })} style={inpStyle}>
            <option value="">— انتخاب تریگر —</option>
            {ctx.triggers.map(t => <option key={t.id} value={t.keyword}>{t.trigger_name || t.keyword}</option>)}
          </select>
        </>)}
      </>)}

      {/* ─── اسکریپت ─── */}
      {node.type === 'script' && (<>
        <label style={{...lblStyle, marginTop:13}}>اسکریپت</label>
        <select value={c.script_id||''} onChange={e => setCfg({ script_id: e.target.value })} style={inpStyle}>
          <option value="">— انتخاب اسکریپت —</option>
          {ctx.scripts.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
        </select>
        {c.script_id && (
          <div style={{ marginTop:10, padding:'10px 12px', borderRadius:12, background:'rgba(255,255,255,.03)', border:'1px solid rgba(255,255,255,.07)' }}>
            <div style={{ fontSize:'11.5px', color:'rgba(232,235,255,.58)', lineHeight:1.7 }}>
              💡 بعد از این نود یه <strong style={{color:'#EC4899'}}>شرط</strong> اضافه کن تا بر اساس پاسخ‌های کاربر شاخه بزنی.
            </div>
            {scriptStates.length > 0 && (
              <div style={{ marginTop:8, paddingTop:8, borderTop:'1px solid rgba(255,255,255,.06)' }}>
                <div style={{ fontSize:10, color:'rgba(232,235,255,.4)', marginBottom:5 }}>مراحل:</div>
                <div style={{ display:'flex', flexWrap:'wrap', gap:4 }}>
                  {scriptStates.map((st,i) => (
                    <span key={st.id} style={{ fontSize:10, padding:'2px 8px', borderRadius:20,
                      background:(st.color||'#6366F1')+'20', color:st.color||'#A5B4FC',
                      border:'1px solid '+(st.color||'#6366F1')+'44' }}>{i+1}. {st.name}</span>
                  ))}
                </div>
              </div>
            )}
          </div>
        )}
      </>)}

      {/* ─── فالوآپ ─── */}
      {node.type === 'followup' && (<>
        <label style={{...lblStyle, marginTop:13}}>قانون فالوآپ</label>
        <select value={c.rule_id||''} onChange={e => setCfg({ rule_id: e.target.value })} style={inpStyle}>
          <option value="">— انتخاب قانون —</option>
          {ctx.rules.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
        </select>
      </>)}

      {/* ─── تأخیر ─── */}
      {node.type === 'delay' && (<>
        <label style={{...lblStyle, marginTop:13}}>به مدت</label>
        <input type="number" min="0" step="1" value={c.hours??''} onChange={e => setCfg({ hours: Number(e.target.value) })}
          placeholder="مثلاً ۲۴" style={inpStyle} />
        <label style={{...lblStyle, marginTop:13}}>واحد</label>
        <div style={{ display:'flex', gap:6 }}>
          {[['h','ساعت'],['d','روز'],['m','دقیقه']].map(([v,l]) => {
            const on = (c.unit||'h') === v;
            return (
              <button key={v} onClick={() => setCfg({...c, unit:v})}
                style={{ flex:1, fontFamily:'inherit', fontSize:12, fontWeight:600, borderRadius:10, padding:'8px 4px', cursor:'pointer', transition:'.2s', border:0,
                  color: on?'#fff':'rgba(232,235,255,.58)',
                  background: on?'linear-gradient(135deg,#6366f1,#8b5cf6)':'rgba(255,255,255,.04)',
                  boxShadow: on?'0 6px 16px -6px rgba(99,102,241,.7)':'none' }}>{l}
              </button>
            );
          })}
        </div>
      </>)}

      {/* ─── شرط ─── */}
      {node.type === 'condition' && (() => {
        const checks = Array.isArray(c.checks) ? c.checks : [];
        const chUid  = () => 'ch_' + Math.random().toString(36).slice(2, 8);
        const setChecks = (arr) => setCfg({ ...c, checks: arr });
        const addCheck  = (type) => {
          const conn = checks.length > 0 ? { connector: 'and' } : {};
          const base = type === 'field' ? { id:chUid(), type:'field', field:'', op:'exists' }
                     :                    { id:chUid(), type:'state', value:'', op:'is' };
          setChecks([...checks, { ...base, ...conn }]);
        };
        const removeCheck = (id)    => setChecks(checks.filter(ch => ch.id !== id));
        const updCheck    = (id, p) => setChecks(checks.map(ch => ch.id === id ? {...ch,...p} : ch));
        const T = { field: PORT_DATA_COLOR, state: PORT_STATE_COLOR, lead: '#818cf8' };
        return (
          <>
            <label style={{...lblStyle, marginTop:13}}>شرط‌ها</label>
            {checks.length === 0 ? (
              <div style={{ border:'1px dashed rgba(129,124,255,.4)', borderRadius:14, padding:14,
                background:'linear-gradient(150deg,rgba(99,102,241,.12),rgba(139,92,246,.06))', textAlign:'center' }}>
                <div style={{ fontSize:'12.5px', color:'rgba(232,235,255,.58)' }}>هنوز هیچ شرطی نداری</div>
                <div style={{ marginTop:9, display:'inline-flex', alignItems:'center', gap:8, fontSize:12, fontWeight:600,
                  background:'rgba(255,255,255,.06)', border:'1px solid rgba(255,255,255,.08)', borderRadius:999, padding:'6px 12px' }}>
                  <span style={{ color:'#34d399' }}>بله</span>
                  <span style={{ color:'rgba(232,235,255,.4)' }}>←</span>
                  <span style={{ color:'#fb7185' }}>خیر</span>
                </div>
              </div>
            ) : (
              <div style={{ display:'flex', flexDirection:'column', gap:4 }}>
                {checks.map((ch, idx) => {
                  const color = T[ch.type] || '#818cf8';
                  const typeIcon = ch.type==='field'?'📋':'📍';
                  const conn = ch.connector || 'and';
                  return (
                    <div key={ch.id||idx}>
                      {idx > 0 && (
                        <div style={{ display:'flex', justifyContent:'center', margin:'3px 0' }}>
                          <div onClick={() => updCheck(ch.id,{connector:conn==='and'?'or':'and'})}
                            style={{ padding:'2px 14px', borderRadius:12, cursor:'pointer', userSelect:'none', fontSize:10, fontWeight:700,
                              background:conn==='and'?'rgba(99,102,241,.18)':'rgba(251,191,36,.15)',
                              color:conn==='and'?'#818CF8':'#FBBF24',
                              border:'1px solid '+(conn==='and'?'rgba(99,102,241,.35)':'rgba(251,191,36,.35)') }}>
                            {conn==='and'?'AND':'OR'}
                          </div>
                        </div>
                      )}
                      <div style={{ borderRadius:10, border:'1px solid '+color+'30', background:color+'08', padding:'8px 10px', display:'flex', alignItems:'center', gap:6 }}>
                        <span style={{ fontSize:13, flexShrink:0 }}>{typeIcon}</span>
                        {ch.type==='field' && (<>
                          <select value={ch.field||''} onChange={e=>updCheck(ch.id,{field:e.target.value})}
                            style={{ ...inpStyle, flex:1, padding:'6px 8px', fontSize:11 }}>
                            <option value="">{fieldOptions.length?'— فیلد —':'— اسکریپت انتخاب کن —'}</option>
                            {fieldOptions.map(f=><option key={f} value={f}>{f}</option>)}
                          </select>
                          <button onClick={()=>updCheck(ch.id,{op:ch.op==='exists'?'empty':'exists'})}
                            style={{ flexShrink:0, padding:'5px 9px', borderRadius:8, cursor:'pointer', fontSize:11, fontWeight:700, border:'none',
                              background:ch.op==='exists'?color+'22':'rgba(251,113,133,.15)',
                              color:ch.op==='exists'?color:'#fb7185' }}>
                            {ch.op==='exists'?'✓ دارد':'✗ ندارد'}
                          </button>
                        </>)}
                        {ch.type==='state' && (
                          <div style={{ flex:1, display:'flex', flexDirection:'column', gap:4 }}>
                            <select value={ch.value||''} onChange={e=>updCheck(ch.id,{value:e.target.value})}
                              style={{ ...inpStyle, padding:'6px 8px', fontSize:11 }}>
                              <option value="">{stateOptions.length?'— مرحله —':'— اسکریپت انتخاب کن —'}</option>
                              {stateOptions.map(s=><option key={s} value={s}>{s}</option>)}
                            </select>
                            <select value={ch.op||'is'} onChange={e=>updCheck(ch.id,{op:e.target.value})}
                              style={{ ...inpStyle, padding:'5px 8px', fontSize:10 }}>
                              <option value="is">در این مرحله باشد</option>
                              <option value="neq">در این مرحله نباشد</option>
                              <option value="stuck">⏳ بیش از N ساعت مانده</option>
                            </select>
                            {ch.op==='stuck' && (
                              <div style={{ display:'flex', gap:6, alignItems:'center' }}>
                                <span style={{ fontSize:10, color:'rgba(232,235,255,.4)', flexShrink:0 }}>بیشتر از</span>
                                <input type="number" min="0" value={ch.hours||''} onChange={e=>updCheck(ch.id,{hours:Number(e.target.value)})}
                                  placeholder="24" style={{ ...inpStyle, padding:'5px 8px', fontSize:11 }} />
                                <span style={{ fontSize:10, color:'rgba(232,235,255,.4)', flexShrink:0 }}>ساعت</span>
                              </div>
                            )}
                          </div>
                        )}
                        <button onClick={()=>removeCheck(ch.id)}
                          style={{ border:'none', background:'rgba(251,113,133,.12)', color:'#fb7185', borderRadius:7, width:24, height:24, cursor:'pointer', fontSize:13, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center' }}>✕</button>
                      </div>
                    </div>
                  );
                })}
              </div>
            )}

            <div style={{ display:'none', alignItems:'center', gap:10, marginTop:13 }}>
              <span style={{ fontSize:'11.5px', color:'rgba(232,235,255,.58)', flexShrink:0 }}>ترکیب:</span>
              <div style={{ display:'flex', flex:1, background:'rgba(255,255,255,.04)', border:'1px solid rgba(255,255,255,.08)', borderRadius:12, padding:3, gap:3 }}>
                {[['all','AND (همه)'],['any','OR (هرکدام)']].map(([v,l]) => {
                  const on = (c.match||'all') === v;
                  return (
                    <button key={v} onClick={()=>setCfg({...c, match:v})}
                      style={{ flex:1, fontFamily:'inherit', fontSize:12, fontWeight:600, borderRadius:9, padding:'8px 6px', border:0, cursor:'pointer', transition:'.22s',
                        color: on?'#fff':'rgba(232,235,255,.58)',
                        background: on?'linear-gradient(135deg,#6366f1,#8b5cf6)':'transparent',
                        boxShadow: on?'0 6px 16px -6px rgba(99,102,241,.7),inset 0 1px 0 rgba(255,255,255,.3)':'none' }}>{l}
                    </button>
                  );
                })}
              </div>
            </div>

            <div style={{ display:'flex', gap:8, marginTop:13 }}>
              {[['field','📋 فیلد'],['state','📍 مرحله']].map(([t,l]) => (
                <button key={t} onClick={()=>addCheck(t)}
                  style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center', gap:5,
                    fontFamily:'inherit', fontSize:12, fontWeight:600, color:'#cdd0ff',
                    background:'rgba(255,255,255,.04)', border:'1px dashed rgba(129,124,255,.45)',
                    borderRadius:11, padding:'9px 6px', cursor:'pointer', transition:'.22s' }}
                  onMouseEnter={e=>{e.currentTarget.style.background='rgba(129,124,255,.16)';e.currentTarget.style.borderStyle='solid';e.currentTarget.style.color='#fff';}}
                  onMouseLeave={e=>{e.currentTarget.style.background='rgba(255,255,255,.04)';e.currentTarget.style.borderStyle='dashed';e.currentTarget.style.color='#cdd0ff';}}>
                  <span style={{ fontSize:15, fontWeight:700 }}>+</span>{l}
                </button>
              ))}
            </div>

            {scoped && (
              <div style={{ marginTop:8, fontSize:10, color:PORT_STATE_COLOR, padding:'4px 8px', borderRadius:6, background:PORT_STATE_COLOR+'0D', border:'1px solid '+PORT_STATE_COLOR+'20' }}>
                📍 از اسکریپت «{scopedScriptName}»
              </div>
            )}
          </>
        );
      })()}

      {/* ─── اکشن ─── */}
      {node.type === 'action' && (<>
        <label style={{...lblStyle, marginTop:13}}>نوع اکشن</label>
        <select value={c.kind||''} onChange={e=>setCfg({kind:e.target.value})} style={inpStyle}>
          <option value="">— انتخاب اکشن —</option>
          {ACTION_KINDS.map(a=><option key={a.id} value={a.id}>{a.label}</option>)}
        </select>
        {c.kind==='send_text' && (<>
          <label style={{...lblStyle, marginTop:13}}>متن پیام</label>
          <textarea value={c.text||''} onChange={e=>setCfg({...c,text:e.target.value})}
            placeholder={'سلام {{name}}، پیامی برای شما داریم...'}
            style={{ ...inpStyle, minHeight:80, resize:'vertical' }} />
          <div style={{ fontSize:10, color:'rgba(232,235,255,.4)', marginTop:4 }}>می‌توانی از {'{{name}}'} برای نام لید استفاده کنی.</div>
        </>)}
        {c.kind==='send_link' && (<>
          <label style={{...lblStyle, marginTop:13}}>متن همراه (اختیاری)</label>
          <input value={c.text||''} onChange={e=>setCfg({...c,text:e.target.value})} placeholder="مثلاً: روی لینک زیر کلیک کن" style={inpStyle} />
          <label style={{...lblStyle, marginTop:13}}>آدرس لینک</label>
          <input value={c.url||''} onChange={e=>setCfg({...c,url:e.target.value})} placeholder="https://..." style={inpStyle} />
        </>)}
        {(c.kind==='send_library'||c.kind==='send_audio') && (() => {
          const groups = ctx.libFiles || [];
          const displayGroups = c.kind==='send_audio'?groups.map(g=>({...g,files:g.files.filter(f=>f.file_type==='audio')})).filter(g=>g.files.length):groups;
          const sel = groups.flatMap(g=>g.files).find(f=>f.id===c.file_id);
          return (<>
            <label style={{...lblStyle, marginTop:13}}>{c.kind==='send_audio'?'🎵 فایل صدا':'📁 فایل از کتابخانه'}</label>
            <select value={c.file_id||''} onChange={e=>setCfg({...c,file_id:e.target.value})} style={inpStyle}>
              <option value="">{displayGroups.length?'— انتخاب فایل —':'— فایلی موجود نیست —'}</option>
              {displayGroups.map(grp=>(
                <optgroup key={grp.id} label={'📁 '+grp.name}>
                  {grp.files.map(f=>{const ic=f.file_type==='video'?'🎬':f.file_type==='image'?'🖼️':f.file_type==='audio'?'🎵':f.file_type==='pdf'?'📄':'📎';return <option key={f.id} value={f.id}>{ic} {f.display}</option>;})}
                </optgroup>
              ))}
            </select>
            {sel && <div style={{ marginTop:6, padding:'6px 10px', borderRadius:8, background:'rgba(52,211,153,.08)', border:'1px solid rgba(52,211,153,.2)', fontSize:11, color:'#34D399' }}>✓ {sel.display}</div>}
            <label style={{...lblStyle, marginTop:8}}>متن همراه (اختیاری)</label>
            <input value={c.text||''} onChange={e=>setCfg({...c,text:e.target.value})} placeholder="کپشن یا متن قبل از فایل" style={inpStyle} />
          </>);
        })()}
        {(c.kind==='change_status'||c.kind==='add_tag') && (<>
          <label style={{...lblStyle, marginTop:13}}>{c.kind==='change_status'?'وضعیت جدید':'تگ'}</label>
          <input value={c.value||''} onChange={e=>setCfg({...c,value:e.target.value})} placeholder={c.kind==='change_status'?'مثلاً buyer':'مثلاً سرد'} style={inpStyle} />
        </>)}
        {c.kind==='notify_seller' && (<>
          <label style={{...lblStyle, marginTop:13}}>متن اطلاع‌رسانی</label>
          <textarea value={c.text||''} onChange={e=>setCfg({...c,text:e.target.value})} placeholder="پیامی که به فروشنده ارسال می‌شود..." style={{ ...inpStyle, minHeight:60, resize:'vertical' }} />
        </>)}
      </>)}

      {/* ─── ادامهٔ مسیر ─── */}
      {def.outPorts.length > 0 && (
        <div style={{ marginTop:13 }}>
          <div style={{ height:1, background:'rgba(255,255,255,.08)', margin:'0 0 10px' }} />
          <div style={{ fontSize:12, fontWeight:600, color:'rgba(232,235,255,.58)', marginBottom:8 }}>ادامهٔ مسیر</div>
          {def.outPorts.map(port => (
            <div key={port} style={{ display:'flex', alignItems:'center', gap:10, marginBottom:8 }}>
              <span style={{ flexShrink:0, width:34, textAlign:'center', fontSize:12, fontWeight:700, color:port==='no'?'#fb7185':'#34d399' }}>{FL_PORT_LABEL[port]||'→'}</span>
              <select value="" onChange={e=>{if(e.target.value&&onAddNext)onAddNext(port,e.target.value);}}
                style={{ flex:1, fontFamily:'inherit', fontSize:'12.5px', fontWeight:500, color:'rgba(232,235,255,.58)',
                  background:'rgba(255,255,255,.04)', border:'1px solid rgba(255,255,255,.08)', borderRadius:12,
                  padding:'11px 13px', cursor:'pointer', colorScheme:'dark', outline:'none', transition:'border-color .22s, background .22s' }}
                onMouseEnter={e=>{e.currentTarget.style.color='#fff';e.currentTarget.style.borderColor='rgba(129,124,255,.5)';e.currentTarget.style.background='rgba(129,124,255,.1)';}}
                onMouseLeave={e=>{e.currentTarget.style.color='rgba(232,235,255,.58)';e.currentTarget.style.borderColor='rgba(255,255,255,.08)';e.currentTarget.style.background='rgba(255,255,255,.04)';}}>
                <option value="">＋ افزودن نود بعدی…</option>
                {SUCCESSORS.map(t=><option key={t} value={t}>{FL_NODES[t].label}</option>)}
              </select>
            </div>
          ))}
          <div style={{ fontSize:11, color:'rgba(232,235,255,.40)', textAlign:'center', lineHeight:1.7 }}>نود جدید خودکار به این نود وصل می‌شود.</div>
        </div>
      )}

      {/* ─── حذف ─── */}
      <button onClick={onDelete}
        style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:8, width:'100%',
          fontFamily:'inherit', fontSize:13, fontWeight:600, color:'#ffd2da',
          background:'linear-gradient(150deg,rgba(244,63,94,.20),rgba(244,63,94,.08))',
          border:'1px solid rgba(244,63,94,.35)', borderRadius:13, padding:12, cursor:'pointer', transition:'.22s', marginTop:13 }}
        onMouseEnter={e=>{e.currentTarget.style.background='linear-gradient(150deg,rgba(244,63,94,.34),rgba(244,63,94,.16))';e.currentTarget.style.color='#fff';e.currentTarget.style.boxShadow='0 10px 24px -10px rgba(244,63,94,.6)';}}
        onMouseLeave={e=>{e.currentTarget.style.background='linear-gradient(150deg,rgba(244,63,94,.20),rgba(244,63,94,.08))';e.currentTarget.style.color='#ffd2da';e.currentTarget.style.boxShadow='none';}}>
        🗑 حذف نود <span style={{ opacity:.6, fontWeight:500 }}>(یا کلید Delete)</span>
      </button>
    </div>
  );
};



// استایل‌ها
const flowSelStyle = { padding: '6px 10px', borderRadius: 8, background: 'rgba(10,8,30,0.75)', color: '#eef0ff',
  colorScheme: 'dark', fontFamily: 'inherit',
  border: '1px solid rgba(255,255,255,0.14)', fontSize: 13, minWidth: 180 };
const btnGhost = { padding: '6px 10px', borderRadius: 8, background: 'var(--bg-2)', color: 'var(--text-2)',
  border: '1px solid rgba(255,255,255,0.1)', cursor: 'pointer', fontSize: 13, lineHeight: 1 };
const btnPrimary = { padding: '7px 16px', borderRadius: 8, background: '#8B5CF6', color: '#fff',
  border: 'none', cursor: 'pointer', fontSize: 13, fontWeight: 700 };
const branchBtn = { flex: 1, minWidth: 64, padding: '8px 6px', borderRadius: 8, border: '1px solid #6366F155',
  background: '#6366F115', color: '#A5B4FC', cursor: 'pointer', fontSize: 12, fontWeight: 700 };
const lblStyle = { display: 'block', fontSize: '11.5px', color: 'rgba(232,235,255,.58)', fontWeight: 500, marginBottom: 7 };
const inpStyle = { width: '100%', fontFamily: 'inherit', fontSize: 13, color: '#eef0ff',
  background: 'rgba(255,255,255,.04)', border: '1px solid rgba(255,255,255,.08)', borderRadius: 12,
  padding: '11px 13px', outline: 'none', boxSizing: 'border-box', colorScheme: 'dark', marginTop: 0,
  transition: 'border-color .25s, background .25s' };

// â•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گ
// ع©ط§ظ…ظ¾ظˆظ†ظ†طھ ع¯ط²ط§ط±ط´ ط²ظ†ط¯ظ‡ظ" ظپظ„ظˆ
// â•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گâ•گ
const FlowReport = ({ campId, data, loading, onRefresh }) => {
  const { Icon } = window.SB_UI;
  const fa = (window.SB_DATA && window.SB_DATA.fa) ? window.SB_DATA.fa : (n => String(n));

  if (!campId) return (
    <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center',
      color:'var(--text-3)', flexDirection:'column', gap:12 }}>
      <Icon name="bar-chart-2" size={40} color="var(--text-3)" />
      <div>ابتدا یک کمپین انتخاب کن تا گزارشش را ببینی</div>
    </div>
  );

  if (loading) return (
    <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center', color:'var(--text-3)' }}>
      در حال بارگذاری...
    </div>
  );

  if (!data) return (
    <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center',
      color:'var(--text-3)', flexDirection:'column', gap:10 }}>
      <Icon name="bar-chart-2" size={36} color="var(--text-3)" />
      <div>برای مشاهده گزارش ↻ بروزرسانی را بزن</div>
    </div>
  );

  const { total, done, nodeStats, nodes } = data;
  const nodeMap = {};
  (nodes || []).forEach(n => { nodeMap[n.id] = n; });
  const defNode = (type) => FL_NODES[type] || { label: type, color: '#64748B', icon: 'box' };
  const platIcon = { telegram: '✈️', bale: '💬', rubika: '🔵', instagram: '📷' };

  const activeNodes = Object.entries(nodeStats || {})
    .filter(function(e) { return e[1].count > 0; })
    .sort(function(a, b) { return b[1].count - a[1].count; });

  return (
    <div style={{ flex:1, overflowY:'auto', padding:20, direction:'rtl' }}>
      {/* کارت‌های خلاصه */}
      <div style={{ display:'grid', gridTemplateColumns:'repeat(3,1fr)', gap:12, marginBottom:24 }}>
        {[
          { lbl:'کل لیدهای وارد‌شده', val:fa(total||0), color:'#6366F1', icon:'users' },
          { lbl:'در جریان (فعال)',     val:fa((total||0)-(done||0)), color:'#F59E0B', icon:'activity' },
          { lbl:'تکمیل‌شده',          val:fa(done||0),  color:'#10B981', icon:'check-circle' },
        ].map(function(s) { return (
          <div key={s.lbl} style={{ padding:'16px 18px', borderRadius:12, background:'var(--bg-2)',
            border:'1px solid '+s.color+'33', display:'flex', alignItems:'center', gap:14 }}>
            <div style={{ width:38, height:38, borderRadius:10, background:s.color+'22',
              display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
              <Icon name={s.icon} size={18} color={s.color} />
            </div>
            <div>
              <div style={{ fontSize:22, fontWeight:800, color:s.color, lineHeight:1 }}>{s.val}</div>
              <div style={{ fontSize:11, color:'var(--text-3)', marginTop:3 }}>{s.lbl}</div>
            </div>
          </div>
        ); })}
      </div>

      {/* دکمه بروزرسانی */}
      <button onClick={onRefresh} style={{ width:'100%', padding:'9px', borderRadius:10, marginBottom:20,
        border:'1px solid rgba(99,102,241,0.3)', background:'rgba(99,102,241,0.08)',
        color:'#818CF8', cursor:'pointer', fontSize:12, fontFamily:'inherit', fontWeight:600 }}>
        ↻ بروزرسانی گزارش
      </button>

      {!total ? (
        <div style={{ textAlign:'center', padding:40, color:'var(--text-3)', fontSize:13 }}>
          📭 هنوز هیچ لیدی وارد این کمپین نشده.
          <br/><span style={{ fontSize:11, marginTop:8, display:'block' }}>وقتی رویداد تریگر برای یک لید رخ دهد، اینجا نمایش داده می‌شود.</span>
        </div>
      ) : (
        <div>
          <div style={{ fontSize:12, fontWeight:700, color:'var(--text-2)', marginBottom:12 }}>موقعیت لیدها در فلو</div>
          <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
            {activeNodes.map(function(entry) {
              var nodeId = entry[0], stat = entry[1];
              var nd = nodeMap[nodeId];
              var type = nd ? nd.type : 'unknown';
              var d = defNode(type);
              var pct = total > 0 ? Math.round(stat.count / total * 100) : 0;
              var cfg = nd && nd.config ? nd.config : {};
              var title = nd ? (nd.label || cfg.script_id || d.label) : (nodeId === '__entry__' ? 'ورود به فلو' : nodeId);
              return (
                <div key={nodeId} style={{ borderRadius:12, background:'var(--bg-2)',
                  border:'1px solid '+d.color+'33', padding:'14px 16px' }}>
                  <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:10 }}>
                    <div style={{ width:32, height:32, borderRadius:8, background:d.color+'22',
                      display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
                      <Icon name={d.icon} size={15} color={d.color} />
                    </div>
                    <div style={{ flex:1, minWidth:0 }}>
                      <div style={{ fontSize:13, fontWeight:700, color:'var(--text-1)' }}>{title}</div>
                      <div style={{ fontSize:10, color:d.color }}>{d.label}</div>
                    </div>
                    <div style={{ textAlign:'left' }}>
                      <div style={{ fontSize:18, fontWeight:800, color:d.color, lineHeight:1 }}>{fa(stat.count)}</div>
                      <div style={{ fontSize:10, color:'var(--text-3)' }}>نفر ({fa(pct)}٪)</div>
                    </div>
                  </div>
                  {/* نوار پیشرفت */}
                  <div style={{ height:5, borderRadius:3, background:'rgba(255,255,255,0.07)', overflow:'hidden', marginBottom:10 }}>
                    <div style={{ height:'100%', width:pct+'%', borderRadius:3, background:d.color, transition:'width .5s' }} />
                  </div>
                  {/* نمونه لیدها */}
                  {stat.leads && stat.leads.length > 0 && (
                    <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
                      {stat.leads.map(function(l, i) { return (
                        <div key={i} style={{ display:'flex', alignItems:'center', gap:5, padding:'4px 9px',
                          borderRadius:20, background:'rgba(255,255,255,0.05)', fontSize:11, color:'var(--text-2)' }}>
                          <span>{platIcon[l.platform] || '👤'}</span>
                          <span>{l.name || '—'}</span>
                          {l.phone && <span style={{ color:'var(--text-3)', fontSize:9, direction:'ltr' }}>{l.phone}</span>}
                        </div>
                      ); })}
                      {stat.count > stat.leads.length && (
                        <div style={{ padding:'4px 9px', borderRadius:20, background:'rgba(255,255,255,0.05)', fontSize:11, color:'var(--text-3)' }}>
                          +{fa(stat.count - stat.leads.length)} نفر دیگر
                        </div>
                      )}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      )}
      {/* ══ پنل مکالمات زنده ══ */}
      {livePanel && (
        <div style={{
          position: 'fixed', left: 0, top: 0, bottom: 0, width: 360,
          background: 'rgba(8,7,24,0.97)', backdropFilter: 'blur(24px)',
          borderRight: '1px solid rgba(34,197,94,0.2)',
          display: 'flex', flexDirection: 'column',
          zIndex: 9999, direction: 'rtl',
          animation: 'slideInRight .25s ease',
          boxShadow: '4px 0 32px rgba(0,0,0,0.6)',
        }}>
          {/* header پنل */}
          <div style={{ padding: '14px 16px', borderBottom: '1px solid rgba(255,255,255,0.07)', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
            <span style={{ width:8, height:8, borderRadius:'50%', background:'#22c55e', boxShadow:'0 0 8px #22c55e', animation:'livePulse 1.5s ease-in-out infinite', flexShrink:0 }} />
            <span style={{ flex:1, fontWeight:800, fontSize:13, color:'#e2e8ff' }}>{livePanel.label}</span>
            <span style={{ fontSize:11, color:'#4ade80', background:'rgba(34,197,94,0.12)', padding:'2px 8px', borderRadius:6, border:'1px solid rgba(34,197,94,0.3)' }}>
              {panelLeads.length} نفر
            </span>
            <button onClick={function() { setLivePanel(null); setSelConv(null); setConvMsgs([]); }}
              style={{ background:'none', border:'none', color:'rgba(255,255,255,0.35)', cursor:'pointer', fontSize:18, lineHeight:1, padding:'2px 4px', flexShrink:0 }}>✕</button>
          </div>

          {!selConv ? (
            /* لیست لیدهای این نود */
            <div style={{ flex:1, overflowY:'auto', padding:'10px 12px', display:'flex', flexDirection:'column', gap:6 }}>
              {panelLeads.length === 0 ? (
                <div style={{ textAlign:'center', color:'rgba(255,255,255,0.3)', padding:40, fontSize:12 }}>کسی در این نود نیست</div>
              ) : panelLeads.map(function(lead, i) {
                return (
                  <div key={i}
                    onClick={function() { if (lead.conv_id) loadConv(lead.conv_id, lead.name || lead.phone || 'لید ' + (i+1)); }}
                    style={{
                      padding:'11px 14px', borderRadius:10, cursor: lead.conv_id ? 'pointer' : 'default',
                      background: lead.conv_id ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.02)',
                      border: '1px solid rgba(255,255,255,0.07)',
                      display:'flex', alignItems:'center', gap:10,
                      transition:'background .15s, border-color .15s',
                    }}
                    onMouseEnter={function(e) { if (lead.conv_id) e.currentTarget.style.background = 'rgba(34,197,94,0.08)'; }}
                    onMouseLeave={function(e) { e.currentTarget.style.background = lead.conv_id ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.02)'; }}>
                    <div style={{ width:34, height:34, borderRadius:10, background:'linear-gradient(135deg,rgba(99,102,241,0.3),rgba(139,92,246,0.2))', display:'flex', alignItems:'center', justifyContent:'center', fontSize:14, flexShrink:0 }}>
                      {lead.platform === 'telegram' ? '✈️' : lead.platform === 'bale' ? '🔵' : '👤'}
                    </div>
                    <div style={{ flex:1, minWidth:0 }}>
                      <div style={{ fontSize:12, fontWeight:700, color:'#e2e8ff', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                        {lead.name || 'ناشناس'}
                      </div>
                      {lead.phone && <div style={{ fontSize:11, color:'rgba(255,255,255,0.35)', marginTop:2 }}>{lead.phone}</div>}
                    </div>
                    {lead.conv_id
                      ? <span style={{ fontSize:10, color:'#4ade80', opacity:0.8, flexShrink:0 }}>مکالمه ←</span>
                      : <span style={{ fontSize:10, color:'rgba(255,255,255,0.2)', flexShrink:0 }}>بدون مکالمه</span>
                    }
                  </div>
                );
              })}
            </div>
          ) : (
            /* نمایش مکالمه */
            <div style={{ flex:1, display:'flex', flexDirection:'column', minHeight:0 }}>
              {/* sub-header */}
              <div style={{ padding:'10px 14px', borderBottom:'1px solid rgba(255,255,255,0.06)', display:'flex', alignItems:'center', gap:8, flexShrink:0 }}>
                <button onClick={function() { setSelConv(null); setConvMsgs([]); }}
                  style={{ background:'none', border:'none', color:'#818CF8', cursor:'pointer', fontSize:13, padding:0, display:'flex', alignItems:'center', gap:4 }}>
                  → بازگشت
                </button>
                <span style={{ flex:1, fontSize:12, fontWeight:700, color:'#e2e8ff', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{selConv.name}</span>
                <span style={{ fontSize:10, color:'rgba(255,255,255,0.25)', background:'rgba(255,255,255,0.05)', padding:'2px 7px', borderRadius:5 }}>فقط خواندنی</span>
              </div>
              {/* پیام‌ها */}
              <div style={{ flex:1, overflowY:'auto', padding:'12px 14px', display:'flex', flexDirection:'column', gap:8 }}>
                {convLoading ? (
                  <div style={{ textAlign:'center', color:'rgba(255,255,255,0.3)', paddingTop:40, fontSize:12 }}>در حال بارگذاری...</div>
                ) : convMsgs.length === 0 ? (
                  <div style={{ textAlign:'center', color:'rgba(255,255,255,0.2)', paddingTop:40, fontSize:12 }}>پیامی وجود ندارد</div>
                ) : convMsgs.map(function(msg, i) {
                  var isUser = msg.role === 'user';
                  return (
                    <div key={msg.id || i} style={{
                      display:'flex', justifyContent: isUser ? 'flex-end' : 'flex-start',
                      animation: 'convMsgIn .2s ease',
                    }}>
                      <div style={{
                        maxWidth:'82%', padding:'9px 12px', borderRadius: isUser ? '14px 14px 4px 14px' : '14px 14px 14px 4px',
                        background: isUser ? 'linear-gradient(135deg,#4f46e5,#7c3aed)' : 'rgba(255,255,255,0.07)',
                        border: isUser ? 'none' : '1px solid rgba(255,255,255,0.1)',
                        fontSize:12, color:'#e8eaff', lineHeight:1.6,
                        wordBreak:'break-word',
                      }}>
                        {msg.content}
                        <div style={{ fontSize:9, opacity:0.45, marginTop:4, textAlign: isUser ? 'left' : 'right' }}>
                          {msg.created_at ? new Date(msg.created_at).toLocaleTimeString('fa-IR',{hour:'2-digit',minute:'2-digit'}) : ''}
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
};

/* ══════════════════════════════════════════════════════════
   FlowLiveView — نمای زنده کامل با انیمیشن توکن
══════════════════════════════════════════════════════════ */
function ConvViewLive({ lead, color, init, onBack }) {
  var [msgs, setMsgs] = useState([]);
  var [loading, setLoading] = useState(true);
  useEffect(function() {
    if (!lead.conv_id) { setLoading(false); return; }
    window.api('/conversations/' + lead.conv_id + '/messages')
      .then(function(d) { if (d && d.success && d.data) setMsgs(d.data.messages || []); })
      .catch(function() {}).finally(function() { setLoading(false); });
  }, [lead.conv_id]);
  return (
    <React.Fragment>
      <div style={{ display:'flex', alignItems:'center', gap:8, padding:'11px 13px', borderBottom:'1px solid rgba(150,120,210,.1)' }}>
        <button onClick={onBack} style={{ background:'none', border:'none', color:'#a855f7', cursor:'pointer', fontSize:12, display:'flex', alignItems:'center', gap:3, padding:0, fontFamily:'inherit' }}>← بازگشت</button>
        <div style={{ width:24, height:24, borderRadius:'50%', background:color, display:'flex', alignItems:'center', justifyContent:'center', fontSize:10, fontWeight:700, color:'#fff', flexShrink:0 }}>{init}</div>
        <span style={{ flex:1, fontSize:12, fontWeight:700, color:'#ece8f4', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{lead.name || lead.phone || 'لید'}</span>
        <span style={{ fontSize:10, color:'#9a90ad', background:'rgba(255,255,255,0.05)', padding:'2px 7px', borderRadius:5 }}>فقط خواندنی</span>
      </div>
      <div style={{ padding:'12px 14px', display:'flex', flexDirection:'column', gap:10, flex:1, overflowY:'auto', maxHeight:'60vh', minHeight:200 }}>
        {loading ? (
          <div style={{ textAlign:'center', color:'#9a90ad', padding:30, fontSize:12 }}>
            <div style={{ width:20, height:20, borderRadius:'50%', border:'2px solid rgba(168,85,247,0.3)', borderTopColor:'#a855f7', animation:'spin .7s linear infinite', margin:'0 auto 10px' }} />
            در حال بارگذاری مکالمه...
          </div>
        ) : msgs.length === 0 ? (
          <div style={{ textAlign:'center', color:'#9a90ad', padding:30, fontSize:12 }}>
            <div style={{ fontSize:28, marginBottom:8 }}>💬</div>
            پیامی وجود ندارد
          </div>
        ) : msgs.map(function(msg, i) {
          var isUser = msg.role === 'user';
          return (
            <div key={msg.id || i} style={{ display:'flex', flexDirection:'column', alignItems: isUser ? 'flex-end' : 'flex-start' }}>
              <div style={{ maxWidth:'88%', fontSize:13, lineHeight:1.7, padding:'9px 13px', borderRadius: isUser?'16px 16px 4px 16px':'16px 16px 16px 4px',
                background: isUser ? 'linear-gradient(135deg,rgba(52,208,88,.22),rgba(31,170,68,.18))' : 'rgba(124,58,237,.18)',
                border: isUser ? '1px solid rgba(52,208,88,.3)' : '1px solid rgba(168,85,247,.25)', color:'#ece8f4' }}>
                {msg.content}
              </div>
              {msg.created_at && (
                <div style={{ fontSize:10, color:'rgba(154,144,173,0.5)', marginTop:3, paddingInline:4 }}>
                  {new Date(msg.created_at).toLocaleTimeString('fa-IR',{hour:'2-digit',minute:'2-digit'})}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </React.Fragment>
  );
}

function FlowLiveView({ campId, nodes, edges, onClose }) {
  var toFa = function(n) { return String(n||0).replace(/\d/g, function(d){ return '۰۱۲۳۴۵۶۷۸۹'[d]; }); };

  var [liveData, setLiveData]   = useState(null);
  var [travelers, setTravelers] = useState([]);
  var [popNode, setPopNode]     = useState(null);
  var [popChat, setPopChat]     = useState(null);
  var [total, setTotal]         = useState(0);
  var [zoom, setZoom]           = useState(1);
  var [monBumps, setMonBumps]   = useState({});
  var [monTokens, setMonTokens] = useState([]);
  var [layoutMode, setLayoutMode] = useState('vertical'); // 'vertical' | 'horizontal' | 'original'
  var [ctxMenu, setCtxMenu]     = useState(null); // {x,y} موقعیت منو راست‌کلیک
  var zoomRef      = useRef(1);
  var monTokenIdRef= useRef(0);
  var prevCountsRef = useRef({});
  var travIdRef     = useRef(0);
  var canvasRef2      = useRef(null);
  var scaledNodesRef  = useRef({});
  var liveDataRef     = useRef(null);

  // ── scale nodes to fit canvas ──
  var COLORS = { trigger:'#f59e0b', script:'#6366f1', followup:'#10b981', delay:'#0ea5e9', condition:'#ec4899', action:'#8b5cf6', end:'#64748b' };
  var ICONS  = { trigger:'zap', script:'file-text', followup:'repeat-2', delay:'clock', condition:'git-branch', action:'sparkles', end:'flag' };

  var scaledNodes = useMemo(function() {
    if (!nodes.length) return {};

    var result = {};

    // ── حالت ۱: چیدمان اصلی طراح ─────────────────────────
    if (layoutMode === 'original') {
      var xs = nodes.map(function(n){ return n.x||0; });
      var ys = nodes.map(function(n){ return n.y||0; });
      var minX = Math.min.apply(null, xs);
      var minY = Math.min.apply(null, ys);
      nodes.forEach(function(n) {
        result[n.id] = Object.assign({}, n, {
          sx: (n.x||0) - minX + 60,
          sy: (n.y||0) - minY + 60,
        });
      });
      return result;
    }

    // ── BFS level assignment (مشترک بین عمودی و افقی) ─────
    var outMap = {}, inDeg = {};
    nodes.forEach(function(n){ outMap[n.id]=[]; inDeg[n.id]=0; });
    edges.forEach(function(e){
      if (outMap[e.from]) outMap[e.from].push(e.to);
      if (inDeg[e.to] !== undefined) inDeg[e.to]++;
    });
    var level = {};
    var queue = nodes.filter(function(n){ return inDeg[n.id]===0 || n.type==='trigger'; }).map(function(n){ return n.id; });
    queue.forEach(function(id){ level[id]=0; });
    var head = 0;
    while (head < queue.length) {
      var id = queue[head++];
      (outMap[id]||[]).forEach(function(nid){
        var proposed = (level[id]||0) + 1;
        if (level[nid] === undefined) { level[nid]=proposed; queue.push(nid); }
        else if (proposed > level[nid]) { level[nid]=proposed; }
      });
    }
    nodes.forEach(function(n){ if (level[n.id]===undefined) level[n.id]=0; });

    var byLevel = {};
    nodes.forEach(function(n){
      var lv = level[n.id]||0;
      if (!byLevel[lv]) byLevel[lv]=[];
      byLevel[lv].push(n);
    });
    var levels = Object.keys(byLevel).map(Number).sort(function(a,b){return a-b;});

    // ── حالت ۲: افقی ──────────────────────────────────────
    if (layoutMode === 'horizontal') {
      var HCOL_W=340, HROW_H=120, PAD=60;
      levels.forEach(function(lv) {
        var arr = byLevel[lv];
        var x = PAD + lv * HCOL_W;
        arr.forEach(function(n, i) {
          result[n.id] = Object.assign({}, n, {
            sx: x,
            sy: PAD + i * HROW_H,
          });
        });
      });
      return result;
    }

    // ── حالت ۳: عمودی (پیش‌فرض) ──────────────────────────
    var CARD_W=270, CARD_H=88, GAP_X=40, GAP_Y=130;
    var PAD_X=60, PAD_Y=50;
    var maxCols = Math.max.apply(null, Object.values(byLevel).map(function(arr){ return arr.length; }));
    var totalW = maxCols * CARD_W + (maxCols-1) * GAP_X + PAD_X*2;
    levels.forEach(function(lv) {
      var arr = byLevel[lv];
      var rowW = arr.length * CARD_W + (arr.length-1) * GAP_X;
      var startX = (totalW - rowW) / 2;
      var y = PAD_Y + lv * (CARD_H + GAP_Y);
      arr.forEach(function(n, i) {
        result[n.id] = Object.assign({}, n, {
          sx: startX + i * (CARD_W + GAP_X),
          sy: y,
        });
      });
    });
    return result;
  }, [nodes, edges, layoutMode]);
  useEffect(function(){ scaledNodesRef.current = scaledNodes; }, [scaledNodes]);

  // ── edge bezier path ──
  var CARD_W_LV=270, CARD_H_LV=88, CIRCLE_R_LV=42;
  function lvEdgePath(fn, tn) {
    var isCircleF = fn.type==='condition'||fn.type==='trigger';
    var isCircleT = tn.type==='condition'||tn.type==='trigger';
    var sx = isCircleF ? fn.sx+CIRCLE_R_LV : fn.sx+CARD_W_LV/2;
    var sy = isCircleF ? fn.sy+CIRCLE_R_LV*2 : fn.sy+CARD_H_LV;
    var tx = isCircleT ? tn.sx+CIRCLE_R_LV : tn.sx+CARD_W_LV/2;
    var ty = isCircleT ? tn.sy : tn.sy;
    var dy = ty-sy, dx = tx-sx;
    var c = Math.abs(dy)*0.45;
    return 'M '+sx+' '+sy+' C '+sx+' '+(sy+c)+', '+tx+' '+(ty-c)+', '+tx+' '+ty;
  }

  // ── poll live data ──
  useEffect(function() {
    function poll() {
      window.api('/campaigns/campaigns/'+campId+'/live').then(function(d) {
        if (!d||!d.ok) return;
        var newCounts = {};
        Object.entries(d.nodeStats||{}).forEach(function(e){ newCounts[e[0]]=e[1].count||0; });
        var prev = prevCountsRef.current;
        var added = [];
        edges.forEach(function(ed) {
          var fn=scaledNodes[ed.from], tn=scaledNodes[ed.to];
          if (!fn||!tn) return;
          var pf=prev[ed.from]||0, nf=newCounts[ed.from]||0, pt=prev[ed.to]||0, nt=newCounts[ed.to]||0;
          if (pf>nf && nt>pt) {
            var n=Math.min(pf-nf, nt-pt, 4);
            var avColors=['#34d058','#a855f7','#ff9a3c','#10b981','#f472b6'];
            for (var i=0;i<n;i++) {
              travIdRef.current++;
              added.push({ id:travIdRef.current, path:lvEdgePath(fn,tn), color:avColors[travIdRef.current%avColors.length], delay:i*220 });
            }
          }
        });
        if (added.length) {
          setTravelers(function(p){ return p.concat(added); });
          var maxD=added.reduce(function(m,t){ return Math.max(m,t.delay); },0);
          var ids=added.map(function(t){ return t.id; });
          setTimeout(function(){ setTravelers(function(p){ return p.filter(function(t){ return ids.indexOf(t.id)===-1; }); }); }, 2400+maxD);
        }
        // ── monitor animations ──
        var bumpSet = {};
        var newMonTokens = [];
        edges.forEach(function(ed) {
          var pf = prevCountsRef.current[ed.from]||0, nf = newCounts[ed.from]||0;
          var pt = prevCountsRef.current[ed.to]||0,   nt = newCounts[ed.to]||0;
          if (pf > nf) bumpSet[ed.from] = true;
          if (nt > pt) { bumpSet[ed.to] = true;
            // توکن از index نود مبدا به مقصد
            var fromIdx = nodeList.findIndex(function(n){ return n.id===ed.from; });
            var toIdx   = nodeList.findIndex(function(n){ return n.id===ed.to; });
            if (fromIdx>=0 && toIdx>=0) {
              monTokenIdRef.current++;
              newMonTokens.push({ id:monTokenIdRef.current, fromIdx:fromIdx, toIdx:toIdx });
            }
          }
        });
        if (Object.keys(bumpSet).length) {
          setMonBumps(bumpSet);
          setTimeout(function(){ setMonBumps({}); }, 500);
        }
        if (newMonTokens.length) {
          setMonTokens(function(p){ return p.concat(newMonTokens); });
          var mids = newMonTokens.map(function(t){ return t.id; });
          setTimeout(function(){ setMonTokens(function(p){ return p.filter(function(t){ return mids.indexOf(t.id)===-1; }); }); }, 1300);
        }

        prevCountsRef.current = newCounts;
        liveDataRef.current = d;
        setLiveData(d);
        setTotal(Object.values(d.nodeStats||{}).reduce(function(s,n){ return s+(n.count||0); },0));
      }).catch(function(){});
    }
    poll();
    var id=setInterval(poll,5000);
    return function(){ clearInterval(id); };
  }, [campId]); // eslint-disable-line

  // ── انیمیشن مداوم — هر ۱.۵ ثانیه یه توکن روی یه خط فعال ──
  useEffect(function() {
    var avColors = ['#34d058','#a855f7','#ff9a3c','#10b981','#f472b6','#60a5fa','#fb923c'];
    var animId = setInterval(function() {
      var sn = scaledNodesRef.current;
      var ld = liveDataRef.current;
      if (!Object.keys(sn).length) return;

      // خطوطی که source آدم داره (یا اگه داده نداریم، همه خطوط)
      var activeEdges = edges.filter(function(ed) {
        if (!sn[ed.from] || !sn[ed.to]) return false;
        if (!ld || !ld.nodeStats) return true; // قبل از اولین poll، همه خطوط
        var cnt = ld.nodeStats[ed.from] ? ld.nodeStats[ed.from].count : 0;
        return cnt > 0;
      });

      // اگه هیچ خط فعالی نیست، از همه خطوط انتخاب کن
      if (!activeEdges.length) activeEdges = edges.filter(function(ed){ return sn[ed.from]&&sn[ed.to]; });
      if (!activeEdges.length) return;

      var ed = activeEdges[Math.floor(Math.random()*activeEdges.length)];
      var fn = sn[ed.from], tn = sn[ed.to];
      if (!fn||!tn) return;

      travIdRef.current++;
      var tid = travIdRef.current;
      var col = avColors[tid % avColors.length];

      // محاسبه path با CARD_W_LV و CIRCLE_R_LV
      var CW=270, CH=88, CR=42;
      var isF=fn.type==='condition'||fn.type==='trigger';
      var isT=tn.type==='condition'||tn.type==='trigger';
      var sx=isF?fn.sx+CR:fn.sx+CW/2, sy=isF?fn.sy+CR*2:fn.sy+CH;
      var tx=isT?tn.sx+CR:tn.sx+CW/2, ty=isT?tn.sy:tn.sy;
      var dy=ty-sy, c=Math.abs(dy)*0.45;
      var path='M '+sx+' '+sy+' C '+sx+' '+(sy+c)+', '+tx+' '+(ty-c)+', '+tx+' '+ty;

      setTravelers(function(p){ return p.concat([{ id:tid, path:path, color:col, delay:0 }]); });
      setTimeout(function(){ setTravelers(function(p){ return p.filter(function(t){ return t.id!==tid; }); }); }, 2300);
    }, 1500);

    return function(){ clearInterval(animId); };
  }, [edges]); // eslint-disable-line

  // بستن context menu با کلیک بیرون
  useEffect(function() {
    if (!ctxMenu) return;
    function close(e) { setCtxMenu(null); }
    document.addEventListener('click', close);
    document.addEventListener('contextmenu', close);
    return function() {
      document.removeEventListener('click', close);
      document.removeEventListener('contextmenu', close);
    };
  }, [ctxMenu]);

  // ── zoom with Ctrl+scroll ──
  useEffect(function() {
    var el = canvasRef2.current;
    if (!el) return;
    function onWheel(e) {
      if (!e.ctrlKey && !e.metaKey) return;
      e.preventDefault();
      var delta = e.deltaY < 0 ? 1.1 : 0.91;
      var nz = Math.max(0.3, Math.min(2.5, zoomRef.current * delta));
      zoomRef.current = nz;
      setZoom(nz);
    }
    el.addEventListener('wheel', onWheel, { passive: false });
    return function() { el.removeEventListener('wheel', onWheel); };
  }, []); // eslint-disable-line

  var nodeList = Object.values(scaledNodes);

  return (
    <div style={{ position:'fixed', inset:0, zIndex:2000, background:'#0a0710', display:'flex', direction:'rtl', fontFamily:'Vazirmatn,system-ui,sans-serif' }}>

      {/* ── پنل چپ: مانیتور ── */}
      <aside style={{ width:272, flexShrink:0, height:'100%', borderLeft:'1px solid rgba(150,120,210,0.12)', background:'linear-gradient(180deg,#0c0814,#0a0710)', display:'flex', flexDirection:'column', position:'relative', zIndex:10 }}>
        {/* header */}
        <div style={{ padding:'16px 18px 13px', borderBottom:'1px solid rgba(150,120,210,0.1)', display:'flex', alignItems:'flex-start', gap:10 }}>
          <button onClick={onClose} title="بستن نمای زنده" style={{ background:'none', border:'none', color:'rgba(255,255,255,0.35)', cursor:'pointer', fontSize:20, lineHeight:1, padding:0, flexShrink:0, marginTop:2 }}>✕</button>
          <div>
            <div style={{ fontSize:15, fontWeight:700, color:'#ece8f4' }}>مانیتور جریان</div>
            <div style={{ display:'flex', alignItems:'center', gap:6, fontSize:11, color:'#9a90ad', marginTop:5 }}>
              <span style={{ width:7, height:7, borderRadius:'50%', background:'#34d058', boxShadow:'0 0 8px #34d058', display:'inline-block', animation:'blink 1.4s ease-in-out infinite' }} />
              <span>زنده</span>
              <span style={{ marginRight:'auto' }}>· {toFa(total)} نفر در جریان</span>
            </div>
          </div>
        </div>
        {/* rows */}
        <div style={{ flex:1, overflowY:'auto', padding:'14px 16px', position:'relative' }}>
          {/* خط عمودی راهنما */}
          <div style={{ position:'absolute', top:30, bottom:24, width:2, right:33, background:'linear-gradient(180deg,rgba(168,85,247,0.5),rgba(255,154,60,0.5))', borderRadius:2 }} />

          {/* توکن‌های متحرک در مانیتور */}
          {monTokens.map(function(mt) {
            var ROW_H = 55; // ارتفاع هر ردیف (padding+content)
            var fromY = mt.fromIdx * ROW_H + ROW_H/2 - 7 + 14;
            var toY   = mt.toIdx   * ROW_H + ROW_H/2 - 7 + 14;
            return (
              <div key={mt.id} style={{
                position:'absolute', right:27, width:14, height:14, borderRadius:'50%',
                background:'radial-gradient(circle at 35% 30%,#7bf09a,#1faa44)',
                boxShadow:'0 0 10px 2px rgba(52,208,88,.8)', zIndex:3,
                top: fromY+'px',
                transition:'top .9s cubic-bezier(.5,0,.5,1), opacity .3s',
              }} ref={function(el){
                if(!el) return;
                requestAnimationFrame(function(){ el.style.top = toY+'px'; });
                setTimeout(function(){ if(el) el.style.opacity='0'; }, 900);
              }} />
            );
          })}

          {nodeList.map(function(node, idx) {
            var ns      = liveData&&liveData.nodeStats&&liveData.nodeStats[node.id];
            var count   = ns ? ns.count   : 0;
            var active  = ns ? (ns.active||0)  : 0;
            var dropped = ns ? (ns.dropped||0) : 0;
            var col    = COLORS[node.type]||'#8b5cf6';
            var ico    = ICONS[node.type]||'zap';
            var isBump = !!monBumps[node.id];
            return (
              <div key={node.id} style={{ display:'flex', alignItems:'center', gap:12, padding:'10px 10px', marginBottom:5, borderRadius:12, border:'1px solid transparent', zIndex:1, position:'relative', transition:'background .15s' }}
                onMouseEnter={function(e){ e.currentTarget.style.background='rgba(255,255,255,0.03)'; e.currentTarget.style.borderColor='rgba(150,120,210,0.14)'; }}
                onMouseLeave={function(e){ e.currentTarget.style.background=''; e.currentTarget.style.borderColor='transparent'; }}>
                <div style={{ width:34, height:34, borderRadius:10, background:'linear-gradient(135deg,'+col+','+col+'88)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, zIndex:2, boxShadow:'0 0 0 4px #0a0710' }}>
                  <Icon name={ico} size={16} color="#fff" />
                </div>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:13, fontWeight:600, color:'#ece8f4', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{node.label||FL_NODES[node.type]?.label||node.type}</div>
                  {count>0 ? (
                    <div style={{ display:'flex', gap:6, marginTop:3, flexWrap:'wrap' }}>
                      {active>0 && <span style={{ fontSize:10, color:'#34d058', background:'rgba(52,208,88,0.12)', padding:'1px 6px', borderRadius:5 }}>● {toFa(active)} در جریان</span>}
                      {dropped>0 && <span style={{ fontSize:10, color:'#fb923c', background:'rgba(251,146,60,0.12)', padding:'1px 6px', borderRadius:5 }}>■ {toFa(dropped)} متوقف</span>}
                    </div>
                  ) : (
                    <div style={{ fontSize:11, color:'#9a90ad', marginTop:1 }}>{FL_NODES[node.type]?.desc||node.type}</div>
                  )}
                </div>
                {count>0 ? (
                  <div style={{
                    fontSize:12, fontWeight:700,
                    color: active>0 && dropped===0 ? '#063417' : '#fff',
                    background: active>0 && dropped===0
                      ? 'linear-gradient(135deg,#34d058,#1faa44)'
                      : active===0
                        ? 'linear-gradient(135deg,#f97316,#ea580c)'
                        : 'linear-gradient(135deg,#34d058 40%,#f97316)',
                    minWidth:28, textAlign:'center', padding:'3px 9px', borderRadius:999, flexShrink:0,
                    transform: isBump ? 'scale(1.22)' : 'scale(1)',
                    transition:'transform .25s cubic-bezier(.34,1.56,.64,1)',
                    boxShadow: isBump ? '0 0 12px rgba(52,208,88,.8)' : 'none',
                  }}>
                    {toFa(count)}
                  </div>
                ) : (
                  <span style={{ width:7, height:7, borderRadius:'50%', background:'rgba(150,120,210,0.25)', display:'inline-block', flexShrink:0 }} />
                )}
              </div>
            );
          })}
        </div>
      </aside>

      {/* ── کانواس اصلی ── */}
      <div ref={canvasRef2}
        onContextMenu={function(e){ e.preventDefault(); setCtxMenu({ x: e.clientX, y: e.clientY }); }}
        style={{ flex:1, position:'relative', overflow:'auto',
          background:'radial-gradient(1100px 650px at 75% 5%, rgba(124,58,237,0.10), transparent 60%), radial-gradient(800px 550px at 15% 90%, rgba(236,72,153,0.07), transparent 55%), #0a0710',
          scrollbarWidth:'thin', scrollbarColor:'rgba(150,120,210,0.25) transparent' }}>

        {/* دکمه‌های zoom */}
        <div style={{ position:'sticky', top:12, left:'auto', zIndex:20, display:'flex', alignItems:'center', gap:4, float:'left', marginLeft:14, pointerEvents:'auto' }}>
          <button onClick={function(){ var nz=Math.min(2.5,zoomRef.current*1.15); zoomRef.current=nz; setZoom(nz); }} style={{ width:30, height:30, borderRadius:8, background:'rgba(20,14,30,0.85)', border:'1px solid rgba(150,120,210,0.25)', color:'#cbb8e8', cursor:'pointer', fontSize:16, display:'flex', alignItems:'center', justifyContent:'center', backdropFilter:'blur(8px)' }}>+</button>
          <button onClick={function(){ zoomRef.current=1; setZoom(1); }} style={{ minWidth:44, height:30, borderRadius:8, background:'rgba(20,14,30,0.85)', border:'1px solid rgba(150,120,210,0.25)', color:'#9a90ad', cursor:'pointer', fontSize:11, fontFamily:'inherit', backdropFilter:'blur(8px)' }}>{Math.round(zoom*100)}٪</button>
          <button onClick={function(){ var nz=Math.max(0.3,zoomRef.current*0.87); zoomRef.current=nz; setZoom(nz); }} style={{ width:30, height:30, borderRadius:8, background:'rgba(20,14,30,0.85)', border:'1px solid rgba(150,120,210,0.25)', color:'#cbb8e8', cursor:'pointer', fontSize:16, display:'flex', alignItems:'center', justifyContent:'center', backdropFilter:'blur(8px)' }}>−</button>
        </div>

        {/* محتوا با zoom — ابعاد بر اساس موقعیت نودها */}
        <div style={{ transformOrigin:'top center', transform:'scale('+zoom+')', transition:'transform .15s',
          minWidth:(nodeList.length ? Math.max.apply(null,nodeList.map(function(n){return (n.sx||0)+310;})) : 700)+'px',
          minHeight:(nodeList.length ? Math.max.apply(null,nodeList.map(function(n){return (n.sy||0)+140;})) : 600)+'px',
          position:'relative' }}>

        <svg style={{ position:'absolute', left:0, top:0, width:'100%', height:'100%', overflow:'visible', pointerEvents:'none', minHeight:600 }}>
          <defs>
            <linearGradient id="lv-g" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0" stopColor="#a855f7"/>
              <stop offset="1" stopColor="#ff9a3c"/>
            </linearGradient>
          </defs>

          {/* edges */}
          {edges.map(function(ed) {
            var fn=scaledNodes[ed.from], tn=scaledNodes[ed.to];
            if (!fn||!tn) return null;
            var d=lvEdgePath(fn,tn);
            return (
              <g key={ed.id}>
                <path d={d} fill="none" stroke="url(#lv-g)" strokeWidth="9" strokeLinecap="round" opacity=".4" style={{filter:'blur(7px)'}} />
                <path d={d} fill="none" stroke="url(#lv-g)" strokeWidth="2.2" strokeLinecap="round" opacity=".9" />
                <path d={d} fill="none" stroke="#ffd9a0" strokeWidth="2.2" strokeLinecap="round" strokeDasharray="2 16" opacity=".8">
                  <animate attributeName="strokeDashoffset" from="0" to="-180" dur="2.2s" repeatCount="indefinite" />
                </path>
              </g>
            );
          })}

        </svg>

        {/* travelers — HTML divs با CSS offset-path (SMIL timing bug رو fix می‌کنه) */}
        {travelers.map(function(t) {
          return (
            <div key={t.id} style={{
              position:'absolute', left:0, top:0, width:22, height:22,
              marginLeft:-11, marginTop:-11,
              borderRadius:'50%',
              background:'radial-gradient(circle at 35% 30%,rgba(255,255,255,0.9),'+t.color+')',
              boxShadow:'0 0 16px 4px '+t.color+',0 0 6px rgba(255,255,255,0.55)',
              pointerEvents:'none', zIndex:5,
              offsetPath:'path("'+t.path+'")',
              offsetDistance:'0%',
              opacity:0,
              animationName:'ptGo',
              animationDuration:'1.9s',
              animationDelay:t.delay+'ms',
              animationTimingFunction:'ease',
              animationFillMode:'forwards',
            }} />
          );
        })}

        {/* nodes */}
        {nodeList.map(function(node) {
          var ns     = liveData&&liveData.nodeStats&&liveData.nodeStats[node.id];
          var count  = ns ? ns.count  : 0;
          var active = ns ? (ns.active||0) : 0;   // pending گام بعدی دارند
          var dropped= ns ? (ns.dropped||0) : 0;  // متوقف شده‌اند
          var leads  = ns ? (ns.leads||[]) : [];
          var col    = COLORS[node.type]||'#8b5cf6';
          var ico    = ICONS[node.type]||'zap';
          var isCircle = node.type==='condition'||node.type==='trigger';

          // badge: سبز = active، نارنجی = dropped، ترکیبی اگه هر دو
          var badgeBg = active>0 && dropped===0
            ? 'linear-gradient(135deg,#34d058,#1faa44)'          // همه active
            : active===0 && dropped>0
              ? 'linear-gradient(135deg,#f97316,#ea580c)'         // همه dropped
              : 'linear-gradient(135deg,#34d058 40%,#f97316)';    // ترکیبی
          var badgeColor = active>0 && dropped===0 ? '#063417' : '#fff';

          var badge = count>0 ? (
            <div onClick={function(e){ e.stopPropagation(); setPopNode({nodeId:node.id,label:node.label||FL_NODES[node.type]?.label||'نود',leads,active,dropped}); setPopChat(null); }}
              style={{ position:'absolute', top:-20, left:'50%', transform:'translateX(-50%)', display:'inline-flex', alignItems:'center', gap:5,
                background:badgeBg, color:badgeColor, fontWeight:700, fontSize:12, padding:'4px 11px', borderRadius:999,
                boxShadow:'0 6px 18px -4px rgba(34,170,68,.6), inset 0 1px 0 rgba(255,255,255,.3)', border:'1px solid rgba(255,255,255,.22)',
                whiteSpace:'nowrap', cursor:'pointer', zIndex:6, transition:'transform .18s cubic-bezier(.34,1.56,.64,1)',
                animation:'lvBadgePop .4s ease' }}
              onMouseEnter={function(e){ e.currentTarget.style.transform='translateX(-50%) scale(1.08)'; }}
              onMouseLeave={function(e){ e.currentTarget.style.transform='translateX(-50%) scale(1)'; }}>
              {active>0 && <span style={{width:7,height:7,borderRadius:'50%',background:badgeColor,opacity:.7,flexShrink:0}} />}
              {toFa(count)} نفر
              {dropped>0 && active>0 && <span style={{fontSize:10,opacity:.8}}>({toFa(dropped)}↓)</span>}
              {dropped>0 && active===0 && <span style={{fontSize:10,opacity:.8}}>متوقف</span>}
            </div>
          ) : null;

          if (isCircle) {
            return (
              <div key={node.id} style={{ position:'absolute', left:node.sx-42, top:node.sy-42, textAlign:'center' }}>
                <div style={{ width:84, height:84, borderRadius:'50%', background:'radial-gradient(circle at 35% 28%,'+col+','+col+'88 70%)', display:'flex', alignItems:'center', justifyContent:'center', color:'#fff', boxShadow:'0 12px 32px -8px rgba(0,0,0,.75), inset 0 2px 6px rgba(255,255,255,.18), 0 0 0 3px '+col+'44', position:'relative' }}>
                  <Icon name={ico} size={28} color="#fff" />
                  {badge}
                </div>
                <div style={{ marginTop:8, fontSize:13, fontWeight:600, color:'#f3eefb', whiteSpace:'nowrap' }}>{node.label||FL_NODES[node.type]?.label||node.type}</div>
              </div>
            );
          }

          return (
            <div key={node.id} style={{ position:'absolute', left:node.sx, top:node.sy, width:270, position:'absolute' }}>
              {badge}
              <div style={{ background:'rgba(26,20,36,0.75)', border:'1px solid rgba(150,120,210,0.18)', borderRadius:18, padding:'14px 15px', backdropFilter:'blur(14px)', WebkitBackdropFilter:'blur(14px)', boxShadow:'0 18px 42px -22px rgba(0,0,0,.95), inset 0 1px 0 rgba(255,255,255,.04)', display:'flex', alignItems:'center', gap:12 }}>
                <div style={{ width:42, height:42, borderRadius:13, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', background:'linear-gradient(150deg,'+col+','+col+'88)', boxShadow:'0 6px 16px -4px rgba(0,0,0,.6)' }}>
                  <Icon name={ico} size={20} color="#fff" />
                </div>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:14, fontWeight:600, color:'#f3eefb', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{node.label||FL_NODES[node.type]?.label||node.type}</div>
                  <div style={{ fontSize:11, color:'#9a90ad', marginTop:3 }}>{FL_NODES[node.type]?.desc||node.type}</div>
                </div>
                <div style={{ width:28, height:28, borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', background:'rgba(255,255,255,.04)', border:'1px solid rgba(255,255,255,.07)', flexShrink:0 }}>
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M7 10l5 5 5-5" stroke="rgba(155,144,173,1)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>
                </div>
              </div>
            </div>
          );
        })}

        </div>

        <div style={{ position:'sticky', bottom:14, left:14, zIndex:20, display:'inline-block', marginTop:8, fontSize:11, color:'#9a90ad', background:'rgba(20,14,30,0.65)', padding:'6px 12px', borderRadius:9, border:'1px solid rgba(150,120,210,0.12)', pointerEvents:'none' }}>
          Ctrl+Scroll برای زوم · راست‌کلیک = چیدمان · روی بَجت کلیک = لیست لیدها
        </div>
      </div>

      {/* ── پاپ‌اور ── */}
      {popNode && (
        <div style={{ position:'fixed', width: popChat ? 380 : 300, background:'rgba(12,8,22,0.98)', backdropFilter:'blur(24px)', border:'1px solid rgba(150,120,210,.28)', borderRadius:18, zIndex:3000, boxShadow:'0 28px 70px -20px rgba(0,0,0,.98), 0 0 0 1px rgba(150,120,210,0.1)', overflow:'hidden', top:'50%', transform:'translateY(-50%)', left:288, maxHeight:'80vh', display:'flex', flexDirection:'column', transition:'width .2s' }}>
          {!popChat ? (
            <React.Fragment>
              <div style={{ display:'flex', alignItems:'center', gap:9, padding:'12px 13px', borderBottom:'1px solid rgba(150,120,210,.1)' }}>
                <span style={{ width:9, height:9, borderRadius:'50%', background:'#34d058', boxShadow:'0 0 8px #34d058', flexShrink:0 }} />
                <span style={{ fontSize:13, fontWeight:700, flex:1, color:'#ece8f4' }}>{popNode.label}</span>
                <div style={{ display:'flex', gap:6, alignItems:'center' }}>
                  {(popNode.active||0)>0 && <span style={{ fontSize:10, color:'#34d058', background:'rgba(52,208,88,0.15)', padding:'2px 7px', borderRadius:5 }}>● {toFa(popNode.active)} فعال</span>}
                  {(popNode.dropped||0)>0 && <span style={{ fontSize:10, color:'#fb923c', background:'rgba(251,146,60,0.15)', padding:'2px 7px', borderRadius:5 }}>■ {toFa(popNode.dropped)} متوقف</span>}
                </div>
                <button onClick={function(){ setPopNode(null); }} style={{ background:'none', border:'none', color:'rgba(255,255,255,0.3)', cursor:'pointer', fontSize:16, lineHeight:1, marginRight:4 }}>✕</button>
              </div>
              <div style={{ maxHeight:280, overflowY:'auto', padding:6 }}>
                {popNode.leads.length===0 ? (
                  <div style={{ padding:'26px 14px', textAlign:'center', color:'#9a90ad', fontSize:12 }}>فعلاً کسی در این نود نیست</div>
                ) : popNode.leads.map(function(lead,i) {
                  var avColors=['#7c3aed','#db2777','#0891b2','#ea580c','#16a34a','#9333ea'];
                  var col=avColors[i%avColors.length];
                  var init=(lead.name||lead.phone||'?')[0];
                  return (
                    <div key={i} onClick={function(){ if(lead.conv_id) setPopChat({lead,color:col,init}); }}
                      style={{ display:'flex', alignItems:'center', gap:10, padding:'9px 10px', borderRadius:11, cursor:lead.conv_id?'pointer':'default', transition:'background .15s',
                        borderLeft: lead.active ? '2px solid #34d058' : '2px solid #f97316', paddingRight:10 }}
                      onMouseEnter={function(e){ if(lead.conv_id) e.currentTarget.style.background='rgba(255,255,255,0.05)'; }}
                      onMouseLeave={function(e){ e.currentTarget.style.background=''; }}>
                      <div style={{ width:34, height:34, borderRadius:'50%', background:col, display:'flex', alignItems:'center', justifyContent:'center', fontSize:13, fontWeight:700, color:'#fff', flexShrink:0 }}>{init}</div>
                      <div style={{ flex:1, minWidth:0 }}>
                        <div style={{ fontSize:13, fontWeight:600, color:'#ece8f4', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{lead.name||lead.phone||'لید ناشناس'}</div>
                        <div style={{ fontSize:11, marginTop:1, color: lead.active ? '#34d058' : '#fb923c' }}>
                          {lead.active ? '● در جریان' : '■ متوقف'}
                          {lead.conv_id ? ' · مکالمه ←' : ''}
                        </div>
                      </div>
                      {lead.conv_id && <svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M14 7l-5 5 5 5" stroke="#9a90ad" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
                    </div>
                  );
                })}
              </div>
            </React.Fragment>
          ) : (
            <ConvViewLive lead={popChat.lead} color={popChat.color} init={popChat.init} onBack={function(){ setPopChat(null); }} />
          )}
        </div>
      )}

      {/* ── منوی راست‌کلیک چیدمان ── */}
      {ctxMenu && (
        <div onClick={function(e){ e.stopPropagation(); }}
          style={{ position:'fixed', left:ctxMenu.x, top:Math.min(ctxMenu.y, window.innerHeight-220), zIndex:4000,
            background:'rgba(10,6,20,0.97)', backdropFilter:'blur(18px)',
            border:'1px solid rgba(150,120,210,0.35)', borderRadius:14,
            padding:'6px', boxShadow:'0 20px 60px rgba(0,0,0,0.8)', minWidth:200, direction:'rtl' }}>
          <div style={{ fontSize:11, color:'#9a90ad', padding:'5px 11px 8px', borderBottom:'1px solid rgba(255,255,255,0.07)', marginBottom:4 }}>
            چیدمان نودها
          </div>
          {[
            { id:'vertical',   icon:'⬇', label:'عمودی', desc:'از بالا به پایین' },
            { id:'horizontal', icon:'➡', label:'افقی',  desc:'از راست به چپ' },
            { id:'original',   icon:'⊞', label:'طراح',  desc:'موقعیت اصلی فلو' },
          ].map(function(opt) {
            var active = layoutMode === opt.id;
            return (
              <div key={opt.id}
                onClick={function(){ setLayoutMode(opt.id); setCtxMenu(null); }}
                style={{ padding:'9px 12px', borderRadius:9, cursor:'pointer', display:'flex', alignItems:'center', gap:10,
                  background: active ? 'rgba(139,92,246,0.22)' : 'transparent',
                  border: active ? '1px solid rgba(139,92,246,0.35)' : '1px solid transparent',
                  marginBottom:3, transition:'background .12s' }}
                onMouseEnter={function(e){ if(!active) e.currentTarget.style.background='rgba(255,255,255,0.06)'; }}
                onMouseLeave={function(e){ if(!active) e.currentTarget.style.background='transparent'; }}>
                <span style={{ fontSize:18, width:24, textAlign:'center', flexShrink:0 }}>{opt.icon}</span>
                <div style={{ flex:1 }}>
                  <div style={{ fontSize:13, fontWeight:active?700:500, color: active ? '#c4b5fd' : '#ece8f4' }}>{opt.label}</div>
                  <div style={{ fontSize:10, color:'#9a90ad', marginTop:1 }}>{opt.desc}</div>
                </div>
                {active && <span style={{ color:'#a78bfa', fontSize:14 }}>✓</span>}
              </div>
            );
          })}
        </div>
      )}

      <style>{`
        @keyframes blink{50%{opacity:.35}}
        @keyframes spin{to{transform:rotate(360deg)}}
        @keyframes lvBadgePop{0%{transform:translateX(-50%) scale(.7);opacity:0}60%{transform:translateX(-50%) scale(1.12)}100%{transform:translateX(-50%) scale(1);opacity:1}}
        @keyframes ptGo{
          0%{offset-distance:0%;opacity:0;transform:scale(0.35)}
          8%{opacity:1;transform:scale(1)}
          80%{opacity:1;transform:scale(1)}
          100%{offset-distance:100%;opacity:0;transform:scale(0.4)}
        }
      `}</style>
    </div>
  );
}

window.FlowBuilder = FlowBuilder;
