// Fluentra Console — EditorPage + InstanceDetailsPage
// Loads AFTER app-pages.jsx, editor-utils.jsx, editor-shapes.jsx, editor-panels.jsx, editor-RuleBuilder.jsx

const _apiFetch = (...a) => window.__FETCH__(...a);
const _useAuth  = ()     => window.useAuth();

/* ══════════════════════════════════════════════════════════════════
   PALETTE (grouped by category)
   ═════════════════════════════════════════════════════════════ */
const PALETTE_GROUPS = [
  {label:'Triggers', items:[
    {kind:'start',  label:'Start'},
    {kind:'end',    label:'End'},
    {kind:'webhook',label:'Webhook'},
  ]},
  {label:'Logic', items:[
    {kind:'cond', label:'Condition'},
    {kind:'fork', label:'Fork'},
    {kind:'join', label:'Join'},
  ]},
  {label:'Flow Control', items:[
    {kind:'varup', label:'Var Update'},
    {kind:'sleep', label:'Sleep'},
    {kind:'wait',  label:'Wait Until'},
    {kind:'sub',   label:'Sub-flow'},
  ]},
  {label:'Actions', items:[
    {kind:'action',label:'Action'},
    {kind:'api',   label:'API Request'},
  ]},
];

const TERMINAL_STATUSES = new Set(['completed','failed','terminated','ttl_expired','expired']);

/* ══════════════════════════════════════════════════════════════════
   EDITOR PAGE
   ═════════════════════════════════════════════════════════════ */
function EditorPageNew() {
  const { tenantId } = _useAuth();
  const [diagramList, setDiagramList]   = React.useState([]);
  const [templateList, setTemplateList] = React.useState([]);
  const [diagramName, setDiagramName]   = React.useState('');
  const [nodes,       setNodes]         = React.useState([]);
  const [edges,       setEdges]         = React.useState([]);
  const [selected,    setSelected]      = React.useState(null);
  const [selectedEdge,setSelectedEdge]  = React.useState(null);
  const [dirty,       setDirty]         = React.useState(false);
  const [loading,     setLoading]       = React.useState(false);
  const [saving,      setSaving]        = React.useState(false);
  const [err,         setErr]           = React.useState('');
  const [debugInfo,   setDebugInfo]     = React.useState('');
  const [newName,     setNewName]       = React.useState('');
  const [meta, setMeta] = React.useState({instantiation_key_expression:'',acceptance_criteria:'',ttl:86400});
  const [rightWidth, setRightWidth] = React.useState(380);
  const [isTemplate, setIsTemplate] = React.useState(false);
  const [draftAlert, setDraftAlert] = React.useState(false);
  const [palFilter,  setPalFilter]  = React.useState('');
  const palRef = React.useRef(null);
  const historyRef = React.useRef([]);
  const skipHistory = React.useRef(false);
  const autosaveTimer = React.useRef(null);

  const draftKey = tenantId ? `fluentra_draft_${tenantId}` : null;

  const pushHistory = (ns, es) => {
    if (skipHistory.current) return;
    const last = historyRef.current[historyRef.current.length - 1];
    if (last && JSON.stringify(last.nodes) === JSON.stringify(ns) && JSON.stringify(last.edges) === JSON.stringify(es)) return;
    historyRef.current.push({ nodes: JSON.parse(JSON.stringify(ns)), edges: JSON.parse(JSON.stringify(es)) });
    if (historyRef.current.length > 50) historyRef.current.shift();
  };
  const resetHistory = (ns, es) => {
    historyRef.current = [{ nodes: JSON.parse(JSON.stringify(ns)), edges: JSON.parse(JSON.stringify(es)) }];
  };

  /* ── Mount: palette scroll to top ── */
  React.useEffect(() => {
    if (palRef.current) palRef.current.scrollTop = 0;
  }, []);

  /* ── Mount: check for draft ── */
  React.useEffect(() => {
    if (!draftKey) return;
    const raw = localStorage.getItem(draftKey);
    if (raw) {
      try {
        const d = JSON.parse(raw);
        if (d && d.nodes && d.nodes.length) setDraftAlert(true);
      } catch(e) {}
    }
  }, [draftKey]);

  /* ── Autosave draft ── */
  React.useEffect(() => {
    if (!draftKey || !dirty) return;
    if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
    autosaveTimer.current = setTimeout(() => {
      localStorage.setItem(draftKey, JSON.stringify({
        nodes, edges, meta, diagramName, timestamp: Date.now()
      }));
    }, 1500);
    return () => { if (autosaveTimer.current) clearTimeout(autosaveTimer.current); };
  }, [nodes, edges, meta, diagramName, dirty, draftKey]);

  /* ── beforeunload warning ── */
  React.useEffect(() => {
    const fn = e => {
      if (dirty) {
        e.preventDefault();
        e.returnValue = '';
      }
    };
    window.addEventListener('beforeunload', fn);
    return () => window.removeEventListener('beforeunload', fn);
  }, [dirty]);

  /* ── Load diagram names + templates ── */
  React.useEffect(() => {
    if (!tenantId) return;
    _apiFetch(`/api/v1/tenants/${tenantId}/diagrams`)
      .then(r => r && r.ok && r.json())
      .then(d => {
        if (!d) return;
        const list = Array.isArray(d) ? d : (d.diagrams||[]);
        const names = list.map(x => typeof x==='string' ? x : (x.name||x.id||x.diagram_name||x.diagram_id||x.title||x.label||JSON.stringify(x)));
        setDiagramList(names);
        const target = window.__EDITOR_LOAD_NAME__;
        if (target && names.includes(target)) {
          loadDiagram(target);
          window.__EDITOR_LOAD_NAME__ = null;
        } else if (names.length) {
          loadDiagram(names[0]);
        }
      })
      .catch(() => {});

    // (Hard-coded example templates removed — editor shows only tenant diagrams)
  }, [tenantId]);

  const loadDiagram = async name => {
    if (!tenantId || !name) return;
    setLoading(true); setErr(''); setDebugInfo('');
    try {
      const r = await _apiFetch(`/api/v1/tenants/${tenantId}/diagrams/${name}`);
      if (!r) { setLoading(false); return; }
      if (!r.ok) { setErr('Failed to load diagram'); setLoading(false); return; }
      const d = await r.json();
      const {nodes:cn, edges:ce} = window.FE.parseDiagram(d);
      setNodes(cn); setEdges(ce); setDiagramName(name);
      setMeta({
        instantiation_key_expression: d.instantiation_key_expression||'',
        acceptance_criteria: d.acceptance_criteria||'',
        ttl: d.ttl||86400,
      });
      setSelected(null); setSelectedEdge(null); setDirty(false); setIsTemplate(false);
      resetHistory(cn, ce);
      if (draftKey) localStorage.removeItem(draftKey);
    } catch(ex) {
      setErr('Error loading diagram');
    }
    setLoading(false);
  };

  const loadTemplate = (tpl) => {
    const {nodes:cn, edges:ce} = tpl.parsed;
    setNodes(cn); setEdges(ce);
    setDiagramName(tpl.name);
    setMeta({
      instantiation_key_expression: tpl.data.instantiation_key_expression||'',
      acceptance_criteria: tpl.data.acceptance_criteria||'',
      ttl: tpl.data.ttl||86400,
    });
    setSelected(null); setSelectedEdge(null); setDirty(true); setIsTemplate(true);
    resetHistory(cn, ce);
  };

  const restoreDraft = () => {
    if (!draftKey) return;
    const raw = localStorage.getItem(draftKey);
    if (!raw) return;
    try {
      const d = JSON.parse(raw);
      setNodes(d.nodes||[]);
      setEdges(d.edges||[]);
      setMeta(d.meta||{instantiation_key_expression:'',acceptance_criteria:'',ttl:86400});
      setDiagramName(d.diagramName||'');
      setDirty(true); setDraftAlert(false); setIsTemplate(false);
      resetHistory(d.nodes||[], d.edges||[]);
    } catch(e) {}
  };

  const clearDraft = () => {
    if (draftKey) localStorage.removeItem(draftKey);
    setDraftAlert(false);
  };

  const saveDiagram = async () => {
    if (!tenantId || !diagramName.trim()) { setErr('Diagram name required'); return; }

    // Pre-validate
    const startNode = nodes.find(n => n.kind === 'start');
    if (startNode) {
      const hasOut = edges.some(e => e.from === startNode.id);
      if (!hasOut) { setErr('Start node must have an outgoing edge'); return; }
    }
    const emptyNode = nodes.find(n => !n.name || !n.name.trim());
    if (emptyNode) { setErr(`Node '${emptyNode.id}' has an empty required name`); return; }

    setSaving(true); setErr(''); setDebugInfo('');
    const debug = { diagramName, nodeCount: nodes.length, edgeCount: edges.length, meta, isTemplate };
    try {
      let saveName = diagramName.trim();
      if (isTemplate) {
        // Auto-clone: strip (template) suffix if present, ensure unique
        saveName = saveName.replace(/\s*\(template\)\s*$/i, '');
        if (diagramList.includes(saveName)) {
          saveName = saveName + '_' + Date.now();
        }
        setDiagramName(saveName);
        setIsTemplate(false);
      }
      const body = window.FE.serializeDiagram(saveName, nodes, edges, meta);
      const r = await _apiFetch(`/api/v1/tenants/${tenantId}/diagrams`, {
        method:'POST',
        headers:{'Content-Type':'application/json'},
        body:JSON.stringify([body]),
      });
      if (r && r.ok) {
        setDirty(false);
        if (!diagramList.includes(saveName)) setDiagramList(l=>[...l,saveName]);
        if (draftKey) localStorage.removeItem(draftKey);
      } else {
        const e = r ? await r.json().catch(()=>null) : null;
        const d = e?.detail;
        let msg = Array.isArray(d) ? d.map(v => v.msg ?? JSON.stringify(v)).join('; ') : (d || 'Failed to save');
        if (msg.includes('Input should be a valid list')) msg = 'Save failed: the server expected a list of diagrams. Please try again.';
        setErr(msg);
        setDebugInfo(JSON.stringify({...debug, responseStatus:r?.status, responseBody:e}, null, 2));
      }
    } catch(ex) {
      setErr('Save error: ' + ex.message);
      setDebugInfo(JSON.stringify({...debug, exception: ex.message, stack: ex.stack}, null, 2));
    }
    setSaving(false);
  };

  const onNodeMove = (id, x, y) => {
    setNodes(ns => ns.map(n => n.id===id ? {...n,x,y} : n));
    setDirty(true);
  };
  const onNodeMoveEnd = (id, x, y) => {
    setNodes(ns => ns.map(n => n.id===id ? {...n,x,y} : n));
    setDirty(true);
  };

  const onDropNode = (kind, x, y) => {
    const id = kind+'_'+Date.now();
    const newNode = {id, x, y, kind, name:id, sub:window.FE.kindToType(kind), _raw:{type:window.FE.kindToType(kind),transitions:[]}};
    const newNodes = [...nodes, newNode];
    setNodes(newNodes);
    setSelected(id); setSelectedEdge(null); setDirty(true);
    pushHistory(newNodes, edges);
  };

  const deleteSelected = () => {
    if (!selected) return;
    const newNodes = nodes.filter(n=>n.id!==selected);
    const newEdges = edges.filter(e=>e.from!==selected && e.to!==selected);
    setNodes(newNodes);
    setEdges(newEdges);
    setSelected(null); setSelectedEdge(null); setDirty(true);
    pushHistory(newNodes, newEdges);
  };

  const onRemoveEdge = (index) => {
    const newEdges = edges.filter((_,i)=>i!==index);
    setEdges(newEdges);
    setSelectedEdge(null); setDirty(true);
    pushHistory(nodes, newEdges);
  };

  // Keyboard shortcuts: Delete / Ctrl+Z
  React.useEffect(() => {
    const h = e => {
      if ((e.key==='z' || e.key==='Z') && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
        e.preventDefault();
        if (historyRef.current.length <= 1) return;
        historyRef.current.pop();
        const prev = historyRef.current[historyRef.current.length - 1];
        skipHistory.current = true;
        setNodes(prev.nodes);
        setEdges(prev.edges);
        setDirty(true);
        setSelected(null);
        setSelectedEdge(null);
        setTimeout(() => { skipHistory.current = false; }, 0);
        return;
      }
      if (e.key==='Delete' || e.key==='Backspace') {
        const el = document.activeElement;
        if (el && (el.tagName==='INPUT' || el.tagName==='TEXTAREA' || el.isContentEditable)) return;
        if (selected) {
          deleteSelected();
        } else if (selectedEdge != null) {
          onRemoveEdge(selectedEdge);
        }
      }
      // Escape cancels connecting (handled inside EditorCanvas, but also clear edge selection)
      if (e.key === 'Escape') {
        setSelectedEdge(null);
      }
    };
    window.addEventListener('keydown',h);
    return () => window.removeEventListener('keydown',h);
  }, [selected, selectedEdge]);

  const importJSON = () => {
    const inp = document.createElement('input');
    inp.type='file'; inp.accept='.json';
    inp.onchange = async ev => {
      const file = ev.target.files[0];
      if (!file) return;
      try {
        const d = JSON.parse(await file.text());
        const {nodes:cn,edges:ce} = window.FE.parseDiagram(d);
        setNodes(cn); setEdges(ce);
        setDiagramName(d.name||'imported');
        setMeta({instantiation_key_expression:d.instantiation_key_expression||'',acceptance_criteria:d.acceptance_criteria||'',ttl:d.ttl||86400});
        setDirty(true); setSelected(null); setSelectedEdge(null); setIsTemplate(false);
        resetHistory(cn, ce);
      } catch(ex) { setErr('Invalid JSON: '+ex.message); }
    };
    inp.click();
  };

  const exportJSON = () => {
    const body = window.FE.serializeDiagram(diagramName||'diagram', nodes, edges, meta);
    const a = document.createElement('a');
    a.href = URL.createObjectURL(new Blob([JSON.stringify(body,null,2)],{type:'application/json'}));
    a.download = (diagramName||'diagram')+'.json';
    a.click();
  };

  const onAddEdge = (fromId, fromPort, toId, toPort) => {
    fromPort = fromPort || 'out';
    toPort   = toPort   || 'in';
    if (edges.find(e => e.from===fromId && (e.fromPort||'out')===fromPort && e.to===toId)) return;
    const color = fromPort==='true' ? '#34D399' : fromPort==='false' ? '#F87171' : undefined;
    const edge  = {from:fromId, fromPort, to:toId, toPort};
    if (color) edge.color = color;

    let newEdges = [...edges, edge];
    // Auto-remove initial start→end edge when the first real connection from start is made
    if (fromId === 'start') {
      const initialIdx = edges.findIndex(e => e.from === 'start' && e.to === 'end' && !e.fromPort && !e.color);
      if (initialIdx >= 0) {
        newEdges = newEdges.filter((_,i) => i !== initialIdx);
      }
    }

    setEdges(newEdges);
    setDirty(true);
    pushHistory(nodes, newEdges);
  };

  const createNew = () => {
    const nm = newName.trim() || 'new_diagram_'+Date.now();
    setNodes([
      {id:'start',x:60,y:120,kind:'start',name:'start',sub:'Start',_raw:{type:'Start',transitions:['end']}},
      {id:'end',  x:290,y:120,kind:'end',  name:'end',  sub:'End',  _raw:{type:'End',transitions:[]}},
    ]);
    setEdges([{from:'start',to:'end'}]);
    setDiagramName(nm);
    setMeta({instantiation_key_expression:'',acceptance_criteria:'',ttl:86400});
    setDirty(true); setSelected(null); setSelectedEdge(null); setNewName(''); setIsTemplate(false);
    resetHistory([
      {id:'start',x:60,y:120,kind:'start',name:'start',sub:'Start',_raw:{type:'Start',transitions:['end']}},
      {id:'end',  x:290,y:120,kind:'end',  name:'end',  sub:'End',  _raw:{type:'End',transitions:[]}},
    ], [{from:'start',to:'end'}]);
  };

  const updateSel = (field, val) => {
    const newNodes = nodes.map(n => n.id===selected ? {...n,[field]:val} : n);
    setNodes(newNodes);
    setDirty(true);
    pushHistory(newNodes, edges);
  };

  const selNode = nodes.find(n=>n.id===selected);
  const selEdges = edges.filter(e=>e.from===selected);

  // Helper: convert expr like event.get('id') → event.id for VariablePicker display
  function exprToPicker(expr) {
    const m = (expr||'').match(/^(event|variables)\.get\(['"]([^'"]+)['"]\)$/);
    return m ? m[1] + '.' + m[2] : expr;
  }

  return (
    <div className="fade-up" style={{display:'flex',flexDirection:'column',height:'calc(100vh - 56px)'}}>
      <window.PageHead
        title="Diagram editor"
        sub={
          (diagramList.length>0 || templateList.length>0) &&
          <select value={diagramName} onChange={e=>{
            const val = e.target.value;
            const tpl = templateList.find(t => t.name === val);
            if (tpl) loadTemplate(tpl);
            else loadDiagram(val);
          }}
            style={{background:'var(--surface)',border:'1px solid var(--line)',borderRadius:6,padding:'2px 8px',color:'var(--ink)',fontSize:12,fontFamily:'Geist Mono, monospace',cursor:'pointer'}}>
            {diagramList.map(d=><option key={d} value={d}>✏️ {d}</option>)}
            {templateList.map(t=><option key={t.name} value={t.name}>📄 {t.name}</option>)}
          </select>
        }
        actions={<>
          <button className="btn btn-ghost btn-sm" onClick={importJSON}><window.I name="download" size={13}/>Import</button>
          <button className="btn btn-ghost btn-sm" onClick={exportJSON}><window.I name="upload" size={13}/>Export</button>
          <button className="btn btn-ghost btn-sm" onClick={()=>{
            const positions = window.FE.autoLayout(nodes.map(n=>n.id), edges, {});
            const newNodes = nodes.map(n => {
              const pos = positions[n.id];
              return pos ? {...n, x: Math.round(pos.x/10)*10, y: Math.round(pos.y/10)*10} : n;
            });
            setNodes(newNodes);
            setDirty(true);
            pushHistory(newNodes, edges);
          }}>
            <window.I name="layout" size={13}/>Layout
          </button>
          <button className="btn btn-accent btn-sm" onClick={saveDiagram} disabled={saving||!dirty||!diagramName}>
            <window.I name="save" size={13}/>{saving?'Saving…':'Save'}
          </button>
          {dirty && <span style={{fontSize:11,color:'var(--warning)',fontFamily:'Geist Mono, monospace'}}>● unsaved</span>}
        </>}
      />

      {draftAlert && (
        <div style={{margin:'0 0 8px',padding:'8px 14px',background:'rgba(251,191,36,.08)',border:'1px solid rgba(251,191,36,.3)',borderRadius:8,fontSize:13,color:'#FBBF24',display:'flex',alignItems:'center',justifyContent:'space-between'}}>
          <span>Autosaved draft found. Restore it?</span>
          <div style={{display:'flex',gap:8}}>
            <button className="btn btn-sm" onClick={restoreDraft} style={{background:'rgba(251,191,36,.15)',border:'1px solid rgba(251,191,36,.4)',color:'#FBBF24'}}>Restore</button>
            <button className="btn btn-ghost btn-sm" onClick={clearDraft}>Discard</button>
          </div>
        </div>
      )}

      {err && (
        <div style={{margin:'0 0 8px',padding:'8px 14px',background:'rgba(248,113,113,.08)',border:'1px solid rgba(248,113,113,.3)',borderRadius:8,fontSize:13,color:'var(--danger)',display:'flex',alignItems:'center',justifyContent:'space-between',gap:12}}>
          <span>{err}</span>
          <div style={{display:'flex',gap:8,alignItems:'center',flexShrink:0}}>
            {debugInfo && (
              <button className="btn btn-ghost btn-sm" onClick={()=>navigator.clipboard.writeText(debugInfo)} style={{fontSize:11}}>Copy debug info</button>
            )}
          </div>
        </div>
      )}

      <div className="de" style={{flex:1,minHeight:0,gridTemplateColumns:`220px 1fr ${rightWidth}px`}}>
        {/* ── Palette ─────────────────────────── */}
        <div className="de-panel" style={{display:'flex',flexDirection:'column'}}>
          <div className="card-head" style={{padding:'10px 12px'}}>
            <h3>Nodes</h3><span className="sub">drag→drop</span>
          </div>
          <div className="node-pal" ref={palRef} style={{flex:1,overflowY:'auto',padding:14,display:'flex',flexDirection:'column',gap:8}}>
            <input
              value={palFilter}
              onChange={e=>setPalFilter(e.target.value)}
              placeholder="Search nodes…"
              style={{...window.FE.DARK_SEL, width:'100%', marginBottom:4}}
            />
            {PALETTE_GROUPS.map(g => {
              const items = g.items.filter(p => p.label.toLowerCase().includes(palFilter.toLowerCase()));
              if (!items.length) return null;
              return (
                <div key={g.label}>
                  <div style={{position:'sticky',top:0,background:'var(--surface)',zIndex:5,padding:'4px 0',fontSize:10,fontFamily:'Geist Mono,monospace',letterSpacing:'.06em',textTransform:'uppercase',color:'var(--ink-faint)'}}>
                    {g.label}
                  </div>
                  {items.map(p => {
                    const c = window.FE.NODE_COLORS[p.kind]||window.FE.NODE_COLORS.action;
                    return (
                      <div key={p.kind} className="pal-node" draggable
                        onDragStart={e => e.dataTransfer.setData('node-kind', p.kind)}>
                        <span style={{width:18,height:18,borderRadius:5,background:c.bg,border:`1px solid ${c.ring}`,display:'inline-flex',alignItems:'center',justifyContent:'center',color:c.ring,flexShrink:0}}>
                          <window.FE.NodeGlyph kind={p.kind} size={9}/>
                        </span>
                        <span style={{fontSize:12.5}}>{p.label}</span>
                      </div>
                    );
                  })}
                </div>
              );
            })}
          </div>

          <div style={{padding:'10px 12px',borderTop:'1px solid var(--line)',display:'flex',flexDirection:'column',gap:6}}>
            <div style={{fontSize:10,fontFamily:'Geist Mono, monospace',letterSpacing:'.05em',textTransform:'uppercase',color:'var(--ink-faint)'}}>New diagram</div>
            <input className="input" placeholder="diagram_name" value={newName} onChange={e=>setNewName(e.target.value)}
              onKeyDown={e=>{if(e.key==='Enter')createNew();}}/>
            <button className="btn btn-ghost btn-sm" style={{width:'100%'}} onClick={createNew}>+ Create blank</button>
          </div>

          <div style={{padding:'8px 12px',borderTop:'1px solid var(--line)',fontFamily:'Geist Mono, monospace',fontSize:10.5,color:'var(--ink-faint)',lineHeight:1.7}}>
            Del = delete node · Ctrl+Z = undo<br/>drag bg = pan · scroll = zoom
          </div>
        </div>

        {/* ── Canvas ──────────────────────────── */}
        <div style={{position:'relative',flex:1,minWidth:0,minHeight:0,overflow:'hidden'}}>
          {loading && (
            <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',background:'rgba(7,9,18,.75)',zIndex:20,fontFamily:'Geist Mono, monospace',color:'var(--ink-faint)',fontSize:13}}>
              Loading diagram…
            </div>
          )}
          <window.FE.EditorCanvas
            nodes={nodes} edges={edges}
            selected={selected} onSelect={setSelected}
            selectedEdge={selectedEdge} onSelectEdge={setSelectedEdge}
            onNodeMove={onNodeMove}
            onNodeMoveEnd={onNodeMoveEnd}
            onDropNode={onDropNode}
            onAddEdge={onAddEdge}
            onRemoveEdge={onRemoveEdge}
            height="100%"
          />
          {selected && (
            <div style={{position:'absolute',right:14,bottom:56,display:'flex',gap:6}}>
              <button className="btn btn-ghost btn-sm" onClick={deleteSelected} aria-label="Delete node"
                style={{background:'rgba(11,13,24,.92)',backdropFilter:'blur(8px)'}}>
                <window.I name="trash" size={12}/>Delete node
              </button>
            </div>
          )}
        </div>

        {/* ── Properties ──────────────────────── */}
        <div className="de-panel" style={{display:'flex',flexDirection:'column',position:'relative'}}>
          {/* resize handle */}
          <div
            onMouseDown={e => {
              e.preventDefault();
              const startX = e.clientX;
              const startW = rightWidth;
              document.body.style.userSelect = 'none';
              const onMove = ev => {
                const newW = Math.max(200, Math.min(500, startW - (ev.clientX - startX)));
                setRightWidth(newW);
              };
              const onUp = () => {
                document.body.style.userSelect = '';
                document.removeEventListener('mousemove', onMove);
                document.removeEventListener('mouseup', onUp);
              };
              document.addEventListener('mousemove', onMove);
              document.addEventListener('mouseup', onUp);
            }}
            style={{
              position:'absolute',left:-8,top:0,bottom:0,width:16,cursor:'col-resize',zIndex:10,
            }}
          >
            <div style={{position:'absolute',left:'50%',top:'50%',transform:'translate(-50%,-50%)',width:3,height:32,borderRadius:2,background:'var(--line-2)'}}/>
          </div>
          <div className="card-head" style={{padding:'10px 12px'}}>
            <h3>Properties</h3>
            <span className="sub">{selNode?selNode.kind:'diagram'}</span>
          </div>
          <div style={{padding:12,display:'flex',flexDirection:'column',gap:12,overflowY:'auto',flex:1}}>
            {selNode ? (
              <>
                {/* Node identity header */}
                <div style={{padding:10,borderRadius:10,
                  background:window.FE.NODE_COLORS[selNode.kind]?.bg||'#111',
                  border:`1px solid ${window.FE.NODE_COLORS[selNode.kind]?.ring||'#333'}`}}>
                  <div style={{display:'flex',alignItems:'center',gap:10}}>
                    <span style={{width:22,height:22,borderRadius:6,background:'rgba(0,0,0,.3)',
                      border:`1px solid ${window.FE.NODE_COLORS[selNode.kind]?.ring}`,
                      display:'inline-flex',alignItems:'center',justifyContent:'center',
                      color:window.FE.NODE_COLORS[selNode.kind]?.ring}}>
                      <window.FE.NodeGlyph kind={selNode.kind} size={10}/>
                    </span>
                    <div>
                      <div style={{fontWeight:600,fontSize:12.5}}>{selNode.name||selNode.id}</div>
                      <div className="mono" style={{fontSize:10,color:'var(--ink-faint)',textTransform:'uppercase'}}>{selNode.kind}</div>
                    </div>
                  </div>
                </div>

                <div className="field">
                  <label>Node ID</label>
                  <input className="input mono" value={selNode.name||selNode.id}
                    onChange={e=>updateSel('name',e.target.value)}/>
                </div>

                <div className="field">
                  <label>Type</label>
                  <select className="input" value={selNode.kind}
                    onChange={e=>updateSel('kind',e.target.value)}
                    style={{background:'#0d0f1c',color:'#EDEFF7',borderColor:'rgba(255,255,255,.12)'}}>
                    {PALETTE_GROUPS.flatMap(g=>g.items).map(p=>(
                      <option key={p.kind} value={p.kind}
                        style={{background:'#0d0f1c',color:'#EDEFF7'}}>{p.label}</option>
                    ))}
                  </select>
                </div>

                {/* ── Kind-specific config panel ── */}
                <window.FE.NodeConfigPanel
                  key={selNode.id}
                  node={selNode}
                  selEdges={selEdges}
                  nodes={nodes}
                  diagramList={diagramList}
                  onChangeRaw={newRaw=>{updateSel('_raw',newRaw); setDirty(true);}}
                />

                {/* ── Outgoing edges (compact, shown for non-cond or as supplement) ── */}
                {selNode.kind !== 'cond' && (
                  <div className="field">
                    <label>Outgoing edges</label>
                    <div style={{display:'flex',flexDirection:'column',gap:4}}>
                      {selEdges.map((e,idx) => (
                        <div key={idx} style={{display:'flex',alignItems:'center',gap:6,padding:'4px 8px',
                          background:'rgba(255,255,255,.03)',borderRadius:6,fontSize:12,
                          fontFamily:'Geist Mono, monospace'}}>
                          <span style={{flex:1,color:'var(--accent-2)'}}>
                            {e.color&&<span style={{fontSize:9,color:e.color}}>⬤ </span>}
                            {e.to}
                          </span>
                          <button className="icon-btn" style={{width:18,height:18,fontSize:11}} aria-label="Delete edge"
                            onClick={()=>{
                              const gi = edges.indexOf(e);
                              const newEdges = edges.filter((_,i)=>i!==gi);
                              setEdges(newEdges);
                              setSelectedEdge(null);
                              setDirty(true);
                              pushHistory(nodes, newEdges);
                            }}>×</button>
                        </div>
                      ))}
                      {selEdges.length===0 && (
                        <div style={{fontSize:11.5,color:'var(--ink-faint)',fontFamily:'Geist Mono, monospace'}}>
                          No outgoing edges
                        </div>
                      )}
                    </div>
                  </div>
                )}

                {/* ── Raw JSON fallback (collapsed for known kinds) ── */}
                {selNode._raw && (
                  <details style={{marginTop:4}}>
                    <summary style={{fontSize:10.5,fontFamily:'Geist Mono,monospace',
                      color:'var(--ink-faint)',cursor:'pointer',userSelect:'none',
                      letterSpacing:'.03em'}}>
                      Raw JSON config ▸
                    </summary>
                    <div className="field" style={{marginTop:8}}>
                      <textarea className="textarea mono" rows={5}
                        defaultValue={JSON.stringify(selNode._raw,null,2)}
                        onBlur={e=>{try{updateSel('_raw',JSON.parse(e.target.value));}catch(ex){setErr('Invalid JSON');}}}/>
                      <div className="hint">Blur to apply. Changes here override the form above.</div>
                    </div>
                  </details>
                )}
              </>
            ) : (
              <>
                <div style={{fontSize:12.5,fontWeight:600,color:'var(--ink)'}}>Diagram settings</div>

                <div className="field">
                  <label>Diagram name</label>
                  <input className="input mono" value={diagramName} onChange={e=>{setDiagramName(e.target.value);setDirty(true);}}/>
                </div>

                <div className="field">
                  <label>Instantiation key</label>
                  <window.FE.VariablePicker
                    sourceFilter="event"
                    value={exprToPicker(meta.instantiation_key_expression)}
                    onChange={v => {
                      const parts = (v||'').split('.');
                      const src = parts[0], key = parts.slice(1).join('.');
                      const expr = src && key ? `${src}.get('${key}')` : v;
                      setMeta(m=>({...m, instantiation_key_expression: expr}));
                      setDirty(true);
                    }}
                  />
                  <div className="hint">Pick an event field that groups related events into the same instance (e.g., event.truck_id).</div>
                </div>

                <div className="field">
                  <label>Acceptance criteria</label>
                  <window.FE.RuleBuilder
                    expr={meta.acceptance_criteria}
                    onChange={val => { setMeta(m=>({...m,acceptance_criteria:val})); setDirty(true); }}
                    extraVars={[]}
                  />
                  <div className="hint" style={{marginTop:6}}>Use the rule builder above to define when this workflow should accept incoming events.</div>
                </div>

                <div className="field">
                  <label>TTL (seconds)</label>
                  <input className="input mono" type="number" value={meta.ttl}
                    onChange={e=>{setMeta(m=>({...m,ttl:parseInt(e.target.value)||86400}));setDirty(true);}}/>
                </div>

                <div style={{fontSize:11,color:'var(--ink-faint)',fontFamily:'Geist Mono, monospace',marginTop:4}}>
                  {nodes.length} nodes · {edges.length} edges
                </div>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
window.EditorPage = EditorPageNew;

/* ══════════════════════════════════════════════════════════════════
   INSTANCE DETAILS PAGE
   ═════════════════════════════════════════════════════════════ */
function InstanceDetailsPage({ instanceId, onBack }) {
  const { tenantId } = _useAuth();
  const [inst,   setInst]   = React.useState(null);
  const [dNodes, setDNodes] = React.useState([]);
  const [dEdges, setDEdges] = React.useState([]);
  const [loading,setLoading]= React.useState(true);
  const [err,    setErr]    = React.useState('');
  const [tab,    setTab]    = React.useState('viz');
  const [acting, setActing] = React.useState('');
  const [live,   setLive]   = React.useState(false);

  React.useEffect(() => {
    if (!instanceId) return;
    const load = async () => {
      setLoading(true); setErr('');
      try {
        const enc = encodeURIComponent(instanceId);
        const r = await _apiFetch(`/instances/${enc}`);
        if (!r||!r.ok) { setErr('Instance not found'); setLoading(false); return; }
        const d = await r.json();
        setInst(d);

        const dr = await _apiFetch(`/instances/${enc}/diagram`);
        if (dr && dr.ok) {
          const dd = await dr.json();
          const {nodes:cn, edges:ce} = window.FE.parseDiagram(dd);
          setDNodes(cn); setDEdges(ce);
        } else {
          const diagName = d.diagram_name || d.diagram_id;
          if (diagName && tenantId) {
            const dr2 = await _apiFetch(`/api/v1/tenants/${tenantId}/diagrams/${encodeURIComponent(diagName)}`);
            if (dr2 && dr2.ok) {
              const dd2 = await dr2.json();
              const arr = Array.isArray(dd2) ? dd2 : [dd2];
              const match = arr.find(x => (x.diagram_name||x.diagram_id)===diagName) || arr[0];
              if (match) {
                const {nodes:cn, edges:ce} = window.FE.parseDiagram(match);
                setDNodes(cn); setDEdges(ce);
              }
            }
          }
        }
      } catch(ex) {
        setErr('Failed to load: ' + ex.message);
      }
      setLoading(false);
    };
    load();
  }, [instanceId, tenantId]);

  React.useEffect(() => {
    if (loading || err || !instanceId) return;
    const enc = encodeURIComponent(instanceId);
    let iv = null;

    const poll = async () => {
      try {
        const r = await _apiFetch(`/instances/${enc}`);
        if (!r || !r.ok) return;
        const d = await r.json();
        setInst(d);
        if (TERMINAL_STATUSES.has(d.status)) {
          clearInterval(iv);
          setLive(false);
        }
      } catch(ex) {}
    };

    iv = setInterval(poll, 2500);
    setLive(true);
    return () => { clearInterval(iv); setLive(false); };
  }, [instanceId, loading, err]);

  const doAction = async action => {
    setActing(action);
    try {
      const enc = encodeURIComponent(instanceId);
      const r = await _apiFetch(`/instances/${enc}/${action}`, {method:'POST'});
      if (r && r.ok) {
        const nr = await _apiFetch(`/instances/${enc}`);
        if (nr && nr.ok) setInst(await nr.json());
      }
    } catch(ex) {}
    setActing('');
  };

  if (loading) return (
    <div style={{display:'flex',alignItems:'center',justifyContent:'center',height:320,flexDirection:'column',gap:12}}>
      <div style={{width:30,height:30,borderRadius:'50%',border:'2px solid var(--accent)',borderTopColor:'transparent',animation:'ec-spin 1s linear infinite'}}/>
      <div style={{fontFamily:'Geist Mono, monospace',fontSize:12,color:'var(--ink-faint)'}}>Loading instance…</div>
      <style>{`@keyframes ec-spin{to{transform:rotate(360deg)}}`}</style>
    </div>
  );

  if (err) return (
    <div className="fade-up">
      <button className="btn btn-ghost btn-sm" onClick={onBack} style={{marginBottom:14}}>← Back</button>
      <div style={{padding:'16px 20px',background:'rgba(248,113,113,.08)',border:'1px solid rgba(248,113,113,.3)',borderRadius:10,color:'var(--danger)'}}>{err}</div>
    </div>
  );

  if (!inst) return null;

  const status      = inst.status || 'running';
  const currentNode = inst.current_node_id;
  const variables   = inst.variables || {};
  const event       = inst.event || inst.current_event || {};
  const waitingFor  = inst.waiting_for || inst.wait_condition || '';
  const timeLeft    = inst.time_remaining ?? inst.ttl_remaining;
  const createdAt   = inst.created_at ? new Date(inst.created_at).toLocaleString() : '—';

  const vizNodes = dNodes.map(n => ({
    ...n,
    active: n.id===currentNode,
    selected: n.id===currentNode,
  }));

  const canTerminate = ['running','waiting','paused'].includes(status);

  return (
    <div className="fade-up">
      <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:14}}>
        <button className="btn btn-ghost btn-sm" onClick={onBack}>← Back to instances</button>
      </div>

      <div style={{display:'flex',alignItems:'flex-end',justifyContent:'space-between',gap:18,marginBottom:18,flexWrap:'wrap'}}>
        <div>
          <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:6}}>
            <h1 className="page-title" style={{margin:0}}>Instance details</h1>
            <window.Badge kind={status}>{status}</window.Badge>
          </div>
          <div style={{display:'flex',alignItems:'center',gap:12,fontSize:13,color:'var(--ink-dim)',flexWrap:'wrap'}}>
            <span className="mono" style={{color:'var(--ink)'}}>{inst.id||instanceId}</span>
            {inst.diagram_name && <><span style={{color:'var(--ink-faint)'}}>·</span><span>diagram <span className="mono" style={{color:'var(--accent-2)'}}>{inst.diagram_name}</span></span></>}
            <span style={{color:'var(--ink-faint)'}}>·</span>
            <span>created <span className="mono">{createdAt}</span></span>
          </div>
        </div>
        <div style={{display:'flex',gap:8}}>
          {status==='running'&&<button className="btn btn-ghost" onClick={()=>doAction('pause')} disabled={!!acting}><window.I name="pause" size={14}/>Pause</button>}
          {status==='paused' &&<button className="btn btn-ghost" onClick={()=>doAction('resume')} disabled={!!acting}><window.I name="play" size={14}/>Resume</button>}
          {canTerminate&&(
            <button className="btn" style={{background:'rgba(248,113,113,.10)',color:'var(--danger)',border:'1px solid rgba(248,113,113,.3)'}}
              onClick={()=>doAction('terminate')} disabled={!!acting}>
              <window.I name="stop" size={14}/>{acting==='terminate'?'Terminating…':'Terminate'}
            </button>
          )}
        </div>
      </div>

      <div style={{display:'grid',gridTemplateColumns:'340px 1fr',gap:14}}>
        {/* Left column */}
        <div style={{display:'flex',flexDirection:'column',gap:14}}>
          <div className="card card-pad">
            <h3 style={{margin:'0 0 12px',fontSize:13.5,fontWeight:600,display:'flex',alignItems:'center',gap:8}}>
              <window.I name="bolt" size={13}/>Instance info
            </h3>
            <dl style={{margin:0,display:'grid',gap:9}}>
              {[
                ['ID',           inst.id||instanceId],
                ['Key',          inst.instantiation_key||'—'],
                ['Status',       status],
                ['Current node', currentNode||'—'],
                ['Diagram',      inst.diagram_name||'—'],
                timeLeft!=null&&['Time remaining', `${Math.round(timeLeft)}s`],
                inst.ttl!=null &&['TTL',           `${inst.ttl}s`],
              ].filter(Boolean).map(([k,v]) => (
                <div key={k} style={{display:'flex',justifyContent:'space-between',alignItems:'center',paddingBottom:7,borderBottom:'1px dashed var(--line)'}}>
                  <dt style={{fontSize:10.5,fontFamily:'Geist Mono, monospace',letterSpacing:'.05em',textTransform:'uppercase',color:'var(--ink-faint)',flexShrink:0}}>{k}</dt>
                  <dd className="mono" style={{margin:0,fontSize:12,color:'var(--ink)',textAlign:'right',maxWidth:190,overflow:'hidden',textOverflow:'ellipsis'}}>{String(v??'—')}</dd>
                </div>
              ))}
            </dl>

            {waitingFor && (
              <div style={{marginTop:12,padding:'10px 12px',background:'rgba(34,211,238,.04)',border:'1px solid rgba(34,211,238,.18)',borderRadius:10}}>
                <div style={{fontSize:10,fontFamily:'Geist Mono, monospace',letterSpacing:'.06em',textTransform:'uppercase',color:'var(--accent)',marginBottom:5}}>Waiting for</div>
                <code className="mono" style={{fontSize:11,color:'var(--accent-2)',lineHeight:1.6,wordBreak:'break-word',display:'block'}}>{waitingFor}</code>
              </div>
            )}
          </div>

          {Object.keys(event).length>0 && (
            <div className="card">
              <div className="card-head"><h3><window.I name="bolt" size={13}/>Current event</h3><span className="sub">JSON</span></div>
              <pre style={{margin:0,padding:14,fontFamily:'Geist Mono, monospace',fontSize:11,lineHeight:1.65,color:'var(--ink-dim)',overflow:'auto',maxHeight:240}}>
                {JSON.stringify(event, null, 2)}
              </pre>
            </div>
          )}
        </div>

        {/* Right column */}
        <div className="card" style={{display:'flex',flexDirection:'column',overflow:'hidden',minHeight:480}}>
          <div className="card-head" style={{paddingRight:8}}>
            <div className="tabs" style={{margin:0,border:0}}>
              {[
                {id:'viz',  label:'Diagram',   icon:'eye'},
                {id:'vars', label:'Variables', icon:'diagram'},
              ].map(t => (
                <div key={t.id} className={'tab'+(tab===t.id?' active':'')} onClick={()=>setTab(t.id)}>
                  <window.I name={t.icon} size={13}/>{t.label}
                </div>
              ))}
            </div>
            <div style={{display:'flex',gap:6,alignItems:'center'}}>
              {live && (
                <span title="Live — updates every 2.5s" style={{display:'inline-flex',alignItems:'center',gap:5,
                  fontSize:10,fontFamily:'Geist Mono,monospace',color:'#34D399',
                  padding:'2px 7px',borderRadius:5,background:'rgba(52,211,153,.08)',
                  border:'1px solid rgba(52,211,153,.18)'}}>
                  <span style={{width:5,height:5,borderRadius:'50%',background:'#34D399',
                    animation:'fd-pulse 1.6s ease-in-out infinite'}}/>LIVE
                </span>
              )}
              <button className="icon-btn" title="Refresh now" aria-label="Refresh now" onClick={async()=>{
                try {
                  const enc = encodeURIComponent(instanceId);
                  const r = await _apiFetch(`/instances/${enc}`);
                  if (r && r.ok) setInst(await r.json());
                } catch(ex) {}
              }}>
                <window.I name="refresh" size={13}/>
              </button>
            </div>
          </div>

          {tab==='viz' && (
            <div style={{padding:16,flex:1}}>
              <FlowDiagram
                title={inst.diagram_name||'diagram'}
                instanceId={inst.id||instanceId}
                meta={`${status}${timeLeft!=null?` · ${Math.round(timeLeft)}s left`:''}`}
                status={status}
                diagramRef={inst.diagram_name||''}
                nodes={vizNodes} edges={dEdges}
                height={420}
              />
              {dNodes.length>0 && (
                <div style={{display:'flex',gap:14,alignItems:'center',marginTop:10,flexWrap:'wrap',fontSize:11.5,fontFamily:'Geist Mono, monospace',color:'var(--ink-faint)'}}>
                  <span style={{display:'inline-flex',alignItems:'center',gap:6}}><span style={{width:10,height:10,borderRadius:3,background:'#A78BFA',boxShadow:'0 0 8px rgba(167,139,250,.6)'}}/>current</span>
                  <span style={{display:'inline-flex',alignItems:'center',gap:6}}><span style={{width:10,height:10,borderRadius:3,background:'rgba(255,255,255,.14)'}}/>upcoming</span>
                  <span style={{marginLeft:'auto'}}>{dNodes.length} nodes · {dEdges.length} edges{currentNode?` · at ${currentNode}`:''}</span>
                </div>
              )}
            </div>
          )}

          {tab==='vars' && (
            <div style={{padding:16,flex:1}}>
              {Object.keys(variables).length>0
                ? <table className="tbl">
                    <thead><tr><th>Variable</th><th>Type</th><th>Value</th></tr></thead>
                    <tbody>
                      {Object.entries(variables).map(([k,v])=> (
                        <tr key={k}>
                          <td className="mono" style={{fontSize:12,color:'var(--accent-2)'}}>{k}</td>
                          <td className="mono" style={{fontSize:11,color:'var(--ink-faint)'}}>{Array.isArray(v)?'array':typeof v}</td>
                          <td className="mono" style={{fontSize:12}}>{typeof v==='string'?`"${v}"`:JSON.stringify(v)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                : <div style={{color:'var(--ink-faint)',fontFamily:'Geist Mono, monospace',fontSize:12}}>No variables</div>
              }
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
window.InstanceDetailsPage = InstanceDetailsPage;
