local _, namespace = ...

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

local KNOB_INSET = 2

-- Two RGB brightening deltas for the track's hover state -- Theme.TG_ON's green channel
-- already sits near the 1.0 clamp ceiling, so a flat delta barely registers there; a larger
-- ON delta still leaves green clamped but lifts red/blue enough to read as a clearly lighter,
-- pastel-toned green on hover. Theme.TG_OFF's gray channels are far enough from the ceiling
-- that the existing smaller delta already produces a strong, correct brighten, so it keeps
-- its proven-working value. Both are clamped per-channel below since additive brightening
-- can overflow 1.0.
local TRACK_HOVER_BRIGHTEN_OFF = 0.15
local TRACK_HOVER_BRIGHTEN_ON = 0.4

local function Clamp01(value)
    if value > 1 then return 1 end
    if value < 0 then return 0 end
    return value
end

-- namespace:CreateToggle(parent, OnValueChanged)
-- Fresh CreateFrame("Button", ...) pill toggle (NOT a reskinned CheckButton). Track color
-- swaps instantly (SetColorTexture, no color-tween) between Theme.TG_OFF/TG_ON; the knob
-- slides via a create-once WTUI:AddToggleAnimation "Translation" group. A WoW Translation
-- is transient (it reverts the knob to its layout anchor when the group finishes), so
-- SetChecked drives a forward Play() in BOTH directions (offset recomputed per direction)
-- and COMMITS the knob's resting anchor in OnFinished — otherwise the ON slide snaps the
-- knob back to the LEFT (G-9-5b). SetChecked guards on actual state change before touching
-- visuals/animation (drift guard).
function namespace:CreateToggle(parent, OnValueChanged)
    local toggle = CreateFrame("Button", nil, parent)
    toggle:SetSize(Theme.TG_W, Theme.TG_H)
    WTUI:AddHoverHighlight(toggle)

    local isChecked = false
    -- Shared with the hooksecurefunc handler below so OnValueChanged only fires when
    -- SetChecked actually flipped the state, not on every redundant/no-op call.
    local stateActuallyChanged = false
    local isHovering = false

    toggle.OnValueChanged = OnValueChanged

    -- Track: single background texture, instant color swap on state change.
    toggle.track = toggle:CreateTexture(nil, "BACKGROUND")
    toggle.track:SetAllPoints()
    toggle.track:SetSnapToPixelGrid(false)
    toggle.track:SetTexelSnappingBias(0)

    -- Rounded corners (ROUND-04/05, spike-002 Pattern 1): OFF/ON/hover are all the SAME
    -- toggle.track texture recolored in place (ApplyTrackColor below), not separate layers
    -- -- so one shared mask call covers all three states. Do NOT add a separate hover
    -- overlay texture here; that would reintroduce the exact layered glow/overlay quick
    -- tasks 260721-v9n/vln explicitly reverted in favor of this same-texture brighten.
    WTUI:AddRoundedMask(toggle, Theme.RADIUS_SM)
    toggle.track:AddMaskTexture(toggle.roundedMask)

    -- Applies the track's current fill color, composed from isChecked (OFF-gray/ON-green)
    -- and isHovering (brightened variant of whichever color is currently showing) so a click
    -- mid-hover and a hover mid-click never fight each other over the same texture.
    local function ApplyTrackColor()
        local color = isChecked and Theme.TG_ON or Theme.TG_OFF
        if isHovering then
            local delta = TRACK_HOVER_BRIGHTEN_OFF
            if isChecked then delta = TRACK_HOVER_BRIGHTEN_ON end
            toggle.track:SetColorTexture(
                Clamp01(color.r + delta),
                Clamp01(color.g + delta),
                Clamp01(color.b + delta),
                color.a
            )
        else
            toggle.track:SetColorTexture(color.r, color.g, color.b, color.a)
        end
    end
    ApplyTrackColor()

    -- Knob: square WT_WHITE texture, inset within the track, alpha shifts 50%->100%.
    local knobSize = Theme.TG_H - (KNOB_INSET * 2)
    -- Horizontal travel between the OFF (left) and ON (right) resting positions.
    local slideX = Theme.TG_W - Theme.TG_H
    toggle.knob = toggle:CreateTexture(nil, "ARTWORK")
    toggle.knob:SetSize(knobSize, knobSize)
    toggle.knob:SetColorTexture(Theme.WT_WHITE.r, Theme.WT_WHITE.g, Theme.WT_WHITE.b, Theme.WT_WHITE.a)
    toggle.knob:SetSnapToPixelGrid(false)
    toggle.knob:SetTexelSnappingBias(0)
    toggle.knob:SetPoint("LEFT", toggle, "LEFT", KNOB_INSET, 0)
    toggle.knob:SetAlpha(Theme.TG_KNOB_OFF.a)

    -- Knob slide: create-once Translation group (Plan 01 helper). Offset is recomputed per
    -- direction below and both directions Play() FORWARD; the OnFinished handler commits
    -- the knob's resting anchor so the transient Translation doesn't revert it (G-9-5b).
    toggle.knobSlide = WTUI:AddToggleAnimation(toggle, toggle.knob, "Translation", {
        to = {x = slideX, y = 0},
        duration = Theme.DUR_OUT,
    })
    local slideAnim = toggle.knobSlide:GetAnimations()

    -- Anchor the knob at the resting spot for a given state: OFF = left inset, ON = left
    -- inset + slideX (right).
    local function AnchorKnobFor(checkedState)
        toggle.knob:ClearAllPoints()
        if checkedState then
            toggle.knob:SetPoint("LEFT", toggle, "LEFT", KNOB_INSET + slideX, 0)
        else
            toggle.knob:SetPoint("LEFT", toggle, "LEFT", KNOB_INSET, 0)
        end
    end

    -- Commit the knob to its CURRENT-state rest. Runs after a slide finishes (so the
    -- finish-revert lands on the right spot) and on programmatic/initial SetChecked.
    local function CommitKnobRest()
        AnchorKnobFor(isChecked)
    end

    toggle.knobSlide:SetScript("OnFinished", CommitKnobRest)

    function toggle:SetChecked(checked, dontRun)
        checked = not not checked -- normalize to boolean

        stateActuallyChanged = (checked ~= isChecked)

        if not stateActuallyChanged then return end -- drift guard (10-RESEARCH Pitfall 3)

        isChecked = checked

        if checked then
            toggle.knob:SetAlpha(1)
        else
            toggle.knob:SetAlpha(Theme.TG_KNOB_OFF.a)
        end
        ApplyTrackColor()

        if dontRun then
            -- Programmatic/initial set: no slide, snap straight to the resting position.
            toggle.knobSlide:Stop()
            CommitKnobRest()
        else
            -- Re-anchor the knob to the rest it is sliding FROM (the state being left) BEFORE
            -- playing, so the relative Translation offset always lands on the destination rest
            -- even when a prior slide was interrupted mid-flight by a rapid re-toggle. Without
            -- this, an interrupted slide leaves the committed anchor stale (its OnFinished never
            -- ran) and the next +slideX/-slideX is applied from the wrong baseline, flinging the
            -- knob a full slideX outside the track (G-10-4). Stop() first for determinism; the
            -- OnFinished commit still lands the destination rest.
            toggle.knobSlide:Stop()
            AnchorKnobFor(not checked)
            slideAnim:SetOffset(checked and slideX or -slideX, 0)
            toggle.knobSlide:Play()
        end
    end

    function toggle:IsChecked()
        return isChecked
    end

    toggle:SetScript("OnClick", function()
        toggle:SetChecked(not isChecked)
    end)

    toggle:SetScript("OnEnter", function()
        isHovering = true
        ApplyTrackColor()
    end)
    toggle:SetScript("OnLeave", function()
        isHovering = false
        ApplyTrackColor()
    end)

    hooksecurefunc(
        toggle,
        "SetChecked",
        function(_, checked, dontRun)
            if dontRun then return end -- To avoid recursion
            if not stateActuallyChanged then return end -- Only fire on an actual state change

            if toggle.OnValueChanged then
                toggle.OnValueChanged(checked)
            end
        end
    )

    return toggle
end
