require "Vehicles/Vehicles"
require "CommonTemplates/CommonTemplates"

W900FreezerRuntime = W900FreezerRuntime or {}

local FREEZER_MIN_TEMP = 0.2
local FREEZER_MAX_TEMP = 1.0
local FREEZER_BATTERY_CHANGE = -1 / 720 / 4

local function getWorldAgeMinutes()
    local gameTime = getGameTime and getGameTime() or nil
    if not gameTime or not gameTime.getWorldAgeHours then
        return 0
    end

    return gameTime:getWorldAgeHours() * 60
end

local function getTruckBedPart(vehicle, part)
    if part and part.getId and part:getId() == "TruckBed" then
        return part
    end

    return vehicle and vehicle.getPartById and vehicle:getPartById("TruckBed") or nil
end

local function ensureFreezerData(part)
    local modData = part:getModData()
    modData.tsarslib = modData.tsarslib or {}
    modData.tsarslib.freezer = modData.tsarslib.freezer or {}
    return modData.tsarslib
end

function W900FreezerRuntime.notifyClients(vehicle, part)
    if not vehicle or not part or not sendServerCommand or not getOnlinePlayers then
        return
    end

    local args = {
        vehicle = vehicle:getId(),
        part = part:getId(),
    }

    local onlinePlayers = getOnlinePlayers()
    if not onlinePlayers then
        return
    end

    for index = 0, onlinePlayers:size() - 1 do
        local onlinePlayer = onlinePlayers:get(index)
        if onlinePlayer then
            sendServerCommand(onlinePlayer, "W900", "refreshFreezerContainer", args)
        end
    end
end

function W900FreezerRuntime.refreshClientsIfNeeded(vehicle, part, forceRefresh)
    local container = part and part.getItemContainer and part:getItemContainer() or nil
    if not container then
        return
    end

    local freezerData = ensureFreezerData(part)
    local tempBucket = math.floor((container:getTemprature() or 0) * 20 + 0.5)
    if forceRefresh or freezerData.lastFreezerSyncBucket ~= tempBucket then
        freezerData.lastFreezerSyncBucket = tempBucket
        W900FreezerRuntime.notifyClients(vehicle, part)
    end
end

local function deactivateFreezer(vehicle, part)
    local freezerData = ensureFreezerData(part)
    freezerData.active = false
    part:getItemContainer():setType("TruckBed")
    part:getItemContainer():setCustomTemperature(FREEZER_MAX_TEMP)
    part:setLightActive(false)
    vehicle:transmitPartModData(part)
    W900FreezerRuntime.refreshClientsIfNeeded(vehicle, part, true)
end

local function updateDeepFrozenFood(part, effectiveElapsedMinutes)
    local freezerData = ensureFreezerData(part)
    local foodItems = part:getItemContainer():getItemsFromCategory("Food")
    local newFreezerTable = {}
    local itemsChanged = false

    for index = 1, foodItems:size() do
        local item = foodItems:get(index - 1)
        if item:canBeFrozen() then
            local previousAge = freezerData.freezer[item:getID()]
            if item:getFreezingTime() < 95 and not previousAge then
                item:setFreezingTime(item:getFreezingTime() + effectiveElapsedMinutes / 50 * 100.0)
                itemsChanged = true
            else
                if previousAge then
                    item:setAge(previousAge + (item:getAge() - previousAge) * 0.02)
                end
                if not item:isFrozen() then
                    item:freeze()
                    itemsChanged = true
                end
                item:setFreezingTime(100)
                newFreezerTable[item:getID()] = item:getAge()
            end
        end
    end

    freezerData.freezer = newFreezerTable
    return itemsChanged
end

local function preserveFrozenFood(part)
    local freezerData = ensureFreezerData(part)
    freezerData.freezer = freezerData.freezer or {}

    local foodItems = part:getItemContainer():getItemsFromCategory("Food")
    local itemsChanged = false

    for index = 1, foodItems:size() do
        local item = foodItems:get(index - 1)
        if item:canBeFrozen() and (item:isFrozen() or item:getFreezingTime() >= 100) then
            local previousAge = freezerData.freezer[item:getID()]
            if previousAge then
                item:setAge(previousAge + (item:getAge() - previousAge) * 0.02)
            end

            if not item:isFrozen() then
                item:freeze()
                itemsChanged = true
            end

            if item:getFreezingTime() < 100 then
                item:setFreezingTime(100)
                itemsChanged = true
            end

            freezerData.freezer[item:getID()] = item:getAge()
        end
    end

    return itemsChanged
end

local function updateCoolingFood(part, effectiveElapsedMinutes, nextTemperature)
    local coolingFactor = math.max(0, math.min(1, (FREEZER_MAX_TEMP - nextTemperature) / (FREEZER_MAX_TEMP - FREEZER_MIN_TEMP)))
    if coolingFactor <= 0 then
        return false
    end

    local freezerData = ensureFreezerData(part)
    freezerData.freezer = freezerData.freezer or {}
    local foodItems = part:getItemContainer():getItemsFromCategory("Food")
    local itemsChanged = false
    for index = 1, foodItems:size() do
        local item = foodItems:get(index - 1)
        if item:canBeFrozen() and not item:isFrozen() then
            local oldFreezingTime = item:getFreezingTime()
            local newFreezingTime = math.min(100, oldFreezingTime + effectiveElapsedMinutes / 50 * 100.0 * coolingFactor)
            if math.abs(newFreezingTime - oldFreezingTime) > 0.01 then
                item:setFreezingTime(newFreezingTime)
                itemsChanged = true
            end

            if newFreezingTime >= 100 then
                item:freeze()
                item:setFreezingTime(100)
                freezerData.freezer[item:getID()] = item:getAge()
                itemsChanged = true
            end
        end
    end

    return itemsChanged
end

function W900FreezerRuntime.updatePart(vehicle, part, elapsedMinutes)
    if not vehicle or not part or not part:getItemContainer() or not part:getInventoryItem() then
        return
    end

    local freezerData = ensureFreezerData(part)
    local worldAgeMinutes = getWorldAgeMinutes()
    local effectiveElapsedMinutes = elapsedMinutes or 0
    if freezerData.lastFreezerRuntimeMinutes then
        effectiveElapsedMinutes = math.max(0, worldAgeMinutes - freezerData.lastFreezerRuntimeMinutes)
    elseif effectiveElapsedMinutes < 0 then
        effectiveElapsedMinutes = 0
    end
    freezerData.lastFreezerRuntimeMinutes = worldAgeMinutes

    if effectiveElapsedMinutes <= 0 then
        return
    end

    local freezerUpgrade = vehicle:getPartById("Freezersystem")
    if not freezerUpgrade or not freezerUpgrade:getInventoryItem() then
        if freezerData.active then
            deactivateFreezer(vehicle, part)
        end
        return
    end

    local currentTemp = part:getItemContainer():getTemprature()
    local previousTempBucket = math.floor((currentTemp or 0) * 20 + 0.5)
    local refreshClients = false
    local itemsChanged = false

    if freezerData.active and VehicleUtils.hasBatteryPowerW900(vehicle, 0.005) then
        if part:getItemContainer():getType() ~= "freezer" then
            part:getItemContainer():setType("freezer")
            refreshClients = true
        end
        part:setLightActive(true)
        itemsChanged = preserveFrozenFood(part) or itemsChanged

        if currentTemp <= FREEZER_MIN_TEMP then
            part:getItemContainer():setCustomTemperature(FREEZER_MIN_TEMP)
            itemsChanged = updateDeepFrozenFood(part, effectiveElapsedMinutes) or itemsChanged
            vehicle:transmitPartModData(part)
        else
            local nextTemp = math.max(FREEZER_MIN_TEMP, currentTemp - 0.04 * effectiveElapsedMinutes)
            itemsChanged = updateCoolingFood(part, effectiveElapsedMinutes, nextTemp) or itemsChanged
            part:getItemContainer():setCustomTemperature(nextTemp)
            refreshClients = math.floor((nextTemp or 0) * 20 + 0.5) ~= previousTempBucket
        end

        VehicleUtils.chargeBatteryW900(vehicle, FREEZER_BATTERY_CHANGE * effectiveElapsedMinutes)
    else
        if part:getItemContainer():getType() ~= "TruckBed" then
            part:getItemContainer():setType("TruckBed")
            refreshClients = true
        end

        if currentTemp < FREEZER_MAX_TEMP then
            local nextTemp = math.min(FREEZER_MAX_TEMP, currentTemp + 0.04 * effectiveElapsedMinutes)
            part:getItemContainer():setCustomTemperature(nextTemp)
            refreshClients = refreshClients or math.floor((nextTemp or 0) * 20 + 0.5) ~= previousTempBucket
        else
            part:getItemContainer():setCustomTemperature(FREEZER_MAX_TEMP)
            part:setLightActive(false)
        end
    end

    if itemsChanged then
        vehicle:transmitPartItem(part)
        refreshClients = true
    end

    if refreshClients then
        W900FreezerRuntime.refreshClientsIfNeeded(vehicle, part, true)
    else
        W900FreezerRuntime.refreshClientsIfNeeded(vehicle, part, false)
    end
end

function W900FreezerRuntime.initPart(vehicle, part)
    local freezerData = ensureFreezerData(part)
    freezerData.lastFreezerRuntimeMinutes = freezerData.lastFreezerRuntimeMinutes or getWorldAgeMinutes()

    if part:getInventoryItem() and part:getItemContainer() then
        if freezerData.active and VehicleUtils.hasBatteryPowerW900(vehicle, 0.001) then
            part:getItemContainer():setType("freezer")
            part:getItemContainer():setCustomTemperature(FREEZER_MIN_TEMP)

            local foodItems = part:getItemContainer():getItemsFromCategory("Food")
            for index = 1, foodItems:size() do
                local item = foodItems:get(index - 1)
                if item:canBeFrozen() then
                    item:setFreezingTime(100)
                    item:freeze()
                end
            end

            part:setLightActive(true)
        else
            part:getItemContainer():setType("TruckBed")
            part:getItemContainer():setCustomTemperature(FREEZER_MAX_TEMP)
            part:setLightActive(false)
        end
    end

    vehicle:transmitPartModData(part)
    W900FreezerRuntime.refreshClientsIfNeeded(vehicle, part, true)
end

function W900FreezerRuntime.createPart(vehicle, part)
    VehicleUtils.createPartInventoryItem(part)
    if CommonTemplates and CommonTemplates.createActivePart then
        CommonTemplates.createActivePart(part)
    end

    local freezerData = ensureFreezerData(part)
    if freezerData.active == nil then
        freezerData.active = false
    end
end

function W900FreezerRuntime.updateLoadedVehicles()
    local vehicles = getCell and getCell():getVehicles() or nil
    if not vehicles then
        return
    end

    for vehicleIndex = 0, vehicles:size() - 1 do
        local vehicle = vehicles:get(vehicleIndex)
        if vehicle then
            local truckBed = getTruckBedPart(vehicle)
            local freezerSystem = vehicle:getPartById("Freezersystem")
            if truckBed and truckBed:getItemContainer() and freezerSystem and freezerSystem:getInventoryItem() then
                local freezerData = truckBed:getModData().tsarslib
                if freezerData and freezerData.active then
                    W900FreezerRuntime.updatePart(vehicle, truckBed, 0)
                end
            end
        end
    end
end

CommonTemplates = CommonTemplates or {}
CommonTemplates.Create = CommonTemplates.Create or {}
CommonTemplates.Init = CommonTemplates.Init or {}
CommonTemplates.Update = CommonTemplates.Update or {}

CommonTemplates.Create.VehicleFreezer = W900FreezerRuntime.createPart
CommonTemplates.Init.VehicleFreezer = W900FreezerRuntime.initPart
CommonTemplates.Update.VehicleFreezer = W900FreezerRuntime.updatePart

Vehicles.Update = Vehicles.Update or {}
Vehicles.Update.VehicleFreezer = W900FreezerRuntime.updatePart

if not W900FreezerRuntime.everyMinuteInstalled then
    Events.EveryOneMinute.Add(W900FreezerRuntime.updateLoadedVehicles)
    W900FreezerRuntime.everyMinuteInstalled = true
end