
local function GetBorderColor(db)
    if not db then return 0, 207/255, 1.0 end
    if db.borderUseClassColor then
        local cc = RAID_CLASS_COLORS and RAID_CLASS_COLORS[select(2, UnitClass("player"))]
        if cc then return cc.r, cc.g, cc.b end
    end
    local r = db.borderColorR or 0
    local g = db.borderColorG or (207/255)
    local b = db.borderColorB or 1.0
    return r, g, b
end

local addonName, ns = ...
local L = ns.L
local W = ns.Widgets

local RangeLib = LibStub("LibRangeCheck-3.0", true)

local inCombat = false

local FRAME_BACKDROP = {
    bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 16, edgeSize = 16,
    insets = { left = 4, right = 4, top = 4, bottom = 4 },
}

local function HasAttackableTarget()
    if not UnitExists("target") then return false end
    if not UnitCanAttack("player", "target") then return false end
    if UnitIsDeadOrGhost("target") then return false end
    return true
end

local MAX_RANGE_BRACKET = 40

local function GetColorForRange(minRange)
    local db = BrightQoL.rangeCheck
    if not db or not db.rangeColors then
        return db and db.rangeColorR or 0.01, db and db.rangeColorG or 0.56, db and db.rangeColorB or 0.91
    end
    local bracket = math.min(MAX_RANGE_BRACKET, math.floor((minRange or 0) / 5) * 5)
    local color = db.rangeColors[bracket] or db.rangeColors[0]
    if color then return color.r, color.g, color.b end
    return 0.01, 0.56, 0.91
end

---------------------------------------------------------------------------
-- Range to Target Frame
---------------------------------------------------------------------------
local rangeFrame = CreateFrame("Frame", "BrightQoL_RangeToTarget", UIParent, "BackdropTemplate")
rangeFrame:SetSize(200, 40)
rangeFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
rangeFrame:Hide()

local rangeLabel = rangeFrame:CreateFontString(nil, "OVERLAY")
rangeLabel:SetFont("Interface\\AddOns\\BrightQoL\\Assets\\Fonts\\Bright.ttf", 24, "OUTLINE")
rangeLabel:SetPoint("CENTER")

local rangeResizeHandle

function rangeFrame:UpdateDisplay()
    local db = BrightQoL.rangeCheck
    if not db then return end

    if not db.enabled or not db.rangeEnabled then
        rangeFrame:SetBackdrop(nil)
        if rangeResizeHandle then rangeResizeHandle:Hide() end
        rangeFrame:Hide()
        return
    end

    if db.rangeUnlock then
        rangeFrame:SetBackdrop(FRAME_BACKDROP)
        rangeFrame:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
        do local _r,_g,_b = GetBorderColor(BrightQoL.rangeCheck); rangeFrame:SetBackdropBorderColor(_r,_g,_b, 1) end
        rangeFrame:EnableMouse(true)
        if rangeResizeHandle then rangeResizeHandle:Show() end
        -- Show preview text in unlock mode
        if db.displayMode == "melee" then
            rangeLabel:SetText(db.meleeText or "- OUT OF RANGE -")
            rangeLabel:SetTextColor(db.meleeColorR or 1, db.meleeColorG or 0, db.meleeColorB or 0)
        end
        rangeFrame:Show()
    else
        rangeFrame:SetBackdrop(nil)
        rangeFrame:EnableMouse(false)
        if rangeResizeHandle then rangeResizeHandle:Hide() end
    end

    if not rangeFrame.initialized then
        rangeFrame:ClearAllPoints()
        local point = db.rangePoint or "CENTER"
        rangeFrame:SetPoint(point, UIParent, point, db.rangeX or 0, db.rangeY or 0)
        rangeFrame:SetSize(db.rangeWidth or 200, db.rangeHeight or 40)
        rangeFrame.initialized = true
    end

    local fontPath = db.rangeFont or "Interface\\AddOns\\BrightQoL\\Assets\\Fonts\\Bright.ttf"
    local fontSize = math.max(10, math.min(72, math.floor((rangeFrame.isResizing and rangeFrame:GetHeight() or (db.rangeHeight or 40)) * 0.55)))
    if not rangeLabel:SetFont(fontPath, fontSize, "OUTLINE") then
        rangeLabel:SetFont("Interface\\AddOns\\BrightQoL\\Assets\\Fonts\\Bright.ttf", fontSize, "OUTLINE")
    end
end

---------------------------------------------------------------------------
-- Melee sound system (mirrors Crosshair)
---------------------------------------------------------------------------
local meleeSoundTicker   = nil
local lastMeleeSoundTime = 0
local MELEE_SOUND_COOLDOWN = 0.9

local function StopMeleeSound()
    if meleeSoundTicker then
        meleeSoundTicker:Cancel()
        meleeSoundTicker = nil
    end
end

local function PlayMeleeSoundOnce(soundID)
    local now = GetTime()
    if now - lastMeleeSoundTime < MELEE_SOUND_COOLDOWN then return end
    lastMeleeSoundTime = now
    local id = type(soundID) == "table" and soundID.id or soundID
    if id and type(id) == "number" then PlaySound(id, "Master") end
end

local function StartMeleeSound(db)
    StopMeleeSound()
    local interval = db.meleeSoundInterval or 3
    local soundID  = db.meleeSoundID or 8959
    PlayMeleeSoundOnce(soundID)
    if interval > 0 then
        meleeSoundTicker = C_Timer.NewTicker(interval, function()
            PlayMeleeSoundOnce(soundID)
        end)
    end
end

---------------------------------------------------------------------------
-- Tick
---------------------------------------------------------------------------
local TICK_RATE = 0.1
local tickAcc   = 0
local lastInRange = nil
local isOutOfMelee = false

local function TickRangeCheck()
    local db = BrightQoL.rangeCheck
    if not db or not db.enabled then rangeFrame:Hide(); return end

    local hasTarget = HasAttackableTarget()

    if db.rangeEnabled and hasTarget then
        if db.rangeCombatOnly and not inCombat and not db.rangeUnlock then
            rangeFrame:Hide()
            return
        end

        if db.displayMode == "melee" then
            -- Use ns.MeleeRangeInfo from CrosshairDisplay (shared namespace)
            local info = ns.MeleeRangeInfo
            local spellID = info and info.GetCurrentSpell and info.GetCurrentSpell()

            -- Allow override from rangeCheck db
            if db.meleeSpellOverrides then
                local key = info and info.GetSpellKey and info.GetSpellKey()
                if key and db.meleeSpellOverrides[key] then
                    spellID = db.meleeSpellOverrides[key]
                end
            end

            if not spellID then
                if not db.rangeUnlock then rangeFrame:Hide() end
                return
            end

            local inMelee = C_Spell.IsSpellInRange(spellID, "target")
            if inMelee == nil then return end

            local wasOut = isOutOfMelee
            isOutOfMelee = not inMelee

            -- Sound on transition from in-range to out-of-range
            if isOutOfMelee and not wasOut and db.meleeSoundEnabled then
                StartMeleeSound(db)
            elseif not isOutOfMelee and wasOut then
                StopMeleeSound()
            end
            lastInRange = inMelee

            if isOutOfMelee then
                rangeLabel:SetText(db.meleeText or "- OUT OF RANGE -")
                rangeLabel:SetTextColor(db.meleeColorR or 1, db.meleeColorG or 0, db.meleeColorB or 0)
                if not db.rangeUnlock then rangeFrame:Show() end
            else
                if not db.rangeUnlock then rangeFrame:Hide() end
            end
        else
            -- Default: distance text mode
            if not RangeLib then rangeFrame:Hide(); return end
            local minRange, maxRange = RangeLib:GetRange("target")
            if minRange and maxRange then
                rangeLabel:SetText(minRange .. "-" .. maxRange .. " yd")
            elseif maxRange then
                rangeLabel:SetText("0-" .. maxRange .. " yd")
            elseif minRange then
                rangeLabel:SetText(minRange .. "+ yd")
            else
                rangeLabel:SetText("-- yd")
            end
            rangeLabel:SetTextColor(GetColorForRange(minRange))
            if not db.rangeUnlock then rangeFrame:Show() end
        end
    else
        isOutOfMelee = false
        StopMeleeSound()
        lastInRange = nil
        if not db.rangeUnlock then rangeFrame:Hide() end
    end
end

local tickFrame = CreateFrame("Frame")
tickFrame:SetScript("OnUpdate", ns.PerfMonitor:Wrap("Range Check", function(self, elapsed)
    tickAcc = tickAcc + elapsed
    if tickAcc < TICK_RATE then return end
    tickAcc = 0
    TickRangeCheck()
end))

---------------------------------------------------------------------------
-- Events
---------------------------------------------------------------------------
local loader = CreateFrame("Frame")
loader:RegisterEvent("PLAYER_LOGIN")
loader:RegisterEvent("PLAYER_REGEN_DISABLED")
loader:RegisterEvent("PLAYER_REGEN_ENABLED")
loader:RegisterEvent("PLAYER_TARGET_CHANGED")

loader:SetScript("OnEvent", function(self, event)
    local db = BrightQoL.rangeCheck
    if not db then return end

    if event == "PLAYER_LOGIN" then
        inCombat = UnitAffectingCombat("player")

        if not RangeLib then
            RangeLib = LibStub("LibRangeCheck-3.0", true)
        end

        db.rangeWidth  = db.rangeWidth  or 200
        db.rangeHeight = db.rangeHeight or 40
        db.rangePoint  = db.rangePoint  or "CENTER"
        db.rangeX      = db.rangeX      or 0
        db.rangeY      = db.rangeY      or 0

        W.MakeDraggable(rangeFrame, {
            db = db,
            unlockKey = "rangeUnlock",
            pointKey = "rangePoint", xKey = "rangeX", yKey = "rangeY",
        })
        rangeResizeHandle = W.CreateResizeHandle(rangeFrame, {
            db = db,
            unlockKey = "rangeUnlock",
            widthKey = "rangeWidth", heightKey = "rangeHeight",
            minW = 75, minH = 10,
            onResize = function() rangeFrame:UpdateDisplay() end,
        })

        rangeFrame.initialized = false
        C_Timer.After(0, function()
            rangeFrame:UpdateDisplay()
        end)

        ns.SettingsIO:RegisterRefresh("rangeCheck", function()
            rangeFrame.initialized = false
            rangeFrame:UpdateDisplay()
        end)
        return
    end

    if event == "PLAYER_REGEN_DISABLED" then
        inCombat = true
    elseif event == "PLAYER_REGEN_ENABLED" then
        inCombat = false
    end

    tickAcc = TICK_RATE
end)

---------------------------------------------------------------------------
-- Namespace exports
---------------------------------------------------------------------------
ns.RangeCheckRangeFrame = rangeFrame
