/* eslint-disable */

declare namespace Noodl {
  function getProjectSettings(): any;
  function getMetaData(): any;

  interface VariablesApi {
    [K in VariableNames]: any
  } 

  /**
   * You can access all variables in your application trough the Noodl.Variables object.
   * Changing a variable will trigger all connections to be updated for all Variable nodes
   * in your project with the corresponding variable name.
   *
   * Example:
   * ```ts
   *  // This will change the variable named MyVariable
   *  // and trigger all variable nodes in your project
   *  Noodl.Variables.MyVariable = "Hello";
   *  
   *  // Use this if you have spaces in your variable name
   *  Noodl.Variables["My Variable"] = 10;
   *  
   *  Noodl.Variables.userName = "Mickeeeey";
   *  
   *  // Reading variables
   *  console.log(Noodl.Variables.userName);
   * ```
   * 
   * {@link https://docs.noodl.net/#/javascript/reference/variables}
   */
  const Variables: VariablesApi;

  /**
   * One step above Variables are Objects, this is a global data model of Noodl objects.
   * Each object is referenced with an Id and can contain a set of properties.
   * You can access all objects in your workspace through their Id and the Noodl.Objects prefix.
   * Change a property of an object will trigger all connections
   * from object nodes with the corresponding Id and property.
   * 
   * Example:
   * ```ts
   *  // This will change the property MyProperty
   *  // of object with id MyObjectId and trigger
   *  // all object nodes (with that id) in your project
   *  Noodl.Objects.MyObjectId.MyProperty = "Hello";
   *  
   *  // Use this notation of that object id contains spaces
   *  Noodl.Objects["Form Values"].input_text = "Whoops";
   *  
   *  Noodl.Objects["Form Values"]["A property with spaces"] = 20;
   *  
   *  // Reading an object property
   *  console.log(Noodl.Objects.CurrentUser.Name);
   *  
   *  // This will set all properties of the object you assign with
   *  // to the object with id "SomeId"
   *  // You cannot set the id property this way,
   *  // that property will be ignored if part of the object you assign
   *  Noodl.Objects.SomeId = {
   *    // ...
   *  }
   * ```
   * 
   * {@link https://docs.noodl.net/#/javascript/reference/objects}
   */
  const Objects: any;

  /**
   * Allows access to Object from Javascript.
   *
   * {@link https://docs.noodl.net/#/javascript/reference/object}
   */
  const Object: any;

  type RecordQuery<T> =
    {
      lessThan: T
    } |
    {
      lessThanOrEqualTo: T
    } |
    {
      greaterThan: T
    } |
    {
      greaterThanOrEqualTo: T
    } |
    {
      equalTo: T
    } |
    {
      notEqualTo: T
    } |
    {
      containedIn: T
    } |
    {
      notContainedIn : T
    } |
    {
      exists: T
    } |
    {
      matchesRegex: T
    } |
    {
      text: T
    } |
    {
      idEqualTo: T
    } |
    {
      idContainedIn: T
    } |
    {
      pointsTo: T
    } |
    {
      relatedTo: T
    };

  type RecordQueryField<T> = T extends RecordQuery<any> ?
    { [K in keyof T]: { [P in K]: T[P] } & Partial<Record<Exclude<keyof T, K>, never>> }[keyof T]
    : never;

  type RecordSortKey<T extends string> = (`${T}` | `-${T}`)[];

  interface RecordsApi {
    /**
     * This is an async function that will query the database using the query
     * that you provide and return the result or throw an exception if failed.
     * 
     * The query parameter has the same format as the advanced query of the Query Records node.
     * {@link https://docs.noodl.net/#/nodes/data/cloud-data/query-records/#advanced-filters}
     * 
     * Example:
     * ```ts
     *  const results = await Noodl.Records.query("myClass", {
     *    Completed: { equalTo: true },
     *  })
     * ```
     * 
     * The result is an array of Noodl.Object. The options can be used to specify sorting,
     * it also follows the same pattern as the advanced filters of the Query Records node.
     * 
     * Example:
     * ```ts
     *  const results = await Noodl.Records.query("myClass", {
     *    Completed: { equalTo: true },
     *  }, {
     *    sort:['createdAt']
     *  })
     * ```
     * 
     * You can also specify the limit for how many records to return as a maximum (defaults to 100)
     * with the limit option, and if you want the returned records to start from a given
     * index specify the skip option.
     * 
     * ```ts
     *  const results = await Noodl.Records.query("myClass", {
     *    Completed: { equalTo: true },
     *  }, {
     *    sort: ['-createdAt'], // use - to sort descending
     *    skip: 50,
     *    limit: 200
     *  })
     * ```
     */
    query<TClassName extends RecordClassName>(
      className: TClassName,
      query?:
        RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }> |
        { and: RecordQueryField<{ [K in keyof DatabaseSchema[TClassName]]: RecordQuery<any> }>[] },
      options?: {
        limit?: number;
        skip?: number;
        sort?: string | RecordSortKey<keyof DatabaseSchema[TClassName]>;
        include?: string | (keyof DatabaseSchema[TClassName])[];
        select?: string | (keyof DatabaseSchema[TClassName])[];
      }
    ): Promise<any>;

    /**
     * This function returns the count of the number of records of a given class,
     * optionally matching a query filter.
     * 
     * Example:
     * ```ts
     *  // The number of records of myClass in the database
     *  const count = await Noodl.Records.count("myClass")
     * 
     *  // The number of myClass records in the database that match a query
     *  const completedCount = await Noodl.Records.count("myClass", {
     *    Completed: { equalTo: true },
     *  })
     * ```
     */
    count(className: RecordClassName, query?: any): Promise<number>;

    /**
     * returns an array of unique values for a given propery or all records in
     * the database of a given class. Optionally you can suppoly a query filter.
     */
    distinct(
      className: RecordClassName,
      property: string,
      query: any
    ): Promise<any>;

    /**
     * Use this function to fetch the latest properties of a specific record
     * from the cloud database. It will return the object / record.
     * 
     * Example:
     * ```ts
     *  // If you use the a record ID you must also specify the class
     *  const myRecord = await Noodl.Records.fetch(myRecordId, { className: "myClass" })
     *  
     *  // You can also fetch a record you have previously fetched or received from a
     *  // query, to get the latest properties from the backend
     *  await Noodl.Records.fetch(myRecord)
     * ```
     */
    fetch(
      objectOrId: string | { getId(): string; },
      options?: {
        className?: RecordClassName;
      }
    ): Promise<any>;

    /**
     * Use this function to write an existing record to the cloud database.
     * It will attempt to save all properties of the record / object
     * if you don't specify the optional properties argument,
     * if so it will set and save those properties.
     * 
     * Example:
     * ```ts
     *  Noodl.Objects[myRecordId].SomeProperty = "hello"
     *
     *  // If you use the record id to save, you need to specify the classname explicitly
     *  // by specfiying null or undefinded for properties it will save all proporties in
     *  // the record
     *  await Noodl.Records.save(myRecordId, null, { className: "myClass" })
     *
     *  // Or use the object directly
     *  await Noodl.Records.save(Noodl.Objects[myRecordId])
     *
     *  // Set specified properties and save only those to the backned
     *  await Noodl.Records.save(myRecord, {
     *      SomeProperty:'hello'
     *  })
     * ```
     */
    save(
      objectOrId: string | { getId(): string; },
      properties: any,
      options?: {
        className?: RecordClassName;
        acl?: any;
      }
    ): Promise<void>;

    /**
     * This function will increment (or decrease) propertis of a certain record
     * saving it to the cloud database in a race condition safe way.
     * That is, normally you would have to first read the current value,
     * modify it and save it to the database. Here you can do it with one operation.
     * 
     * Example:
     * ```ts
     *  // Modify the specified numbers in the cloud
     *  await Noodl.Records.increment(myRecord, {
     *      Score: 10,
     *      Life: -1
     *  })
     *  
     *  // Like save, you can use a record Id and class
     *  await Noodl.Records.save(myRecordId, { Likes: 1 }, { className: "myClass" })
     * ```
     * 
     * Using the options you can also specify access control as described in this guide,
     * this let's you control which users can access a specific record.
     * The access control is specified as below:
     * ```ts
     *  await Noodl.Records.save(myRecord, null, {
     *    acl: {
     *      // "*" means everyone, this rules gives everyone read access but not write
     *      "*": { read: true, write: false },
     *      // give a specific user write access
     *      "a-user-id": { read: true, write: true },
     *      // give a specific role write access
     *      "role:a-role-name": { read: true, write: true },
     *    }
     *  })
     * ```
     */
    increment(
      objectOrId: string | { getId(): string; },
      properties: any,
      options?: {
        className?: RecordClassName;
      }
    ): Promise<void>;

    /**
     * This function will create a new record in the cloud database and return
     * the Noodl.Object of the newly created record.
     * If it's unsuccessful it will throw an exception.
     * 
     * Example:
     * ```ts
     *  const myNewRecord = await Noodl.Records.create("myClass",{
     *    SomeProperty:"Hello"
     *  })
     * 
     *  console.log(myNewRecord.SomeProperty)
     * ```
     * 
     * You can use the options agrument to specify access control rules
     * as detailed under Noodl.Records.save above.
     */
    create(
      className: RecordClassName,
      properties: any,
      options?: {
        acl?: any
      }
    ): Promise<any>;

    /**
     * Use this function to delete an existing record from the cloud database.
     * 
     * Example:
     * ```ts
     *  // If you specify the id of a record to be deleted, you must also provide the
     *  // class name in the options
     *  await Noodl.Records.delete(myRecordId,{className:"myClass"})
     * 
     *  // Or use the object directly (provided it was previously fetched or received via a query)
     *  await Noodl.Records.delete(Noodl.Objects[myRecordId])
     * ```
     */
    delete(
      objectOrId: string | { getId(): string; },
      options?: {
        className?: RecordClassName;
      }
    ): Promise<void>;

    /**
     * Use this function to add a relation between two records.
     * 
     * Example:
     * ```ts
     *  // You can either specify the Ids and classes directly
     *  await Noodl.Records.addRelation({
     *    className: "myClass",
     *    recordId: "owning-record-id",
     *    key: "the-relation-key-on-the-owning-record",
     *    targetRecordId: "the-id-of-the-record-to-add-a-relation-to",
     *    targetClassName: "the-class-of-the-target-record"
     *  })
     *  
     *  // Or if you already have two records that have been previously fetched or returned from a
     *  // query
     *  await Noodl.Records.addRelation({
     *    record: myRecord,
     *    key: 'relation-key',
     *    targetRecord: theTargetRecord
     *  })
     * ```
     */
    addRelation(
      options: {
        recordId: string | { getId(): string; },
        className?: RecordClassName,
        key: string,
        targetRecordId: string | { getId(): string; },
        targetClassName?: RecordClassName
      }
    ): Promise<void>;

    /**
     * Use this function to remove a relation between two records.
     * 
     * ```ts
     *  // You can either specify the Ids and classes directly
     *  await Noodl.Records.removeRelation({
     *    className: "myClass",
     *    recordId: "owning-record-id",
     *    key: "the-relation-key-on-the-owning-record",
     *    targetRecordId: "the-id-of-the-record-to-remove-a-relation-to",
     *    targetClassName: "the-class-of-the-target-record"
     *  })
     *  
     *  // Or if you already have two records that have been previously fetched or returned from a
     *  // query
     *  await Noodl.Records.removeRelation({
     *    record: myRecord,
     *    key: 'relation-key',
     *    targetRecord: theTargetRecord
     *  })
     * ```
     */
    removeRelation(
      options: {
        recordId: string | { getId(): string; },
        className?: RecordClassName,
        key: string,
        targetRecordId: string | { getId(): string; },
        targetClassName?: RecordClassName
      }
    ): Promise<void>;
    

    /**
     * compute a set of aggregates based on properties in the records.
     * It can be limited with a query.
     * 
     * You can use the following aggregate functions:
     * - sum Compute the sum of a number property access matching records.
     * - min Compute the minimum value of a number property access matching records.
     * - max Compute the maximum value of a number property access matching records.
     * - avg Compute the average value of a number property access matching records.
     */
    aggregate(
      className: RecordClassName,
      aggregates: any,
      query: any
    ): Promise<any>;
  }

  /**
   * With Records you can query, read and write records to the cloud database.
   * All functions are async and will throw an exception if they fail.
   *
   * Example:
   * ```ts
   *  try {
   *    await Noodl.Records.delete(myRecord)
   *  }
   *  catch(e) {
   *    console.log(e)
   *  }
   * ```
   * 
   * {@link https://docs.noodl.net/#/javascript/reference/records}
   */
  const Records: RecordsApi;

  interface CurrentUserObject {
    UserId: string;

    // TODO: Fill in the User Record here.
    Properties: Record<string, any>;

    /**
     * Attempt to save the properties of the current user.
     *
     * If you have made changes to the current() user object you will need
     * to call this function to write the changes to the backend.
     */
    save(): Promise<void>;

    /**
     * Fetch that laters properties of the user object from the cloud database.
     * It will throw an exception if the user session has expired.
     */
    fetch(): Promise<void>;
  }

  interface UsersApi {
    /**
     * Attempt to login to create a user session.
     *
     * After a successful login you can access the user object with `Noodl.Users.Current`.
     */
    logIn(options: { username: string; password: string }): Promise<any>;

    /**
     * Get a session token for a user that you can later send to the client to log that user in.
     * This does not require a password and must be runon a cloud function
     * (since they all have full access to the database).
     *
     * You can provide a duration for the session,
     * or it will expire after 24 hours as default.
     *
     * If successful this call will return a user object that contains a session token
     * that you can return to the client and use with the become function.
     * 
     * __installationId__ is an optional that is a unique id for the client if you don't want
     * to share sessions between different clients. Most common is to generate a random id
     * on the client and pass to the cloud function when you are logging in.
     */
    impersonate(username: string, options?: { duration?: number, installationID?: string }): Promise<any>;

    /**
     * Return the current user object and properties if one exists.
     */
    get Current(): CurrentUserObject | undefined;
  }

  /**
   * The Noodl.Users object let's you access the current session user.
   *
   * {@link https://docs.noodl.net/#/javascript/reference/users}
   */
  const Users: UsersApi;

  interface FilesApi {
    /**
     * Delete a file that has been uploaded to the backend.
     * You need to provide the file name that was returned when the file was uploaded.
     * So not the full url but the hash+filename returned by the upload function.
     */
    delete(filename: string): Promise<void>;
  }

  /**
   * The Noodl.Files service lets you access the cloud services files.
   *
   * {@link https://docs.noodl.net/#/javascript/reference/files}
   */
  const Files: FilesApi;
}

interface ComponentApi {
  /**
   * `Component.Object` is the Component Object of the current component and
   * you can use it just like any other Noodl.Object.
   * Most commonly this means accessing the properties of the object.
   * When you set a property any Component Object node in this component
   * instance will update accordingly.
   */
  Object: any;

  /**
   * Object is the Parent Component Object,
   * that is the Component Object of the parent component in the visual heirarchy.
   * It is also used like any other Noodl.Object.
   */
  ParentObject: any;

  /**
   * If this component is the template of a repeater this will contain
   * the object of the items array corresponding to this specific component instance.
   * That is the same object as if you set an object Id Source to From Repeater, as shown below.
   */
  RepeaterObject: any;
}

/**
 * The `Component` object is ony available in Function and Script nodes and
 * it contains things related to the component scope where the
 * Function or Script node is executing.
 *
 * {@link https://docs.noodl.net/#/javascript/reference/component}
 */
declare const Component: ComponentApi;
