local elemname = "bform"

---@class bform : bform_prototype
---@field pl table
---@field send_on_update boolean
---@field auth_enabled boolean
---@field is_active boolean
---@field formname string
---@field version number
---@field hash string
---@field store table -- allows elements to store stuff during creation, gets reset every render
---@field prepend table
---@field changes_acknowledged boolean
---@field on_changed function
local class = setmetatable({}, {__index = aom_bform.prototype})
class.type = elemname

---@return string
function class:render(data, ...)
    if not self.store then self.store = {} end
    for k, v in pairs(self.store) do self.store[k] = nil end
    self:apply_offsets()
    local fs = {}
    table.insert(fs, "formspec_version["..self.version.."]")
    table.insert(fs, "size["..self.size[1]..","..self.size[2].."]")
    table.insert(fs, "no_prepend[]")
    if self.prepend then
        table.insert(fs, table.concat(self.prepend))
    end
     self:render_children(fs, data, ...)
    return table.concat(fs)
end

---@return string, string
function class:get_hash_elem(player)
    local pi = self:check_player(player)
    local hash = string.sub(core.sha1(tostring({}) .. tostring(math.random())), 1, 16)
    pi.last_hash = hash
    local fs = {}
    table.insert(fs, "field[99,99;0,0;"..hash..";hash;hash]")
    table.insert(fs, "field_close_on_enter["..hash..";false]")
    table.insert(fs, "set_focus[nofocus;true]")
    table.insert(fs, "button[99,99;0,0;nofocus; ]")
    table.insert(fs, "field_close_on_enter[nofocus;false]")
    return table.concat(fs), hash
end

---@return string
function class:get_form(player, forceupdate)
    aom_bform.shown[self.formname] = self
    if forceupdate or (self._last_formspec == nil) or (not self.changes_acknowledged) then
        local c = os.clock()
        self._last_formspec = self:render({player=player})
        aom_bform.debug("rendered a formspec and took " .. tostring((os.clock() - c)*1000) .. "ms")
    end
    -- don't store the hash elem in the form, store it for each player instead inside `get_hash_elem`
    local formspec = self._last_formspec
    if self.auth_enabled then
        local hash_fs, hash = self:get_hash_elem(player)
        aom_bform.debug(hash .. " is current hash given", "auth")
        formspec = formspec .. hash_fs
    end
    return formspec
end

---@return table
function class:check_player(player)
    local pi = self.pl[player]
    if not pi then pi = {}; self.pl[player] = pi end
    return pi
end

---@return bform
function class:show_form(player, forceupdate)
    core.show_formspec(player:get_player_name(), self.formname, self:get_form(player, forceupdate))
    local pi = self:check_player(player)
    pi.is_active = true
    return self
end

function class:on_form_closed(player)
    local pi = self:check_player(player)
    pi.is_active = false
end

function class:on_changed(sources)
    if self.send_on_update then
        for player, pi in pairs(self.pl) do if pi.is_active then
            self:show_form(player, true)
        end end
    end
end

---@return boolean
function class:is_auth(player, fields)
    if not core.is_player(player) then return false end
    local pi = self:check_player(player)
    if pi.last_hash == nil then return false end
    return (fields[pi.last_hash] ~= nil)
end

---@param self bform
---@param children table
---@return bform
function class:add_children(children)
    for i, child in ipairs(children or {}) do
        self:add_child(child)
    end
    aom_bform.prototype.signal_changes(self)
    aom_bform.prototype.init_children(self)
    return self
end

---@return bform
function class:new(size, version, formname)
    local ret = {
        formname = formname or "no_form_name",
        id = "root",
        size = size or {24, 12},
        version = version or 6,
        children = {},
        hash = "",
        pl = {},
        send_on_update = false,
        auth_enabled = true,
        changes_acknowledged = false,
        store = {},
        id_reg = {},
    }
    return setmetatable(ret, {__index = class})
end

aom_bform.element.form = class
