-- ============================================================
-- FILE: Nameplates_ColorState.lua
-- PURPOSE: Optional nameplate color-state layer. Adds settings
--   controls for player-facing nameplate color categories and
--   applies those colors without editing Nameplates.lua.
-- HOW IT WORKS:
--   1. Ensures MidnightUISettings.Nameplates.colorState defaults.
--   2. Appends color controls to the existing Nameplates settings
--      page after Settings_UI.lua has built it.
--   3. Watches visible nameplates and repaints MidnightUI's custom
--      health/threat bars after the main nameplate updater runs.
--   4. If a refresh is requested during restricted combat moments,
--      it is marked pending and replayed when combat ends.
-- DEPENDS ON:
--   MidnightUISettings.Nameplates
--   MidnightUI_Settings.Controls
--   C_NamePlate, UnitReaction, UnitThreatSituation
-- EXPORTS:
--   _G.MidnightUI_Nameplates_ColorState
--   _G.MidnightUI_ApplyNameplateColorStates()
-- OWNED SAVEDVARIABLES:
--   MidnightUISettings.Nameplates.colorState
-- EVENTS REGISTERED:
--   ADDON_LOADED, NAME_PLATE_UNIT_ADDED, NAME_PLATE_UNIT_REMOVED,
--   PLAYER_TARGET_CHANGED, UNIT_THREAT_SITUATION_UPDATE,
--   UNIT_THREAT_LIST_UPDATE, UNIT_TARGET, PLAYER_REGEN_ENABLED
-- FRAMES CREATED:
--   One event frame; settings controls appended to Nameplates page.
-- GOTCHAS:
--   This module intentionally does not change Nameplates.lua. It
--   discovers the existing custom entry on each visible plate and
--   reapplies color choices after the main nameplate refresh.
-- ============================================================

local M = _G.MidnightUI_Nameplates_ColorState or {}
_G.MidnightUI_Nameplates_ColorState = M

local W8 = "Interface\\Buttons\\WHITE8X8"
local pairs, ipairs, type, tostring, tonumber, math, string, pcall =
      pairs, ipairs, type, tostring, tonumber, math, string, pcall

local DEFAULTS = {
    enabled = false,
    health = {
        target         = { 1.00, 0.84, 0.20, 1.0 },
        enemyPlayer    = { 0.85, 0.22, 0.22, 1.0 },
        hostileCombat  = { 0.72, 0.16, 0.14, 1.0 },
        hostileIdle    = { 0.55, 0.12, 0.12, 1.0 },
        elite          = { 0.78, 0.36, 0.12, 1.0 },
        neutral        = { 0.82, 0.72, 0.28, 1.0 },
        friendlyPlayer = { 0.28, 0.56, 0.95, 1.0 },
        friendlyNpc    = { 0.22, 0.68, 0.30, 1.0 },
        tapped         = { 0.42, 0.42, 0.46, 1.0 },
    },
    threat = {
        tankSafe   = { 0.20, 0.78, 0.30, 1.0 },
        tankWarn   = { 0.95, 0.72, 0.18, 1.0 },
        tankDanger = { 0.85, 0.18, 0.16, 1.0 },
        dpsSafe    = { 0.20, 0.78, 0.30, 1.0 },
        dpsWarn    = { 0.95, 0.72, 0.18, 1.0 },
        dpsDanger  = { 0.85, 0.18, 0.16, 1.0 },
        noData     = { 0.58, 0.58, 0.64, 1.0 },
    },
}

local HEALTH_ROWS = {
    { key = "target",         label = "Current Target" },
    { key = "enemyPlayer",    label = "Enemy Player" },
    { key = "hostileCombat",  label = "Hostile Mob: Fighting" },
    { key = "hostileIdle",    label = "Hostile Mob: Idle" },
    { key = "elite",          label = "Elite / Boss Mob" },
    { key = "neutral",        label = "Neutral Mob" },
    { key = "friendlyPlayer", label = "Friendly Player" },
    { key = "friendlyNpc",    label = "Friendly NPC" },
    { key = "tapped",         label = "Tagged By Others" },
}

local THREAT_ROWS = {
    { key = "tankSafe",   label = "Tank: Secure" },
    { key = "tankWarn",   label = "Tank: Warning" },
    { key = "tankDanger", label = "Tank: Losing" },
    { key = "dpsSafe",    label = "DPS: Safe" },
    { key = "dpsWarn",    label = "DPS: Warning" },
    { key = "dpsDanger",  label = "DPS: Aggro" },
    { key = "noData",     label = "No Threat Data" },
}

local controls = {}
local eventFrame = CreateFrame("Frame")
local pendingApply = false
local settingsBuilt = false
local hookGuard = false

local function CopyColor(c)
    return { c[1] or 1, c[2] or 1, c[3] or 1, c[4] or 1 }
end

local function EnsureSettings()
    _G.MidnightUISettings = _G.MidnightUISettings or {}
    _G.MidnightUISettings.Nameplates = _G.MidnightUISettings.Nameplates or {}
    local s = _G.MidnightUISettings.Nameplates
    s.colorState = s.colorState or {}
    local cs = s.colorState
    if cs.enabled == nil then cs.enabled = DEFAULTS.enabled end
    cs.health = cs.health or {}
    cs.threat = cs.threat or {}
    for key, color in pairs(DEFAULTS.health) do
        if type(cs.health[key]) ~= "table" then cs.health[key] = CopyColor(color) end
    end
    for key, color in pairs(DEFAULTS.threat) do
        if type(cs.threat[key]) ~= "table" then cs.threat[key] = CopyColor(color) end
    end
    return cs
end

local function GetColor(kind, key)
    local cs = EnsureSettings()
    local bucket = cs[kind] or DEFAULTS[kind]
    local c = bucket and bucket[key] or DEFAULTS[kind][key]
    return c or { 1, 1, 1, 1 }
end

local function SetColor(kind, key, r, g, b, a)
    local cs = EnsureSettings()
    cs[kind][key] = { r, g, b, a or 1 }
    if controls[kind] and controls[kind][key] and controls[kind][key].swatch then
        controls[kind][key].swatch:SetColorTexture(r, g, b, a or 1)
    end
    M.RequestApply("color")
end

local function Debug(msg)
    if _G.MidnightUI_DebugSource then
        _G.MidnightUI_DebugSource("NameplateColorState", msg)
    end
end

local function IsTrue(fn, ...)
    if type(fn) ~= "function" then return false end
    local ok, value = pcall(fn, ...)
    return ok and value == true
end

local function SafeNumber(fn, ...)
    if type(fn) ~= "function" then return nil end
    local ok, value = pcall(fn, ...)
    if ok then return tonumber(value) end
    return nil
end

local function IsTankRole()
    if UnitGroupRolesAssigned and UnitGroupRolesAssigned("player") == "TANK" then return true end
    if GetSpecialization and GetSpecializationInfo then
        local spec = GetSpecialization()
        if spec then
            local ok, _, _, _, role = pcall(GetSpecializationInfo, spec)
            if ok and role == "TANK" then return true end
        end
    end
    return false
end

local function GetPlateEntry(unit)
    if not C_NamePlate or not C_NamePlate.GetNamePlateForUnit then return nil end
    local ok, plate = pcall(C_NamePlate.GetNamePlateForUnit, unit)
    if not ok or not plate then return nil end
    local root = plate.MidnightUIRoot
    if root and root.MidnightUIEntry then return root.MidnightUIEntry end
    return nil
end

local function ClassifyHealth(unit)
    if not unit or not UnitExists or not UnitExists(unit) then return nil end
    if IsTrue(UnitIsTapDenied, unit) then return "tapped" end
    if IsTrue(UnitIsUnit, unit, "target") then return "target" end

    if IsTrue(UnitCanAttack, "player", unit) then
        if IsTrue(UnitIsPlayer, unit) then return "enemyPlayer" end
        local classification = nil
        if UnitClassification then
            local ok, value = pcall(UnitClassification, unit)
            if ok then classification = value end
        end
        if classification == "worldboss" or classification == "rareelite" or classification == "elite" then
            return "elite"
        end
        if IsTrue(UnitAffectingCombat, unit) or IsTrue(UnitAffectingCombat, "player") then
            return "hostileCombat"
        end
        return "hostileIdle"
    end

    local reaction = SafeNumber(UnitReaction, unit, "player")
    if reaction == 4 then return "neutral" end
    if IsTrue(UnitIsFriend, "player", unit) then
        if IsTrue(UnitIsPlayer, unit) then return "friendlyPlayer" end
        return "friendlyNpc"
    end
    return nil
end

local function ClassifyThreat(unit)
    if not unit or not UnitExists or not UnitExists(unit) then return nil end
    if IsTrue(UnitIsFriend, "player", unit) then return nil end
    if not IsTrue(UnitAffectingCombat, unit) and not IsTrue(UnitAffectingCombat, "player") then return nil end

    local status = SafeNumber(UnitThreatSituation, "player", unit)
    if UnitDetailedThreatSituation then
        local ok, _, detailedStatus = pcall(UnitDetailedThreatSituation, "player", unit)
        if ok and tonumber(detailedStatus) then status = tonumber(detailedStatus) end
    end
    if status == nil then return "noData" end

    local tank = IsTankRole()
    if tank then
        if status >= 3 then return "tankSafe" end
        if status == 2 then return "tankWarn" end
        return "tankDanger"
    end
    if status >= 3 then return "dpsDanger" end
    if status == 2 then return "dpsWarn" end
    return "dpsSafe"
end

local function PaintEntry(unit)
    local cs = EnsureSettings()
    local entry = GetPlateEntry(unit)
    if not entry then return end

    local function HookBar(bar)
        if not bar or bar._muiColorStateHooked then return end
        bar._muiColorStateHooked = true
        hooksecurefunc(bar, "SetStatusBarColor", function(self)
            if hookGuard then return end
            local color = self._muiColorStateColor
            if not color then return end
            if EnsureSettings().enabled ~= true then return end
            hookGuard = true
            self:SetStatusBarColor(color[1], color[2], color[3], color[4] or 1)
            hookGuard = false
        end)
    end

    HookBar(entry.healthBar)
    HookBar(entry.threatBar)
    HookBar(entry.threatBar2)

    if cs.enabled ~= true then
        if entry.healthBar then entry.healthBar._muiColorStateColor = nil end
        if entry.threatBar then entry.threatBar._muiColorStateColor = nil end
        if entry.threatBar2 then entry.threatBar2._muiColorStateColor = nil end
        return
    end

    local healthKey = ClassifyHealth(unit)
    local hc = healthKey and GetColor("health", healthKey)
    if hc and entry.healthBar then
        entry.healthBar._muiColorStateColor = hc
        entry.healthBar:SetStatusBarColor(hc[1], hc[2], hc[3], hc[4] or 1)
        if entry.healthBg then
            entry.healthBg:SetColorTexture(hc[1] * 0.15, hc[2] * 0.15, hc[3] * 0.15, 0.6)
        end
    elseif entry.healthBar then
        entry.healthBar._muiColorStateColor = nil
    end

    local threatKey = ClassifyThreat(unit)
    local tc = threatKey and GetColor("threat", threatKey)
    if tc then
        if entry.threatBar and entry.threatBar:IsShown() then
            entry.threatBar._muiColorStateColor = tc
            entry.threatBar:SetStatusBarColor(tc[1], tc[2], tc[3], tc[4] or 1)
        elseif entry.threatBar then
            entry.threatBar._muiColorStateColor = nil
        end
        if entry.threatBar2 and entry.threatBar2:IsShown() then
            entry.threatBar2._muiColorStateColor = tc
            entry.threatBar2:SetStatusBarColor(tc[1], tc[2], tc[3], tc[4] or 1)
        elseif entry.threatBar2 then
            entry.threatBar2._muiColorStateColor = nil
        end
    else
        if entry.threatBar then entry.threatBar._muiColorStateColor = nil end
        if entry.threatBar2 then entry.threatBar2._muiColorStateColor = nil end
    end
end

function M.ApplyVisible()
    local cs = EnsureSettings()
    if cs.enabled ~= true then return end
    if not C_NamePlate or not C_NamePlate.GetNamePlates then return end
    local ok, plates = pcall(C_NamePlate.GetNamePlates)
    if not ok or type(plates) ~= "table" then return end
    for _, plate in ipairs(plates) do
        local root = plate and plate.MidnightUIRoot
        local entry = root and root.MidnightUIEntry
        if entry and entry.unit then PaintEntry(entry.unit) end
    end
end

function M.RequestApply(reason)
    if InCombatLockdown and InCombatLockdown() and reason == "settings" then
        pendingApply = true
        Debug("settings change queued until combat ends")
        return
    end
    if C_Timer then
        C_Timer.After(0, M.ApplyVisible)
    else
        M.ApplyVisible()
    end
end

_G.MidnightUI_ApplyNameplateColorStates = function()
    M.RequestApply("manual")
end

local function RefreshSwatches()
    for _, kind in ipairs({ "health", "threat" }) do
        local bucket = controls[kind]
        if type(bucket) == "table" then
            for key, refs in pairs(bucket) do
                if type(refs) == "table" and refs.swatch then
                    local c = GetColor(kind, key)
                    refs.swatch:SetColorTexture(c[1], c[2], c[3], c[4] or 1)
                end
            end
        end
    end
    local cs = EnsureSettings()
    if controls.enableToggle then controls.enableToggle:SetChecked(cs.enabled == true) end
end

local function ClearColorHooks()
    if not C_NamePlate or not C_NamePlate.GetNamePlates then return end
    local ok, nameplates = pcall(C_NamePlate.GetNamePlates)
    if not ok or type(nameplates) ~= "table" then return end
    for _, plate in ipairs(nameplates) do
        local root = plate and plate.MidnightUIRoot
        local entry = root and root.MidnightUIEntry
        if entry then
            if entry.healthBar then entry.healthBar._muiColorStateColor = nil end
            if entry.threatBar then entry.threatBar._muiColorStateColor = nil end
            if entry.threatBar2 then entry.threatBar2._muiColorStateColor = nil end
        end
    end
end

local function OpenColorPicker(kind, key)
    local c = GetColor(kind, key)
    local function apply()
        local r, g, b = ColorPickerFrame:GetColorRGB()
        local a = c[4] or 1
        if OpacitySliderFrame and OpacitySliderFrame.GetValue then
            a = 1 - (OpacitySliderFrame:GetValue() or 0)
        end
        SetColor(kind, key, r, g, b, a)
    end
    local function cancel(previous)
        if type(previous) == "table" then
            SetColor(kind, key, previous.r or previous[1] or c[1], previous.g or previous[2] or c[2], previous.b or previous[3] or c[3], previous.opacity and (1 - previous.opacity) or previous[4] or c[4] or 1)
        end
    end

    if ColorPickerFrame.SetupColorPickerAndShow then
        ColorPickerFrame:SetupColorPickerAndShow({
            r = c[1], g = c[2], b = c[3],
            opacity = 1 - (c[4] or 1),
            hasOpacity = true,
            swatchFunc = apply,
            opacityFunc = apply,
            cancelFunc = cancel,
        })
    else
        ColorPickerFrame.hasOpacity = true
        ColorPickerFrame.opacity = 1 - (c[4] or 1)
        ColorPickerFrame.previousValues = { c[1], c[2], c[3], c[4] or 1 }
        ColorPickerFrame.func = apply
        ColorPickerFrame.opacityFunc = apply
        ColorPickerFrame.cancelFunc = cancel
        ColorPickerFrame:SetColorRGB(c[1], c[2], c[3])
        ColorPickerFrame:Show()
    end
end

local function MakeText(parent, text, size)
    local fs = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    fs:SetText(text or "")
    fs:SetTextColor(0.84, 0.88, 0.94)
    if fs.SetFontObject and size == "large" then fs:SetFontObject(GameFontNormal) end
    return fs
end

local function MakeHeader(parent, text, y)
    local h = CreateFrame("Frame", nil, parent)
    h:SetHeight(24)
    h:SetPoint("TOPLEFT", parent, "TOPLEFT", 24, y)
    h:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -24, y)
    local bg = h:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.00, 0.78, 1.00, 0.06)
    local bar = h:CreateTexture(nil, "ARTWORK")
    bar:SetWidth(3)
    bar:SetPoint("TOPLEFT")
    bar:SetPoint("BOTTOMLEFT")
    bar:SetColorTexture(0.00, 0.78, 1.00, 0.95)
    local label = MakeText(h, text, "large")
    label:SetPoint("LEFT", bar, "RIGHT", 10, 0)
    return h
end

local function MakeColorButton(parent, kind, def, x, y)
    controls[kind] = controls[kind] or {}
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(250, 28)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    btn:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    btn:SetBackdropColor(0.055, 0.065, 0.105, 0.85)
    btn:SetBackdropBorderColor(0.16, 0.20, 0.30, 0.75)

    local swatch = btn:CreateTexture(nil, "ARTWORK")
    swatch:SetSize(18, 18)
    swatch:SetPoint("LEFT", btn, "LEFT", 8, 0)
    local c = GetColor(kind, def.key)
    swatch:SetColorTexture(c[1], c[2], c[3], c[4] or 1)

    local label = MakeText(btn, def.label)
    label:SetPoint("LEFT", swatch, "RIGHT", 8, 0)
    label:SetPoint("RIGHT", btn, "RIGHT", -8, 0)
    label:SetJustifyH("LEFT")

    btn:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(0.30, 0.88, 1.00, 0.75)
    end)
    btn:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(0.16, 0.20, 0.30, 0.75)
    end)
    btn:SetScript("OnClick", function()
        OpenColorPicker(kind, def.key)
    end)

    controls[kind][def.key] = { button = btn, swatch = swatch }
    return btn
end

local function BuildSettingsControls()
    if settingsBuilt then return true end
    local S = _G.MidnightUI_Settings
    local C = S and S.Controls
    local anchor = C and C.slHoverNPBorderSize
    local parent = anchor and anchor.GetParent and anchor:GetParent()
    if not parent then return false end

    EnsureSettings()
    local y = tonumber(C._npY) or -900
    y = y - 8

    MakeHeader(parent, "Nameplate Color States", y)
    y = y - 32

    local toggle = CreateFrame("CheckButton", nil, parent, "InterfaceOptionsCheckButtonTemplate")
    toggle:SetPoint("TOPLEFT", parent, "TOPLEFT", 24, y)
    toggle.Text:SetText("Enable state-based nameplate colors")
    toggle.Text:SetFontObject("GameFontNormalSmall")
    toggle:SetChecked(EnsureSettings().enabled == true)
    toggle:SetScript("OnClick", function(self)
        EnsureSettings().enabled = (self:GetChecked() == true)
        if self:GetChecked() == true then
            M.RequestApply("settings")
        else
            ClearColorHooks()
        end
        if _G.MidnightUI_RefreshNameplates then _G.MidnightUI_RefreshNameplates() end
    end)
    controls.enableToggle = toggle
    y = y - 30

    local healthLabel = MakeText(parent, "Health bar colors")
    healthLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 24, y)
    y = y - 24
    for i, def in ipairs(HEALTH_ROWS) do
        local col = ((i - 1) % 2)
        local row = math.floor((i - 1) / 2)
        MakeColorButton(parent, "health", def, 24 + (col * 270), y - (row * 34))
    end
    y = y - (math.ceil(#HEALTH_ROWS / 2) * 34) - 18

    local threatLabel = MakeText(parent, "Threat bar colors")
    threatLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 24, y)
    y = y - 24
    for i, def in ipairs(THREAT_ROWS) do
        local col = ((i - 1) % 2)
        local row = math.floor((i - 1) / 2)
        MakeColorButton(parent, "threat", def, 24 + (col * 270), y - (row * 34))
    end
    y = y - (math.ceil(#THREAT_ROWS / 2) * 34) - 18

    local reset = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    reset:SetSize(150, 24)
    reset:SetPoint("TOPLEFT", parent, "TOPLEFT", 24, y)
    reset:SetText("Reset Colors")
    if S.StyleSettingsButton then S.StyleSettingsButton(reset) end
    reset:SetScript("OnClick", function()
        local cs = EnsureSettings()
        for key, color in pairs(DEFAULTS.health) do cs.health[key] = CopyColor(color) end
        for key, color in pairs(DEFAULTS.threat) do cs.threat[key] = CopyColor(color) end
        RefreshSwatches()
        M.RequestApply("settings")
    end)
    y = y - 38

    C._npY = y
    if parent.SetHeight then parent:SetHeight(math.max(parent:GetHeight() or 1, math.abs(y) + 60)) end
    settingsBuilt = true
    RefreshSwatches()
    return true
end

local function HookSettingsApply()
    local S = _G.MidnightUI_Settings
    if not S or S._muiColorStateHooked then return end
    local oldApply = S.ApplyNameplateSettings
    if type(oldApply) ~= "function" then return end
    S._muiColorStateHooked = true
    S.ApplyNameplateSettings = function(...)
        local r1, r2, r3, r4 = oldApply(...)
        M.RequestApply("settings")
        return r1, r2, r3, r4
    end
end

eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
eventFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
eventFrame:RegisterEvent("UNIT_THREAT_SITUATION_UPDATE")
eventFrame:RegisterEvent("UNIT_THREAT_LIST_UPDATE")
eventFrame:RegisterEvent("UNIT_TARGET")
eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")

eventFrame:SetScript("OnEvent", function(_, event, arg1)
    if event == "ADDON_LOADED" and arg1 ~= "MidnightUI" then return end
    if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then
        EnsureSettings()
        BuildSettingsControls()
        HookSettingsApply()
        M.RequestApply("load")
        return
    end
    if event == "PLAYER_REGEN_ENABLED" and pendingApply then
        pendingApply = false
        M.RequestApply("combat-ended")
        return
    end
    if event == "NAME_PLATE_UNIT_ADDED" and arg1 then
        if C_Timer then C_Timer.After(0, function() PaintEntry(arg1) end) end
        return
    end
    M.RequestApply(event)
end)

if C_Timer then
    C_Timer.After(0, function()
        EnsureSettings()
        BuildSettingsControls()
        HookSettingsApply()
    end)
end
