--[[
    Commander 3.0 - Tab Strip Component

    Manages multiple terminal sessions as tabs.
    Uses Tab class for proper encapsulation and lifecycle management.

    Supports:
    - Tab creation/deletion with proper session lifecycle
    - Tab switching
    - Drag-and-drop reordering
    - Cross-window tab transfers
    - Tab renaming
]]

local ADDON_NAME, C3 = ...

-------------------------------------------------------------------------------
-- Tab Strip Class
-------------------------------------------------------------------------------

local TabStrip = C3.class()
C3.UI = C3.UI or {}
C3.UI.TabStrip = TabStrip

-- Constants
local TAB_HEIGHT = 20
local TAB_PADDING = 2
local MAX_TABS = 16

-------------------------------------------------------------------------------
-- Toast Notification (fading popup)
-------------------------------------------------------------------------------

local function ShowRejectedTabToast(tabStrip)
    if not tabStrip or not tabStrip.window then return end
    local parent = tabStrip.window.content or tabStrip.window.frame

    -- Create temporary frame anchored at top of output area
    local f = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    f:SetSize(180, 24)
    f:SetPoint("TOP", parent, "TOP", 0, -8)
    f:SetFrameStrata("TOOLTIP")

    -- Red tinted backdrop
    f:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    f:SetBackdropColor(0.4, 0.1, 0.1, 0.9)
    f:SetBackdropBorderColor(0.8, 0.2, 0.2, 1)

    -- "Max tabs" text
    local text = f:CreateFontString(nil, "OVERLAY")
    local font = C3.UI.Theme and C3.UI.Theme.Font or "Fonts\\FRIZQT__.TTF"
    text:SetFont(font, 11, "")
    text:SetPoint("CENTER", 0, 0)
    local errColor = (C3.UI.Theme and C3.UI.Theme:GetColor("accent.error")) or "BF616A"
    text:SetText(string.format("|cff%sMaximum tabs reached|r", errColor))

    f:Show()

    -- Fade out animation
    local elapsed = 0
    local duration = 0.8
    f:SetScript("OnUpdate", function(self, dt)
        elapsed = elapsed + dt
        local progress = elapsed / duration
        if progress >= 1 then
            self:Hide()
            self:SetParent(nil)
            return
        end
        self:SetAlpha(1 - progress)
    end)
end

---Create a new tab strip
---@param parent frame Parent frame
---@param options? table
function TabStrip:init(parent, options)
    options = options or {}

    self.parent = parent
    self.window = options.window  -- Parent TerminalWindow for cross-window operations
    self.tabs = {}           -- Array of Tab objects
    self.activeTab = nil     -- Currently active Tab
    self.dragTab = nil       -- Tab being dragged

    -- Callbacks
    self.onTabSelected = options.onTabSelected
    self.onTabClosed = options.onTabClosed
    self.onTabCreated = options.onTabCreated

    -- Create context menu for tab right-click actions
    self.contextMenu = C3.UI.ContextMenu:new(parent)

    -- Create the container frame
    self:_createFrame()
end

-------------------------------------------------------------------------------
-- Frame Creation
-------------------------------------------------------------------------------

function TabStrip:_createFrame()
    -- Parent to external tabBar if available, otherwise fallback to parent frame
    local parentFrame = (self.window and self.window.tabBar) or self.parent

    self.frame = CreateFrame("Frame", nil, parentFrame)
    self.frame:SetPoint("TOPLEFT", 4, -4)
    self.frame:SetPoint("BOTTOMRIGHT", -64, 4)  -- Room for controls on right, fill height
    self.frame:EnableMouse(true)

    -- Click-to-focus on empty tab bar area
    self.frame:SetScript("OnMouseDown", function(_, button)
        if button == "LeftButton" and self.window and self.window.input then
            self.window.input:Focus()
        end
    end)

    -- NO background - tabBar has its own backdrop now

    -- Tab container (fills tab strip, new tab button added separately)
    self.tabContainer = CreateFrame("Frame", nil, self.frame)
    self.tabContainer:SetAllPoints()

    -- Create new tab button
    self:_createNewTabButton()
end

function TabStrip:_createNewTabButton()
    local btn = CreateFrame("Button", nil, self.frame)
    btn:SetSize(24, TAB_HEIGHT - 4)

    -- Background on hover
    local bg = btn:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0, 0, 0, 0)  -- Transparent by default
    btn._bg = bg

    local Theme = C3.UI.Theme
    local tabColors = Theme and Theme.colors and Theme.colors.tabs
    local textInactive = { 0.6, 0.64, 0.68 }
    local textActive = { 0.9, 0.92, 0.95 }
    local hoverBg = { 0.11, 0.12, 0.13, 0.35 }
    if Theme then
        if tabColors and type(tabColors.textInactive) == "string" then
            local r, g, b = Theme:HexToRGB(tabColors.textInactive)
            textInactive = { r, g, b }
        end
        if tabColors and type(tabColors.textActive) == "string" then
            local r, g, b = Theme:HexToRGB(tabColors.textActive)
            textActive = { r, g, b }
        end
        if tabColors and type(tabColors.hover) == "table" then
            hoverBg = tabColors.hover
        end
    end

    -- Plus sign
    local text = btn:CreateFontString(nil, "OVERLAY")
    local font = C3.UI.Theme and C3.UI.Theme.Font or "Fonts\\FRIZQT__.TTF"
    local fontSize = C3.UI.Theme and math.max(C3.UI.Theme:GetEffectiveFontSize() - 2, 10) or 11
    text:SetFont(font, fontSize, "")
    text:SetPoint("CENTER", 0, 0)
    text:SetText("+")
    text:SetTextColor(textInactive[1], textInactive[2], textInactive[3])
    btn._text = text

    local self_ = self
    btn:SetScript("OnClick", function()
        self_:CreateTab({ activate = true })
    end)

    btn:SetScript("OnEnter", function()
        text:SetTextColor(textActive[1], textActive[2], textActive[3])
        bg:SetColorTexture(hoverBg[1], hoverBg[2], hoverBg[3], hoverBg[4] or 1)
    end)

    btn:SetScript("OnLeave", function()
        text:SetTextColor(textInactive[1], textInactive[2], textInactive[3])
        bg:SetColorTexture(0, 0, 0, 0)
    end)

    self.newTabBtn = btn
end

-------------------------------------------------------------------------------
-- Tab Management
-------------------------------------------------------------------------------

---Create a new tab
---@param options? table { session?, buffer?, name?, activate? }
---@return Tab?
function TabStrip:CreateTab(options)
    options = options or {}

    -- Check tab limit
    if #self.tabs >= MAX_TABS then
        ShowRejectedTabToast(self)
        return nil
    end

    -- Create Tab object
    local tab = C3.UI.Tab:new({
        session = options.session,
        buffer = options.buffer,
        name = options.name,
    })

    -- Attach to this strip (creates button)
    tab:AttachTo(self)
    table.insert(self.tabs, tab)

    -- Layout all tabs
    self:_layoutTabs()

    -- Fire callback
    if self.onTabCreated then
        self.onTabCreated(tab)
    end

    -- Activate if first tab or requested
    if #self.tabs == 1 or options.activate then
        self:SelectTab(tab)
    end

    return tab
end

---Create a tab at a specific position
---@param options table
---@param position number
---@return Tab
function TabStrip:CreateTabAt(options, position)
    options = options or {}

    -- Create Tab object
    local tab = C3.UI.Tab:new({
        session = options.session,
        buffer = options.buffer,
        name = options.name,
    })

    -- Attach to this strip
    tab:AttachTo(self)

    -- Insert at position
    position = math.max(1, math.min(position or (#self.tabs + 1), #self.tabs + 1))
    table.insert(self.tabs, position, tab)

    -- Layout all tabs
    self:_layoutTabs()

    -- Fire callback
    if self.onTabCreated then
        self.onTabCreated(tab)
    end

    -- Activate if requested
    if options.activate then
        self:SelectTab(tab)
    end

    return tab
end

---Add an existing Tab object to this strip
---@param tab Tab
---@param position number?
function TabStrip:AddTab(tab, position)
    if tab.tabStrip then
        error("Tab is already attached. Use TransferTab instead.")
    end

    tab:AttachTo(self)
    position = position or (#self.tabs + 1)
    position = math.max(1, math.min(position, #self.tabs + 1))
    table.insert(self.tabs, position, tab)

    self:_layoutTabs()
end

---Layout tabs horizontally
function TabStrip:_layoutTabs()
    local containerWidth = self.tabContainer:GetWidth() or 200
    local numTabs = #self.tabs

    -- Reserve space for new tab button
    local NEW_TAB_BTN_WIDTH = 28
    local availableWidth = containerWidth - NEW_TAB_BTN_WIDTH

    if numTabs == 0 then
        -- Position new tab button at start when no tabs
        if self.newTabBtn then
            self.newTabBtn:ClearAllPoints()
            self.newTabBtn:SetPoint("LEFT", self.tabContainer, "LEFT", 0, 0)
        end
        -- Notify window of zero tabs
        if self.window and self.window.OnTabCountChanged then
            self.window:OnTabCountChanged(0)
        end
        return
    end

    -- Calculate tab width - tabs fill available space exactly
    local totalPadding = (numTabs - 1) * TAB_PADDING
    local availableForTabs = availableWidth - totalPadding
    local tabWidth = math.floor(availableForTabs / numTabs)

    -- Calculate remainder pixels to distribute to last tab
    local remainder = availableForTabs - (tabWidth * numTabs)

    local xOffset = 0
    for i, tab in ipairs(self.tabs) do
        -- Update tab number to match position
        if tab.SetNumber then
            tab:SetNumber(i)
        end
        if tab.button then
            tab.button:ClearAllPoints()
            tab.button:SetPoint("LEFT", self.tabContainer, "LEFT", xOffset, 0)
            -- Last tab gets any extra pixels from rounding
            local thisTabWidth = (i == numTabs) and (tabWidth + remainder) or tabWidth
            tab.button:SetWidth(thisTabWidth)
            xOffset = xOffset + thisTabWidth + TAB_PADDING
        end
    end

    -- Position new tab button after last tab
    if self.newTabBtn then
        self.newTabBtn:ClearAllPoints()
        self.newTabBtn:SetPoint("LEFT", self.tabContainer, "LEFT", xOffset, 0)
    end

    -- Notify window of tab count change (for auto-hide logic)
    if self.window and self.window.OnTabCountChanged then
        self.window:OnTabCountChanged(numTabs)
    end
end

---Select a tab
---@param tab Tab
---@param noCallback? boolean If true, do not fire onTabSelected event
function TabStrip:SelectTab(tab, noCallback)
    if not tab then return end

    -- Deselect previous
    if self.activeTab then
        self.activeTab:SetActive(false)
    end

    -- Select new
    self.activeTab = tab
    tab:SetActive(true)

    -- Fire callback (unless suppressed)
    if not noCallback and self.onTabSelected then
        self.onTabSelected(tab)
    end
end

---Close a tab
---@param tab Tab
function TabStrip:CloseTab(tab)
    -- Don't close last tab
    if #self.tabs <= 1 then
        return
    end

    local tabIndex = self:_getTabIndex(tab)
    if not tabIndex then return end

    -- Fire callback before removal
    if self.onTabClosed then
        self.onTabClosed(tab)
    end

    -- Remove from array
    table.remove(self.tabs, tabIndex)

    -- Destroy tab (handles session release, button cleanup)
    tab:Destroy()

    -- Re-layout
    self:_layoutTabs()

    -- If we closed the active tab, select adjacent
    if self.activeTab == tab then
        local newIndex = math.min(tabIndex, #self.tabs)
        self:SelectTab(self.tabs[newIndex])
    end
end

---Remove a tab without destroying it (for transfers)
---@param tab Tab
---@return boolean success
function TabStrip:DetachTab(tab)
    local tabIndex = self:_getTabIndex(tab)
    if not tabIndex then return false end

    -- Remove from array
    table.remove(self.tabs, tabIndex)

    -- Detach from this strip (removes button)
    tab:Detach()

    -- Re-layout
    self:_layoutTabs()

    -- Handle active tab
    if self.activeTab == tab then
        local newIndex = math.min(tabIndex, #self.tabs)
        if newIndex >= 1 and self.tabs[newIndex] then
            self:SelectTab(self.tabs[newIndex])
        else
            self.activeTab = nil
        end
    end

    -- Fire callback (for window auto-close check)
    if self.onTabClosed then
        self.onTabClosed(tab)
    end

    return true
end

---Find tab index
---@param tab Tab
---@return number?
function TabStrip:_getTabIndex(tab)
    for i, t in ipairs(self.tabs) do
        if t.id == tab.id then
            return i
        end
    end
    return nil
end

-------------------------------------------------------------------------------
-- Drag and Drop
-------------------------------------------------------------------------------

---Start dragging a tab
---@param tab Tab
function TabStrip:StartTabDrag(tab)
    if not tab then return end
    self.dragTab = tab

    -- Bring window to front on drag start
    if self.window and C3.WindowRegistry then
        C3.WindowRegistry:Focus(self.window)
    end

    -- Disable window drag while tab is dragging
    self:_disableWindowDrag()

    -- Start drag controller
    if C3.DragController then
        C3.DragController:BeginTabDrag(self, tab)
    elseif C3.DragFeedback then
        C3.DragFeedback:Start("tab", "Dragging tab")
    end
end

---End tab drag
function TabStrip:EndTabDrag()
    if self._endingDrag then return end
    if not self.dragTab then return end
    self._endingDrag = true
    self.dragTab = nil

    if C3.DragController then
        C3.DragController:EndDrag()
    else
        if C3.DragFeedback then
            C3.DragFeedback:Stop()
        end
        self:_layoutTabs()
    end

    -- Re-enable window drag
    self:_restoreWindowDrag()

    C3.DragState = nil
    self._endingDrag = false
end

---Disable window drag during tab drag
function TabStrip:_disableWindowDrag()
    if not self.window then return end
    if self.window.SetDragLocked then
        self.window:SetDragLocked(true)
    end
end

---Restore window drag after tab drag
function TabStrip:_restoreWindowDrag()
    if not self.window then return end
    if self.window.SetDragLocked then
        self.window:SetDragLocked(false)
    end
end

---Reorder a tab within the same window
---@param tab Tab
---@param newPosition number
function TabStrip:_reorderTab(tab, newPosition)
    if not newPosition then
        self:_layoutTabs()
        return
    end

    local oldIndex = self:_getTabIndex(tab)
    if not oldIndex then
        self:_layoutTabs()
        return
    end

    -- Adjust target position if removing before it
    local adjustedPosition = newPosition
    if oldIndex < newPosition then
        adjustedPosition = newPosition - 1
    end

    -- Clamp to valid range
    adjustedPosition = math.max(1, math.min(#self.tabs, adjustedPosition))

    if oldIndex ~= adjustedPosition then
        table.remove(self.tabs, oldIndex)
        table.insert(self.tabs, adjustedPosition, tab)
    end

    self:_layoutTabs()
end

---Swap two tabs (possibly across windows)
---@param tab Tab
---@param targetWindow table
---@param targetTab Tab
function TabStrip:_swapWithTarget(tab, targetWindow, targetTab)
    if not targetTab then
        self:_layoutTabs()
        return
    end

    local targetStrip = targetWindow.tabs
    if not targetStrip then
        self:_layoutTabs()
        return
    end

    if targetWindow == self.window then
        -- Same window swap
        local idx1 = self:_getTabIndex(tab)
        local idx2 = self:_getTabIndex(targetTab)
        if idx1 and idx2 then
            self.tabs[idx1], self.tabs[idx2] = self.tabs[idx2], self.tabs[idx1]
        end
        self:_layoutTabs()
    else
        -- Cross-window swap
        local myIndex = self:_getTabIndex(tab)
        local theirIndex = targetStrip:_getTabIndex(targetTab)

        if myIndex and theirIndex then
            -- Detach both tabs
            self:DetachTab(tab)
            targetStrip:DetachTab(targetTab)

            -- Add to opposite windows
            targetStrip:AddTab(tab, theirIndex)
            self:AddTab(targetTab, myIndex)

            -- Select the swapped tabs
            targetStrip:SelectTab(tab)
            self:SelectTab(targetTab)
        end
    end
end

---Transfer a tab to another window
---@param tab Tab
---@param targetWindow table
---@param insertPosition number?
function TabStrip:_transferTab(tab, targetWindow, insertPosition)
    if not targetWindow or not targetWindow.tabs then
        self:_layoutTabs()
        return
    end

    -- Detach from this strip
    if not self:DetachTab(tab) then
        self:_layoutTabs()
        return
    end

    -- Add to target
    targetWindow:AdoptTab(tab, insertPosition)
end

---Detach a tab to a new window
---@param tab Tab
function TabStrip:_detachToNewWindow(tab)
    -- Can't detach if only one tab
    if #self.tabs <= 1 then
        self:_layoutTabs()
        return
    end

    -- Check window limit first
    if C3.WindowRegistry and not C3.WindowRegistry:CanCreate() then
        C3.WindowRegistry:ShowMaxWindowsToast()
        self:_layoutTabs()
        return
    end

    -- Get cursor position for new window placement
    local scale = UIParent:GetEffectiveScale()
    local cursorX, cursorY = GetCursorPosition()
    cursorX, cursorY = cursorX / scale, cursorY / scale

    -- Create new window
    local newWindow = C3:CreateTerminalWindow({
        width = self.window and self.window.frame:GetWidth() or 700,
        height = self.window and self.window.frame:GetHeight() or 450,
        skipInitialTab = true,  -- Don't create default tab
    })
    if not newWindow then
        self:_layoutTabs()
        return
    end

    -- Detach from this strip
    self:DetachTab(tab)

    -- Position at cursor
    newWindow.frame:ClearAllPoints()
    newWindow.frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT",
                             cursorX - 50, cursorY + 30)

    -- Adopt the tab
    newWindow:AdoptTab(tab)

    -- Focus the new window
    if C3.WindowRegistry then
        C3.WindowRegistry:Focus(newWindow)
    else
        newWindow:Show()
    end
end

-------------------------------------------------------------------------------
-- Context Menu
-------------------------------------------------------------------------------

---Show context menu for a tab
---@param tab Tab
function TabStrip:ShowTabMenu(tab)
    local menu = {
        { text = "Duplicate Tab", func = function()
            self:CreateTab({ activate = true })
        end },
        { text = "Detach to New Window", func = function()
            self:_detachToNewWindow(tab)
        end, isDisabled = (#self.tabs <= 1) },
        { isSeparator = true },
        { text = "Close Tab", func = function()
            self:CloseTab(tab)
        end, isDisabled = (#self.tabs <= 1) },
    }

    local x, y = GetCursorPosition()
    self.contextMenu:Show(menu, x, y)
end

-------------------------------------------------------------------------------
-- Public API
-------------------------------------------------------------------------------

---Get active tab
---@return Tab?
function TabStrip:GetActiveTab()
    return self.activeTab
end

---Get all tabs
---@return Tab[]
function TabStrip:GetTabs()
    return self.tabs
end

---Get tab by ID
---@param id string
---@return Tab?
function TabStrip:GetTabById(id)
    for _, tab in ipairs(self.tabs) do
        if tab.id == id then
            return tab
        end
    end
    return nil
end

---Get tab count
---@return number
function TabStrip:GetTabCount()
    return #self.tabs
end

---Add a tab from an existing session (legacy compatibility)
---@param session table
---@return Tab
function TabStrip:Add(session)
    return self:CreateTab({ session = session, name = session.name })
end

---Remove a tab (legacy compatibility)
---@param tab Tab
function TabStrip:Remove(tab)
    self:CloseTab(tab)
end

---Get all tabs (legacy compatibility)
---@return Tab[]
function TabStrip:GetAll()
    return self.tabs
end

return TabStrip
