/* eslint-disable no-redeclare */
import { 
  IBackendInfo, IFile, IFileCreateOptions,
  ListFileKind, AccessOperation, IUser, IPatchRevision, IApplication, IHttpHistory, IHttpHistoryBulkAdd, HistoryListOptions, 
  ContextChangeRecord, ContextDeleteEvent, ContextDeleteBulkEvent, ContextListOptions, ContextListResult, IProjectExecution, IProjectExecutionListOptions, IBulkOperationResult, FileBreadcrumb,
} from '@api-client/core/build/browser.js';
import { Patch } from '@api-client/json';
import { IConfigEnvironment, IConfigInit } from '../lib/config/Config.js';
import { EventTypes } from './EventTypes.js';
import { ISessionInitInfo } from '../store/HttpStore.js';
import { StoreAuthenticationInit } from '../bindings/base/StoreBindings.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: Patch.JsonPatch, 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: Patch.JsonPatch, app: IApplication, 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: Patch.JsonPatch, 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 const StoreEvents = Object.freeze({
  /**
   * Initializes the store configuration for the application.
   * 
   * @param init The configuration initialization.
   * @param target Optional events target.
   */
  initEnvironment: async (init: IConfigInit, target: EventTarget = document.body): Promise<void> => {
    const e = new CustomEvent(EventTypes.Store.initEnvironment, {
      bubbles: true,
      cancelable: true,
      composed: true,
      detail: init,
    });
    target.dispatchEvent(e);
    await ((e.detail) as unknown as { result: Promise<void> }).result;
  },

  /**
   * Reads the information about the data store.
   * 
   * @param baseUri The store's base URI
   * @param target Optional events target.
   */
  storeInfo: async (baseUri: string, target: EventTarget=document.body): Promise<IBackendInfo> => {
    const e = new CustomEvent(EventTypes.Store.info, {
      bubbles: true,
      cancelable: true,
      composed: true,
      detail: {
        baseUri,
        result: undefined,
      },
    });
    target.dispatchEvent(e);
    return ((e.detail.result as unknown) as Promise<IBackendInfo>);
  },
  Global: Object.freeze({
    /**
     * Sets the store environment to be used globally by the application.
     * This initializes the store with the configuration.
     * 
     * Note, this won't initialize the session with the store.
     * 
     * @param env The environment to set. Note, it must have the `location` set.
     */
    setEnv: async (env: IConfigEnvironment, target: EventTarget=document.body): Promise<void> => {
      const e = new CustomEvent(EventTypes.Store.Global.setEnv, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          env,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await (e.detail.result as unknown);
    },
    /**
     * Reads the currently set global environment.
     */
    getEnv: async (target: EventTarget=document.body): Promise<IConfigEnvironment> => {
      const e = new CustomEvent(EventTypes.Store.Global.getEnv, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return (e.detail.result as unknown) as Promise<IConfigEnvironment>;
    },
  }),
  Auth: Object.freeze({
    /**
     * Checks whether the user is authenticated in the store for the environment
     * @returns True if the user is authenticated in the store.
     */
    isAuthenticated: async (target: EventTarget=document.body): Promise<boolean> => {
      const e = new CustomEvent(EventTypes.Store.Auth.isAuthenticated, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<boolean>);
    },
    /**
     * Authenticates the current environment, if needed.
     * 
     * @param update Whether to update the stored environment configuration when the token was renewed.
     * @param env Optional environment to authenticated if different than default
     * @param force Whether to force authentication.
     * @param target Optional events target.
     * @returns The session information.
     */
    authenticate: async (options?: StoreAuthenticationInit, target: EventTarget=document.body): Promise<ISessionInitInfo> => {
      const e = new CustomEvent(EventTypes.Store.Auth.authenticate, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          options,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<ISessionInitInfo>);
    },
    /**
     * Reads the store access token in use, if any.
     * Note, this does not perform authentication. The user has to be already authenticated.
     * 
     * @param target Optional events target.
     * @returns The current access token value to use in the store or undefined if none is in use, or the event is not handled.
     */
    getToken: async (target: EventTarget=document.body): Promise<string | undefined> => {
      const e = new CustomEvent(EventTypes.Store.Auth.getToken, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<string | undefined>);
    },
  }),
  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 (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,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<IUser>);
    },
    list: async (options?: ContextListOptions, target: EventTarget=document.body): Promise<ContextListResult<IUser>> => {
      const e = new CustomEvent(EventTypes.Store.User.list, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          options,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<ContextListResult<IUser>>);
    },
  }),
  History: Object.freeze({
    create: async (history: IHttpHistory, target: EventTarget = document.body): Promise<ContextChangeRecord<IHttpHistory>> => {
      const e = new CustomEvent(EventTypes.Store.History.create, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          history,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<ContextChangeRecord<IHttpHistory>>);
    },
    createBulk: async (info: IHttpHistoryBulkAdd, target: EventTarget = document.body): Promise<IBulkOperationResult<ContextChangeRecord<IHttpHistory>>> => {
      const e = new CustomEvent(EventTypes.Store.History.createBulk, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          info,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return (e.detail.result as unknown) as Promise<IBulkOperationResult<ContextChangeRecord<IHttpHistory>>>;
    },
    list: async (init: HistoryListOptions, target: EventTarget=document.body): Promise<ContextListResult<IHttpHistory>> => {
      const e = new CustomEvent(EventTypes.Store.History.list, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          init,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<ContextListResult<IHttpHistory>>);
    },

    delete: async (key: string, parent?: string, target: EventTarget=document.body): Promise<void> => {
      const e = new ContextDeleteEvent(EventTypes.Store.History.delete, key, parent);
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Deletes history request in a bulk operation.
     * @param keys The keys of history objects to delete.
     */
    deleteBulk: async (keys: string[], target: EventTarget=document.body): Promise<void> => {
      const e = new ContextDeleteBulkEvent(EventTypes.Store.History.deleteBulk, keys);
      target.dispatchEvent(e);
      await e.detail.result;
    },
    read: async (key: string, target: EventTarget=document.body): Promise<IHttpHistory> => {
      const e = new CustomEvent(EventTypes.Store.History.read, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<IHttpHistory>);
    },

    /**
     * Clears all history from a project
     * @param project The key of the parent project
     */
    clearSpace: async (space: string, target: EventTarget=document.body): Promise<void> => {
      const e = new CustomEvent<{ result: Promise<void> | undefined, space: string }>(EventTypes.Store.History.clearSpace, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          space,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Clears all history from a project
     * @param project The key of the parent project
     */
    clearProject: async (space: string, project: string, target: EventTarget=document.body): Promise<void> => {
      const e = new CustomEvent<{ result: Promise<void> | undefined, project: string, space: string }>(EventTypes.Store.History.clearProject, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          project,
          space,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Clears all history from a request
     * @param project The key of the parent project
     * @param request The key of the request to clear the history for.
     */
    clearRequest: async (space: string, project: string, request: string, target: EventTarget=document.body): Promise<void> => {
      const e = new CustomEvent<{ result: Promise<void> | undefined, request: string, project: string, space: string }>(EventTypes.Store.History.clearRequest, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          request,
          project,
          space,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await e.detail.result;
    },
  }),
  ProjectRuns: Object.freeze({
    /**
     * Adds an execution logs object to the store.
     * @param item The execution log to add.
     */
    add: async (item: IProjectExecution, target: EventTarget = document.body): Promise<ContextChangeRecord<IProjectExecution>> => {
      const e = new CustomEvent(EventTypes.Store.ProjectRuns.add, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          item,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<ContextChangeRecord<IProjectExecution>>);
    },

    /**
     * Reads an execution logs item.
     * 
     * @param key The key of the history object to read.
     */
    read: async (key: string, target: EventTarget = document.body): Promise<IProjectExecution> => {
      const e = new CustomEvent(EventTypes.Store.ProjectRuns.read, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          key,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<IProjectExecution>);
    },

    /**
     * Lists the execution logs data.
     * @param options List options. Note, space is added by the bindings.
     */
    list: async (options: IProjectExecutionListOptions, target: EventTarget = document.body): Promise<ContextListResult<IProjectExecution>> => {
      const e = new CustomEvent(EventTypes.Store.ProjectRuns.list, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          options,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      return ((e.detail.result as unknown) as Promise<ContextListResult<IProjectExecution>>);
    },

    /**
     * Marks the data as deleted.
     * 
     * @param key The key of the object to delete.
     */
    delete: async (key: string, parent?: string, target: EventTarget = document.body): Promise<void> => {
      const e = new ContextDeleteEvent(EventTypes.Store.ProjectRuns.delete, key, parent);
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Deletes a history in a bulk operation.
     * 
     * @param keys The list of history keys to delete.
     */
    deleteBulk: async (keys: string[], parent?: string, target: EventTarget = document.body): Promise<void> => {
      const e = new ContextDeleteBulkEvent(EventTypes.Store.ProjectRuns.deleteBulk, keys, parent);
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Clears the datastore from history for a space.
     * @param project The parent space key. Note, space is added by the bindings. This can be empty.
     */
    clearSpace: async (space: string, target: EventTarget = document.body): Promise<void> => {
      const e = new CustomEvent<{ result: Promise<void> | undefined, space: string }>(EventTypes.Store.ProjectRuns.clearSpace, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          space,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Clears all history from a project
     * @param project The parent space key. Note, space is added by the bindings. This can be empty.
     * @param project The parent project key
     */
    clearProject: async (space: string, project: string, target: EventTarget = document.body): Promise<void> => {
      const e = new CustomEvent<{ result: Promise<void> | undefined, project: string, space: string }>(EventTypes.Store.ProjectRuns.clearProject, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          project,
          space,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await e.detail.result;
    },

    /**
     * Clears all history from a folder.
     * @param project The parent space key. Note, space is added by the bindings. This can be empty.
     * @param project The parent project key
     */
    clearParent: async (space: string, project: string, folder: string, target: EventTarget = document.body): Promise<void> => {
      const e = new CustomEvent<{ result: Promise<void> | undefined, project: string, space: string, folder: string }>(EventTypes.Store.ProjectRuns.clearParent, {
        bubbles: true,
        cancelable: true,
        composed: true,
        detail: {
          project,
          space,
          folder,
          result: undefined,
        },
      });
      target.dispatchEvent(e);
      await e.detail.result;
    },
  }),
});
