-- [ CANVAS MODE - DOCK ] ----------------------------------------------------------------------------
local _, Orbit = ...
local L = Orbit.L
local OrbitEngine = Orbit.Engine
local CanvasMode = OrbitEngine.CanvasMode
local Dialog = CanvasMode.Dialog
local C = CanvasMode.Constants
local Layout = OrbitEngine.Layout
local Levels = Orbit.Constants.Levels
local BuildAnchorPoint = OrbitEngine.PositionUtils.BuildAnchorPoint
local BuildComponentSelfAnchor = OrbitEngine.PositionUtils.BuildComponentSelfAnchor
local AnchorOffsetsToFinal = OrbitEngine.PositionUtils.AnchorOffsetsToFinal
local GameTooltip = Orbit.Tooltip

local DOCK_OFFSET_X = 14
local DOCK_OFFSET_Y = 8
local EXCLUSIVE_PAIRS = { HealthText = "Status", Status = "HealthText" }
local DOCK_ICON_ALPHA = 0.7
local DOCK_TEXT_KEYS = { Missions = true } -- keys that have no reliable dock icon; show text label instead
local DOCK_BG_COLOR = { 0.2, 0.2, 0.2, 0.6 }
local DOCK_BG_HOVER = { 0.3, 0.5, 0.3, 0.8 }
local DOCK_DROP_GREEN = { 0.3, 0.8, 0.3 }
local function DisplayName(key)
    local Schema = CanvasMode and CanvasMode.SettingsSchema
    if Schema and Schema.ResolveTitle then return Schema.ResolveTitle(key) end
    return key
end
-- [ DOCK FRAME ] ------------------------------------------------------------------------------------
Dialog.DisabledDock = CreateFrame("Frame", nil, Dialog)
Dialog.DisabledDock:SetPoint("TOPLEFT", Dialog.PreviewContainer, "BOTTOMLEFT", DOCK_OFFSET_X, DOCK_OFFSET_Y)
Dialog.DisabledDock:SetPoint("TOPRIGHT", Dialog.PreviewContainer, "BOTTOMRIGHT", -DOCK_OFFSET_X, DOCK_OFFSET_Y)
Dialog.DisabledDock:SetHeight(C.DOCK_HEIGHT)
Dialog.DisabledDock:SetFrameLevel(Dialog.PreviewContainer:GetFrameLevel() + 50)

-- Dock hint text (shown when empty)
Dialog.DisabledDock.EmptyHint = Dialog.DisabledDock:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
Dialog.DisabledDock.EmptyHint:SetPoint("CENTER", Dialog.DisabledDock, "CENTER", 0, 0)
Dialog.DisabledDock.EmptyHint:SetText(L.TOUR_CM_DOCK_HINT)
Dialog.DisabledDock.EmptyHint:SetTextColor(1, 1, 1, 0.3)

if Orbit.EventBus then
    Orbit.EventBus:On("ORBIT_LOCALE_REBUILT", function()
        Dialog.DisabledDock.EmptyHint:SetText(L.TOUR_CM_DOCK_HINT)
    end)
end

-- Container for dock component icons (horizontal row)
Dialog.DisabledDock.IconContainer = CreateFrame("Frame", nil, Dialog.DisabledDock)
Dialog.DisabledDock.IconContainer:SetPoint("TOPLEFT", Dialog.DisabledDock, "TOPLEFT", C.DOCK_PADDING, -C.DOCK_PADDING)
Dialog.DisabledDock.IconContainer:SetPoint("BOTTOMRIGHT", Dialog.DisabledDock, "BOTTOMRIGHT", -C.DOCK_PADDING, C.DOCK_PADDING)

-- Drop highlight for dock
Dialog.DisabledDock.DropHighlight = Dialog.DisabledDock:CreateTexture(nil, "ARTWORK")
Dialog.DisabledDock.DropHighlight:SetAllPoints()
Dialog.DisabledDock.DropHighlight:SetColorTexture(DOCK_DROP_GREEN[1], DOCK_DROP_GREEN[2], DOCK_DROP_GREEN[3], 0.2)
Dialog.DisabledDock.DropHighlight:Hide()

-- [ DIVIDER (between dock and override settings, shown on component selection) ] --------------------
Dialog.ViewportDivider = Dialog:CreateTexture(nil, "ARTWORK")
Dialog.ViewportDivider:SetAtlas("ui-journeys-renown-divider", true)
Dialog.ViewportDivider:SetPoint("TOPLEFT", Dialog.DisabledDock, "BOTTOMLEFT", 0, 0)
Dialog.ViewportDivider:SetPoint("TOPRIGHT", Dialog.DisabledDock, "BOTTOMRIGHT", 0, 0)
Dialog.ViewportDivider:Hide()

-- [ OVERRIDE SETTINGS CONTAINER (below divider) ] ---------------------------------------------------
Dialog.OverrideContainer = CreateFrame("Frame", nil, Dialog)
Dialog.OverrideContainer:SetPoint("TOPLEFT", Dialog.ViewportDivider, "BOTTOMLEFT", 0, -C.OVERRIDE_SECTION_PADDING)
Dialog.OverrideContainer:SetPoint("TOPRIGHT", Dialog.ViewportDivider, "BOTTOMRIGHT", 0, -C.OVERRIDE_SECTION_PADDING)
Dialog.OverrideContainer:SetHeight(1)
Dialog.OverrideContainer:SetFrameLevel(Dialog.NineSlice:GetFrameLevel() + Levels.CanvasOverlay)
Dialog.OverrideContainer:Hide()

Dialog.OverrideContainer.Title = Dialog.OverrideContainer:CreateFontString(nil, "ARTWORK", "GameFontNormal")
Dialog.OverrideContainer.Title:SetPoint("TOPLEFT", Dialog.OverrideContainer, "TOPLEFT", C.DIALOG_INSET, 0)

-- [ DOCK LAYOUT ] -----------------------------------------------------------------------------------
function Dialog:LayoutDockIcons()
    local container = self.DisabledDock.IconContainer
    local stride = C.DOCK_ICON_SIZE + C.DOCK_ICON_SPACING
    local x = 0

    for key, icon in pairs(self.dockComponents) do
        if icon:IsShown() then
            icon:ClearAllPoints()
            icon:SetPoint("TOPLEFT", container, "TOPLEFT", x, 0)
            x = x + stride
        end
    end
end

-- [ ADD TO DOCK ] -----------------------------------------------------------------------------------
function Dialog:AddToDock(key, sourceComponent)
    if self.dockComponents[key] then return end

    local icon = CreateFrame("Button", nil, self.DisabledDock.IconContainer)
    OrbitEngine.Pixel:Enforce(icon)
    icon:SetSize(C.DOCK_ICON_SIZE, C.DOCK_ICON_SIZE)
    icon.key = key

    local iScale = icon:GetEffectiveScale() or 1
    local edgePx = OrbitEngine.Pixel:Multiple(1, iScale)
    local innerSize = OrbitEngine.Pixel:Snap(C.DOCK_ICON_SIZE, iScale) - OrbitEngine.Pixel:Multiple(4, iScale)

    icon.bg = icon:CreateTexture(nil, "BACKGROUND")
    icon.bg:SetAllPoints()
    icon.bg:SetColorTexture(unpack(DOCK_BG_COLOR))

    for _, edge in ipairs({"TOP", "BOTTOM", "LEFT", "RIGHT"}) do
        local t = icon:CreateTexture(nil, "BORDER")
        t:SetColorTexture(0.15, 0.15, 0.15, 0.4)
        if edge == "TOP" then t:SetPoint("TOPLEFT"); t:SetPoint("TOPRIGHT"); t:SetHeight(edgePx)
        elseif edge == "BOTTOM" then t:SetPoint("BOTTOMLEFT"); t:SetPoint("BOTTOMRIGHT"); t:SetHeight(edgePx)
        elseif edge == "LEFT" then t:SetPoint("TOPLEFT"); t:SetPoint("BOTTOMLEFT"); t:SetWidth(edgePx)
        else t:SetPoint("TOPRIGHT"); t:SetPoint("BOTTOMRIGHT"); t:SetWidth(edgePx) end
    end

    local isTexture = sourceComponent and sourceComponent.GetTexture
    local isFontString = sourceComponent and sourceComponent.GetFont ~= nil
    local isIconFrame = sourceComponent and sourceComponent.Icon and sourceComponent.Icon.GetTexture

    if DOCK_TEXT_KEYS[key] then
        icon.visual = icon:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        icon.visual:SetPoint("CENTER")
        icon.visual:SetText(DisplayName(key))
        icon.visual:SetTextColor(0.9, 0.9, 0.9, 1)
        icon.visual:SetWordWrap(true)
        icon.visual:SetWidth(innerSize)
    elseif isTexture and not isFontString then
        icon.visual = icon:CreateTexture(nil, "OVERLAY")
        icon.visual:SetPoint("CENTER")
        icon.visual:SetSize(innerSize, innerSize)

        local atlasName = sourceComponent.GetAtlas and sourceComponent:GetAtlas()
        if atlasName then
            icon.visual:SetAtlas(atlasName)
        else
            local texturePath = sourceComponent:GetTexture()
            if texturePath then icon.visual:SetTexture(texturePath) end

            if sourceComponent.GetTexCoord then
                local ULx, ULy, LLx, LLy, URx, URy, LRx, LRy = sourceComponent:GetTexCoord()
                if ULx and ULy then
                    if LRx then
                        icon.visual:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
                    else
                        icon.visual:SetTexCoord(ULx, ULy, LLx, LLy)
                    end
                end
            end

            if key == "MarkerIcon" then
                local tc = Orbit.MarkerIconTexCoord
                if tc then icon.visual:SetTexCoord(tc[1], tc[2], tc[3], tc[4]) end
            end

            if sourceComponent.orbitSpriteIndex then
                local index = sourceComponent.orbitSpriteIndex
                local rows = sourceComponent.orbitSpriteRows or 4
                local cols = sourceComponent.orbitSpriteCols or 4
                local col = (index - 1) % cols
                local row = math.floor((index - 1) / cols)
                local w, h = 1 / cols, 1 / rows
                icon.visual:SetTexCoord(col * w, (col + 1) * w, row * h, (row + 1) * h)
            end
        end

        icon.visual:SetDesaturated(true)
        icon.visual:SetAlpha(DOCK_ICON_ALPHA)
    elseif isIconFrame then
        icon.visual = icon:CreateTexture(nil, "OVERLAY")
        icon.visual:SetPoint("CENTER")
        icon.visual:SetSize(innerSize, innerSize)

        local iconTex = sourceComponent.Icon
        local texturePath = iconTex and iconTex:GetTexture()
        local atlasName = iconTex and iconTex.GetAtlas and iconTex:GetAtlas()
        local StatusMixin = Orbit.StatusIconMixin
        if atlasName then
            icon.visual:SetAtlas(atlasName)
        elseif texturePath then
            icon.visual:SetTexture(texturePath)
            if iconTex.GetTexCoord then
                local ULx, ULy, LLx, LLy, URx, URy, LRx, LRy = iconTex:GetTexCoord()
                if ULx and ULy then
                    if LRx then
                        icon.visual:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
                    else
                        icon.visual:SetTexCoord(ULx, ULy, LLx, LLy)
                    end
                end
            end
        elseif StatusMixin and key == "DefensiveIcon" then
            icon.visual:SetTexture(StatusMixin:GetDefensiveTexture())
        elseif StatusMixin and key == "CrowdControlIcon" then
            icon.visual:SetTexture(StatusMixin:GetCrowdControlTexture())
        else
            local previewAtlases = Orbit.IconPreviewAtlases or {}
            if previewAtlases[key] then icon.visual:SetAtlas(previewAtlases[key], false) end
        end

        icon.visual:SetDesaturated(true)
        icon.visual:SetAlpha(DOCK_ICON_ALPHA)
    elseif key == "Portrait" then
        icon.visual = icon:CreateTexture(nil, "OVERLAY")
        icon.visual:SetPoint("CENTER")
        icon.visual:SetSize(innerSize, innerSize)
        SetPortraitTexture(icon.visual, "player")
        icon.visual:SetDesaturated(true)
        icon.visual:SetAlpha(DOCK_ICON_ALPHA)
    else
        icon.visual = icon:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        icon.visual:SetPoint("CENTER")
        icon.visual:SetText(DisplayName(key):sub(1, 4))
        icon.visual:SetTextColor(0.9, 0.9, 0.9, 1)
    end

    icon:SetScript("OnEnter", function(self)
        self.bg:SetColorTexture(unpack(DOCK_BG_HOVER))
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(DisplayName(key), 1, 1, 1)
        GameTooltip:AddLine(L.CFG_CM_DOCK_CLICK_TT, 0.7, 0.7, 0.7)
        GameTooltip:Show()
    end)

    icon:SetScript("OnLeave", function(self)
        self.bg:SetColorTexture(unpack(DOCK_BG_COLOR))
        GameTooltip:Hide()
    end)

    icon:SetScript("OnClick", function(self)
        Dialog:RestoreFromDock(self.key)
    end)

    self.dockComponents[key] = icon

    local alreadyTracked = false
    for _, k in ipairs(self.disabledComponentKeys) do
        if k == key then alreadyTracked = true break end
    end
    if not alreadyTracked then table.insert(self.disabledComponentKeys, key) end

    -- Stage a fresh copy so PluginMixin's identity-keyed disabled cache re-resolves
    local Txn = OrbitEngine.CanvasMode.Transaction
    if Txn and Txn:IsActive() then Txn:SetDisabledComponents({ unpack(self.disabledComponentKeys) }) end

    self:LayoutDockIcons()

    if not self._exclusiveSwapping then
        local partner = EXCLUSIVE_PAIRS[key]
        if partner and self.dockComponents[partner] then
            self._exclusiveSwapping = true
            self:RestoreFromDock(partner)
            self._exclusiveSwapping = nil
        end
    end
end

-- [ REMOVE FROM DOCK ] ------------------------------------------------------------------------------
function Dialog:RemoveFromDock(key)
    local icon = self.dockComponents[key]
    if icon then
        icon:Hide()
        icon:SetParent(nil)
        self.dockComponents[key] = nil
    end

    for i, k in ipairs(self.disabledComponentKeys) do
        if k == key then table.remove(self.disabledComponentKeys, i) break end
    end

    -- Stage a fresh copy so PluginMixin's identity-keyed disabled cache re-resolves
    local Txn = OrbitEngine.CanvasMode.Transaction
    if Txn and Txn:IsActive() then Txn:SetDisabledComponents({ unpack(self.disabledComponentKeys) }) end

    self:LayoutDockIcons()
end

-- [ RESTORE FROM DOCK ] -----------------------------------------------------------------------------
function Dialog:RestoreFromDock(key)
    local dockIcon = self.dockComponents[key]

    if dockIcon and dockIcon.storedSubFrame then
        local subFrame = dockIcon.storedSubFrame
        subFrame:Show()
        self:RemoveFromDock(key)
        return
    end

    local storedComp = dockIcon and dockIcon.storedDraggableComp

    self:RemoveFromDock(key)

    if storedComp then
        -- Restore pre-drag position if available (drag updates posX/anchorX to cursor pos)
        local pos = storedComp._preDragPos
        if pos then
            storedComp.posX = pos.posX
            storedComp.posY = pos.posY
            storedComp.anchorX = pos.anchorX
            storedComp.anchorY = pos.anchorY
            storedComp.offsetX = pos.offsetX
            storedComp.offsetY = pos.offsetY
            storedComp.selfAnchorY = pos.selfAnchorY
            storedComp.justifyH = pos.justifyH
            storedComp._preDragPos = nil
        end
        storedComp:Show()
        Dialog.previewComponents[key] = storedComp
        -- Re-anchor using restored position data
        local preview = self.previewFrame
        if preview then
            storedComp:ClearAllPoints()
            local anchorPoint = BuildAnchorPoint(storedComp.anchorX or "CENTER", storedComp.anchorY or "CENTER")
            local selfAnchor = BuildComponentSelfAnchor(storedComp.isFontString, storedComp.isAuraContainer, storedComp.selfAnchorY, storedComp.justifyH)
            local ax = storedComp.anchorX or "CENTER"
            local ay = storedComp.anchorY or "CENTER"
            local fx, fy = AnchorOffsetsToFinal(ax, ay, storedComp.offsetX, storedComp.offsetY, storedComp.posX, storedComp.posY)
            storedComp:SetPoint(selfAnchor, preview, anchorPoint, fx, fy)
        end
        if Dialog.activeFilter and Dialog.activeFilter ~= "All" then Dialog:ApplyFilter(Dialog.activeFilter) end
        return
    end

    local savedPositions = self.targetPlugin and self.targetPlugin:GetSetting(self.targetSystemIndex, "ComponentPositions") or {}
    local pos = savedPositions[key]

    local dragComponents = OrbitEngine.ComponentDrag:GetComponentsForFrame(self.targetFrame)
    local data = dragComponents and dragComponents[key]

    if data and data.component then
        local canvasFrame = self.targetFrame.orbitCanvasFrame or self.targetFrame
        local frameW = canvasFrame:GetWidth()
        local frameH = canvasFrame:GetHeight()

        local centerX, centerY = 0, 0
        local anchorX, anchorY = "CENTER", "CENTER"
        local offsetX, offsetY = 0, 0

        if pos and pos.anchorX then
            anchorX = pos.anchorX
            anchorY = pos.anchorY or "CENTER"
            offsetX = pos.offsetX or 0
            offsetY = pos.offsetY or 0
            centerX, centerY = OrbitEngine.PositionUtils.AnchorToCenter(anchorX, anchorY, offsetX, offsetY, frameW / 2, frameH / 2)
        end

        local compData = {
            component = data.component,
            x = centerX, y = centerY,
            anchorX = anchorX, anchorY = anchorY,
            offsetX = offsetX, offsetY = offsetY,
            justifyH = pos and pos.justifyH or "CENTER",
            overrides = pos and pos.overrides,
            posX = pos and pos.posX, posY = pos and pos.posY,
        }

        if CanvasMode.CreateDraggableComponent then
            local comp = CanvasMode.CreateDraggableComponent(Dialog.previewFrame, key, data.component, centerX, centerY, compData)
            if comp then comp:SetFrameLevel(Dialog.previewFrame:GetFrameLevel() + Levels.CanvasOverlay) end
            Dialog.previewComponents[key] = comp
        end
    end
    if Dialog.activeFilter and Dialog.activeFilter ~= "All" then Dialog:ApplyFilter(Dialog.activeFilter) end
    if OrbitEngine.CanvasComponentSettings and self.targetPlugin then
        OrbitEngine.CanvasComponentSettings:ApplyInitialPluginPreviews(self.targetPlugin, self.targetSystemIndex)
    end

    if not self._exclusiveSwapping then
        local partner = EXCLUSIVE_PAIRS[key]
        if partner and not self.dockComponents[partner] and self.previewComponents[partner] then
            self._exclusiveSwapping = true
            local comp = self.previewComponents[partner]
            comp:Hide()
            local sourceComp = comp.sourceComponent or comp.visual or comp
            self:AddToDock(partner, sourceComp)
            if self.dockComponents[partner] then
                self.dockComponents[partner].storedDraggableComp = comp
            end
            self.previewComponents[partner] = nil
            self._exclusiveSwapping = nil
        end
    end
end

-- [ CLEAR DOCK ] ------------------------------------------------------------------------------------
function Dialog:ClearDock()
    for key, icon in pairs(self.dockComponents) do
        icon:Hide()
        icon:SetParent(nil)
    end
    wipe(self.dockComponents)
    wipe(self.disabledComponentKeys)
    self:LayoutDockIcons()
end
