// ============================================================================
// public/screens/flow-builder.jsx — Visual Flow Builder (Phase 4)
// ----------------------------------------------------------------------------
// Custom SVG canvas: draggable nodes, bezier edges, pan/zoom, auto-save.
// Node types: start, greeting, listen, intent, branch, pitch, objection,
//             book, transfer, whatsapp, voicemail, end
// ============================================================================

const NODE_TYPES = {
  start:     { label: "Start",       color: "#888780", icon: "play",    hasIn: false, hasOut: true },
  greeting:  { label: "Greeting",    color: "#1D9E75", icon: "message", hasIn: true,  hasOut: true },
  listen:    { label: "Listen",      color: "#378ADD", icon: "mic",     hasIn: true,  hasOut: true },
  intent:    { label: "Intent",      color: "#BA7517", icon: "branch",  hasIn: true,  hasOut: true,  multiOut: true },
  branch:    { label: "Branch",      color: "#BA7517", icon: "branch",  hasIn: true,  hasOut: true,  multiOut: true },
  pitch:     { label: "Pitch",       color: "#1D9E75", icon: "send",    hasIn: true,  hasOut: true },
  objection: { label: "Objection",   color: "#D85A30", icon: "shield",  hasIn: true,  hasOut: true },
  book:      { label: "Book meeting",color: "#534AB7", icon: "calendar",hasIn: true,  hasOut: true },
  transfer:  { label: "Transfer",    color: "#534AB7", icon: "phone",   hasIn: true,  hasOut: true },
  whatsapp:  { label: "WhatsApp",    color: "#1D9E75", icon: "whatsapp",hasIn: true,  hasOut: true },
  voicemail: { label: "Voicemail",   color: "#888780", icon: "mic",     hasIn: true,  hasOut: true },
  end:       { label: "End",         color: "#888780", icon: "square",  hasIn: true,  hasOut: false },
};

const NODE_W = 180, NODE_H = 64, PORT_R = 7;

function FlowBuilderScreen({ go }) {
  const [flows, setFlows]         = React.useState(null);
  const [activeFlowId, setActive] = React.useState(null);
  const [error, setError]         = React.useState(null);
  const [showCreate, setShowCreate] = React.useState(false);

  const loadFlows = React.useCallback(async () => {
    const r = await VoaisAPI.get("/api/flows");
    if (r.ok && r.data?.ok) setFlows(r.data.flows);
  }, []);

  React.useEffect(() => { loadFlows(); }, [loadFlows]);

  if (activeFlowId) {
    return <FlowCanvas flowId={activeFlowId} onBack={() => { setActive(null); loadFlows(); }}/>;
  }

  if (flows === null) return <DashboardSkeleton/>;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "var(--gap-grid)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        <Badge tone="blue" dot>{flows.length} flows</Badge>
        <div style={{ flex: 1 }}/>
        <Btn kind="primary" size="sm" icon={<I.plus size={13}/>} onClick={() => setShowCreate(true)}>New flow</Btn>
      </div>

      {flows.length === 0 && (
        <Card>
          <div style={{ padding: "40px 24px", textAlign: "center", maxWidth: 520, margin: "0 auto" }}>
            <div style={{ width: 64, height: 64, borderRadius: 16, margin: "0 auto 20px", display: "grid", placeItems: "center", background: "var(--accent-soft)", color: "var(--accent)" }}>
              <I.flow size={28}/>
            </div>
            <h2 style={{ fontSize: 22, fontWeight: 600, letterSpacing: "-0.02em", margin: "14px 0 10px" }}>No flows yet</h2>
            <p style={{ color: "var(--ink-3)", fontSize: 13.5, lineHeight: 1.6, margin: "0 0 20px" }}>
              Flows define the conversation logic your AI agents follow. Start from a template or build from scratch.
            </p>
            <Btn kind="primary" icon={<I.plus size={14}/>} onClick={() => setShowCreate(true)}>Create your first flow</Btn>
          </div>
        </Card>
      )}

      {flows.length > 0 && (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: "var(--gap-grid)" }}>
          {flows.map(f => (
            <FlowListCard key={f.id} flow={f} onOpen={() => setActive(f.id)} onDelete={async () => {
              if (!confirm("Delete this flow?")) return;
              const r = await VoaisAPI.del("/api/flows/" + f.id);
              if (!r.ok) alert(r.data?.msg || "Delete failed.");
              loadFlows();
            }}/>
          ))}
        </div>
      )}

      {showCreate && <CreateFlowModal onClose={() => setShowCreate(false)} onCreated={(id) => { setShowCreate(false); setActive(id); }}/>}
    </div>
  );
}

// ── Flow list card ──────────────────────────────────────────────────────
function FlowListCard({ flow, onOpen, onDelete }) {
  const statusTone = flow.status === "published" ? "green" : flow.status === "draft" ? "gray" : "yellow";
  return (
    <Card padded={false}>
      <div style={{ padding: 16, cursor: "pointer" }} onClick={onOpen}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <I.flow size={18} style={{ color: "var(--accent)" }}/>
          <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600, flex: 1 }}>{flow.name}</h3>
          <Badge tone={statusTone} dot>{flow.status}</Badge>
        </div>
        {flow.description && <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 6, lineHeight: 1.5 }}>{flow.description}</div>}
        <div style={{ display: "flex", gap: 12, marginTop: 12, fontSize: 12, color: "var(--ink-3)" }}>
          <span>{flow.node_count || 0} nodes</span>
          <span>{flow.edge_count || 0} edges</span>
          {flow.agents_using > 0 && <span style={{ color: "var(--accent)" }}>{flow.agents_using} agent{flow.agents_using > 1 ? "s" : ""}</span>}
        </div>
      </div>
      <div style={{ padding: "0 16px 12px", display: "flex", gap: 8 }}>
        <Btn kind="primary" size="sm" style={{ flex: 1 }} onClick={onOpen}>Open editor</Btn>
        <Btn kind="ghost" size="sm" onClick={e => { e.stopPropagation(); onDelete(); }} title="Delete"><I.trash size={13}/></Btn>
      </div>
    </Card>
  );
}

// ── Create Flow Modal ───────────────────────────────────────────────────
function CreateFlowModal({ onClose, onCreated }) {
  const [name, setName]           = React.useState("");
  const [desc, setDesc]           = React.useState("");
  const [template, setTemplate]   = React.useState("");
  const [templates, setTemplates] = React.useState([]);
  const [submitting, setSub]      = React.useState(false);

  React.useEffect(() => {
    VoaisAPI.get("/api/flows/templates").then(r => {
      if (r.ok && r.data?.ok) setTemplates(r.data.templates);
    });
  }, []);

  const submit = async () => {
    if (!name.trim()) return;
    setSub(true);
    const r = await VoaisAPI.post("/api/flows", { name: name.trim(), description: desc.trim() || null, template: template || null });
    setSub(false);
    if (r.ok && r.data?.flowId) onCreated(r.data.flowId);
    else alert(r.data?.msg || "Failed to create flow.");
  };

  return (
    <Modal title="New flow" onClose={onClose} width={520}>
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <Field label="Flow name *">
          <input className="input" autoFocus placeholder="e.g. Lead Qualification" value={name} onChange={e => setName(e.target.value)}/>
        </Field>
        <Field label="Description">
          <textarea className="textarea" rows={2} placeholder="What this flow does..." value={desc} onChange={e => setDesc(e.target.value)}/>
        </Field>
        <Field label="Start from template" hint="Or leave blank for a minimal start/end flow">
          <select className="select" value={template} onChange={e => setTemplate(e.target.value)}>
            <option value="">Blank (start + greeting + end)</option>
            {templates.map(t => <option key={t.id} value={t.id}>{t.name} ({t.nodeCount} nodes)</option>)}
          </select>
        </Field>
        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <Btn kind="ghost" onClick={onClose}>Cancel</Btn>
          <Btn kind="primary" onClick={submit} disabled={submitting || !name.trim()}>
            {submitting ? "Creating..." : "Create flow"}
          </Btn>
        </div>
      </div>
    </Modal>
  );
}


// ============================================================================
// FlowCanvas — the actual SVG drag-drop editor
// ============================================================================
function FlowCanvas({ flowId, onBack }) {
  const [flow, setFlow]           = React.useState(null);
  const [nodes, setNodes]         = React.useState([]);
  const [edges, setEdges]         = React.useState([]);
  const [selected, setSelected]   = React.useState(null); // node_key
  const [loading, setLoading]     = React.useState(true);
  const [saving, setSaving]       = React.useState(false);
  const [validRes, setValidRes]   = React.useState(null);
  const [showPalette, setShowPalette] = React.useState(false);
  const [connecting, setConnecting] = React.useState(null); // { from_key, mouseX, mouseY }

  const svgRef    = React.useRef(null);
  const saveTimer = React.useRef(null);

  // Pan / zoom state
  const [pan, setPan]   = React.useState({ x: 0, y: 0 });
  const [zoom, setZoom] = React.useState(1);
  const panStart = React.useRef(null);

  // Load flow
  React.useEffect(() => {
    (async () => {
      setLoading(true);
      const r = await VoaisAPI.get("/api/flows/" + flowId);
      if (r.ok && r.data?.ok) {
        setFlow(r.data.flow);
        setNodes(r.data.flow.nodes || []);
        setEdges(r.data.flow.edges || []);
      }
      setLoading(false);
    })();
  }, [flowId]);

  // Auto-save (debounced)
  const triggerSave = React.useCallback((n, e) => {
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(async () => {
      setSaving(true);
      await VoaisAPI.put("/api/flows/" + flowId + "/canvas", { nodes: n, edges: e });
      setSaving(false);
    }, 1200);
  }, [flowId]);

  // Update nodes and trigger save
  const updateNodes = (fn) => {
    setNodes(prev => {
      const next = fn(prev);
      triggerSave(next, edges);
      return next;
    });
  };

  const updateEdges = (fn) => {
    setEdges(prev => {
      const next = fn(prev);
      triggerSave(nodes, next);
      return next;
    });
  };

  // Add node
  const addNode = (type) => {
    const maxKey = nodes.reduce((m, n) => {
      const num = parseInt(n.node_key.replace("n", ""), 10);
      return num > m ? num : m;
    }, 0);
    const key = "n" + (maxKey + 1);
    const meta = NODE_TYPES[type] || NODE_TYPES.greeting;
    const newNode = {
      node_key: key, type,
      pos_x: 300 + Math.random() * 100 - 50,
      pos_y: 200 + Math.random() * 100,
      title: meta.label,
      subtitle: "",
      data: null,
    };
    updateNodes(prev => [...prev, newNode]);
    setSelected(key);
    setShowPalette(false);
  };

  // Delete node
  const deleteNode = (key) => {
    updateNodes(prev => prev.filter(n => n.node_key !== key));
    setEdges(prev => {
      const next = prev.filter(e => e.from_key !== key && e.to_key !== key);
      triggerSave(nodes.filter(n => n.node_key !== key), next);
      return next;
    });
    if (selected === key) setSelected(null);
  };

  // Delete edge
  const deleteEdge = (idx) => {
    updateEdges(prev => prev.filter((_, i) => i !== idx));
  };

  // Node drag
  const dragRef = React.useRef(null);
  const handleNodeMouseDown = (e, nodeKey) => {
    if (e.button !== 0) return;
    e.stopPropagation();
    const svg = svgRef.current;
    const pt = svgPoint(svg, e);
    const node = nodes.find(n => n.node_key === nodeKey);
    if (!node) return;
    dragRef.current = { key: nodeKey, startX: pt.x - node.pos_x, startY: pt.y - node.pos_y };
    setSelected(nodeKey);

    const onMove = (ev) => {
      const mp = svgPoint(svg, ev);
      const dx = mp.x - dragRef.current.startX;
      const dy = mp.y - dragRef.current.startY;
      setNodes(prev => prev.map(n => n.node_key === nodeKey ? { ...n, pos_x: Math.round(dx), pos_y: Math.round(dy) } : n));
    };
    const onUp = () => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
      // Save after drag
      setNodes(prev => { triggerSave(prev, edges); return prev; });
    };
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };

  // Port connection drag
  const handlePortMouseDown = (e, fromKey) => {
    e.stopPropagation();
    const svg = svgRef.current;
    const pt = svgPoint(svg, e);
    setConnecting({ from_key: fromKey, mx: pt.x, my: pt.y });

    const onMove = (ev) => {
      const mp = svgPoint(svg, ev);
      setConnecting(prev => prev ? { ...prev, mx: mp.x, my: mp.y } : null);
    };
    const onUp = (ev) => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
      // Check if dropped on an input port
      const mp = svgPoint(svg, ev);
      const target = nodes.find(n => {
        const meta = NODE_TYPES[n.type];
        if (!meta?.hasIn) return false;
        const px = n.pos_x + NODE_W / 2, py = n.pos_y;
        return Math.abs(mp.x - px) < 20 && Math.abs(mp.y - py) < 20;
      });
      if (target && target.node_key !== fromKey) {
        // Add edge if not duplicate
        const exists = edges.some(e => e.from_key === fromKey && e.to_key === target.node_key);
        if (!exists) {
          updateEdges(prev => [...prev, { from_key: fromKey, to_key: target.node_key, label: null, condition: null }]);
        }
      }
      setConnecting(null);
    };
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };

  // Canvas pan
  const handleCanvasMouseDown = (e) => {
    if (e.target !== svgRef.current && !e.target.classList.contains("flow-bg")) return;
    setSelected(null);
    panStart.current = { x: e.clientX - pan.x, y: e.clientY - pan.y };
    const onMove = (ev) => {
      setPan({ x: ev.clientX - panStart.current.x, y: ev.clientY - panStart.current.y });
    };
    const onUp = () => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
    };
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };

  // Zoom
  const handleWheel = (e) => {
    e.preventDefault();
    const delta = e.deltaY > 0 ? -0.08 : 0.08;
    setZoom(prev => Math.min(2, Math.max(0.3, prev + delta)));
  };

  // Publish
  const handlePublish = async () => {
    setSaving(true);
    // Save first
    await VoaisAPI.put("/api/flows/" + flowId + "/canvas", { nodes, edges });
    const r = await VoaisAPI.post("/api/flows/" + flowId + "/publish");
    setSaving(false);
    if (r.ok) alert("Published v" + r.data.version);
    else alert(r.data?.msg || "Publish failed.");
  };

  // Validate
  const handleValidate = async () => {
    await VoaisAPI.put("/api/flows/" + flowId + "/canvas", { nodes, edges });
    const r = await VoaisAPI.post("/api/flows/" + flowId + "/validate");
    if (r.ok) setValidRes(r.data);
  };

  // Fit view
  const fitView = () => {
    if (!nodes.length) return;
    const xs = nodes.map(n => n.pos_x), ys = nodes.map(n => n.pos_y);
    const minX = Math.min(...xs), maxX = Math.max(...xs) + NODE_W;
    const minY = Math.min(...ys), maxY = Math.max(...ys) + NODE_H;
    const cw = svgRef.current?.parentElement?.clientWidth || 800;
    const ch = svgRef.current?.parentElement?.clientHeight || 500;
    const z = Math.min(cw / (maxX - minX + 100), ch / (maxY - minY + 100), 1.5);
    setZoom(Math.max(0.3, Math.min(1.2, z)));
    setPan({ x: -minX * z + 50, y: -minY * z + 30 });
  };

  if (loading) return <DashboardSkeleton/>;

  const selectedNode = nodes.find(n => n.node_key === selected);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 0, height: "calc(100vh - 140px)" }}>
      {/* Toolbar */}
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 0", flexWrap: "wrap" }}>
        <Btn kind="ghost" size="sm" icon={<I.arrow_left size={13}/>} onClick={onBack}>Flows</Btn>
        <div style={{ width: 1, height: 20, background: "var(--line)" }}/>
        <span style={{ fontSize: 15, fontWeight: 600 }}>{flow?.name || "Flow"}</span>
        <Badge tone={flow?.status === "published" ? "green" : "gray"} dot>{flow?.status}</Badge>
        <div style={{ flex: 1 }}/>
        {saving && <span style={{ fontSize: 12, color: "var(--ink-3)" }}>Saving...</span>}
        <Btn kind="ghost" size="sm" icon={<I.plus size={13}/>} onClick={() => setShowPalette(!showPalette)}>Add node</Btn>
        <Btn kind="ghost" size="sm" icon={<I.check size={13}/>} onClick={handleValidate}>Validate</Btn>
        <Btn kind="ghost" size="sm" onClick={fitView}>Fit</Btn>
        <Btn kind="primary" size="sm" icon={<I.zap size={13}/>} onClick={handlePublish}>Publish</Btn>
      </div>

      {/* Validation results */}
      {validRes && (
        <div style={{ padding: "8px 12px", background: validRes.valid ? "var(--ok-soft, rgba(0,180,80,0.1))" : "var(--err-soft, rgba(220,40,40,0.1))", borderRadius: 8, fontSize: 12, marginBottom: 8 }}>
          <div style={{ fontWeight: 600, marginBottom: 4 }}>{validRes.valid ? "Flow is valid" : "Validation issues found"}</div>
          {validRes.errors?.map((e, i) => <div key={i} style={{ color: "var(--err)" }}>Error: {e}</div>)}
          {validRes.warnings?.map((w, i) => <div key={i} style={{ color: "var(--warn)" }}>Warning: {w}</div>)}
          <Btn kind="ghost" size="sm" onClick={() => setValidRes(null)} style={{ marginTop: 4 }}>Dismiss</Btn>
        </div>
      )}

      <div style={{ display: "flex", flex: 1, gap: 0, minHeight: 0 }}>
        {/* Node palette */}
        {showPalette && (
          <div style={{ width: 180, borderRight: "1px solid var(--line)", padding: "8px", overflowY: "auto", background: "var(--surface)", flexShrink: 0 }}>
            <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8, color: "var(--ink-3)" }}>Node palette</div>
            {Object.entries(NODE_TYPES).map(([type, meta]) => (
              <div key={type} onClick={() => addNode(type)} style={{
                display: "flex", alignItems: "center", gap: 8, padding: "8px 10px",
                borderRadius: 8, cursor: "pointer", marginBottom: 4, fontSize: 12,
                background: "var(--surface-2)",
              }}>
                <div style={{ width: 10, height: 10, borderRadius: 3, background: meta.color, flexShrink: 0 }}/>
                {meta.label}
              </div>
            ))}
          </div>
        )}

        {/* SVG Canvas */}
        <div style={{ flex: 1, position: "relative", overflow: "hidden", background: "var(--surface-2)", borderRadius: 8 }}
             onWheel={handleWheel}>
          <svg ref={svgRef} width="100%" height="100%"
            style={{ cursor: connecting ? "crosshair" : "grab" }}
            onMouseDown={handleCanvasMouseDown}>

            {/* Dot grid background */}
            <defs>
              <pattern id="flowgrid" width={24 * zoom} height={24 * zoom} patternUnits="userSpaceOnUse"
                       x={pan.x % (24 * zoom)} y={pan.y % (24 * zoom)}>
                <circle cx={12 * zoom} cy={12 * zoom} r={0.8} fill="var(--ink-3)" opacity="0.2"/>
              </pattern>
              <marker id="flowArrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                <path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
              </marker>
            </defs>
            <rect className="flow-bg" width="100%" height="100%" fill="url(#flowgrid)"/>

            <g transform={`translate(${pan.x}, ${pan.y}) scale(${zoom})`}>
              {/* Edges */}
              {edges.map((e, i) => {
                const fromNode = nodes.find(n => n.node_key === e.from_key);
                const toNode   = nodes.find(n => n.node_key === e.to_key);
                if (!fromNode || !toNode) return null;
                const x1 = fromNode.pos_x + NODE_W / 2;
                const y1 = fromNode.pos_y + NODE_H;
                const x2 = toNode.pos_x + NODE_W / 2;
                const y2 = toNode.pos_y;
                const cy1 = y1 + Math.abs(y2 - y1) * 0.4;
                const cy2 = y2 - Math.abs(y2 - y1) * 0.4;
                return (
                  <g key={i}>
                    <path d={`M${x1} ${y1} C${x1} ${cy1}, ${x2} ${cy2}, ${x2} ${y2}`}
                      fill="none" stroke="var(--ink-3)" strokeWidth="1.5" opacity="0.5"
                      markerEnd="url(#flowArrow)"
                      style={{ cursor: "pointer" }}
                      onClick={() => { if (confirm("Delete this edge?")) deleteEdge(i); }}
                    />
                    {e.label && (
                      <text x={(x1 + x2) / 2} y={(y1 + y2) / 2 - 8}
                        textAnchor="middle" fontSize="11" fill="var(--ink-3)"
                        fontFamily="var(--font-sans, sans-serif)" fontWeight="500">
                        {e.label}
                      </text>
                    )}
                  </g>
                );
              })}

              {/* Connecting line (while dragging) */}
              {connecting && (
                <line x1={nodes.find(n => n.node_key === connecting.from_key)?.pos_x + NODE_W / 2 || 0}
                      y1={(nodes.find(n => n.node_key === connecting.from_key)?.pos_y || 0) + NODE_H}
                      x2={connecting.mx} y2={connecting.my}
                      stroke="var(--accent)" strokeWidth="2" strokeDasharray="6 4" opacity="0.7"/>
              )}

              {/* Nodes */}
              {nodes.map(n => {
                const meta = NODE_TYPES[n.type] || NODE_TYPES.greeting;
                const isSel = selected === n.node_key;
                return (
                  <g key={n.node_key} transform={`translate(${n.pos_x}, ${n.pos_y})`}
                    onMouseDown={e => handleNodeMouseDown(e, n.node_key)}
                    style={{ cursor: "grab" }}>
                    {/* Shadow / selection */}
                    {isSel && <rect x={-3} y={-3} width={NODE_W + 6} height={NODE_H + 6} rx={14} fill="none"
                      stroke="var(--accent)" strokeWidth="2" strokeDasharray="6 3" opacity="0.6"/>}
                    {/* Body */}
                    <rect x={0} y={0} width={NODE_W} height={NODE_H} rx={10}
                      fill="var(--surface)" stroke={meta.color} strokeWidth={isSel ? 2 : 1} opacity="0.95"/>
                    {/* Color accent bar */}
                    <rect x={0} y={0} width={6} height={NODE_H} rx={3} fill={meta.color} opacity="0.8"/>
                    {/* Title */}
                    <text x={20} y={24} fontSize="13" fontWeight="600" fill="var(--ink-1)"
                      fontFamily="var(--font-sans, sans-serif)">
                      {(n.title || meta.label).slice(0, 22)}
                    </text>
                    {/* Subtitle */}
                    {n.subtitle && (
                      <text x={20} y={44} fontSize="11" fill="var(--ink-3)"
                        fontFamily="var(--font-sans, sans-serif)">
                        {n.subtitle.slice(0, 28)}
                      </text>
                    )}
                    {/* Type badge */}
                    <text x={NODE_W - 10} y={24} fontSize="10" fill={meta.color} textAnchor="end"
                      fontFamily="var(--font-sans, sans-serif)" fontWeight="500">
                      {n.type}
                    </text>
                    {/* Input port */}
                    {meta.hasIn && (
                      <circle cx={NODE_W / 2} cy={0} r={PORT_R}
                        fill="var(--surface)" stroke={meta.color} strokeWidth="2"
                        style={{ cursor: "pointer" }}/>
                    )}
                    {/* Output port */}
                    {meta.hasOut && (
                      <circle cx={NODE_W / 2} cy={NODE_H} r={PORT_R}
                        fill="var(--surface)" stroke={meta.color} strokeWidth="2"
                        style={{ cursor: "crosshair" }}
                        onMouseDown={e => handlePortMouseDown(e, n.node_key)}/>
                    )}
                  </g>
                );
              })}
            </g>
          </svg>

          {/* Zoom indicator */}
          <div style={{ position: "absolute", bottom: 12, right: 12, fontSize: 11, color: "var(--ink-3)",
            background: "var(--surface)", padding: "4px 10px", borderRadius: 6, opacity: 0.8 }}>
            {Math.round(zoom * 100)}%
          </div>
        </div>

        {/* Right panel — node config */}
        {selectedNode && (
          <div style={{ width: 260, borderLeft: "1px solid var(--line)", padding: 12, overflowY: "auto", background: "var(--surface)", flexShrink: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <div style={{ width: 10, height: 10, borderRadius: 3, background: (NODE_TYPES[selectedNode.type] || {}).color || "#888" }}/>
              <span style={{ fontSize: 13, fontWeight: 600, flex: 1 }}>{selectedNode.type}</span>
              <button className="topbar-icon-btn" onClick={() => setSelected(null)}><I.x size={13}/></button>
            </div>

            <Field label="Title">
              <input className="input" value={selectedNode.title || ""}
                onChange={e => updateNodes(prev => prev.map(n => n.node_key === selected ? { ...n, title: e.target.value } : n))}/>
            </Field>
            <div style={{ height: 10 }}/>
            <Field label="Subtitle">
              <input className="input" value={selectedNode.subtitle || ""}
                onChange={e => updateNodes(prev => prev.map(n => n.node_key === selected ? { ...n, subtitle: e.target.value } : n))}/>
            </Field>

            {/* Type-specific config */}
            {(selectedNode.type === "greeting" || selectedNode.type === "pitch") && (
              <div style={{ marginTop: 12 }}>
                <Field label="Script" hint="What the AI says in this step">
                  <textarea className="textarea" rows={4}
                    value={selectedNode.data?.script || ""}
                    onChange={e => updateNodes(prev => prev.map(n => n.node_key === selected
                      ? { ...n, data: { ...n.data, script: e.target.value } } : n))}/>
                </Field>
              </div>
            )}

            {selectedNode.type === "listen" && (
              <div style={{ marginTop: 12 }}>
                <Field label="Silence timeout (ms)" hint="How long to wait for speech">
                  <input className="input" type="number" value={selectedNode.data?.silence_ms || 1500}
                    onChange={e => updateNodes(prev => prev.map(n => n.node_key === selected
                      ? { ...n, data: { ...n.data, silence_ms: Number(e.target.value) } } : n))}/>
                </Field>
              </div>
            )}

            {(selectedNode.type === "intent" || selectedNode.type === "branch") && (
              <div style={{ marginTop: 12 }}>
                <Field label="Branch labels" hint="One per line — each becomes an output edge label">
                  <textarea className="textarea" rows={3}
                    value={selectedNode.data?.branches?.join("\n") || "Interested\nNot interested"}
                    onChange={e => updateNodes(prev => prev.map(n => n.node_key === selected
                      ? { ...n, data: { ...n.data, branches: e.target.value.split("\n").filter(Boolean) } } : n))}/>
                </Field>
              </div>
            )}

            {selectedNode.type === "transfer" && (
              <div style={{ marginTop: 12 }}>
                <Field label="Transfer to" hint="Phone number or extension">
                  <input className="input" value={selectedNode.data?.transfer_to || ""}
                    onChange={e => updateNodes(prev => prev.map(n => n.node_key === selected
                      ? { ...n, data: { ...n.data, transfer_to: e.target.value } } : n))}/>
                </Field>
              </div>
            )}

            <div style={{ marginTop: 16 }}>
              <Btn kind="ghost" size="sm" style={{ color: "var(--err)" }}
                icon={<I.trash size={13}/>}
                onClick={() => deleteNode(selected)}>Delete node</Btn>
            </div>

            {/* Connected edges */}
            <div style={{ marginTop: 16, fontSize: 12 }}>
              <div style={{ fontWeight: 600, color: "var(--ink-3)", marginBottom: 6 }}>Edges</div>
              {edges.filter(e => e.from_key === selected || e.to_key === selected).map((e, i) => {
                const other = e.from_key === selected ? e.to_key : e.from_key;
                const dir = e.from_key === selected ? "out" : "in";
                const otherNode = nodes.find(n => n.node_key === other);
                return (
                  <div key={i} style={{ padding: "4px 0", display: "flex", alignItems: "center", gap: 6, color: "var(--ink-3)" }}>
                    <span>{dir === "out" ? "→" : "←"}</span>
                    <span style={{ flex: 1 }}>{otherNode?.title || other}</span>
                    {e.label && <Badge tone="gray">{e.label}</Badge>}
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ── SVG coordinate helper ───────────────────────────────────────────────
function svgPoint(svg, evt) {
  const rect = svg.getBoundingClientRect();
  // Convert to SVG coord space accounting for pan/zoom
  // We read pan/zoom from the transform attribute of the inner <g>
  const g = svg.querySelector("g[transform]");
  if (!g) return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };
  const ctm = g.getScreenCTM();
  if (!ctm) return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };
  return {
    x: (evt.clientX - ctm.e) / ctm.a,
    y: (evt.clientY - ctm.f) / ctm.d,
  };
}


// Expose
window.FlowBuilderScreen = FlowBuilderScreen;
