-- =========================================================
-- FS25 Fertilizer Depot - Placeable Specialization
-- =========================================================

local modName = g_currentModName

---@class PlaceableDepot
PlaceableDepot = {}
PlaceableDepot.SPEC_TABLE_NAME = "spec_" .. modName .. ".fertilizerDepot"

function PlaceableDepot.prerequisitesPresent(...)
    return true
end

function PlaceableDepot.registerEventListeners(placeableType)
    SpecializationUtil.registerEventListener(placeableType, "onLoad",                  PlaceableDepot)
    SpecializationUtil.registerEventListener(placeableType, "onPostFinalizePlacement",  PlaceableDepot)
    SpecializationUtil.registerEventListener(placeableType, "onDelete",                PlaceableDepot)
    SpecializationUtil.registerEventListener(placeableType, "onReadStream",             PlaceableDepot)
    SpecializationUtil.registerEventListener(placeableType, "onWriteStream",            PlaceableDepot)
    SpecializationUtil.registerEventListener(placeableType, "saveToXMLFile",            PlaceableDepot)
end

function PlaceableDepot.registerXMLPaths(schema, basePath)
    schema:setXMLSpecializationType("FertilizerDepot")
    schema:register(XMLValueType.NODE_INDEX, basePath .. ".fertilizerDepot#playerTrigger",
        "Player walk-in trigger node (inside building)")
    schema:register(XMLValueType.NODE_INDEX, basePath .. ".fertilizerDepot#unloadTrigger",
        "Vehicle unload marker node (front of selling area)")
    schema:register(XMLValueType.NODE_INDEX, basePath .. ".fertilizerDepot#productSpawnMarker",
        "Product output node — bags and tanks spawn here")
    schema:register(XMLValueType.NODE_INDEX, basePath .. ".fertilizerDepot#baleTrigger",
        "Bale unload trigger node")
    schema:setXMLSpecializationType()
end

function PlaceableDepot.registerSavegameXMLPaths(schema, basePath)
    schema:setXMLSpecializationType("FertilizerDepot")
    schema:register(XMLValueType.STRING, basePath .. ".storage.fill(?)#type",   "Fill type name")
    schema:register(XMLValueType.FLOAT,  basePath .. ".storage.fill(?)#liters", "Stored liters")
    schema:register(XMLValueType.INT,    basePath .. ".coop#ownerFarmId",       "Coop owner farm ID")
    -- Histórico de transações
    schema:register(XMLValueType.STRING, basePath .. ".transactions.entry(?)#kind",     "Transaction kind (buy/sell)")
    schema:register(XMLValueType.INT,    basePath .. ".transactions.entry(?)#farmId",   "Farm that operated")
    schema:register(XMLValueType.STRING, basePath .. ".transactions.entry(?)#fillType", "Fill type name")
    schema:register(XMLValueType.FLOAT,  basePath .. ".transactions.entry(?)#liters",   "Liters")
    schema:register(XMLValueType.FLOAT,  basePath .. ".transactions.entry(?)#value",    "Money value")
    schema:register(XMLValueType.INT,    basePath .. ".transactions.entry(?)#day",      "Game day")
    schema:register(XMLValueType.FLOAT,  basePath .. ".transactions.entry(?)#hour",     "Game hour")
    schema:register(XMLValueType.INT,    basePath .. ".delivery#status",         "Delivery status (0=none 1=pending 2=loaded)")
    schema:register(XMLValueType.INT,    basePath .. ".delivery#farmId",         "Delivery farm ID")
    schema:register(XMLValueType.FLOAT,  basePath .. ".delivery#baseCost",       "Delivery base cost")
    schema:register(XMLValueType.FLOAT,  basePath .. ".delivery#deliveryCost",   "Delivery total cost with fee")
    schema:register(XMLValueType.STRING, basePath .. ".delivery.item(?)#name",   "Fill type name for delivery item")
    schema:register(XMLValueType.FLOAT,  basePath .. ".delivery.item(?)#liters", "Liters for delivery item")
    schema:register(XMLValueType.FLOAT,  basePath .. ".delivery.item(?)#cost",   "Base cost for delivery item")
    schema:setXMLSpecializationType()
end

-- ─── onLoad ──────────────────────────────────────────────

function PlaceableDepot:onLoad(savegame)
    local spec = {}
    self[PlaceableDepot.SPEC_TABLE_NAME] = spec
    spec.depotId         = nil
    spec.savegame        = savegame

    spec.playerTriggerNode = self.xmlFile:getValue(
        "placeable.fertilizerDepot#playerTrigger", nil,
        self.components, self.i3dMappings)

    spec.unloadTriggerNode = self.xmlFile:getValue(
        "placeable.fertilizerDepot#unloadTrigger", nil,
        self.components, self.i3dMappings)

    spec.productSpawnNode = self.xmlFile:getValue(
        "placeable.fertilizerDepot#productSpawnMarker", nil,
        self.components, self.i3dMappings)

    spec.baleTriggerNode = self.xmlFile:getValue(
        "placeable.fertilizerDepot#baleTrigger", nil,
        self.components, self.i3dMappings)

    spec.baleTriggerCallbackTarget = nil
    spec.baleTriggerAdded = false

    if spec.playerTriggerNode == nil then
        DepotLogger.warning("playerTrigger node not found — check i3dMappings")
    else
        DepotLogger.debug("playerTrigger node loaded: %s", tostring(spec.playerTriggerNode))
    end
end

-- ─── Helper: descobre quem é o dono do placeable ──────────
-- Usado pelo DepotManager pra capturar o ownerFarmId na hora do registro.
function PlaceableDepot:getOwnerFarmId()
    DepotLogger.info(">>> getOwnerFarmId: self.ownerFarmId=%s spec_placeable=%s",
        tostring(self.ownerFarmId), tostring(self.spec_placeable ~= nil))

    -- Em FS25, placeable.ownerFarmId é setado durante a colocação
    if self.ownerFarmId and self.ownerFarmId > 0 then
        DepotLogger.info(">>> usando self.ownerFarmId=%d", self.ownerFarmId)
        return self.ownerFarmId
    end
    -- Fallback: tenta via spec_placeable
    local spec = self.spec_placeable
    if spec then
        DepotLogger.info(">>> spec_placeable.ownerFarmId=%s", tostring(spec.ownerFarmId))
        if spec.ownerFarmId and spec.ownerFarmId > 0 then
            return spec.ownerFarmId
        end
    end
    -- Fallback 2: tenta via g_localPlayer.farmId (singleplayer)
    if g_localPlayer and g_localPlayer.farmId and g_localPlayer.farmId > 0 then
        DepotLogger.info(">>> fallback: usando g_localPlayer.farmId=%d", g_localPlayer.farmId)
        return g_localPlayer.farmId
    end
    DepotLogger.warning(">>> getOwnerFarmId retornou 0 — NENHUMA fonte de farmId disponível!")
    return 0
end

-- ─── Registro de triggers (roda em AMBOS server e cliente) ──
-- Ponto-chave do multiplayer: TODO jogador precisa ter os nodes de trigger
-- registrados no DepotManager local, senão não vê marcador nem prompt.
--
-- IMPORTANTE: função LOCAL (não método da specialization). Métodos `:` numa
-- specialization do FS25 não ficam disponíveis na instância a menos que sejam
-- registrados via registerFunction. Como função local que recebe o placeable
-- por parâmetro, funciona sempre, em qualquer contexto.
local function createBaleTriggerTarget(placeable)
    return {
        placeable = placeable,

        baleTriggerCallback = function(target, triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
            if not onEnter or not g_server then return end
            if not g_currentMission or not g_currentMission.getNodeObject then return end

            local object = g_currentMission:getNodeObject(otherId)
            if object == nil or Bale == nil or object.isa == nil or not object:isa(Bale) then
                return
            end

            if object.getCanBeSold ~= nil and not object:getCanBeSold() then
                return
            end

            if object.dynamicMountType ~= nil
                and MountableObject ~= nil
                and object.dynamicMountType ~= MountableObject.MOUNT_TYPE_NONE then
                return
            end

            local spec = target.placeable[PlaceableDepot.SPEC_TABLE_NAME]
            local depotId = spec and spec.depotId
            if depotId == nil or not g_DepotManager or not g_DepotManager.depotSystem then
                return
            end

            local fillTypeIndex = object:getFillType()
            local liters = object:getFillLevel()
            local fillTypeName = g_fillTypeManager and g_fillTypeManager:getFillTypeNameByIndex(fillTypeIndex)
            if fillTypeName == nil or liters == nil or liters <= 0 then
                return
            end

            local farmId = 0
            if object.getOwnerFarmId ~= nil then
                farmId = object:getOwnerFarmId() or 0
            end
            if farmId <= 0 and target.placeable.getOwnerFarmId ~= nil then
                farmId = target.placeable:getOwnerFarmId() or 0
            end

            local ok, msgKey, acceptedLiters = g_DepotManager.depotSystem:sellBaleFillType(
                depotId, fillTypeName, fillTypeIndex, liters, farmId)

            if not ok or acceptedLiters == nil or acceptedLiters <= 0 then
                DepotLogger.info("Bale trigger rejected bale: depot=%s fillType=%s liters=%.0f msg=%s",
                    tostring(depotId), tostring(fillTypeName), liters or 0, tostring(msgKey))
                return
            end

            local newFillLevel = math.max(0, liters - acceptedLiters)
            if object.setFillLevel ~= nil then
                object:setFillLevel(newFillLevel)
            end

            if newFillLevel < 0.01 and object.delete ~= nil then
                object:delete()
            end

            DepotLogger.info("Bale trigger accepted %.0fL of %s at depot #%d",
                acceptedLiters, fillTypeName, depotId)
        end
    }
end

local function registerTriggersLocally(placeable, depotId)
    local spec = placeable[PlaceableDepot.SPEC_TABLE_NAME]
    if not spec or not g_DepotManager then return end
    if not depotId then return end

    spec.depotId = depotId

    if not g_DepotManager.depotSystem:getDepot(depotId) then
        g_DepotManager.depotSystem._depots[depotId] = {
            id = depotId, storageLevel = {},
        }
    end
    g_DepotManager.depots[depotId] = placeable
    g_DepotManager.depotNodes[depotId] = spec.playerTriggerNode or placeable.rootNode

    if spec.unloadTriggerNode then
        g_DepotManager:registerDepotUnloadNode(depotId, spec.unloadTriggerNode)
    end
    if spec.productSpawnNode then
        g_DepotManager:registerDepotProductSpawnNode(depotId, spec.productSpawnNode)
    end

    if g_server and spec.baleTriggerNode and not spec.baleTriggerAdded then
        spec.baleTriggerCallbackTarget = createBaleTriggerTarget(placeable)
        addTrigger(spec.baleTriggerNode, "baleTriggerCallback", spec.baleTriggerCallbackTarget)
        spec.baleTriggerAdded = true
        DepotLogger.info("Depot #%d bale trigger registered: %s", depotId, tostring(spec.baleTriggerNode))
    end

    DepotLogger.info(">>> Triggers registrados LOCALMENTE: depot=%d player=%s unload=%s (isServer=%s)",
        depotId, tostring(spec.playerTriggerNode), tostring(spec.unloadTriggerNode),
        tostring(g_server ~= nil))
end

-- ─── onPostFinalizePlacement ──────────────────────────────

function PlaceableDepot:onPostFinalizePlacement()
    local spec = self[PlaceableDepot.SPEC_TABLE_NAME]
    DepotLogger.info("onPostFinalizePlacement fired for depotId=%s savegame=%s isServer=%s",
        tostring(spec.depotId), tostring(spec.savegame ~= nil), tostring(g_server ~= nil))

    if g_server and g_DepotManager then
        local depotId = g_DepotManager:registerDepot(self)

        -- Se a cooperativa já existe, rejeita esse placeable
        if depotId == nil and self._coopRejected then
            DepotLogger.warning("Cooperativa rejeitada: já existe uma no mapa")
            local placingFarmId = self:getOwnerFarmId()
            if placingFarmId and placingFarmId > 0 and self.price then
                g_currentMission:addMoney(self.price, placingFarmId,
                    MoneyType.SHOP_PROPERTY_SELL, true, true)
                DepotLogger.info("Reembolso de %.2f pra farmId=%d", self.price, placingFarmId)
            end
            if g_currentMission.hud and g_currentMission.hud.showInGameMessage then
                local msg = "Só pode existir UMA Cooperativa Agrícola por mapa. Placeable removido."
                g_currentMission.hud:showInGameMessage("Cooperativa", msg)
            end
            self:delete()
            return
        end

        -- Registra triggers no servidor (função unificada)
        registerTriggersLocally(self, depotId)
    end

    if g_server and spec.depotId and spec.savegame then
        local sg = spec.savegame
        local loadKey = sg.key .. ".FS25_CooperativaAgricola.fertilizerDepot"
        DepotLogger.info("PlaceableDepot:loading from key=%s", loadKey)

        g_DepotManager.depotSystem:loadFromXML(
            sg.xmlFile, spec.depotId, loadKey .. ".storage")

        local persistedOwner = sg.xmlFile:getInt(loadKey .. ".coop#ownerFarmId", 0)
        if persistedOwner and persistedOwner > 0 and g_DepotManager.coopState then
            g_DepotManager.coopState.ownerFarmId = persistedOwner
            DepotLogger.info("Owner restaurado do save: farmId=%d", persistedOwner)
        end

        -- Carrega o histórico de transações
        if g_DepotManager.transactionLog then
            g_DepotManager.transactionLog:loadFromXML(sg.xmlFile, loadKey .. ".transactions")
        end
    end
    spec.savegame = nil
end

-- ─── onDelete ────────────────────────────────────────────

function PlaceableDepot:onDelete()
    local spec = self[PlaceableDepot.SPEC_TABLE_NAME]
    if not spec then return end

    if g_server and spec.baleTriggerAdded and spec.baleTriggerNode then
        removeTrigger(spec.baleTriggerNode)
        spec.baleTriggerAdded = false
        spec.baleTriggerCallbackTarget = nil
    end

    if g_DepotManager and spec.depotId then
        g_DepotManager:unregisterDepot(spec.depotId)
    end

    self[PlaceableDepot.SPEC_TABLE_NAME] = nil
end

-- ─── Network Sync ────────────────────────────────────────

function PlaceableDepot:onWriteStream(streamId, connection)
    local spec = self[PlaceableDepot.SPEC_TABLE_NAME]
    streamWriteInt32(streamId, spec.depotId or 0)

    -- ownerFarmId
    streamWriteUInt8(streamId,
        (g_DepotManager and g_DepotManager.coopState and g_DepotManager.coopState.ownerFarmId) or 0)

    -- Estoque
    local depot = g_DepotManager and g_DepotManager.depotSystem:getDepot(spec.depotId)
    if depot then
        local count = 0
        for _ in pairs(depot.storageLevel) do count = count + 1 end
        streamWriteUInt16(streamId, count)
        for name, liters in pairs(depot.storageLevel) do
            streamWriteString(streamId, name)
            streamWriteFloat32(streamId, liters)
        end
    else
        streamWriteUInt16(streamId, 0)
    end
end

function PlaceableDepot:onReadStream(streamId, connection)
    local spec = self[PlaceableDepot.SPEC_TABLE_NAME]

    local netId = streamReadInt32(streamId)
    spec.netDepotId = netId

    -- ownerFarmId
    local ownerFarmId = streamReadUInt8(streamId)
    if g_DepotManager and g_DepotManager.coopState and ownerFarmId > 0 then
        g_DepotManager.coopState.ownerFarmId = ownerFarmId
        DepotLogger.info(">>> [CLIENT] ownerFarmId sincronizado: %d", ownerFarmId)
    end

    -- REGISTRO UNIFICADO DE TRIGGERS (este é o conserto do multiplayer!)
    -- Antes, se algo falhasse aqui, o cliente ficava sem marcadores.
    registerTriggersLocally(self, netId)

    -- Estoque
    local count = streamReadUInt16(streamId)
    local depot = g_DepotManager and g_DepotManager.depotSystem:getDepot(netId)
    for _ = 1, count do
        local name   = streamReadString(streamId)
        local liters = streamReadFloat32(streamId)
        if depot then
            depot.storageLevel[name] = liters
        end
    end

    DepotLogger.info(">>> [CLIENT] depot #%d lido: owner=%d, %d fillTypes em estoque",
        netId, ownerFarmId, count)
end

-- ─── Save ────────────────────────────────────────────────

function PlaceableDepot:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self[PlaceableDepot.SPEC_TABLE_NAME]
    if not spec or not spec.depotId then return end
    if not g_DepotManager then return end

    g_DepotManager.depotSystem:saveToXML(xmlFile, spec.depotId, key .. ".storage")

    if g_DepotManager.coopState then
        xmlFile:setInt(key .. ".coop#ownerFarmId",
            g_DepotManager.coopState.ownerFarmId or 0)
    end

    -- Persiste o histórico de transações
    if g_DepotManager.transactionLog then
        g_DepotManager.transactionLog:saveToXML(xmlFile, key .. ".transactions")
    end
end
