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

export interface FileSystemAdapter {
  mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void>;
  writeFile(filePath: string, data: string): Promise<void>;
  readFile(filePath: string, encoding: string): Promise<string>;
  readdir(dirPath: string): Promise<string[]>;
  unlink(filePath: string): Promise<void>;
  access(filePath: string): Promise<void>;
}

export class NodeFileSystemAdapter implements FileSystemAdapter {
  async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> {
    await fs.mkdir(dirPath, options);
  }

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

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

  async readdir(dirPath: string): Promise<string[]> {
    return await fs.readdir(dirPath);
  }

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

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

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

  async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> {
    if (options?.recursive) {
      // Handle both absolute and relative paths
      const normalizedPath = path.normalize(dirPath);
      const parts = normalizedPath.split(path.sep).filter(p => p);
      let currentPath = normalizedPath.startsWith(path.sep) ? path.sep : '';
      
      for (const part of parts) {
        currentPath = path.join(currentPath, part);
        this.directories.add(currentPath);
      }
    } else {
      this.directories.add(dirPath);
    }
  }

  async writeFile(filePath: string, data: string): Promise<void> {
    const dir = path.dirname(filePath);
    // Skip directory check for root or current directory
    if (dir !== '.' && dir !== '/' && dir !== '') {
      if (!this.directories.has(dir)) {
        throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
      }
    }
    this.files.set(filePath, data);
  }

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

  async readdir(dirPath: string): Promise<string[]> {
    if (!this.directories.has(dirPath)) {
      throw new Error(`ENOENT: no such file or directory, scandir '${dirPath}'`);
    }
    
    const files: string[] = [];
    for (const [filePath] of this.files) {
      if (path.dirname(filePath) === dirPath) {
        files.push(path.basename(filePath));
      }
    }
    return files;
  }

  async unlink(filePath: string): Promise<void> {
    if (!this.files.has(filePath)) {
      throw new Error(`ENOENT: no such file or directory, unlink '${filePath}'`);
    }
    this.files.delete(filePath);
  }

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

  // Test helper methods
  clear(): void {
    this.files.clear();
    this.directories.clear();
  }

  getFileCount(): number {
    return this.files.size;
  }

  hasFile(filePath: string): boolean {
    return this.files.has(filePath);
  }

  hasDirectory(dirPath: string): boolean {
    return this.directories.has(dirPath);
  }
}