local MAJOR, MINOR = "LibMatic-OverlayGuard-1.0", 2
assert(LibStub, MAJOR .. " requires LibStub")
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end

lib.maxHold = lib.maxHold or 10
lib.watchTimeout = lib.watchTimeout or 20

lib._blockers = lib._blockers or {}
lib._queue = lib._queue or {}

lib._clock = lib._clock or function()
    if _G.GetTime then return _G.GetTime() end
    return 0
end

function lib:SetClock(fn)
    if type(fn) == "function" then self._clock = fn end
end

local function now(self)
    return self._clock()
end

function lib:_activeCount()
    local n = 0
    for _ in pairs(self._blockers) do n = n + 1 end
    return n
end

function lib:_expireStaleBlockers()
    local t = now(self)
    for key, info in pairs(self._blockers) do
        if info.timeout and (t - info.openedAt) >= info.timeout then
            self._blockers[key] = nil
        end
    end
end

function lib:IsBlocked()
    if not self._armed and _G.CreateFrame then self:_armDefaultWatches() end
    self:_expireStaleBlockers()
    return self:_activeCount() > 0
end

function lib:Block(key, opts)
    if key == nil then return end
    opts = opts or {}
    if self._blockers[key] then return end
    self._blockers[key] = { openedAt = now(self), timeout = opts.timeout }
end

function lib:_flush()
    local t = now(self)
    local q = self._queue
    self._queue = {}
    for i = 1, #q do
        local item = q[i]
        if (t - item.at) <= self.maxHold then
            item.fn()
        end
    end
end

function lib:Unblock(key)
    if key == nil or not self._blockers[key] then return end
    self._blockers[key] = nil
    self:_expireStaleBlockers()
    if self:_activeCount() == 0 then
        self:_flush()
    end
end

function lib:Poll()
    self:_expireStaleBlockers()
    if self:_activeCount() == 0 and #self._queue > 0 then
        self:_flush()
    end
end

function lib:RunWhenClear(fn)
    if type(fn) ~= "function" then return end
    if not self:IsBlocked() then
        fn()
        return
    end
    self._queue[#self._queue + 1] = { fn = fn, at = now(self) }
end

local function hookShowHide(self, frame, key)
    if not frame or not frame.HookScript then return end
    if frame.IsShown and frame:IsShown() then
        self:Block(key, { timeout = self.watchTimeout })
    end
    frame:HookScript("OnShow", function() self:Block(key, { timeout = self.watchTimeout }) end)
    frame:HookScript("OnHide", function() self:Unblock(key) end)
end

function lib:RegisterWatch(spec)
    if type(spec) ~= "table" then return end
    if spec.frame and spec.key then
        hookShowHide(self, spec.frame, spec.key)
    end
end

function lib:_armDefaultWatches()
    if self._armed then return end
    if not _G.CreateFrame then return end
    self._armed = true

    local watcher = _G.CreateFrame("Frame")
    watcher:RegisterEvent("CINEMATIC_START")
    watcher:RegisterEvent("CINEMATIC_STOP")
    watcher:RegisterEvent("PLAY_MOVIE")
    watcher:RegisterEvent("STOP_MOVIE")
    watcher:SetScript("OnEvent", function(_, event)
        if event == "CINEMATIC_START" then
            self:Block("cinematic")
        elseif event == "CINEMATIC_STOP" then
            self:Unblock("cinematic")
        elseif event == "PLAY_MOVIE" then
            self:Block("movie")
        elseif event == "STOP_MOVIE" then
            self:Unblock("movie")
        end
    end)
    self._watcher = watcher

    hookShowHide(self, _G.EventToastManagerFrame, "eventtoast")
    hookShowHide(self, _G.TalkingHeadFrame, "talkinghead")
    if _G.C_MajorFactions then
        hookShowHide(self, _G.MajorFactionRenownFrame, "renown")
    end

    if _G.C_Timer and _G.C_Timer.NewTicker then
        self._ticker = _G.C_Timer.NewTicker(0.5, function() self:Poll() end)
    end
end

local SIM_STYLES = {
    levelup = { dim = 0.45, label = "LEVEL UP", banner = "gold" },
    renown = { dim = 0.55, label = "RENOWN", banner = "renown", sub = "Level 12" },
    cinematic = { dim = 1.0, letterbox = true },
}

local function clampSimSeconds(s)
    s = tonumber(s) or 4
    if s < 1 then return 1 end
    if s > 20 then return 20 end
    return s
end

local function ensureSimFrame(self)
    if self._simFrame then return self._simFrame end
    if not _G.CreateFrame then return nil end
    local f = _G.CreateFrame("Frame", nil, _G.UIParent)
    f:SetAllPoints(_G.UIParent)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetToplevel(true)
    f:Hide()

    local dim = f:CreateTexture(nil, "BACKGROUND")
    dim:SetAllPoints(f)
    dim:SetTexture("Interface\\Buttons\\WHITE8X8")
    f.simDim = dim

    local topBar = f:CreateTexture(nil, "ARTWORK")
    topBar:SetTexture("Interface\\Buttons\\WHITE8X8")
    topBar:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -110)
    topBar:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -110)
    topBar:SetHeight(3)
    f.simTopBar = topBar

    local bottomBar = f:CreateTexture(nil, "ARTWORK")
    bottomBar:SetTexture("Interface\\Buttons\\WHITE8X8")
    bottomBar:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 110)
    bottomBar:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 110)
    bottomBar:SetHeight(3)
    f.simBottomBar = bottomBar

    local strip = f:CreateTexture(nil, "ARTWORK")
    strip:SetTexture("Interface\\Buttons\\WHITE8X8")
    strip:SetPoint("CENTER", f, "CENTER", 0, 160)
    strip:SetSize(440, 60)
    f.simStrip = strip

    local label = f:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
    label:SetPoint("CENTER", strip, "CENTER", 0, 0)
    f.simLabel = label

    local sub = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    sub:SetPoint("TOP", label, "BOTTOM", 0, -8)
    f.simSub = sub

    self._simFrame = f
    return f
end

local function styleSimFrame(f, key)
    local cfg = SIM_STYLES[key] or SIM_STYLES.levelup
    f.simDim:SetVertexColor(0, 0, 0, cfg.dim)
    if cfg.letterbox then
        f.simTopBar:SetVertexColor(0.35, 0.35, 0.35, 0.85)
        f.simBottomBar:SetVertexColor(0.35, 0.35, 0.35, 0.85)
        f.simTopBar:Show()
        f.simBottomBar:Show()
    else
        f.simTopBar:Hide()
        f.simBottomBar:Hide()
    end
    if cfg.label then
        if cfg.banner == "gold" then
            f.simStrip:SetVertexColor(0.80, 0.65, 0.10, 0.85)
            f.simLabel:SetTextColor(1, 0.82, 0)
        else
            f.simStrip:SetVertexColor(0.10, 0.20, 0.35, 0.85)
            f.simLabel:SetTextColor(0.95, 0.85, 0.55)
        end
        f.simStrip:Show()
        f.simLabel:SetText(cfg.label)
        f.simLabel:Show()
        if cfg.sub then
            f.simSub:SetText(cfg.sub)
            f.simSub:Show()
        else
            f.simSub:Hide()
        end
    else
        f.simStrip:Hide()
        f.simLabel:Hide()
        f.simSub:Hide()
    end
end

function lib:SimulateOverlay(style, seconds)
    local key = "levelup"
    if type(style) == "string" then
        local s = style:lower()
        if SIM_STYLES[s] then key = s end
    end
    seconds = clampSimSeconds(seconds)

    local f = ensureSimFrame(self)
    if f then
        styleSimFrame(f, key)
        f:Show()
    end

    self:Block("simulate", { timeout = seconds + 5 })

    local function finish()
        if self._simFrame then self._simFrame:Hide() end
        self:Unblock("simulate")
    end
    if _G.C_Timer and _G.C_Timer.After then
        _G.C_Timer.After(seconds, finish)
    else
        finish()
    end
end
