RealisticLivestock_PlaceableHusbandryAnimals = {}


function RealisticLivestock_PlaceableHusbandryAnimals.registerFunctions(placeable)
	SpecializationUtil.registerFunction(placeable, "setHasUnreadRLMessages", PlaceableHusbandryAnimals.setHasUnreadRLMessages)
	SpecializationUtil.registerFunction(placeable, "getHasUnreadRLMessages", PlaceableHusbandryAnimals.getHasUnreadRLMessages)
	SpecializationUtil.registerFunction(placeable, "getRLMessages", PlaceableHusbandryAnimals.getRLMessages)
	SpecializationUtil.registerFunction(placeable, "addRLMessage", PlaceableHusbandryAnimals.addRLMessage)
	SpecializationUtil.registerFunction(placeable, "addRLMessageDirect", PlaceableHusbandryAnimals.addRLMessageDirect)
	SpecializationUtil.registerFunction(placeable, "deleteRLMessage", PlaceableHusbandryAnimals.deleteRLMessage)
	SpecializationUtil.registerFunction(placeable, "getNextRLMessageUniqueId", PlaceableHusbandryAnimals.getNextRLMessageUniqueId)
	SpecializationUtil.registerFunction(placeable, "setNextRLMessageUniqueId", PlaceableHusbandryAnimals.setNextRLMessageUniqueId)
	SpecializationUtil.registerFunction(placeable, "getAIManager", PlaceableHusbandryAnimals.getAIManager)
end

PlaceableHusbandryAnimals.registerFunctions = Utils.appendedFunction(PlaceableHusbandryAnimals.registerFunctions, RealisticLivestock_PlaceableHusbandryAnimals.registerFunctions)


function PlaceableHusbandryAnimals:setHasUnreadRLMessages(hasUnreadMessages)
    
    self.spec_husbandryAnimals.unreadMessages = hasUnreadMessages

end


function PlaceableHusbandryAnimals:getHasUnreadRLMessages()
    
    return self.spec_husbandryAnimals.unreadMessages or false

end


function PlaceableHusbandryAnimals:getRLMessages()

    return self.spec_husbandryAnimals.messages or {}

end


-- Direct message insertion, bypassing the aggregator
function PlaceableHusbandryAnimals:addRLMessageDirect(id, animal, args, date, uniqueId, isLoading)

    local spec = self.spec_husbandryAnimals

    if spec.messages == nil then spec.messages = {} end

    if date == nil then

        local environment = g_currentMission.environment
        local month = environment.currentPeriod + 2
        local currentDayInPeriod = environment.currentDayInPeriod

        if month > 12 then month = month - 12 end

        local daysPerPeriod = environment.daysPerPeriod
        local day = 1 + math.floor((currentDayInPeriod - 1) * (RLConstants.DAYS_PER_MONTH[month] / daysPerPeriod))
        local year = environment.currentYear

        date = string.format("%s/%s/%s", day, month, year + RLConstants.START_YEAR.FULL)

    end

    for i, arg in pairs(args or {}) do args[i] = tostring(arg) end

    table.insert(spec.messages, {
        ["id"] = id,
        ["animal"] = animal,
        ["args"] = args or {},
        ["date"] = date,
        ["uniqueId"] = uniqueId or spec:getNextRLMessageUniqueId()
    })

    if not isLoading and #spec.messages > PlaceableHusbandryAnimals.maxNumMessages then table.remove(spec.messages, 1) end

    spec.unreadMessages = true

end


-- Main message entry point - routes through aggregator when in summary mode
function PlaceableHusbandryAnimals:addRLMessage(id, animal, args, date, uniqueId, isLoading)
    if isLoading then
        -- Loading from save - bypass aggregator
        self:addRLMessageDirect(id, animal, args, date, uniqueId, isLoading)
    else
        -- Route through aggregator (handles summary mode check)
        RLMessageAggregator.queueMessage(self, id, animal, args, date)
    end
end


function PlaceableHusbandryAnimals:deleteRLMessage(uniqueId)

    local spec = self.spec_husbandryAnimals

    for i, message in pairs(spec.messages or {}) do

        if message.uniqueId == uniqueId then
            table.remove(spec.messages, i)
            return
        end

    end

end


function PlaceableHusbandryAnimals:setNextRLMessageUniqueId(nextUniqueId)

    self.spec_husbandryAnimals.rlMessageUniqueId = nextUniqueId or 0

end


function PlaceableHusbandryAnimals:getNextRLMessageUniqueId()

    local spec = self.spec_husbandryAnimals

    if spec.rlMessageUniqueId == nil then spec.rlMessageUniqueId = 0 end

    spec.rlMessageUniqueId = spec.rlMessageUniqueId + 1

    return spec.rlMessageUniqueId

end


function RealisticLivestock_PlaceableHusbandryAnimals:saveToXMLFile(xmlFile, key)

    local spec = self.spec_husbandryAnimals

    xmlFile:setInt(key .. ".messages#uniqueId", spec.rlMessageUniqueId or 0)
    xmlFile:setBool(key .. ".messages#unreadMessages", spec.unreadMessages or false)

    for i, message in pairs(spec.messages or {}) do

        local messageKey = string.format("%s.messages.message(%d)", key, i - 1)

        xmlFile:setString(messageKey .. "#id", message.id)
        xmlFile:setString(messageKey .. "#date", message.date)
        if message.animal ~= nil then xmlFile:setString(messageKey .. "#animal", message.animal) end
        xmlFile:setInt(messageKey .. "#uniqueId", message.uniqueId)
        
        for j, arg in pairs(message.args) do

            xmlFile:setString(string.format("%s.args.arg(%d)#value", messageKey, j - 1), arg)

        end

    end

    spec.aiAnimalManager:saveToXMLFile(xmlFile, key)

end

PlaceableHusbandryAnimals.saveToXMLFile = Utils.prependedFunction(PlaceableHusbandryAnimals.saveToXMLFile, RealisticLivestock_PlaceableHusbandryAnimals.saveToXMLFile)


function RealisticLivestock_PlaceableHusbandryAnimals:loadFromXMLFile(xmlFile, key)

    local spec = self.spec_husbandryAnimals
    
    spec.rlMessageUniqueId = xmlFile:getInt(key .. ".messages#uniqueId", 0)

    xmlFile:iterate(key .. ".messages.message", function(_, messageKey)
    
        local id = xmlFile:getString(messageKey .. "#id")
        local date = xmlFile:getString(messageKey .. "#date")
        local animal = xmlFile:getString(messageKey .. "#animal")
        local uniqueId = xmlFile:getInt(messageKey .. "#uniqueId")
        local args = {}

        xmlFile:iterate(messageKey .. ".args.arg", function(_, argKey)

            table.insert(args, xmlFile:getString(argKey .. "#value"))

        end)
        
        if RLMessage[id] == nil then
            Log:warning("Discarding unknown message id '%s' from savegame (date=%s)", tostring(id), tostring(date))
        else
            self:addRLMessage(id, animal, args, date, uniqueId, true)
        end
    
    end)

    spec.unreadMessages = xmlFile:getBool(key .. ".messages#unreadMessages", false)

    spec.aiAnimalManager:loadFromXMLFile(xmlFile, key)

end

PlaceableHusbandryAnimals.loadFromXMLFile = Utils.prependedFunction(PlaceableHusbandryAnimals.loadFromXMLFile, RealisticLivestock_PlaceableHusbandryAnimals.loadFromXMLFile)


function PlaceableHusbandryAnimals:getAIManager()

    local spec = self.spec_husbandryAnimals

    if spec.aiAnimalManager == nil then spec.aiAnimalManager = AIAnimalManager.new(self) end

    return spec.aiAnimalManager

end


function RealisticLivestock_PlaceableHusbandryAnimals:onLoad()

    self.spec_husbandryAnimals.aiAnimalManager = AIAnimalManager.new(self, self.isServer)
    RLMapBridge.onHusbandryLoad(self)

end

PlaceableHusbandryAnimals.onLoad = Utils.appendedFunction(PlaceableHusbandryAnimals.onLoad, RealisticLivestock_PlaceableHusbandryAnimals.onLoad)


function RealisticLivestock_PlaceableHusbandryAnimals.onSettingChanged(name, state)

    PlaceableHusbandryAnimals[name] = state

end


function RealisticLivestock_PlaceableHusbandryAnimals:updateVisualAnimals(_)
    local spec = self.spec_husbandryAnimals
    local animals = spec.clusterSystem:getAnimals()

    spec.clusterHusbandry:setClusters(animals)
    spec.clusterHusbandry:updateVisuals()
    self:raiseActive()
end

PlaceableHusbandryAnimals.updateVisualAnimals = Utils.overwrittenFunction(PlaceableHusbandryAnimals.updateVisualAnimals, RealisticLivestock_PlaceableHusbandryAnimals.updateVisualAnimals)



--- Handle both RLRM internal calls (table of Animal objects) and base game API calls
--- (subTypeIndex, numAnimals, age) used by external mods like HB's CFTA incubator system.
function RealisticLivestock_PlaceableHusbandryAnimals:addAnimals(superFunc, animals, ...)

    if type(animals) == "table" then
        Log:trace("addAnimals: RLRM path - %d animal(s) in table", #animals)
        for _, animal in pairs(animals) do self:addCluster(animal) end
    else
        -- Base game signature: addAnimals(subTypeIndex, numAnimals, age)
        local numAnimals, age = ...
        Log:trace("addAnimals: base game path - subTypeIndex=%s numAnimals=%s age=%s",
            tostring(animals), tostring(numAnimals), tostring(age))
        superFunc(self, animals, numAnimals, age)
    end

end

PlaceableHusbandryAnimals.addAnimals = Utils.overwrittenFunction(PlaceableHusbandryAnimals.addAnimals, RealisticLivestock_PlaceableHusbandryAnimals.addAnimals)




function RealisticLivestock_PlaceableHusbandryAnimals:onDayChanged()
    RmSafeUtils.safeCall("PlaceableHusbandryAnimals:onDayChanged", function()

        local minTemp = math.floor(g_currentMission.environment.weather.temperatureUpdater.currentMin)

        local environment = g_currentMission.environment
        local month = environment.currentPeriod + 2
        local currentDayInPeriod = environment.currentDayInPeriod

        if month > 12 then month = month - 12 end

        local daysPerPeriod = environment.daysPerPeriod
        local day = 1 + math.floor((currentDayInPeriod - 1) * (RLConstants.DAYS_PER_MONTH[month] / daysPerPeriod))
        local year = environment.currentYear

        local spec = self.spec_husbandryAnimals
        local animals = spec.clusterSystem:getAnimals()

        local totalChildren, deadParents, childrenToSell, childrenToSellMoney, lowHealthDeaths, oldAgeDeaths, randomDeaths, randomDeathsMoney = 0, 0, 0, 0, 0, 0, 0, 0

        for _, animal in ipairs(animals) do

            if self.isServer and RealisticLivestock.testAnimalPrefix ~= nil then
                if not string.startsWith(animal.uniqueId, RealisticLivestock.testAnimalPrefix) then
                    continue
                end
            end

            if animal.monthsSinceLastBirth == nil then
                animal.monthsSinceLastBirth = 0
            end

            if animal.isParent == nil then
                animal.isParent = false
            end

            local a, b, c, d, e, f, g, h = RmSafeUtils.safeAnimalCall(animal, "onDayChanged", function()
                return animal:onDayChanged(spec, self.isServer, day, month, year, currentDayInPeriod, daysPerPeriod)
            end, {0, 0, 0, 0, 0, 0, 0, 0})

            totalChildren = totalChildren + (a or 0)
            deadParents = deadParents + (b or 0)
            childrenToSell = childrenToSell + (c or 0)
            childrenToSellMoney = childrenToSellMoney + (d or 0)
            lowHealthDeaths = lowHealthDeaths + (e or 0)
            oldAgeDeaths = oldAgeDeaths + (f or 0)
            randomDeaths = randomDeaths + (g or 0)
            randomDeathsMoney = randomDeathsMoney + (h or 0)

        end

        if self.isServer then

            if childrenToSell > 0 and childrenToSellMoney > 0 then
                local farmIndex = spec:getOwnerFarmId()
                local farm = g_farmManager:getFarmById(farmIndex)

                g_currentMission:addMoneyChange(childrenToSellMoney, farmIndex, MoneyType.SOLD_ANIMALS, true)

                if farm ~= nil then
                    farm:changeBalance(childrenToSellMoney, MoneyType.SOLD_ANIMALS)
                end
            end

            if randomDeaths > 0 then

                local farmIndex = spec:getOwnerFarmId()
                local farm = g_farmManager:getFarmById(farmIndex)

                if randomDeathsMoney > 0 then

                    g_currentMission:addMoneyChange(randomDeathsMoney, farmIndex, MoneyType.SOLD_ANIMALS, true)

                    if farm ~= nil then
                        farm:changeBalance(randomDeathsMoney, MoneyType.SOLD_ANIMALS)
                    end

                end

            end

            spec.aiAnimalManager:onDayChanged()

        end

        spec.minTemp = minTemp

        if randomDeaths > 0 or oldAgeDeaths > 0 or lowHealthDeaths > 0 or deadParents > 0 or totalChildren > 0 then spec.clusterHusbandry:updateVisuals() end

        self:raiseActive()

        if self:getHasUnreadRLMessages() and g_localPlayer ~= nil and g_localPlayer.farmId == self:getOwnerFarmId() then

            g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, string.format(g_i18n:getText("rl_ui_unreadMessages"), self:getName()))

        end

    end)
end

PlaceableHusbandryAnimals.onDayChanged = Utils.overwrittenFunction(PlaceableHusbandryAnimals.onDayChanged, RealisticLivestock_PlaceableHusbandryAnimals.onDayChanged)


function RealisticLivestock_PlaceableHusbandryAnimals:onPeriodChanged(_)
    RmSafeUtils.safeCall("PlaceableHusbandryAnimals:onPeriodChanged", function()

        if self.isServer then

            local animals = self.spec_husbandryAnimals.clusterSystem:getClusters()
            local totalTreatmentCost = 0

            for _, animal in pairs(animals) do
                if RealisticLivestock.testAnimalPrefix ~= nil then
                    if not string.startsWith(animal.uniqueId, RealisticLivestock.testAnimalPrefix) then
                        continue
                    end
                end
                local treatmentCost = RmSafeUtils.safeAnimalCall(animal, "onPeriodChanged", function()
                    return animal:onPeriodChanged()
                end, {0})
                totalTreatmentCost = totalTreatmentCost + (treatmentCost or 0)
            end

            if totalTreatmentCost > 0 then g_currentMission:addMoneyChange(totalTreatmentCost, self.spec_husbandryAnimals:getOwnerFarmId(), MoneyType.MEDICINE, true) end

            if RealisticLivestock.testAnimalPrefix == nil then
                g_diseaseManager:calculateTransmission(animals)
            end

        end

    end)
end

PlaceableHusbandryAnimals.onPeriodChanged = Utils.overwrittenFunction(PlaceableHusbandryAnimals.onPeriodChanged, RealisticLivestock_PlaceableHusbandryAnimals.onPeriodChanged)