import { useCallback, useMemo, useState } from 'react';
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import { createRootNode, DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
import {
  getEntryId,
  isDocumentEntry,
  isFolderEntry,
  createDocumentEntryKey,
  createFolderEntryKey,
} from '../../app/entryKey';
import type { FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
import type { Document } from '../../types/documents';

type FolderId = FolderIdentifier | 'root';

interface FolderSummary {
  id?: FolderId;
  name?: string;
  parent_id?: FolderId | null;
  parentId?: FolderId | null;
  children?: FolderId[];
  subfolders?: FolderSummary[];
  has_children?: boolean;
  hasChildren?: boolean;
  [key: string]: unknown;
}

interface FolderContentsEntry {
  folder?: FolderSummary | null;
  documents?: Document[];
  subfolders?: FolderSummary[];
  __includesDocuments?: boolean;
  __sortField?: string | null;
  __sortDirection?: string | null;
  [key: string]: unknown;
}

interface FolderTreeNode extends FolderSummary {
  id: FolderId;
  children: FolderId[];
  expanded?: boolean;
  loaded?: boolean;
  hasChildren?: boolean;
}

interface ApiClient {
  get<T = FolderContentsEntry>(path: string, config?: { params?: Record<string, unknown> }): Promise<{ data: T }>;
}

interface SelectionHelpers {
  focusedDocumentId: Identifier | null;
  setFocusedDocumentId: Dispatch<SetStateAction<Identifier | null>>;
  setSelectedEntries: Dispatch<SetStateAction<string[]>>;
  setSelectionOrder: Dispatch<SetStateAction<string[]>>;
  selectionOrderRef: MutableRefObject<string[] | null>;
  selectionAnchorRef: MutableRefObject<string | null>;
}

interface UseFolderTreeOptions {
  initialSelectedFolder?: FolderId;
  apiClient: ApiClient;
  tenantIdRef: MutableRefObject<Identifier | null>;
  documentsSortFieldRef: MutableRefObject<string>;
  documentsSortDirectionRef: MutableRefObject<string>;
  selectionHelpers: SelectionHelpers;
  setDocuments: Dispatch<SetStateAction<Document[]>>;
  setFolderContents: Dispatch<SetStateAction<Map<FolderId, FolderContentsEntry>>>;
  folderContentsRef: MutableRefObject<Map<FolderId, FolderContentsEntry>>;
}

interface FolderOption {
  id: FolderId;
  label: string;
}

const useFolderTree = ({
  initialSelectedFolder = 'root',
  apiClient,
  tenantIdRef,
  documentsSortFieldRef,
  documentsSortDirectionRef,
  selectionHelpers,
  setDocuments,
  setFolderContents,
  folderContentsRef,
}: UseFolderTreeOptions) => {
  const [folderNodes, setFolderNodes] = useState<Map<FolderId, FolderTreeNode>>(() => {
    const rootNode = createRootNode() as FolderTreeNode;
    return new Map([[rootNode.id, rootNode]]);
  });

  const [selectedFolder, setSelectedFolder] = useState<FolderId>(initialSelectedFolder || 'root');
  const [currentFolder, setCurrentFolder] = useState<FolderSummary | null>(null);
  const [currentSubfolders, setCurrentSubfolders] = useState<FolderSummary[]>([]);

  const {
    focusedDocumentId,
    setFocusedDocumentId,
    setSelectedEntries,
    setSelectionOrder,
    selectionOrderRef,
    selectionAnchorRef,
  } = selectionHelpers;

  const applySelectedFolder = useCallback(
    (folderId: FolderId, contents?: FolderContentsEntry | null) => {
      const subfolders = Array.isArray(contents?.subfolders) ? contents.subfolders : [];
      const docs = Array.isArray(contents?.documents) ? contents.documents : [];
      const folderInfo = contents?.folder ?? null;

      setCurrentSubfolders(subfolders);
      setDocuments(docs);
      setCurrentFolder(folderInfo);

      const availableDocKeys = docs
        .map((doc) => createDocumentEntryKey(doc?.id as Identifier))
        .filter(Boolean);
      const availableDocKeySet = new Set(availableDocKeys);
      const availableFolderKeys = new Set(
        subfolders
          .map((folder) => createFolderEntryKey(folder?.id as Identifier))
          .filter(Boolean),
      );

      let nextDocKeys: string[] = [];
      let mergedSelection: string[] = [];

      setSelectedEntries((previous) => {
        const previousFolderKeys = previous
          .filter(isFolderEntry)
          .filter((key) => availableFolderKeys.has(key));
        const previousDocKeys = previous.filter(isDocumentEntry);
        nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key));
        mergedSelection = [...previousFolderKeys, ...nextDocKeys];
        return mergedSelection;
      });

      const nextFocus = (() => {
        if (focusedDocumentId) {
          const currentFocusedKey = createDocumentEntryKey(focusedDocumentId);
          if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) {
            return focusedDocumentId;
          }
        }
        if (nextDocKeys.length) {
          const lastDocKey = nextDocKeys[nextDocKeys.length - 1];
          return getEntryId(lastDocKey) || null;
        }
        return null;
      })();

      setFocusedDocumentId(nextFocus);
      const nextAnchor = mergedSelection.length ? mergedSelection[mergedSelection.length - 1] : null;
      selectionAnchorRef.current = nextAnchor;
      selectionOrderRef.current = mergedSelection;
      setSelectionOrder(mergedSelection);
    },
    [
      focusedDocumentId,
      selectionAnchorRef,
      selectionOrderRef,
      setDocuments,
      setFocusedDocumentId,
      setSelectedEntries,
      setSelectionOrder,
    ],
  );

  const expandFolderAncestors = useCallback((targetId: FolderId | null) => {
    if (!targetId || targetId === 'root') {
      return;
    }

    setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => {
      const next = new Map<FolderId, FolderTreeNode>(prev);
      const node = next.get(targetId);
      let currentId = node?.parentId ?? 'root';
      let guard = 0;

      while (currentId && guard < 32) {
        guard += 1;
        const currentNode = next.get(currentId);
        if (!currentNode) break;
        if (!currentNode.expanded) {
          next.set(currentId, { ...currentNode, expanded: true });
        }
        currentId = currentNode.parentId ?? 'root';
      }
      return next;
    });
  }, []);

  const ensureFolderData = useCallback(
    async (
      folderId: FolderId,
      {
        includeDocuments = true,
        prefetchDepth = 0,
        force = false,
        sortField = documentsSortFieldRef.current,
        sortDirection = documentsSortDirectionRef.current,
      }: {
        includeDocuments?: boolean;
        prefetchDepth?: number;
        force?: boolean;
        sortField?: string;
        sortDirection?: string;
      } = {},
    ): Promise<FolderContentsEntry> => {
      const requestTenantId = tenantIdRef.current;
      const cached = folderContentsRef.current.get(folderId);
      const cachedSortField = cached?.__sortField || documentsSortFieldRef.current;
      const cachedSortDirection = cached?.__sortDirection || documentsSortDirectionRef.current;
      const cachedSortMatches = cachedSortField === sortField && cachedSortDirection === sortDirection;

      if (!force && cached) {
        const includesDocuments = Boolean(cached.__includesDocuments);
        if (!includeDocuments || (includesDocuments && cachedSortMatches)) {
          if (prefetchDepth > 0) {
            const subfolders = Array.isArray(cached.subfolders) ? cached.subfolders : [];
            await Promise.allSettled(
              subfolders.map((entry) =>
                ensureFolderData(entry.id, {
                  includeDocuments: false,
                  prefetchDepth: prefetchDepth - 1,
                  force: false,
                }),
              ),
            );
          }
          return cached;
        }
      }

      const path = folderId === 'root' ? 'root' : folderId;
      const params: Record<string, unknown> = {};
      if (!includeDocuments) {
        params.include_documents = false;
      } else {
        params.sort = sortField;
        params.dir = sortDirection;
      }
      const requestConfig = Object.keys(params).length ? { params } : {};
      const { data } = await apiClient.get<FolderContentsEntry>(`/folders/${path}/contents`, requestConfig);
      const childFolders = Array.isArray(data.subfolders) ? data.subfolders : [];
      const childIds = childFolders
        .map((child) => (child?.id ?? null) as FolderId | null)
        .filter((id): id is FolderId => Boolean(id));

      const enriched = {
        ...data,
        __includesDocuments: includeDocuments,
        __sortField: includeDocuments ? sortField : cachedSortField,
        __sortDirection: includeDocuments ? sortDirection : cachedSortDirection,
      };

      if (tenantIdRef.current !== requestTenantId) {
        return enriched;
      }

      setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => {
        const next = new Map<FolderId, FolderTreeNode>(prev);
        const existingNode = next.get(folderId) || {
          id: folderId,
          name: folderId === 'root' ? DEFAULT_FOLDER_NAME : data.folder?.name || 'Folder',
          parentId: data.folder?.parent_id || 'root',
          children: [],
          expanded: folderId === 'root',
          loaded: false,
          hasChildren: false,
        };

        next.set(folderId, {
          ...existingNode,
          name: folderId === 'root' ? DEFAULT_FOLDER_NAME : data.folder?.name || existingNode.name,
          parentId: data.folder?.parent_id ?? existingNode.parentId ?? 'root',
          children: childIds,
          expanded: folderId === 'root' ? true : existingNode.expanded,
          loaded: true,
          hasChildren: childIds.length > 0,
        });

        childFolders.forEach((child) => {
          const childId = (child?.id ?? null) as FolderId | null;
          if (!childId) {
            return;
          }
          const childNode = next.get(childId);
          const previousChildren = Array.isArray(childNode?.children) ? childNode.children : [];
          const childHasChildren = (() => {
            if (childNode?.loaded) {
              return previousChildren.length > 0;
            }
            if (Array.isArray(child?.subfolders)) {
              return child.subfolders.length > 0;
            }
            const flag = [child?.has_children, child?.hasChildren, childNode?.hasChildren]
              .find((value) => value != null);
            return Boolean(flag);
          })();
          next.set(childId, {
            id: childId,
            name: child.name,
            parentId: (child.parent_id ?? 'root') as FolderId,
            children: previousChildren,
            expanded: childNode?.expanded ?? false,
            loaded: childNode?.loaded ?? false,
            hasChildren: childHasChildren,
          });
        });

        return next;
      });

      if (prefetchDepth > 0 && childIds.length > 0 && tenantIdRef.current === requestTenantId) {
        await Promise.allSettled(
          childIds.map((childId) =>
            ensureFolderData(childId, {
              includeDocuments: false,
              force: false,
              prefetchDepth: prefetchDepth - 1,
            }),
          ),
        );
      }

      setFolderContents((prev: Map<FolderId, FolderContentsEntry>) => {
        if (tenantIdRef.current !== requestTenantId) {
          return prev;
        }
        const next = new Map<FolderId, FolderContentsEntry>(prev);
        if (includeDocuments) {
          next.set(folderId, enriched);
        } else {
          const existingEntry = next.get(folderId);
          if (existingEntry) {
            next.set(folderId, {
              ...existingEntry,
              ...data,
              documents: existingEntry.__includesDocuments
                ? existingEntry.documents
                : data.documents,
              __includesDocuments: existingEntry.__includesDocuments || false,
              __sortField: existingEntry.__sortField ?? enriched.__sortField,
              __sortDirection: existingEntry.__sortDirection ?? enriched.__sortDirection,
            });
          } else {
            next.set(folderId, enriched);
          }
        }
        return next;
      });

      return enriched;
    },
    [
      apiClient,
      documentsSortDirectionRef,
      documentsSortFieldRef,
      tenantIdRef,
      setFolderContents,
      folderContentsRef,
    ],
  );

  const ensureFolderAncestorsLoaded = useCallback(
    async (targetId: FolderId | null) => {
      if (!targetId || targetId === 'root') {
        return;
      }
      let current = targetId;
      let guard = 0;
      while (current && current !== 'root' && guard < 32) {
        guard += 1;
        const node = folderNodes.get(current);
        if (node?.loaded) {
          current = node.parentId ?? 'root';
          continue;
        }
        await ensureFolderData(current, { includeDocuments: false, prefetchDepth: 0 });
        current = folderNodes.get(current)?.parentId ?? 'root';
      }
    },
    [folderNodes, ensureFolderData],
  );

  const isInvalidFolderDrop = useCallback(
    (sourceId: FolderId | null, targetId: FolderId | null) => {
      if (!sourceId) return false;
      if (!targetId || targetId === 'root') {
        return false;
      }
      if (sourceId === targetId) {
        return true;
      }

      let current = targetId;
      const visited = new Set();
      while (current && current !== 'root' && !visited.has(current)) {
        visited.add(current);
        if (current === sourceId) {
          return true;
        }
        const node = folderNodes.get(current);
        if (!node) break;
        current = node.parentId ?? 'root';
      }
      return false;
    },
    [folderNodes],
  );

  const resetFolderTreeState = useCallback(() => {
    const rootNode = createRootNode() as FolderTreeNode;
    setFolderNodes(new Map<FolderId, FolderTreeNode>([[rootNode.id, rootNode]]));
    setFolderContents(new Map<FolderId, FolderContentsEntry>());
    setSelectedFolder('root');
    setCurrentFolder(null);
    setCurrentSubfolders([]);
  }, [setFolderContents]);

  const currentFolderName = useMemo(() => {
    if (selectedFolder === 'root' || !currentFolder) return DEFAULT_FOLDER_NAME;
    return currentFolder.name;
  }, [selectedFolder, currentFolder]);

  const folderOptions: FolderOption[] = useMemo(() => {
    const cache = new Map<FolderId, string>();
    const computePath = (id: FolderId | null): string => {
      if (cache.has(id as FolderId)) {
        return cache.get(id as FolderId) as string;
      }
      if (!id || id === 'root') {
        cache.set('root', DEFAULT_FOLDER_NAME);
        return DEFAULT_FOLDER_NAME;
      }
      const node = folderNodes.get(id);
      if (!node) {
        return 'Folder';
      }
      const parentId = (node.parentId || 'root') as FolderId;
      const parentPath = computePath(parentId);
      const name = node.name || 'Folder';
      const fullPath = parentId === 'root' ? name : `${parentPath}/${name}`;
      cache.set(id, fullPath);
      return fullPath;
    };

    const entries: FolderOption[] = [];
    folderNodes.forEach((node, id) => {
      if (!node) return;
      entries.push({ id, label: computePath(id) });
    });

    entries.sort((a, b) => {
      if (a.id === 'root') return -1;
      if (b.id === 'root') return 1;
      return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' });
    });

    return entries;
  }, [folderNodes]);

  const folderLabelMap = useMemo(() => {
    const map = new Map<FolderId, string>();
    folderOptions.forEach((option) => {
      map.set(option.id, option.label);
    });
    return map;
  }, [folderOptions]);

  return {
    folderNodes,
    setFolderNodes,
    selectedFolder,
    setSelectedFolder,
    currentFolder,
    setCurrentFolder,
    currentSubfolders,
    setCurrentSubfolders,
    currentFolderName,
    folderOptions,
    folderLabelMap,
    applySelectedFolder,
    ensureFolderData,
    ensureFolderAncestorsLoaded,
    expandFolderAncestors,
    isInvalidFolderDrop,
    resetFolderTreeState,
  };
};

export default useFolderTree;
