var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { ChevronDown, ChevronRight, File, Folder, } from '@procore/core-icons/dist';
import React from 'react';
import { areEqual, FixedSizeList } from 'react-window';
import { useIntersectionObserver } from '../../effects/IntersectionObserver';
import { mergeRefs } from '../../effects/mergeRefs';
import { useResizeObserver } from '../../effects/ResizeObserver';
import { useI18nContext } from '../../state/I18n';
import { useTimer } from '../../state/Timer';
import { useVisibility } from '../../state/Visibility';
import { parseFilename } from '../../utils/filename';
import { Overlay } from '../Overlay';
import { Spinner } from '../Spinner';
import { Tooltip } from '../Tooltip';
import { useTree } from './Tree.hooks';
import { StyledChevronContainer, StyledFilenameCaption, StyledIconContainer, StyledTree, StyledTreeRowContainer, StyledTreeRowContent, StyledTreeRowWrapper, } from './Tree.styles';
var spacing = 32;
var rootSpacing = 24;
var rowHeight = 36;
// Selected nodes have a bold text, and browsers occasionally miscalculate scrollWidth by one pixel down.
// That situations might cause an issue when we get a second horizontal scrollbar.
// This padding serves as compensation to prevent the described issue.
var rowRightPadding = 12;
// For most of browsers it usually varies between 12px and 20px.
// To avoid tricky cross-browser calculations of scrollbar width. The maximum width is used.
var scrollbarWidth = 20;
function getPadding(level) {
    return rootSpacing + level * spacing;
}
var intersectionThresholds = Array.from({ length: 101 }).map(function (_, index) { return index / 100; });
var tooltipDelay = 300;
// TODO refactor this to not be upper snake case
export var ROOT_NODE_ID = 'ROOT_ID';
var defaultIsExpandable = function (node) { return defaultGetType(node) === 'branch'; };
var defaultIsSelectable = function (node) { return node.type === 'leaf'; };
var defaultGetType = function (node) {
    return node.type;
};
var defaultGetParentId = function (node) { return node.parentId; };
var defaultGetLabel = function (node) { return node.name; };
function noop() { }
/**

 The tree allows users to navigate between folder / file directories,
 and optionally select those files.

 @since 10.19.0

 @see [Storybook](https://procore.github.io/core/latest/?path=/story/demos-tree--demo)

 @see [Design Guidelines](https://design.procore.com/tree)

 */
export var Tree = React.forwardRef(function (_a, ref) {
    var options = _a.options, _b = _a.expanded, expanded = _b === void 0 ? [] : _b, _c = _a.selected, selected = _c === void 0 ? [] : _c, _d = _a.selectionLimit, selectionLimit = _d === void 0 ? Infinity : _d, getRootBase = _a.getRoot, getChildrenBase = _a.getChildren, getIconBase = _a.getIcon, _e = _a.getLabel, getLabel = _e === void 0 ? defaultGetLabel : _e, _f = _a.getParentId, getParentId = _f === void 0 ? defaultGetParentId : _f, _g = _a.getType, getType = _g === void 0 ? defaultGetType : _g, _h = _a.isExpandable, isExpandable = _h === void 0 ? defaultIsExpandable : _h, _j = _a.isSelectable, isSelectable = _j === void 0 ? defaultIsSelectable : _j, _k = _a.multiple, multiple = _k === void 0 ? true : _k, _l = _a.onSelect, onSelect = _l === void 0 ? noop : _l, _m = _a.onCollapse, onCollapse = _m === void 0 ? noop : _m, _o = _a.onExpand, onExpand = _o === void 0 ? function () { return Promise.resolve(); } : _o, rowRendererBase = _a.rowRenderer, _p = _a.maxVisibleNodes, maxVisibleNodes = _p === void 0 ? Infinity : _p, visibleHeight = _a.visibleHeight, _q = _a.autoExpandParent, autoExpandParent = _q === void 0 ? true : _q, innerElementType = _a.innerElementType, outerElementType = _a.outerElementType, onKeyDown = _a.onKeyDown, children = _a.children, props = __rest(_a, ["options", "expanded", "selected", "selectionLimit", "getRoot", "getChildren", "getIcon", "getLabel", "getParentId", "getType", "isExpandable", "isSelectable", "multiple", "onSelect", "onCollapse", "onExpand", "rowRenderer", "maxVisibleNodes", "visibleHeight", "autoExpandParent", "innerElementType", "outerElementType", "onKeyDown", "children"]);
    var wrapperRef = React.useRef(null);
    var listRef = React.useRef(null);
    var listInnerRef = React.useRef(null);
    var listOuterRefRef = React.useRef(null);
    var _r = React.useState(0), contentMaxWidth = _r[0], setContentMaxWidth = _r[1];
    var defaultGetRoot = React.useCallback(function (nodes) {
        return nodes.find(function (node) { return defaultGetParentId(node) === ROOT_NODE_ID; });
    }, []);
    var getRoot = getRootBase || defaultGetRoot;
    var rootNode = React.useMemo(function () { return getRoot(options); }, [options, getRoot]);
    var _s = React.useState(false), isTreeFocused = _s[0], setIsTreeFocused = _s[1];
    var _t = React.useState({
        width: 0,
        height: 0,
    }), containerSize = _t[0], setContainerSize = _t[1];
    var handleContainerResize = React.useCallback(function (entries) {
        if (entries[0]) {
            var _a = entries[0]
                .target, height = _a.clientHeight, width = _a.clientWidth;
            setContainerSize({ width: width, height: height });
        }
    }, [setContainerSize]);
    var setResizeObserverTarget = useResizeObserver(handleContainerResize);
    var defaultGetChildren = React.useCallback(function (node) {
        return Array.isArray(options)
            ? options.filter(function (childNode) { return getParentId(childNode) === node.id; })
            : [];
    }, [getParentId, options]);
    var getChildren = getChildrenBase || defaultGetChildren;
    var _u = useTree({
        options: options,
        expanded: expanded,
        selected: selected,
        selectionLimit: selectionLimit,
        multiple: multiple,
        autoExpandParent: autoExpandParent,
        onSelect: onSelect,
        onCollapse: onCollapse,
        onExpand: onExpand,
        getRoot: getRoot,
        getChildren: getChildren,
        getParentId: getParentId,
    }), nodes = _u.nodes, isExpanded = _u.isExpanded, isSelected = _u.isSelected, isLoading = _u.isLoading, highlightedNode = _u.highlightedNode, isNodeHighlighted = _u.isHighlighted, highlight = _u.highlight, handleSelection = _u.handleSelection, handleExpansion = _u.handleExpansion, _setExpanded = _u.setExpanded, _setSelected = _u.setSelected, isFileLimitReached = _u.isFileLimitReached, listNavigation = _u.listNavigation;
    var setSelected = React.useCallback(function (selected) {
        var nodes = Array.isArray(selected) ? selected : [selected];
        _setSelected(new Set(nodes.map(function (node) { return node.id; })));
    }, [_setSelected]);
    var setExpanded = React.useCallback(function (expanded) {
        var nodes = Array.isArray(expanded) ? expanded : [expanded];
        _setExpanded(new Set(nodes.map(function (node) { return node.id; })));
    }, [_setExpanded]);
    React.useImperativeHandle(ref, function () { return ({
        rootEl: wrapperRef.current,
        setSelected: setSelected,
        setExpanded: setExpanded,
        toggleSelected: function (node) { return handleSelection(node); },
        toggleExpanded: function (node) { return handleExpansion(node); },
    }); });
    var defaultGetIcon = React.useCallback(function (node) {
        var isFolder = getType(node) === 'branch';
        return isFolder ? React.createElement(Folder, null) : React.createElement(File, null);
    }, [getType]);
    var getIcon = getIconBase || defaultGetIcon;
    var isHighlighted = React.useCallback(function (node) { return isTreeFocused && isNodeHighlighted(node); }, [isNodeHighlighted, isTreeFocused]);
    var handleAccessibility = function (e) {
        onKeyDown && onKeyDown(e);
        var key = e.key;
        switch (key) {
            case 'Enter':
                e.preventDefault();
                isSelectable(highlightedNode) && handleSelection(highlightedNode);
                break;
            case 'Space Bar':
            case ' ':
                e.preventDefault();
                isExpandable(highlightedNode) && handleExpansion(highlightedNode);
                break;
            case 'ArrowDown':
            case 'Down':
                e.preventDefault();
                listNavigation.increment();
                break;
            case 'ArrowUp':
            case 'Up':
                e.preventDefault();
                listNavigation.decrement();
                break;
        }
    };
    React.useEffect(function () {
        var _a;
        if (isTreeFocused) {
            (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollToItem(listNavigation.index);
        }
    }, [isTreeFocused, listNavigation.index]);
    var defaultRowRenderer = React.useCallback(function (props) { return React.createElement(TreeRow, __assign({}, props)); }, []);
    React.useEffect(function () {
        var _a;
        var rowContentSizes = [];
        var childNodes = Array.from(((_a = listInnerRef.current) === null || _a === void 0 ? void 0 : _a.childNodes) || []);
        childNodes.forEach(function (child) {
            var _a;
            var rowContent = child.querySelector('[data-row-content]');
            if (rowContent) {
                var leftPadding = (_a = child.dataset.padding) !== null && _a !== void 0 ? _a : '0';
                rowContentSizes.push(rowContent.scrollWidth + parseInt(leftPadding, 10) + rowRightPadding);
            }
        });
        var maxSizeRowContent = Math.max.apply(Math, rowContentSizes) + scrollbarWidth;
        setContentMaxWidth(maxSizeRowContent);
    });
    var rowRenderer = rowRendererBase || defaultRowRenderer;
    var itemData = React.useMemo(function () { return ({
        nodes: nodes,
        getIcon: getIcon,
        getLabel: getLabel,
        getType: getType,
        isExpandable: isExpandable,
        isSelectable: isSelectable,
        isExpanded: isExpanded,
        isSelected: isSelected,
        isLoading: isLoading,
        highlight: highlight,
        isHighlighted: isHighlighted,
        handleSelection: handleSelection,
        handleExpansion: handleExpansion,
        rowRenderer: rowRenderer,
        isFileLimitReached: isFileLimitReached,
        selectionLimit: selectionLimit,
        isTreeFocused: isTreeFocused,
        treeContainer: wrapperRef.current,
    }); }, [
        getIcon,
        getLabel,
        getType,
        handleExpansion,
        handleSelection,
        highlight,
        isExpandable,
        isExpanded,
        isFileLimitReached,
        isHighlighted,
        isLoading,
        isSelectable,
        isSelected,
        isTreeFocused,
        nodes,
        rowRenderer,
        selectionLimit,
    ]);
    if (!rootNode) {
        console.error('Tree must have a valid root node');
        return null;
    }
    return (React.createElement(StyledTree, __assign({}, props, { ref: mergeRefs(wrapperRef, setResizeObserverTarget), tabIndex: 0, onFocus: function () { return setIsTreeFocused(true); }, onBlur: function () { return setIsTreeFocused(false); }, onKeyDown: handleAccessibility }),
        React.createElement(FixedSizeList, { ref: listRef, height: visibleHeight ||
                Math.min(nodes.length * rowHeight, rowHeight * maxVisibleNodes), innerRef: listInnerRef, outerRef: listOuterRefRef, itemCount: nodes.length, itemSize: rowHeight, innerElementType: innerElementType, outerElementType: outerElementType, itemData: itemData, width: Math.max(contentMaxWidth, containerSize.width) }, TreeNode)));
});
var TreeNode = React.memo(function (_a) {
    var _b = _a.data, nodes = _b.nodes, rowRenderer = _b.rowRenderer, highlight = _b.highlight, isTreeFocused = _b.isTreeFocused, props = __rest(_b, ["nodes", "rowRenderer", "highlight", "isTreeFocused"]), index = _a.index, style = _a.style;
    var node = nodes[index];
    var isHighlighted = props.isHighlighted(node);
    var _c = React.useState(false), isMouseOver = _c[0], setMouseOver = _c[1];
    var timer = useTimer({});
    var _d = useVisibility({}), shouldShowTooltip = _d.isVisible, showTooltip = _d.show, hideTooltip = _d.hide;
    React.useEffect(function () {
        if (isHighlighted && !shouldShowTooltip) {
            timer.setTimer(showTooltip, tooltipDelay);
        }
        return function () {
            if (isTreeFocused && !isHighlighted && shouldShowTooltip) {
                hideTooltip();
            }
        };
    });
    var onMouseEnter = function () {
        setMouseOver(true);
        if (!shouldShowTooltip) {
            timer.setTimer(showTooltip, tooltipDelay);
        }
        highlight(node);
    };
    var onMouseLeave = function () {
        setMouseOver(false);
        timer.cancel();
        hideTooltip();
    };
    return (React.createElement(StyledTreeRowWrapper, { tabIndex: -1, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, style: style, "data-padding": getPadding(node.level) }, rowRenderer(__assign({ node: node, shouldShowTooltip: shouldShowTooltip, isMouseOver: isMouseOver }, props))));
}, areEqual);
var TreeRow = function (_a) {
    var node = _a.node, getIcon = _a.getIcon, getLabel = _a.getLabel, handleExpansion = _a.handleExpansion, handleSelection = _a.handleSelection, selectionLimit = _a.selectionLimit, isFileLimitReached = _a.isFileLimitReached, shouldShowTooltip = _a.shouldShowTooltip, isMouseOver = _a.isMouseOver, treeContainer = _a.treeContainer, props = __rest(_a, ["node", "getIcon", "getLabel", "handleExpansion", "handleSelection", "selectionLimit", "isFileLimitReached", "shouldShowTooltip", "isMouseOver", "treeContainer"]);
    var expanderRef = React.useRef(null);
    var isSelected = props.isSelected(node);
    var isSelectable = props.isSelectable(node);
    var isExpandable = props.isExpandable(node);
    var isExpanded = props.isExpanded(node);
    var isLoading = props.isLoading(node);
    var isHighlighted = props.isHighlighted(node);
    var nodeType = props.getType(node);
    var rowLabel = getLabel(node);
    var onClick = React.useCallback(function (event) {
        var _a;
        if (isSelectable && isExpandable) {
            // @ts-ignore
            ((_a = expanderRef === null || expanderRef === void 0 ? void 0 : expanderRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))
                ? handleExpansion(node)
                : handleSelection(node);
        }
        else if (isSelectable) {
            handleSelection(node);
        }
        else if (isExpandable) {
            handleExpansion(node);
        }
    }, [handleExpansion, handleSelection, isExpandable, isSelectable, node]);
    var onMouseDown = React.useCallback(function (e) {
        e.preventDefault();
        treeContainer.focus();
    }, [treeContainer]);
    return (React.createElement(TreeRowContainer, { level: node.level, onClick: onClick, onMouseDown: onMouseDown, isSelected: isSelected, isSelectable: isSelectable, isExpandable: isExpandable, isHighlighted: isHighlighted, isFileLimitReached: isFileLimitReached, "data-qa": "core-tree-row-container" },
        React.createElement(StyledTreeRowContent, { "data-row-content": true },
            React.createElement(TreeRowChevron, { ref: expanderRef, isExpandable: isExpandable, isExpanded: isExpanded }),
            React.createElement(TreeRowIcon, { isLoading: isLoading, isSelected: isSelected, icon: getIcon(node) }),
            React.createElement(TreeRowTooltip, { isSelected: isSelected, isSelectable: isSelectable, isHighlighted: isHighlighted, selectionLimit: selectionLimit, isFileLimitReached: isFileLimitReached, shouldShowTooltip: shouldShowTooltip, isMouseOver: isMouseOver, fileName: rowLabel, nodeType: nodeType, treeContainer: treeContainer },
                React.createElement(TreeRowName, { value: rowLabel })))));
};
var IconContainer = function (_a) {
    var children = _a.children, isSelected = _a.isSelected, props = __rest(_a, ["children", "isSelected"]);
    return (React.createElement(StyledIconContainer, __assign({ "$isSelected": isSelected }, props), children));
};
var TreeRowChevron = React.forwardRef(function (_a, ref) {
    var isExpanded = _a.isExpanded, isExpandable = _a.isExpandable;
    var icon = isExpanded ? React.createElement(ChevronDown, null) : React.createElement(ChevronRight, null);
    return (React.createElement(StyledChevronContainer, { ref: ref }, isExpandable ? icon : null));
});
var TreeRowIcon = function (_a) {
    var isSelected = _a.isSelected, isLoading = _a.isLoading, icon = _a.icon;
    return (React.createElement(IconContainer, { isSelected: isSelected, marginLeft: "sm", marginRight: "sm" }, isLoading ? React.createElement(Spinner, { size: "sm" }) : icon));
};
var TreeRowName = function (_a) {
    var value = _a.value;
    return (React.createElement(StyledFilenameCaption, null, value));
};
var TreeRowOverlay = function (_a) {
    var _b = _a.message, key = _b.key, _c = _b.options, options = _c === void 0 ? {} : _c, props = __rest(_a, ["message"]);
    var I18n = useI18nContext();
    return (React.createElement(Overlay, __assign({}, __assign(__assign({}, props), { placement: 'top-left' })),
        React.createElement(Tooltip.Content, null, I18n.t("core.tree.".concat(key), options))));
};
var getSelectionLimitMessage = function (selectionLimit) { return ({
    key: 'selectionLimit',
    options: {
        count: selectionLimit,
    },
}); };
var getUnsupportedFileTypeMessage = function (fileName) {
    var _a = parseFilename(fileName), extension = _a.extension, isFilename = _a.isFilename;
    var key = isFilename ? 'specific' : 'unspecific';
    return {
        key: "unsupportedFileType.".concat(key),
        options: {
            fileType: ".".concat(extension.toUpperCase()),
        },
    };
};
var TreeRowTooltip = function (_a) {
    var children = _a.children, isFocused = _a.isHighlighted, isFileLimitReached = _a.isFileLimitReached, isSelectable = _a.isSelectable, isSelected = _a.isSelected, nodeType = _a.nodeType, selectionLimit = _a.selectionLimit, shouldShowTooltip = _a.shouldShowTooltip, isMouseOver = _a.isMouseOver, fileName = _a.fileName, treeContainer = _a.treeContainer;
    var rowRef = React.useRef(null);
    var overlayRef = React.useRef(null);
    var isUserInteracting = isFocused || isMouseOver;
    var isTooltipVisible = isUserInteracting && shouldShowTooltip && !isSelected;
    var isFile = nodeType === 'leaf';
    var _b = React.useState(false), isCropped = _b[0], setCropped = _b[1];
    var registerRowEl = useIntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
            if (entry.rootBounds &&
                entry.rootBounds.left - entry.boundingClientRect.left >= 0) {
                setCropped(true);
                return;
            }
            setCropped(false);
        });
    }, {
        root: treeContainer,
        rootMargin: '0px',
        threshold: intersectionThresholds,
    });
    return (React.createElement(React.Fragment, null,
        React.createElement("div", { ref: mergeRefs(rowRef, registerRowEl) }, children),
        isTooltipVisible && !isCropped && (React.createElement(React.Fragment, null,
            isFileLimitReached && isSelectable && (React.createElement(TreeRowOverlay, { overlayRef: overlayRef, targetRef: rowRef, message: getSelectionLimitMessage(selectionLimit) })),
            !isSelectable && isFile && (React.createElement(TreeRowOverlay, { overlayRef: overlayRef, targetRef: rowRef, message: getUnsupportedFileTypeMessage(fileName) }))))));
};
var TreeRowContainer = function (_a) {
    var children = _a.children, isHighlighted = _a.isHighlighted, isSelected = _a.isSelected, isSelectable = _a.isSelectable, isExpandable = _a.isExpandable, isFileLimitReached = _a.isFileLimitReached, level = _a.level, props = __rest(_a, ["children", "isHighlighted", "isSelected", "isSelectable", "isExpandable", "isFileLimitReached", "level"]);
    return (React.createElement(StyledTreeRowContainer, __assign({ isSelectable: (isSelectable &&
            ((isFileLimitReached && isSelected) || !isFileLimitReached)) ||
            isExpandable, isHighlighted: isHighlighted, isSelected: isSelected, style: { paddingLeft: getPadding(level) } }, props), children));
};
//# sourceMappingURL=Tree.js.map