// Import types
import Query from './Query';
import FilterOpts from './FilterOpts';
import MutateResult from './MutateResult';
import Id from './Id';
import PaginatedResponse from './PaginatedResponse';

/**
 * Interface describing a basic collection.
 * @author Benedikt Arnarsson
 */
interface BasicCollectionInterface<DocumentType extends { [k: string]: any }> {
  /*------------------------------------------------------------------------*/
  /*                            'CRUD' Functions                            */
  /*------------------------------------------------------------------------*/

  /**
   * Run a query
   * @author Gabe Abrams
   * @param query the query to run
   * @param [includeMongoTimestamp] if true, include the timestamp
   *   in the mongo objects
   * @returns documents
   */
  find(query: Query, includeMongoTimestamp?: boolean): Promise<DocumentType[]>;

  /**
   * Run a query with pagination
   * @author Yuen Ler Chow
   * @param query the query to run
   * @param perPage the number of items per page
   * @param pageNumber the page number to return, 1-indexed
   * @param [includeMongoTimestamp] if true, include the timestamp
   *   in the mongo objects
   * @returns documents
   */
  findPaged(
    opts: {
      query: Query,
      perPage?: number,
      pageNumber?: number,
      includeMongoTimestamp?: boolean,
    },
  ): Promise<PaginatedResponse<DocumentType>> ;

  /**
   * Write a record to the collection
   * @author Gabe Abrams
   * @param obj the object to insert
   */
  insert(obj: DocumentType): Promise<void>;

  /**
   * Delete the first document that matches the query in the collection
   * @author Gabe Abrams
   * @param query the query that will match the item
   */
  delete(query: Query): Promise<void>;

  /**
   * Delete all documents that match the query in the collection
   * @author Gabe Abrams
   * @param query the query that will match the items to delete
   */
  deleteAll(query: Query): Promise<void>;

  /*------------------------------------------------------------------------*/
  /*                          Additional Functions                          */
  /*------------------------------------------------------------------------*/

  /**
   * Find elements then only return the values for one specific property from
   *   each item
   * @author Gabe Abrams
   * @param query the query to run
   * @param prop the name of the property to extract
   * @param [excludeFalsy] if true, exclude falsy values
   * @returns array of values of the property
   */
  findAndExtractProp(
    query: Query,
    prop: string,
    excludeFalsy?: boolean,
  ): Promise<any[]>;

  /**
   * Count the number of matching elements
   * @author Gabe Abrams
   * @param query the query to run
   * @returns number of documents that match
   */
  count(query: Query): Promise<number>;

  /**
   * List distinct values for a property in a collection
   * @author Gabe Abrams
   * @param prop the property to list distinct values for
   * @param [query] the query to run. If excluded, all distinct
   *   values are included
   * @returns array of distinct values
   */
  distinct(prop: string, query?: Query): Promise<any[]>;

  /**
   * Increment value of an integer for an object
   * @author Gabe Abrams
   * @param id id of the object to increment
   * @param prop property to increment
   */
  increment(id: Id, prop: string): Promise<MutateResult>;

  /**
   * Increment value of an integer for an object, found by a query
   * @author Gabe Abrams
   * @param query query to find the object to increment
   * @param prop property to increment
   */
  incrementByQuery(query: Query, prop: string): Promise<MutateResult>;

  /**
   * Add/update object values in an entry in the collection. The entry must
   *   already exist
   * @author Gabe Abrams
   * @param query query to apply to find the object to update
   * @param updates map of updates: { prop => value } where prop is
   *   the potentially nested name of the property
   *   (e.g. "age" or "profile.age")
   */
  updatePropValues(query: Query, updates: Query): Promise<MutateResult>;

  /**
   * Add an object to an array in an object
   * @author Gabe Abrams
   * @param id the id of the object to modify
   * @param arrayProp the name of the array to insert into
   * @param obj the object to insert into the array
   */
  push(id: Id, arrayProp: string, obj: any): Promise<MutateResult>

  /**
   * Filter an array in an entry
   * @author Gabe Abrams
   * @param opts object containing all args
   * @param opts.id the id of the object to modify
   * @param opts.arrayProp the name of the array to filter
   * @param opts.compareProp the name of the array entry prop to compare
   * @param opts.compareValue the value of the array entry prop to filter
   *   out
   */
  filterOut(opts: FilterOpts): Promise<MutateResult>
}

export default BasicCollectionInterface;
