-- cl_sdf_system_net.lua — net.Receive handlers côté client.
-- Menu admin / fouille terminée / fouille annulée / jackpot.

SDFSystem = SDFSystem or {}
SDFSystem.MenuData = SDFSystem.MenuData or {}

-- ─── Palette / fonts (fallback si DBALib pas encore chargé) ───────────────
local FALLBACK_PAL = {
    bg       = { surface = Color(16, 22, 34), elevated = Color(26, 37, 64) },
    gold     = { base = Color(212, 175, 55) },
    text     = { primary = Color(232, 236, 240), secondary = Color(106, 117, 133) },
    semantic = { success = Color(95, 192, 128), danger = Color(200, 85, 107), info = Color(56, 139, 253) },
}
local function GetPal()
    if DBALib and DBALib.Theme and DBALib.Theme.palette then
        return DBALib.Theme.palette
    end
    return FALLBACK_PAL
end
local function GetFont(key)
    if DBALib and DBALib.Fonts and DBALib.Fonts[key] then return DBALib.Fonts[key] end
    return "DermaDefault"
end

-- ─── S→C : Ouverture du menu admin ───────────────────────────────────────
net.Receive("SDFSystem.OpenMenu", function()
    if SDFSystem.OpenAdminMenu then
        SDFSystem.OpenAdminMenu()
    end
end)

-- ─── S→C : Résultat d'un spawn admin ─────────────────────────────────────
net.Receive("SDFSystem.SpawnResult", function()
    local ok  = net.ReadBool()
    local msg = net.ReadString()
    if not DBALib or not DBALib.UI then return end
    DBALib.UI.Notify({ message = msg, type = ok and "success" or "danger", duration = 4 })
end)

-- ─── S→C : Sync liste des entités ────────────────────────────────────────
net.Receive("SDFSystem.MenuSync", function()
    local count = net.ReadUInt(SDFSystem.DB_ID_BITS)
    local list  = {}
    for _ = 1, count do
        local dbId  = net.ReadUInt(SDFSystem.DB_ID_BITS)
        local model = net.ReadString()
        local x     = net.ReadFloat()
        local y     = net.ReadFloat()
        local z     = net.ReadFloat()
        local shortName = string.match(model, "([^/]+)%.mdl$") or model
        table.insert(list, {
            dbId      = dbId,
            model     = model,
            shortName = shortName,
            pos       = Vector(x, y, z),
            posStr    = string.format("(%.0f, %.0f, %.0f)", x, y, z),
        })
    end
    SDFSystem.MenuData = list
    if SDFSystem.RefreshManageTab then SDFSystem.RefreshManageTab() end
end)

-- ─── S→C : Fouille terminée ───────────────────────────────────────────────
-- Format réseau :
--   Bool   hasLoot
--   String description
--   UInt8  xpGain (légal, 0 si illégal/contraband)
--   UInt2  rarityIdx (0=commun ... 3=legendaire)
--   Bool   isJackpot (géré par SDFSystem.Jackpot séparé)
--   String outcome ("success"|"nothing"|"rat"|"contraband")
net.Receive("SDFSystem.SearchComplete", function()
    local hasLoot   = net.ReadBool()
    local lootDesc  = net.ReadString()
    local xpGain    = net.ReadUInt(8)
    local rarityIdx = net.ReadUInt(SDFSystem.RARITY_BITS)
    local _jackpot  = net.ReadBool()
    local outcome   = net.ReadString()

    if not DBALib or not DBALib.UI then return end

    local notifyType = "info"
    local message    = lootDesc

    if outcome == "success" and hasLoot then
        local rarities = SDFSystem.Config.Rarities
        local rarity   = rarities[rarityIdx + 1]
        if rarity then
            notifyType = rarity.notifyType or "success"
            message    = "Trouvé [" .. rarity.label .. "] : " .. lootDesc
            if xpGain > 0 then
                message = message .. " (+" .. xpGain .. " XP)"
            end
            local snd = rarity.sound or ""
            if snd ~= "" and DBALib.Sound then DBALib.Sound(snd) end
        else
            notifyType = "success"
            if xpGain > 0 then
                message = "Vous avez trouvé : " .. lootDesc .. " (+" .. xpGain .. " XP)"
            end
            DBALib.Sound("ui_notify")
        end

    elseif outcome == "nothing" then
        notifyType = "warning"
        DBALib.Sound("ui_error")

    elseif outcome == "rat" then
        notifyType = "danger"
        DBALib.Sound("ui_error")

    elseif outcome == "contraband" then
        notifyType = "warning"
        DBALib.Sound("ui_levelup")
    end

    DBALib.UI.Notify({ message = message, type = notifyType, duration = 5 })
end)

-- ─── S→C : Fouille annulée ───────────────────────────────────────────────
net.Receive("SDFSystem.SearchCancelled", function()
    local reason = net.ReadString()

    if not DBALib or not DBALib.UI then return end
    DBALib.Sound("ui_error")

    local msg = "Fouille interrompue"
    if reason and reason ~= "" then msg = msg .. " : " .. reason end
    DBALib.UI.Notify({ message = msg, type = "warning", duration = 4 })
end)

-- ─── S→C : Jackpot ───────────────────────────────────────────────────────
-- Overlay doré + "JACKPOT" centré, fade out sur Duration secondes.
net.Receive("SDFSystem.Jackpot", function()
    local duration = net.ReadFloat()

    local startTime = CurTime()
    local snd = SDFSystem.Config.Jackpot.Sound or ""
    if snd ~= "" then
        surface.PlaySound(snd)
    end
    if DBALib and DBALib.Sound then DBALib.Sound("ui_levelup") end

    local hookId = "SDFSystem.JackpotOverlay"
    hook.Add("HUDPaint", hookId, function()
        local age = CurTime() - startTime
        if age > duration then
            hook.Remove("HUDPaint", hookId)
            return
        end

        local alpha  = math.max(0, 1.0 - age / duration)
        local sw, sh = ScrW(), ScrH()
        local pal    = GetPal()
        local gold   = pal.gold and pal.gold.base or Color(212, 175, 55)

        -- Fond doré semi-transparent.
        surface.SetDrawColor(ColorAlpha(gold, math.floor(80 * alpha)))
        surface.DrawRect(0, 0, sw, sh)

        -- Cadre lumineux.
        surface.SetDrawColor(ColorAlpha(gold, math.floor(200 * alpha)))
        surface.DrawOutlinedRect(0, 0, sw, sh, 18)

        -- Texte "JACKPOT".
        local font = GetFont("Display_2xl")
        surface.SetFont(font)
        local tw, th = surface.GetTextSize("JACKPOT")
        local tx = (sw - tw) / 2
        local ty = (sh - th) / 2 - 20

        draw.SimpleText("JACKPOT", font, tx + 3, ty + 3,
            ColorAlpha(Color(0, 0, 0), math.floor(200 * alpha)),
            TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
        draw.SimpleText("JACKPOT", font, tx, ty,
            ColorAlpha(gold, math.floor(255 * alpha)),
            TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)

        draw.SimpleText("Trouvaille légendaire !", GetFont("Body_lg"),
            sw / 2, ty + th + 10,
            ColorAlpha(pal.text and pal.text.primary or Color(232, 236, 240), math.floor(220 * alpha)),
            TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
    end)
end)

-- ─── Effet caméra première personne pendant la fouille ────────────────────
-- Léger balancement de tête + regard incliné vers le bas = sensation de fouiller.
-- État de fouille mis en cache (0.15s) pour éviter un ents.FindByClass par frame.
local fpNextCheck, fpSearching = 0, false
local function LocalSearching()
    local now = CurTime()
    if now > fpNextCheck then
        fpNextCheck = now + 0.15
        fpSearching = SDFSystem.IsSearching and SDFSystem.IsSearching(LocalPlayer()) or false
    end
    return fpSearching
end

hook.Add("CalcView", "SDFSystem.SearchViewBob", function(ply, pos, ang, fov)
    local imm = SDFSystem.Config and SDFSystem.Config.Immersion
    if not imm or not imm.FirstPersonEffect then return end
    if not IsValid(ply) then return end
    if ply:ShouldDrawLocalPlayer() then return end  -- thirdperson actif : ne pas interférer
    if not LocalSearching() then return end

    local t     = CurTime()
    local amp   = tonumber(imm.FirstPersonBobAmp) or 0.8
    local pitch = tonumber(imm.FirstPersonPitch)  or 5

    return {
        origin     = pos + Vector(0, 0, math.sin(t * 7) * amp),
        angles     = ang + Angle(pitch + math.sin(t * 7) * 0.9, math.sin(t * 3.5) * 0.5, 0),
        fov        = fov,
        drawviewer = false,
    }
end)

-- ─── Posture de fouille (manipulation d'os ValveBiped) ────────────────────
-- Debout, buste penché en avant, bras tendus devant qui farfouillent (rotation
-- + haut/bas, mains déphasées). Appliqué localement par chaque client pour
-- chaque joueur en fouille (aucun réseau). Reset propre à la fin.
local ARM_ZERO = Angle(0, 0, 0)
local sdfArmActive = {}  -- [ply] = true tant que sa posture est manipulée

local SPINE_BONES = {
    "ValveBiped.Bip01_Spine",
    "ValveBiped.Bip01_Spine1",
    "ValveBiped.Bip01_Spine2",
}
local ALL_POSE_BONES = {
    "ValveBiped.Bip01_Spine",
    "ValveBiped.Bip01_Spine1",
    "ValveBiped.Bip01_Spine2",
    "ValveBiped.Bip01_R_UpperArm",
    "ValveBiped.Bip01_L_UpperArm",
    "ValveBiped.Bip01_R_Forearm",
    "ValveBiped.Bip01_L_Forearm",
}

local function SetBone(ply, boneName, ang)
    local id = ply:LookupBone(boneName)
    if id then ply:ManipulateBoneAngles(id, ang) end
end

local function ResetPose(ply)
    if not IsValid(ply) then return end
    for _, b in ipairs(ALL_POSE_BONES) do
        SetBone(ply, b, ARM_ZERO)
    end
end

hook.Add("Think", "SDFSystem.ArmAnim", function()
    local imm = SDFSystem.Config and SDFSystem.Config.Immersion
    if not imm or not imm.ArmAnim then return end

    local now     = CurTime()
    local spd     = tonumber(imm.ArmSpeed)    or 8
    local lean    = tonumber(imm.LeanForward) or 22
    local fwd     = tonumber(imm.ArmForward)  or 50
    local spread  = tonumber(imm.ArmSpread)   or 18
    local dig     = tonumber(imm.ArmDig)      or 22
    local updown  = tonumber(imm.ArmUpDown)   or 5
    local rot     = tonumber(imm.ArmRotate)   or 6

    local stillSearching = {}

    for _, ent in ipairs(ents.FindByClass("sdf_trash")) do
        if IsValid(ent) and ent.GetSearcher and ent.GetSearchEnd then
            local ply = ent:GetSearcher()
            local se  = ent:GetSearchEnd()
            if IsValid(ply) and se and se > 0 and now < se then
                stillSearching[ply] = true
                sdfArmActive[ply]   = true

                -- Fouille : l'avant-bras s'étend/se rétracte = allers-retours
                -- AVANT/ARRIÈRE (mouvement principal). Bob vertical et rotation
                -- gardés faibles. Léger déphasage entre les deux mains (1.2 rad).
                local pR = now * spd
                local pL = now * spd + 1.2
                local digR = math.sin(pR) * dig      -- avant/arrière (avant-bras)
                local digL = math.sin(pL) * dig
                local bobR = math.cos(pR) * updown    -- petit haut/bas (épaule)
                local bobL = math.cos(pL) * updown
                local rotR = math.sin(pR) * rot       -- légère rotation poignet
                local rotL = math.sin(pL) * rot

                -- ⚠ Sur ce squelette : pitch (1er) = côté ; yaw (2e) = avant/flexion ;
                --    roll (3e) = rotation.
                -- Buste penché en AVANT.
                SetBone(ply, "ValveBiped.Bip01_Spine",  Angle(0, lean * 0.34, 0))
                SetBone(ply, "ValveBiped.Bip01_Spine1", Angle(0, lean * 0.33, 0))
                SetBone(ply, "ValveBiped.Bip01_Spine2", Angle(0, lean * 0.33, 0))

                -- Bras : reach avant + écartement, avec un léger bob vertical seulement.
                SetBone(ply, "ValveBiped.Bip01_R_UpperArm", Angle( spread, -(fwd + bobR), 0))
                SetBone(ply, "ValveBiped.Bip01_L_UpperArm", Angle(-spread, -(fwd + bobL), 0))

                -- Avant-bras : extension/rétraction (yaw) = geste de fouille avant/arrière.
                SetBone(ply, "ValveBiped.Bip01_R_Forearm",  Angle(0, -(fwd * 0.4 + digR), rotR))
                SetBone(ply, "ValveBiped.Bip01_L_Forearm",  Angle(0, -(fwd * 0.4 + digL), rotL))
            end
        end
    end

    -- Réinitialiser la posture des joueurs qui ne fouillent plus.
    for ply in pairs(sdfArmActive) do
        if not stillSearching[ply] then
            ResetPose(ply)
            sdfArmActive[ply] = nil
        end
    end
end)
