import {DemoBox} from "../../src/components/demo/demo-box";

Headless Tree internally flattens the visibly rendered items and their nested tree structure into
a flat list of items, making it easier to handle as library consumer and enabling virtualization in 
an easy way.

However, by using `tree.getRootItem()` and `item.getChildren()` it's still very easy to render items
in a nested way, if you want to do so.

```ts jsx
import type { Meta } from "@storybook/react";
import React, { type FC, useState } from "react";
import {
  type ItemInstance,
  dragAndDropFeature,
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cn from "classnames";

const meta = {
  title: "React/Guides/Nested Rendering",
  tags: ["guide/nested-rendering", "homepage"],
} satisfies Meta;

export default meta;

// story-start
const Item: FC<{ item: ItemInstance<string> }> = ({ item }) => {
  return (
    <li
      style={{ paddingLeft: `20px` }}
      role="treeitem"
      aria-selected={item.isSelected()}
      aria-expanded={item.isExpanded()}
    >
      <button {...item.getProps()}>
        <div
          className={cn("treeitem", {
            focused: item.isFocused(),
            expanded: item.isExpanded(),
            selected: item.isSelected(),
            folder: item.isFolder(),
            drop: item.isDragTarget(),
          })}
        >
          {item.getItemName()}
        </div>
      </button>
      {item.isExpanded() && item.getChildren().length > 0 && (
        <ul role="group">
          {item.getChildren().map((child) => (
            <Item key={child.getKey()} item={child} />
          ))}
        </ul>
      )}
    </li>
  );
};

export const NestedRendering = () => {
  const [state, setState] = useState({});
  const tree = useTree<string>({
    state,
    setState,
    rootItemId: "folder",
    getItemName: (item) => item.getItemData(),
    isItemFolder: (item) => !item.getItemData().endsWith("item"),
    hotkeys: {
      customEvent: {
        hotkey: "Escape",
        handler: () => alert("Hello!"),
      },
    },
    dataLoader: {
      getItem: (itemId) => itemId,
      getChildren: (itemId) => [
        `${itemId}-1`,
        `${itemId}-2`,
        `${itemId}-3`,
        `${itemId}-1item`,
        `${itemId}-2item`,
        `${itemId}-3item`,
      ],
    },
    indent: 20,
    features: [
      syncDataLoaderFeature,
      selectionFeature,
      hotkeysCoreFeature,
      dragAndDropFeature,
    ],
  });

  return (
    <div {...tree.getContainerProps()} className="tree">
      <ul role="group" {...tree.getContainerProps()} className="tree">
        {tree
          .getRootItem()
          ?.getChildren()
          .map((item) => <Item key={item.getKey()} item={item} />)}
      </ul>
      <div style={tree.getDragLineStyle()} className="dragline" />
      <style>
        {`
        .tree ul {
          list-style: none;
          padding: 0;
          margin: 0;
        }
        `}
      </style>
    </div>
  );
};
```

You can also use this to implement expand/collapse animations. This is not as easy as just rendering the items in a nested way, 
since CSS animations also require items to be rendered after a collapse is triggered, for the duration of the animation.

The following sample shows a custom feature implementation that overwrites the `item.expand()` and `item.collapse()` methods to
keep track of animation state, and to postpone the actual collapse until after the animation is finished.
See the guide on plugins for more information on how to implement custom features like this.

```ts jsx
import type { Meta } from "@storybook/react";
import React, { type FC, useState } from "react";
import {
  type FeatureImplementation,
  type ItemInstance,
  dragAndDropFeature,
  hotkeysCoreFeature,
  makeStateUpdater,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import cn from "classnames";

const meta = {
  title: "React/Guides/Expand Collapse Animations",
  tags: ["guide/nested-rendering", "homepage"],
} satisfies Meta;

export default meta;

// story-start
// Define customs for a new state that stores if items are currently expanding or collapsing
declare module "@headless-tree/core" {
  export interface TreeState<T> {
    expandingOrCollapsingItems: string[];
  }
  export interface TreeConfig<T> {
    setExpandingOrCollapsingItems?: (items: string[]) => void;
  }
  export interface ItemInstance<T> {
    isExpandingOrCollapsing: () => boolean;
  }
}

// Custom feature for handling expand/collapse animations
// Storing whether an item is currently expanding or collapsing allows
// us to keep them rendered after collapsing while the animation is happening
const animationFeature: FeatureImplementation<string> = {
  getInitialState: (initialState) => ({
    expandingOrCollapsingItems: [],
    ...initialState,
  }),

  getDefaultConfig: (defaultConfig, tree) => {
    return {
      setExpandingOrCollapsingItems: makeStateUpdater(
        "expandingOrCollapsingItems",
        tree,
      ),
      ...defaultConfig,
    };
  },

  stateHandlerNames: {
    expandingOrCollapsingItems: "setExpandingOrCollapsingItems",
  },

  itemInstance: {
    // We overwrite the expand and collapse methods to add the expanding/collapsing state
    expand: ({ tree, item, itemId }) => {
      if (!item.isFolder()) {
        return;
      }

      if (tree.getState().loadingItemChildrens?.includes(itemId)) {
        return;
      }

      tree.applySubStateUpdate("expandedItems", (items) => [...items, itemId]);

      tree.applySubStateUpdate("expandingOrCollapsingItems", (items) => [
        ...items,
        itemId,
      ]);
      tree.rebuildTree();

      setTimeout(() => {
        tree.applySubStateUpdate("expandingOrCollapsingItems", (items) =>
          items.filter((id) => id !== itemId),
        );
      }, 0);
    },

    collapse: ({ tree, item, itemId }) => {
      if (!item.isFolder()) {
        return;
      }

      tree.applySubStateUpdate("expandingOrCollapsingItems", (items) => [
        ...items,
        itemId,
      ]);
      setTimeout(() => {
        tree.applySubStateUpdate("expandingOrCollapsingItems", (items) =>
          items.filter((id) => id !== itemId),
        );
        tree.applySubStateUpdate("expandedItems", (items) =>
          items.filter((id) => id !== itemId),
        );
        tree.rebuildTree();
      }, 300);
    },

    isExpandingOrCollapsing: ({ tree, itemId }) =>
      tree.getState().expandingOrCollapsingItems.includes(itemId),
  },
};

const Item: FC<{ item: ItemInstance<string> }> = ({ item }) => {
  return (
    <li
      style={{ paddingLeft: `20px` }}
      role="treeitem"
      aria-selected={item.isSelected()}
      aria-expanded={item.isExpanded()}
    >
      <button {...item.getProps()}>
        <div
          className={cn("treeitem", {
            focused: item.isFocused(),
            expanded: item.isExpanded(),
            selected: item.isSelected(),
            folder: item.isFolder(),
            drop: item.isDragTarget(),
          })}
        >
          {item.getItemName()}
        </div>
      </button>
      {item.isExpanded() && item.getChildren().length > 0 && (
        <div
          className={cn("expand-container", {
            expandingOrCollapsing: item.isExpandingOrCollapsing(),
          })}
        >
          <ul role="group">
            {item.getChildren().map((child) => (
              <Item key={child.getKey()} item={child} />
            ))}
          </ul>
        </div>
      )}
    </li>
  );
};

export const ExpandCollapseAnimations = () => {
  const [state, setState] = useState({});
  const tree = useTree<string>({
    state,
    setState,
    rootItemId: "folder",
    getItemName: (item) => item.getItemData(),
    isItemFolder: (item) => !item.getItemData().endsWith("item"),
    hotkeys: {
      customEvent: {
        hotkey: "Escape",
        handler: () => alert("Hello!"),
      },
    },
    dataLoader: {
      getItem: (itemId) => itemId,
      getChildren: (itemId) => [
        `${itemId}-1`,
        `${itemId}-2`,
        `${itemId}-3`,
        `${itemId}-1item`,
        `${itemId}-2item`,
        `${itemId}-3item`,
      ],
    },
    indent: 20,
    features: [
      syncDataLoaderFeature,
      selectionFeature,
      hotkeysCoreFeature,
      dragAndDropFeature,
      animationFeature,
    ],
  });

  return (
    <div {...tree.getContainerProps()} className="tree">
      <ul role="group" {...tree.getContainerProps()} className="tree">
        {tree
          .getRootItem()
          ?.getChildren()
          .map((item) => <Item key={item.getKey()} item={item} />)}
      </ul>
      <div style={tree.getDragLineStyle()} className="dragline" />
      <style>
        {`
        .tree ul {
          list-style: none;
          padding: 0;
          margin: 0;
        }
        .expand-container {
          overflow: hidden;
        }
        .expand-container ul {
          transition: margin-top 0.3s ease-in-out;
          margin-top: 0;
        }
        .expand-container.expandingOrCollapsing ul {
          margin-top: -100%;
        }
        `}
      </style>
    </div>
  );
};
```
