import { focus } from '@dojo/framework/core/middleware/focus';
import dimensions from '@dojo/framework/core/middleware/dimensions';
import { createICacheMiddleware } from '@dojo/framework/core/middleware/icache';
import { create, tsx } from '@dojo/framework/core/vdom';
import { Keys, isRenderResult } from '../common/util';
import theme from '../middleware/theme';
import offscreen from '../middleware/offscreen';
import * as listItemCss from '../theme/default/list-item.m.css';
import * as menuItemCss from '../theme/default/menu-item.m.css';
import * as css from '../theme/default/list.m.css';
import * as fixedCss from './list.m.css';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
import LoadingIndicator from '../loading-indicator';
import { throttle } from '@dojo/framework/core/util';
import Icon from '../icon';
const menuItemFactory = create({ theme }).properties();
export const MenuItem = menuItemFactory(function MenuItem({ properties, children, middleware: { theme } }) {
    const { onSelect, active = false, onRequestActive, disabled = false, widgetId } = properties();
    const themedCss = theme.classes(menuItemCss);
    function select() {
        !disabled && onSelect();
    }
    function requestActive() {
        !disabled && !active && onRequestActive();
    }
    return (tsx("div", { id: widgetId, key: "root", onpointermove: throttle(() => {
            requestActive();
        }, 500), classes: [
            theme.variant(),
            themedCss.root,
            active && themedCss.active,
            disabled && themedCss.disabled
        ], onclick: () => {
            requestActive();
            select();
        }, role: "menuitem", "aria-disabled": disabled ? 'true' : 'false' }, children()));
});
const listItemFactory = create({ theme })
    .properties()
    .children();
export const ListItem = listItemFactory(function ListItem({ properties, children, middleware: { theme } }) {
    const { onSelect, active = false, onRequestActive, selected = false, disabled = false, widgetId, draggable, dragged, onDragStart, onDragEnd, onDragOver, onDrop, movedUp, movedDown, collapsed, theme: themeProp, variant } = properties();
    const themedCss = theme.classes(listItemCss);
    function select() {
        !disabled && onSelect();
    }
    function requestActive() {
        !disabled && !active && onRequestActive();
    }
    const [firstChild, ...otherChildren] = children();
    const { leading = undefined, primary, trailing = undefined } = isRenderResult(firstChild)
        ? { primary: [firstChild, ...otherChildren] }
        : firstChild;
    return (tsx("div", { id: widgetId, key: "root", onpointermove: throttle(() => {
            requestActive();
        }, 500), classes: [
            theme.variant(),
            themedCss.root,
            themedCss.height,
            selected && themedCss.selected,
            active && themedCss.active,
            disabled && themedCss.disabled,
            movedUp && themedCss.movedUp,
            movedDown && themedCss.movedDown,
            collapsed && themedCss.collapsed,
            dragged && themedCss.dragged,
            draggable && themedCss.draggable
        ], onclick: () => {
            requestActive();
            select();
        }, role: "option", "aria-disabled": disabled ? 'true' : 'false', "aria-selected": selected ? 'true' : 'false', draggable: draggable, ondragenter: (event) => event.preventDefault(), ondragstart: onDragStart, ondragend: onDragEnd, ondragover: onDragOver, ondrop: onDrop, styles: { visibility: dragged ? 'hidden' : undefined } },
        leading ? tsx("span", { classes: themedCss.leading }, leading) : undefined,
        tsx("span", { classes: themedCss.primary }, primary),
        trailing ? tsx("span", { classes: themedCss.trailing }, trailing) : undefined,
        draggable && !trailing && (tsx(Icon, { type: "barsIcon", classes: { '@dojo/widgets/icon': { icon: [themedCss.dragIcon] } }, theme: themeProp, variant: variant }))));
});
const factory = create({
    icache: createICacheMiddleware(),
    focus,
    theme,
    offscreen,
    dimensions,
    resource: createResourceMiddleware()
})
    .properties()
    .children();
const defaultItemsInView = 10;
export const List = factory(function List({ children, properties, id, middleware: { icache, focus, theme, resource, offscreen, dimensions } }) {
    const { activeIndex, focusable = true, initialValue, itemsInView = defaultItemsInView, menu = false, onActiveIndexChange, onBlur, onFocus, onRequestClose, onValue, widgetId, theme: themeProp, variant, resource: { template, options = resource.createOptions((curr, next) => (Object.assign({}, curr, next))) }, classes, height = 'fixed' } = properties();
    const { get, template: { read } } = resource.template(template);
    const themedCss = theme.classes(css);
    const [itemRenderer] = children();
    function setActiveIndex(index) {
        if (onActiveIndexChange) {
            onActiveIndexChange(index);
        }
        else {
            icache.set('activeIndex', index);
        }
    }
    function setValue(value) {
        icache.set('value', value.value);
        onValue(value);
    }
    function onKeyDown(event, total) {
        const { disabled, activeIndex } = properties();
        event.stopPropagation();
        let computedActiveIndex = activeIndex === undefined ? icache.get('activeIndex') : activeIndex;
        switch (event.which) {
            case Keys.Enter:
            case Keys.Space:
                event.preventDefault();
                if (activeItem) {
                    const itemDisabled = disabled ? disabled(activeItem) : activeItem.disabled;
                    if (!itemDisabled) {
                        setValue(activeItem);
                    }
                }
                break;
            case Keys.Down:
                event.preventDefault();
                if (event.metaKey || event.ctrlKey) {
                    setActiveIndex(total - 1);
                }
                else {
                    const effectiveActiveIndex = computedActiveIndex !== undefined ? computedActiveIndex + 1 : 0;
                    setActiveIndex(effectiveActiveIndex % total);
                }
                break;
            case Keys.Up:
                event.preventDefault();
                if (event.metaKey || event.ctrlKey) {
                    setActiveIndex(0);
                }
                else {
                    const effectiveActiveIndex = computedActiveIndex !== undefined
                        ? computedActiveIndex - 1 + total
                        : total - 1;
                    setActiveIndex(effectiveActiveIndex % total);
                }
                break;
            case Keys.Escape:
                event.preventDefault();
                onRequestClose && onRequestClose();
                break;
            case Keys.Home:
                event.preventDefault();
                setActiveIndex(0);
                break;
            case Keys.End:
                event.preventDefault();
                setActiveIndex(total - 1);
                break;
            default:
                if (!event.metaKey && !event.ctrlKey && event.key.length === 1) {
                    icache.set('resetInputTextTimer', (existingTimer) => {
                        if (existingTimer) {
                            clearTimeout(existingTimer);
                        }
                        return setTimeout(() => {
                            icache.delete('inputText');
                            icache.delete('previousInputText');
                        }, 800);
                    });
                    icache.set('inputText', (value = '') => {
                        return `${value}${event.key}`;
                    });
                }
                break;
        }
    }
    function renderItems(start, count) {
        const renderedItems = [];
        const { size: resourceRequestSize } = options();
        const { meta: { total = 0 } } = get(options(), { meta: true, read });
        if (total) {
            let pages = [];
            for (let i = 0; i < Math.min(total - start, count); i++) {
                const index = i + startNode;
                const page = Math.floor(index / resourceRequestSize) + 1;
                if (pages.indexOf(page) === -1) {
                    pages.push(page);
                }
            }
            if (!pages.length) {
                pages.push(1);
            }
            const pageItems = pages.map((page, index) => {
                if (index === pages.length - 1) {
                    options({ offset: (page - 1) * options().size });
                }
                return get(Object.assign({}, options(), { offset: (page - 1) * options().size }), { read });
            });
            for (let i = 0; i < Math.min(total - start, count); i++) {
                const index = i + startNode;
                const page = Math.floor(index / resourceRequestSize) + 1;
                const pageIndex = pages.indexOf(page);
                const indexWithinPage = index - (page - 1) * resourceRequestSize;
                const items = pageItems[pageIndex];
                if (items && items[indexWithinPage]) {
                    const { value, label, disabled, divider } = items[indexWithinPage];
                    renderedItems[i] = renderItem({ value, label, disabled, divider }, index);
                }
                else if (!items) {
                    renderedItems[i] = renderPlaceholder(index);
                }
            }
        }
        return renderedItems;
    }
    function renderPlaceholder(index) {
        const itemProps = {
            widgetId: `${idBase}-item-${index}`,
            key: `item-${index}`,
            onSelect: () => { },
            active: false,
            onRequestActive: () => {
                setActiveIndex(index);
            },
            disabled: true,
            classes,
            variant
        };
        const itemHeight = icache.getOrSet('itemHeight', 0);
        return (tsx("div", { key: `item-${index}`, styles: { height: `${itemHeight}px` } }, menu ? (tsx(MenuItem, Object.assign({}, itemProps, { theme: theme.compose(menuItemCss, css, 'item') }),
            tsx(LoadingIndicator, null))) : (tsx(ListItem, Object.assign({}, itemProps, { selected: false, theme: theme.compose(listItemCss, css, 'item'), dragged: icache.get('dragIndex') === index, draggable: draggable, onDragStart: (event) => onDragStart(event, index), onDragEnd: onDragEnd, onDragOver: (event) => onDragOver(event, index), onDrop: (event) => onDrop(event, index), movedUp: icache.get('dragOverIndex') === index &&
                icache.get('dragIndex') < icache.get('dragOverIndex'), movedDown: icache.get('dragOverIndex') === index &&
                icache.get('dragIndex') > icache.get('dragOverIndex'), collapsed: icache.get('dragIndex') === index &&
                icache.get('dragOverIndex') !== undefined }),
            tsx(LoadingIndicator, null)))));
    }
    function onDragStart(event, index) {
        if (!draggable) {
            return;
        }
        icache.set('dragIndex', index);
        event.dataTransfer.setData('text/plain', `${index}`);
    }
    function onDragOver(event, index) {
        const dragIndex = icache.get('dragIndex');
        if (!draggable || dragIndex === undefined) {
            return;
        }
        event.preventDefault();
        event.dataTransfer.dropEffect = 'move';
        let targetIndex = index;
        if (event.offsetY < 10 && index === dragIndex + 1) {
            targetIndex = undefined;
        }
        else if (event.offsetY > itemHeight - 10 && index === dragIndex - 1) {
            targetIndex = undefined;
        }
        if (icache.get('dragOverIndex') !== targetIndex) {
            icache.set('dragOverIndex', targetIndex);
        }
    }
    function onDragEnd(event) {
        if (!draggable) {
            return;
        }
        event.preventDefault();
        icache.set('dragIndex', undefined);
        icache.set('dragOverIndex', undefined);
    }
    function onDrop(event, index) {
        if (!draggable) {
            return;
        }
        event.preventDefault();
        const from = event.dataTransfer && event.dataTransfer.getData('text/plain');
        if (from === null) {
            return;
        }
        setActiveIndex(index);
        onMove && onMove(parseInt(from, 10), index);
    }
    function renderItem(data, index) {
        const { disabled } = properties();
        const { value, label, divider, disabled: optionDisabled = false } = data;
        const itemDisabled = disabled ? disabled(data) : optionDisabled;
        const selected = value === selectedValue;
        const active = index === computedActiveIndex;
        if (active) {
            activeItem = data;
        }
        const itemProps = {
            widgetId: `${idBase}-item-${index}`,
            key: `item-${index}`,
            onSelect: () => {
                setValue(data);
            },
            active,
            onRequestActive: () => {
                setActiveIndex(index);
            },
            disabled: itemDisabled,
            classes,
            variant
        };
        let item;
        if (itemRenderer) {
            item = itemRenderer({
                value,
                label,
                disabled: itemDisabled,
                active,
                selected
            }, Object.assign({}, itemProps, { theme: themeProp }));
        }
        else {
            const children = label || value;
            item = menu ? (tsx(MenuItem, Object.assign({}, itemProps, { theme: theme.compose(menuItemCss, css, 'item') }), children)) : (tsx(ListItem, Object.assign({}, itemProps, { theme: theme.compose(listItemCss, css, 'item'), selected: selected, dragged: icache.get('dragIndex') === index, draggable: draggable, onDragStart: (event) => onDragStart(event, index), onDragEnd: onDragEnd, onDragOver: (event) => onDragOver(event, index), onDrop: (event) => onDrop(event, index), movedUp: icache.get('dragOverIndex') === index &&
                    icache.get('dragIndex') < icache.get('dragOverIndex'), movedDown: icache.get('dragOverIndex') === index &&
                    icache.get('dragIndex') > icache.get('dragOverIndex'), collapsed: icache.get('dragIndex') === index &&
                    icache.get('dragOverIndex') !== undefined }), children));
        }
        return divider ? [item, tsx("hr", { classes: themedCss.divider })] : item;
    }
    let { value: selectedValue, draggable, onMove } = properties();
    if (selectedValue === undefined) {
        if (initialValue !== undefined && initialValue !== icache.get('initial')) {
            icache.set('initial', initialValue);
            icache.set('value', initialValue);
        }
        selectedValue = icache.get('value');
    }
    let calculatedItemsInView = icache.getOrSet('calculatedItemsInView', 0);
    if (itemsInView !== icache.get('itemsInView')) {
        const offscreenItemProps = {
            selected: false,
            onSelect: () => { },
            active: false,
            onRequestActive: () => { },
            onActive: () => { },
            scrollIntoView: false,
            widgetId: 'offcreen',
            theme: themeProp
        };
        const offscreenMenuItem = itemRenderer ? (itemRenderer({
            selected: false,
            active: false,
            value: 'offscreen',
            label: 'offscreen',
            disabled: false
        }, offscreenItemProps)) : menu ? (tsx(MenuItem, Object.assign({}, offscreenItemProps), "offscreen")) : (tsx(ListItem, Object.assign({}, offscreenItemProps), "offscreen"));
        const itemHeight = icache.getOrSet('itemHeight', offscreen(() => (tsx("div", { styles: { padding: '0' }, classes: [themedCss.root, fixedCss.root] }, offscreenMenuItem)), (node) => node.getBoundingClientRect().height));
        if (itemsInView !== 'fill') {
            calculatedItemsInView = itemsInView;
            itemHeight && icache.set('menuHeight', calculatedItemsInView * itemHeight);
        }
        else {
            const dims = dimensions.get('fill-root');
            if (dims.size.height) {
                calculatedItemsInView = Math.ceil(dims.size.height / itemHeight);
                itemHeight && icache.set('menuHeight', dims.size.height);
            }
            else if (dims.size.width) {
                calculatedItemsInView = defaultItemsInView;
                itemHeight && icache.set('menuHeight', calculatedItemsInView * itemHeight);
            }
            else {
                return tsx("div", { key: "fill-root", styles: { height: '100%' } });
            }
        }
        icache.set('itemsInView', itemsInView);
        icache.set('calculatedItemsInView', calculatedItemsInView);
    }
    const menuHeight = icache.get('menuHeight');
    const idBase = widgetId || `menu-${id}`;
    let rootStyles = {};
    if (menuHeight) {
        rootStyles =
            height === 'fixed' ? { height: `${menuHeight}px` } : { maxHeight: `${menuHeight}px` };
    }
    const shouldFocus = focus.shouldFocus();
    const itemHeight = icache.getOrSet('itemHeight', 0);
    let scrollTop = icache.getOrSet('scrollTop', 0);
    const nodePadding = Math.min(calculatedItemsInView, 20);
    const renderedItemsCount = calculatedItemsInView + 2 * nodePadding;
    let computedActiveIndex = activeIndex === undefined ? icache.get('activeIndex') : activeIndex;
    const inputText = icache.get('inputText');
    const { meta: { total = 0 } } = get(options(), { meta: true, read });
    if (inputText && inputText !== icache.get('previousInputText') && total) {
        const items = get(Object.assign({}, options(), { offset: 0, size: total }));
        const first = computedActiveIndex !== undefined ? items.slice(0, computedActiveIndex) : items;
        const second = computedActiveIndex !== undefined ? items.slice(computedActiveIndex) : [];
        let foundIndex = computedActiveIndex;
        if (foundIndex !== undefined) {
            for (let i = 1; i < second.length; i++) {
                const item = second[i];
                if (item && item.label.toLowerCase().indexOf(inputText.toLowerCase()) === 0) {
                    foundIndex = foundIndex + i;
                    break;
                }
            }
        }
        if (foundIndex === computedActiveIndex) {
            for (let i = 0; i < first.length; i++) {
                const item = first[i];
                if (item && item.label.toLowerCase().indexOf(inputText.toLowerCase()) === 0) {
                    foundIndex = i;
                    break;
                }
            }
        }
        if (foundIndex !== undefined && foundIndex !== computedActiveIndex) {
            setActiveIndex(foundIndex);
        }
        icache.set('previousInputText', inputText);
    }
    const previousActiveIndex = icache.get('previousActiveIndex');
    computedActiveIndex = activeIndex === undefined ? icache.get('activeIndex') : activeIndex;
    let activeItem = undefined;
    if (computedActiveIndex !== undefined && computedActiveIndex !== previousActiveIndex) {
        const visibleStartIndex = Math.floor(scrollTop / itemHeight);
        const visibleEndIndex = visibleStartIndex + calculatedItemsInView - 1;
        if (computedActiveIndex < visibleStartIndex) {
            scrollTop = computedActiveIndex * itemHeight;
        }
        else if (computedActiveIndex > visibleEndIndex) {
            scrollTop = Math.max(computedActiveIndex + 1 - calculatedItemsInView, 0) * itemHeight;
        }
        if (icache.get('scrollTop') !== scrollTop) {
            icache.set('scrollTop', scrollTop);
        }
        icache.set('previousActiveIndex', computedActiveIndex);
    }
    const startNode = Math.max(0, Math.floor(scrollTop / itemHeight) - nodePadding);
    const offsetY = startNode * itemHeight;
    const items = renderItems(startNode, renderedItemsCount);
    const totalContentHeight = total * itemHeight;
    return (tsx("div", { key: "root", classes: [theme.variant(), themedCss.root, fixedCss.root], tabIndex: focusable ? 0 : -1, onkeydown: (e) => {
            total && onKeyDown(e, total);
        }, focus: () => shouldFocus, onfocus: onFocus, onpointerdown: focusable ? undefined : (event) => event.preventDefault(), onblur: onBlur, scrollTop: scrollTop, onscroll: (e) => {
            const newScrollTop = e.target.scrollTop;
            if (scrollTop !== newScrollTop) {
                icache.set('scrollTop', newScrollTop);
            }
        }, styles: rootStyles, role: menu ? 'menu' : 'listbox', "aria-orientation": "vertical", "aria-activedescendant": computedActiveIndex !== undefined
            ? `${idBase}-item-${computedActiveIndex}`
            : undefined, id: idBase },
        tsx("div", { classes: fixedCss.wrapper, styles: {
                height: `${totalContentHeight}px`
            }, key: "wrapper" },
            tsx("div", { classes: fixedCss.transformer, styles: {
                    transform: `translateY(${offsetY}px)`
                }, key: "transformer" }, items))));
});
export default List;

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