/**
 * This component is an object inspector, similar to what you'd see if you did console.log(MyFancyObject) inside of chrome dev tools. It was forked so that I could add highlighting to the nodes and values.
 */
import React, { Component } from "react";
import TreeView from "../tree-view/TreeView";
import ObjectRootLabel from "./ObjectRootLabel";
import ObjectLabel from "./ObjectLabel";
import ThemeProvider from "../styles/ThemeProvider";

const createIterator = () =>
	function* (obj: any) {
		// If obj is a primitive then return immediately
		const shouldIterate = (typeof obj === "object" && obj !== null) || typeof obj === "function";
		if (!shouldIterate) return;

		// Objects and Arrays are processed by this
		if (Array.isArray(obj) || !obj[Symbol.iterator]) {
			const keys = Object.getOwnPropertyNames(obj);

			for (const propertyName of keys) {
				if (obj.propertyIsEnumerable(propertyName)) {
					const propertyValue = obj[propertyName];
					yield {
						name: propertyName || '""',
						data: propertyValue,
					};
				}
			}
			return;
		}
		// This code doesn't seem to get hit. Possibly it gets called if a function is passed, though the central logger never has that situation
		let i = 0;
		for (const entry of obj) {
			// Normally, the entries are tuples, name value pairs (though it's technically possible for an object to have non-tuple members)
			const isTuple = Array.isArray(entry) && entry.length === 2;
			yield isTuple
				? {
						name: entry[0],
						data: entry[1],
				  }
				: {
						name: i.toString(),
						data: entry,
				  };
			i++;
		}
	};

const defaultNodeRenderer = ({ onMatchFound, searchBoxText, filterStrings, depth, name, data, isNonenumerable }) =>
	depth === 0 ? (
		<ObjectRootLabel
			onMatchFound={onMatchFound}
			searchBoxText={searchBoxText}
			filterStrings={filterStrings}
			name={name}
			data={data}
		/>
	) : (
		<ObjectLabel
			onMatchFound={onMatchFound}
			searchBoxText={searchBoxText}
			filterStrings={filterStrings}
			name={name}
			data={data}
			isNonenumerable={isNonenumerable}
		/>
	);

/**
 * Tree-view for objects
 */
class ObjectInspector extends Component<{
	/** An integer specifying to which level the tree should be initially expanded. */
	expandLevel?: number;
	/** An array containing all the paths that should be expanded when the component is initialized, or a string of just one path */
	expandPaths?: string[];
	name?: string;
	/** Not required prop because we also allow undefined value */
	data: any;
	/** A known theme or theme object */
	theme: string | object;
	/** Provide a custom nodeRenderer */
	nodeRenderer?: (args: any) => JSX.Element;
	searchBoxText: string;
	filterStrings: string[];
	expanded?: boolean;
}> {
	state: any;

	constructor(props) {
		super(props);
		this.state = {
			expandLevel: props.expandLevel,
			allExpanded: false,
		};
	}

	render() {
		const { nodeRenderer, theme = "chromeLight", name = "", expanded = false } = this.props;
		const { expandLevel } = this.state;
		const dataIterator = createIterator();

		const renderer = nodeRenderer || defaultNodeRenderer;
		return (
			<ThemeProvider theme={theme}>
				<TreeView
					{...this.props}
					expanded={expanded}
					name={name}
					nodeRenderer={renderer}
					dataIterator={dataIterator}
					onMatchFound={expandLevel}
				/>
			</ThemeProvider>
		);
	}
}

export default React.memo(ObjectInspector);
