/* eslint-disable no-redeclare */
import type { Operation } from '@api-client/json/types.js'
import { EventTypes } from './EventTypes.js'
import type { FileBreadcrumb, IFile } from '@api-client/core/models/store/File.js'
import type { IApplication } from '@api-client/core/models/Application.js'
import type { ContextListOptions, ContextListResult, IPatchRevision } from '@api-client/core/events/BaseEvents.js'
import { type IFileCreateOptions, ListFileKind } from '@api-client/core/sdk/FilesSdk.js'
import type { IUser } from '@api-client/core/models/store/User.js'
import type { AccessOperation } from '@api-client/core/models/store/Permission.js'

/**
 * Reads file metadata from the store.
 *
 * @param key The file key
 * @returns THe file metadata
 */
function fileRead(key: string, media: false, target?: EventTarget): Promise<IFile>
/**
 * Reads file contents from the store.
 *
 * @param key The file key
 * @returns THe file contents
 */
function fileRead(key: string, media: true, target?: EventTarget): Promise<unknown>

/**
 * Reads a user file definition from the store.
 * @param key The file key
 * @param media When true it reads file contents rather than metadata.
 */
function fileRead(key: string, media: boolean, target: EventTarget = document.body): Promise<IFile | unknown> {
  const e = new CustomEvent(EventTypes.Store.File.read, {
    bubbles: true,
    cancelable: true,
    composed: true,
    detail: {
      key,
      media,
      result: undefined,
    },
  })
  target.dispatchEvent(e)
  return e.detail.result as unknown as Promise<IFile | unknown>
}

/**
 * Patches file's meta in the store.
 *
 * @param key The file key
 * @param value The patch to apply.
 */
function patchFile(
  key: string,
  id: string,
  value: Operation[],
  app: IApplication,
  media: false,
  target?: EventTarget
): Promise<IPatchRevision>

/**
 * Patches file's content in the store.
 *
 * @param key The file key
 * @param value The patch to apply.
 */
function patchFile(
  key: string,
  id: string,
  value: Operation[],
  app: IApplication,
  // eslint-disable-next-line @typescript-eslint/unified-signatures
  media: true,
  target?: EventTarget
): Promise<IPatchRevision>

/**
 * Patches a file in the store.
 * @param key The key of the file to patch
 * @param value The JSON patch to be processed.
 * @returns The JSON patch to revert the change using the `@api-client/json` library
 */
function patchFile(
  key: string,
  id: string,
  value: Operation[],
  app: IApplication,
  media?: boolean,
  target: EventTarget = document.body
): Promise<IPatchRevision> {
  const e = new CustomEvent(EventTypes.Store.File.patch, {
    bubbles: true,
    cancelable: true,
    composed: true,
    detail: {
      key,
      value,
      media,
      id,
      app,
      result: undefined,
    },
  })
  target.dispatchEvent(e)
  return e.detail.result as unknown as Promise<IPatchRevision>
}

export default Object.freeze({
  File: Object.freeze({
    /**
     * Lists files (spaces, projects, etc) in the store.
     *
     * @param kinds the list of kinds to list. Spaces are always included.
     * @param options Optional query options.
     */
    list: async (
      kinds?: ListFileKind[],
      options?: ContextListOptions,
      target: EventTarget = document.body
    ): Promise<ContextListResult<IFile>> => {
      const e = new CustomEvent(EventTypes.Store.File.list, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          kinds,
          options,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<ContextListResult<IFile>>
    },
    /**
     * Lists files that are shared with the user.
     *
     * @param kinds the list of kinds to list. Spaces are always included.
     * @param options Optional query options.
     */
    listShared: async (
      kinds?: ListFileKind[],
      options?: ContextListOptions,
      target: EventTarget = document.body
    ): Promise<ContextListResult<IFile>> => {
      const e = new CustomEvent(EventTypes.Store.File.listShared, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          kinds,
          options,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<ContextListResult<IFile>>
    },
    /**
     * Creates a file in the store.
     *
     * @param meta The definition of a file that extends the IFile interface.
     * @param media The file content to create with the file meta, if available.
     * @param opts Optional options when creating a file
     * @returns The key of the creates file.
     */
    create: async (
      meta: IFile,
      media?: unknown,
      opts?: IFileCreateOptions,
      target: EventTarget = document.body
    ): Promise<string> => {
      const e = new CustomEvent(EventTypes.Store.File.create, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          meta,
          media,
          opts,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<string>
    },
    /**
     * A special case of a create file flow.
     * It allow to create a file only from a name and its kind.
     * The store has logic to recognize the file from its `kind` and create a file.
     *
     * This is helpful when creating a new file from the "create" dialog.
     *
     * @param name The name provided by the user.
     * @param kind File kind.
     * @param opts Optional options when creating a file
     * @returns The key of the creates file.
     */
    createDefault: async (
      name: string,
      kind: string,
      opts?: IFileCreateOptions,
      target: EventTarget = document.body
    ): Promise<string> => {
      const e = new CustomEvent(EventTypes.Store.File.createDefault, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          name,
          kind,
          opts,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<string>
    },
    read: fileRead,
    patch: patchFile,
    /**
     * Deletes the file in the store.
     *
     * @param key The key of the file to delete.
     */
    delete: async (key: string, target: EventTarget = document.body): Promise<string> => {
      const e = new CustomEvent(EventTypes.Store.File.delete, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<string>
    },
    /**
     * Updates the sharing options of the file.
     *
     * @param key The file key
     * @param id The generated by the client random id. This id is reported back by the server.
     * @param value The patch operation on the file's ACL
     */
    patchUsers: async (
      key: string,
      id: string,
      value: AccessOperation[],
      app: IApplication,
      target: EventTarget = document.body
    ): Promise<void> => {
      const e = new CustomEvent(EventTypes.Store.File.patchUsers, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key,
          value,
          id,
          app,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      await (e.detail.result as unknown as Promise<void>)
    },
    /**
     * Lists the users that have direct permission to the file.
     *
     * @param key The file key
     */
    listUsers: async (key: string, target: EventTarget = document.body): Promise<ContextListResult<IUser>> => {
      const e = new CustomEvent(EventTypes.Store.File.listUsers, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<ContextListResult<IUser>>
    },

    /**
     * Lists breadcrumbs to the file.
     *
     * @param fileOrFolder The if of the file or folder to query for.
     * @param options Optional query options.
     */
    breadcrumbs: async (
      fileOrFolder: string,
      target: EventTarget = document.body
    ): Promise<ContextListResult<FileBreadcrumb>> => {
      const e = new CustomEvent(EventTypes.Store.File.breadcrumbs, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key: fileOrFolder,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<ContextListResult<FileBreadcrumb>>
    },
  }),
  /**
   * Reads the current user.
   */
  User: Object.freeze({
    me: async (target: EventTarget = document.body): Promise<IUser> => {
      const e = new CustomEvent(EventTypes.Store.User.me, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<IUser>
    },
    read: async (
      organizationId: string,
      key: string,
      target: EventTarget = document.body
    ): Promise<IUser | undefined> => {
      const e = new CustomEvent(EventTypes.Store.User.read, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key,
          organizationId,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<IUser>
    },
    list: async (
      organizationId: string,
      options?: ContextListOptions,
      target: EventTarget = document.body
    ): Promise<ContextListResult<IUser>> => {
      const e = new CustomEvent(EventTypes.Store.User.list, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          organizationId,
          options,
          result: undefined,
        },
      })
      target.dispatchEvent(e)
      return e.detail.result as unknown as Promise<ContextListResult<IUser>>
    },
  }),
})
