package gui

// contactsGui.go implements pages to view and manage a user's contacts

import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/container"
import "fyne.io/fyne/v2/widget"
import "fyne.io/fyne/v2/theme"
import "fyne.io/fyne/v2/dialog"
import "fyne.io/fyne/v2/data/binding"
import "fyne.io/fyne/v2/layout"
import "fyne.io/fyne/v2/canvas"

import "seekia/internal/appMemory"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/imagery"
import "seekia/internal/myContacts"
import "seekia/internal/myIdentity"
import "seekia/internal/mySettings"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/profiles/viewableProfiles"

import "image"
import "strings"
import "slices"
import "errors"


func setMyContactsPage(window fyne.Window, identityType string, previousPage func()){

	appMemory.SetMemoryEntry("CurrentViewedPage", "MyContacts")

	if (identityType != "Mate" && identityType != "Moderator" && identityType != "Host"){
		setErrorEncounteredPage(window, errors.New("setMyContactsPage called with invalid identityType: " + identityType), previousPage)
		return
	}

	currentPage := func(){ setMyContactsPage(window, identityType, previousPage) }
	
	title := getPageTitleCentered("My " + identityType + " Contacts")

	backButton := getBackButtonCentered(previousPage)

	addContactButton := getWidgetCentered(widget.NewButtonWithIcon("Add Contact", theme.ContentAddIcon(), func(){
			setAddContactPage(window, currentPage, currentPage)
		}))

	createACategoryButton := widget.NewButtonWithIcon("Create A Category", theme.ContentAddIcon(), func(){
			setCreateAContactCategoryPage(window, identityType, currentPage, currentPage)
		})
	
	deleteACategoryButton := widget.NewButtonWithIcon("Delete A Category", theme.DeleteIcon(), func(){
			setDeleteACategoryPage(window, identityType, currentPage, currentPage)
		})

	buttonsRow := container.NewHBox(layout.NewSpacer(), addContactButton, createACategoryButton, deleteACategoryButton, layout.NewSpacer())

	//TODO: Add change identity type ability?

	myContactsMapList, err := myContacts.GetMyContactsMapList(identityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (len(myContactsMapList) == 0){

		noContactsExistLabel := getBoldLabelCentered("No " + identityType + " contacts exist.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), addContactButton, widget.NewSeparator(), noContactsExistLabel)

		setPageContent(page, window)
		return
	}

	allContactCategoriesList, err := myContacts.GetAllMyContactCategories(identityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	helpers.SortStringListToUnicodeOrder(allContactCategoriesList)

	allCategoryOptionsList := []string{"All Contacts"}
	allCategoryOptionsList = append(allCategoryOptionsList, allContactCategoriesList...)
	allCategoryOptionsList = append(allCategoryOptionsList, "No Category")

	getCurrentCategory := func()(string, error){

		exists, currentCategory, err := mySettings.GetSetting("My" + identityType + "ContactsPageViewedCategory")
		if (err != nil) { return "", err }
		if (exists == false){
			return "All Contacts", nil
		}

		isACategory := slices.Contains(allCategoryOptionsList, currentCategory)
		if (isACategory == false){
			// Category must have been deleted
			return "All Contacts", nil
		}

		return currentCategory, nil
	}

	currentCategory, err := getCurrentCategory()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	categoryLabel := getBoldLabelCentered("Category:")

	categorySelector := widget.NewSelect(allCategoryOptionsList, func(newCategory string){

			err := mySettings.SetSetting("My" + identityType + "ContactsPageViewedCategory", newCategory)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			currentPage()
		})

	categorySelector.Selected = currentCategory

	selectCategoryRow := container.NewHBox(layout.NewSpacer(), categoryLabel, categorySelector, layout.NewSpacer())

	getContactsContainer := func()(*fyne.Container, error){

		appNetworkType, err := getAppNetworkType.GetAppNetworkType()
		if (err != nil) { return nil, err } 

		anyContactsFound := false

		contactsContainer := container.NewVBox()

		for _, contactMap := range myContactsMapList{

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

				if (currentCategory == "All Contacts"){
					return true, nil
				}

				contactCategoriesBase64ListString, exists := contactMap["Categories"]
				if (exists == false){
					if (currentCategory == "No Category"){
						return true, nil
					}
					return false, nil
				}

				contactCategoriesBase64List := strings.Split(contactCategoriesBase64ListString, "+")

				for _, categoryNameBase64 := range contactCategoriesBase64List{

					categoryNameString, err := encoding.DecodeBase64StringToUnicodeString(categoryNameBase64)
					if (err != nil) {
						return false, errors.New("Malformed MyContacts map list: Category name not Base64: " + categoryNameBase64)
					}
			
					if (categoryNameString == currentCategory){
						return true, nil
					}
				}

				return false, nil
			}

			contactBelongsToCurrentCategory, err := checkIfContactBelongsToCurrentCategory()
			if (err != nil) { return nil, err }
			if (contactBelongsToCurrentCategory == false){
				continue
			}

			anyContactsFound = true

			contactIdentityHashString, exists := contactMap["IdentityHash"]
			if (exists == false) {
				return nil, errors.New("Contact map malformed: Missing IdentityHash")
			}

			contactIdentityHash, _, err := identity.ReadIdentityHashString(contactIdentityHashString)
			if (err != nil){
				return nil, errors.New("Contact map malformed: contains invalid IdentityHash: " + contactIdentityHashString)
			}

			contactName, exists := contactMap["Name"]
			if (exists == false) {
				return nil, errors.New("Contact map malformed: Missing Name")
			}

			getContactAvatarOrImage := func()(image.Image, error){

				getAllowUnknownViewableStatusBool := func()bool{

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

				allowUnknownViewableStatusBool := getAllowUnknownViewableStatusBool()

				profileFound, _, retrieveAnyUserProfileAttributeFunction, err := viewableProfiles.GetRetrieveAnyNewestViewableUserProfileAttributeFunction(contactIdentityHash, appNetworkType, true, allowUnknownViewableStatusBool, true)
				if (err != nil) { return nil, err }
				if (profileFound == false){

					emojiImageObject, err := getEmojiImageObject(2929)				
					if (err != nil) { return nil, err }

					return emojiImageObject, nil
				}

				attributeExists, _, photosAttributeValue, err := retrieveAnyUserProfileAttributeFunction("Photos")
				if (err != nil) { return nil, err }
				if (attributeExists == true){

					base64PhotosList := strings.Split(photosAttributeValue, "+")
					firstPhotoBase64 := base64PhotosList[0]

					userImageObject, err := imagery.ConvertJPEGBase64StringToImageObject(firstPhotoBase64)
					if (err != nil) {
						return nil, errors.New("Database corrupt: Contains profile with invalid Photos attribute.")
					}

					return userImageObject, nil
				}

				getContactEmojiIdentifier := func()(int, error){

					attributeExists, _, avatarAttributeValue, err := retrieveAnyUserProfileAttributeFunction("Avatar")
					if (err != nil) { return 0, err }
					if (attributeExists == false){
						return 2929, nil
					}

					userEmojiIdentifier, err := helpers.ConvertStringToInt(avatarAttributeValue)
					if (err != nil) {
						return 0, errors.New("Database corrupt: Contains profile with invalid Avatar attribute: " + avatarAttributeValue)
					}

					return userEmojiIdentifier, nil
				}

				contactEmojiIdentifier, err := getContactEmojiIdentifier()
				if (err != nil) { return nil, err }

				emojiImageObject, err := getEmojiImageObject(contactEmojiIdentifier)				
				if (err != nil) { return nil, err }

				return emojiImageObject, nil
			}

			contactImageObject, err := getContactAvatarOrImage()
			if (err != nil) { return nil, err }

			contactFyneImage := canvas.NewImageFromImage(contactImageObject)
			contactFyneImage.FillMode = canvas.ImageFillContain
			imageSize := getCustomFyneSize(10)
			contactFyneImage.SetMinSize(imageSize)
			imageBoxed := getFyneImageBoxed(contactFyneImage)

			trimmedIdentityHash, _, err := helpers.TrimAndFlattenString(contactIdentityHashString, 20)
			if (err != nil) { return nil, err }
			contactIdentityHashLabel := widget.NewLabel(trimmedIdentityHash)
			contactNameLabel := getBoldLabel(contactName)

			contactNameIdentityHashColumn := getContainerBoxed(container.NewVBox(contactNameLabel, contactIdentityHashLabel))

			viewProfileButton := widget.NewButtonWithIcon("Profile", theme.VisibilityIcon(), func(){
					setViewPeerProfilePageFromIdentityHash(window, contactIdentityHash, currentPage)
				})
			chatButton := widget.NewButtonWithIcon("Chat", theme.MailComposeIcon(), func(){

					myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(identityType)
					if (err != nil){
						setErrorEncounteredPage(window, err, currentPage)
						return
					}
					if (myIdentityExists == true && contactIdentityHash == myIdentityHash){
						dialogTitle := translate("Identity Hash Is Self.")
						dialogMessageA := getLabelCentered("This contact is your own identity.")
						dialogMessageB := getLabelCentered("You cannot chat with yourself.")
						dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
						dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
						return
					}

					setViewAConversationPage(window, contactIdentityHash, true, currentPage)
				})

			manageButton := widget.NewButtonWithIcon("Manage", theme.DocumentCreateIcon(), func(){
					setManageContactPage(window, contactIdentityHash, currentPage)
				})

			row := container.NewHBox(imageBoxed, contactNameIdentityHashColumn, viewProfileButton, chatButton, manageButton)
			rowCentered := getContainerCentered(row)

			contactRow := getContainerBoxed(rowCentered)

			contactsContainer.Add(contactRow)
		}

		if (anyContactsFound == false){

			description1 := getBoldLabelCentered("No contacts belong to this category.")

			categoryNameLabel := getLabelCentered("Category Name:")

			currentCategoryLabel := getBoldLabelCentered(currentCategory)

			categoryNameRow := container.NewHBox(layout.NewSpacer(), categoryNameLabel, currentCategoryLabel, layout.NewSpacer())

			noContactsExistContainer := container.NewVBox(description1, categoryNameRow)

			return noContactsExistContainer, nil		
		}

		return contactsContainer, nil
	}

	contactsContainer, err := getContactsContainer()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsRow, widget.NewSeparator(), selectCategoryRow, widget.NewSeparator(), contactsContainer)

	setPageContent(page, window)
}


func setAddContactPage(window fyne.Window, previousPage func(), nextPage func()){

	currentPage := func(){setAddContactPage(window, previousPage, nextPage)}

	title := getPageTitleCentered("Add Contact")
	
	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Enter the identity hash of your new contact below.")

	identityHashEntry := widget.NewEntry()
	identityHashEntry.SetPlaceHolder("Enter Identity Hash.")
	identityHashEntryBoxed := getWidgetBoxed(identityHashEntry)

	identityHashEntryWithDescriptionGrid := getContainerCentered(container.NewGridWithColumns(1, description, identityHashEntryBoxed))
	
	nextButton := getWidgetCentered(widget.NewButtonWithIcon("Next", theme.NavigateNextIcon(), func(){

			userIdentityHashString := identityHashEntry.Text

			userIdentityHash, userIdentityType, err := identity.ReadIdentityHashString(userIdentityHashString)
			if (err != nil){
				dialogTitle := translate("Invalid Identity Hash")
				dialogMessageA := getLabelCentered("The identity hash you entered is invalid.")
				dialogContent := container.NewVBox(dialogMessageA)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}

			myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(userIdentityType)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			if (myIdentityExists == true && myIdentityHash == userIdentityHash){
				dialogTitle := translate("Identity Hash Is Self")
				dialogMessageA := getLabelCentered("The identity hash you entered is your own.")
				dialogMessageB := getLabelCentered("You cannot add yourself as a contact.")
				dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}

			setAddContactFromIdentityHashPage(window, userIdentityHash, currentPage, nextPage)
		}))
	
	page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashEntryWithDescriptionGrid, nextButton)

	setPageContent(page, window)
}

func setAddContactFromIdentityHashPage(window fyne.Window, userIdentityHash [16]byte, previousPage func(), nextPage func()){

	userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(userIdentityHash)
	if (err != nil) {
		userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:])
		setErrorEncounteredPage(window, errors.New("setAddContactFromIdentityHashPage called with invalid identity hash: " + userIdentityHashHex), previousPage)
		return
	}

	currentPage := func(){setAddContactFromIdentityHashPage(window, userIdentityHash, previousPage, nextPage)}

	title := getPageTitleCentered("Add Contact")
	
	backButton := getBackButtonCentered(previousPage)

	isMyContact, err := myContacts.CheckIfUserIsMyContact(userIdentityHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (isMyContact == true){

		description1 := getBoldLabelCentered("Cannot add contact.")
		description2 := getLabelCentered("User is already a contact.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2)
		setPageContent(page, window)
		return
	}

	enterNameLabel := getBoldLabelCentered("Enter contact name:")
	
	nameEntry := widget.NewEntry()
	nameEntry.SetPlaceHolder("Enter contact name...")
	nameEntryBoxed := getWidgetBoxed(nameEntry)

	wideText := "                                               "
	nameEntryWidenerA := widget.NewLabel(wideText)
	nameEntryWidenerB := widget.NewLabel(wideText)

	enterNameEntryWidened := getContainerCentered(container.NewGridWithColumns(3, nameEntryWidenerA, nameEntryBoxed, nameEntryWidenerB))

	enterDescriptionLabel := getBoldLabelCentered("Enter contact description:")

	descriptionEntry := widget.NewMultiLineEntry()
	descriptionEntry.Wrapping = 3
	descriptionEntry.SetPlaceHolder("Enter description....")
	descriptionEntryBoxed := getWidgetBoxed(descriptionEntry)

	descriptionEntryWidenerA := widget.NewLabel(wideText)
	descriptionEntryWidenerB := widget.NewLabel(wideText)

	enterDescriptionEntryWidened := getContainerCentered(container.NewGridWithColumns(3, descriptionEntryWidenerA, descriptionEntryBoxed, descriptionEntryWidenerB))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), enterNameLabel, enterNameEntryWidened, widget.NewSeparator(), enterDescriptionLabel, enterDescriptionEntryWidened, widget.NewSeparator())

	allContactCategories, err := myContacts.GetAllMyContactCategories(userIdentityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	contactCategoriesListBinding := binding.NewStringList()

	if (len(allContactCategories) != 0){
		
		selectCategoriesLabel := getBoldLabelCentered("Select categories for the contact:")

		getNumberOfGridColumns := func()int{
			numberOfCategories := len(allContactCategories)
			if (numberOfCategories <= 2){
				return numberOfCategories
			}
			return 3
		}

		numberOfGridColumns := getNumberOfGridColumns()

		categoriesGrid := container.NewGridWithColumns(numberOfGridColumns)
		
		for index, categoryName := range allContactCategories{

			categoryNameTrimmed, _, err := helpers.TrimAndFlattenString(categoryName, 7)
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage)
				return
			}

			onCheckedFunction := func(isChecked bool){
				currentContactCategoriesList, err := contactCategoriesListBinding.Get()
				if (err != nil){
					setErrorEncounteredPage(window, err, previousPage)
					return
				}
				if (isChecked == true){
					newContactCategoriesList := helpers.AddItemToStringListAndAvoidDuplicate(currentContactCategoriesList, categoryName)
					contactCategoriesListBinding.Set(newContactCategoriesList)
				} else {
					newContactCategoriesList, _ := helpers.DeleteAllMatchingItemsFromList(currentContactCategoriesList, categoryName)
					contactCategoriesListBinding.Set(newContactCategoriesList)
				}
			}

			categoryCheck := widget.NewCheck(categoryNameTrimmed, onCheckedFunction)
			categoryCheckBoxed := getWidgetBoxed(categoryCheck)
			categoriesGrid.Add(categoryCheckBoxed)

			if (index > 15){
				break
			}
		}

		categoriesGridBoxed := getContainerCentered(getContainerBoxed(categoriesGrid))

		page.Add(selectCategoriesLabel)
		page.Add(categoriesGridBoxed)
	}

	addContactButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Add Contact"), theme.ConfirmIcon(), func(){

		newContactName := nameEntry.Text
		if (newContactName == ""){

			dialogTitle := translate("Name Is Empty")
			dialogMessageA := getLabelCentered(translate("You have not entered a contact name."))
			dialogMessageB := getLabelCentered(translate("Enter a name and retry."))
			dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
			dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
			return
		}

		newContactDescription := descriptionEntry.Text

		contactCategoriesList, err := contactCategoriesListBinding.Get()
		if (err != nil) {
			setErrorEncounteredPage(window, err, currentPage)
			return
		}

		alreadyExists, err := myContacts.AddContact(userIdentityHash, newContactName, contactCategoriesList, newContactDescription)
		if (err != nil) {
			setErrorEncounteredPage(window, err, currentPage)
			return
		}
		if (alreadyExists == true){
			setErrorEncounteredPage(window, errors.New("Contact already exists after checking already."), currentPage)
			return
		}

		nextPage()
	}))

	page.Add(addContactButton)

	setPageContent(page, window)
}


func setManageContactPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func()){
	
	currentPage := func(){setManageContactPage(window, contactIdentityHash, previousPage)}

	title := getPageTitleCentered("Manage Contact")

	backButton := getBackButtonCentered(previousPage)

	contactExists, contactName, contactAddedTime, contactCategoriesList, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash)
	if (err != nil)	{
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}
	if (contactExists == false){		
		setErrorEncounteredPage(window, errors.New("setManageContactPage called with missing contact."), previousPage)
		return
	}

	contactIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(contactIdentityHash)
	if (err != nil){
		contactIdentityHashHex := encoding.EncodeBytesToHexString(contactIdentityHash[:])
		setErrorEncounteredPage(window, errors.New("GetMyContactDetails not verifying identity hash: " + contactIdentityHashHex), previousPage)
		return
	}

	identityHashLabel := widget.NewLabel("Identity Hash:")
	identityHashText := getBoldLabel(contactIdentityHashString)
	viewIdentityHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewIdentityHashPage(window, contactIdentityHash, currentPage)
		})
	contactIdentityHashRow := container.NewHBox(layout.NewSpacer(), identityHashLabel, identityHashText, viewIdentityHashButton, layout.NewSpacer())

	nameLabel := widget.NewLabel("Name:")
	contactNameLabel := getBoldLabel(contactName)
	contactNameRow := container.NewHBox(layout.NewSpacer(), nameLabel, contactNameLabel, layout.NewSpacer())

	contactCategoriesLabel := getLabelCentered("Categories:")

	getContactCategoriesListString := func()(string, error){

		if (len(contactCategoriesList) == 0){
			return "None", nil
		}

		contactCategoriesListString := strings.Join(contactCategoriesList, ", ")

		contactCategoriesListString, _, err := helpers.TrimAndFlattenString(contactCategoriesListString, 20)
		if (err != nil) { return "", err }

		return contactCategoriesListString, nil
	}

	contactCategoriesListString, err := getContactCategoriesListString()
	if (err != nil)	{
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}

	contactCategoriesListLabel := getBoldLabel(contactCategoriesListString)
	contactCategoriesRow := container.NewHBox(layout.NewSpacer(), contactCategoriesLabel, contactCategoriesListLabel, layout.NewSpacer())

	addedAgoString, err := helpers.ConvertUnixTimeToTimeAgoTranslated(contactAddedTime, false)
	addedTimeAgoLabel := getItalicLabelCentered("Contact added " + addedAgoString + ".")

	getContactDescriptionRow := func()(*fyne.Container, error){

		descriptionLabel := widget.NewLabel("Description:")

		if (contactDescription == ""){
			noneLabel := getBoldLabel("None")
			descriptionRow := container.NewHBox(layout.NewSpacer(), descriptionLabel, noneLabel, layout.NewSpacer())
			return descriptionRow, nil
		}

		trimmedDescription, changesOccurred, err := helpers.TrimAndFlattenString(contactDescription, 20)
		if (err != nil) { return nil, err }
		if (changesOccurred == false){
			contactDescriptionLabel := getBoldLabel(contactDescription)
			descriptionRow := container.NewHBox(layout.NewSpacer(), descriptionLabel, contactDescriptionLabel, layout.NewSpacer())
			return descriptionRow, nil
		}

		contactDescriptionTextLabel := getBoldLabel(trimmedDescription)
		viewFullDescriptionButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewTextPage(window, "Viewing Contact Description", contactDescription, false, currentPage)
			})
		contactDescriptionRow := container.NewHBox(layout.NewSpacer(), descriptionLabel, contactDescriptionTextLabel, viewFullDescriptionButton, layout.NewSpacer())
	
		return contactDescriptionRow, nil
	}

	contactDescriptionRow, err := getContactDescriptionRow()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	renameButton := widget.NewButtonWithIcon("Edit Name", theme.DocumentCreateIcon(), func(){
			setRenameContactPage(window, contactIdentityHash, currentPage, currentPage)
		})
	editCategoriesButton := widget.NewButtonWithIcon("Edit Categories", theme.ListIcon(), func(){
			setEditContactCategoriesPage(window, contactIdentityHash, currentPage, currentPage)
		})
	editDescriptionButton := widget.NewButtonWithIcon("Edit Description", theme.DocumentCreateIcon(), func(){
			setEditContactDescriptionPage(window, contactIdentityHash, currentPage, currentPage)
		})
	deleteButton := widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), func(){
			setConfirmDeleteContactPage(window, contactIdentityHash, currentPage, previousPage)
		})
	
	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, renameButton, editCategoriesButton, editDescriptionButton, deleteButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), contactIdentityHashRow, contactNameRow, contactCategoriesRow, contactDescriptionRow, addedTimeAgoLabel, widget.NewSeparator(), buttonsGrid)

	setPageContent(page, window)
}

func setRenameContactPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){

	title := getPageTitleCentered("Rename Contact")

	backButton := getBackButtonCentered(previousPage)

	contactExists, contactName, _, currentContactCategoriesList, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash)
	if (err != nil)	{
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}
	if (contactExists == false){
		setErrorEncounteredPage(window, errors.New("Trying to rename contact that does not exist."), previousPage)
		return
	}

	currentNameLabelA := getLabelCentered("Current Contact Name:")
	currentNameLabelB := getBoldLabelCentered(contactName)

	enterNameLabel := getLabelCentered("Enter new name:")

	enterNameEntry := widget.NewEntry()
	enterNameEntry.SetPlaceHolder("Enter new name.")
	enterNameEntryBoxed := getWidgetBoxed(enterNameEntry)

	changeNameButton := getWidgetCentered(widget.NewButtonWithIcon("Change Name", theme.ConfirmIcon(), func(){

			newName := enterNameEntry.Text

			if (newName == ""){

				dialogTitle := translate("Name Is Empty")
				dialogMessageA := getLabelCentered("You must enter a new name.")
				dialogContent := container.NewVBox(dialogMessageA)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}

			err := myContacts.EditContact(contactIdentityHash, newName, currentContactCategoriesList, contactDescription)
			if (err != nil) {
				setErrorEncounteredPage(window, err, previousPage)
				return
			}
			nextPage()
		}))

	widener := widget.NewLabel("                                                                          ")

	enterNameEntryWithButton := getContainerCentered(container.NewGridWithColumns(1, enterNameEntryBoxed, changeNameButton, widener))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), currentNameLabelA, currentNameLabelB, widget.NewSeparator(), enterNameLabel, enterNameEntryWithButton)

	setPageContent(page, window)
}


func setEditContactCategoriesPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){

	currentPage := func(){setEditContactCategoriesPage(window, contactIdentityHash, previousPage, nextPage)}

	contactIdentityType, err := identity.GetIdentityTypeFromIdentityHash(contactIdentityHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	contactExists, contactName, _, currentContactCategoriesList, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash)
	if (err != nil)	{
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (contactExists == false){
		setErrorEncounteredPage(window, errors.New("Trying to rename contact that does not exist."), previousPage)
		return
	}

	allMyContactCategoriesList, err := myContacts.GetAllMyContactCategories(contactIdentityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	title := getPageTitleCentered("Edit Contact Categories")

	backButton := getBackButtonCentered(previousPage)

	createACategoryButton := getWidgetCentered(widget.NewButtonWithIcon("Create A Category", theme.ContentAddIcon(), func(){
			setCreateAContactCategoryPage(window, contactIdentityType, currentPage, currentPage)
		}))

	if (len(allMyContactCategoriesList) == 0){

		noCategoriesExistLabel := getBoldLabelCentered("No categories exist.")
		
		page := container.NewVBox(title, backButton, widget.NewSeparator(), noCategoriesExistLabel, createACategoryButton)
		setPageContent(page, window)
		return
	}
	
	description1 := getLabelCentered("Select the categories the contact should be a member of.")

	getNumberOfColumns := func()int{
		if (len(allMyContactCategoriesList) == 1){
			return 1			
		}
		return 2
	}

	numberOfColumns := getNumberOfColumns()

	categoryChecksGrid := container.NewGridWithColumns(numberOfColumns)

	// We sort the categories so they show up in the same order every time.

	helpers.SortStringListToUnicodeOrder(allMyContactCategoriesList)

	for _, categoryName := range allMyContactCategoriesList{
	
		handleCheckFunction := func(response bool){

			getNewCategoriesList := func()[]string{

				if (response == true){
					newCategoriesList := append(currentContactCategoriesList, categoryName)
					return newCategoriesList
				}

				newCategoriesList, _ := helpers.DeleteAllMatchingItemsFromList(currentContactCategoriesList, categoryName)
				return newCategoriesList
			}

			newCategoriesList := getNewCategoriesList()

			err := myContacts.EditContact(contactIdentityHash, contactName, newCategoriesList, contactDescription) 
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage)
				return
			}

			currentPage()			
		}

		categoryCheck := widget.NewCheck(categoryName, handleCheckFunction)
		
		contactIsInCategory := slices.Contains(currentContactCategoriesList, categoryName)
		if (contactIsInCategory == true){
			categoryCheck.Checked = true
		}

		categoryCheckBoxed := getWidgetBoxed(categoryCheck)

		categoryChecksGrid.Add(categoryCheckBoxed)
	}

	categoryChecksGridCentered := getContainerCentered(categoryChecksGrid)

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, widget.NewSeparator(), createACategoryButton, widget.NewSeparator(), categoryChecksGridCentered)

	setPageContent(page, window)
}

func setEditContactDescriptionPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){

	currentPage := func(){setEditContactDescriptionPage(window, contactIdentityHash, previousPage, nextPage)}

	title := getPageTitleCentered("Edit Contact Description")

	backButton := getBackButtonCentered(previousPage)

	contactExists, contactName, _, contactCategoriesList, currentContactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash)
	if (err != nil)	{
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}
	if (contactExists == false){
		setErrorEncounteredPage(window, errors.New("Trying to rename contact that does not exist."), previousPage)
		return
	}

	editDescriptionDescription := getLabelCentered("Edit your contact description below.")

	enterDescriptionEntry := widget.NewMultiLineEntry()
	enterDescriptionEntry.Wrapping = 3
	if (currentContactDescription == ""){
		enterDescriptionEntry.SetPlaceHolder("Enter description.")
	} else {
		enterDescriptionEntry.SetText(currentContactDescription)
	}
	enterDescriptionEntryBoxed := getWidgetBoxed(enterDescriptionEntry)

	entryHeightenerA := container.NewVBox(widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""))

	entryHeightenerB := container.NewVBox(widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""))

	changeDescriptionButton := getWidgetCentered(widget.NewButtonWithIcon("Change Description", theme.ConfirmIcon(), func(){

			newContactDescription := enterDescriptionEntry.Text

			err := myContacts.EditContact(contactIdentityHash, contactName, contactCategoriesList, newContactDescription)
			if (err != nil) {
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			nextPage()
		}))

	enterDescriptionEntryWithButton := container.NewBorder(nil, changeDescriptionButton, entryHeightenerA, entryHeightenerB, enterDescriptionEntryBoxed)

	page := container.NewVBox(title, backButton, widget.NewSeparator(), editDescriptionDescription, enterDescriptionEntryWithButton)

	setPageContent(page, window)
}

func setConfirmDeleteContactPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){

	currentPage := func(){setConfirmDeleteContactPage(window, contactIdentityHash, previousPage, nextPage)}

	contactExists, contactName, contactAddedTime, _, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (contactExists == false){
		setErrorEncounteredPage(window, errors.New("setConfirmDeleteContactPage called with non-contact identity hash"), previousPage)
		return
	}

	title := getPageTitleCentered("Confirm Delete Contact")

	backButton := getBackButtonCentered(previousPage)

	description1 := getBoldLabelCentered("Delete contact?")
	
	description2 := getLabelCentered("The user will be removed from your contacts.")

	contactIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(contactIdentityHash)
	if (err != nil){
		contactIdentityHashHex := encoding.EncodeBytesToHexString(contactIdentityHash[:])
		setErrorEncounteredPage(window, errors.New("setConfirmDeleteContactPage not verifying identity hash: " + contactIdentityHashHex), previousPage)
	}

	identityHashTitle := widget.NewLabel("Identity Hash:")
	identityHashLabel := getBoldLabel(contactIdentityHashString)
	identityHashRow := container.NewHBox(layout.NewSpacer(), identityHashTitle, identityHashLabel, layout.NewSpacer())

	contactNameLabel := widget.NewLabel("Contact Name:")
	contactNameText := getBoldLabel(contactName)
	contactNameRow := container.NewHBox(layout.NewSpacer(), contactNameLabel, contactNameText, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), identityHashRow, contactNameRow)

	if (contactDescription != ""){
		contactDescriptionLabel := widget.NewLabel("Contact Description:")
		contactDescriptionTrimmed, changesOccurred, err := helpers.TrimAndFlattenString(contactDescription, 20)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		contactDescriptionTextLabel := getBoldLabel(contactDescriptionTrimmed)
		if (changesOccurred == false){
			
			contactDescriptionRow := container.NewHBox(layout.NewSpacer(), contactDescriptionLabel, contactDescriptionTextLabel, layout.NewSpacer())

			page.Add(contactDescriptionRow)
		} else {
			viewFullDescriptionButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewTextPage(window, "Viewing Contact Description", contactDescription, false, currentPage)
				})
			
			contactDescriptionRow := container.NewHBox(layout.NewSpacer(), contactDescriptionLabel, contactDescriptionTextLabel, viewFullDescriptionButton, layout.NewSpacer())

			page.Add(contactDescriptionRow)
		}
	}

	addedAgoString, err := helpers.ConvertUnixTimeToTimeAgoTranslated(contactAddedTime, false)
	addedTimeAgoLabel := getItalicLabelCentered("Contact added " + addedAgoString + ".")
	page.Add(addedTimeAgoLabel)
	
	deleteContactButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm Delete", theme.DeleteIcon(), func(){
			err := myContacts.DeleteContact(contactIdentityHash)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			nextPage()
		}))
	page.Add(deleteContactButton)

	setPageContent(page, window)
}


func setCreateAContactCategoryPage(window fyne.Window, identityType string, previousPage func(), nextPage func()){

	currentPage := func(){setCreateAContactCategoryPage(window, identityType, previousPage, nextPage)}

	title := getPageTitleCentered("Create " + identityType + " Contact Category")

	backButton := getBackButtonCentered(previousPage)

	description1 := getLabelCentered("Create a " + identityType + " contact category.")

	enterNameLabel := getBoldLabelCentered("Enter category name:")

	descriptionsContainer := container.NewVBox(description1, enterNameLabel)

	enterNameEntry := widget.NewEntry()
	enterNameEntry.SetPlaceHolder("Enter category name.")
	enterNameEntryBoxed := getWidgetBoxed(enterNameEntry)

	enterNameEntryWithDescriptions := getContainerCentered(container.NewGridWithColumns(1, descriptionsContainer, enterNameEntryBoxed))

	contactCategoriesList, err := myContacts.GetAllMyContactCategories(identityType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	createCategoryButton := getWidgetCentered(widget.NewButtonWithIcon("Create Category", theme.ConfirmIcon(), func(){
			categoryName := enterNameEntry.Text
			if (categoryName == ""){
				dialogTitle := translate("Name Is Empty")
				dialogMessageA := getLabelCentered("You must enter a category name.")
				dialogContent := container.NewVBox(dialogMessageA)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}
			categoryAlreadyExists := slices.Contains(contactCategoriesList, categoryName)
			if (categoryAlreadyExists == true){
				dialogTitle := translate("Category Already Exists")
				dialogMessageA := getLabelCentered("The category you are trying to create already exists.")
				dialogContent := container.NewVBox(dialogMessageA)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}
			err := myContacts.AddContactCategory(identityType, categoryName)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			nextPage()
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), enterNameEntryWithDescriptions, createCategoryButton)
	
	if (len(contactCategoriesList) != 0){

		page.Add(widget.NewSeparator())
		
		existingCategoriesLabel := getItalicLabelCentered("Existing Categories:")
		page.Add(existingCategoriesLabel)
		
		for _, categoryName := range contactCategoriesList{
			categoryNameLabel := getBoldLabelCentered(categoryName)
			page.Add(categoryNameLabel)
		}
	}
	
	setPageContent(page, window)
}


func setDeleteACategoryPage(window fyne.Window, identityType string, previousPage func(), nextPage func()){

	currentPage := func(){setDeleteACategoryPage(window, identityType, previousPage, nextPage)}

	title := getPageTitleCentered("Delete " + identityType + " Contact Category")

	backButton := getBackButtonCentered(previousPage)

	myContactCategoriesList, err := myContacts.GetAllMyContactCategories(identityType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (len(myContactCategoriesList) == 0){
		
		description1 := getBoldLabelCentered("No " + identityType + " contact categories exist.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1)

		setPageContent(page, window)
		return
	}
	
	description := getLabelCentered("Select the contact category to delete.")

	// We sort the categories so they show up in the same order every time.

	helpers.SortStringListToUnicodeOrder(myContactCategoriesList)

	categorySelector := widget.NewSelect(myContactCategoriesList, nil)
	categorySelector.PlaceHolder = "Select Category..."

	categorySelectorCentered := getWidgetCentered(categorySelector)

	deleteButton := getWidgetCentered(widget.NewButtonWithIcon("Delete Category", theme.DeleteIcon(), func(){

			categoryToDeleteIndex := categorySelector.SelectedIndex()
			if (categoryToDeleteIndex == -1){

				dialogTitle := translate("No Category Selected")
				dialogMessageA := getLabelCentered("You must select a category to delete.")
				dialogContent := container.NewVBox(dialogMessageA)
				dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
				return
			}

			categoryToDelete := categorySelector.Selected

			err := myContacts.DeleteContactCategory(identityType, categoryToDelete)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			nextPage()
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), categorySelectorCentered, deleteButton)

	setPageContent(page, window)
}




