local NS = rawget(_G, "MCDVoiceCooldownNS") or {}
_G.MCDVoiceCooldownNS = NS

NS.OptionsState = NS.OptionsState or {}

local GetNumClasses = rawget(_G, "GetNumClasses")
local GetClassInfo = rawget(_G, "GetClassInfo")
local GetNumSpecializationsForClassID = rawget(_G, "GetNumSpecializationsForClassID")
local GetSpecializationInfoForClassID = rawget(_G, "GetSpecializationInfoForClassID")

local L = NS.L or function(key, ...)
    if select("#", ...) > 0 then
        return string.format(tostring(key), ...)
    end
    return tostring(key)
end

local CONST = NS.Constants or {}
local Utils = NS.Utils or {}

local ALL_CLASSES_ID = CONST.ALL_CLASSES_ID or 0
local ALL_SPECS_ID = CONST.ALL_SPECS_ID or 0
local OBJECT_TYPE_SPELL = CONST.OBJECT_TYPE_SPELL or "spell"
local OBJECT_TYPE_ITEM = CONST.OBJECT_TYPE_ITEM or "item"

local function TrimText(value)
    if Utils.TrimText then
        return Utils.TrimText(value)
    end
    return tostring(value or ""):match("^%s*(.-)%s*$")
end

local function GetApi()
    return NS.API
end

local function BuildInlineTexture(texture, size, left, right, top, bottom)
    if type(texture) ~= "string" and type(texture) ~= "number" then
        return ""
    end

    size = tonumber(size) or 14
    if left and right and top and bottom then
        return string.format("|T%s:%d:%d:0:0:256:256:%d:%d:%d:%d|t ", tostring(texture), size, size, left, right, top, bottom)
    end
    return string.format("|T%s:%d:%d|t ", tostring(texture), size, size)
end

local function GetClassColorCode(classFile)
    local color = RAID_CLASS_COLORS and classFile and RAID_CLASS_COLORS[classFile]
    if not color then
        return nil
    end

    local r = math.floor(((tonumber(color.r) or 1) * 255) + 0.5)
    local g = math.floor(((tonumber(color.g) or 1) * 255) + 0.5)
    local b = math.floor(((tonumber(color.b) or 1) * 255) + 0.5)
    return string.format("|cff%02x%02x%02x", r, g, b)
end

local function BuildClassDisplayText(className, classFile)
    local icon = ""
    local coords = CLASS_ICON_TCOORDS and classFile and CLASS_ICON_TCOORDS[classFile]
    if coords then
        icon = BuildInlineTexture(
            "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES",
            14,
            math.floor((coords[1] or 0) * 256),
            math.floor((coords[2] or 1) * 256),
            math.floor((coords[3] or 0) * 256),
            math.floor((coords[4] or 1) * 256)
        )
    end

    local text = tostring(className or "-")
    local colorCode = GetClassColorCode(classFile)
    if colorCode then
        return icon .. colorCode .. text .. "|r"
    end
    return icon .. text
end

local function BuildSpecDisplayText(specName, specIcon)
    return BuildInlineTexture(specIcon, 14) .. tostring(specName or "-")
end

local function IsSoundSourceValue(value)
    value = tostring(value or "")
    return value == "builtin" or value == "sharedmedia" or value == "custom" or value == "tts"
end

function NS.OptionsState:GetState(owner)
    owner = owner or NS.AceOptions or self
    owner.state = owner.state or {}
    local api = GetApi()
    if not api then
        return owner.state
    end

    if owner.state.classID == nil or owner.state.specID == nil then
        self:SyncScopeToCurrentSpec(owner)
    end
    local classID = tonumber(owner.state.classID) or 0
    if classID == ALL_CLASSES_ID then
        owner.state.specID = ALL_SPECS_ID
    end
    local modeTts, modeSound = api.GetModes()
    owner.state.notifyMode = owner.state.notifyMode or modeSound
    owner.state.fixedCD = true
    owner.state.checkTalent = owner.state.checkTalent == true
    owner.state.talentId = tonumber(owner.state.talentId) or 0
    owner.state.talentName = tostring(owner.state.talentName or "")
    owner.state.talentCD = tonumber(owner.state.talentCD) or 0
    owner.state.delayEnabled = owner.state.delayEnabled == true
    owner.state.delaySeconds = math.max(0, tonumber(owner.state.delaySeconds) or 0)

    local defaultBuiltin = owner:GetDefaultBuiltinSoundPath()
    owner.state.builtinSoundPath = owner:NormalizeSoundPath(owner.state.builtinSoundPath or defaultBuiltin)
    if owner.state.builtinSoundPath == "" or not owner:IsBuiltinSoundPath(owner.state.builtinSoundPath) then
        owner.state.builtinSoundPath = defaultBuiltin
    end
    owner.state.sharedMediaSound = TrimText(owner.state.sharedMediaSound or "")
    owner.state.useSharedMediaSound = owner.state.useSharedMediaSound == true
    owner.state.customSoundPath = owner:NormalizeSoundPath(owner.state.customSoundPath or "")
    owner.state.useCustomSound = owner.state.useCustomSound == true
    owner.state.soundSource = tostring(owner.state.soundSource or "")

    if TrimText(owner.state.soundPath or "") == "" and tostring(owner.state.notifyMode or "") ~= tostring(modeTts) then
        owner.state.soundPath = owner.state.builtinSoundPath
    else
        owner.state.soundPath = owner:NormalizeSoundPath(owner.state.soundPath or "")
    end

    if owner.state.soundSource == "" then
        local resolved = owner:ResolveSoundSourceFields(owner.state, modeTts, modeSound)
        owner.state.notifyMode = resolved.notifyMode
        owner.state.soundSource = resolved.soundSource
        owner.state.soundPath = resolved.soundPath
        owner.state.builtinSoundPath = resolved.builtinSoundPath
        owner.state.customSoundPath = resolved.customSoundPath
        owner.state.sharedMediaSound = resolved.sharedMediaSound
        owner.state.useCustomSound = resolved.useCustomSound == true
        owner.state.useSharedMediaSound = resolved.useSharedMediaSound == true
    end

    return owner.state
end

function NS.OptionsState:SyncScopeToCurrentSpec(owner)
    owner = owner or NS.AceOptions or self
    local api = GetApi()
    owner.state = owner.state or {}
    if not api or type(api.GetCurrentClassSpec) ~= "function" then
        return
    end

    local classID, specID = api.GetCurrentClassSpec()
    owner.state.classID = tonumber(classID) or 0
    owner.state.specID = tonumber(specID) or 0
    owner.state.selectedKey = nil
    owner.state.selectedCollectionKey = nil
end

function NS.OptionsState:GetClassOptionList(owner)
    local options = {
        { classID = ALL_CLASSES_ID, className = L("ALL_CLASSES"), classFile = nil },
    }
    if type(GetNumClasses) ~= "function" or type(GetClassInfo) ~= "function" then
        return options
    end

    local classCount = tonumber(GetNumClasses()) or 0
    for classIndex = 1, classCount do
        local ok, className, classFile, classID = pcall(GetClassInfo, classIndex)
        classID = tonumber(classID) or 0
        if ok and classID > 0 and type(className) == "string" and className ~= "" then
            options[#options + 1] = {
                classID = classID,
                className = className,
                classFile = classFile,
            }
        end
    end

    return options
end

function NS.OptionsState:GetClassValues(owner)
    local values = {}
    local classList = self:GetClassOptionList(owner)
    for _, option in ipairs(classList) do
        values[option.classID] = BuildClassDisplayText(option.className, option.classFile)
    end
    return values
end

function NS.OptionsState:GetSpecOptionList(owner, classID)
    classID = tonumber(classID) or 0
    local options = {
        {
            specID = ALL_SPECS_ID,
            specName = L("ALL_SPECS"),
            specIcon = nil,
        }
    }
    if classID == ALL_CLASSES_ID then
        return options
    end
    if classID < 0 then
        return options
    end

    if type(GetNumSpecializationsForClassID) ~= "function" or type(GetSpecializationInfoForClassID) ~= "function" then
        return options
    end

    local specCount = tonumber(GetNumSpecializationsForClassID(classID)) or 0
    for specIndex = 1, specCount do
        local ok, specID, specName, _, specIcon = pcall(GetSpecializationInfoForClassID, classID, specIndex)
        specID = tonumber(specID) or 0
        if ok and specID > 0 then
            options[#options + 1] = {
                specID = specID,
                specName = (type(specName) == "string" and specName ~= "") and specName or L("FALLBACK_SPEC", " " .. tostring(specID)),
                specIcon = specIcon,
            }
        end
    end

    return options
end

function NS.OptionsState:GetSpecValues(owner, classID)
    local values = {}
    for _, option in ipairs(self:GetSpecOptionList(owner, classID)) do
        values[option.specID] = BuildSpecDisplayText(option.specName, option.specIcon)
    end
    return values
end

function NS.OptionsState:EnsureValidScope(owner)
    owner = owner or NS.AceOptions or self
    local state = self:GetState(owner)
    if state.classID == nil then
        self:SyncScopeToCurrentSpec(owner)
    end
    state.classID = tonumber(state.classID) or 0
    state.specID = tonumber(state.specID) or 0

    if state.classID == ALL_CLASSES_ID then
        state.specID = ALL_SPECS_ID
        return
    end

    if state.classID < 0 then
        self:SyncScopeToCurrentSpec(owner)
    end

    local specValues = self:GetSpecValues(owner, state.classID)
    if not specValues[state.specID] then
        state.specID = ALL_SPECS_ID
    end
end

function NS.OptionsState:ClearEditorFields(owner)
    owner = owner or NS.AceOptions or self
    local api = GetApi()
    local state = self:GetState(owner)
    if not api or type(api.GetModes) ~= "function" then
        return
    end
    local _, modeSound = api.GetModes()

    state.selectedKey = nil
    state.spellId = 0
    state.objectType = nil
    state.spellName = ""
    state.baseCD = 0
    state.fixedCD = true
    state.checkTalent = false
    state.talentId = 0
    state.talentName = ""
    state.talentCD = 0
    state.delayEnabled = false
    state.delaySeconds = 0
    state.notifyMode = modeSound
    state.ttsText = ""
    state.ttsRate = 0
    state.builtinSoundPath = owner:GetDefaultBuiltinSoundPath()
    state.sharedMediaSound = ""
    state.useSharedMediaSound = false
    state.customSoundPath = ""
    state.useCustomSound = false
    state.soundSource = "builtin"
    state.soundPath = state.builtinSoundPath
end

function NS.OptionsState:SyncStateFromWidgets(owner)
    owner = owner or NS.AceOptions or self
    local widgets = owner.editorWidgets or owner.widgets
    local state = self:GetState(owner)
    local api = GetApi()
    if not widgets or not api then
        return
    end

    if widgets.classDrop then
        state.classID = tonumber(widgets.classDrop:GetValue()) or state.classID or 0
    end
    if widgets.specDrop then
        if (tonumber(state.classID) or 0) == ALL_CLASSES_ID then
            state.specID = ALL_SPECS_ID
        else
            state.specID = tonumber(widgets.specDrop:GetValue()) or state.specID or 0
        end
    end
    if widgets.spellId then
        state.spellId = tonumber(widgets.spellId:GetText() or "") or 0
    end
    if widgets.spellName then
        state.spellName = tostring(widgets.spellName:GetText() or "")
    end
    if widgets.baseCD then
        state.baseCD = tonumber(widgets.baseCD:GetText() or "") or 0
    end
    if widgets.checkTalent then
        if type(widgets.checkTalent.GetValue) == "function" then
            state.checkTalent = widgets.checkTalent:GetValue() == true
        elseif type(widgets.checkTalent.GetChecked) == "function" then
            state.checkTalent = widgets.checkTalent:GetChecked() == true
        end
    end
    if widgets.talentId then
        state.talentId = tonumber(widgets.talentId:GetText() or "") or 0
    end
    if widgets.talentName then
        state.talentName = tostring(widgets.talentName:GetText() or "")
    end
    if widgets.talentCD then
        state.talentCD = tonumber(widgets.talentCD:GetText() or "") or 0
    end
    if widgets.delayEnabled then
        if type(widgets.delayEnabled.GetValue) == "function" then
            state.delayEnabled = widgets.delayEnabled:GetValue() == true
        elseif type(widgets.delayEnabled.GetChecked) == "function" then
            state.delayEnabled = widgets.delayEnabled:GetChecked() == true
        end
    end
    if widgets.delaySeconds then
        state.delaySeconds = math.max(0, tonumber(widgets.delaySeconds:GetText() or "") or 0)
    end
    state.fixedCD = true
    local modeTts, modeSound = api.GetModes()
    if widgets.notifyMode then
        local value = tostring(widgets.notifyMode:GetValue() or state.soundSource or "")
        if IsSoundSourceValue(value) then
            state.soundSource = value
            state.notifyMode = (value == "tts") and modeTts or modeSound
        else
            state.notifyMode = (value == tostring(modeTts)) and modeTts or modeSound
            if state.notifyMode == modeTts then
                state.soundSource = "tts"
            elseif tostring(state.soundSource or "") == "" then
                state.soundSource = "builtin"
            end
        end
    end
    if widgets.ttsText then
        state.ttsText = tostring(widgets.ttsText:GetText() or "")
    end
    if widgets.builtinSound and type(widgets.builtinSound.GetValue) == "function" then
        local builtinPath = owner:NormalizeSoundPath(widgets.builtinSound:GetValue() or state.builtinSoundPath or "")
        if builtinPath == "" or not owner:IsBuiltinSoundPath(builtinPath) then
            builtinPath = owner:GetDefaultBuiltinSoundPath()
        end
        state.builtinSoundPath = builtinPath
    end
    if widgets.sharedMediaSound and type(widgets.sharedMediaSound.GetValue) == "function" then
        state.sharedMediaSound = TrimText(widgets.sharedMediaSound:GetValue() or state.sharedMediaSound or "")
    end
    if widgets.soundPath then
        state.customSoundPath = owner:NormalizeSoundPath(widgets.soundPath:GetText() or state.customSoundPath or "")
    end

    local source = tostring(state.soundSource or "")
    local sourceEntry = {
        notifyMode = (source == "tts") and modeTts or modeSound,
        soundSource = source,
        soundPath = state.soundPath,
        customSoundPath = state.customSoundPath,
        builtinSoundPath = state.builtinSoundPath,
        sharedMediaSound = state.sharedMediaSound,
    }

    if source == "tts" then
        sourceEntry.soundPath = ""
        sourceEntry.sharedMediaSound = ""
    elseif source == "sharedmedia" then
        sourceEntry.soundPath = owner:ResolveSharedMediaSoundPath(state.sharedMediaSound, "")
        sourceEntry.customSoundPath = ""
    elseif source == "custom" then
        sourceEntry.soundPath = state.customSoundPath
        sourceEntry.sharedMediaSound = ""
    elseif source == "builtin" then
        sourceEntry.soundPath = state.builtinSoundPath or owner:GetDefaultBuiltinSoundPath()
        sourceEntry.sharedMediaSound = ""
        sourceEntry.customSoundPath = ""
    else
        if tostring(state.notifyMode or modeSound) == tostring(modeTts) then
            sourceEntry.soundSource = "tts"
        elseif TrimText(state.sharedMediaSound or "") ~= "" then
            sourceEntry.soundSource = "sharedmedia"
            sourceEntry.soundPath = owner:ResolveSharedMediaSoundPath(state.sharedMediaSound, "")
        elseif TrimText(state.customSoundPath or "") ~= "" then
            sourceEntry.soundSource = "custom"
            sourceEntry.soundPath = state.customSoundPath
        else
            sourceEntry.soundSource = "builtin"
            sourceEntry.soundPath = state.builtinSoundPath or owner:GetDefaultBuiltinSoundPath()
        end
    end

    owner:ApplySoundSourceToState(owner:ResolveSoundSourceFields(sourceEntry, modeTts, modeSound))
end
