
// myChatConversations provides functions to generate and retrieve a user's chat conversations
// These conversations can be filtered and sorted based on the recipient's qualities
// Examples: Sort recipients by age, filter recipients who do not fulfill our desires

package myChatConversations

import "seekia/internal/appMemory"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/myChatFilters"
import "seekia/internal/messaging/myChatMessages"
import "seekia/internal/messaging/myReadStatus"
import "seekia/internal/myBlockedUsers"
import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/myIdentity"
import "seekia/internal/mySettings"
import "seekia/internal/profiles/viewableProfiles"

import "slices"
import "sync"
import "errors"


// This mutex will be locked whenever we update chat conversations
var updatingMyChatConversationsMutex sync.Mutex

var myMateChatConversationsMapListDatastore *myMapList.MyMapList
var myModeratorChatConversationsMapListDatastore *myMapList.MyMapList

// This function must be called whenever an app user signs in
func InitializeMyChatConversationsDatastores()error{

	updatingMyChatConversationsMutex.Lock()
	defer updatingMyChatConversationsMutex.Unlock()

	newMyMateChatConversationsMapListDatastore, err := myMapList.CreateNewMapList("MyMateChatConversations")
	if (err != nil) { return err }

	newMyModeratorChatConversationsMapListDatastore, err := myMapList.CreateNewMapList("MyModeratorChatConversations")
	if (err != nil) { return err }

	myMateChatConversationsMapListDatastore = newMyMateChatConversationsMapListDatastore
	myModeratorChatConversationsMapListDatastore = newMyModeratorChatConversationsMapListDatastore

	return nil
}

func getMyConversationsMapListDatastore(identityType string)(*myMapList.MyMapList, error) {

	if (identityType == "Mate"){
		return myMateChatConversationsMapListDatastore, nil
	}
	if (identityType == "Moderator"){
		return myModeratorChatConversationsMapListDatastore, nil		
	}
	
	return nil, errors.New("getMyConversationsMapListDatastore called with invalid identity type: " + identityType)
}

// This function checks if conversations are generated and sorted
func GetMyChatConversationsReadyStatus(identityType string, networkType byte)(bool, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, errors.New("GetMyChatConversationsReadyStatus called with invalid networkType: " + networkTypeString)
	}

	generatedStatus, err := getMyChatConversationsGeneratedStatus(identityType)
	if (err != nil) { return false, err }
	if (generatedStatus == false) {
		return false, nil
	}

	exists, sortedStatus, err := mySettings.GetSetting(identityType + "ChatConversationsSortedStatus")
	if (err != nil) { return false, err }
	if (exists == false || sortedStatus != "Yes") {
		return false, nil
	}

	exists, conversationsNetworkTypeString, err := mySettings.GetSetting(identityType + "ChatConversationsNetworkType")
	if (err != nil) { return false, err }
	if (exists == false){
		// This should not happen, because ...ChatConversationsNetworkType is set whenever conversations are generated
		return false, errors.New("mySettings is missing " + identityType + "ChatConversationsNetworkType when " + identityType + "ChatConversationsGeneratedStatus exists.")
	}
	conversationsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(conversationsNetworkTypeString)
	if (err != nil) {
		return false, errors.New("MySettings contains invalid " + identityType + "ChatConversationsNetworkType: " + conversationsNetworkTypeString)
	}
	if (conversationsNetworkType != networkType) {

		// Conversations must have been generated for a different networkType.
		// This should not happen, because we should only call this function using our current appNetworkType
		// Whenever we change network types, we reset the ChatConversationsGeneratedStatus to No.

		//TODO: Log this.

		err := mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "No")
		if (err != nil) { return false, err }

		return false, nil
	}

	return true, nil
}

// Our conversations will need a refresh any time a new message sent to the user's inboxes is downloaded
func CheckIfMyChatConversationsNeedRefresh(identityType string)(bool, error){

	exists, needsRefresh, err := mySettings.GetSetting(identityType + "ChatConversationsNeedRefreshYesNo")
	if (err != nil) { return true, err }
	if (exists == true && needsRefresh == "No") {
		return false, nil
	}

	return true, nil
}

func getMyChatConversationsGeneratedStatus(identityType string)(bool, error){

	chatMessagesReady, err := myChatMessages.GetMyChatMessagesReadyStatus(identityType)
	if (err != nil) { return false, err }
	if (chatMessagesReady == false) {
		return false, nil
	}

	exists, readyStatus, err := mySettings.GetSetting(identityType + "ChatConversationsGeneratedStatus")
	if (err != nil) { return false, err }
	if (exists == false || readyStatus != "Yes"){
		return false, nil
	}

	return true, nil
}

// This function returns the ready (generated and sorted) chat conversations map list
//Outputs:
//	-bool: Chat conversations are ready
//	-[]map[string]string
//	-error
func GetMyChatConversationsMapList(identityType string, networkType byte)(bool, []map[string]string, error){

	if (identityType != "Mate" && identityType != "Moderator"){
		return false, nil, errors.New("GetMyChatConversationsMapList called with invalid identityType: " + identityType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, nil, errors.New("GetMyChatConversationsMapList called with invalid networkType: " + networkTypeString)
	}

	conversationsReady, err := GetMyChatConversationsReadyStatus(identityType, networkType)
	if (err != nil) { return false, nil, err }
	if (conversationsReady == false) {
		return false, nil, nil
	}

	myReadyConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType)
	if (err != nil) { return false, nil, err }

	myReadyChatConversationsMapList, err := myReadyConversationsMapListDatastore.GetMapList()
	if (err != nil) { return false, nil, err }

	return true, myReadyChatConversationsMapList, nil
}

//Outputs:
//	-bool: Conversations ready
//	-string: Number of conversations
//	-error
func GetNumberOfConversations(identityType string, networkType byte)(bool, int, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, 0, errors.New("GetNumberOfConversations called with invalid networkType: " + networkTypeString)
	}

	conversationsReady, conversationsMap, err := GetMyChatConversationsMapList(identityType, networkType)
	if (err != nil) { return false, 0, err }
	if (conversationsReady == false){
		return false, 0, nil
	}

	numberOfConversations := len(conversationsMap)
	
	return true, numberOfConversations, nil
}

func GetConversationsSortByAttribute(identityType string) (string, error){

	exists, currentAttribute, err := mySettings.GetSetting(identityType + "ChatConversations_SortByAttribute")
	if (err != nil) { return "", err }
	if (exists == false){
		return "MatchScore", nil
	}

	return currentAttribute, nil
}

func GetConversationsSortDirection(identityType string) (string, error){

	exists, sortDirection, err := mySettings.GetSetting(identityType + "ChatConversations_SortDirection")
	if (err != nil) { return "", err }
	if (exists == false){
		return "Descending", nil
	}

	if (sortDirection != "Ascending" && sortDirection != "Descending"){
		return "", errors.New("MySettings malformed: Invalid ChatConversations_SortDirection: " + sortDirection)
	}

	return sortDirection, nil
}

//Outputs:
//	-bool: Conversations ready
//	-string: Number of unread conversations
//	-error
func GetNumberOfUnreadConversations(identityType string, networkType byte)(bool, int, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, 0, errors.New("GetNumberOfUnreadConversations called with invalid networkType: " + networkTypeString)
	}

	conversationsReady, conversationsMapList, err := GetMyChatConversationsMapList(identityType, networkType)
	if (err != nil) { return false, 0, err }
	if (conversationsReady == false){
		return false, 0, nil
	}

	numberOfUnreadConversations := 0

	for _, conversationMap := range conversationsMapList {

		myIdentityHashString, exists := conversationMap["MyIdentityHash"]
		if (exists == false) {
			return false, 0, errors.New("Invalid conversation map: Item missing MyIdentityHash")
		}

		theirIdentityHashString, exists := conversationMap["TheirIdentityHash"]
		if (exists == false) {
			return false, 0, errors.New("Invalid conversation map: Item missing TheirIdentityHash")
		}

		myIdentityHash, myIdentityType, err := identity.ReadIdentityHashString(myIdentityHashString)
		if (err != nil){
			return false, 0, errors.New("Invalid conversation map: Item contains invalid MyIdentityHash: " + myIdentityHashString)
		}

		theirIdentityHash, theirIdentityType, err := identity.ReadIdentityHashString(theirIdentityHashString)
		if (err != nil){
			return false, 0, errors.New("Invalid conversation map: Item contains invalid TheirIdentityHash: " + theirIdentityHashString)
		}
		if (myIdentityType != theirIdentityType){
			return false, 0, errors.New("Invalid conversation map: Item contains mismatched My and Their identityTypes.")
		}
		if (myIdentityType != identityType){
			return false, 0, errors.New("GetMyChatConversationsMapList returning conversation map for different identityType participants.")
		}

		readUnreadStatus, err := myReadStatus.GetConversationReadUnreadStatus(myIdentityHash, theirIdentityHash, networkType)
		if (err != nil) { return false, 0, err }
		if (readUnreadStatus == "Unread"){
			numberOfUnreadConversations += 1
		}
	}

	return true, numberOfUnreadConversations, nil
}


//Outputs:
//	-bool: Build encountered error
//	-string: Error encountered
//	-bool: Build is stopped (will be stopped if user went to different page)
//	-bool: Conversations are ready
//	-float64: Percentage Progress (0 - 1)
//	-error
func GetChatConversationsBuildStatus(identityType string, networkType byte)(bool, string, bool, bool, float64, error){

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, "", false, false, 0, errors.New("GetChatConversationsBuildStatus called with invalid networkType: " + networkTypeString)
	}

	exists, encounteredError := appMemory.GetMemoryEntry(identityType + "ChatConversationsBuildEncounteredError")
	if (exists == false){
		// No build exists. A build has not been started since Seekia was started
		return false, "", false, false, 0, nil
	}
	if (encounteredError == "Yes"){
		exists, errorEncountered := appMemory.GetMemoryEntry(identityType + "ChatConversationsBuildError")
		if (exists == false){
			return false, "", false, false, 0, errors.New("Chat conversations build error encountered error is yes, but no error exists.")
		}

		return true, errorEncountered, false, false, 0, nil
	}

	isStopped := CheckIfBuildMyConversationsIsStopped()
	if (isStopped == true){
		return false, "", true, false, 0, nil
	}

	conversationsReadyBool, err := GetMyChatConversationsReadyStatus(identityType, networkType)
	if (err != nil) { return false, "", false, false, 0, err }
	if (conversationsReadyBool == true){

		return false, "", false, true, 1, nil
	}

	exists, currentPercentageString := appMemory.GetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus")
	if (exists == false){
		// No build exists. A build has not been started since Seekia was started
		return false, "", false, false, 0, nil
	}

	currentPercentageFloat, err := helpers.ConvertStringToFloat64(currentPercentageString)
	if (err != nil){
		return false, "", false, false, 0, errors.New("ChatConversationsReadyProgressStatus is invalid: Not a float: " + currentPercentageString)
	}

	return false, "", false, false, currentPercentageFloat, nil
}

func CheckIfBuildMyConversationsIsStopped()bool{

	exists, buildStoppedStatus := appMemory.GetMemoryEntry("StopBuildMyConversationsYesNo")
	if (exists == false || buildStoppedStatus != "No") {
		return true
	}

	return false
}


// This function will cancel the current build (if one is running)
// It will then start updating our chat messages and conversations list
func StartUpdatingMyConversations(identityType string, networkType byte) error{

	if (identityType != "Mate" && identityType != "Moderator") {
		return errors.New("StartUpdatingMyConversations called with invalid identity type: " + identityType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("StartUpdatingMyConversations called with invalid networkType: " + networkTypeString)
	}

	networkTypeString := helpers.ConvertByteToString(networkType)

	appMemory.SetMemoryEntry("StopBuildMyConversationsYesNo", "Yes")

	// We wait for any existing build to stop
	updatingMyChatConversationsMutex.Lock()

	appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildEncounteredError", "No")
	appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildError", "")	
	appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", "0")

	appMemory.SetMemoryEntry("StopBuildMyConversationsYesNo", "No")

	updateMyChatConversations := func()error{

		myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(identityType)
		if (err != nil) { return err }
		if (myIdentityExists == false){

			// Our identity does not exist.

			// We first update our myChatMessagesMapList ready status by running the below function

			updateProgressFunction := func(_ int)error{
				return nil
			}

			_, err := myChatMessages.GetUpdatedMyChatMessagesMapList(identityType, networkType, updateProgressFunction)
			if (err != nil) { return err }

			// Now we overwrite our conversation datastores with empty map lists.
			// Using the DeleteMapList function achieves this.

			myChatConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType)
			if (err != nil) { return err }

			err = myChatConversationsMapListDatastore.DeleteMapList()
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "Yes")
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsNetworkType", networkTypeString)
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsSortedStatus", "Yes")
			if (err != nil) { return err }

			appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", "1")

			err = mySettings.SetSetting(identityType + "ChatConversationsNeedRefreshYesNo", "No")
			if (err != nil) { return err }

			return nil
		}

		isStopped := CheckIfBuildMyConversationsIsStopped()
		if (isStopped == true) {
			// User has moved to a different page. Stop generating.
			return nil
		}

		getConversationsNeedToBeGeneratedStatus := func()(bool, error){

			messagesReadyStatus, err := myChatMessages.GetMyChatMessagesReadyStatus(identityType)
			if (err != nil) { return false, err }
			if (messagesReadyStatus == false){
				return true, nil
			}

			conversationsGenerated, err := getMyChatConversationsGeneratedStatus(identityType)
			if (err != nil) { return false, err }
			if (conversationsGenerated == false){
				return true, nil
			}

			exists, chatConversationsNetworkTypeString, err := mySettings.GetSetting(identityType + "ChatConversationsNetworkType")
			if (err != nil) { return false, err }
			if (exists == false){
				// This should not happen, because MatchesNetworkType is set to Yes whenever matches are generated
				return false, errors.New("...ChatConversationsNetworkType missing when ...ChatConversationsGeneratedStatus exists.")
			}

			chatConversationsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(chatConversationsNetworkTypeString)
			if (err != nil) {
				return false, errors.New("mySettings contains invalid ...ChatConversationsNetworkType: " + chatConversationsNetworkTypeString)
			}
			if (chatConversationsNetworkType != networkType){
				// This should not happen, because ...ChatConversationsGeneratedStatus should be set to No whenever app network
				//		type is changed, and StartUpdatingMyConversations should only be called with the current app network type.
				return true, nil
			}

			return false, nil
		}

		conversationsNeedToBeGenerated, err := getConversationsNeedToBeGeneratedStatus()
		if (err != nil) { return err }
		if (conversationsNeedToBeGenerated == true){

			// We generate conversations

			err := mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "No")
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsSortedStatus", "No")
			if (err != nil) { return err }

			updatePercentageProgressFunction := func(input int)error{

				// Input is a value between 0-100
				// We reduce it down to a value between 0-50

				newProgressInt, err := helpers.ScaleIntProportionally(true, input, 0, 100, 0, 50)
				if (err != nil) { return err }

				newPercentageProgressFloat := float64(newProgressInt)/100

				newPercentageProgressString := helpers.ConvertFloat64ToString(newPercentageProgressFloat)

				appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", newPercentageProgressString)

				return nil
			}

			myChatMessagesMapList, err := myChatMessages.GetUpdatedMyChatMessagesMapList(identityType, networkType, updatePercentageProgressFunction)
			if (err != nil) { return err }

			appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", ".50")

			// Conversations map stores data as TheirIdentityHash -> MostRecentMessageTime
			conversationsMap := make(map[[16]byte]int64)

			for _, messageMap := range myChatMessagesMapList {

				isStopped := CheckIfBuildMyConversationsIsStopped()
				if (isStopped == true) {
					// User has moved to a different page. Stop generating.
					return nil
				}

				messageMyIdentityHashString, exists := messageMap["MyIdentityHash"]
				if (exists == false){
					return errors.New("Malformed message map: missing MyIdentityHash.")
				}

				messageMyIdentityHash, messageMyIdentityHashIdentityType, err := identity.ReadIdentityHashString(messageMyIdentityHashString)
				if (err != nil){
					return errors.New("Malformed message map: contains invalid MyIdentityHash: " + messageMyIdentityHashString)
				}
				if (messageMyIdentityHashIdentityType != identityType){
					return errors.New("GetUpdatedMyChatMessagesMapList returning message with different identity type.")
				}

				if (messageMyIdentityHash != myIdentityHash){
					// This should not happen, because our old identity's messages should have been deleted.
					return errors.New("GetUpdatedMyChatMessagesMapList returning map list containing message from a different identityHash than our current one for provided identityType.")
				}

				messageTheirIdentityHashString, exists := messageMap["TheirIdentityHash"]
				if (exists == false){
					return errors.New("Malformed message map: Missing TheirIdentityHash.")
				}

				messageTheirIdentityHash, theirIdentityType, err := identity.ReadIdentityHashString(messageTheirIdentityHashString)
				if (err != nil) {
					return errors.New("Malformed message map: Contains invalid TheirIdentityHash: " + messageTheirIdentityHashString)
				}

				if (theirIdentityType != identityType){
					return errors.New("myChatMessagesMapList contains message map between different identity types.")
				}

				messageNetworkType, exists := messageMap["NetworkType"]
				if (exists == false){
					return errors.New("myChatMessagesMapList returning messageMap missing NetworkType.")
				}
				if (messageNetworkType != networkTypeString){
					return errors.New("myChatMessagesMapList returning messageMap with different NetworkType")
				}

				messageCreationTimeString, exists := messageMap["CreationTime"]
				if (exists == false){
					return errors.New("Malformed message map: Missing CreationTime.")
				}

				currentMessageCreationTimeInt64, err := helpers.ConvertStringToInt64(messageCreationTimeString)
				if (err != nil) {
					return errors.New("Malformed message map: Contains invalid CreationTime: " + messageCreationTimeString)
				}

				existingMostRecentMessageCreationTime, exists := conversationsMap[messageTheirIdentityHash]
				if (exists == false || currentMessageCreationTimeInt64 > existingMostRecentMessageCreationTime){

					conversationsMap[messageTheirIdentityHash] = currentMessageCreationTimeInt64
				}
			}

			appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", ".60")

			myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash)
			if (err != nil){
				myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:])
				return errors.New("GetMyIdentityHash returning invalid myIdentityHash: " + myIdentityHashHex)
			}

			newConversationsMapList := make([]map[string]string, 0)

			for theirIdentityHash, conversationMostRecentMessageTime := range conversationsMap {

				isStopped := CheckIfBuildMyConversationsIsStopped()
				if (isStopped == true) {
					// User has moved to a different page. Stop generating.
					return nil
				}

				theyAreBlocked, _, _, _, err := myBlockedUsers.CheckIfUserIsBlocked(theirIdentityHash)
				if (err != nil) { return err }
				if (theyAreBlocked == true) {
					continue
				}

				userPassesChatFilters, err := myChatFilters.CheckIfUserPassesAllMyChatFilters(theirIdentityHash, networkType)
				if (err != nil) { return err }
				if (userPassesChatFilters == false){
					continue
				}

				theirIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(theirIdentityHash)
				if (err != nil){
					theirIdentityHashHex := encoding.EncodeBytesToHexString(theirIdentityHash[:])
					return errors.New("conversationsMap contains invalid theirIdentityHash: " + theirIdentityHashHex)
				}

				mostRecentMessageCreationTimeString := helpers.ConvertInt64ToString(conversationMostRecentMessageTime)

				newConversationMap := map[string]string{
					"MyIdentityHash": myIdentityHashString,
					"TheirIdentityHash": theirIdentityHashString,
					"NetworkType": networkTypeString,
					"MostRecentMessageTime": mostRecentMessageCreationTimeString,
				}

				newConversationsMapList = append(newConversationsMapList, newConversationMap)
			}

			myChatConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType)
			if (err != nil) { return err }

			err = myChatConversationsMapListDatastore.OverwriteMapList(newConversationsMapList)
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "Yes")
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsNetworkType", networkTypeString)
			if (err != nil) { return err }
		}

		isStopped = CheckIfBuildMyConversationsIsStopped()
		if (isStopped == true) {
			// User has moved to a different page. Stop generating.
			return nil
		}

		appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", ".70")

		conversationsReady, err := GetMyChatConversationsReadyStatus(identityType, networkType)
		if (err != nil) { return err }
		if (conversationsReady == false){

			// Now we sort conversations.

			currentSortByAttribute, err := GetConversationsSortByAttribute(identityType)
			if (err != nil) { return err }
			currentSortDirection, err := GetConversationsSortDirection(identityType)
			if (err != nil) { return err }

			myChatConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType)
			if (err != nil) { return err }

			currentConversationsMapList, err := myChatConversationsMapListDatastore.GetMapList()
			if (err != nil) { return err }

			// We use this map to make sure there are no duplicate recipients
			// This should never happen, unless the user's stored map list was edited or there is a bug
			recipientsMap := make(map[[16]byte]struct{})

			// Map structure: Their Identity Hash -> Sort By Attribute Value
			recipientAttributeValuesMap := make(map[string]float64)

			getAllowUnknownViewableStatusBool := func()bool{

				if (identityType == "Mate"){
					return false
				}
				return true
			}

			allowUnknownViewableStatusBool := getAllowUnknownViewableStatusBool()

			maximumIndex := len(currentConversationsMapList) - 1

			for index, conversationMap := range currentConversationsMapList{

				conversationMyIdentityHashString, exists := conversationMap["MyIdentityHash"]
				if (exists == false) {
					return errors.New("Malformed conversation map during sort: item missing MyIdentityHash.")
				}
				conversationMyIdentityHash, conversationMyIdentityHashIdentityType, err := identity.ReadIdentityHashString(conversationMyIdentityHashString)
				if (err != nil){
					return errors.New("Malformed conversation map during sort: contains invalid MyIdentityHash: " + conversationMyIdentityHashString)
				}
				if (conversationMyIdentityHashIdentityType != identityType){
					return errors.New("Malformed conversation map during sort: contains conversation with different identity type.")
				}

				if (conversationMyIdentityHash != myIdentityHash){
					// This should not happen, because our conversations should be regenerated whenever we change our identity
					return errors.New("Malformed conversation map during sort: Contains different identity hash.")
				}

				theirIdentityHashString, exists := conversationMap["TheirIdentityHash"]
				if (exists == false) {
					return errors.New("ChatConversations map list item missing TheirIdentityHash")
				}

				theirIdentityHash, theirIdentityType, err := identity.ReadIdentityHashString(theirIdentityHashString)
				if (err != nil){
					return errors.New("ChatConversations map list item contains invalid theirIdentityHash: " + theirIdentityHashString)
				}
				if (theirIdentityType != identityType){
					return errors.New("ChatConversations map list item contains invalid theirIdentityHash: Different identityType: " + theirIdentityType)
				}

				_, exists = recipientsMap[theirIdentityHash]
				if (exists == true){
					return errors.New("ChatConversations map list is malformed: Contains two conversation maps with the same recipient")
				}
				recipientsMap[theirIdentityHash] = struct{}{}

				profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(theirIdentityHash, networkType, currentSortByAttribute, true, allowUnknownViewableStatusBool, true)
				if (err != nil) { return err }
				if (profileExists == true && attributeExists == true){

					attributeValueFloat, err := helpers.ConvertStringToFloat64(attributeValue)
					if (err != nil) {
						return errors.New("User attribute cannot be converted to float during conversation build: " + attributeValue)
					}

					recipientAttributeValuesMap[theirIdentityHashString] = attributeValueFloat
				}

				isStopped := CheckIfBuildMyConversationsIsStopped()
				if (isStopped == true){
					// User has moved to a different page. Stop generating.
					return nil
				}

				newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 70, 85)
				if (err != nil) { return err }

				newProgressFloat := float64(newScaledPercentageInt)/100

				newProgressString := helpers.ConvertFloat64ToString(newProgressFloat)

				appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", newProgressString)
			}

			compareConversationMapsFunction := func(conversationMapA map[string]string, conversationMapB map[string]string) int {

				identityHashA, exists := conversationMapA["TheirIdentityHash"]
				if (exists == false) {
					panic("Malformed conversations map list: Item missing TheirIdentityHash")
				}
				identityHashB, exists := conversationMapB["TheirIdentityHash"]
				if (exists == false) {
					panic("Malformed conversations map list: Item missing TheirIdentityHash")
				}

				if (identityHashA == identityHashB){
					panic("Malformed conversations map list: Two conversations contain the same TheirIdentityHash.")
				}

				attributeValueA, attributeValueAExists := recipientAttributeValuesMap[identityHashA]

				attributeValueB, attributeValueBExists := recipientAttributeValuesMap[identityHashB]

				if (attributeValueAExists == false && attributeValueBExists == false){

					// We don't know the attribute value for either recipient
					// We sort recipients in unicode order
					if (identityHashA < identityHashB){
						return -1
					}
					return 1

				} else if (attributeValueAExists == true && attributeValueBExists == false){

					// We sort unknown attribute recipient conversations to the back of the list

					return -1

				} else if (attributeValueAExists == false && attributeValueBExists == true){

					return 1
				}

				// Both recipient attribute values exist

				if (attributeValueA == attributeValueB){
					// If values are equal, we want the result to be the same with each refresh
					// We sort the identity hashses in unicode order
					if (identityHashA < identityHashB){
						return -1
					}
					return 1
				}

				if (attributeValueA < attributeValueB){
					if (currentSortDirection == "Ascending"){
						return -1
					}
					return 1
				}
				if (currentSortDirection == "Ascending"){
					return 1
				}
				return -1
			}

			slices.SortFunc(currentConversationsMapList, compareConversationMapsFunction)

			err = myChatConversationsMapListDatastore.OverwriteMapList(currentConversationsMapList)
			if (err != nil) { return err }

			err = mySettings.SetSetting(identityType + "ChatConversationsSortedStatus", "Yes")
			if (err != nil) { return err }
		}

		appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", "1")

		err = mySettings.SetSetting(identityType + "ChatConversationsNeedRefreshYesNo", "No")
		if (err != nil) { return err }

		return nil
	}

	updateFunction := func(){

		err := updateMyChatConversations()
		if (err != nil){
			appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildEncounteredError", "Yes")
			appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildError", err.Error())
		}

		updatingMyChatConversationsMutex.Unlock()
	}

	go updateFunction()
	
	return nil
}



