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

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { FlatTree } from "@coreai-ds/react-components";
import type { TreeItemValue } from "@coreai-ds/react-components";
import type { TreeItemOpenChangeEvent } from "@coreai-ds/react-components";
import type { TreeItemOpenChangeData } from "@coreai-ds/react-components";
import { FlatTreeItem } from "@coreai-ds/react-components";
import { TreeItemLayout } from "@coreai-ds/react-components";
import { Spinner } from "@coreai-ds/react-components";

const useStyles = makeStyles({
    screenReadersOnly: {
        position: 'absolute',
        width: '1px',
        height: '1px',
        margin: '-1',
        overflow: 'hidden',
        clip: 'rect(0,0,0,0)',
        whiteSpace: 'nowrap',
    },
});

type SubtreeProps = {
    value: TreeItemValue;
    onDataLoading?(): void;
    onDataLoaded?(): void;
};

type QueryResult<Value> = {
    value: Value;
    state: 'idle' | 'loading' | 'loaded' | 'error';
};

/**
 * This function is just for the sake of the example,
 * a library for fetching data (like react-query) might be a better option
 */
function useQuery<Value>(initialValue: Value): QueryResult<Value> & {
    query: (fn: () => PromiseLike<Value> | Value) => void;
} {
    const [queryResult, setQueryResult] = React.useState<QueryResult<Value>>({ value: initialValue, state: 'idle' });
    const query = React.useCallback((fn: () => PromiseLike<Value> | Value) => {
        setQueryResult(curr => ({ ...curr, state: 'loading' }));
        Promise.resolve(fn())
            .then(nextValue => {
            setQueryResult({ value: nextValue, state: 'loaded' });
        })
            .catch(() => {
            setQueryResult(curr => ({ ...curr, state: 'error' }));
        });
    }, []);
    return { ...queryResult, query } as const;
}

interface Entity {
    name: string;
    value: string;
}

interface Response {
    results: {
        name: string;
    }[];
}

const mockFetch = (type: string): Promise<Response> => {
    return new Promise<Response>(resolve => {
        // eslint-disable-next-line @nx/workspace-no-restricted-globals
        setTimeout(() => {
            const mockData: Response = {
                results: Array.from({ length: 10 }, (_, index) => ({
                    name: `${type} ${index + 1}`,
                })),
            };
            resolve(mockData);
        }, 1000);
    });
};

const Subtree: React.FC<SubtreeProps> = ({ onDataLoaded, onDataLoading, value }) => {
    const [open, setOpen] = React.useState(false);
    // useQuery here is just a helper to simulate async data fetching
    // you can use any other async data fetching library like react-query, swr, etc.
    const { query, value: items, state } = useQuery<Entity[]>([]);
    // we need to focus the first item when the subtree is opened
    const firstItemRef = React.useRef<HTMLDivElement>(null);
    const handleOpenChange = React.useCallback((e: TreeItemOpenChangeEvent, data: TreeItemOpenChangeData) => {
        setOpen(data.open);
    }, [setOpen]);
    React.useEffect(() => {
        if (open && state === 'idle') {
            onDataLoading?.();
            query(async () => {
                // mockFetch is just a helper to simulate an API endpoint.
                // you probably will be using some custom API endpoint here.
                const json = await mockFetch(value.toString());
                return json.results.map<Entity>(entity => ({ value: `${value}/${entity.name}`, name: entity.name }));
            });
        }
    }, [open, onDataLoading, query, state, value]);
    React.useEffect(() => {
        if (open && state === 'loaded') {
            onDataLoaded?.();
            firstItemRef.current?.focus();
        }
    }, [open, state, onDataLoaded]);
    return (<>
      <FlatTreeItem value={value} aria-level={1} aria-setsize={3} aria-posinset={1} itemType="branch" open={open} onOpenChange={handleOpenChange}>
        <TreeItemLayout expandIcon={state === 'loading' ? <Spinner size="tiny"/> : undefined}>
          {value.toString()}
        </TreeItemLayout>
      </FlatTreeItem>
      {open &&
            items.map((item, index) => (<FlatTreeItem key={item.value} ref={index === 0 ? firstItemRef : null} parentValue={value} value={item.value} aria-level={2} aria-setsize={items.length} aria-posinset={index + 1} itemType="leaf">
            <TreeItemLayout>{item.name}</TreeItemLayout>
          </FlatTreeItem>))}
    </>);
};

export const LazyLoading = (): JSXElement => {
    const [ariaMessage, setAriaMessage] = React.useState('');
    const styles = useStyles();
    return (<>
      <FlatTree aria-label="Lazy Loading">
        <Subtree value="People" onDataLoaded={React.useCallback(() => setAriaMessage(`people items loaded`), [])} onDataLoading={React.useCallback(() => setAriaMessage(`loading people items...`), [])}/>
        <Subtree value="Planet" onDataLoaded={React.useCallback(() => setAriaMessage(`planet items loaded`), [])} onDataLoading={React.useCallback(() => setAriaMessage(`loading planet items...`), [])}/>
        <Subtree value="Starship" onDataLoaded={React.useCallback(() => setAriaMessage(`starship items loaded`), [])} onDataLoading={React.useCallback(() => setAriaMessage(`loading starship items...`), [])}/>
      </FlatTree>
      <div aria-live="polite" aria-atomic="true" className={styles.screenReadersOnly}>
        {ariaMessage}
      </div>
    </>);
};

export default LazyLoading;
