-- ---------------------------------------------------------------------------
--  Building a freemode ped from an appearance table.
--
--  The same code paints the creator preview and the live player, so what you
--  saw while editing is exactly what walks into the world.
-- ---------------------------------------------------------------------------

PedBuild = {}

local NO_OVERLAY = 255      -- the native's "none" value; our model stores -1

local function loadModel(name)
  local hash = joaat(name)
  if not IsModelInCdimage(hash) or not IsModelValid(hash) then return nil end
  RequestModel(hash)
  local waited = 0
  while not HasModelLoaded(hash) and waited < 10000 do
    Wait(10)
    waited = waited + 10
  end
  if not HasModelLoaded(hash) then return nil end
  return hash
end

PedBuild.LoadModel = loadModel

--- Apply an appearance table to an existing freemode ped.
function PedBuild.Apply(ped, a)
  if not ped or not DoesEntityExist(ped) or type(a) ~= 'table' then return end

  SetPedHeadBlendData(
    ped,
    a.parents.father, a.parents.mother, 0,
    a.parents.father, a.parents.mother, 0,
    a.parents.shapeMix + 0.0, a.parents.skinMix + 0.0, 0.0,
    false
  )

  for _, f in ipairs(Appearance.FEATURES) do
    local v = a.features[tostring(f.id)]
    if v then SetPedFaceFeature(ped, f.id, v + 0.0) end
  end

  for _, o in ipairs(Appearance.OVERLAYS) do
    local ov = a.overlays[o.key]
    if ov then
      local index = (ov.index == nil or ov.index < 0) and NO_OVERLAY or ov.index
      SetPedHeadOverlay(ped, o.id, index, ov.opacity + 0.0)
      if o.tint and index ~= NO_OVERLAY then
        -- tint type 1 = hair palette, 2 = make-up palette
        SetPedHeadOverlayColor(ped, o.id, o.tint == 'hair' and 1 or 2, ov.colour, ov.colour)
      end
    end
  end

  SetPedComponentVariation(ped, 2, a.hair.style, 0, 0)
  SetPedHairColor(ped, a.hair.colour, a.hair.highlight)
  SetPedEyeColor(ped, a.eyeColour)

  for _, c in ipairs(Appearance.COMPONENTS) do
    local cc = a.components[c.key]
    if cc then SetPedComponentVariation(ped, c.id, cc.drawable, cc.texture, 0) end
  end

  for _, p in ipairs(Appearance.PROPS) do
    local pp = a.props[p.key]
    if pp then
      if not pp.drawable or pp.drawable < 0 then
        ClearPedProp(ped, p.id)
      else
        SetPedPropIndex(ped, p.id, pp.drawable, pp.texture, true)
      end
    end
  end
end

--- How many variations this particular model actually has, so the creator's
--- sliders stop at real values instead of guessed ones.
function PedBuild.Limits(ped)
  local limits = { components = {}, props = {}, overlays = {}, hair = 0 }
  if not ped or not DoesEntityExist(ped) then return limits end

  limits.hair = math.max(0, GetNumberOfPedDrawableVariations(ped, 2) - 1)

  for _, c in ipairs(Appearance.COMPONENTS) do
    local drawables = math.max(0, GetNumberOfPedDrawableVariations(ped, c.id) - 1)
    local current = GetPedDrawableVariation(ped, c.id)
    limits.components[c.key] = {
      drawable = drawables,
      texture  = math.max(0, GetNumberOfPedTextureVariations(ped, c.id, current) - 1),
    }
  end

  for _, p in ipairs(Appearance.PROPS) do
    local drawables = math.max(-1, GetNumberOfPedPropDrawableVariations(ped, p.id) - 1)
    local current = GetPedPropIndex(ped, p.id)
    limits.props[p.key] = {
      drawable = drawables,
      texture  = math.max(0, GetNumberOfPedPropTextureVariations(ped, p.id, current) - 1),
    }
  end

  for _, o in ipairs(Appearance.OVERLAYS) do
    limits.overlays[o.key] = o.max
  end

  return limits
end

--- Create a standalone preview ped (never networked).
function PedBuild.CreatePreview(appearance, coords, heading)
  local modelName = Appearance.MODELS[appearance.model] or Appearance.MODELS.m
  local hash = loadModel(modelName)
  if not hash then return nil end

  local ped = CreatePed(2, hash, coords.x, coords.y, coords.z, heading + 0.0, false, false)
  SetModelAsNoLongerNeeded(hash)

  SetEntityInvincible(ped, true)
  FreezeEntityPosition(ped, true)
  SetBlockingOfNonTemporaryEvents(ped, true)
  SetPedDefaultComponentVariation(ped)
  SetPedCanRagdoll(ped, false)
  SetEntityCollision(ped, false, false)

  PedBuild.Apply(ped, appearance)
  return ped
end

--- Turn the local player into this character.
function PedBuild.ApplyToPlayer(appearance)
  local modelName = Appearance.MODELS[appearance.model] or Appearance.MODELS.m
  local hash = loadModel(modelName)
  if not hash then return false end

  SetPlayerModel(PlayerId(), hash)
  SetModelAsNoLongerNeeded(hash)

  local ped = PlayerPedId()
  SetPedDefaultComponentVariation(ped)
  PedBuild.Apply(ped, appearance)
  -- freemode peds start with no head blend applied until this settles a frame
  Wait(50)
  PedBuild.Apply(ped, appearance)
  return true
end

--- A calm idle so the preview does not stand like a mannequin.
function PedBuild.Idle(ped)
  local dict = 'anim@heists@heist_corona@team_idles@female_a'
  RequestAnimDict(dict)
  local waited = 0
  while not HasAnimDictLoaded(dict) and waited < 3000 do
    Wait(10)
    waited = waited + 10
  end
  if HasAnimDictLoaded(dict) then
    TaskPlayAnim(ped, dict, 'idle', 2.0, 2.0, -1, 1, 0, false, false, false)
  end
end
