local _, ns = ...
local MythicTools = ns.MythicTools

local max = math.max

local DEFAULT_ALERT_SOUND = "Interface\\AddOns\\SkyMythicHistory\\Media\\Sounds\\OhNo.ogg"
local DEFAULT_ALERT_FONT = "default"
local DEFAULT_ALERT_POSITION = "TOP"
local DEFAULT_ALERT_OUTLINE = "OUTLINE"
local OLD_DEFAULT_ALERT_TEMPLATE = "You already ran [runs] keys with [player] ([timed] timed)"
local OLD_PTBR_ALERT_TEMPLATE = "Voce ja fez [runs] chaves com [player] ([timed] no tempo)"
local DEFAULT_ALERT_TEMPLATE = "{{star}} You already ran [runs] keys with [player] ([timed] timed) | Note : [note]"
local RAID_TARGET_ICONS = {
    star = 1,
    circle = 2,
    diamond = 3,
    triangle = 4,
    moon = 5,
    square = 6,
    cross = 7,
    x = 7,
    skull = 8,
}

local FALLBACK_SOUNDS = {
    {value = DEFAULT_ALERT_SOUND, label = "Oh no"},
    {value = "soundkit:8959", label = "Raid warning"},
    {value = "soundkit:12867", label = "Ready check"},
    {value = "soundkit:567397", label = "Map ping"},
    {value = "soundkit:18019", label = "UI alert"},
}

local FALLBACK_FONTS = {
    {value = "default", label = "Default"},
    {value = "friz", label = "Friz Quadrata"},
    {value = "morpheus", label = "Morpheus"},
    {value = "skurri", label = "Skurri"},
}

local ALERT_POSITIONS = {
    TOP = {"TOP", "TOP", 0, -180},
    CENTER = {"CENTER", "CENTER", 0, 120},
    BOTTOM = {"BOTTOM", "BOTTOM", 0, 220},
}

local function GetSharedMedia()
    return LibStub and LibStub("LibSharedMedia-3.0", true) or nil
end

local function AddUniqueOption(options, seen, value, label)
    if type(value) ~= "string" or value == "" or seen[value] then
        return
    end

    seen[value] = true
    options[#options + 1] = {value = value, label = label or value}
end

local function TrimText(value)
    value = tostring(value or "")
    value = value:gsub("^%s+", "")
    value = value:gsub("%s+$", "")
    value = value:gsub("%s+", " ")
    return value
end

local function BuildReadableSoundLabel(soundName, sharedMedia)
    local label = TrimText(tostring(soundName or ""))
    label = label:gsub("|c%x%x%x%x%x%x%x%x", "")
    label = label:gsub("|r", "")

    local hasNonASCII = label:find("[^\032-\126]") ~= nil
    local asciiOnly = TrimText(label:gsub("[^\032-\126]", ""))

    if hasNonASCII then
        local pathLabel
        if sharedMedia and sharedMedia.Fetch then
            local ok, fetchedPath = pcall(sharedMedia.Fetch, sharedMedia, "sound", soundName, true)
            if ok and type(fetchedPath) == "string" and fetchedPath ~= "" then
                pathLabel = fetchedPath:match("([^\\/:]+)%.%w+$") or fetchedPath:match("([^\\/:]+)$")
            end
        end
        label = TrimText(pathLabel or asciiOnly)
    else
        label = asciiOnly ~= "" and asciiOnly or label
    end

    if label == "" then
        label = "Custom sound"
    end

    if #label > 44 then
        label = label:sub(1, 41) .. "..."
    end

    return label
end

local function GetSoundKitID(value)
    local id = type(value) == "string" and value:match("^soundkit:(%d+)$") or nil
    return tonumber(id)
end

local function GetFontPath(value)
    if value == "friz" then
        return "Fonts\\FRIZQT__.TTF"
    elseif value == "morpheus" then
        return "Fonts\\MORPHEUS.TTF"
    elseif value == "skurri" then
        return "Fonts\\SKURRI.TTF"
    elseif value == "default" or value == nil or value == "" then
        return STANDARD_TEXT_FONT
    end

    local sharedMedia = GetSharedMedia()
    if sharedMedia and sharedMedia.Fetch then
        local ok, path = pcall(sharedMedia.Fetch, sharedMedia, "font", value, true)
        if ok and type(path) == "string" and path ~= "" then
            return path
        end
    end

    return STANDARD_TEXT_FONT
end

local function FormatTokenNumber(value)
    return tostring(tonumber(value) or 0)
end

local function FormatTokenPercent(value)
    value = tonumber(value) or 0
    return ("%d%%"):format(math.floor(value + 0.5))
end

local function FormatTokenRating(value, count)
    if (tonumber(count) or 0) <= 0 then
        return ""
    end

    value = tonumber(value) or 0
    local formatted = ("%.1f"):format(value)
    return formatted:gsub("%.0$", "")
end

local function IsDefaultAlertTemplate(value)
    return value == DEFAULT_ALERT_TEMPLATE
        or value == OLD_DEFAULT_ALERT_TEMPLATE
        or value == OLD_PTBR_ALERT_TEMPLATE
end

local function RenderIconTokens(value)
    value = tostring(value or "")
    for token, iconIndex in pairs(RAID_TARGET_ICONS) do
        value = value:gsub("{{" .. token .. "}}", ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:14:14:0:0|t"):format(iconIndex))
    end
    return value
end

function MythicTools:GetAlertSoundOptions()
    local options = {}
    local seen = {}

    for _, option in ipairs(FALLBACK_SOUNDS) do
        AddUniqueOption(options, seen, option.value, option.label)
    end

    local sharedMedia = GetSharedMedia()
    if sharedMedia and sharedMedia.List then
        local ok, sounds = pcall(sharedMedia.List, sharedMedia, "sound")
        if ok and type(sounds) == "table" then
            table.sort(sounds, function(a, b)
                return tostring(a or "") < tostring(b or "")
            end)
            for _, soundName in ipairs(sounds) do
                if type(soundName) == "string" and soundName ~= "" then
                    AddUniqueOption(options, seen, soundName, BuildReadableSoundLabel(soundName, sharedMedia))
                end
            end
        end
    end

    return options
end

function MythicTools:GetDefaultAlertSoundKey()
    return DEFAULT_ALERT_SOUND
end

function MythicTools:GetAlertFontOptions()
    local options = {}
    local seen = {}

    for _, option in ipairs(FALLBACK_FONTS) do
        AddUniqueOption(options, seen, option.value, option.label)
    end

    local sharedMedia = GetSharedMedia()
    if sharedMedia and sharedMedia.List then
        local ok, fonts = pcall(sharedMedia.List, sharedMedia, "font")
        if ok and type(fonts) == "table" then
            table.sort(fonts)
            for _, fontName in ipairs(fonts) do
                AddUniqueOption(options, seen, fontName, fontName)
            end
        end
    end

    return options
end

function MythicTools:GetAlertOutlineOptions()
    return {
        {value = "", label = "None"},
        {value = "OUTLINE", label = "Outline"},
        {value = "THICKOUTLINE", label = "Thick outline"},
        {value = "MONOCHROMEOUTLINE", label = "Mono outline"},
    }
end

function MythicTools:GetAlertPositionOptions()
    return {
        {value = "TOP", label = "Top"},
        {value = "CENTER", label = "Center"},
        {value = "BOTTOM", label = "Bottom"},
        {value = "CUSTOM", label = "Custom / dragged"},
    }
end

function MythicTools:GetDefaultHistoryAlertTemplate()
    local localized = self.L and self:L("KNOWN_PLAYER_ALERT_TEMPLATE") or nil
    if type(localized) == "string" and localized ~= "" and localized ~= "KNOWN_PLAYER_ALERT_TEMPLATE" then
        return localized
    end

    return DEFAULT_ALERT_TEMPLATE
end

function MythicTools:GetHistoryAlertTemplate(channel)
    local settings = self.db and self.db.settings or {}
    local template
    if channel == "chat" then
        template = settings.historyAlertChatTemplate
    else
        template = settings.historyAlertFloatingTemplate
    end

    if type(template) ~= "string" or template == "" then
        return self:GetDefaultHistoryAlertTemplate()
    end

    return template
end

function MythicTools:GetHistoryAlertTokenValues(fullName, playerEntry)
    fullName = self:NormalizePlayerName(fullName) or self:TrimText(fullName)
    playerEntry = type(playerEntry) == "table" and playerEntry or {}

    local totalRuns = tonumber(playerEntry.totalRuns) or 0
    local timedRuns = tonumber(playerEntry.timedRuns) or 0
    local overtimeRuns = tonumber(playerEntry.overtimeRuns) or max(totalRuns - timedRuns, 0)
    local successRate = tonumber(playerEntry.successRate)
    if not successRate and totalRuns > 0 then
        successRate = (timedRuns / totalRuns) * 100
    end

    local noteText = playerEntry.noteText or playerEntry.note
    if (type(noteText) ~= "string" or noteText == "") and self.db and self.db.playerNotes then
        noteText = self.db.playerNotes[fullName] or ""
    end

    return {
        player = self:GetShortName(fullName),
        full_player = fullName or "",
        runs = FormatTokenNumber(totalRuns),
        timed = FormatTokenNumber(timedRuns),
        overtime = FormatTokenNumber(overtimeRuns),
        success_rate = FormatTokenPercent(successRate or 0),
        rating = FormatTokenRating(playerEntry.averageRating, playerEntry.ratingCount),
        note = noteText or "",
    }
end

function MythicTools:RenderHistoryAlertTemplate(template, fullName, playerEntry)
    template = type(template) == "string" and template or self:GetDefaultHistoryAlertTemplate()
    local values = self:GetHistoryAlertTokenValues(fullName, playerEntry)

    for token, value in pairs(values) do
        local replacement = tostring(value or ""):gsub("%%", "%%%%")
        template = template:gsub("%[" .. token .. "%]", replacement)
    end

    return RenderIconTokens(template)
end

function MythicTools:BuildKnownPlayerMessage(fullName, playerEntry, templateOrChannel)
    local template = templateOrChannel
    if templateOrChannel == "chat" or templateOrChannel == "floating" or templateOrChannel == nil then
        template = self:GetHistoryAlertTemplate(templateOrChannel or "floating")
    end

    return self:RenderHistoryAlertTemplate(template, fullName, playerEntry)
end

function MythicTools:NormalizeAlertSettings()
    if not (self.db and self.db.settings) then
        return
    end

    local settings = self.db.settings
    settings.historyAlertChat = settings.historyAlertChat ~= false
    settings.chatAlerts = settings.historyAlertChat
    settings.historyAlertFloating = settings.historyAlertFloating and true or false
    settings.historyAlertSound = settings.historyAlertSound and true or false
    if settings.historyAlertSoundKey == nil
        or settings.historyAlertSoundKey == ""
        or settings.historyAlertSoundKey == "soundkit:8959" then
        settings.historyAlertSoundKey = DEFAULT_ALERT_SOUND
    end
    local defaultTemplate = self:GetDefaultHistoryAlertTemplate()
    if type(settings.historyAlertFloatingTemplate) ~= "string"
        or settings.historyAlertFloatingTemplate == ""
        or IsDefaultAlertTemplate(settings.historyAlertFloatingTemplate) then
        settings.historyAlertFloatingTemplate = defaultTemplate
    end
    if type(settings.historyAlertChatTemplate) ~= "string"
        or settings.historyAlertChatTemplate == ""
        or IsDefaultAlertTemplate(settings.historyAlertChatTemplate) then
        settings.historyAlertChatTemplate = defaultTemplate
    end
    settings.historyAlertFont = settings.historyAlertFont or DEFAULT_ALERT_FONT
    settings.historyAlertFontSize = self:Clamp(tonumber(settings.historyAlertFontSize) or 22, 10, 48)
    settings.historyAlertOutline = settings.historyAlertOutline or DEFAULT_ALERT_OUTLINE
    settings.historyAlertPosition = settings.historyAlertPosition or DEFAULT_ALERT_POSITION
    settings.historyAlertLocked = settings.historyAlertLocked ~= false
    if type(settings.historyAlertPoint) ~= "table" or #settings.historyAlertPoint < 4 then
        local point = ALERT_POSITIONS[settings.historyAlertPosition] or ALERT_POSITIONS[DEFAULT_ALERT_POSITION]
        settings.historyAlertPoint = {point[1], point[2], point[3], point[4]}
    end
end

function MythicTools:ResolveAlertSound(value)
    value = value or (self.db and self.db.settings and self.db.settings.historyAlertSoundKey) or DEFAULT_ALERT_SOUND

    local soundKitID = GetSoundKitID(value)
    if soundKitID then
        return "kit", soundKitID
    end

    local sharedMedia = GetSharedMedia()
    if sharedMedia and sharedMedia.Fetch then
        local ok, path = pcall(sharedMedia.Fetch, sharedMedia, "sound", value, true)
        if ok and type(path) == "string" and path ~= "" then
            return "file", path
        end
    end

    if type(value) == "string" and value:find("[/\\]") then
        return "file", value
    end

    local defaultKitID = GetSoundKitID(DEFAULT_ALERT_SOUND)
    if defaultKitID then
        return "kit", defaultKitID
    end
    if type(DEFAULT_ALERT_SOUND) == "string" and DEFAULT_ALERT_SOUND:find("[/\\]") then
        return "file", DEFAULT_ALERT_SOUND
    end
    return "kit", 8959
end

function MythicTools:PlayHistoryAlertSound()
    if not (self.db and self.db.settings and self.db.settings.historyAlertSound) then
        return
    end

    local soundType, soundValue = self:ResolveAlertSound(self.db.settings.historyAlertSoundKey)
    if soundType == "file" and PlaySoundFile then
        PlaySoundFile(soundValue, "Master")
    elseif soundValue and PlaySound then
        PlaySound(soundValue, "Master")
    end
end

function MythicTools:GetHistoryAlertPoint()
    local settings = self.db and self.db.settings or {}
    local point = settings.historyAlertPoint
    if type(point) == "table" and #point >= 4 then
        return point[1] or "CENTER", UIParent, point[2] or point[1] or "CENTER", point[3] or 0, point[4] or 0
    end

    local preset = ALERT_POSITIONS[settings.historyAlertPosition or DEFAULT_ALERT_POSITION] or ALERT_POSITIONS[DEFAULT_ALERT_POSITION]
    return preset[1], UIParent, preset[2], preset[3], preset[4]
end

function MythicTools:SetHistoryAlertPositionPreset(position)
    if not (self.db and self.db.settings) then
        return
    end

    if position == "CUSTOM" then
        self.db.settings.historyAlertPosition = "CUSTOM"
        return
    end

    position = ALERT_POSITIONS[position] and position or DEFAULT_ALERT_POSITION
    local point = ALERT_POSITIONS[position]
    self.db.settings.historyAlertPosition = position
    self.db.settings.historyAlertPoint = {point[1], point[2], point[3], point[4]}
    if self.historyAlertFrame then
        self.historyAlertFrame:ClearAllPoints()
        self.historyAlertFrame:SetPoint(self:GetHistoryAlertPoint())
    end
end

function MythicTools:GetHistoryAlertCoordinates()
    local settings = self.db and self.db.settings or {}
    local point = settings.historyAlertPoint
    if type(point) == "table" and #point >= 4 then
        return tonumber(point[3]) or 0, tonumber(point[4]) or 0
    end

    local preset = ALERT_POSITIONS[settings.historyAlertPosition or DEFAULT_ALERT_POSITION] or ALERT_POSITIONS[DEFAULT_ALERT_POSITION]
    return preset[3] or 0, preset[4] or 0
end

function MythicTools:SetHistoryAlertCoordinates(x, y, skipRefresh)
    if not (self.db and self.db.settings) then
        return
    end

    x = self:Clamp(tonumber(x) or 0, -500, 500)
    y = self:Clamp(tonumber(y) or 0, -500, 500)
    self.db.settings.historyAlertPosition = "CUSTOM"
    self.db.settings.historyAlertPoint = {"CENTER", "CENTER", x, y}

    if self.historyAlertFrame then
        self.historyAlertFrame:ClearAllPoints()
        self.historyAlertFrame:SetPoint(self:GetHistoryAlertPoint())
    end

    if not skipRefresh and self.RefreshSettingsView then
        self:RefreshSettingsView()
    end
end

function MythicTools:SetHistoryAlertMoveLocked(locked)
    if not (self.db and self.db.settings) then
        return
    end

    self.db.settings.historyAlertLocked = locked and true or false
    if self.db.settings.historyAlertLocked then
        if self.historyAlertFrame and self.historyAlertFrame.moveMode then
            self.historyAlertFrame.moveMode = false
            self.historyAlertFrame:Hide()
        end
    else
        self:ShowHistoryAlertMoveHandle()
    end

    if self.RefreshSettingsView then
        self:RefreshSettingsView()
    end
end

function MythicTools:RefreshHistoryAlertMoveState()
    local frame = self.historyAlertFrame
    if not frame then
        return
    end

    local locked = not (self.db and self.db.settings and self.db.settings.historyAlertLocked == false)
    frame:EnableMouse(not locked)
    if frame.SetBackdropColor then
        if locked then
            frame:SetBackdropColor(0.03, 0.03, 0.03, 0)
            frame:SetBackdropBorderColor(0.7, 0.7, 0.7, 0)
        else
            frame:SetBackdropColor(0.03, 0.03, 0.03, 0.78)
            frame:SetBackdropBorderColor(0.7, 0.7, 0.7, 0.85)
        end
    end
end

function MythicTools:StoreHistoryAlertPoint(skipRefresh)
    local frame = self.historyAlertFrame
    if not (frame and self.db and self.db.settings) then
        return
    end

    local point, _, relativePoint, xOfs, yOfs = frame:GetPoint(1)
    xOfs = self:Clamp(tonumber(xOfs) or 0, -500, 500)
    yOfs = self:Clamp(tonumber(yOfs) or 0, -500, 500)
    self.db.settings.historyAlertPosition = "CUSTOM"
    self.db.settings.historyAlertPoint = {point or "CENTER", relativePoint or point or "CENTER", xOfs, yOfs}
    if not skipRefresh and self.RefreshSettingsView then
        self:RefreshSettingsView()
    end
end

function MythicTools:RefreshHistoryFloatingAlertStyle()
    local frame = self.historyAlertFrame
    if not frame then
        return
    end

    local settings = self.db and self.db.settings or {}
    local fontPath = GetFontPath(settings.historyAlertFont)
    local fontSize = self:Clamp(tonumber(settings.historyAlertFontSize) or 22, 10, 48)
    local outline = settings.historyAlertOutline or DEFAULT_ALERT_OUTLINE

    frame.Text:SetFont(fontPath, fontSize, outline)
    frame.Text:SetShadowColor(0, 0, 0, 0.9)
    frame.Text:SetShadowOffset(1, -1)
    frame:SetSize(max(420, fontSize * 18), max(62, fontSize * 2.6))
end

function MythicTools:CreateHistoryFloatingAlertFrame()
    if self.historyAlertFrame then
        return self.historyAlertFrame
    end

    local frame = CreateFrame("Frame", "MythicToolsHistoryFloatingAlertFrame", UIParent, "BackdropTemplate")
    frame:SetFrameStrata("FULLSCREEN_DIALOG")
    frame:SetFrameLevel(650)
    frame:SetClampedToScreen(true)
    frame:EnableMouse(true)
    frame:SetMovable(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
        insets = {left = 1, right = 1, top = 1, bottom = 1},
    })
    frame:SetBackdropColor(0.03, 0.03, 0.03, 0.78)
    frame:SetBackdropBorderColor(0.7, 0.7, 0.7, 0.85)
    frame:Hide()

    frame.Text = frame:CreateFontString(nil, "OVERLAY")
    frame.Text:SetPoint("CENTER")
    frame.Text:SetJustifyH("CENTER")
    frame.Text:SetJustifyV("MIDDLE")
    frame.Text:SetTextColor(1, 0.9, 0.62, 1)

    frame:SetScript("OnDragStart", function(self)
        if MythicTools.db and MythicTools.db.settings and MythicTools.db.settings.historyAlertLocked == false then
            self._mtDragging = true
            self._mtDragElapsed = 0
            self:StartMoving()
            self:SetScript("OnUpdate", function(frame, elapsed)
                frame._mtDragElapsed = (frame._mtDragElapsed or 0) + (elapsed or 0)
                if frame._mtDragElapsed >= 0.05 then
                    frame._mtDragElapsed = 0
                    MythicTools:StoreHistoryAlertPoint()
                end
            end)
        end
    end)
    frame:SetScript("OnDragStop", function(self)
        if self._mtDragging then
            self._mtDragging = false
            self:SetScript("OnUpdate", nil)
            self:StopMovingOrSizing()
            MythicTools:StoreHistoryAlertPoint()
        end
    end)

    self.historyAlertFrame = frame
    self:RefreshHistoryFloatingAlertStyle()
    self:RefreshHistoryAlertMoveState()
    return frame
end

function MythicTools:ShowHistoryAlertMoveHandle()
    local frame = self:CreateHistoryFloatingAlertFrame()
    frame.moveMode = true
    frame:SetScript("OnUpdate", nil)
    frame:ClearAllPoints()
    frame:SetPoint(self:GetHistoryAlertPoint())
    self:RefreshHistoryFloatingAlertStyle()
    self:RefreshHistoryAlertMoveState()
    frame.Text:SetText("Known player alert")
    frame:SetAlpha(1)
    frame:Show()
end

function MythicTools:ShowHistoryFloatingAlert(message, preview)
    if not preview and not (self.db and self.db.settings and self.db.settings.historyAlertFloating) then
        return
    end

    local frame = self:CreateHistoryFloatingAlertFrame()
    frame.moveMode = false
    frame:ClearAllPoints()
    frame:SetPoint(self:GetHistoryAlertPoint())
    self:RefreshHistoryFloatingAlertStyle()
    self:RefreshHistoryAlertMoveState()
    frame.Text:SetText(message or "")
    frame.elapsed = 0
    frame.duration = preview and 4 or 5
    frame:SetAlpha(1)
    frame:Show()
    frame:SetScript("OnUpdate", function(self, elapsed)
        self.elapsed = (self.elapsed or 0) + (elapsed or 0)
        local remaining = (self.duration or 5) - self.elapsed
        if remaining <= 0 then
            self:SetScript("OnUpdate", nil)
            if MythicTools.db and MythicTools.db.settings and MythicTools.db.settings.historyAlertLocked == false then
                MythicTools:ShowHistoryAlertMoveHandle()
            else
                self:Hide()
            end
            return
        end
        if remaining < 1 then
            self:SetAlpha(remaining)
        end
    end)
end

function MythicTools:PreviewHistoryAlert()
    local message = self:BuildKnownPlayerMessage("Example-Azralon", {
        totalRuns = 8,
        timedRuns = 6,
        overtimeRuns = 2,
        successRate = 75,
        averageRating = 4.5,
        ratingCount = 2,
        noteText = "Reliable tank",
    })
    self:ShowHistoryFloatingAlert(message, true)
    self:PlayHistoryAlertSound()
end
