local _, ns = ...
local L = ns.L or {}

local DATA = ns.Data or {}
local SILVERMOON_COURT_FAVOR_QUEST_ID = DATA.SILVERMOON_COURT_FAVOR_QUEST_ID or 89289
local SILVERMOON_RUNESTONE_POOL = DATA.SILVERMOON_RUNESTONE_POOL or {}
local SALTHERILS_FAVOR_ITEM_ID = DATA.SALTHERILS_FAVOR_ITEM_ID or 238987
local MIDNIGHT_ABUNDANT_OFFERINGS_QUEST_ID = DATA.MIDNIGHT_ABUNDANT_OFFERINGS_QUEST_ID or 89507
local MIDNIGHT_ABUNDANT_OFFERINGS_META_QUEST_ID = DATA.MIDNIGHT_ABUNDANT_OFFERINGS_META_QUEST_ID or 93890
local MIDNIGHT_ABUNDANCE_CONTINENT_MAP_ID = DATA.MIDNIGHT_ABUNDANCE_CONTINENT_MAP_ID or 2537
local MIDNIGHT_ABUNDANCE_POI_MAP = DATA.MIDNIGHT_ABUNDANCE_POI_MAP or {}
local MIDNIGHT_STORMARION_ASSAULT_QUEST_ID = DATA.MIDNIGHT_STORMARION_ASSAULT_QUEST_ID or 90962
local MIDNIGHT_STORMARION_ASSAULT_META_QUEST_ID = DATA.MIDNIGHT_STORMARION_ASSAULT_META_QUEST_ID or 93892
local MIDNIGHT_VOID_ASSAULTS_QUEST_ID = DATA.MIDNIGHT_VOID_ASSAULTS_QUEST_ID or 94386
local MIDNIGHT_VOID_ASSAULTS_QUEST_POOL = DATA.MIDNIGHT_VOID_ASSAULTS_QUEST_POOL or { MIDNIGHT_VOID_ASSAULTS_QUEST_ID }
local COURT_FAVOR_ACCOUNT_MEMORY_KEY = "court_favor_account"

local Util = ns.Util or {}
local safeCopyTable = Util.SafeCopyTable
local stringContainsAnyPattern = Util.StringContainsAnyPattern

local WoWApi = ns.WoWApi or {}
local getMapNameByID = WoWApi.GetMapNameByID
local getItemCountByID = WoWApi.GetItemCountByID

local Quest = ns.Quest or {}
local isQuestCompleted = Quest.IsQuestCompleted
local isQuestCompletedOnAccount = Quest.IsQuestCompletedOnAccount
local isQuestActive = Quest.IsQuestActive
local getTaskQuestIDsOnMap = Quest.GetTaskQuestIDsOnMap
local getQuestObjectiveLines = Quest.GetQuestObjectiveLines
local isQuestTurnInReady = Quest.IsQuestTurnInReady
local getQuestTitle = Quest.GetQuestTitle
local buildQuestPoolState = Quest.BuildQuestPoolState

local WeeklyMemory = ns.WeeklyMemory or {}
local getRememberedWeeklyMemory = WeeklyMemory.GetRememberedWeeklyMemory
local rememberWeeklyCompletion = WeeklyMemory.RememberWeeklyCompletion

local WorldWeeklyEvents = {}
ns.WorldWeeklyEvents = WorldWeeklyEvents

local WorldWeeklyLostLegends = ns.WorldWeeklyLostLegends or {}
local buildLostLegendsRow = WorldWeeklyLostLegends.BuildRow or function()
    return nil
end

local function markAccountWideRenownRow(row)
    if type(row) == "table" then
        row.accountWide = true
        row.renownAccountWide = true
    end
    return row
end

local function getStoredCompletedEventRow(rowKey)
    if type(rowKey) ~= "string" or rowKey == "" or not ns.db or type(ns.db.characters) ~= "table" then
        return nil
    end

    local now = time()
    for characterKey, character in pairs(ns.db.characters) do
        local worldWeekly = type(character) == "table" and character.worldWeekly or nil
        local resetAt = type(worldWeekly) == "table" and tonumber(worldWeekly.resetAt) or nil
        local state = type(worldWeekly) == "table" and type(worldWeekly.state) == "table" and worldWeekly.state or nil
        local rows = type(state) == "table" and type(state.events) == "table" and state.events or nil
        if resetAt and resetAt > now and rows then
            for _, row in ipairs(rows) do
                if type(row) == "table"
                    and row.key == rowKey
                    and (row.completed == true or row.status == "complete")
                then
                    return row, characterKey
                end
            end
        end
    end

    return nil
end

local function finalizeEventQuestRow(row, missingHint)
    if type(row) ~= "table" then
        return nil
    end

    row.tooltipTitle = row.label or row.tooltipTitle

    if row.status == "missing" then
        row.tooltipLines = {
            missingHint or (L.PVE_EVENTS_ACTIVE_HINT or "This quest is active. Check the quest log for the current objective."),
        }
    elseif row.status == "turnin" then
        row.tooltipLines = {
            L.PVP_WEEKLY_TURNIN_TOOLTIP or "This quest is done and can now be turned in.",
        }
    elseif row.status == "complete" then
        row.tooltipLines = {
            L.PVE_EVENTS_DONE_HINT or "Already completed this week.",
        }
    else
        local objectiveLines = getQuestObjectiveLines(row.questID)
        if #objectiveLines > 0 then
            row.tooltipLines = objectiveLines
        else
            row.tooltipLines = {
                L.PVE_EVENTS_ACTIVE_HINT or "This quest is active. Check the quest log for the current objective.",
            }
        end
    end

    return row
end

local function isTaskQuestVisibleOnAnyMap(questID, mapIDs)
    if not questID or questID <= 0 then
        return false, nil
    end

    for _, mapID in ipairs(mapIDs or {}) do
        for _, visibleQuestID in ipairs(getTaskQuestIDsOnMap(mapID)) do
            if visibleQuestID == questID then
                return true, mapID
            end
        end
    end

    return false, nil
end

local function getActiveAbundanceLocationLabel()
    if not (C_AreaPoiInfo and type(C_AreaPoiInfo.GetEventsForMap) == "function" and type(C_AreaPoiInfo.GetAreaPOIInfo) == "function") then
        return nil
    end

    local okPois, poiIDs = pcall(C_AreaPoiInfo.GetEventsForMap, MIDNIGHT_ABUNDANCE_CONTINENT_MAP_ID)
    if not okPois or type(poiIDs) ~= "table" then
        return nil
    end

    for _, rawPoiID in ipairs(poiIDs) do
        local poiID = tonumber(rawPoiID)
        local uiMapID = poiID and MIDNIGHT_ABUNDANCE_POI_MAP[poiID] or nil
        if uiMapID then
            local okInfo, info = pcall(C_AreaPoiInfo.GetAreaPOIInfo, MIDNIGHT_ABUNDANCE_CONTINENT_MAP_ID, poiID)
            local poiName = okInfo and type(info) == "table" and tostring(info.name or "") or ""
            local mapName = getMapNameByID(uiMapID) or ""

            if mapName ~= "" and poiName ~= "" then
                return string.format("%s - %s", mapName, poiName)
            end
            if mapName ~= "" then
                return mapName
            end
            if poiName ~= "" then
                return poiName
            end
        end
    end

    return nil
end

local function buildMidnightEventQuestRow(config)
    if type(config) ~= "table" then
        return nil
    end

    local quests = {}
    if type(config.questPool) == "table" then
        for _, questInfo in ipairs(config.questPool) do
            local questID = type(questInfo) == "table" and tonumber(questInfo.id) or tonumber(questInfo)
            if questID and questID > 0 then
                quests[#quests + 1] = {
                    id = questID,
                    fallbackTitle = (type(questInfo) == "table" and questInfo.fallbackTitle) or config.fallbackTitle or config.fallbackLabel,
                }
            end
        end
    elseif tonumber(config.questID) then
        quests[#quests + 1] = {
            id = tonumber(config.questID),
            fallbackTitle = config.fallbackTitle or config.fallbackLabel,
        }
    end

    if #quests == 0 then
        return nil
    end

    local row = buildQuestPoolState({
        key = config.key,
        labelKey = config.labelKey,
        fallbackLabel = config.fallbackLabel,
        quests = quests,
    })

    row = finalizeEventQuestRow(row, config.acceptHint or (L.PVE_EVENTS_ACTIVE_HINT or "This quest is active. Check the quest log for the current objective."))
    if type(row) ~= "table" then
        return nil
    end

    if row.status ~= "missing" then
        return row
    end

    local tooltipLines = {}
    if type(config.acceptHint) == "string" and config.acceptHint ~= "" then
        tooltipLines[#tooltipLines + 1] = config.acceptHint
    end

    local availabilityLabel = nil
    if type(config.getAvailabilityLabel) == "function" then
        availabilityLabel = config.getAvailabilityLabel()
    end

    local visibleOnMap, visibleMapID, visibleQuestID = false, nil, nil
    for _, questInfo in ipairs(quests) do
        visibleOnMap, visibleMapID = isTaskQuestVisibleOnAnyMap(questInfo.id, config.mapIDs)
        if visibleOnMap then
            visibleQuestID = questInfo.id
            break
        end
    end
    if not availabilityLabel and visibleOnMap then
        availabilityLabel = getQuestTitle(visibleQuestID, row.label)
        local mapName = getMapNameByID(visibleMapID)
        if type(mapName) == "string" and mapName ~= "" then
            availabilityLabel = string.format("%s - %s", mapName, availabilityLabel)
        end
    end

    if availabilityLabel then
        tooltipLines[#tooltipLines + 1] = string.format(
            L.PVE_EVENTS_AVAILABLE_HINT or "Currently active: %s",
            availabilityLabel
        )
    elseif config.metaQuestID and isQuestActive(config.metaQuestID) then
        tooltipLines[#tooltipLines + 1] = L.PVE_EVENTS_WEEKLY_ACTIVE_HINT
            or "This Midnight event is active this week."
    end

    if #tooltipLines > 0 then
        row.tooltipLines = tooltipLines
    end
    row.seen = (availabilityLabel ~= nil) or visibleOnMap or (config.metaQuestID and isQuestActive(config.metaQuestID)) or false

    return row
end

local function buildSilvermoonCourtFavorRow()
    local row = markAccountWideRenownRow(buildQuestPoolState({
        key = "court_favor",
        labelKey = "PVE_EVENTS_ROW_COURT_FAVOR",
        fallbackLabel = "Favor of the Court",
        quests = {
            { id = SILVERMOON_COURT_FAVOR_QUEST_ID, fallbackTitle = "Favor of the Court" },
        },
    }))

    if row and (row.completed == true or row.status == "complete") then
        rememberWeeklyCompletion(COURT_FAVOR_ACCOUNT_MEMORY_KEY, row.questID or SILVERMOON_COURT_FAVOR_QUEST_ID)
    elseif row then
        local accountCompleted = type(isQuestCompletedOnAccount) == "function"
            and isQuestCompletedOnAccount(SILVERMOON_COURT_FAVOR_QUEST_ID)
        local memory = getRememberedWeeklyMemory(COURT_FAVOR_ACCOUNT_MEMORY_KEY)
        local storedRow = getStoredCompletedEventRow("court_favor")
        if accountCompleted or memory or storedRow then
            row.text = L.STATUS_DONE or "Done"
            row.status = "complete"
            row.seen = true
            row.completed = true
            row.questID = (type(memory) == "table" and memory.questID)
                or (type(storedRow) == "table" and storedRow.questID)
                or SILVERMOON_COURT_FAVOR_QUEST_ID
            row.turnInQuestID = nil
        end
    end

    return markAccountWideRenownRow(finalizeEventQuestRow(
        row,
        L.PVE_EVENTS_COURT_ACCEPT_HINT or "This quest can be accepted from Lord Saltheril in the Court of Silvermoon."
    ))
end

local function buildSilvermoonRunestoneRow()
    local questDefs = {}
    for _, questID in ipairs(SILVERMOON_RUNESTONE_POOL) do
        questDefs[#questDefs + 1] = { id = questID }
    end

    local row = buildQuestPoolState({
        key = "runestones",
        labelKey = "PVE_EVENTS_ROW_RUNESTONES",
        fallbackLabel = "Fortify the Runestones",
        quests = questDefs,
    })

    return finalizeEventQuestRow(
        row,
        L.PVE_EVENTS_RUNESTONES_ACCEPT_HINT or "This quest can be accepted in the Court of Silvermoon."
    )
end

local function buildAbundantOfferingsRow()
    return buildMidnightEventQuestRow({
        key = "abundant_offerings",
        labelKey = "PVE_EVENTS_ROW_ABUNDANT_OFFERINGS",
        fallbackLabel = "Abundant Offerings",
        fallbackTitle = "Abundant Offerings",
        questID = MIDNIGHT_ABUNDANT_OFFERINGS_QUEST_ID,
        metaQuestID = MIDNIGHT_ABUNDANT_OFFERINGS_META_QUEST_ID,
        acceptHint = L.PVE_EVENTS_ABUNDANT_OFFERINGS_ACCEPT_HINT
            or "This weekly becomes available during the active Abundance event.",
        getAvailabilityLabel = getActiveAbundanceLocationLabel,
    })
end

local function buildStormarionAssaultRow()
    return buildMidnightEventQuestRow({
        key = "stormarion_assault",
        labelKey = "PVE_EVENTS_ROW_STORMARION_ASSAULT",
        fallbackLabel = "Stormarion Assault",
        fallbackTitle = "Stormarion Assault",
        questID = MIDNIGHT_STORMARION_ASSAULT_QUEST_ID,
        metaQuestID = MIDNIGHT_STORMARION_ASSAULT_META_QUEST_ID,
        mapIDs = { 2405 },
        acceptHint = L.PVE_EVENTS_STORMARION_ASSAULT_ACCEPT_HINT
            or "This weekly becomes available in the Singularity during the active Midnight event.",
    })
end

local function buildVoidAssaultsRow()
    return buildMidnightEventQuestRow({
        key = "void_assaults",
        labelKey = "PVE_EVENTS_ROW_VOID_ASSAULTS",
        fallbackLabel = "Void Assaults",
        fallbackTitle = "Void Assaults",
        questPool = MIDNIGHT_VOID_ASSAULTS_QUEST_POOL,
        acceptHint = L.PVE_EVENTS_VOID_ASSAULTS_ACCEPT_HINT
            or "This weekly becomes available during active Void Assaults.",
    })
end

local function buildSaltherilsFavorItemRow()
    local count = getItemCountByID(SALTHERILS_FAVOR_ITEM_ID)
    local label = L.PVE_EVENTS_ROW_SALTHERILS_FAVOR or "Saltheril's Favor"

    return {
        key = "saltherils_favor",
        label = label,
        text = tostring(count),
        status = count > 0 and "open" or "missing",
        seen = count > 0,
        completed = false,
        itemID = SALTHERILS_FAVOR_ITEM_ID,
        tooltipTitle = label,
        tooltipLines = {
            L.PVE_EVENTS_SALTHERILS_FAVOR_HINT or "Tracks how many Saltheril's Favor items are currently in your bags. Use them to unlock additional Court of Silvermoon tasks. They expire at the weekly reset.",
            string.format(L.PVE_EVENTS_SALTHERILS_FAVOR_COUNT or "Currently owned: %d", count),
        },
    }
end

local function shouldSkipFallbackEventRow(entry)
    if type(entry) ~= "table" then
        return false
    end

    local key = string.lower(tostring(entry.key or ""))
    if key == "spark" or key == "soiree" or key == "housing"
        or key == "abundant_offerings" or key == "lost_legends" or key == "stormarion_assault"
        or key == "void_assaults"
        or key == "abundance" or key == "haranir" or key == "stormarion"
    then
        return true
    end

    local label = string.lower(tostring(entry.label or entry.tooltipTitle or ""))
    return stringContainsAnyPattern(label, {
        "vereint gegen die leere",
        "spark",
        "runestone",
        "runenstein",
        "soiree",
        "housing weekly",
        "housing",
        "einweihungsfeier",
        "ab die post",
        "verlorene tiere",
        "vermisste tiere",
        "abundant offerings",
        "offerings in abundance",
        "opfergaben im überfluss",
        "abundance",
        "lost legends",
        "verlorene legenden",
        "stormarion assault",
        "sturmarionangriff",
        "void assaults",
        "leerenvorstöße",
        "leerenvorstoesse",
    })
end

local function buildEventDisplaySummary(rows)
    local completed = 0
    local total = 0

    for _, row in ipairs(rows or {}) do
        if row and row.excludeFromSummary ~= true then
            total = total + 1
            if row.status == "complete" or row.completed == true then
                completed = completed + 1
            end
        end
    end

    return {
        completed = completed,
        open = math.max(0, total - completed),
        total = total,
        text = string.format("%d/%d", completed, total),
    }
end

function WorldWeeklyEvents.BuildRows(fallbackOverview)
    local eventRows = {
        buildSilvermoonCourtFavorRow(),
        buildSilvermoonRunestoneRow(),
        buildSaltherilsFavorItemRow(),
        buildAbundantOfferingsRow(),
        buildLostLegendsRow(),
        buildStormarionAssaultRow(),
        buildVoidAssaultsRow(),
    }

    for _, entry in ipairs(type(fallbackOverview) == "table" and fallbackOverview.events or {}) do
        if type(entry) == "table" and not shouldSkipFallbackEventRow(entry) then
            eventRows[#eventRows + 1] = safeCopyTable(entry)
        end
    end

    return eventRows
end

function WorldWeeklyEvents.ShouldSkipFallbackRow(entry)
    return shouldSkipFallbackEventRow(entry)
end

function WorldWeeklyEvents.BuildSummary(rows)
    return buildEventDisplaySummary(rows)
end
