text: 'use strict';
  
  /**
   * Dependencies
   */
  const EmailAddress = require('./email-address');
  const Personalization = require('./personalization');
  const toCamelCase = require('../helpers/to-camel-case');
  const toSnakeCase = require('../helpers/to-snake-case');
  const deepClone = require('../helpers/deep-clone');
  const arrayToJSON = require('../helpers/array-to-json');
  const { DYNAMIC_TEMPLATE_CHAR_WARNING } = require('../constants');
  const {validateMailSettings, validateTrackingSettings} = require('../helpers/validate-settings');
  
  /**
   * Mail class
   */
  class Mail {
  
    /**
     * Constructor
     */
    constructor(data) {
  
      //Initialize array and object properties
      this.isDynamic = false;
      this.hideWarnings = false;
      this.personalizations = [];
      this.attachments = [];
      this.content = [];
      this.categories = [];
      this.headers = {};
      this.sections = {};
      this.customArgs = {};
      this.trackingSettings = {};
      this.mailSettings = {};
      this.asm = {};
  
      //Helper properties
      this.substitutions = null;
      this.substitutionWrappers = null;
      this.dynamicTemplateData = null;
  
      //Process data if given
      if (data) {
        this.fromData(data);
      }
    }
  
    /**
     * Build from data
     */
    fromData(data) {
  
      //Expecting object
      if (typeof data !== 'object') {
        throw new Error('Expecting object for Mail data');
      }
  
      //Convert to camel case to make it workable, making a copy to prevent
      //changes to the original objects
      data = deepClone(data);
      data = toCamelCase(data, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers', 'sections']);
  
      //Extract properties from data
      const {
        to, from, replyTo, cc, bcc, sendAt, subject, text, html, content,
        templateId, personalizations, attachments, ipPoolName, batchId,
        sections, headers, categories, category, customArgs, asm, mailSettings,
        trackingSettings, substitutions, substitutionWrappers, dynamicTemplateData, isMultiple,
        hideWarnings,
      } = data;
  
      //Set data
      this.setFrom(from);
      this.setReplyTo(replyTo);
      this.setSubject(subject);
      this.setSendAt(sendAt);
      this.setTemplateId(templateId);
      this.setBatchId(batchId);
      this.setIpPoolName(ipPoolName);
      this.setAttachments(attachments);
      this.setContent(content);
      this.setSections(sections);
      this.setHeaders(headers);
      this.setCategories(category);
      this.setCategories(categories);
      this.setCustomArgs(customArgs);
      this.setAsm(asm);
      this.setMailSettings(mailSettings);
      this.setTrackingSettings(trackingSettings);
      this.setHideWarnings(hideWarnings);
  
      if (this.isDynamic) {
        this.setDynamicTemplateData(dynamicTemplateData);
      } else {
        this.setSubstitutions(substitutions);
        this.setSubstitutionWrappers(substitutionWrappers);
      }
  
      //Add contents from text/html properties
      this.addTextContent(text);
      this.addHtmlContent(html);
  
      //Using "to" property for personalizations
      if (personalizations) {
        this.setPersonalizations(personalizations);
      } else if (isMultiple && Array.isArray(to)) {
        //Multiple individual emails
        to.forEach(to => this.addTo(to, cc, bcc));
      } else {
        //Single email (possibly with multiple recipients in the to field)
        this.addTo(to, cc, bcc);
      }
    }
  
    /**
     * Set from email
     */
    setFrom(from) {
      if (typeof from === 'undefined') {
        return;
      }
      if (typeof from !== 'string' && typeof from.email !== 'string') {
        throw new Error('String or address object expected for `from`');
      }
      this.from = EmailAddress.create(from);
    }
  
    /**
     * Set reply to
     */
    setReplyTo(replyTo) {
      if (typeof replyTo === 'undefined') {
        return;
      }
      if (typeof replyTo !== 'string' && typeof replyTo.email !== 'string') {
        throw new Error('String or address object expected for `replyTo`');
      }
      this.replyTo = EmailAddress.create(replyTo);
    }
  
    /**
     * Set subject
     */
    setSubject(subject) {
      if (typeof subject === 'undefined') {
        return;
      }
      if (typeof subject !== 'string') {
        throw new Error('String expected for `subject`');
      }
      this.subject = subject;
    }
  
    /**
     * Set send at
     */
    setSendAt(sendAt) {
      if (typeof sendAt === 'undefined') {
        return;
      }
      if (!Number.isInteger(sendAt)) {
        throw new Error('Integer expected for `sendAt`');
      }
      this.sendAt = sendAt;
    }
  
    /**
     * Set template ID, also checks if the template is dynamic or legacy
     */
    setTemplateId(templateId) {
      if (typeof templateId === 'undefined') {
        return;
      }
      if (typeof templateId !== 'string') {
        throw new Error('String expected for `templateId`');
      }
  
      if (templateId.indexOf('d-') === 0) {
        this.isDynamic = true;
      }
  
      this.templateId = templateId;
    }
  
    /**
     * Set batch ID
     */
    setBatchId(batchId) {
      if (typeof batchId === 'undefined') {
        return;
      }
      if (typeof batchId !== 'string') {
        throw new Error('String expected for `batchId`');
      }
      this.batchId = batchId;
    }
  
    /**
     * Set IP pool name
     */
    setIpPoolName(ipPoolName) {
      if (typeof ipPoolName === 'undefined') {
        return;
      }
      if (typeof ipPoolName !== 'string') {
        throw new Error('String expected for `ipPoolName`');
      }
      this.ipPoolName = ipPoolName;
    }
  
    /**
     * Set ASM
     */
    setAsm(asm) {
      if (typeof asm === 'undefined') {
        return;
      }
      if (typeof asm !== 'object') {
        throw new Error('Object expected for `asm`');
      }
      if (typeof asm.groupId !== 'number') {
        throw new Error('Expected `asm` to include an integer in its `groupId` field');
      }
      if (asm.groupsToDisplay &&
        (!Array.isArray(asm.groupsToDisplay) || !asm.groupsToDisplay.every(group => typeof group === 'number'))) {
        throw new Error('Array of integers expected for `asm.groupsToDisplay`');
      }
      this.asm = asm;
    }
  
    /**
     * Set personalizations
     */
    setPersonalizations(personalizations) {
      if (typeof personalizations === 'undefined') {
        return;
      }
      if (!Array.isArray(personalizations) ||
        !personalizations.every(personalization => typeof personalization === 'object')) {
        throw new Error('Array of objects expected for `personalizations`');
      }
  
      //Clear and use add helper to add one by one
      this.personalizations = [];
      personalizations
        .forEach(personalization => this.addPersonalization(personalization));
    }
  
    /**
     * Add personalization
     */
    addPersonalization(personalization) {
  
      //We should either send substitutions or dynamicTemplateData
      //depending on the templateId
      if (this.isDynamic && personalization.substitutions) {
        delete personalization.substitutions;
      } else if (!this.isDynamic && personalization.dynamicTemplateData) {
        delete personalization.dynamicTemplateData;
      }
  
      //Convert to class if needed
      if (!(personalization instanceof Personalization)) {
        personalization = new Personalization(personalization);
      }
  
      //If this is dynamic, set dynamicTemplateData, or set substitutions
      if (this.isDynamic) {
        this.applyDynamicTemplateData(personalization);
      } else {
        this.applySubstitutions(personalization);
      }
  
      //Push personalization to array
      this.personalizations.push(personalization);
    }
  
    /**
     * Convenience method for quickly creating personalizations
     */
    addTo(to, cc, bcc) {
      if (
        typeof to === 'undefined' &&
        typeof cc === 'undefined' &&
        typeof bcc === 'undefined'
      ) {
        throw new Error('Provide at least one of to, cc or bcc');
      }
      this.addPersonalization(new Personalization({to, cc, bcc}));
    }
  
    /**
     * Set substitutions
     */
    setSubstitutions(substitutions) {
      if (typeof substitutions === 'undefined') {
        return;
      }
      if (typeof substitutions !== 'object') {
        throw new Error('Object expected for `substitutions`');
      }
      this.substitutions = substitutions;
    }
  
    /**
     * Set substitution wrappers
     */
    setSubstitutionWrappers(wrappers) {
      if (typeof wrappers === 'undefined') {
        return;
      }
      if (!Array.isArray(wrappers) || wrappers.length !== 2) {
        throw new Error(
          'Array expected with two elements for `substitutionWrappers`'
        );
      }
      this.substitutionWrappers = wrappers;
    }
  
    /**
     * Helper which applies globally set substitutions to personalizations
     */
    applySubstitutions(personalization) {
      if (personalization instanceof Personalization) {
        personalization.reverseMergeSubstitutions(this.substitutions);
        personalization.setSubstitutionWrappers(this.substitutionWrappers);
      }
    }
  
    /**
     * Helper which applies globally set dynamic_template_data to personalizations
     */
    applyDynamicTemplateData(personalization) {
      if (personalization instanceof Personalization) {
        personalization.deepMergeDynamicTemplateData(this.dynamicTemplateData);
      }
    }
  
    /**
     * Set dynamicTemplateData
     */
    setDynamicTemplateData(dynamicTemplateData) {
      if (typeof dynamicTemplateData === 'undefined') {
        return;
      }
      if (typeof dynamicTemplateData !== 'object') {
        throw new Error('Object expected for `dynamicTemplateData`');
      }
  
      // Check dynamic template for non-escaped characters and warn if found
      if (!this.hideWarnings) {
        Object.values(dynamicTemplateData).forEach(value => {
          if (/['"&]/.test(value)) {
            console.warn(DYNAMIC_TEMPLATE_CHAR_WARNING);
          }
        });
      }
  
      this.dynamicTemplateData = dynamicTemplateData;
    }
  
    /**
     * Set content
     */
    setContent(content) {
      if (typeof content === 'undefined') {
        return;
      }
      if (!Array.isArray(content)) {
        throw new Error('Array expected for `content`');
      }
      if (!content.every(contentField => typeof contentField === 'object')) {
        throw new Error('Expected each entry in `content` to be an object');
      }
      if (!content.every(contentField => typeof contentField.type === 'string')) {
        throw new Error('Expected each `content` entry to contain a `type` string');
      }
      if (!content.every(contentField => typeof contentField.value === 'string')) {
        throw new Error('Expected each `content` entry to contain a `value` string');
      }
      this.content = content;
    }
  
    /**
     * Add content
     */
    addContent(content) {
      if (typeof content !== 'object') {
        throw new Error('Object expected for `content`');
      }
      this.content.push(content);
    }
  
    /**
     * Add text content
     */
    addTextContent(text) {
      if (typeof text === 'undefined') {
        return;
      }
      if (typeof text !== 'string') {
        throw new Error('String expected for `text`');
      }
      this.addContent({
        value: text,
        type: 'text/plain',
      });
    }
  
    /**
     * Add HTML content
     */
    addHtmlContent(html) {
      if (typeof html === 'undefined') {
        return;
      }
      if (typeof html !== 'string') {
        throw new Error('String expected for `html`');
      }
      this.addContent({
        value: html,
        type: 'text/html',
      });
    }
  
    /**
     * Set attachments
     */
    setAttachments(attachments) {
      if (typeof attachments === 'undefined') {
        return;
      }
      if (!Array.isArray(attachments)) {
        throw new Error('Array expected for `attachments`');
      }
      if (!attachments.every(attachment => typeof attachment.content === 'string')) {
        throw new Error('Expected each attachment to contain a `content` string');
      }
      if (!attachments.every(attachment => typeof attachment.filename === 'string')) {
        throw new Error('Expected each attachment to contain a `filename` string');
      }
      if (!attachments.every(attachment => attachment.type && typeof attachment.type === 'string')) {
        throw new Error('Expected the attachment\'s `type` field to be a string');
      }
      if (!attachments.every(attachment => attachment.disposition && typeof attachment.disposition === 'string')) {
        throw new Error('Expected the attachment\'s `disposition` field to be a string');
      }
      this.attachments = attachments;
    }
  
    /**
     * Add attachment
     */
    addAttachment(attachment) {
      if (typeof attachment !== 'object') {
        throw new Error('Object expected for `attachment`');
      }
      this.attachments.push(attachment);
    }
  
    /**
     * Set categories
     */
    setCategories(categories) {
      if (typeof categories === 'undefined') {
        return;
      }
      if (typeof categories === 'string') {
        categories = [categories];
      }
      if (!Array.isArray(categories) ||
        !categories.every(cat => typeof cat === 'string')) {
        throw new Error('Array of strings expected for `categories`');
      }
      this.categories = categories;
    }
  
    /**
     * Add category
     */
    addCategory(category) {
      if (typeof category !== 'string') {
        throw new Error('String expected for `category`');
      }
      this.categories.push(category);
    }
  
    /**
     * Set headers
     */
    setHeaders(headers) {
      if (typeof headers === 'undefined') {
        return;
      }
      if (typeof headers !== 'object') {
        throw new Error('Object expected for `headers`');
      }
      this.headers = headers;
    }
  
    /**
     * Add a header
     */
    addHeader(key, value) {
      if (typeof key !== 'string') {
        throw new Error('String expected for header key');
      }
      if (typeof value !== 'string') {
        throw new Error('String expected for header value');
      }
      this.headers[key] = value;
    }
  
    /**
     * Set sections
     */
    setSections(sections) {
      if (typeof sections === 'undefined') {
        return;
      }
      if (typeof sections !== 'object') {
        throw new Error('Object expected for `sections`');
      }
      this.sections = sections;
    }
  
    /**
     * Set custom args
     */
    setCustomArgs(customArgs) {
      if (typeof customArgs === 'undefined') {
        return;
      }
      if (typeof customArgs !== 'object') {
        throw new Error('Object expected for `customArgs`');
      }
      this.customArgs = customArgs;
    }
  
    /**
     * Set tracking settings
     */
    setTrackingSettings(settings) {
      if (typeof settings === 'undefined') {
        return;
      }
      validateTrackingSettings(settings);
      this.trackingSettings = settings;
    }
  
    /**
     * Set mail settings
     */
    setMailSettings(settings) {
      if (typeof settings === 'undefined') {
        return;
      }
      validateMailSettings(settings);
      this.mailSettings = settings;
    }
  
    /**
     * Set hide warnings
     */
    setHideWarnings(hide) {
      if (typeof hide === 'undefined') {
        return;
      }
      if (typeof hide !== 'boolean') {
        throw new Error('Boolean expected for `hideWarnings`');
      }
      this.hideWarnings = hide;
    }
  
    /**
     * To JSON
     */
    toJSON() {
  
      //Extract properties from self
      const {
        from, replyTo, sendAt, subject, content, templateId,
        personalizations, attachments, ipPoolName, batchId, asm,
        sections, headers, categories, customArgs, mailSettings,
        trackingSettings,
      } = this;
  
      //Initialize with mandatory values
      const json = {
        from, subject,
        personalizations: arrayToJSON(personalizations),
      };
  
      //Array properties
      if (Array.isArray(attachments) && attachments.length > 0) {
        json.attachments = arrayToJSON(attachments);
      }
      if (Array.isArray(categories) && categories.length > 0) {
        json.categories = categories.filter(cat => cat !== '');
      }
      if (Array.isArray(content) && content.length > 0) {
        json.content = arrayToJSON(content);
      }
  
      //Object properties
      if (Object.keys(headers).length > 0) {
        json.headers = headers;
      }
      if (Object.keys(mailSettings).length > 0) {
        json.mailSettings = mailSettings;
      }
      if (Object.keys(trackingSettings).length > 0) {
        json.trackingSettings = trackingSettings;
      }
      if (Object.keys(customArgs).length > 0) {
        json.customArgs = customArgs;
      }
      if (Object.keys(sections).length > 0) {
        json.sections = sections;
      }
      if (Object.keys(asm).length > 0) {
        json.asm = asm;
      }
  
      //Simple properties
      if (typeof replyTo !== 'undefined') {
        json.replyTo = replyTo;
      }
      if (typeof sendAt !== 'undefined') {
        json.sendAt = sendAt;
      }
      if (typeof batchId !== 'undefined') {
        json.batchId = batchId;
      }
      if (typeof templateId !== 'undefined') {
        json.templateId = templateId;
      }
      if (typeof ipPoolName !== 'undefined') {
        json.ipPoolName = ipPoolName;
      }
  
      //Return as snake cased object
      return toSnakeCase(json, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers', 'sections']);
    }
  
    /**************************************************************************
     * Static helpers
     ***/
  
    /**
     * Create a Mail instance from given data
     */
    static create(data) {
  
      //Array?
      if (Array.isArray(data)) {
        return data
          .filter(item => !!item)
          .map(item => this.create(item));
      }
  
      //Already instance of Mail class?
      if (data instanceof Mail) {
        return data;
      }
  
      //Create instance
      return new Mail(data);
    }
  }
  
  //Export class
  module.exports = Mail;
  