// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */
import type { JSXElement } from "@coreai-ds/react-components";
import { useHeadlessFlatTree_unstable } from "@coreai-ds/react-components";
import type { HeadlessFlatTreeItemProps } from "@coreai-ds/react-components";
import * as React from 'react';
import { FixedSizeList } from "react-window";
import type { TreeNavigationEvent_unstable } from "@coreai-ds/react-components";
import type { TreeNavigationData_unstable } from "@coreai-ds/react-components";
import type { FlatTreeProps } from "@coreai-ds/react-components";
import type { FixedSizeListProps } from "react-window";
import { useFlatTree_unstable } from "@coreai-ds/react-components";
import { useFlatTreeStyles_unstable } from "@coreai-ds/react-components";
import { useFlatTreeContextValues_unstable } from "@coreai-ds/react-components";
import { assertSlots } from "@coreai-ds/react-components";
import type { FlatTreeSlots } from "@coreai-ds/react-components";
import { TreeProvider } from "@coreai-ds/react-components";
import type { ForwardRefComponent } from "@coreai-ds/react-components";
import type { ListChildComponentProps } from "react-window";
import type { HeadlessFlatTreeItem } from "@coreai-ds/react-components";
import { FlatTreeItem } from "@coreai-ds/react-components";
import { TreeItemLayout } from "@coreai-ds/react-components";

type ItemProps = HeadlessFlatTreeItemProps & {
    content: string;
};

const defaultItems: ItemProps[] = [
    {
        value: 'flatTreeItem_lvl-1_item-1',
        content: `Level 1, item 1`,
    },
    ...Array.from({ length: 300 }, (_, i) => ({
        value: `flatTreeItem_lvl-1_item-1--child:${i}`,
        parentValue: 'flatTreeItem_lvl-1_item-1',
        content: `Item ${i + 1}`,
    })),
    {
        value: 'flatTreeItem_lvl-1_item-2',
        content: `Level 1, item 2`,
    },
    ...Array.from({ length: 300 }, (_, index) => ({
        value: `flatTreeItem_lvl-1_item-2--child:${index}`,
        parentValue: 'flatTreeItem_lvl-1_item-2',
        content: `Item ${index + 1}`,
    })),
];

type FixedSizeTreeProps = Omit<FlatTreeProps, 'children'> & {
    listProps: FixedSizeListProps & {
        ref?: React.Ref<FixedSizeList>;
    };
};

/**
 * FixedSizeTree is a recomposition of Tree component that uses react-window FixedSizeList to render items.
 */
const FixedSizeTree = React.forwardRef<HTMLElement, FixedSizeTreeProps>((props, ref) => {
    const state = useFlatTree_unstable(props, ref);
    useFlatTreeStyles_unstable(state);
    const contextValues = useFlatTreeContextValues_unstable(state);
    assertSlots<FlatTreeSlots>(state);
    const handleOuterRef = React.useCallback((instance: HTMLElement | null) => {
        if (instance) {
            // This element stays between the tree and treeitem
            // Due to accessibility issues this element should have role="none"
            instance.setAttribute('role', 'none');
        }
    }, []);
    return (<TreeProvider value={contextValues.tree}>
      <state.root>
        <FixedSizeList outerRef={handleOuterRef} {...props.listProps}/>
      </state.root>
    </TreeProvider>);
}) as ForwardRefComponent<FixedSizeTreeProps>;

interface FixedSizeTreeItemProps extends ListChildComponentProps {
    data: HeadlessFlatTreeItem<ItemProps>[];
}

const FixedSizeTreeItem = (props: FixedSizeTreeItemProps) => {
    const flatTreeItem = props.data[props.index];
    const { content, ...treeItemProps } = flatTreeItem.getTreeItemProps();
    return (<FlatTreeItem {...treeItemProps} style={props.style}>
      <TreeItemLayout>{content}</TreeItemLayout>
    </FlatTreeItem>);
};

export const Virtualization = (): JSXElement => {
    const headlessTree = useHeadlessFlatTree_unstable(defaultItems);
    const listRef = React.useRef<FixedSizeList>(null);
    const items = React.useMemo(() => Array.from(headlessTree.items()), [headlessTree]);
    /**
     * Since navigation is not possible due to the fact that not all items are rendered,
     * we need to scroll to the next item and then invoke navigation.
     */
    const handleNavigation = (event: TreeNavigationEvent_unstable, data: TreeNavigationData_unstable) => {
        const nextItem = headlessTree.getNextNavigableItem(items, data);
        if (!nextItem) {
            return;
        }
        // if the next item is not rendered, scroll to it and try to navigate again
        if (!headlessTree.getElementFromItem(nextItem)) {
            event.preventDefault(); // preventing default disables internal navigation.
            listRef.current?.scrollToItem(nextItem.index);
            // waiting for the next animation frame to allow the list to be scrolled
            return requestAnimationFrame(() => headlessTree.navigate(data));
        }
    };
    return (<FixedSizeTree {...headlessTree.getTreeProps()} listProps={{
            ref: listRef,
            height: 300,
            itemCount: items.length,
            itemData: items,
            itemSize: 32,
            width: 300,
            children: FixedSizeTreeItem,
        }} onNavigation={handleNavigation} aria-label="Virtualization"/>);
};

export default Virtualization;
