local log = require("neo-tree.log")

---@class neotree.collections.ListNode
---@field prev neotree.collections.ListNode?
---@field next neotree.collections.ListNode?
---@field value any

local Node = {}
function Node:new(value)
  local props = { prev = nil, next = nil, value = value }
  setmetatable(props, self)
  self.__index = self
  return props
end

---@class neotree.collections.LinkedList
---@field head neotree.collections.ListNode?
---@field tail neotree.collections.ListNode?
---@field size integer
local LinkedList = {}

---@return neotree.collections.LinkedList
function LinkedList:new()
  local props = { head = nil, tail = nil, size = 0 }
  setmetatable(props, self)
  self.__index = self
  return props
end

---@param node neotree.collections.ListNode
function LinkedList:add_node(node)
  if self.head == nil then
    self.head = node
    self.tail = node
  else
    self.tail.next = node
    node.prev = self.tail
    self.tail = node
  end
  self.size = self.size + 1
  return node
end

---@param node neotree.collections.ListNode
function LinkedList:remove_node(node)
  if node.prev ~= nil then
    node.prev.next = node.next
  end
  if node.next ~= nil then
    node.next.prev = node.prev
  end
  if self.head == node then
    self.head = node.next
  end
  if self.tail == node then
    self.tail = node.prev
  end
  self.size = self.size - 1
  node.prev = nil
  node.next = nil
  node.value = nil
end

-- First in Last Out
---@class neotree.collections.Queue
---@field _list neotree.collections.LinkedList
local Queue = {}

---@return neotree.collections.Queue
function Queue:new()
  local props = { _list = LinkedList:new() }
  setmetatable(props, self)
  self.__index = self
  return props
end

---Add an element to the end of the queue.
---@param value any The value to add.
function Queue:add(value)
  self._list:add_node(Node:new(value))
end

---Iterates over the entire list, running func(value) on each element.
---If func returns true, the element is removed from the list.
---@param func function The function to run on each element.
---@return table? result
function Queue:for_each(func)
  local node = self._list.head
  while node ~= nil do
    local result = func(node.value)
    local node_is_next = false
    if result then
      if type(result) == "boolean" then
        local node_to_remove = node
        node = node.next
        node_is_next = true
        self._list:remove_node(node_to_remove)
      elseif type(result) == "table" then
        if result.handled == true then
          log.trace(
            "Handler ",
            node.value.id,
            " for "
              .. node.value.event
              .. " returned handled = true, skipping the rest of the queue."
          )
          return result
        end
      end
    end
    if not node_is_next then
      ---@diagnostic disable-next-line: need-check-nil
      node = node.next
    end
  end
end

function Queue:is_empty()
  return self._list.size == 0
end

function Queue:remove_by_id(id)
  local current = self._list.head
  while current ~= nil do
    local is_match = false
    local item = current.value
    if item ~= nil then
      local item_id = item.id or item
      if item_id == id then
        is_match = true
      end
    end
    if is_match then
      local next = current.next
      self._list:remove_node(current)
      current = next
    else
      current = current.next
    end
  end
end

return {
  Queue = Queue,
  LinkedList = LinkedList,
}
