utils/history.lua
---@class History
--- A class that manages a history buffer with undo/redo capabilities.
local History = {}
History.__index = History

script.register_metatable('RedMew_HistoryClass', History)

local DEFAULT_SIZE = 100
local math_min = math.min
local math_max = math.max
local insert = table.insert
local remove = table.remove

--- Creates a new History instance.
---@param size number? Optional maximum size of the history buffer. Defaults to 100 if not provided or invalid.
---@return History
function History.new(size)
    return setmetatable({
        buffer = {},       -- Table storing the history elements
        index = 0,         -- Current position in the history buffer
        max_size = (size and size > 0 or DEFAULT_SIZE) -- Max number of items to store
    }, History)
end

--- Keeps only the elements within the specified range [left, right].
---@param self History The history object.
---@param left number The starting index of the range (1-based).
---@param right number The ending index of the range (1-based).
local function range(self, left, right)
    local result = {}
    local len = #self.buffer

    left = math_max(1, left)
    right = math_min(right, len)

    for i = left, right do
        insert(result, self.buffer[i])
    end

    self.buffer = result

    if self.index > #self.buffer then
        self.index = #self.buffer
    end
end

--- Returns the current size of the history buffer.
---@param self History The history object.
---@return number
function History:size()
    return #self.buffer
end

--- ADD a new element to the history buffer.
--- If the current position is not at the end, discards all "redo" states.
--- Ensures the buffer does not exceed max_size by trimming oldest entries.
---@param self History The history object.
---@param element any The element to add. Nil elements are ignored.
function History:add(element)
    if element == nil then
        return
    end

    if self.index ~= #self.buffer then
        range(self, 1, self.index)
    end

    insert(self.buffer, element)
    self.index = #self.buffer

    if #self.buffer > self.max_size then
        range(self, 1 + #self.buffer - self.max_size, #self.buffer)
    end
end

--- REMOVE the element at the specified index.
---@param self History The history object.
---@param index number? Optional index to remove; defaults to current index.
function History:remove(index)
    if index == nil then
        index = self.index
    end
    if not self.buffer[index] then
        return
    end
    -- Adjust index if removing current or previous
    if self.index >= index then
        self.index = self.index - 1
    end
    remove(self.buffer, index)
end

--- GET the element at the specified index.
--- If no index is provided, returns the current element.
---@param self History The history object.
---@param index number? Optional index to retrieve; defaults to current index.
---@return any
function History:get(index)
    if index == nil then
        index = self.index
    end
    return self.buffer[index]
end

--- GET the current position index in the history buffer.
---@param self History The history object.
---@return number The current index.
function History:get_index()
    return self.index
end

--- GET the previous element in the history without changing the current index.
---@param self History The history object.
---@return any|nil The previous element or nil if none exists.
function History:peek_previous()
    return self.buffer[self.index - 1]
end

--- GET the next element in the history without changing the current index.
---@param self History The history object.
---@return any|nil The next element or nil if none exists.
function History:peek_next()
    return self.buffer[self.index + 1]
end

--- MOVE to the previous history element, if possible.
--- Updates the current index and returns the element.
---@param self History The history object.
---@return any|nil The previous element, or nil if at the beginning.
function History:previous()
    if not self.buffer[self.index - 1] then
        return nil
    end
    self.index = self.index - 1
    return self.buffer[self.index]
end

--- MOVE to the next history element, if possible.
--- Updates the current index and returns the element.
---@param self History The history object.
---@return any|nil The next element, or nil if at the latest.
function History:next()
    if not self.buffer[self.index + 1] then
        return nil
    end
    self.index = self.index + 1
    return self.buffer[self.index]
end

--- CLEAR the entire history buffer and resets the current index.
---@param self History The history object.
function History:clear()
    self.buffer = {}
    self.index = 0
end

return History
