local ADDON_NAME, ns = ...

-- =========================================================
-- pom_heights.lua
-- All "heights" logic + UI lives here ONLY.
-- Other files should NOT read/write PlateOMaticDB.heights directly.
-- =========================================================

ns.heights = ns.heights or {}
local H = ns.heights

local DEFAULTS = {
    normal      = 16,
    plusOne     = 18,
    boss        = 20,
    eliteCaster = 18,
    eliteMelee  = 18,

    -- legacy compatibility during transition
    highlevel   = 20,
    caster      = 18,
    melee       = 18,
}

local ORDER = {
    { key = "normal",      label = "Normal Height" },
    { key = "boss",        label = "Boss / Highlevel Height" },
    { key = "plusOne",     label = "Mini boss Height" },
    { key = "eliteCaster", label = "Elite Caster Height" },
    { key = "eliteMelee",  label = "Elite Melee Height" },
}

local LEGACY_TO_MODERN = {
    highlevel = "boss",
    caster    = "eliteCaster",
    melee     = "eliteMelee",
}

local MODERN_TO_LEGACY = {
    boss        = "highlevel",
    eliteCaster = "caster",
    eliteMelee  = "melee",
}

-- READ-ONLY getter
local function GetDB()
    if not PlateOMaticDB then return nil end
    if type(PlateOMaticDB.heights) ~= "table" then return nil end
    return PlateOMaticDB.heights
end

-- write helper: only when we intentionally want to create/write
local function EnsureWritableDB()
    if not PlateOMaticDB then return nil end
    PlateOMaticDB.heights = PlateOMaticDB.heights or {}
    return PlateOMaticDB.heights
end

local function Clamp(v, lo, hi)
    v = tonumber(v)
    if not v then return nil end
    if v < lo then v = lo end
    if v > hi then v = hi end
    return v
end

local function SyncLegacyModernHeights(tbl)
    if type(tbl) ~= "table" then return end

    for oldKey, newKey in pairs(LEGACY_TO_MODERN) do
        if tbl[newKey] == nil and tbl[oldKey] ~= nil then
            tbl[newKey] = tbl[oldKey]
        elseif tbl[oldKey] == nil and tbl[newKey] ~= nil then
            tbl[oldKey] = tbl[newKey]
        end
    end
end

local function GetHeightValue(tbl, key)
    if type(tbl) ~= "table" then return nil end

    local legacyKey = MODERN_TO_LEGACY[key]
    if legacyKey then
        if tbl[key] ~= nil then
            return tbl[key]
        end
        return tbl[legacyKey]
    end

    return tbl[key]
end

local function SetHeightValue(tbl, key, value)
    if type(tbl) ~= "table" then return end

    tbl[key] = value

    local legacyKey = MODERN_TO_LEGACY[key]
    if legacyKey then
        tbl[legacyKey] = value
    end
end

-- ---------------------------------------------------------
-- Shared style resolver helpers
-- Keep these aligned with pom_mixins.lua
-- ---------------------------------------------------------
local function GetSafeUnitLevel(unit)
    if not unit or not UnitExists(unit) then return nil end
    local lvl = UnitLevel(unit)
    if type(lvl) ~= "number" then return nil end
    return lvl
end

local function GetSafePlayerLevel()
    local lvl = UnitLevel("player")
    if type(lvl) ~= "number" then return nil end
    return lvl
end

local function IsBossLikeUnit(unit)
    if not unit or not UnitExists(unit) then return false end

    local lvl = GetSafeUnitLevel(unit)
    if lvl == -1 then
        return true
    end

    if UnitIsBossMob then
        local ok, r = pcall(UnitIsBossMob, unit)
        if ok and r then
            return true
        end
    end

    return false
end

local function ResolveEliteStyle(unit)
    if not unit or not UnitExists(unit) then
        return "eliteMelee"
    end

    -- Same conservative split as pom_mixins.lua
    local class = UnitClassBase and UnitClassBase(unit)
    if class == "PALADIN" then
        return "eliteCaster"
    end

    return "eliteMelee"
end

-- ---------------------------------------------------------
-- Ensure defaults + new global override keys (owned here)
-- NOTE: This writes, but it's intentional. Ideally called from InitDB / ToggleFrame.
-- ---------------------------------------------------------
function H:Ensure()
    if not PlateOMaticDB then return end

    local db = EnsureWritableDB()
    if not db then return end

    for k, v in pairs(DEFAULTS) do
        if db[k] == nil then
            db[k] = v
        end
    end

    SyncLegacyModernHeights(db)

    -- global override (stored on PlateOMaticDB root, but still OWNED by this file)
    if PlateOMaticDB.heightsGlobalEnabled == nil then
        PlateOMaticDB.heightsGlobalEnabled = false
    end
    if PlateOMaticDB.heightsGlobalValue == nil then
        PlateOMaticDB.heightsGlobalValue = 16
    end
end

-- ---------------------------------------------------------
-- StyleKey helper (height should follow style, not color)
-- ---------------------------------------------------------
function H:GetStyleKey(unit)
    if not unit or not UnitExists(unit) then
        return "normal"
    end

    local lvl = GetSafeUnitLevel(unit)
    local pl  = GetSafePlayerLevel()
    local cls = UnitClassification(unit)

    if IsBossLikeUnit(unit) then
        return "boss"
    end

    if lvl and pl and lvl >= pl + 2 then
        return "boss"
    end

    if lvl and pl and lvl == pl + 1 then
        return "plusOne"
    end

    if cls == "elite" then
        return ResolveEliteStyle(unit)
    end

    return "normal"
end

-- Backward-compat wrapper for any old callers
function H:GetTypeKey(unit)
    return self:GetStyleKey(unit)
end

function H:Get(typeKey)
    local db = GetDB()
    local def = DEFAULTS[typeKey] or DEFAULTS.normal

    if not db then
        return def
    end

    local raw = GetHeightValue(db, typeKey)
    local v = Clamp(raw, 6, 64)
    if not v then
        return def
    end
    return v
end

function H:Set(typeKey, value)
    local db = EnsureWritableDB()
    if not db then return end

    local v = Clamp(value, 6, 64)
    if not v then return end

    SetHeightValue(db, typeKey, v)
end

function H:GetGlobalEnabled()
    return PlateOMaticDB and PlateOMaticDB.heightsGlobalEnabled and true or false
end

function H:GetGlobalValue()
    if not PlateOMaticDB then return 16 end
    local v = Clamp(PlateOMaticDB.heightsGlobalValue, 6, 64)
    if not v then v = 16 end
    return v
end

function H:SetGlobalEnabled(v)
    if not PlateOMaticDB then return end
    PlateOMaticDB.heightsGlobalEnabled = v and true or false
end

function H:SetGlobalValue(v)
    if not PlateOMaticDB then return end
    local n = Clamp(v, 6, 64)
    if not n then return end
    PlateOMaticDB.heightsGlobalValue = n
end

-- ---------------------------------------------------------
-- Apply height to a statusbar (MED FIX)
-- IMPORTANT:
--   Apply MUST NOT write to DB. InitDB/ToggleFrame owns Ensure().
-- ---------------------------------------------------------
function H:Apply(healthBar, styleKey)
    if not (healthBar and healthBar.SetHeight) then return end

    local value
    if self:GetGlobalEnabled() then
        value = self:GetGlobalValue()
    else
        value = self:Get(styleKey)
    end

    -- Blizzard locks TOP+BOTTOM; must re-anchor to allow SetHeight
    healthBar:ClearAllPoints()

    local parent = healthBar:GetParent()
    if parent then
        healthBar:SetPoint("TOPLEFT", parent, "TOPLEFT", 1, -1)
        healthBar:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -1, -1)
    end

    healthBar:SetHeight(value)
end

-- Apply directly from unit (safe even when you don't run UpdateState)
function H:ApplyForUnit(healthBar, unit)
    if not unit then return end
    local styleKey = self:GetStyleKey(unit)
    self:Apply(healthBar, styleKey)
end

-- =========================================================
-- UI helpers
-- =========================================================
local function MakeRow(parent, label, key, x, y, onChange)
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(420, 20)
    row:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

    local txt = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    txt:SetPoint("LEFT", row, "LEFT", 0, 0)
    txt:SetText(label)

    local eb = CreateFrame("EditBox", nil, row, "InputBoxTemplate")
    eb:SetSize(60, 18)
    eb:SetPoint("RIGHT", row, "RIGHT", 0, 0)
    eb:SetAutoFocus(false)
    eb:SetNumeric(true)

    local function refresh()
        eb:SetNumber(H:Get(key))
    end

    eb:SetScript("OnEnterPressed", function(self)
        local v = tonumber(self:GetText())
        if v then
            H:Set(key, v)
            refresh()
            if onChange then onChange() end
        else
            refresh()
        end
        self:ClearFocus()
    end)

    eb:SetScript("OnEscapePressed", function(self)
        refresh()
        self:ClearFocus()
    end)

    refresh()
    row._refresh = refresh
    return row
end

local function MakeGlobalRow(parent, x, y, onChange)
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(480, 22)
    row:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

    local cb = CreateFrame("CheckButton", nil, row, "ChatConfigCheckButtonTemplate")
    cb:SetPoint("LEFT", row, "LEFT", 0, 0)

    local lbl = cb.Text or cb.text
    if lbl and lbl.SetText then
        lbl:SetText("Use Global Height Override")
        lbl:SetTextColor(1, 0.9, 0.6)
    end

    local eb = CreateFrame("EditBox", nil, row, "InputBoxTemplate")
    eb:SetSize(60, 18)
    eb:SetPoint("RIGHT", row, "RIGHT", 0, 0)
    eb:SetAutoFocus(false)
    eb:SetNumeric(true)

    local hint = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hint:SetPoint("RIGHT", eb, "LEFT", -8, 0)
    hint:SetText("Height")

    local function refresh()
        cb:SetChecked(H:GetGlobalEnabled())
        eb:SetNumber(H:GetGlobalValue())
        eb:SetEnabled(H:GetGlobalEnabled())
        if H:GetGlobalEnabled() then
            eb:SetAlpha(1); hint:SetAlpha(1)
        else
            eb:SetAlpha(0.4); hint:SetAlpha(0.4)
        end
    end

    cb:SetScript("OnClick", function(self)
        H:SetGlobalEnabled(self:GetChecked() and true or false)
        refresh()
        if onChange then onChange() end
    end)

    eb:SetScript("OnEnterPressed", function(self)
        local v = tonumber(self:GetText())
        if v then
            H:SetGlobalValue(v)
            refresh()
            if onChange then onChange() end
        else
            refresh()
        end
        self:ClearFocus()
    end)

    eb:SetScript("OnEscapePressed", function(self)
        refresh()
        self:ClearFocus()
    end)

    refresh()
    row._refresh = refresh
    return row
end

function H:BuildUI(parent, onChange)
    self:Ensure()

    local p = CreateFrame("Frame", nil, parent)
    p:SetAllPoints(parent)

    local title = p:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", p, "TOPLEFT", 16, -16)
    title:SetText("Heights")

    local sub = p:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sub:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -6)
    sub:SetText("Style-based heights with backward-compatible storage. Enter number and press Enter.")

    local y = -58
    p._rows = {}

    local gr = MakeGlobalRow(p, 16, y, onChange)
    table.insert(p._rows, gr)
    y = y - 30

    for i = 1, #ORDER do
        local it = ORDER[i]
        local r = MakeRow(p, it.label, it.key, 16, y, onChange)
        p._rows[i + 1] = r
        y = y - 24
    end

    local resetBtn = CreateFrame("Button", nil, p, "UIPanelButtonTemplate")
    resetBtn:SetSize(220, 22)
    resetBtn:SetPoint("TOPLEFT", p, "TOPLEFT", 16, y - 8)
    resetBtn:SetText("Reset Heights to Default")
    resetBtn:SetScript("OnClick", function()
        local db = EnsureWritableDB()
        if not db then return end

        for k, v in pairs(DEFAULTS) do
            db[k] = v
        end

        SyncLegacyModernHeights(db)

        PlateOMaticDB.heightsGlobalEnabled = false
        PlateOMaticDB.heightsGlobalValue = 16

        if p._rows then
            for _, r in ipairs(p._rows) do
                if r and r._refresh then r._refresh() end
            end
        end

        if onChange then onChange() end
    end)

    return p
end

function H:ToggleFrame(onChange)
    self:Ensure()

    if self._frame and self._frame:IsShown() then
        self._frame:Hide()
        return
    end

    if not self._frame then
        local f = CreateFrame("Frame", "POM_HeightsFrame", UIParent, "BasicFrameTemplateWithInset")
        f:SetSize(560, 300)
        f:SetPoint("CENTER")
        f:SetFrameStrata("FULLSCREEN_DIALOG")
        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 - Heights")
        end

        self._frame = f
        self._content = self:BuildUI(f, onChange)
    end

    if self._content and self._content._rows then
        for _, r in ipairs(self._content._rows) do
            if r and r._refresh then r._refresh() end
        end
    end

    self._frame:Show()
end