"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/*
Copyright 2019 Adobe
All Rights Reserved.

NOTICE: Adobe permits you to use, modify, and distribute this file in
accordance with the terms of the Adobe license agreement accompanying
it. If you have received this file from a source other than Adobe,
then your use, modification, or distribution of it requires the prior
written permission of Adobe.
*/
const react_1 = __importStar(require("react"));
const VirtualManager_1 = __importDefault(require("./VirtualManager"));
const memoize_1 = __importDefault(require("../common/memoize"));
require("../common/shims");
function createPropertyGetter(property, defaultValue, validator) {
    if (property == null) {
        return typeof defaultValue === "function"
            ? defaultValue()
            : undefined;
    }
    if (typeof property === "function") {
        return (item) => {
            const result = property(item);
            if (validator) {
                validator(result, item);
            }
            return result;
        };
    }
    if (typeof property === "string" || typeof property === "symbol") {
        return (item) => item[property];
    }
    throw new Error(`Unsupported property: ${String(property)}`);
}
exports.default = (0, react_1.forwardRef)(function Virtualizer(properties, ref) {
    let { items, itemKey, itemType, itemRect, scrollToItem, onLayout, cacheElements = true, style, children: factory, ...otherProps } = properties;
    let horizontal = properties.direction === "horizontal";
    const itemKeyFunction = (0, react_1.useMemo)(() => (0, memoize_1.default)(createPropertyGetter(itemKey, () => {
        //  if user provides no key property/function
        //  then we use the item index as key
        const itemToIndex = new Map(items.map((item, index) => [item, String(index)]));
        return (item) => itemToIndex.get(item);
    }, (value, item) => {
        if (typeof value !== 'string' || value.length === 0) {
            throw new Error(`Invalid key, expected a unique string, actual: (${JSON.stringify(value || null)}) for item: ${JSON.stringify(item)}`);
        }
    })), [itemKey, items]);
    const itemTypeFunction = (0, react_1.useMemo)(() => (0, memoize_1.default)(createPropertyGetter(itemType, () => () => "default")), [itemType, items]);
    const itemRectFunction = (0, react_1.useMemo)(() => createPropertyGetter(itemRect), [itemRect, items]);
    let [renderKeys, setRenderKeys] = (0, react_1.useState)(new Array());
    // we use a ref so we can persist the manager between calls to this component function.
    const cache = (0, react_1.useRef)();
    if (cache.current == null) {
        cache.current = {};
    }
    function scrollByFunction(x, y) {
        const container = cache.current.container;
        // x is ignored, since we only scroll vertically
        if (container && y) {
            // Would be cleaner to use container.scrollBy, but UXP doesn't support this
            // Instead, we need to compute the scrollTop to set based on the passed in offset
            const clientHeight = container.clientHeight;
            const scrollHeight = container.scrollHeight;
            const maxScrollTop = Math.max(0, scrollHeight - clientHeight);
            const scrollTop = Math.max(0, Math.min(maxScrollTop, container.scrollTop + y));
            container.scrollTo({ top: scrollTop });
        }
    }
    function scrollToItemFunction(key, options) {
        let container = cache.current.container;
        if (container) {
            let manager = VirtualManager_1.default.instance(container);
            manager === null || manager === void 0 ? void 0 : manager.scrollToItem(key, options);
        }
    }
    function getManager() {
        var _a;
        let container = (_a = cache.current) === null || _a === void 0 ? void 0 : _a.container;
        return container ? VirtualManager_1.default.instance(container) : null;
    }
    function getRenderKeys() {
        return renderKeys;
    }
    function getItemRect(key) {
        let manager = getManager();
        if (manager) {
            let rect = manager.getItemRect(null, key);
            if (rect) {
                //  we remove just the properties we want since the manager stores extra information
                //  on the rect structure.
                let { x, y, width, height } = rect;
                return { x, y, width, height };
            }
        }
        return { x: 0, y: 0, width: 0, height: 0 };
    }
    function getVisibleRect() {
        var _a;
        return ((_a = getManager()) === null || _a === void 0 ? void 0 : _a.getVisibleRect()) || { x: 0, y: 0, width: 0, height: 0 };
    }
    (0, react_1.useImperativeHandle)(ref, () => ({
        scrollToItem: scrollToItemFunction,
        scrollBy: scrollByFunction,
        getRenderKeys,
        getItemRect,
        getVisibleRect,
        container: cache.current.container,
    }), [cache.current.container]);
    function setContainer(container) {
        if (container) {
            cache.current.container = container;
            //  we also update the renderKeys immediately
            //  this matters for future renders where
            //  we are calling setContainer with the cached container
            //  we want the correct renderKeys BEFORE we hit this functions element declaration.
            renderKeys = VirtualManager_1.default.update({
                container,
                horizontal,
                items,
                renderKeys,
                setRenderKeys,
                itemKey: itemKeyFunction,
                itemType: itemTypeFunction,
                itemRect: itemRectFunction,
                onLayout,
            });
            //  if we have a scrollToItem then we scroll to it now.
            if (scrollToItem) {
                scrollToItemFunction(scrollToItem);
            }
        }
    }
    //  There is a delay between future renders and calling setContainer.
    //  if we know the Container already, we call setContainer now
    if (cache.current.container) {
        setContainer(cache.current.container);
    }
    style = Object.assign({
        position: "relative",
        overflowX: horizontal ? "scroll" : "hidden",
        overflowY: horizontal ? "hidden" : "scroll",
        flex: "1 1 auto",
        zIndex: 0
    }, style);
    return (react_1.default.createElement("div", { ref: setContainer, style: style, ...otherProps }, VirtualManager_1.default.getReactElements(items, renderKeys, itemKeyFunction, itemTypeFunction, factory, 
    //  if we aren't caching elements then we return a new cache object each time
    //  which effectively disables caching
    cacheElements ? cache.current : {})));
});
