---@class GrimmsUltrawide
local GU = LibStub("AceAddon-3.0"):GetAddon("GrimmsUltrawide")

local profiles = {
    bigWigs = {},
    bigWigsMythicPlus = {},
    aes = {},
    warpDeplete = {},
    ellesmere = { dps = {}, healer = {} },
    -- Raw EllesmereUIDB.profiles[profileName] objects (not just the .addons
    -- sub-table). Needed for settings like specOverrides/specUnlockOverrides
    -- that live as SIBLINGS of "addons" at the profile root, rather than
    -- inside addons.<ModuleName> like everything else in profiles.ellesmere.
    ellesmereProfiles = { dps = {}, healer = {} }
}

local function LoadProfiles()
    -- BigWigs
    if BigWigs3DB then
        local namespaces = BigWigs3DB["namespaces"]
        local barsNamespace = namespaces and namespaces["BigWigs_Plugins_Bars"]
        local bigWigsProfiles = barsNamespace and barsNamespace["profiles"]
        if bigWigsProfiles then
            for p, _ in pairs(bigWigsProfiles) do
                local isMatch = GU:StartsWithIgnoreCase(p, "atrocityui")
                if isMatch then
                    table.insert(profiles.bigWigs, bigWigsProfiles[p])
                end
            end
        end

        -- MythicPlus namespace - holds the Keystone (instance keys) anchor.
        local mythicPlusNamespace = namespaces and namespaces["MythicPlus"]
        local mythicPlusProfiles = mythicPlusNamespace and mythicPlusNamespace["profiles"]
        if mythicPlusProfiles then
            for p, _ in pairs(mythicPlusProfiles) do
                local isMatch = GU:StartsWithIgnoreCase(p, "atrocityui")
                if isMatch then
                    table.insert(profiles.bigWigsMythicPlus, mythicPlusProfiles[p])
                end
            end
        end
    end

    -- WarpDeplete
    if WarpDepleteDB then
        local warpDepleteProfiles = WarpDepleteDB["profiles"]
        for p, _ in pairs(warpDepleteProfiles) do
            local isMatch = GU:StartsWithIgnoreCase(p, "atrocityui")
            if isMatch then
                table.insert(profiles.warpDeplete, warpDepleteProfiles[p])
            end
        end
    end

    -- AtrocityEssentials (chat panels)
    if atrocityEssentialsDB then
        local aesProfiles = atrocityEssentialsDB["profiles"]
        for p, _ in pairs(aesProfiles) do
            local isMatch = GU:StartsWithIgnoreCase(p, "atrocityui")
            if isMatch then
                table.insert(profiles.aes, aesProfiles[p])
            end
        end
    end

    -- EllesmereUI itself (minimap, unit frames, etc.)
    -- EllesmereUI keeps its live per-module settings inside a single
    -- account-wide EllesmereUIDB, under
    -- EllesmereUIDB.profiles[profileName].addons.<ModuleName> - NOT directly
    -- under profiles[profileName].<ModuleName>. Ellesmere's own profile names
    -- don't follow the "AtrocityUI" prefix convention used elsewhere in this
    -- addon, so split every profile into dps/healer by name instead of
    -- filtering by prefix - matches Atrocity.lua's LoadProfiles().
    profiles.ellesmere = { dps = {}, healer = {} }
    profiles.ellesmereProfiles = { dps = {}, healer = {} }
    if EllesmereUIDB then
        local ellesmereProfiles = EllesmereUIDB["profiles"]
        if ellesmereProfiles then
            for p, profile in pairs(ellesmereProfiles) do
                local addons = profile and profile["addons"]
                if addons then
                    local isHealer = GU:ContainsIgnoreCase(p, "healer")
                    if isHealer then
                        table.insert(profiles.ellesmere.healer, addons)
                        table.insert(profiles.ellesmereProfiles.healer, profile)
                    else
                        table.insert(profiles.ellesmere.dps, addons)
                        table.insert(profiles.ellesmereProfiles.dps, profile)
                    end
                end
            end
        end
    end
end

local function SetValue(dest, path, value)
    -- Do nothing if dest is nil
    if not dest then return end

    local keys = GU:Split(path, ".");

    for j, _ in pairs(dest) do
        -- Traverse the table up to the second-to-last key
        local current = dest[j]
        for i = 1, #keys - 1 do
            local key = keys[i]
            key = tonumber(key) or key

            -- If the key doesn't exist or isn't a table, create a new table
            if type(current[key]) ~= "table" then
                current[key] = {}
            end
            current = current[key]
        end

        -- Set the value at the final key
        current[keys[#keys]] = value
    end
end

---Applies the EllesmereUI-specific layout/font tweaks.
---@param ztp table self.db.global.ellesmereUI
local function ApplyLayoutTweaks(ztp)
    -- TODO: this is where the real EllesmereUI hooks need to go. These tweaks
    -- need to talk to whatever EllesmereUI itself uses to store its settings.
    -- Once that's known, replace the guards below with real SetValue calls
    -- against that table, following the same pattern as Atrocity.lua.

    if ztp.fonts.resize then
        -- General font size - nameplates: friendly name, left text slot, and
        -- cast bar spell name all follow the "Desired font size" slider
        -- instead of being hardcoded.
        local targets = { profiles.ellesmere.dps, profiles.ellesmere.healer }
        for _, dest in ipairs(targets) do
            SetValue(dest, "EllesmereUINameplates.friendlyNameSize", ztp.fonts.size)
            SetValue(dest, "EllesmereUINameplates.textSlotLeftSize", ztp.fonts.size)
            SetValue(dest, "EllesmereUINameplates.castNameSize", ztp.fonts.size)
        end
    end

    if ztp.layout.panels then
        -- Bigger chat panels (AtrocityEssentials)
        SetValue(profiles.aes, "Chat.Width", 500)
        SetValue(profiles.aes, "Chat.Height", 263)
        SetValue(profiles.aes, "Chat.EditBoxFontSize", 16)
        SetValue(profiles.aes, "Chat.FontSize", 16)
        SetValue(profiles.aes, "Chat.TabFontSize", 14)
    end

    if ztp.layout.minimap then
        local targets = { profiles.ellesmere.dps, profiles.ellesmere.healer }
        for _, dest in ipairs(targets) do
            -- Bigger minimap
            SetValue(dest, "EllesmereUIMinimap.minimap.mapSize", 270)
            SetValue(dest, "EllesmereUIMinimap.minimap.position",
                { point = "CENTER", relPoint = "CENTER", x = 1584, y = 584 })

            -- Queue Status moves alongside the minimap
            SetValue(dest, "EllesmereUIActionBars.barPositions.QueueStatus",
                { point = "CENTER", relPoint = "CENTER", x = 1694, y = 473 })
        end
    end

    if ztp.layout.damageMeters then
        local targets = { profiles.ellesmere.dps, profiles.ellesmere.healer }
        for _, dest in ipairs(targets) do
            SetValue(dest, "EllesmereUIDamageMeters.dm.windowCount", 2)

            -- Damage meter windows carry their own live drag-lock state
            -- (a "locked" sibling key on each window entry, confirmed
            -- present in the active atrocityUI profile but absent from the
            -- Default profile's clean template). Unlocking before writing
            -- position, then relocking after, matches the shape EllesmereUI
            -- itself uses when the window is actually repositioned in-game -
            -- previously we wrote position with only x/y (no point/
            -- relPoint, no locked toggle), which the active profile did not
            -- pick up. Values below are the confirmed-correct current
            -- on-screen coordinates pulled from EllesmereUI.lua's active
            -- atrocityUI profile.
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.1.locked", false)
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.1.width", 250)
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.1.height", 263)
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.1.position",
                { point = "CENTER", relPoint = "CENTER", x = 1594, y = -587.5 })
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.1.locked", true)

            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.2.locked", false)
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.2.width", 250)
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.2.height", 263)
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.2.position",
                { point = "CENTER", relPoint = "CENTER", x = 1344, y = -587.5 })
            SetValue(dest, "EllesmereUIDamageMeters.dm.windows.2.locked", true)
        end

        -- Tooltip (AtrocityEssentials) - repositioned/resized to sit next to
        -- the damage meter windows above.
        SetValue(profiles.aes, "Tooltips.Position.XOffset", -1)
        SetValue(profiles.aes, "Tooltips.Position.YOffset", 267)
        SetValue(profiles.aes, "Tooltips.FontSize", 15)
        SetValue(profiles.aes, "Tooltips.HeaderFontSize", 16)
        SetValue(profiles.aes, "Tooltips.SmallFontSize", 13)
    end

    if ztp.layout.unitFrames then
        -- Action Bar 4 and Pet Bar: repositioned/resized alongside the unit
        -- frame toggle. Icon size lives under bars.<Bar>.buttonWidth/
        -- buttonHeight (NOT "iconSize" - that key only exists on Cooldown
        -- Manager bars), icon count under bars.<Bar>.numIcons/
        -- overrideNumIcons, and position under barPositions.<Bar> - all
        -- confirmed against EllesmereUI.lua's SavedVariables structure.
        do
            local targets = { profiles.ellesmere.dps, profiles.ellesmere.healer }
            for _, dest in ipairs(targets) do
                SetValue(dest, "EllesmereUIActionBars.bars.Bar4.buttonWidth", 43)
                SetValue(dest, "EllesmereUIActionBars.bars.Bar4.buttonHeight", 43)
                SetValue(dest, "EllesmereUIActionBars.bars.Bar4.numIcons", 12)
                SetValue(dest, "EllesmereUIActionBars.bars.Bar4.overrideNumIcons", 12)
                SetValue(dest, "EllesmereUIActionBars.barPositions.Bar4",
                    { point = "CENTER", relPoint = "CENTER", x = -1175, y = -588 })

                SetValue(dest, "EllesmereUIActionBars.bars.PetBar.buttonWidth", 43)
                SetValue(dest, "EllesmereUIActionBars.bars.PetBar.buttonHeight", 43)
                SetValue(dest, "EllesmereUIActionBars.bars.PetBar.numIcons", 12)
                SetValue(dest, "EllesmereUIActionBars.bars.PetBar.overrideNumIcons", 12)
                SetValue(dest, "EllesmereUIActionBars.barPositions.PetBar",
                    { point = "CENTER", relPoint = "CENTER", x = 1174, y = -566 })
            end
        end

        -- Raid Frames: 20-man frame size, party frame size, and anchor
        -- position - all keyed by EllesmereUI's own "Spec Group" system
        -- (Raid Frames > Frames > the group dropdown showing "Default
        -- Editing Mode" / "Healer Mode" / "DPS/Tank Mode 1/2/3"), confirmed
        -- via screenshot. "Healer Mode" maps to the 7 healer spec IDs below;
        -- everything else (Default Editing Mode + all DPS/Tank modes) falls
        -- through to specOverrides' "default" entry for settings that don't
        -- have their own per-spec-group override recorded yet.
        --
        -- IMPORTANT: this is NOT related to Blizzard Edit Mode or any
        -- "layout ID" concept - that was a wrong assumption from earlier in
        -- this file's history (specUnlockOverrides.layouts[4/9/10] was
        -- mistakenly treated as if it mapped to these spec groups). Verified
        -- wrong via an in-game C_EditMode.GetLayouts() dump that showed
        -- completely different, unrelated layout data. All position/size
        -- handling here is spec-based, using the same detection for all
        -- three settings below.
        local HEALER_SPEC_IDS = { [65] = true, [105] = true, [256] = true, [257] = true, [264] = true, [270] = true, [1468] = true }

        -- specOverrides sweep: durability for the size settings, which live
        -- at the profile ROOT (a sibling of "addons", not inside
        -- addons.<ModuleName>) - so we work from the raw profile objects in
        -- profiles.ellesmereProfiles rather than profiles.ellesmere/SetValue().
        -- EllesmereUI re-applies a spec-keyed value onto the flat field
        -- below whenever you swap specs, and auto-creates a new spec-keyed
        -- entry (seeded from whatever the flat value was at the time) the
        -- first time it sees a spec with no entry yet - confirmed happening
        -- for spec 258, seeded with a stale size. Left alone, a future spec
        -- swap would silently re-break the flat fix below. So every
        -- spec-keyed entry gets explicitly swept to its correct target every
        -- time - 100/60 for both settings if it's a healer spec key, 215/50
        -- for partyFrame if not (20-man frame targets 100/60 either way,
        -- since healer and default/dps share that particular target).
        local profileTargets = { profiles.ellesmereProfiles.dps, profiles.ellesmereProfiles.healer }
        for _, dest in ipairs(profileTargets) do
            for _, profile in pairs(dest) do
                local specOverrides = profile.specOverrides
                if specOverrides then
                    for _, entry in pairs(specOverrides) do
                        if entry.module == "EllesmereUIRaidFrames" and entry.values then
                            for specKey, specValues in pairs(entry.values) do
                                local isHealerSpecKey = HEALER_SPEC_IDS[specKey] == true
                                for key, _ in pairs(specValues) do
                                    if key:match("frameWidth$") then
                                        specValues[key] = 100
                                    elseif key:match("frameHeight$") then
                                        specValues[key] = 60
                                    elseif key:match("partyFrameWidth$") then
                                        specValues[key] = isHealerSpecKey and 100 or 215
                                    elseif key:match("partyFrameHeight$") then
                                        specValues[key] = isHealerSpecKey and 60 or 50
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end

        -- addons.EllesmereUIRaidFrames.frameWidth/frameHeight/partyFrameWidth/
        -- partyFrameHeight/unlockPos: the real live fields EllesmereUI
        -- renders from (confirmed - setting unlockPos does visibly move the
        -- frame, and this is the same spec-based gate already proven to work
        -- correctly for party frame size).
        --
        -- frameWidth/frameHeight are safe unconditionally (healer and
        -- default/dps share the same target: 100/60). partyFrameWidth/
        -- partyFrameHeight and unlockPos are NOT safe unconditionally -
        -- healer has a different target than everyone else for both, so each
        -- is bidirectional: always explicitly write whichever target matches
        -- the CURRENT spec, never skip - skipping can't repair a value a
        -- previous apply-on-the-other-side already changed.
        local currentSpecID = GetSpecializationInfo(GetSpecialization() or 0)
        local isCurrentlyHealer = currentSpecID and HEALER_SPEC_IDS[currentSpecID]

        local targets = { profiles.ellesmere.dps, profiles.ellesmere.healer }
        for _, dest in ipairs(targets) do
            SetValue(dest, "EllesmereUIRaidFrames.frameWidth", 100)
            SetValue(dest, "EllesmereUIRaidFrames.frameHeight", 60)

            if isCurrentlyHealer then
                SetValue(dest, "EllesmereUIRaidFrames.partyFrameWidth", 100)
                SetValue(dest, "EllesmereUIRaidFrames.partyFrameHeight", 60)
                SetValue(dest, "EllesmereUIRaidFrames.unlockPos",
                    { point = "CENTER", relPoint = "CENTER", x = 0, y = -400 })
            else
                SetValue(dest, "EllesmereUIRaidFrames.partyFrameWidth", 215)
                SetValue(dest, "EllesmereUIRaidFrames.partyFrameHeight", 50)
                SetValue(dest, "EllesmereUIRaidFrames.unlockPos",
                    { point = "CENTER", relPoint = "CENTER", x = -1467, y = 597 })
            end
        end
    end
end

local function ApplyBigWigsTweaks(ztp)
    if not BigWigs3DB then return end

    -- Font resizing
    if ztp.fonts.resize then
        SetValue(profiles.bigWigs, "fontSize", ztp.fonts.size)
        SetValue(profiles.bigWigs, "fontSizeEmph", ztp.fonts.size)
    end

    if ztp.bigWigs then
        -- Bars - Exact Positioning - Bars
        SetValue(profiles.bigWigs, "normalWidth", 268)
        SetValue(profiles.bigWigs, "normalHeight", 22)

        -- Bars - Exact Positioning - Emphasized bars
        SetValue(profiles.bigWigs, "expWidth", 270)
        SetValue(profiles.bigWigs, "expHeight", 25)

        SetValue(profiles.bigWigs, "visibleBarLimit", 5)
        SetValue(profiles.bigWigs, "visibleBarLimitEmph", 5)

        -- Keystone anchor (MythicPlus "instance keys" bar) - locked to the
        -- position currently found in the SavedVariables at the time this
        -- was written: {"CENTER", "CENTER", -473, 79}.
        SetValue(profiles.bigWigsMythicPlus, "instanceKeysPosition",
            { "CENTER", "CENTER", -473, 79 })
    end
end

local function ApplyWarpDepleteTweaks(ztp)
    if not WarpDepleteDB then return end

    if GU.db.global.scaleFactor then
        SetValue(profiles.warpDeplete, "frameScale", GU.db.global.scaleFactor)
    end

    if ztp.fonts.resize then
        SetValue(profiles.warpDeplete, "objectivesFontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "keyFontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "bar1FontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "bar2FontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "bar3FontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "timerFontSize", 24) -- default to big font for clock
        SetValue(profiles.warpDeplete, "keyDetailsFontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "deathsFontSize", ztp.fonts.size)
        SetValue(profiles.warpDeplete, "forcesFontSize", ztp.fonts.size)
    end

    if ztp.layout.minimap then
        SetValue(profiles.warpDeplete, "frameY", 170)
    end
end

function GU:ApplyEllesmereTweaks()
    local ztp = self.db.global.ellesmereUI

    LoadProfiles()

    ApplyLayoutTweaks(ztp)
    ApplyBigWigsTweaks(ztp)
    ApplyWarpDepleteTweaks(ztp)

    if ztp.layout.buffsObjectives then
        self:ImportEllesmereBuffsObjectivesEditMode()
    end

    ReloadUI()
end

---Ensures the GrimmsUltrawide minimap button is listed in EllesmereUI's
---"Ungroup Minimap Buttons" list, so EllesmereUI shows it individually
---instead of folding it into the grouped button cluster.
function GU:RegisterEllesmereMinimapButton()
    if not EllesmereUIDB then return end

    local dbProfiles = EllesmereUIDB["profiles"]
    if not dbProfiles then return end

    local buttonName = "LibDBIcon10_GrimmsUltrawide"

    for p, profile in pairs(dbProfiles) do
        if GU:StartsWithIgnoreCase(p, "atrocityui") then
            local addons = profile.addons
            local minimapCfg = addons and addons.EllesmereUIMinimap and addons.EllesmereUIMinimap.minimap
            if minimapCfg then
                if type(minimapCfg.ungroupedButtons) ~= "table" then
                    minimapCfg.ungroupedButtons = {}
                end

                if not minimapCfg.ungroupedButtons[buttonName] then
                    local nextOrder = 0
                    for _, order in pairs(minimapCfg.ungroupedButtons) do
                        if type(order) == "number" and order > nextOrder then
                            nextOrder = order
                        end
                    end
                    minimapCfg.ungroupedButtons[buttonName] = nextOrder + 1
                end
            end
        end
    end
end
