--[[
    Commander 3.0 - VFS Nodes

    File and directory node definitions for the Virtual Filesystem.
]]

local ADDON_NAME, C3 = ...

-------------------------------------------------------------------------------
-- Base Node
-------------------------------------------------------------------------------

local Node = C3.class()
C3.VFSNode = Node

Node.TYPE = {
    FILE = "file",
    DIRECTORY = "dir",
    LINK = "link",
}

function Node:init(name, nodeType)
    self.name = name
    self.type = nodeType or Node.TYPE.FILE
    self.created = GetTime()
    self.modified = GetTime()
    self.metadata = {}
end

function Node:IsFile()
    return self.type == Node.TYPE.FILE
end

function Node:IsDirectory()
    return self.type == Node.TYPE.DIRECTORY
end

function Node:IsLink()
    return self.type == Node.TYPE.LINK
end

function Node:Touch()
    self.modified = GetTime()
end

function Node:SetMeta(key, value)
    self.metadata[key] = value
    self:Touch()
end

function Node:GetMeta(key)
    return self.metadata[key]
end

---Serialize node for storage
---@return table
function Node:Serialize()
    return {
        name = self.name,
        type = self.type,
        created = self.created,
        modified = self.modified,
        metadata = self.metadata,
    }
end

---Deserialize node from storage
---@param data table
---@return Node
function Node.Deserialize(data)
    local node = Node:new(data.name, data.type)
    node.created = data.created or GetTime()
    node.modified = data.modified or GetTime()
    node.metadata = data.metadata or {}
    return node
end

-------------------------------------------------------------------------------
-- File Node
-------------------------------------------------------------------------------

local FileNode = Node:extend()
C3.VFSFileNode = FileNode

function FileNode:init(name, content)
    Node.init(self, name, Node.TYPE.FILE)
    self.content = content or ""
    self.size = #self.content
end

function FileNode:Read()
    return self.content
end

function FileNode:Write(content)
    self.content = content or ""
    self.size = #self.content
    self:Touch()
end

function FileNode:Append(content)
    self.content = self.content .. (content or "")
    self.size = #self.content
    self:Touch()
end

function FileNode:Serialize()
    local data = Node.Serialize(self)
    data.content = self.content
    data.size = self.size
    return data
end

function FileNode.Deserialize(data)
    local node = FileNode:new(data.name, data.content)
    node.created = data.created or GetTime()
    node.modified = data.modified or GetTime()
    node.metadata = data.metadata or {}
    node.size = data.size or #(data.content or "")
    return node
end

-------------------------------------------------------------------------------
-- Directory Node
-------------------------------------------------------------------------------

local DirNode = Node:extend()
C3.VFSDirNode = DirNode

function DirNode:init(name)
    Node.init(self, name, Node.TYPE.DIRECTORY)
    self.children = {} -- name -> true (actual nodes stored in driver)
end

function DirNode:AddChild(name)
    self.children[name] = true
    self:Touch()
end

function DirNode:RemoveChild(name)
    self.children[name] = nil
    self:Touch()
end

function DirNode:HasChild(name)
    return self.children[name] == true
end

function DirNode:GetChildren()
    local names = {}
    for name in pairs(self.children) do
        table.insert(names, name)
    end
    table.sort(names)
    return names
end

function DirNode:IsEmpty()
    return next(self.children) == nil
end

function DirNode:Serialize()
    local data = Node.Serialize(self)
    data.children = self.children
    return data
end

function DirNode.Deserialize(data)
    local node = DirNode:new(data.name)
    node.created = data.created or GetTime()
    node.modified = data.modified or GetTime()
    node.metadata = data.metadata or {}
    node.children = data.children or {}
    return node
end

-------------------------------------------------------------------------------
-- Link Node (Symbolic Link)
-------------------------------------------------------------------------------

local LinkNode = Node:extend()
C3.VFSLinkNode = LinkNode

function LinkNode:init(name, target)
    Node.init(self, name, Node.TYPE.LINK)
    self.target = target or "/"
end

function LinkNode:GetTarget()
    return self.target
end

function LinkNode:SetTarget(target)
    self.target = target
    self:Touch()
end

function LinkNode:Serialize()
    local data = Node.Serialize(self)
    data.target = self.target
    return data
end

function LinkNode.Deserialize(data)
    local node = LinkNode:new(data.name, data.target)
    node.created = data.created or GetTime()
    node.modified = data.modified or GetTime()
    node.metadata = data.metadata or {}
    return node
end

-------------------------------------------------------------------------------
-- Factory
-------------------------------------------------------------------------------

local NodeFactory = {}
C3.VFSNodeFactory = NodeFactory

function NodeFactory.Create(nodeType, name, ...)
    if nodeType == Node.TYPE.FILE then
        return FileNode:new(name, ...)
    elseif nodeType == Node.TYPE.DIRECTORY then
        return DirNode:new(name)
    elseif nodeType == Node.TYPE.LINK then
        return LinkNode:new(name, ...)
    else
        error("Unknown node type: " .. tostring(nodeType))
    end
end

function NodeFactory.Deserialize(data)
    if not data or not data.type then
        return nil
    end

    if data.type == Node.TYPE.FILE then
        return FileNode.Deserialize(data)
    elseif data.type == Node.TYPE.DIRECTORY then
        return DirNode.Deserialize(data)
    elseif data.type == Node.TYPE.LINK then
        return LinkNode.Deserialize(data)
    else
        return Node.Deserialize(data)
    end
end

return Node
