-- Replace Obsidian embed blocks like ![[macros.tex]] with the content of the
-- corresponding LaTeX macro file stored in *.tex.md.

local macro_cache = {}
-- Keep a small explicit map for quick lookups if desired, but resolution
-- will try relative locations from the current document by default.
local macro_files = {
    -- leave empty or add overrides if needed
}

-- Mapping from macro name to the include path to inject in Quarto shortcode
local macro_include = {
    ["macros.tex"] = "../../macros.tex.md",
    ["local_macros.tex"] = "local_macros.tex.md",
}

local function read_file(path)
    local handle = io.open(path, "r")
    if not handle then
        return nil
    end

    local content = handle:read("*a")
    handle:close()
    return content
end

local function strip_markdown_suffix(target)
    return target:gsub("%.md$", "")
end

local function find_macro_file(target)
    local key = strip_markdown_suffix(target):match("([^/\\]+)$")
    if not key then
        return nil
    end

    if macro_cache[key] then
        return macro_cache[key]
    end

    -- Helper utilities
    local function file_exists(path)
        local f = io.open(path, "r")
        if f then
            f:close(); return true
        end
        return false
    end

    local function dirname(path)
        if not path then return nil end
        local dir = path:match("(.*/)")
        if dir then
            -- strip trailing '/'
            return dir:gsub("/$", "")
        end
        return '.'
    end

    local function join(a, b)
        if not a or a == '' or a == '.' then return b end
        if a:sub(-1) == '/' then return a .. b end
        return a .. '/' .. b
    end

    -- Try quick explicit mapping first
    if macro_files[key] and file_exists(macro_files[key]) then
        macro_cache[key] = macro_files[key]
        return macro_files[key]
    end

    -- Determine current input file directory from Pandoc state
    local input_file = nil
    if PANDOC_STATE and PANDOC_STATE.input_files and #PANDOC_STATE.input_files > 0 then
        input_file = PANDOC_STATE.input_files[1]
    end
    local cur_dir = dirname(input_file) or '.'

    -- Search candidates in order of preference
    local candidates = {
        join(cur_dir, key .. ".md"),
        join(cur_dir, key),
        key .. ".md",
        key,
    }

    -- Walk up directories from cur_dir to try to find a project root (look for _quarto.yml)
    local function find_project_root(start)
        local dir = start or '.'
        for i = 1, 16 do
            local qcfg = join(dir, '_quarto.yml')
            if file_exists(qcfg) then return dir end
            -- go up one
            local parent = dir:match("(.*/).-$")
            if not parent then break end
            dir = parent:gsub("/$", "")
        end
        return nil
    end

    local project_root = find_project_root(cur_dir)
    if project_root then
        table.insert(candidates, join(project_root, key .. ".md"))
        table.insert(candidates, join(project_root, key))
    end

    -- Finally check workspace root (pwd) as a fallback
    -- Use os.getenv("PWD") which Quarto/Pandoc normally runs with
    local pwd = os.getenv('PWD')
    if pwd then
        table.insert(candidates, join(pwd, key .. ".md"))
        table.insert(candidates, join(pwd, key))
    end

    for _, cand in ipairs(candidates) do
        if cand and file_exists(cand) then
            macro_cache[key] = cand
            return cand
        end
    end

    return nil
end

local function expand_macro_embed(el)
    local text = pandoc.utils.stringify(el)
    local trimmed = text:gsub("^%s+", ""):gsub("%s+$", "")
    local target = trimmed:match("^!%[%[(.-)%]%]$") or trimmed:match("^%[%[(.-)%]%]$")
    if not target then
        return nil
    end

    local path = find_macro_file(target)
    if not path then
        return nil
    end

    -- Compute include path relative to current input file directory
    local function dirname(path)
        if not path then return nil end
        local dir = path:match("(.*/)")
        if dir then
            return dir:gsub("/$", "")
        end
        return '.'
    end

    local input_file = nil
    if PANDOC_STATE and PANDOC_STATE.input_files and #PANDOC_STATE.input_files > 0 then
        input_file = PANDOC_STATE.input_files[1]
    end
    local cur_dir = dirname(input_file) or '.'

    local function split(path)
        local t = {}
        for part in path:gmatch("[^/]+") do table.insert(t, part) end
        return t
    end

    local function relative_path(from, to)
        if not from or not to then return to end
        -- handle same path
        if from == to then return "." end
        local from_abs = (from:sub(1, 1) == '/')
        local to_abs = (to:sub(1, 1) == '/')
        -- if one is absolute and other not, fall back to `to`
        if from_abs ~= to_abs then return to end
        local from_parts = split(from)
        local to_parts = split(to)
        -- find common prefix
        local i = 1
        while i <= #from_parts and i <= #to_parts and from_parts[i] == to_parts[i] do
            i = i + 1
        end
        local up = #from_parts - i + 1
        local rel_parts = {}
        for j = 1, up do table.insert(rel_parts, "..") end
        for j = i, #to_parts do table.insert(rel_parts, to_parts[j]) end
        if #rel_parts == 0 then return "." end
        return table.concat(rel_parts, "/")
    end

    local include_path = relative_path(cur_dir, path)
    if not include_path then
        -- fallback to absolute path if relative couldn't be computed
        include_path = path
    end

    local shortcode = string.format("{{< include %s >}}", include_path)
    return pandoc.RawBlock("markdown", shortcode)
end

function Para(el)
    return expand_macro_embed(el)
end

function Plain(el)
    return expand_macro_embed(el)
end
