local _, SinsToolkit = ...

local Vault = {}
SinsToolkit.ProfileVault = Vault

local MAX_ACTION_SLOTS = 180
local CHARACTER_MACRO_START = (MAX_ACCOUNT_MACROS or 120) + 1
local HasActionCompat = (C_ActionBar and C_ActionBar.HasAction) or HasAction

local function EnsureDB()
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
end

local function GetBackup(id)
    EnsureDB()
    return SinsToolkitDB.profileVaultBackups[id]
end

local function GetBackupSummary(backup)
    if not backup then return "Backup not found." end
    local actionCount = backup.actions and #backup.actions or 0
    local accountMacroCount = backup.macros and backup.macros.account and #backup.macros.account or 0
    local characterMacroCount = backup.macros and backup.macros.character and #backup.macros.character or 0
    local bindingCount = backup.bindings and #backup.bindings or 0
    return ("%s\n\nThis will replace action bars, update/create macros, and replace keybindings on this character.\n\nActions: %d\nAccount macros: %d\nCharacter macros: %d\nKeybindings: %d"):format(
        backup.name or "Selected backup",
        actionCount,
        accountMacroCount,
        characterMacroCount,
        bindingCount
    )
end

StaticPopupDialogs["SINS_TOOLKIT_PROFILE_RESTORE_CONFIRM"] = {
    text = "Restore Profile Vault backup?\n\n%s",
    button1 = YES,
    button2 = CANCEL,
    OnAccept = function(_, data)
        if not data then return end
        local ok, message = Vault:RestoreBackup(data.id, true)
        if data.callback then data.callback(ok, message) end
    end,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

StaticPopupDialogs["SINS_TOOLKIT_PROFILE_DELETE_CONFIRM"] = {
    text = "Delete Profile Vault backup?\n\n%s",
    button1 = DELETE or "Delete",
    button2 = CANCEL,
    OnAccept = function(_, data)
        if not data then return end
        local ok, message = Vault:DeleteBackup(data.id, true)
        if data.callback then data.callback(ok, message) end
    end,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

local function DeepCopy(value)
    if type(value) ~= "table" then return value end
    local copy = {}
    for k, v in pairs(value) do
        copy[k] = DeepCopy(v)
    end
    return copy
end

local function GetCharacterKey()
    local name = UnitName("player") or UNKNOWN
    local realm = GetRealmName() or ""
    return name .. "-" .. realm, name, realm
end

local function GetClassInfo()
    local localizedClass, classToken = UnitClass("player")
    return classToken or "UNKNOWN", localizedClass or "Unknown"
end

local function MakeBackupID()
    local characterKey = GetCharacterKey()
    return characterKey .. "-" .. time()
end

local function CaptureAction(slot)
    local actionType, id, subType = GetActionInfo(slot)
    if not actionType or not id then return nil end

    local action = { slot = slot, type = actionType, id = id, subType = subType }
    if actionType == "macro" then
        local name, icon, body = GetMacroInfo(id)
        action.name = name
        action.icon = icon
        action.body = body
    elseif actionType == "equipmentset" and C_EquipmentSet then
        action.name = C_EquipmentSet.GetEquipmentSetInfo(id)
    end
    return action
end

local function CaptureActions()
    local actions = {}
    for slot = 1, MAX_ACTION_SLOTS do
        local action = CaptureAction(slot)
        if action then
            actions[#actions + 1] = action
        end
    end
    return actions
end

local function CaptureMacros()
    local macros = { account = {}, character = {} }
    local accountCount, characterCount = GetNumMacros()

    for index = 1, accountCount do
        local name, icon, body = GetMacroInfo(index)
        if name then
            macros.account[#macros.account + 1] = { name = name, icon = icon, body = body }
        end
    end

    for offset = 1, characterCount do
        local index = CHARACTER_MACRO_START + offset - 1
        local name, icon, body = GetMacroInfo(index)
        if name then
            macros.character[#macros.character + 1] = { name = name, icon = icon, body = body }
        end
    end

    return macros
end

local function CaptureBindings()
    local bindings = {}
    for index = 1, GetNumBindings() do
        local command, _, key1, key2 = GetBinding(index)
        if command and (key1 or key2) then
            bindings[#bindings + 1] = { command = command, key1 = key1, key2 = key2 }
        end
    end
    return bindings
end

function Vault:CreateBackup(name)
    EnsureDB()

    name = (name or ""):match("^%s*(.-)%s*$")
    if name == "" then
        name = "Backup"
    end

    local characterKey, characterName, realmName = GetCharacterKey()
    local classToken, className = GetClassInfo()
    local backup = {
        id = MakeBackupID(),
        name = name,
        character = characterName,
        realm = realmName,
        characterKey = characterKey,
        classToken = classToken,
        className = className,
        createdAt = date("%Y-%m-%d %H:%M:%S"),
        createdAtEpoch = time(),
        actions = CaptureActions(),
        macros = CaptureMacros(),
        bindings = CaptureBindings(),
    }

    SinsToolkitDB.profileVaultBackups[backup.id] = backup
    return backup
end

local function ClearActionSlot(slot)
    if HasActionCompat and HasActionCompat(slot) then
        PickupAction(slot)
        ClearCursor()
    end
end

local function PickupStoredAction(action)
    if not action then return false end

    if action.type == "spell" then
        if C_Spell and C_Spell.PickupSpell then
            C_Spell.PickupSpell(action.id)
        elseif PickupSpell then
            PickupSpell(action.id)
        end
    elseif action.type == "item" then
        if C_Item and C_Item.PickupItem then
            C_Item.PickupItem(action.id)
        elseif PickupItem then
            PickupItem(action.id)
        end
    elseif action.type == "macro" then
        PickupMacro(action.name or action.id)
    elseif action.type == "flyout" and PickupSpellBookItem then
        PickupSpellBookItem(action.id, BOOKTYPE_SPELL)
    elseif action.type == "equipmentset" and C_EquipmentSet and action.name then
        C_EquipmentSet.PickupEquipmentSetByName(action.name)
    elseif action.type == "summonmount" and C_MountJournal and C_MountJournal.Pickup then
        C_MountJournal.Pickup(action.id)
    elseif action.type == "companion" and PickupCompanion then
        PickupCompanion(action.subType or "CRITTER", action.id)
    end

    return GetCursorInfo() ~= nil
end

local function RestoreActions(actions)
    local restored = 0
    local skipped = 0

    for slot = 1, MAX_ACTION_SLOTS do
        ClearActionSlot(slot)
    end

    for _, action in ipairs(actions or {}) do
        ClearCursor()
        if PickupStoredAction(action) then
            PlaceAction(action.slot)
            restored = restored + 1
        else
            skipped = skipped + 1
        end
        ClearCursor()
    end

    return restored, skipped
end

local function FindMacroIndex(name, perCharacter)
    local accountCount, characterCount = GetNumMacros()
    local startIndex = perCharacter and CHARACTER_MACRO_START or 1
    local endIndex = perCharacter and (CHARACTER_MACRO_START + characterCount - 1) or accountCount

    for index = startIndex, endIndex do
        local macroName = GetMacroInfo(index)
        if macroName == name then
            return index
        end
    end
end

local function UpsertMacro(macro, perCharacter)
    if not macro or not macro.name then return end

    local index = FindMacroIndex(macro.name, perCharacter)
    if index then
        EditMacro(index, macro.name, macro.icon or "INV_Misc_QuestionMark", macro.body or "")
    else
        CreateMacro(macro.name, macro.icon or "INV_Misc_QuestionMark", macro.body or "", perCharacter)
    end
end

local function RestoreMacros(macros)
    for _, macro in ipairs((macros and macros.account) or {}) do
        UpsertMacro(macro, false)
    end
    for _, macro in ipairs((macros and macros.character) or {}) do
        UpsertMacro(macro, true)
    end
end

local function RestoreBindings(bindings)
    for index = 1, GetNumBindings() do
        local _, _, key1, key2 = GetBinding(index)
        if key1 then SetBinding(key1, nil) end
        if key2 then SetBinding(key2, nil) end
    end

    for _, binding in ipairs(bindings or {}) do
        local key1, key2 = GetBindingKey(binding.command)
        if key1 then SetBinding(key1, nil) end
        if key2 then SetBinding(key2, nil) end
    end

    for _, binding in ipairs(bindings or {}) do
        if binding.key1 then SetBinding(binding.key1, binding.command) end
        if binding.key2 then SetBinding(binding.key2, binding.command) end
    end
    SaveBindings(GetCurrentBindingSet())
end

function Vault:GetRestoreSummary(id)
    return GetBackupSummary(GetBackup(id))
end

function Vault:ConfirmRestoreBackup(id, callback)
    local backup = GetBackup(id)
    if not backup then
        if callback then callback(false, "Backup not found.") end
        return
    end
    StaticPopup_Show("SINS_TOOLKIT_PROFILE_RESTORE_CONFIRM", GetBackupSummary(backup), nil, { id = id, callback = callback })
end

function Vault:ConfirmDeleteBackup(id, callback)
    local backup = GetBackup(id)
    if not backup then
        if callback then callback(false, "Backup not found.") end
        return
    end
    StaticPopup_Show("SINS_TOOLKIT_PROFILE_DELETE_CONFIRM", backup.name or "Selected backup", nil, { id = id, callback = callback })
end

function Vault:RestoreBackup(id, confirmed)
    EnsureDB()
    if InCombatLockdown and InCombatLockdown() then
        return false, "Profile Vault restore is blocked while in combat."
    end
    if not confirmed then
        return false, "Profile Vault restore requires confirmation."
    end

    local backup = GetBackup(id)
    if not backup then
        return false, "Backup not found."
    end

    RestoreMacros(backup.macros)
    local restoredActions, skippedActions = RestoreActions(backup.actions)
    RestoreBindings(backup.bindings)
    local bindingCount = backup.bindings and #backup.bindings or 0
    return true, ("Restored %s. Actions: %d restored, %d skipped. Bindings: %d applied."):format(
        backup.name or "backup",
        restoredActions or 0,
        skippedActions or 0,
        bindingCount
    )
end

function Vault:DeleteBackup(id, confirmed)
    EnsureDB()
    if not confirmed then
        return false, "Profile Vault delete requires confirmation."
    end
    SinsToolkitDB.profileVaultBackups[id] = nil
    return true, "Profile backup deleted."
end

function Vault:GetBackups()
    EnsureDB()
    local backups = {}
    for id, backup in pairs(SinsToolkitDB.profileVaultBackups) do
        local copy = DeepCopy(backup)
        copy.id = id
        backups[#backups + 1] = copy
    end
    table.sort(backups, function(a, b)
        return (a.createdAtEpoch or 0) > (b.createdAtEpoch or 0)
    end)
    return backups
end
