// workflow-deep.jsx — §06 Workflows · L1 + L2 deep states
//
// Covers: selection · adding nodes · node library · inspector · validation
// run states · canvas controls · triggers · empty state · hover preview.
// Workflow code lives in a separate (sister) repo today; this is the design
// reference for the future ghast integration.

// ── Sample node graph used across all variants ──
const WF_NODES = [
  { id: 'trig',  x: 50,  y: 130, label: 'Schedule · 9:00 daily', icon: 'history',  kind: 'trigger' },
  { id: 'slack', x: 290, y: 60,  label: 'Read Slack · #eng',     icon: 'plug',     kind: 'source' },
  { id: 'gh',    x: 290, y: 200, label: 'Read GitHub · open PRs', icon: 'plug',    kind: 'source' },
  { id: 'agent', x: 540, y: 130, label: 'Summarize w/ Ghast',     icon: 'sparkle', kind: 'agent' },
  { id: 'notion', x: 760, y: 130, label: 'Write to Notion',       icon: 'plug',    kind: 'output' },
];
const WF_EDGES = [
  ['trig', 'slack'], ['trig', 'gh'],
  ['slack', 'agent'], ['gh', 'agent'],
  ['agent', 'notion'],
];

// ── Status palette per node-run state ──
const NODE_RUN_STYLES = {
  idle:     (t) => ({ borderColor: t.border,  ring: null }),
  queued:   (t) => ({ borderColor: t.fg3,     ring: null }),
  running:  (t) => ({ borderColor: '#5865F2', ring: '#5865F2' }),
  success:  (t) => ({ borderColor: '#28c840', ring: null }),
  failed:   (t) => ({ borderColor: '#ff5f57', ring: '#ff5f57' }),
  skipped:  (t) => ({ borderColor: t.border,  opacity: 0.45 }),
  waiting:  (t) => ({ borderColor: '#febc2e', ring: '#febc2e' }),
};


// ── Wf shell — sidebar + topBar + canvas with edges + nodes ──
function WfShell({
  dark,
  title = 'Daily Slack digest',
  topRight,
  canvasOverlays,           // floats on top of nodes (palette / mini-map / inspector)
  nodeStates = {},          // { nodeId: 'running'|'success'|... }
  selectedIds = [],         // ['agent']
  badges = {},              // { nodeId: { text, tone } } — eg validation 'missing api key'
  edgeFlow = false,         // animate dot along edges
  liveIndicator = true,
}) {
  const t = dark ? G.d : G.l;
  const sidebar = (
    <AppSidebar title="Workflows" dark={dark} search onNew
      sections={[{ title: 'Active', items: [
        { label: 'Daily Slack digest', icon: 'workflow', active: true, dot: 'on' },
        { label: 'PR triage', icon: 'workflow', dot: 'on' },
        { label: 'Customer email replies', icon: 'workflow' },
      ]}, { title: 'Drafts', items: [
        { label: 'Untitled — May 2', icon: 'workflow' },
      ]}]}
    />
  );

  return (
    <AppShell dark={dark} active="workflow" sidebar={sidebar}
      topBar={<AppTopBar dark={dark} breadcrumbs={['Workflows', title]}
        right={<>
          {liveIndicator && <Tag><Icon name="dot" size={11}/> Live</Tag>}
          {topRight || <>
            <Button variant="secondary" size="sm" icon={<Icon name="play" size={11}/>}>Run</Button>
            <Button variant="primary" size="sm">Save</Button>
          </>}
        </>} />}
    >
      <div style={{ position: 'relative', height: '100%', overflow: 'hidden',
        backgroundImage: `radial-gradient(circle, ${t.fg4} 1px, transparent 1px)`,
        backgroundSize: '20px 20px',
      }}>
        {/* Edges */}
        <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
          {WF_EDGES.map(([fromId, toId], i) => {
            const a = WF_NODES.find(n => n.id === fromId);
            const b = WF_NODES.find(n => n.id === toId);
            const x1 = a.x + 200, y1 = a.y + 32;
            const x2 = b.x, y2 = b.y + 32;
            const mx = (x1 + x2) / 2;
            const path = `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`;
            const fromState = nodeStates[fromId];
            const toState = nodeStates[toId];
            const isLive = fromState === 'success' && (toState === 'running' || toState === 'queued');
            return (
              <g key={i}>
                <path d={path} stroke={isLive ? '#5865F2' : t.fg3} strokeWidth={isLive ? 2 : 1.5} fill="none"
                  strokeDasharray={isLive ? '4 3' : '0'} />
                {edgeFlow && isLive && (
                  <circle r="3" fill="#5865F2">
                    <animateMotion dur="1.4s" repeatCount="indefinite" path={path} />
                  </circle>
                )}
              </g>
            );
          })}
        </svg>

        {/* Nodes */}
        {WF_NODES.map((n) => {
          const state = nodeStates[n.id] || 'idle';
          const cfg = NODE_RUN_STYLES[state] ? NODE_RUN_STYLES[state](t) : NODE_RUN_STYLES.idle(t);
          const selected = selectedIds.includes(n.id);
          const badge = badges[n.id];
          const isAgent = n.kind === 'agent';
          return (
            <div key={n.id} style={{
              position: 'absolute', left: n.x, top: n.y, width: 200, padding: 12,
              background: t.surface,
              border: `${selected ? 1.5 : 1}px solid ${selected ? t.fg : cfg.borderColor}`,
              borderRadius: 12, boxShadow: t.shadow, opacity: cfg.opacity ?? 1,
              outline: cfg.ring ? `3px solid ${cfg.ring}33` : 'none',
              outlineOffset: cfg.ring ? 1 : 0,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                <div style={{
                  width: 22, height: 22, borderRadius: 6,
                  background: n.kind === 'trigger' ? t.fg : t.surface2,
                  color: n.kind === 'trigger' ? t.bg : t.fg2,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  <Icon name={n.icon} size={12} color={n.kind === 'trigger' ? t.bg : t.fg2} />
                </div>
                <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.5, textTransform: 'uppercase' }}>{n.kind}</div>
                <span style={{ flex: 1 }} />
                {/* Run-state badge */}
                {state === 'running' && <span style={{ width: 11, height: 11, border: `1.5px solid #5865F2`, borderTopColor: 'transparent', borderRadius: 6, animation: 'gh-spin .8s linear infinite' }} />}
                {state === 'success' && <Icon name="check" size={11} color="#28c840" />}
                {state === 'failed' && <Icon name="close" size={11} color="#ff5f57" />}
                {state === 'waiting' && <Icon name="lock" size={11} color="#febc2e" />}
              </div>
              <div style={{ fontSize: 12.5, fontWeight: 500 }}>{n.label}</div>
              {/* validation badge */}
              {badge && (
                <div style={{
                  position: 'absolute', top: -8, right: -8,
                  height: 18, padding: '0 6px',
                  background: badge.tone === 'error' ? '#ff5f57' : '#febc2e',
                  color: '#fff', borderRadius: 9,
                  fontSize: 10, fontFamily: G.mono, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 3,
                }}>
                  <Icon name="warn" size={9} color="#fff" />
                  {badge.text}
                </div>
              )}
              {/* run summary inline (success state) */}
              {state === 'success' && (
                <div style={{ marginTop: 6, fontSize: 10, color: t.fg3, fontFamily: G.mono }}>
                  {n.id === 'slack' && '12 messages · 0.3s'}
                  {n.id === 'gh' && '4 PRs · 0.4s'}
                  {n.id === 'trig' && '0.0s'}
                  {n.id === 'agent' && '4.2s · 1,820 tokens'}
                  {n.id === 'notion' && '1 page · 0.6s'}
                </div>
              )}
              {state === 'failed' && (
                <div style={{ marginTop: 6, fontSize: 10, color: '#ff5f57', fontFamily: G.mono }}>
                  Notion 401 · token expired
                </div>
              )}
              {/* connector dots */}
              <div style={{ position: 'absolute', left: -5, top: 27, width: 10, height: 10, borderRadius: 5, background: t.surface, border: `1.5px solid ${t.fg3}` }} />
              <div style={{ position: 'absolute', right: -5, top: 27, width: 10, height: 10, borderRadius: 5, background: t.fg, border: 'none' }} />
            </div>
          );
        })}

        {canvasOverlays}
      </div>
    </AppShell>
  );
}


// ═════════════════════════════════════════════════════════════
// A · SELECTION
// ═════════════════════════════════════════════════════════════
function WfSelection({ dark, variant = 'single' }) {
  const t = dark ? G.d : G.l;
  const isMulti = variant === 'multi';
  const isLasso = variant === 'lasso';
  const ids = isMulti ? ['slack', 'gh', 'agent'] : ['agent'];
  return (
    <WfShell dark={dark} selectedIds={ids}
      canvasOverlays={
        <>
          {/* Lasso rect for variant=lasso */}
          {isLasso && (
            <div style={{
              position: 'absolute', left: 270, top: 40, width: 380, height: 230,
              background: 'rgba(88,101,242,0.06)',
              border: `1.5px dashed #5865F2`,
              borderRadius: 4, pointerEvents: 'none',
            }} />
          )}
          {/* Floating action toolbar above selection (only when something selected) */}
          {ids.length > 0 && (
            <div style={{
              position: 'absolute',
              left: ids.length === 1 ? WF_NODES.find(n => n.id === ids[0]).x + 60 : 380,
              top: 30,
              padding: '4px 6px', background: t.surface,
              border: `1px solid ${t.border}`, borderRadius: 999, boxShadow: t.shadow,
              display: 'flex', gap: 2, alignItems: 'center',
            }}>
              <span style={{ fontSize: 11, fontFamily: G.mono, color: t.fg3, padding: '0 8px' }}>
                {ids.length === 1 ? '1 selected' : `${ids.length} selected`}
              </span>
              <span style={{ width: 1, height: 14, background: t.border }} />
              <button title="Duplicate" style={iconBtnStyle(t)}><Icon name="copy" size={12} /></button>
              <button title="Group as sub-workflow" style={iconBtnStyle(t)}><Icon name="layers" size={12} /></button>
              <button title="Disable" style={iconBtnStyle(t)}><Icon name="eye" size={12} /></button>
              <button title="Delete" style={iconBtnStyle(t, '#ff5f57')}><Icon name="trash" size={12} /></button>
            </div>
          )}
        </>
      }
    />
  );
}

function iconBtnStyle(t, color) {
  return {
    width: 26, height: 26, borderRadius: 14,
    background: 'transparent', border: 'none', color: color || t.fg2,
    cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
  };
}


// ═════════════════════════════════════════════════════════════
// B · ADDING NODES
// ═════════════════════════════════════════════════════════════
function WfAddNode({ dark, variant = 'edge-plus' }) {
  const t = dark ? G.d : G.l;
  return (
    <WfShell dark={dark}
      canvasOverlays={
        <>
          {/* Variant: + on an edge between agent and notion */}
          {variant === 'edge-plus' && (() => {
            const a = WF_NODES.find(n => n.id === 'agent');
            const b = WF_NODES.find(n => n.id === 'notion');
            const cx = (a.x + 200 + b.x) / 2;
            const cy = (a.y + 32 + b.y + 32) / 2;
            return (
              <div style={{
                position: 'absolute', left: cx - 14, top: cy - 14,
                width: 28, height: 28, borderRadius: 14,
                background: t.fg, color: t.bg,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                cursor: 'pointer', boxShadow: t.shadowLg,
              }}>
                <Icon name="plus" size={14} color={t.bg} />
              </div>
            );
          })()}

          {/* Variant: dragging a node from palette → ghost preview at cursor */}
          {variant === 'drag-ghost' && (
            <div style={{
              position: 'absolute', left: 420, top: 280, width: 200, padding: 12,
              background: t.surface, border: `1.5px dashed ${t.fg2}`,
              borderRadius: 12, boxShadow: '0 12px 28px rgba(0,0,0,0.18)',
              opacity: 0.92, pointerEvents: 'none', transform: 'rotate(-1.5deg)',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                <div style={{ width: 22, height: 22, borderRadius: 6, background: t.surface2, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <Icon name="workflow" size={12} color={t.fg2} />
                </div>
                <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.5 }}>BRANCH</div>
              </div>
              <div style={{ fontSize: 12.5, fontWeight: 500 }}>If / else</div>
              <div style={{ fontSize: 10, color: t.fg3, marginTop: 2 }}>Release on a valid drop zone</div>
            </div>
          )}

          {/* Variant: double-click empty → quick search popup */}
          {variant === 'quick-search' && (
            <div style={{
              position: 'absolute', left: 350, top: 320, width: 320,
              background: t.surface, border: `1px solid ${t.border}`,
              borderRadius: 12, boxShadow: t.shadowLg, overflow: 'hidden',
            }}>
              <div style={{ padding: '8px 12px', borderBottom: `0.5px solid ${t.border2}`, display: 'flex', alignItems: 'center', gap: 8 }}>
                <Icon name="search" size={11} color={t.fg3} />
                <span style={{ fontSize: 12, fontFamily: G.mono, color: t.fg }}>http</span>
                <span style={{ flex: 1 }} />
                <Kbd>↵</Kbd>
              </div>
              {[
                { label: 'HTTP request', icon: 'globe', kind: 'source' },
                { label: 'HTTP webhook trigger', icon: 'history', kind: 'trigger' },
                { label: 'HTTP response', icon: 'plug', kind: 'output' },
              ].map((it, i) => (
                <div key={i} style={{
                  padding: '8px 12px', display: 'flex', alignItems: 'center', gap: 10,
                  fontSize: 12, fontFamily: G.mono,
                  background: i === 0 ? t.surface2 : 'transparent',
                  borderBottom: i === 2 ? 'none' : `0.5px solid ${t.border2}`,
                }}>
                  <Icon name={it.icon} size={11} color={t.fg2} />
                  <span style={{ flex: 1 }}>{it.label}</span>
                  <Tag style={{ height: 16, fontSize: 9 }}>{it.kind}</Tag>
                </div>
              ))}
            </div>
          )}

          {/* Variant: paste hint */}
          {variant === 'paste' && (
            <div style={{
              position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)',
              padding: '8px 14px',
              background: t.surface, border: `1px solid ${t.border}`,
              borderRadius: 999, boxShadow: t.shadow,
              display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 12, color: t.fg2,
            }}>
              <Icon name="copy" size={11} color={t.fg2} />
              <span>3 nodes pasted from clipboard · drag to position</span>
              <Kbd>⌘V</Kbd>
            </div>
          )}
        </>
      }
    />
  );
}


// ═════════════════════════════════════════════════════════════
// C · NODE LIBRARY (palette deep)
// ═════════════════════════════════════════════════════════════
function WfPalette({ dark, variant = 'categories' }) {
  const t = dark ? G.d : G.l;
  const groups = variant === 'mcp' ? [
    { title: 'MCP · github', items: [
      { name: 'list_repos', kind: 'mcp' },
      { name: 'create_issue', kind: 'mcp' },
      { name: 'merge_pr', kind: 'mcp' },
      { name: 'get_pr_files', kind: 'mcp' },
    ]},
    { title: 'MCP · linear', items: [
      { name: 'list_issues', kind: 'mcp' },
      { name: 'add_comment', kind: 'mcp' },
    ]},
    { title: 'MCP · stripe', items: [
      { name: 'list_charges', kind: 'mcp' },
      { name: 'refund', kind: 'mcp' },
    ]},
  ] : variant === 'skills' ? [
    { title: 'Bundled skills', items: [
      { name: 'web-fetch', kind: 'skill', icon: 'globe' },
      { name: 'web-search', kind: 'skill', icon: 'search' },
      { name: 'screenshot', kind: 'skill', icon: 'image' },
    ]},
    { title: 'Prompt apps', items: [
      { name: 'Translate', kind: 'prompt', icon: 'docs' },
      { name: 'Summarize thread', kind: 'prompt', icon: 'sparkle' },
      { name: 'Audit security', kind: 'prompt', icon: 'lock' },
    ]},
  ] : [
    { title: 'Triggers', items: [
      { name: 'Schedule', icon: 'history' },
      { name: 'Webhook', icon: 'globe' },
      { name: 'Manual', icon: 'play' },
      { name: 'Telegram event', icon: 'sound' },
    ]},
    { title: 'Sources', items: [
      { name: 'HTTP request', icon: 'globe' },
      { name: 'Read file', icon: 'file' },
      { name: 'Database query', icon: 'box' },
    ]},
    { title: 'Transforms', items: [
      { name: 'Map / filter', icon: 'sparkle' },
      { name: 'Code (JS / Py)', icon: 'code' },
      { name: 'JSON parse', icon: 'docs' },
    ]},
    { title: 'Agent', items: [
      { name: 'AI step (chat)', icon: 'sparkle', highlight: true },
      { name: 'Agent crew', icon: 'cube' },
      { name: 'Tool call', icon: 'zap' },
    ]},
    { title: 'Control flow', items: [
      { name: 'If / else', icon: 'workflow' },
      { name: 'Loop over array', icon: 'refresh' },
      { name: 'Parallel split', icon: 'layers' },
      { name: 'Wait', icon: 'history' },
    ]},
    { title: 'Outputs', items: [
      { name: 'Write Notion', icon: 'plug' },
      { name: 'Send Slack', icon: 'sound' },
      { name: 'Email', icon: 'mail' },
    ]},
  ];
  return (
    <WfShell dark={dark}
      canvasOverlays={
        <div style={{ position: 'absolute', top: 16, right: 16, width: 280 }}>
          <Glass radius={14} dark={dark} strong>
            <div style={{ padding: 10 }}>
              {/* Search + tabs */}
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
                <div style={{
                  flex: 1, height: 28, padding: '0 10px',
                  background: t.surface2, border: `1px solid ${t.border}`,
                  borderRadius: 14,
                  display: 'flex', alignItems: 'center', gap: 6,
                  fontSize: 11, color: t.fg3,
                }}>
                  <Icon name="search" size={11} />
                  <span>Search nodes…</span>
                  <span style={{ flex: 1 }} />
                  <Kbd>/</Kbd>
                </div>
              </div>
              <div style={{ display: 'flex', gap: 4, marginBottom: 8, padding: 2, background: t.surface2, borderRadius: 999 }}>
                {[
                  { id: 'categories', label: 'All' },
                  { id: 'skills',     label: 'Skills' },
                  { id: 'mcp',        label: 'MCP' },
                ].map((tab) => (
                  <div key={tab.id} style={{
                    flex: 1, padding: '4px 8px', borderRadius: 999,
                    background: tab.id === variant ? t.bg : 'transparent',
                    border: tab.id === variant ? `1px solid ${t.border}` : 'none',
                    color: tab.id === variant ? t.fg : t.fg3,
                    fontSize: 11, textAlign: 'center', cursor: 'pointer',
                  }}>{tab.label}</div>
                ))}
              </div>

              <div className="gh-scroll" style={{ maxHeight: 380, overflow: 'auto' }}>
                {groups.map((g, i) => (
                  <div key={i}>
                    <div style={{ fontSize: 10, color: t.fg3, fontFamily: G.mono, letterSpacing: 0.6, padding: '8px 6px 4px', textTransform: 'uppercase' }}>
                      {g.title}
                    </div>
                    {g.items.map((it, j) => (
                      <div key={j} style={{
                        padding: '6px 8px', borderRadius: 6, cursor: 'grab',
                        display: 'flex', alignItems: 'center', gap: 8,
                        fontSize: 12,
                        background: it.highlight ? t.surface2 : 'transparent',
                        fontFamily: it.kind === 'mcp' ? G.mono : G.font,
                      }}>
                        <Icon name={it.icon || 'cube'} size={12} color={t.fg2} />
                        <span style={{ flex: 1, color: t.fg }}>{it.name}</span>
                        {it.kind && <Tag style={{ height: 16, fontSize: 9 }}>{it.kind}</Tag>}
                      </div>
                    ))}
                  </div>
                ))}
              </div>
            </div>
          </Glass>
        </div>
      }
    />
  );
}


// ═════════════════════════════════════════════════════════════
// E · INSPECTOR (right pane after node selected)
// ═════════════════════════════════════════════════════════════
function WfInspector({ dark, variant = 'inputs' }) {
  const t = dark ? G.d : G.l;
  return (
    <WfShell dark={dark} selectedIds={['agent']}
      canvasOverlays={
        <div style={{
          position: 'absolute', top: 0, right: 0, bottom: 0, width: 340,
          background: t.surface, borderLeft: `1px solid ${t.border}`,
          display: 'flex', flexDirection: 'column',
        }}>
          {/* Header */}
          <div style={{ padding: '12px 14px', borderBottom: `0.5px solid ${t.border}` }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div style={{ width: 22, height: 22, borderRadius: 6, background: t.surface2, border: `1px solid ${t.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <Icon name="sparkle" size={12} color={t.fg2} />
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>Summarize w/ Ghast</div>
                <div style={{ fontSize: 10, color: t.fg3, fontFamily: G.mono, marginTop: 1 }}>step.agent · {variant}</div>
              </div>
              <button style={iconBtnStyle(t)}><Icon name="moreH" size={12} /></button>
            </div>
          </div>
          {/* Tabs */}
          <div style={{ display: 'flex', borderBottom: `0.5px solid ${t.border2}`, padding: '0 8px' }}>
            {[
              { id: 'inputs', label: 'Inputs' },
              { id: 'output', label: 'Output' },
              { id: 'retry',  label: 'Retry' },
              { id: 'error',  label: 'Error' },
            ].map((tab) => (
              <div key={tab.id} style={{
                padding: '8px 12px', fontSize: 11, fontFamily: G.mono, letterSpacing: 0.4,
                color: tab.id === variant ? t.fg : t.fg3,
                borderBottom: tab.id === variant ? `1.5px solid ${t.fg}` : '1.5px solid transparent',
                cursor: 'pointer', textTransform: 'uppercase',
              }}>{tab.label}</div>
            ))}
          </div>
          {/* Body */}
          <div className="gh-scroll" style={{ flex: 1, overflow: 'auto', padding: 14 }}>
            {variant === 'inputs' && (
              <>
                <Field t={t} label="Model"      value="ghast-2-pro" type="select" />
                <Field t={t} label="System prompt" value="Summarize the following Slack messages and PR list into a one-page digest…" type="text" multi />
                <Field t={t} label="Slack messages" value="{{ nodes.read_slack.messages }}" type="ref" hint="from upstream node" />
                <Field t={t} label="GitHub PRs" value="{{ nodes.read_gh.prs }}" type="ref" hint="from upstream node" />
                <Field t={t} label="Max output tokens" value="2,048" type="select" />
                <Field t={t} label="Temperature" value="0.4" type="slider" pct={0.4} />
              </>
            )}
            {variant === 'output' && (
              <>
                <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.6, marginBottom: 6 }}>SAMPLE OUTPUT (last run)</div>
                <pre style={{
                  margin: 0, padding: 10, background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8,
                  fontFamily: G.mono, fontSize: 10.5, color: t.fg2, lineHeight: 1.55,
                  whiteSpace: 'pre-wrap', maxHeight: 280, overflow: 'auto',
                }}>{`{
  "headline": "Auth refactor unblocked by JWT helper merge",
  "highlights": [
    { "topic": "auth", "summary": "Bypass on auth.ts:5 fixed in PR #4112" },
    { "topic": "infra", "summary": "Postgres failover playbook updated" }
  ],
  "open_threads": 3,
  "tokens_used": 1820
}`}</pre>
                <div style={{ marginTop: 12, fontSize: 11, color: t.fg3, fontFamily: G.mono }}>
                  Pin this output as a test fixture? · <span style={{ color: t.fg, cursor: 'pointer', textDecoration: 'underline' }}>Pin</span>
                </div>
              </>
            )}
            {variant === 'retry' && (
              <>
                <Field t={t} label="On error" value="Retry with backoff" type="select" />
                <Field t={t} label="Max retries" value="3" type="number" />
                <Field t={t} label="Backoff" value="exponential · 1s base" type="select" />
                <Field t={t} label="Timeout (per attempt)" value="30s" type="select" />
                <Field t={t} label="Total timeout" value="3m" type="select" />
                <div style={{
                  marginTop: 14, padding: 10,
                  background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8,
                  fontSize: 11, color: t.fg2, lineHeight: 1.5,
                }}>
                  <strong style={{ color: t.fg }}>Behavior</strong>: try once, wait 1s, retry; wait 2s, retry; wait 4s, retry. After 3 failures, mark this node failed and run the <code style={{ fontFamily: G.mono }}>on-error</code> branch (if any).
                </div>
              </>
            )}
            {variant === 'error' && (
              <>
                <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.6, marginBottom: 8 }}>LAST ERROR · 12 min ago</div>
                <div style={{
                  padding: 12, background: 'rgba(255,95,87,0.06)',
                  border: `1px solid #ff5f57`, borderRadius: 8, marginBottom: 12,
                }}>
                  <div style={{ fontSize: 12, fontWeight: 600, color: '#ff5f57', marginBottom: 4 }}>Anthropic 503 · upstream timeout</div>
                  <div style={{ fontSize: 11, color: t.fg2, lineHeight: 1.55 }}>
                    Provider returned no response after 30s. 3 retries exhausted. Run id: <code style={{ fontFamily: G.mono, fontSize: 10 }}>r_8a3f2c</code>.
                  </div>
                </div>
                <pre style={{
                  margin: 0, padding: 10, background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8,
                  fontFamily: G.mono, fontSize: 10, color: t.fg3, lineHeight: 1.5,
                  whiteSpace: 'pre', maxHeight: 200, overflow: 'auto',
                }}>{`Error: Provider request failed
  at AnthropicAdapter.invoke (anthropic-adapter.ts:142)
  at AgentStep.run (agent-step.ts:67)
  at WorkflowRuntime.execute (runtime.ts:204)
  caused by: AbortError: timeout 30000ms exceeded`}</pre>
                <div style={{ marginTop: 12, display: 'flex', gap: 6 }}>
                  <Button variant="primary" size="sm">Retry now</Button>
                  <Button variant="secondary" size="sm">Open in chat</Button>
                </div>
              </>
            )}
          </div>
        </div>
      }
    />
  );
}

function Field({ t, label, value, type = 'text', hint, multi, pct }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <div style={{ fontSize: 11, fontWeight: 500, color: t.fg2, marginBottom: 4 }}>{label}</div>
      <div style={{
        minHeight: multi ? 60 : 32, padding: '6px 10px',
        background: type === 'ref' ? t.surface2 : t.surface,
        border: `1px solid ${t.border}`, borderRadius: 8,
        display: 'flex', alignItems: type === 'slider' ? 'center' : 'flex-start',
        gap: 8, fontSize: 12,
        fontFamily: type === 'ref' ? G.mono : G.font,
        color: type === 'ref' ? '#5865F2' : t.fg,
      }}>
        {type === 'slider' ? (
          <>
            <div style={{ flex: 1, height: 4, background: t.surface2, borderRadius: 2, overflow: 'hidden', position: 'relative' }}>
              <div style={{ width: `${pct * 100}%`, height: '100%', background: t.fg }} />
            </div>
            <span style={{ fontFamily: G.mono, fontSize: 11, color: t.fg2 }}>{value}</span>
          </>
        ) : (
          <>
            <span style={{ flex: 1, lineHeight: 1.5 }}>{value}</span>
            {type === 'select' && <Icon name="chevronDown" size={11} color={t.fg3} />}
          </>
        )}
      </div>
      {hint && <div style={{ fontSize: 10, color: t.fg3, marginTop: 3, fontFamily: G.mono }}>{hint}</div>}
    </div>
  );
}


// ═════════════════════════════════════════════════════════════
// H · VALIDATION + RUNTIME ERRORS
// ═════════════════════════════════════════════════════════════
function WfValidation({ dark, variant = 'badges' }) {
  const t = dark ? G.d : G.l;
  if (variant === 'global') {
    return (
      <WfShell dark={dark}
        topRight={<>
          <Tag style={{ background: 'rgba(255,95,87,0.15)', color: '#ff5f57', border: '1px solid rgba(255,95,87,0.3)' }}><Icon name="warn" size={11} color="#ff5f57"/> 3 issues</Tag>
          <Button variant="secondary" size="sm" disabled icon={<Icon name="play" size={11}/>}>Run</Button>
          <Button variant="primary" size="sm">Save</Button>
        </>}
        canvasOverlays={
          <div style={{
            position: 'absolute', bottom: 16, left: 16, width: 420,
            background: t.surface, border: `1px solid #ff5f57`,
            borderRadius: 12, boxShadow: t.shadowLg, overflow: 'hidden',
          }}>
            <div style={{ padding: '8px 14px', background: 'rgba(255,95,87,0.06)', borderBottom: `0.5px solid ${t.border2}`, display: 'flex', alignItems: 'center', gap: 8 }}>
              <Icon name="warn" size={12} color="#ff5f57" />
              <span style={{ fontSize: 12, fontWeight: 600 }}>3 issues — fix to publish</span>
              <span style={{ flex: 1 }} />
              <button style={iconBtnStyle(t)}><Icon name="close" size={11} /></button>
            </div>
            {[
              { node: 'Read Slack · #eng', issue: 'Missing API token — set SLACK_TOKEN secret', sev: 'error' },
              { node: 'Summarize w/ Ghast', issue: 'system prompt is empty', sev: 'error' },
              { node: 'Write to Notion', issue: 'Notion DB id "1f2…" no longer accessible', sev: 'warn' },
            ].map((iss, i) => (
              <div key={i} style={{
                padding: '10px 14px', display: 'flex', alignItems: 'flex-start', gap: 10,
                borderBottom: i === 2 ? 'none' : `0.5px solid ${t.border2}`,
                cursor: 'pointer',
              }}>
                <Icon name={iss.sev === 'error' ? 'close' : 'warn'} size={12} color={iss.sev === 'error' ? '#ff5f57' : '#febc2e'} style={{ marginTop: 2 }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 12, fontWeight: 500 }}>{iss.node}</div>
                  <div style={{ fontSize: 11, color: t.fg3, marginTop: 1 }}>{iss.issue}</div>
                </div>
                <Icon name="chevronRight" size={11} color={t.fg3} style={{ marginTop: 4 }} />
              </div>
            ))}
          </div>
        }
        badges={{
          slack:  { text: '!', tone: 'error' },
          agent:  { text: '!', tone: 'error' },
          notion: { text: '?', tone: 'warn' },
        }}
      />
    );
  }
  // badges variant: just shows node-level red dots, no global panel
  return (
    <WfShell dark={dark}
      topRight={<>
        <Tag style={{ background: 'rgba(255,95,87,0.15)', color: '#ff5f57', border: '1px solid rgba(255,95,87,0.3)' }}>2 errors</Tag>
        <Button variant="secondary" size="sm" disabled icon={<Icon name="play" size={11}/>}>Run</Button>
        <Button variant="primary" size="sm">Save</Button>
      </>}
      badges={{
        slack:  { text: 'missing token', tone: 'error' },
        agent:  { text: 'empty prompt', tone: 'error' },
      }}
    />
  );
}


// ═════════════════════════════════════════════════════════════
// D · RUN STATE
// ═════════════════════════════════════════════════════════════
function WfRun({ dark, variant = 'mid' }) {
  const t = dark ? G.d : G.l;
  const states = {
    'mid':       { trig: 'success', slack: 'success', gh: 'success', agent: 'running', notion: 'queued' },
    'all-pass':  { trig: 'success', slack: 'success', gh: 'success', agent: 'success', notion: 'success' },
    'failed':    { trig: 'success', slack: 'success', gh: 'success', agent: 'success', notion: 'failed' },
    'waiting':   { trig: 'success', slack: 'success', gh: 'success', agent: 'waiting',  notion: 'queued' },
    'skipped':   { trig: 'success', slack: 'success', gh: 'skipped', agent: 'success', notion: 'success' },
  }[variant];

  const progress = {
    'mid':      { done: 3, total: 5, label: 'Running · 3 of 5', tone: '#5865F2' },
    'all-pass': { done: 5, total: 5, label: 'Completed in 6.3s', tone: '#28c840' },
    'failed':   { done: 5, total: 5, label: 'Failed at "Write to Notion"', tone: '#ff5f57' },
    'waiting':  { done: 3, total: 5, label: 'Waiting for permission · "Summarize w/ Ghast"', tone: '#febc2e' },
    'skipped':  { done: 5, total: 5, label: 'Completed (1 skipped)', tone: '#28c840' },
  }[variant];

  return (
    <WfShell dark={dark}
      topRight={<>
        <Tag style={{ background: progress.tone, color: '#fff', border: 'none' }}>
          <Icon name={variant === 'mid' || variant === 'waiting' ? 'refresh' : (variant === 'failed' ? 'close' : 'check')} size={11} color="#fff" />
          {progress.label}
        </Tag>
        {(variant === 'mid' || variant === 'waiting') && <Button variant="secondary" size="sm" icon={<Icon name="stop" size={11}/>}>Stop</Button>}
        {variant !== 'mid' && variant !== 'waiting' && <Button variant="secondary" size="sm" icon={<Icon name="refresh" size={11}/>}>Run again</Button>}
      </>}
      nodeStates={states}
      edgeFlow={variant === 'mid'}
      canvasOverlays={
        <>
          {/* Top progress bar */}
          <div style={{
            position: 'absolute', top: 0, left: 0, right: 0, height: 3,
            background: t.surface2,
          }}>
            <div style={{ width: `${(progress.done / progress.total) * 100}%`, height: '100%', background: progress.tone, transition: 'width .3s ease' }} />
          </div>

          {/* Permission gate modal (waiting) */}
          {variant === 'waiting' && (
            <div style={{
              position: 'absolute', left: WF_NODES.find(n => n.id === 'agent').x + 220, top: 100,
              width: 280, padding: 14,
              background: t.surface, border: `1px solid #febc2e`,
              borderRadius: 12, boxShadow: t.shadowLg,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                <Icon name="lock" size={13} color="#febc2e" />
                <div style={{ fontSize: 12, fontWeight: 600 }}>Approval required</div>
              </div>
              <div style={{ fontSize: 11, color: t.fg2, lineHeight: 1.55, marginBottom: 12 }}>
                <strong style={{ color: t.fg }}>Summarize w/ Ghast</strong> wants to spend up to <strong style={{ color: t.fg }}>$0.04</strong> on Anthropic API.
              </div>
              <div style={{ display: 'flex', gap: 6 }}>
                <Button variant="primary" size="sm">Allow</Button>
                <Button variant="secondary" size="sm">Allow always</Button>
                <Button variant="ghost" size="sm">Deny</Button>
              </div>
            </div>
          )}

          {/* Run log popout (failed) */}
          {variant === 'failed' && (
            <div style={{
              position: 'absolute', bottom: 16, left: 16, width: 420,
              background: t.surface, border: `1px solid ${t.border}`, borderRadius: 12, overflow: 'hidden', boxShadow: t.shadowLg,
            }}>
              <div style={{ padding: '8px 14px', borderBottom: `0.5px solid ${t.border2}`, display: 'flex', alignItems: 'center', gap: 8, fontFamily: G.mono, fontSize: 11, color: t.fg2 }}>
                <Icon name="terminal" size={11} />
                <span>Run log · r_8a3f2c</span>
                <span style={{ flex: 1 }} />
                <span>03:42 elapsed</span>
              </div>
              <pre style={{
                margin: 0, padding: 12, fontFamily: G.mono, fontSize: 10.5, lineHeight: 1.6,
                background: dark ? '#0a0a0a' : '#fafaf9', maxHeight: 160, overflow: 'auto', whiteSpace: 'pre',
              }}>{`[09:00:00] trigger fired (schedule daily)
[09:00:00] read_slack ✓ 12 messages
[09:00:00] read_gh    ✓ 4 PRs
[09:00:01] agent      ✓ 1820 tokens · $0.024
[09:00:05] write_notion · attempting…
[09:00:35] write_notion ✗ 401 Unauthorized · token expired
[09:00:35] retry 1/3 · backoff 1s
[09:00:36] write_notion ✗ 401 · giving up
[09:00:36] workflow failed`}</pre>
              <div style={{ padding: '8px 14px', borderTop: `0.5px solid ${t.border2}`, display: 'flex', gap: 6 }}>
                <Button variant="primary" size="sm">Retry from here</Button>
                <Button variant="secondary" size="sm">Open in chat to fix</Button>
              </div>
            </div>
          )}
        </>
      }
    />
  );
}


// ═════════════════════════════════════════════════════════════
// F · CANVAS CONTROLS (zoom + minimap + grid)
// ═════════════════════════════════════════════════════════════
function WfCanvasControls({ dark, variant = 'minimap' }) {
  const t = dark ? G.d : G.l;
  return (
    <WfShell dark={dark}
      canvasOverlays={
        <>
          {/* Zoom + history controls bottom-left */}
          <div style={{
            position: 'absolute', bottom: 16, left: 16,
            padding: 4, background: t.surface, border: `1px solid ${t.border}`,
            borderRadius: 999, boxShadow: t.shadow,
            display: 'flex', alignItems: 'center', gap: 2,
          }}>
            <button style={iconBtnStyle(t)}><Icon name="plus" size={13} /></button>
            <span style={{ fontSize: 11, fontFamily: G.mono, color: t.fg2, padding: '0 6px' }}>100%</span>
            <button style={iconBtnStyle(t)}><Icon name="close" size={11} style={{ transform: 'rotate(45deg)' }} /></button>
            <span style={{ width: 1, height: 14, background: t.border, margin: '0 2px' }} />
            <button title="Fit to view" style={iconBtnStyle(t)}><Icon name="grid" size={12} /></button>
            <button title="Reset view" style={iconBtnStyle(t)}><Icon name="refresh" size={12} /></button>
            <span style={{ width: 1, height: 14, background: t.border, margin: '0 2px' }} />
            <button title="Undo (⌘Z)" style={iconBtnStyle(t)}><Icon name="arrowLeft" size={12} /></button>
            <button title="Redo (⌘⇧Z)" style={iconBtnStyle(t)}><Icon name="arrowRight" size={12} /></button>
            <span style={{ width: 1, height: 14, background: t.border, margin: '0 2px' }} />
            <button title="Snap to grid" style={{
              ...iconBtnStyle(t),
              background: variant === 'grid' ? t.fg : 'transparent',
              color: variant === 'grid' ? t.bg : t.fg2,
            }}><Icon name="grid" size={11} color={variant === 'grid' ? t.bg : t.fg2} /></button>
          </div>

          {/* Mini-map bottom-right */}
          {variant !== 'no-minimap' && (
            <div style={{
              position: 'absolute', bottom: 16, right: 16, width: 200, height: 130,
              background: t.surface, border: `1px solid ${t.border}`,
              borderRadius: 10, boxShadow: t.shadow, overflow: 'hidden',
            }}>
              <div style={{ padding: '4px 8px', fontSize: 10, fontFamily: G.mono, color: t.fg3, borderBottom: `0.5px solid ${t.border2}` }}>map</div>
              <div style={{ position: 'relative', height: 110, background: t.surface2 }}>
                {/* Mini nodes */}
                {WF_NODES.map((n, i) => (
                  <div key={i} style={{
                    position: 'absolute',
                    left: 6 + n.x * 0.18, top: 6 + n.y * 0.18,
                    width: 36, height: 12,
                    background: t.fg2, borderRadius: 2,
                  }} />
                ))}
                {/* Mini edges (very rough) */}
                <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
                  {WF_EDGES.map(([from, to], i) => {
                    const a = WF_NODES.find(n => n.id === from);
                    const b = WF_NODES.find(n => n.id === to);
                    return <line key={i}
                      x1={6 + a.x * 0.18 + 36} y1={6 + a.y * 0.18 + 6}
                      x2={6 + b.x * 0.18}      y2={6 + b.y * 0.18 + 6}
                      stroke={t.fg3} strokeWidth="0.6" />;
                  })}
                </svg>
                {/* Viewport rectangle */}
                <div style={{
                  position: 'absolute', left: 12, top: 18, width: 130, height: 70,
                  border: `1.5px solid ${t.fg}`, borderRadius: 2,
                  background: 'transparent', pointerEvents: 'none',
                }} />
              </div>
            </div>
          )}

          {/* Status bar — bottom center */}
          <div style={{
            position: 'absolute', bottom: 16, left: '50%', transform: 'translateX(-50%)',
            padding: '4px 12px',
            background: t.surface, border: `1px solid ${t.border}`,
            borderRadius: 999, boxShadow: t.shadow,
            display: 'flex', alignItems: 'center', gap: 12,
            fontSize: 10, fontFamily: G.mono, color: t.fg3,
          }}>
            <span><Icon name="git" size={10} style={{ verticalAlign: -1, marginRight: 4 }} />v12 · saved 4m ago</span>
            <span>5 nodes · 5 edges</span>
            <span>last run 2h ago · ✓</span>
          </div>
        </>
      }
    />
  );
}


// ═════════════════════════════════════════════════════════════
// J · TRIGGERS
// ═════════════════════════════════════════════════════════════
function WfTrigger({ dark, variant = 'schedule' }) {
  const t = dark ? G.d : G.l;
  return (
    <WfShell dark={dark} selectedIds={['trig']}
      canvasOverlays={
        <div style={{
          position: 'absolute', top: 0, right: 0, bottom: 0, width: 360,
          background: t.surface, borderLeft: `1px solid ${t.border}`,
          display: 'flex', flexDirection: 'column',
        }}>
          <div style={{ padding: '12px 14px', borderBottom: `0.5px solid ${t.border}` }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Trigger</div>
            <div style={{ fontSize: 10, color: t.fg3, fontFamily: G.mono, marginTop: 1 }}>step.trigger · {variant}</div>
          </div>
          {/* Trigger type picker */}
          <div style={{ padding: 10, borderBottom: `0.5px solid ${t.border2}` }}>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
              {[
                { id: 'schedule', icon: 'history', label: 'Schedule' },
                { id: 'webhook',  icon: 'globe',   label: 'Webhook' },
                { id: 'manual',   icon: 'play',    label: 'Manual' },
                { id: 'event',    icon: 'sound',   label: 'Event' },
              ].map((opt) => (
                <div key={opt.id} style={{
                  padding: '8px 10px', borderRadius: 8,
                  background: opt.id === variant ? t.surface2 : 'transparent',
                  border: `1px solid ${opt.id === variant ? t.fg : t.border}`,
                  display: 'flex', alignItems: 'center', gap: 8,
                  fontSize: 12, color: opt.id === variant ? t.fg : t.fg2,
                  cursor: 'pointer',
                }}>
                  <Icon name={opt.icon} size={12} />
                  {opt.label}
                </div>
              ))}
            </div>
          </div>
          {/* Body per variant */}
          <div className="gh-scroll" style={{ flex: 1, overflow: 'auto', padding: 14 }}>
            {variant === 'schedule' && (
              <>
                <Field t={t} label="When" value="Every weekday at 9:00 AM" type="select" />
                <Field t={t} label="Cron expression" value="0 9 * * 1-5" type="text" />
                <Field t={t} label="Timezone" value="Asia/Shanghai (UTC+8)" type="select" />
                <div style={{ marginTop: 14, padding: 10, background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8 }}>
                  <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.6, marginBottom: 6 }}>NEXT 5 RUNS</div>
                  {[
                    'Mon · 2026-05-05 · 09:00 CST',
                    'Tue · 2026-05-06 · 09:00 CST',
                    'Wed · 2026-05-07 · 09:00 CST',
                    'Thu · 2026-05-08 · 09:00 CST',
                    'Fri · 2026-05-09 · 09:00 CST',
                  ].map((r, i) => (
                    <div key={i} style={{ fontSize: 11, fontFamily: G.mono, color: t.fg2, padding: '2px 0' }}>· {r}</div>
                  ))}
                </div>
              </>
            )}
            {variant === 'webhook' && (
              <>
                <Field t={t} label="URL" value="https://hooks.ghast.trapezohe.ai/wf/9k4q-2pf7-m3da" type="ref" hint="POST any JSON body to trigger" />
                <Field t={t} label="Authentication" value="Bearer token (auto-generated)" type="select" />
                <Field t={t} label="Secret" value="whsec_8a••••••••••f4c" type="text" hint="rotate every 90 days" />
                <div style={{ marginTop: 14 }}>
                  <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.6, marginBottom: 6 }}>SAMPLE CURL</div>
                  <pre style={{
                    margin: 0, padding: 10,
                    background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8,
                    fontFamily: G.mono, fontSize: 10, color: t.fg2, lineHeight: 1.5,
                    whiteSpace: 'pre-wrap',
                  }}>{`curl -X POST \\
  -H "Authorization: Bearer whsec_..." \\
  -H "Content-Type: application/json" \\
  -d '{"channel": "#eng"}' \\
  https://hooks.ghast.trapezohe.ai/wf/9k4q-2pf7-m3da`}</pre>
                </div>
              </>
            )}
            {variant === 'manual' && (
              <>
                <div style={{ padding: 12, background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8, marginBottom: 14 }}>
                  <div style={{ fontSize: 12, fontWeight: 500, marginBottom: 4 }}>Manual trigger</div>
                  <div style={{ fontSize: 11, color: t.fg3, lineHeight: 1.55 }}>
                    Workflow only runs when you click <strong style={{ color: t.fg }}>Run</strong>. Good for one-off tasks and testing.
                  </div>
                </div>
                <Field t={t} label="Sample input (JSON)" value={`{\n  "channel": "#eng",\n  "lookback_hours": 24\n}`} type="text" multi />
                <Button variant="primary" size="md" full icon={<Icon name="play" size={12} color={t.bg}/>}>Run with this input</Button>
              </>
            )}
            {variant === 'event' && (
              <>
                <Field t={t} label="Event source" value="Telegram" type="select" />
                <Field t={t} label="Event type" value="message_received" type="select" />
                <Field t={t} label="Filter" value="chat_id == @ghast_eng_chat" type="text" />
                <div style={{ marginTop: 14, padding: 10, background: t.surface2, border: `1px solid ${t.border2}`, borderRadius: 8, fontSize: 11, color: t.fg2 }}>
                  Event will fire <strong style={{ color: t.fg }}>at most every 5 seconds</strong> per chat to avoid runaway loops.
                </div>
              </>
            )}
          </div>
        </div>
      }
    />
  );
}


// ═════════════════════════════════════════════════════════════
// EMPTY STATE + HOVER PREVIEW (extras)
// ═════════════════════════════════════════════════════════════
function WfEmpty({ dark }) {
  const t = dark ? G.d : G.l;
  const sidebar = (
    <AppSidebar title="Workflows" dark={dark} search onNew sections={[]} />
  );
  return (
    <AppShell dark={dark} active="workflow" sidebar={sidebar}
      topBar={<AppTopBar dark={dark} breadcrumbs={['Workflows']} />}
    >
      <div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 18, padding: 40 }}>
        <div style={{
          width: 56, height: 56, borderRadius: 14, background: t.surface2, border: `1px solid ${t.border}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icon name="workflow" size={26} color={t.fg2} />
        </div>
        <div style={{ fontSize: 18, fontWeight: 600, letterSpacing: -0.3 }}>No workflows yet</div>
        <div style={{ fontSize: 13, color: t.fg2, textAlign: 'center', maxWidth: 380, lineHeight: 1.55 }}>
          Build a workflow by chaining triggers, agents, and actions. Run them on a schedule, a webhook, or by hand.
        </div>
        <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
          <Button variant="primary" size="md" icon={<Icon name="plus" size={12} color={t.bg}/>}>Blank workflow</Button>
          <Button variant="secondary" size="md">Browse templates</Button>
        </div>
        {/* Templates row */}
        <div style={{ marginTop: 20, display: 'flex', gap: 10, flexWrap: 'wrap', justifyContent: 'center', maxWidth: 760 }}>
          {[
            { title: 'Daily standup digest', desc: 'Slack + GitHub → Notion at 9am', icon: 'history' },
            { title: 'PR triage', desc: 'New PR → AI review comment', icon: 'git' },
            { title: 'Meeting notes', desc: 'Calendar + transcripts → summary', icon: 'docs' },
          ].map((tpl, i) => (
            <Card key={i} style={{ padding: 14, width: 220 }}>
              <Icon name={tpl.icon} size={16} color={t.fg2} />
              <div style={{ fontSize: 13, fontWeight: 600, marginTop: 8 }}>{tpl.title}</div>
              <div style={{ fontSize: 11, color: t.fg3, marginTop: 4, lineHeight: 1.5 }}>{tpl.desc}</div>
            </Card>
          ))}
        </div>
      </div>
    </AppShell>
  );
}

function WfHover({ dark }) {
  const t = dark ? G.d : G.l;
  return (
    <WfShell dark={dark}
      canvasOverlays={
        <div style={{
          position: 'absolute', left: WF_NODES.find(n => n.id === 'agent').x + 30, top: WF_NODES.find(n => n.id === 'agent').y - 80,
          width: 260, padding: 12,
          background: t.surface, border: `1px solid ${t.border}`,
          borderRadius: 10, boxShadow: t.shadowLg,
        }}>
          <div style={{ fontSize: 10, fontFamily: G.mono, color: t.fg3, letterSpacing: 0.6, marginBottom: 4 }}>LAST RUN · 2h ago</div>
          <div style={{ fontSize: 12, fontWeight: 600 }}>Summarize w/ Ghast</div>
          <div style={{ fontSize: 10, color: t.fg3, fontFamily: G.mono, marginTop: 4, display: 'flex', gap: 12 }}>
            <span><Icon name="check" size={10} color="#28c840" style={{ verticalAlign: -1, marginRight: 3 }} />success</span>
            <span>4.2s</span>
            <span>1,820 tok</span>
            <span>$0.024</span>
          </div>
          <div style={{
            marginTop: 8, padding: 8, background: t.surface2, borderRadius: 6,
            fontSize: 10, color: t.fg2, lineHeight: 1.5, fontFamily: G.mono,
            maxHeight: 60, overflow: 'hidden',
          }}>
            "headline": "Auth refactor unblocked by JWT helper merge", "highlights": [...]
          </div>
        </div>
      }
    />
  );
}


Object.assign(window, {
  WfShell, WfSelection, WfAddNode, WfPalette, WfInspector,
  WfValidation, WfRun, WfCanvasControls, WfTrigger, WfEmpty, WfHover,
});
