-- laundering/sv_commands.lua
-- Concommands admin : spawn_npc, remove_npc, list_npcs, cleanup.
-- Permission : SuperAdmin in-game, ou console RCON (ply == NULL).

Laundering = Laundering or {}

-- ============================================================================
-- Helper : check permission
-- ============================================================================

local function isSuperAdminOrRcon(ply)
    if not IsValid(ply) then return true end  -- console RCON
    return ply:IsSuperAdmin()
end

-- Helper : feedback (console ou notify client via net)
local function feedback(ply, msg)
    if IsValid(ply) then
        -- Net send via Laundering.Error réutilisé pour simplicité admin
        -- (type "info" pour feedback positif, on envoie juste le message)
        net.Start("Laundering.Error")
            net.WriteString(string.sub(tostring(msg), 1, 128))
        net.Send(ply)
    end
    print("[Laundering] " .. tostring(msg))
end

-- ============================================================================
-- Résolution de coordonnées
-- Si ply valide et args vides → trace POV
-- Si ply invalide (RCON) et args = {x, y, z, yaw} → utiliser ces coords
-- ============================================================================

local function resolveCoords(ply, args)
    if IsValid(ply) then
        -- Trace depuis le POV du joueur
        local tr = ply:GetEyeTrace()
        if not tr or not tr.Hit then
            return nil, nil, "Trace invalide — regardez une surface."
        end
        return tr.HitPos, ply:EyeAngles().y, nil
    else
        -- Console RCON : nécessite x y z yaw
        if #args < 4 then
            return nil, nil, "Usage console : laundering_spawn_npc <x> <y> <z> <yaw>"
        end
        local x   = tonumber(args[1])
        local y   = tonumber(args[2])
        local z   = tonumber(args[3])
        local yaw = tonumber(args[4])
        if not x or not y or not z or not yaw then
            return nil, nil, "Coordonnées invalides (doivent être des nombres)."
        end
        return Vector(x, y, z), yaw, nil
    end
end

-- ============================================================================
-- laundering_spawn_npc
-- In-game : regarde une surface → spawn NPC à cet endroit
-- Console  : laundering_spawn_npc <x> <y> <z> <yaw>
-- ============================================================================

concommand.Add("laundering_spawn_npc", function(ply, cmd, args)
    if not isSuperAdminOrRcon(ply) then
        if IsValid(ply) then
            DarkRP.notify(ply, 4, 5, "Permission refusée — SuperAdmin requis.")
        end
        return
    end

    local pos, yaw, err = resolveCoords(ply, args)
    if err then
        feedback(ply, "Erreur : " .. err)
        return
    end

    -- Sauvegarde en DB
    local mapName = game.GetMap()
    local id = Laundering.Storage.SaveNPC(mapName, pos, yaw)
    if not id then
        feedback(ply, "Erreur SQL lors de la sauvegarde du NPC.")
        return
    end

    -- Spawn de l'entité
    local ent = Laundering.SpawnNPC(pos, yaw, id)
    if not IsValid(ent) then
        feedback(ply, "Erreur lors du spawn du NPC (entité invalide).")
        Laundering.Storage.RemoveNPC(id)
        return
    end

    feedback(ply, string.format(
        "NPC #%d spawné à (%.0f, %.0f, %.0f) yaw=%.1f sur map '%s'.",
        id, pos.x, pos.y, pos.z, yaw, mapName
    ))
end)

-- ============================================================================
-- laundering_remove_npc
-- In-game : regarde le NPC → le supprime
-- Console  : non supporté (pas de trace disponible)
-- ============================================================================

concommand.Add("laundering_remove_npc", function(ply, cmd, args)
    if not isSuperAdminOrRcon(ply) then
        if IsValid(ply) then
            DarkRP.notify(ply, 4, 5, "Permission refusée — SuperAdmin requis.")
        end
        return
    end

    if not IsValid(ply) then
        print("[Laundering] laundering_remove_npc requiert un joueur in-game (trace POV).")
        return
    end

    local tr = ply:GetEyeTrace()
    if not IsValid(tr.Entity) then
        feedback(ply, "Aucune entité visée.")
        return
    end

    local ent = tr.Entity
    if ent:GetClass() ~= "laundering_npc" then
        feedback(ply, "L'entité visée n'est pas un NPC blanchisseur.")
        return
    end

    local npcID = ent.LaunderingNPCID
    if npcID then
        local ok = Laundering.Storage.RemoveNPC(npcID)
        if not ok then
            feedback(ply, string.format("Erreur SQL lors de la suppression du NPC #%d.", npcID))
            return
        end
    else
        feedback(ply, "Attention : NPC sans ID (non persisté). Suppression en mémoire seulement.")
    end

    Laundering._npcs[ent] = nil
    ent._intentionalRemove = true  -- évite que EntityRemoved hook respawn
    ent:Remove()

    feedback(ply, string.format(
        "NPC %s supprimé.",
        npcID and ("#" .. npcID) or "(non persisté)"
    ))
end)

-- ============================================================================
-- laundering_list_npcs
-- Affiche tous les NPCs de la map courante (id, position)
-- ============================================================================

concommand.Add("laundering_list_npcs", function(ply, cmd, args)
    if not isSuperAdminOrRcon(ply) then
        if IsValid(ply) then
            DarkRP.notify(ply, 4, 5, "Permission refusée — SuperAdmin requis.")
        end
        return
    end

    local mapName = game.GetMap()
    local rows    = Laundering.Storage.LoadNPCsForMap(mapName)

    print(string.format("[Laundering] NPCs sur la map '%s' (%d):", mapName, #rows))
    for _, row in ipairs(rows) do
        print(string.format(
            "  #%d  pos=(%.0f, %.0f, %.0f)  yaw=%.1f",
            row.id, row.x, row.y, row.z, row.angle_y
        ))
    end

    if IsValid(ply) then
        feedback(ply, string.format(
            "%d NPC(s) sur '%s' — voir console serveur pour détails.",
            #rows, mapName
        ))
    end
end)

-- ============================================================================
-- laundering_cleanup
-- Retire les entités laundering_npc orphelines (non tracées dans _npcs)
-- ============================================================================

concommand.Add("laundering_cleanup", function(ply, cmd, args)
    if not isSuperAdminOrRcon(ply) then
        if IsValid(ply) then
            DarkRP.notify(ply, 4, 5, "Permission refusée — SuperAdmin requis.")
        end
        return
    end

    -- Refresh complet : supprime tous les NPCs en jeu (marqués intentionnel pour
    -- éviter le respawn auto via EntityRemoved hook) puis recharge depuis la DB.
    local existing = ents.FindByClass("laundering_npc")
    local removed  = 0
    for _, ent in ipairs(existing) do
        if IsValid(ent) then
            ent._intentionalRemove = true
            ent:Remove()
            removed = removed + 1
        end
    end
    Laundering._npcs = {}

    local mapName = game.GetMap()
    local rows    = Laundering.Storage.LoadNPCsForMap(mapName)
    local spawned = 0
    for _, row in ipairs(rows) do
        local ent = Laundering.SpawnNPC(Vector(row.x, row.y, row.z), row.angle_y, row.id)
        if IsValid(ent) then spawned = spawned + 1 end
    end

    feedback(ply, string.format(
        "Cleanup terminé : %d NPC(s) supprimé(s), %d NPC(s) rechargé(s) depuis la DB.",
        removed, spawned
    ))
end)
