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

local GEAR_SIDE_LEFT_SLOT_NAMES = {
    HeadSlot = true,
    NeckSlot = true,
    ShoulderSlot = true,
    BackSlot = true,
    ChestSlot = true,
    WristSlot = true,
    SecondaryHandSlot = true,
}
local GEAR_ENCHANTABLE_SLOT_IDS = {
    [1] = true,
    [3] = true,
    [5] = true,
    [7] = true,
    [8] = true,
    [11] = true,
    [12] = true,
    [16] = true,
    [17] = true,
}
local GEAR_SOCKETABLE_SLOT_MAX = {
    [1] = 1,
    [2] = 2,
    [6] = 1,
    [9] = 1,
    [11] = 2,
    [12] = 2,
}
local GEAR_SIDE_MARKER_OFFSET_X = 9
local GEAR_SIDE_MARKER_TOP_OFFSET_Y = -1
local GEAR_SIDE_MARKER_GAP_Y = -2

local GearSocketEnchantRuntime = {
    enabled = false,
    hooksReady = false,
}

local function isCharacterFrameShown()
    local characterFrame = _G.CharacterFrame
    return characterFrame and characterFrame.IsShown and characterFrame:IsShown()
end

local cachedEmptySocketLines = nil

local function getGearSlotName(slotButton)
    local name = slotButton and slotButton.GetName and slotButton:GetName() or nil
    if type(name) ~= "string" or name == "" then
        return nil
    end

    return (name:gsub("^Character", ""))
end

local function isGearSlotOnLeft(slotName)
    return type(slotName) == "string" and GEAR_SIDE_LEFT_SLOT_NAMES[slotName] == true
end

local function getEmptySocketLines()
    if cachedEmptySocketLines then
        return cachedEmptySocketLines
    end

    local lines = {}
    for globalName, value in pairs(_G) do
        if type(globalName) == "string"
            and type(value) == "string"
            and globalName:find("EMPTY_SOCKET_", 1, true)
        then
            lines[#lines + 1] = value
        end
    end

    cachedEmptySocketLines = lines
    return lines
end

local function getItemIconTexture(itemLink)
    if type(itemLink) ~= "string" or itemLink == "" then
        return nil
    end

    if type(GetItemInfo) == "function" then
        local _, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemLink)
        if icon then
            return icon
        end
    end

    if C_Item and type(C_Item.GetItemInfoInstant) == "function" then
        local _, _, _, _, icon = C_Item.GetItemInfoInstant(itemLink)
        return icon
    end

    return nil
end

local function getInventoryItemGemLink(itemLink, gemIndex)
    if type(itemLink) ~= "string" or itemLink == "" then
        return nil
    end

    if C_Item and type(C_Item.GetItemGem) == "function" then
        local ok, _, gemLink = pcall(C_Item.GetItemGem, itemLink, gemIndex)
        if ok and gemLink then
            return gemLink
        end
    end

    if type(GetItemGem) == "function" then
        local ok, _, gemLink = pcall(GetItemGem, itemLink, gemIndex)
        if ok and gemLink then
            return gemLink
        end
    end

    return nil
end

local function getInventoryTooltipData(slotID)
    if not (C_TooltipInfo and type(C_TooltipInfo.GetInventoryItem) == "function") then
        return nil
    end

    local ok, tooltipData = pcall(C_TooltipInfo.GetInventoryItem, "player", slotID)
    if ok then
        return tooltipData
    end

    return nil
end

local function getGearSocketEnchantScanTooltip()
    local tooltip = _G.VaultloomGearSocketEnchantScanTooltip
    if tooltip then
        return tooltip
    end

    tooltip = CreateFrame("GameTooltip", "VaultloomGearSocketEnchantScanTooltip", nil, "GameTooltipTemplate")
    tooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
    return tooltip
end

local function getInventoryEnchantName(slotID)
    slotID = tonumber(slotID)
    if not slotID or slotID < 1 then
        return nil
    end

    local itemLink = type(GetInventoryItemLink) == "function" and GetInventoryItemLink("player", slotID) or nil
    if type(itemLink) ~= "string" or itemLink == "" then
        return nil
    end

    local enchantID = itemLink:match("item:%-?%d+:(%-?%d+)")
    if not enchantID or tonumber(enchantID) == nil or tonumber(enchantID) <= 0 then
        return nil
    end

    local tooltipData = C_TooltipInfo and type(C_TooltipInfo.GetHyperlink) == "function" and C_TooltipInfo.GetHyperlink(itemLink) or nil
    if tooltipData and type(tooltipData.lines) == "table" then
        for _, lineData in ipairs(tooltipData.lines) do
            local text = lineData and lineData.leftText or nil
            if type(text) == "string" and text ~= "" then
                local cleanText = text:gsub("|A:[^|]+|a", ""):gsub("%s+", " ")
                cleanText = cleanText:gsub("^%s+", ""):gsub("%s+$", "")
                if (text:find("Professions%-ChatIcon%-Quality%-Tier") or text:find("|A:Professions"))
                    and not text:find("Socket")
                    and not text:find("socket")
                    and cleanText ~= ""
                then
                    cleanText = cleanText:gsub("^Enchanted:%s*", "")
                    if cleanText ~= "" then
                        return cleanText
                    end
                end
            end
        end
    end

    local tooltip = getGearSocketEnchantScanTooltip()
    tooltip:ClearLines()
    tooltip:SetHyperlink(itemLink)

    for index = 1, tooltip:NumLines() do
        local line = _G["VaultloomGearSocketEnchantScanTooltipTextLeft" .. index]
        if line then
            local text = line:GetText()
            if type(text) == "string" and text ~= "" then
                local r, g, b = line:GetTextColor()
                if ((g or 0) > 0.90 and (r or 1) < 0.20 and (b or 1) < 0.20)
                    or text:find("Professions%-ChatIcon%-Quality%-Tier")
                then
                    if not text:find("Socket") and not text:find("socket") then
                        local cleanText = text:gsub("|A:[^|]+|a", ""):gsub("%s+", " ")
                        cleanText = cleanText:gsub("^%s+", ""):gsub("%s+$", "")
                        cleanText = cleanText:gsub("^Enchanted:%s*", "")
                        if cleanText ~= "" then
                            return cleanText
                        end
                    end
                end
            end
        end
    end

    return nil
end

local function getInventorySocketVisuals(slotID)
    slotID = tonumber(slotID)
    if not slotID or slotID < 1 then
        return {}
    end

    local itemLink = type(GetInventoryItemLink) == "function" and GetInventoryItemLink("player", slotID) or nil
    if not itemLink then
        return {}
    end

    local visuals = {}
    for gemIndex = 1, 4 do
        local gemLink = getInventoryItemGemLink(itemLink, gemIndex)
        if gemLink then
            visuals[#visuals + 1] = {
                texture = getItemIconTexture(gemLink),
                gemLink = gemLink,
                empty = false,
            }
        end
    end

    local emptySocketCount = 0
    local tooltipData = getInventoryTooltipData(slotID)
    if tooltipData and type(tooltipData.lines) == "table" then
        local emptySocketLines = getEmptySocketLines()
        for _, line in ipairs(tooltipData.lines) do
            local text = line and line.leftText or nil
            if type(text) == "string" and text ~= "" then
                for _, socketLine in ipairs(emptySocketLines) do
                    if text:find(socketLine, 1, true) then
                        emptySocketCount = emptySocketCount + 1
                        break
                    end
                end
            end
        end
    end

    for _ = 1, emptySocketCount do
        visuals[#visuals + 1] = {
            texture = "Interface\\ItemSocketingFrame\\UI-EmptySocket-Prismatic",
            gemLink = nil,
            empty = true,
        }
    end

    local maxSockets = tonumber(GEAR_SOCKETABLE_SLOT_MAX[slotID]) or 0
    if maxSockets > 0 and #visuals > maxSockets then
        while #visuals > maxSockets do
            table.remove(visuals)
        end
    end

    return visuals
end

local function isOffhandWeapon(unit, slotID)
    slotID = tonumber(slotID)
    if slotID ~= 17 then
        return false
    end

    local itemLink = type(GetInventoryItemLink) == "function" and GetInventoryItemLink(unit or "player", slotID) or nil
    if not itemLink or itemLink == "" then
        return false
    end

    local equipLoc = nil
    if C_Item and type(C_Item.GetItemInfoInstant) == "function" then
        local _, _, _, itemEquipLoc = C_Item.GetItemInfoInstant(itemLink)
        equipLoc = itemEquipLoc
    end

    if not equipLoc and type(GetItemInfo) == "function" then
        local _, _, _, _, _, _, _, _, itemEquipLoc = GetItemInfo(itemLink)
        equipLoc = itemEquipLoc
    end

    return equipLoc == "INVTYPE_WEAPON" or equipLoc == "INVTYPE_WEAPONOFFHAND"
end

local function canSlotBeEnchanted(slotID)
    slotID = tonumber(slotID)
    if not slotID or not GEAR_ENCHANTABLE_SLOT_IDS[slotID] then
        return false
    end

    local itemLink = type(GetInventoryItemLink) == "function" and GetInventoryItemLink("player", slotID) or nil
    if type(itemLink) ~= "string" or itemLink == "" then
        return false
    end

    local itemEquipLoc, itemClassID, itemSubClassID = nil, nil, nil
    local weaponClassID = (Enum and Enum.ItemClass and Enum.ItemClass.Weapon) or 2
    local armorClassID = (Enum and Enum.ItemClass and Enum.ItemClass.Armor) or 4
    if C_Item and type(C_Item.GetItemInfoInstant) == "function" then
        local _, _, _, equipLoc, _, classID, subClassID = C_Item.GetItemInfoInstant(itemLink)
        itemEquipLoc = equipLoc
        itemClassID = classID
        itemSubClassID = subClassID
    end

    if slotID == 17 then
        if itemClassID == weaponClassID then
            return true
        end
        if itemClassID == armorClassID then
            if itemSubClassID == 6 or itemEquipLoc == "INVTYPE_HOLDABLE" or itemSubClassID == 0 then
                return false
            end
        end
        return isOffhandWeapon("player", slotID)
    end

    return true
end

local function getInventoryEnchantStatus(slotID)
    slotID = tonumber(slotID)
    if not slotID or slotID < 1 then
        return false, nil
    end

    local itemLink = type(GetInventoryItemLink) == "function" and GetInventoryItemLink("player", slotID) or nil
    if not itemLink or itemLink == "" then
        return false, nil
    end

    local enchantID = itemLink:match("item:%-?%d+:(%-?%d+)")
    enchantID = tonumber(enchantID)
    if enchantID ~= nil and enchantID > 0 then
        return true, getInventoryEnchantName(slotID)
    end

    return false, nil
end

local function ensureGearSocketEnchantOverlay(slotButton)
    if not slotButton then
        return nil
    end

    local overlay = slotButton.VaultloomSocketEnchantOverlay
    if overlay then
        return overlay
    end

    overlay = CreateFrame("Frame", nil, slotButton)
    overlay:SetAllPoints(slotButton)
    overlay:SetFrameLevel(slotButton:GetFrameLevel() + 10)
    overlay:EnableMouse(false)

    overlay.socketHolders = {}
    for index = 1, 4 do
        local holder = CreateFrame("Button", nil, overlay, BACKDROP_TEMPLATE)
        holder:SetSize(14, 14)
        holder:EnableMouse(true)

        if holder.SetBackdrop then
            holder:SetBackdrop({
                bgFile = "Interface\\Buttons\\WHITE8X8",
                edgeFile = "Interface\\Buttons\\WHITE8X8",
                tile = false,
                edgeSize = 1,
                insets = { left = 0, right = 0, top = 0, bottom = 0 },
            })
            holder:SetBackdropColor(0.03, 0.03, 0.04, 0.88)
            holder:SetBackdropBorderColor(0.22, 0.22, 0.24, 0.90)
        end

        holder.icon = holder:CreateTexture(nil, "OVERLAY")
        holder.icon:SetPoint("TOPLEFT", 1, -1)
        holder.icon:SetPoint("BOTTOMRIGHT", -1, 1)
        holder.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        holder:SetScript("OnEnter", function(selfButton)
            GameTooltip:SetOwner(selfButton, "ANCHOR_RIGHT")
            if selfButton.vlGemLink then
                GameTooltip:SetHyperlink(selfButton.vlGemLink)
            else
                GameTooltip:AddLine(L.FEATURE_GEAR_SOCKET_ENCHANT_EMPTY_SOCKET or "Empty socket", 1.00, 0.32, 0.32, true)
                GameTooltip:AddLine(L.FEATURE_GEAR_SOCKET_ENCHANT_EMPTY_SOCKET_NOTE or "No gem inserted.", 0.88, 0.86, 0.82, true)
            end
            GameTooltip:Show()
        end)
        holder:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
        holder:Hide()
        overlay.socketHolders[index] = holder
    end

    overlay.enchantStatus = CreateFrame("Button", nil, overlay, BACKDROP_TEMPLATE)
    overlay.enchantStatus:SetSize(14, 14)
    overlay.enchantStatus:EnableMouse(true)
    if overlay.enchantStatus.SetBackdrop then
        overlay.enchantStatus:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 0, right = 0, top = 0, bottom = 0 },
        })
        overlay.enchantStatus:SetBackdropColor(0.03, 0.03, 0.04, 0.88)
        overlay.enchantStatus:SetBackdropBorderColor(0.22, 0.22, 0.24, 0.90)
    end
    overlay.enchantStatus.icon = overlay.enchantStatus:CreateTexture(nil, "OVERLAY")
    overlay.enchantStatus.icon:SetPoint("TOPLEFT", 1, -1)
    overlay.enchantStatus.icon:SetPoint("BOTTOMRIGHT", -1, 1)
    overlay.enchantStatus.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
    overlay.enchantStatus:SetScript("OnEnter", function(selfButton)
        GameTooltip:SetOwner(selfButton, "ANCHOR_RIGHT")
        if selfButton.vlEnchantName and selfButton.vlEnchantName ~= "" then
            GameTooltip:AddLine(selfButton.vlEnchantName, 0.20, 1.00, 0.45, true)
        elseif selfButton.vlMissing then
            GameTooltip:AddLine(L.FEATURE_GEAR_SOCKET_ENCHANT_MISSING or "Missing enchant", 1.00, 0.20, 0.20, true)
            GameTooltip:AddLine(L.FEATURE_GEAR_SOCKET_ENCHANT_MISSING_NOTE or "This item can be enchanted.", 0.88, 0.86, 0.82, true)
        end
        GameTooltip:Show()
    end)
    overlay.enchantStatus:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
    overlay.enchantStatus:Hide()

    slotButton.VaultloomSocketEnchantOverlay = overlay
    return overlay
end

function GearSocketEnchantRuntime:HideSlot(slotButton)
    local overlay = slotButton and slotButton.VaultloomSocketEnchantOverlay or nil
    if not overlay then
        return
    end

    overlay:Hide()
    for _, holder in ipairs(overlay.socketHolders or {}) do
        holder:Hide()
    end
    if overlay.enchantStatus then
        overlay.enchantStatus:Hide()
    end
end

function GearSocketEnchantRuntime:RefreshSlot(slotButton, slotName)
    if not slotButton then
        return
    end

    if self.enabled ~= true then
        self:HideSlot(slotButton)
        return
    end

    if not isCharacterFrameShown() then
        self:HideSlot(slotButton)
        return
    end

    local overlay = ensureGearSocketEnchantOverlay(slotButton)
    if not overlay then
        return
    end

    local slotID = tonumber(slotButton:GetID())
    local itemLink = slotID and type(GetInventoryItemLink) == "function" and GetInventoryItemLink("player", slotID) or nil
    if not itemLink then
        self:HideSlot(slotButton)
        return
    end

    slotName = slotName or getGearSlotName(slotButton)
    local placeLeft = isGearSlotOnLeft(slotName)

    local socketVisuals = getInventorySocketVisuals(slotID)
    local visibleSocketCount = 0
    for index, holder in ipairs(overlay.socketHolders or {}) do
        local visual = socketVisuals[index]
        if visual and visual.texture then
            holder.icon:SetTexture(visual.texture)
            if visual.empty then
                holder.icon:SetVertexColor(1, 1, 1, 1)
                if holder.SetBackdropBorderColor then
                    holder:SetBackdropColor(0.04, 0.04, 0.06, 0.92)
                    holder:SetBackdropBorderColor(0.58, 0.62, 0.76, 0.95)
                end
            else
                holder.icon:SetVertexColor(1, 1, 1, 1)
                if holder.SetBackdropBorderColor then
                    holder:SetBackdropColor(0.03, 0.03, 0.04, 0.88)
                    holder:SetBackdropBorderColor(0.22, 0.22, 0.24, 0.90)
                end
            end
            holder.vlGemLink = visual.gemLink
            holder:Show()
            visibleSocketCount = visibleSocketCount + 1
        else
            holder.vlGemLink = nil
            holder:Hide()
        end
    end

    local isEnchanted, enchantName = getInventoryEnchantStatus(slotID)
    local isEnchantable = canSlotBeEnchanted(slotID)
    local enchantShown = false
    if overlay.enchantStatus then
        overlay.enchantStatus:ClearAllPoints()
        if placeLeft then
            overlay.enchantStatus:SetPoint("TOPLEFT", slotButton, "TOPRIGHT", GEAR_SIDE_MARKER_OFFSET_X, GEAR_SIDE_MARKER_TOP_OFFSET_Y)
        else
            overlay.enchantStatus:SetPoint("TOPRIGHT", slotButton, "TOPLEFT", -GEAR_SIDE_MARKER_OFFSET_X, GEAR_SIDE_MARKER_TOP_OFFSET_Y)
        end
        if isEnchantable then
            overlay.enchantStatus.vlEnchantName = isEnchanted and enchantName or nil
            overlay.enchantStatus.vlMissing = not isEnchanted
            overlay.enchantStatus.icon:SetTexture(isEnchanted and "Interface\\RaidFrame\\ReadyCheck-Ready" or "Interface\\RaidFrame\\ReadyCheck-NotReady")
            if overlay.enchantStatus.SetBackdropBorderColor then
                if isEnchanted then
                    overlay.enchantStatus:SetBackdropColor(0.02, 0.08, 0.03, 0.92)
                    overlay.enchantStatus:SetBackdropBorderColor(0.20, 0.70, 0.28, 0.95)
                else
                    overlay.enchantStatus:SetBackdropColor(0.10, 0.03, 0.03, 0.92)
                    overlay.enchantStatus:SetBackdropBorderColor(0.86, 0.18, 0.18, 0.95)
                end
            end
            overlay.enchantStatus:Show()
            enchantShown = true
        else
            overlay.enchantStatus.vlEnchantName = nil
            overlay.enchantStatus.vlMissing = nil
            overlay.enchantStatus:Hide()
        end
    end

    for index, holder in ipairs(overlay.socketHolders or {}) do
        holder:ClearAllPoints()
        if index <= visibleSocketCount then
            if index == 1 then
                if enchantShown and overlay.enchantStatus and overlay.enchantStatus:IsShown() then
                    if placeLeft then
                        holder:SetPoint("TOPLEFT", overlay.enchantStatus, "BOTTOMLEFT", 0, GEAR_SIDE_MARKER_GAP_Y)
                    else
                        holder:SetPoint("TOPRIGHT", overlay.enchantStatus, "BOTTOMRIGHT", 0, GEAR_SIDE_MARKER_GAP_Y)
                    end
                else
                    if placeLeft then
                        holder:SetPoint("TOPLEFT", slotButton, "TOPRIGHT", GEAR_SIDE_MARKER_OFFSET_X, GEAR_SIDE_MARKER_TOP_OFFSET_Y)
                    else
                        holder:SetPoint("TOPRIGHT", slotButton, "TOPLEFT", -GEAR_SIDE_MARKER_OFFSET_X, GEAR_SIDE_MARKER_TOP_OFFSET_Y)
                    end
                end
            else
                local previousHolder = overlay.socketHolders[index - 1]
                if previousHolder then
                    if placeLeft then
                        holder:SetPoint("TOPLEFT", previousHolder, "BOTTOMLEFT", 0, GEAR_SIDE_MARKER_GAP_Y)
                    else
                        holder:SetPoint("TOPRIGHT", previousHolder, "BOTTOMRIGHT", 0, GEAR_SIDE_MARKER_GAP_Y)
                    end
                end
            end
        end
    end

    if visibleSocketCount > 0 or (overlay.enchantStatus and overlay.enchantStatus:IsShown()) then
        overlay:Show()
    else
        overlay:Hide()
    end
end

function GearSocketEnchantRuntime:RefreshAll()
    if self.enabled ~= true or not isCharacterFrameShown() then
        return
    end

    for _, slotName in ipairs(ns:GetCharacterGearSlotNames()) do
        self:RefreshSlot(ns:GetCharacterGearSlotButton(slotName), slotName)
    end
end

function GearSocketEnchantRuntime:HideAll()
    for _, slotName in ipairs(ns:GetCharacterGearSlotNames()) do
        self:HideSlot(ns:GetCharacterGearSlotButton(slotName))
    end
end

function GearSocketEnchantRuntime:EnsureHooks()
    if self.hooksReady then
        return
    end

    local paperDollReady = type(PaperDollItemSlotButton_Update) == "function"
    local characterFrame = _G.CharacterFrame
    if not paperDollReady and not characterFrame then
        return
    end

    self.hooksReady = true

    if type(hooksecurefunc) == "function" and paperDollReady then
        hooksecurefunc("PaperDollItemSlotButton_Update", function(button)
            if button and button.GetName and tostring(button:GetName() or ""):match("^Character") then
                GearSocketEnchantRuntime:RefreshSlot(button)
            end
        end)
    end

    if characterFrame and characterFrame.HookScript then
        characterFrame:HookScript("OnShow", function()
            GearSocketEnchantRuntime:RefreshAll()
        end)
    end
end

function GearSocketEnchantRuntime:SetEnabled(enabled)
    enabled = enabled == true
    self.enabled = enabled

    if not self.eventFrame then
        self.eventFrame = CreateFrame("Frame")
        self.eventFrame:SetScript("OnEvent", function(_, event, ...)
            if event == "ADDON_LOADED" then
                local loadedName = ...
                if loadedName == "Blizzard_CharacterUI" then
                    GearSocketEnchantRuntime:EnsureHooks()
                    GearSocketEnchantRuntime:RefreshAll()
                end
                return
            end

            if event == "UNIT_INVENTORY_CHANGED" then
                local unit = ...
                if unit ~= "player" then
                    return
                end
            end

            GearSocketEnchantRuntime:RefreshAll()
        end)
    end

    self.eventFrame:UnregisterAllEvents()

    if not enabled then
        self:HideAll()
        return
    end

    self:EnsureHooks()
    self.eventFrame:RegisterEvent("ADDON_LOADED")
    self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    self.eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
    self.eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
    self.eventFrame:RegisterEvent("BAG_UPDATE_DELAYED")
    self:RefreshAll()
end

ns:RegisterFeatureDefinition("gear_socket_enchant", {
    id = "gear_socket_enchant",
    order = 25,
    category = "character",
    title = function() return L.FEATURE_GEAR_SOCKET_ENCHANT_TITLE or "Gear Sockets & Enchants" end,
    description = function() return L.FEATURE_GEAR_SOCKET_ENCHANT_DESCRIPTION or "Shows socket icons and missing enchant markers next to your gear slots." end,
    note = function() return L.FEATURE_GEAR_SOCKET_ENCHANT_NOTE or "Only in the character window." end,
    previewLabel = function() return L.FEATURE_GEAR_SOCKET_ENCHANT_PREVIEW_LABEL or "Gear Side Markers" end,
    previewNote = function() return L.FEATURE_GEAR_SOCKET_ENCHANT_PREVIEW_NOTE or "Filled sockets show their gem icons, empty sockets glow red, and missing enchants get a red x." end,
    requiresReload = false,
    apply = function(isEnabled)
        GearSocketEnchantRuntime:SetEnabled(isEnabled == true)
    end,
})
