---@class AddonPrivate
local Private = select(2, ...)

---@class TheaterTroupe
-- Reads the Theater Troupe world-event Area POI (Dornogal / Isle of Dorn) to find
-- which play is showing next + when, and whether it's a play still needed for the
-- "We're Here All Night" achievement. The upcoming play IS knowable in advance
-- (the map event POI names it with a countdown), so we can flag needed ones.
local theater = {}
Private.TheaterTroupe = theater

local ACHIEVEMENT = 40859               -- We're Here All Night
local MAPS = { 2339, 2248 }             -- Dornogal, then Isle of Dorn (POI may live on either)
local STAGE = { mapID = 2248, x = 56.41, y = 51.44 }
local KHAZ_ALGAR_CONTINENT = 2274       -- only surface the line while on this continent
local ACTIVE_WINDOW = 600               -- event is live for the first 10 min of each hour
local PLAYS = {
    "The Rivals", "The Wanderer", "Forget Me Not",
    "The Cruelty of Dornic", "The Thraegar's Descent",
}

-- Is a play (matched by name) still incomplete for the achievement?
local function playNeeded(playName)
    local num = GetAchievementNumCriteria(ACHIEVEMENT) or 0
    for i = 1, num do
        local name, _, completed = GetAchievementCriteriaInfo(ACHIEVEMENT, i)
        if name and name == playName then return not completed end
    end
    return false
end

-- True once all 5 plays are done (feature then stays silent).
function theater:AllDone()
    return select(4, GetAchievementInfo(ACHIEVEMENT)) and true or false
end

-- Inspect the Theater Troupe area POI. Returns { play, seconds, needed, mapID, poiID }
-- or nil if the POI can't be found / read. `play` may be nil if the POI exists but
-- its text didn't name a known play (then `needed` is nil).
function theater:GetStatus()
    if self:AllDone() then return nil end
    local capi = C_AreaPoiInfo
    if not (capi and capi.GetAreaPOIForMap and capi.GetAreaPOIInfo) then return nil end
    for _, mapID in ipairs(MAPS) do
        local pois = capi.GetAreaPOIForMap(mapID)
        if pois then
            for _, poiID in ipairs(pois) do
                local info = capi.GetAreaPOIInfo(mapID, poiID)
                if info then
                    local blob = (info.name or "") .. "\n" .. (info.description or "")
                    local isTheater = blob:find("Theater", 1, true) or blob:find("Troupe", 1, true)
                    local foundPlay
                    for _, play in ipairs(PLAYS) do
                        if blob:find(play, 1, true) then foundPlay = play; isTheater = true; break end
                    end
                    if isTheater then
                        local secs = capi.GetAreaPOISecondsLeft and capi.GetAreaPOISecondsLeft(poiID)
                        return {
                            play = foundPlay,
                            seconds = secs,
                            needed = foundPlay and playNeeded(foundPlay) or nil,
                            mapID = mapID, poiID = poiID,
                        }
                    end
                end
            end
        end
    end
    return nil
end

function theater:FormatTime(secs)
    if not secs or secs <= 0 then return "" end
    local m = math.floor(secs / 60)
    if m >= 60 then return string.format("%dh %dm", math.floor(m / 60), m % 60) end
    if m >= 1 then return string.format("%dm", m) end
    return string.format("%ds", secs)
end

-- Which of the 5 plays are still incomplete (reliable, straight from criteria).
function theater:GetRemainingPlays()
    local out = {}
    local num = GetAchievementNumCriteria(ACHIEVEMENT) or 0
    for i = 1, num do
        local name, _, completed = GetAchievementCriteriaInfo(ACHIEVEMENT, i)
        if name and not completed then out[#out + 1] = name end
    end
    return out
end

-- Seconds until the next showing. The event fires at the top of every hour, which
-- aligns to the UTC hour boundary, so GetServerTime() (epoch) gives it directly -
-- no POI, works during the countdown, region-independent.
function theater:GetSecondsToNextShowing()
    if not GetServerTime then return nil end
    return 3600 - (GetServerTime() % 3600)
end

-- The event is on stage for the first 10 min of each hour. That's the same as the
-- countdown to the NEXT top-of-hour still being above 50 min. Returns (active, secsLeft).
function theater:IsActive()
    local secs = self:GetSecondsToNextShowing()
    if not secs then return false end
    if secs > (3600 - ACTIVE_WINDOW) then return true, secs - (3600 - ACTIVE_WINDOW) end
    return false
end

-- Only surface the planner line while the player is on the Khaz Algar continent.
-- We compare the player's continent both to the known id AND to Isle of Dorn's
-- continent (derived live), so it holds even if the continent id ever differs.
function theater:InKhazAlgar()
    local O = Private.Objectives
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    if not (O and mapID) then return false end
    local cont = O:GetContinentID(mapID)
    if not cont then return false end
    return cont == KHAZ_ALGAR_CONTINENT or cont == O:GetContinentID(2248)
end

-- One line for the Do-it-now planner. Shown while We're Here All Night is unfinished.
-- Reliable: countdown to next showing + how many plays remain. If the live event POI
-- happens to be readable (event active) and names a NEEDED play, we highlight that.
function theater:GetPlannerLine()
    if self:AllDone() then return nil end              -- achievement earned -> never show
    local remaining = self:GetRemainingPlays()
    if #remaining == 0 then return nil end             -- all plays done -> never show
    if not self:InKhazAlgar() then return nil end      -- only inside Khaz Algar
    local active, activeLeft = self:IsActive()
    if active then
        local st = self:GetStatus()   -- if the live POI is readable, name the play
        if st and st.play then
            return string.format("|cff00ff00Theater Troupe ACTIVE:|r %s%s  (%s left)",
                st.play, st.needed and " |cff00ff00(needed!)|r" or " (done)", self:FormatTime(activeLeft))
        end
        return string.format("|cff00ff00Theater Troupe ACTIVE NOW!|r  (%s left)  \226\128\148  %d play%s left",
            self:FormatTime(activeLeft), #remaining, #remaining == 1 and "" or "s")
    end
    local t = self:GetSecondsToNextShowing()
    return string.format("|cffffd700Theater Troupe|r  next ~%s  \226\128\148  %d play%s left",
        t and self:FormatTime(t) or "?", #remaining, #remaining == 1 and "" or "s")
end

function theater:GetStage() return STAGE end

-- Diagnostic: dump every Area POI (name + timer) on the maps we care about, so we
-- can see how the Theater Troupe event is actually represented. Run with the world
-- map open to the zone that shows the event icon.
function theater:Debug()
    local addon = Private.Addon
    local capi = C_AreaPoiInfo
    if not (capi and capi.GetAreaPOIForMap) then addon:Print("C_AreaPoiInfo.GetAreaPOIForMap missing"); return end
    local maps, seen = {}, {}
    local function add(m) if m and not seen[m] then seen[m] = true; maps[#maps + 1] = m end end
    local pm = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    add(pm)
    if WorldMapFrame and WorldMapFrame.GetMapID then add(WorldMapFrame:GetMapID()) end
    if pm then
        local info = C_Map.GetMapInfo(pm); if info then add(info.parentMapID) end
        -- children of the player's map (Dornogal may be a child interior map)
        local kids = C_Map.GetMapChildrenInfo and C_Map.GetMapChildrenInfo(pm, nil, true)
        if kids then for _, k in ipairs(kids) do add(k.mapID) end end
    end
    add(2339); add(2248); add(2274); add(2276)
    for _, m in ipairs(maps) do
        local mi = C_Map.GetMapInfo(m)
        local pois = capi.GetAreaPOIForMap(m)
        local tasks = C_TaskQuest and C_TaskQuest.GetQuestsOnMap and C_TaskQuest.GetQuestsOnMap(m)
        addon:Print(string.format("|cffffd700Map %d (%s): %d POIs, %d tasks|r", m, mi and mi.name or "?",
            pois and #pois or 0, tasks and #tasks or 0))
        if pois then
            for _, id in ipairs(pois) do
                local i = capi.GetAreaPOIInfo(m, id)
                if i then
                    local secs = capi.GetAreaPOISecondsLeft and capi.GetAreaPOISecondsLeft(id)
                    addon:Print(string.format("  poi[%s] %s%s%s", tostring(id), tostring(i.name),
                        (i.description and i.description ~= "") and (" | " .. i.description:sub(1, 50)) or "",
                        (secs and secs > 0) and (" | " .. self:FormatTime(secs)) or ""))
                end
            end
        end
        if tasks then
            for _, t in ipairs(tasks) do
                local qid = t.questId or t.questID
                local title = qid and C_QuestLog and C_QuestLog.GetTitleForQuestID and C_QuestLog.GetTitleForQuestID(qid)
                addon:Print(string.format("  task[%s] %s", tostring(qid), tostring(title or "?")))
            end
        end
    end
    -- also check vignettes, in case the event is one
    if C_VignetteInfo and C_VignetteInfo.GetVignettes then
        for _, guid in ipairs(C_VignetteInfo.GetVignettes()) do
            local v = C_VignetteInfo.GetVignetteInfo(guid)
            if v and v.name then addon:Print("|cff88ccffvignette:|r " .. v.name) end
        end
    end
end

-- Full status for the /drusttheater command.
function theater:Print()
    local addon = Private.Addon
    if self:AllDone() then
        addon:Print("Theater Troupe: |cff00ff00We're Here All Night is complete|r - all 5 plays done.")
        return
    end
    local remaining = self:GetRemainingPlays()
    local active, activeLeft = self:IsActive()
    if active then
        addon:Print(string.format("Theater Troupe: |cff00ff00ACTIVE NOW|r (~%s left) in Dornogal.", self:FormatTime(activeLeft)))
    else
        local t = self:GetSecondsToNextShowing()
        addon:Print(string.format("Theater Troupe: next showing in |cffffd700~%s|r (top of the hour, Dornogal).",
            t and self:FormatTime(t) or "?"))
    end
    addon:Print("  Plays still needed (" .. #remaining .. "): " .. table.concat(remaining, ", "))
    local st = self:GetStatus()
    if st and st.play then
        addon:Print("  Live event play now: |cffffd700" .. st.play .. "|r" .. (st.needed and " |cff00ff00(needed)|r" or " (done)"))
    end
end
