<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <h1>See comments and open the console</h1>
  </body>

</html>
// Stub object
var spService = {
  ConvertSelectPropertiesToViewFields(){
    return 'fake';
  },
  GetListItems(){
    return Promise.resolve({
      getEnumerator(){
        var index = 0;
        var createJobValue = name => ({
          get_lookupValue(){
            return name;
          }
        });
        var createFakeItem = (id) => ({
          get_item(propertyName){
            if(propertyName == 'PwC_JobCodesMulti') return [createJobValue('alpha'), createJobValue('beta'), createJobValue('gama')];
            if(propertyName == 'ID') return id;
            return 'value for '+propertyName;
          }
        });
        var fakeItems = [
          createFakeItem(1),
          createFakeItem(2),
          createFakeItem(3)
        ];
        return {
          moveNext(){
            index++;
            return index < fakeItems.length;
          },
          get_current(){
            return fakeItems[index];
          }
        }
      }
    });
  }
};

function GetRelatedBillingDocumentsFromList(selectProperties, currentBillCyclePath, clientCode, jobCodes, engagementCode, enhanceFunctions) {
  
  var webUrl = 'some URL';
  var viewFields = 'im not using it in this mock example';
  var camlQuery = 'im not using it in this mock example';
  var billCyclesListId = '{c23bbae4-34f7-494c-8f67-acece3ba60da}';
  
  return spService
          .GetListItems(billCyclesListId, camlQuery, selectProperties)
          .then(getEnumerator)
          .then(enumeratorToArray)
          .then(processListItems);
  
  function getEnumerator(result){
    return result.getEnumerator();
  }
  
  /*
  Helper function to avoid checking if listItem is defined later, and get rid of enumerator
  */
  function enumeratorToArray(enumerator){
    var temp = [];
    if(!enumerator) return temp;
    while (enumerator.moveNext()){
      temp.push(enumerator.get_current());
    }
    return temp;
  }
  
  /*
  At this point we always have an empty or filled array!
  */
  function listItemToValue(listItem){
    return selectProperties
      .map(extractListItemProperty)
      .map(stringifyJobTitle)
      .reduce(fillObject, {});
      
    function extractListItemProperty(property) {
      return {
        property,
        item: listItem.get_item(property)
      }
    }
    
    function extractLookupValue(jobValue) {
      return jobValue.get_lookupValue();
    }
    
    function stringifyJobTitle(value) {
      var jobTitle = value.item;
      return {
        property: value.property,
        jobTitle: Array.isArray(jobTitle) ? jobTitle.map(extractLookupValue).join(';') : jobTitle
      };
    };
    
    function fillObject(objectToFill, value) {
      objectToFill[value.property] = value.jobTitle;
      return objectToFill;
    };
  }
  
  function addContentType(listItem){
    var promise = getContentTypeOfCurrentItem(listItem.ID.toString());
    promise.then(function(cname){
      listItem['Document Type'] = cname; //we add the doc type to each listItem, not only the last one
    });
    return promise;
  }
  
  function processListItems(listItems) {
    var listItemsWithValues = listItems.map(listItemToValue);
    
    var promises = listItemsWithValues.map(addContentType);
    Promise.all(promises).then(youCanUseTheData);
    
    function youCanUseTheData(){
      /*
      At this point, each listItem holds the 'Document Type' info
      */
      listItemsWithValues.forEach(function (listItem) {
        console.log(listItem);
      });
    }
    
  }
}

function getContentTypeOfCurrentItem(id) {
  return Promise.resolve('fake id for '+id);
}

/*
TESTING 
*/
GetRelatedBillingDocumentsFromList(['ID', 'PwC_JobCodesMulti']);
/* Styles go here */

// cname is always undefined, because getContentTypeOfCurrentItem returns nothing
var cname = getContentTypeOfCurrentItem(listItemValues['ID'].toString());
listItemsWithValues['Document Type'] = cname;


function GetRelatedBillingDocumentsFromList(selectProperties, currentBillCyclePath, clientCode, jobCodes, engagementCode, enhanceFunctions) {
  $log.info('Retrieving related billing documents for bill cycle with name [' + currentBillCyclePath + ']');
  var deferred = $q.defer();
  var webUrl = _spPageContextInfo.webAbsoluteUrl;
  var viewFields = spService.ConvertSelectPropertiesToViewFields(selectProperties);
  // query must return the documents for the same client but in other bill cycles not the current one
  var camlQuery = '<View Scope="RecursiveAll">' + viewFields +
    '<Query>' +
    '<Where>' +
    '<And>' +
    '<Eq>' +
    '<FieldRef Name="ClientCode" />' +
    '<Value Type="Text">' + clientCode + '</Value>' +
    '</Eq>' +
    '<Neq>' +
    '<FieldRef Name="ContentType" />' +
    '<Value Type="Computed">Bill Cycle</Value>' +
    '</Neq>' +
    '</And>' +
    '</Where>' +
    '</Query>' +
    '</View>';

  var billCyclesListId = '{c23bbae4-34f7-494c-8f67-acece3ba60da}';
  spService.GetListItems(billCyclesListId, camlQuery, selectProperties)
    .then(function (listItems) { 
      var listItemsWithValues = []; // creating an empty array to get filled later is a map

      if (listItems) {
        var enumerator = listItems.getEnumerator();
        var promises = [];
        while (enumerator.moveNext()) {
          var listItem = enumerator.get_current();
          var listItemValues = []; // this is an object, not an array, you're using it as a dictionary
          selectProperties
            .forEach(function (propertyName) {
              var value = listItem.get_item(propertyName);
              if (propertyName === 'PwC_JobCodesMulti') {
                jobvalue = '';
                value.forEach(function (jobvalues) {
                  jobvalue += jobvalues.get_lookupValue() + ';';
                });
                listItemValues[propertyName] = jobvalue;
              } else {
                listItemValues[propertyName] = value;
              }
              //listItemValues[propertyName] = value;
            });

          listItemsWithValues.push(listItemValues);
        }

        // you're using only the last ocurrence here, i think is not the intention
        var cname = getContentTypeOfCurrentItem(listItemValues['ID'].toString());
        listItemsWithValues['Document Type'] = cname;
      }

      listItemsWithValues.forEach(function (listItem) {
        var fileDirRef = listItem['FileRef'];
        var id = listItem['ID'];
        var title = listItem['Title'];
        var serverUrl = _spPageContextInfo.webAbsoluteUrl.replace(_spPageContextInfo.webServerRelativeUrl, '');
        var dispFormUrl = serverUrl + '/sites/billing/_layouts/15/DocSetHome.aspx?id=' + fileDirRef;
        //listItem["FileRef"] = dispFormUrl;
        //listItem["Bill Cycle"] = dispFormUrl;

        var parentLink = listItem['FileRef'];
        arrayofstrings = parentLink.split('/');
        var billCycleFolderName = arrayofstrings[arrayofstrings.length - 2];
        arrayofstrings.pop();
        var hyperLink = '<a href="' + arrayofstrings.join('/') + '">' + billCycleFolderName + '</a>';
        listItem['Bill Cycle'] = hyperLink;

      });

      var enhancedListItemValues = spService.SpSearchQuery.EnhanceSearchResults(listItemsWithValues, enhanceFunctions);
      deferred.resolve(listItemsWithValues);
    })
    .catch(function (message) {
      deferred.reject();
    });

  return deferred.promise;
}

function getContentTypeOfCurrentItem(id) {

  var clientContext = new SP.ClientContext.get_current();
  var oList = clientContext.get_web().get_lists().getByTitle('Bill Cycles');
  listItem = oList.getItemById(id);
  clientContext.load(listItem);
  listContentTypes = oList.get_contentTypes();
  clientContext.load(listContentTypes);
  clientContext.executeQueryAsync(
    // whats the reference to this at this point? i think its undefined or window, because you're calling getContentTypeOfCurrentItem() directly
    Function.createDelegate(this, getContentTypeOfCurrentItemSucceeded),
    function (error, errorInfo) {
      $log.warn('Retrieving list item result failed');
      deferred.reject(errorInfo); // deferred is not available in this scope!
    }
  );
}

function getContentTypeOfCurrentItemSucceeded(sender, args) {
  var ctid = listItem.get_item('ContentTypeId').toString(); // listItem is not available at this point
  var ct_enumerator = listContentTypes.getEnumerator();
  while (ct_enumerator.moveNext()) {
    var ct = ct_enumerator.get_current();
    if (ct.get_id().toString() == ctid) {
      var contentTypeName = ct.get_name();
      return contentTypeName; // what's the point of this return? being an OK callback, who handles this value?
    }
  }
}