-- :Minify:--
local apis = ({...})[1]
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$"
local fs = apis.fs
local native = apis.peripheral
local peripheral = {}
local sides = {"top", "bottom", "left", "right", "front", "back"}

function peripheral.getType(name)
    if native.isPresent(name) then return native.getType(name) end
    for n = 1, #sides do
        local side = sides[n]
        if native.hasType(side, "peripheral_hub") and
            native.call(side, "isPresentRemote", name) then
            return native.call(side, "getTypeRemote", name)
        end
    end
    return nil
end

function peripheral.getNames()
    local names = {}
    for n = 1, #sides do
        local side = sides[n]
        if native.isPresent(side) then table.insert(names, side) end
        if native.hasType(side, "peripheral_hub") then
            local hubSides = native.call(side, "getConnectedSides")
            for _, hubSide in ipairs(hubSides) do
                table.insert(names, hubSide)
            end
        end
    end
    return names
end

local disks = {}
local internal = {}

local function norm(path)
    if not path or path == "" then return "/" end
    return fs.combine("/", path)
end

local function createDisk(id, basePath, readonly, periph)
    basePath = norm(basePath)

    local disk = {address = id, isReadOnly = function() return readonly end}

    function disk:spaceUsed()
        return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
    end

    function disk:spaceTotal() return fs.getCapacity(basePath) end

    function disk:list(path)
        local p = fs.combine(basePath, path)
        if not fs.exists(p) or not fs.isDir(p) then
            return nil, "not directory"
        end
        return fs.list(p)
    end

    function disk:fileExists(path)
        local p = fs.combine(basePath, path)
        return fs.exists(p) and not fs.isDir(p)
    end

    function disk:directoryExists(path)
        local p = fs.combine(basePath, path)
        return fs.exists(p) and fs.isDir(p)
    end

    function disk:type(path)
        local p = fs.combine(basePath, path)
        if not fs.exists(p) then
            return nil
        elseif fs.isDir(p) then
            return "directory"
        else
            return "file"
        end
    end

    function disk:makeDirectory(path)
        local p = fs.combine(basePath, path)
        fs.makeDir(p)
        return true
    end

    function disk:remove(path)
        local p = fs.combine(basePath, path)
        if fs.exists(p) then fs.delete(p) end
        return true
    end

    function disk:setLabel(label) periph.setLabel(label) end

    function disk:getLabel(label) return periph.getLabel() end

    function disk:attributes(path)
        local p = fs.combine(basePath, path)
        return fs.attributes(p)
    end

    function disk:open(path, mode)
        local p = fs.combine(basePath, path)
        return fs.open(p, mode)
    end

    return disk
end

internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
    setLabel = function(label)
        local h = fs.open("/.label", "w")
        h.write(label)
        h.close()
    end,
    getLabel = function()
        local h = fs.open("/.label", "r")
        if not h then return "$" end
        local label = h.readAll()
        h.close()
        return label
    end
})

local function refresh()
    for id, _ in pairs(disks) do
        if not peripheral.getType(id) then disks[id] = nil end
    end

    for _, name in ipairs(peripheral.getNames()) do
        if peripheral.getType(name) == "disk" then
            if not disks[name] then
                local mount = disk.getMountPath(name)
                if mount then
                    disks[name] = createDisk(name, mount, false, disk)
                end
            end
        end
    end
end

local function iter()
    refresh()
    local combined = {}

    for id, obj in pairs(internal) do combined[id] = obj end
    for id, obj in pairs(disks) do combined[id] = obj end

    return pairs(combined)
end

return {refresh = refresh, list = iter}
