---@class PriorityQueue
---@field data any[] внутренний массив-куча (индексация с 1)
---@field private cmp fun(a:any, b:any):boolean компаратор: true, если a выше по приоритету, чем b
local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue

---Создать очередь с приоритетом.
---@param cmp fun(a:any, b:any):boolean|nil если nil, используется a < b (мин-куча)
---@return PriorityQueue
function PriorityQueue.new(cmp)
    local self = setmetatable({}, PriorityQueue)
    self.data = {}
    self.cmp = cmp or function(a, b) return a < b end
    return self
end

-- ===== Внутренние утилиты =====

---@param i integer индекс узла
---@param j integer индекс узла
function PriorityQueue:_swap(i, j)
    self.data[i], self.data[j] = self.data[j], self.data[i]
end

---@param i integer
function PriorityQueue:_sift_up(i)
    while i > 1 do
        local p = math.floor(i / 2)
        if self.cmp(self.data[i], self.data[p]) then
            self:_swap(i, p)
            i = p
        else
            break
        end
    end
end

---@param i integer
function PriorityQueue:_sift_down(i)
    local n = #self.data
    while true do
        local l, r = i * 2, i * 2 + 1
        local best = i

        if l <= n and self.cmp(self.data[l], self.data[best]) then
            best = l
        end
        if r <= n and self.cmp(self.data[r], self.data[best]) then
            best = r
        end

        if best ~= i then
            self:_swap(i, best)
            i = best
        else
            break
        end
    end
end

-- ===== Публичный API =====

---Вставка элемента.
---@param x any
function PriorityQueue:insert(x)
    table.insert(self.data, x)
    self:_sift_up(#self.data)
end

---Посмотреть первый элемент без удаления.
---@return any|nil
function PriorityQueue:peek()
    return self.data[1]
end

---Извлечь первый элемент (корень кучи).
---@return any|nil
function PriorityQueue:pop()
    local n = #self.data
    if n == 0 then return nil end

    local top = self.data[1]
    local last = self.data[n]
    self.data[n] = nil

    if n > 1 then
        self.data[1] = last
        self:_sift_down(1)
    end

    return top
end

---Текущее количество элементов.
---@return integer
function PriorityQueue:size()
    return #self.data
end

---Пуста ли очередь.
---@return boolean
function PriorityQueue:is_empty()
    return #self.data == 0
end

--- Shallow-копирование очереди
function PriorityQueue:copy()
    local _data = {}
    for i, v in ipairs(self.data) do
        _data[i] = v
    end
    return setmetatable({
        data = _data,
        cmp = self.cmp
    }, PriorityQueue)
end

return PriorityQueue
