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

import { File } from './classes.file.js';

export class MetaData {
  public static async hasMetaData(optionsArg: { file: File }) {
    // lets find the existing metadata file
    const existingFile = await optionsArg.file.parentDirectoryRef.getFile({
      path: optionsArg.file.name + '.metadata',
    });
    return !!existingFile;
  }

  // static
  public static async createForFile(optionsArg: { file: File }) {
    const metaData = new MetaData();
    metaData.fileRef = optionsArg.file;

    // lets find the existing metadata file
    metaData.metadataFile = await metaData.fileRef.parentDirectoryRef.getFileStrict({
      path: metaData.fileRef.name + '.metadata',
      createWithContents: '{}',
    });

    return metaData;
  }

  // instance
  /**
   * the file that contains the metadata
   */
  metadataFile!: File;

  /**
   * the file that the metadata is for
   */
  fileRef!: File;

  public async getFileType(optionsArg?: {
    useFileExtension?: boolean;
    useMagicBytes?: boolean;
  }): Promise<plugins.smartmime.IFileTypeResult | undefined> {
    if ((optionsArg && optionsArg.useFileExtension) || !optionsArg) {
      const fileType = await plugins.smartmime.detectMimeType({
        path: this.fileRef.name,
      });

      return fileType;
    }
    if (optionsArg && optionsArg.useMagicBytes) {
      const fileType = await plugins.smartmime.detectMimeType({
        buffer: await this.fileRef.getMagicBytes({
          length: 100,
        })
      });

      return fileType;
    }
    throw new Error('optionsArg.useFileExtension and optionsArg.useMagicBytes cannot both be false');
  }

  /**
   * gets the size of the fileRef
   */
  public async getSizeInBytes(): Promise<number> {
    const stat = await this.fileRef.parentDirectoryRef.bucketRef.fastStat({
      path: this.fileRef.getBasePath(),
    });
    return stat.ContentLength!;
  }

  private prefixCustomMetaData = 'custom_';

  public async storeCustomMetaData<T = any>(optionsArg: { key: string; value: T }) {
    const data = await this.metadataFile.getJsonData();
    data[this.prefixCustomMetaData + optionsArg.key] = optionsArg.value;
    await this.metadataFile.writeJsonData(data);
  }

  public async getCustomMetaData<T = any>(optionsArg: { key: string }): Promise<T> {
    const data = await this.metadataFile.getJsonData();
    return data[this.prefixCustomMetaData + optionsArg.key];
  }

  public async deleteCustomMetaData(optionsArg: { key: string }) {
    const data = await this.metadataFile.getJsonData();
    delete data[this.prefixCustomMetaData + optionsArg.key];
    await this.metadataFile.writeJsonData(data);
  }

  /**
   * set a lock on the ref file
   * @param optionsArg
   */
  public async setLock(optionsArg: { lock: string; expires: number }) {
    const data = await this.metadataFile.getJsonData();
    data.lock = optionsArg.lock;
    data.lockExpires = optionsArg.expires;
    await this.metadataFile.writeJsonData(data);
  }

  /**
   * remove the lock on the ref file
   * @param optionsArg
   */
  public async removeLock(optionsArg: { force: boolean }) {
    const data = await this.metadataFile.getJsonData();
    delete data.lock;
    delete data.lockExpires;
    await this.metadataFile.writeJsonData(data);
  }

  public async checkLocked(): Promise<boolean> {
    const data = await this.metadataFile.getJsonData();
    return data.lock && data.lockExpires > Date.now();
  }

  public async getLockInfo(): Promise<{ lock: string; expires: number }> {
    const data = await this.metadataFile.getJsonData();
    return { lock: data.lock, expires: data.lockExpires };
  }
}
