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,
    highlevel = 20,
    caster    = 18,
    melee     = 18,
}

local ORDER = {
    { key = "normal",    label = "Normal Height" },
    { key = "highlevel", label = "Highlevel (+2) Height" },
    { key = "plusOne",   label = "Player +1 Height" },
    { key = "caster",    label = "Elite Caster Height" },
    { key = "melee",     label = "Elite Melee Height" },
}

-- 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

-- ---------------------------------------------------------
-- 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

    -- 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

-- ---------------------------------------------------------
-- TypeKey helper (so other files don't need to touch height rules)
-- ---------------------------------------------------------
function H:GetTypeKey(unit)
    local lvl = UnitLevel(unit)
    local pl  = UnitLevel("player")
    local cls = UnitClassification(unit)

    local typeKey = "normal"
    if lvl == -1 or (lvl and pl and lvl >= pl + 2) then
        typeKey = "highlevel"
    elseif lvl and pl and lvl > pl then
        typeKey = "plusOne"
    elseif cls == "elite" then
        if UnitPowerType(unit) == 0 then typeKey = "caster" else typeKey = "melee" end
    end
    return typeKey
end

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

    if not db then
        return def
    end

    local v = Clamp(db[typeKey], 6, 64)
    if not v then
        -- READ-ONLY path: do NOT write fallback into DB here
        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
    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, typeKey)
    if not (healthBar and healthBar.SetHeight) then return end

    local value
    if self:GetGlobalEnabled() then
        value = self:GetGlobalValue()
    else
        value = self:Get(typeKey)
    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 typeKey = self:GetTypeKey(unit)
    self:Apply(healthBar, typeKey)
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)

    -- IMPORTANT: cb:GetName() can be nil (we create with nil name), so don't concat it.
    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("Per-type heights + optional global override. 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

        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
