import d from 'debug';

const debug = d('LC:ConversationQuery');

export default class ConversationQuery {
  static _encode(value) {
    if (value instanceof Date) {
      return { __type: 'Date', iso: value.toJSON() };
    }
    if (value instanceof RegExp) {
      return value.source;
    }
    return value;
  }

  static _quote(s) {
    return `\\Q${s.replace('\\E', '\\E\\\\E\\Q')}\\E`;
  }

  static _calculateFlag(options) {
    return ['withLastMessagesRefreshed', 'compact'].reduce(
      // eslint-disable-next-line no-bitwise
      (prev, key) => (prev << 1) + Boolean(options[key]),
      0
    );
  }

  /**
   * 构造一个用 AND 连接所有查询的 ConversationQuery
   * @param {...ConversationQuery} queries
   * @return {ConversationQuery}
   */
  static and(...queries) {
    if (queries.length < 2) {
      throw new Error('The queries must contain at least two elements');
    }
    if (!queries.every(q => q instanceof ConversationQuery)) {
      throw new Error(
        'The element of queries must be an instance of ConversationQuery'
      );
    }
    const combined = new ConversationQuery(queries[0]._client);
    combined._where.$and = queries.map(q => q._where);
    return combined;
  }

  /**
   * 构造一个用 OR 连接所有查询的 ConversationQuery
   * @param  {...ConversationQuery} queries
   * @return {ConversationQuery}
   */
  static or(...queries) {
    const combined = ConversationQuery.and(...queries);
    combined._where.$or = combined._where.$and;
    delete combined._where.$and;
    return combined;
  }

  /**
   * Create a ConversationQuery
   * @param  {IMClient} client
   */
  constructor(client) {
    this._client = client;
    this._where = {};
    this._extraOptions = {};
  }

  _addCondition(key, condition, value) {
    // Check if we already have a condition
    if (!this._where[key]) {
      this._where[key] = {};
    }
    this._where[key][condition] = this.constructor._encode(value);
    return this;
  }

  toJSON() {
    const json = {
      where: this._where,
      flag: this.constructor._calculateFlag(this._extraOptions),
    };
    if (typeof this._skip !== 'undefined') json.skip = this._skip;
    if (typeof this._limit !== 'undefined') json.limit = this._limit;
    if (typeof this._order !== 'undefined') json.sort = this._order;
    debug(json);
    return json;
  }

  /**
   * 增加查询条件,指定聊天室的组员包含某些成员即可返回
   * @param {string[]} peerIds - 成员 ID 列表
   * @return {ConversationQuery} self
   */
  containsMembers(peerIds) {
    return this.containsAll('m', peerIds);
  }

  /**
   * 增加查询条件,指定聊天室的组员条件满足条件的才返回
   *
   * @param {string[]} - 成员 ID 列表
   * @param {Boolean} includeSelf - 是否包含自己
   * @return {ConversationQuery} self
   */
  withMembers(peerIds, includeSelf) {
    const peerIdsSet = new Set(peerIds);
    if (includeSelf) {
      peerIdsSet.add(this._client.id);
    }
    this.sizeEqualTo('m', peerIdsSet.size);
    return this.containsMembers(Array.from(peerIdsSet));
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段满足等于条件时即可返回
   *
   * @param {string} key
   * @param value
   * @return {ConversationQuery} self
   */
  equalTo(key, value) {
    this._where[key] = this.constructor._encode(value);
    return this;
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段满足小于条件时即可返回
   * @param {string} key
   * @param value
   * @return {ConversationQuery} self
   */
  lessThan(key, value) {
    return this._addCondition(key, '$lt', value);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段满足小于等于条件时即可返回

   * @param {string} key
   * @param value
   * @return {ConversationQuery} self
   */
  lessThanOrEqualTo(key, value) {
    return this._addCondition(key, '$lte', value);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段满足大于条件时即可返回
   *
   * @param {string} key
   * @param value
   * @return {ConversationQuery} self
   */

  greaterThan(key, value) {
    return this._addCondition(key, '$gt', value);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段满足大于等于条件时即可返回
   *
   * @param {string} key
   * @param value
   * @return {ConversationQuery} self
   */

  greaterThanOrEqualTo(key, value) {
    return this._addCondition(key, '$gte', value);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段满足不等于条件时即可返回
   *
   * @param {string} key
   * @param value
   * @return {ConversationQuery} self
   */
  notEqualTo(key, value) {
    return this._addCondition(key, '$ne', value);
  }

  /**
   * 增加查询条件,当 conversation 存在指定的字段时即可返回
   *
   * @since 3.5.0
   * @param {string} key
   * @return {ConversationQuery} self
   */
  exists(key) {
    return this._addCondition(key, '$exists', true);
  }

  /**
   * 增加查询条件,当 conversation 不存在指定的字段时即可返回
   *
   * @since 3.5.0
   * @param {string} key
   * @return {ConversationQuery} self
   */
  doesNotExist(key) {
    return this._addCondition(key, '$exists', false);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段对应的值包含在指定值中时即可返回
   *
   * @param {string} key
   * @param values
   * @return {ConversationQuery} self
   */
  containedIn(key, values) {
    return this._addCondition(key, '$in', values);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段对应的值不包含在指定值中时即可返回
   *
   * @param {string} key
   * @param values
   * @return {ConversationQuery} self
   */
  notContainsIn(key, values) {
    return this._addCondition(key, '$nin', values);
  }

  /**
   * 增加查询条件,当conversation的属性中对应的字段中的元素包含所有的值才可返回
   *
   * @param {string} key
   * @param values
   * @return {ConversationQuery} self
   */
  containsAll(key, values) {
    return this._addCondition(key, '$all', values);
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段对应的值包含此字符串即可返回
   *
   * @param {string} key
   * @param {string} subString
   * @return {ConversationQuery} self
   */
  contains(key, subString) {
    return this._addCondition(
      key,
      '$regex',
      ConversationQuery._quote(subString)
    );
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段对应的值以此字符串起始即可返回
   *
   * @param {string} key
   * @param {string} prefix
   * @return {ConversationQuery} self
   */
  startsWith(key, prefix) {
    return this._addCondition(
      key,
      '$regex',
      `^${ConversationQuery._quote(prefix)}`
    );
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段对应的值以此字符串结束即可返回
   *
   * @param {string} key
   * @param {string} suffix
   * @return {ConversationQuery} self
   */
  endsWith(key, suffix) {
    return this._addCondition(
      key,
      '$regex',
      `${ConversationQuery._quote(suffix)}$`
    );
  }

  /**
   * 增加查询条件,当 conversation 的属性中对应的字段对应的值满足提供的正则表达式即可返回
   *
   * @param {string} key
   * @param {RegExp} regex
   * @return {ConversationQuery} self
   */
  matches(key, regex) {
    this._addCondition(key, '$regex', regex);
    // Javascript regex options support mig as inline options but store them
    // as properties of the object. We support mi & should migrate them to
    // modifiers
    let _modifiers = '';
    if (regex.ignoreCase) {
      _modifiers += 'i';
    }
    if (regex.multiline) {
      _modifiers += 'm';
    }

    if (_modifiers && _modifiers.length) {
      this._addCondition(key, '$options', _modifiers);
    }
    return this;
  }

  /**
   * 添加查询约束条件,查找 key 类型是数组,该数组的长度匹配提供的数值
   *
   * @param {string} key
   * @param {Number} length
   * @return {ConversationQuery} self
   */
  sizeEqualTo(key, length) {
    return this._addCondition(key, '$size', length);
  }

  /**
   * 设置返回集合的大小上限
   *
   * @param {Number} limit - 上限
   * @return {ConversationQuery} self
   */
  limit(limit) {
    this._limit = limit;
    return this;
  }

  /**
   * 设置返回集合的起始位置,一般用于分页
   *
   * @param {Number} skip - 起始位置跳过几个对象
   * @return {ConversationQuery} self
   */
  skip(skip) {
    this._skip = skip;
    return this;
  }

  /**
   * 设置返回集合按照指定key进行增序排列
   *
   * @param {string} key
   * @return {ConversationQuery} self
   */
  ascending(key) {
    this._order = key;
    return this;
  }

  /**
   * 设置返回集合按照指定key进行增序排列,如果已设置其他排序,原排序的优先级较高
   *
   * @param {string} key
   * @return {ConversationQuery} self
   */
  addAscending(key) {
    if (this._order) {
      this._order += `,${key}`;
    } else {
      this._order = key;
    }
    return this;
  }

  /**
   * 设置返回集合按照指定 key 进行降序排列
   *
   * @param {string} key
   * @return {ConversationQuery} self
   */
  descending(key) {
    this._order = `-${key}`;
    return this;
  }

  /**
   * 设置返回集合按照指定 key 进行降序排列,如果已设置其他排序,原排序的优先级较高
   *
   * @param {string} key
   * @return {ConversationQuery} self
   */
  addDescending(key) {
    if (this._order) {
      this._order += `,-${key}`;
    } else {
      this._order = `-${key}`;
    }
    return this;
  }

  /**
   * 设置返回的 conversations 刷新最后一条消息
   * @param  {Boolean} [enabled=true]
   * @return {ConversationQuery} self
   */
  withLastMessagesRefreshed(enabled = true) {
    this._extraOptions.withLastMessagesRefreshed = enabled;
    return this;
  }

  /**
   * 设置返回的 conversations 为精简模式,即不含成员列表
   * @param  {Boolean} [enabled=true]
   * @return {ConversationQuery} self
   */
  compact(enabled = true) {
    this._extraOptions.compact = enabled;
    return this;
  }

  /**
   * 执行查询
   * @return {Promise.<ConversationBase[]>}
   */
  async find() {
    return this._client._executeQuery(this);
  }

  /**
   * 返回符合条件的第一个结果
   * @return {Promise.<ConversationBase>}
   */
  async first() {
    return (await this.limit(1).find())[0];
  }
}