import { EventTypes } from '../../events/EventTypes.js'
import { PlatformBindings } from './PlatformBindings.js'
// import { EventTypes } from '../../events/EventTypes.js';

export interface IFileFilter {
  /**
   * An optional description of the category of files types allowed.
   *
   * @platform web
   * @platform electron
   */
  description?: string
  /**
   * An Object with the keys set to the MIME type and the values an Array of file extensions.
   *
   * ```
   * "description": "Images",
   * "accept": {
   *  'image/*': ['.png', '.gif', '.jpeg', '.jpg']
   * }
   * ```
   *
   * @platform web
   * @platform electron
   */
  accept: Record<string, string[]>
}

export interface ISaveFileDialogInit {
  /**
   * Only when the platform supports it.
   * The dialog title. Cannot be displayed on some _Linux_ desktop environments.
   */
  title?: string
  /**
   * Only when the platform supports it.
   * Absolute directory path, absolute file path, or file name to use by default.
   */
  defaultPath?: string

  /**
   * The list of file filters to apply to the dialog.
   */
  filters?: IFileFilter[]

  /**
   * Default false. By default the picker should include an option to not apply any file type filters.
   * Setting this option to true means that option is not available.
   *
   * @platform web
   */
  excludeAcceptAllOption?: boolean
}

export interface ISaveFileDialogResult {
  /**
   * Whether or not the dialog was canceled.
   */
  canceled: boolean

  /**
   * If the dialog is canceled, this will be `undefined`.
   *
   * In **electron** this is the path to the selected file.
   * In the **web** platform this is autogenerated id to the file handle
   * the app should use with the further communication.
   */
  filePath?: string
}

export interface IOpenFileDialogInit {
  /**
   * Only when the platform supports it.
   * The dialog title. Cannot be displayed on some _Linux_ desktop environments.
   */
  title?: string
  /**
   * Only when the platform supports it.
   * Absolute directory path, absolute file path, or file name to use by default.
   */
  defaultPath?: string
  /**
   * The list of file filters to apply to the dialog.
   */
  filters?: IFileFilter[]

  /**
   * Default false. By default the picker should include an option to not apply any file type filters.
   * Setting this option to true means that option is not available.
   *
   * @platform web
   */
  excludeAcceptAllOption?: boolean

  /**
   * When set to true multiple files may be selected.
   * @platform web
   */
  multiple?: boolean
}

export interface IOpenFileDialogResult {
  /**
   * Whether or not the dialog was canceled.
   */
  canceled: boolean

  /**
   * If the dialog is cancelled this will be an empty array.
   *
   * In **electron** this is an array of file paths chosen by the user.
   * In the **web** platform this is autogenerated list of ids to the file handles
   * the app should use with the further communication.
   */
  filePath?: string[]
}

interface ReadWriteOptions {
  /**
   * Whether the `path` will no longer be used.
   * This is relevant for the `web` environment when the bindings keep a reference to a file entry.
   * This should always be set to true when the read/write operation is done only once.
   */
  dispose?: boolean
}

export interface FileReadOptions extends ReadWriteOptions {
  /**
   * The type of the return value. It can be either an instance of a File or a string.
   *
   * @default string
   */
  returnType?: 'file' | 'string'
}

export interface FileWriteOptions extends ReadWriteOptions {
  /**
   * File encoding.
   */
  encoding?: 'utf8'
}

/**
 * The base class for file access bindings.
 */
export abstract class FileBindings extends PlatformBindings {
  async initialize(): Promise<void> {
    window.addEventListener(EventTypes.Filesystem.requestSaveFile, this.handleRequestSaveFile.bind(this))
    window.addEventListener(EventTypes.Filesystem.requestOpenFile, this.handleRequestOpenFile.bind(this))
    window.addEventListener(EventTypes.Filesystem.writeFile, this.handleWriteFile.bind(this))
    window.addEventListener(EventTypes.Filesystem.readFile, this.handleReadFile.bind(this))
  }

  protected handleRequestSaveFile(e: Event): void {
    const event = e as CustomEvent<{ options?: ISaveFileDialogInit; result: Promise<ISaveFileDialogResult> }>
    event.detail.result = this.saveFileDialog(event.detail.options)
  }

  protected handleRequestOpenFile(e: Event): void {
    const event = e as CustomEvent<{ options?: IOpenFileDialogInit; result: Promise<IOpenFileDialogResult> }>
    event.detail.result = this.openFileDialog(event.detail.options)
  }

  protected handleWriteFile(e: Event): void {
    const event = e as CustomEvent<{
      path: string
      contents: BufferSource | Blob | string
      options?: FileWriteOptions
      result: Promise<void>
    }>
    event.detail.result = this.writeFile(event.detail.path, event.detail.contents, event.detail.options)
  }

  protected handleReadFile(e: Event): void {
    const event = e as CustomEvent<{
      path: string
      options?: FileReadOptions
      result: Promise<ArrayBuffer | string>
    }>
    event.detail.result = this.readFile(event.detail.path, event.detail.options)
  }

  /**
   * Opens a dialog to pick a file to save.
   *
   * @param opts The dialog configuration.
   */
  abstract saveFileDialog(opts?: ISaveFileDialogInit): Promise<ISaveFileDialogResult>

  /**
   * Opens a dialog to pick a file for reading.
   *
   * @param opts The dialog configuration.
   */
  abstract openFileDialog(opts?: IOpenFileDialogInit): Promise<IOpenFileDialogResult>

  /**
   * Writes contents to the file.
   *
   * @param path The path returned by the `saveFileDialog()`.
   * @param contents The contents to write to the file.
   */
  abstract writeFile(path: string, contents: BufferSource | Blob | string, opts?: FileWriteOptions): Promise<void>

  /**
   * Reads file data.
   * @param path The path returned by the `openFileDialog()`.
   * @param returnType The type of the return value. It can be either a buffer or a string.
   */
  abstract readFile(path: string, opts?: FileReadOptions): Promise<ArrayBuffer | string>

  /**
   * Disposes of previously created path with the `saveFileDialog()` or `openFileDialog()`.
   * This is mandatory to call when during reading or writing to a file the `dispose` option
   * is not set to true, after the file is no longer in use.
   *
   * @param path The previously created path.
   */
  abstract dispose(path: string): Promise<void>
}
