import React, { useState, useEffect, useRef } from 'react' import { holosphereService, HoloSphereService, HolonData, HolonLens } from '@/lib/HoloSphereService' import * as h3 from 'h3-js' interface HolonBrowserProps { isOpen: boolean onClose: () => void onSelectHolon: (holonData: HolonData) => void shapeMode?: boolean } interface HolonInfo { id: string name: string description?: string latitude: number longitude: number resolution: number resolutionName: string data: Record lastUpdated: number } export function HolonBrowser({ isOpen, onClose, onSelectHolon, shapeMode = false }: HolonBrowserProps) { const [holonId, setHolonId] = useState('') const [holonInfo, setHolonInfo] = useState(null) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const [lenses, setLenses] = useState([]) const [selectedLens, setSelectedLens] = useState('') const [lensData, setLensData] = useState(null) const [isLoadingData, setIsLoadingData] = useState(false) const inputRef = useRef(null) useEffect(() => { if (isOpen && inputRef.current) { inputRef.current.focus() } }, [isOpen]) const handleSearchHolon = async () => { if (!holonId.trim()) { setError('Please enter a Holon ID') return } setIsLoading(true) setError(null) setHolonInfo(null) try { // Check if it's a valid H3 cell ID const isH3Cell = h3.isValidCell(holonId) // Check if it's a numeric Holon ID (workspace/group identifier) const isNumericId = /^\d{6,20}$/.test(holonId) // Check if it's an alphanumeric identifier const isAlphanumericId = /^[a-zA-Z0-9_-]{3,50}$/.test(holonId) if (!isH3Cell && !isNumericId && !isAlphanumericId) { throw new Error('Invalid Holon ID. Enter an H3 cell ID (e.g., 872a1070bffffff) or a numeric Holon ID (e.g., 1002848305066)') } // Get holon information based on ID type let resolution: number let lat: number let lng: number if (isH3Cell) { resolution = h3.getResolution(holonId) ;[lat, lng] = h3.cellToLatLng(holonId) } else { // For non-H3 IDs, use default values resolution = -1 // Indicates non-geospatial holon lat = 0 lng = 0 } // Try to get metadata from the holon let metadata = null try { metadata = await holosphereService.getData(holonId, 'metadata') } catch (error) { console.log('No metadata found for holon') } // Get available lenses by trying to fetch data from common lens types // Use the improved categories from HolonShapeUtil const commonLenses = [ 'active_users', 'users', 'rankings', 'stats', 'tasks', 'progress', 'events', 'activities', 'items', 'shopping', 'active_items', 'proposals', 'offers', 'requests', 'checklists', 'roles', 'general', 'metadata', 'environment', 'social', 'economic', 'cultural', 'data' ] const availableLenses: string[] = [] for (const lens of commonLenses) { try { // Use getDataWithWait for better Gun data retrieval (shorter timeout for browser) const data = await holosphereService.getDataWithWait(holonId, lens, 1000) if (data && (Array.isArray(data) ? data.length > 0 : Object.keys(data).length > 0)) { availableLenses.push(lens) console.log(`✓ Found lens: ${lens} with ${Object.keys(data).length} keys`) } } catch (error) { // Lens doesn't exist or is empty, skip } } // If no lenses found, add 'general' as default if (availableLenses.length === 0) { availableLenses.push('general') } const holonData: HolonInfo = { id: holonId, name: metadata?.name || `Holon ${holonId.slice(-8)}`, description: metadata?.description || '', latitude: lat, longitude: lng, resolution: resolution, resolutionName: resolution >= 0 ? HoloSphereService.getResolutionName(resolution) : 'Workspace / Group', data: {}, lastUpdated: metadata?.lastUpdated || Date.now() } setHolonInfo(holonData) setLenses(availableLenses) setSelectedLens(availableLenses[0]) } catch (error) { console.error('Error searching holon:', error) setError(`Failed to load holon: ${error instanceof Error ? error.message : 'Unknown error'}`) } finally { setIsLoading(false) } } const handleLoadLensData = async (lens: string) => { if (!holonInfo) return setIsLoadingData(true) try { // Use getDataWithWait for better Gun data retrieval const data = await holosphereService.getDataWithWait(holonInfo.id, lens, 2000) setLensData(data) console.log(`📊 Loaded lens data for ${lens}:`, data) } catch (error) { console.error('Error loading lens data:', error) setLensData(null) } finally { setIsLoadingData(false) } } useEffect(() => { if (selectedLens && holonInfo) { handleLoadLensData(selectedLens) } }, [selectedLens, holonInfo]) const handleSelectHolon = () => { if (holonInfo) { const holonData: HolonData = { id: holonInfo.id, name: holonInfo.name, description: holonInfo.description, latitude: holonInfo.latitude, longitude: holonInfo.longitude, resolution: holonInfo.resolution, data: holonInfo.data, timestamp: holonInfo.lastUpdated } onSelectHolon(holonData) onClose() } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSearchHolon() } else if (e.key === 'Escape') { onClose() } } if (!isOpen) return null const contentStyle: React.CSSProperties = shapeMode ? { width: '100%', height: '100%', overflow: 'auto', padding: '20px', position: 'relative', display: 'flex', flexDirection: 'column', } : {} const renderContent = () => ( <> {!shapeMode && (

🌐 Holon Browser

Enter a Holon ID (numeric like 1002848305066 or H3 cell like 872a1070bffffff) to browse its data

)}
{/* Holon ID Input */}
setHolonId(e.target.value)} onKeyDown={handleKeyDown} placeholder="e.g., 1002848305066 or 872a1070bffffff" className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 z-[10001] relative" disabled={isLoading} style={{ zIndex: 10001 }} />
{error && (

{error}

)}
{/* Holon Information */} {holonInfo && (

📍 {holonInfo.name}

{holonInfo.resolution >= 0 ? ( <>

Coordinates

{holonInfo.latitude.toFixed(6)}, {holonInfo.longitude.toFixed(6)}

Resolution

{holonInfo.resolutionName} (Level {holonInfo.resolution})

) : (

Type

{holonInfo.resolutionName}

)}

Holon ID

{holonInfo.id}

Last Updated

{new Date(holonInfo.lastUpdated).toLocaleString()}

{holonInfo.description && (

Description

{holonInfo.description}

)} {/* Available Lenses */}

Available Data Categories

{lenses.map((lens) => ( ))}
{/* Lens Data */} {selectedLens && (

Data: {selectedLens}

{isLoadingData && ( Loading... )}
{lensData && (
                      {JSON.stringify(lensData, null, 2)}
                    
)} {!lensData && !isLoadingData && (

No data available for this category

)}
)} {/* Action Buttons */}
)}
) // If in shape mode, return content without modal overlay if (shapeMode) { return (
{renderContent()}
) } // Otherwise, return with modal overlay return (
e.stopPropagation()} > {renderContent()}
) }