import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle } from 'react';
import { socketClient } from '../lib/socket';
import { WorkspaceMode } from '../lib/types';
import FileTreeNode from './FileTreeNode';

export interface FileTreeEntry {
  name: string;
  path: string;
  type: 'file' | 'directory' | 'symlink';
  size?: number;
  modified?: string;
  isHidden?: boolean;
}

export interface FileTreeHandle {
  refresh: () => void;
}

interface FileTreeProps {
  workspaceMode: WorkspaceMode;
  onFileSelect?: (path: string) => void;
}

interface FileTreeState {
  directoryCache: Map<string, FileTreeEntry[]>;
  expandedPaths: Set<string>;
  loadingPaths: Set<string>;
  errors: Map<string, string>;
}

const FileTree = forwardRef<FileTreeHandle, FileTreeProps>(function FileTree({ workspaceMode, onFileSelect }, ref) {
  const [state, setState] = useState<FileTreeState>({
    directoryCache: new Map(),
    expandedPaths: new Set(),
    loadingPaths: new Set(),
    errors: new Map(),
  });

  const [rootLoaded, setRootLoaded] = useState(false);

  // Use refs to hold the latest state so loadDirectory always reads current values
  // without needing to be in its dependency array (which causes re-creation loops)
  const stateRef = useRef(state);
  stateRef.current = state;

  const loadDirectory = useCallback(async (path: string) => {
    const currentState = stateRef.current;

    // Don't reload if already cached
    if (currentState.directoryCache.has(path)) {
      return;
    }

    // Don't reload if already loading
    if (currentState.loadingPaths.has(path)) {
      return;
    }

    setState(prev => {
      const newLoading = new Set(prev.loadingPaths);
      newLoading.add(path);
      const newErrors = new Map(prev.errors);
      newErrors.delete(path);
      return {
        ...prev,
        loadingPaths: newLoading,
        errors: newErrors,
      };
    });

    try {
      const result = await socketClient.requestFileTree({
        path: path || undefined,
        recursive: false,
        maxDepth: 1,
      });

      if (result.success && result.entries) {
        setState(prev => {
          const newCache = new Map(prev.directoryCache);
          newCache.set(path, result.entries!);
          const newLoading = new Set(prev.loadingPaths);
          newLoading.delete(path);
          return {
            ...prev,
            directoryCache: newCache,
            loadingPaths: newLoading,
          };
        });
      } else {
        setState(prev => {
          const newErrors = new Map(prev.errors);
          newErrors.set(path, result.error || 'Failed to load directory');
          const newLoading = new Set(prev.loadingPaths);
          newLoading.delete(path);
          return {
            ...prev,
            errors: newErrors,
            loadingPaths: newLoading,
          };
        });
      }
    } catch (error) {
      setState(prev => {
        const newErrors = new Map(prev.errors);
        newErrors.set(path, error instanceof Error ? error.message : 'Unknown error');
        const newLoading = new Set(prev.loadingPaths);
        newLoading.delete(path);
        return {
          ...prev,
          errors: newErrors,
          loadingPaths: newLoading,
        };
      });
    }
  }, []);

  // Load root directory on mount
  useEffect(() => {
    if (!rootLoaded) {
      loadDirectory('');
      setRootLoaded(true);
    }
  }, [rootLoaded, loadDirectory]);

  // Expose refresh method to parent via ref
  useImperativeHandle(ref, () => ({
    refresh: () => {
      setState({
        directoryCache: new Map(),
        expandedPaths: new Set(),
        loadingPaths: new Set(),
        errors: new Map(),
      });
      setRootLoaded(false);
    },
  }), []);

  const toggleExpand = useCallback((path: string) => {
    setState(prev => {
      const newExpanded = new Set(prev.expandedPaths);
      if (newExpanded.has(path)) {
        newExpanded.delete(path);
      } else {
        newExpanded.add(path);
        // Load children if not already cached
        if (!prev.directoryCache.has(path)) {
          setTimeout(() => loadDirectory(path), 0);
        }
      }
      return { ...prev, expandedPaths: newExpanded };
    });
  }, [loadDirectory]);

  const handleFileSelect = useCallback((path: string) => {
    if (onFileSelect) {
      onFileSelect(path);
    }
  }, [onFileSelect]);

  // Build visible nodes list based on expanded state
  const buildVisibleNodes = useCallback(() => {
    const visibleNodes: Array<{ entry: FileTreeEntry; depth: number }> = [];
    
    const processNode = (entry: FileTreeEntry, depth: number) => {
      visibleNodes.push({ entry, depth });
      
      // If this is an expanded directory with cached children, process them
      if (entry.type === 'directory' && 
          state.expandedPaths.has(entry.path) && 
          state.directoryCache.has(entry.path)) {
        const children = state.directoryCache.get(entry.path)!;
        for (const child of children) {
          processNode(child, depth + 1);
        }
      }
    };
    
    // Process root entries
    const rootEntries = state.directoryCache.get('') || [];
    for (const entry of rootEntries) {
      processNode(entry, 0);
    }
    
    return visibleNodes;
  }, [state.expandedPaths, state.directoryCache]);

  const rootEntries = state.directoryCache.get('') || [];
  const rootError = state.errors.get('');
  const rootLoading = state.loadingPaths.has('');

  return (
    <div className="p-2">
      {rootLoading && (
        <div className="flex items-center justify-center py-8">
          <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-brand"></div>
        </div>
      )}
      
      {rootError && (
        <div className="p-4 text-center text-red-500 text-sm">
          <p>{rootError}</p>
          <button
            onClick={() => loadDirectory('')}
            className="mt-2 px-3 py-1 bg-brand text-white rounded text-xs hover:bg-brand/80"
          >
            Retry
          </button>
        </div>
      )}

      {!rootLoading && !rootError && rootEntries.length === 0 && (
        <div className="p-4 text-center text-text-muted text-sm">
          Empty directory
        </div>
      )}

      <div className="space-y-0.5">
        {buildVisibleNodes().map(({ entry, depth }) => {
          const isExpanded = state.expandedPaths.has(entry.path);
          const isLoading = state.loadingPaths.has(entry.path);
          const error = state.errors.get(entry.path);
          const hasChildren = entry.type === 'directory';

          return (
            <FileTreeNode
              key={entry.path}
              entry={entry}
              depth={depth}
              isExpanded={isExpanded}
              isLoading={isLoading}
              hasChildren={hasChildren}
              error={error}
              onToggle={toggleExpand}
              onSelect={handleFileSelect}
            />
          );
        })}
      </div>
    </div>
  );
});

export default FileTree;
