local _, Addon = ...

local Options = {}
Addon.Options = Options
local L = Addon.L

local frame
local updating = false
local sliders = {}
local checks = {}
local bindingButton
local clearBindingButton
local bindingListening = false

local GATHERING_BINDING_ACTION = "MIYAMAP_TOGGLE_GATHERING"
local FALLBACK_MODIFIER_KEYS = {
    LALT = true,
    RALT = true,
    LCTRL = true,
    RCTRL = true,
    LSHIFT = true,
    RSHIFT = true,
    LMETA = true,
    RMETA = true,
}

local function GetBindingContext()
    if not C_KeyBindings or not C_KeyBindings.GetBindingContextForAction then
        return nil
    end

    local ok, context = pcall(C_KeyBindings.GetBindingContextForAction, GATHERING_BINDING_ACTION)
    if ok and not Addon:IsSecret(context) then
        return context
    end
    return nil
end

local function GetGatheringBindingKeys()
    if type(GetBindingKey) ~= "function" then
        return nil, nil
    end

    local context = GetBindingContext()
    local ok, key1, key2
    if context ~= nil then
        ok, key1, key2 = pcall(GetBindingKey, GATHERING_BINDING_ACTION, nil, context)
    else
        ok, key1, key2 = pcall(GetBindingKey, GATHERING_BINDING_ACTION)
    end
    if not ok then
        return nil, nil
    end
    if Addon:IsSecret(key1) then
        key1 = nil
    end
    if Addon:IsSecret(key2) then
        key2 = nil
    end
    return key1, key2
end

local function SetBindingForContext(key, action)
    if type(SetBinding) ~= "function" then
        return false
    end

    local context = GetBindingContext()
    local ok, result
    if context ~= nil then
        ok, result = pcall(SetBinding, key, action, context)
    else
        ok, result = pcall(SetBinding, key, action)
    end
    return ok and result ~= false
end

local function SaveBindingChanges()
    if type(SaveBindings) ~= "function" or type(GetCurrentBindingSet) ~= "function" then
        return false
    end

    local setOK, bindingSet = pcall(GetCurrentBindingSet)
    if not setOK then
        return false
    end
    local saveOK, result = pcall(SaveBindings, bindingSet)
    return saveOK and result ~= false
end

local function GetBindingDisplayText(key)
    if not key then
        return L.KEYBIND_NOT_SET
    end
    if type(GetBindingText) == "function" then
        local ok, text = pcall(GetBindingText, key, 1)
        if ok and type(text) == "string" and text ~= "" and not Addon:IsSecret(text) then
            return text
        end
    end
    return key
end

local UpdateBindingControls

local function StopBindingCapture()
    bindingListening = false
    if bindingButton then
        bindingButton:EnableKeyboard(false)
        if bindingButton.EnableGamePadButton then
            bindingButton:EnableGamePadButton(false)
        end
        if bindingButton.SetPropagateKeyboardInput then
            bindingButton:SetPropagateKeyboardInput(true)
        end
    end
    if UpdateBindingControls then
        UpdateBindingControls()
    end
end

local function RestoreGatheringBindings(key1, key2)
    if key1 then
        SetBindingForContext(key1, GATHERING_BINDING_ACTION)
    end
    if key2 then
        SetBindingForContext(key2, GATHERING_BINDING_ACTION)
    end
end

local function AssignGatheringBinding(key)
    if type(InCombatLockdown) == "function" and InCombatLockdown() then
        Addon:Print(L.KEYBIND_COMBAT)
        return false
    end

    local oldKey1, oldKey2 = GetGatheringBindingKeys()
    if oldKey1 then
        SetBindingForContext(oldKey1, nil)
    end
    if oldKey2 and oldKey2 ~= oldKey1 then
        SetBindingForContext(oldKey2, nil)
    end

    if not SetBindingForContext(key, GATHERING_BINDING_ACTION) or not SaveBindingChanges() then
        SetBindingForContext(key, nil)
        RestoreGatheringBindings(oldKey1, oldKey2)
        SaveBindingChanges()
        Addon:Print(L.KEYBIND_FAILED)
        return false
    end

    Addon:Print(string.format(L.KEYBIND_SAVED, GetBindingDisplayText(key)))
    return true
end

local function ClearGatheringBinding()
    if type(InCombatLockdown) == "function" and InCombatLockdown() then
        Addon:Print(L.KEYBIND_COMBAT)
        return false
    end

    local key1, key2 = GetGatheringBindingKeys()
    if key1 then
        SetBindingForContext(key1, nil)
    end
    if key2 and key2 ~= key1 then
        SetBindingForContext(key2, nil)
    end
    if not SaveBindingChanges() then
        RestoreGatheringBindings(key1, key2)
        SaveBindingChanges()
        Addon:Print(L.KEYBIND_FAILED)
        return false
    end

    Addon:Print(L.KEYBIND_CLEARED)
    return true
end

local function BuildFallbackChord(key)
    local parts = {}
    if type(IsAltKeyDown) == "function" and IsAltKeyDown() then
        parts[#parts + 1] = "ALT"
    end
    if type(IsControlKeyDown) == "function" and IsControlKeyDown() then
        parts[#parts + 1] = "CTRL"
    end
    if type(IsShiftKeyDown) == "function" and IsShiftKeyDown() then
        parts[#parts + 1] = "SHIFT"
    end
    parts[#parts + 1] = key
    return table.concat(parts, "-")
end

local function ProcessBindingInput(input)
    if not bindingListening then
        return
    end
    if bindingButton.SetPropagateKeyboardInput then
        bindingButton:SetPropagateKeyboardInput(false)
    end
    if input == "ESCAPE" then
        StopBindingCapture()
        return
    end

    local key = input
    if type(GetConvertedKeyOrButton) == "function" then
        key = GetConvertedKeyOrButton(input)
    end
    if not key
        or FALLBACK_MODIFIER_KEYS[key]
        or (type(IsKeyPressIgnoredForBinding) == "function" and IsKeyPressIgnoredForBinding(key))
    then
        return
    end

    local chord
    if type(CreateKeyChordStringUsingMetaKeyState) == "function" then
        chord = CreateKeyChordStringUsingMetaKeyState(key)
    else
        chord = BuildFallbackChord(key)
    end
    if type(chord) ~= "string" or chord == "" then
        return
    end

    StopBindingCapture()
    AssignGatheringBinding(chord)
    UpdateBindingControls()
end

UpdateBindingControls = function()
    if not bindingButton or not clearBindingButton then
        return
    end

    local key = GetGatheringBindingKeys()
    bindingButton:SetText(bindingListening and L.KEYBIND_CAPTURE or GetBindingDisplayText(key))
    clearBindingButton:SetEnabled(key ~= nil and not bindingListening)
end

local function MakeButton(parent, text, width, onClick)
    local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    button:SetSize(width, 24)
    button:SetText(text)
    button:SetScript("OnClick", onClick)
    return button
end

local function MakeSectionLabel(parent, text, x, y)
    local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetPoint("TOPLEFT", x, y)
    label:SetText(text)
    return label
end

local function MakeSlider(key, parent, labelText, x, y, width, minimum, maximum, step, onChange, formatter)
    local label = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    label:SetPoint("TOPLEFT", x, y)
    label:SetText(labelText)

    local valueText = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    valueText:SetPoint("TOPRIGHT", parent, "TOPLEFT", x + width, y)
    valueText:SetJustifyH("RIGHT")

    local slider = CreateFrame("Slider", nil, parent, "OptionsSliderTemplate")
    slider:SetPoint("TOPLEFT", x, y - 18)
    slider:SetSize(width, 16)
    slider:SetMinMaxValues(minimum, maximum)
    slider:SetValueStep(step)
    slider:SetObeyStepOnDrag(true)
    if slider.Text then
        slider.Text:Hide()
    end
    if slider.Low then
        slider.Low:SetText(tostring(minimum))
    end
    if slider.High then
        slider.High:SetText(tostring(maximum))
    end

    slider.valueText = valueText
    slider.formatter = formatter or tostring
    slider.step = step
    slider:SetScript("OnValueChanged", function(self, value)
        local rounded = math.floor((value / self.step) + 0.5) * self.step
        self.valueText:SetText(self.formatter(rounded))
        if not updating then
            onChange(rounded)
        end
    end)
    sliders[key] = slider
    return slider
end

local function MakeCheck(key, parent, text, x, y, onClick)
    local check = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    check:SetPoint("TOPLEFT", x, y)
    check.Text:SetText(text)
    check.Text:SetTextColor(1, 1, 1)
    check:SetScript("OnClick", function(self)
        if not updating then
            onClick(self:GetChecked())
        end
    end)
    checks[key] = check
    return check
end

local function ApplyProfile(profile)
    if Addon.UI:ApplyProfile(profile) then
        Options:Refresh()
    end
end

local function CreateOptionsFrame()
    frame = CreateFrame("Frame", "MapaEnMiniaturaOptionsFrame", UIParent, "BackdropTemplate")
    frame:SetSize(540, 720)
    frame:SetPoint("CENTER")
    frame:SetFrameStrata("DIALOG")
    frame:SetFrameLevel(80)
    frame:SetClampedToScreen(true)
    frame:SetToplevel(true)
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
        tile = true,
        tileSize = 32,
        edgeSize = 24,
        insets = { left = 7, right = 7, top = 7, bottom = 7 },
    })
    frame:SetBackdropColor(0.015, 0.015, 0.025, 0.98)
    frame:Hide()

    local titleBar = CreateFrame("Frame", nil, frame)
    titleBar:SetPoint("TOPLEFT", 12, -10)
    titleBar:SetPoint("TOPRIGHT", -36, -10)
    titleBar:SetHeight(28)
    titleBar:EnableMouse(true)
    titleBar:RegisterForDrag("LeftButton")
    titleBar:SetScript("OnDragStart", function()
        frame:StartMoving()
    end)
    titleBar:SetScript("OnDragStop", function()
        frame:StopMovingOrSizing()
    end)

    local title = titleBar:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("LEFT", 8, 0)
    title:SetText(L.APP_NAME)

    local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
    closeButton:SetPoint("TOPRIGHT", -3, -3)
    closeButton:SetScript("OnClick", function()
        frame:Hide()
    end)

    local description = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    description:SetPoint("TOPLEFT", 28, -48)
    description:SetPoint("TOPRIGHT", -28, -48)
    description:SetJustifyH("LEFT")
    description:SetText(L.OPTIONS_DESCRIPTION)

    MakeSectionLabel(frame, L.QUICK_PROFILES, 30, -76)
    local normalButton = MakeButton(frame, L.PROFILE_NORMAL, 112, function()
        ApplyProfile("normal")
    end)
    normalButton:SetPoint("TOPLEFT", 30, -96)
    local compactButton = MakeButton(frame, L.PROFILE_COMPACT, 112, function()
        ApplyProfile("compacto")
    end)
    compactButton:SetPoint("LEFT", normalButton, "RIGHT", 8, 0)
    local miniButton = MakeButton(frame, L.PROFILE_MINI, 112, function()
        ApplyProfile("mini")
    end)
    miniButton:SetPoint("LEFT", compactButton, "RIGHT", 8, 0)

    MakeSectionLabel(frame, L.MAP_SHAPE, 30, -135)
    local squareButton = MakeButton(frame, L.SHAPE_SQUARE, 112, function()
        Addon.UI:SetShape("square")
        Options:Refresh()
    end)
    squareButton:SetPoint("TOPLEFT", 30, -155)
    local circleButton = MakeButton(frame, L.SHAPE_CIRCLE, 112, function()
        Addon.UI:SetShape("circle")
        Options:Refresh()
    end)
    circleButton:SetPoint("LEFT", squareButton, "RIGHT", 8, 0)

    MakeSlider("size", frame, L.MAP_SIZE, 30, -205, 210, 120, 650, 10, function(value)
        local minimum = Addon.db.shape == "circle" and Addon.MIN_SIZE or Addon.MIN_SQUARE_SIZE
        Addon.db.size = Addon:Clamp(value, minimum, Addon.MAX_SIZE)
        Addon.UI:SetSize(Addon.db.size)
    end)
    MakeSlider("icons", frame, L.ICON_SIZE, 300, -205, 210, 18, 48, 1, function(value)
        Addon.db.addonButtonSize = value
        Addon.AddonButtons:Layout()
    end)

    MakeSlider("alpha", frame, L.OPACITY, 30, -267, 210, 35, 100, 5, function(value)
        Addon.db.alpha = value / 100
        Addon.UI:SetAlpha(Addon.db.alpha)
    end, function(value)
        return value .. "%"
    end)
    MakeSlider("gap", frame, L.ICON_SPACING, 300, -267, 210, 0, 30, 1, function(value)
        Addon.db.addonButtonDistance = value
        Addon.AddonButtons:Layout()
    end)

    MakeSlider("border", frame, L.BORDER_THICKNESS, 30, -329, 210, 0, 16, 1, function(value)
        Addon.UI:SetBorderSize(value)
    end)
    MakeSlider("columns", frame, L.PREFERRED_COLUMNS, 300, -329, 210, 1, 8, 1, function(value)
        Addon.db.addonHubColumns = value
        Addon.AddonButtons:Layout()
    end)

    MakeSlider("arrow", frame, L.ARROW_SIZE, 30, -391, 210, 8, 40, 1, function(value)
        Addon.db.playerArrowSize = value
        Addon.Markers:SetPlayerArrowSize(value)
    end)

    MakeSectionLabel(frame, L.BEHAVIOR, 30, -436)
    MakeCheck("replace", frame, L.REPLACE_MINIMAP, 26, -456, function(value)
        Addon.UI:SetReplacementMode(value, true)
        Options:Refresh()
    end)
    MakeCheck("hub", frame, L.SHOW_ADDON_BUTTON, 286, -456, function(value)
        Addon.UI:SetAddonButtonsShown(value)
    end)
    MakeCheck("lock", frame, L.LOCK_MAP_BUTTONS, 26, -486, function(value)
        Addon.db.locked = not not value
    end)
    MakeCheck("bosses", frame, L.SHOW_DUNGEON_BOSSES, 286, -486, function(value)
        Addon.Markers:SetBossesShown(value)
    end)
    MakeCheck("adds", frame, L.SHOW_DUNGEON_ENEMIES, 286, -516, function(value)
        Addon.Markers:SetAddsShown(value)
    end)
    MakeCheck("gathering", frame, L.GATHERING_OPTION, 26, -516, function(value)
        Addon.Gathering:SetShown(value, false)
    end)
    MakeCheck("services", frame, L.SHOW_NEARBY_SERVICES, 286, -546, function(value)
        Addon.Gathering:SetNativeServicesShown(value, false)
    end)

    MakeSectionLabel(frame, L.KEY_BINDINGS, 30, -582)
    local bindingLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    bindingLabel:SetPoint("TOPLEFT", 30, -610)
    bindingLabel:SetText(L.GATHERING_KEYBIND_LABEL)

    bindingButton = MakeButton(frame, "", 145, function()
        if type(InCombatLockdown) == "function" and InCombatLockdown() then
            Addon:Print(L.KEYBIND_COMBAT)
            return
        end
        bindingListening = true
        bindingButton:EnableKeyboard(true)
        if bindingButton.EnableGamePadButton then
            bindingButton:EnableGamePadButton(true)
        end
        UpdateBindingControls()
    end)
    bindingButton:SetPoint("TOPLEFT", 286, -603)
    bindingButton:SetScript("OnKeyDown", function(_, key)
        ProcessBindingInput(key)
    end)
    bindingButton:SetScript("OnGamePadButtonDown", function(_, key)
        ProcessBindingInput(key)
    end)
    bindingButton:EnableMouseWheel(true)
    bindingButton:SetScript("OnMouseWheel", function(_, delta)
        if bindingListening then
            ProcessBindingInput(delta > 0 and "MOUSEWHEELUP" or "MOUSEWHEELDOWN")
        end
    end)

    clearBindingButton = MakeButton(frame, L.KEYBIND_CLEAR, 72, function()
        StopBindingCapture()
        ClearGatheringBinding()
        UpdateBindingControls()
    end)
    clearBindingButton:SetPoint("LEFT", bindingButton, "RIGHT", 8, 0)

    local resetButtons = MakeButton(frame, L.REPOSITION_BUTTONS, 150, function()
        Addon.AddonButtons:ResetAnchor()
        Addon.UI:ResetMinimapButtonPosition()
    end)
    resetButtons:SetPoint("BOTTOMLEFT", 30, 24)

    local resetAll = MakeButton(frame, L.RESET_ALL, 150, function()
        Addon.UI:ResetSettings()
        Options:Refresh()
    end)
    resetAll:SetPoint("LEFT", resetButtons, "RIGHT", 10, 0)

    local help = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    help:SetPoint("BOTTOMLEFT", 30, 70)
    help:SetText(L.OPTIONS_HELP)
    help:SetTextColor(0.55, 0.75, 1)

    frame:SetScript("OnShow", function()
        Options:Refresh()
    end)
    frame:SetScript("OnHide", function()
        StopBindingCapture()
    end)
    table.insert(UISpecialFrames, frame:GetName())
end

function Options:Refresh()
    if not frame then
        return
    end

    updating = true
    local minimumSize = Addon.db.shape == "circle" and Addon.MIN_SIZE or Addon.MIN_SQUARE_SIZE
    sliders.size:SetMinMaxValues(minimumSize, Addon.MAX_SIZE)
    if sliders.size.Low then
        sliders.size.Low:SetText(tostring(minimumSize))
    end
    sliders.size:SetValue(Addon.db.size)
    sliders.alpha:SetValue(math.floor((Addon.db.alpha * 100) + 0.5))
    sliders.border:SetValue(Addon.db.borderSize)
    sliders.arrow:SetValue(Addon.db.playerArrowSize)
    sliders.icons:SetValue(Addon.db.addonButtonSize)
    sliders.gap:SetValue(Addon.db.addonButtonDistance)
    sliders.columns:SetValue(Addon.db.addonHubColumns)
    checks.replace:SetChecked(Addon.db.replaceMinimap)
    checks.hub:SetChecked(Addon.db.showAddonButtons)
    checks.lock:SetChecked(Addon.db.locked)
    checks.bosses:SetChecked(Addon.db.showBosses)
    checks.adds:SetChecked(Addon.db.showAdds)
    checks.gathering:SetChecked(Addon.Gathering:IsEnabled())
    checks.services:SetChecked(Addon.Gathering:AreNativeServicesShown())
    UpdateBindingControls()
    updating = false
end

function Options:Toggle()
    if not frame then
        CreateOptionsFrame()
    end
    frame:SetShown(not frame:IsShown())
end

function Options:Open()
    if not frame then
        CreateOptionsFrame()
    end
    frame:Show()
end
