local addonName, L = ...

L.overlay = {
    frame = nil,
    texture = nil,
    shown = false,
    previewActive = false,
    previewProfileKey = nil,
    editModeEnabled = false,
    editModeProfileKey = nil,
    sequenceTicker = nil,
    sequenceState = "idle",
    activeConfig = nil,
    activeProfileKey = nil,
    lastPose = nil,
    imageAnimationElapsed = 0,
    imageAnimationFrame = nil,
    dragStartCursorX = nil,
    dragStartCursorY = nil,
    dragStartOffsetX = nil,
    dragStartOffsetY = nil,
    dragging = false,
}

local overlay = L.overlay
local IMAGE_OVERLAY_SCREEN_MARGIN = 0

local function ShouldRefreshOverlayUI(self, profileKey)
    return self.IsTabbedSettingsActive and self:IsTabbedSettingsActive()
end

local function RoundNearest(value)
    if value >= 0 then
        return math.floor(value + 0.5)
    end

    return math.ceil(value - 0.5)
end

local function GetCursorPositionInUIParent()
    local cursorX, cursorY = GetCursorPosition()
    local parentScale = UIParent:GetEffectiveScale() or 1
    return cursorX / parentScale, cursorY / parentScale
end

local function SaveOverlayPosition(profileKey, offsetX, offsetY)
    if offsetX and offsetY then
        L:SetProfileDBValue(profileKey, "imageX", RoundNearest(offsetX))
        L:SetProfileDBValue(profileKey, "imageY", RoundNearest(offsetY))
    end
end

local function ClampOverlayPosition(x, y)
    local frame = overlay.frame
    local parent = UIParent
    if not frame or not parent then
        return x or 0, y or 0
    end

    local parentWidth = parent:GetWidth() or 0
    local parentHeight = parent:GetHeight() or 0
    local frameScale = frame:GetScale() or 1
    local frameWidth = (frame:GetWidth() or 0) * frameScale
    local frameHeight = (frame:GetHeight() or 0) * frameScale

    local halfAvailableX = math.max(0, (parentWidth * 0.5) - (frameWidth * 0.5) - IMAGE_OVERLAY_SCREEN_MARGIN)
    local halfAvailableY = math.max(0, (parentHeight * 0.5) - (frameHeight * 0.5) - IMAGE_OVERLAY_SCREEN_MARGIN)

    return L:Clamp(x or 0, -halfAvailableX, halfAvailableX),
        L:Clamp(y or 0, -halfAvailableY, halfAvailableY)
end

local function StopOverlayDrag(frame, persistPosition)
    if overlay.dragging ~= true then
        return
    end

    overlay.dragging = false
    overlay.dragStartCursorX = nil
    overlay.dragStartCursorY = nil

    local profileKey = overlay.editModeProfileKey or "bloodlust"
    local _, _, _, offsetX, offsetY = frame:GetPoint(1)
    offsetX, offsetY = ClampOverlayPosition(offsetX, offsetY)
    if persistPosition then
        SaveOverlayPosition(profileKey, offsetX, offsetY)
    end

    overlay.dragStartOffsetX = nil
    overlay.dragStartOffsetY = nil

    frame:ClearAllPoints()
    frame:SetPoint(
        "CENTER",
        UIParent,
        "CENTER",
        L:GetProfileDBValue(profileKey, "imageX"),
        L:GetProfileDBValue(profileKey, "imageY")
    )

    if persistPosition and L.NotifySettingsChanged then
        L:NotifySettingsChanged("overlay_position", profileKey)
    end
end


function L:EnsureOverlayFrame()
    if overlay.frame then
        return overlay.frame, overlay.texture
    end

    local frame = CreateFrame("Frame", "LustomizerImageOverlayFrame", UIParent)
    frame:SetMovable(true)
    frame:EnableMouse(false)
    frame:RegisterForDrag("LeftButton")
    frame:SetClampedToScreen(false)
    frame:SetFrameStrata("DIALOG")
    frame:Hide()

    local texture = frame:CreateTexture(nil, "ARTWORK")
    texture:SetPoint("CENTER", frame, "CENTER", 0, 0)
    texture:SetBlendMode("BLEND")

    frame:SetScript("OnDragStart", function(self)
        if overlay.editModeEnabled then
            local cursorX, cursorY = GetCursorPositionInUIParent()
            local _, _, _, offsetX, offsetY = self:GetPoint(1)
            overlay.dragging = true
            overlay.dragStartCursorX = cursorX
            overlay.dragStartCursorY = cursorY
            overlay.dragStartOffsetX = offsetX or L:GetProfileDBValue(overlay.editModeProfileKey or "bloodlust", "imageX")
            overlay.dragStartOffsetY = offsetY or L:GetProfileDBValue(overlay.editModeProfileKey or "bloodlust", "imageY")
        end
    end)

    frame:SetScript("OnDragStop", function(self)
        StopOverlayDrag(self, true)
    end)

    frame:SetScript("OnHide", function(self)
        StopOverlayDrag(self, false)
    end)

    frame:SetScript("OnUpdate", function(self)
        if overlay.dragging ~= true then
            return
        end

        local cursorX, cursorY = GetCursorPositionInUIParent()
        local deltaX = cursorX - (overlay.dragStartCursorX or cursorX)
        local deltaY = cursorY - (overlay.dragStartCursorY or cursorY)
        local dragScale = self:GetScale() or 1
        local offsetX = (overlay.dragStartOffsetX or 0) + (deltaX / dragScale)
        local offsetY = (overlay.dragStartOffsetY or 0) + (deltaY / dragScale)
        offsetX, offsetY = ClampOverlayPosition(offsetX, offsetY)

        self:ClearAllPoints()
        self:SetPoint("CENTER", UIParent, "CENTER", offsetX, offsetY)
    end)

    overlay.frame = frame
    overlay.texture = texture
    return frame, texture
end

local function GetImageFrameScale(image)
    local frameScaleW = 1
    local frameScaleH = 1

    if image.fileWidth and image.frameWidth and image.fileWidth > 0 and image.frameWidth > 0 then
        frameScaleW = (image.frameWidth * image.columns) / image.fileWidth
    end
    if image.fileHeight and image.frameHeight and image.fileHeight > 0 and image.frameHeight > 0 then
        frameScaleH = (image.frameHeight * image.rows) / image.fileHeight
    end

    return frameScaleW, frameScaleH
end

function L:ApplyOverlayImageFrame(image, frameIndex)
    self:EnsureOverlayFrame()

    overlay.texture:SetTexture(image and image.file or nil)

    if not self:IsAnimatedImageEntry(image) then
        overlay.texture:SetTexCoord(0, 1, 0, 1)
        overlay.imageAnimationFrame = nil
        return
    end

    local clampedFrame = math.max(1, math.min(frameIndex or 1, image.frames))
    if overlay.imageAnimationFrame == clampedFrame then
        return
    end

    -- Animated images are packed as sprite sheets. Compute texcoords manually so
    -- wobble/rotation can stay texture-only without moving the parent frame.
    local zeroBasedFrame = clampedFrame - 1
    local row = math.floor(zeroBasedFrame / image.columns)
    local column = zeroBasedFrame % image.columns
    local frameScaleW, frameScaleH = GetImageFrameScale(image)
    local deltaX = frameScaleW / image.columns
    local deltaY = frameScaleH / image.rows
    local left = deltaX * column
    local right = left + deltaX
    local top = deltaY * row
    local bottom = top + deltaY

    overlay.texture:SetTexCoord(left, right, top, bottom)
    overlay.imageAnimationFrame = clampedFrame
end

function L:UpdateOverlayImageAnimation(image, elapsed)
    self:EnsureOverlayFrame()

    if not self:IsAnimatedImageEntry(image) then
        overlay.imageAnimationElapsed = 0
        self:ApplyOverlayImageFrame(image, 1)
        return
    end

    local frameRate = math.max(0.01, tonumber(image.frameRate) or 10)
    local totalFrames = math.max(1, image.frames)
    local frameIndex = (math.floor(math.max(0, elapsed) * frameRate) % totalFrames) + 1

    overlay.imageAnimationElapsed = math.max(0, elapsed)
    self:ApplyOverlayImageFrame(image, frameIndex)
end

function L:SetOverlayTextureScale(multiplier)
    self:EnsureOverlayFrame()
    overlay.texture:ClearAllPoints()
    overlay.texture:SetPoint("CENTER", overlay.frame, "CENTER", 0, 0)
    overlay.texture:SetScale(multiplier or 1)
end

function L:SetOverlayTextureRotation(radians)
    self:EnsureOverlayFrame()
    overlay.texture:SetRotation(radians or 0)
end

function L:ApplyOverlayPose(pose)
    self:EnsureOverlayFrame()
    local x = pose.x or 0
    local y = pose.y or 0
    local frameScale = pose.frameScale or 1
    local alpha = pose.alpha or 1
    local textureScale = pose.textureScale or 1
    local rotation = pose.rotation or 0
    local lastPose = overlay.lastPose

    -- Only touch frame properties that actually changed; this keeps ticker-driven
    -- playback cheap and avoids unnecessary point resets while animating.
    if (not lastPose) or lastPose.x ~= x or lastPose.y ~= y then
        overlay.frame:ClearAllPoints()
        overlay.frame:SetPoint("CENTER", UIParent, "CENTER", x, y)
    end
    if (not lastPose) or lastPose.frameScale ~= frameScale then
        overlay.frame:SetScale(frameScale)
    end
    if (not lastPose) or lastPose.alpha ~= alpha then
        overlay.frame:SetAlpha(alpha)
    end
    if (not lastPose) or lastPose.textureScale ~= textureScale then
        self:SetOverlayTextureScale(textureScale)
    end
    if (not lastPose) or lastPose.rotation ~= rotation then
        self:SetOverlayTextureRotation(rotation)
    end

    overlay.lastPose = {
        x = x,
        y = y,
        frameScale = frameScale,
        alpha = alpha,
        textureScale = textureScale,
        rotation = rotation,
    }
end

function L:ApplyOverlayVisuals(profileKey)
    profileKey = profileKey or overlay.activeProfileKey or overlay.editModeProfileKey or "bloodlust"

    self:EnsureOverlayFrame()

    local image = self:EnsureValidSelectedImage(profileKey)
    local width = self:GetProfileDBValue(profileKey, "imageWidth")
    local height = self:GetProfileDBValue(profileKey, "imageHeight")
    local scale = self:GetProfileDBValue(profileKey, "imageScale")

    overlay.texture:SetSize(width, height)
    overlay.texture:SetDesaturated(self:GetProfileDBValue(profileKey, "imageGrayscale") == true)
    self:UpdateOverlayImageAnimation(image, 0)

    overlay.frame:SetSize(width, height)
    overlay.frame:SetScale(scale)
    overlay.frame:SetClampedToScreen(false)

    self:ApplyOverlayPose({
        x = self:GetProfileDBValue(profileKey, "imageX"),
        y = self:GetProfileDBValue(profileKey, "imageY"),
        alpha = 1,
        frameScale = scale,
        textureScale = 1,
        rotation = 0,
    })
end

function L:StopOverlaySequence(hideCompletely, suppressUIRefresh)
    local activeProfileKey = overlay.activeProfileKey

    if overlay.sequenceTicker then
        overlay.sequenceTicker:Cancel()
        overlay.sequenceTicker = nil
    end

    overlay.sequenceState = "idle"
    overlay.activeConfig = nil
    overlay.activeProfileKey = nil
    overlay.previewActive = false
    overlay.previewProfileKey = nil
    overlay.lastPose = nil
    overlay.imageAnimationElapsed = 0
    overlay.imageAnimationFrame = nil

    if hideCompletely and overlay.frame and not overlay.editModeEnabled then
        overlay.frame:Hide()
        overlay.shown = false
    end

    if (not suppressUIRefresh) and self.NotifySettingsChanged and ShouldRefreshOverlayUI(self, activeProfileKey or "bloodlust") then
        self:NotifySettingsChanged("overlay_sequence_stopped", activeProfileKey)
    end

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

function L:GetDirectionalOffset(direction, width, height, scale)
    local distX = width * scale
    local distY = height * scale

    if direction == "top" then
        return 0, distY
    elseif direction == "left" then
        return -distX, 0
    elseif direction == "right" then
        return distX, 0
    end

    return 0, -distY
end

function L:BuildOverlayConfig(profileKey)
    profileKey = profileKey or "bloodlust"

    local image = self:EnsureValidSelectedImage(profileKey)
    local width = self:GetProfileDBValue(profileKey, "imageWidth")
    local height = self:GetProfileDBValue(profileKey, "imageHeight")
    local scale = self:GetProfileDBValue(profileKey, "imageScale")
    local direction = self:GetProfileDBValue(profileKey, "imageDirection")
    local finalX = self:GetProfileDBValue(profileKey, "imageX")
    local finalY = self:GetProfileDBValue(profileKey, "imageY")
    local enterOffsetX, enterOffsetY = self:GetDirectionalOffset(direction, width, height, scale)
    local exitOffsetX, exitOffsetY = self:GetDirectionalOffset(direction, width, height, scale)

    -- Resolve the full playback plan up front so enter/loop/exit phases can stay
    -- separate and deterministic while the ticker only evaluates poses over time.
    local config = {
        profileKey = profileKey,
        image = image,
        imageFile = image and image.file or nil,
        grayscale = self:GetProfileDBValue(profileKey, "imageGrayscale") == true,
        width = width,
        height = height,
        scale = scale,
        finalX = finalX,
        finalY = finalY,
        enterX = finalX + enterOffsetX,
        enterY = finalY + enterOffsetY,
        exitX = finalX + exitOffsetX,
        exitY = finalY + exitOffsetY,
        enterDuration = math.max(0.01, tonumber(self:GetProfileDBValue(profileKey, "imageEnterDuration")) or self.DEFAULT_IMAGE_ENTER_DURATION),
        exitDuration = math.max(0.01, tonumber(self:GetProfileDBValue(profileKey, "imageExitDuration")) or self.DEFAULT_IMAGE_EXIT_DURATION),
        enterEasing = self:GetProfileDBValue(profileKey, "imageEnterEasing"),
        exitEasing = self:GetProfileDBValue(profileKey, "imageExitEasing"),
        activeDuration = 0,
        startMoveEnabled = self:GetProfileDBValue(profileKey, "startMoveEnabled") == true,
        startFadeEnabled = self:GetProfileDBValue(profileKey, "startFadeEnabled") == true,
        loopWobbleEnabled = self:GetProfileDBValue(profileKey, "loopWobbleEnabled") == true,
        loopSpinEnabled = self:GetProfileDBValue(profileKey, "loopSpinEnabled") == true,
        wobbleScale = self:Clamp(tonumber(self:GetProfileDBValue(profileKey, "imageWobbleScale")) or self.DEFAULT_IMAGE_WOBBLE_SCALE, 0, 0.5),
        wobbleStepDuration = math.max(0.01, tonumber(self:GetProfileDBValue(profileKey, "imageWobbleStepDuration")) or self.DEFAULT_IMAGE_WOBBLE_STEP_DURATION),
        spinDirection = self:GetProfileDBValue(profileKey, "loopSpinDirection"),
        spinDuration = math.max(0.01, tonumber(self:GetProfileDBValue(profileKey, "loopSpinDuration")) or self.DEFAULT_LOOP_SPIN_DURATION),
        endMoveEnabled = self:GetProfileDBValue(profileKey, "endMoveEnabled") == true,
        endFadeEnabled = self:GetProfileDBValue(profileKey, "endFadeEnabled") == true,
    }

    config.activeDuration = math.max(0, self:GetProfileDuration(profileKey) - config.enterDuration - config.exitDuration)
    return config
end

function L:GetWobbleTextureScaleAtTime(amplitude, stepDuration, elapsed)
    local maxScale = 1 + amplitude
    local minScale = 1 - amplitude
    local cycle = stepDuration * 2
    if cycle <= 0 then
        return 1
    end

    local phase = elapsed % cycle
    if phase < stepDuration then
        local t = self:EaseInOutSine(phase / stepDuration)
        return self:Lerp(maxScale, minScale, t)
    end

    local t = self:EaseInOutSine((phase - stepDuration) / stepDuration)
    return self:Lerp(minScale, maxScale, t)
end

function L:GetSpinRotationAtTime(duration, elapsed, direction)
    duration = math.max(0.01, duration or self.DEFAULT_LOOP_SPIN_DURATION)
    local turns = (elapsed % duration) / duration
    local angle = turns * math.pi * 2
    if direction == "cw" then
        angle = -angle
    end
    return angle
end

function L:EvaluateOverlayEnterPose(config, elapsed)
    local progress = self:ApplyEasing(config.enterEasing, elapsed / config.enterDuration)
    local pose = {
        x = config.finalX,
        y = config.finalY,
        alpha = 1,
        frameScale = config.scale,
        textureScale = 1,
        rotation = 0,
    }

    if config.startMoveEnabled then
        pose.x = self:Lerp(config.enterX, config.finalX, progress)
        pose.y = self:Lerp(config.enterY, config.finalY, progress)
    end

    if config.startFadeEnabled then
        pose.alpha = self:Lerp(0, 1, progress)
    end

    return pose
end

function L:EvaluateOverlayLoopPose(config, elapsed)
    local textureScale = 1
    -- Wobble scales the texture only. The frame position remains fixed so the
    -- image never drifts around the screen during the active phase.
    if config.loopWobbleEnabled then
        textureScale = self:GetWobbleTextureScaleAtTime(config.wobbleScale, config.wobbleStepDuration, elapsed)
    end

    local rotation = 0
    if config.loopSpinEnabled then
        rotation = self:GetSpinRotationAtTime(config.spinDuration, elapsed, config.spinDirection)
    end

    return {
        x = config.finalX,
        y = config.finalY,
        alpha = 1,
        frameScale = config.scale,
        textureScale = textureScale,
        rotation = rotation,
    }
end

function L:EvaluateOverlayExitPose(config, elapsed)
    local progress = self:ApplyEasing(config.exitEasing, elapsed / config.exitDuration)
    local pose = {
        x = config.finalX,
        y = config.finalY,
        alpha = 1,
        frameScale = config.scale,
        textureScale = 1,
        rotation = 0,
    }

    if config.endMoveEnabled then
        pose.x = self:Lerp(config.finalX, config.exitX, progress)
        pose.y = self:Lerp(config.finalY, config.exitY, progress)
    end

    if config.endFadeEnabled then
        pose.alpha = self:Lerp(1, 0, progress)
    end

    return pose
end

function L:StartOverlayPlayback(kind, isPreview, profileKey)
    profileKey = profileKey or overlay.activeProfileKey or "bloodlust"
    local resumeAnimationElapsed = 0

    if kind ~= "exit" and self:GetProfileDBValue(profileKey, "useImageOverlay") ~= true then
        return
    end

    local previewActive = (kind == "exit" and overlay.previewActive == true) or isPreview == true
    if kind == "exit" and overlay.activeProfileKey == profileKey then
        resumeAnimationElapsed = overlay.imageAnimationElapsed or 0
    end

    self:EnsureOverlayFrame()
    self:StopOverlaySequence(false, true)
    overlay.previewActive = previewActive
    overlay.previewProfileKey = previewActive and profileKey or nil
    overlay.activeProfileKey = profileKey

    local config = self:BuildOverlayConfig(profileKey)
    overlay.activeConfig = config
    overlay.texture:SetSize(config.width, config.height)
    overlay.texture:SetDesaturated(config.grayscale)
    self:UpdateOverlayImageAnimation(config.image, resumeAnimationElapsed)
    overlay.frame:SetSize(config.width, config.height)
    overlay.frame:SetScale(config.scale)
    overlay.frame:SetClampedToScreen(false)

    overlay.shown = true
    overlay.frame:Show()

    -- Exit playback reuses the last image animation time so sprite sheets do not
    -- jump back to frame 1 just because the overlay is being hidden.
    if kind == "exit" then
        overlay.sequenceState = "exit"
        self:ApplyOverlayPose(self:EvaluateOverlayExitPose(config, 0))
    else
        overlay.sequenceState = "enter"
        self:ApplyOverlayPose(self:EvaluateOverlayEnterPose(config, 0))
    end

    local startTime = GetTime()
    overlay.sequenceTicker = C_Timer.NewTicker(0.016, function()
        if not overlay.frame then
            self:StopOverlaySequence(true)
            return
        end

        local elapsed = GetTime() - startTime
        self:UpdateOverlayImageAnimation(config.image, resumeAnimationElapsed + elapsed)
        if kind == "exit" then
            if elapsed < config.exitDuration then
                overlay.sequenceState = "exit"
                self:ApplyOverlayPose(self:EvaluateOverlayExitPose(config, elapsed))
                return
            end

            self:StopOverlaySequence(true)
            return
        end

        if elapsed < config.enterDuration then
            overlay.sequenceState = "enter"
            self:ApplyOverlayPose(self:EvaluateOverlayEnterPose(config, elapsed))
            return
        end

        local loopElapsed = elapsed - config.enterDuration
        if loopElapsed < config.activeDuration then
            overlay.sequenceState = "loop"
            self:ApplyOverlayPose(self:EvaluateOverlayLoopPose(config, loopElapsed))
            return
        end

        local exitElapsed = loopElapsed - config.activeDuration
        if exitElapsed < config.exitDuration then
            overlay.sequenceState = "exit"
            self:ApplyOverlayPose(self:EvaluateOverlayExitPose(config, exitElapsed))
            return
        end

        self:StopOverlaySequence(true)
    end)

    if self.NotifySettingsChanged and ShouldRefreshOverlayUI(self, profileKey) then
        self:NotifySettingsChanged("overlay_preview_shown", profileKey)
    end
end

function L:ShowOverlayPreview(profileKey)
    profileKey = profileKey or "bloodlust"
    self:SetEditMode(false, profileKey)
    self:StartOverlayPlayback("full", true, profileKey)
    if self.EnsurePreviewVisibilityMonitor then
        self:EnsurePreviewVisibilityMonitor()
    end
end

function L:HideOverlay(profileKey)
    if not overlay.shown or not overlay.frame then
        return
    end
    if profileKey and overlay.activeProfileKey and overlay.activeProfileKey ~= profileKey then
        return
    end
    self:StartOverlayPlayback("exit", false, profileKey or overlay.activeProfileKey or "bloodlust")
end

function L:StopOverlayPreview()
    if overlay.previewActive ~= true then
        return
    end
    if overlay.sequenceState == "exit" then
        return
    end

    if overlay.shown and overlay.frame then
        self:HideOverlay(overlay.previewProfileKey)
        return
    end

    self:StopOverlaySequence(true)
end

function L:IsOverlayPreviewActive(profileKey)
    if profileKey then
        return overlay.previewActive == true and overlay.previewProfileKey == profileKey
    end
    return overlay.previewActive == true
end

function L:SetEditMode(enabled, profileKey)
    profileKey = profileKey or overlay.editModeProfileKey or "bloodlust"
    if enabled == true and self.IsSlashAlertTestActive and self:IsSlashAlertTestActive() then
        if self.NotifySettingsChanged then
            self:NotifySettingsChanged("overlay_edit_blocked_by_test", profileKey)
        end
        return
    end
    overlay.editModeEnabled = enabled == true
    overlay.editModeProfileKey = overlay.editModeEnabled and profileKey or nil

    if overlay.editModeEnabled and self.IsTextEditModeEnabled and self:IsTextEditModeEnabled() then
        self:SetTextEditMode(false, nil)
    end

    self:EnsureOverlayFrame()
    self:ApplyOverlayVisuals(profileKey)

    overlay.frame:EnableMouse(overlay.editModeEnabled)
    overlay.frame:SetMovable(overlay.editModeEnabled)
    overlay.frame:SetClampedToScreen(false)

    if overlay.editModeEnabled then
        -- Edit mode always shows the frame in a neutral pose so drag operations are
        -- predictable and not competing with animated playback.
        self:StopOverlaySequence(false)
        overlay.shown = true
        overlay.frame:Show()
        if self.EnsurePreviewVisibilityMonitor then
            self:EnsurePreviewVisibilityMonitor()
        end
        self:UpdateOverlayImageAnimation(self:EnsureValidSelectedImage(profileKey), 0)
        self:ApplyOverlayPose({
            x = self:GetProfileDBValue(profileKey, "imageX"),
            y = self:GetProfileDBValue(profileKey, "imageY"),
            alpha = 1,
            frameScale = self:GetProfileDBValue(profileKey, "imageScale"),
            textureScale = 1,
            rotation = 0,
        })
    else
        if profileKey == "bloodlust" and self.trackedDebuffPresent then
            self:StartOverlayPlayback("full", false, profileKey)
        elseif overlay.activeProfileKey == profileKey and overlay.shown then
            self:StartOverlayPlayback("full", false, profileKey)
        elseif overlay.frame then
            overlay.frame:Hide()
            overlay.shown = false
        end
    end

    if self.NotifySettingsChanged and ShouldRefreshOverlayUI(self, profileKey) then
        self:NotifySettingsChanged("overlay_edit_mode", profileKey)
    end
end

function L:IsEditModeEnabled(profileKey)
    if profileKey then
        return overlay.editModeEnabled == true and overlay.editModeProfileKey == profileKey
    end
    return overlay.editModeEnabled == true
end

function L:GetOverlaySequenceState()
    return overlay.sequenceState
end

function L:IsOverlayShown(profileKey)
    if profileKey then
        return overlay.shown == true and overlay.activeProfileKey == profileKey
    end
    return overlay.shown == true
end
