/**
 * Schema definition language tools
 */

'use strict';

const _ = require('lodash');

/**
 * Retrieves a type description from its configuration or its related model
 * @return String
 */
const getTypeDescription = (type, model = {}) => {
  const str = _.get(type, '_description') || _.get(model, 'info.description');

  if (str) {
    return `"""\n${str}\n"""\n`;
  }

  return '';
};

/**
 * Receive an Object and return a string which is following the GraphQL specs.
 * @param {Object} fields
 * @param {Object} description
 * @param {Object} model the underlying strapi model of those fields
 * @param {string} type the type of object we are converting to SQL (query, mutation, or fields)
 */
const toSDL = (fields, configurations = {}, model = {}, type = 'field') => {
  if (['query', 'mutation'].includes(type)) {
    return operationToSDL({ fields, configurations });
  }

  return fieldsToSDL({ fields, model, configurations });
};

/**
 * Generated a SDL for a type
 * @param {Object} options
 * @param {Object} options.fields fields to convert to SDL
 * @param {Object} options.configurations fields configurations (descriptions and deprecations)
 * @param {Object} options.model the underlying strapi model of those fields
 */
const fieldsToSDL = ({ fields, configurations, model }) => {
  return Object.entries(fields)
    .map(([key, value]) => {
      const [attr] = key.split('(');
      const attributeName = _.trim(attr);

      const description = _.isString(configurations[attributeName])
        ? configurations[attributeName]
        : _.get(configurations, [attributeName, 'description']) ||
          _.get(model, ['attributes', attributeName, 'description']);

      const deprecated =
        _.get(configurations, [attributeName, 'deprecated']) ||
        _.get(model, ['attributes', attributeName, 'deprecated']);

      return applyMetadatas(`${key}: ${value}`, { description, deprecated });
    })
    .join('\n');
};

/**
 * Generated a SDL for a query or a mutation object
 * @param {Object} options
 * @param {Object} options.fields fields to convert to SDL
 * @param {Object} options.configurations fields configurations (descriptions and deprecations)
 */
const operationToSDL = ({ fields, configurations }) => {
  return Object.entries(fields)
    .map(([key, value]) => {
      const [attr] = key.split('(');
      const attributeName = _.trim(attr);

      return applyMetadatas(`${key}: ${value}`, configurations[attributeName]);
    })
    .join('\n');
};

/**
 * Applies description and deprecated to a field definition
 * @param {string} definition field definition
 * @param {Object} metadatas field metadatas
 * @param {string} metadatas.description field description
 * @param {string} metadatas.deprecated field deprecation
 */
const applyMetadatas = (definition, metadatas = {}) => {
  const { description, deprecated } = metadatas;

  let tmpDef = definition;
  if (description) {
    tmpDef = `"""\n${description}\n"""\n${tmpDef}`;
  }

  if (deprecated) {
    tmpDef = `${tmpDef} @deprecated(reason: "${deprecated}")`;
  }

  return tmpDef;
};

module.exports = {
  toSDL,
  getTypeDescription,
};
