import { create, tsx, diffProperty } from '@dojo/framework/core/vdom';
import { createICacheMiddleware } from '@dojo/framework/core/middleware/icache';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
import theme from '../middleware/theme';
import focus from '@dojo/framework/core/middleware/focus';
import { flat } from '@dojo/framework/shim/array';
import { Keys } from '../common/util';
import Icon from '../icon';
import Checkbox from '../checkbox';
import { ListItem } from '../list';
import * as css from '../theme/default/tree.m.css';
import LoadingIndicator from '../loading-indicator';
const resource = createResourceMiddleware();
const icache = createICacheMiddleware();
const factory = create({ theme, icache, diffProperty, focus, resource })
    .properties()
    .children();
export default factory(function Tree({ middleware: { theme, icache, diffProperty, resource }, properties, children }) {
    diffProperty('value', properties, ({ value: current }, { value: next }) => {
        if ((current || next) && current !== next) {
            icache.set('value', next);
        }
    });
    diffProperty('initialChecked', properties, ({ initialChecked: current }, { initialChecked: next }) => {
        if ((current || next) && current !== next) {
            icache.set('checkedIds', next || []);
        }
    });
    diffProperty('initialExpanded', properties, ({ initialExpanded: current }, { initialExpanded: next }) => {
        if ((current || next) && current !== next) {
            icache.set('expandedIds', next || []);
        }
    });
    const { checkable = false, selectable = false, checkedIds, expandedIds, onValue, onCheck, onExpand, disabledIds, resource: { template }, parentSelection = false, theme: themeProp, classes, variant } = properties();
    const { get, template: { read } } = resource.template(template);
    const themedCss = theme.classes(css);
    const defaultRenderer = (n) => n.value;
    const [itemRenderer] = children();
    const activeNode = icache.get('activeNode');
    const selectedNode = icache.get('value');
    if (checkedIds) {
        icache.set('checkedIds', checkedIds);
    }
    if (expandedIds) {
        icache.set('expandedIds', expandedIds);
    }
    const expandedNodes = icache.getOrSet('expandedIds', []);
    const checkedNodes = icache.getOrSet('checkedIds', []);
    function activateNode(id) {
        icache.set('activeNode', id);
    }
    function selectNode(id) {
        icache.set('value', id);
        onValue && onValue(id);
    }
    function checkNode(id, checked) {
        if (checked) {
            icache.set('checkedIds', (currentChecked = []) => [...currentChecked, id]);
        }
        else {
            icache.set('checkedIds', (currentChecked = []) => currentChecked ? currentChecked.filter((n) => n !== id) : []);
        }
        onCheck && onCheck(icache.get('checkedIds') || []);
    }
    function expandNode(id) {
        icache.set('expandedIds', (currentExpanded = []) => [...currentExpanded, id]);
        onExpand && onExpand(icache.get('expandedIds') || []);
    }
    function collapseNode(id) {
        icache.set('expandedIds', (currentExpanded = []) => currentExpanded ? currentExpanded.filter((n) => n !== id) : []);
        onExpand && onExpand(icache.get('expandedIds') || []);
    }
    function createNodeFlatMap(nodeId = 'root') {
        let nodes = [];
        const options = resource.createOptions((curr, next) => (Object.assign({}, curr, next)));
        const { meta: { total } } = get(options({ query: { parent: nodeId } }), { read, meta: true });
        if (total) {
            const results = get(options({ size: total }), { read });
            const queriedNodes = results ? flat(results) : [];
            queriedNodes.forEach((node) => {
                nodes.push(node);
                if (expandedNodes.indexOf(node.id) !== -1) {
                    nodes = [...nodes, ...createNodeFlatMap(node.id)];
                }
            });
        }
        return nodes;
    }
    function onKeyDown(event) {
        event.stopPropagation();
        const nodes = createNodeFlatMap();
        const activeIndex = nodes.findIndex((node) => node.id === activeNode);
        switch (event.which) {
            // select
            case Keys.Enter:
            case Keys.Space:
                event.preventDefault();
                if (activeNode && selectedNode !== activeNode && !nodes[activeIndex].hasChildren) {
                    selectable && selectNode(activeNode);
                    checkable && checkNode(activeNode, !checkedNodes.includes(activeNode));
                }
                break;
            // next
            case Keys.Down:
                event.preventDefault();
                activateNode(nodes[(activeIndex + 1) % nodes.length].id);
                break;
            // previous
            case Keys.Up:
                event.preventDefault();
                if (activeIndex - 1 < 0) {
                    activateNode(nodes[nodes.length - 1].id);
                }
                else {
                    activateNode(nodes[activeIndex - 1].id);
                }
                break;
            // expand
            case Keys.Right:
                event.preventDefault();
                if (activeNode &&
                    !expandedNodes.includes(activeNode) &&
                    nodes[activeIndex].hasChildren) {
                    expandNode(activeNode);
                }
                break;
            // collapse
            case Keys.Left:
                event.preventDefault();
                if (activeNode && expandedNodes.includes(activeNode)) {
                    collapseNode(activeNode);
                }
                break;
        }
    }
    function mapNodeTree(nodeId = 'root') {
        const { meta } = get({ size: 30, offset: 0, query: { parent: nodeId } }, { read, meta: true });
        if (meta.status !== 'read') {
            return tsx(LoadingIndicator, { theme: themeProp, classes: classes, variant: variant });
        }
        const results = get({
            size: meta.total || 0,
            offset: 0,
            query: { parent: nodeId }
        }, { read });
        const nodes = results ? flat(results) : [];
        return (tsx("ol", { classes: [
                nodeId === 'root' ? themedCss.root : null,
                themedCss.nodeParent,
                theme.variant()
            ], onkeydown: onKeyDown, tabIndex: 0, role: nodeId === 'root' ? 'tree' : 'group' }, nodes.map((node) => {
            const isExpanded = expandedNodes.includes(node.id);
            return (tsx("li", { classes: [
                    themedCss.node,
                    node.hasChildren && themedCss.leaf,
                    selectable && themedCss.selectable,
                    node.id === selectedNode && themedCss.selected
                ], role: 'treeitem' },
                tsx(TreeNode, { classes: classes, theme: themeProp, variant: variant, activeNode: activeNode, checkable: checkable, selectable: selectable, selected: node.id === selectedNode, checked: checkedNodes.includes(node.id), value: selectedNode, disabled: (disabledIds || []).includes(node.id), expanded: isExpanded, parentSelection: parentSelection, node: node, onActive: () => activateNode(node.id), onValue: () => selectNode(node.id), onCheck: (checked) => checkNode(node.id, checked), onExpand: (expanded) => {
                        if (expanded) {
                            expandNode(node.id);
                        }
                        else {
                            collapseNode(node.id);
                        }
                    } }, itemRenderer || defaultRenderer),
                isExpanded && mapNodeTree(node.id)));
        })));
    }
    return mapNodeTree();
});
const treeNodeFactory = create({ theme })
    .properties()
    .children();
export const TreeNode = treeNodeFactory(function TreeNode({ middleware: { theme }, properties, children }) {
    const { node, checkable, selectable, activeNode, checked, disabled, expanded, selected, onActive, onValue, onCheck, onExpand, parentSelection, theme: themeProp, classes, variant } = properties();
    const [itemRenderer] = children();
    const themedCss = theme.classes(css);
    const isActive = node.id === activeNode;
    const isExpandable = node.hasChildren;
    return (tsx(ListItem, { active: isActive, selected: selected, onRequestActive: () => {
            onActive();
        }, onSelect: () => {
            isExpandable && onExpand(!expanded);
            if (parentSelection || !isExpandable) {
                selectable && onValue();
                checkable && onCheck(!checked);
            }
        }, disabled: disabled, widgetId: node.id, classes: classes, theme: themeProp, variant: variant },
        tsx("div", { classes: themedCss.contentWrapper },
            tsx("div", { classes: themedCss.content },
                isExpandable && (tsx("div", { classes: themedCss.expander },
                    tsx(Icon, { type: expanded ? 'downIcon' : 'rightIcon', theme: themeProp, classes: classes, variant: variant }))),
                checkable && (parentSelection || !isExpandable) && (tsx("div", { onpointerdown: (event) => {
                        // don't allow the check's activity to effect our expand/collapse
                        event.stopPropagation();
                    } },
                    tsx(Checkbox, { checked: !!checked, onValue: (value) => {
                            onCheck(value);
                        }, disabled: disabled, classes: classes, theme: themeProp, variant: variant }))),
                tsx("div", { classes: themedCss.title }, itemRenderer(node))))));
});

/*# sourceMappingURL=index.mjs.map*/