"use client" import { useMemo, useState, type ReactNode } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { ospfNodeState, type OspfNeighborState } from "@/lib/ospf-state" export interface OspfTopologyNode { id: string; label: string; short: string; site: string; x: number; y: number } export interface OspfTopologyEdge { from: string; to: string; state: OspfNeighborState; cost: number | null } interface TopologyView { nodes: OspfTopologyNode[] edges: OspfTopologyEdge[] nodeStates: ReadonlyMap> } export function OspfTopologyScope({ nodes, edges, selectedId, onSelect, width, height, children }: { nodes: OspfTopologyNode[]; edges: OspfTopologyEdge[]; selectedId: string | null onSelect: (id: string | null) => void; width: number; height: number children: (view: TopologyView) => ReactNode }) { const focal = nodes.find(node => node.id === selectedId) ?? nodes[0] const [context, setContext] = useState(focal?.id) const [page, setPage] = useState(0) const [search, setSearch] = useState("") const incident = useMemo(() => edges.filter(edge => edge.from === focal?.id || edge.to === focal?.id), [edges, focal?.id]) const pageCount = Math.max(1, Math.ceil(incident.length / 8)) if (context !== focal?.id) { setContext(focal?.id); setPage(0) } if (page > pageCount - 1) setPage(pageCount - 1) const currentPage = context === focal?.id ? Math.min(page, pageCount - 1) : 0 const nodeStates = useMemo(() => { const states = new Map() for (const edge of edges) for (const id of new Set([edge.from, edge.to])) { const values = states.get(id) ?? []; values.push(edge.state); states.set(id, values) } return new Map(nodes.map(node => [node.id, ospfNodeState(states.get(node.id) ?? [])])) }, [nodes, edges]) const view = useMemo(() => { if (nodes.length <= 12 && edges.length <= 12) return { nodes, edges, nodeStates } const visibleEdges = incident.slice(currentPage * 8, (currentPage + 1) * 8) const ids = new Set([focal?.id, ...visibleEdges.flatMap(edge => [edge.from, edge.to])]) const peers = nodes.filter(node => node.id !== focal?.id && ids.has(node.id)) const radiusX = width * 0.39, radiusY = height * 0.30 const positioned = peers.map((node, index) => { const angle = peers.length === 1 ? 0 : 2 * Math.PI * index / peers.length - Math.PI / 2 return { ...node, x: width / 2 + radiusX * Math.cos(angle), y: height / 2 + radiusY * Math.sin(angle) } }) return { nodes: focal ? [{ ...focal, x: width / 2, y: height / 2 }, ...positioned] : [], edges: visibleEdges, nodeStates } }, [nodes, edges, nodeStates, focal, incident, currentPage, width, height]) const matches = useMemo(() => { const query = search.trim().toLowerCase() return nodes.filter(node => !query || `${node.label} ${node.id}`.toLowerCase().includes(query)) }, [nodes, search]) if (nodes.length <= 12 && edges.length <= 12) return children(view) return

Связи узла: {focal?.label}

Показаны {view.nodes.length} из {nodes.length} узлов. Связи узла: {incident.length ? currentPage * 8 + 1 : 0}–{Math.min((currentPage + 1) * 8, incident.length)} из {incident.length}. Всего в топологии {edges.length} связей.

setSearch(event.target.value)} />

Найдено {matches.length}; показаны первые {Math.min(5, matches.length)}. Уточните поиск, чтобы найти другой узел.

{matches.slice(0, 5).map(node => )}
{children(view)}
}