local _, namespace = ...

local WTUI = namespace.WildThingsUI
local Theme = namespace.Theme

local BACKGROUND_COLOR = Theme.INPUT_BG
local BORDER_COLOR = Theme.BORDER_STRONG

local DEFAULT_HEIGHT = Theme.H_INPUT
local GLOW_OFFSET = 2

-- ASSIGN-01 shift-click fix: this addon's CreateEditBox has no AceGUI-style enumerated
-- widget registry, so a plain module-local "currently focused addon EditBox" tracker is
-- the closest analog-faithful adaptation of the AceGUI precedent this was modelled on
-- (AceGUIWidget-EditBox, no longer vendored here). Set in OnEditFocusGained,
-- cleared in OnEditFocusLost (both below), so it always reflects whichever addon
-- EditBox instance currently holds focus (or none).
local currentlyFocused

-- Secure post-hook, registered once at module load. Hooks the LIVE
-- `ChatFrameUtil.InsertLink` table field -- the function the real shift-click item path
-- (HandleModifiedItemClick, used by both bags and Blizzard_GuildBankUI) actually calls.
-- The previously hooked bare global `ChatEdit_InsertLink` is, on Interface 120001/120000,
-- a one-time deprecated compat snapshot (Blizzard_DeprecatedChatInfo) the live item path
-- never invokes, so the old hook never fired (the cause of "ging vorher auch schon nicht").
-- AceGUI-3.0's own EditBox hooks that same dead alias and is itself stale on this client. This file loads exactly once per session via WildThings.toc's file list,
-- so no re-entry guard is needed; a defensive load guard (verifying the ChatFrameUtil table
-- and its InsertLink field exist) keeps a client lacking either from erroring at load. The
-- live path calls it with dot syntax as ChatFrameUtil.InsertLink(link) -- no implicit self --
-- so the hook's first parameter IS the link. Defensive by construction: a non-string/empty
-- link, no tracked focused box, or a tracked box that lost focus are all silent no-ops --
-- normal chat shift-click-to-link is never touched when no addon EditBox is focused.
if ChatFrameUtil and ChatFrameUtil.InsertLink then
    hooksecurefunc(ChatFrameUtil, "InsertLink", function(link)
        if type(link) ~= "string" or link == "" then return end
        if currentlyFocused and currentlyFocused:IsVisible() and currentlyFocused:HasFocus() then
            currentlyFocused:Insert(link)
        end
    end)
end

function namespace:CreateEditBox(parent, title, OnValueChanged)
    local editBox = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")

    local OnTextSet, minValue
    local warningActive = false

    editBox.OnEnter = function() end
    editBox.OnLeave = function() end

    editBox:SetScript("OnEnter", function(_self) _self.OnEnter() end)
    editBox:SetScript("OnLeave", function(_self) _self.OnLeave() end)

    editBox:SetAutoFocus(false)
    editBox:SetHeight(DEFAULT_HEIGHT)
    -- Top/bottom insets grown alongside DEFAULT_HEIGHT (18->28) so the text baseline sits
    -- vertically centered in the taller box (was (4, 4, 1, 0) -- text glued to the top).
    editBox:SetTextInsets(4, 4, 5, 5)
    editBox:SetFontObject(WildThingsFont15)

    editBox.currentValue = ""

    -- Background
    editBox.tex = editBox:CreateTexture(nil, "BACKGROUND")
    editBox.tex:SetPoint("TOPLEFT", editBox, "TOPLEFT", 1, -1)
    editBox.tex:SetPoint("BOTTOMRIGHT", editBox, "BOTTOMRIGHT", -1, 1)
    editBox.tex:SetColorTexture(BACKGROUND_COLOR.r, BACKGROUND_COLOR.g, BACKGROUND_COLOR.b, BACKGROUND_COLOR.a)
    editBox.tex:SetSnapToPixelGrid(false)
    editBox.tex:SetTexelSnappingBias(0)

    -- Rounded base fill (ROUND-04/05, spike-002 nine-slice MaskTexture technique).
    -- editBox.glow (below) is deliberately left unmasked -- it is a functional focus/
    -- warning ring, not decorative chrome (Pitfall 8 / A4).
    WTUI:AddRoundedMask(editBox, Theme.RADIUS_SM)
    editBox.tex:AddMaskTexture(editBox.roundedMask)

    function editBox:SetBackgroundColor(r, g, b, a)
        editBox.tex:SetColorTexture(r, g, b, a)
    end

    editBox.Left:Hide()
    editBox.Middle:Hide()
    editBox.Right:Hide()

    -- Highlight
    WTUI:AddHoverHighlight(editBox)

    -- Border (rounded ring, ROUND-BORDER-01): editBox.tex above is already inset 1px and
    -- masked, so the ring shows through as a 1px rounded band tracing the corner arcs.
    WTUI:AddRoundedBorder(editBox, Theme.RADIUS_SM, 1)
    editBox:SetBorderColor(BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, BORDER_COLOR.a)

    -- Focus glow: a dedicated child frame ~2px outward, with its OWN border/SetBorderColor.
    -- The border helper is intentionally never called a second time on editBox itself (that
    -- would clobber editBox:SetBorderColor) -- see 09-RESEARCH.md Pitfall/T-09-02-B.
    editBox.glow = CreateFrame("Frame", nil, editBox)
    editBox.glow:SetPoint("TOPLEFT", editBox, "TOPLEFT", -GLOW_OFFSET, GLOW_OFFSET)
    editBox.glow:SetPoint("BOTTOMRIGHT", editBox, "BOTTOMRIGHT", GLOW_OFFSET, -GLOW_OFFSET)
    editBox.glow:SetFrameLevel(math.max(0, editBox:GetFrameLevel() - 1))
    -- RADIUS_MD, not SM: the glow frame sits GLOW_OFFSET px outside the box, so its arcs need
    -- a correspondingly larger radius to stay concentric with the RADIUS_SM ring inside it.
    WTUI:AddRoundedBorder(editBox.glow, Theme.RADIUS_MD, 1)
    editBox.glow:HideBorder()

    -- Title
    editBox.title = editBox:CreateFontString()
    editBox.title:SetFontObject(WildThingsFont13)
    editBox.title:SetPoint("BOTTOMLEFT", editBox, "TOPLEFT")
    editBox.title:SetText(title)
    editBox.title:SetTextColor(Theme.TEXT_DIM.r, Theme.TEXT_DIM.g, Theme.TEXT_DIM.b, Theme.TEXT_DIM.a)

    -- Ensures that if the edit box is (FullRange)Numeric, that currentValue is numeric too
    -- Also makes sure the currentValue respects the minValue
    -- Should be called everytime before OnValueChanged is called
    local function EnsureNumeric()
        if editBox:IsNumeric() or editBox:IsNumericFullRange() then
            -- Ensure value is numeric
            editBox.currentValue = tonumber(editBox.currentValue)

            if not editBox.currentValue then
                editBox.currentValue = minValue or 0
            end

            -- Respect the min value
            if minValue then
                editBox.currentValue = math.max(minValue, editBox.currentValue)
            end
        end
    end

    -- Calls OnValueChanged with the currentValue
    -- Mostly used for updating the highlight when it has a dependency on outside information
    function editBox:Refresh()
        EnsureNumeric()

        -- Numeric edit boxes render in the Mono-Num font family for tabular alignment
        if editBox:IsNumeric() or editBox:IsNumericFullRange() then
            editBox:SetFontObject(WildThingsFontNum15)
        else
            editBox:SetFontObject(WildThingsFont15)
        end

        OnValueChanged(editBox.currentValue)
    end

    -- Highlight functions
    function editBox:ShowHighlight(r, g, b)
        editBox:SetBorderColor(r, g, b)
    end

    function editBox:HideHighlight()
        editBox:SetBorderColor(BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, BORDER_COLOR.a)
    end

    -- Warning state: forces both the main border and the glow to Theme.DANGER, overriding
    -- whatever focus color would otherwise apply, while active. Reuses ShowHighlight/HideHighlight
    -- rather than adding a parallel border system.
    function editBox:SetWarning(isWarning)
        warningActive = isWarning and true or false

        if warningActive then
            editBox:ShowHighlight(Theme.DANGER.r, Theme.DANGER.g, Theme.DANGER.b)
            editBox.glow:SetBorderColor(Theme.DANGER.r, Theme.DANGER.g, Theme.DANGER.b, Theme.DANGER_A13.a)
            editBox.glow:ShowBorder()
        elseif editBox:HasFocus() then
            editBox:ShowHighlight(Theme.ACCENT.r, Theme.ACCENT.g, Theme.ACCENT.b)
            editBox.glow:SetBorderColor(Theme.ACCENT.r, Theme.ACCENT.g, Theme.ACCENT.b, Theme.ACCENT_A20.a)
            editBox.glow:ShowBorder()
        else
            editBox:HideHighlight()
            editBox.glow:HideBorder()
        end
    end

    -- Leading icon: a Lucide icon inset left, text shifted right via SetTextInsets
    function editBox:SetLeadingIcon(stem)
        if not editBox.leadingIcon then
            editBox.leadingIcon = editBox:CreateTexture(nil, "ARTWORK")
        end

        editBox.leadingIcon:SetTexture(Theme.GetIconPath(stem))
        editBox.leadingIcon:SetSize(Theme.ICON_SM, Theme.ICON_SM)
        editBox.leadingIcon:ClearAllPoints()
        editBox.leadingIcon:SetPoint("LEFT", editBox, "LEFT", 4, 0)
        editBox.leadingIcon:Show()

        editBox:SetTextInsets(4 + Theme.ICON_SM + 4, 4, 5, 5)
    end

    -- For (FullRange)Numeric edit boxes, sets the minimum possible value
    -- The value that is passed to OnValueChanged is guaranteed to be at least equal to this value
    -- The value displayed by the edit box can be below this while editing it, but on focus lost it will be clamped
    function editBox:SetMinimum(value)
        minValue = value
    end

    OnTextSet = function()
        local text = editBox:GetText()

        editBox.currentValue = text

        EnsureNumeric()
        OnValueChanged(editBox.currentValue)
    end

    editBox:SetScript(
        "OnTextSet",
        OnTextSet
    )

    editBox:SetScript(
        "OnTextChanged",
        function(_, userInput)
            if not userInput then return end -- Handled OnTextSet

            OnTextSet()
        end
    )

    -- Enter commits (and dismisses) a single-line box, but in a MULTILINE box Enter is
    -- how the user types a line break -- clearing focus there makes the box effectively
    -- one line tall no matter how much height it was given. Gated on IsMultiLine() rather
    -- than removed, so every single-line consumer (search, settings, numeric inputs) keeps
    -- its existing commit-on-Enter behaviour unchanged. Matches the vendored AceGUI
    -- MultiLineEditBox, which binds OnEscapePressed but deliberately never OnEnterPressed.
    editBox:SetScript(
        "OnEnterPressed",
        function()
            if editBox:IsMultiLine() then return end
            editBox:ClearFocus()
        end
    )

    -- Multiline boxes lose the Enter escape hatch above, so Escape becomes the only way
    -- out of the box without clicking away -- same binding AceGUI's multiline widget uses.
    editBox:SetScript(
        "OnEscapePressed",
        function()
            editBox:ClearFocus()
        end
    )

    -- Focus glow: accent border + soft accent glow on focus gain, restored to idle on focus loss.
    -- Skipped entirely while a warning is active (SetWarning owns the border/glow color then).
    editBox:SetScript(
        "OnEditFocusGained",
        function()
            -- ASSIGN-01 shift-click fix: track this box as the focused addon EditBox,
            -- unconditionally (before the warningActive early-return below) -- the box
            -- genuinely holds keyboard focus regardless of its warning-glow visual state.
            currentlyFocused = editBox

            if warningActive then return end

            editBox:ShowHighlight(Theme.ACCENT.r, Theme.ACCENT.g, Theme.ACCENT.b)
            editBox.glow:SetBorderColor(Theme.ACCENT.r, Theme.ACCENT.g, Theme.ACCENT.b, Theme.ACCENT_A20.a)
            editBox.glow:ShowBorder()
        end
    )

    editBox:SetScript(
        "OnEditFocusLost",
        function()
            -- ASSIGN-01 shift-click fix: clear the focused-box tracker only if this box
            -- was the one tracked (a stale clear from an already-superseded box must never
            -- clobber a newer focus gain).
            if currentlyFocused == editBox then currentlyFocused = nil end

            -- Existing numeric-clamp behavior, preserved verbatim
            if editBox:IsNumeric() or editBox:IsNumericFullRange() then
                EnsureNumeric()
                editBox:SetText(editBox.currentValue)
            end

            -- Focus-glow teardown, skipped while a warning is active
            if warningActive then return end

            editBox:HideHighlight()
            editBox.glow:HideBorder()
        end
    )

    editBox:HideHighlight()

    C_Timer.After(0, function() editBox:Refresh() end)

    return editBox
end
