-- TOG Tools — Main Window
-- Root AceGUI frame containing a TabGroup.
-- Tabs are dynamically built from addon.modules, sorted alphabetically.
-- Each module registers itself in addon.modules with:
--   { tabKey, label, Draw(container), WINDOW_SIZE }

local _, addon = ...
local AceGUI = LibStub("AceGUI-3.0")

-- ---------------------------------------------------------------------------
-- Module
-- ---------------------------------------------------------------------------
local MainWindow = {}
addon.MainWindow = MainWindow

MainWindow.frame     = nil
MainWindow.tabs      = nil
MainWindow.activeTab = nil

-- ---------------------------------------------------------------------------
-- ESC proxy — intercepts ESC before the AceGUI frame sees it
-- ---------------------------------------------------------------------------
local _escProxy = CreateFrame("Frame", "TOGToolsEscProxy", UIParent)
_escProxy:SetSize(1, 1)
_escProxy:SetAlpha(0)
_escProxy:SetPoint("CENTER")
_escProxy:Hide()
tinsert(UISpecialFrames, "TOGToolsEscProxy")

-- Settings-open inhibit. ESC pressed while the Blizzard Settings panel is
-- open hides every UISpecialFrames entry — including this proxy — via
-- CloseSpecialWindows. Without the flag, the cascade would fire our
-- OnHide and close the main window alongside Settings. addon:OpenSettings
-- sets _settingsOpen before opening; _HookSettingsClose hooks the Settings
-- panel's OnHide and defers a clear one frame later so any same-frame
-- proxy hide is still suppressed.
MainWindow._settingsOpen = false

_escProxy:SetScript("OnHide", function()
    if MainWindow._settingsOpen then return end
    if MainWindow.frame then
        MainWindow:Close()
    end
end)

local _settingsHooked = false
function MainWindow:_HookSettingsClose()
    if _settingsHooked then return end
    -- Retail uses SettingsPanel; older Classic uses InterfaceOptionsFrame.
    -- rawget so the unknown-global lookup doesn't throw on flavours that
    -- lack one or the other.
    local panel = rawget(_G, "SettingsPanel") or rawget(_G, "InterfaceOptionsFrame")
    if not (panel and panel.HookScript) then return end
    panel:HookScript("OnHide", function()
        -- Defer one frame so any same-frame UISpecialFrames cascade
        -- (proxy hide alongside Settings hide) sees the flag still set
        -- and stays suppressed. Then re-arm the proxy so ESC closes the
        -- main window normally in subsequent presses.
        C_Timer.After(0, function()
            MainWindow._settingsOpen = false
            if MainWindow.frame then _escProxy:Show() end
        end)
    end)
    _settingsHooked = true
end

-- ---------------------------------------------------------------------------
-- Build the ordered tab list from registered modules. Modules disabled via
-- Settings > Modules are skipped — their tabs disappear from the strip and
-- the engines short-circuit on the same `db.global.<configKey>.enabled`
-- flag. addon:IsModuleEnabled defaults to true for modules without a
-- configKey (legacy / future modules that haven't opted into the toggle).
-- ---------------------------------------------------------------------------
local function BuildTabDefs()
    local defs = {}
    local devOn = addon.DB and addon.DB.global and addon.DB.global.devTools
    for _, mod in pairs(addon.modules) do
        -- devOnly modules (e.g. the Item DB builder) only appear when the
        -- Developer Tools master switch is on; otherwise they're hidden
        -- regardless of their own enabled flag.
        --
        -- showWhenDev: a container that may host a dev-only sub-view (the Logs
        -- tab hosts the Diagnostics sub-tab) stays visible when Developer Tools
        -- is on even if its own module toggle is off — so a dev who disabled
        -- gameplay logging can still reach Diagnostics.
        local enabled = addon:IsModuleEnabled(mod.tabKey)
        local devShow = mod.showWhenDev and devOn
        if (not mod.devOnly or devOn) and (enabled or devShow) then
            tinsert(defs, { value = mod.tabKey, text = mod.label })
        end
    end
    table.sort(defs, function(a, b) return a.text < b.text end)
    return defs
end

-- ---------------------------------------------------------------------------
-- Per-tab window sizing
-- Each module may expose WINDOW_SIZE = { width, height, locked=true }
-- or WINDOW_SIZE = { minWidth, minHeight } for resizable tabs.
-- ---------------------------------------------------------------------------
function MainWindow:ApplyTabSize(tabKey)
    if not (self.frame and self.frame.frame) then return end
    local mod = addon.modules[tabKey]
    local spec = mod and mod.WINDOW_SIZE
    if not spec then return end

    local f = self.frame
    if spec.locked then
        f:EnableResize(false)
        if f.frame.SetResizeBounds then
            f.frame:SetResizeBounds(spec.width, spec.height, spec.width, spec.height)
        else
            if f.frame.SetMinResize then f.frame:SetMinResize(spec.width, spec.height) end
            if f.frame.SetMaxResize then f.frame:SetMaxResize(spec.width, spec.height) end
        end
        f.frame:SetWidth(spec.width)
        f.frame:SetHeight(spec.height)
        f:DoLayout()
    else
        f:EnableResize(true)
        local minW = spec.minWidth  or 500
        local minH = spec.minHeight or 380
        if f.frame.SetResizeBounds then
            f.frame:SetResizeBounds(minW, minH)
        elseif f.frame.SetMinResize then
            f.frame:SetMinResize(minW, minH)
        end
        local saved = addon.DB.char.frames.mainWindow
        local w = math.max(minW, saved.width  or minW)
        local h = math.max(minH, saved.height or minH)
        f.frame:SetWidth(w)
        f.frame:SetHeight(h)
        f:DoLayout()
    end
end

-- ---------------------------------------------------------------------------
-- Open / Close / Toggle
-- ---------------------------------------------------------------------------
function MainWindow:Open(tabKey)
    if self.frame then
        if tabKey then self:SelectTab(tabKey) end
        self.frame.frame:Raise()
        return
    end

    local tabDefs = BuildTabDefs()
    if #tabDefs == 0 then return end

    -- Tab resolution order: explicit caller arg → last-selected (if the
     -- module still exists) → alphabetically first.
    local savedTab = addon.DB.char.lastTab
    if savedTab and not addon.modules[savedTab] then savedTab = nil end
    local defaultTab = tabKey or savedTab or (tabDefs[1] and tabDefs[1].value)
    self.activeTab = defaultTab

    local f = AceGUI:Create("Frame")
    f:SetTitle(addon.UI.Brand("TOG Tools"))
    f:SetStatusText("v" .. addon.Version)
    f:SetLayout("Fill")
    f:EnableResize(true)

    -- AceGUI's Frame widget hard-codes itself to FULLSCREEN_DIALOG strata
    -- with frame level 100 (see Ace3/AceGUI-3.0/widgets/
    -- AceGUIContainer-Frame.lua:81-82). Blizzard's UnitPopup context menu
    -- — the one that pops when you right-click a player name in chat —
    -- also defaults to FULLSCREEN_DIALOG (see Blizzard_Menu/Menu.lua:2084).
    -- Same strata, and the AceGUI level-100 wins z-order, so the menu we
    -- raise from the Whisper Log Other column lands BEHIND the window.
    -- Dropping the window one strata to DIALOG puts the menu's
    -- FULLSCREEN_DIALOG naturally above us. No noticeable side effects
    -- for our case — the main window is a content tool, not a modal that
    -- needs to sit above other UI; DIALOG is the strata most addon
    -- windows of this kind use.
    if f.frame and f.frame.SetFrameStrata then
        f.frame:SetFrameStrata("DIALOG")
    end

    local saved = addon.DB.char.frames.mainWindow
    saved.width  = saved.width  or 640
    saved.height = saved.height or 460
    f:SetStatusTable(saved)

    -- ----- Bottom-row icon strip -----------------------------------------
    -- Status bar shrinks at the right edge to make room for the help (24)
    -- and gear (20) icons, with 3 px gaps. Close button (AceGUI default)
    -- is at BOTTOMRIGHT, -27, 17 with width 100 → its left edge sits at
    -- -127, so working right-to-left:
    --   close left (-127) -[3]- gear right (-130) … gear left (-150)
    --   -[3]- help right (-153) … help left (-177) -[3]- statusbg (-180)
    -- Vertical centres line up at y=27 across the row: close (17+10),
    -- gear (17+10), help (15+12).
    local statusbg = f.statustext:GetParent()
    statusbg:ClearAllPoints()
    statusbg:SetPoint("BOTTOMLEFT",  f.frame, "BOTTOMLEFT",   15, 15)
    statusbg:SetPoint("BOTTOMRIGHT", f.frame, "BOTTOMRIGHT", -180, 15)

    -- Help "i" icon. OnEnter reads the active tab module's `help` field at
    -- hover time, so a per-tab help block appears here automatically with no
    -- per-icon wiring. Modules without help get a short placeholder.
    local helpIcon = addon.UI.MakeIcon(f.frame, {
        size           = 24,
        texture        = "Interface\\Common\\help-i",
        texCoordCrop   = false,
        onEnter = function(iconFrame)
            local key = self.activeTab
            local mod = addon.modules and addon.modules[key]
            -- Container tabs (like Logs) host their own sub-tab strip
            -- via a separate registry (addon.logCategories). When the
            -- user is on the Logs tab, surface the ACTIVE SUB-TAB's
            -- help instead of the generic outer-Logs blurb so the i
            -- icon stays useful per-view. Falls back to the outer
            -- tab's help if no sub-tab is active or has a help block.
            local help
            if key == "logs" then
                local frames   = addon.DB and addon.DB.char and addon.DB.char.frames
                local subKey   = frames and frames.logsActiveSubTab
                local cat      = subKey and addon.logCategories and addon.logCategories[subKey]
                help = cat and cat.help or nil
            end
            if not help then
                help = (mod and mod.help) or {
                    title = (mod and mod.label) or tostring(key or ""),
                    lines = { "No help available for this tab yet." },
                }
            end
            addon.Tooltip.Owner(iconFrame)
            -- The second arg is `forced`, and Blizzard passes it wherever they
            -- set a floor (Blizzard_TalentUI's SpecButton_OnEnter). The matching
            -- reset to (0, false) is NOT written here on purpose: it happens in
            -- addon.UI.MakeIcon's OnLeave, so it cannot be forgotten. Without it
            -- this 360 would pin every tooltip in the game until logout, since
            -- GameTooltip_OnHide does not clear a minimum width.
            GameTooltip:SetMinimumWidth(360, true)
            GameTooltip:AddLine(addon.UI.Brand(help.title or key), 1, 1, 1, true)
            for _, line in ipairs(help.lines or {}) do
                GameTooltip:AddLine(line, 0.9, 0.9, 0.9, true)
            end
            GameTooltip:AddLine(" ", nil, nil, nil, true)
            GameTooltip:AddLine(addon.UI.Brand("Bottom-row icons (every tab):"), 0.9, 0.9, 0.9, true)
            -- Plain hyphens, not em dashes: the client's fonts stop at Latin-1,
            -- so anything past it draws as a box in the tooltip.
            GameTooltip:AddLine("|TInterface\\Common\\help-i:14:14|t Help - hover for the active tab's help.", 0.9, 0.9, 0.9, true)
            GameTooltip:AddLine("|TInterface\\Icons\\Trade_Engineering:14:14|t Settings - open TOG Tools settings (ESC > Options > AddOns > TOG Tools).", 0.9, 0.9, 0.9, true)
            GameTooltip:Show()
        end,
    })
    helpIcon:SetPoint("BOTTOMRIGHT", f.frame, "BOTTOMRIGHT", -153, 15)
    self._helpIcon = helpIcon

    -- Settings gear. Opens the Blizzard Interface Options panel directly
    -- to the TOG Tools page via addon:OpenSettings().
    local gearIcon = addon.UI.MakeIcon(f.frame, {
        size           = 20,
        texture        = "Interface\\Icons\\Trade_Engineering",
        texCoordCrop   = true,   -- Blizzard Icons have ~8% transparent padding
        tooltipTitle   = addon.UI.Brand("Settings"),
        tooltipBody    = "Opens TOG Tools settings at |cffffd700ESC > Options > AddOns > TOG Tools|r. Also reachable by right-clicking the minimap button.",
        onClick = function()
            if addon.OpenSettings then addon:OpenSettings() end
        end,
    })
    gearIcon:SetPoint("BOTTOMRIGHT", f.frame, "BOTTOMRIGHT", -130, 17)
    self._gearIcon = gearIcon

    -- Cleanup is owned by MainWindow:Close() — see the comment there. This
    -- callback only fires on the AceGUI X-button path; ESC + slash + minimap
    -- toggle go through Close() directly, so duplicating the work here would
    -- be wrong. Just delegate.
    f:SetCallback("OnClose", function() MainWindow:Close() end)

    local tg = AceGUI:Create("TabGroup")
    tg:SetTabs(tabDefs)
    tg:SetLayout("Flow")
    tg:SetFullWidth(true)
    tg:SetFullHeight(true)

    tg:SetCallback("OnGroupSelected", function(widget, _, group)
        self.activeTab = group
        addon.DB.char.lastTab = group
        self:ApplyTabSize(group)
        widget:ReleaseChildren()
        self:DrawTab(group, widget)
    end)

    f:AddChild(tg)
    self.frame = f
    self.tabs  = tg

    _escProxy:Show()
    self:ApplyTabSize(defaultTab)
    tg:SelectTab(defaultTab)
end

function MainWindow:Close()
    if self.frame then
        -- Detach the custom icon children BEFORE Release so the pooled
        -- AceGUI Frame goes back to AceGUI's widget pool clean. Without
        -- this, the next addon to AceGUI:Create("Frame") gets our frame
        -- with our gear / help icons still parented to it — and our
        -- gear's OnClick (which calls addon:OpenSettings) bleeds into
        -- whatever the other addon's main window is. Has to live HERE,
        -- not in the f:SetCallback("OnClose", ...) handler, because
        -- AceGUI:Release fires "OnRelease" (not "OnClose"), so ESC /
        -- _escProxy / slash-command closes never trigger the AceGUI
        -- OnClose callback — only the X-button path does.
        addon.UI.DetachChildren(self, { "_helpIcon", "_gearIcon" })
        AceGUI:Release(self.frame)
        self.frame = nil
        self.tabs  = nil
    end
    _escProxy:Hide()
end

function MainWindow:Toggle(tabKey)
    if self.frame then
        self:Close()
    else
        self:Open(tabKey)
    end
end

function MainWindow:SelectTab(key)
    if self.tabs then
        self.tabs:SelectTab(key)
    end
end

-- ---------------------------------------------------------------------------
-- Tab drawing — delegates to the module that owns the tab
-- ---------------------------------------------------------------------------
function MainWindow:DrawTab(group, container)
    -- Reset status bar to version string; individual tabs may override it.
    if self.frame then self.frame:SetStatusText("v" .. addon.Version) end
    local mod = addon.modules[group]
    if mod and mod.Draw then
        mod:Draw(container)
    end
end

function MainWindow:Refresh()
    if not (self.frame and self.tabs) then return end
    self.tabs:ReleaseChildren()
    self:DrawTab(self.activeTab, self.tabs)
end

-- ---------------------------------------------------------------------------
-- Rebuild the tab strip from the current enabled-state of each module.
-- Called by Settings > Modules toggles (and any in-tab "Enable" checkbox
-- that wants the disappearance to be immediate). If the previously active
-- tab is still enabled, selection stays put; otherwise the first remaining
-- enabled tab is selected. If everything is disabled, the window closes —
-- the user gets back via RMB on the minimap button or /togt settings to
-- re-enable at least one module.
-- ---------------------------------------------------------------------------
function MainWindow:Rebuild()
    if not (self.frame and self.tabs) then return end
    local tabDefs = BuildTabDefs()
    if #tabDefs == 0 then
        self:Close()
        return
    end
    self.tabs:SetTabs(tabDefs)
    local target = self.activeTab
    local stillValid = false
    for _, def in ipairs(tabDefs) do
        if def.value == target then stillValid = true break end
    end
    if not stillValid then target = tabDefs[1].value end
    self.activeTab = target
    self:ApplyTabSize(target)
    self.tabs:SelectTab(target)
end
