-----------------------------------
--Controller for the Personnel UI
-----------------------------------

local DataSaver = require("lib_data_saver")

local Class = require("lib_class")

local PersonnelController = Class()

--- Called by the class constructor
--- @return nil
function PersonnelController:init()
	self.Document = nil ---@type Document the RML document
	self.Key = nil ---@type number the key for the game progression
	self.SelectedEntry = nil ---@type string the id of the selected entry
	self.SaveData = nil ---@type table the data for the entries seen
end

--- Called by the RML document
--- @param document Document
function PersonnelController:initialize(document)

    self.Document = document

    ---Load background choice
    self.Document:GetElementById("main_background"):SetClass(ScpuiSystem:getBackgroundClass(), true)

    ---Load the desired font size from the save file
	self.Document:GetElementById("main_background"):SetClass(("base_font" .. ScpuiSystem:getFontPixelSize()), true)

    self.SaveData = DataSaver:loadDataFromFile("personnelSeen", true) or {}

	--Get the key for the game progression
	self.Key = self:getPlayerProgress()

	Personnel:sortEntries(self.Key)

    self.SelectedEntry = nil

    self:createEntries()

end

--- Get the player's game progression
--- @return number progress the key for the game progression
function PersonnelController:getPlayerProgress()
	local BtA = require("btafunctions")
	return BtA:getGameProgress()
end

--- Get the fully resolved personnel entry at the player's progression
--- Applies all overrides whose Key <= self.Key cumulatively
--- @param entry personnel_entry
--- @return table resolved
function PersonnelController:getResolvedEntry(entry)

	-- Start with a shallow copy of the base entry
	local resolved = {}

	for k, v in pairs(entry) do
		if k ~= "Progression" then
			resolved[k] = v
		end
	end

	if not entry.Progression or not self.Key then
		return resolved
	end

	-- Sort overrides ascending by key (stable progression layering)
	table.sort(entry.Progression, function(a, b)
		return tonumber(a.Key) < tonumber(b.Key)
	end)

	for _, override in ipairs(entry.Progression) do
		local k = tonumber(override.Key)

		if k and self.Key >= k then
			for field, value in pairs(override) do
				if field ~= "Key" and value ~= nil then
					resolved[field] = value
				end
			end
		end
	end

	return resolved
end

--- Sort the entries by affiliation, then by last name, then by first name
--- @return nil
function PersonnelController:sortEntries()

	local function splitName(name)
		local first, last = name:match("(%S+)%s+(%S+)")
		return first, last
	end

    table.sort(Personnel.entries, function(a, b)
        -- First compare affiliations
        if a.Affiliation ~= b.Affiliation then
            return a.Affiliation < b.Affiliation
        end

        -- If affiliations are the same, compare last names
        local a_first, a_last = splitName(a.Name)
        local b_first, b_last = splitName(b.Name)

        if a_last ~= b_last then
            return a_last < b_last
        end

        -- If last names are the same, compare first names
        return a_first < b_first
    end)
end

--- Format the name to be used as a button id
--- @param name string the name to format
--- @param suffix string the suffix to add
--- @return string result the formatted name
function PersonnelController:formatNameToButton(name, suffix)
	local lower_name = name:lower()
	local formatted_name = lower_name:gsub("%s", "_")
	return formatted_name .. suffix
end

--- Create the entry item element
--- @param entry personnel_entry the entry to create
--- @return Element el the element created
function PersonnelController:createEntryItem(entry)

	local last_read = self.SaveData[entry.Name]

	local function insertCallsign(name, callsign)
		local first, last = name:match("(%S+)%s+(%S+)")

		if first and last and callsign and callsign ~= "" then
			return first .. ' "' .. callsign .. '" ' .. last
		else
			return name
		end
	end

	local resolved = self:getResolvedEntry(entry)

	local callsign = resolved.Callsign
	local affiliation = resolved.Affiliation

    local el = self.Document:CreateElement("div")
	el.id = self:formatNameToButton(entry.Name, "_button")
	el:SetClass("portrait", true)
	el:SetClass("button_1", true)
    el:AddEventListener("click", function(_, _, _)
        self:selectEntry(entry)
    end)

    local img_el = self.Document:CreateElement("img")
	img_el:SetAttribute("src", entry.File)
	el:AppendChild(img_el)

	local name_el = self.Document:CreateElement("div")
	name_el:SetClass("name", true)
	name_el.inner_rml = insertCallsign(entry.Name, callsign)
	el:AppendChild(name_el)

	local new_el = self.Document:CreateElement("div")
	new_el.id = self:formatNameToButton(entry.Name, "_new")
	new_el:SetClass("new", true)
	new_el.inner_rml = "!"
	el:AppendChild(new_el)
	if not last_read or last_read < self.Key then
		new_el:SetClass("hidden", false)
	else
		new_el:SetClass("hidden", true)
	end

	if affiliation then
		local faction = Factions:getFactionByAcronym(affiliation)
		local logo_el = self.Document:CreateElement("img")
		logo_el:SetClass("logo", true)
		logo_el:SetAttribute("src", faction.Logo)
		el:AppendChild(logo_el)
	end

    return el

end

--- Create the entry elements for all the personnel
--- @return nil
function PersonnelController:createEntries()

	local parent_el = self.Document:GetElementById("portrait_grid")

	local faction = nil
	local faction_group_el = nil

	local locked_saved_data = DataSaver:loadDataFromFile("personnelLocks", true) or {}

    for i, v in ipairs(Personnel.entries) do
		local resolved = self:getResolvedEntry(v)

		local visible = not resolved.Locked
		local affiliation = resolved.Affiliation

		if not affiliation then
			affiliation = "OTHER"
		end

		--Check if the entry has been unlocked in the save file
		if locked_saved_data[v.Name] then
			visible = true
		end

		if visible then
			ba.print("Adding entry " .. i .. ": " .. v.Name .. "\n" )

			if faction ~= affiliation then
				faction = affiliation
				local faction_el = self.Document:CreateElement("div")
				faction_el:SetClass("faction", true)
				faction_el.inner_rml = Factions:getFactionByAcronym(affiliation).DisplayName
				parent_el:AppendChild(faction_el)

				faction_group_el = self.Document:CreateElement("div")
				faction_group_el:SetClass("faction_group", true)
				parent_el:AppendChild(faction_group_el)
			end
			if faction_group_el then
				faction_group_el:AppendChild(self:createEntryItem(v))
			end
		end
    end
end

--- Create a detail element for a bio
--- @param header string the header for the detail
--- @param value string the value for the detail
--- @return Element el the element created
function PersonnelController:createBioDetail(header, value)
	local el = self.Document:CreateElement("div")
	el:SetClass("bio_detail", true)

	local header_el = self.Document:CreateElement("span")
	header_el:SetClass("bio_header", true)
	header_el.inner_rml = header .. ": "
	el:AppendChild(header_el)

	local value_el = self.Document:CreateElement("span")
	value_el:SetClass("bio_value", true)
	value_el.inner_rml = value
	el:AppendChild(value_el)

	return el
end

--- Format the height to be used in the bio
--- @param height number the height to format
--- @return string result the formatted height
function PersonnelController:formatHeight(height)
	return string.format("%.2fm", height)
end

--- Format the weight to be used in the bio
--- @param weight number the weight to format
--- @return string result the formatted weight
function PersonnelController:formatWeight(weight)
	return string.format("%.1fkg", weight)
end

--- Select an entry and display its details
--- @param entry personnel_entry the entry to select
--- @return nil
function PersonnelController:selectEntry(entry)

	local key = self:formatNameToButton(entry.Name, "_button")

    if key ~= self.SelectedEntry then

		if self.SelectedEntry then
			local old_entry = self.Document:GetElementById(self.SelectedEntry)
			if old_entry then old_entry:SetPseudoClass("checked", false) end
		end

		local this_entry = self.Document:GetElementById(key)
		self.SelectedEntry = key
		this_entry:SetPseudoClass("checked", true)

		self.SaveData[entry.Name] = self.Key
		DataSaver:saveDataToFile("personnelSeen", self.SaveData, true)

		local new_el = self.Document:GetElementById(self:formatNameToButton(entry.Name, "_new"))
		new_el:SetClass("hidden", true)

		local resolved = self:getResolvedEntry(entry)

		local biography = resolved.Biography
		local callsign = resolved.Callsign
		local rank = resolved.Rank
		local position = resolved.Position
		local posting = resolved.Posting
		local status = resolved.Status
		local commendations = resolved.Commendations
		local height = resolved.Height
		local weight = resolved.Weight
		local gender = resolved.Gender
		local species = resolved.Species
		local affiliation = resolved.Affiliation

		local details_el = self.Document:GetElementById("portrait_details")
		ScpuiSystem:clearEntries(details_el)

		local name_el = self:createBioDetail("Name", entry.Name)
		name_el:SetClass("bio_name", true)
		name_el:SetClass("bio_detail", false)
		details_el:AppendChild(name_el)

		if callsign then
			local el = self:createBioDetail("Callsign", callsign)
			details_el:AppendChild(el)
		end
		if rank then
			local el = self:createBioDetail("Rank", rank)
			details_el:AppendChild(el)
		end
		if position then
			local el = self:createBioDetail("Position", position)
			details_el:AppendChild(el)
		end
		if posting then
			local el = self:createBioDetail("Posting", posting)
			details_el:AppendChild(el)
		end
		if status then
			local el = self:createBioDetail("Status", status)
			details_el:AppendChild(el)
		end
		if commendations then
			local el = self:createBioDetail("Commendations", commendations)
			details_el:AppendChild(el)
		end
		if height then
			local el = self:createBioDetail("Height", self:formatHeight(height))
			details_el:AppendChild(el)
		end
		if weight then
			local el = self:createBioDetail("Weight", self:formatWeight(weight))
			details_el:AppendChild(el)
		end
		if gender then
			local el = self:createBioDetail("Pronouns", gender)
			details_el:AppendChild(el)
		end
		if species then
			local el = self:createBioDetail("Species", species)
			details_el:AppendChild(el)
		end
		if affiliation then
			local el = self:createBioDetail("Affiliation", Factions:getFactionByAcronym(affiliation).DisplayName)
			details_el:AppendChild(el)
		end

		local bio_el = self.Document:CreateElement("div")
		bio_el:SetClass("bio_full", true)
		bio_el.inner_rml = tostring(biography)
		details_el:AppendChild(bio_el)
	end

end

--- Called by the RML document when the exit button is pressed
--- @param element Element The main document element
--- @return nil
function PersonnelController:exit(element)

    ui.playElementSound(element, "click", "success")
    ScpuiSystem:returnToState(ScpuiSystem.data.LastState)
    self.Document:Close()

end

--- Global keydown function handles all keypresses
--- @param element Element The main document element
--- @param event Event The event that was triggered
--- @return nil
function PersonnelController:global_keydown(element, event)
    if event.parameters.key_identifier == rocket.key_identifier.ESCAPE then
        event:StopPropagation()
        self.Document:Close()
        ScpuiSystem:returnToState(ScpuiSystem.data.LastState)
    end
end

--- Run all the frame functions
--- @return nil
function PersonnelController:unload()
end

return PersonnelController
