/* global React, Icon, UI, DATA */
const { useState: useBState, useRef: useBRef, useEffect: useBEffect, useCallback } = React;

const SUBTYPE_LABEL = {
  manual: "Manual run", file_uploaded: "File uploaded", email_received: "Email received", folder_updated: "Folder updated", scheduled: "Scheduled run", api_request: "API request", webhook: "Webhook received",
  file_upload: "Upload file", gmail: "Read email attachments", gdrive: "Read Google Drive folder", api_input: "Receive from API", db_query: "Query database",
  ocr: "OCR document", field_extraction: "Extract fields", table_extraction: "Extract tables", field_selection: "Select fields", field_mapping: "Map fields",
  generate_csv: "Generate CSV", generate_excel: "Generate Excel", generate_docx: "Generate Word", generate_pdf: "Generate PDF", pdf_to_excel: "Convert PDF to Excel", pdf_to_pdf: "Transform PDF layout",
  translate: "Translate", transliterate: "Transliterate", summarize: "Summarize", split_pages: "Split pages", generate_pptx: "Generate PowerPoint", calculate: "Calculate values", detect_duplicates: "Detect duplicates", consolidate: "Consolidate documents",
  condition: "Conditional branch", review: "Human review", email_send: "Send email", google_drive_upload: "Upload to Google Drive", onedrive_upload: "Upload to OneDrive", db_insert: "Insert database record", api_post: "Post to ERP / CRM API", webhook_send: "Send webhook", download: "Downloadable artifact", wait: "Wait", merge: "Merge branches", error_handler: "Error handler",
};
const MODE_LABEL = { configuration_only: "Configuration only", runtime: "Runtime", configuration_and_runtime: "Config + runtime", human_review_when_low_confidence: "Review if low confidence" };

const NODE_W = 196, NODE_H = 64;

function WorkflowBuilder({ navigate, toast, params }) {
  const proj = DATA.projects.find((p) => p.id === params?.id) || DATA.projects[0];
  const seeded = params?.id === "proj_po" || !params?.id;
  const [nodes, setNodes] = useBState(() => seeded ? DATA.heroNodes.map((n) => ({ ...n })) : []);
  const [edges, setEdges] = useBState(() => seeded ? DATA.heroEdges.map((e) => ({ ...e })) : []);
  const [selected, setSelected] = useBState(null);
  const [pan, setPan] = useBState({ x: 24, y: 40 });
  const [zoom, setZoom] = useBState(0.78);
  const [drawer, setDrawer] = useBState({ open: false, tab: "validation" });
  const [issues, setIssues] = useBState([]);
  const [test, setTest] = useBState({ state: "idle", current: -1, statuses: {} });
  const [palOpen, setPalOpen] = useBState(true);
  const [ghost, setGhost] = useBState(null); // {item, x, y}
  const [tempEdge, setTempEdge] = useBState(null); // {from, x, y}
  const [status, setStatus] = useBState(proj.status === "active" ? "active" : "draft");

  const canvasRef = useBRef(null);
  const drag = useBRef(null);

  const screenToContent = (cx, cy) => {
    const r = canvasRef.current.getBoundingClientRect();
    return { x: (cx - r.left - pan.x) / zoom, y: (cy - r.top - pan.y) / zoom };
  };

  // ---- global pointer handling ----
  useBEffect(() => {
    const move = (e) => {
      if (ghost) { setGhost((g) => g && { ...g, x: e.clientX, y: e.clientY }); }
      if (tempEdge) { const p = screenToContent(e.clientX, e.clientY); setTempEdge((t) => t && { ...t, x: p.x, y: p.y }); }
      const d = drag.current;
      if (!d) return;
      if (d.mode === "pan") { setPan({ x: d.startPan.x + (e.clientX - d.sx), y: d.startPan.y + (e.clientY - d.sy) }); }
      else if (d.mode === "node") { const p = screenToContent(e.clientX, e.clientY); setNodes((ns) => ns.map((n) => n.id === d.id ? { ...n, x: p.x - d.off.x, y: p.y - d.off.y } : n)); }
    };
    const up = (e) => {
      if (ghost) {
        const r = canvasRef.current.getBoundingClientRect();
        if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
          const p = screenToContent(e.clientX, e.clientY);
          addNode(ghost.item, p.x - NODE_W / 2, p.y - NODE_H / 2);
        }
        setGhost(null);
      }
      if (tempEdge) setTempEdge(null);
      drag.current = null;
    };
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
    return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
  }, [ghost, tempEdge, pan, zoom]);

  const addNode = (item, x, y) => {
    const id = item.type + "_" + Math.random().toString(36).slice(2, 7);
    const node = { id, type: item.type, subtype: item.subtype, label: SUBTYPE_LABEL[item.subtype] || item.label, x, y, config: {}, mode: item.type === "trigger" ? "runtime" : item.subtype === "field_selection" || item.subtype === "field_mapping" ? "configuration_only" : "runtime" };
    if (item.type === "processing") node.agent = ({ ocr: "OCR Agent", field_extraction: "Extraction Agent", translate: "Translation Agent", transliterate: "Transliteration Agent", summarize: "Summarization Agent", calculate: "Calculation Agent", detect_duplicates: "Validation Agent", generate_pptx: "Presentation Agent" })[item.subtype] || "Transformation Agent";
    setNodes((ns) => [...ns, node]);
    setSelected(id);
    setTest({ state: "idle", current: -1, statuses: {} });
  };
  const deleteNode = (id) => { setNodes((ns) => ns.filter((n) => n.id !== id)); setEdges((es) => es.filter((e) => e.from !== id && e.to !== id)); if (selected === id) setSelected(null); };
  const updateNode = (id, patch) => setNodes((ns) => ns.map((n) => n.id === id ? { ...n, ...patch, config: patch.config ? { ...n.config, ...patch.config } : n.config } : n));

  const startNodeDrag = (e, n) => {
    e.stopPropagation();
    setSelected(n.id);
    const p = screenToContent(e.clientX, e.clientY);
    drag.current = { mode: "node", id: n.id, off: { x: p.x - n.x, y: p.y - n.y } };
  };
  const startPan = (e) => { if (e.target === canvasRef.current || e.target.dataset.bg) { setSelected(null); drag.current = { mode: "pan", sx: e.clientX, sy: e.clientY, startPan: { ...pan } }; } };
  const startEdge = (e, n) => { e.stopPropagation(); const p = screenToContent(e.clientX, e.clientY); setTempEdge({ from: n.id, x: p.x, y: p.y }); };
  const completeEdge = (e, target) => {
    if (!tempEdge) return; e.stopPropagation();
    if (tempEdge.from !== target.id && !edges.some((ed) => ed.from === tempEdge.from && ed.to === target.id)) {
      setEdges((es) => [...es, { from: tempEdge.from, to: target.id }]);
    }
    setTempEdge(null);
  };

  const validate = () => {
    const iss = [];
    if (!nodes.some((n) => n.type === "trigger")) iss.push({ sev: "error", msg: "Workflow needs at least one trigger node." });
    if (!nodes.some((n) => n.type === "input")) iss.push({ sev: "error", msg: "Workflow needs at least one input source." });
    if (!nodes.some((n) => n.type === "output")) iss.push({ sev: "warning", msg: "No output node — results won't be delivered anywhere." });
    const connected = new Set(); edges.forEach((e) => { connected.add(e.from); connected.add(e.to); });
    nodes.forEach((n) => { if (nodes.length > 1 && !connected.has(n.id)) iss.push({ sev: "warning", msg: `"${n.label}" is not connected to the workflow.` }); });
    const drive = nodes.find((n) => n.subtype === "google_drive_upload");
    if (drive) iss.push({ sev: "error", msg: "Google Drive connector authorization expired — reconnect before activating." });
    const sel = nodes.find((n) => n.subtype === "field_selection");
    if (sel) iss.push({ sev: "info", msg: `"Select configured fields" runs in configuration only — 4 fields are saved for runtime.` });
    if (iss.filter((i) => i.sev === "error").length === 0) iss.push({ sev: "ok", msg: "All required checks passed. Workflow can be activated." });
    setIssues(iss);
    setDrawer({ open: true, tab: "validation" });
  };

  const runTest = () => {
    const order = topoOrder(nodes, edges);
    setDrawer({ open: true, tab: "logs" });
    setTest({ state: "running", current: 0, statuses: {} });
    let i = 0;
    const tick = () => {
      if (i >= order.length) { setTest((t) => ({ ...t, state: "done", current: -1 })); return; }
      const id = order[i];
      setTest((t) => ({ ...t, current: i, statuses: { ...t.statuses, [id]: "running" } }));
      setTimeout(() => { setTest((t) => ({ ...t, statuses: { ...t.statuses, [id]: "completed" } })); i++; tick(); }, 520);
    };
    tick();
  };

  const selNode = nodes.find((n) => n.id === selected);
  const errorCount = issues.filter((i) => i.sev === "error").length;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100vh", width: "100vw", background: "var(--ef-bg)", overflow: "hidden" }}>
      {/* toolbar */}
      <div style={{ height: 56, flexShrink: 0, borderBottom: "1px solid var(--ef-border)", display: "flex", alignItems: "center", padding: "0 16px", gap: 14, background: "var(--ef-bg)" }}>
        <button className="btn btn-ghost btn-sm" onClick={() => navigate("project", { id: proj.id })}><Icon name="arrow-left" size={16} /> Back</button>
        <div style={{ width: 1, height: 24, background: "var(--ef-border)" }} />
        <div style={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <span style={{ fontWeight: 800, fontSize: 15, color: "var(--ef-fg-1)", whiteSpace: "nowrap" }}>{proj.workflow}</span>
            <UI.StatusBadge status={status} size="sm" />
          </div>
          <span className="muted" style={{ fontSize: 11.5 }}><Icon name="check" size={11} color="var(--ef-success)" style={{ display: "inline", verticalAlign: "-1px" }} /> Saved · version 1</span>
        </div>
        <div style={{ flex: 1 }} />
        <button className="btn btn-secondary btn-sm" onClick={validate}><Icon name="shield-check" size={15} /> Validate{errorCount ? <span className="badge badge-solid" style={{ background: "var(--ef-danger)", color: "#fff", fontSize: 10, padding: "1px 6px" }}>{errorCount}</span> : null}</button>
        <button className="btn btn-secondary btn-sm" onClick={runTest} disabled={test.state === "running"}>{test.state === "running" ? <><Icon name="loader" size={15} className="spin" /> Testing…</> : <><Icon name="play" size={15} /> Test workflow</>}</button>
        <button className="btn btn-primary btn-sm" onClick={() => toast("Workflow run queued", "success")}><Icon name="zap" size={15} /> Run now</button>
        <button className="btn btn-accent btn-sm" onClick={() => { setStatus((s) => s === "active" ? "paused" : "active"); toast(status === "active" ? "Workflow paused" : "Workflow activated", "success"); }}>{status === "active" ? "Pause" : "Activate"}</button>
        <button className="btn btn-ghost btn-icon btn-sm"><Icon name="more-horizontal" size={18} /></button>
      </div>

      <div style={{ flex: 1, display: "flex", minHeight: 0 }}>
        {/* palette */}
        <div style={{ width: palOpen ? 224 : 0, flexShrink: 0, borderRight: palOpen ? "1px solid var(--ef-border)" : 0, background: "var(--ef-bone-2)", overflowY: "auto", transition: "width 160ms", overflowX: "hidden" }}>
          <div style={{ padding: "12px 14px 8px", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
            <span className="eyebrow">Add nodes</span>
          </div>
          {Object.entries(DATA.palette).map(([group, items]) => (
            <div key={group} style={{ padding: "0 10px 10px" }}>
              <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ef-fg-4)", padding: "8px 6px 4px" }}>{group}</div>
              {items.map((it) => {
                const m = UI.NODE_TYPES[it.type];
                return (
                  <div key={it.subtype} onPointerDown={(e) => { e.preventDefault(); setGhost({ item: it, x: e.clientX, y: e.clientY }); }}
                    style={{ display: "flex", alignItems: "center", gap: 9, padding: "7px 8px", borderRadius: 7, cursor: "grab", marginBottom: 2, background: "var(--ef-bg-raised)", border: "1px solid var(--ef-border)" }}
                    title="Drag onto the canvas">
                    <span style={{ width: 6, height: 6, borderRadius: 999, background: m.color, flexShrink: 0 }} />
                    <Icon name={m.icon} size={14} color="var(--ef-fg-3)" />
                    <span style={{ fontSize: 12, fontWeight: 600, color: "var(--ef-fg-1)", lineHeight: 1.2 }}>{it.label}</span>
                  </div>
                );
              })}
            </div>
          ))}
        </div>

        {/* canvas */}
        <div style={{ flex: 1, position: "relative", overflow: "hidden", background: "var(--ef-bg)", minWidth: 0 }}>
          <div ref={canvasRef} onPointerDown={startPan} style={{ position: "absolute", inset: 0, cursor: drag.current?.mode === "pan" ? "grabbing" : "default",
            backgroundImage: "radial-gradient(var(--ef-ink-100) 1px, transparent 1px)", backgroundSize: `${22 * zoom}px ${22 * zoom}px`, backgroundPosition: `${pan.x}px ${pan.y}px` }}>
            <div data-bg="1" style={{ position: "absolute", inset: 0 }} />
            <div style={{ position: "absolute", left: 0, top: 0, transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, transformOrigin: "0 0" }}>
              {/* edges */}
              <svg style={{ position: "absolute", overflow: "visible", pointerEvents: "none", left: 0, top: 0 }} width="1" height="1">
                {edges.map((e, i) => {
                  const a = nodes.find((n) => n.id === e.from), b = nodes.find((n) => n.id === e.to);
                  if (!a || !b) return null;
                  return <EdgePath key={i} a={a} b={b} active={test.statuses[e.from] === "completed" && test.statuses[e.to]} />;
                })}
                {tempEdge && (() => { const a = nodes.find((n) => n.id === tempEdge.from); return a ? <EdgePath a={a} b={{ x: tempEdge.x - NODE_W, y: tempEdge.y - NODE_H / 2 }} dashed /> : null; })()}
              </svg>
              {/* nodes */}
              {nodes.map((n) => (
                <NodeBox key={n.id} n={n} selected={selected === n.id} testStatus={test.statuses[n.id]}
                  onDragStart={startNodeDrag} onStartEdge={startEdge} onCompleteEdge={completeEdge} edgeActive={!!tempEdge}
                  onSelect={() => setSelected(n.id)} onDelete={() => deleteNode(n.id)} />
              ))}
            </div>
          </div>

          {/* zoom controls */}
          <div style={{ position: "absolute", left: 14, bottom: 14, display: "flex", gap: 6, alignItems: "center", background: "var(--ef-bg-raised)", border: "1px solid var(--ef-border)", borderRadius: 8, padding: 4, boxShadow: "var(--ef-shadow-sm)" }}>
            <button className="btn btn-ghost btn-icon btn-sm" onClick={() => setZoom((z) => Math.max(0.4, +(z - 0.12).toFixed(2)))}><Icon name="minus" size={16} /></button>
            <span className="mono" style={{ fontSize: 12, fontWeight: 600, width: 40, textAlign: "center", color: "var(--ef-fg-2)" }}>{Math.round(zoom * 100)}%</span>
            <button className="btn btn-ghost btn-icon btn-sm" onClick={() => setZoom((z) => Math.min(1.4, +(z + 0.12).toFixed(2)))}><Icon name="plus" size={16} /></button>
            <div style={{ width: 1, height: 18, background: "var(--ef-border)", margin: "0 2px" }} />
            <button className="btn btn-ghost btn-icon btn-sm" title="Reset view" onClick={() => { setZoom(0.78); setPan({ x: 24, y: 40 }); }}><Icon name="maximize" size={15} /></button>
            <button className="btn btn-ghost btn-icon btn-sm" title="Toggle palette" onClick={() => setPalOpen((v) => !v)}><Icon name="panel-right" size={15} /></button>
          </div>

          {nodes.length === 0 && (
            <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none" }}>
              <div style={{ textAlign: "center", color: "var(--ef-fg-4)" }}><Icon name="git-branch" size={34} color="var(--ef-ink-300)" /><div style={{ marginTop: 12, fontSize: 14, fontWeight: 600 }}>Drag a trigger from the left to begin</div></div>
            </div>
          )}
        </div>

        {/* config panel */}
        <div style={{ width: 320, flexShrink: 0, borderLeft: "1px solid var(--ef-border)", background: "var(--ef-bg)", overflowY: "auto" }}>
          {selNode ? <ConfigPanel n={selNode} updateNode={updateNode} deleteNode={deleteNode} toast={toast} />
            : <div style={{ padding: 24, textAlign: "center", color: "var(--ef-fg-4)" }}><div style={{ marginTop: 40 }}><Icon name="sliders-horizontal" size={26} color="var(--ef-ink-300)" /></div><div style={{ marginTop: 12, fontSize: 13.5, fontWeight: 600 }}>Select a node to configure it</div><div style={{ fontSize: 12.5, marginTop: 6, lineHeight: 1.5 }}>Set inputs, connectors, mapping, error handling, and runtime behavior.</div></div>}
        </div>
      </div>

      {/* bottom drawer */}
      <div style={{ flexShrink: 0, borderTop: "1px solid var(--ef-border)", background: "var(--ef-bg)", height: drawer.open ? 196 : 38, transition: "height 160ms", display: "flex", flexDirection: "column" }}>
        <div style={{ display: "flex", alignItems: "center", padding: "0 14px", height: 38, gap: 4, flexShrink: 0 }}>
          {[["validation", "Validation", errorCount], ["logs", "Test logs"], ["output", "Node output"]].map(([id, label, count]) => (
            <button key={id} onClick={() => setDrawer({ open: true, tab: id })} style={{ border: 0, background: "transparent", padding: "8px 12px", fontSize: 12.5, fontWeight: 700, color: drawer.open && drawer.tab === id ? "var(--ef-fg-1)" : "var(--ef-fg-4)", borderBottom: drawer.open && drawer.tab === id ? "2px solid var(--ef-ink-950)" : "2px solid transparent" }}>
              {label}{count ? <span className="badge badge-solid" style={{ marginLeft: 6, background: "var(--ef-danger)", color: "#fff", fontSize: 10, padding: "0 5px" }}>{count}</span> : null}</button>
          ))}
          <div style={{ flex: 1 }} />
          <button className="btn btn-ghost btn-icon btn-sm" onClick={() => setDrawer((d) => ({ ...d, open: !d.open }))}><Icon name={drawer.open ? "chevron-down" : "chevron-up"} size={16} /></button>
        </div>
        {drawer.open && (
          <div style={{ flex: 1, overflowY: "auto", padding: "4px 16px 14px" }}>
            {drawer.tab === "validation" && (issues.length === 0 ? <div className="muted" style={{ fontSize: 13, padding: 8 }}>Run <b>Validate</b> to check the workflow before activating.</div>
              : issues.map((it, i) => <DrawerRow key={i} sev={it.sev} msg={it.msg} />))}
            {drawer.tab === "logs" && (test.state === "idle" ? <div className="muted" style={{ fontSize: 13, padding: 8 }}>Run <b>Test workflow</b> to simulate an execution against a sample document.</div>
              : <div className="mono" style={{ fontSize: 12, lineHeight: 1.9 }}>
                  {topoOrder(nodes, edges).map((id) => { const n = nodes.find((x) => x.id === id); const st = test.statuses[id]; return (
                    <div key={id} style={{ display: "flex", alignItems: "center", gap: 8, color: st === "completed" ? "var(--ef-fg-2)" : st === "running" ? "var(--ef-info)" : "var(--ef-fg-4)" }}>
                      <Icon name={st === "completed" ? "check" : st === "running" ? "loader" : "circle"} size={13} className={st === "running" ? "spin" : ""} color={st === "completed" ? "var(--ef-success)" : "currentColor"} />
                      <span>{n?.label}</span>{st === "completed" && <span className="muted">· done</span>}
                    </div>); })}
                  {test.state === "done" && <div style={{ marginTop: 8, color: "var(--ef-success)", fontWeight: 700 }}>✓ Test passed · 1 file · 2 outputs · 0 errors</div>}
                </div>)}
            {drawer.tab === "output" && (selNode ? <NodeOutputPreview n={selNode} /> : <div className="muted" style={{ fontSize: 13, padding: 8 }}>Select a node to preview its output schema.</div>)}
          </div>
        )}
      </div>

      {ghost && (
        <div style={{ position: "fixed", left: ghost.x, top: ghost.y, transform: "translate(-50%,-50%)", pointerEvents: "none", zIndex: 90, background: "var(--ef-bg-raised)", border: `1.5px solid ${UI.NODE_TYPES[ghost.item.type].color}`, borderRadius: 9, padding: "8px 12px", boxShadow: "var(--ef-shadow-lg)", display: "flex", alignItems: "center", gap: 8, opacity: 0.95 }}>
          <Icon name={UI.NODE_TYPES[ghost.item.type].icon} size={15} color={UI.NODE_TYPES[ghost.item.type].color} />
          <span style={{ fontSize: 12.5, fontWeight: 700, color: "var(--ef-fg-1)" }}>{ghost.item.label}</span>
        </div>
      )}
    </div>
  );
}

function NodeBox({ n, selected, testStatus, onDragStart, onStartEdge, onCompleteEdge, edgeActive, onSelect, onDelete }) {
  const m = UI.NODE_TYPES[n.type];
  const ring = testStatus === "running" ? "var(--ef-info)" : testStatus === "completed" ? "var(--ef-success)" : selected ? "var(--ef-ink-950)" : "var(--ef-border-strong)";
  return (
    <div onPointerDown={(e) => onDragStart(e, n)} onPointerUp={(e) => edgeActive && onCompleteEdge(e, n)}
      style={{ position: "absolute", left: n.x, top: n.y, width: NODE_W, minHeight: NODE_H, background: "var(--ef-bg-raised)", border: `1.5px solid ${ring}`, borderRadius: 10, boxShadow: selected ? "var(--ef-shadow-md)" : "var(--ef-shadow-sm)", cursor: "grab", userSelect: "none", transition: "border-color 120ms, box-shadow 120ms" }}>
      <div style={{ height: 4, background: m.color, borderRadius: "9px 9px 0 0" }} />
      <div style={{ padding: "8px 10px 9px", display: "flex", gap: 9, alignItems: "flex-start" }}>
        <div style={{ width: 26, height: 26, borderRadius: 7, background: "var(--ef-bone-2)", border: "1px solid var(--ef-border)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
          <Icon name={m.icon} size={15} color={m.color} /></div>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ef-fg-4)" }}>{m.label}</div>
          <div style={{ fontSize: 12.5, fontWeight: 700, color: "var(--ef-fg-1)", lineHeight: 1.25, marginTop: 1 }}>{n.label}</div>
          {n.agent && <div className="muted" style={{ fontSize: 10.5, marginTop: 3 }}>{n.agent}</div>}
        </div>
        {selected && <button onPointerDown={(e) => e.stopPropagation()} onClick={onDelete} style={{ border: 0, background: "transparent", padding: 2, cursor: "pointer", flexShrink: 0 }}><Icon name="trash-2" size={13} color="var(--ef-fg-4)" /></button>}
      </div>
      {n.mode && n.mode !== "runtime" && <div style={{ position: "absolute", bottom: -9, left: 10, fontSize: 9, fontWeight: 700, padding: "1px 6px", borderRadius: 999, background: n.mode.includes("configuration") ? "var(--ef-beam-100)" : "var(--ef-bone-3)", color: n.mode.includes("configuration") ? "var(--ef-beam-700)" : "var(--ef-fg-3)", border: "1px solid var(--ef-border)" }}>{MODE_LABEL[n.mode]}</div>}
      {/* ports */}
      {n.type !== "trigger" && <Port side="left" active={edgeActive} />}
      <Port side="right" onPointerDown={(e) => onStartEdge(e, n)} />
    </div>
  );
}
function Port({ side, onPointerDown, active }) {
  return <div onPointerDown={onPointerDown} style={{ position: "absolute", [side]: -7, top: "50%", transform: "translateY(-50%)", width: 14, height: 14, borderRadius: 999, background: "var(--ef-bg-raised)", border: "1.5px solid var(--ef-ink-400)", cursor: side === "right" ? "crosshair" : "default", boxShadow: active && side === "left" ? "0 0 0 4px var(--ef-beam-200)" : "none", zIndex: 2 }} />;
}

function EdgePath({ a, b, dashed, active }) {
  const x1 = a.x + NODE_W, y1 = a.y + NODE_H / 2;
  const x2 = b.x, y2 = b.y + NODE_H / 2;
  const dx = Math.max(40, Math.abs(x2 - x1) * 0.5);
  const d = `M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2} ${y2}`;
  return <>
    <path d={d} fill="none" stroke={active ? "var(--ef-success)" : "var(--ef-ink-300)"} strokeWidth={active ? 2.5 : 1.75} strokeDasharray={dashed ? "5 5" : "0"} />
    {!dashed && <circle cx={x2} cy={y2} r="3" fill={active ? "var(--ef-success)" : "var(--ef-ink-300)"} />}
  </>;
}

function ConfigPanel({ n, updateNode, deleteNode, toast }) {
  const m = UI.NODE_TYPES[n.type];
  return (
    <div style={{ padding: 18 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
        <span style={{ width: 8, height: 8, borderRadius: 999, background: m.color }} />
        <span className="eyebrow">{m.label}</span>
      </div>
      <input value={n.label} onChange={(e) => updateNode(n.id, { label: e.target.value })} className="input" style={{ fontWeight: 700, fontSize: 15, padding: "8px 10px", marginBottom: 16 }} />

      <UI.Field label="Step mode" help="Configuration steps run only during setup; runtime steps run on every execution.">
        <select className="select" value={n.mode} onChange={(e) => updateNode(n.id, { mode: e.target.value })}>
          {Object.entries(MODE_LABEL).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
        </select>
      </UI.Field>

      {n.type === "trigger" && <UI.Field label="Accepted file types"><div style={{ display: "flex", gap: 7, flexWrap: "wrap" }}>{["PDF", "DOCX", "XLSX", "JPG", "PNG"].map((t) => <span key={t} className={`chip ${["PDF"].includes(t) ? "selected" : ""}`}>{t}</span>)}</div></UI.Field>}

      {n.type === "input" && <UI.Field label="Source" help="Where this input reads documents from."><input className="input" defaultValue={n.config.source || "Manual upload / Gmail"} /></UI.Field>}

      {n.type === "processing" && <>
        <UI.Field label="Agent"><div className="card" style={{ padding: "10px 12px", display: "flex", alignItems: "center", gap: 10 }}><Icon name="sparkles" size={16} color="var(--ef-violet)" /><span style={{ fontSize: 13, fontWeight: 600, color: "var(--ef-fg-1)" }}>{n.agent || "Transformation Agent"}</span></div></UI.Field>
        {n.subtype === "field_extraction" && <UI.Field label="Template" help="Match new documents to this saved template."><select className="select"><option>Zara purchase order (v2)</option><option>No template — extract all</option></select></UI.Field>}
        {n.subtype === "field_selection" && <UI.Field label="Selected fields"><div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{["Vendor name", "PO number", "Amount", "Delivery date"].map((f) => <span key={f} className="badge badge-solid" style={{ background: "var(--ef-bone-2)" }}><Icon name="check" size={11} color="var(--ef-success)" />{f}</span>)}</div></UI.Field>}
        {(n.subtype === "translate" || n.subtype === "transliterate") && <UI.Field label="Target language"><select className="select"><option>English</option><option>Bangla</option><option>Spanish</option><option>Arabic</option></select></UI.Field>}
        {n.subtype === "summarize" && <UI.Field label="Prompt"><textarea className="textarea" defaultValue="Summarize for executives." style={{ minHeight: 64 }} /></UI.Field>}
        {(n.subtype === "generate_csv" || n.subtype === "generate_excel") && <UI.Field label="Columns" help="Mapped during template setup."><div className="muted" style={{ fontSize: 12.5 }}>4 columns · A–D</div></UI.Field>}
      </>}

      {n.type === "decision" && <UI.Field label="Condition"><div className="card mono" style={{ padding: "10px 12px", fontSize: 12, lineHeight: 1.6, background: "var(--ef-bone-2)" }}>IF amount &gt; 10000<br />→ Human review<br />ELSE → continue</div></UI.Field>}

      {n.type === "output" && <>
        <UI.Field label="Connector"><div className="card" style={{ padding: "10px 12px", display: "flex", alignItems: "center", gap: 10 }}><Icon name={n.subtype.includes("drive") ? "hard-drive" : n.subtype.includes("email") ? "send" : "webhook"} size={16} color="var(--ef-fg-3)" /><span style={{ fontSize: 13, fontWeight: 600, color: "var(--ef-fg-1)", flex: 1 }}>{n.connector || "Configure"}</span>{n.subtype === "google_drive_upload" && <UI.StatusBadge status="expired" size="sm" />}</div></UI.Field>
        {n.subtype === "google_drive_upload" && <UI.Field label="Destination folder"><input className="input" defaultValue={n.config.folder || "/Purchase Orders/2026"} /></UI.Field>}
        {n.subtype === "email_send" && <UI.Field label="Recipients"><input className="input" defaultValue={n.config.to || "ops@fakirfashion.com"} /></UI.Field>}
      </>}

      <UI.Field label="On error">
        <select className="select"><option>Retry up to 3 times</option><option>Notify and stop</option><option>Move file to error folder</option><option>Continue with remaining files</option></select>
      </UI.Field>

      <button className="btn btn-secondary btn-sm" style={{ width: "100%", marginTop: 4 }} onClick={() => toast("Node test passed", "success")}><Icon name="play" size={14} /> Test this node</button>
      <button className="btn btn-danger btn-sm" style={{ width: "100%", marginTop: 10 }} onClick={() => deleteNode(n.id)}><Icon name="trash-2" size={14} /> Remove node</button>
    </div>
  );
}

function NodeOutputPreview({ n }) {
  const schema = n.type === "processing" && n.subtype === "field_extraction"
    ? { fields: { vendor_name: "string", po_number: "string", amount: "number", delivery_date: "date" }, confidence: 0.94 }
    : n.subtype === "generate_csv" ? { artifact: "po_4417.csv", rows: 1, columns: 4 }
    : { status: "ok", node: n.subtype };
  return <pre className="mono" style={{ margin: 0, fontSize: 12, lineHeight: 1.7, color: "var(--ef-fg-2)" }}>{JSON.stringify(schema, null, 2)}</pre>;
}

function topoOrder(nodes, edges) {
  const incoming = {}; nodes.forEach((n) => (incoming[n.id] = 0));
  edges.forEach((e) => { if (incoming[e.to] != null) incoming[e.to]++; });
  const queue = nodes.filter((n) => incoming[n.id] === 0).map((n) => n.id);
  const seen = new Set(queue); const order = [];
  while (queue.length) {
    const id = queue.shift(); order.push(id);
    edges.filter((e) => e.from === id).forEach((e) => { if (!seen.has(e.to)) { seen.add(e.to); queue.push(e.to); } });
  }
  nodes.forEach((n) => { if (!order.includes(n.id)) order.push(n.id); });
  return order;
}

function DrawerRow({ sev, msg }) {
  const map = { error: ["alert-circle", "var(--ef-danger)"], warning: ["alert-triangle", "var(--ef-warning)"], info: ["info", "var(--ef-info)"], ok: ["check-circle", "var(--ef-success)"] };
  const [ic, c] = map[sev] || map.info;
  return <div style={{ display: "flex", gap: 10, alignItems: "flex-start", padding: "7px 8px", fontSize: 13 }}><Icon name={ic} size={16} color={c} style={{ marginTop: 1 }} /><span style={{ color: "var(--ef-fg-2)" }}>{msg}</span></div>;
}

window.WorkflowBuilder = WorkflowBuilder;
