import { promises as fs } from 'fs';
import path from 'path';

export interface FileSystemAdapter {
  readdir(dirPath: string): Promise<any[]>;
  stat(filePath: string): Promise<{ isDirectory(): boolean }>;
  access(filePath: string): Promise<void>;
  readFile(filePath: string, encoding: string): Promise<string>;
  writeFile(filePath: string, content: string): Promise<void>;
}

export class NodeFileSystemAdapter implements FileSystemAdapter {
  async readdir(dirPath: string): Promise<any[]> {
    return await fs.readdir(dirPath, { withFileTypes: true });
  }

  async stat(filePath: string): Promise<{ isDirectory(): boolean }> {
    return await fs.stat(filePath);
  }

  async access(filePath: string): Promise<void> {
    await fs.access(filePath);
  }

  async readFile(filePath: string, encoding: string): Promise<string> {
    return await fs.readFile(filePath, { encoding: encoding as BufferEncoding });
  }

  async writeFile(filePath: string, content: string): Promise<void> {
    await fs.writeFile(filePath, content);
  }
}

export class InMemoryFileSystemAdapter implements FileSystemAdapter {
  private directories: Map<string, any[]> = new Map();
  private files: Map<string, string> = new Map();
  private dirStats: Set<string> = new Set();

  constructor() {
    // Initialize with root directory
    this.directories.set('/', []);
    this.dirStats.add('/');
  }

  async readdir(dirPath: string): Promise<any[]> {
    const normalizedPath = path.normalize(dirPath);
    const entries = this.directories.get(normalizedPath);
    
    if (!entries) {
      throw new Error(`ENOENT: no such file or directory, scandir '${dirPath}'`);
    }
    
    return entries;
  }

  async stat(filePath: string): Promise<{ isDirectory(): boolean }> {
    const normalizedPath = path.normalize(filePath);
    
    return {
      isDirectory: () => this.dirStats.has(normalizedPath)
    };
  }

  async access(filePath: string): Promise<void> {
    const normalizedPath = path.normalize(filePath);
    
    if (!this.files.has(normalizedPath) && !this.dirStats.has(normalizedPath)) {
      throw new Error(`ENOENT: no such file or directory, access '${filePath}'`);
    }
  }

  async readFile(filePath: string, encoding: string): Promise<string> {
    const normalizedPath = path.normalize(filePath);
    const content = this.files.get(normalizedPath);
    
    if (content === undefined) {
      throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
    }
    
    return content;
  }

  async writeFile(filePath: string, content: string): Promise<void> {
    const normalizedPath = path.normalize(filePath);
    this.files.set(normalizedPath, content);
  }

  // Test helper methods
  addDirectory(dirPath: string, entries: Array<{ name: string; isDirectory: boolean }>): void {
    const normalizedPath = path.normalize(dirPath);
    
    // Add the directory itself
    this.dirStats.add(normalizedPath);
    
    // Create mock directory entries
    const mockEntries = entries.map(entry => ({
      name: entry.name,
      isDirectory: () => entry.isDirectory
    }));
    
    this.directories.set(normalizedPath, mockEntries);
    
    // Add subdirectories to dirStats
    entries.forEach(entry => {
      if (entry.isDirectory) {
        const subPath = path.join(normalizedPath, entry.name);
        this.dirStats.add(subPath);
      }
    });
  }

  addGitDirectory(dirPath: string): void {
    const normalizedPath = path.normalize(dirPath);
    const gitPath = path.join(normalizedPath, '.git');
    
    // Mark the .git path as accessible
    this.dirStats.add(gitPath);
    
    // Also update parent directory entries if it exists
    const parentEntries = this.directories.get(normalizedPath);
    if (parentEntries) {
      const hasGit = parentEntries.some(e => e.name === '.git');
      if (!hasGit) {
        parentEntries.push({
          name: '.git',
          isDirectory: () => true
        });
      }
    }
  }

  clear(): void {
    this.directories.clear();
    this.files.clear();
    this.dirStats.clear();
    
    // Re-initialize with root
    this.directories.set('/', []);
    this.dirStats.add('/');
  }
}