#!/usr/bin/lua

-- pinentry-rofi.lua: A pinentry program for GnuPG that uses Rofi.
-- Version: 3.0.0 (Compatible & Definitive)

-- --- Configuration ---
local LOG_FILE = string.format("/tmp/pinentry-log-%s.txt", os.getenv("USER") or "unknown")
-- To disable logging, change the next line to 'true'
local DISABLE_LOGGING = false

-- --- State Variables ---
local gpg_state = {
    win_title = "GPG Password Prompt",
    win_prompt = "Password",
    win_mesg = "",
    version = "3.0.0",
    env = {} -- Table to store environment variables
}

-- --- Logger ---
local function log(msg)
    if not DISABLE_LOGGING then
        local file = io.open(LOG_FILE, "a")
        if file then
            file:write(string.format("[%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), msg))
            file:close()
        end
    end
end

-- --- Assuan Protocol Helper ---
local function assuan_send(msg)
    log("SEND: " .. msg)
    print(msg)
    io.stdout:flush()
end

-- --- URL Decoding Helper ---
local function rawurldecode(str)
    return (string.gsub(str or "", "%%(%x%x)", function(hex)
        return string.char(tonumber(hex, 16))
    end))
end

-- --- Rofi Interaction ---
local function get_pin_from_rofi()
    local rofi_cmd_base = "rofi -dmenu -theme-str 'listview {lines: 0;}' -input /dev/null -password"

    -- Safely quote arguments for the shell
    local function shell_quote(str)
        return "'" .. string.gsub(str or "", "'", "'\\''") .. "'"
    end

    -- Pango markup escaping
    local function escape_pango(str)
        if not str then return "" end
        str = string.gsub(str, "&", "&amp;")
        str = string.gsub(str, "<", "&lt;")
        str = string.gsub(str, ">", "&gt;")
        return str
    end

    local rofi_cmd = rofi_cmd_base
        .. " -p " .. shell_quote(rawurldecode(gpg_state.win_prompt))
        .. " -title " .. shell_quote(rawurldecode(gpg_state.win_title))

    if gpg_state.win_mesg and gpg_state.win_mesg ~= "" then
        local decoded_mesg = rawurldecode(gpg_state.win_mesg)
        local pango_safe_mesg = escape_pango(decoded_mesg):gsub("[\r\n]+$", "")
        rofi_cmd = rofi_cmd .. " -mesg " .. shell_quote(pango_safe_mesg)
    end

    log("Executing Rofi: " .. rofi_cmd)

    local rofi_pipe = io.popen(rofi_cmd, "r")
    if not rofi_pipe then
        log("FATAL: Failed to execute io.popen for rofi.")
        assuan_send("ERR 83886179 Rofi execution failed <pinentry>")
        return
    end

    local password = rofi_pipe:read("*a")
    local _, _, status = rofi_pipe:close()

    if status ~= 0 then
        log("Rofi was cancelled or failed. Exit status: " .. tostring(status))
        assuan_send("ERR 83886179 Operation cancelled <rofi>")
    else
        password = password:gsub("[\r\n]+$", "") -- Trim trailing newline
        if password and #password > 0 then
            assuan_send("D " .. password)
        end
        assuan_send("OK")
    end
end

-- --- Main Program Loop ---
local function main()
    log("Pinentry session started.")
    assuan_send("OK Please go ahead")

    for line in io.lines() do
        log("RECV: " .. line)
        local cmd, arg = line:match("^([A-Z_]+)%s*(.*)$")
        cmd = cmd or ""

        if cmd == "OPTION" then
            -- CORRECTED: Store environment variables instead of calling os.setenv
            local key, value = arg:match("^putenv%s+([A-Za-z_0-9]+)=(.+)$")
            if key and value then
                log("Storing Env from OPTION: " .. key .. "=" .. value)
                gpg_state.env[key] = value
            end
            assuan_send("OK")
        elseif cmd == "SETDESC" then
            gpg_state.win_mesg = arg
            assuan_send("OK")
        elseif cmd == "SETPROMPT" then
            gpg_state.win_prompt = arg
            assuan_send("OK")
        elseif cmd == "SETTITLE" then
            gpg_state.win_title = arg
            assuan_send("OK")
        elseif cmd == "GETINFO" then
            if arg == "version" then
                assuan_send("D " .. gpg_state.version)
            end
            assuan_send("OK")
        elseif cmd == "GETPIN" then
            get_pin_from_rofi()
        elseif cmd == "BYE" then
            assuan_send("OK")
            log("Session ended by BYE command.")
            return
        elseif cmd == "INQUIRE" then
            assuan_send("END")
        else
            assuan_send("OK")
        end
    end
    log("Input stream closed (EOF). Session ending.")
end

-- --- Execution Guard ---
os.execute("umask 0077")
local sess_type = os.getenv("XDG_SESSION_TYPE") or ""
if sess_type ~= "tty" then
    main()
else
    os.execute("pinentry-tty")
end