--[[
    The main collection: one named tameable rare per entry, with a field
    dossier on the right. Regions filter the hunt; zones organise the results.
]]

local _, ns = ...

local RareCatalogPanel = ns.NewModule("RareCatalogPanel")
ns.UI = ns.UI or {}
ns.UI.RareCatalogPanel = RareCatalogPanel

local Theme = ns.UI.Theme
local ALL_REGIONS = 0
local LIST_WIDTH = 430
local ROUTE_COLOR = { r = 0.25, g = 0.91, b = 0.80 }
local ZONE_COLOR = { r = 0.95, g = 0.69, b = 0.22 }
local UNKNOWN_COLOR = { r = 0.58, g = 0.55, b = 0.51 }
local CAPTURED_COLOR = { r = 0.35, g = 0.95, b = 0.62 }
local KILLED_COLOR = { r = 0.96, g = 0.38, b = 0.24 }
local BOTH_COLOR = { r = 1.00, g = 0.76, b = 0.24 }

local function MapName(mapID)
    if not mapID then return ns.L["DETAIL_ZONE_UNKNOWN"] end
    local info = C_Map and C_Map.GetMapInfo and C_Map.GetMapInfo(mapID)
    return info and info.name or ns.L["RARE_CATALOG_MAP_FORMAT"]:format(mapID)
end

local function FamilyName(record)
    local family = record.family and ns.Data.API:GetFamily(record.family)
    return family and ns.L[family.nameKey] or ns.L["RARE_CATALOG_FAMILY_UNKNOWN"]
end

local function OutcomePresentation(npcID)
    local outcome = ns.Collection.RareState:GetOutcome(npcID)
    if outcome == "both" then
        return ns.L["RARE_CATALOG_CAPTURED_AND_KILLED"], BOTH_COLOR, true
    elseif outcome == "captured" then
        return ns.L["RARE_CATALOG_CAPTURED"], CAPTURED_COLOR, true
    elseif outcome == "killed" then
        return ns.L["RARE_CATALOG_KILLED"], KILLED_COLOR, true
    end
    return nil, nil, false
end

local function OpenRareRoute(record)
    local locations = ns.Data.RareCatalog:GetLocations(record.npcID, record.mapID)
    if #locations == 0 then
        ns.Print(ns.L["NAV_NO_EXACT_LOCATIONS"])
        return false
    end
    local navigation = ns.Navigation.NativeWaypoint
    if navigation:IsHuntingCreature(record.npcID) then
        navigation:Clear()
        return false
    end

    local selected = locations[1]
    local bestDistance
    for _, location in ipairs(locations) do
        local playerPosition = C_Map.GetPlayerMapPosition(location.mapID, "player")
        if playerPosition then
            local distance = (playerPosition.x - location.x) ^ 2
                + (playerPosition.y - location.y) ^ 2
            if not bestDistance or distance < bestDistance then
                selected, bestDistance = location, distance
            end
        end
    end
    if OpenWorldMap then
        OpenWorldMap(record.mapID)
    elseif ToggleWorldMap then
        ToggleWorldMap()
    end
    C_Timer.After(0, function()
        if WorldMapFrame and WorldMapFrame.SetMapID then
            WorldMapFrame:SetMapID(record.mapID)
        end
        ns.Navigation.SearchAreaMap:ShowRoute("npc:" .. record.npcID)
    end)
    return navigation:Select(selected.mapID, selected.x, selected.y,
        record.name, MapName(selected.mapID), nil, locations, record.npcID)
end

local function CreateDetail(parent)
    local detail = { record = nil }

    detail.Empty = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    detail.Empty:SetPoint("CENTER", parent, "CENTER", 0, 20)
    detail.Empty:SetWidth(360)
    detail.Empty:SetJustifyH("CENTER")
    detail.Empty:SetText(ns.L["RARE_CATALOG_SELECT"])
    detail.Empty:SetTextColor(0.58, 0.56, 0.52)

    detail.Number = parent:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
    detail.Number:SetPoint("TOPLEFT", parent, "TOPLEFT", 18, -14)

    detail.Name = parent:CreateFontString(nil, "ARTWORK")
    detail.Name:SetFont("Fonts\\MORPHEUS.TTF", 25, "OUTLINE")
    detail.Name:SetPoint("TOPLEFT", detail.Number, "BOTTOMLEFT", 0, -2)
    detail.Name:SetPoint("RIGHT", parent, "RIGHT", -18, 0)
    detail.Name:SetJustifyH("LEFT")
    detail.Name:SetTextColor(0.96, 0.79, 0.38)

    detail.Family = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    detail.Family:SetPoint("TOPLEFT", detail.Name, "BOTTOMLEFT", 0, -4)
    detail.Family:SetTextColor(0.35, 0.82, 0.76)

    local modelHost = Theme:CreatePanel(parent, Theme.color.panel, Theme.color.line)
    modelHost:SetPoint("TOPLEFT", parent, "TOPLEFT", 18, -88)
    modelHost:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -18, -88)
    -- The dossier shares a compact journal page with the list. 180 px leaves
    -- a useful model stage without pushing field notes into the action bar.
    modelHost:SetHeight(180)
    detail.Model = ns.UI.ModelViewer:Create(modelHost)

    detail.Status = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    detail.Status:SetPoint("TOPLEFT", modelHost, "BOTTOMLEFT", 4, -14)

    detail.Info = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    detail.Info:SetPoint("TOPLEFT", detail.Status, "BOTTOMLEFT", 0, -8)
    detail.Info:SetPoint("RIGHT", parent, "RIGHT", -22, 0)
    detail.Info:SetJustifyH("LEFT")
    detail.Info:SetJustifyV("TOP")
    detail.Info:SetSpacing(4)
    detail.Info:SetTextColor(0.76, 0.73, 0.67)

    detail.Track = CreateFrame("Button", nil, parent, "BackdropTemplate")
    detail.Track:SetSize(190, 30)
    detail.Track:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 18, 16)
    detail.Track:SetNormalFontObject(GameFontNormal)
    Theme:ApplyButton(detail.Track, true)
    detail.Track:SetScript("OnClick", function()
        if detail.record then OpenRareRoute(detail.record) end
    end)

    detail.Capture = CreateFrame("Button", nil, parent, "BackdropTemplate")
    detail.Capture:SetSize(190, 30)
    detail.Capture:SetPoint("LEFT", detail.Track, "RIGHT", 10, 0)
    detail.Capture:SetNormalFontObject(GameFontNormal)
    Theme:ApplyButton(detail.Capture, false)
    detail.Capture:SetScript("OnClick", function()
        if not detail.record then return end
        ns.Collection.RareState:SetCaptured(detail.record.npcID,
            not ns.Collection.RareState:IsCaptured(detail.record.npcID))
    end)

    function detail:ShowRecord(record)
        self.record = record
        self.Empty:Hide()
        self.Number:SetText(("NPC %d"):format(record.npcID))
        self.Name:SetText(record.name)
        self.Family:SetText(FamilyName(record))
        local captured = ns.Collection.RareState:IsCaptured(record.npcID)
        local outcomeText, outcomeColor, completed = OutcomePresentation(record.npcID)
        local locationCount = #(record.locations or {})
        self.Model:SetDisplayID(record.displayIDs and record.displayIDs[1])
        self.Model:SetDiscovered(completed)
        self.Status:SetText(outcomeText or ns.L["DETAIL_UNDISCOVERED"])
        self.Status:SetTextColor(outcomeColor and outcomeColor.r or 0.72,
            outcomeColor and outcomeColor.g or 0.72,
            outcomeColor and outcomeColor.b or 0.72)
        self.Info:SetText(ns.L["RARE_CATALOG_DETAIL_FORMAT"]:format(
            MapName(record.mapID),
            record.specialization and ns.L["SPEC_" .. record.specialization]
                or ns.L["RARE_CATALOG_FAMILY_UNKNOWN"],
            locationCount))
        local hunting = ns.Navigation.NativeWaypoint:IsHuntingCreature(record.npcID)
        self.Track:SetText(locationCount == 0 and ns.L["HUNT_NO_ROUTE"]
            or (hunting and ns.L["BUTTON_HUNT_STOP"]
                or ns.L["BUTTON_HUNT_START"]))
        self.Track:SetAlpha(locationCount > 0 and 1 or 0.45)
        self.Track:EnableMouse(locationCount > 0)
        self.Capture:SetText(captured and ns.L["RARE_CATALOG_UNMARK"]
            or ns.L["RARE_CATALOG_MARK"])
    end

    local function SetVisible(visible)
        for _, region in ipairs({ detail.Number, detail.Name, detail.Family,
            modelHost, detail.Status, detail.Info, detail.Track, detail.Capture }) do
            region:SetShown(visible)
        end
        detail.Empty:SetShown(not visible)
    end
    detail.SetVisible = SetVisible
    SetVisible(false)
    return detail
end

function RareCatalogPanel:Create(parent)
    local panel = { selectedRegionID = ALL_REGIONS, recordsByKey = {} }

    local heading = parent:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    heading:SetPoint("TOPLEFT", parent, "TOPLEFT", 12, -8)
    heading:SetText(ns.L["RARE_CATALOG_TITLE"])
    heading:SetTextColor(Theme.color.gold[1], Theme.color.gold[2], Theme.color.gold[3])

    local summary = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    summary:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -5)
    summary:SetPoint("RIGHT", parent, "RIGHT", -12, 0)
    summary:SetWordWrap(false)
    summary:SetTextColor(Theme.color.muted[1], Theme.color.muted[2], Theme.color.muted[3])

    local search = CreateFrame("EditBox", nil, parent, "SearchBoxTemplate")
    search:SetSize(330, 28)
    search:SetPoint("TOPLEFT", summary, "BOTTOMLEFT", 0, -12)
    search:SetAutoFocus(false)
    search:SetMaxLetters(80)
    search:SetFontObject(GameFontHighlightSmall)
    search:SetTextColor(Theme.color.text[1], Theme.color.text[2],
        Theme.color.text[3], 1)
    if search.Instructions then
        search.Instructions:SetText(ns.L["FILTER_SEARCH_PLACEHOLDER"])
        search.Instructions:SetTextColor(Theme.color.muted[1],
            Theme.color.muted[2], Theme.color.muted[3], 0.90)
    end
    panel.search = search

    local regionButton = CreateFrame("Button", nil, parent, "BackdropTemplate")
    regionButton:SetSize(300, 28)
    regionButton:SetPoint("LEFT", search, "RIGHT", 12, 0)
    regionButton:SetNormalFontObject(GameFontNormal)
    Theme:ApplyButton(regionButton, false)
    regionButton.MenuArrow = Theme:CreateMenuArrow(regionButton)

    local body = CreateFrame("Frame", nil, parent)
    body:SetPoint("TOPLEFT", search, "BOTTOMLEFT", -4, -10)
    body:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)

    local listShell = Theme:CreatePanel(body, Theme.color.ink, Theme.color.line)
    listShell:SetPoint("TOPLEFT", body, "TOPLEFT", 0, 0)
    listShell:SetPoint("BOTTOMLEFT", body, "BOTTOMLEFT", 0, 0)
    listShell:SetWidth(LIST_WIDTH)
    local listHost = CreateFrame("Frame", nil, listShell)
    listHost:SetPoint("TOPLEFT", listShell, "TOPLEFT", 6, -6)
    listHost:SetPoint("BOTTOMRIGHT", listShell, "BOTTOMRIGHT", -4, 6)

    local detailShell = Theme:CreatePanel(body, Theme.color.panel, Theme.color.line)
    detailShell:SetPoint("TOPLEFT", listShell, "TOPRIGHT", 10, 0)
    detailShell:SetPoint("BOTTOMRIGHT", body, "BOTTOMRIGHT", 0, 0)
    panel.detail = CreateDetail(detailShell)

    panel.list = ns.UI.PetList:Create(listHost, {
        onSelect = function(key)
            local record = panel.recordsByKey[key]
            if record then
                panel.detail:SetVisible(true)
                panel.detail:ShowRecord(record)
            end
        end,
    })

    panel.regions = ns.Collection.RegionProgress:GetRegions()
    panel.regionIndex = 0

    function panel:SetRegion(regionID)
        self.selectedRegionID = regionID or ALL_REGIONS
        self.regionIndex = 0
        local label = ns.L["RARE_CATALOG_ALL_REGIONS"]
        for index, region in ipairs(self.regions) do
            if region.id == self.selectedRegionID then
                self.regionIndex, label = index, region.name
                break
            end
        end
        regionButton:SetText(label)
        self:Refresh()
    end

    regionButton:SetScript("OnClick", function(self)
        if MenuUtil and MenuUtil.CreateContextMenu then
            MenuUtil.CreateContextMenu(self, function(_, root)
                root:CreateRadio(ns.L["RARE_CATALOG_ALL_REGIONS"],
                    function() return panel.selectedRegionID == ALL_REGIONS end,
                    function() panel:SetRegion(ALL_REGIONS) end)
                for _, region in ipairs(panel.regions) do
                    root:CreateRadio(region.name,
                        function(data) return panel.selectedRegionID == data.id end,
                        function(data) panel:SetRegion(data.id) end, region)
                end
            end)
        elseif #panel.regions > 0 then
            panel:SetRegion(panel.regions[panel.regionIndex % #panel.regions + 1].id)
        end
    end)

    function panel:FocusRare(npcID)
        local record = ns.Data.RareCatalog:Get(npcID)
        if not record then return false end
        self:SetRegion(ALL_REGIONS)
        search:SetText(record.name)
        local key = "npc:" .. npcID
        self.list:SetSelected(key)
        self.detail:SetVisible(true)
        self.detail:ShowRecord(record)
        return true
    end

    function panel:Refresh()
        local query = search:GetText():lower()
        local matches = {}
        wipe(self.recordsByKey)
        for _, record in ipairs(ns.Data.RareCatalog:GetAll()) do
            local zoneName, familyName = MapName(record.mapID), FamilyName(record)
            local region = ns.Collection.RegionProgress:GetRegionForMap(record.mapID)
            local inRegion = self.selectedRegionID == ALL_REGIONS
                or (region and region.id == self.selectedRegionID)
            local inSearch = query == "" or record.name:lower():find(query, 1, true)
                or zoneName:lower():find(query, 1, true)
                or familyName:lower():find(query, 1, true)
            if inRegion and inSearch then
                matches[#matches + 1] = { record = record,
                    zoneName = zoneName, familyName = familyName }
            end
        end
        table.sort(matches, function(a, b)
            if a.zoneName ~= b.zoneName then return a.zoneName < b.zoneName end
            return a.record.name < b.record.name
        end)
        local elements, previousZone = {}, nil
        for _, match in ipairs(matches) do
            if match.zoneName ~= previousZone then
                previousZone = match.zoneName
                elements[#elements + 1] = { kind = "header", name = previousZone }
            end
            local record = match.record
            local key = "npc:" .. record.npcID
            local locationCount = #(record.locations or {})
            local outcomeText, outcomeColor, completed =
                OutcomePresentation(record.npcID)
            self.recordsByKey[key] = record
            elements[#elements + 1] = {
                name = record.name, subtitle = match.familyName,
                portraitDisplayID = record.displayIDs and record.displayIDs[1],
                icon = "Interface\\Icons\\Ability_Hunter_BeastCall",
                desaturated = not completed, selectionID = key,
                statusText = outcomeText
                    or (locationCount > 0 and ns.L["RARE_CATALOG_TRACES"]:format(locationCount)
                        or (record.mapID and ns.L["RARE_CATALOG_ZONE_ONLY"]
                            or ns.L["DETAIL_LOCATION_UNKNOWN"])),
                statusColor = outcomeColor
                    or (locationCount > 0 and ROUTE_COLOR
                        or (record.mapID and ZONE_COLOR or UNKNOWN_COLOR)),
                catalogText = tostring(record.npcID),
            }
        end
        local progressSet, selectedRegion
        if self.selectedRegionID ~= ALL_REGIONS then
            for _, region in ipairs(self.regions) do
                if region.id == self.selectedRegionID then
                    progressSet = region.npcIDs
                    selectedRegion = region
                    break
                end
            end
        end
        local completed, total = ns.Collection.RareState:GetProgress(progressSet)
        local progressText = selectedRegion
            and ns.L["RARE_CATALOG_REGION_COMPLETED"]:format(
                selectedRegion.name, completed, total)
            or ns.L["RARE_CATALOG_GLOBAL_COMPLETED"]:format(
                completed, total)
        summary:SetText(ns.L["RARE_CATALOG_SUMMARY"]:format(
            #matches, #(ns.Data.RareCatalog:GetAll())) .. "  ·  " .. progressText)
        self.list:SetElements(elements)
        if self.detail.record then self.detail:ShowRecord(self.detail.record) end
    end

    -- SearchBoxTemplate owns OnTextChanged to hide its placeholder and manage
    -- the clear button. Hooking preserves that behaviour; replacing the script
    -- left "Buscar" painted underneath the text the player typed.
    search:HookScript("OnTextChanged", function() panel:Refresh() end)
    ns.Events:Subscribe("RARE_COLLECTION_CHANGED", panel, function() panel:Refresh() end)
    ns.Events:Subscribe("NAVIGATION_CHANGED", panel, function()
        if panel.detail.record then panel.detail:ShowRecord(panel.detail.record) end
    end)
    panel:SetRegion(ALL_REGIONS)
    return panel
end
