local OPCODE = 204
local marked = {}
local pulseEvent
local bright = false

local function isHellish(creature)
  return creature and creature:isMonster() and creature:getName():sub(1, 8) == 'Hellish '
end

local function pulseMarks()
  bright = not bright
  local color = bright and '#ff2020' or '#ff8a20'
  local player = g_game.getLocalPlayer()
  local position = player and player:getPosition()
  if position then
    for _, creature in ipairs(g_map.getSpectators(position, false)) do
      if isHellish(creature) then
        marked[creature:getId()] = true
      end
    end
  end
  for creatureId in pairs(marked) do
    local creature = g_map.getCreatureById(creatureId)
    -- Random spawns are renamed by the server after their initial appearance.
    -- Older clients can keep the original cached name (for example "Rat"),
    -- so an opcode mark must remain authoritative for the creature lifetime.
    if creature then
      pcall(function() creature:setInformationColor(color) end)
      creature:clearText()
    else
      marked[creatureId] = nil
    end
  end
  pulseEvent = scheduleEvent(pulseMarks, 300)
end

local function applyMark(creatureId, attempts, hellishName)
  local creature = g_map.getCreatureById(creatureId)
  if creature then
    if hellishName and hellishName:sub(1, 8) == 'Hellish ' then
      pcall(function() creature:setName(hellishName) end)
    end
    creature:setOutfitShader('outfit_repera_elite_outline')
    pcall(function() creature:setInformationColor('#ff2020') end)
    creature:clearText()
    marked[creatureId] = true
    return
  end
  if attempts > 0 then
    scheduleEvent(function() applyMark(creatureId, attempts - 1, hellishName) end, 100)
  end
end

local function onOpcode(protocol, opcode, buffer)
  local ok, message = pcall(json.decode, buffer)
  if not ok or type(message) ~= 'table' then return end
  if (message.action == 'mark' or message.action == 'hellish') and message.creatureId then
    applyMark(tonumber(message.creatureId), 10, message.name)
  end
end

local function onCreatureAppear(creature)
  if isHellish(creature) then
    applyMark(creature:getId(), 2)
  end
end

local function onCreatureDisappear(creature)
  marked[creature:getId()] = nil
end

local function onGameEnd()
  marked = {}
end

function init()
  ProtocolGame.registerExtendedOpcode(OPCODE, onOpcode)
  connect(Creature, { onAppear = onCreatureAppear, onDisappear = onCreatureDisappear })
  connect(g_game, { onGameEnd = onGameEnd })
  pulseMarks()
end

function terminate()
  ProtocolGame.unregisterExtendedOpcode(OPCODE)
  disconnect(Creature, { onAppear = onCreatureAppear, onDisappear = onCreatureDisappear })
  disconnect(g_game, { onGameEnd = onGameEnd })
  if pulseEvent then
    removeEvent(pulseEvent)
    pulseEvent = nil
  end
  for creatureId in pairs(marked) do
    local creature = g_map.getCreatureById(creatureId)
    if creature then creature:resetInformationColor() end
  end
  marked = {}
end
