const datefns = require('date-fns');
const ObjectId = require('mongoose').Types.ObjectId;

/**
 * Get table header of the model
 * @param {Model} model 
 */
function getHeaders(model, full) {
  let headers = Object.keys(model.schema.paths).filter((key) => {
    //TODO 
    return key.substring(0, 1) != '_';
  }).map((key) => {
    let options = model.schema.path(key).options || {};
    return {
      data: key,
      title: options.desc || key,
      orderable: !(options.orderable === false),
      searchable: options.searchable || false,
      listingOrder: options.listingOrder || 1000,
      listing: options.listing,
      unit: options.unit,
      type: getType(options.type),
    }
  })

  if (!full) {
    headers = headers.filter(function (obj) {
      if (obj.listing === false) {
        return false;
      } else return true;
    });
  }
  headers = headers.sort((a, b) => {
    return a.listingOrder - b.listingOrder;
  });
  //console.table(headers);
  return headers;
}

function getType(typeFunc) {
  let type;
  switch (typeFunc) {
    case Number:
      type = "number";
      break;
    case String:
      type = 'string';
      break;
    case Date:
      type = 'date';
      break;
    case Boolean:
      type = 'boolean';
      break;
    default:
      type = "other";
  }
  return type;
}


/**
 * 
 * @param  {Model} model
 * @returns 
 */
async function getListBuilder(params, model, populates) {

  let { page, length, search, filters } = params;

  //let paths = model.schema.paths;
  let schema = model.schema;

  let baseQuery = params.base || {};
  let query = Object.assign({}, baseQuery);


  //filters
  try {
    // filters = JSON.parse(filters);
    //console.log('filters: ', filters);
    Object.keys(filters)
      .filter(data => schema.paths[data]) //filter not in schema
      .filter(data => {
        let value = filters[data];
        if (value === undefined) return false; //undefined
        if (Array.isArray(value) && value.length <= 0) return false;
        return true;
      }).forEach(data => {
        //console.log('data', data);
        let options = schema.path(data).options;
        //console.log('options', options);
        let value = filters[data];
        if (Array.isArray(value) && options.type == Date) {
          query[data] = {};
          if (value[0]) {
            query[data].$gte = datefns.startOfDay(new Date(value[0]));
          }
          if (value[1]) {
            query[data].$lte = datefns.endOfDay(new Date(value[1]));
          }
        } else if (Array.isArray(value)) {
          if (query[data]) {
            query[data].$in = value;
          } else {
            query[data] = { $in: value };
          }

        } else {
          query[data] = value;
        }
      })
  } catch (e) {
    console.warn(e);
  }


  //search
  if (search) { search = search.trim(); }
  if (search) {
    search = escapeRegExp(search);
    let match = { $regex: new RegExp(search, 'i') };
    let searchable = getHeaders(model).filter(h => h.searchable);
    if (searchable.length > 0) {
      query.$or = searchable.map(header => {
        let field = {};
        field[header.data] = match;
        return field;
      });
      if (ObjectId.isValid(search)) {
        query.$or.push({ _id: search }); // add id search
      }
    }
  }


  //console.log('search: ', search);
  //console.log('query : ', JSON.stringify(query));

  //pagination
  page = parseInt(page) || 1;
  if (page <= 0) page = 1;
  length = parseInt(length) || 30;
  if (length <= 0) length = 30;
  else if (length > 500) length = 500;

  let start = (page - 1) * length
  let recordsTotal = await model.countDocuments(baseQuery);
  let recordsFiltered = await model.countDocuments(query);

  // console.log('baseQuery', baseQuery);
  // console.log('query', query);

  /** @type {Query} */
  let theQuery = model.find(query)
    .skip(start)
    .limit(length);

  //order
  let { orderBy, order } = params;
  if (orderBy) {
    if (order != 'asc' && order != 'desc') order = 'desc';
    let sort = {};
    sort[orderBy] = order;
    theQuery.sort(sort)
  }

  //populate
  if (populates) {
    populates.forEach(p => theQuery.populate(p))
  }

  let data = await theQuery.exec();

  data = data.map(item => item.toObject());

  return { page, length, recordsFiltered, recordsTotal, data, filters, query }


}



function escapeRegExp(str) {
  var len;
  var s;
  var i;

  var RE_CHARS = /[-\/\\^$*+?.()|[\]{}]/g; // eslint-disable-line no-useless-escape


  // Check if the string starts with a forward slash...
  if (str[0] === '/') {
    // Find the last forward slash...
    len = str.length;
    for (i = len - 1; i >= 0; i--) {
      if (str[i] === '/') {
        break;
      }
    }
  }
  // If we searched the string to no avail or if the first letter is not `/`, assume that the string is not of the form `/[...]/[guimy]`:
  if (i === void 0 || i <= 0) {
    return str.replace(RE_CHARS, '\\$&');
  }
  // We need to de-construct the string...
  s = str.substring(1, i);

  // Only escape the characters between the `/`:
  s = s.replace(RE_CHARS, '\\$&');

  // Reassemble:
  str = str[0] + s + str.substring(i);

  return str;
}


module.exports = { getHeaders, getListBuilder, escapeRegExp }