'use client';

import { createContext, forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import type { DropTarget } from 'react-aria-components';
import {
  Button,
  Collection,
  DropIndicator,
  Tree as ReactAriaTree,
  TreeItem,
  TreeItemContent,
  type TreeItemContentProps,
  type TreeItemContentRenderProps,
  type TreeItemProps,
  useDragAndDrop,
} from 'react-aria-components';
import type { Key, Selection } from 'react-stately';
import { useTreeData } from 'react-stately';

import clsx from 'clsx';

type TreeContextType = {
  onExpand?: (key: Key, item: TreeItemType) => void;
  onCollapse?: (key: Key, item: TreeItemType) => void;
  treeData?: ReturnType<typeof useTreeData<TreeItemType>>;
  treeOpenIcon?: React.ReactNode;
  treeCloseIcon?: React.ReactNode;
};

const TreeContext = createContext<TreeContextType>({});

interface MyTreeItemProps extends Partial<TreeItemProps> {
  title: string;
  isLastChild?: boolean;
  isRoot?: boolean;
}

export type TreeItemType = {
  id: number | string;
  title: string;
  type: 'directory' | 'file';
  value?: any;
  children?: TreeItemType[];
};

export type TreeRef = {
  updateItem: (item: TreeItemType) => void;
  addItem: (item: TreeItemType, parentKey?: Key | null) => void;
  removeItem: (itemId: number) => void;
  expandAllItems: () => void;
  collapseAllItems: () => void;
  applyReorder: (sourceKey: number, targetKey: number, dropPosition: 'before' | 'after') => void;
  clearItemsByParentKey: (parentKey: Key) => void;
  clearAllTrees: () => void;
};

export type TreeProps = {
  items: TreeItemType[];
  selectionMode?: 'single' | 'multiple' | 'none';
  selectedKeys?: 'all' | Set<number | string>;
  selectedParentKey?: Key | null;
  onSelectionChange?: (keys: Selection) => void;
  enableDragAndDrop?: boolean;
  onReorder?: (
    sourceKey: number,
    targetKey: number,
    dropPosition: 'before' | 'after',
    ParentKey: number | null
  ) => void;
  preventAutoReorder?: boolean;
  onExpand?: (key: Key, item: TreeItemType) => void;
  onCollapse?: (key: Key, item: TreeItemType) => void;
  onSelect?: (key: Key | null, item: TreeItemType | null) => void;
  'aria-label'?: string;
  className?: string;
  treeOpenIcon?: React.ReactNode;
  treeCloseIcon?: React.ReactNode;
  persistSelectionOnCollapse?: boolean;
};

const IconTreeBranch = () => (
  <svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
    <line x1="0.5" y1="0" x2="0.5" y2="48" stroke="#343434" strokeDasharray="2 2" />
    <line x1="25" y1="23.5" x2="1" y2="23.5" stroke="#343434" strokeDasharray="2 2" />
  </svg>
);

const IconTreeBranchLast = () => (
  <svg width="24" height="48" viewBox="0 0 24 48" fill="none" xmlns="http://www.w3.org/2000/svg">
    <line x1="0.5" y1="0" x2="0.5" y2="24" stroke="#343434" strokeDasharray="2 2" />
    <line x1="25" y1="23.5" x2="1" y2="23.5" stroke="#343434" strokeDasharray="2 2" />
  </svg>
);

const MyTreeItemContentInner = ({
  level,
  hasChildItems,
  isExpanded,
  isSelected,
  lastChild,
  children,
  isRoot,
  onExpandChange,
}: {
  level: number;
  hasChildItems: boolean;
  isExpanded: boolean;
  isSelected: boolean;
  lastChild?: boolean;
  children?: React.ReactNode;
  isRoot?: boolean;
  actualChildCount?: number;
  onExpandChange?: (isExpanded: boolean) => void;
}) => {
  const { treeOpenIcon, treeCloseIcon } = useContext(TreeContext);
  if (hasChildItems || isRoot) {
    return (
      <Button
        className={clsx(
          'cursor-pointer flex items-center h-10 hover:bg-dabeeo-gray-eb w-full text-dabeeo-black-34 text-sm',
          isSelected && 'bg-dabeeo-yellow'
        )}
        style={{ paddingLeft: `${level * 16}px` }}
        slot="chevron"
        onClick={() => {
          onExpandChange?.(!isExpanded);
        }}
      >
        {treeOpenIcon && treeCloseIcon ? (
          <div className="mr-3">{isExpanded ? treeOpenIcon : treeCloseIcon}</div>
        ) : (
          <div className="w-6 h-6 bg-dabeeo-gray-eb border border-dabeeo-black-34 hover:bg-dabeeo-gray-da mr-4 shrink-0">
            {isExpanded ? '-' : '+'}
          </div>
        )}
        <span className="font-semibold">{children}</span>
      </Button>
    );
  }

  return (
    <div
      className={clsx(
        'cursor-pointer flex items-center h-10 hover:bg-dabeeo-gray-eb w-full text-dabeeo-black-34 text-sm pl-9',
        isSelected && 'bg-dabeeo-yellow'
      )}
    >
      <div className="mr-3 flex-shrink-0">{lastChild ? <IconTreeBranchLast /> : <IconTreeBranch />}</div>
      <span className="font-normal">{children}</span>
    </div>
  );
};

const MyTreeItemContent = (
  props: Omit<TreeItemContentProps, 'children'> & {
    children?: React.ReactNode;
    lastChild?: boolean;
    isRoot?: boolean;
    onExpandChange?: (isExpanded: boolean) => void;
  }
) => {
  return (
    <TreeItemContent>
      {(renderProps: TreeItemContentRenderProps) => {
        const { hasChildItems, isExpanded, isSelected, level } = renderProps;
        return (
          <>
            <MyTreeItemContentInner
              level={level}
              hasChildItems={hasChildItems}
              isExpanded={isExpanded}
              isSelected={isSelected}
              lastChild={props.lastChild}
              isRoot={props.isRoot}
              onExpandChange={props.onExpandChange}
            >
              {props.children}
            </MyTreeItemContentInner>
            <Button slot="drag" className="sr-only">
              드래그
            </Button>
          </>
        );
      }}
    </TreeItemContent>
  );
};

const TreeDropIndicator = ({ target }: { target: DropTarget }) => {
  return (
    <DropIndicator
      target={target}
      className={({ isDropTarget }) =>
        clsx('w-full h-0 outline-2 outline-primary/50', isDropTarget ? 'opacity-100' : 'opacity-0')}
    />
  );
};

const MyTreeItem = (props: MyTreeItemProps) => {
  const { onExpand, onCollapse, treeData } = useContext(TreeContext);

  const handleExpandChange = (isExpanded: boolean) => {
    if (!props.id || !treeData) return;

    const item = treeData.getItem(props.id);
    if (!item) return;

    if (isExpanded) {
      onExpand?.(props.id, item.value);
    } else {
      onCollapse?.(props.id, item.value);
    }
  };

  return (
    <TreeItem
      textValue={props.title}
      {...props}
      hasChildItems={props.isRoot ? true : undefined}
      data-item-id={props.id}
      className="
        data-[dragging]:opacity-60
        data-[drop-target]:outline
        data-[drop-target]:outline-2
        data-[drop-target]:outline-[var(--highlight-background)]
        data-[drop-target]:bg-[var(--highlight-overlay)]
      "
    >
      <MyTreeItemContent lastChild={props.isLastChild} isRoot={props.isRoot} onExpandChange={handleExpandChange}>
        {props.title}
      </MyTreeItemContent>
      {props.children}
    </TreeItem>
  );
};

export const Tree = forwardRef<TreeRef, TreeProps>(
  (
    {
      'aria-label': ariaLabel = 'Tree',
      items,
      selectionMode = 'single',
      selectedKeys,
      selectedParentKey,
      enableDragAndDrop = true,
      className,
      onSelectionChange,
      onReorder,
      preventAutoReorder = false,
      onExpand,
      onCollapse,
      onSelect,
      treeOpenIcon,
      treeCloseIcon,
      persistSelectionOnCollapse = false,
    },
    ref
  ) => {
    const [expandedKeys, setExpandedKeys] = useState<Set<Key>>(new Set());
    const [treeKey, setTreeKey] = useState(0);

    const handleExpandedChange = (keys: Set<Key>) => {
      setExpandedKeys(keys);
    };

    const tree = useTreeData({
      initialItems: items,
      getKey: (item) => item.id,
      getChildren: (item) => item.children || [],
    });

    const updateItem = (item: TreeItemType) => {
      tree.update(item.id, item);
    };

    const addItem = (item: TreeItemType, parentKey?: Key | null) => {
      tree.getItem(-1) && tree.remove(-1);
      // 중복 체크: 이미 존재하는 아이템은 추가하지 않음
      const targetParentKey = parentKey ?? selectedParentKey ?? null;
      const existingItem = tree.getItem(item.id);

      if (
        (existingItem && existingItem.parentKey === targetParentKey)
        || (targetParentKey && targetParentKey === item.id)
      ) {
        return;
      }

      tree.append(targetParentKey, item);
    };

    const removeItem = (itemId: number) => {
      tree.remove(itemId);
      const childrens = tree.getItem(selectedParentKey as Key)?.children;
      itemId !== -1 && childrens?.length === 1 && updateMyParentItem();
    };

    const expandAllItems = () => {
      setExpandedKeys(new Set([...expandedKeys, ...tree.items.map((item) => item.key)]));
    };

    const collapseAllItems = () => {
      setExpandedKeys(new Set());
    };

    const clearAllTrees = () => {
      const keysToRemove = tree.items.map((item) => item.key);
      keysToRemove.forEach((key) => {
        tree.remove(key);
      });
    };

    const clearItemsByParentKey = (parentKey: Key) => {
      const removeRecursively = (key: Key) => {
        const item = tree.getItem(key);
        if (item && item.children) {
          item.children.forEach((child) => {
            removeRecursively(child.key);
          });
        }
        tree.remove(key);
      };

      const itemsToRemove: Key[] = [];
      tree.items.forEach((item) => {
        if (item.parentKey === parentKey) {
          itemsToRemove.push(item.key);
        }
      });

      itemsToRemove.forEach((key) => {
        removeRecursively(key);
      });
    };

    const applyReorder = (sourceKey: number, targetKey: number, dropPosition: 'before' | 'after') => {
      if (dropPosition === 'before') {
        tree.moveBefore(targetKey, new Set([sourceKey]));
      } else {
        tree.moveAfter(targetKey, new Set([sourceKey]));
      }
    };

    const updateMyParentItem = () => {
      if (selectedParentKey) {
        const parentItem = tree.getItem(selectedParentKey);
        if (parentItem) {
          tree.update(selectedParentKey, {
            ...parentItem.value,
            children: [],
            ...(parentItem.value.value && { value: { ...parentItem.value.value, children: [] } }),
          });
        }
      }
    };

    useImperativeHandle(ref, () => ({
      updateItem,
      addItem,
      removeItem,
      expandAllItems,
      collapseAllItems,
      applyReorder,
      clearItemsByParentKey,
      clearAllTrees,
    }));

    useEffect(() => {
      if (persistSelectionOnCollapse) return;
      if (selectedKeys) {
        setExpandedKeys(new Set([...expandedKeys, ...selectedKeys]));
      }
    }, [selectedKeys, persistSelectionOnCollapse]);

    useEffect(() => {
      if (items) {
        setTreeKey((prev) => prev + 1);
      }
    }, [items]);

    const handleSelectionChange = (keys: Selection) => {
      onSelectionChange?.(keys);

      if (onSelect && selectionMode === 'single' && keys !== 'all') {
        const selectedKey = keys instanceof Set ? Array.from(keys)[0] : null;
        if (selectedKey) {
          const item = tree.getItem(selectedKey);
          if (item) {
            onSelect(selectedKey, item ? item.value : null);
          }
        } else {
          onSelect(selectedKey, null);
        }
      }
    };

    const { dragAndDropHooks } = useDragAndDrop({
      getItems: (keys) =>
        [...keys].map((key) => ({
          'text/plain': tree.getItem(key)?.value.title || '',
        })),
      renderDropIndicator(target) {
        return <TreeDropIndicator target={target} />;
      },
      onReorder(e) {
        const targetItem = tree.getItem(e.target.key);
        const sourceKey = Array.from(e.keys)[0] as number;
        const dropPosition = e.target.dropPosition === 'before' ? 'before' : 'after';
        const targetParentKey = Number(targetItem?.parentKey) || null;

        if (preventAutoReorder) {
          onReorder?.(sourceKey, Number(e.target.key), dropPosition, targetParentKey);
        } else {
          if (dropPosition === 'before') {
            tree.moveBefore(e.target.key, e.keys);
          } else {
            tree.moveAfter(e.target.key, e.keys);
          }
          onReorder?.(sourceKey, Number(e.target.key), dropPosition, targetParentKey);
        }
      },
    });

    const calculateIsLastChild = (item: ReturnType<typeof tree.getItem>) => {
      if (!item || !item.parentKey) return false;

      const parent = tree.getItem(item.parentKey);
      if (!parent) return false;

      const siblings = parent.children || [];
      const index = siblings.findIndex((sibling) => sibling.key === item.key);
      return index === siblings.length - 1;
    };

    // MEMO: tree.items가 변경될 때 전체 트리를 다시 렌더링하기 위해 사용하는 key
    // 현재는 하나의 item이 변경되어도 전체 트리가 다시 렌더링됨. 변경된 item과 같은 부모 아래의 아이템들만 선택적으로 렌더링되도록 개선 필요함
    const treeRenderKey = useMemo(() => {
      const generateTreeHash = (items: typeof tree.items): string => {
        return items
          .map((item) => {
            const childrenHash = item.children ? generateTreeHash(item.children) : '';
            return `${item.key}-${item.children?.length || 0}-${childrenHash}`;
          })
          .join('|');
      };
      return generateTreeHash(tree.items);
    }, [tree.items]);

    return (
      <TreeContext.Provider value={{ onExpand, onCollapse, treeData: tree, treeOpenIcon, treeCloseIcon }}>
        <ReactAriaTree
          key={`${treeKey}-${treeRenderKey}`}
          aria-label={ariaLabel}
          selectionMode={selectionMode}
          selectedKeys={selectedKeys}
          onSelectionChange={handleSelectionChange}
          items={tree.items}
          dragAndDropHooks={enableDragAndDrop ? dragAndDropHooks : undefined}
          className={clsx(className, 'w-full')}
          expandedKeys={expandedKeys}
          onExpandedChange={handleExpandedChange}
        >
          {function renderItem(item) {
            const isLastChild = calculateIsLastChild(item);
            return (
              <MyTreeItem
                title={item.value.title || ''}
                isLastChild={isLastChild}
                isRoot={item.value.type === 'directory'}
              >
                <Collection items={item.children || []}>{renderItem}</Collection>
              </MyTreeItem>
            );
          }}
        </ReactAriaTree>
      </TreeContext.Provider>
    );
  }
);

Tree.displayName = 'Tree';
