import * as plugins from './plugins.js';
import { SmartFile } from './classes.smartfile.js';
import { StreamFile } from './classes.streamfile.js';
import { VirtualDirectory } from './classes.virtualdirectory.js';

export class SmartFileFactory {
  private smartFs: any; // Will be typed as SmartFs once we import from @push.rocks/smartfs

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

  /**
   * Creates a default factory using Node.js filesystem provider
   */
  public static nodeFs(): SmartFileFactory {
    const smartFs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
    return new SmartFileFactory(smartFs);
  }

  /**
   * Get the underlying SmartFs instance
   */
  public getSmartFs(): any {
    return this.smartFs;
  }

  // ============================================
  // SmartFile Factory Methods
  // ============================================

  /**
   * Creates a SmartFile from a file path on disk
   */
  public async fromFilePath(
    filePath: string,
    baseArg: string = process.cwd()
  ): Promise<SmartFile> {
    if (!this.smartFs) {
      throw new Error('No SmartFs instance available. Cannot read from filesystem without SmartFs.');
    }

    filePath = plugins.path.resolve(filePath);
    const content = await this.smartFs.file(filePath).read();
    const fileBuffer = Buffer.from(content);

    return new SmartFile({
      contentBuffer: fileBuffer,
      base: baseArg,
      path: plugins.path.relative(baseArg, filePath),
    }, this.smartFs);
  }

  /**
   * Creates a SmartFile from a URL
   */
  public async fromUrl(urlArg: string): Promise<SmartFile> {
    const response = await plugins.smartrequest.SmartRequest.create()
      .url(urlArg)
      .accept('binary')
      .get();
    const buffer = Buffer.from(await response.arrayBuffer());

    return new SmartFile({
      contentBuffer: buffer,
      base: process.cwd(),
      path: urlArg,
    }, this.smartFs);
  }

  /**
   * Creates a SmartFile from a Buffer
   */
  public fromBuffer(
    filePath: string,
    contentBufferArg: Buffer,
    baseArg: string = process.cwd()
  ): SmartFile {
    // Use filePath as-is if it's already relative, otherwise compute relative path
    const relativePath = plugins.path.isAbsolute(filePath)
      ? plugins.path.relative(baseArg, filePath)
      : filePath;

    return new SmartFile({
      contentBuffer: contentBufferArg,
      base: baseArg,
      path: relativePath,
    }, this.smartFs);
  }

  /**
   * Creates a SmartFile from a string
   */
  public fromString(
    filePath: string,
    contentStringArg: string,
    encodingArg: 'utf8' | 'binary' = 'utf8',
    baseArg: string = process.cwd()
  ): SmartFile {
    // Use filePath as-is if it's already relative, otherwise compute relative path
    const relativePath = plugins.path.isAbsolute(filePath)
      ? plugins.path.relative(baseArg, filePath)
      : filePath;

    return new SmartFile({
      contentBuffer: Buffer.from(contentStringArg, encodingArg),
      base: baseArg,
      path: relativePath,
    }, this.smartFs);
  }

  /**
   * Creates a SmartFile from a stream
   */
  public async fromStream(
    stream: plugins.stream.Readable,
    filePath: string,
    baseArg: string = process.cwd()
  ): Promise<SmartFile> {
    return new Promise<SmartFile>((resolve, reject) => {
      const chunks: Buffer[] = [];
      stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
      stream.on('error', (error) => reject(error));
      stream.on('end', () => {
        const contentBuffer = Buffer.concat(chunks);
        const smartfile = new SmartFile({
          contentBuffer: contentBuffer,
          base: baseArg,
          path: plugins.path.relative(baseArg, filePath),
        }, this.smartFs);
        resolve(smartfile);
      });
    });
  }

  /**
   * Creates a SmartFile from folded JSON
   */
  public async fromFoldedJson(foldedJsonArg: string): Promise<SmartFile> {
    const parsed = plugins.smartjson.parse(foldedJsonArg);
    return new SmartFile(parsed, this.smartFs);
  }

  // ============================================
  // StreamFile Factory Methods
  // ============================================

  /**
   * Creates a StreamFile from a file path
   */
  public async streamFromPath(filePath: string): Promise<StreamFile> {
    return StreamFile.fromPath(filePath, this.smartFs);
  }

  /**
   * Creates a StreamFile from a URL
   */
  public async streamFromUrl(url: string): Promise<StreamFile> {
    return StreamFile.fromUrl(url, this.smartFs);
  }

  /**
   * Creates a StreamFile from a Buffer
   */
  public streamFromBuffer(buffer: Buffer, relativeFilePath?: string): StreamFile {
    return StreamFile.fromBuffer(buffer, relativeFilePath, this.smartFs);
  }

  /**
   * Creates a StreamFile from a Node.js Readable stream
   */
  public streamFromStream(
    stream: plugins.stream.Readable,
    relativeFilePath?: string,
    multiUse: boolean = false
  ): StreamFile {
    return StreamFile.fromStream(stream, relativeFilePath, multiUse, this.smartFs);
  }

  // ============================================
  // VirtualDirectory Factory Methods
  // ============================================

  /**
   * Creates a VirtualDirectory from a filesystem directory path
   */
  public async virtualDirectoryFromPath(pathArg: string): Promise<VirtualDirectory> {
    return VirtualDirectory.fromFsDirPath(pathArg, this.smartFs, this);
  }

  /**
   * Creates an empty VirtualDirectory
   */
  public virtualDirectoryEmpty(): VirtualDirectory {
    return new VirtualDirectory(this.smartFs, this);
  }

  /**
   * Creates a VirtualDirectory from an array of SmartFiles
   */
  public virtualDirectoryFromFileArray(files: SmartFile[]): VirtualDirectory {
    const vdir = new VirtualDirectory(this.smartFs, this);
    vdir.addSmartfiles(files);
    return vdir;
  }

  /**
   * Creates a VirtualDirectory from a transferable object
   */
  public async virtualDirectoryFromTransferable(
    virtualDirTransferableObjectArg: plugins.smartfileInterfaces.VirtualDirTransferableObject
  ): Promise<VirtualDirectory> {
    const newVirtualDir = new VirtualDirectory(this.smartFs, this);
    for (const fileArg of virtualDirTransferableObjectArg.files) {
      const smartFile = SmartFile.enfoldFromJson(fileArg) as SmartFile;
      // Update the smartFs reference
      (smartFile as any).smartFs = this.smartFs;
      newVirtualDir.addSmartfiles([smartFile]);
    }
    return newVirtualDir;
  }
}
