--- A table to keep track of all created Peds.
local Peds = {}
local distPeds = {}

--- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area.
--
-- This function sets up a circular area using `createCirclePoly`. When the player enters this area, a Ped is created using `makePed`.
-- When the player exits the area, the Ped is deleted.
--
---@param data table A table containing Ped data and properties. Should include at least `model` and `coords`.
---@param coords vector4 A `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
---@param scenario boolean (optional) String specifying the scenario the Ped should perform.
---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
--
---@usage
-- ```lua
-- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true)
-- ```
function makeDistPed(data, coords, freeze, collision, scenario, anim, synced, func)
    local zoneCoords = type(data) == "table" and data.coords or coords
    local randName = keyGen()..keyGen()
    distPeds[#distPeds+1] = createCirclePoly({
        name = randName,
        coords = vec3(zoneCoords.x, zoneCoords.y, zoneCoords.z - 1.03),
        radius = 50.0,
        onEnter = function()
            Peds[randName] = makePed(data, coords, freeze, collision, scenario, anim, synced)
            if func then func(Peds[randName]) end
        end,
        onExit = function()
            DeletePed(Peds[randName])
            Peds[randName] = nil
        end,
        debug = debugMode,
    })
end

--- Creates a Ped (pedestrian character) with specified properties.
--
-- This function creates a Ped at the given coordinates and applies appearance and clothing based on the provided data.
--
-- If `data` is a table with `custom` properties, it customizes the Ped's appearance accordingly.
--
---@param data modelHash|table Either a string/model hash of the Ped model to use, or a table containing `model` and `custom` data.
---@param coords vector4 `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
---@param scenario string (optional) String specifying the scenario the Ped should perform.
---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
--
---@return ped entityID The handle of the created Ped.
--
---@usage
-- ```lua
-- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true)
-- ```
function makePed(data, coords, freeze, collision, scenario, anim, synced, fade)
    local ped = nil
    local model = nil
    if type(data) == "table" then
        model = data.model
        loadModel(data.model)
        ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
        -- Inheritance
        SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)

        -- Face Features
        for k, v in pairs({
            "noseWidth", "noseHeight", "noseSize", "noseBoneHeight", "nosePeakHeight", "noseBoneTwist",
            "eyebrowHeight", "eyebrowDepth",
            "cheekBoneHeight", "cheekBoneWidth", "cheeckWidth",
            "eyeOpening", "lipThickness",
            "jawWidth", "jawSize",
            "chinLowering", "chinLength", "chinSize", "chinHole",
            "neckThickness"
        }) do
            SetPedFaceFeature(ped, k - 1, data.custom[v])
        end

        -- Appearance
        SetPedComponentVariation(ped, 2, data.custom.Hair, 0, 0)
        SetPedHairColor(ped, data.custom.HairTexture, data.custom.HairHighlight or 0)
        SetPedHeadOverlay(ped, 2, data.custom.Eyebrows, data.custom.EyebrowsOpacity)
        SetPedHeadOverlayColor(ped, 2, 1, data.custom.EyebrowsColor, 0)
        SetPedEyeColor(ped, data.custom.Eyecolor)
        SetPedHeadOverlay(ped, 4, data.custom.Makeup, data.custom.MakeupOpacity)
        SetPedHeadOverlayColor(ped, 4, 1, data.custom.MakeupColor, 0)
        SetPedHeadOverlay(ped, 8, data.custom.Lipstick, data.custom.LipstickOpacity)
        SetPedHeadOverlayColor(ped, 8, 1, data.custom.LipstickColor, 0)
        SetPedHeadOverlay(ped, 1, data.custom.Beard, data.custom.BeardOpacity)
        SetPedHeadOverlayColor(ped, 1, 1, data.custom.BeardColor, 0)

        -- Clothes
        SetPedComponentVariation(ped, 1, data.custom.Mask, data.custom.MaskVariant, 0)
        SetPedComponentVariation(ped, 7, data.custom.Scarf, data.custom.ScarfVariant, 0)
        SetPedComponentVariation(ped, 11, data.custom.Jacket, data.custom.JacketVariant, 0)
        SetPedComponentVariation(ped, 8, data.custom.Shirt, data.custom.ShirtVariant, 0)
        SetPedComponentVariation(ped, 9, data.custom.Vest, data.custom.VestVariant, 0)
        SetPedComponentVariation(ped, 5, data.custom.Bags, data.custom.BagsVariant, 0)
        SetPedComponentVariation(ped, 3, data.custom.Arms, data.custom.ArmsVariant, 0)
        SetPedComponentVariation(ped, 4, data.custom.Pants, data.custom.PantsVariant, 0)
        SetPedComponentVariation(ped, 6, data.custom.Shoes, data.custom.ShoesVariant, 0)
        SetPedComponentVariation(ped, 10, data.custom.Decal, data.custom.DecalVariant, 0)

        -- Accessories
        SetPedPropIndex(ped, 0, data.custom.Hat, data.custom.HatVariant, true)
        SetPedPropIndex(ped, 1, data.custom.Glasses, data.custom.GlassesVariant, true)

        SetPedPropIndex(ped, 2, data.custom.Ear, data.custom.EarVariant, true)
        SetPedPropIndex(ped, 6, data.custom.Watches, data.custom.WatchesVariant, true)
        SetPedPropIndex(ped, 7, data.custom.Bracelets, data.custom.BraceletsVariant, true)
    else
        model = data
        loadModel(model)
        if gameName == "rdr3" then
            ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
            SetEntityAlpha(ped, 0, false)
            SetEntityVisible(ped, 1) -- SetEntityVisible
            SetEntityAlpha(ped, 255, false) -- SetEntityAlpha
            SetRandomOutfitVariation(ped, true) -- Invisible without
        else
            ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
        end
    end

    SetEntityInvincible(ped, true)
    SetBlockingOfNonTemporaryEvents(ped, true)
	FreezeEntityPosition(ped, freeze and freeze or true)

    if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
    if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
    if anim then
        loadAnimDict(anim[1])
        TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
    end
    if DoesEntityExist(ped) then
        debugPrint("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^5"..(model).."^7' | ^2Coord^7: "..formatCoord(coords))
    else
        print("error ped")
    end
    unloadModel(model)
    Peds[keyGen()..keyGen()] = ped
    if fade ~= false and gameName ~= "rdr3" then
        SetEntityAlpha(ped, 0, false)
        CreateThread(function()
            fadeInEnt(ped)
        end)
    end
    return ped
end

--- Generates random Ped data by filling in missing customization options with random values.
--
-- This function takes in a data table that may have some customization options missing in `data.custom`.
--
-- It generates random values for any missing options and returns a new data table with complete customization.
--
---@param data table A table containing at least a `model` field, and possibly a `custom` table with customization options.
--
---@return generatedTable table A new table containing `model` and `custom` with all customization options filled.
--
---@usage
-- ```lua
-- local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
-- ```
function GenerateRandomPedData(data)
    local newTable = {
        model = data.model,
        custom = {},
    }
    local isMale = data.model == `MP_M_Freemode_01`
    local randomTable = {
        -- Inheritance
        faceFather = math.random(0, 45), faceMother = math.random(0, 45), faceMix = (math.random(0, 9) / 10),
        skinFather = math.random(0, 45), skinMother = math.random(0, 45), skinMix = (math.random(0, 9) / 10),
        raceShape = math.random(0, 45), raceSkin = math.random(0, 45), raceMix = (math.random(0, 9) / 10),

        -- Face Features
        noseWidth = (math.random(0, 9) / 10),
        noseHeight = (math.random(0, 9) / 10),
        noseSize = (math.random(0, 9) / 10),
        noseBoneHeight = (math.random(0, 9) / 10),
        nosePeakHeight = (math.random(0, 9) / 10),
        noseBoneTwist = (math.random(0, 9) / 10),

        eyebrowHeight = (math.random(0, 9) / 10),
        eyebrowDepth = (math.random(0, 9) / 10),

        cheekBoneHeight = (math.random(0, 9) / 10),
        cheekBoneWidth = (math.random(0, 9) / 10),
        cheeckWidth = (math.random(0, 9) / 10),

        eyeOpening = (math.random(0, 9) / 10),
        lipThickness = (math.random(0, 9) / 10),

        jawWidth = (math.random(0, 9) / 10),
        jawSize = (math.random(0, 9) / 10),

        chinLowering = (math.random(0, 9) / 10),
        chinLength = (math.random(0, 9) / 10),
        chinSize = (math.random(0, 9) / 10),
        chinHole = (math.random(0, 9) / 10),

        neckThickness = (math.random(0, 9) / 10),

        -- Appearance
        Hair = math.random(0, isMale and 147 or 261), HairTexture = math.random(0, 63), HairHighlight = math.random(0, 63),
        Eyebrows = math.random(0, 33),
        EyebrowsOpacity = 0.9, EyebrowsColor = 0,
        Eyecolor = math.random(0, 30),
        Makeup = 0, MakeupOpacity = 0, MakeupColor = 0,
        Lipstick = 0, LipstickOpacity = 0, LipstickColor = 0,
        Beard = isMale and math.random(0, 28) or -1,
        BeardOpacity = isMale and 0.9 or 0.0, BeardColor = 0,

        -- Clothing
        Mask = math.random(0, 252), MaskVariant = 0,
        Scarf = math.random(0, isMale and 249 or 198), ScarfVariant = 0,
        Jacket = math.random(0, isMale and 634 or 713), JacketVariant = 0,
        Shirt = math.random(0, isMale and 237 or 299), ShirtVariant = 0,
        Vest = math.random(0, isMale and 81 or 91), VestVariant = 0,
        Bags = math.random(0, isMale and 138 or 148), BagsVariant = 0,
        Arms = math.random(0, isMale and 224 or 261), ArmsVariant = 0,
        Pants = math.random(0, isMale and 255 or 275), PantsVariant = 0,
        Shoes = math.random(0, isMale and 157 or 199), ShoesVariant = 0,
        Decal = math.random(0, isMale and 238 or 253), DecalVariant = 0,

        -- Accessories
        Hat = math.random(0, isMale and 232 or 229), HatVariant = 0,
        Glasses = math.random(0, isMale and 68 or 71), GlassesVariant = 0,
        Ear = math.random(0, isMale and 51 or 40), EarVariant = 0,
        Watches = math.random(0, isMale and 46 or 35), WatchesVariant = 0,
        Bracelets = math.random(0, isMale and 13 or 20), BraceletsVariant = 0,
    }
    for option in pairs(randomTable) do
        if not data.custom[option] then
            newTable.custom[option] = randomTable[option]
            debugPrint("^6Bridge^7: ^2Picking Random Ped option ^7[^5"..option.."^7]: ^6"..newTable.custom[option].."^7")
        else
            newTable.custom[option] = data.custom[option]
        end
    end
    return newTable
end

onPlayerUnload(function()
    for k in pairs(Peds) do
        DeletePed(Peds[k])
    end
    for i = 1, #distPeds do
        removeZoneTarget(distPeds[i])
    end
    distPeds = {}
end)

--- Cleans up all created Peds when the resource stops.
onResourceStop(function()
    for k in pairs(Peds) do
        DeletePed(Peds[k])
    end
end, true)
