local _, addon = ...
local HLY = addon.HLY

local DEFAULT_SOUND_NAME = "None"
local MIN_TRACKED_COOLDOWN = 1.5

local C_Timer = C_Timer
local CreateFrame = CreateFrame
local GetInventoryItemCooldown = GetInventoryItemCooldown
local GetInventoryItemID = GetInventoryItemID
local GetInventorySlotInfo = GetInventorySlotInfo
local IsFlying = IsFlying
local PlaySoundFile = PlaySoundFile
local UnitAffectingCombat = UnitAffectingCombat
local UnitOnTaxi = UnitOnTaxi
local wipe = wipe

local eventFrame = CreateFrame("Frame")
local slotState = {}
local cooldownTicker
local sharedCooldownState = {
    armed = false,
    lastStartTime = 0,
}

local SLOT_CONFIGS = {
    {
        key = "trinket1",
        slotId = GetInventorySlotInfo("Trinket0Slot"),
        label = "Trinket 1",
    },
    {
        key = "trinket2",
        slotId = GetInventorySlotInfo("Trinket1Slot"),
        label = "Trinket 2",
    },
}

local SOUND_CHANNELS = {
    Master = "Master",
    SFX = "SFX",
    Music = "Music",
    Ambience = "Ambience",
    Dialog = "Dialog",
}

local BUILT_IN_SOUNDS = {
    [addon.brand .. " Duemmster Spasti"] = "Interface\\AddOns\\HLYqol\\Assets\\dummster-spasti.mp3",
    [addon.brand .. " Indian Song"] = "Interface\\AddOns\\HLYqol\\Assets\\indian-song.mp3",
    [addon.brand .. " Moment"] = "Interface\\AddOns\\HLYqol\\Assets\\moment.mp3",
    [addon.brand .. " Ohoho"] = "Interface\\AddOns\\HLYqol\\Assets\\ohoho.mp3",
    [addon.brand .. " Sieht Nicht Gut Aus"] = "Interface\\AddOns\\HLYqol\\Assets\\sieht-nicht-gut-aus.mp3",
    [addon.brand .. " Spongebob Fail"] = "Interface\\AddOns\\HLYqol\\Assets\\spongebob-fail.mp3",
}

local function GetTrinketDB(self)
    return self.db and self.db.trinketAlert
end

local function RegisterBuiltInMedia()
    if not addon.LSM then
        return
    end

    if not addon.LSM:Fetch("sound", DEFAULT_SOUND_NAME, true) then
        addon.LSM:Register("sound", DEFAULT_SOUND_NAME, "Interface\\Buttons\\WHITE8X8")
    end

    for soundName, path in pairs(BUILT_IN_SOUNDS) do
        if not addon.LSM:Fetch("sound", soundName, true) then
            addon.LSM:Register("sound", soundName, path)
        end
    end
end

function HLY:GetSoundList()
    local values = {}
    if not addon.LSM then
        values[DEFAULT_SOUND_NAME] = DEFAULT_SOUND_NAME
        return values
    end

    for soundName in pairs(addon.LSM:HashTable("sound")) do
        values[soundName] = soundName
    end

    return values
end

function HLY:EnsureTrinketAlertDefaults()
    local db = GetTrinketDB(self)
    if not db then
        return
    end

    if SOUND_CHANNELS[db.channel] == nil then
        db.channel = "Master"
    end

    db.sounds = db.sounds or {}
    db.trackedCooldowns = db.trackedCooldowns or {}

    for _, slotConfig in ipairs(SLOT_CONFIGS) do
        if type(db.sounds[slotConfig.key]) ~= "string" or db.sounds[slotConfig.key] == "" then
            db.sounds[slotConfig.key] = DEFAULT_SOUND_NAME
        end
    end
end

function HLY:PlayTrinketAlert(soundKey, forcePlay)
    local db = GetTrinketDB(self)
    if not db or (not db.enabled and not forcePlay) then
        return true
    end

    local soundName = db.sounds and db.sounds[soundKey]
    if soundName == DEFAULT_SOUND_NAME then
        return true
    end

    local path = addon.LSM and addon.LSM:Fetch("sound", soundName, true)
    if not path then
        return false
    end

    PlaySoundFile(path, db.channel or "Master")
    return true
end

local function IsFlightSuppressed()
    if UnitOnTaxi and UnitOnTaxi("player") then
        return true
    end

    if IsFlying and IsFlying() then
        return true
    end

    return false
end

local function StopTicker()
    if cooldownTicker then
        cooldownTicker:Cancel()
        cooldownTicker = nil
    end
end

local function GetCooldownInfo(slotId)
    local startTime, duration, isEnabled = GetInventoryItemCooldown("player", slotId)
    if not startTime or not duration or isEnabled == 0 then
        return 0, false, 0, 0, false
    end

    if startTime <= 0 or duration <= 0 then
        return 0, false, startTime, duration, false
    end

    local remaining = (startTime + duration) - GetTime()
    local isTrackedCooldown = duration > MIN_TRACKED_COOLDOWN
    if remaining > 0 then
        return remaining, true, startTime, duration, isTrackedCooldown
    end

    return 0, false, startTime, duration, false
end

function HLY:SaveTrackedTrinketCooldown(slotConfig, state)
    local db = GetTrinketDB(self)
    if not db then
        return
    end

    db.trackedCooldowns = db.trackedCooldowns or {}

    if state and state.itemID and state.armed and state.onCooldown and state.lastStartTime and state.lastStartTime > 0 then
        db.trackedCooldowns[slotConfig.key] = {
            itemID = state.itemID,
            armed = true,
            lastStartTime = state.lastStartTime,
        }
        return
    end

    db.trackedCooldowns[slotConfig.key] = nil
end

function HLY:SaveSharedTrinketCooldownState()
    local db = GetTrinketDB(self)
    if not db then
        return
    end

    db.trackedCooldowns = db.trackedCooldowns or {}

    if sharedCooldownState.armed and sharedCooldownState.lastStartTime and sharedCooldownState.lastStartTime > 0 then
        db.trackedCooldowns.shared = {
            armed = true,
            lastStartTime = sharedCooldownState.lastStartTime,
        }
        return
    end

    db.trackedCooldowns.shared = nil
end

function HLY:UpdateSharedTrinketCooldownState(activeCooldowns, armActiveCooldowns)
    local first = activeCooldowns[1]
    local second = activeCooldowns[2]

    local sharedActive = false
    local sharedStartTime = 0

    if first and second and first.itemID and second.itemID and first.onCooldown and second.onCooldown then
        if first.startTime and second.startTime and first.startTime > 0 and first.startTime == second.startTime then
            sharedActive = true
            sharedStartTime = first.startTime
        end
    end

    if sharedActive then
        if armActiveCooldowns or sharedCooldownState.lastStartTime ~= sharedStartTime then
            sharedCooldownState.armed = true
            sharedCooldownState.lastStartTime = sharedStartTime
        end
    else
        sharedCooldownState.armed = false
        sharedCooldownState.lastStartTime = 0
    end

    self:SaveSharedTrinketCooldownState()
end

function HLY:UpdateTrinketSlotState(slotConfig)
    local currentItemID = GetInventoryItemID("player", slotConfig.slotId)
    local _, hasCooldown, startTime, _, isTrackedCooldown = GetCooldownInfo(slotConfig.slotId)
    local currentOnCooldown = hasCooldown and isTrackedCooldown
    local state = slotState[slotConfig.slotId]

    if not state then
        slotState[slotConfig.slotId] = {
            itemID = currentItemID,
            onCooldown = currentOnCooldown,
            armed = false,
            lastStartTime = startTime or 0,
        }
        return false, currentOnCooldown
    end

    if state.itemID ~= currentItemID then
        state.itemID = currentItemID
        state.onCooldown = currentOnCooldown
        state.armed = false
        state.lastStartTime = startTime or 0
        self:SaveTrackedTrinketCooldown(slotConfig, state)
        return false, currentOnCooldown
    end

    if currentOnCooldown and currentItemID then
        if not state.onCooldown or state.lastStartTime ~= startTime then
            state.armed = true
            state.lastStartTime = startTime
        end
    end

    local becameReady = state.armed and state.onCooldown and not currentOnCooldown and currentItemID
    if becameReady then
        state.armed = false
    end

    if not currentOnCooldown then
        state.lastStartTime = 0
    end

    state.onCooldown = currentOnCooldown
    self:SaveTrackedTrinketCooldown(slotConfig, state)
    return becameReady and true or false, currentOnCooldown
end

function HLY:CheckTrinketAlerts()
    if not self.db or not self.db.trinketAlert or not self.db.trinketAlert.enabled then
        StopTicker()
        return
    end

    if IsFlightSuppressed() then
        self:PrimeTrinketStates()
        return
    end

    local anyOnCooldown = false
    local activeCooldowns = {}

    for _, slotConfig in ipairs(SLOT_CONFIGS) do
        local becameReady, onCooldown = self:UpdateTrinketSlotState(slotConfig)
        if becameReady then
            self:PlayTrinketAlert(slotConfig.key)
        end
        if onCooldown then
            anyOnCooldown = true
        end

        local _, hasCooldown, startTime, duration, isTrackedCooldown = GetCooldownInfo(slotConfig.slotId)
        activeCooldowns[#activeCooldowns + 1] = {
            itemID = GetInventoryItemID("player", slotConfig.slotId),
            onCooldown = hasCooldown and isTrackedCooldown,
            startTime = startTime,
            duration = duration,
        }
    end

    local hadSharedCooldown = sharedCooldownState.armed
    local previousSharedStart = sharedCooldownState.lastStartTime
    self:UpdateSharedTrinketCooldownState(activeCooldowns, false)

    if hadSharedCooldown and not sharedCooldownState.armed and previousSharedStart > 0 then
        for _, slotConfig in ipairs(SLOT_CONFIGS) do
            local state = slotState[slotConfig.slotId]
            if state and state.itemID then
                state.armed = false
                state.lastStartTime = 0
            end
        end
    end

    if not anyOnCooldown then
        StopTicker()
    end
end

function HLY:EnsureTrinketTicker()
    if cooldownTicker or not self.db or not self.db.trinketAlert or not self.db.trinketAlert.enabled then
        return
    end

    cooldownTicker = C_Timer.NewTicker(0.15, function()
        self:CheckTrinketAlerts()
    end)
end

local function ShouldForceTicker()
    return UnitAffectingCombat and UnitAffectingCombat("player") or false
end

function HLY:PrimeTrinketStates(armActiveCooldowns)
    local db = GetTrinketDB(self)
    if not db then
        return
    end

    wipe(slotState)
    local anyOnCooldown = false
    local activeCooldowns = {}

    sharedCooldownState.armed = false
    sharedCooldownState.lastStartTime = 0

    db.trackedCooldowns = db.trackedCooldowns or {}

    for _, slotConfig in ipairs(SLOT_CONFIGS) do
        local itemID = GetInventoryItemID("player", slotConfig.slotId)
        local _, hasCooldown, startTime, duration, isTrackedCooldown = GetCooldownInfo(slotConfig.slotId)
        local onCooldown = hasCooldown and isTrackedCooldown
        local persisted = db.trackedCooldowns[slotConfig.key]
        local armed = false
        local lastStartTime = onCooldown and startTime or 0

        if persisted and persisted.itemID == itemID and onCooldown and persisted.armed and persisted.lastStartTime == startTime then
            armed = true
            lastStartTime = persisted.lastStartTime
        end

        if armActiveCooldowns and itemID and onCooldown then
            armed = true
            lastStartTime = startTime
        end

        slotState[slotConfig.slotId] = {
            itemID = itemID,
            onCooldown = onCooldown,
            armed = armed,
            lastStartTime = lastStartTime,
        }
        self:SaveTrackedTrinketCooldown(slotConfig, slotState[slotConfig.slotId])

        if onCooldown then
            anyOnCooldown = true
        end

        activeCooldowns[#activeCooldowns + 1] = {
            itemID = itemID,
            onCooldown = onCooldown,
            startTime = startTime,
            duration = duration,
        }
    end

    local persistedShared = db.trackedCooldowns.shared
    if persistedShared and persistedShared.armed then
        sharedCooldownState.armed = true
        sharedCooldownState.lastStartTime = persistedShared.lastStartTime or 0
    end

    self:UpdateSharedTrinketCooldownState(activeCooldowns, armActiveCooldowns)

    if anyOnCooldown or ShouldForceTicker() then
        self:EnsureTrinketTicker()
    else
        StopTicker()
    end
end

function HLY:HandleTrinketCooldownRelatedUpdate()
    self:CheckTrinketAlerts()

    if cooldownTicker then
        return
    end

    if ShouldForceTicker() then
        self:EnsureTrinketTicker()
        return
    end

    for _, slotConfig in ipairs(SLOT_CONFIGS) do
        local _, hasCooldown, _, _, isTrackedCooldown = GetCooldownInfo(slotConfig.slotId)
        if hasCooldown and isTrackedCooldown then
            self:EnsureTrinketTicker()
            return
        end
    end
end

function HLY:HandleTrinketAlertEvent(event, arg1)
    if not self.db or not self.db.trinketAlert then
        return
    end

    if event == "PLAYER_LOGOUT" then
        for _, slotConfig in ipairs(SLOT_CONFIGS) do
            self:SaveTrackedTrinketCooldown(slotConfig, slotState[slotConfig.slotId])
        end
        self:SaveSharedTrinketCooldownState()
        return
    end

    if event == "UNIT_INVENTORY_CHANGED" and arg1 ~= "player" then
        return
    end

    if event == "PLAYER_REGEN_DISABLED" then
        self:PrimeTrinketStates()
        self:EnsureTrinketTicker()
        return
    end

    if event == "PLAYER_REGEN_ENABLED" then
        self:HandleTrinketCooldownRelatedUpdate()
        return
    end

    if event == "PLAYER_EQUIPMENT_CHANGED" or event == "UNIT_INVENTORY_CHANGED" then
        self:PrimeTrinketStates(true)
    end

    if event == "PLAYER_ENTERING_WORLD" then
        self:PrimeTrinketStates()
        return
    end

    if event == "PLAYER_MOUNT_DISPLAY_CHANGED" or event == "UPDATE_BONUS_ACTIONBAR" then
        self:PrimeTrinketStates()
        if IsFlightSuppressed() then
            return
        end
    end

    self:HandleTrinketCooldownRelatedUpdate()
end

function HLY:SetupTrinketAlert()
    if self.trinketAlertSetup then
        return
    end

    RegisterBuiltInMedia()
    self:EnsureTrinketAlertDefaults()

    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:RegisterEvent("BAG_UPDATE_COOLDOWN")
    eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
    eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
    eventFrame:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED")
    eventFrame:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
    eventFrame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
    eventFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
    eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
    eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    eventFrame:RegisterEvent("PLAYER_LOGOUT")
    eventFrame:SetScript("OnEvent", function(_, event, ...)
        self:HandleTrinketAlertEvent(event, ...)
    end)

    self:PrimeTrinketStates()
    self.trinketAlertSetup = true
end

function HLY:GetTrinketAlertChannels()
    return SOUND_CHANNELS
end

-- LibHLYUI options pane (v2) ------------------------------------------------
function HLY:BuildTrinketAlertOptions(pane)
    pane:Note("Spielt getrennte Sounds ab, wenn deine Schmuckstücke wieder bereit sind.")
    pane:Checkbox({
        label = "Enable",
        get = function() return self.db.trinketAlert.enabled end,
        set = function(v)
            self.db.trinketAlert.enabled = v
            self:PrimeTrinketStates()
        end,
    })
    pane:Dropdown({
        label = "Audio Channel",
        values = function() return self:GetTrinketAlertChannels() end,
        get = function() return self.db.trinketAlert.channel end,
        set = function(v) self.db.trinketAlert.channel = v end,
    })
    pane:Section("Trinket Sounds")
    pane:Dropdown({
        label = "Trinket 1",
        values = function() return self:GetSoundList() end, soundPreview = true,
        get = function() return self.db.trinketAlert.sounds.trinket1 end,
        set = function(v) self.db.trinketAlert.sounds.trinket1 = v end,
    })
    pane:Dropdown({
        label = "Trinket 2",
        values = function() return self:GetSoundList() end, soundPreview = true,
        get = function() return self.db.trinketAlert.sounds.trinket2 end,
        set = function(v) self.db.trinketAlert.sounds.trinket2 = v end,
    })
end

addon.options = addon.options or {}
addon.options[#addon.options + 1] = {
    group = "Alerts", groupOrder = 300, name = "Trinket Alert", order = 2,
    build = "BuildTrinketAlertOptions",
}
