--------------------------------------------------------------------------------
-- Nanami-UI: MapReveal -- Reveal unexplored world map areas
-- Adapted from ShaguTweaks-extras worldmap-reveal approach
-- Uses LibMapOverlayData (from !Libs) supplemented with Turtle WoW zones
-- Features persistent overlay discovery via GetMapOverlayInfo() API
--------------------------------------------------------------------------------

SFrames.MapReveal = SFrames.MapReveal or {}

local MapReveal = SFrames.MapReveal
local origWorldMapFrame_Update = nil
local overlayDBPatched = false

local errata = {
    ["Interface\\WorldMap\\Tirisfal\\BRIGHTWATERLAKE"] = { offsetX = { 587, 584 } },
    ["Interface\\WorldMap\\Silverpine\\BERENSPERIL"]   = { offsetY = { 417, 415 } },
}

-- Zones only in TurtleWoW_Zones: not in patched WorldMapOverlay.dbc but needed for fog reveal
local TurtleWoW_Zones = {
    ["UpperKarazhan2f"] = {
        "OUTLAND:1024:768:0:0",
    },
}

-- Runtime-discovered overlay data (populated by scanning and passive collection)
local scannedOverlays = {}

local function IsTurtleWoW()
    return TargetHPText and TargetHPPercText
end

local function GetOverlayDB()
    return MapOverlayData or LibMapOverlayData or zMapOverlayData or mapOverlayData
end

--------------------------------------------------------------------------------
-- Persistence: save/load discovered overlay data to SFramesDB
--------------------------------------------------------------------------------
local function GetGlobalDB()
    if not SFramesGlobalDB then SFramesGlobalDB = {} end
    return SFramesGlobalDB
end

local function LoadPersistedData()
    local gdb = GetGlobalDB()
    local saved = gdb.MapRevealScanData
    if type(saved) ~= "table" then return end

    local count = 0
    for zone, overlays in pairs(saved) do
        if type(overlays) == "table" and table.getn(overlays) > 0 then
            if not scannedOverlays[zone] or table.getn(scannedOverlays[zone]) < table.getn(overlays) then
                scannedOverlays[zone] = overlays
                count = count + 1
            end
        end
    end
    return count
end

local function SavePersistedData()
    local gdb = GetGlobalDB()
    if type(gdb.MapRevealScanData) ~= "table" then
        gdb.MapRevealScanData = {}
    end

    for zone, overlays in pairs(scannedOverlays) do
        local existing = gdb.MapRevealScanData[zone]
        if not existing or table.getn(existing) < table.getn(overlays) then
            gdb.MapRevealScanData[zone] = overlays
        end
    end
end

--------------------------------------------------------------------------------
-- Overlay name index for deduplication when merging
--------------------------------------------------------------------------------
local function BuildOverlayIndex(overlayList)
    local idx = {}
    if not overlayList then return idx end
    for i = 1, table.getn(overlayList) do
        local entry = overlayList[i]
        local _, _, name = string.find(entry, "^([^:]+):")
        if name then
            idx[string.upper(name)] = i
        end
    end
    return idx
end

local function MergeScannedDataIntoDB()
    local db = GetOverlayDB()
    if not db then return end

    for zone, data in pairs(scannedOverlays) do
        if not db[zone] then
            db[zone] = data
        else
            local existingIdx = BuildOverlayIndex(db[zone])
            for i = 1, table.getn(data) do
                local _, _, newName = string.find(data[i], "^([^:]+):")
                if newName and not existingIdx[string.upper(newName)] then
                    table.insert(db[zone], data[i])
                    existingIdx[string.upper(newName)] = table.getn(db[zone])
                end
            end
        end
    end
end

local function PatchOverlayDB()
    if overlayDBPatched then return end
    overlayDBPatched = true

    if not IsTurtleWoW() then return end

    local db = GetOverlayDB()
    if not db then return end

    for zone, data in pairs(TurtleWoW_Zones) do
        if table.getn(data) > 0 then
            db[zone] = data
        end
    end

    MergeScannedDataIntoDB()
end

local function MergeOverlaysForZone(zone)
    local db = GetOverlayDB()
    if not db or not scannedOverlays[zone] then return end

    if not db[zone] then
        db[zone] = scannedOverlays[zone]
        return
    end

    local existingIdx = BuildOverlayIndex(db[zone])
    local data = scannedOverlays[zone]
    for i = 1, table.getn(data) do
        local _, _, newName = string.find(data[i], "^([^:]+):")
        if newName and not existingIdx[string.upper(newName)] then
            table.insert(db[zone], data[i])
            existingIdx[string.upper(newName)] = table.getn(db[zone])
        end
    end
end

--------------------------------------------------------------------------------
-- Passive overlay discovery: captures explored overlay data from the API
-- every time the world map updates, discovering new overlays automatically
--------------------------------------------------------------------------------
local function PassiveCollectOverlays()
    local mapFile = GetMapInfo and GetMapInfo()
    if not mapFile or mapFile == "" or mapFile == "World" then return end

    local numOverlays = GetNumMapOverlays and GetNumMapOverlays() or 0
    if numOverlays == 0 then return end

    local currentOverlays = {}
    local hasNew = false

    for i = 1, numOverlays do
        local texName, texW, texH, offX, offY = GetMapOverlayInfo(i)
        if texName and texName ~= "" then
            local _, _, name = string.find(texName, "\\([^\\]+)$")
            if name then
                name = string.upper(name)
                table.insert(currentOverlays, name .. ":" .. texW .. ":" .. texH .. ":" .. offX .. ":" .. offY)
            end
        end
    end

    if table.getn(currentOverlays) == 0 then return end

    if not scannedOverlays[mapFile] then
        scannedOverlays[mapFile] = currentOverlays
        hasNew = true
    else
        local existingIdx = BuildOverlayIndex(scannedOverlays[mapFile])
        for i = 1, table.getn(currentOverlays) do
            local _, _, newName = string.find(currentOverlays[i], "^([^:]+):")
            if newName and not existingIdx[string.upper(newName)] then
                table.insert(scannedOverlays[mapFile], currentOverlays[i])
                existingIdx[string.upper(newName)] = table.getn(scannedOverlays[mapFile])
                hasNew = true
            end
        end
    end

    if hasNew then
        MergeOverlaysForZone(mapFile)
        SavePersistedData()
    end
end

local function GetConfig()
    if not SFramesDB or type(SFramesDB.MapReveal) ~= "table" then
        return { enabled = true, unexploredAlpha = 0.7 }
    end
    return SFramesDB.MapReveal
end

local function NextPowerOf2(n)
    local p = 16
    while p < n do
        p = p * 2
    end
    return p
end

local function DoMapRevealUpdate()
    local db = GetOverlayDB()
    if not db then return end

    local mapFileName = GetMapInfo and GetMapInfo()
    if not mapFileName then mapFileName = "World" end

    local zoneData = db[mapFileName]
    if not zoneData then return end

    local prefix = "Interface\\WorldMap\\" .. mapFileName .. "\\"

    local numExploredOverlays = GetNumMapOverlays and GetNumMapOverlays() or 0
    local explored = {}
    for i = 1, numExploredOverlays do
        local textureName = GetMapOverlayInfo(i)
        if textureName and textureName ~= "" then
            explored[textureName] = true
        end
    end

    local cfg = GetConfig()
    local dimR, dimG, dimB = 0.4, 0.4, 0.4
    if cfg.unexploredAlpha then
        dimR = cfg.unexploredAlpha
        dimG = cfg.unexploredAlpha
        dimB = cfg.unexploredAlpha
    end

    local textureCount = 0

    for idx = 1, table.getn(zoneData) do
        local entry = zoneData[idx]
        local _, _, name, sW, sH, sX, sY = string.find(entry, "^(%u+):(%d+):(%d+):(%d+):(%d+)$")
        if not name then
            _, _, name, sW, sH, sX, sY = string.find(entry, "^([^:]+):(%d+):(%d+):(%d+):(%d+)$")
        end
        if name then
            local textureWidth  = tonumber(sW)
            local textureHeight = tonumber(sH)
            local offsetX       = tonumber(sX)
            local offsetY       = tonumber(sY)
            local textureName   = prefix .. name

            local isExplored = explored[textureName]

            if cfg.enabled or isExplored then
                if errata[textureName] then
                    local e = errata[textureName]
                    if e.offsetX and e.offsetX[1] == offsetX then
                        offsetX = e.offsetX[2]
                    end
                    if e.offsetY and e.offsetY[1] == offsetY then
                        offsetY = e.offsetY[2]
                    end
                end

                local numTexturesHorz = math.ceil(textureWidth / 256)
                local numTexturesVert = math.ceil(textureHeight / 256)
                local neededTextures = textureCount + (numTexturesHorz * numTexturesVert)

                if neededTextures > NUM_WORLDMAP_OVERLAYS then
                    for j = NUM_WORLDMAP_OVERLAYS + 1, neededTextures do
                        WorldMapDetailFrame:CreateTexture("WorldMapOverlay" .. j, "ARTWORK")
                    end
                    NUM_WORLDMAP_OVERLAYS = neededTextures
                end

                for row = 1, numTexturesVert do
                    local texturePixelHeight, textureFileHeight
                    if row < numTexturesVert then
                        texturePixelHeight = 256
                        textureFileHeight  = 256
                    else
                        texturePixelHeight = math.mod(textureHeight, 256)
                        if texturePixelHeight == 0 then texturePixelHeight = 256 end
                        textureFileHeight = NextPowerOf2(texturePixelHeight)
                    end

                    for col = 1, numTexturesHorz do
                        if textureCount > NUM_WORLDMAP_OVERLAYS then return end

                        local texture = _G["WorldMapOverlay" .. (textureCount + 1)]

                        local texturePixelWidth, textureFileWidth
                        if col < numTexturesHorz then
                            texturePixelWidth = 256
                            textureFileWidth  = 256
                        else
                            texturePixelWidth = math.mod(textureWidth, 256)
                            if texturePixelWidth == 0 then texturePixelWidth = 256 end
                            textureFileWidth = NextPowerOf2(texturePixelWidth)
                        end

                        texture:SetWidth(texturePixelWidth)
                        texture:SetHeight(texturePixelHeight)
                        texture:SetTexCoord(0, texturePixelWidth / textureFileWidth,
                                            0, texturePixelHeight / textureFileHeight)
                        texture:ClearAllPoints()
                        texture:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT",
                                         offsetX + (256 * (col - 1)),
                                         -(offsetY + (256 * (row - 1))))

                        local tileIndex = ((row - 1) * numTexturesHorz) + col
                        texture:SetTexture(textureName .. tileIndex)

                        if not isExplored then
                            texture:SetVertexColor(dimR, dimG, dimB, 1)
                        else
                            texture:SetVertexColor(1, 1, 1, 1)
                        end

                        texture:Show()
                        textureCount = textureCount + 1
                    end
                end
            end
        end
    end
end

function MapReveal:Initialize()
    local db = GetOverlayDB()
    if not db then
        SFrames:Print("MapReveal: LibMapOverlayData 未找到，地图揭示功能不可用。")
        return
    end

    local loadedCount = LoadPersistedData()
    PatchOverlayDB()

    if loadedCount and loadedCount > 0 then
        MergeScannedDataIntoDB()
    end

    if not origWorldMapFrame_Update and WorldMapFrame_Update then
        origWorldMapFrame_Update = WorldMapFrame_Update
        WorldMapFrame_Update = function()
            for i = 1, NUM_WORLDMAP_OVERLAYS do
                local tex = _G["WorldMapOverlay" .. i]
                if tex then tex:Hide() end
            end

            origWorldMapFrame_Update()

            PassiveCollectOverlays()

            local cfg = GetConfig()
            if cfg.enabled then
                DoMapRevealUpdate()
            end
        end
    end
end

function MapReveal:Toggle()
    if not SFramesDB then SFramesDB = {} end
    if type(SFramesDB.MapReveal) ~= "table" then
        SFramesDB.MapReveal = { enabled = true, unexploredAlpha = 0.7 }
    end

    SFramesDB.MapReveal.enabled = not SFramesDB.MapReveal.enabled

    if SFramesDB.MapReveal.enabled then
        SFrames:Print("地图迷雾揭示: |cff00ff00已开启|r")
        if not origWorldMapFrame_Update and WorldMapFrame_Update then
            self:Initialize()
        end
    else
        SFrames:Print("地图迷雾揭示: |cffff0000已关闭|r")
    end

    if WorldMapFrame and WorldMapFrame:IsShown() then
        WorldMapFrame_Update()
    end
end

function MapReveal:SetAlpha(alpha)
    if not SFramesDB or type(SFramesDB.MapReveal) ~= "table" then return end
    SFramesDB.MapReveal.unexploredAlpha = alpha
    if SFramesDB.MapReveal.enabled and WorldMapFrame and WorldMapFrame:IsShown() then
        WorldMapFrame_Update()
    end
end

function MapReveal:Refresh()
    if WorldMapFrame and WorldMapFrame:IsShown() then
        WorldMapFrame_Update()
    end
end

--------------------------------------------------------------------------------
-- Map Scanner: enumerate all continents/zones, collect overlay data from
-- explored areas, discover new zones, and merge into the overlay database.
-- Usage: /nui mapscan  or  SFrames.MapReveal:ScanAllMaps()
--------------------------------------------------------------------------------
local scanFrame = nil
local scanQueue = {}
local scanIndex = 0
local scanRunning = false
local scanResults = {}
local scanNewZones = {}
local scanUpdatedZones = {}
local savedMapC, savedMapZ = 0, 0

local function ExtractOverlayName(fullPath)
    if not fullPath or fullPath == "" then return nil end
    local _, _, name = string.find(fullPath, "\\([^\\]+)$")
    return name and string.upper(name) or nil
end

local function ProcessScanZone()
    if scanIndex > table.getn(scanQueue) then
        MapReveal:FinishScan()
        return
    end

    local entry = scanQueue[scanIndex]
    SetMapZoom(entry.cont, entry.zone)

    local mapFile = GetMapInfo and GetMapInfo() or ""
    if mapFile == "" then
        scanIndex = scanIndex + 1
        return
    end

    local numOverlays = GetNumMapOverlays and GetNumMapOverlays() or 0
    if numOverlays > 0 then
        local overlays = {}
        for i = 1, numOverlays do
            local texName, texW, texH, offX, offY = GetMapOverlayInfo(i)
            if texName and texName ~= "" then
                local name = ExtractOverlayName(texName)
                if name then
                    table.insert(overlays, name .. ":" .. texW .. ":" .. texH .. ":" .. offX .. ":" .. offY)
                end
            end
        end

        if table.getn(overlays) > 0 then
            local db = GetOverlayDB()
            local existing = db and db[mapFile]

            if not existing then
                scanNewZones[mapFile] = overlays
                scanResults[mapFile] = { overlays = overlays, status = "new", count = table.getn(overlays) }
            else
                local existingIdx = BuildOverlayIndex(existing)
                local newEntries = 0
                for i = 1, table.getn(overlays) do
                    local _, _, oName = string.find(overlays[i], "^([^:]+):")
                    if oName and not existingIdx[string.upper(oName)] then
                        newEntries = newEntries + 1
                    end
                end

                if newEntries > 0 then
                    scanUpdatedZones[mapFile] = overlays
                    scanResults[mapFile] = {
                        overlays = overlays, status = "updated",
                        count = table.getn(existing) + newEntries,
                        oldCount = table.getn(existing)
                    }
                else
                    scanResults[mapFile] = { status = "ok", count = table.getn(existing) }
                end
            end
        end
    end

    scanIndex = scanIndex + 1
end

function MapReveal:FinishScan()
    scanRunning = false
    if scanFrame then
        scanFrame:SetScript("OnUpdate", nil)
    end

    if savedMapZ > 0 then
        SetMapZoom(savedMapC, savedMapZ)
    elseif savedMapC > 0 then
        SetMapZoom(savedMapC, 0)
    else
        if SetMapToCurrentZone then SetMapToCurrentZone() end
    end

    local cf = DEFAULT_CHAT_FRAME
    local newCount = 0
    local updCount = 0
    local newOverlayCount = 0

    for zone, overlays in pairs(scanNewZones) do
        newCount = newCount + 1
        scannedOverlays[zone] = overlays
    end
    for zone, overlays in pairs(scanUpdatedZones) do
        updCount = updCount + 1
        if not scannedOverlays[zone] then
            scannedOverlays[zone] = overlays
        else
            local existingIdx = BuildOverlayIndex(scannedOverlays[zone])
            for i = 1, table.getn(overlays) do
                local _, _, oName = string.find(overlays[i], "^([^:]+):")
                if oName and not existingIdx[string.upper(oName)] then
                    table.insert(scannedOverlays[zone], overlays[i])
                    existingIdx[string.upper(oName)] = table.getn(scannedOverlays[zone])
                    newOverlayCount = newOverlayCount + 1
                end
            end
        end
    end

    MergeScannedDataIntoDB()
    SavePersistedData()

    cf:AddMessage("|cffffb3d9[Nanami-UI]|r 地图扫描完成!")
    cf:AddMessage(string.format("  扫描了 |cff00ff00%d|r 个区域", table.getn(scanQueue)))

    if newCount > 0 then
        cf:AddMessage(string.format("  发现 |cff00ff00%d|r 个新区域 (已自动添加迷雾数据):", newCount))
        for zone, overlays in pairs(scanNewZones) do
            cf:AddMessage("    |cff00ffff" .. zone .. "|r (" .. table.getn(overlays) .. " 个覆盖层)")
        end
    end

    if updCount > 0 then
        cf:AddMessage(string.format("  更新了 |cffffff00%d|r 个区域 (发现更多已探索覆盖层):", updCount))
        for zone, info in pairs(scanResults) do
            if info.status == "updated" then
                cf:AddMessage("    |cffffff00" .. zone .. "|r (" .. (info.oldCount or 0) .. " -> " .. info.count .. ")")
            end
        end
    end

    if newCount == 0 and updCount == 0 then
        cf:AddMessage("  所有区域数据已是最新，未发现变动。")
    end

    cf:AddMessage("  数据已自动保存，下次登录无需重新扫描。")
    cf:AddMessage("  提示: 新发现的区域仅记录已探索区域的覆盖层，完全探索后再次扫描可获取完整数据。")

    if WorldMapFrame and WorldMapFrame:IsShown() then
        WorldMapFrame_Update()
    end
end

function MapReveal:ScanAllMaps()
    if scanRunning then
        SFrames:Print("地图扫描正在进行中...")
        return
    end

    local db = GetOverlayDB()
    if not db then
        SFrames:Print("MapReveal: 未找到覆盖层数据库，无法扫描。")
        return
    end

    scanRunning = true
    scanQueue = {}
    scanIndex = 1
    scanResults = {}
    scanNewZones = {}
    scanUpdatedZones = {}

    savedMapC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
    savedMapZ = GetCurrentMapZone and GetCurrentMapZone() or 0

    local numContinents = 0
    if GetMapContinents then
        local continents = { GetMapContinents() }
        numContinents = table.getn(continents)
    end

    for c = 1, numContinents do
        local zones = { GetMapZones(c) }
        for z = 1, table.getn(zones) do
            table.insert(scanQueue, { cont = c, zone = z, name = zones[z] or "" })
        end
    end

    SFrames:Print("开始扫描所有地图... (共 " .. table.getn(scanQueue) .. " 个区域)")

    if not scanFrame then
        scanFrame = CreateFrame("Frame")
    end

    local scanElapsed = 0
    scanFrame:SetScript("OnUpdate", function()
        scanElapsed = scanElapsed + (arg1 or 0)
        if scanElapsed < 0.02 then return end
        scanElapsed = 0

        if not scanRunning then return end
        local batch = 0
        while scanIndex <= table.getn(scanQueue) and batch < 5 do
            ProcessScanZone()
            batch = batch + 1
        end
        if scanIndex > table.getn(scanQueue) then
            MapReveal:FinishScan()
        end
    end)
end

function MapReveal:ExportScannedData()
    local cf = DEFAULT_CHAT_FRAME
    local hasData = false

    for zone, overlays in pairs(scannedOverlays) do
        hasData = true
        cf:AddMessage("|cffffb3d9[MapReveal Export]|r |cff00ffff" .. zone .. "|r = {")
        for _, entry in ipairs(overlays) do
            cf:AddMessage('    "' .. entry .. '",')
        end
        cf:AddMessage("}")
    end

    if not hasData then
        cf:AddMessage("|cffffb3d9[MapReveal]|r 没有扫描到的新数据可导出。先运行 /nui mapscan")
    end
end

function MapReveal:ShowStats()
    local cf = DEFAULT_CHAT_FRAME
    local db = GetOverlayDB()
    cf:AddMessage("|cffffb3d9[MapReveal]|r 覆盖层数据统计:")

    local dbZones, dbOverlays = 0, 0
    if db then
        for zone, data in pairs(db) do
            dbZones = dbZones + 1
            dbOverlays = dbOverlays + table.getn(data)
        end
    end
    cf:AddMessage(string.format("  数据库: |cff00ff00%d|r 个区域, |cff00ff00%d|r 个覆盖层", dbZones, dbOverlays))

    local scanZones, scanOverlays = 0, 0
    for zone, data in pairs(scannedOverlays) do
        scanZones = scanZones + 1
        scanOverlays = scanOverlays + table.getn(data)
    end
    cf:AddMessage(string.format("  已发现: |cff00ff00%d|r 个区域, |cff00ff00%d|r 个覆盖层 (通过扫描/浏览)", scanZones, scanOverlays))

    local savedZones = 0
    local gdb = GetGlobalDB()
    if type(gdb.MapRevealScanData) == "table" then
        for _ in pairs(gdb.MapRevealScanData) do
            savedZones = savedZones + 1
        end
    end
    cf:AddMessage(string.format("  已持久化: |cff00ff00%d|r 个区域", savedZones))

    cf:AddMessage("  提示: 打开世界地图浏览各区域可自动发现新覆盖层数据")
end

function MapReveal:ClearSavedData()
    local gdb = GetGlobalDB()
    gdb.MapRevealScanData = nil
    scannedOverlays = {}
    SFrames:Print("地图扫描数据已清除。重新加载UI后生效。")
end
