import {
  Diamond,
  Eraser,
  Hand,
  Minus,
  MousePointer2,
  Plus,
  Spline,
  Square,
  StickyNote,
  Type,
  Circle,
  Play,
  FileText,
  CheckSquare,
  Pencil,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Field, Input, Select, Textarea } from "@/components/ui/input";
import {
  NODE_DEFAULTS,
  STICKY_SWATCHES,
  clamp,
  diamondPoints,
  documentPath,
  edgePath,
  hitNode,
  nearestPort,
  portPoint,
  snap,
  strokeHits,
} from "@/lib/board-geom";
import { cn } from "@/lib/cn";
import { nid } from "@/lib/ids";
import { useWorkspace } from "@/lib/store";
import type { BoardKind, BoardNode, NodeType, Port, Stroke } from "@/lib/types";

type Tool =
  | "select"
  | "pan"
  | "pen"
  | "erase"
  | "sticky"
  | "rect"
  | "ellipse"
  | "text"
  | "start"
  | "process"
  | "decision"
  | "document"
  | "taskcard"
  | "connect";

type Camera = { x: number; y: number; scale: number };

const BOARD_TOOLS: { id: Tool; label: string; icon: typeof Pencil }[] = [
  { id: "select", label: "Select", icon: MousePointer2 },
  { id: "pan", label: "Pan", icon: Hand },
  { id: "pen", label: "Pen", icon: Pencil },
  { id: "erase", label: "Erase", icon: Eraser },
  { id: "sticky", label: "Sticky", icon: StickyNote },
  { id: "rect", label: "Rect", icon: Square },
  { id: "ellipse", label: "Ellipse", icon: Circle },
  { id: "text", label: "Text", icon: Type },
  { id: "connect", label: "Connect", icon: Spline },
];

const FLOW_TOOLS: { id: Tool; label: string; icon: typeof Pencil }[] = [
  { id: "select", label: "Select", icon: MousePointer2 },
  { id: "pan", label: "Pan", icon: Hand },
  { id: "start", label: "Start", icon: Play },
  { id: "process", label: "Process", icon: Square },
  { id: "decision", label: "Decision", icon: Diamond },
  { id: "document", label: "Document", icon: FileText },
  { id: "taskcard", label: "Task", icon: CheckSquare },
  { id: "connect", label: "Connect", icon: Spline },
  { id: "erase", label: "Erase", icon: Eraser },
];

const PLACE_TOOLS: Tool[] = [
  "sticky",
  "rect",
  "ellipse",
  "text",
  "start",
  "process",
  "decision",
  "document",
  "taskcard",
];

export function WorkspaceCanvas({ boardId }: { boardId: string }) {
  const navigate = useNavigate();
  const board = useWorkspace((s) => s.boards.find((b) => b.id === boardId));
  const patchBoard = useWorkspace((s) => s.patchBoard);
  const updateBoard = useWorkspace((s) => s.updateBoard);
  const promoteNode = useWorkspace((s) => s.promoteNode);
  const projects = useWorkspace((s) => s.projects);
  const tasks = useWorkspace((s) => s.tasks);
  const notes = useWorkspace((s) => s.notes);

  const kind: BoardKind = board?.kind ?? "board";
  const tools = kind === "flow" ? FLOW_TOOLS : BOARD_TOOLS;

  const rootRef = useRef<HTMLDivElement>(null);
  const [tool, setTool] = useState<Tool>("select");
  const [camera, setCamera] = useState<Camera>({ x: 40, y: 40, scale: 1 });
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [selectedEdge, setSelectedEdge] = useState<string | null>(null);
  const [editingId, setEditingId] = useState<string | null>(null);
  const [space, setSpace] = useState(false);
  const [currentStroke, setCurrentStroke] = useState<number[] | null>(null);
  const [connect, setConnect] = useState<{
    from: string;
    port: Port;
    x: number;
    y: number;
  } | null>(null);
  const drag = useRef<{
    mode: "pan" | "node" | "resize" | "pen" | "erase";
    id?: string;
    ox: number;
    oy: number;
    nx?: number;
    ny?: number;
    nw?: number;
    nh?: number;
  } | null>(null);

  const toolRef = useRef(tool);
  toolRef.current = tool;
  const cameraRef = useRef(camera);
  cameraRef.current = camera;
  const spaceRef = useRef(space);
  spaceRef.current = space;

  const selected = board?.nodes.find((n) => n.id === selectedId) ?? null;
  const nodesById = useMemo(
    () => Object.fromEntries((board?.nodes ?? []).map((n) => [n.id, n])),
    [board?.nodes],
  );

  const toWorld = useCallback((cx: number, cy: number) => {
    const el = rootRef.current;
    if (!el) return { x: 0, y: 0 };
    const r = el.getBoundingClientRect();
    const cam = cameraRef.current;
    return { x: (cx - r.left - cam.x) / cam.scale, y: (cy - r.top - cam.y) / cam.scale };
  }, []);

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
      if (e.code === "Space") {
        e.preventDefault();
        setSpace(true);
      }
      if (e.key === "Escape") {
        setSelectedId(null);
        setSelectedEdge(null);
        setEditingId(null);
        setConnect(null);
        setTool("select");
      }
      if (e.key === "v" || e.key === "V") setTool("select");
      if (e.key === "h" || e.key === "H") setTool("pan");
      if ((e.key === "Delete" || e.key === "Backspace") && (selectedId || selectedEdge) && board) {
        e.preventDefault();
        if (selectedId) {
          patchBoard(boardId, {
            nodes: board.nodes.filter((n) => n.id !== selectedId),
            edges: board.edges.filter((ed) => ed.from !== selectedId && ed.to !== selectedId),
          });
          setSelectedId(null);
        } else if (selectedEdge) {
          patchBoard(boardId, { edges: board.edges.filter((ed) => ed.id !== selectedEdge) });
          setSelectedEdge(null);
        }
      }
    };
    const onUp = (e: KeyboardEvent) => {
      if (e.code === "Space") setSpace(false);
    };
    window.addEventListener("keydown", onKey);
    window.addEventListener("keyup", onUp);
    return () => {
      window.removeEventListener("keydown", onKey);
      window.removeEventListener("keyup", onUp);
    };
  }, [board, boardId, patchBoard, selectedEdge, selectedId]);

  useEffect(() => {
    const el = rootRef.current;
    if (!el) return;
    const handle = (e: WheelEvent) => {
      e.preventDefault();
      const world = toWorld(e.clientX, e.clientY);
      const r = el.getBoundingClientRect();
      const cam = cameraRef.current;
      const next = clamp(cam.scale * (e.deltaY > 0 ? 0.92 : 1.08), 0.25, 2.8);
      setCamera({
        scale: next,
        x: e.clientX - r.left - world.x * next,
        y: e.clientY - r.top - world.y * next,
      });
    };
    el.addEventListener("wheel", handle, { passive: false });
    return () => el.removeEventListener("wheel", handle);
  }, [toWorld]);

  const placeNode = (type: NodeType, x: number, y: number) => {
    if (!board) return;
    const def = NODE_DEFAULTS[type] ?? { w: 160, h: 72 };
    const node: BoardNode = {
      id: nid("n"),
      type,
      x: snap(x - def.w / 2),
      y: snap(y - def.h / 2),
      w: def.w,
      h: def.h,
      text: type === "start" ? "Start" : type === "text" ? "Label" : type === "sticky" ? "Note" : type,
      fill: def.fill,
    };
    patchBoard(boardId, { nodes: [...board.nodes, node] });
    setSelectedId(node.id);
    setTool("select");
  };

  const onPointerDown = (e: React.PointerEvent) => {
    if (!board) return;
    const world = toWorld(e.clientX, e.clientY);
    const current = spaceRef.current || e.button === 1 || toolRef.current === "pan" ? "pan" : toolRef.current;
    (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);

    if (current === "pan") {
      drag.current = { mode: "pan", ox: e.clientX - camera.x, oy: e.clientY - camera.y };
      return;
    }
    if (current === "pen") {
      setCurrentStroke([world.x, world.y]);
      drag.current = { mode: "pen", ox: world.x, oy: world.y };
      return;
    }
    if (current === "erase") {
      const nextStrokes = board.strokes.filter((s) => !strokeHits(s.points, world.x, world.y, 14));
      const hit = hitNode(board.nodes, world.x, world.y);
      if (hit) {
        patchBoard(boardId, {
          nodes: board.nodes.filter((n) => n.id !== hit.id),
          edges: board.edges.filter((ed) => ed.from !== hit.id && ed.to !== hit.id),
          strokes: nextStrokes,
        });
        if (selectedId === hit.id) setSelectedId(null);
      } else if (nextStrokes.length !== board.strokes.length) {
        patchBoard(boardId, { strokes: nextStrokes });
      }
      drag.current = { mode: "erase", ox: world.x, oy: world.y };
      return;
    }
    if (PLACE_TOOLS.includes(current)) {
      placeNode(current as NodeType, world.x, world.y);
      return;
    }

    const hit = hitNode(board.nodes, world.x, world.y);
    if (current === "connect") {
      if (hit) {
        const port = nearestPort(hit, world.x, world.y);
        setConnect({ from: hit.id, port, x: world.x, y: world.y });
        setSelectedId(hit.id);
      }
      return;
    }

    if (hit) {
      const handle =
        world.x > hit.x + hit.w - 12 && world.y > hit.y + hit.h - 12;
      setSelectedId(hit.id);
      setSelectedEdge(null);
      if (handle) {
        drag.current = {
          mode: "resize",
          id: hit.id,
          ox: world.x,
          oy: world.y,
          nw: hit.w,
          nh: hit.h,
        };
      } else {
        drag.current = {
          mode: "node",
          id: hit.id,
          ox: world.x - hit.x,
          oy: world.y - hit.y,
        };
      }
      return;
    }
    setSelectedId(null);
    setSelectedEdge(null);
    setEditingId(null);
  };

  const onPointerMove = (e: React.PointerEvent) => {
    if (!board) return;
    const world = toWorld(e.clientX, e.clientY);
    if (connect) {
      setConnect({ ...connect, x: world.x, y: world.y });
      return;
    }
    const d = drag.current;
    if (!d) return;
    if (d.mode === "pan") {
      setCamera((c) => ({ ...c, x: e.clientX - d.ox, y: e.clientY - d.oy }));
      return;
    }
    if (d.mode === "pen") {
      setCurrentStroke((pts) => (pts ? [...pts, world.x, world.y] : [world.x, world.y]));
      return;
    }
    if (d.mode === "erase") {
      const nextStrokes = board.strokes.filter((s) => !strokeHits(s.points, world.x, world.y, 14));
      if (nextStrokes.length !== board.strokes.length) patchBoard(boardId, { strokes: nextStrokes });
      return;
    }
    if (d.mode === "node" && d.id) {
      const x = snap(world.x - d.ox);
      const y = snap(world.y - d.oy);
      patchBoard(boardId, {
        nodes: board.nodes.map((n) => (n.id === d.id ? { ...n, x, y } : n)),
      });
      return;
    }
    if (d.mode === "resize" && d.id && d.nw != null && d.nh != null) {
      const node = board.nodes.find((n) => n.id === d.id);
      if (!node) return;
      const w = snap(Math.max(64, d.nw + (world.x - d.ox)));
      const h = snap(Math.max(36, d.nh + (world.y - d.oy)));
      patchBoard(boardId, {
        nodes: board.nodes.map((n) => (n.id === d.id ? { ...n, w, h } : n)),
      });
    }
  };

  const onPointerUp = (e: React.PointerEvent) => {
    const world = toWorld(e.clientX, e.clientY);
    if (connect && board) {
      const hit = hitNode(board.nodes, world.x, world.y);
      if (hit && hit.id !== connect.from) {
        const toPort = nearestPort(hit, world.x, world.y);
        patchBoard(boardId, {
          edges: [
            ...board.edges,
            {
              id: nid("e"),
              from: connect.from,
              to: hit.id,
              fromPort: connect.port,
              toPort,
              label: "",
            },
          ],
        });
      }
      setConnect(null);
    }
    if (drag.current?.mode === "pen" && currentStroke && board) {
      if (currentStroke.length >= 4) {
        const stroke: Stroke = {
          id: nid("stk"),
          color: "var(--color-accent)",
          width: 2,
          points: currentStroke,
        };
        patchBoard(boardId, { strokes: [...board.strokes, stroke] });
      }
      setCurrentStroke(null);
    }
    drag.current = null;
  };

  const fit = () => {
    if (!board || !rootRef.current) return;
    const nodes = board.nodes;
    if (!nodes.length) {
      setCamera({ x: 40, y: 40, scale: 1 });
      return;
    }
    const minX = Math.min(...nodes.map((n) => n.x)) - 40;
    const minY = Math.min(...nodes.map((n) => n.y)) - 40;
    const maxX = Math.max(...nodes.map((n) => n.x + n.w)) + 40;
    const maxY = Math.max(...nodes.map((n) => n.y + n.h)) + 40;
    const r = rootRef.current.getBoundingClientRect();
    const scale = clamp(Math.min(r.width / (maxX - minX), r.height / (maxY - minY)), 0.4, 1.4);
    setCamera({
      scale,
      x: (r.width - (maxX - minX) * scale) / 2 - minX * scale,
      y: (r.height - (maxY - minY) * scale) / 2 - minY * scale,
    });
  };

  const cursor =
    space || tool === "pan"
      ? "grab"
      : tool === "pen"
        ? "crosshair"
        : tool === "erase"
          ? "cell"
          : PLACE_TOOLS.includes(tool)
            ? "copy"
            : "default";

  if (!board) {
    return <div className="p-8 text-sm text-ink-2">This page is not in the book.</div>;
  }

  return (
    <div className="flex flex-col h-[calc(100dvh-60px)] md:h-[calc(100dvh-60px)]">
      <div className="flex items-center gap-2 px-3 md:px-4 py-2 border-b border-line bg-surface flex-wrap">
        <Input
          value={board.title}
          onChange={(e) => updateBoard(board.id, { title: e.target.value })}
          className="max-w-xs font-semibold"
        />
        <Select
          value={board.projectId ?? ""}
          onChange={(e) => updateBoard(board.id, { projectId: e.target.value || null })}
          className="max-w-[180px]"
        >
          <option value="">Unfiled</option>
          {projects.map((p) => (
            <option key={p.id} value={p.id}>
              {p.name}
            </option>
          ))}
        </Select>
        <div className="flex items-center gap-0.5 overflow-x-auto">
          {tools.map((t) => {
            const Icon = t.icon;
            return (
              <button
                key={t.id}
                type="button"
                title={t.label}
                onClick={() => setTool(t.id)}
                className={cn(
                  "size-9 inline-flex items-center justify-center rounded-md text-ink-2 hover:bg-sunken hover:text-ink",
                  tool === t.id && "bg-accent-soft text-accent",
                )}
              >
                <Icon className="size-4" strokeWidth={1.75} />
              </button>
            );
          })}
        </div>
        <div className="ml-auto flex items-center gap-1">
          <Button size="sm" variant="ghost" onClick={() => setCamera((c) => ({ ...c, scale: clamp(c.scale / 1.1, 0.25, 2.8) }))}>
            <Minus className="size-3.5" />
          </Button>
          <span className="font-mono text-[12px] tabular-nums w-12 text-center text-ink-2">
            {Math.round(camera.scale * 100)}%
          </span>
          <Button size="sm" variant="ghost" onClick={() => setCamera((c) => ({ ...c, scale: clamp(c.scale * 1.1, 0.25, 2.8) }))}>
            <Plus className="size-3.5" />
          </Button>
          <Button size="sm" onClick={fit}>
            Fit
          </Button>
        </div>
      </div>
      <div className="flex-1 min-h-0 grid md:grid-cols-[1fr_280px]">
        <div
          ref={rootRef}
          className="relative overflow-hidden dot-grid touch-none"
          style={{ cursor }}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onDoubleClick={(e) => {
            const world = toWorld(e.clientX, e.clientY);
            const hit = hitNode(board.nodes, world.x, world.y);
            if (hit) setEditingId(hit.id);
          }}
        >
          <div
            className="absolute origin-top-left"
            style={{ transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.scale})` }}
          >
            <svg className="overflow-visible" width={1} height={1}>
              <defs>
                <marker
                  id={`arrow-${board.id}`}
                  markerWidth="10"
                  markerHeight="7"
                  refX="9"
                  refY="3.5"
                  orient="auto"
                >
                  <polygon points="0 0, 10 3.5, 0 7" fill="var(--color-accent)" />
                </marker>
              </defs>
              {board.strokes.map((s) => (
                <polyline
                  key={s.id}
                  fill="none"
                  stroke={s.color}
                  strokeWidth={s.width}
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  points={toPairs(s.points)}
                />
              ))}
              {currentStroke ? (
                <polyline
                  fill="none"
                  stroke="var(--color-accent)"
                  strokeWidth={2}
                  strokeLinecap="round"
                  points={toPairs(currentStroke)}
                />
              ) : null}
              {board.edges.map((ed) => {
                const a = nodesById[ed.from];
                const b = nodesById[ed.to];
                if (!a || !b) return null;
                const p1 = portPoint(a, ed.fromPort);
                const p2 = portPoint(b, ed.toPort);
                const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
                return (
                  <g key={ed.id}>
                    <path
                      d={edgePath(p1, p2, ed.fromPort, ed.toPort)}
                      fill="none"
                      stroke="transparent"
                      strokeWidth={14}
                      className="cursor-pointer"
                      onPointerDown={(ev) => {
                        ev.stopPropagation();
                        setSelectedEdge(ed.id);
                        setSelectedId(null);
                      }}
                    />
                    <path
                      d={edgePath(p1, p2, ed.fromPort, ed.toPort)}
                      fill="none"
                      stroke={selectedEdge === ed.id ? "var(--color-accent)" : "var(--color-ink-2)"}
                      strokeWidth={selectedEdge === ed.id ? 2 : 1.4}
                      markerEnd={`url(#arrow-${board.id})`}
                      className="pointer-events-none"
                    />
                    {ed.label ? (
                      <text x={mid.x} y={mid.y - 6} textAnchor="middle" fontSize={11} fill="var(--color-ink-2)">
                        {ed.label}
                      </text>
                    ) : null}
                  </g>
                );
              })}
              {connect ? (
                (() => {
                  const from = nodesById[connect.from];
                  if (!from) return null;
                  const p1 = portPoint(from, connect.port);
                  return (
                    <path
                      d={edgePath(p1, { x: connect.x, y: connect.y }, connect.port, "w")}
                      fill="none"
                      stroke="var(--color-accent)"
                      strokeDasharray="4 3"
                      strokeWidth={1.5}
                    />
                  );
                })()
              ) : null}
            </svg>
            {board.nodes.map((node) => (
              <NodeView
                key={node.id}
                node={node}
                selected={node.id === selectedId}
                showPorts={tool === "connect" || node.id === selectedId}
                linkedTask={tasks.find((t) => t.id === node.linkedTaskId)?.title}
                linkedNote={notes.find((n) => n.id === node.linkedNoteId)?.title}
              />
            ))}
            {editingId
              ? (() => {
                  const node = board.nodes.find((n) => n.id === editingId);
                  if (!node) return null;
                  return (
                    <textarea
                      autoFocus
                      value={node.text}
                      onChange={(e) =>
                        patchBoard(boardId, {
                          nodes: board.nodes.map((n) =>
                            n.id === node.id ? { ...n, text: e.target.value } : n,
                          ),
                        })
                      }
                      onBlur={() => setEditingId(null)}
                      className="absolute resize-none bg-surface text-ink text-[13px] p-2 border border-accent outline-none rounded-md"
                      style={{ left: node.x, top: node.y, width: node.w, height: node.h }}
                    />
                  );
                })()
              : null}
          </div>
        </div>
        <aside className="hidden md:flex flex-col border-l border-line bg-surface overflow-y-auto">
          {selected ? (
            <div className="p-4 flex flex-col gap-3">
              <div className="text-[12px] font-semibold text-ink-2">Selected node</div>
              <Field label="Text">
                <Textarea
                  value={selected.text}
                  rows={4}
                  onChange={(e) =>
                    patchBoard(boardId, {
                      nodes: board.nodes.map((n) =>
                        n.id === selected.id ? { ...n, text: e.target.value } : n,
                      ),
                    })
                  }
                />
              </Field>
              {selected.type === "sticky" ? (
                <div className="flex gap-1.5">
                  {STICKY_SWATCHES.map((c) => (
                    <button
                      key={c}
                      type="button"
                      aria-label="Sticky colour"
                      className={cn(
                        "size-7 rounded-md border border-line",
                        selected.fill === c && "outline outline-2 outline-accent outline-offset-1",
                      )}
                      style={{ background: c }}
                      onClick={() =>
                        patchBoard(boardId, {
                          nodes: board.nodes.map((n) =>
                            n.id === selected.id ? { ...n, fill: c } : n,
                          ),
                        })
                      }
                    />
                  ))}
                </div>
              ) : null}
              <Button
                size="sm"
                variant="primary"
                onClick={() => {
                  const id = promoteNode(boardId, selected.id, "task");
                  if (id) {
                    toast("Node posted as a task");
                    void navigate({ to: "/tasks", search: { focus: id } });
                  }
                }}
              >
                {selected.linkedTaskId ? "Open linked task" : "Promote to task"}
              </Button>
              <Button
                size="sm"
                onClick={() => {
                  const id = promoteNode(boardId, selected.id, "note");
                  if (id) {
                    toast("Node posted as a note");
                    void navigate({ to: "/notes/$noteId", params: { noteId: id } });
                  }
                }}
              >
                {selected.linkedNoteId ? "Open linked note" : "Promote to note"}
              </Button>
              {selected.linkedTaskId ? (
                <p className="text-[12px] text-ink-2">
                  Linked task: {tasks.find((t) => t.id === selected.linkedTaskId)?.title}
                </p>
              ) : null}
            </div>
          ) : selectedEdge ? (
            <div className="p-4">
              <Field label="Connector label">
                <Input
                  value={board.edges.find((ed) => ed.id === selectedEdge)?.label ?? ""}
                  onChange={(e) =>
                    patchBoard(boardId, {
                      edges: board.edges.map((ed) =>
                        ed.id === selectedEdge ? { ...ed, label: e.target.value } : ed,
                      ),
                    })
                  }
                />
              </Field>
              <p className="text-[12px] text-ink-3">Delete removes the selected connector.</p>
            </div>
          ) : (
            <div className="p-4 text-[13px] text-ink-2 leading-relaxed">
              <p className="font-semibold text-ink mb-1">Canvas</p>
              Click a tool, then click the page to place. Drag to move. Double-click to edit text.
              Connect ports on flows. Promote a node into a task or a note — it stays linked here.
              <p className="mt-3 text-[12px] text-ink-3">
                Space pans · scroll zooms · V select · Delete removes
              </p>
            </div>
          )}
        </aside>
      </div>
    </div>
  );
}

function toPairs(points: number[]): string {
  const out: string[] = [];
  for (let i = 0; i < points.length; i += 2) out.push(`${points[i]},${points[i + 1]}`);
  return out.join(" ");
}

function NodeView({
  node,
  selected,
  showPorts,
  linkedTask,
  linkedNote,
}: {
  node: BoardNode;
  selected: boolean;
  showPorts: boolean;
  linkedTask?: string;
  linkedNote?: string;
}) {
  const stroke = selected ? "var(--color-accent)" : "var(--color-line)";
  const fill = node.fill ?? "var(--color-surface)";
  const common = {
    width: node.w,
    height: node.h,
    className: "overflow-visible pointer-events-none",
  };
  return (
    <div
      className="absolute"
      style={{ left: node.x, top: node.y, width: node.w, height: node.h }}
    >
      <svg {...common}>
        {node.type === "ellipse" || node.type === "start" ? (
          <rect
            x={0.5}
            y={0.5}
            width={node.w - 1}
            height={node.h - 1}
            rx={node.type === "start" ? node.h / 2 : node.h / 2}
            fill={fill}
            stroke={stroke}
            strokeWidth={selected ? 2 : 1}
          />
        ) : node.type === "decision" || node.type === "diamond" ? (
          <polygon
            points={diamondPoints(node.w, node.h)}
            fill={fill}
            stroke={stroke}
            strokeWidth={selected ? 2 : 1}
          />
        ) : node.type === "document" ? (
          <path
            d={documentPath(node.w, node.h)}
            fill={fill}
            stroke={stroke}
            strokeWidth={selected ? 2 : 1}
          />
        ) : node.type === "text" ? (
          <rect x={0} y={0} width={node.w} height={node.h} fill="transparent" />
        ) : (
          <rect
            x={0.5}
            y={0.5}
            width={node.w - 1}
            height={node.h - 1}
            rx={6}
            fill={fill}
            stroke={stroke}
            strokeWidth={selected ? 2 : 1}
          />
        )}
      </svg>
      <div
        className={cn(
          "absolute inset-0 px-2 py-1.5 text-[12.5px] leading-snug text-ink pointer-events-none overflow-hidden",
          node.type === "decision" && "flex items-center justify-center text-center px-5",
          node.type === "start" && "flex items-center justify-center font-semibold",
          node.type === "text" && "font-semibold text-[14px] px-0",
        )}
      >
        <span className="line-clamp-5 whitespace-pre-wrap">{node.text}</span>
        {linkedTask || linkedNote ? (
          <span className="block mt-1 text-[10px] text-accent truncate">
            {linkedTask ? `Task · ${linkedTask}` : `Note · ${linkedNote}`}
          </span>
        ) : null}
      </div>
      {selected ? (
        <i className="absolute right-0 bottom-0 size-2.5 bg-accent rounded-[2px] pointer-events-none" />
      ) : null}
      {showPorts
        ? (["n", "s", "e", "w"] as Port[]).map((p) => {
            const pos =
              p === "n"
                ? { left: node.w / 2 - 4, top: -4 }
                : p === "s"
                  ? { left: node.w / 2 - 4, top: node.h - 4 }
                  : p === "w"
                    ? { left: -4, top: node.h / 2 - 4 }
                    : { left: node.w - 4, top: node.h / 2 - 4 };
            return (
              <i
                key={p}
                className="absolute size-2 rounded-full bg-surface border border-accent pointer-events-none"
                style={pos}
              />
            );
          })
        : null}
    </div>
  );
}
