import { SmartFile } from './classes.smartfile.js';
import * as plugins from './plugins.js';

export interface IVirtualDirectoryConstructorOptions {
  mode: '';
}

/**
 * a virtual directory exposes a fs api
 * Use SmartFileFactory to create instances of this class
 */
export class VirtualDirectory {
  // STATIC
  public static async fromFsDirPath(
    pathArg: string,
    smartFs?: any,
    factory?: any
  ): Promise<VirtualDirectory> {
    if (!smartFs || !factory) {
      throw new Error('No SmartFs/Factory instance available. Create VirtualDirectory through SmartFileFactory.');
    }

    const newVirtualDir = new VirtualDirectory(smartFs, factory);

    // Use smartFs to list directory and factory to create SmartFiles
    const entries = await smartFs.directory(pathArg).recursive().list();
    const smartfiles = await Promise.all(
      entries
        .filter((entry: any) => entry.isFile)
        .map((entry: any) => factory.fromFilePath(entry.path, pathArg))
    );
    newVirtualDir.addSmartfiles(smartfiles);

    return newVirtualDir;
  }

  public static async fromVirtualDirTransferableObject(
    virtualDirTransferableObjectArg: plugins.smartfileInterfaces.VirtualDirTransferableObject,
    smartFs?: any,
    factory?: any
  ): Promise<VirtualDirectory> {
    const newVirtualDir = new VirtualDirectory(smartFs, factory);
    for (const fileArg of virtualDirTransferableObjectArg.files) {
      const smartFile = SmartFile.enfoldFromJson(fileArg) as SmartFile;
      // Update smartFs reference if available
      if (smartFs) {
        (smartFile as any).smartFs = smartFs;
      }
      newVirtualDir.addSmartfiles([smartFile]);
    }
    return newVirtualDir;
  }

  public static fromFileArray(files: SmartFile[], smartFs?: any, factory?: any): VirtualDirectory {
    const vdir = new VirtualDirectory(smartFs, factory);
    vdir.addSmartfiles(files);
    return vdir;
  }

  public static empty(smartFs?: any, factory?: any): VirtualDirectory {
    return new VirtualDirectory(smartFs, factory);
  }

  // INSTANCE
  public smartfileArray: SmartFile[] = [];
  private smartFs?: any;
  private factory?: any;

  constructor(smartFs?: any, factory?: any) {
    this.smartFs = smartFs;
    this.factory = factory;
  }

  // ============================================
  // Collection Mutations
  // ============================================

  public addSmartfiles(smartfileArrayArg: SmartFile[]) {
    this.smartfileArray = this.smartfileArray.concat(smartfileArrayArg);
  }

  public addSmartfile(smartfileArg: SmartFile): void {
    this.smartfileArray.push(smartfileArg);
  }

  public removeByPath(pathArg: string): boolean {
    const initialLength = this.smartfileArray.length;
    this.smartfileArray = this.smartfileArray.filter(f => f.path !== pathArg);
    return this.smartfileArray.length < initialLength;
  }

  public clear(): void {
    this.smartfileArray = [];
  }

  public merge(otherVDir: VirtualDirectory): void {
    this.addSmartfiles(otherVDir.smartfileArray);
  }

  // ============================================
  // Collection Queries
  // ============================================

  public exists(pathArg: string): boolean {
    return this.smartfileArray.some(f => f.path === pathArg);
  }

  public has(pathArg: string): boolean {
    return this.exists(pathArg);
  }

  public async getFileByPath(pathArg: string): Promise<SmartFile | undefined> {
    return this.smartfileArray.find(f => f.path === pathArg);
  }

  public listFiles(): SmartFile[] {
    return [...this.smartfileArray];
  }

  public listDirectories(): string[] {
    const dirs = new Set<string>();
    for (const file of this.smartfileArray) {
      const dir = plugins.path.dirname(file.path);
      if (dir !== '.') {
        dirs.add(dir);
      }
    }
    return Array.from(dirs).sort();
  }

  public filter(predicate: (file: SmartFile) => boolean): VirtualDirectory {
    const newVDir = new VirtualDirectory(this.smartFs, this.factory);
    newVDir.addSmartfiles(this.smartfileArray.filter(predicate));
    return newVDir;
  }

  public map(fn: (file: SmartFile) => SmartFile): VirtualDirectory {
    const newVDir = new VirtualDirectory(this.smartFs, this.factory);
    newVDir.addSmartfiles(this.smartfileArray.map(fn));
    return newVDir;
  }

  public find(predicate: (file: SmartFile) => boolean): SmartFile | undefined {
    return this.smartfileArray.find(predicate);
  }

  public size(): number {
    return this.smartfileArray.length;
  }

  public isEmpty(): boolean {
    return this.smartfileArray.length === 0;
  }

  public async toVirtualDirTransferableObject(): Promise<plugins.smartfileInterfaces.VirtualDirTransferableObject> {
    return {
      files: this.smartfileArray.map((smartfileArg) =>
        smartfileArg.foldToJson(),
      ),
    };
  }

  public async saveToDisk(dirArg: string) {
    console.log(`writing VirtualDirectory with ${this.smartfileArray.length} files to directory:
    --> ${dirArg}`);
    for (const smartfileArg of this.smartfileArray) {
      const filePath = await smartfileArg.writeToDir(dirArg);
      console.log(`wrote ${smartfileArg.relative} to
        --> ${filePath}`);
    }
  }

  public async shiftToSubdirectory(subDir: string): Promise<VirtualDirectory> {
    const newVirtualDir = new VirtualDirectory(this.smartFs, this.factory);
    for (const file of this.smartfileArray) {
      if (file.path.startsWith(subDir)) {
        const adjustedFilePath = plugins.path.relative(subDir, file.path);
        file.path = adjustedFilePath;
        newVirtualDir.addSmartfiles([file]);
      }
    }
    return newVirtualDir;
  }

  public async loadFromDisk(dirArg: string): Promise<void> {
    // Load from disk, replacing current collection
    this.clear();
    const loaded = await VirtualDirectory.fromFsDirPath(dirArg, this.smartFs, this.factory);
    this.addSmartfiles(loaded.smartfileArray);
  }

  public async addVirtualDirectory(
    virtualDir: VirtualDirectory,
    newRoot: string,
  ): Promise<void> {
    for (const file of virtualDir.smartfileArray) {
      file.path = plugins.path.join(newRoot, file.path);
    }
    this.addSmartfiles(virtualDir.smartfileArray);
  }
}
