// ─── تشخیص نوع رویداد از متن پیام ───────────────────────
function detectEventType(text) {
  const t = text.toLowerCase();
  if (/سلام|هلو|درود|خوندم|دیدم|اومدم/.test(t))            return { type: 'script',   reason: 'پیام اولیه — اسکریپت جواب می‌ده' };
  if (/پرداخت|کارت|بانک|واریز|رها|نیمه|ناقص/.test(t))       return { type: 'trigger',  reason: 'سیگنال پرداخت رها شده — تریگر فوری فعال' };
  if (/مشاوره|جلسه|قرار|وقت|زمان|ساعت/.test(t))             return { type: 'followup', reason: 'درخواست زمان‌بندی — فالوآپ مدیریت می‌کنه' };
  if (/قیمت|هزینه|تخفیف|اقساط|چقدر|کمپین/.test(t))          return { type: 'campaign', reason: 'سوال قیمت — کمپین مناسب پیدا می‌کنه' };
  if (/خریدم|پرداخت کردم|ثبت‌نام|خرید|گرفتم/.test(t))       return { type: 'campaign', reason: 'تأیید خرید — کمپین Upsell فعال' };
  if (/نیستم|بعداً|فردا|بعد|بیشتر فکر|شاید/.test(t))        return { type: 'followup', reason: 'تعلل — فالوآپ زمان‌بندی می‌کنه' };
  if (/چطور|چی|چرا|توضیح|بگو|بیشتر/.test(t))                return { type: 'script',   reason: 'سوال عمومی — اسکریپت هدایت می‌کنه' };
  return { type: 'bot', reason: 'پیام نامشخص — بات نگه می‌داره' };
}

// ─── پاسخ شبیه‌سازی‌شده برای هر handler ──────────────────
function simulateReply(ownerType, msgText) {
  const t = msgText;
  const replies = {
    trigger: [
      'متوجه شدیم پرداختت ناتمام موند 🙏 الان یه لینک امن برات می‌فرستیم.',
      'یه اتفاق افتاده که نیاز به پیگیری فوری داره. یه لحظه صبر کن.',
    ],
    followup: [
      'وقت مشاوره‌ات ثبت شد. قبل از جلسه یادآوری می‌فرستیم.',
      'باشه، یه زمان مناسب هماهنگ می‌کنیم. چه ساعتی بهتره؟',
    ],
    campaign: [
      'بذار مشاورمون قیمت دقیق رو باهاتون در میان بذاره. شماره داری؟',
      'ممنون از خریدت! یه پیشنهاد ویژه برات داریم 🎁',
      'این کمپین دقیقاً برای این موقعیت طراحی شده. بیشتر بگو.',
    ],
    script: [
      'سلام! در خدمتتم 🙏 بفرمایید.',
      'چطور می‌تونم کمکتون کنم؟',
      'هدفتون از برنامه‌نویسی چیه؟',
    ],
    bot: [
      'پیامتون دریافت شد. به زودی پاسخ می‌دیم.',
      'ممنون از تماستون. یه لحظه صبر کنید.',
    ],
  };
  const list = replies[ownerType] || replies.bot;
  return list[Math.floor(Math.random() * list.length)];
}

// ─── کامپوننت چت شبیه‌ساز ────────────────────────────────
const ChatSimulator = ({ priorityOrder, colors, handlerLabels, entities }) => {
  const [messages, setMessages]       = useState([]);
  const [input, setInput]             = useState('');
  const [activeOwner, setActiveOwner] = useState(null);
  const [viewMode, setViewMode]       = useState('chat'); // chat | admin
  const bottomRef = useRef(null);

  useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);

  const send = () => {
    const txt = input.trim();
    if (!txt) return;
    const { type, reason } = detectEventType(txt);

    const inIdx    = priorityOrder.indexOf(type);
    const curIdx   = activeOwner ? priorityOrder.indexOf(activeOwner) : 999;
    const wins     = inIdx <= curIdx;
    const newOwner = wins ? type : activeOwner;
    const prevOwner = activeOwner;

    // موجودیت فعال برای log
    const activeEntForLog = ((entities && entities[newOwner]) || [])[0];

    // log سیستمی
    const logParts = [
      `تشخیص: ${handlerLabels[type]} (اولویت #${inIdx + 1})`,
      wins && prevOwner && prevOwner !== type
        ? `${handlerLabels[prevOwner]} pause شد`
        : !wins
        ? `${handlerLabels[newOwner]} همچنان در کنترل (اولویت بالاتر)`
        : null,
      activeEntForLog ? `← ${activeEntForLog.name}` : null,
    ].filter(Boolean);

    // موجودیت فعال برای نمایش
    const ents = (entities && entities[newOwner]) || [];
    const activeEnt = ents[0];
    const entLabel = newOwner === 'campaign' ? 'کمپین فعال'
                   : newOwner === 'script'   ? 'اسکریپت فعال'
                   : newOwner === 'followup' ? 'الگوی فالوآپ'
                   : newOwner === 'trigger'  ? 'تریگر'
                   : 'بات';

    // داده ادمین
    const adminData = {
      'handler فعال': handlerLabels[newOwner],
      'اولویت': `#${priorityOrder.indexOf(newOwner) + 1}`,
      ...(activeEnt ? { [entLabel]: activeEnt.name } : {}),
      'تغییر': wins && prevOwner && prevOwner !== type ? `${handlerLabels[prevOwner]} → ${handlerLabels[newOwner]}` : 'بدون تغییر',
      'دلیل': reason,
      'action': newOwner === 'trigger' ? 'ارسال پیام فوری' : newOwner === 'followup' ? 'زمان‌بندی تماس' : newOwner === 'campaign' ? 'اجرای flow کمپین' : newOwner === 'script' ? 'اجرای اسکریپت' : 'نگه‌داری لید',
    };

    setMessages(prev => [...prev,
      { kind: 'user',   text: txt },
      { kind: 'log',    parts: logParts, owner: newOwner, changed: wins && prevOwner !== newOwner },
      { kind: 'reply',  text: simulateReply(newOwner, txt), adminData, owner: newOwner },
    ]);
    setActiveOwner(newOwner);
    setInput('');
  };

  const reset = () => { setMessages([]); setActiveOwner(null); };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>

      {/* نوار بالا */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        {/* مالک فعلی */}
        <div style={{
          flex: 1, display: 'flex', alignItems: 'center', gap: 8,
          padding: '7px 12px', borderRadius: 9,
          background: activeOwner ? colors[activeOwner] + '10' : 'rgba(255,255,255,0.03)',
          border: `1px solid ${activeOwner ? colors[activeOwner] + '30' : 'rgba(255,255,255,0.07)'}`,
          fontSize: 11,
        }}>
          <div style={{ width: 7, height: 7, borderRadius: '50%', background: activeOwner ? colors[activeOwner] : '#334155' }} />
          <span style={{ color: '#475569' }}>مالک فعلی:</span>
          <span style={{ color: activeOwner ? colors[activeOwner] : '#334155', fontWeight: 700 }}>
            {activeOwner ? handlerLabels[activeOwner] : 'هنوز پیامی نیست'}
          </span>
        </div>
        {/* سوئیچ نمایش */}
        <div style={{ display: 'flex', background: 'rgba(255,255,255,0.04)', borderRadius: 8, padding: 3, gap: 2 }}>
          {[['chat','چت'],['admin','ادمین']].map(([v, l]) => (
            <button key={v} onClick={() => setViewMode(v)} style={{
              padding: '4px 12px', borderRadius: 6, border: 'none', cursor: 'pointer', fontSize: 11,
              background: viewMode === v ? 'rgba(255,255,255,0.1)' : 'transparent',
              color: viewMode === v ? '#e2e8f0' : '#475569', fontWeight: viewMode === v ? 600 : 400,
            }}>{l}</button>
          ))}
        </div>
        {messages.length > 0 && (
          <button onClick={reset} style={{ fontSize: 10, color: '#334155', background: 'none', border: 'none', cursor: 'pointer', padding: '4px 8px' }}>ریست</button>
        )}
      </div>

      {/* پیام‌ها */}
      <div style={{ height: 340, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 6, padding: '4px 2px' }}>
        {messages.length === 0 && (
          <div style={{ textAlign: 'center', color: '#334155', fontSize: 12, marginTop: 60 }}>
            یه پیام بفرست تا ببینیم چی فعال می‌شه 👇
          </div>
        )}
        {messages.map((m, i) => {
          if (m.kind === 'user') return (
            <div key={i} style={{ display: 'flex', justifyContent: 'flex-start' }}>
              <div style={{ maxWidth: '72%', padding: '8px 12px', borderRadius: '12px 12px 12px 4px', fontSize: 12, lineHeight: 1.6, background: 'rgba(59,130,246,0.12)', border: '1px solid rgba(59,130,246,0.2)', color: '#93C5FD' }}>
                {m.text}
              </div>
            </div>
          );

          if (m.kind === 'log') return (
            <div key={i} style={{ display: 'flex', justifyContent: 'center' }}>
              <div style={{
                padding: '5px 12px', borderRadius: 20, fontSize: 10, lineHeight: 1.7,
                background: m.changed ? colors[m.owner] + '12' : 'rgba(255,255,255,0.04)',
                border: `1px solid ${m.changed ? colors[m.owner] + '30' : 'rgba(255,255,255,0.06)'}`,
                color: m.changed ? colors[m.owner] : '#475569',
                display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'center',
              }}>
                {m.parts.map((p, pi) => (
                  <span key={pi}>
                    {pi > 0 && <span style={{ opacity: 0.3, marginLeft: 8 }}>·</span>}
                    {p}
                  </span>
                ))}
              </div>
            </div>
          );

          if (m.kind === 'reply') return (
            <div key={i} style={{ display: 'flex', justifyContent: 'flex-end' }}>
              <div style={{
                maxWidth: '78%', borderRadius: '12px 12px 4px 12px', overflow: 'hidden',
                border: `1px solid ${colors[m.owner]}30`,
              }}>
                {/* هدر handler */}
                <div style={{ padding: '5px 12px', background: colors[m.owner] + '20', fontSize: 10, fontWeight: 700, color: colors[m.owner], display: 'flex', alignItems: 'center', gap: 6 }}>
                  <div style={{ width: 6, height: 6, borderRadius: '50%', background: colors[m.owner] }} />
                  {handlerLabels[m.owner]}
                </div>
                {/* محتوا */}
                <div style={{ padding: '8px 12px', background: 'rgba(255,255,255,0.04)', fontSize: 12, lineHeight: 1.7 }}>
                  {viewMode === 'chat' ? (
                    <span style={{ color: '#cbd5e1' }}>{m.text}</span>
                  ) : (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                      {Object.entries(m.adminData).map(([k, v]) => (
                        <div key={k} style={{ display: 'flex', gap: 8, alignItems: 'baseline' }}>
                          <span style={{ color: '#475569', minWidth: 80, fontSize: 10 }}>{k}</span>
                          <span style={{ color: '#94a3b8', fontFamily: 'JetBrains Mono', fontSize: 11 }}>{v}</span>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              </div>
            </div>
          );
          return null;
        })}
        <div ref={bottomRef} />
      </div>

      {/* input */}
      <div style={{ display: 'flex', gap: 8 }}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && send()}
          placeholder="پیام بفرست... مثلاً: سلام / مشاوره می‌خوام / قیمت چنده؟"
          style={{ flex: 1, padding: '9px 14px', borderRadius: 10, fontSize: 12, background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', color: '#e2e8f0', outline: 'none' }}
        />
        <button onClick={send} style={{ padding: '9px 16px', borderRadius: 10, border: 'none', background: '#3B82F6', color: '#fff', fontSize: 12, fontWeight: 600, cursor: 'pointer', flexShrink: 0 }}>
          ارسال
        </button>
      </div>

      {/* نمونه‌ها */}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
        {['سلام','قیمت چنده؟','مشاوره می‌خوام','پرداختم رها شد','بعداً بهتون زنگ می‌زنم','خریدم','نمی‌دونم'].map(s => (
          <button key={s} onClick={() => setInput(s)} style={{ padding: '3px 10px', borderRadius: 20, fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.08)', background: 'rgba(255,255,255,0.03)', color: '#475569' }}>
            {s}
          </button>
        ))}
      </div>
    </div>
  );
};

// ─── engine: شبیه‌ساز اولویت ─────────────────────────────
// هر رویداد یه handler_type داره. engine بر اساس ترتیب فعلی
// تعیین می‌کنه کی کنترل رو می‌گیره.
function runSimulation(events, priorityOrder) {
  let activeType = null;
  return events.map(ev => {
    const inIdx  = priorityOrder.indexOf(ev.type);
    const curIdx = activeType ? priorityOrder.indexOf(activeType) : 999;
    const wins   = inIdx <= curIdx; // عدد کوچکتر = اولویت بالاتر
    const prev   = activeType;
    if (wins) activeType = ev.type;
    return { ...ev, owner: wins ? ev.type : activeType, changed: wins && prev !== ev.type, from: wins && prev ? prev : null };
  });
}

// ─── سناریوهای واقعی سیستم ────────────────────────────────
const SCENARIOS = [
  {
    id: 's1', name: 'علی — لید جدید', emoji: '👤',
    desc: 'لید تازه وارد می‌شه، اسکریپت شروع می‌کنه، بعد لینک پرداخت رو رها می‌کنه',
    events: [
      { t:0,  icon:'👤', label:'وارد شد',             type:'script'   },
      { t:10, icon:'💬', label:'پیام اول فرستاد',      type:'script'   },
      { t:25, icon:'📢', label:'کمپین خوش‌آمدگویی',   type:'campaign' },
      { t:45, icon:'🔗', label:'لینک پرداخت باز کرد', type:'trigger'  },
      { t:70, icon:'⏰', label:'۲۴ ساعت بی‌جواب',      type:'campaign' },
      { t:90, icon:'✅', label:'پرداخت کرد',           type:'campaign' },
    ],
  },
  {
    id: 's2', name: 'سارا — No-Show', emoji: '📅',
    desc: 'مشاوره رزرو کرد ولی نیومد — تریگر فوری فعال می‌شه',
    events: [
      { t:0,  icon:'👤', label:'وارد شد',             type:'script'   },
      { t:15, icon:'📢', label:'کمپین فعال شد',        type:'campaign' },
      { t:30, icon:'📅', label:'مشاوره رزرو کرد',     type:'followup' },
      { t:50, icon:'❌', label:'No-Show مشاوره',       type:'trigger'  },
      { t:65, icon:'📞', label:'فالوآپ پیگیری',        type:'followup' },
      { t:85, icon:'💰', label:'خرید کرد',             type:'campaign' },
    ],
  },
  {
    id: 's3', name: 'رضا — لید سرد', emoji: '🧊',
    desc: 'بعد از ۳۰ روز بی‌خبری کمپین بازگشت فعال می‌شه',
    events: [
      { t:0,  icon:'👤', label:'وارد شد',             type:'script'   },
      { t:10, icon:'😶', label:'۳۰ روز بی‌خبر',        type:'bot'      },
      { t:35, icon:'📢', label:'کمپین بازگشت',         type:'campaign' },
      { t:55, icon:'💬', label:'دوباره پیام داد',       type:'trigger'  },
      { t:75, icon:'📞', label:'مشاوره خواست',          type:'followup' },
    ],
  },
  {
    id: 's4', name: 'مریم — وسط کمپین', emoji: '⚡',
    desc: 'وسط کمپین یه تریگر فوری میاد — کی برنده می‌شه؟',
    events: [
      { t:0,  icon:'📢', label:'کمپین پیگیری فعاله',  type:'campaign' },
      { t:20, icon:'⚡', label:'تریگر keyword',         type:'trigger'  },
      { t:40, icon:'📢', label:'کمپین ادامه داد',       type:'campaign' },
      { t:60, icon:'⏰', label:'فالوآپ زمان‌بندی',      type:'followup' },
      { t:80, icon:'✅', label:'ثبت‌نام کرد',           type:'campaign' },
    ],
  },
  {
    id: 's5', name: 'حسین — بعد از خرید', emoji: '🛒',
    desc: 'بعد از خرید کمپین Upsell شروع می‌کنه',
    events: [
      { t:0,  icon:'✅', label:'خرید کرد',             type:'campaign' },
      { t:15, icon:'📢', label:'Upsell فعال شد',        type:'campaign' },
      { t:35, icon:'💬', label:'سوال فرستاد',           type:'script'   },
      { t:55, icon:'⚡', label:'تریگر keyword خاص',     type:'trigger'  },
      { t:75, icon:'📢', label:'Upsell ادامه داد',       type:'campaign' },
    ],
  },
  {
    id: 's6', name: 'نیلوفر — ریکاوری', emoji: '🔄',
    desc: 'مکالمه رها شد — بات نگه می‌داره تا handler بیاد',
    events: [
      { t:0,  icon:'👤', label:'وارد شد',              type:'script'   },
      { t:15, icon:'😶', label:'مکالمه قطع شد',        type:'bot'      },
      { t:30, icon:'⚡', label:'تریگر ریکاوری',         type:'trigger'  },
      { t:50, icon:'📢', label:'کمپین ریکاوری',         type:'campaign' },
      { t:70, icon:'📞', label:'فالوآپ تلفنی',          type:'followup' },
    ],
  },
];

// ─── کامپوننت Timeline یک سناریو ─────────────────────────
const ScenarioTimeline = ({ scenario, priorityOrder, colors }) => {
  const result = runSimulation(scenario.events, priorityOrder);
  const maxT   = Math.max(...scenario.events.map(e => e.t)) + 15;

  return (
    <div style={{
      background: 'rgba(255,255,255,0.02)',
      border: '1px solid rgba(255,255,255,0.07)',
      borderRadius: 12, padding: '16px 20px', marginBottom: 10,
    }}>
      {/* header سناریو */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
        <span style={{ fontSize: 16 }}>{scenario.emoji}</span>
        <div>
          <div style={{ fontSize: 13, fontWeight: 700, color: '#e2e8f0' }}>{scenario.name}</div>
          <div style={{ fontSize: 11, color: '#475569' }}>{scenario.desc}</div>
        </div>
      </div>

      {/* خط timeline */}
      <div style={{ position: 'relative', margin: '32px 0 28px' }}>
        {/* track */}
        <div style={{ height: 4, background: 'rgba(255,255,255,0.06)', borderRadius: 2, position: 'relative' }}>
          {/* رنگ‌آمیزی بر اساس owner */}
          {result.map((ev, i) => {
            const next = result[i + 1];
            const x1 = ev.t / maxT * 100;
            const x2 = next ? next.t / maxT * 100 : 100;
            return (
              <div key={i} style={{
                position: 'absolute', right: x1 + '%', width: (x2 - x1) + '%',
                height: '100%', background: colors[ev.owner] || '#475569',
                borderRadius: 2, opacity: 0.8,
              }} />
            );
          })}
        </div>

        {/* pin‌های رویداد */}
        {result.map((ev, i) => (
          <div key={i} style={{
            position: 'absolute', right: (ev.t / maxT * 100) + '%',
            transform: 'translateX(50%)',
            top: -24, display: 'flex', flexDirection: 'column', alignItems: 'center',
          }}>
            <div style={{ fontSize: 13, marginBottom: 3 }}>{ev.icon}</div>
            <div style={{
              width: 8, height: 8, borderRadius: '50%',
              background: colors[ev.owner], border: '2px solid #0d0d14',
              position: 'relative', top: 14,
            }} />
            <div style={{
              position: 'absolute', top: 28, fontSize: 9, color: '#64748b',
              whiteSpace: 'nowrap', textAlign: 'center', maxWidth: 70,
              lineHeight: 1.3,
            }}>{ev.label}</div>
          </div>
        ))}
      </div>

      {/* لاگ تغییر owner */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 3, marginTop: 8 }}>
        {result.filter(ev => ev.changed).map((ev, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 10 }}>
            <div style={{ width: 6, height: 6, borderRadius: '50%', background: colors[ev.owner], flexShrink: 0 }} />
            <span style={{ color: '#475569' }}>t+{ev.t}</span>
            <span style={{ color: colors[ev.owner], fontWeight: 600 }}>{ev.owner === 'trigger' ? 'تریگر' : ev.owner === 'followup' ? 'فالوآپ' : ev.owner === 'campaign' ? 'کمپین' : ev.owner === 'script' ? 'اسکریپت' : 'بات'}</span>
            <span style={{ color: '#334155' }}>کنترل گرفت</span>
            {ev.from && <span style={{ color: '#1e293b' }}>← از {ev.from === 'trigger' ? 'تریگر' : ev.from === 'followup' ? 'فالوآپ' : ev.from === 'campaign' ? 'کمپین' : ev.from === 'script' ? 'اسکریپت' : 'بات'}</span>}
          </div>
        ))}
      </div>
    </div>
  );
};

// ─── MindMap — نقشه زنده با انیمیشن bezier (طراحی اصلی) ─────
const MindMap = ({ entities, colors, handlerLabels, brainScript, items, onBrainClick, onRefresh, onClose }) => {

  /* ── load Vazirmatn ── */
  useEffect(() => {
    if (!document.getElementById('_vzf')) {
      const l = document.createElement('link');
      l.id='_vzf'; l.rel='stylesheet';
      l.href='https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap';
      document.head.appendChild(l);
    }
  }, []);

  /* ── refs ── */
  const stageRef = useRef(null);
  const rafRef   = useRef(null);
  const animRef  = useRef({ i:0, phase:'incoming', start:0, paused:false, pausedEl:0 });
  const logRef   = useRef([]);
  const msgsRef  = useRef([]);
  const [idle, setIdle]   = useState(false);

  /* ── state ── */
  const [tick, setTick]             = useState(0);
  const [stageScale, setStageScale] = useState(0.78);
  const [chatInput, setChatInput]   = useState('');
  const [mode, setMode]             = useState('live'); // live | trace | sandbox | settings
  const [liveEvents, setLiveEvents] = useState([]);
  const [selectedNode, setSelectedNode] = useState(null);
  const [selectedEvent, setSelectedEvent] = useState(null);
  const [dashboard, setDashboard]   = useState(null);
  const [sandboxText, setSandboxText] = useState('');
  const [sandboxPlatform, setSandboxPlatform] = useState('bale');
  const [sandboxLoading, setSandboxLoading] = useState(false);
  const [sandboxResult, setSandboxResult] = useState(null);
  const [selectedStep, setSelectedStep] = useState(null);
  const [sandboxTurns, setSandboxTurns] = useState([]);
  const [sandboxRecording, setSandboxRecording] = useState(false);
  const [sandboxVoiceStatus, setSandboxVoiceStatus] = useState('');
  const modeRef = useRef(mode);
  const sandboxSessionRef = useRef(`lab-${Date.now()}-${Math.random().toString(36).slice(2,8)}`);
  const sandboxRecorderRef = useRef(null);
  const sandboxAudioFileRef = useRef(null);
  const sandboxStreamRef = useRef(null);
  const sandboxAudioChunksRef = useRef([]);
  const sandboxDiscardRecordingRef = useRef(false);
  const packetPauseRef = useRef({ hovering:false, locked:false, wasPaused:false });
  useEffect(()=>{ modeRef.current=mode; },[mode]);

  /* ── helpers ── */
  const BX = 500, BY = 348;
  const D  = { incoming:1800, thinking:2200, routing:2200, stateflow:2800, responding:1500, outgoing:2300, returned:1300, gap:900 };
  const col  = (h, a) => a == null ? `oklch(0.74 0.19 ${h})` : `oklch(0.74 0.19 ${h} / ${a})`;
  const fa   = (s) => String(s).replace(/[0-9]/g, d => '۰۱۲۳۴۵۶۷۸۹'[+d]);
  const ctP  = (ax,ay,bx2,by2,sign) => {
    const mx=(ax+bx2)/2,my=(ay+by2)/2,dx=bx2-ax,dy=by2-ay;
    const L=Math.hypot(dx,dy)||1,nx=-dy/L,ny=dx/L,o=L*.14*sign;
    return {x:mx+nx*o,y:my+ny*o};
  };
  const quad = (p0,cp,p1,t) => {
    const m=1-t,a=m*m,b=2*m*t,c=t*t;
    return {x:a*p0.x+b*cp.x+c*p1.x,y:a*p0.y+b*cp.y+c*p1.y};
  };
  const E = React.createElement;

  /* ── categories (همان موقعیت طراحی اصلی) ── */
  const HUE = {trigger:350,bot:322,followup:292,campaign:264,script:238};
  const rawCats = [
    {id:'trigger', name:'تریگرها',   desc:'نقطه ورود',   x:505,y:74, sign:-1},
    {id:'bot',     name:'بات‌ها',     desc:'پاسخ خودکار', x:122,y:196,sign:1 },
    {id:'followup',name:'فالوآپ‌ها', desc:'پیگیری',       x:892,y:212,sign:1 },
    {id:'campaign',name:'کمپین‌ها',  desc:'کمپین فعال',  x:852,y:560,sign:-1},
    {id:'script',  name:'اسکریپت‌ها',desc:'مکالمه',      x:150,y:566,sign:-1},
  ];
  const LEAF_DEF = {
    trigger:  ['لید جدید','پیام ورودی','فرم سایت'],
    bot:      ['بات پاسخ','دستیار فروش'],
    followup: ['No-Show','پرداخت رها شده','پیگیری مشاوره'],
    campaign: ['جذب لید جدید','Upsell بعد خرید','وفاداری'],
    script:   ['تماس اول','تماس دوم','ریکاوری مکالمه'],
  };
  const CNT_DEF = {trigger:6,bot:4,followup:12,campaign:33,script:8};

  const cats = rawCats.map(c => {
    const h=HUE[c.id];
    const cp=ctP(BX,BY,c.x,c.y,c.sign);
    const path=`M ${BX} ${BY} Q ${cp.x.toFixed(1)} ${cp.y.toFixed(1)} ${c.x} ${c.y}`;
    const ents=entities[c.id]||[];
    const leafEntities=ents.length>0?ents.slice(0,3):LEAF_DEF[c.id].map((name,idx)=>({id:`placeholder:${c.id}:${idx}`,name,placeholder:true}));
    const leafNames=leafEntities.map(e=>e.name);
    const base=Math.atan2(c.y-BY,c.x-BX);
    const n=leafNames.length,spread=.52,R=98;
    const leaves=leafNames.map((name,i)=>{
      const ang=base+spread*(i-(n-1)/2);
      const lx=c.x+R*Math.cos(ang),ly=c.y+R*Math.sin(ang);
      return {name,entity:leafEntities[i],category:c.id,x:+lx.toFixed(1),y:+ly.toFixed(1),conn:`M ${c.x} ${c.y} L ${lx.toFixed(1)} ${ly.toFixed(1)}`,hue:h,idx:i};
    });
    const count=ents.length>0?fa(ents.length):fa(CNT_DEF[c.id]);
    return {...c,hue:h,cp,path,leaves,count};
  });
  const byId=Object.fromEntries(cats.map(c=>[c.id,c]));

  const playLabTraceOnMap = (data, inputMode='text') => {
    if(!data?.input)return;
    const routeStep=(data.trace||[]).find(step=>step.stage==='route');
    const stateStep=(data.trace||[]).find(step=>step.stage==='state');
    const routeId=routeStep?.diff?.scriptId||routeStep?.diff?.campaignId||routeStep?.diff?.triggerId||null;
    let cat='bot';
    let leaf=0;
    if(routeId&&routeId!=='brain_main'){
      for(const category of ['trigger','campaign','followup','script','bot']){
        const idx=(entities[category]||[]).findIndex(item=>String(item.id)===String(routeId));
        if(idx>=0){cat=category;leaf=Math.min(idx,2);break;}
      }
    }
    const routeTitle=String(routeStep?.title||'');
    if(routeId!=='brain_main'){
      if(routeTitle.includes('اسکریپت'))cat='script';
      else if(routeTitle.includes('کمپین'))cat='campaign';
      else if(routeTitle.includes('تریگر'))cat='trigger';
      else if(routeTitle.includes('فالوآپ'))cat='followup';
    }
    const targetCat=byId[cat]||byId.bot;
    if(routeId){
      const exactLeaf=targetCat.leaves.findIndex(item=>String(item.entity?.id)===String(routeId));
      if(exactLeaf>=0)leaf=exactLeaf;
    }
    const channelLabel={bale:'بله',bale2:'بله',telegram:'تلگرام',voice_call:'تماس صوتی',web:'گفتگوی وب'}[data.channel]||data.channel||'ورودی';
    const destinationLabel=routeId==='brain_main'?'پاسخ مستقیم مغز اصلی':routeTitle.replace(/^مقصد:\s*/,'')||targetCat.name;
    const scriptStates=Array.isArray(stateStep?.diff?.allStates)?stateStep.diff.allStates:[];
    msgsRef.current=[{
      text:data.input,src:0,cat:targetCat.id,leaf,channel:data.channel,sourceLabel:`ورودی ${channelLabel}`,
      destinationLabel,inputMode,reply:data.reply||null,directBrain:routeId==='brain_main',scriptId:stateStep?.diff?.scriptId||((routeId&&routeId!=='brain_main')?routeId:null),conf:typeof data.confidence==='number'?data.confidence/100:null,
      scriptStates,currentState:stateStep?.diff?.state||null,currentStep:stateStep?.diff?.step??null,
      intent:data.intent||String(data.input).slice(0,24),reason:routeStep?.reason||'تصمیم ثبت‌شدهٔ مغز اصلی',trace:data.trace||[],
    }];
    const anim=animRef.current;
    anim.i=0;anim.phase='incoming';anim.start=performance.now();anim.paused=false;anim.pausedEl=0;
    setIdle(false);setTick(value=>value+1);
  };

  /* ── animation loop (throttled to ~30fps for performance) ── */
  const frameRef = useRef(0);
  const pushLog=()=>{
    const a=animRef.current,msgs=msgsRef.current,m=msgs[a.i];
    const t=byId[m?.cat]; if(!t)return;
    const lf=t.leaves[m.leaf]||t.leaves[0];
    logRef.current=[...logRef.current,
      {intent:m.intent,dest:m.destinationLabel||`${t.name} · ${lf?.name||''}`,confPct:m.conf==null?'—':fa(Math.round(m.conf*100))+'٪',color:col(t.hue),glow:col(t.hue,.8)}
    ].slice(-7);
  };
  useEffect(()=>{
    const a=animRef.current; a.start=performance.now();
    const loop=()=>{
      rafRef.current=requestAnimationFrame(loop);
      if(a.paused)return;
      const now=performance.now(),el=now-a.start,dur=D[a.phase];
      if(el>=dur){
        if     (a.phase==='incoming') {a.phase='thinking'; a.start=now;}
        else if(a.phase==='thinking') {a.phase='routing';  a.start=now;}
        else if(a.phase==='routing')  {
          const current=msgsRef.current[a.i];
          a.phase=current?.scriptStates?.length?'stateflow':'responding';a.start=now;
        }
        else if(a.phase==='stateflow'){a.phase='responding';a.start=now;}
        else if(a.phase==='responding'){a.phase='outgoing';a.start=now;}
        else if(a.phase==='outgoing'){a.phase='returned';a.start=now;pushLog();}
        else if(a.phase==='returned'){a.phase='gap';a.start=now;}
        else{
          const len=msgsRef.current.length;
          if(len===0){a.paused=true;}
          else{a.i=(a.i+1)%len;a.phase='incoming';a.start=now;}
        }
      }
      /* مسیر با CSS نرم می‌ماند؛ React فقط حدود ۱۰fps بازترسیم می‌شود تا پنل جزئیات سنگین نشود. */
      frameRef.current=(frameRef.current+1)%6;
      if(frameRef.current===0) setTick(t=>t+1);
    };
    rafRef.current=requestAnimationFrame(loop);
    return ()=>{if(rafRef.current)cancelAnimationFrame(rafRef.current);};
  },[]);

  /* ── polling پیام‌های واقعی از سرور ── */
  const CATS_VALID = new Set(['trigger','bot','followup','campaign','script']);
  const fetchEvents = () => {
    window.api('/orchestrator/recent-events?limit=30').then(r => {
      if (!r.success || !r.data?.length) { setIdle(true); return; }
      /* فقط پیام‌های ۱۰ دقیقه اخیر — قدیمی‌تر از اون idle می‌شه */
      const cutoff = Date.now() - 10 * 60 * 1000;
      const fresh = r.data.filter(ev => new Date(ev.time).getTime() >= cutoff);
      setLiveEvents(r.data);
      if(modeRef.current==='sandbox')return;
      if (!fresh.length) { setIdle(true); msgsRef.current = []; return; }
      const mapped = fresh.map(ev => {
        const actualCat=ev.ownerRegistered&&CATS_VALID.has(ev.cat)?ev.cat:(ev.scriptId?'script':null);
        const cn=actualCat?byId[actualCat]:null;
        const routeRef=ev.ownerRef||ev.scriptId||null;
        const exactLeaf=cn?.leaves?.findIndex(item=>String(item.entity?.id)===String(routeRef))??-1;
        const leaf=exactLeaf>=0?exactLeaf:0;
        const entityName=exactLeaf>=0?cn.leaves[exactLeaf]?.name:null;
        const channelLabel={bale:'بله',bale2:'بله',telegram:'تلگرام',telegram2:'تلگرام',voice_call:'تماس صوتی',web:'گفتگوی وب'}[ev.platform]||ev.platform||'کانال نامشخص';
        const destinationLabel=entityName||(ev.scriptId?`اسکریپت ${ev.scriptId}`:(ev.activeOwner||'مقصد ثبت نشده'));
        return {
          text:   ev.text,
          src:    0,
          cat:    cn?.id||null,
          leaf,
          noRoute:!cn,
          channel:ev.platform,
          sourceLabel:`ورودی ${channelLabel}`,
          destinationLabel,
          inputMode:'recorded',
          reply:ev.reply||null,
          scriptStates:ev.state?.current_state_name?[{id:ev.state.current_state_id||'current',name:ev.state.current_state_name,order:ev.state.current_step,status:'current'}]:[],
          currentState:ev.state?.current_state_name||null,
          currentStep:ev.state?.current_step??null,
          scriptId:ev.scriptId||ev.state?.script_id||null,
          conf:   null,
          intent: (ev.text || '').slice(0, 24),
          reason: cn?`مسیر ثبت‌شده: ${destinationLabel}`:'برای این پیام مقصدی ثبت نشده است',
          name:   ev.name,
          event:  ev,
        };
      });
      const prev = msgsRef.current;
      if (JSON.stringify(prev.map(m=>m.text)) !== JSON.stringify(mapped.map(m=>m.text))) {
        msgsRef.current = mapped;
        setIdle(false);
        const a = animRef.current;
        if (a.i >= mapped.length) { a.i = 0; a.phase = 'incoming'; a.start = performance.now(); }
      }
    }).catch(() => setIdle(true));
  };
  useEffect(() => {
    fetchEvents();
    const tid = setInterval(fetchEvents, 12000);
    return () => clearInterval(tid);
  }, []); // eslint-disable-line

  useEffect(() => {
    const loadDashboard = () => window.api('/orchestrator/dashboard').then(r => {
      if (r.success) setDashboard(r.data);
    }).catch(() => {});
    loadDashboard();
    const tid = setInterval(loadDashboard, 12000);
    return () => clearInterval(tid);
  }, []);

  const runSandbox = async () => {
    const text = sandboxText.trim();
    if (!text || sandboxLoading) return;
    setSandboxLoading(true); setSandboxResult(null); setSelectedStep(null);
    try {
      const r = await window.api('/orchestrator/lab-session/turn', {
        method:'POST', body:{ text, channel:sandboxPlatform, sessionId:sandboxSessionRef.current }
      });
      if (r.success) {
        setSandboxResult(r.data);
        setSelectedStep(r.data.trace?.[0] || null);
        playLabTraceOnMap(r.data,'text');
        setSandboxTurns(turns=>[...turns,
          {role:'user',text,channel:sandboxPlatform},
          {role:'assistant',text:r.data.reply || (r.data.silence?'[سکوت کنترل‌شده]':'[پاسخی تولید نشد]'),source:r.data.source,model:r.data.model}
        ]);
        setSandboxText('');
      } else setSandboxResult({ error:r.error || 'خطا در اجرای آزمایش' });
    } catch (e) { setSandboxResult({ error:e.message || 'خطای شبکه' }); }
    setSandboxLoading(false);
  };

  const blobToBase64 = blob => new Promise((resolve,reject)=>{
    const reader=new FileReader();
    reader.onload=()=>resolve(String(reader.result||'').split(',')[1]||'');
    reader.onerror=()=>reject(reader.error||new Error('خواندن فایل صوتی ناموفق بود'));
    reader.readAsDataURL(blob);
  });

  const runSandboxVoice = async (blob, mimeType) => {
    if (!blob?.size || sandboxLoading) return;
    setSandboxLoading(true); setSandboxResult(null); setSelectedStep(null);
    setSandboxVoiceStatus('در حال ذخیره و تبدیل ویس به متن…');
    try {
      const audioBase64=await blobToBase64(blob);
      const r=await window.api('/orchestrator/lab-session/voice-turn',{
        method:'POST', body:{audioBase64,mimeType,channel:sandboxPlatform,sessionId:sandboxSessionRef.current}
      });
      if(r.success){
        setSandboxResult(r.data); setSelectedStep(r.data.trace?.[0]||null);
        playLabTraceOnMap(r.data,'voice');
        setSandboxTurns(turns=>[...turns,
          {role:'user',text:r.data.input,channel:sandboxPlatform,voice:true,durationMs:r.data.inputArtifact?.durationMs},
          {role:'assistant',text:r.data.reply||(r.data.silence?'[سکوت کنترل‌شده]':'[پاسخی تولید نشد]'),source:r.data.source,model:r.data.model}
        ]);
        setSandboxVoiceStatus(`ویس ذخیره شد · متن: «${r.data.input}»`);
      } else {
        setSandboxResult({error:r.error||'تشخیص ویس ناموفق بود',voice:r.data?.voice});
        setSandboxVoiceStatus('ویس محلی ذخیره شد، اما متن قابل‌اعتمادی تشخیص داده نشد.');
      }
    } catch(e){
      setSandboxResult({error:e.message||'خطا در پردازش ویس'});
      setSandboxVoiceStatus('پردازش ویس ناموفق بود.');
    }
    setSandboxLoading(false);
  };

  const replayLastSandboxVoice = async () => {
    if(sandboxLoading)return;
    setSandboxLoading(true);setSandboxResult(null);setSelectedStep(null);
    setSandboxVoiceStatus('در حال بازپخش و تبدیل آخرین ویس محلی…');
    try{
      const r=await window.api('/orchestrator/lab-session/replay-last-voice',{
        method:'POST',body:{channel:sandboxPlatform,sessionId:sandboxSessionRef.current}
      });
      if(r.success){
        setSandboxResult(r.data);setSelectedStep(r.data.trace?.[0]||null);
        playLabTraceOnMap(r.data,'voice');
        setSandboxTurns(turns=>[...turns,
          {role:'user',text:r.data.input,channel:sandboxPlatform,voice:true,durationMs:r.data.inputArtifact?.durationMs},
          {role:'assistant',text:r.data.reply||(r.data.silence?'[سکوت کنترل‌شده]':'[پاسخی تولید نشد]'),source:r.data.source,model:r.data.model}
        ]);
        setSandboxVoiceStatus(`ویس بازپخش و ذخیره شد · متن: «${r.data.input}»`);
      }else{
        setSandboxResult({error:r.error||'بازپخش ویس ناموفق بود',voice:r.data?.voice});
        setSandboxVoiceStatus('بازپخش ویس محلی ناموفق بود.');
      }
    }catch(e){setSandboxResult({error:e.message||'خطا در بازپخش ویس'});setSandboxVoiceStatus('بازپخش ویس محلی ناموفق بود.');}
    setSandboxLoading(false);
  };

  const toggleSandboxRecording = async () => {
    if (sandboxRecording) {
      sandboxRecorderRef.current?.stop();
      return;
    }
    try {
      const stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:true,noiseSuppression:true}});
      const preferred=['audio/webm;codecs=opus','audio/ogg;codecs=opus','audio/webm'];
      const mimeType=preferred.find(t=>window.MediaRecorder?.isTypeSupported?.(t))||'';
      const recorder=new MediaRecorder(stream,mimeType?{mimeType}:undefined);
      sandboxStreamRef.current=stream; sandboxRecorderRef.current=recorder; sandboxAudioChunksRef.current=[];
      recorder.ondataavailable=e=>{if(e.data?.size)sandboxAudioChunksRef.current.push(e.data);};
      recorder.onstop=()=>{
        const actualType=recorder.mimeType||mimeType||'audio/webm';
        const blob=new Blob(sandboxAudioChunksRef.current,{type:actualType});
        sandboxStreamRef.current?.getTracks().forEach(t=>t.stop());
        sandboxStreamRef.current=null; sandboxRecorderRef.current=null; setSandboxRecording(false);
        if(sandboxDiscardRecordingRef.current){sandboxDiscardRecordingRef.current=false;return;}
        runSandboxVoice(blob,actualType);
      };
      recorder.start(250); setSandboxRecording(true); setSandboxVoiceStatus('در حال ضبط… برای پایان دوباره کلیک کن');
    } catch(e) {
      setSandboxResult({error:e?.name==='NotAllowedError'?'اجازهٔ میکروفن داده نشد. دسترسی میکروفن را برای این صفحه فعال کن.':(e.message||'میکروفن در دسترس نیست')});
    }
  };

  const uploadSandboxVoice = async event => {
    const file=event.target.files?.[0];
    if(!file)return;
    await runSandboxVoice(file,file.type||(/\.wav$/i.test(file.name)?'audio/wav':/\.ogg$/i.test(file.name)?'audio/ogg':'audio/webm'));
    event.target.value='';
  };

  const resetSandbox = async () => {
    if(sandboxRecorderRef.current?.state==='recording') { sandboxDiscardRecordingRef.current=true; sandboxRecorderRef.current.stop(); }
    try { await window.api('/orchestrator/lab-session/reset',{method:'POST',body:{sessionId:sandboxSessionRef.current}}); } catch {}
    sandboxSessionRef.current=`lab-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;
    setSandboxTurns([]); setSandboxResult(null); setSelectedStep(null); setSandboxText(''); setSandboxVoiceStatus('');
  };

  const inspectTraceStep = step => {
    setSelectedStep(step);
    const phase=(step?.stage==='voice-input'||step?.stage==='start'||step?.stage==='input')?'incoming':
      step?.stage==='brain'?'thinking':step?.stage==='route'?'routing':step?.stage==='state'?'stateflow':step?.stage==='reply'?'outgoing':'responding';
    const anim=animRef.current;
    anim.phase=phase;anim.paused=true;anim.pausedEl=(D[phase]||1000)*.72;anim.start=performance.now()-anim.pausedEl;
    setTick(value=>value+1);
  };

  /* ── stage scale measurement ── */
  useEffect(()=>{
    const measure=()=>{
      const el=stageRef.current; if(!el)return;
      const w=el.clientWidth-8,h=el.clientHeight-200;
      const s=Math.max(.32,Math.min(w/1012,h/712,1.0));
      setStageScale(prev=>Math.abs(s-prev)>.005?s:prev);
    };
    measure();const tid=setTimeout(measure,200);
    window.addEventListener('resize',measure);
    return ()=>{clearTimeout(tid);window.removeEventListener('resize',measure);};
  },[]);

  /* ── controls ── */
  const select=(i)=>{
    const a=animRef.current;
    a.i=i%msgsRef.current.length;a.phase='incoming';a.start=performance.now();a.paused=false;
    setTick(t=>t+1);
  };
  const toggle=()=>{
    const a=animRef.current,now=performance.now();
    if(a.paused){a.paused=false;a.start=now-a.pausedEl;}
    else        {a.paused=true; a.pausedEl=now-a.start;}
    setTick(t=>t+1);
  };
  const pausePacketMotion=()=>{
    const a=animRef.current,p=packetPauseRef.current;
    if(!p.hovering&&!p.locked)p.wasPaused=a.paused;
    p.hovering=true;
    if(!a.paused){a.pausedEl=performance.now()-a.start;a.paused=true;}
    setTick(t=>t+1);
  };
  const releasePacketMotion=(unlock=false)=>{
    const a=animRef.current,p=packetPauseRef.current;
    p.hovering=false;
    if(unlock)p.locked=false;
    if(p.locked)return;
    if(!p.wasPaused&&a.paused){a.paused=false;a.start=performance.now()-a.pausedEl;}
    p.wasPaused=false;
    setTick(t=>t+1);
  };
  const closeSelectedNode=()=>{
    if(selectedNode?.kind==='message')releasePacketMotion(true);
    setSelectedNode(null);
  };
  useEffect(()=>{
    if(selectedNode?.kind!=='message'&&packetPauseRef.current.locked)releasePacketMotion(true);
  },[selectedNode]);
  const sendChat=async()=>{
    const text=chatInput.trim(); if(!text)return;
    setChatInput('');
    let preview=null;
    try {
      const r=await window.api('/orchestrator/brain-preview',{method:'POST',body:{text,platform:'web'}});
      if(r.success) preview=r.data;
    } catch {}
    const targetType=preview?.targetType;
    const type=targetType==='trigger'?'trigger':targetType==='script'?'script':targetType==='campaign'?'campaign':'bot';
    const cn=byId[type]||byId['bot'];
    msgsRef.current=[...msgsRef.current,
      {text,src:0,cat:cn.id,leaf:0,conf:null,intent:text.slice(0,22),reason:preview?.reason||`پیش‌نمایش محلی — مسیر: ${cn.name}`}
    ];
    if(preview){setSandboxResult(preview);setSelectedStep(preview.trace?.[0]||null);}
    const a=animRef.current;
    a.phase='incoming'; a.start=performance.now(); a.paused=false;
    a.i=msgsRef.current.length-1;
    setIdle(false);
    setTick(t=>t+1);
  };

  /* ── render values (هر frame محاسبه می‌شه) ── */
  const a=animRef.current,msgs=msgsRef.current;
  const phase=a.phase,msg=msgs[a.i]||msgs[0];
  const target=(msg?.noRoute||msg?.directBrain)?null:(byId[msg?.cat]||null),trig=byId['trigger'];
  const nowT=performance.now(),elT=a.paused?a.pausedEl:nowT-a.start;
  const prog=Math.max(0,Math.min(1,elT/(D[phase]||1)));
  const brain={x:BX,y:BY};
  const incoming=!!msg&&phase==='incoming',analyzing=!!msg&&phase==='thinking';
  const routing=!!msg&&phase==='routing',stateflow=!!msg&&phase==='stateflow',responding=!!msg&&phase==='responding';
  const outgoing=!!msg&&phase==='outgoing',returned=!!msg&&phase==='returned';
  const outputFlow=responding||outgoing||returned;
  const showDec=routing||stateflow||outputFlow;

  /* Real script-state lane. Pending states are visible, but the packet only travels
     across completed states and the current state reported by the trace. */
  const rawScriptStates=Array.isArray(msg?.scriptStates)?msg.scriptStates:[];
  const rawCurrentIndex=Math.max(0,rawScriptStates.findIndex(s=>s?.status==='current'||String(s?.name)===String(msg?.currentState)));
  const stateWindowStart=rawScriptStates.length>5?Math.max(0,Math.min(rawCurrentIndex-2,rawScriptStates.length-5)):0;
  const visibleScriptStates=rawScriptStates.slice(stateWindowStart,stateWindowStart+5);
  const stateNodes=visibleScriptStates.map((state,index)=>({
    ...state,x:270+(index*125),y:640,
    isCurrent:state?.status==='current'||String(state?.name)===String(msg?.currentState),
  }));
  const currentStateNode=stateNodes.find(node=>node.isCurrent)||null;
  const stateOrigin=target?{x:target.x,y:target.y}:brain;
  const currentVisibleIndex=currentStateNode?stateNodes.indexOf(currentStateNode):Math.max(0,stateNodes.findIndex(node=>node.status==='pending')-1);
  const stateTravelNodes=[stateOrigin,...stateNodes.slice(0,Math.max(0,currentVisibleIndex)+1).map(node=>({x:node.x,y:node.y}))];
  const pointOnPolyline=(points,t)=>{
    if(!points.length)return brain;
    if(points.length===1)return points[0];
    const lengths=points.slice(1).map((point,index)=>Math.hypot(point.x-points[index].x,point.y-points[index].y));
    const total=lengths.reduce((sum,length)=>sum+length,0)||1;
    let distance=t*total;
    for(let index=0;index<lengths.length;index++){
      if(distance<=lengths[index]){
        const local=lengths[index]?distance/lengths[index]:1;
        return {x:points[index].x+(points[index+1].x-points[index].x)*local,y:points[index].y+(points[index+1].y-points[index].y)*local};
      }
      distance-=lengths[index];
    }
    return points[points.length-1];
  };
  const responseOrigin=currentStateNode?{x:currentStateNode.x,y:currentStateNode.y}:target?{x:target.x,y:target.y}:brain;
  const outputCp=responseOrigin===brain?trig?.cp:{x:responseOrigin.x<=BX?790:210,y:330};
  const outputPath=trig&&outputCp?`M ${responseOrigin.x} ${responseOrigin.y} Q ${outputCp.x} ${outputCp.y} ${trig.x} ${trig.y}`:null;

  /* category views */
  const catViews=cats.map(c=>{
    const isSrc=(incoming||outgoing||returned)&&c.id==='trigger';
    const isTgt=(routing||stateflow||responding)&&c.id===target?.id;
    const active=isSrc||isTgt,h=c.hue;
    return {...c,active,
      displayName:isSrc&&msg?.sourceLabel?msg.sourceLabel:c.name,
      displayDesc:isSrc?(outgoing||returned?'پاسخ خروجی':msg?.inputMode==='voice'?'پیام صوتی':msg?.inputMode==='text'?'پیام متنی':'پیام ثبت‌شده'):c.desc,
      ns:{  /* nodeStyle */
        position:'absolute',left:c.x+'px',top:c.y+'px',
        transform:`translate(-50%,-50%) scale(${active?1.06:1})`,
        display:'flex',alignItems:'center',gap:'10px',
        padding:'10px 14px',borderRadius:'15px',whiteSpace:'nowrap',
        background:active?`linear-gradient(150deg,${col(h,.28)},rgba(20,11,26,.92))`:'rgba(19,11,25,.78)',
        border:`1px solid ${active?col(h,.9):'rgba(255,255,255,.1)'}`,
        boxShadow:active?`0 0 0 1px ${col(h,.4)},0 10px 34px ${col(h,.45)},0 0 46px ${col(h,.3)}`:'0 6px 20px rgba(0,0,0,.45)',
        backdropFilter:'blur(12px)',WebkitBackdropFilter:'blur(12px)',
        transition:'all .35s',zIndex:active?8:5,pointerEvents:'auto',cursor:'pointer',
      },
      ds:{width:'11px',height:'11px',borderRadius:'4px',flex:'0 0 auto',background:col(h),boxShadow:`0 0 12px ${col(h,.95)}`},
      cs:{  /* countStyle */
        marginInlineStart:'4px',fontSize:'12px',fontWeight:'800',
        color:active?'#fff':col(h,.95),
        background:active?col(h,.3):'rgba(255,255,255,.06)',
        border:`1px solid ${active?col(h,.7):'rgba(255,255,255,.08)'}`,
        padding:'2px 8px',borderRadius:'999px',
      },
    };
  });

  /* leaf views */
  const leafViews=[],leafLinks=[];
  cats.forEach(c=>c.leaves.forEach(lf=>{
    const isSrc=(incoming||outgoing||returned)&&c.id==='trigger'&&lf.idx===msg?.src;
    const isTgt=(routing||stateflow||responding)&&c.id===target?.id&&lf.idx===msg?.leaf;
    const active=isSrc||isTgt,h=lf.hue;
    leafLinks.push({conn:lf.conn,stroke:col(h,.18)});
    leafViews.push({name:isSrc&&msg?.sourceLabel?msg.sourceLabel:lf.name,entity:lf.entity,category:lf.category,style:{
      position:'absolute',left:lf.x+'px',top:lf.y+'px',transform:'translate(-50%,-50%)',
      padding:'5px 11px',borderRadius:'999px',fontSize:'11.5px',fontWeight:active?'700':'500',whiteSpace:'nowrap',
      background:active?col(h,.2):'rgba(255,255,255,.04)',
      border:`1px solid ${active?col(h,.75):'rgba(255,255,255,.08)'}`,
      color:active?'#fff':'rgba(225,210,225,.55)',
      boxShadow:active?`0 0 18px ${col(h,.55)}`:'none',
      textShadow:active?`0 0 9px ${col(h,.8)}`:'none',
      transition:'all .3s',zIndex:active?7:4,pointerEvents:'auto',cursor:'pointer',
    }});
  }));

  /* SVG overlay */
  const gp=(d,h,key,w,op)=>[
    E('path',{key:key+'h',d,fill:'none',stroke:col(h,op*.5),strokeWidth:(w||3)+5,strokeLinecap:'round',filter:'url(#mg1)',opacity:op*.7}),
    E('path',{key:key+'c',d,fill:'none',stroke:'#fff',strokeWidth:1.6,strokeLinecap:'round',opacity:op*.9}),
    E('path',{key:key+'m',d,fill:'none',stroke:col(h),strokeWidth:w||3,strokeLinecap:'round',filter:'url(#mg1)',opacity:op}),
  ];
  const pt=(p,h,key)=>[
    E('circle',{key:key+'g',cx:p.x,cy:p.y,r:13,fill:col(h,.4),filter:'url(#mg1)'}),
    E('circle',{key:key+'o',cx:p.x,cy:p.y,r:7,fill:col(h,.9)}),
    E('circle',{key:key+'i',cx:p.x,cy:p.y,r:3.2,fill:'#fff'}),
  ];
  const ov=[];
  if(incoming&&trig){
    ov.push(...gp(trig.path,trig.hue,'inc',2.6,.85));
    ov.push(...pt(quad(brain,trig.cp,{x:trig.x,y:trig.y},1-prog),trig.hue,'incp'));
  }
  if(analyzing){
    const r=26+prog*64,op=Math.max(0,.5*(1-prog));
    ov.push(E('circle',{key:'th1',cx:BX,cy:BY,r,fill:'none',stroke:col(340,op),strokeWidth:2}));
    const p2=(prog+.5)%1,r2=26+p2*64,op2=Math.max(0,.4*(1-p2));
    ov.push(E('circle',{key:'th2',cx:BX,cy:BY,r:r2,fill:'none',stroke:col(330,op2),strokeWidth:2}));
  }
  if((routing||stateflow||responding)&&target){
    ov.push(...gp(target.path,target.hue,'rt',3,.95));
    const lf=target.leaves[msg?.leaf||0]||target.leaves[0];
    if(lf)ov.push(...gp(lf.conn,target.hue,'rtl',1.8,.85));
    if(routing)ov.push(...pt(quad(brain,target.cp,{x:target.x,y:target.y},prog),target.hue,'rtp'));
    if(responding&&!currentStateNode){
      const r=12+prog*70,op=Math.max(0,1-prog);
      ov.push(E('circle',{key:'dl',cx:target.x,cy:target.y,r,fill:'none',stroke:col(target.hue,op*.8),strokeWidth:2.4}));
    }
  }
  if((stateflow||responding||outgoing||returned)&&stateNodes.length){
    const stateHue=target?.hue??238;
    const connectors=[stateOrigin,...stateNodes.map(node=>({x:node.x,y:node.y}))];
    connectors.slice(1).forEach((point,index)=>{
      const previous=connectors[index];
      ov.push(...gp(`M ${previous.x} ${previous.y} L ${point.x} ${point.y}`,stateHue,`state-${index}`,1.6,.62));
    });
    if(stateflow)ov.push(...pt(pointOnPolyline(stateTravelNodes,prog),stateHue,'statep'));
    if(responding&&currentStateNode){
      const r=12+prog*58,op=Math.max(0,1-prog);
      ov.push(E('circle',{key:'state-response',cx:currentStateNode.x,cy:currentStateNode.y,r,fill:'none',stroke:col(stateHue,op*.85),strokeWidth:2.4}));
    }
  }
  if((outgoing||returned)&&trig){
    ov.push(...gp(outputPath,trig.hue,'out',2.8,.9));
    if(outgoing)ov.push(...pt(quad(responseOrigin,outputCp,{x:trig.x,y:trig.y},prog),trig.hue,'outp'));
    if(returned){
      const r=12+prog*62,op=Math.max(0,1-prog);
      ov.push(E('circle',{key:'returned',cx:trig.x,cy:trig.y,r,fill:'none',stroke:col(trig.hue,op*.85),strokeWidth:2.5}));
    }
  }

  let packetPos=null;
  if(incoming&&trig)packetPos=quad(brain,trig.cp,{x:trig.x,y:trig.y},1-prog);
  else if(analyzing)packetPos=brain;
  else if(routing)packetPos=target?quad(brain,target.cp,{x:target.x,y:target.y},prog):brain;
  else if(stateflow)packetPos=pointOnPolyline(stateTravelNodes,prog);
  else if(responding)packetPos=responseOrigin;
  else if(outgoing)packetPos=quad(responseOrigin,outputCp,{x:trig.x,y:trig.y},prog);
  else if(returned)packetPos={x:trig.x,y:trig.y};
  const packetBubbleX=packetPos?Math.max(140,Math.min(860,packetPos.x)):0;
  const packetStage=incoming?'در حال ورود به مغز':analyzing?'مغز در حال تصمیم‌گیری':routing?(target?'در حال ارسال به مقصد':'پاسخ مستقیم مغز'):stateflow?`عبور از استیت ${msg?.currentState||''}`:responding?(currentStateNode?'پاسخ در استیت جاری آماده شد':'پاسخ در مقصد آماده شد'):outgoing?`ارسال مستقیم پاسخ به ${msg?.sourceLabel||'کانال'}`:returned?'پاسخ تحویل شد':'ثبت‌شده';
  const packetText=outputFlow&&msg?.reply?msg.reply:msg?.text;
  const isVoicePacket=msg?.inputMode==='voice';
  const responseOriginLabel=currentStateNode?.name||msg?.currentState||msg?.destinationLabel||(target?.name||'مغز اصلی');
  const packetRouteLabel=routing||stateflow||responding?`← ${currentStateNode?.name||msg?.destinationLabel||'مقصد ثبت نشده'}`:outgoing||returned?`${responseOriginLabel} → ${msg?.sourceLabel||'کانال ورودی'}`:'';

  /* rail data */
  const current={
    channel:msg?.sourceLabel||trig?.leaves[msg?.src||0]?.name||'ورودی',
    text:msg?.text||'',
    time:incoming?'در حال دریافت…':'هم‌اکنون',
  };
  const decision=showDec&&target&&msg?{
    catName:target.name,
    leaf:(target.leaves[msg.leaf||0]||target.leaves[0])?.name||'',
    reason:msg.reason,
    confPct:msg.conf == null?'ثبت نشده':fa(Math.round(msg.conf*100))+'٪',confW:msg.conf == null?'0%':Math.round(msg.conf*100)+'%',
    chipBg:col(target.hue,.22),chipBorder:col(target.hue,.6),
    chipShadow:`0 0 16px ${col(target.hue,.4)}`,leafColor:col(target.hue,.95),
    barBg:`linear-gradient(90deg,${col(target.hue,.6)},${col(target.hue)})`,barGlow:col(target.hue,.7),
  }:null;
  const queue=msgs.length>1?[1,2].map(k=>{const j=(a.i+k)%msgs.length;return{text:msgs[j]?.text||'',idx:j};}):[];
  const recent=logRef.current.slice().reverse();
  const legend=cats.map(c=>({name:c.name,color:col(c.hue),glow:col(c.hue,.7)}));
  const statusText=!msg?'آماده':incoming?`${msg?.sourceLabel||'ورودی'} ← دریافت`:analyzing?'در حال تصمیم‌گیری…':routing?`ارسال به ${msg?.destinationLabel||target?.name||'مقصد'}…`:stateflow?`در اسکریپت: ${msg?.currentState||'استیت جاری'}`:responding?(currentStateNode?`پاسخ در استیت ${currentStateNode.name} آماده شد`:'پاسخ آماده شد'):outgoing?`خروج مستقیم به ${msg?.sourceLabel||'کانال'}…`:returned?'✓ پاسخ تحویل کانال شد':'آماده';
  const thinkGlow=analyzing?.95:routing?.6:(stateflow||responding||outgoing||returned)?.58:.32;
  const kpis=[
    {val:fa(dashboard?.messagesToday ?? 0),label:'پیام واقعی امروز'},
    {val:fa(dashboard?.activeLeads ?? 0),label:'لید فعال'},
    {val:fa(dashboard?.activeRoutes ?? 0),label:'مسیر فعال'},
  ];

  const artifactThrough = stage => {
    const trace=Array.isArray(msg?.trace)?msg.trace:[];
    const index=trace.findIndex(step=>step.stage===stage);
    if(index<0)return { snapshot:null,trace:[] };
    return { snapshot:trace[index]?.snapshot||null,trace:trace.slice(0,index+1) };
  };
  const executionSummary=()=>({
    phase,channel:msg?.channel||null,channelLabel:msg?.sourceLabel||null,
    inputMode:msg?.inputMode||null,message:msg?.text||null,reply:msg?.reply||null,
    destination:msg?.destinationLabel||null,scriptId:msg?.scriptId||null,
    currentState:msg?.currentState||null,currentStep:msg?.currentStep??null,
    intent:msg?.intent||null,confidence:msg?.conf??null,reason:msg?.reason||null,
  });
  const openBrainDetails=async()=>{
    setSelectedNode({kind:'brain',title:'مغز اصلی',loading:true,data:{execution:executionSummary(),artifact:artifactThrough('brain')}});
    try{
      const [scriptResult,contextResult]=await Promise.all([
        window.api('/orchestrator/brain-script'),window.api('/orchestrator/brain-context')
      ]);
      setSelectedNode({kind:'brain',title:'مغز اصلی',data:{
        script:scriptResult?.success?scriptResult.data:null,
        activeContext:contextResult?.success?contextResult.data:null,
        dashboard,execution:executionSummary(),artifact:artifactThrough('brain')
      }});
    }catch(error){setSelectedNode({kind:'brain',title:'مغز اصلی',data:{error:error.message,execution:executionSummary(),artifact:artifactThrough('brain')}});}
  };
  const openScriptDetails=async entity=>{
    const scriptId=entity?.id||msg?.scriptId;
    setSelectedNode({kind:'script',title:entity?.name||msg?.destinationLabel||'اسکریپت',category:'script',loading:true,data:{id:scriptId,execution:executionSummary(),artifact:artifactThrough('route')}});
    if(!scriptId)return;
    try{
      const [scriptResult,mapResult]=await Promise.all([
        window.api(`/scripts/${encodeURIComponent(scriptId)}`),window.api(`/scripts/${encodeURIComponent(scriptId)}/map`)
      ]);
      const script=scriptResult?.data?.script||scriptResult?.script||entity||null;
      const versions=scriptResult?.data?.versions||scriptResult?.versions||[];
      const states=mapResult?.data?.states||mapResult?.states||[];
      setSelectedNode({kind:'script',title:script?.name||entity?.name||'اسکریپت',category:'script',data:{
        script,states,versions,execution:executionSummary(),artifact:artifactThrough('route')
      }});
    }catch(error){setSelectedNode({kind:'script',title:entity?.name||'اسکریپت',category:'script',data:{id:scriptId,error:error.message,execution:executionSummary(),artifact:artifactThrough('route')}});}
  };
  const openStateDetails=async state=>{
    const scriptId=msg?.scriptId||state?.scriptId;
    setSelectedNode({kind:'state',title:state?.name||'استیت',category:'script',loading:true,data:{state,scriptId,execution:executionSummary(),artifact:artifactThrough('state')}});
    try{
      const [mapResult,scriptResult]=scriptId?await Promise.all([
        window.api(`/scripts/${encodeURIComponent(scriptId)}/map`),window.api(`/scripts/${encodeURIComponent(scriptId)}`)
      ]):[null,null];
      const states=mapResult?.data?.states||mapResult?.states||[];
      const fullState=states.find(item=>String(item.id)===String(state?.id))||state;
      const script=scriptResult?.data?.script||scriptResult?.script||null;
      setSelectedNode({kind:'state',title:fullState?.name||'استیت',category:'script',data:{
        state:fullState,progressStatus:state?.status||null,script,
        allStates:states,execution:executionSummary(),artifact:artifactThrough('state')
      }});
    }catch(error){setSelectedNode({kind:'state',title:state?.name||'استیت',category:'script',data:{state,scriptId,error:error.message,execution:executionSummary(),artifact:artifactThrough('state')}});}
  };

  /* ── JSX ── */
  return (
    <div dir="rtl" style={{
      position:'fixed',inset:0,zIndex:500,
      fontFamily:"'Vazirmatn',system-ui,sans-serif",
      background:'radial-gradient(1100px 720px at 60% 42%, #1a0a1e 0%, #0c0512 48%, #06030a 100%)',
      color:'#f6eef4',display:'flex',flexDirection:'column',overflow:'hidden',
    }}>
      <style>{`
        @keyframes brainPulse{0%,100%{filter:drop-shadow(0 0 18px rgba(255,40,160,.55)) drop-shadow(0 0 42px rgba(255,40,160,.35));transform:translate(-50%,-50%) scale(1);}50%{filter:drop-shadow(0 0 28px rgba(255,60,180,.85)) drop-shadow(0 0 70px rgba(255,40,160,.55));transform:translate(-50%,-50%) scale(1.025);}}
        @keyframes mmRing{0%{transform:scale(.55);opacity:.55;}70%{opacity:0;}100%{transform:scale(1.5);opacity:0;}}
        @keyframes mmDash{to{stroke-dashoffset:-26;}}
        @keyframes mmBlink{0%,100%{opacity:1;}50%{opacity:.25;}}
        @keyframes mmVoice{0%,100%{height:4px;opacity:.55;}50%{height:13px;opacity:1;}}
        @keyframes mmSpin{to{transform:rotate(360deg);}}
        .mm-scrl::-webkit-scrollbar{width:5px;}
        .mm-scrl::-webkit-scrollbar-thumb{background:rgba(255,80,170,.3);border-radius:9px;}
      `}</style>

      {/* dot grid */}
      <div style={{position:'absolute',inset:0,backgroundImage:'radial-gradient(rgba(255,255,255,.04) 1px,transparent 1px)',backgroundSize:'34px 34px',opacity:.5,pointerEvents:'none',maskImage:'radial-gradient(circle at 56% 46%,#000 30%,transparent 78%)',WebkitMaskImage:'radial-gradient(circle at 56% 46%,#000 30%,transparent 78%)'}}/>
      {/* center glow */}
      <div style={{position:'absolute',width:680,height:680,left:'54%',top:'46%',transform:'translate(-50%,-50%)',background:'radial-gradient(circle,rgba(255,42,154,.16) 0%,rgba(255,42,154,0) 62%)',pointerEvents:'none',filter:'blur(8px)'}}/>

      {/* ── HEADER ── */}
      <div style={{position:'relative',zIndex:20,display:'flex',alignItems:'center',justifyContent:'space-between',padding:'13px 24px 11px',flexShrink:0,borderBottom:'1px solid rgba(255,255,255,.06)'}}>
        <div style={{display:'flex',alignItems:'center',gap:12}}>
          {onClose&&<button onClick={onClose} style={{display:'flex',alignItems:'center',gap:5,padding:'6px 11px',borderRadius:9,background:'rgba(255,255,255,.06)',border:'1px solid rgba(255,255,255,.1)',color:'rgba(225,205,225,.7)',cursor:'pointer',fontSize:12,fontWeight:600,flexShrink:0}} title="بازگشت به ارکستریشن">
            <Icon name="arrow-right" size={14}/>
            <span>بازگشت</span>
          </button>}
          <img src="/assets/brain.png" alt="" style={{width:40,height:40,objectFit:'contain',filter:'drop-shadow(0 0 12px rgba(255,42,154,.7))'}} onError={e=>e.target.style.display='none'}/>
          <div style={{display:'flex',flexDirection:'column',gap:2}}>
            <div style={{display:'flex',alignItems:'center',gap:9}}>
              <span style={{fontSize:20,fontWeight:800}}>Orchestra</span>
              <span style={{display:'inline-flex',alignItems:'center',gap:5,fontSize:11,fontWeight:600,color:'oklch(0.8 0.16 150)',background:'oklch(0.7 0.16 150 / .12)',border:'1px solid oklch(0.7 0.16 150 / .3)',padding:'2px 8px',borderRadius:999}}>
                <span style={{width:6,height:6,borderRadius:'50%',background:'oklch(0.8 0.18 150)',boxShadow:'0 0 8px oklch(0.8 0.18 150)',animation:'mmBlink 2s infinite'}}/>آنلاین
              </span>
            </div>
            <span style={{fontSize:12,color:'rgba(225,205,225,.5)',fontWeight:500}}>مغز مرکزی هوشمند · موتور ارجاع پیام</span>
          </div>
        </div>
        <div style={{display:'flex',alignItems:'center',gap:3,padding:4,borderRadius:12,background:'rgba(255,255,255,.035)',border:'1px solid rgba(255,255,255,.07)'}}>
          {[
            ['live','● زنده'],['trace','ردیابی'],['sandbox','آزمایشگاه'],['settings','تنظیمات']
          ].map(([id,label])=>(
            <button key={id} data-command-mode={id} onClick={()=>{setMode(id);setSelectedNode(null);}} style={{
              padding:'7px 13px',borderRadius:9,border:'none',fontFamily:'inherit',fontSize:11.5,fontWeight:700,cursor:'pointer',
              color:mode===id?'#fff':'rgba(225,205,225,.48)',
              background:mode===id?'linear-gradient(135deg,rgba(236,72,153,.34),rgba(139,92,246,.26))':'transparent',
              boxShadow:mode===id?'inset 0 0 0 1px rgba(255,120,200,.24)':'none',
            }}>{label}</button>
          ))}
        </div>
        <div style={{display:'flex',alignItems:'center',gap:9}}>
          {kpis.map((k,i)=>(
            <div key={i} style={{display:'flex',flexDirection:'column',gap:2,padding:'6px 13px',borderRadius:12,background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.07)',minWidth:88}}>
              <span style={{fontSize:17,fontWeight:700,color:'#fff'}}>{k.val}</span>
              <span style={{fontSize:10,color:'rgba(225,205,225,.5)',fontWeight:500}}>{k.label}</span>
            </div>
          ))}
          {onClose&&(
            <button onClick={onClose} style={{marginRight:4,padding:'8px 14px',borderRadius:12,border:'1px solid rgba(255,255,255,.12)',background:'rgba(255,255,255,.05)',color:'rgba(225,205,225,.7)',fontFamily:'inherit',fontSize:12,fontWeight:600,cursor:'pointer'}}>
              ✕ بستن
            </button>
          )}
        </div>
      </div>

      {/* ── BODY ── */}
      <div style={{position:'relative',zIndex:10,flex:'1 1 auto',display:'flex',alignItems:'stretch',minHeight:0}}>

        {/* پنل مرکز فرماندهی: جزئیات، ردیابی، آزمایش امن و تنظیمات */}
        {(mode!=='live'||selectedNode)&&(
          <aside className="mm-scrl" style={{flex:'0 0 370px',display:'flex',flexDirection:'column',gap:12,minHeight:0,padding:'14px 16px',borderLeft:'1px solid rgba(255,255,255,.06)',background:'rgba(8,4,13,.34)',overflowY:'auto'}}>
            {selectedNode ? (<>
              <div style={{display:'flex',alignItems:'center',gap:8}}>
                <button onClick={closeSelectedNode} style={{border:'none',background:'rgba(255,255,255,.06)',color:'#cbd5e1',borderRadius:8,padding:'6px 9px',cursor:'pointer'}}>→</button>
                <div style={{flex:1}}>
                  <div style={{fontSize:14,fontWeight:800}}>{selectedNode.title}</div>
                  <div style={{fontSize:10.5,color:'rgba(225,205,225,.4)',marginTop:2}}>جزئیات واقعی این گره</div>
                </div>
                <span style={{fontSize:10,padding:'3px 8px',borderRadius:20,color:'#f9a8d4',background:'rgba(236,72,153,.1)',border:'1px solid rgba(236,72,153,.2)'}}>{selectedNode.kind}</span>
              </div>
              <div style={{padding:12,borderRadius:14,background:'rgba(255,255,255,.035)',border:'1px solid rgba(255,255,255,.07)'}}>
                <div style={{fontSize:11,fontWeight:700,color:'#f9a8d4',marginBottom:8}}>وضعیت و مبنای حضور</div>
                <div style={{fontSize:11.5,lineHeight:1.8,color:'rgba(235,220,235,.72)'}}>
                  {selectedNode.kind==='message'?'این داده همان بستهٔ واقعی در حال حرکت است. موقعیت، محتوای نمایش‌داده‌شده و آرتیفکت‌های مسیر از اجرای جاری خوانده شده‌اند.':
                   selectedNode.kind==='script'?'تنظیمات کامل اسکریپت، همهٔ استیت‌ها، نسخه‌ها و آرتیفکت مسیر ورود از منابع واقعی سیستم خوانده شده‌اند.':
                   selectedNode.kind==='state'?'رکورد کامل این استیت، وضعیت آن در اجرای جاری، متن، شرط خروج، داده‌های جمع‌آوری و آرتیفکت تا همین لایه نمایش داده می‌شوند.':
                   selectedNode.kind==='brain'?'مغز مرکزی همیشه در مسیر ورودی قرار دارد و مقصد معتبر را انتخاب می‌کند.':
                   selectedNode.kind==='category'?`${(entities[selectedNode.category]||[]).length} موجودیت فعال در این گروه از دیتابیس خوانده شده است.`:
                   selectedNode.data?.placeholder?'این نمونه فقط جای‌نماست و تنظیم واقعی در دیتابیس ندارد.':'این موجودیت از توپولوژی فعال سیستم خوانده شده است.'}
                </div>
              </div>
              {selectedNode.kind==='message'&&(
                <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:7}}>
                  {[
                    ['کانال',selectedNode.data?.channelLabel],['مرحله',selectedNode.data?.stage],
                    ['مقصد',selectedNode.data?.destination],['استیت جاری',selectedNode.data?.currentState||'—']
                  ].map(([label,value])=><div key={label} style={{padding:'9px 10px',borderRadius:10,background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.06)'}}>
                    <div style={{fontSize:9.5,color:'#94a3b8'}}>{label}</div><div style={{fontSize:11,fontWeight:700,color:'#e9d5ff',marginTop:3}}>{value||'—'}</div>
                  </div>)}
                </div>
              )}
              {!selectedNode.loading&&(selectedNode.kind==='brain'||selectedNode.kind==='script'||selectedNode.kind==='state')&&(
                <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:7}}>
                  {selectedNode.kind==='brain'&&[
                    ['شناسه',selectedNode.data?.script?.id],['فعال',selectedNode.data?.script?.is_active?'بله':'خیر'],
                    ['مسیر جاری',selectedNode.data?.execution?.destination],['کانال',selectedNode.data?.execution?.channelLabel]
                  ].map(([label,value])=><div key={label} style={{padding:'9px 10px',borderRadius:10,background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.06)'}}><div style={{fontSize:9.5,color:'#94a3b8'}}>{label}</div><div style={{fontSize:11,fontWeight:700,color:'#e9d5ff',marginTop:3,wordBreak:'break-word'}}>{value??'—'}</div></div>)}
                  {selectedNode.kind==='script'&&[
                    ['شناسه',selectedNode.data?.script?.id],['فعال',selectedNode.data?.script?.is_active?'بله':'خیر'],
                    ['تعداد استیت',selectedNode.data?.states?.length],['استیت جاری',selectedNode.data?.execution?.currentState]
                  ].map(([label,value])=><div key={label} style={{padding:'9px 10px',borderRadius:10,background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.06)'}}><div style={{fontSize:9.5,color:'#94a3b8'}}>{label}</div><div style={{fontSize:11,fontWeight:700,color:'#e9d5ff',marginTop:3,wordBreak:'break-word'}}>{value??'—'}</div></div>)}
                  {selectedNode.kind==='state'&&[
                    ['شناسه',selectedNode.data?.state?.id],['ترتیب',selectedNode.data?.state?.order_num??selectedNode.data?.state?.order],
                    ['وضعیت اجرا',selectedNode.data?.progressStatus],['اسکریپت',selectedNode.data?.script?.name]
                  ].map(([label,value])=><div key={label} style={{padding:'9px 10px',borderRadius:10,background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.06)'}}><div style={{fontSize:9.5,color:'#94a3b8'}}>{label}</div><div style={{fontSize:11,fontWeight:700,color:'#e9d5ff',marginTop:3,wordBreak:'break-word'}}>{value??'—'}</div></div>)}
                </div>
              )}
              <div>
                <div style={{fontSize:11,fontWeight:700,color:'rgba(225,205,225,.52)',marginBottom:7}}>داده‌های گره</div>
                {selectedNode.loading?<div style={{padding:16,borderRadius:12,background:'rgba(0,0,0,.2)',color:'#c4b5fd',fontSize:11}}>در حال خواندن دادهٔ کامل از سیستم…</div>:<pre dir="ltr" style={{margin:0,padding:12,borderRadius:12,background:'rgba(0,0,0,.24)',border:'1px solid rgba(255,255,255,.06)',color:'#c4b5fd',fontSize:10.5,lineHeight:1.65,whiteSpace:'pre-wrap',wordBreak:'break-word',textAlign:'left'}}>{JSON.stringify(selectedNode.data||{},null,2)}</pre>}
              </div>
              {selectedNode.kind==='category'&&(
                <div style={{display:'flex',flexDirection:'column',gap:7}}>
                  <div style={{fontSize:11,fontWeight:700,color:'rgba(225,205,225,.52)'}}>اعضای فعال</div>
                  {(entities[selectedNode.category]||[]).map(e=><button key={e.id} onClick={()=>selectedNode.category==='script'?openScriptDetails(e):setSelectedNode({kind:'entity',title:e.name,category:selectedNode.category,data:e})} style={{textAlign:'right',padding:'9px 10px',borderRadius:10,border:'1px solid rgba(255,255,255,.07)',background:'rgba(255,255,255,.03)',color:'#ddd6fe',fontFamily:'inherit',fontSize:11.5,cursor:'pointer'}}>{e.name}</button>)}
                </div>
              )}
              {selectedNode.kind==='brain'&&brainScript&&(
                <div style={{padding:'9px 12px',borderRadius:10,border:'1px solid rgba(251,191,36,.18)',background:'rgba(251,191,36,.06)',color:'#fcd34d',fontSize:10.5,lineHeight:1.7}}>محتوای اسکریپت مغز طبق توافق فعلاً فقط خواندنی است و تغییر داده نشده.</div>
              )}
            </>) : mode==='trace' ? (<>
              <div>
                <div style={{fontSize:14,fontWeight:800}}>ردیابی پیام‌های واقعی</div>
                <div style={{fontSize:10.5,color:'rgba(225,205,225,.4)',marginTop:3}}>پیام، پاسخ، لید، مسیر و استیت ثبت‌شده</div>
              </div>
              {selectedEvent&&(
                <div style={{padding:12,borderRadius:14,background:'rgba(236,72,153,.07)',border:'1px solid rgba(236,72,153,.18)'}}>
                  <button onClick={()=>setSelectedEvent(null)} style={{float:'left',border:'none',background:'transparent',color:'#94a3b8',cursor:'pointer'}}>✕</button>
                  <div style={{fontSize:11,color:'#f9a8d4',fontWeight:700}}>{selectedEvent.name} · {selectedEvent.platform}</div>
                  <p style={{fontSize:12.5,lineHeight:1.8,color:'#f4ecf2'}}>«{selectedEvent.text}»</p>
                  <div style={{fontSize:10.5,color:'#94a3b8',marginBottom:5}}>پاسخ واقعی</div>
                  <div style={{fontSize:11.5,lineHeight:1.8,color:selectedEvent.reply?'#bbf7d0':'#fda4af'}}>{selectedEvent.reply||'هنوز پاسخی ثبت نشده'}</div>
                  <pre dir="ltr" style={{margin:'10px 0 0',padding:10,borderRadius:10,background:'rgba(0,0,0,.22)',color:'#c4b5fd',fontSize:9.5,lineHeight:1.55,whiteSpace:'pre-wrap',wordBreak:'break-word',textAlign:'left'}}>{JSON.stringify({conversationId:selectedEvent.conversationId,leadId:selectedEvent.leadId,activeOwner:selectedEvent.activeOwner,scriptId:selectedEvent.scriptId,lead:selectedEvent.lead,state:selectedEvent.state},null,2)}</pre>
                </div>
              )}
              <div style={{display:'flex',flexDirection:'column',gap:7}}>
                {liveEvents.length===0?<div style={{fontSize:11,color:'#64748b'}}>پیام اخیری ثبت نشده است.</div>:liveEvents.map(ev=><button key={ev.id} onClick={()=>setSelectedEvent(ev)} style={{textAlign:'right',padding:'10px 11px',borderRadius:11,border:`1px solid ${selectedEvent?.id===ev.id?'rgba(236,72,153,.35)':'rgba(255,255,255,.06)'}`,background:selectedEvent?.id===ev.id?'rgba(236,72,153,.09)':'rgba(255,255,255,.025)',color:'#e9d5ff',fontFamily:'inherit',cursor:'pointer'}}>
                  <div style={{display:'flex',gap:7,alignItems:'center'}}><span style={{fontSize:10,color:'#f9a8d4'}}>{ev.platform}</span><span style={{fontSize:10,color:'#64748b'}}>{ev.name}</span></div>
                  <div style={{fontSize:11.5,marginTop:4,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{ev.text}</div>
                </button>)}
              </div>
              <div style={{fontSize:10.5,lineHeight:1.7,color:'#64748b',padding:10,borderRadius:10,border:'1px dashed rgba(148,163,184,.18)'}}>در پیام‌های قدیمی فقط اطلاعاتی نمایش داده می‌شود که قبلاً واقعاً ثبت شده؛ دادهٔ حدسی ساخته نمی‌شود.</div>
            </>) : mode==='sandbox' ? (<>
              <div>
                <div style={{fontSize:14,fontWeight:800}}>مکالمهٔ واقعی آزمایشگاهی</div>
                <div style={{fontSize:10.5,color:'#86efac',marginTop:3}}>همان موتور production · لید تست جدا · بدون ارسال و API پولی</div>
              </div>
              <select value={sandboxPlatform} onChange={e=>setSandboxPlatform(e.target.value)} style={{padding:'9px 10px',borderRadius:10,background:'#160b1b',border:'1px solid rgba(255,255,255,.1)',color:'#e9d5ff',fontFamily:'inherit',flexShrink:0}}>
                <option value="bale">بله</option><option value="telegram">تلگرام</option><option value="voice_call">تماس صوتی</option><option value="web">گفتگوی وب</option>
              </select>
              {sandboxTurns.length>0&&<div style={{display:'flex',flexDirection:'column',gap:7,minHeight:86,maxHeight:180,overflowY:'auto',padding:'2px 3px',flexShrink:0}}>
                {sandboxTurns.map((turn,i)=><div key={i} style={{alignSelf:turn.role==='user'?'flex-start':'flex-end',maxWidth:'92%',padding:'8px 10px',borderRadius:turn.role==='user'?'12px 12px 3px 12px':'12px 12px 12px 3px',background:turn.role==='user'?'rgba(59,130,246,.14)':'rgba(236,72,153,.12)',border:`1px solid ${turn.role==='user'?'rgba(96,165,250,.22)':'rgba(244,114,182,.2)'}`}}>
                  <div style={{fontSize:9.5,color:turn.role==='user'?'#93c5fd':'#f9a8d4',marginBottom:3}}>{turn.role==='user'?(turn.voice?'🎙 شما · ویس':'شما'):'فروشنده AI'}{turn.model?` · ${turn.model}`:''}</div>
                  <div style={{fontSize:11.5,lineHeight:1.75,color:'#f4ecf2'}}>{turn.text}</div>
                  {turn.voice&&turn.durationMs?<div style={{fontSize:9,color:'#64748b',marginTop:3}}>{Math.round(turn.durationMs/100)/10} ثانیه</div>:null}
                </div>)}
              </div>}
              <textarea value={sandboxText} onChange={e=>setSandboxText(e.target.value)} onKeyDown={e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();runSandbox();}}} placeholder="پیام بعدی مشتری را وارد کن…" style={{minHeight:76,padding:11,borderRadius:11,resize:'vertical',background:'rgba(255,255,255,.04)',border:'1px solid rgba(255,255,255,.1)',color:'#f4ecf2',fontFamily:'inherit',fontSize:12,lineHeight:1.7,flexShrink:0}}/>
              <div style={{display:'flex',gap:7,flexShrink:0,flexWrap:'wrap'}}>
                <button disabled={sandboxLoading||!sandboxText.trim()} onClick={runSandbox} style={{flex:1,padding:'10px',borderRadius:11,border:'none',background:'linear-gradient(135deg,#db2777,#7c3aed)',color:'#fff',fontFamily:'inherit',fontWeight:800,cursor:'pointer',opacity:sandboxLoading?.6:1}}>{sandboxLoading?'موتور در حال پاسخ…':'ارسال نوبت و دیدن رفتار'}</button>
                <button disabled={sandboxLoading} onClick={toggleSandboxRecording} title={sandboxRecording?'پایان ضبط':'ضبط ویس مشتری'} style={{padding:'10px 13px',borderRadius:11,border:`1px solid ${sandboxRecording?'rgba(251,113,133,.55)':'rgba(96,165,250,.28)'}`,background:sandboxRecording?'rgba(225,29,72,.2)':'rgba(59,130,246,.1)',color:sandboxRecording?'#fda4af':'#bfdbfe',fontFamily:'inherit',fontWeight:800,cursor:'pointer',opacity:sandboxLoading?.55:1}}>{sandboxRecording?'■ پایان':'🎙 ویس'}</button>
                <input ref={sandboxAudioFileRef} aria-label="انتخاب فایل صوتی" type="file" accept="audio/*,.wav,.ogg,.webm,.mp3,.m4a" onChange={uploadSandboxVoice} style={{display:'none'}}/>
                <button disabled={sandboxLoading||sandboxRecording} onClick={()=>sandboxAudioFileRef.current?.click()} title="اجرای فایل صوتی محلی" style={{padding:'10px 11px',borderRadius:11,border:'1px solid rgba(129,140,248,.3)',background:'rgba(99,102,241,.1)',color:'#c7d2fe',fontFamily:'inherit',fontWeight:800,cursor:'pointer',opacity:sandboxLoading?.55:1}}>📁 صوت</button>
                <button disabled={sandboxLoading||sandboxRecording} onClick={replayLastSandboxVoice} title="بازپخش آخرین ویس پذیرفته‌شده" style={{padding:'10px 9px',borderRadius:11,border:'1px solid rgba(52,211,153,.28)',background:'rgba(16,185,129,.09)',color:'#a7f3d0',fontFamily:'inherit',fontWeight:800,cursor:'pointer',opacity:sandboxLoading?.55:1}}>↻ آخرین</button>
                <button onClick={resetSandbox} style={{padding:'10px 12px',borderRadius:11,border:'1px solid rgba(255,255,255,.1)',background:'rgba(255,255,255,.04)',color:'#cbd5e1',fontFamily:'inherit',cursor:'pointer'}}>شروع مجدد</button>
              </div>
              {sandboxVoiceStatus&&<div style={{fontSize:10.5,lineHeight:1.7,color:sandboxRecording?'#fda4af':'#93c5fd',padding:'7px 9px',borderRadius:9,background:'rgba(59,130,246,.06)',border:'1px solid rgba(96,165,250,.12)'}}>{sandboxVoiceStatus}</div>}
              {sandboxResult?.error&&<div style={{color:'#fda4af',fontSize:11.5}}>{sandboxResult.error}</div>}
              {sandboxResult?.trace&&(<>
                <div style={{display:'flex',flexDirection:'column',gap:6}}>{sandboxResult.trace.map((s,i)=><button key={s.id} onClick={()=>inspectTraceStep(s)} style={{display:'flex',alignItems:'center',gap:8,textAlign:'right',padding:'9px 10px',borderRadius:10,border:`1px solid ${selectedStep?.id===s.id?'rgba(244,114,182,.4)':'rgba(255,255,255,.07)'}`,background:selectedStep?.id===s.id?'rgba(236,72,153,.1)':'rgba(255,255,255,.025)',color:'#e9d5ff',fontFamily:'inherit',cursor:'pointer'}}><span style={{width:20,height:20,borderRadius:7,display:'grid',placeItems:'center',fontSize:10,background:s.status==='suppressed'?'rgba(251,191,36,.12)':'rgba(74,222,128,.1)',color:s.status==='suppressed'?'#fcd34d':'#86efac'}}>{i+1}</span><span style={{fontSize:11.5,fontWeight:700}}>{s.title}</span></button>)}</div>
                {selectedStep&&<div style={{padding:11,borderRadius:12,background:'rgba(0,0,0,.22)',border:'1px solid rgba(255,255,255,.06)'}}>
                  <div style={{fontSize:11.5,color:'#f9a8d4',fontWeight:700,marginBottom:5}}>{selectedStep.reason}</div>
                  <div style={{fontSize:10,color:'#64748b',marginBottom:6}}>آرتیفکت کامل تا این مرحله</div>
                  <pre dir="ltr" style={{margin:0,color:'#c4b5fd',fontSize:9.5,lineHeight:1.55,whiteSpace:'pre-wrap',wordBreak:'break-word',textAlign:'left'}}>{JSON.stringify(selectedStep.snapshot,null,2)}</pre>
                </div>}
              </>)}
            </>) : (<>
              <div>
                <div style={{fontSize:14,fontWeight:800}}>تنظیمات ارکستریشن</div>
                <div style={{fontSize:10.5,color:'rgba(225,205,225,.4)',marginTop:3}}>ترتیب مالکیت handlerها؛ تریگر همیشه اول است</div>
              </div>
              <div style={{padding:11,borderRadius:11,background:'rgba(245,158,11,.06)',border:'1px solid rgba(245,158,11,.18)',fontSize:10.5,lineHeight:1.75,color:'#fcd34d'}}>فقط اولویت‌های واقعیِ مصرف‌شده در runtime قابل ویرایش‌اند؛ ترتیب نمایشی ذخیره نمی‌شود.</div>
              {window.RoutingPriorities ? React.createElement(window.RoutingPriorities,{embedded:true}) : <div style={{color:'#64748b',fontSize:11}}>تنظیمات در حال بارگذاری است…</div>}
              <div style={{fontSize:10.5,lineHeight:1.75,color:'#64748b',padding:10,borderRadius:10,border:'1px dashed rgba(148,163,184,.18)'}}>محتوای اسکریپت‌ها فعلاً دست‌نخورده مانده است. برای دیدن داده‌های هر بخش روی همان گره در نقشه کلیک کن.</div>
            </>)}
          </aside>
        )}

        {/* ── RAIL ── */}
        <aside style={{flex:'0 0 282px',display:mode==='live'&&!selectedNode?'flex':'none',flexDirection:'column',gap:11,minHeight:0,padding:'14px 18px',borderLeft:'1px solid rgba(255,255,255,.05)'}}>
          <div style={{display:'flex',alignItems:'center',justifyContent:'space-between'}}>
            <span style={{fontSize:13.5,fontWeight:700}}>جریان پیام‌ها</span>
            <span title={dashboard?.channels?.bale2?.error||''} style={{display:'inline-flex',alignItems:'center',gap:5,fontSize:11,color:dashboard?.channels?.bale2?.connected?'#86efac':'#fda4af'}}>
              <span style={{width:5,height:5,borderRadius:'50%',background:dashboard?.channels?.bale2?.connected?'#4ade80':'#fb7185',boxShadow:`0 0 6px ${dashboard?.channels?.bale2?.connected?'#4ade80':'#fb7185'}`}}/>{dashboard?.channels?.bale2?.connected?'بله متصل':'بله قطع'}
            </span>
          </div>

          {/* current message card */}
          <div style={{borderRadius:18,padding:'13px 14px',background:'linear-gradient(160deg,rgba(40,16,44,.9),rgba(18,9,24,.86))',border:'1px solid rgba(255,255,255,.09)',backdropFilter:'blur(14px)',WebkitBackdropFilter:'blur(14px)',flexShrink:0}}>
            {idle||!msgs.length?(
              <div style={{display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',gap:10,padding:'18px 0',color:'rgba(225,205,225,.35)',fontSize:12,textAlign:'center'}}>
                <span style={{fontSize:22,opacity:.5,animation:'mmBlink 2.5s ease-in-out infinite'}}>📭</span>
                <span>هنوز پیامی دریافت نشده</span>
                <span style={{fontSize:10,opacity:.6}}>وقتی لید پیام بفرسته اینجا نشون داده می‌شه</span>
              </div>
            ):(
              <>
            <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:10}}>
              <span style={{display:'inline-flex',alignItems:'center',gap:5,fontSize:11,fontWeight:600,color:'oklch(0.78 0.18 350)',background:'oklch(0.72 0.19 350 / .12)',border:'1px solid oklch(0.72 0.19 350 / .28)',padding:'2px 8px',borderRadius:999}}>
                <span style={{width:4,height:4,borderRadius:'50%',background:'oklch(0.78 0.18 350)'}}/>{current.channel}
              </span>
              <span style={{fontSize:10.5,color:'rgba(225,205,225,.4)'}}>{current.time}</span>
            </div>
            <p style={{margin:0,fontSize:13.5,lineHeight:1.8,fontWeight:500,color:'#f4ecf2'}}>«{current.text}»</p>
            <div style={{height:1,background:'linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent)',margin:'12px 0'}}/>
            {analyzing?(
              <div style={{display:'flex',alignItems:'center',gap:10}}>
                <span style={{width:15,height:15,borderRadius:'50%',border:'2px solid rgba(255,42,154,.25)',borderTopColor:'oklch(0.78 0.18 340)',animation:'mmSpin .8s linear infinite',flex:'0 0 auto',display:'inline-block'}}/>
                <span style={{fontSize:12.5,fontWeight:600,color:'oklch(0.85 0.12 340)'}}>مغز در حال تحلیل پیام…</span>
              </div>
            ):decision?(
              <div>
                <div style={{display:'flex',alignItems:'center',gap:7,marginBottom:10}}>
                  <span style={{fontSize:11,color:'rgba(225,205,225,.5)',fontWeight:600}}>ارجاع به</span>
                  <span style={{fontSize:14,color:'rgba(225,205,225,.4)'}}>←</span>
                  <span style={{fontSize:12.5,fontWeight:700,color:'#fff',padding:'3px 10px',borderRadius:8,background:decision.chipBg,border:`1px solid ${decision.chipBorder}`,boxShadow:decision.chipShadow}}>{decision.catName}</span>
                  <span style={{fontSize:12,fontWeight:600,color:decision.leafColor}}>{decision.leaf}</span>
                </div>
                <div style={{display:'flex',alignItems:'center',gap:9,marginBottom:9}}>
                  <div style={{flex:1,height:6,borderRadius:99,background:'rgba(255,255,255,.07)',overflow:'hidden'}}>
                    <div style={{height:'100%',borderRadius:99,width:decision.confW,background:decision.barBg,boxShadow:`0 0 10px ${decision.barGlow}`,transition:'width .5s ease'}}/>
                  </div>
                  <span style={{fontSize:12.5,fontWeight:800,color:'#fff',minWidth:36}}>{decision.confPct}</span>
                </div>
                <p style={{margin:0,fontSize:11.5,lineHeight:1.65,color:'rgba(225,205,225,.6)'}}>{decision.reason}</p>
              </div>
            ):(
              <span style={{fontSize:12,color:'rgba(225,205,225,.35)'}}>در انتظار پیام…</span>
            )}
              </>
            )}
          </div>

          {/* queue */}
          {queue.length>0&&(
            <>
            <div style={{fontSize:11.5,fontWeight:700,color:'rgba(225,205,225,.5)'}}>در صف</div>
            <div style={{display:'flex',flexDirection:'column',gap:6}}>
              {queue.map((q,i)=>(
                <div key={i} onClick={()=>select(q.idx)} style={{display:'flex',alignItems:'center',gap:9,padding:'9px 11px',borderRadius:12,background:'rgba(255,255,255,.025)',border:'1px solid rgba(255,255,255,.06)',cursor:'pointer',transition:'background .2s'}}>
                  <span style={{width:6,height:6,borderRadius:'50%',background:'rgba(225,205,225,.3)',flex:'0 0 auto'}}/>
                  <span style={{fontSize:12,color:'rgba(235,220,235,.75)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',flex:1}}>{q.text}</span>
                </div>
              ))}
            </div>
            </>
          )}

          {/* recent */}
          <div style={{fontSize:11.5,fontWeight:700,color:'rgba(225,205,225,.5)',paddingTop:4}}>ارجاع‌های اخیر</div>
          <div className="mm-scrl" style={{display:'flex',flexDirection:'column',gap:6,overflowY:'auto',flex:1,minHeight:0}}>
            {recent.length===0?(
              <span style={{fontSize:11,color:'rgba(225,205,225,.28)'}}>هنوز ارجاعی ثبت نشده…</span>
            ):recent.map((r,i)=>(
              <div key={i} style={{display:'flex',alignItems:'center',gap:9,padding:'8px 11px',borderRadius:11,background:'rgba(255,255,255,.02)',border:'1px solid rgba(255,255,255,.05)'}}>
                <span style={{width:7,height:7,borderRadius:2,background:r.color,boxShadow:`0 0 8px ${r.glow}`,flex:'0 0 auto'}}/>
                <div style={{display:'flex',flexDirection:'column',gap:1,flex:1,minWidth:0}}>
                  <span style={{fontSize:11.5,fontWeight:600,color:'#ece0ec',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{r.intent}</span>
                  <span style={{fontSize:10,color:'rgba(225,205,225,.4)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{r.dest}</span>
                </div>
                <span style={{fontSize:11,fontWeight:700,color:'rgba(235,220,235,.65)'}}>{r.confPct}</span>
              </div>
            ))}
          </div>
        </aside>

        {/* ── STAGE ── */}
        <div ref={stageRef} style={{flex:'1 1 auto',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',minWidth:0,gap:10,padding:'10px 14px 18px',overflowY:'auto',overflowX:'hidden'}}>

          {msg&&<div style={{display:'flex',alignItems:'center',gap:8,maxWidth:'92%',padding:'7px 12px',borderRadius:12,background:'rgba(10,5,16,.72)',border:'1px solid rgba(255,255,255,.09)',boxShadow:'0 8px 24px rgba(0,0,0,.25)',fontSize:11.5,fontWeight:700,flexShrink:0}}>
            {isVoicePacket&&<span title="پیام صوتی" style={{display:'inline-flex',alignItems:'center',gap:4,padding:'3px 7px',borderRadius:999,color:'#bfdbfe',background:'rgba(59,130,246,.16)',border:'1px solid rgba(96,165,250,.32)',boxShadow:'0 0 14px rgba(59,130,246,.16)',fontSize:9.5,whiteSpace:'nowrap'}}>🎙 ویس</span>}
            <span style={{color:incoming?'#f9a8d4':'#cbd5e1'}}>{msg.sourceLabel||'ورودی پیام'}</span>
            <span style={{color:'#64748b'}}>←</span>
            <span style={{color:analyzing?'#f9a8d4':'#e9d5ff'}}>مغز اصلی</span>
            <span style={{color:'#64748b'}}>←</span>
            <span style={{color:(routing||stateflow||responding)&&target?col(target.hue):outputFlow?'#86efac':'#94a3b8'}}>{msg.destinationLabel||target?.name||'در انتظار تصمیم'}</span>
            {msg.currentState&&<><span style={{color:'#64748b'}}>←</span><span style={{color:stateflow||outputFlow?'#a5b4fc':'#94a3b8'}}>استیت: {msg.currentState}</span></>}
            <span style={{fontSize:9.5,color:'#64748b',fontWeight:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>«{msg.text}»</span>
          </div>}

          {/* scaled mindmap canvas */}
          <div style={{transform:`scale(${stageScale})`,transformOrigin:'center center',flexShrink:0}}>
            <div style={{position:'relative',width:1000,height:704}}>
              <svg viewBox="0 0 1000 704" style={{position:'absolute',inset:0,width:'100%',height:'100%',overflow:'visible'}}>
                <defs>
                  <filter id="mg1" x="-60%" y="-60%" width="220%" height="220%">
                    <feGaussianBlur stdDeviation="3.4" result="b"/>
                    <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
                  </filter>
                </defs>
                {/* ambient rings */}
                <circle cx={BX} cy={BY} r={150} fill="none" stroke="rgba(255,42,154,.12)" strokeWidth={1}/>
                <circle cx={BX} cy={BY} r={240} fill="none" stroke="rgba(255,42,154,.07)" strokeWidth={1}/>
                <circle cx={BX} cy={BY} r={330} fill="none" stroke="rgba(255,42,154,.04)" strokeWidth={1}/>
                {/* dashed bezier lines */}
                {cats.map((c,i)=>(
                  <path key={i} d={c.path} fill="none" stroke={col(c.hue,.32)} strokeWidth={1.6} strokeDasharray="2 7" strokeLinecap="round" style={{animation:'mmDash 1.6s linear infinite'}}/>
                ))}
                {/* leaf connectors */}
                {leafLinks.map((l,i)=>(
                  <path key={i} d={l.conn} fill="none" stroke={l.stroke} strokeWidth={1.1}/>
                ))}
                {/* animated overlay */}
                {E('g',{key:'ov'},ov)}
              </svg>

              {(stateflow||responding||outgoing||returned)&&stateNodes.map((node,index)=>{
                const active=node.isCurrent;
                const done=node.status==='done';
                const label=active?'جاری':done?'انجام‌شده':'در انتظار';
                return <div key={node.id||`${node.name}-${index}`} data-script-state={node.id||node.name} onClick={()=>openStateDetails(node)} title={`مشاهده داده‌های کامل استیت ${node.name||node.order||index+1}`} style={{
                  position:'absolute',left:node.x,top:node.y,transform:'translate(-50%,-50%)',zIndex:12,
                  width:108,padding:'8px 9px',borderRadius:12,textAlign:'center',direction:'rtl',
                  background:active?'linear-gradient(145deg,rgba(67,56,202,.88),rgba(24,18,50,.96))':'rgba(16,12,28,.92)',
                  border:`1px solid ${active?'rgba(165,180,252,.95)':done?'rgba(129,140,248,.48)':'rgba(255,255,255,.11)'}`,
                  boxShadow:active?'0 0 0 1px rgba(129,140,248,.32),0 0 28px rgba(99,102,241,.55)':'0 7px 20px rgba(0,0,0,.42)',
                  color:active?'#fff':'#d8d3e2',cursor:'pointer'
                }}>
                  <div style={{fontSize:10.5,fontWeight:800,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{node.name||`استیت ${node.order??index+1}`}</div>
                  <div style={{fontSize:8.5,marginTop:3,color:active?'#c7d2fe':done?'#a5b4fc':'#777087'}}>{label}</div>
                </div>;
              })}

              {packetPos&&msg&&<button type="button" data-command-packet="active" aria-label="جزئیات پیام در حال عبور" title="برای دیدن داده‌های این پیام کلیک کنید"
                onMouseEnter={pausePacketMotion}
                onMouseLeave={()=>releasePacketMotion(false)}
                onFocus={pausePacketMotion}
                onBlur={()=>releasePacketMotion(false)}
                onClick={()=>{
                  const p=packetPauseRef.current,a=animRef.current;
                  if(!p.hovering&&!p.locked)p.wasPaused=a.paused;
                  p.locked=true;pausePacketMotion();
                  setSelectedNode({
                    kind:'message',title:outputFlow?'پاسخ در حال عبور':'پیام در حال عبور',
                    data:{
                      phase,stage:packetStage,route:packetRouteLabel,content:packetText,
                      originalMessage:msg.text,reply:msg.reply||null,inputMode:msg.inputMode||'text',
                      channel:msg.channel||null,channelLabel:msg.sourceLabel||'ورودی پیام',
                      destination:msg.destinationLabel||target?.name||'مغز اصلی',
                      currentState:msg.currentState||null,currentStep:msg.currentStep??null,
                      intent:msg.intent||null,confidence:msg.conf??null,reason:msg.reason||null,
                      scriptStates:msg.scriptStates||[],trace:msg.trace||[]
                    }
                  });
                }} style={{
                position:'absolute',left:packetBubbleX,top:packetPos.y,
                transform:packetPos.y<125?'translate(-50%,18px)':'translate(-50%,-115%)',
                width:260,maxWidth:260,padding:'8px 10px',borderRadius:12,zIndex:22,pointerEvents:'auto',cursor:'pointer',
                background:'linear-gradient(150deg,rgba(29,16,38,.96),rgba(10,6,17,.96))',
                border:`1px solid ${(routing||stateflow||responding)&&target?col(target.hue,.72):outputFlow?'rgba(74,222,128,.65)':col(345,.62)}`,
                boxShadow:`0 8px 30px rgba(0,0,0,.5),0 0 22px ${(routing||stateflow||responding)&&target?col(target.hue,.28):outputFlow?'rgba(74,222,128,.24)':col(345,.24)}`,
                transition:'left .08s linear,top .08s linear,border-color .25s,box-shadow .2s',textAlign:'right',direction:'rtl',fontFamily:'inherit',color:'inherit',outline:'none'
              }}>
                <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:8,marginBottom:4}}>
                  <span style={{display:'inline-flex',alignItems:'center',gap:5,fontSize:10,fontWeight:800,color:'#f9a8d4'}}>
                    {isVoicePacket&&<span title="پیام صوتی" style={{display:'inline-flex',alignItems:'center',gap:3,padding:'2px 6px',borderRadius:999,color:'#bfdbfe',background:'rgba(59,130,246,.18)',border:'1px solid rgba(96,165,250,.35)'}}>
                      <span style={{fontSize:11}}>🎙</span>
                      <span style={{display:'inline-flex',alignItems:'center',gap:1,height:13}}>{[0,1,2].map(i=><span key={i} style={{display:'block',width:2,height:5,borderRadius:2,background:'#93c5fd',animation:`mmVoice .75s ${i*.12}s ease-in-out infinite`}}/>)}</span>
                      <span>ویس</span>
                    </span>}
                    {msg.sourceLabel||'ورودی پیام'}
                  </span>
                  <span style={{fontSize:9.5,color:'#94a3b8'}}>{packetStage}</span>
                </div>
                <div style={{fontSize:11.5,lineHeight:1.65,color:'#fff',display:'-webkit-box',WebkitLineClamp:2,WebkitBoxOrient:'vertical',overflow:'hidden'}}>«{packetText}»</div>
                {packetRouteLabel&&<div style={{fontSize:9.5,color:outputFlow?'#86efac':target?col(target.hue,.95):'#fbbf24',marginTop:4,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{packetRouteLabel}</div>}
                <div style={{fontSize:8.5,color:'#94a3b8',marginTop:5,borderTop:'1px solid rgba(255,255,255,.06)',paddingTop:4}}>با نگه‌داشتن موس متوقف می‌شود · برای جزئیات کلیک کنید</div>
              </button>}

              {/* brain glow + expand ring */}
              <div style={{position:'absolute',left:BX,top:BY,width:300,height:300,transform:'translate(-50%,-50%)',pointerEvents:'none'}}>
                <div style={{position:'absolute',left:'50%',top:'50%',width:240,height:240,transform:'translate(-50%,-50%)',borderRadius:'50%',background:'radial-gradient(circle,rgba(255,42,154,.42) 0%,rgba(255,42,154,0) 66%)',opacity:thinkGlow,transition:'opacity .4s'}}/>
                <div style={{position:'absolute',left:'50%',top:'50%',width:178,height:178,transform:'translate(-50%,-50%)',borderRadius:'50%',border:'1px solid rgba(255,80,180,.18)',animation:'mmRing 3.4s ease-out infinite'}}/>
              </div>
              <img data-command-node="brain" onClick={openBrainDetails} title="مشاهده داده‌های کامل مغز اصلی" src="/assets/brain.png" alt="🧠" style={{position:'absolute',left:BX,top:BY,width:182,transform:'translate(-50%,-50%)',animation:'brainPulse 3.4s ease-in-out infinite',zIndex:6,cursor:'pointer'}} onError={e=>e.target.style.display='none'} />

              {/* brain label */}
              <div style={{position:'absolute',left:BX,top:BY+96,transform:'translate(-50%,0)',zIndex:7,display:'flex',flexDirection:'column',alignItems:'center',gap:3,textAlign:'center',pointerEvents:'none'}}>
                <span style={{fontSize:13.5,fontWeight:800,color:'#fff',textShadow:'0 0 14px rgba(255,42,154,.8)'}}>مغز اصلی</span>
                <span style={{fontSize:11,fontWeight:600,color:analyzing?'oklch(0.85 0.12 340)':outputFlow?'#86efac':routing&&target?col(target.hue,.95):'rgba(235,220,235,.6)',transition:'color .3s'}}>{statusText}</span>
              </div>

              {/* category nodes */}
              {catViews.map(c=>(
                <div key={c.id} data-command-node={c.id} onClick={()=>setSelectedNode({kind:'category',title:c.name,category:c.id,data:{id:c.id,name:c.name,description:c.desc,count:(entities[c.id]||[]).length,priority:items.findIndex(x=>x.id===c.id)+1}})} style={c.ns}>
                  <span style={c.ds}/>
                  <div style={{display:'flex',flexDirection:'column',gap:1}}>
                    <span style={{fontSize:13.5,fontWeight:700,color:'#fff',lineHeight:1.2}}>{c.displayName}</span>
                    <span style={{fontSize:10.5,fontWeight:500,color:'rgba(225,205,225,.5)',lineHeight:1.2}}>{c.displayDesc}</span>
                  </div>
                  <span style={c.cs}>{c.count}</span>
                </div>
              ))}

              {/* leaves */}
              {leafViews.map((l,i)=>(
                <div key={i} data-command-leaf={`${l.category}:${l.entity?.id||i}`} onClick={()=>l.category==='script'&&!l.entity?.placeholder?openScriptDetails(l.entity):setSelectedNode({kind:'entity',title:l.name,category:l.category,data:l.entity})} style={l.style}>{l.name}</div>
              ))}
            </div>
          </div>

          {/* legend + controls */}
          <div style={{display:'flex',alignItems:'center',gap:12,flexWrap:'wrap',justifyContent:'center',flexShrink:0}}>
            <div style={{display:'flex',alignItems:'center',gap:12,padding:'8px 14px',borderRadius:13,background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.06)'}}>
              {legend.map((g,i)=>(
                <span key={i} style={{display:'inline-flex',alignItems:'center',gap:5,fontSize:11,fontWeight:600,color:'rgba(235,220,235,.7)'}}>
                  <span style={{width:8,height:8,borderRadius:3,background:g.color,boxShadow:`0 0 7px ${g.glow}`}}/>{g.name}
                </span>
              ))}
            </div>
            <button onClick={toggle} style={{display:'inline-flex',alignItems:'center',gap:7,padding:'9px 16px',borderRadius:13,background:'linear-gradient(135deg,oklch(0.66 0.22 350),oklch(0.6 0.2 320))',border:'none',color:'#fff',fontFamily:'inherit',fontSize:13,fontWeight:700,cursor:'pointer',boxShadow:'0 5px 20px oklch(0.6 0.22 340 / .5)'}}>
              {a.paused?'▶ پخش':'❚❚ مکث'}
            </button>
            <button onClick={onRefresh} style={{padding:'9px 13px',borderRadius:13,border:'1px solid rgba(255,255,255,.1)',background:'rgba(255,255,255,.04)',color:'rgba(225,205,225,.6)',fontFamily:'inherit',fontSize:12,cursor:'pointer'}}>↺ بازسازی</button>
          </div>

          {/* chat input */}
          <div style={{display:'flex',gap:8,maxWidth:580,width:'100%',flexShrink:0}}>
            <input
              value={chatInput}
              onChange={e=>setChatInput(e.target.value)}
              onKeyDown={e=>e.key==='Enter'&&sendChat()}
              placeholder="پیش‌نمایش امن پیام — مسیر واقعی بدون ارسال"
              style={{flex:1,padding:'10px 14px',borderRadius:12,fontSize:13,fontFamily:'inherit',background:'rgba(255,255,255,.06)',border:'1px solid rgba(255,255,255,.13)',color:'#f4ecf2',outline:'none'}}
            />
            <button onClick={sendChat} style={{padding:'10px 20px',borderRadius:12,border:'none',background:'linear-gradient(135deg,oklch(0.66 0.22 350),oklch(0.6 0.2 320))',color:'#fff',fontFamily:'inherit',fontSize:13,fontWeight:700,cursor:'pointer',flexShrink:0}}>
              پیش‌نمایش
            </button>
          </div>
        </div>

      </div>
    </div>
  );
};

// ─── Orchestra Board ──────────────────────────────────────
const OrchestraBoard = () => {
  const { Icon, useToast } = window.SB_UI;
  const toast = useToast();

  const DEFAULTS = [
    { id: 'trigger',  label: 'تریگر',    icon: 'zap',       color: '#F87171', desc: 'رویداد فوری — بلافاصله قطع می‌کنه' },
    { id: 'followup', label: 'فالوآپ',   icon: 'clock',     color: '#FB923C', desc: 'قرار تماس از قبل تنظیم شده' },
    { id: 'campaign', label: 'کمپین',    icon: 'megaphone', color: '#60A5FA', desc: 'برنامه بازاریابی در حال اجرا' },
    { id: 'script',   label: 'اسکریپت', icon: 'file-text', color: '#A78BFA', desc: 'رفتار پیشفرض لید گیری' },
    { id: 'bot',      label: 'بات',      icon: 'cpu',       color: '#34D399', desc: 'اگه هیچ handler دیگه‌ای نبود' },
  ];

  const [items, setItems]             = useState(DEFAULTS);
  const [entities, setEntities]       = useState({ campaign: [], script: [], followup: [], trigger: [], bot: [] });
  const [saving, setSaving]           = useState(false);
  const [dirty, setDirty]             = useState(false);
  const [dragging, setDragging]       = useState(null);
  const [innerDirty, setInnerDirty]   = useState(false);
  const [innerSaving, setInnerSaving] = useState(false);
  const [tab, setTab]                 = useState('mindmap'); // mindmap | scenarios
  const [brainScript, setBrainScript] = useState(null);
  const [showBrain, setShowBrain]     = useState(false);
  const [brainEdit, setBrainEdit]           = useState('');
  const [brainFallback, setBrainFallback]   = useState('');
  const [brainSaving, setBrainSaving]       = useState(false);
  const [distPrompt, setDistPrompt]         = useState('');     // متن هاردکد توزیع
  const [distUnlocked, setDistUnlocked]     = useState(false);  // بعد از وارد کردن رمز درست
  const [distPassword, setDistPassword]     = useState('');
  const [distSaving, setDistSaving]         = useState(false);
  const dragIdx      = useRef(null);
  const innerDragIdx = useRef(null);

  const colors = Object.fromEntries(DEFAULTS.map(d => [d.id, d.color]));
  const priorityOrder = items.map(i => i.id);

  const loadData = () => {
    Promise.all([
      window.api('/campaigns/campaigns').catch(() => []),
      window.api('/scripts').catch(() => ({})),
      window.api('/followup/templates').catch(() => ({ success: false })),
      window.api('/orchestrator/rules').catch(() => ({ success: false })),
      window.api('/orchestrator/brain-topology').catch(() => ({ success: false })),
    ]).then(([camps, scripts, followups, rules, topology]) => {
      const ruleMap = {};
      if (rules.success) rules.data.forEach(r => { if (r.ref_id) ruleMap[r.ref_id] = r.priority; });
      const campList = Array.isArray(camps)
        ? camps.filter(c => c.is_active).map(c => ({ id: c.id, name: c.name, priority: ruleMap[c.id] ?? null, is_active: c.is_active }))
            .sort((a, b) => a.priority === null ? 1 : b.priority === null ? -1 : a.priority - b.priority)
        : [];
      const rawScripts = scripts?.data?.scripts || scripts?.scripts || scripts?.data || [];
      const scriptList = Array.isArray(rawScripts)
        ? rawScripts.filter(s => s.is_active).map(s => ({ ...s, badge: s.is_default ? 'پیشفرض' : null }))
        : [];
      const followupList = followups.success && Array.isArray(followups.data)
        ? [...new Map(followups.data.map(f => {
            const camp = f.name?.match(/^\[(.*?)\]/)?.[1] || '';
            return [camp, { id: camp, name: camp }];
          })).values()].filter(f => f.name)
        : [];
      const triggerList = topology?.success && Array.isArray(topology.data?.triggers)
        ? topology.data.triggers.map(t => ({ ...t, name: t.name || t.keyword }))
        : [];
      window.api('/orchestrator/brain-script').then(r => {
        if (r.success && r.data) {
          setBrainScript(r.data);
          setBrainEdit(r.data.system_prompt || '');
          setBrainFallback(r.data.fallback_message || '');
          setDistPrompt(r.data.distribution_prompt || '');
        }
      }).catch(() => {});
      // مغز زیرساخت مستقل است و عمداً در API فهرست اسکریپت‌های کاری نمایش داده نمی‌شود.
      // تنها منبع وضعیت آن /orchestrator/brain-script است؛ اینجا مقدار بارگذاری‌شده را null نکن.
      setEntities({
        campaign: campList, script: scriptList.filter(s => !s.badge), followup: followupList,
        trigger: triggerList,
        bot:     [{ id: 'b1', name: 'بات پاسخ‌دهنده عمومی' }],
      });
    });
  };

  useEffect(() => { loadData(); }, []);

  // drag داخلی کمپین
  const onInnerDragStart = (e, i) => { e.stopPropagation(); innerDragIdx.current = i; };
  const onInnerDragEnter = (e, i) => {
    e.preventDefault(); e.stopPropagation();
    if (innerDragIdx.current === null || innerDragIdx.current === i) return;
    setEntities(prev => {
      const camps = [...prev.campaign];
      const [moved] = camps.splice(innerDragIdx.current, 1);
      camps.splice(i, 0, moved);
      innerDragIdx.current = i;
      return { ...prev, campaign: camps };
    });
    setInnerDirty(true);
  };
  const onInnerDragEnd = (e) => { e.stopPropagation(); innerDragIdx.current = null; };

  const saveInnerOrder = async () => {
    setInnerSaving(true);
    const r = await window.api('/orchestrator/rules');
    if (r.success) {
      const registered = entities.campaign.filter(c => c.priority !== null);
      await Promise.all(registered.map((c, i) => {
        const rule = r.data.find(x => x.ref_id === c.id);
        if (!rule) return;
        return window.api(`/orchestrator/rules/${rule.id}`, { method: 'PUT', body: { priority: (i + 1) * 2 } });
      }));
    }
    setInnerSaving(false); setInnerDirty(false);
    toast('ترتیب کمپین‌ها ذخیره شد', 'success');
  };

  // drag خارجی بلوک‌ها
  const onDragStart = (e, i) => { dragIdx.current = i; setDragging(i); e.dataTransfer.effectAllowed = 'move'; };
  const onDragEnter = (e, i) => {
    e.preventDefault();
    if (dragIdx.current === null || dragIdx.current === i) return;
    setItems(prev => {
      const next = [...prev];
      const [moved] = next.splice(dragIdx.current, 1);
      next.splice(i, 0, moved);
      dragIdx.current = i;
      return next;
    });
  };
  const onDragEnd = () => { setDragging(null); dragIdx.current = null; setDirty(true); };

  return (
    <div style={{ maxWidth: 640, margin: '40px auto', padding: '0 16px' }}>

      {/* تب‌ها */}
      <div style={{ display: 'flex', gap: 4, marginBottom: 24, background: 'rgba(255,255,255,0.03)', borderRadius: 10, padding: 4 }}>
        {[['mindmap','نقشه سیستم'],['scenarios','سناریوها']].map(([id, label]) => (
          <button key={id} onClick={() => setTab(id)} style={{
            flex: 1, padding: '7px 0', borderRadius: 7, border: 'none', cursor: 'pointer',
            background: tab === id ? 'rgba(255,255,255,0.08)' : 'transparent',
            color: tab === id ? '#e2e8f0' : '#475569',
            fontSize: 13, fontWeight: tab === id ? 600 : 400, transition: 'all 0.15s',
            position: 'relative',
          }}>
            {label}
          </button>
        ))}
      </div>

      {/* ─── تب اولویت ─── */}
      {false && (<>
        <div style={{ marginBottom: 20 }}>
          <div style={{ fontSize: 14, color: '#64748b' }}>
            وقتی دو handler همزمان می‌خوان یه لید رو بگیرن، بالاترین در لیست برنده می‌شه.
            <span style={{ color: '#3B82F6', marginRight: 6 }}>بکش و بچین.</span>
          </div>
        </div>

        {/* ─── بلوک مغز اصلی ─── */}
        <div onClick={() => brainScript && setShowBrain(true)} style={{
          display: 'flex', alignItems: 'center', gap: 12,
          padding: '12px 16px', borderRadius: 12, marginBottom: 4,
          background: 'linear-gradient(135deg, rgba(251,191,36,0.08), rgba(245,158,11,0.04))',
          border: '1px solid rgba(251,191,36,0.25)',
          cursor: brainScript ? 'pointer' : 'default',
          transition: 'background 0.15s',
        }}>
          <div style={{ fontSize: 20 }}>🧠</div>
          <div style={{ flex: 1 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ color: '#FCD34D', fontWeight: 700, fontSize: 13 }}>مغز اصلی</span>
              <span style={{ fontSize: 9, padding: '2px 6px', borderRadius: 8, background: 'rgba(251,191,36,0.1)', color: '#F59E0B', border: '1px solid rgba(251,191,36,0.2)' }}>🔒 قفل</span>
              {!brainScript && (
                <span style={{ fontSize: 9, padding: '2px 6px', borderRadius: 8, background: 'rgba(239,68,68,0.1)', color: '#EF4444', border: '1px solid rgba(239,68,68,0.2)' }}>⚠ در حال بارگذاری...</span>
              )}
            </div>
            <div style={{ fontSize: 11, color: '#64748b', marginTop: 2 }}>
              {brainScript ? brainScript.name : 'همیشه اول — توزیع‌کننده و fallback'}
            </div>
          </div>
          <div style={{ fontSize: 10, color: '#F59E0B' }}>مشاهده اسکریپت ←</div>
        </div>

        {/* ─── Modal اسکریپت مغز ─── */}
        {showBrain && brainScript && (
          <div style={{
            position: 'fixed', inset: 0, zIndex: 1000,
            background: 'rgba(0,0,0,0.7)', display: 'flex', alignItems: 'center', justifyContent: 'center',
          }} onClick={() => { setShowBrain(false); setDistUnlocked(false); setDistPassword(''); }}>
            <div onClick={e => e.stopPropagation()} style={{
              width: 620, maxHeight: '80vh', borderRadius: 16, overflow: 'hidden',
              background: '#0f172a', border: '1px solid rgba(251,191,36,0.2)',
              display: 'flex', flexDirection: 'column',
            }}>
              {/* هدر */}
              <div style={{ padding: '16px 20px', borderBottom: '1px solid rgba(255,255,255,0.06)', display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 18 }}>🧠</span>
                <div style={{ flex: 1 }}>
                  <div style={{ color: '#FCD34D', fontWeight: 700, fontSize: 14 }}>{brainScript.name}</div>
                  <div style={{ color: '#475569', fontSize: 10, marginTop: 2 }}>این اسکریپت قابل ویرایش است — تغییرات بلافاصله اعمال می‌شه</div>
                </div>
                <button onClick={() => { setShowBrain(false); setDistUnlocked(false); setDistPassword(''); }} style={{ background: 'none', border: 'none', color: '#475569', fontSize: 18, cursor: 'pointer' }}>✕</button>
              </div>
              {/* متن اسکریپت */}
              <div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px' }}>

                {/* ─── بخش هاردکد منطق توزیع — بالای متن ادمین، قفل با رمز ─── */}
                <div style={{ marginBottom: 16, border: '1px solid rgba(251,191,36,0.25)', borderRadius: 10, overflow: 'hidden' }}>
                  <div style={{ padding: '8px 12px', background: 'rgba(251,191,36,0.06)', display: 'flex', alignItems: 'center', gap: 6 }}>
                    <span style={{ fontSize: 12 }}>{distUnlocked ? '🔓' : '🔒'}</span>
                    <span style={{ color: '#FCD34D', fontSize: 11, fontWeight: 700, flex: 1 }}>منطق اصلی توزیع (همیشه بالای متن ادمین قرار می‌گیرد)</span>
                    {!distUnlocked && (
                      <span style={{ color: '#64748b', fontSize: 10 }}>برای ویرایش رمز عبور لازم است</span>
                    )}
                  </div>
                  <textarea
                    value={distPrompt}
                    onChange={e => setDistPrompt(e.target.value)}
                    readOnly={!distUnlocked}
                    style={{
                      width: '100%', minHeight: 180, background: distUnlocked ? 'rgba(251,191,36,0.04)' : 'rgba(255,255,255,0.02)',
                      border: 'none', borderTop: '1px solid rgba(251,191,36,0.15)',
                      color: distUnlocked ? '#cbd5e1' : '#64748b', fontSize: 12, lineHeight: 1.8, padding: '12px',
                      fontFamily: 'inherit', resize: 'vertical', outline: 'none', boxSizing: 'border-box',
                      cursor: distUnlocked ? 'text' : 'default',
                    }}
                  />
                  <div style={{ padding: '8px 12px', background: 'rgba(251,191,36,0.06)', display: 'flex', alignItems: 'center', gap: 8 }}>
                    {!distUnlocked ? (
                      <>
                        <input
                          type="password"
                          value={distPassword}
                          onChange={e => setDistPassword(e.target.value)}
                          placeholder="رمز عبور ادمین"
                          style={{
                            flex: 1, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)',
                            borderRadius: 6, color: '#cbd5e1', fontSize: 12, padding: '6px 10px', fontFamily: 'inherit', outline: 'none',
                          }}
                        />
                        <button onClick={async () => {
                          const r = await window.api('/auth/verify-password', { method: 'POST', body: { password: distPassword } });
                          if (r.success) { setDistUnlocked(true); toast('باز شد — حالا می‌تونی ویرایش کنی', 'success'); }
                          else toast(r.error || 'رمز اشتباه است', 'error');
                        }} style={{ padding: '6px 14px', borderRadius: 6, border: 'none', background: 'rgba(251,191,36,0.15)', color: '#FCD34D', fontSize: 11, fontWeight: 700, cursor: 'pointer' }}>
                          باز کردن قفل
                        </button>
                      </>
                    ) : (
                      <button disabled={distSaving} onClick={async () => {
                        setDistSaving(true);
                        const r = await window.api('/orchestrator/brain-script/distribution', { method: 'PUT', body: { distribution_prompt: distPrompt, password: distPassword } });
                        setDistSaving(false);
                        if (r.success) { toast('منطق توزیع ذخیره شد', 'success'); setDistUnlocked(false); setDistPassword(''); }
                        else toast(r.error || 'خطا', 'error');
                      }} style={{ padding: '6px 14px', borderRadius: 6, border: 'none', background: '#F59E0B', color: '#000', fontSize: 11, fontWeight: 700, cursor: 'pointer', marginRight: 'auto' }}>
                        {distSaving ? 'در حال ذخیره...' : 'ذخیره منطق توزیع'}
                      </button>
                    )}
                  </div>
                </div>

                <div style={{ color: '#64748b', fontSize: 11, marginBottom: 6 }}>متن آزاد ادمین — زیر بخش بالا اضافه می‌شه:</div>
                <textarea
                  value={brainEdit}
                  onChange={e => setBrainEdit(e.target.value)}
                  style={{
                    width: '100%', minHeight: 220, background: 'rgba(255,255,255,0.03)',
                    border: '1px solid rgba(255,255,255,0.08)', borderRadius: 10,
                    color: '#cbd5e1', fontSize: 12, lineHeight: 1.8, padding: '12px',
                    fontFamily: 'inherit', resize: 'vertical', outline: 'none', boxSizing: 'border-box',
                  }}
                />
                <div style={{ marginTop: 14 }}>
                  <div style={{ color: '#94a3b8', fontSize: 11, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6 }}>
                    <span>⚠️</span>
                    <span>پیام هنگام قطعی سرویس هوش مصنوعی</span>
                    <span style={{ color: '#475569', fontSize: 10 }}>(اگه خالی بمونه پیام پیش‌فرض فرستاده می‌شه)</span>
                  </div>
                  <input
                    value={brainFallback}
                    onChange={e => setBrainFallback(e.target.value)}
                    placeholder="متأسفم، الان یه مشکل فنی موقت داریم. لطفاً چند دقیقه دیگه دوباره پیام بده 🙏"
                    style={{
                      width: '100%', background: 'rgba(255,255,255,0.03)',
                      border: '1px solid rgba(251,191,36,0.2)', borderRadius: 8,
                      color: '#cbd5e1', fontSize: 12, padding: '9px 12px',
                      fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box',
                    }}
                  />
                </div>
              </div>
              {/* فوتر */}
              <div style={{ padding: '12px 20px', borderTop: '1px solid rgba(255,255,255,0.06)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
                <button onClick={() => { setShowBrain(false); setDistUnlocked(false); setDistPassword(''); }} style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid rgba(255,255,255,0.08)', background: 'none', color: '#475569', fontSize: 12, cursor: 'pointer' }}>
                  بستن
                </button>
                <button disabled={brainSaving} onClick={async () => {
                  setBrainSaving(true);
                  const r = await window.api('/orchestrator/brain-script', { method: 'PUT', body: { system_prompt: brainEdit, fallback_message: brainFallback } });
                  setBrainSaving(false);
                  if (r.success) { setBrainScript(prev => ({...prev, system_prompt: brainEdit, fallback_message: brainFallback})); toast('اسکریپت ذخیره شد', 'success'); setShowBrain(false); }
                  else toast(r.error || 'خطا', 'error');
                }} style={{ padding: '7px 16px', borderRadius: 8, border: 'none', background: '#F59E0B', color: '#000', fontSize: 12, fontWeight: 700, cursor: 'pointer' }}>
                  {brainSaving ? 'در حال ذخیره...' : 'ذخیره'}
                </button>
              </div>
            </div>
          </div>
        )}

        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {items.map((item, i) => {
            const ents = entities[item.id] || [];
            return (
              <div key={item.id} draggable={item.id !== 'trigger'}
                onDragStart={item.id !== 'trigger' ? e => onDragStart(e, i) : undefined}
                onDragEnter={item.id !== 'trigger' ? e => onDragEnter(e, i) : undefined}
                onDragEnd={item.id !== 'trigger' ? onDragEnd : undefined} onDragOver={e => e.preventDefault()}
                style={{
                  background: 'rgba(255,255,255,0.03)',
                  border: `1px solid ${i === 0 ? item.color + '40' : 'rgba(255,255,255,0.07)'}`,
                  borderRadius: 12, cursor: item.id === 'trigger' ? 'default' : (dragging === i ? 'grabbing' : 'grab'),
                  overflow: 'hidden', opacity: dragging === i ? 0.4 : 1, transition: 'opacity 0.15s',
                }}
                onMouseEnter={e => { if (dragging === null) e.currentTarget.style.background = 'rgba(255,255,255,0.05)'; }}
                onMouseLeave={e => e.currentTarget.style.background = 'rgba(255,255,255,0.03)'}
              >
                <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '13px 16px' }}>
                  <div style={{
                    width: 24, height: 24, borderRadius: 7, flexShrink: 0,
                    background: i === 0 ? item.color + '20' : 'rgba(255,255,255,0.04)',
                    border: `1px solid ${i === 0 ? item.color + '50' : 'rgba(255,255,255,0.08)'}`,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 10, fontWeight: 700, fontFamily: 'JetBrains Mono',
                    color: i === 0 ? item.color : '#475569',
                  }}>{i + 1}</div>
                  <div style={{ width: 34, height: 34, borderRadius: 9, flexShrink: 0, background: item.color + '12', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <Icon name={item.icon} size={15} color={item.color} />
                  </div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 14, fontWeight: 600, color: '#e2e8f0' }}>{item.label}</div>
                    <div style={{ fontSize: 11, color: '#475569', marginTop: 1 }}>{item.desc}</div>
                  </div>
                  {ents.length > 0 && (
                    <div style={{ fontSize: 11, padding: '2px 8px', borderRadius: 20, background: item.color + '15', color: item.color, fontFamily: 'JetBrains Mono', fontWeight: 600, flexShrink: 0 }}>
                      {ents.length}
                    </div>
                  )}
                  <div style={{ color: '#2d3748', flexShrink: 0 }}>
                    {item.id === 'trigger' ? <span style={{ fontSize:9, color:item.color }}>اولویت قطعی</span> : <Icon name="grip-vertical" size={15} />}
                  </div>
                </div>

                {ents.length > 0 && (
                  <div style={{ borderTop: '1px solid rgba(255,255,255,0.05)', padding: '8px 16px 10px', display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center' }}>
                    {item.id === 'campaign' && innerDirty && (
                      <button onClick={saveInnerOrder} disabled={innerSaving} style={{ padding: '3px 12px', borderRadius: 20, border: 'none', background: '#3B82F6', color: '#fff', fontSize: 11, fontWeight: 600, cursor: 'pointer', marginRight: 'auto', flexShrink: 0 }}>
                        {innerSaving ? '...' : 'ذخیره ترتیب'}
                      </button>
                    )}
                    {ents.map((e, ei) => {
                      const isCamp = item.id === 'campaign';
                      const isReg  = isCamp && e.priority !== null;
                      return (
                        <div key={e.id} draggable={isCamp}
                          onDragStart={isCamp ? ev => onInnerDragStart(ev, ei) : undefined}
                          onDragEnter={isCamp ? ev => onInnerDragEnter(ev, ei) : undefined}
                          onDragEnd={isCamp ? onInnerDragEnd : undefined}
                          onDragOver={isCamp ? ev => ev.preventDefault() : undefined}
                          style={{
                            fontSize: 11, padding: '3px 10px', borderRadius: 20,
                            background: isReg ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.03)',
                            border: `1px solid ${isReg ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.06)'}`,
                            color: isReg ? '#cbd5e1' : '#475569',
                            display: 'flex', alignItems: 'center', gap: 5,
                            cursor: isCamp ? 'grab' : 'default', userSelect: 'none',
                          }}>
                          {isCamp && <span style={{ fontSize: 9, fontFamily: 'JetBrains Mono', fontWeight: 700, color: isReg ? item.color : '#334155', minWidth: 16 }}>{isReg ? `#${e.priority}` : '—'}</span>}
                          {e.name}
                          {e.badge && <span style={{ fontSize: 9, padding: '1px 5px', borderRadius: 10, background: item.color + '20', color: item.color, fontWeight: 700 }}>{e.badge}</span>}
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            );
          })}
        </div>

        {dirty && (
          <div style={{ display: 'flex', gap: 10, marginTop: 16, justifyContent: 'flex-end' }}>
            <button onClick={() => { setItems(DEFAULTS); setDirty(true); }} style={{ padding: '8px 16px', borderRadius: 8, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.1)', background: 'transparent', color: '#64748b', fontSize: 13 }}>بازنشانی</button>
            <button onClick={save} disabled={saving} style={{ padding: '8px 20px', borderRadius: 8, border: 'none', background: saving ? '#1e3a5f' : '#3B82F6', color: '#fff', fontSize: 13, fontWeight: 600, cursor: saving ? 'default' : 'pointer' }}>
              {saving ? 'در حال ذخیره...' : 'ذخیره ترتیب'}
            </button>
          </div>
        )}
      </>)}

      {/* ─── تب نقشه سیستم ─── */}
      {tab === 'mindmap' && (
        <MindMap
          entities={entities}
          colors={colors}
          handlerLabels={{ trigger:'تریگر', followup:'فالوآپ', campaign:'کمپین', script:'اسکریپت', bot:'بات' }}
          brainScript={brainScript}
          items={items}
          onBrainClick={() => brainScript && setShowBrain(true)}
          onRefresh={loadData}
          onClose={() => window.SB_setPage?.('leadpool')}
        />
      )}

      {/* ─── تب سناریوها ─── */}
      {tab === 'scenarios' && (<>
        <div style={{ marginBottom: 16, fontSize: 12, color: '#475569', lineHeight: 1.7 }}>
          سناریوها بر اساس <span style={{ color: '#60A5FA', fontWeight: 600 }}>ترتیب فعلی اولویت</span> محاسبه می‌شن.
          ترتیب را از تب «ترتیب اجرا» در بخش اتوماسیون تغییر بده؛ این سناریوها خودکار به‌روز می‌شوند.
        </div>

        {/* legend */}
        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 16 }}>
          {items.map((item, i) => (
            <div key={item.id} style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, color: '#64748b' }}>
              <div style={{ width: 8, height: 8, borderRadius: 2, background: item.color }} />
              <span style={{ color: '#475569', fontFamily: 'JetBrains Mono', fontSize: 9 }}>{i + 1}</span>
              {item.label}
            </div>
          ))}
        </div>

        {SCENARIOS.map(sc => (
          <ScenarioTimeline key={sc.id} scenario={sc} priorityOrder={priorityOrder} colors={colors} />
        ))}
      </>)}

    </div>
  );
};

window.OrchestraBoard = OrchestraBoard;

// Automation routing studio: fixed runtime order, dynamic database-backed catalog.
const RoutingPriorities = ({ embedded = false } = {}) => {
  const { useToast } = window.SB_UI;
  const toast = useToast();
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [triggers, setTriggers] = useState([]);
  const [scripts, setScripts] = useState([]);
  const [campaigns, setCampaigns] = useState([]);
  const [baseline, setBaseline] = useState({ triggers:{}, scripts:{}, campaigns:{} });
  const [tab, setTab] = useState('overview');
  const [selected, setSelected] = useState({ triggers:null, scripts:null, campaigns:null });
  const [testText, setTestText] = useState('');
  const [testPlatform, setTestPlatform] = useState('all');
  const [testing, setTesting] = useState(false);
  const [testResult, setTestResult] = useState(null);
  const [aiFillingId, setAiFillingId] = useState(null);

  const list = value => {
    if (Array.isArray(value)) return value;
    try { const parsed = JSON.parse(value || '[]'); return Array.isArray(parsed) ? parsed : []; }
    catch { return String(value || '').split(/[,،\n]/).map(x => x.trim()).filter(Boolean); }
  };
  const compact = value => String(value || '').trim();
  const snapshot = (triggerRows, scriptRows, campaignRows) => ({
    triggers:Object.fromEntries(triggerRows.map(x => [x.id, { route_priority:x.route_priority, mission:compact(x.mission) }])),
    scripts:Object.fromEntries(scriptRows.map(x => [x.id, { route_priority:x.route_priority, route_mission:compact(x.route_mission), route_keywords:list(x.route_keywords), route_negative_keywords:list(x.route_negative_keywords), route_examples:list(x.route_examples) }])),
    campaigns:Object.fromEntries(campaignRows.map(x => [x.id, { owner_priority:x.owner_priority, goal:compact(x.goal) }])),
  });

  const load = () => {
    setLoading(true);
    Promise.all([
      window.api('/scripts/campaigns/triggers').catch(() => ({ success:false })),
      window.api('/scripts').catch(() => ({ success:false })),
      window.api('/campaigns/campaigns').catch(() => []),
      window.api('/orchestrator/rules').catch(() => ({ success:false })),
    ]).then(([triggerRes, scriptRes, campaignRes, ruleRes]) => {
      const triggerRows = triggerRes?.data?.triggers || triggerRes?.triggers || [];
      const scriptRows = scriptRes?.data?.scripts || scriptRes?.scripts || scriptRes?.data || [];
      const ruleRows = ruleRes?.success && Array.isArray(ruleRes.data) ? ruleRes.data : [];
      const ruleByCampaign = Object.fromEntries(ruleRows.filter(r => r.type === 'campaign' && r.ref_id).map(r => [r.ref_id, r]));
      const nextTriggers = (Array.isArray(triggerRows) ? triggerRows : []).filter(x => x.is_active).map(x => ({ ...x, route_priority:Number(x.route_priority || 0) }));
      const nextScripts = (Array.isArray(scriptRows) ? scriptRows : []).filter(x => x.is_active && x.id !== 'brain_main').map(x => ({ ...x, route_priority:Number(x.route_priority || 0) }));
      const nextCampaigns = (Array.isArray(campaignRes) ? campaignRes : []).filter(x => x.is_active).map(x => ({
        ...x, rule_id:ruleByCampaign[x.id]?.id || null, owner_priority:Number(ruleByCampaign[x.id]?.priority ?? 20),
      }));
      setTriggers(nextTriggers.map(x => ({ ...x, mission:compact(x.mission) })));
      setScripts(nextScripts.map(x => ({ ...x, route_mission:compact(x.route_mission), route_keywords:list(x.route_keywords), route_negative_keywords:list(x.route_negative_keywords), route_examples:list(x.route_examples) })));
      setCampaigns(nextCampaigns.map(x => ({ ...x, goal:compact(x.goal) })));
      setBaseline(snapshot(nextTriggers, nextScripts, nextCampaigns));
    }).finally(() => setLoading(false));
  };
  useEffect(() => { load(); }, []);

  const fieldChanges = (kind, row, base, fields) => fields.filter(f => JSON.stringify(row[f.key]) !== JSON.stringify(base?.[f.key])).map(f => ({ kind, id:row.id, name:row.trigger_name || row.name || row.keyword, key:f.key, label:f.label, from:base?.[f.key], to:row[f.key] }));
  const changes = [
    ...triggers.flatMap(x => fieldChanges('تریگر',x,baseline.triggers[x.id],[{key:'mission',label:'مأموریت'},{key:'route_priority',label:'اولویت'}])),
    ...scripts.flatMap(x => fieldChanges('اسکریپت',x,baseline.scripts[x.id],[{key:'route_mission',label:'مأموریت'},{key:'route_keywords',label:'عبارت‌های مثبت'},{key:'route_negative_keywords',label:'عبارت‌های منفی'},{key:'route_examples',label:'مثال‌های انتخاب'},{key:'route_priority',label:'اولویت'}])),
    ...campaigns.flatMap(x => fieldChanges('کمپین',x,baseline.campaigns[x.id],[{key:'goal',label:'مأموریت'},{key:'owner_priority',label:'اولویت مالکیت'}])),
  ];
  const dirty = changes.length > 0;

  const save = async () => {
    if (!dirty) return;
    const readable = value => Array.isArray(value) ? (value.join('، ') || '—') : (value === '' || value == null ? '—' : value);
    const summary = changes.map(x => `${x.kind} «${x.name}» — ${x.label}: ${readable(x.from)} ← ${readable(x.to)}`).join('\n');
    if (!confirm(`این تغییرها روی تشخیص مسیر یا مالکیت لید اثر می‌گذارند:\n\n${summary}\n\nاعمال شوند؟`)) return;
    setSaving(true);
    try {
      const changed = (kind,id) => changes.some(x => x.kind === kind && x.id === id);
      const jobs = [];
      triggers.filter(t => changed('تریگر',t.id)).forEach(t => jobs.push(window.api(`/scripts/campaigns/triggers/${t.id}`, { method:'PUT', body:{ mission:compact(t.mission), route_priority:Number(t.route_priority || 0) } })));
      scripts.filter(s => changed('اسکریپت',s.id)).forEach(s => jobs.push(window.api(`/scripts/${s.id}`, { method:'PUT', body:{ route_mission:compact(s.route_mission), route_keywords:list(s.route_keywords), route_negative_keywords:list(s.route_negative_keywords), route_examples:list(s.route_examples), route_priority:Number(s.route_priority || 0) } })));
      campaigns.filter(c => changes.some(x => x.kind === 'کمپین' && x.id === c.id && x.key === 'goal')).forEach(c => jobs.push(window.api(`/campaigns/campaigns/${c.id}`, { method:'PUT', body:{ goal:compact(c.goal) } })));
      campaigns.filter(c => changes.some(x => x.kind === 'کمپین' && x.id === c.id && x.key === 'owner_priority')).forEach(c => jobs.push(c.rule_id
        ? window.api(`/orchestrator/rules/${c.rule_id}`, { method:'PUT', body:{ priority:Number(c.owner_priority || 0) } })
        : window.api('/orchestrator/rules', { method:'POST', body:{ name:c.name, type:'campaign', ref_id:c.id, priority:Number(c.owner_priority || 0) } })));
      await Promise.all(jobs);
      toast('تغییرات مسیرها و مأموریت‌ها اعمال شد', 'success');
      load();
    } catch (e) { toast(e.message || 'خطا در اعمال تغییرات', 'error'); }
    finally { setSaving(false); }
  };

  const updateRow = (kind,id,patch) => ({ triggers:setTriggers, scripts:setScripts, campaigns:setCampaigns }[kind])(xs => xs.map(x => x.id === id ? { ...x, ...patch } : x));
  const fillScriptWithAi = async row => {
    setAiFillingId(row.id);
    try {
      const response = await window.api(`/scripts/${row.id}/route-metadata/ai`, { method:'POST', body:{ apply:false } });
      const payload = response?.data || response;
      const metadata = payload?.metadata;
      if (!metadata) throw new Error('پاسخ AI متادیتای معتبری نداشت');
      updateRow('scripts', row.id, {
        route_mission:metadata.route_mission || '',
        route_keywords:list(metadata.route_keywords),
        route_negative_keywords:list(metadata.route_negative_keywords),
        route_examples:list(metadata.route_examples),
      });
      toast('پیشنهاد Mistral در فرم قرار گرفت؛ پیش‌نمایش را بررسی و سپس اعمال کنید', 'success');
    } catch (e) { toast(e.message || 'تکمیل اطلاعات با AI ناموفق بود', 'error'); }
    finally { setAiFillingId(null); }
  };
  const badge = (text,color) => <span style={{ fontSize:9.5, padding:'3px 8px', borderRadius:20, color, background:`${color}16`, border:`1px solid ${color}35`, whiteSpace:'nowrap' }}>{text}</span>;
  const missionOf = (kind,row) => kind === 'triggers' ? (row.mission || `تشخیص فوری «${row.keyword}» و هدایت به مسیر مشخص`) : kind === 'scripts' ? (row.route_mission || `انتخاب «${row.name}» برای پیام‌های مرتبط`) : (row.goal || 'مأموریت هنوز تعریف نشده است');
  const kindMeta = {
    triggers:{ title:'تریگرها', color:'#F87171', type:'قانون‌محور', rule:'تطبیق مستقیم کلیدواژه؛ قبل از تمام مسیرها بررسی می‌شود.' },
    scripts:{ title:'اسکریپت‌ها', color:'#A78BFA', type:'داینامیک', rule:'براساس عبارت‌های مثبت، منفی، مثال‌ها و اولویت امتیاز می‌گیرد.' },
    campaigns:{ title:'کمپین‌ها', color:'#60A5FA', type:'رویدادمحور', rule:'با زمان، سگمنت یا رویداد شروع می‌شود؛ از متن پیام انتخاب نمی‌شود.' },
  };

  const inputStyle = { width:'100%', boxSizing:'border-box', padding:'9px 10px', borderRadius:9, border:'1px solid var(--border-soft)', background:'var(--bg)', color:'var(--text)', fontFamily:'inherit', fontSize:11.5, lineHeight:1.7 };
  const Field = ({ label, hint, children }) => <label style={{ display:'flex', flexDirection:'column', gap:5 }}><span style={{ fontSize:10.5, fontWeight:700, color:'var(--text-2)' }}>{label}</span>{children}{hint && <span style={{ fontSize:9.5, color:'var(--text-3)' }}>{hint}</span>}</label>;

  const Detail = ({ kind, row }) => {
    if (!row) return <div style={{ padding:30, textAlign:'center', color:'var(--text-3)', fontSize:11 }}>موردی برای نمایش وجود ندارد.</div>;
    const meta = kindMeta[kind];
    return <div style={{ padding:16, display:'flex', flexDirection:'column', gap:14 }}>
      <div style={{ display:'flex', alignItems:'flex-start', gap:10 }}><div style={{ flex:1 }}><div style={{ fontSize:14, fontWeight:800 }}>{row.trigger_name || row.name || row.keyword}</div><div style={{ fontSize:10, color:'var(--text-3)', marginTop:3 }}>شناسه: {row.id}</div></div>{badge(meta.type,meta.color)}{badge('فعال','#34D399')}</div>
      <Field label="مأموریت" hint="به زبان روشن بنویسید این بخش چه مسئولیتی دارد و خروجی مطلوبش چیست.">
        <textarea rows="3" value={kind === 'triggers' ? row.mission : kind === 'scripts' ? row.route_mission : row.goal} onChange={e => updateRow(kind,row.id,kind === 'triggers' ? {mission:e.target.value} : kind === 'scripts' ? {route_mission:e.target.value} : {goal:e.target.value})} style={{ ...inputStyle, resize:'vertical' }} placeholder="مأموریت را تعریف کنید…" />
      </Field>
      {kind === 'triggers' && <>
        <Field label="شرط انتخاب"><input value={row.keyword || ''} readOnly style={{ ...inputStyle, opacity:.78 }} /></Field>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}><Field label="اسکریپت مقصد"><input value={row.script_name || row.script_id || 'دوره رایگان'} readOnly style={{ ...inputStyle, opacity:.78 }} /></Field><Field label="اولویت بررسی" hint="عدد بیشتر زودتر بررسی می‌شود"><input type="number" value={row.route_priority} onChange={e => updateRow(kind,row.id,{route_priority:Number(e.target.value || 0)})} style={inputStyle} /></Field></div>
      </>}
      {kind === 'scripts' && <>
        <button onClick={()=>fillScriptWithAi(row)} disabled={aiFillingId===row.id} style={{ padding:'10px 12px', borderRadius:10, border:'1px solid rgba(167,139,250,.35)', background:'rgba(167,139,250,.1)', color:'#C4B5FD', fontFamily:'inherit', fontWeight:800, cursor:aiFillingId===row.id?'default':'pointer', opacity:aiFillingId===row.id ? 0.65 : 1 }}>
          {aiFillingId===row.id ? 'در حال تحلیل اسکریپت با Mistral…' : '✨ تکمیل مأموریت و نیت‌ها با AI'}
        </button>
        <div style={{ padding:'8px 10px', borderRadius:9, background:'rgba(52,211,153,.07)', border:'1px solid rgba(52,211,153,.2)', fontSize:9.5, color:'#6EE7B7', lineHeight:1.8 }}>Mistral متن اسکریپت و مسیرهای فعال دیگر را می‌بیند. خروجی هم‌پوشان فقط رد می‌شود و تا تأیید شما ذخیره نخواهد شد.</div>
        <Field label="عبارت‌های مثبت" hint="هر عبارت در یک خط؛ وجودشان امتیاز انتخاب را بالا می‌برد"><textarea rows="3" value={list(row.route_keywords).join('\n')} onChange={e => updateRow(kind,row.id,{route_keywords:e.target.value.split('\n').map(x=>x.trim()).filter(Boolean)})} style={{ ...inputStyle, resize:'vertical' }} /></Field>
        <Field label="عبارت‌های منفی" hint="اگر یکی از این موارد دیده شود، اسکریپت حذف می‌شود"><textarea rows="2" value={list(row.route_negative_keywords).join('\n')} onChange={e => updateRow(kind,row.id,{route_negative_keywords:e.target.value.split('\n').map(x=>x.trim()).filter(Boolean)})} style={{ ...inputStyle, resize:'vertical' }} /></Field>
        <Field label="مثال‌های انتخاب" hint="نمونه پیام‌هایی که باید به این اسکریپت برسند"><textarea rows="3" value={list(row.route_examples).join('\n')} onChange={e => updateRow(kind,row.id,{route_examples:e.target.value.split('\n').map(x=>x.trim()).filter(Boolean)})} style={{ ...inputStyle, resize:'vertical' }} /></Field>
        <Field label="اولویت پایه" hint="عدد بیشتر در امتیاز نهایی اثر مثبت دارد"><input type="number" value={row.route_priority} onChange={e => updateRow(kind,row.id,{route_priority:Number(e.target.value || 0)})} style={inputStyle} /></Field>
      </>}
      {kind === 'campaigns' && <>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}><Field label="نوع شروع"><input value={row.always_on ? 'همیشه فعال / رویدادمحور' : (row.type || 'زمان‌بندی‌شده')} readOnly style={{ ...inputStyle, opacity:.78 }} /></Field><Field label="اولویت مالکیت" hint="عدد کمتر برنده است"><input type="number" value={row.owner_priority} onChange={e => updateRow(kind,row.id,{owner_priority:Number(e.target.value || 0)})} style={inputStyle} /></Field></div>
        <div style={{ padding:10, borderRadius:9, background:'rgba(96,165,250,.07)', border:'1px solid rgba(96,165,250,.2)', fontSize:10.5, color:'#93C5FD', lineHeight:1.8 }}>این کمپین از روی متن پیام انتخاب نمی‌شود؛ شروع آن تابع رویداد، سگمنت یا بازه زمانی تعریف‌شده در خود کمپین است.</div>
      </>}
    </div>;
  };

  const Catalog = ({ kind, rows }) => {
    const chosenId = selected[kind] || rows[0]?.id;
    const chosen = rows.find(x => x.id === chosenId) || rows[0];
    const meta = kindMeta[kind];
    return <div style={{ display:'grid', gridTemplateColumns:'minmax(230px,34%) 1fr', border:'1px solid var(--border-soft)', borderRadius:14, overflow:'hidden', background:'var(--surface)', minHeight:430 }}>
      <div style={{ borderLeft:'1px solid var(--border-soft)', padding:10, display:'flex', flexDirection:'column', gap:7 }}>
        <div style={{ padding:'5px 4px 10px' }}><div style={{ fontSize:13, fontWeight:800, color:meta.color }}>{meta.title} ({rows.length})</div><div style={{ fontSize:9.5, lineHeight:1.7, color:'var(--text-3)', marginTop:4 }}>{meta.rule}</div></div>
        {rows.length === 0 ? <div style={{ padding:20, textAlign:'center', color:'var(--text-3)', fontSize:11 }}>مورد فعالی وجود ندارد</div> : rows.map(row => <button key={row.id} onClick={()=>setSelected(s=>({...s,[kind]:row.id}))} style={{ textAlign:'right', padding:'10px 11px', borderRadius:10, cursor:'pointer', fontFamily:'inherit', border:`1px solid ${chosen?.id===row.id?`${meta.color}55`:'var(--border-soft)'}`, background:chosen?.id===row.id?`${meta.color}12`:'var(--bg)', color:'var(--text)' }}><div style={{ fontSize:11.5, fontWeight:700 }}>{row.trigger_name || row.name || row.keyword}</div><div style={{ fontSize:9.5, color:'var(--text-3)', marginTop:4, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{missionOf(kind,row)}</div></button>)}
      </div><Detail kind={kind} row={chosen}/>
    </div>;
  };

  const Overview = () => <>
    <div style={{ padding:'15px 17px', borderRadius:14, border:'1px solid rgba(245,158,11,.3)', background:'linear-gradient(135deg,rgba(245,158,11,.08),rgba(99,102,241,.05))' }}>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}><span style={{ fontSize:18 }}>🔒</span><div><div style={{ color:'#FBBF24', fontWeight:800, fontSize:13 }}>مسیر ثابت پاسخ زنده</div><div style={{ marginTop:4, color:'var(--text-3)', fontSize:9.5 }}>این ترتیب بخشی از موتور است و از پنل قابل جابه‌جایی نیست.</div></div></div>
      <div style={{ display:'flex', alignItems:'center', flexWrap:'wrap', gap:7, marginTop:13 }}>{['پیام ورودی','تریگر فوری','اسکریپت مناسب','مسیر آموخته‌شده','مغز مرکزی'].map((x,i)=><React.Fragment key={x}><span style={{ padding:'6px 10px', borderRadius:8, background:'var(--surface)', border:'1px solid var(--border-soft)', color:'var(--text-2)', fontSize:10.5 }}>{x}</span>{i<4&&<span style={{ color:'#FBBF24' }}>←</span>}</React.Fragment>)}</div>
    </div>
    <div style={{ display:'grid', gridTemplateColumns:'repeat(3,minmax(0,1fr))', gap:10 }}>{[['triggers',triggers,'⚡'],['scripts',scripts,'📋'],['campaigns',campaigns,'🎯']].map(([kind,rows,icon])=>{const meta=kindMeta[kind];return <button key={kind} onClick={()=>setTab(kind)} style={{ padding:14, borderRadius:13, border:'1px solid var(--border-soft)', background:'var(--surface)', textAlign:'right', cursor:'pointer', fontFamily:'inherit', color:'var(--text)' }}><div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}><span style={{ fontSize:20 }}>{icon}</span>{badge(meta.type,meta.color)}</div><div style={{ fontSize:13, fontWeight:800, marginTop:10, color:meta.color }}>{meta.title} · {rows.length}</div><div style={{ fontSize:10, color:'var(--text-3)', lineHeight:1.7, marginTop:6 }}>{meta.rule}</div><div style={{ fontSize:10, color:'var(--text-2)', marginTop:9 }}>{rows.filter(x=>compact(kind==='triggers'?x.mission:kind==='scripts'?x.route_mission:x.goal)).length} مأموریت تعریف‌شده</div></button>})}</div>
    <div style={{ padding:12, borderRadius:11, border:'1px dashed rgba(99,102,241,.3)', color:'var(--text-3)', fontSize:10.5, lineHeight:1.8 }}>کاتالوگ داینامیک است: با ایجاد یا فعال‌کردن تریگر، اسکریپت یا کمپین، این صفحه از دیتابیس به‌روزرسانی می‌شود. داینامیک بودن به معنی تغییر خودکار ترتیب ثابت موتور نیست.</div>
  </>;

  const runTest = async () => {
    if (!testText.trim()) return;
    setTesting(true); setTestResult(null);
    try { const res = await window.api('/orchestrator/brain-preview',{ method:'POST', body:{ text:testText.trim(), platform:testPlatform } }); setTestResult(res?.data || res); }
    catch (e) { toast(e.message || 'خطا در تست مسیر','error'); }
    finally { setTesting(false); }
  };
  const TestLab = () => <div style={{ display:'grid', gridTemplateColumns:'minmax(260px,40%) 1fr', gap:12 }}>
    <div style={{ padding:15, borderRadius:13, border:'1px solid var(--border-soft)', background:'var(--surface)', display:'flex', flexDirection:'column', gap:11 }}><div><div style={{ fontSize:13, fontWeight:800 }}>تست امن مسیر</div><div style={{ fontSize:10, color:'#34D399', marginTop:4 }}>بدون ارسال پیام و بدون API پولی</div></div><textarea rows="6" value={testText} onChange={e=>setTestText(e.target.value)} placeholder="یک پیام نمونه وارد کنید…" style={{ ...inputStyle, resize:'vertical' }}/><select value={testPlatform} onChange={e=>setTestPlatform(e.target.value)} style={inputStyle}><option value="all">همه کانال‌ها</option><option value="telegram">تلگرام</option><option value="bale">بله</option><option value="web">وب</option></select><button disabled={testing||!testText.trim()} onClick={runTest} style={{ padding:10, borderRadius:9, border:'none', background:'#6366F1', color:'#fff', opacity:(testing||!testText.trim()) ? 0.55 : 1, cursor:'pointer', fontFamily:'inherit', fontWeight:800 }}>{testing?'در حال تشخیص…':'تشخیص مسیر'}</button></div>
    <div style={{ padding:15, borderRadius:13, border:'1px solid var(--border-soft)', background:'var(--surface)' }}>{!testResult?<div style={{ height:'100%', minHeight:220, display:'grid', placeItems:'center', textAlign:'center', color:'var(--text-3)', fontSize:11 }}>نتیجه تشخیص و دلیل انتخاب اینجا نمایش داده می‌شود.</div>:<><div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}><div><div style={{ fontSize:10,color:'var(--text-3)' }}>مسیر انتخاب‌شده</div><div style={{ fontSize:15,fontWeight:800,color:'#A5B4FC',marginTop:4 }}>{testResult.targetName}</div></div>{badge(testResult.targetType==='trigger'?'تریگر':testResult.targetType==='script'?'اسکریپت':'مغز مرکزی',testResult.targetType==='trigger'?'#F87171':testResult.targetType==='script'?'#A78BFA':'#FBBF24')}</div><div style={{ marginTop:13,padding:10,borderRadius:9,background:'var(--bg)',fontSize:10.5,lineHeight:1.8,color:'var(--text-2)' }}><b>دلیل:</b> {testResult.reason}</div>{testResult.candidates?.length>0&&<div style={{ marginTop:13 }}><div style={{ fontSize:10,fontWeight:700,color:'var(--text-3)',marginBottom:6 }}>نامزدهای بررسی‌شده</div>{testResult.candidates.map(x=><div key={x.id} style={{ display:'flex',gap:8,alignItems:'center',padding:'7px 0',borderBottom:'1px solid var(--border-soft)',fontSize:10.5 }}><span style={{ flex:1 }}>{x.name}</span><span style={{ color:'#A5B4FC' }}>امتیاز {x.score}</span><span style={{ color:'var(--text-3)' }}>{x.hits?.join('، ')||'بدون تطبیق'}</span></div>)}</div>}<div style={{ marginTop:12,fontSize:9.5,color:'#34D399' }}>✓ فقط پیش‌نمایش؛ هیچ وضعیت یا پیامی تغییر نکرد.</div></>}</div>
  </div>;

  if (loading) return <div style={{ padding:embedded?16:40, textAlign:'center', color:'var(--text-3)', fontSize:11 }}>در حال بارگذاری مسیرها و مأموریت‌ها...</div>;
  const tabs = [['overview','نمای کلی'],['triggers','تریگرها'],['scripts','اسکریپت‌ها'],['campaigns','کمپین‌ها'],['test','تست مسیر']];
  const readable = value => Array.isArray(value) ? (value.join('، ') || '—') : (value === '' || value == null ? '—' : value);
  return <div style={{ maxWidth:1080, margin:embedded?'0':'20px auto', padding:embedded?'0':'0 16px 30px', display:'flex', flexDirection:'column', gap:12 }}>
    <div style={{ display:'flex', gap:4, padding:4, borderRadius:11, background:'var(--surface)', border:'1px solid var(--border-soft)', alignSelf:'flex-start' }}>{tabs.map(([id,label])=><button key={id} onClick={()=>setTab(id)} style={{ padding:'7px 13px',borderRadius:8,border:'none',background:tab===id?'#6366F1':'transparent',color:tab===id?'#fff':'var(--text-3)',fontFamily:'inherit',fontSize:10.5,cursor:'pointer' }}>{label}</button>)}</div>
    {tab==='overview'&&<Overview/>}
    {tab==='triggers'&&<Catalog kind="triggers" rows={triggers}/>}
    {tab==='scripts'&&<Catalog kind="scripts" rows={scripts}/>}
    {tab==='campaigns'&&<Catalog kind="campaigns" rows={campaigns}/>}
    {tab==='test'&&<TestLab/>}
    {dirty && <div style={{ padding:'12px 14px', borderRadius:10, border:'1px solid rgba(99,102,241,.3)', background:'rgba(99,102,241,.07)' }}>
      <div style={{ fontSize:11, fontWeight:700, color:'#A5B4FC', marginBottom:7 }}>پیش‌نمایش تغییرات ({changes.length})</div>
      {changes.map(x => <div key={`${x.kind}:${x.id}`} style={{ fontSize:11, color:'var(--text-2)', lineHeight:1.9 }}>
        {x.kind} «{x.name}» — {x.label}: <span style={{ color:'var(--text-3)' }}>{readable(x.from)}</span> ← <span style={{ color:'#C7D2FE', fontWeight:700 }}>{readable(x.to)}</span>
      </div>)}
    </div>}
    <div style={{ display:'flex', justifyContent:'flex-end' }}>
      <button onClick={save} disabled={saving || !dirty} style={{ padding:'8px 20px', borderRadius:8, border:'none', background:dirty?'#6366F1':'rgba(255,255,255,.08)', color:dirty?'#fff':'var(--text-3)', cursor:(saving||!dirty)?'default':'pointer', fontFamily:'inherit', fontSize:12, fontWeight:700 }}>
        {saving ? 'در حال اعمال...' : dirty ? `اعمال ${changes.length} تغییر` : 'تغییری برای اعمال نیست'}
      </button>
    </div>
  </div>;
};
window.RoutingPriorities = RoutingPriorities;
