-- laundering/cl_ui.lua
-- Interface utilisateur DBALib pour le système de blanchiment.
-- Tous les composants passent par DBALib.UI — jamais de Derma natif.
-- NOTE : vérification DBALib faite en lazy (pas d'early-return au top-level)
--        pour respecter l'ordre de chargement GMod.

Laundering    = Laundering    or {}
Laundering.UI = Laundering.UI or {}

-- ============================================================================
-- State module
-- ============================================================================

local CurrentFrame  = nil
local ProgressBar   = nil
local CurrentNPC    = nil
local CurrentDirty  = 0
local TxStart       = 0
local TxDuration    = 0
local TxRunning     = false
local TxAmount      = 0
local CurrentMethodId = ""

-- ============================================================================
-- Helper DBALib ready
-- ============================================================================

local function dbaLibReady()
    if DBALib and DBALib.UI and DBALib.UI.Frame and DBALib.UI.MoneyInput then
        return true
    end
    ErrorNoHalt("[Laundering] DBALib v0.8.0+ requis — UI désactivée.\n")
    return false
end

-- ============================================================================
-- Helper : fermer la frame proprement
-- ============================================================================

local function closeFrame()
    hook.Remove("Think", "Laundering.ProgressTick")
    TxRunning = false

    if IsValid(CurrentFrame) then
        CurrentFrame:Close()
    end
    CurrentFrame = nil
    ProgressBar  = nil
end

-- ============================================================================
-- Helper : construire les lignes du tableau des tiers
-- ============================================================================

local function buildTierRows()
    local rows = {}
    for _, tier in ipairs(Laundering.Config.Tiers) do
        local minStr = (tier.min == 1) and "0+" or DBALib.Format.Money(tier.min)
        local maxStr = (tier.max >= 1000000000) and "illimité" or DBALib.Format.Money(tier.max)
        rows[#rows + 1] = {
            tranche = minStr .. " – " .. maxStr,
            taux    = string.format("%.0f%%", tier.baseRate * 100),
            risque  = string.format("%.0f%% | %.0f–%.0f%%",
                tier.partialChance * 100,
                tier.partialMin * 100,
                tier.partialMax * 100
            ),
        }
    end
    return rows
end

-- ============================================================================
-- Helper : obtenir le tier correspondant à un montant (pour la confirmation)
-- ============================================================================

local function getTierForAmount(amount)
    return Laundering.GetTierForAmount and Laundering.GetTierForAmount(amount) or Laundering.Config.Tiers[1]
end

-- ============================================================================
-- Laundering.UI.OpenMenu
-- Appelé depuis cl_net.lua quand le serveur envoie OpenMenu.
-- ============================================================================

function Laundering.UI.OpenMenu(npc, dirty)
    if not dbaLibReady() then return end

    -- Un seul menu à la fois
    if IsValid(CurrentFrame) then
        closeFrame()
        return
    end

    CurrentNPC   = npc
    CurrentDirty = dirty

    DBALib.Sound("ui_open")

    local S = DBALib.Scale

    -- Variable pour l'input montant (accessible par closures)
    local moneyInput = nil

    -- Contenu du frame (DBALib.UI.VBox est immediate-mode :
    -- on crée d'abord les enfants avec parent=body, puis VBox positionne)
    local function buildContent(body)
        local innerPad = S(12)
        local innerW   = body:GetWide() - innerPad * 2

        -- Stat card : solde actuel
        local statCard = DBALib.UI.StatCard({
            parent = body,
            value  = DBALib.Format.Money(CurrentDirty),
            label  = "Argent sale en poche",
        })
        statCard:SetWide(innerW)

        local divider1 = DBALib.UI.Divider({ parent = body })
        divider1:SetWide(innerW)

        -- Tableau des taux (h = header 36 + 1 row 32 + petite marge)
        local tierTable = DBALib.UI.Table({
            parent     = body,
            columns    = {
                { id = "tranche", label = "Tranche",     w = 2, sortable = false },
                { id = "taux",    label = "Taux nominal", w = 1, sortable = false },
                { id = "risque",  label = "Risque dégradé (chance | fourchette)", w = 3, sortable = false },
            },
            rows       = buildTierRows(),
            filterable = false,
            h          = #Laundering.Config.Tiers * 32 + 50,
        })
        tierTable:SetWide(innerW)

        local divider2 = DBALib.UI.Divider({ parent = body })
        divider2:SetWide(innerW)

        -- Saisie du montant
        local defaultAmt = math.min(1000, CurrentDirty)
        defaultAmt = math.max(defaultAmt, Laundering.Config.MinAmount)

        moneyInput = DBALib.UI.MoneyInput({
            parent = body,
            value  = defaultAmt,
            min    = Laundering.Config.MinAmount,
            max    = math.min(CurrentDirty, Laundering.Config.MaxAmount),
        })
        moneyInput:SetWide(innerW)

        -- Bouton Blanchir
        local blanchirBtn = DBALib.UI.Button({
            parent  = body,
            label   = "Blanchir",
            variant = "primary",
            size    = "lg",
            onClick = function()
                if not IsValid(CurrentNPC) then
                    Laundering.UI.OnError("NPC invalide.")
                    return
                end

                local amount = math.floor(moneyInput:GetValue() or 0)
                if amount < Laundering.Config.MinAmount then
                    DBALib.UI.Notify({
                        message  = string.format("Montant minimum : %s.", DBALib.Format.Money(Laundering.Config.MinAmount)),
                        type     = "warning",
                        duration = 4,
                    })
                    return
                end

                local tier = getTierForAmount(amount)
                local estimMin, estimMax
                if tier then
                    estimMin = math.floor(amount * tier.partialMin)
                    estimMax = math.floor(amount * tier.baseRate)
                else
                    estimMin = math.floor(amount * 0.5)
                    estimMax = math.floor(amount * 0.8)
                end

                -- Confirmation avant envoi
                DBALib.UI.Confirm({
                    title   = "Confirmer le blanchiment",
                    message = string.format(
                        "Blanchir %s ?\n\nRécupération estimée : entre %s et %s selon le risque.",
                        DBALib.Format.Money(amount),
                        DBALib.Format.Money(estimMin),
                        DBALib.Format.Money(estimMax)
                    ),
                    onYes = function()
                        net.Start("Laundering.RequestStart")
                            net.WriteEntity(CurrentNPC)
                            net.WriteUInt(amount, 30)
                        net.SendToServer()
                    end,
                })
            end,
        })
        blanchirBtn:SetWide(innerW)

        -- Positionnement vertical via VBox (immediate-mode)
        DBALib.UI.VBox({
            parent  = body,
            items   = { statCard, divider1, tierTable, divider2, moneyInput, blanchirBtn },
            gap     = S(10),
            padding = innerPad,
        })
    end

    CurrentFrame = DBALib.UI.Frame({
        title     = "Blanchisseur",
        w         = 560,
        h         = 520,
        draggable = false,
        closable  = true,
        content   = buildContent,
    })

    -- Cleanup sur fermeture
    if IsValid(CurrentFrame) then
        local origClose = CurrentFrame.OnClose
        CurrentFrame.OnClose = function(self)
            hook.Remove("Think", "Laundering.ProgressTick")
            -- Note : on ne cancel pas la transaction serveur si TxRunning.
            -- La transaction continue côté serveur et créditera le joueur à la fin.
            TxRunning = false
            CurrentFrame = nil
            ProgressBar  = nil
            if origClose then origClose(self) end
        end
    end
end

-- ============================================================================
-- Laundering.UI.OnStarted
-- Remplace le contenu du frame par la progress bar + bouton Annuler.
-- ============================================================================

function Laundering.UI.OnStarted(amount, duration, methodId)
    if not dbaLibReady() then return end

    TxStart       = CurTime()
    TxDuration    = duration
    TxRunning     = true
    TxAmount      = amount
    CurrentMethodId = methodId

    DBALib.Sound("ui_notify")

    -- Si la frame n'est pas ouverte (transaction démarrée par un autre addon V2+),
    -- on ne crée pas de frame (l'utilisateur verra juste les toasts).
    if not IsValid(CurrentFrame) then
        ProgressBar = nil
        -- Setup le tick pour le cas où la frame sera créée plus tard
        -- (dans ce cas il n'y a pas de progress bar mais le hook reste inactif)
        return
    end

    -- Frame.body est le panel public exposé par DBALib (cf. frame.lua line 17)
    local body = CurrentFrame.body
    if not IsValid(body) then
        ProgressBar = nil
        return
    end

    -- Nettoyer les enfants existants
    for _, child in ipairs(body:GetChildren()) do
        if IsValid(child) then child:Remove() end
    end

    local S        = DBALib.Scale
    local innerPad = S(12)
    local innerW   = body:GetWide() - innerPad * 2

    -- Label informatif
    local statCard = DBALib.UI.StatCard({
        parent = body,
        value  = DBALib.Format.Money(amount),
        label  = "Blanchiment en cours…",
    })
    statCard:SetWide(innerW)

    -- Progress bar
    ProgressBar = DBALib.UI.ProgressBar({
        parent  = body,
        value   = 0,
        max     = duration,
        variant = "linear",
        label   = "Progression",
    })
    ProgressBar:SetWide(innerW)

    -- Bouton Annuler
    local cancelBtn = DBALib.UI.Button({
        parent  = body,
        label   = "Annuler",
        variant = "danger",
        onClick = function()
            net.Start("Laundering.CancelRequest")
            net.SendToServer()
        end,
    })
    cancelBtn:SetWide(innerW)

    -- Positionnement vertical
    DBALib.UI.VBox({
        parent  = body,
        items   = { statCard, ProgressBar, cancelBtn },
        gap     = S(12),
        padding = innerPad,
    })

    -- Hook Think pour mettre à jour la progress bar (throttlé à 30 Hz)
    hook.Add("Think", "Laundering.ProgressTick", function()
        if not TxRunning then
            hook.Remove("Think", "Laundering.ProgressTick")
            return
        end
        if not IsValid(ProgressBar) then
            hook.Remove("Think", "Laundering.ProgressTick")
            return
        end

        if (ProgressBar._nextTick or 0) > CurTime() then return end
        ProgressBar._nextTick = CurTime() + 0.033  -- ~30 Hz

        local elapsed = math.min(CurTime() - TxStart, TxDuration)
        ProgressBar:SetValue(elapsed)
    end)
end

-- ============================================================================
-- Laundering.UI.OnCompleted
-- ============================================================================

function Laundering.UI.OnCompleted(dirtyIn, cleanOut, isPartial, methodId)
    if not dbaLibReady() then return end

    hook.Remove("Think", "Laundering.ProgressTick")
    TxRunning = false

    if isPartial then
        DBALib.Sound("ui_error")
        DBALib.UI.Notify({
            message  = string.format(
                "Blanchiment partiel : +%s (sur %s)",
                DBALib.Format.Money(cleanOut),
                DBALib.Format.Money(dirtyIn)
            ),
            type     = "warning",
            duration = 6,
        })
    else
        DBALib.Sound("ui_levelup")
        DBALib.UI.Notify({
            message  = "Blanchiment réussi : +" .. DBALib.Format.Money(cleanOut),
            type     = "success",
            duration = 5,
        })
    end

    closeFrame()
end

-- ============================================================================
-- Laundering.UI.OnCancelled
-- ============================================================================

function Laundering.UI.OnCancelled(amount, reason, methodId)
    if not dbaLibReady() then return end

    hook.Remove("Think", "Laundering.ProgressTick")
    TxRunning = false

    DBALib.Sound("ui_error")
    DBALib.UI.Notify({
        message  = "Blanchiment annulé (" .. reason .. ") — Argent sale remboursé.",
        type     = "warning",
        duration = 5,
    })

    closeFrame()
end

-- ============================================================================
-- Laundering.UI.OnError
-- ============================================================================

function Laundering.UI.OnError(msg)
    if not dbaLibReady() then return end

    DBALib.Sound("ui_error")
    DBALib.UI.Notify({
        message  = tostring(msg),
        type     = "danger",
        duration = 5,
    })
end
