-- Layout registry for the hyprlua session.
--
-- Adding a layout is a one-file drop-in: create `layouts/<name>.lua` returning a
-- spec table (see scrolling.lua for the shape) and it is auto-discovered here — no
-- other wiring. Exposes the global `layouts` table used by hyprland.lua (to pick the
-- default) and by the astal-menu popup (over `hyprctl eval`) to switch a workspace's
-- layout live. A JSON manifest is written for the menu to enumerate the choices.

local M = { registry = {}, order = {}, current = {} }   -- current: ws(str) -> name

local HOME = os.getenv("HOME")
local DIR = HOME .. "/.config/hypr/layouts"
local MANIFEST = HOME .. "/.cache/astal-menu/layouts.json"
local STATE = HOME .. "/.cache/astal-menu/layouts-state.json"

local function scan()
    local p = io.popen("ls '" .. DIR .. "' 2>/dev/null")
    if not p then return {} end
    local names = {}
    for line in p:lines() do
        local n = line:match("^(.+)%.lua$")
        if n and n ~= "init" then names[#names + 1] = n end
    end
    p:close()
    table.sort(names)
    return names
end

local function jstr(s) return '"' .. tostring(s):gsub('\\', '\\\\'):gsub('"', '\\"') .. '"' end

-- Write layouts.json so the menu can list {name,label,directional,dirs,default_dir}.
function M.write_manifest()
    local items = {}
    for _, name in ipairs(M.order) do
        local s = M.registry[name]
        local dirs = {}
        for _, d in ipairs(s.dirs or {}) do dirs[#dirs + 1] = jstr(d) end
        local dlabels = {}
        for _, d in ipairs(s.dir_labels or {}) do dlabels[#dlabels + 1] = jstr(d) end
        items[#items + 1] = table.concat({
            "{", '"name":', jstr(s.name),
            ',"label":', jstr(s.label or s.name),
            ',"directional":', tostring(s.directional or false),
            ',"dirs":[', table.concat(dirs, ","), "]",
            ',"dir_labels":[', table.concat(dlabels, ","), "]",
            ',"default_dir":', jstr(s.default_dir or ""),
            ',"fit_method":', tostring(s.fit_method or false),
            ',"stepper":', tostring(s.stepper or false), "}",
        })
    end
    os.execute("mkdir -p '" .. MANIFEST:match("(.+)/[^/]+$") .. "' 2>/dev/null")
    local f = io.open(MANIFEST, "w")
    if f then f:write("[" .. table.concat(items, ",") .. "]\n"); f:close() end
end

function M.load()
    for _, name in ipairs(scan()) do
        local ok, spec = pcall(require, "layouts." .. name)
        if ok and type(spec) == "table" and spec.name then
            if not M.registry[spec.name] then M.order[#M.order + 1] = spec.name end
            M.registry[spec.name] = spec
        end
    end
    M.write_manifest()
    return M
end

-- Global default layout (called once from hyprland.lua).
function M.set_default(name)
    local s = M.registry[name]; if not s then return end
    hl.config({ general = { layout = s.backend } })
    if s.config then hl.config(s.config) end
end

-- Switch one workspace's layout live (called by the menu via hyprctl eval).
-- `dir` is applied for directional layouts (scrolling.direction is a global option,
-- so this affects every scrolling workspace — hyprland has no per-ws direction).
local function write_state()
    local parts = {}
    for ws, name in pairs(M.current) do
        parts[#parts + 1] = jstr(ws) .. ":" .. jstr(name)
    end
    local f = io.open(STATE, "w")
    if f then f:write("{" .. table.concat(parts, ",") .. "}\n"); f:close() end
end

function M.set(ws, name, dir)
    local s = M.registry[name]; if not s then return end
    ws = tostring(ws)
    hl.workspace_rule({ workspace = ws, layout = s.backend })
    if s.lua then
        -- custom Lua layout: orientation/options are per-layout state, set via a message
        -- to the active workspace's layout (not the global scrolling:direction option).
        if dir and dir ~= "" then hl.dispatch(hl.dsp.layout("orient " .. dir)) end
    elseif s.directional and dir and dir ~= "" then
        hl.config({ scrolling = { direction = dir } })
    end
    M.current[ws] = name          -- remember so the menu can show the real per-ws layout
    write_state()
end

-- Toggle the scrolling "centered follow" (focus_fit_method 0=fit / 1=centered).
-- column_width stays < 1.0 (set in scrolling.lua) so neighbours remain clickable.
function M.set_fit(method)
    hl.config({ scrolling = { focus_fit_method = tonumber(method) or 0 } })
end

-- Re-apply the saved per-workspace layout choices. `hyprctl reload` resets the Lua VM
-- AND drops every runtime `workspace_rule`, so without this any workspace the user had
-- switched to Columns (or another non-default layout) silently reverts to the default
-- while the menu still shows the old choice. We replay what M.set recorded in
-- layouts-state.json. Called from hyprland.lua right after set_default().
function M.restore()
    local f = io.open(STATE, "r")
    if not f then return end
    local data = f:read("*a") or ""
    f:close()
    for ws, name in data:gmatch('"([^"]+)"%s*:%s*"([^"]+)"') do
        local s = M.registry[name]
        if s then
            hl.workspace_rule({ workspace = tostring(ws), layout = s.backend })
            M.current[tostring(ws)] = name
        end
    end
end

_G.layouts = M.load()
return M
