--[[----------------------------------------------------------------------------
  A7InfoLRCli.lua

  Kapselt den Aufruf des gebuendelten Kommandozeilen-Helfers "a7infolr-cli"
  (s. bin/) - liest dessen JSON-Antwort (ueber eine temporaere Datei, da
  LrTasks.execute() keine stdout-Ausgabe liefert) und dekodiert sie mit
  JsonDecode in eine Lua-Tabelle.

  Performance: a7infolr-cli ist ein schlankes, als Release gebautes natives
  Tool, das nur die fuer den jeweiligen Modus (quick|full) benoetigten Tags
  parst - der Aufruf-Overhead bleibt damit pro Foto gering.
------------------------------------------------------------------------------]]

local LrFileUtils = import 'LrFileUtils'
local LrMD5       = import 'LrMD5'
local LrPathUtils = import 'LrPathUtils'
local LrTasks     = import 'LrTasks'

local JsonDecode  = require 'JsonDecode'

local A7InfoLRCli = {}

local function cliPath()
    local name = WIN_ENV and "a7infolr-cli.exe" or "a7infolr-cli"
    return LrPathUtils.child(LrPathUtils.child(_PLUGIN.path, "bin"), name)
end

-- Eindeutiger Dateiname (Basis ohne Endung) ueber MD5-Digest aus Fotopfad +
-- Zeitstempel + Zufallszahl (es gibt kein LrUUID in diesem SDK - s. Modul-
-- liste der API-Referenz). 'tag' unterscheidet mehrere Temp-Dateien fuer
-- denselben Aufruf (z.B. "json" vs. "overlay"), damit ihre Pfade nicht
-- kollidieren, auch wenn sie in derselben Sekunde/demselben Tick entstehen.
local function tempFilePath(photoPath, tag, extension)
    local seed = string.format("%s-%s-%s-%s-%s", photoPath, tag, os.time(), os.clock(), math.random(1, 1e9))
    return LrPathUtils.child(LrPathUtils.getStandardFilePath("temp"), LrMD5.digest(seed) .. "." .. extension)
end

-- POSIX (macOS): Single-Quote-Escaping ' -> '\''
-- Windows (cmd.exe): Double-Quote-Wrapping, innere " werden zu ""
local function shellQuote(s)
    if WIN_ENV then
        return '"' .. string.gsub(s, '"', '""') .. '"'
    end
    return "'" .. string.gsub(s, "'", "'\\''") .. "'"
end

--[[----------------------------------------------------------------------------
  public table result, string errorMessage

  A7InfoLRCli.readInfo(string photoPath, string mode, table overlaySize)

  Ruft a7infolr-cli synchron fuer die angegebene Datei auf (mode "quick"|"full",
  Default "full"), liest die erzeugte JSON-Antwort und liefert sie als
  Lua-Tabelle zurueck. Bei Fehlern (Prozess-rc != 0, JSON-Status "error",
  Parse-Fehler) wird stattdessen nil plus eine Fehlermeldung geliefert.

  overlaySize (optional): {width = <int>, height = <int>} - die im Plugin
  angezeigte Bildgroesse (s. A7InfoLRDialog.computeDisplaySize). Wird sie
  mitgegeben, fordert dieser Aufruf zusaetzlich ein natives Tracking-/Eye-AF-
  Overlay-PNG in genau dieser Groesse an (--af-overlay-output/-size); falls die
  Aufnahme einen entsprechenden Marker enthaelt, erscheint dessen Pfad in
  result.focus.trackingOverlay.image. ACHTUNG: Anders als die JSON-Antwort wird
  diese Bilddatei HIER NICHT geloescht - sie muss waehrend der gesamten
  Lebensdauer des Dialogs existieren. Der Aufrufer (A7InfoLRMenuItem.lua) ist
  fuer das Aufraeumen verantwortlich, nachdem der modale Dialog geschlossen
  wurde (s. dortigen Kommentar zu LrDialogs.presentModalDialog).

  Muss aus einem asynchronen Task heraus aufgerufen werden (ruft LrTasks.execute
  auf, das blockiert).
------------------------------------------------------------------------------]]
function A7InfoLRCli.readInfo(photoPath, mode, overlaySize, cropInfo)
    mode = mode or "full"

    local outputPath = tempFilePath(photoPath, "json", "json")
    local cmd = string.format(
        "%s %s --output %s --mode %s",
        shellQuote(cliPath()), shellQuote(photoPath), shellQuote(outputPath), shellQuote(mode))

    local overlayPath
    if overlaySize and overlaySize.width and overlaySize.height
       and overlaySize.width > 0 and overlaySize.height > 0 then
        overlayPath = tempFilePath(photoPath, "overlay", "png")
        cmd = string.format("%s --af-overlay-output %s --af-overlay-size %dx%d",
            cmd, shellQuote(overlayPath), overlaySize.width, overlaySize.height)
        -- Crop-Parameter: nur uebergeben wenn kein Vollbild (vermeidet unnoetige Argumente).
        if cropInfo and (cropInfo.left > 0 or cropInfo.top > 0
                      or cropInfo.right < 1 or cropInfo.bottom < 1) then
            local cropStr = string.format("%.6f,%.6f,%.6f,%.6f",
                cropInfo.left, cropInfo.top, cropInfo.right, cropInfo.bottom)
            cmd = string.format("%s --crop %s", cmd, shellQuote(cropStr))
        end
    end

    -- LrTasks.execute liefert den Prozess-Exit-Code (0 = Ok, s. ExitCode-Enum in
    -- main.cpp). rc ~= 0 bedeutet, dass die CLI mit Fehler beendet wurde - die
    -- Ausgabedatei enthaelt in diesem Fall i.d.R. ein JSON-Fehlerobjekt (das die
    -- nachfolgende status=="error"-Pruefung behandelt). Nur wenn zusaetzlich KEINE
    -- Ausgabedatei geschrieben wurde (z.B. Absturz oder Dateirechte-Problem),
    -- liefern wir eine Fehlermeldung mit dem Exit-Code fuer die Diagnose.
    local rc = LrTasks.execute(cmd)

    local rawJson = LrFileUtils.exists(outputPath) and LrFileUtils.readFile(outputPath)
    if LrFileUtils.exists(outputPath) then
        LrFileUtils.delete(outputPath)
    end

    -- Falls die CLI das Overlay nicht erzeugt hat (kein Tracking-/Eye-AF-Marker
    -- in dieser Aufnahme), existiert die angefragte PNG-Datei schlicht nicht -
    -- kein Aufraeumen noetig, A7InfoLRDialog erkennt das ueber trackingOverlay == nil.
    if overlayPath and not LrFileUtils.exists(overlayPath) then
        overlayPath = nil
    end

    if not rawJson then
        if overlayPath then LrFileUtils.delete(overlayPath) end
        return nil, "a7infolr-cli lieferte keine Antwort (rc=" .. tostring(rc) .. ")."
    end

    local ok, decoded = pcall(JsonDecode.decode, rawJson)
    if not ok or type(decoded) ~= "table" then
        if overlayPath then LrFileUtils.delete(overlayPath) end
        return nil, "Antwort von a7infolr-cli konnte nicht gelesen werden: " .. tostring(decoded)
    end

    if decoded.status == "error" then
        if overlayPath then LrFileUtils.delete(overlayPath) end
        return nil, decoded.message or "a7infolr-cli meldete einen unbekannten Fehler."
    end

    return decoded
end

return A7InfoLRCli
