--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


local PLUGIN = PLUGIN

-- Util Functions:
-- A function to sync apartment name and type with the client (for ESP)
function PLUGIN:SyncApartments(client)
	if !CAMI.PlayerHasAccess(client, "Helix - Observer Extra ESP", nil) then return end

	local nameAndType = self:AppGetNameAndType()

	for _, v in pairs(player.GetAll()) do
		if !CAMI.PlayerHasAccess(v, "Helix - Observer Extra ESP", nil) then continue end
		netstream.Start(v, "SyncApartments", nameAndType)
	end
end

-- A function to sync the apartment doors, name and type with the client (for ESP) on initial spawn
function PLUGIN:SyncToOneApartments(client, appID, foundApartment, genericData, bDatafile)
	if (appID) then
		local nameTable = {}
		appID = tonumber(appID)
		if !self.apartments[appID] then return end

		nameTable[appID] = {
			name = self.apartments[appID].name
		}

		netstream.Start(client, "SyncApartments", nameTable, true, bDatafile)
		return
	end

	if foundApartment and genericData then
		local names = self:GetAppNames()
		netstream.Start(client, "TerminalUpdateHousing", foundApartment, names, genericData)
	end

	if !CAMI.PlayerHasAccess(client, "Helix - Observer Extra ESP", nil) then return end

	local nameAndType = self:AppGetNameAndType()
	netstream.Start(client, "SyncApartments", nameAndType)
end

function PLUGIN:SyncApartmentsToDatapad(client, curCollect)
	if !self:HasAccessToDatafile(client) then return end

	local toSend = {}
	local curApartments = {}
	local curShops = {}

	for appID, v in pairs(self.apartments) do
		v.appID = appID

		if v.type == "normal" then
			curApartments[#curApartments + 1] = v
			continue
		end

		curShops[#curShops + 1] = v
	end

	for i = curCollect - 4, curCollect do
		if curApartments[i] then
			toSend[#toSend + 1] = curApartments[i]
		end

		if curShops[i] then
			toSend[#toSend + 1] = curShops[i]
		end
	end

	netstream.Start(client, "SyncApartmentsToDatapad", toSend)
end

-- A function to get doors, type and names from the apartments table
function PLUGIN:AppGetNameAndType()
	local namesAndType = {}
	for appID, tApartment in pairs(self.apartments) do
		namesAndType[appID] = {
			name = tApartment.name,
			doors = tApartment.doors,
			type = tApartment.type
		}
	end

	return namesAndType
end

-- A function to get names from the apartments table
function PLUGIN:GetAppNames()
	local names = {}
	for appID, tApartment in pairs(self.apartments) do
		names[appID] = {
			name = tApartment.name
		}
	end

	return names
end

-- A function to update an apartment in the database
function PLUGIN:UpdateApartment(appID)
	local queryUpdate = mysql:Update("ix_apartments_"..game.GetMap())
		queryUpdate:Where("app_id", appID)
		queryUpdate:Update("app_name", self.apartments[appID].name)
		local doors = {}
		for _, v in pairs(self.apartments[appID].doors) do
			if (IsValid(Entity(v))) then
				local id = Entity(v):MapCreationID()
				if (id == -1) then continue end
				doors[#doors + 1] = id
			end
		end
		queryUpdate:Update("app_doors", util.TableToJSON(doors))
		queryUpdate:Update("app_employees", util.TableToJSON(self.apartments[appID].employees))
		queryUpdate:Update("app_permits", util.TableToJSON(self.apartments[appID].permits))
		queryUpdate:Update("app_tenants", util.TableToJSON(self.apartments[appID].tenants))
		queryUpdate:Update("app_lastactions", util.TableToJSON(self.apartments[appID].lastActions))
		queryUpdate:Update("app_rent", self.apartments[appID].rent)
		queryUpdate:Update("app_payments", util.TableToJSON(self.apartments[appID].payments))
		queryUpdate:Update("app_type", self.apartments[appID].type)
		queryUpdate:Update("app_noassign", self.apartments[appID].noAssign)
		queryUpdate:Update("app_rentdue", self.apartments[appID].rentDue)
	queryUpdate:Execute()
end

local function HandlePermitUpdateForEachChar(newPermit, permits, char, charID, isEmployee)
	if char then
		local genericData = char:GetGenericdata()
		local charPermits = ix.permits.get()
		if isEmployee then
			for k, _ in pairs(charPermits) do
				charPermits[k] = false
			end
			for k, v in pairs(genericData.permits) do
				charPermits[k] = v
			end
			for k, v in pairs(permits) do
				if charPermits[k] != v then return end
			end
		end

		char:SetPermit(newPermit, !permits[newPermit])
	else
		local query = mysql:Select("ix_characters_data")
		query:Where("key", "genericdata")
		query:Where("id", tostring(charID))
		query:Select("data")
		query:Callback(function(result)
			if (!istable(result) or #result == 0) then
				return
			end
			local genericData = util.JSONToTable(result[1]["data"])

			local charPermits = ix.permits.get()
			if isEmployee then
				for k, _ in pairs(charPermits) do
					charPermits[k] = false
				end
				for k, v in pairs(genericData.permits) do
					charPermits[k] = v
				end
				for k, v in pairs(permits) do
					if charPermits[k] != v then return end
				end
			end
			if !permits[newPermit] then
				genericData.permits[newPermit] = !permits[newPermit]
			else
				genericData.permits[newPermit] = nil
			end
			local addPermit = mysql:Update("ix_characters_data")
				addPermit:Where("id", tostring(charID))
				addPermit:Where("key", "genericdata")
				addPermit:Update("data", util.TableToJSON(genericData))
			addPermit:Execute()
		end)
		query:Execute()
	end
end

function PLUGIN:HandleShopPermitUpdate(appID, newPermit)
	local apartment = self.apartments[appID]
	if apartment.type != "shop" then return end
	local permits = {}
	for permit, bValue in pairs(apartment.permits) do
		permits[permit] = bValue
	end
	permits[newPermit] = !permits[newPermit]
	for cid, employee in pairs(apartment.employees) do
		local char
		local charID
		self:LookUpCardItemIDByCID(tostring(cid), function(result)
			local idCardID = result[1].idcard or false
			if !result then return end
			if !ix.item.instances[idCardID] then
				ix.item.LoadItemByID(idCardID, false, function(item)
					if !item then return end
					char = ix.char.loaded[item:GetData("owner")]
					charID = item:GetData("owner")

					HandlePermitUpdateForEachChar(newPermit, permits, char, charID, true)
				end)
			else
				char = ix.char.loaded[ix.item.instances[idCardID]:GetData("owner")]
				charID = ix.item.instances[idCardID]:GetData("owner")

				HandlePermitUpdateForEachChar(newPermit, permits, char, charID, true)
			end
		end)
	end

	for cid, tenant in pairs(apartment.tenants) do
		local char
		local charID
		self:LookUpCardItemIDByCID(tostring(cid), function(result)
			local idCardID = result[1].idcard or false
			if !result then return end
			if !ix.item.instances[idCardID] then
				ix.item.LoadItemByID(idCardID, false, function(item)
					if !item then return end
					char = ix.char.loaded[item:GetData("owner")]
					charID = item:GetData("owner")

					HandlePermitUpdateForEachChar(newPermit, permits, char, charID, false)
				end)
			else
				char = ix.char.loaded[ix.item.instances[idCardID]:GetData("owner")]
				charID = ix.item.instances[idCardID]:GetData("owner")

				HandlePermitUpdateForEachChar(newPermit, permits, char, charID, false)
			end
		end)
	end
end

-- A function to check if a door is already added to an apartment
function PLUGIN:CheckIfDoorAlreadyAdded(client, entDoor, bNoNotify)
	for key, tInfo in pairs(self.apartments) do
		for _, doorIndex in pairs(tInfo.doors) do
			if doorIndex == entDoor:EntIndex() then
				if !bNoNotify then
					client:NotifyLocalized("Cette porte est déjà ajoutée à un appartement ! - "..tInfo.name)
				end

				return key, tInfo.name
			end
		end
	end

	return false
end

-- A function to check if an apartment exists before adding a door to it
function PLUGIN:CheckIfApartmentExists(client, appName)
	for key, tInfo in pairs(self.apartments) do
		if string.lower(tInfo.name) == string.lower(appName) then
			return key
		end
	end

	return false
end

-- A function check what apartment a CID belongs to
function PLUGIN:GetApartmentByCID(cid, type)
	for key, tInfo in pairs(self.apartments) do
		if (type and type:find("shop")) then
			if tInfo.type != "shop" then continue end
		end

		if (type and type:find("apartment")) then
			if tInfo.type == "shop" then continue end
		end

		if tInfo.tenants[cid] or tInfo.employees[cid] then
			return key
		end
	end

	return false
end

-- A function to find the apartment to assign
function PLUGIN:GetWhichApartmentToAssign(appType)
	local appTenants = {}
	local maxTenants = ix.config.Get("housingMaxTenants")
	for appID, tApartment in pairs(self.apartments) do
		if tApartment.noAssign == true then continue end
		if table.IsEmpty(tApartment.doors) then continue end
		if table.Count(tApartment.tenants) >= maxTenants then continue end
		if tApartment.type != appType then continue end

		appTenants[appID] = table.Count(tApartment.tenants)
	end

	local fillOrder = {}
	for i = 0, maxTenants - 1 do
		fillOrder[#fillOrder + 1] = i
	end
	if (maxTenants >= 2) then
		if (ix.config.Get("housingFirstFull")) then
			fillOrder = table.Reverse(fillOrder)
		elseif (ix.config.Get("housingFirstDouble")) then
			fillOrder[1] = 1
			fillOrder[2] = 0
		end
	end

	for _, i in ipairs(fillOrder) do
		for appID2, tenantCount in pairs(appTenants) do
			if tenantCount == i then
				return appID2
			end
		end
	end

	return false
end

function PLUGIN:InventoryItemAdded(oldInv, inventory, item)
	if (oldInv != nil) then return end

	if (item.uniqueID == "apartmentkey" or item.uniqueID == "shopkey") then
		PLUGIN:UpdateItemIDTenant(item:GetData("cid"), item:GetID(), item.uniqueID)
		PLUGIN:UpdateItemIDEmployee(item:GetData("cid"), item:GetID(), item.uniqueID)
	end
end

-- A function to get what apartment the door is assigned to
function PLUGIN:GetDoorApartment(client, entDoor)
	if !self:CheckIfDoorAlreadyAdded(client, entDoor) then
		client:NotifyLocalized("Impossible de trouver un appartement auquel cette porte est attribuée...")
	end
end

-- A function to check if a key has access
function PLUGIN:GetKeyHasAccess(item, entDoor, bNoNotify)
	local client = item:GetOwner()
	local _, appName = self:CheckIfDoorAlreadyAdded(client, entDoor, true)
	local itemCID = item:GetData("cid") or false
	local itemID = item:GetID()

	for appID, tApartment in pairs(self.apartments) do
		if !tApartment.tenants[itemCID] and !tApartment.employees[itemCID] then continue end
		for _, doorIndex in pairs(tApartment.doors) do
			if doorIndex != entDoor:EntIndex() then continue end

			if tApartment.tenants[itemCID] and tApartment.tenants[itemCID].key == itemID then return appID, itemCID
			elseif tApartment.employees[itemCID] and tApartment.employees[itemCID].key == itemID then return appID, itemCID end
		end
	end

	if !bNoNotify then
		client:NotifyLocalized("You don't have access to this housing!")
	end

	return false
end

-- A function get if a key already has access to another apartment
function PLUGIN:GetKeyAlreadyHasOtherAccess(item)
	local itemCID = item:GetData("cid") or false

	for key, tApartment in pairs(self.apartments) do
		local tenant = tApartment.tenants[itemCID]
		if (tenant and tenant != false) then return key end
	end

	return false
end

function PLUGIN:HasCIDInInventory(client, cid)
	local bFound = false
	for _, v in pairs(client:GetCharacter():GetInventory():GetItems()) do
		if v.uniqueID != "id_card" then continue end
		if v:GetData("cid", false) and tonumber(v:GetData("cid", false)) == tonumber(cid) then
			bFound = v
			break
		end
	end

	return bFound
end

function PLUGIN:RequestAssignedApartmentInfo(client, cid, bShop)
	if !self:HasCIDInInventory(client, cid) then return end

	local assignedApp
	if !bShop then assignedApp = self:GetApartmentByCID(cid, "apartment") end
	if bShop then assignedApp = self:GetApartmentByCID(cid, "shop") end
	if !assignedApp then
		netstream.Start(client, "ReplyApartmentInfo", false, false)
		return
	end

	netstream.Start(client, "ReplyApartmentInfo", self.apartments[assignedApp], assignedApp, bShop)
end

function PLUGIN:ToggleApartmentNoAssign(client, appID)
	if !appID then client:NotifyLocalized("Invalid apartment") return end

	if self.apartments[appID].noAssign then
		self.apartments[appID].noAssign = false
		client:NotifyLocalized("Faire basculer l'affectation de cet appartement sur faux.")
	else
		self.apartments[appID].noAssign = true
		client:NotifyLocalized("Faire basculer l'affectation de cet appartement sur vrai.")
	end

	self:UpdateApartment(appID)
end

function PLUGIN:AddDoorInteraction(client, appID, cid)
	self.apartments[appID].lastActions[cid] = os.time()
	self:UpdateApartment(appID)
end

function PLUGIN:PayRent(client, amount, appID, cid)
	local cidItem = self:HasCIDInInventory(client, cid)
	if !cidItem then return false end

	local cidData = cidItem:GetData("cid")
	if !cidData then return false end

	if cidItem:GetCredits() < amount then
		netstream.Start(client, "SendHousingErrorMessage", "PAS ASSEZ DE CRÉDITS")
		return
	end

	cidData = Schema:ZeroNumber(cidData, 5)

	if self.apartments[appID].payments[cidData] then
		self.apartments[appID].payments[cidData] = {amount = amount + self.apartments[appID].payments[cidData].amount, date = os.time()}
	else
		self.apartments[appID].payments[cidData] = {amount = amount, date = os.time()}
	end

	cidItem:TakeCredits(amount, "Housing", "Rent")
	ix.city.main:AddCredits(amount)

	self:UpdateApartment(appID)

	netstream.Start(client, "UpdateHousingPaymentPanel", self.apartments[appID], appID, amount)
end

function PLUGIN:SetTenantAutoPayment(client, appID, cid, bEnabled)
	local cidItem = self:HasCIDInInventory(client, cid)
	if !cidItem then return false end

	local cidData = cidItem:GetData("cid")
	if !cidData then return false end

	cidData = Schema:ZeroNumber(cidData, 5)

	self.apartments[appID].tenants[cidData].autoPay = bEnabled
	self:UpdateApartment(appID)
end

function PLUGIN:HasAccessToDatafile(client)
	local combineutilities = ix.plugin.list["combineutilities"]
	if !combineutilities then return end
	if (!combineutilities:HasAccessToDatafile(client)) then return false end

	return true
end

-- Hooks

netstream.Hook("SetTenantAutoPayment", function(client, appID, cid, bEnabled)
	PLUGIN:SetTenantAutoPayment(client, appID, cid, bEnabled)
end)

hook.Add("CanPlayerAccessDoor", "CheckForKeyItem", function(client, entDoor)
	for _, item in ipairs(client:GetCharacter():GetInventory():GetItemsByUniqueID("apartmentkey")) do
		local appID, cid = PLUGIN:GetKeyHasAccess(item, entDoor, true)
		if (appID and cid) then
			PLUGIN:AddDoorInteraction(client, appID, cid)
			return true
		end
	end

	for _, item in ipairs(client:GetCharacter():GetInventory():GetItemsByUniqueID("shopkey")) do
		local appID, cid = PLUGIN:GetKeyHasAccess(item, entDoor, true)
		if (appID and cid) then
			PLUGIN:AddDoorInteraction(client, appID, cid)
			return true
		end
	end

	return false
end)

hook.Add("CAMI.PlayerUsergroupChanged", "SyncApartmentsToOne", function(client)
	PLUGIN:SyncToOneApartments(client)
end)

netstream.Hook("RequestApartmentNames", function(client, appID)
	PLUGIN:SyncToOneApartments(client, appID)
end)

function PLUGIN:RefreshApartmentsPanel(client)
	if !CAMI.PlayerHasAccess(client, "Helix - Manage Apartments", nil) then return end

	local nameAndType = PLUGIN:AppGetNameAndType()

	for appID, tApartment in pairs(PLUGIN.apartments) do
		nameAndType[appID].rent = tApartment.rent
	end

	netstream.Start(client, "SyncApartmentsApartmentsPanel", nameAndType)
end

netstream.Hook("RequestApartmentNamesApartmentsPanel", function(client)
	PLUGIN:RefreshApartmentsPanel(client)
end)

netstream.Hook("CreateApartmentApartmentsUI", function(client, type, name)
	PLUGIN:CreateApartment(client, name, false, type)
	PLUGIN:RefreshApartmentsPanel(client)
end)

netstream.Hook("RequestApartmentNamesDatafile", function(client, appID)
	PLUGIN:SyncToOneApartments(client, appID, false, false, true)
end)

netstream.Hook("ixApartmentsRequest", function(client, cid, type)
	PLUGIN:HandleAssignment(client, cid, type)
end)

netstream.Hook("RequestAssignedApartmentInfo", function(client, cid, bShop)
	PLUGIN:RequestAssignedApartmentInfo(client, cid, bShop)
end)

netstream.Hook("ixApartmentsAssignKeyToApartment", function(client, keyItemID, appID, bRemove)
	if !CAMI.PlayerHasAccess(client, "Helix - Manage Apartments Key", nil) then return end
	if !ix.item.instances[keyItemID] then return end

	if !bRemove then
		ix.item.instances[keyItemID]:AssignToApartment(appID)
	else
		ix.item.instances[keyItemID]:RemoveApartment()
	end
end)

netstream.Hook("ixApartmentsAssignCIDToKey", function(client, keyItemID, cid)
	if !CAMI.PlayerHasAccess(client, "Helix - Manage Apartments Key", nil) then return end
	if !ix.item.instances[keyItemID] then return end

	ix.item.instances[keyItemID]:SetCID(cid)
end)

netstream.Hook("PayRent", function(client, amount, appID, cid)
	PLUGIN:PayRent(client, amount, appID, cid)
end)

netstream.Hook("RequestApartmentNamesDatapad", function(client, curCollect)
	PLUGIN:SyncApartmentsToDatapad(client, curCollect)
end)

netstream.Hook("ChangeApartmentName", function(client, appID, newName)
	if !CAMI.PlayerHasAccess(client, "Helix - Manage Apartments", nil) then return end
	if !PLUGIN.apartments[appID] then return end
	PLUGIN.apartments[appID].name = newName
	PLUGIN:UpdateApartment(appID)

	for _, doorEntID in pairs(PLUGIN.apartments[appID].doors) do
		local entDoor = Entity(doorEntID)
		if (IsValid(entDoor) and entDoor:IsDoor()) then
			entDoor:SetNetVar("visible", true)
			entDoor:SetNetVar("name", newName)

			local doorPlugin = ix.plugin.list["doors"]
			if doorPlugin then
				doorPlugin:SaveDoorData()
			end
		end
	end

	netstream.Start(client, "UpdateApartmentList")
end)

netstream.Hook("RemoveApartmentHousing", function(client, name)
	PLUGIN:RemoveApartment(client, name)
	netstream.Start(client, "UpdateApartmentList")
end)

netstream.Hook("RemoveTenant", function(client, tenantCID, appID)
	if !PLUGIN:HasAccessToDatafile(client) then return end
	if !PLUGIN.apartments[appID] then return end
	if !PLUGIN.apartments[appID].tenants[tenantCID] then
		for newAppID, tApartment in pairs(PLUGIN.apartments) do
			if !tApartment.tenants[tenantCID] then continue end

			PLUGIN:RemoveTenant(client, tenantCID, newAppID, true)
		end

		return
	end

	PLUGIN:RemoveTenant(client, tenantCID, appID, true)
end)

netstream.Hook("RemoveEmployee", function(client, employeeCID, appID)
	if !PLUGIN:HasAccessToDatafile(client) and !PLUGIN.apartments[appID].tenants[client:GetCharacter():GetGenericdata().cid] then return end
	if !PLUGIN.apartments[appID] then return end
	if !PLUGIN.apartments[appID].employees[employeeCID] then
		for newAppID, tApartment in pairs(PLUGIN.apartments) do
			if !tApartment.employees[employeeCID] then continue end

			PLUGIN:RemoveEmployee(client, employeeCID, newAppID, true)
		end

		return
	end

	PLUGIN:RemoveEmployee(client, employeeCID, appID, true)
end)

function PLUGIN.RenewKey(idCard, genericData, client, appID, bEmployee)
	if !PLUGIN:HasAccessToDatafile(client) then return end

	if !idCard then return end
	local cid = idCard:GetData("cid")
	if !cid or istable(cid) then client:NotifyLocalized("CID IS TABLE") return end

	if !PLUGIN.apartments[appID] then return end
	if !bEmployee then
		if !PLUGIN.apartments[appID].tenants[cid] then return end
	else
		if !PLUGIN.apartments[appID].employees[cid] then return end
	end
	local character = ix.char.loaded[genericData.id]
	if character then
		local appType = PLUGIN.apartments[appID].type
		if !character:GetPurchasedItems()[appType == "shop" and "shopkey" or "apartmentkey"] then
			character:SetPurchasedItems(appType == "shop" and "shopkey" or "apartmentkey", 1)
		else
			client:NotifyLocalized("Il y a déjà une clé qui attend au terminal de collecte pour ce CID.")
		end
		if !bEmployee then
			PLUGIN.apartments[appID].tenants[cid].key = true
		else
			PLUGIN.apartments[appID].employees[cid].key = true
		end
		PLUGIN:UpdateApartment(appID)
		client:NotifyLocalized("Succès. Leur clé actuelle est désactivée et ils peuvent maintenant prendre une nouvelle clé dans le terminal de collecte.")
		netstream.Start(client, "UpdateIndividualApartment", appID, PLUGIN.apartments[appID])
		return
	end

	client:NotifyLocalized("Echec. La personne n'est pas en ligne, donc aucune clé peut lui être réassignée.")
end

netstream.Hook("RenewKey", function(client, tenantCID, appID, bEmployee)
	if !PLUGIN:HasAccessToDatafile(client) then return end
	if !PLUGIN.apartments[appID] then return end
	tenantCID = Schema:ZeroNumber(tenantCID, 5)

	if !PLUGIN.apartments[appID].tenants[tenantCID] and !PLUGIN.apartments[appID].employees[tenantCID] then return end

	PLUGIN:LookUpCardItemIDByCID(tostring(tenantCID), function(result)
		local idCardID = result[1].idcard or false
		if !result then client:NotifyLocalized("This CID doesn't exist!") return end

		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(item)
				if !item then return end
				item:LoadOwnerGenericData(PLUGIN.RenewKey, false, client, appID, bEmployee)
			end)
		else
			ix.item.instances[idCardID]:LoadOwnerGenericData(PLUGIN.RenewKey, false, client, appID, bEmployee)
		end
	end)
end)

function PLUGIN.AddEmployeeManually(idCard, genericData, client, appID)
	local cid = idCard:GetData("cid")
	if !cid or istable(cid) then client:NotifyLocalized("CID IS TABLE") return end
	if PLUGIN.apartments[appID].type != "shop" then return client:NotifyLocalized("This is not a shop!") end
	cid = Schema:ZeroNumber(idCard:GetData("cid"), 5)
	for newAppID, tApartment in pairs(PLUGIN.apartments) do
		if tApartment.type != "shop" then continue end
		if tApartment.employees[cid] then return client:NotifyLocalized(tostring(cid) .. " is already being an employee in " .. PLUGIN.apartments[newAppID].name) end
		if tApartment.tenants[cid] then return client:NotifyLocalized(tostring(cid) .. " is already being a tenant in " .. PLUGIN.apartments[newAppID].name) end
	end
	local character = ix.char.loaded[genericData.id]
	if character then
		for k, v in pairs(PLUGIN.apartments[appID].permits) do
			character:SetPermit(k, v)
		end
		if PLUGIN:AddKeyToApartment(client, cid, appID, nil, nil, true) then
			local appType = PLUGIN.apartments[appID].type
			local item = (appType != "shop" and "apartmentkey" or "shopkey")
			if !character:GetPurchasedItems()[item] then
				character:SetPurchasedItems(item, 1)
			else
				client:NotifyLocalized("There is already a"..(appType != "shop" and "n apartment" or " shop").." key waiting at the pickup dispenser for this cid. It has been updated.")
			end
			netstream.Start(client, "UpdateIndividualApartment", appID, PLUGIN.apartments[appID])
		end
		netstream.Start(client, "AddEmployeeToList", PLUGIN.apartments[appID], cid, appID)
	else
		client:NotifyLocalized("Fail. The character was not online, so no employee was assigned.")
	end
end

function PLUGIN.AddTenantManually(idCard, genericData, client, appID)
	local cid = idCard:GetData("cid")
	if !cid or istable(cid) then client:NotifyLocalized("CID IS TABLE") return end
	cid = Schema:ZeroNumber(idCard:GetData("cid"), 5)

	if PLUGIN.apartments[appID].employees[cid] then return client:NotifyLocalized("You can't assign person who's already is an employee as a tenant.") end

	if PLUGIN.apartments[appID].type == "shop" then
		if table.IsEmpty(genericData.permits) then
			client:NotifyLocalized("Ce CID n'a pas de permis, mais a quand même été ajouté.")
		end
	end

	if PLUGIN.apartments[appID].type == "shop" then
		genericData.shop = appID
	else
		genericData.housing = appID
	end

	local combineutilities = ix.plugin.list["combineutilities"]
	if combineutilities and combineutilities.UpdateGenericData then
		combineutilities:UpdateGenericData(genericData)
	end


	local character = ix.char.loaded[genericData.id]
	if character then
		if PLUGIN.apartments[appID].type == "shop" then
			for k, v in pairs(PLUGIN.apartments[appID].permits) do
				character:SetPermit(k, v)
			end
		end
		if PLUGIN:AddKeyToApartment(client, cid, appID) then
			local appType = PLUGIN.apartments[appID].type
			local item = (appType != "shop" and "apartmentkey" or "shopkey")

			if !character:GetPurchasedItems()[item] then
				character:SetPurchasedItems(item, 1)
			else
				client:NotifyLocalized("Il existe déjà un"..(appType != "shop" and "apartement" or " commerce").." avec une clé dans un terminal de collecte.")
			end

			netstream.Start(client, "UpdateIndividualApartment", appID, PLUGIN.apartments[appID])
		end

		if PLUGIN.apartments[appID].type == "shop" then
			for _, shopTerminal in pairs(ents.FindByClass("ix_shopterminal")) do
				shopTerminal:UpdateScreen()
			end
		end
	else
		client:NotifyLocalized("Échec. Le personnage n'était pas en ligne, donc aucun locataire n'a été assigné.")
	end
end

netstream.Hook("AddTenant", function(client, appID, cid)
	if !PLUGIN:HasAccessToDatafile(client) then return end
	if !appID or !cid then return end

	cid = Schema:ZeroNumber(cid, 5)

	if !PLUGIN:CheckIfCIDExistsLoaded(cid) then
		if !PLUGIN:CheckIfCIDExistsDatabase(cid) then
			client:NotifyLocalized("Ce CID n'existe pas !")
			return
		end

		client:NotifyLocalized("Ce CID n'existe pas !")
		return
	end

	PLUGIN:LookUpCardItemIDByCID(tostring(cid), function(result)
		local idCardID = result[1].idcard or false
		if !result then client:NotifyLocalized("Ce CID n'existe pas !") return end

		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(item)
				if !item then return end
				item:LoadOwnerGenericData(PLUGIN.AddTenantManually, false, client, appID)
			end)
		else
			ix.item.instances[idCardID]:LoadOwnerGenericData(PLUGIN.AddTenantManually, false, client, appID)
		end
	end)
end)

netstream.Hook("AddEmployee", function(client, appID, cid)
	if !PLUGIN:HasAccessToDatafile(client) and !PLUGIN.apartments[appID].tenants[client:GetCharacter():GetGenericdata().cid] then return end
	if !appID or !cid then return end
	if PLUGIN.apartments[appID].type != "shop" then return client:NotifyLocalized("This is not a shop!") end


	cid = Schema:ZeroNumber(cid, 5)

	if !PLUGIN:CheckIfCIDExistsLoaded(cid) then
		if !PLUGIN:CheckIfCIDExistsDatabase(cid) then
			client:NotifyLocalized("This CID doesn't exist!")
			return
		end

		client:NotifyLocalized("This CID doesn't exist!")
		return
	end

	PLUGIN:LookUpCardItemIDByCID(tostring(cid), function(result)
		local idCardID = result[1].idcard or false
		if !result then client:NotifyLocalized("This CID doesn't exist!") return end

		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(item)
				if !item then return end
				item:LoadOwnerGenericData(PLUGIN.AddEmployeeManually, false, client, appID)
			end)
		else
			ix.item.instances[idCardID]:LoadOwnerGenericData(PLUGIN.AddEmployeeManually, false, client, appID)
		end
	end)
end)

netstream.Hook("ApartmentUpdateRent", function(client, appID, newRent)
	if !PLUGIN:HasAccessToDatafile(client) then return end

	PLUGIN:SetApartmentRent(client, appID, false, newRent)
	netstream.Start(client, "UpdateIndividualApartment", appID, PLUGIN.apartments[appID])
end)

netstream.Hook("ApartmentExtendRentDueDateByDays", function(client, appID, days)
	if !PLUGIN:HasAccessToDatafile(client) then return end
	PLUGIN.apartments[appID].rentDue = PLUGIN.apartments[appID].rentDue + 3600 * 24 * tonumber(days)
	PLUGIN:UpdateApartment(appID)

	netstream.Start(client, "UpdateIndividualApartment", appID, PLUGIN.apartments[appID])
end)

netstream.Hook("BuyShopViaTerminal", function(client, ent, permits)
	if !IsValid(ent) then return end

	if ent:IsPurchasable() then
		local shopID = ent:GetShopID()
		local character = client:GetCharacter()
		local idCard = character:GetInventory():HasItem("id_card")

		if (client:EyePos():DistToSqr(ent:GetPos()) > 62500) then return end
		if !shopID then return client:NotifyLocalized("Can't find any shop with this ID.") end
		if !idCard then return client:NotifyLocalized("You don't have CID in your inventory!") end
		if !permits or #table.GetKeys(permits) > 3 then return end
		if character:GetGenericdata().socialCredits < ent:GetShopSocialCreditReq() then
			return client:NotifyLocalized("You don't have enough social credits!")
		end

		if !idCard:HasCredits(ent:GetShopCost()) then
			return client:NotifyLocalized("You don't have enough credits!")
		end

		idCard:TakeCredits(ent:GetShopCost(), "Shop Terminal", "Shop bought.")
		local idCardNumber = idCard:GetData("cid", "00000")

		for i = 1, #PLUGIN.apartments do
			local apartment = PLUGIN.apartments[i]
			if apartment.type == "shop" and apartment.tenants[idCardNumber] then
				return client:NotifyLocalized("You already own a shop!")
			end
		end

		for k, _ in pairs(PLUGIN.apartments[shopID].permits) do
			if permits[k] then
				PLUGIN.apartments[shopID].permits[k] = true
			else
				PLUGIN.apartments[shopID].permits[k] = false
			end
		end

		idCard:LoadOwnerGenericData(PLUGIN.AddTenantManually, false, client, shopID)
	end
end)