import * as plugins from './plugins.js';

export interface ISmartfileConstructorOptions {
  path: string;
  contentBuffer: Buffer;
  base: string;
}

/**
 * an vinyl file compatible in memory file class
 * Use SmartFileFactory to create instances of this class
 */
export class SmartFile extends plugins.smartjson.Smartjson {
  // ========
  // INSTANCE
  // ========

  /**
   * Reference to the SmartFs instance for filesystem operations
   */
  private smartFs?: any;
  /**
   * the relative path of the file
   */
  @plugins.smartjson.foldDec()
  public accessor path: string = '';

  /**
   * a parsed path
   */
  public get parsedPath(): plugins.path.ParsedPath {
    return plugins.path.parse(this.path);
  }
  public get absolutePath() {
    return plugins.path.join(this.base, this.path);
  }

  public get absoluteParsedPath() {
    return plugins.path.parse(this.absolutePath);
  }

  /**
   * the content of the file as Buffer
   */
  @plugins.smartjson.foldDec()
  public accessor contentBuffer: Buffer = Buffer.alloc(0);

  /**
   * The current working directory of the file
   * Note:this is similar to gulp and different from native node path base
   */
  @plugins.smartjson.foldDec()
  public accessor base: string = process.cwd();

  /**
   * sync the file with disk
   */
  @plugins.smartjson.foldDec()
  public accessor sync: boolean = false;

  /**
   * the constructor of Smartfile
   * @param optionsArg
   * @param smartFs optional SmartFs instance for filesystem operations
   */

  constructor(
    optionsArg: ISmartfileConstructorOptions = {
      path: '',
      contentBuffer: Buffer.alloc(0),
      base: process.cwd(),
    },
    smartFs?: any,
  ) {
    super();
    this.contentBuffer = optionsArg.contentBuffer || Buffer.alloc(0);
    this.path = optionsArg.path;
    this.base = optionsArg.base;
    this.smartFs = smartFs;
  }

  /**
   * set contents from string
   * @param contentString
   */
  public setContentsFromString(
    contentString: string,
    encodingArg: 'utf8' | 'binary' = 'utf8',
  ) {
    this.contents = Buffer.from(contentString, encodingArg);
  }

  /**
   * write file to disk at its original location
   * Behaviours:
   * - no argument write to exactly where the file was picked up
   * - Requires SmartFs instance (create via SmartFileFactory)
   */
  public async write() {
    if (!this.smartFs) {
      throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
    }

    const writePath = plugins.smartpath.transform.makeAbsolute(
      this.path,
      this.base,
    );
    console.log(`writing to ${writePath}`);
    await this.smartFs.file(writePath).write(this.contentBuffer);
  }

  /**
   * writes the file to path given as argument
   * note: if the path is not absolute, takes process.cwd() as base
   * @param filePathArg
   */
  public async writeToDiskAtPath(filePathArg: string) {
    if (!this.smartFs) {
      throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
    }

    if (!plugins.path.isAbsolute(filePathArg)) {
      filePathArg = plugins.path.join(process.cwd(), filePathArg);
    }

    await this.smartFs.file(filePathArg).write(this.contentBuffer);
  }

  /**
   * writes the file to a directory combined with the relative path portion
   * @param dirPathArg
   * @returns
   */
  public async writeToDir(dirPathArg: string) {
    if (!this.smartFs) {
      throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
    }

    dirPathArg = plugins.smartpath.transform.toAbsolute(dirPathArg) as string;
    const filePath = plugins.path.join(dirPathArg, this.path);
    await this.smartFs.file(filePath).write(this.contentBuffer);
    return filePath;
  }

  /**
   * read file from disk
   */
  public async read() {
    if (!this.smartFs) {
      throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
    }

    const filePath = plugins.path.join(this.base, this.path);
    const content = await this.smartFs.file(filePath).read();
    this.contentBuffer = Buffer.from(content);
  }

  /**
   * deletes the file from disk at its original location
   */
  public async delete() {
    if (!this.smartFs) {
      throw new Error('No SmartFs instance available. Create SmartFile through SmartFileFactory.');
    }

    const filePath = plugins.path.join(this.base, this.path);
    await this.smartFs.file(filePath).delete();
  }

  /**
   * Renames the file to the specified new name.
   * - Updates the `path` property with the new name.
   * - Writes the file to the new location if it exists on disk.
   * @param newName The new name of the file (including extension if applicable).
   * @param writeToDisk (optional) If true, also renames the file on the disk.
   * @returns The updated file path after renaming.
   */
  public async rename(
    newName: string,
    writeToDisk: boolean = false,
  ): Promise<string> {
    // Validate the new name
    if (!newName || typeof newName !== 'string') {
      throw new Error('Invalid new name provided.');
    }

    // Extract the directory path
    const oldFilePath = this.path;
    const dirPath = plugins.path.dirname(this.path);

    // Create the new file path
    const newFilePath = plugins.path.join(dirPath, newName);

    // Update the `path` property
    this.path = newFilePath;

    // Optionally write the renamed file to disk
    if (writeToDisk) {
      const oldAbsolutePath = plugins.smartpath.transform.makeAbsolute(
        oldFilePath,
        this.base,
      );
      const newAbsolutePath = plugins.smartpath.transform.makeAbsolute(
        newFilePath,
        this.base,
      );

      // Rename the file on disk
      await plugins.fsPromises.rename(oldAbsolutePath, newAbsolutePath);
    }

    // Return the new path
    return this.path;
  }

  // -----------------------------------------------
  // vinyl compatibility
  // -----------------------------------------------
  /**
   * vinyl-compatibility: alias of this.contentBuffer
   */
  get contents(): Buffer {
    return this.contentBuffer;
  }
  set contents(contentsArg) {
    this.contentBuffer = contentsArg;
  }

  /**
   * vinyl-compatibility
   */
  public get cwd() {
    return process.cwd();
  }

  /**
   * return relative path of file
   */
  public get relative(): string {
    return this.path;
  }

  /**
   * return truw when the file has content
   */
  public isNull(): boolean {
    if (!this.contentBuffer) {
      return true;
    }
    return false;
  }

  /**
   * return true if contents are Buffer
   */
  public isBuffer(): boolean {
    if (this.contents instanceof Buffer) {
      return true;
    }
    return false;
  }

  public isDirectory() {
    return false;
  }

  public isStream() {
    return false;
  }

  public isSymbolic() {
    return false;
  }

  public async getHash(typeArg: 'path' | 'content' | 'all' = 'all') {
    const pathHash = await plugins.smarthash.sha256FromString(this.path);
    const contentHash = await plugins.smarthash.sha256FromBuffer(
      this.contentBuffer,
    );
    const combinedHash = await plugins.smarthash.sha256FromString(
      pathHash + contentHash,
    );
    switch (typeArg) {
      case 'path':
        return pathHash;
      case 'content':
        return contentHash;
      case 'all':
      default:
        return combinedHash;
    }
  }

  // update things
  public updateFileName(fileNameArg: string) {
    const oldFileName = this.parsedPath.base;
    this.path = this.path.replace(new RegExp(oldFileName + '$'), fileNameArg);
  }

  public async editContentAsString(
    editFuncArg: (fileStringArg: string) => Promise<string>,
  ) {
    const newFileString = await editFuncArg(this.contentBuffer.toString());
    this.contentBuffer = Buffer.from(newFileString);
  }

  /**
   * Returns a ReadableStream from the file's content buffer
   */
  public getStream(): plugins.stream.Readable {
    const stream = new plugins.stream.Readable();
    stream.push(this.contentBuffer); // Push the content buffer to the stream
    stream.push(null); // Push null to signify the end of the stream (EOF)
    return stream;
  }

  /**
   * Returns the size of the file in bytes
   */
  public async getSize(): Promise<number> {
    return this.contentBuffer.length;
  }

  /**
   * Parse content as string with specified encoding
   */
  public parseContentAsString(encodingArg: BufferEncoding = 'utf8'): string {
    return this.contentBuffer.toString(encodingArg);
  }

  /**
   * Parse content as buffer
   */
  public parseContentAsBuffer(): Buffer {
    return this.contentBuffer;
  }
}
