local addonName, ZL = ...

-- Blizzard's hover-highlight textures are full-map composites and cannot be
-- stacked to represent many simultaneous states. Zone Locked instead builds a
-- cached ownership raster from GetMapInfoAtPosition and renders each zone as
-- its own set of horizontal spans with explicit coastline/border lines.
local PinMixin = CreateFromMixins(MapCanvasPinMixin)
local ProviderMixin = CreateFromMixins(MapCanvasDataProviderMixin)

local COLORS = {
    locked = { 0.90, 0.06, 0.05, 0.24 },
    complete = { 0.04, 0.78, 0.18, 0.25 },
    current = { 0.02, 0.48, 1.00, 0.32 },
    border = { 0.03, 0.025, 0.02, 0.82 },
}

function PinMixin:OnLoad()
    self:UseFrameLevelType("PIN_FRAME_LEVEL_AREA_POI")
    self:SetScalingLimits(1, 1, 1.25)
end

function PinMixin:OnAcquired(mapID, state, x, y, width, height)
    self.mapID, self.state = mapID, state
    self:SetPosition(x, y)
    self:SetSize(math.max(1, width), math.max(1, height))
    local color = COLORS[state] or COLORS.locked
    self.Texture:SetColorTexture(color[1], color[2], color[3], color[4])
    self:Show()
end

function PinMixin:OnReleased()
    self.mapID, self.state = nil, nil
end

function ProviderMixin:RemoveAllData()
    self:GetMap():RemoveAllPinsByTemplate("ZoneLockedMapStatePin")
end

local function NormalizeZone(mapID, zoneSet)
    while mapID do
        if zoneSet[mapID] then return mapID end
        local info = C_Map.GetMapInfo(mapID)
        mapID = info and info.parentMapID
    end
end

function ZL:GetMapRaster(continentID, expansionKey)
    self.MapRasterCache = self.MapRasterCache or {}
    local faction = UnitFactionGroup("player") or "Neutral"
    local _, _, raceID = UnitRace("player")
    local cacheKey = table.concat({ continentID, expansionKey or "none", faction, raceID or 0 }, ":")
    if self.MapRasterCache[cacheKey] then return self.MapRasterCache[cacheKey] end

    local columns, rows = 96, 60
    local zoneSet, cells = {}, {}
    for _, mapID in ipairs(self:GetContinentZoneIDs(continentID, expansionKey)) do zoneSet[mapID] = true end
    for row = 1, rows do
        cells[row] = {}
        for column = 1, columns do
            local info = C_Map.GetMapInfoAtPosition and C_Map.GetMapInfoAtPosition(
                continentID, (column - 0.5) / columns, (row - 0.5) / rows)
            cells[row][column] = NormalizeZone(info and info.mapID, zoneSet)
        end
    end
    local raster = { columns = columns, rows = rows, cells = cells }
    self.MapRasterCache[cacheKey] = raster
    return raster
end

local function ZoneState(mapID, activeMapID, unlocked)
    if mapID == activeMapID then return "current" end
    local position = ZL:GetRoutePosition(mapID)
    if position and position <= unlocked and ZL:IsZoneComplete(mapID) then return "complete" end
    return "locked"
end

local function AddHorizontalBorders(map, raster, canvasWidth)
    local cellWidth = canvasWidth / raster.columns
    for row = 1, raster.rows - 1 do
        local column = 1
        while column <= raster.columns do
            local top, bottom = raster.cells[row][column], raster.cells[row + 1][column]
            if top ~= bottom and (top or bottom) then
                local first = column
                repeat
                    column = column + 1
                    if column > raster.columns then break end
                    top, bottom = raster.cells[row][column], raster.cells[row + 1][column]
                until top == bottom or (not top and not bottom)
                local count = column - first
                map:AcquirePin("ZoneLockedMapStatePin", nil, "border",
                    ((first - 1) + count / 2) / raster.columns, row / raster.rows,
                    math.max(1, count * cellWidth + 1), 1.4)
            else
                column = column + 1
            end
        end
    end
end

local function AddVerticalBorders(map, raster, canvasHeight)
    local cellHeight = canvasHeight / raster.rows
    for column = 1, raster.columns - 1 do
        local row = 1
        while row <= raster.rows do
            local left, right = raster.cells[row][column], raster.cells[row][column + 1]
            if left ~= right and (left or right) then
                local first = row
                repeat
                    row = row + 1
                    if row > raster.rows then break end
                    left, right = raster.cells[row][column], raster.cells[row][column + 1]
                until left == right or (not left and not right)
                local count = row - first
                map:AcquirePin("ZoneLockedMapStatePin", nil, "border",
                    column / raster.columns, ((first - 1) + count / 2) / raster.rows,
                    1.4, math.max(1, count * cellHeight + 1))
            else
                row = row + 1
            end
        end
    end
end

function ProviderMixin:RefreshAllData()
    self:RemoveAllData()
    if not ZL_Saved.settings.mapOverlay then return end
    local map = self:GetMap()
    local viewedMapID = map and map:GetMapID()
    if not viewedMapID or not ZL_Saved or #(ZL_Saved.route or {}) == 0 then return end
    local continentID, expansionKey = ZL:GetMapContinentID(viewedMapID)
    if continentID ~= viewedMapID or not expansionKey or not ZL:IsExpansionEnabled(expansionKey) then return end

    local canvas = map:GetCanvasContainer()
    local canvasWidth, canvasHeight = canvas:GetWidth(), canvas:GetHeight()
    if canvasWidth <= 1 or canvasHeight <= 1 then return end
    local raster = ZL:GetMapRaster(continentID, expansionKey)
    local cellWidth, cellHeight = canvasWidth / raster.columns, canvasHeight / raster.rows
    local activeMapID, unlocked = ZL:GetActiveZone(), ZL:GetUnlockedIndex()

    for row = 1, raster.rows do
        local column = 1
        while column <= raster.columns do
            local mapID = raster.cells[row][column]
            if mapID then
                local first = column
                repeat
                    column = column + 1
                until column > raster.columns or raster.cells[row][column] ~= mapID
                local count = column - first
                map:AcquirePin("ZoneLockedMapStatePin", mapID, ZoneState(mapID, activeMapID, unlocked),
                    ((first - 1) + count / 2) / raster.columns, (row - 0.5) / raster.rows,
                    count * cellWidth + 0.7, cellHeight + 0.7)
            else
                column = column + 1
            end
        end
    end
    AddHorizontalBorders(map, raster, canvasWidth)
    AddVerticalBorders(map, raster, canvasHeight)
end

function ZL:CreateWorldMapOverlay()
    if not WorldMapFrame or self.WorldMapProvider then return end
    local pool = CreateUnsecuredRegionPoolInstance and CreateUnsecuredRegionPoolInstance("ZoneLockedMapStatePin") or
        CreateFramePool("Frame")
    pool.parent = WorldMapFrame:GetCanvas()
    pool.createFunc = function()
        local pin = Mixin(CreateFrame("Frame", nil, WorldMapFrame:GetCanvas()), PinMixin)
        pin:SetSize(4, 4)
        pin:EnableMouse(false)
        pin.Texture = pin:CreateTexture(nil, "BACKGROUND")
        pin.Texture:SetAllPoints()
        pin:OnLoad()
        return pin
    end
    pool.resetFunc = function(_, pin)
        pin:Hide()
        pin:ClearAllPoints()
        pin:OnReleased()
        pin.pinTemplate, pin.owningMap = nil, nil
    end
    pool.creationFunc, pool.resetterFunc = pool.createFunc, pool.resetFunc
    WorldMapFrame.pinPools["ZoneLockedMapStatePin"] = pool
    self.WorldMapProvider = ProviderMixin
    WorldMapFrame:AddDataProvider(self.WorldMapProvider)
end

function ZL:RefreshWorldMapOverlay()
    if self.WorldMapProvider and not ZL_Saved.settings.mapOverlay and self.WorldMapProvider.RemoveAllData then
        self.WorldMapProvider:RemoveAllData()
    elseif WorldMapFrame and WorldMapFrame:IsShown() and self.WorldMapProvider and self.WorldMapProvider.RefreshAllData then
        self.WorldMapProvider:RefreshAllData()
    end
end
