import { useCallback, useMemo } from 'react';
import type { DragEvent } from 'react';
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils';
import {
  createFolder,
  deleteFolder,
  moveFolder as moveFolderRequest,
  renameFolder as renameFolderRequest,
} from '../../lib/apiClient';
import type { FolderId } from '../../types/identifiers';

type FolderKey = FolderId | 'root';

interface FolderNode {
  id: FolderKey;
  name?: string;
  parentId?: FolderKey | null;
  children: FolderId[];
  expanded?: boolean;
  loaded?: boolean;
  hasChildren?: boolean;
}

interface FolderContentsState {
  folder?: { id: FolderKey; name?: string };
  subfolders?: any[];
  documents?: any[];
  [key: string]: unknown;
}

interface EnsureFolderOptions {
  force?: boolean;
  includeDocuments?: boolean;
  prefetchDepth?: number;
}

interface LoadFolderOptions {
  preserveSearch?: boolean;
}

interface SelectFolderOptions {
  replace?: boolean;
  immediate?: boolean;
}

interface FolderClickHandlers {
  onToggle: (folderId: FolderKey) => Promise<void>;
  onSelect: (folderId: FolderKey, options?: SelectFolderOptions) => Promise<void>;
  onDrop: (event: DragEvent<HTMLElement>, folderId: FolderKey) => Promise<void>;
  onDragOver: (event: DragEvent<HTMLElement>, folderId: FolderKey) => void;
  onDragLeave: (event: DragEvent<HTMLElement>) => void;
}

interface UseFolderTreeActionsOptions {
  token?: string | null;
  folderNodes: Map<FolderKey, FolderNode>;
  setFolderNodes: (updater: (prev: Map<FolderKey, FolderNode>) => Map<FolderKey, FolderNode>) => void;
  selectedFolder: FolderKey;
  setSelectedFolder: (folderId: FolderKey) => void;
  ensureFolderData: (folderId: FolderKey, options?: EnsureFolderOptions) => Promise<any>;
  ensureFolderAncestorsLoaded: (folderId: FolderKey) => Promise<void>;
  expandFolderAncestors: (folderId: FolderKey, options?: { includeSelf?: boolean }) => void;
  applySelectedFolder: (folderId: FolderKey, contents: any) => void;
  notifyApiError: (error: unknown, message?: string) => void;
  setStatusMessage: (message: string, level?: string) => void;
  setFolderContents: (
    updater: (prev: Map<FolderKey, FolderContentsState>) => Map<FolderKey, FolderContentsState>,
  ) => void;
  setCurrentFolder: (updater: (prev: any) => any) => void;
  setSearchResultIds: (value: any) => void;
  isFilterActive: boolean;
  navigate?: (path: string, options?: { replace?: boolean }) => void;
  handleFileDrop: (dataTransfer: DataTransfer, folderId: FolderKey) => Promise<void> | void;
  moveDocumentsToFolder: (docIds: FolderId[], folderId: FolderKey) => Promise<void>;
  draggedDocumentIds: FolderId[];
  draggedFolderId: FolderKey | null;
  setDraggedDocumentIds: (ids: FolderId[]) => void;
  setDraggedFolderId: (id: FolderKey | null) => void;
  isInvalidFolderDrop: (sourceFolderId: FolderKey, targetFolderId: FolderKey) => boolean;
  setCreatingFolder: (value: boolean) => void;
}

const useFolderTreeActions = ({
  token,
  folderNodes,
  setFolderNodes,
  selectedFolder,
  setSelectedFolder,
  ensureFolderData,
  ensureFolderAncestorsLoaded,
  expandFolderAncestors,
  applySelectedFolder,
  notifyApiError,
  setStatusMessage,
  setFolderContents,
  setCurrentFolder,
  setSearchResultIds,
  isFilterActive,
  navigate,
  handleFileDrop,
  moveDocumentsToFolder,
  draggedDocumentIds,
  draggedFolderId,
  setDraggedDocumentIds,
  setDraggedFolderId,
  isInvalidFolderDrop,
  setCreatingFolder,
}: UseFolderTreeActionsOptions) => {
  const moveFolder = useCallback(
    async (folderId: FolderKey, targetFolderId: FolderKey | null) => {
      const node = folderNodes.get(folderId);
      if (!node) {
        setStatusMessage('Folder metadata unavailable. Try refreshing.', 'error');
        return;
      }

      const previousParentKey = node.parentId ?? 'root';
      const targetKey = targetFolderId && targetFolderId !== 'root' ? targetFolderId : 'root';

      if (previousParentKey === targetKey) {
        return;
      }

      const parent_id = targetKey === 'root' ? null : targetKey;

      try {
        await moveFolderRequest(folderId, parent_id);

        setFolderNodes((prev) => {
          const next = new Map(prev);
          const currentNode = next.get(folderId);
          if (!currentNode) {
            return prev;
          }

          next.set(folderId, { ...currentNode, parentId: parent_id ?? null });

          const previousParent = next.get(previousParentKey);
          if (previousParent) {
            const remainingChildren = (previousParent.children || []).filter(
              (childId) => childId !== folderId,
            );
            next.set(previousParentKey, {
              ...previousParent,
              children: remainingChildren,
              hasChildren: remainingChildren.length > 0,
            });
          }

          if (!next.has(targetKey)) {
            next.set(targetKey, {
              id: targetKey,
              name: targetKey === 'root' ? DEFAULT_FOLDER_NAME : 'Folder',
              parentId: targetKey === 'root' ? null : null,
              children: [],
              expanded: targetKey === 'root',
              loaded: false,
              hasChildren: false,
            });
          }

          const targetNode = next.get(targetKey);
          if (targetNode && !targetNode.children.includes(folderId)) {
            next.set(targetKey, {
              ...targetNode,
              children: [...targetNode.children, folderId],
              hasChildren: true,
            });
          }

          return next;
        });

        const refreshTargets = new Set([previousParentKey, targetKey]);
        await Promise.all(
          Array.from(refreshTargets).map((key) =>
            ensureFolderData(key === 'root' ? 'root' : key, { force: true, prefetchDepth: 1 }),
          ),
        );

        if (selectedFolder === folderId) {
          await ensureFolderData(folderId, { force: true, prefetchDepth: 1 });
          setSelectedFolder(folderId);
        }

        setStatusMessage('Folder moved.', 'success');
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to move folder.';
        notifyApiError(error, message);

        const refreshTargets = new Set([previousParentKey, targetKey]);
        await Promise.all(
          Array.from(refreshTargets).map((key) =>
            ensureFolderData(key === 'root' ? 'root' : key, { force: true, prefetchDepth: 1 }),
          ),
        );
      }
    },
    [
      ensureFolderData,
      folderNodes,
      notifyApiError,
      selectedFolder,
      setFolderNodes,
      setSelectedFolder,
      setStatusMessage,
    ],
  );

  const loadFolder = useCallback(
    async (folderId: FolderKey | null, { preserveSearch = false }: LoadFolderOptions = {}) => {
      const targetId = folderId || 'root';
      setSelectedFolder(targetId);
      await ensureFolderAncestorsLoaded(targetId);
      expandFolderAncestors(targetId);
      try {
        const contents = await ensureFolderData(targetId, { force: true, prefetchDepth: 1 });
        if (targetId !== 'root') {
          try {
            await ensureFolderData('root', {
              force: false,
              includeDocuments: false,
              prefetchDepth: 1,
            });
          } catch (error) {
            console.warn('Failed to refresh root folder tree', error);
          }
        }
        applySelectedFolder(targetId, contents);
        if (!preserveSearch) {
          setSearchResultIds(null);
        }
      } catch (error) {
        notifyApiError(error, 'Failed to load folder contents.');
      }
    },
    [
      applySelectedFolder,
      ensureFolderAncestorsLoaded,
      ensureFolderData,
      expandFolderAncestors,
      notifyApiError,
      setSearchResultIds,
      setSelectedFolder,
    ],
  );

  const selectFolder = useCallback(
    async (folderId: FolderKey | null, { replace = false, immediate = false }: SelectFolderOptions = {}) => {
      const targetId = folderId && folderId !== 'root' ? folderId : 'root';

      await ensureFolderAncestorsLoaded(targetId);
      expandFolderAncestors(targetId);

      if (!navigate || immediate) {
        await loadFolder(targetId, { preserveSearch: isFilterActive });
        setSelectedFolder(targetId);
        return;
      }

      const path = targetId === 'root' ? '/documents' : `/documents/folder/${targetId}`;
      navigate(path, { replace });
    },
    [
      ensureFolderAncestorsLoaded,
      expandFolderAncestors,
      isFilterActive,
      loadFolder,
      navigate,
      setSelectedFolder,
    ],
  );

  const handleFolderRename = useCallback(
    async (folderId: FolderKey, nextName: string) => {
      if (!token) {
        setStatusMessage('Log in to rename folders.', 'error');
        return false;
      }
      const trimmed = nextName?.trim?.() || '';
      if (!trimmed) {
        setStatusMessage('Folder name cannot be empty.', 'error');
        return false;
      }
      try {
        await renameFolderRequest(folderId, trimmed);

        setFolderNodes((prev) => {
          const next = new Map(prev);
          const node = next.get(folderId);
          if (node) {
            next.set(folderId, { ...node, name: trimmed });
          }
          return next;
        });

        setFolderContents((prev) => {
          if (!prev.has(folderId)) {
            return prev;
          }
          const next = new Map(prev);
          const existing = next.get(folderId) || {};
          const folderInfo = existing.folder
            ? { ...existing.folder, name: trimmed }
            : { id: folderId, name: trimmed };
          next.set(folderId, { ...existing, folder: folderInfo });
          return next;
        });

        setCurrentFolder((prev) => (prev?.id === folderId ? { ...prev, name: trimmed } : prev));
        setStatusMessage('Folder renamed.', 'success');
        return true;
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to rename folder.';
        notifyApiError(error, message);
        return false;
      }
    },
    [
      notifyApiError,
      setCurrentFolder,
      setFolderContents,
      setFolderNodes,
      setStatusMessage,
      token,
    ],
  );

  const handleFolderCreate = useCallback(
    async (name: string, parentId?: FolderKey | null) => {
      if (!token) {
        setStatusMessage('Log in to create folders.', 'error');
        return false;
      }
      if (!name.trim()) {
        setStatusMessage('Folder name cannot be empty.', 'error');
        return false;
      }

      const targetParentId = parentId !== undefined
        ? (parentId === 'root' ? null : parentId)
        : (selectedFolder === 'root' ? null : selectedFolder);

      const payload = {
        name: name.trim(),
        parent_id: targetParentId,
      };
      setCreatingFolder(true);
      let succeeded = false;
      try {
        const data = await createFolder(payload);
        const folderData = (data as { folder?: { id?: FolderKey; name?: string; parent_id?: FolderKey | null; children?: FolderKey[] } }).folder;
        if (!folderData?.id) {
          throw new Error('Folder creation failed.');
        }
        setStatusMessage('Folder created.', 'success');
        setFolderNodes((prev) => {
          const next = new Map(prev);
          const parentId = folderData.parent_id ?? payload.parent_id ?? 'root';
          const parentNode = next.get(parentId);
          if (parentNode) {
            next.set(parentId, {
              ...parentNode,
              children: parentNode.children.concat([folderData.id]),
              loaded: true,
              hasChildren: true,
            });
          }
          next.set(folderData.id, {
            id: folderData.id,
            name: folderData.name ?? payload.name,
            parentId: parentId,
            children: folderData.children || [],
            expanded: false,
            loaded: false,
            hasChildren: Array.isArray(folderData.children) ? folderData.children.length > 0 : false,
          });
          return next;
        });

        // Refresh the parent folder to ensure consistency
        const refreshTarget = targetParentId || 'root';
        await ensureFolderData(refreshTarget, { force: true, prefetchDepth: 1 });

        await selectFolder(folderData.id, { immediate: true });
        succeeded = true;
        return true;
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to create folder.';
        notifyApiError(error, message);
        return false;
      } finally {
        setCreatingFolder(false);
        if (!succeeded) {
          setStatusMessage('Folder creation failed.', 'error');
        }
      }
    },
    [
      ensureFolderData,
      notifyApiError,
      selectFolder,
      selectedFolder,
      setCreatingFolder,
      setFolderNodes,
      setStatusMessage,
      token,
    ],
  );

  const handleFolderDelete = useCallback(
    async (folderId: FolderKey, { showMessage = true }: { showMessage?: boolean } = {}) => {
      if (!token) {
        if (showMessage) {
          setStatusMessage('Log in to manage folders.', 'error');
        }
        return false;
      }
      if (!folderId || folderId === 'root') {
        if (showMessage) {
          setStatusMessage('The root folder cannot be removed.', 'error');
        }
        return false;
      }

      try {
        const contents = await ensureFolderData(folderId, {
          force: true,
          prefetchDepth: 1,
        });
        const hasChildren = (contents.subfolders || []).length > 0;
        const hasDocs = (contents.documents || []).length > 0;
        if (hasChildren || hasDocs) {
          if (showMessage) {
            setStatusMessage('Folder must be empty before it can be deleted.', 'error');
          }
          return false;
        }

        await deleteFolder(folderId);

        setFolderNodes((prev) => {
          const next = new Map(prev);
          const node = next.get(folderId);
          next.delete(folderId);
          if (node) {
            const parentId = node.parentId || 'root';
            const parentNode = next.get(parentId);
            if (parentNode) {
              const remaining = parentNode.children.filter((id) => id !== folderId);
              next.set(parentId, {
                ...parentNode,
                children: remaining,
                hasChildren: remaining.length > 0,
              });
            }
          }
          return next;
        });

        setFolderContents((prev) => {
          const next = new Map(prev);
          next.delete(folderId);
          return next;
        });

        if (selectedFolder === folderId) {
          const node = folderNodes.get(folderId);
          const parentId = node?.parentId || 'root';
          setSelectedFolder(parentId);
          const parentContents = await ensureFolderData(parentId, {
            force: true,
            prefetchDepth: 1,
          });
          applySelectedFolder(parentId, parentContents);
        } else if (selectedFolder !== 'root') {
          await ensureFolderData(selectedFolder, { force: true, prefetchDepth: 1 });
        }

        if (showMessage) {
          setStatusMessage('Folder deleted.', 'success');
        }
        return true;
      } catch (error) {
        const message = error.response?.data?.error || 'Failed to delete folder.';
        notifyApiError(error, message);
        if (showMessage) {
          setStatusMessage(message, 'error');
        }
        return false;
      }
    },
    [
      token,
      applySelectedFolder,
      ensureFolderData,
      folderNodes,
      notifyApiError,
      selectedFolder,
      setFolderContents,
      setFolderNodes,
      setSelectedFolder,
      setStatusMessage,
    ],
  );

  const folderClickHandlers: FolderClickHandlers = useMemo(
    () => ({
      onToggle: async (folderId: FolderKey) => {
        const node = folderNodes.get(folderId);
        const nextExpanded = !(node?.expanded ?? false);
        if (nextExpanded) {
          try {
            await ensureFolderData(folderId, {
              includeDocuments: false,
              prefetchDepth: 1,
            });
          } catch (error) {
            notifyApiError(error, 'Failed to load folder.');
          }
        } else if (node && !node.loaded) {
          try {
            await ensureFolderData(folderId, {
              includeDocuments: false,
              prefetchDepth: 1,
            });
          } catch (error) {
            notifyApiError(error, 'Failed to load folder.');
          }
        }
        setFolderNodes((prev) => {
          const next = new Map(prev);
          const current = next.get(folderId);
          if (!current) return prev;
          next.set(folderId, { ...current, expanded: nextExpanded });
          return next;
        });
      },
      onSelect: selectFolder,
      onDrop: async (event: DragEvent<HTMLElement>, folderId: FolderKey) => {
        event.preventDefault();
        event.stopPropagation();
        event.currentTarget.classList.remove('is-drop-target');

        let folderIds: FolderId[] = [];
        try {
          const rawFolderList = event.dataTransfer.getData('application/x-papercrate-folder-list');
          if (rawFolderList) {
            const parsed = JSON.parse(rawFolderList);
            if (Array.isArray(parsed)) {
              folderIds = parsed.filter(Boolean);
            }
          }
        } catch (error) {
          console.warn('[folders] Failed to parse folder list drag payload', error);
        }

        if (!folderIds.length) {
          let folderSourceId = draggedFolderId;
          if (!folderSourceId) {
            try {
              if (event.dataTransfer.types?.includes('application/x-papercrate-folder')) {
                folderSourceId = event.dataTransfer.getData('application/x-papercrate-folder');
              }
            } catch (error) {
              console.warn('[folders] Failed to read folder id from drag payload', error);
            }
          }

          if (folderSourceId) {
            folderIds = [folderSourceId];
          }
        }

        folderIds = Array.from(new Set(folderIds.filter(Boolean)));

        if (folderIds.length) {
          setDraggedFolderId(null);
          const invalidMove = folderIds.some((sourceId) => isInvalidFolderDrop(sourceId, folderId));
          if (invalidMove) {
            setStatusMessage(
              'Cannot move a folder into itself or one of its descendants.',
              'error',
            );
            return;
          }

          for (const sourceId of folderIds) {
            await moveFolder(sourceId, folderId);
          }
        }

        if (hasFiles(event)) {
          await handleFileDrop(event.dataTransfer, folderId);
          return;
        }

        let docIds: FolderId[] = [];
        try {
          const raw = event.dataTransfer.getData('application/x-papercrate-doc-list');
          if (raw) {
            const parsed = JSON.parse(raw);
            if (Array.isArray(parsed)) {
              docIds = parsed.filter(Boolean);
            }
          }
        } catch (error) {
          console.warn('[documents] Failed to parse document list drag payload', error);
        }

        if (!docIds.length) {
          try {
            const single = event.dataTransfer.getData('application/x-papercrate-doc');
            if (single) {
              docIds = [single];
            }
          } catch (error) {
            console.warn('[documents] Failed to read single document drag payload', error);
          }
        }

        if (!docIds.length && draggedDocumentIds.length) {
          docIds = draggedDocumentIds;
        }

        docIds = Array.from(new Set(docIds));

        if (!docIds.length || folderId === selectedFolder) {
          return;
        }

        setDraggedDocumentIds([]);
        await moveDocumentsToFolder(docIds, folderId);
      },
      onDragOver: (event: DragEvent<HTMLElement>, folderId: FolderKey) => {
        const folderDragActive = Boolean(draggedFolderId);
        if (folderDragActive && isInvalidFolderDrop(draggedFolderId, folderId)) {
          return;
        }

        if (hasFiles(event)) {
          event.preventDefault();
          event.dataTransfer.dropEffect = 'copy';
          event.currentTarget.classList.add('is-drop-target');
          return;
        }

        if (draggedDocumentIds.length || folderDragActive) {
          event.preventDefault();
          event.dataTransfer.dropEffect = 'move';
          event.currentTarget.classList.add('is-drop-target');
        }
      },
      onDragLeave: (event: DragEvent<HTMLElement>) => {
        event.currentTarget.classList.remove('is-drop-target');
      },
    }),
    [
      draggedDocumentIds,
      draggedFolderId,
      ensureFolderData,
      folderNodes,
      handleFileDrop,
      isInvalidFolderDrop,
      moveDocumentsToFolder,
      moveFolder,
      notifyApiError,
      selectFolder,
      selectedFolder,
      setDraggedDocumentIds,
      setDraggedFolderId,
      setFolderNodes,
      setStatusMessage,
    ],
  );

  return {
    loadFolder,
    selectFolder,
    handleFolderRename,
    handleFolderCreate,
    handleFolderDelete,
    folderClickHandlers,
  };
};

export default useFolderTreeActions;
