--- A pure data container that is contained by a single entity.
-- @classmod Component

local PATH = (...):gsub('%.[^%.]+$', '')

local Components = require(PATH..".components")
local Utils      = require(PATH..".utils")

local Component = {}
Component.__mt = {
   __index = Component,
}

--- Creates a new ComponentClass.
-- @tparam function populate Function that populates a Component with values
-- @treturn Component A new ComponentClass
function Component.new(name, populate)
   if (type(name) ~= "string") then
      Utils.error(2, "bad argument #1 to 'Component.new' (string expected, got %s)", type(name))
   end

   if (string.match(name, Components.__REJECT_MATCH) ~= "") then
      Utils.error(2, "bad argument #1 to 'Component.new' (Component names can't start with '%s', got %s)", Components.__REJECT_PREFIX, name)
   end

   if (rawget(Components, name)) then
      Utils.error(2, "bad argument #1 to 'Component.new' (ComponentClass with name '%s' was already registerd)", name) -- luacheck: ignore
   end

   if (type(populate) ~= "function" and type(populate) ~= "nil") then
      Utils.error(2, "bad argument #1 to 'Component.new' (function/nil expected, got %s)", type(populate))
   end

   local componentClass = setmetatable({
      __populate = populate,

      __name             = name,
      __isComponentClass = true,
   }, Component.__mt)

   componentClass.__mt = {
      __index = componentClass
   }

   Components[name] = componentClass

   return componentClass
end

-- Internal: Populates a Component with values.
function Component:__populate() -- luacheck: ignore
end

-- Callback: When the Component gets removed or replaced in an Entity.
function Component:removed() -- luacheck: ignore
end

-- Callback: When the Component gets serialized as part of an Entity.
function Component:serialize()
   local data = Utils.shallowCopy(self, {})

   --This values shouldn't be copied over
   data.__componentClass   = nil
   data.__entity           = nil
   data.__isComponent      = nil
   data.__isComponentClass = nil

   return data
end

-- Callback: When the Component gets deserialized from serialized data.
function Component:deserialize(data)
   Utils.shallowCopy(data, self)
end

-- Internal: Creates a new Component.
-- @param entity The Entity that will receive this Component.
-- @return A new Component
function Component:__new(entity)
   local component = setmetatable({
      __componentClass = self,

      __entity           = entity,
      __isComponent      = true,
      __isComponentClass = false,
   }, self.__mt)

   return component
end

-- Internal: Creates and populates a new Component.
-- @param entity The Entity that will receive this Component.
-- @param ... Varargs passed to the populate function
-- @return A new populated Component
function Component:__initialize(entity, ...)
   local component = self:__new(entity)

   ---@diagnostic disable-next-line: redundant-parameter
   self.__populate(component, ...)

   return component
end

--- Returns true if the Component has a name.
-- @treturn boolean
function Component:hasName()
   return self.__name and true or false
end

--- Returns the name of the Component.
-- @treturn string
function Component:getName()
   return self.__name
end

return setmetatable(Component, {
   __call = function(_, ...)
      return Component.new(...)
   end,
})
