<commit_before>import {Assert, emptyObj, nl, ToJSON, Vector2i, VRect, WaitXThenRun, E} from "js-vextensions";
import * as React from "react";
import {Droppable, DroppableProvided, DroppableStateSnapshot} from "react-beautiful-dnd";
import {Button, Column, Div, Row} from "react-vcomponents";
import {BaseComponentPlus, BaseComponentWithConnector, GetDOM, RenderSource, WarnOfTransientObjectProps} from "react-vextensions";
import {GetFillPercent_AtPath} from "Store/firebase/nodeRatings";
import {GetNodeChildrenL3, HolderType} from "Store/firebase/nodes";
import {MapNodeL3,Polarity} from "Store/firebase/nodes/@MapNode";
import {ArgumentType} from "Store/firebase/nodes/@MapNodeRevision";
import {MapNodeType, MapNodeType_Info} from "Store/firebase/nodes/@MapNodeType";
import {NodeConnectorBackground} from "UI/@Shared/Maps/MapNode/NodeConnectorBackground";
import {NodeUI} from "UI/@Shared/Maps/MapNode/NodeUI";
import {Icon, IsSpecialEmptyArray, MaybeLog, Observer} from "vwebapp-framework";
import {DroppableInfo} from "Utils/UI/DNDStructures";
import {ES} from "Utils/UI/GlobalStyles";
import {store} from "Store";
import {GetNodeView} from "Store/main/maps/mapViews/$mapView";
import {runInAction} from "mobx";
import {Map} from "../../../../../Store/firebase/maps/@Map";
import {IsMultiPremiseArgument} from "../../../../../Store/firebase/nodes/$node";

import {ArgumentsControlBar} from "../ArgumentsControlBar";
import {NodeChildHolderBox} from "./NodeChildHolderBox";

type Props = {
	map: Map, node: MapNodeL3, path: string, nodeChildrenToShow: MapNodeL3[], type: HolderType,
	separateChildren: boolean, showArgumentsControlBar: boolean, linkSpawnPoint: number, vertical?: boolean, minWidth?: number,
	onHeightOrDividePointChange?: (dividePoint: number)=>void,
};
const initialState = {
	childrenWidthOverride: null as number,
	oldChildBoxOffsets: null as {[key: number]: Vector2i},
	placeholderRect: null as VRect,
};

@WarnOfTransientObjectProps
@Observer
export class NodeChildHolder extends BaseComponentPlus({minWidth: 0} as Props, initialState, {} as {nodeChildren_fillPercents: {[key: string]: number}}) {
	/* static ValidateProps(props) {
		let {node, path} = props;
		//Assert(SplitStringBySlash_Cached(path).Distinct().length == SplitStringBySlash_Cached(path).length, `Node path contains a circular link! (${path})`);
	} */

	childBoxes: {[key: number]: NodeUI} = {};
	render() {
		const {map, node, path, nodeChildrenToShow, type, separateChildren, showArgumentsControlBar, linkSpawnPoint, vertical, minWidth, onHeightOrDividePointChange} = this.props;
		let {childrenWidthOverride, oldChildBoxOffsets, placeholderRect} = this.state;
		childrenWidthOverride = (childrenWidthOverride | 0).KeepAtLeast(minWidth);

		const nodeView = GetNodeView(map._key, path);
		const nodeChildren_fillPercents = IsSpecialEmptyArray(nodeChildrenToShow) ? emptyObj : nodeChildrenToShow.filter(a=>a).ToMap(child=>`${child._key}`, child=>{
			return GetFillPercent_AtPath(child, `${path}/${child._key}`);
		});
		this.Stash({nodeChildren_fillPercents});

		const {initialChildLimit} = store.main.maps;
		const {currentNodeBeingAdded_path} = store.main.maps;

		let nodeChildrenToShowHere = nodeChildrenToShow;
		let nodeChildrenToShowInRelevanceBox;
		if (IsMultiPremiseArgument(node) && type != HolderType.Relevance) {
			nodeChildrenToShowHere = nodeChildrenToShow.filter(a=>a && a.type != MapNodeType.Argument);
			nodeChildrenToShowInRelevanceBox = nodeChildrenToShow.filter(a=>a && a.type == MapNodeType.Argument);
		}

		let upChildren = separateChildren ? nodeChildrenToShowHere.filter(a=>a.finalPolarity == Polarity.Supporting) : [];
		let downChildren = separateChildren ? nodeChildrenToShowHere.filter(a=>a.finalPolarity == Polarity.Opposing) : [];

		// apply sorting (regardless of direction, both are ordered by score/priority; "up" reordering is applied on the *child-ui list*, not the child-node list)
		if (separateChildren) {
			upChildren = upChildren.OrderByDescending(child=>nodeChildren_fillPercents[child._key]);
			downChildren = downChildren.OrderByDescending(child=>nodeChildren_fillPercents[child._key]);
		} else {
			nodeChildrenToShowHere = nodeChildrenToShowHere.OrderByDescending(child=>nodeChildren_fillPercents[child._key]);
			// if (IsArgumentNode(node)) {
			const isArgument_any = node.type == MapNodeType.Argument && node.current.argumentType == ArgumentType.Any;
			if (node.childrenOrder && !isArgument_any) {
				nodeChildrenToShowHere = nodeChildrenToShowHere.OrderBy(child=>node.childrenOrder.indexOf(child._key).IfN1Then(Number.MAX_SAFE_INTEGER));
			}
		}

		let childLimit_up = ((nodeView || {}).childLimit_up || initialChildLimit).KeepAtLeast(initialChildLimit);
		let childLimit_down = ((nodeView || {}).childLimit_down || initialChildLimit).KeepAtLeast(initialChildLimit);
		// if the map's root node, or an argument node, show all children
		const showAll = node._key == map.rootNode || node.type == MapNodeType.Argument;
		if (showAll) [childLimit_up, childLimit_down] = [100, 100];

		const RenderChild = (child: MapNodeL3, index: number, collection, direction = "down" as "up" | "down")=>{
			/* if (pack.node.premiseAddHelper) {
				return <PremiseAddHelper mapID={map._id} parentNode={node} parentPath={path}/>;
			} */

			const childLimit = direction == "down" ? childLimit_down : childLimit_up;
			const isFarthestChildFromDivider = index == (direction == "down" ? childLimit - 1 : 0);
			// const isFarthestChildFromDivider = index == childLimit - 1;
			return (
				// <ErrorBoundary errorUI={props=>props.defaultUI(E(props, {style: {width: 500, height: 300}}))}>
				// <ErrorBoundary key={child._key} errorUIStyle={{ width: 500, height: 300 }}>
				<NodeUI key={child._key} ref={c=>this.childBoxes[child._key] = c} indexInNodeList={index} map={map} node={child}
					path={`${path}/${child._key}`} widthOverride={childrenWidthOverride} onHeightOrPosChange={this.OnChildHeightOrPosChange}>
					{isFarthestChildFromDivider && !showAll && (collection.length > childLimit || childLimit != initialChildLimit) &&
						<ChildLimitBar {...{map, path, childrenWidthOverride, childLimit}} direction={direction} childCount={collection.length}/>}
				</NodeUI>
			);
		};

		const RenderGroup = (group: "all" | "up" | "down")=>{
			const direction = group == "up" ? "up" : "down";
			const refName = `${group}ChildHolder`;
			const childLimit = group == "up" ? childLimit_up : childLimit_down; // "all" and "down" share a child-limit
			const childrenHere = group == "all" ? nodeChildrenToShowHere : group == "up" ? upChildren : downChildren;
			// if direction is up, we need to have the first-in-children-array/highest-fill-percent entries show at the *bottom*, so reverse the children-here array
			if (direction == "up") childrenHere.reverse();
			const childrenHereUIs = childrenHere.slice(0, childLimit).map((pack, index)=>{
				return RenderChild(pack, index, childrenHere, direction);
			});
			// if direction is up, we need to have the first-in-children-array/highest-fill-percent entries show at the *bottom*, so reverse the children-uis array
			// if (direction == 'up') childrenHereUIs.reverse();

			const dragBox = document.querySelector(".NodeUI_Inner.DragPreview");
			const dragBoxRect = dragBox && VRect.FromLTWH(dragBox.getBoundingClientRect());

			return (
				<Droppable type="MapNode" droppableId={ToJSON(droppableInfo.VSet({subtype: group, childIDs: childrenHere.map(a=>a._key)}))} /* renderClone={(provided, snapshot, descriptor) => {
					const index = descriptor.index;
					const pack = childrenHere.slice(0, childLimit)[index];
					return RenderChild(pack, index, childrenHere);
				}} */>
					{(provided: DroppableProvided, snapshot: DroppableStateSnapshot)=>{
						const dragIsOverDropArea = provided.placeholder.props["on"] != null;
						if (dragIsOverDropArea) {
							WaitXThenRun(0, ()=>this.StartGeneratingPositionedPlaceholder(group));
						}

						return (
							<Column ref={c=>{ this[`${group}ChildHolder`] = c; provided.innerRef(GetDOM(c) as any); }} ct className={refName} {...provided.droppableProps}
								style={E(
									{position: "relative"},
									childrenHere.length == 0 && {position: "absolute", top: group == "down" ? "100%" : 0, width: MapNodeType_Info.for[MapNodeType.Claim].minWidth, height: 100},
								)}>
								{/* childrenHere.length == 0 && <div style={{ position: 'absolute', top: '100%', width: '100%', height: 200 }}/> */}
								{childrenHereUIs}<commit_msg>* Fix the sorting isn't working correctly for nodes, in up group, when child-limit-bar is showing. (ie. lowest scores shows in first group)<commit_after>import {Assert, emptyObj, nl, ToJSON, Vector2i, VRect, WaitXThenRun, E} from "js-vextensions";
import * as React from "react";
import {Droppable, DroppableProvided, DroppableStateSnapshot} from "react-beautiful-dnd";
import {Button, Column, Div, Row} from "react-vcomponents";
import {BaseComponentPlus, BaseComponentWithConnector, GetDOM, RenderSource, WarnOfTransientObjectProps} from "react-vextensions";
import {GetFillPercent_AtPath} from "Store/firebase/nodeRatings";
import {GetNodeChildrenL3, HolderType} from "Store/firebase/nodes";
import {MapNodeL3, Polarity} from "Store/firebase/nodes/@MapNode";
import {ArgumentType} from "Store/firebase/nodes/@MapNodeRevision";
import {MapNodeType, MapNodeType_Info} from "Store/firebase/nodes/@MapNodeType";
import {NodeConnectorBackground} from "UI/@Shared/Maps/MapNode/NodeConnectorBackground";
import {NodeUI} from "UI/@Shared/Maps/MapNode/NodeUI";
import {Icon, IsSpecialEmptyArray, MaybeLog, Observer} from "vwebapp-framework";
import {DroppableInfo} from "Utils/UI/DNDStructures";
import {ES} from "Utils/UI/GlobalStyles";
import {store} from "Store";
import {GetNodeView} from "Store/main/maps/mapViews/$mapView";
import {runInAction} from "mobx";
import {Map} from "../../../../../Store/firebase/maps/@Map";
import {IsMultiPremiseArgument} from "../../../../../Store/firebase/nodes/$node";

import {ArgumentsControlBar} from "../ArgumentsControlBar";
import {NodeChildHolderBox} from "./NodeChildHolderBox";

type Props = {
	map: Map, node: MapNodeL3, path: string, nodeChildrenToShow: MapNodeL3[], type: HolderType,
	separateChildren: boolean, showArgumentsControlBar: boolean, linkSpawnPoint: number, vertical?: boolean, minWidth?: number,
	onHeightOrDividePointChange?: (dividePoint: number)=>void,
};
const initialState = {
	childrenWidthOverride: null as number,
	oldChildBoxOffsets: null as {[key: number]: Vector2i},
	placeholderRect: null as VRect,
};

@WarnOfTransientObjectProps
@Observer
export class NodeChildHolder extends BaseComponentPlus({minWidth: 0} as Props, initialState, {} as {nodeChildren_fillPercents: {[key: string]: number}}) {
	/* static ValidateProps(props) {
		let {node, path} = props;
		//Assert(SplitStringBySlash_Cached(path).Distinct().length == SplitStringBySlash_Cached(path).length, `Node path contains a circular link! (${path})`);
	} */

	childBoxes: {[key: number]: NodeUI} = {};
	render() {
		const {map, node, path, nodeChildrenToShow, type, separateChildren, showArgumentsControlBar, linkSpawnPoint, vertical, minWidth, onHeightOrDividePointChange} = this.props;
		let {childrenWidthOverride, oldChildBoxOffsets, placeholderRect} = this.state;
		childrenWidthOverride = (childrenWidthOverride | 0).KeepAtLeast(minWidth);

		const nodeView = GetNodeView(map._key, path);
		const nodeChildren_fillPercents = IsSpecialEmptyArray(nodeChildrenToShow) ? emptyObj : nodeChildrenToShow.filter(a=>a).ToMap(child=>`${child._key}`, child=>{
			return GetFillPercent_AtPath(child, `${path}/${child._key}`);
		});
		this.Stash({nodeChildren_fillPercents});

		const {initialChildLimit} = store.main.maps;
		const {currentNodeBeingAdded_path} = store.main.maps;

		let nodeChildrenToShowHere = nodeChildrenToShow;
		let nodeChildrenToShowInRelevanceBox;
		if (IsMultiPremiseArgument(node) && type != HolderType.Relevance) {
			nodeChildrenToShowHere = nodeChildrenToShow.filter(a=>a && a.type != MapNodeType.Argument);
			nodeChildrenToShowInRelevanceBox = nodeChildrenToShow.filter(a=>a && a.type == MapNodeType.Argument);
		}

		let upChildren = separateChildren ? nodeChildrenToShowHere.filter(a=>a.finalPolarity == Polarity.Supporting) : [];
		let downChildren = separateChildren ? nodeChildrenToShowHere.filter(a=>a.finalPolarity == Polarity.Opposing) : [];

		// apply sorting (regardless of direction, both are ordered by score/priority; "up" reordering is applied on the *child-ui list*, not the child-node list)
		if (separateChildren) {
			upChildren = upChildren.OrderByDescending(child=>nodeChildren_fillPercents[child._key]);
			downChildren = downChildren.OrderByDescending(child=>nodeChildren_fillPercents[child._key]);
		} else {
			nodeChildrenToShowHere = nodeChildrenToShowHere.OrderByDescending(child=>nodeChildren_fillPercents[child._key]);
			// if (IsArgumentNode(node)) {
			const isArgument_any = node.type == MapNodeType.Argument && node.current.argumentType == ArgumentType.Any;
			if (node.childrenOrder && !isArgument_any) {
				nodeChildrenToShowHere = nodeChildrenToShowHere.OrderBy(child=>node.childrenOrder.indexOf(child._key).IfN1Then(Number.MAX_SAFE_INTEGER));
			}
		}

		let childLimit_up = ((nodeView || {}).childLimit_up || initialChildLimit).KeepAtLeast(initialChildLimit);
		let childLimit_down = ((nodeView || {}).childLimit_down || initialChildLimit).KeepAtLeast(initialChildLimit);
		// if the map's root node, or an argument node, show all children
		const showAll = node._key == map.rootNode || node.type == MapNodeType.Argument;
		if (showAll) [childLimit_up, childLimit_down] = [100, 100];

		const RenderChild = (child: MapNodeL3, index: number, collection_untrimmed: MapNodeL3[], direction = "down" as "up" | "down")=>{
			/* if (pack.node.premiseAddHelper) {
				return <PremiseAddHelper mapID={map._id} parentNode={node} parentPath={path}/>;
			} */

			const childLimit = direction == "down" ? childLimit_down : childLimit_up;
			const isFarthestChildFromDivider = index == (direction == "down" ? childLimit - 1 : 0);
			// const isFarthestChildFromDivider = index == childLimit - 1;
			return (
				// <ErrorBoundary errorUI={props=>props.defaultUI(E(props, {style: {width: 500, height: 300}}))}>
				// <ErrorBoundary key={child._key} errorUIStyle={{ width: 500, height: 300 }}>
				<NodeUI key={child._key} ref={c=>this.childBoxes[child._key] = c} indexInNodeList={index} map={map} node={child}
					path={`${path}/${child._key}`} widthOverride={childrenWidthOverride} onHeightOrPosChange={this.OnChildHeightOrPosChange}>
					{isFarthestChildFromDivider && !showAll && (collection_untrimmed.length > childLimit || childLimit != initialChildLimit) &&
						<ChildLimitBar {...{map, path, childrenWidthOverride, childLimit}} direction={direction} childCount={collection_untrimmed.length}/>}
				</NodeUI>
			);
		};

		const RenderGroup = (group: "all" | "up" | "down")=>{
			const direction = group == "up" ? "up" : "down";
			const refName = `${group}ChildHolder`;
			const childLimit = group == "up" ? childLimit_up : childLimit_down; // "all" and "down" share a child-limit

			const childrenHere_untrimmed = group == "all" ? nodeChildrenToShowHere : group == "up" ? upChildren : downChildren;
			const childrenHere = childrenHere_untrimmed.slice(0, childLimit); // trim to the X most significant children (ie. strongest arguments)
			// if direction is up, we need to have the first-in-children-array/highest-fill-percent entries show at the *bottom*, so reverse the children-here array
			if (direction == "up") childrenHere.reverse();

			const childrenHereUIs = childrenHere.map((pack, index)=>{
				return RenderChild(pack, index, childrenHere_untrimmed, direction);
			});
			// if direction is up, we need to have the first-in-children-array/highest-fill-percent entries show at the *bottom*, so reverse the children-uis array
			// if (direction == 'up') childrenHereUIs.reverse();

			const dragBox = document.querySelector(".NodeUI_Inner.DragPreview");
			const dragBoxRect = dragBox && VRect.FromLTWH(dragBox.getBoundingClientRect());

			return (
				<Droppable type="MapNode" droppableId={ToJSON(droppableInfo.VSet({subtype: group, childIDs: childrenHere.map(a=>a._key)}))} /* renderClone={(provided, snapshot, descriptor) => {
					const index = descriptor.index;
					const pack = childrenHere.slice(0, childLimit)[index];
					return RenderChild(pack, index, childrenHere);
				}} */>
					{(provided: DroppableProvided, snapshot: DroppableStateSnapshot)=>{
						const dragIsOverDropArea = provided.placeholder.props["on"] != null;
						if (dragIsOverDropArea) {
							WaitXThenRun(0, ()=>this.StartGeneratingPositionedPlaceholder(group));
						}

						return (
							<Column ref={c=>{ this[`${group}ChildHolder`] = c; provided.innerRef(GetDOM(c) as any); }} ct className={refName} {...provided.droppableProps}
								style={E(
									{position: "relative"},
									childrenHere.length == 0 && {position: "absolute", top: group == "down" ? "100%" : 0, width: MapNodeType_Info.for[MapNodeType.Claim].minWidth, height: 100},
								)}>
								{/* childrenHere.length == 0 && <div style={{ position: 'absolute', top: '100%', width: '100%', height: 200 }}/> */}
								{childrenHereUIs}
{'fraction_non_alphanumeric': 0.1415121584936175, 'fraction_numerical': 0.006064806792583608, 'mean_word_length': 5.450819672131147, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5625729', 'n_tokens_mistral': 5878, 'n_tokens_neox': 5303, 'n_words': 1204}