-- ~/.hammerspoon/Monocle.lua
local Monocle = {}

-- CONFIGURATION
local blurRadius = 20        
local dimAlpha = 0.3         
local shakeTrigger = 250     -- Energy required (higher = harder shake)

local overlay = nil
local filter = nil
local mouseTimer = nil
local clickWatcher = nil
local energy = 0

function Monocle.show()
    local screen = hs.screen.mainScreen()
    local res = screen:fullFrame()
    local win = hs.window.focusedWindow()
    if not win then return end

    if not overlay then
        overlay = hs.canvas.new(res)
        overlay:level(101) 
        overlay:behavior({
            hs.canvas.windowBehaviors.canJoinAllSpaces, 
            hs.canvas.windowBehaviors.ignoresMouseEvents
        })
        
        -- Layer 1: Background Dimming
        overlay[1] = {
            type = "rectangle",
            action = "fill",
            fillColor = { black = 1, alpha = dimAlpha },
            frame = { x = 0, y = 0, w = "100%", h = "100%" },
        }

        -- APPLY THE BLUR FILTER TO THE WHOLE CANVAS
        overlay:setFilter({
            name = "CIGaussianBlur",
            inputRadius = blurRadius
        })
    end

    overlay:frame(res)
    overlay:show()
    win:raise()
end

function Monocle.hide()
    if overlay then overlay:hide() end
    energy = 0
end

function Monocle.toggle()
    if overlay and overlay:isShowing() then
        Monocle.hide()
    else
        Monocle.show()
    end
end

function Monocle.init()
    -- 1. HYPERKEY BINDING
    if hyper then
        hs.hotkey.bind(hyper, "f", function()
            Monocle.toggle()
        end)
    end

    -- 2. Focus Follower
    filter = hs.window.filter.new(nil)
    filter:subscribe(hs.window.filter.windowFocused, function()
        if overlay and overlay:isShowing() then
            Monocle.show()
        end
    end)

    -- 3. Click Outside to Disable
    clickWatcher = hs.eventtap.new({1}, function(event)
        if overlay and overlay:isShowing() then
            local clickPoint = event:location()
            local win = hs.window.focusedWindow()
            if win then
                local f = win:frame()
                local isInside = (clickPoint.x >= f.x and clickPoint.x <= (f.x + f.w) and 
                                 clickPoint.y >= f.y and clickPoint.y <= (f.y + f.h))
                if not isInside then
                    Monocle.hide()
                end
            end
        end
        return false 
    end):start()

    -- 4. Vector-Based Shake Detection
    local lastPos = hs.mouse.absolutePosition()
    local lastDx, lastDy = 0, 0
    
    mouseTimer = hs.timer.doEvery(0.02, function()
        local newPos = hs.mouse.absolutePosition()
        local dx = newPos.x - lastPos.x
        local dy = newPos.y - lastPos.y
        
        -- If current direction (dx) is opposite of last direction (lastDx)
        -- That indicates a shake reversal.
        local reversedX = (dx * lastDx < 0)
        local reversedY = (dy * lastDy < 0)
        
        if reversedX or reversedY then
            local speed = math.sqrt(dx*dx + dy*dy)
            if speed > 15 then 
                energy = energy + (speed * 2)
            end
        else
            -- Constant decay (15 units per cycle)
            energy = math.max(0, energy - 15)
        end

        if energy > shakeTrigger then
            Monocle.toggle()
            energy = 0 
        end
        
        lastDx, lastDy = dx, dy
        lastPos = newPos
    end)
end

return Monocle