-- Implementation of file preview with support for syntax highlighting,
-- directory and archive contents, and images,
-- falling back to showing stats of unsupported files.
-- Requires bat for syntax highlighting (https://github.com/sharkdp/bat),
-- viu for image preview (https://github.com/atanunq/viu),
-- and ouch for archive preview (https://github.com/ouch-org/ouch)

---@diagnostic disable
local xplr = xplr
---@diagnostic enable

local M = {}

local function mimetype(n)
    return xplr.util.shell_execute("file", { "--brief", "--mime-type", n.absolute_path }).stdout:sub(1, -2)
end

local function filetype(n)
    local type = "other"
    if n.is_file then
        local mime = mimetype(n)
        if (mime:match("text") or mime:match("json") or mime:match("csv") or mime:match("empty") or mime:match("javascript")) then
            type = "text"
        elseif (mime:match("zip") or mime:match("tar")) then
            type = "archive"
        elseif (mime:match("image")) then
            type = "image"
        end
    elseif n.is_dir then
        type = "directory"
    end
    return type
end

local function stats(n)
    return xplr.util.to_yaml(xplr.util.node(n.absolute_path))
end

local function endswith(s, suffix)
    return s:sub(- #suffix) == suffix
end

local function render_text(n, ctx)
    local result = xplr.util.shell_execute("bat",
        { "--color=always", "--style=plain", "--line-range=:" .. ctx.layout_size.height - 2, n.absolute_path })
    local out = (result.returncode == 0 and result.stdout) or stats(n)
    -- Replace tabs with 4 spaces (for some reason tabs don't seem to render properly)
    return out:gsub("\t", "    ")
end

local function render_image(n, ctx)
    -- xplr doesn't support image protocols such as sixel or kitty
    -- so we need to render blocks instead
    local result = xplr.util.shell_execute("viu",
        { "--blocks", "--static", "--width", ctx.layout_size.width, n.absolute_path })
    return (result.returncode == 0 and result.stdout) or stats(n)
end

local function render_directory(n, ctx)
    local result = xplr.util.shell_execute("sh",
        { "-c", "tree -aC --noreport " ..
        xplr.util.shell_escape(n.absolute_path) .. "| head --lines=" .. ctx.layout_size.height - 1 .. "| tail +2" })
    return (result.returncode == 0 and result.stdout) or stats(n)
end

local function render_archive(n, ctx)
    -- To keep from lagging out xplr,
    -- we only extract archives that are 10 MiB or less in size
    -- (mibibytes, not megabytes, since that's what ls -h shows).
    if n.size > 10485760 then
        return stats(n)
    end
    local result = xplr.util.shell_execute("sh",
        { "-c", "ouch list " ..
        xplr.util.shell_escape(n.absolute_path) .. "| head --lines=" .. ctx.layout_size.height - 1 .. "| tail +2" })
    local out = (result.returncode == 0 and result.stdout) or stats(n)
    -- Since ouch doesn't support forcing coloration in non-interactive terminals (a strange omission TBH),
    -- we need to add the colors ourselves.
    -- The following is a bit brittle, since it will break if a file for some reason ends in /, but eh.
    local body = ""
    for line in string.gmatch(out, "[^\n]+") do
        -- Remove extra / from end of directories that head adds for whatever reason
        if endswith(line, "//") then
            line = line:sub(1, -2)
        end
        -- Directories end with a / and should be highlighted blue
        if endswith(line, "/") then
            local style = { fg = "Blue", bg = nil, add_modifiers = { "Bold" }, sub_modifiers = {} }
            body = body .. xplr.util.paint(line, style) .. "\n"
        else
            body = body .. line .. "\n"
        end
    end
    return body
end

xplr.fn.custom.preview_pane = {}
xplr.fn.custom.preview_pane.render = function(ctx)
    local title = nil
    local body = ""
    local n = ctx.app.focused_node
    -- Follow symlinks
    if n then
        -- Symlinks to symlinks are a thing, so we must support that
        while n.canonical do
            n = n.canonical
        end
    end

    if n then
        title = { format = n.absolute_path, style = xplr.util.lscolor(n.absolute_path) }
        local type = filetype(n)
        if type == "text" then
            body = render_text(n, ctx)
        elseif type == "image" then
            body = render_image(n, ctx)
        elseif type == "directory" then
            body = render_directory(n, ctx)
        elseif type == "archive" then
            body = render_archive(n, ctx)
        else
            body = stats(n)
        end
    end

    return { CustomParagraph = { ui = { title = title }, body = body } }
end

M.preview_pane = { Dynamic = "custom.preview_pane.render" }
return M
