local ADDON_NAME, ns = ...

-- Always unify namespace (prevents split-table bugs across files)
ns = _G[ADDON_NAME] or ns
_G[ADDON_NAME] = ns

-- Make the addon table globally reachable (other modules / RobUI integration expects this)
_G.PlateOMatic = _G.PlateOMatic or ns

-- =========================================================
-- Plate-o-Matic (pom.lua)
-- Main entry file
-- =========================================================

local DB_VERSION = 4                -- "display"/feature version (keep if you want)
local CURRENT_DB_VERSION = 5        -- internal schema version (migrations use this)

-- =========================================================
-- Skins
-- =========================================================
ns.SKINS = {
    { name = "Base",       texture = "Interface\\AddOns\\pom\\skins\\base.tga", ignoreColor = false },
    { name = "Yellow",             texture = "Interface\\AddOns\\pom\\skins\\yellow.tga", ignoreColor = true },
    { name = "Cool Blue",          texture = "Interface\\AddOns\\pom\\skins\\coolblue.tga", ignoreColor = true },
    { name = "Gray",               texture = "Interface\\AddOns\\pom\\skins\\gray.tga", ignoreColor = true },
    { name = "Statusbar 1",        texture = "Interface\\AddOns\\pom\\skins\\statusbar1.tga", ignoreColor = true },
    { name = "MiniBoss",           texture = "Interface\\AddOns\\pom\\skins\\miniboss1.tga", ignoreColor = true },

    { name = "Status: Hostile",    texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_hostile_256x32.tga", ignoreColor = true },
    { name = "Status: Friendly",   texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_friendly_256x32.tga", ignoreColor = true },
    { name = "Status: Neutral",    texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_neutral_256x32.tga", ignoreColor = true },

    { name = "Class: Warrior",     texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_warrior_256x32.tga", ignoreColor = true },
    { name = "Class: Paladin",     texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_paladin_256x32.tga", ignoreColor = true },
    { name = "Class: Hunter",      texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_hunter_256x32.tga", ignoreColor = true },
    { name = "Class: Rogue",       texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_rogue_256x32.tga", ignoreColor = true },
    { name = "Class: Priest",      texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_priest_256x32.tga", ignoreColor = true },
    { name = "Class: Death Knight",texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_deathknight_256x32.tga", ignoreColor = true },
    { name = "Class: Shaman",      texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_shaman_256x32.tga", ignoreColor = true },
    { name = "Class: Mage",        texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_mage_256x32.tga", ignoreColor = true },
    { name = "Class: Warlock",     texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_warlock_256x32.tga", ignoreColor = true },
    { name = "Class: Monk",        texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_monk_256x32.tga", ignoreColor = true },
    { name = "Class: Druid",       texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_druid_256x32.tga", ignoreColor = true },
    { name = "Class: Demon Hunter",texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_demonhunter_256x32.tga", ignoreColor = true },
    { name = "Class: Evoker",      texture = "Interface\\AddOns\\pom\\skins\\robui_statusbar_evoker_256x32.tga", ignoreColor = true },

    { name = "Flat (Plater)",      texture = "Interface\\Buttons\\WHITE8x8",            ignoreColor = false },
    { name = "Smooth",             texture = "Interface\\TargetingFrame\\UI-StatusBar", ignoreColor = false },
    { name = "Blizzard Raid",      texture = "Interface\\RaidFrame\\Raid-Bar-Hp-Fill",  ignoreColor = false },
}

-- =========================================================
-- Defaults (SavedVariables)
-- =========================================================
ns.DEFAULTS = {
    dbVersion = DB_VERSION, -- keep for display/back-compat

    skins   = { normal=23, plusOne=23, highlevel=23, caster=23, melee=23 },
    heights = { normal=16, plusOne=18, highlevel=20, caster=18, melee=18 },

    fontPath    = "Fonts\\FRIZQT__.TTF",
    fontSize    = 10,
    fontOutline = "OUTLINE",

    hideClassIcon    = true,
    alwaysTargetGlow = true,
    targetScale      = 1.15,
    showTargetArrow  = true,
    arrowPointingOut = false,

    showQuestIcon  = true,
    showQuestProg  = true,

    useBaseColorsSolo = false,

    nameAnchor  = "LEFT",
    globalScale = 1.0,

    buffSize   = 20,
    debuffSize = 20,

    targetBorderColor = { r=1.0, g=0.82, b=0.0, a=1.0 },
}

-- =========================================================
-- NEW DEFAULTS (moved groups)
-- =========================================================
local BASECOLORS_DEFAULTS = {
    normal    = { r=0.8, g=0.8, b=0.8 },
    plusOne   = { r=1.0, g=0.5, b=0.0 },
    highlevel = { r=0.8, g=0.0, b=1.0 },
    caster    = { r=1.0, g=0.4, b=0.8 },
    melee     = { r=0.0, g=0.6, b=1.0 },
    neutral   = { r=0.9, g=0.8, b=0.3 }, -- reaction==4 and not in combat lock
}

local THREAT_DEFAULTS = {
    enabled = true,

    colorBarByThreat = true,
    flashAggro       = true,
    flashDuration    = 0,     -- 0=until fixed

    showThreatPerc   = true,
    pctTextMin       = 1,

    gradientStart    = 60,
    gradientEnd      = 100,

    tankModeEnabled  = true,
    checkOffTank     = true,

    groupTintEnabled = false,

    colors = {
        high   = { r=1.0, g=0.0, b=0.0 },
        medium = { r=1.0, g=1.0, b=0.0 },
        low    = { r=0.5, g=0.5, b=0.5 },

        safe   = { r=0.2, g=0.8, b=0.2 },
        ot     = { r=0.6, g=0.2, b=1.0 },

        group  = { r=0.2, g=0.7, b=1.0, a=1.0 },
    },
}

-- =========================================================
-- Locals
-- =========================================================
local U      = ns.util
local Mixins = ns.mixins
local Config = ns.config

-- =========================================================
-- Deep merge: fill ONLY missing keys (never overwrite user)
-- =========================================================
local function DeepMergeMissing(dst, src)
    if not dst or not src then return end
    for k, v in pairs(src) do
        if dst[k] == nil then
            dst[k] = (type(v) == "table") and CopyTable(v) or v
        else
            if type(v) == "table" and type(dst[k]) == "table" then
                DeepMergeMissing(dst[k], v)
            end
        end
    end
end

-- =========================================================
-- Apply styling to one plate
-- =========================================================
-- Apply styling to one plate
-- =========================================================
local function Apply(frame)
    if not frame or frame:IsForbidden() then return end
    if not U.IsNameplateUnit(frame.unit) then return end

    if not frame.POM_Mixin then
        frame.POM_Mixin = Mixins:New(frame, ns.SKINS)
    end

    U.KillBlizzard(frame, PlateOMaticDB)

    frame.POM_Mixin:UpdateVisuals()
    frame.POM_Mixin:UpdateQuest()
    frame.POM_Mixin:UpdateName()
    frame.POM_Mixin:UpdateHealth()

    if frame.POM_Mixin.UpdateHeight then
        frame.POM_Mixin:UpdateHeight()
    end

    if U.IsSafeUnit(frame.unit) then
        frame.POM_Mixin:UpdateState()
    end

    -- Raid markers (own module)
    if ns.raidmarkers then
        ns.raidmarkers:Apply(frame)
    end
end


-- =========================================================
-- DB MIGRATIONS
-- - v is internal schema version stored in PlateOMaticDB.__version
-- - NEVER wipe. Only move/rename/copy when safe.
-- =========================================================
local MIGRATIONS = {}

MIGRATIONS[1] = function(db)
    -- noop
end

MIGRATIONS[2] = function(db)
    db.baseColors = db.baseColors or {}
    db.threat     = db.threat or {}
end

MIGRATIONS[3] = function(db)
    db.baseColors = db.baseColors or {}
    db.threat     = db.threat or {}

    DeepMergeMissing(db.baseColors, BASECOLORS_DEFAULTS)
    DeepMergeMissing(db.threat, THREAT_DEFAULTS)

    if db.colors and type(db.colors) == "table" then
        local old = db.colors

        local mapBase = { "normal","plusOne","highlevel","caster","melee","neutral" }
        for _, k in ipairs(mapBase) do
            if type(old[k]) == "table" and type(db.baseColors[k]) == "table" then
                local c = old[k]
                if c.r ~= nil then db.baseColors[k].r = c.r end
                if c.g ~= nil then db.baseColors[k].g = c.g end
                if c.b ~= nil then db.baseColors[k].b = c.b end
            end
        end

        local tc = db.threat.colors or {}
        db.threat.colors = tc

        local function pull(oldKey, newKey)
            if type(old[oldKey]) == "table" and type(tc[newKey]) == "table" then
                if old[oldKey].r ~= nil then tc[newKey].r = old[oldKey].r end
                if old[oldKey].g ~= nil then tc[newKey].g = old[oldKey].g end
                if old[oldKey].b ~= nil then tc[newKey].b = old[oldKey].b end
                if old[oldKey].a ~= nil then tc[newKey].a = old[oldKey].a end
            end
        end

        pull("threatHigh",     "high")
        pull("threatMedium",   "medium")
        pull("threatLow",      "low")
        pull("threatTankSafe", "safe")
        pull("threatOffTank",  "ot")
        pull("groupAggro",     "group")
    end

    local t = db.threat
    if db.flashAggro ~= nil then t.flashAggro = db.flashAggro and true or false end
    if db.flashDuration ~= nil then t.flashDuration = tonumber(db.flashDuration) or t.flashDuration end
    if db.showThreatPerc ~= nil then t.showThreatPerc = db.showThreatPerc and true or false end
    if db.colorBarByThreat ~= nil then t.colorBarByThreat = db.colorBarByThreat and true or false end
    if db.tankModeEnabled ~= nil then t.tankModeEnabled = db.tankModeEnabled and true or false end
    if db.checkOffTank ~= nil then t.checkOffTank = db.checkOffTank and true or false end
    if db.groupAggroColorEnabled ~= nil then t.groupTintEnabled = db.groupAggroColorEnabled and true or false end
end

-- 4 -> 5: one-time import from old per-character DB if present (PomcaDB)
MIGRATIONS[4] = function(db)
    if db.__migratedFromPerChar then return end
    if not PomcaDB or type(PomcaDB) ~= "table" then return end

    local looksEmpty = true
    for k, _ in pairs(db) do
        if k ~= "__version" and k ~= "dbVersion" and k ~= "__migratedFromPerChar" then
            looksEmpty = false
            break
        end
    end

    if not looksEmpty then
        db.__migratedFromPerChar = true
        return
    end

    DeepMergeMissing(db, PomcaDB)
    db.baseColors = db.baseColors or {}
    db.threat     = db.threat or {}

    db.__migratedFromPerChar = true
end

-- =========================================================
-- DB init + Migrations + Defaults
-- =========================================================
local function InitDB()
    PlateOMaticDB = PlateOMaticDB or {}
    _G.PlateOMaticDB = PlateOMaticDB
    local db = PlateOMaticDB

    -- Determine starting schema version
    local v = tonumber(db.__version)

    if not v then
        -- Heuristic detection to avoid always treating existing users as v=1:
        -- If user already has the new buckets, treat as schema 4 baseline.
        if type(db.threat) == "table" or type(db.baseColors) == "table" then
            v = 4
        else
            v = 1
        end
        db.__version = v
    end

    while v < CURRENT_DB_VERSION do
        local step = MIGRATIONS[v]
        if step then
            pcall(step, db)
        end
        v = v + 1
        db.__version = v
    end

    db.baseColors = db.baseColors or {}
    db.threat     = db.threat or {}

    DeepMergeMissing(db, ns.DEFAULTS)
    DeepMergeMissing(db.baseColors, BASECOLORS_DEFAULTS)
    DeepMergeMissing(db.threat, THREAT_DEFAULTS)

    db.dbVersion = DB_VERSION

    if db.useBaseColorsSolo == nil then
        db.useBaseColorsSolo = false
    end

    -- Ensure heights module owns its keys (so it doesn't have to self-heal later)
    if ns and ns.heights and ns.heights.Ensure then
        pcall(ns.heights.Ensure, ns.heights)
    end
end

-- =========================================================
-- Menu
-- =========================================================
local function CreateMenu()
    if ns._menu then
        ns._menu:SetShown(not ns._menu:IsShown())
        return
    end

    local f = CreateFrame("Frame", "POM_MenuFrame", UIParent, "BasicFrameTemplateWithInset")
    f:SetSize(1020, 760)
    f:SetPoint("CENTER")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)

    if f.TitleText then
        f.TitleText:SetText("Plate-o-Matic")
    end

    local function applyAll()
        U.RefreshAllPlates(Apply)
        if ns.preview and ns.preview.frame and ns.preview.frame.Refresh then
            ns.preview.frame:Refresh()
        end
    end

    Config:Build(f, ns.SKINS, applyAll)

    ns._menu = f
    f:Show()
end

-- =========================================================
-- Hooks
-- =========================================================
hooksecurefunc("CompactUnitFrame_UpdateAll", Apply)
hooksecurefunc("CompactUnitFrame_UpdateHealth", Apply)
hooksecurefunc("CompactUnitFrame_UpdateName", Apply)
hooksecurefunc("CompactUnitFrame_UpdateStatusText", Apply)

hooksecurefunc("CompactUnitFrame_UpdateHealthBorder", function(frame)
    if frame and not frame:IsForbidden() and U.IsNameplateUnit(frame.unit) then
        U.KillBlizzard(frame, PlateOMaticDB)
    end
end)

hooksecurefunc("CompactUnitFrame_UpdateSelectionHighlight", function(frame)
    if frame and not frame:IsForbidden() and U.IsNameplateUnit(frame.unit) then
        if frame.selectionHighlight then
            U.LockAlpha(frame.selectionHighlight)
        end
    end
end)

hooksecurefunc("CompactUnitFrame_UpdateAuras", function(frame)
    if not frame or frame:IsForbidden() or not U.IsNameplateUnit(frame.unit) then return end

    local bScale = (PlateOMaticDB.buffSize or 20) / 18
    local dScale = (PlateOMaticDB.debuffSize or 20) / 18

    if frame.BuffFrame then frame.BuffFrame:SetScale(bScale) end
    if frame.DebuffFrame then frame.DebuffFrame:SetScale(dScale) end
end)

-- =========================================================
-- Events
-- =========================================================
local ev = CreateFrame("Frame")
ev:RegisterEvent("PLAYER_LOGIN")
ev:RegisterEvent("NAME_PLATE_UNIT_ADDED")
ev:RegisterEvent("PLAYER_TARGET_CHANGED")
ev:RegisterEvent("GROUP_ROSTER_UPDATE")
ev:RegisterEvent("PLAYER_ROLES_ASSIGNED")
ev:RegisterEvent("PLAYER_ENTERING_WORLD")

ev:SetScript("OnEvent", function(_, event, unit)
    if event == "PLAYER_LOGIN" then
        InitDB()
        if ns and ns.UpdateTankCache then
            ns:UpdateTankCache()
        end
        print("|cFF00FF00Plate-o-Matic:|r Loaded.")

    elseif event == "NAME_PLATE_UNIT_ADDED" then
        local plate = C_NamePlate.GetNamePlateForUnit(unit)
        if plate and plate.UnitFrame then
            Apply(plate.UnitFrame)
        end

    else
        if ns and ns.UpdateTankCache then
            ns:UpdateTankCache()
        end
        U.RefreshAllPlates(Apply)
    end
end)

-- =========================================================
-- Slash command
-- =========================================================
SLASH_PLATEOMATIC1 = "/pom"
SlashCmdList["PLATEOMATIC"] = function(msg)
    if msg == "reset" then
        PlateOMaticDB = nil
        _G.PlateOMaticDB = nil
        C_UI.Reload()
        return
    end
    CreateMenu()
end
