local _, ns = ...

local player
local blackout
local activeMovie
local returnToLibrary
local finishing
local preloadTicker
local preloadMovieID
local preloadGeneration = 0
local cinematicNotified
local subtitleEventActive
local subtitleRestoreState
local narcissusAFKOriginal
local narcissusAFKSuppressed

local ERROR_MESSAGES = {
    [1] = "The client could not open the movie codec.",
    [2] = "The movie data is not installed yet.",
    [3] = "The movie file could not be read.",
    [4] = "The movie file could not be opened.",
}

-- Narcissus' AFK screen only recognizes Blizzard's global MovieFrame. Its AFK
-- transition calls CloseWindows(), which hides this addon's separate player.
-- Preserve the user's setting and suppress only that transition for the exact
-- lifetime of a library movie.
local function SuppressNarcissusAFKScreen()
    if narcissusAFKSuppressed then return end
    local database = _G.NarcissusDB
    if type(database) == "table" and database.AFKScreen then
        narcissusAFKOriginal = database.AFKScreen
        narcissusAFKSuppressed = true
        database.AFKScreen = false
    end
end

local function RestoreNarcissusAFKScreen()
    if not narcissusAFKSuppressed then return end
    local database = _G.NarcissusDB
    if type(database) == "table" then database.AFKScreen = narcissusAFKOriginal end
    narcissusAFKOriginal = nil
    narcissusAFKSuppressed = nil
end

local function NotifyFinished(userCanceled, didError)
    if not activeMovie or not cinematicNotified then return end
    if CinematicFinished and Enum and Enum.CinematicType then
        pcall(CinematicFinished, Enum.CinematicType.GameMovie, userCanceled and true or false, didError and true or false)
    end
end

local function CaptureSubtitleFrame()
    if C_AddOns and C_AddOns.LoadAddOn then
        pcall(C_AddOns.LoadAddOn, "Blizzard_Subtitles")
    end
    local frame = _G.SubtitlesFrame
    if frame and not subtitleRestoreState then
        subtitleRestoreState = {
            frame = frame,
            strata = frame:GetFrameStrata(),
            level = frame:GetFrameLevel(),
            showSubtitles = frame.showSubtitles,
        }
    end
    return frame
end

local function ActivateSubtitleFrame(frame, movieFrame, enabled)
    if not frame or not movieFrame then return end
    -- Blizzard's subtitle handler only raises frame *level*. Our custom movie
    -- uses FULLSCREEN_DIALOG, so the stock subtitle frame otherwise remains on
    -- a lower strata and is completely hidden behind the video.
    frame:SetFrameStrata(movieFrame:GetFrameStrata())
    frame:SetFrameLevel(movieFrame:GetFrameLevel() + 2)
    -- The stock callback reads the global movieSubtitle CVar. This addon's
    -- dedicated toggle intentionally controls only movies launched here.
    frame.showSubtitles = enabled and true or false
    if enabled then
        frame:Show()
    elseif frame.HideSubtitles then
        frame:HideSubtitles()
    end
end

local function RestoreSubtitleFrame()
    local state = subtitleRestoreState
    subtitleRestoreState = nil
    if not state or not state.frame then return end
    state.frame:SetFrameStrata(state.strata)
    state.frame:SetFrameLevel(state.level)
    state.frame.showSubtitles = state.showSubtitles
end

local function FinishPlayback(userCanceled, didError)
    if finishing then return end
    finishing = true
    if subtitleEventActive and EventRegistry then
        EventRegistry:TriggerEvent("Subtitles.OnMovieCinematicStop")
        subtitleEventActive = nil
    end
    RestoreSubtitleFrame()
    if player then
        player:SetScript("OnUpdate", nil)
        player:StopMovie()
        player:Hide()
    end
    if blackout then blackout:Hide() end
    NotifyFinished(userCanceled, didError)
    RestoreNarcissusAFKScreen()
    cinematicNotified = nil
    activeMovie = nil
    finishing = nil
    if returnToLibrary and ns.UI then ns.UI:Show() end
    returnToLibrary = nil
end

local function CancelPreload(movieID)
    preloadGeneration = preloadGeneration + 1
    if preloadTicker then
        preloadTicker:Cancel()
        preloadTicker = nil
    end
    movieID = movieID or preloadMovieID
    if movieID and CancelPreloadingMovie then pcall(CancelPreloadingMovie, movieID) end
    preloadMovieID = nil
end

local function CreatePlayer()
    if player then return player end

    blackout = CreateFrame("Frame", "MemoriesOfAzerothMovieBlackout", UIParent)
    blackout:SetAllPoints(UIParent)
    blackout:SetFrameStrata("FULLSCREEN_DIALOG")
    blackout:SetFrameLevel(0)
    blackout:EnableMouse(true)
    local black = blackout:CreateTexture(nil, "BACKGROUND")
    black:SetAllPoints()
    black:SetColorTexture(0, 0, 0, 1)
    blackout:Hide()

    player = CreateFrame("MovieFrame", "MemoriesOfAzerothMovieFrame", UIParent)
    player:SetAllPoints(UIParent)
    player:SetFrameStrata("FULLSCREEN_DIALOG")
    player:SetFrameLevel(blackout:GetFrameLevel() + 1)
    player:EnableKeyboard(true)
    if player.SetPropagateKeyboardInput then player:SetPropagateKeyboardInput(false) end
    player:Hide()
    player:SetScript("OnMovieFinished", function() FinishPlayback(false, false) end)
    player:SetScript("OnKeyUp", function(_, key)
        if issecretvalue and issecretvalue(key) then return end
        if key == "ESCAPE" or key == "SPACE" or key == "ENTER" then FinishPlayback(true, false) end
    end)
    player:SetScript("OnHide", function()
        if activeMovie and not finishing then FinishPlayback(true, false) end
    end)
    player:RegisterEvent("PLAYER_LOGOUT")
    player:SetScript("OnEvent", RestoreNarcissusAFKScreen)

    return player
end

function ns:StopMovie()
    CancelPreload()
    FinishPlayback(true, false)
end

function ns:CancelPendingMovie()
    CancelPreload()
end

local function StartPlayback(movie, quietMissingData)
    local self = ns
    if type(movie) ~= "table" or not self:IsSafeNumber(movie.id) or movie.id <= 0 then
        self:SetStatus("This movie entry is invalid.", "danger")
        return false
    end
    if InCombatLockdown and InCombatLockdown() then
        self:SetStatus("Movies cannot be started during combat.", "warning")
        return false
    end
    if activeMovie then
        self:SetStatus("A movie is already playing.", "warning")
        return false
    end

    local movieFrame = CreatePlayer()
    returnToLibrary = self.UI and self.UI.frame and self.UI.frame:IsShown() or false
    if returnToLibrary then self.UI:HideForPlayback() end

    activeMovie = movie
    cinematicNotified = nil
    SuppressNarcissusAFKScreen()
    local subtitlesEnabled = not self.db or self.db.showSubtitles ~= false
    local subtitleFrame = CaptureSubtitleFrame()
    movieFrame:EnableSubtitles(subtitlesEnabled)
    blackout:Show()
    movieFrame:Show()

    -- MovieFrame requires an update handler while its decoder is active. Keep
    -- the exceptional handler bounded to playback and remove it immediately.
    movieFrame:SetScript("OnUpdate", function() end)
    local success, returnCode = movieFrame:StartMovie(movie.id, false)
    if issecretvalue and (issecretvalue(success) or issecretvalue(returnCode)) then
        success, returnCode = false, nil
    end
    if not success then
        FinishPlayback(false, true)
        if not (quietMissingData and returnCode == 2) then
            local message = ERROR_MESSAGES[returnCode] or "This movie is not available in the current client."
            self:SetStatus(message, "warning")
        end
        return false, returnCode
    end

    self:RememberMovie(movie.id, movie.title)
    if CinematicStarted and Enum and Enum.CinematicType then
        cinematicNotified = pcall(CinematicStarted, Enum.CinematicType.GameMovie, movie.id, true)
    end
    if EventRegistry then
        EventRegistry:TriggerEvent("Subtitles.OnMovieCinematicPlay", movieFrame)
        subtitleEventActive = true
    end
    ActivateSubtitleFrame(subtitleFrame, movieFrame, subtitlesEnabled)
    return true, 0
end


function ns:PlayMovie(movie)
    if type(movie) ~= "table" or not self:IsSafeNumber(movie.id) or movie.id <= 0 then
        self:SetStatus("This movie entry is invalid.", "danger")
        return false
    end
    if InCombatLockdown and InCombatLockdown() then
        self:SetStatus("Movies cannot be started during combat.", "warning")
        return false
    end
    if activeMovie then
        self:SetStatus("A movie is already playing.", "warning")
        return false
    end

    CancelPreload()

    -- Blizzard's own movie frame asks StartMovie directly. The readiness APIs
    -- can lag by a frame, so gating the click on IsMoviePlayable made the first
    -- click merely warm the cache and a second click appear necessary.
    local started, returnCode = StartPlayback(movie, true)
    if started then return true end
    if returnCode ~= 2 then return false end

    if PreloadMovie and C_Timer and C_Timer.NewTicker then
        PreloadMovie(movie.id)
        preloadMovieID = movie.id
        local requestGeneration = preloadGeneration
        self:SetStatus("Preparing “" .. movie.title .. "”…", "muted")
        local attempts = 0
        local requestTicker
        requestTicker = C_Timer.NewTicker(0.1, function(ticker)
            if requestGeneration ~= preloadGeneration or preloadTicker ~= requestTicker then
                ticker:Cancel()
                return
            end
            attempts = attempts + 1
            local ready = not IsMoviePlayable or IsMoviePlayable(movie.id)
            if issecretvalue and issecretvalue(ready) then ready = false end
            if not ready and IsMovieReadable then
                ready = IsMovieReadable(movie.id)
                if issecretvalue and issecretvalue(ready) then ready = false end
            end
            if ready then
                local prepared, preparedCode = StartPlayback(movie, true)
                if prepared then
                    ticker:Cancel()
                    preloadTicker = nil
                    preloadMovieID = nil
                    return
                elseif preparedCode ~= 2 then
                    ticker:Cancel()
                    preloadTicker = nil
                    preloadMovieID = nil
                    return
                end
            end

            if GetMovieDownloadProgress then
                local inProgress, downloaded, total = GetMovieDownloadProgress(movie.id)
                if issecretvalue and (issecretvalue(inProgress) or issecretvalue(downloaded) or issecretvalue(total)) then
                    inProgress, downloaded, total = nil, nil, nil
                end
                if inProgress and type(downloaded) == "number" and type(total) == "number" and total > 0 then
                    local percent = math.floor((downloaded / total) * 100 + 0.5)
                    self:SetStatus("Preparing “" .. movie.title .. "”… " .. percent .. "%", "muted")
                end
            end

            if attempts >= 600 then
                ticker:Cancel()
                preloadTicker = nil
                CancelPreload(movie.id)
                self:SetStatus("This movie could not be prepared by the client.", "warning")
            end
        end, 600)
        preloadTicker = requestTicker
        return true
    end

    self:SetStatus(ERROR_MESSAGES[2], "warning")
    return false
end
