const { defineNonEnumerableProperty } = require('./modelUtils');
const { isPromise } = require('../utils/promiseUtils');

const TABLE_METADATA = '$$tableMetadata';

function fetchTableMetadata(
  modelClass,
  { parentBuilder = null, knex = null, force = false, table = null } = {}
) {
  // The table isn't necessarily same as `modelClass.getTableName()` for example if
  // a view is queried instead.
  if (!table) {
    if (parentBuilder) {
      table = parentBuilder.tableNameFor(modelClass);
    } else {
      table = modelClass.getTableName();
    }
  }

  // Call tableMetadata first instead of accessing the cache directly beause
  // tableMetadata may have been overriden.
  let metadata = modelClass.tableMetadata({ table });

  if (!force && metadata) {
    return Promise.resolve(metadata);
  }

  // Memoize metadata but only for modelClass. The hasOwnProperty check
  // will fail for subclasses and the value gets recreated.
  if (!modelClass.hasOwnProperty(TABLE_METADATA)) {
    defineNonEnumerableProperty(modelClass, TABLE_METADATA, new Map());
  }

  // The cache needs to be checked in addition to calling tableMetadata
  // because the cache may contain a temporary promise in which case
  // tableMetadata returns null.
  metadata = modelClass[TABLE_METADATA].get(table);

  if (!force && metadata) {
    return Promise.resolve(metadata);
  } else {
    const promise = modelClass
      .query(knex)
      .childQueryOf(parentBuilder)
      .columnInfo({ table })
      .then(columnInfo => {
        const metadata = {
          columns: Object.keys(columnInfo)
        };

        modelClass[TABLE_METADATA].set(table, metadata);
        return metadata;
      });

    modelClass[TABLE_METADATA].set(table, promise);
    return promise;
  }
}

function tableMetadata(modelClass, { table } = {}) {
  if (modelClass.hasOwnProperty(TABLE_METADATA)) {
    const metadata = modelClass[TABLE_METADATA].get(table || modelClass.getTableName());

    if (isPromise(metadata)) {
      return null;
    } else {
      return metadata;
    }
  } else {
    return null;
  }
}

module.exports = {
  fetchTableMetadata,
  tableMetadata
};
