import { JsonValue } from "../types/types";
import {
useState,
useMemo,
useCallback,
useRef,
useEffect,
memo,
FC,
ReactElement,
} from "react";
import {
Text,
TouchableOpacity,
View,
StyleSheet,
FlatList,
} from "react-native";
import { displayValue } from "../utils/displayValue";
import { gameUIColors } from "../ui/gameUI";
import { CopyButton } from "../ui/components/CopyButton";
import { ChevronRight } from "../icons";
import { IndentGuidesOverlay } from "./IndentGuidesOverlay";
// Stable constants to prevent re-renders [[memory:4875251]]
const HIT_SLOP_10 = { top: 10, bottom: 10, left: 10, right: 10 };
const ITEM_HEIGHT = 24; // Fixed height per row for crisp guides
const LONG_ITEM_HEIGHT = 24; // Keep uniform height to match VS Code tree
const CHUNK_SIZE = 50; // Process data in chunks to avoid blocking UI
const MAX_DEPTH_LIMIT = 15; // Prevent excessive nesting
const MAX_ITEMS_PER_LEVEL = 500; // Limit items to prevent memory issues
const LONG_KEY_THRESHOLD = 30; // Keys longer than this use vertical layout
// Pre-computed indent styles (VS Code-style width)
const INDENT_WIDTH = 16;
const INDENT_STYLES = Array.from(
{ length: MAX_DEPTH_LIMIT + 1 },
(_, depth) =>
StyleSheet.create({
container: {
marginLeft: depth * INDENT_WIDTH,
},
}).container
);
// Enhanced type color cache using centralized theme colors [[memory:4875251]]
const TYPE_COLOR_CACHE = new Map([
["string", gameUIColors.dataTypes.string],
["number", gameUIColors.dataTypes.number],
["bigint", gameUIColors.optional], // Purple for bigint (distinct from number)
["boolean", gameUIColors.dataTypes.boolean],
["null", gameUIColors.dataTypes.null],
["undefined", gameUIColors.dataTypes.undefined],
["function", gameUIColors.dataTypes.function],
["symbol", gameUIColors.critical], // Pink for symbols (distinct from function)
["date", gameUIColors.critical], // Pink for dates
["error", gameUIColors.error], // Red for errors
["array", gameUIColors.dataTypes.array],
["object", gameUIColors.dataTypes.object],
["map", gameUIColors.info], // Cyan for maps (distinct from object/array)
["set", gameUIColors.success], // Green for sets (distinct from map/array/object)
["circular", gameUIColors.warning], // Yellow for circular references
]);
// Pre-computed stable styles with React Query-inspired design
const STABLE_STYLES = StyleSheet.create({
container: {
backgroundColor: gameUIColors.primary + "08", // bg-white/[0.03]
borderRadius: 8,
borderWidth: 1,
borderColor: gameUIColors.primary + "14", // border-white/[0.08]
// Remove flex: 1 and minHeight to allow natural sizing
},
header: {
flexDirection: "column",
paddingHorizontal: 16, // Increased padding like dev tools
paddingVertical: 12,
},
headerRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 6,
},
title: {
color: gameUIColors.primary, // text-white
fontSize: 14,
fontWeight: "500", // font-medium
},
description: {
color: gameUIColors.secondary, // text-gray-400
fontSize: 12,
marginTop: 2,
},
typeLegend: {
flexDirection: "row",
flexWrap: "wrap",
gap: 6,
marginTop: 8,
paddingTop: 8,
borderTopWidth: 1,
borderTopColor: gameUIColors.primary + "14", // border-white/[0.08]
},
typeBadge: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
borderWidth: 1,
},
typeColor: {
width: 8,
height: 8,
borderRadius: 4,
marginRight: 4,
},
typeName: {
fontSize: 10,
fontWeight: "500",
color: gameUIColors.secondary, // text-gray-400
},
itemContainer: {
minHeight: ITEM_HEIGHT,
backgroundColor: "transparent",
position: "relative",
flexDirection: "row",
alignItems: "flex-start", // Align items to top for better alignment with expand arrows
},
itemTouchable: {
flex: 1,
flexDirection: "row",
alignItems: "flex-start", // Changed from center to align expand arrow with first line of text
paddingLeft: 0, // Remove padding to align with tree lines
paddingRight: 16,
paddingVertical: 2, // Further reduced for even tighter spacing
minHeight: 24, // Match ITEM_HEIGHT for consistency
},
itemTouchablePressed: {
backgroundColor: gameUIColors.primary + "0A", // slightly more visible on press
},
itemSelected: {
backgroundColor: gameUIColors.primary + "14", // selected row highlight (subtle)
},
expanderContainer: {
width: 16, // Reduced to minimize space
alignItems: "center",
justifyContent: "center",
marginTop: 4, // Align with text baseline
},
expanderIcon: {
width: 12,
height: 12,
},
labelContainer: {
flex: 1,
flexDirection: "row",
alignItems: "flex-start",
paddingLeft: 2,
},
labelContainerVertical: {
flex: 1,
flexDirection: "column",
paddingLeft: 2, // Reduced padding for tighter alignment
paddingVertical: 2,
},
labelContainerVerticalRow: {
flexDirection: "row",
alignItems: "center",
marginBottom: 2,
},
labelText: {
color: gameUIColors.primary, // text-white
fontSize: 12,
fontWeight: "500", // font-medium
fontFamily: "monospace",
marginRight: 8,
flexShrink: 1,
},
labelTextTruncated: {
color: gameUIColors.primary, // text-white
fontSize: 12,
fontWeight: "500", // font-medium
fontFamily: "monospace",
flexShrink: 1,
},
valueText: {
fontSize: 12,
fontFamily: "monospace",
flex: 1,
color: gameUIColors.primaryLight, // text-gray-300
},
loadingContainer: {
padding: 16,
alignItems: "center",
},
loadingText: {
color: gameUIColors.secondary, // text-gray-400
fontSize: 12,
},
noDataContainer: {
padding: 16,
alignItems: "center",
},
noDataText: {
color: gameUIColors.secondary, // text-gray-400
fontSize: 12,
},
listContent: {
paddingBottom: 8,
},
headerTouchable: {
flex: 1,
flexDirection: "row",
alignItems: "center",
},
expanderMargin: {
marginLeft: 8,
},
});
// Type definitions for flattened data structure
interface FlatDataItem {
id: string;
key: string;
value: JsonValue;
valueType: string;
depth: number;
isExpandable: boolean;
isExpanded: boolean;
parentId?: string;
hasChildren: boolean;
childCount: number;
path: string[];
type: string; // For FlatList optimization
isLastChild?: boolean; // Track if this is the last child of its parent
parentHasMoreSiblings?: boolean[]; // Track which parent levels have more siblings
siblingIndex?: number; // Index among siblings
totalSiblings?: number; // Total number of siblings
}
// Enhanced type detection optimized for performance
const getValueType = (value: JsonValue): string => {
if (value === null) return "null";
if (value === undefined) return "undefined";
if (Array.isArray(value)) return "array";
if (value instanceof Date) return "date";
if (value instanceof Error) return "error";
if (value instanceof Map) return "map";
if (value instanceof Set) return "set";
if (value instanceof RegExp) return "regexp";
if (typeof value === "function") return "function";
if (typeof value === "symbol") return "symbol";
if (typeof value === "bigint") return "bigint";
if (typeof value === "object") return "object";
return typeof value;
};
// Get value count for collections
const getValueCount = (value: JsonValue, valueType: string): number => {
if (value === null) return 0;
switch (valueType) {
case "array":
return Array.isArray(value) ? value.length : 0;
case "object":
return typeof value === "object" &&
!(value instanceof Date) &&
!(value instanceof Error) &&
!(value instanceof RegExp) &&
!(value instanceof Map) &&
!(value instanceof Set)
? Object.keys(value).length
: 0;
case "map":
return value instanceof Map ? value.size : 0;
case "set":
return value instanceof Set ? value.size : 0;
default:
return 0;
}
};
// Format value for display
const formatValue = (value: JsonValue, valueType: string): string => {
if (value === null) return "null";
if (value === undefined) return "undefined";
switch (valueType) {
case "string":
return `"${String(value)}"`;
case "boolean":
return value === true ? "true" : "false";
case "function":
return typeof value === "function"
? value.toString().slice(0, 50) + "..."
: "undefined";
case "symbol":
return typeof value === "symbol" ? String(value) : "undefined";
case "date":
return value instanceof Date ? value.toISOString() : "undefined";
case "regexp":
return value instanceof RegExp ? value.toString() : "undefined";
case "bigint":
return typeof value === "bigint" ? value.toString() + "n" : "undefined";
case "error":
return value instanceof Error
? `${value.name}: ${value.message}`
: "undefined";
default:
return displayValue(value);
}
};
// Optimized type color lookup using cache [[memory:4875251]]
const getTypeColor = (valueType: string): string => {
return TYPE_COLOR_CACHE.get(valueType) || gameUIColors.dataTypes.array;
};
// Memoized components for performance
const ExpanderComponent = ({
expanded,
onPress,
}: {
expanded: boolean;
onPress: () => void;
}) => {
return (
);
};
ExpanderComponent.displayName = "Expander";
const Expander = memo(ExpanderComponent);
// Type legend component to replace inline type indicators
const TypeLegendComponent = ({
visibleTypes,
}: {
visibleTypes: string[];
}): ReactElement => {
const uniqueTypes = Array.from(new Set(visibleTypes)).slice(0, 8); // Limit to 8 most common types
return (
{uniqueTypes.map((type) => {
const color = getTypeColor(type);
return (
{type}
);
})}
);
};
TypeLegendComponent.displayName = "TypeLegend";
const TypeLegend = memo(TypeLegendComponent);
// Optimized data flattening with chunked processing to prevent UI blocking [[memory:4875251]]
const useDataFlattening = (
data: JsonValue,
maxDepth = 10,
autoExpandFirstLevel = false
) => {
const [flatData, setFlatData] = useState([]);
const flatDataMapRef = useRef<
Map
>(new Map());
// Initialize with root expanded and optionally first level
const getInitialExpanded = useCallback(() => {
const initial = new Set(["root"]);
if (autoExpandFirstLevel && data && typeof data === "object") {
if (Array.isArray(data)) {
data.forEach((_, index) => {
initial.add(`root.${index}`);
});
} else {
Object.keys(data).forEach((key) => {
initial.add(`root.${key}`);
});
}
}
return initial;
}, [autoExpandFirstLevel, data]);
const [expandedItems, setExpandedItems] = useState>(() =>
getInitialExpanded()
);
const [isProcessing, setIsProcessing] = useState(false);
// Debug logging - commented out for less noise
// Store circular cache outside of re-renders to prevent reset
const circularCacheRef = useRef>(new WeakSet