File size: 16,778 Bytes
78d0e31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
"use client"

import type React from "react"
import { useEffect, useRef, useState, useCallback } from "react"
import { useMobile } from "@/hooks/use-mobile"

type Node = {
  id: string
  name: string
  val: number
  color: string
  group?: string
  x: number
  y: number
  vx: number
  vy: number
  fx?: number
  fy?: number
  isNew?: boolean
  joinedAt?: number
  networkType: "testnet" | "mainnet"
}

type Link = {
  source: string
  target: string
  value: number
  strength: number
}

type GraphData = {
  nodes: Node[]
  links: Link[]
}

type NetworkGraphProps = {
  centerNodeId?: string
  segment?: string
}

const TESTNET_COLORS = {
  center: "#FF6B35", // FlameBorn orange
  healer: "#00A86B", // Medical green
  guardian: "#0077B5", // Professional blue
  chw: "#9B59B6", // Community purple
  facility: "#E74C3C", // Emergency red
  new: "#F39C12", // New member gold
}

const MAINNET_COLORS = {
  center: "#FF4500", // Deeper orange for mainnet
  healer: "#228B22", // Forest green
  guardian: "#4169E1", // Royal blue
  chw: "#8A2BE2", // Blue violet
  facility: "#DC143C", // Crimson
  new: "#FFD700", // Gold
}

const generateLiveNodes = (count: number, centerNodeId: string, networkType: "testnet" | "mainnet"): Node[] => {
  const colors = networkType === "testnet" ? TESTNET_COLORS : MAINNET_COLORS

  const nodes: Node[] = [
    {
      id: centerNodeId,
      name: `FlameBorn ${networkType === "testnet" ? "Testnet" : "Mainnet"} Hub`,
      val: 8,
      color: colors.center,
      group: "center",
      x: 400,
      y: 300,
      vx: 0,
      vy: 0,
      fx: 400,
      fy: 300,
      networkType,
    },
  ]

  const nodeTypes = [
    {
      type: "healer",
      color: colors.healer,
      names: ["Dr. Amara Kone", "Dr. Kwame Asante", "Dr. Fatima Al-Rashid", "Dr. John Mwangi"],
    },
    {
      type: "guardian",
      color: colors.guardian,
      names: ["Guardian Sarah", "Guardian Ahmed", "Guardian Zara", "Guardian Kofi"],
    },
    { type: "chw", color: colors.chw, names: ["CHW Aisha", "CHW Babatunde", "CHW Naledi", "CHW Kemi"] },
    {
      type: "facility",
      color: colors.facility,
      names: ["Lagos General", "Nairobi Health Center", "Accra Medical", "Cairo Clinic"],
    },
  ]

  const locations = [
    "Lagos, Nigeria",
    "Nairobi, Kenya",
    "Accra, Ghana",
    "Cairo, Egypt",
    "Kano, Nigeria",
    "Kampala, Uganda",
    "Dakar, Senegal",
    "Addis Ababa, Ethiopia",
    "Casablanca, Morocco",
    "Cape Town, South Africa",
    "Abidjan, Ivory Coast",
    "Dar es Salaam, Tanzania",
  ]

  for (let i = 1; i <= count; i++) {
    const nodeType = nodeTypes[i % nodeTypes.length]
    const angle = (i / count) * 2 * Math.PI + Math.sin(Date.now() * 0.001 + i) * 0.1
    const radius = 150 + Math.sin(Date.now() * 0.0005 + i) * 50
    const baseX = 400 + Math.cos(angle) * radius
    const baseY = 300 + Math.sin(angle) * radius

    const isNewNode = Math.random() > 0.95 // 5% chance of being a new node
    const joinedRecently = Date.now() - Math.random() * 300000 // Joined within last 5 minutes

    nodes.push({
      id: `${networkType}-node-${i}`,
      name: `${nodeType.names[i % nodeType.names.length]} ${Math.ceil(i / nodeType.names.length)}`,
      val: isNewNode ? 5 : 2 + Math.random() * 3,
      color: isNewNode ? colors.new : nodeType.color,
      group: nodeType.type,
      x: baseX + (Math.random() - 0.5) * 100,
      y: baseY + (Math.random() - 0.5) * 100,
      vx: (Math.random() - 0.5) * 2,
      vy: (Math.random() - 0.5) * 2,
      isNew: isNewNode,
      joinedAt: joinedRecently,
      networkType,
    })
  }

  return nodes
}

const generateLiveLinks = (nodes: Node[], centerNodeId: string): Link[] => {
  const links: Link[] = []

  // Connect all nodes to center with varying strength
  nodes.forEach((node) => {
    if (node.id !== centerNodeId) {
      const distance = Math.sqrt(Math.pow(node.x - 400, 2) + Math.pow(node.y - 300, 2))
      const strength = Math.max(0.1, 1 - distance / 300)

      links.push({
        source: centerNodeId,
        target: node.id,
        value: node.isNew ? 3 : 1,
        strength,
      })
    }
  })

  // Create dynamic connections between nodes
  const nonCenterNodes = nodes.filter((node) => node.id !== centerNodeId)
  for (let i = 0; i < nonCenterNodes.length; i++) {
    for (let j = i + 1; j < nonCenterNodes.length; j++) {
      const node1 = nonCenterNodes[i]
      const node2 = nonCenterNodes[j]

      // Higher chance of connection if nodes are of same type or nearby
      const sameType = node1.group === node2.group
      const distance = Math.sqrt(Math.pow(node1.x - node2.x, 2) + Math.pow(node1.y - node2.y, 2))

      const connectionChance = sameType ? 0.15 : 0.05
      const distanceBonus = distance < 100 ? 0.1 : 0

      if (Math.random() < connectionChance + distanceBonus) {
        links.push({
          source: node1.id,
          target: node2.id,
          value: 1,
          strength: 0.3,
        })
      }
    }
  }

  return links
}

const NetworkGraph: React.FC<NetworkGraphProps> = ({ centerNodeId = "flameborn-hub", segment }) => {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const containerRef = useRef<HTMLDivElement>(null)
  const animationRef = useRef<number>()
  const isMobile = useMobile()

  const [dimensions, setDimensions] = useState({ width: 800, height: 600 })
  const [testnetData, setTestnetData] = useState<GraphData>({ nodes: [], links: [] })
  const [mainnetData, setMainnetData] = useState<GraphData>({ nodes: [], links: [] })
  const [activeNetwork, setActiveNetwork] = useState<"testnet" | "mainnet">("testnet")
  const [stats, setStats] = useState({
    testnet: { nodes: 0, newJoins: 0 },
    mainnet: { nodes: 0, newJoins: 0 },
  })

  // Physics simulation parameters
  const forceStrength = 0.03
  const centerForce = 0.02
  const repelForce = 30
  const dampening = 0.95

  const updateDimensions = useCallback(() => {
    if (containerRef.current) {
      const { width, height } = containerRef.current.getBoundingClientRect()
      setDimensions({ width: Math.max(width, 400), height: Math.max(height, 300) })
    }
  }, [])

  // Initialize and update network data
  useEffect(() => {
    const initializeNetworks = () => {
      // Generate testnet data
      const testnetNodes = generateLiveNodes(25, `${centerNodeId}-testnet`, "testnet")
      const testnetLinks = generateLiveLinks(testnetNodes, `${centerNodeId}-testnet`)
      setTestnetData({ nodes: testnetNodes, links: testnetLinks })

      // Generate mainnet data
      const mainnetNodes = generateLiveNodes(40, `${centerNodeId}-mainnet`, "mainnet")
      const mainnetLinks = generateLiveLinks(mainnetNodes, `${centerNodeId}-mainnet`)
      setMainnetData({ nodes: mainnetNodes, links: mainnetLinks })

      // Update stats
      setStats({
        testnet: {
          nodes: testnetNodes.length - 1,
          newJoins: testnetNodes.filter((n) => n.isNew).length,
        },
        mainnet: {
          nodes: mainnetNodes.length - 1,
          newJoins: mainnetNodes.filter((n) => n.isNew).length,
        },
      })
    }

    initializeNetworks()
    updateDimensions()

    // Update networks every 10 seconds to simulate live joining
    const networkInterval = setInterval(() => {
      initializeNetworks()
    }, 10000)

    // Resize listener
    window.addEventListener("resize", updateDimensions)

    return () => {
      clearInterval(networkInterval)
      window.removeEventListener("resize", updateDimensions)
    }
  }, [centerNodeId, updateDimensions])

  // Physics simulation and rendering
  useEffect(() => {
    if (!canvasRef.current || dimensions.width === 0) return

    const canvas = canvasRef.current
    const ctx = canvas.getContext("2d")
    if (!ctx) return

    canvas.width = dimensions.width
    canvas.height = dimensions.height

    const currentData = activeNetwork === "testnet" ? testnetData : mainnetData
    if (currentData.nodes.length === 0) return

    const nodes = [...currentData.nodes]
    const links = currentData.links

    const simulate = () => {
      // Apply forces
      nodes.forEach((node, i) => {
        if (node.fx !== undefined && node.fy !== undefined) return // Skip fixed nodes

        let fx = 0,
          fy = 0

        // Center attraction
        const centerX = dimensions.width / 2
        const centerY = dimensions.height / 2
        const dcx = centerX - node.x
        const dcy = centerY - node.y
        const centerDistance = Math.sqrt(dcx * dcx + dcy * dcy)

        if (centerDistance > 0) {
          fx += (dcx / centerDistance) * centerForce * centerDistance * 0.01
          fy += (dcy / centerDistance) * centerForce * centerDistance * 0.01
        }

        // Node repulsion
        nodes.forEach((other, j) => {
          if (i === j) return
          const dx = node.x - other.x
          const dy = node.y - other.y
          const distance = Math.sqrt(dx * dx + dy * dy)

          if (distance > 0 && distance < 100) {
            const force = repelForce / (distance * distance)
            fx += (dx / distance) * force
            fy += (dy / distance) * force
          }
        })

        // Link forces
        links.forEach((link) => {
          if (link.source === node.id) {
            const target = nodes.find((n) => n.id === link.target)
            if (target) {
              const dx = target.x - node.x
              const dy = target.y - node.y
              const distance = Math.sqrt(dx * dx + dy * dy)
              const targetDistance = 80

              if (distance > 0) {
                const force = (distance - targetDistance) * link.strength * forceStrength
                fx += (dx / distance) * force
                fy += (dy / distance) * force
              }
            }
          }
          if (link.target === node.id) {
            const source = nodes.find((n) => n.id === link.source)
            if (source) {
              const dx = source.x - node.x
              const dy = source.y - node.y
              const distance = Math.sqrt(dx * dx + dy * dy)
              const targetDistance = 80

              if (distance > 0) {
                const force = (distance - targetDistance) * link.strength * forceStrength
                fx += (dx / distance) * force
                fy += (dy / distance) * force
              }
            }
          }
        })

        // Apply forces to velocity
        node.vx += fx
        node.vy += fy

        // Apply dampening
        node.vx *= dampening
        node.vy *= dampening

        // Update position
        node.x += node.vx
        node.y += node.vy

        // Boundary constraints
        const margin = 50
        if (node.x < margin) {
          node.x = margin
          node.vx = 0
        }
        if (node.x > dimensions.width - margin) {
          node.x = dimensions.width - margin
          node.vx = 0
        }
        if (node.y < margin) {
          node.y = margin
          node.vy = 0
        }
        if (node.y > dimensions.height - margin) {
          node.y = dimensions.height - margin
          node.vy = 0
        }
      })
    }

    const render = () => {
      ctx.clearRect(0, 0, dimensions.width, dimensions.height)

      const time = Date.now() * 0.001

      // Draw links with animation
      ctx.strokeStyle = activeNetwork === "testnet" ? "rgba(255,107,53,0.3)" : "rgba(255,69,0,0.4)"
      ctx.lineWidth = 1

      links.forEach((link) => {
        const sourceNode = nodes.find((n) => n.id === link.source)
        const targetNode = nodes.find((n) => n.id === link.target)

        if (sourceNode && targetNode) {
          const opacity = 0.2 + Math.sin(time * 2 + link.value) * 0.1
          ctx.strokeStyle = activeNetwork === "testnet" ? `rgba(255,107,53,${opacity})` : `rgba(255,69,0,${opacity})`

          ctx.beginPath()
          ctx.moveTo(sourceNode.x, sourceNode.y)
          ctx.lineTo(targetNode.x, targetNode.y)
          ctx.stroke()
        }
      })

      // Draw nodes with enhanced animation
      nodes.forEach((node) => {
        const isPulseNode = node.group === "center"
        const isNewNode = node.isNew

        let pulseIntensity = 1
        let glowIntensity = 5

        if (isPulseNode) {
          pulseIntensity = 1 + Math.sin(time * 3) * 0.3
          glowIntensity = 20 + Math.sin(time * 4) * 10
        } else if (isNewNode) {
          pulseIntensity = 1 + Math.sin(time * 5) * 0.4
          glowIntensity = 10 + Math.sin(time * 6) * 5
        } else {
          pulseIntensity = 1 + Math.sin(time * 0.5 + Number.parseInt(node.id.slice(-2) || "0")) * 0.1
          glowIntensity = 3 + Math.sin(time * 0.3) * 2
        }

        const baseSize = node.val * 3
        const size = baseSize * pulseIntensity

        // Enhanced glow effect
        ctx.shadowColor = node.color
        ctx.shadowBlur = glowIntensity

        // Main node circle
        ctx.beginPath()
        ctx.arc(node.x, node.y, size, 0, 2 * Math.PI)
        ctx.fillStyle = node.color
        ctx.fill()

        // Additional rings for special nodes
        if (isPulseNode) {
          ctx.beginPath()
          const ringRadius = size + 10 + Math.sin(time * 4) * 5
          ctx.arc(node.x, node.y, ringRadius, 0, 2 * Math.PI)
          ctx.strokeStyle = node.color + "60"
          ctx.lineWidth = 2
          ctx.stroke()
        }

        if (isNewNode) {
          ctx.beginPath()
          const newRingRadius = size + 5 + Math.sin(time * 8) * 3
          ctx.arc(node.x, node.y, newRingRadius, 0, 2 * Math.PI)
          ctx.strokeStyle = "#FFD700" + "80"
          ctx.lineWidth = 1.5
          ctx.stroke()
        }

        ctx.shadowBlur = 0
      })
    }

    const animate = () => {
      simulate()
      render()
      animationRef.current = requestAnimationFrame(animate)
    }

    animate()

    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current)
      }
    }
  }, [testnetData, mainnetData, activeNetwork, dimensions])

  return (
    <div className="relative">
      {/* Network Toggle */}
      <div className="absolute top-4 left-4 z-10 flex gap-2">
        <button
          onClick={() => setActiveNetwork("testnet")}
          className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
            activeNetwork === "testnet"
              ? "bg-orange-500 text-white shadow-lg"
              : "bg-white/10 text-white/70 hover:bg-white/20"
          }`}
        >
          Testnet ({stats.testnet.nodes})
        </button>
        <button
          onClick={() => setActiveNetwork("mainnet")}
          className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
            activeNetwork === "mainnet"
              ? "bg-red-500 text-white shadow-lg"
              : "bg-white/10 text-white/70 hover:bg-white/20"
          }`}
        >
          Mainnet ({stats.mainnet.nodes})
        </button>
      </div>

      {/* Live Stats */}
      <div className="absolute top-4 right-4 z-10 bg-black/50 backdrop-blur-sm rounded-lg p-3 text-white text-sm">
        <div className="flex items-center gap-2 mb-1">
          <div
            className={`w-2 h-2 rounded-full ${activeNetwork === "testnet" ? "bg-orange-500" : "bg-red-500"} animate-pulse`}
          ></div>
          <span className="font-medium">{activeNetwork.toUpperCase()} LIVE</span>
        </div>
        <div className="text-xs text-gray-300">
          New joins: {activeNetwork === "testnet" ? stats.testnet.newJoins : stats.mainnet.newJoins}
        </div>
      </div>

      {/* Canvas */}
      <div ref={containerRef} className="w-full h-full bg-black rounded-lg overflow-hidden">
        <canvas ref={canvasRef} className="w-full h-full cursor-pointer" style={{ display: "block" }} />
      </div>

      {/* Network Info */}
      <div className="absolute bottom-4 left-4 right-4 z-10 bg-black/50 backdrop-blur-sm rounded-lg p-4 text-white">
        <div className="flex justify-between items-center">
          <div>
            <h3 className="font-bold text-lg">
              FlameBorn {activeNetwork === "testnet" ? "Testnet" : "Mainnet"} Network
            </h3>
            <p className="text-sm text-gray-300">
              Live visualization of {activeNetwork === "testnet" ? "testing" : "production"} network participants
            </p>
          </div>
          <div className="text-right">
            <div className="text-2xl font-bold text-orange-400">
              {activeNetwork === "testnet" ? stats.testnet.nodes : stats.mainnet.nodes}
            </div>
            <div className="text-xs text-gray-400">Active Nodes</div>
          </div>
        </div>
      </div>
    </div>
  )
}

export default NetworkGraph