SkyPhoneConfigurator = SkyPhoneConfigurator or {}

local TABLE_NAME = "sky_phone_configurator"
local CONFIG_ROW_ID = 1
local MAX_CHANGES = 1000
local MAX_PAYLOAD_BYTES = 8 * 1024 * 1024
local MAX_STRUCTURED_DEPTH = 20
local MAX_STRUCTURED_ENTRIES = 20000
local REDACTED_VALUE = "***REDACTED***"
local configurator_enabled = Config.PhoneConfigurator.Enabled == true
local default_config
local default_media
local stored_config
local stored_media
local revision = 1
local updated_at
local updated_by_name
local is_sequence

local FIXED_CONFIG_PATHS = {
    CommandPermissions = true,
    ["AdminPanel.AdminGroups"] = true,
    ["TestData.AdminGroups"] = true,
    ["FlipTok.AdminGroups"] = true,
    ["Picstagram.AdminGroups"] = true,
}

if configurator_enabled then
    print(
        "[sky_phone] Phone Configurator enabled: file-based settings from config.lua " ..
        "(except Config.CommandPermissions) and media.lua are disabled; SQL configuration is active."
    )
end

local CLIENT_CONFIG_KEYS = {
    AdminPanel = true,
    Animations = true,
    Banking = true,
    Billing = true,
    Bridge = true,
    Calendar = true,
    Calls = true,
    Command = true,
    CrewLink = true,
    Crypto = true,
    CustomApps = true,
    DarkChat = true,
    EasyShare = true,
    Feather = true,
    FlipTok = true,
    Garage = true,
    Health = true,
    Housing = true,
    LocalPages = true,
    Mail = true,
    MapMarkers = true,
    Marketplace = true,
    Memos = true,
    Messages = true,
    Payphones = true,
    Phone = true,
    Picstagram = true,
    Radio = true,
    Security = true,
    Sim = true,
    SkyRide = true,
    Speaker = true,
    TestData = true,
}

local function copy_value(value, active)
    local value_type = type(value)
    if value_type ~= "table" then
        return value
    end

    active = active or {}
    if active[value] then
        error("[sky_phone] Phone configurator cannot copy a cyclic table.")
    end
    active[value] = true

    local copy = {}
    for key, child in pairs(value) do
        copy[key] = copy_value(child, active)
    end
    active[value] = nil
    return copy
end

local function serialize_value(value, active)
    local value_type = type(value)
    if value_type == "nil" or value_type == "boolean" or value_type == "string" then
        return value
    end
    if value_type == "number" then
        if value ~= value or value == math.huge or value == -math.huge then
            error("[sky_phone] Phone configurator cannot serialize a non-finite number.")
        end
        return value
    end
    if value_type == "vector2" then
        return { __skyType = "vector2", x = value.x, y = value.y }
    end
    if value_type == "vector3" then
        return { __skyType = "vector3", x = value.x, y = value.y, z = value.z }
    end
    if value_type == "vector4" then
        return { __skyType = "vector4", x = value.x, y = value.y, z = value.z, w = value.w }
    end
    if value_type ~= "table" then
        error(("[sky_phone] Phone configurator cannot serialize value type '%s'."):format(value_type))
    end

    active = active or {}
    if active[value] then
        error("[sky_phone] Phone configurator cannot serialize a cyclic table.")
    end
    active[value] = true

    local has_numeric_map_key = false
    if not is_sequence(value) then
        for key in pairs(value) do
            if type(key) == "number" then
                has_numeric_map_key = true
                break
            end
        end
    end
    if has_numeric_map_key then
        local entries = {}
        for key, child in pairs(value) do
            if type(key) ~= "string" and type(key) ~= "number" then
                error("[sky_phone] Phone configurator only supports string and numeric table keys.")
            end
            entries[#entries + 1] = {
                key = key,
                keyType = type(key),
                value = serialize_value(child, active),
            }
        end
        table.sort(entries, function(left, right)
            if left.keyType ~= right.keyType then
                return left.keyType < right.keyType
            end
            if left.keyType == "number" then
                return left.key < right.key
            end
            return tostring(left.key) < tostring(right.key)
        end)
        active[value] = nil
        return { __skyType = "map", entries = entries }
    end

    local serialized = {}
    for key, child in pairs(value) do
        if type(key) ~= "string" and type(key) ~= "number" then
            error("[sky_phone] Phone configurator only supports string and numeric table keys.")
        end
        serialized[key] = serialize_value(child, active)
    end
    active[value] = nil
    return serialized
end

local function deserialize_value(value)
    if type(value) ~= "table" then
        return value
    end

    if value.__skyType == "vector2" then
        return vector2(tonumber(value.x) or 0.0, tonumber(value.y) or 0.0)
    end
    if value.__skyType == "vector3" then
        return vector3(tonumber(value.x) or 0.0, tonumber(value.y) or 0.0, tonumber(value.z) or 0.0)
    end
    if value.__skyType == "vector4" then
        return vector4(
            tonumber(value.x) or 0.0,
            tonumber(value.y) or 0.0,
            tonumber(value.z) or 0.0,
            tonumber(value.w) or 0.0
        )
    end
    if value.__skyType == "map" then
        local decoded = {}
        for _, entry in ipairs(value.entries or {}) do
            local key = entry.keyType == "number" and tonumber(entry.key) or entry.key
            decoded[key] = deserialize_value(entry.value)
        end
        return decoded
    end

    local decoded = {}
    for key, child in pairs(value) do
        decoded[key] = deserialize_value(child)
    end
    return decoded
end

local function apply_runtime_table(current, replacement)
    for key in pairs(current) do
        if replacement[key] == nil then
            current[key] = nil
        end
    end

    for key, value in pairs(replacement) do
        local current_value = current[key]
        if type(current_value) == "table" and type(value) == "table" then
            apply_runtime_table(current_value, value)
        else
            current[key] = value
        end
    end
end

is_sequence = function(value)
    if type(value) ~= "table" then
        return false
    end

    local count = 0
    local maximum = 0
    for key in pairs(value) do
        if type(key) ~= "number" or key < 1 or key % 1 ~= 0 then
            return false
        end
        count = count + 1
        maximum = math.max(maximum, key)
    end
    return count > 0 and count == maximum
end

local function upgrade_legacy_map(defaults, saved)
    local default_types = {}
    local numeric_only = true
    for _, entry in ipairs(defaults.entries or {}) do
        default_types[tostring(entry.key)] = entry.keyType
        if entry.keyType ~= "number" then
            numeric_only = false
        end
    end

    local entries = {}
    for key, child in pairs(saved) do
        local key_type = default_types[tostring(key)] or (numeric_only and "number" or type(key))
        local normalized_key = key_type == "number" and tonumber(key) or tostring(key)
        if normalized_key == nil then
            error("[sky_phone] Phone configurator could not migrate a numeric configuration key.")
        end
        entries[#entries + 1] = {
            key = normalized_key,
            keyType = key_type,
            value = copy_value(child),
        }
    end
    table.sort(entries, function(left, right)
        if left.keyType ~= right.keyType then
            return left.keyType < right.keyType
        end
        if left.keyType == "number" then
            return left.key < right.key
        end
        return tostring(left.key) < tostring(right.key)
    end)
    return { __skyType = "map", entries = entries }
end

local function merge_values(defaults, saved, path, excluded_paths)
    path = path or ""
    if type(defaults) ~= "table" or type(saved) ~= "table" then
        return copy_value(saved)
    end
    if path == "Companies.Definitions" then
        return copy_value(saved)
    end
    if defaults.__skyType == "map" and not saved.__skyType then
        saved = upgrade_legacy_map(defaults, saved)
    end
    if defaults.__skyType == "map" and saved.__skyType == "map" then
        local saved_entries = {}
        for _, entry in ipairs(saved.entries or {}) do
            saved_entries[entry.keyType .. ":" .. tostring(entry.key)] = entry
        end

        local entries = {}
        local included = {}
        for _, entry in ipairs(defaults.entries or {}) do
            local identity = entry.keyType .. ":" .. tostring(entry.key)
            local saved_entry = saved_entries[identity]
            entries[#entries + 1] = {
                key = entry.key,
                keyType = entry.keyType,
                value = saved_entry
                    and merge_values(
                        entry.value,
                        saved_entry.value,
                        path .. "." .. tostring(entry.key),
                        excluded_paths
                    )
                    or copy_value(entry.value),
            }
            included[identity] = true
        end
        for _, entry in ipairs(saved.entries or {}) do
            local identity = entry.keyType .. ":" .. tostring(entry.key)
            if not included[identity] then
                entries[#entries + 1] = copy_value(entry)
            end
        end
        return { __skyType = "map", entries = entries }
    end
    if defaults.__skyType or saved.__skyType then
        return copy_value(saved)
    end
    if is_sequence(defaults) then
        local merged = copy_value(saved)
        for index, child in ipairs(defaults) do
            merged[index] = saved[index] ~= nil
                and merge_values(child, saved[index], path .. "." .. tostring(index), excluded_paths)
                or copy_value(child)
        end
        return merged
    end
    if is_sequence(saved) then
        return copy_value(saved)
    end

    local merged = copy_value(defaults)
    for key, child in pairs(saved) do
        local child_path = path == "" and tostring(key) or (path .. "." .. tostring(key))
        if not excluded_paths or not excluded_paths[child_path] then
            if merged[key] ~= nil then
                merged[key] = merge_values(merged[key], child, child_path, excluded_paths)
            else
                merged[key] = copy_value(child)
            end
        end
    end
    return merged
end

local function decode_payload(encoded, scope)
    if type(encoded) ~= "string" or encoded == "" then
        error(("[sky_phone] Phone configurator has an empty %s SQL payload."):format(scope))
    end

    local success, decoded = pcall(json.decode, encoded)
    if not success or type(decoded) ~= "table" then
        error(("[sky_phone] Phone configurator failed to decode the %s SQL payload."):format(scope))
    end
    return decoded
end

local function encode_payload(payload, scope)
    local success, encoded = pcall(json.encode, payload)
    if not success or type(encoded) ~= "string" then
        error(("[sky_phone] Phone configurator failed to encode the %s SQL payload."):format(scope))
    end
    if #encoded > MAX_PAYLOAD_BYTES then
        error(("[sky_phone] Phone configurator %s payload exceeds %s bytes."):format(scope, MAX_PAYLOAD_BYTES))
    end
    return encoded
end

local function affected_rows(result)
    if type(result) == "number" then
        return result
    end
    return type(result) == "table" and tonumber(result.affectedRows) or 0
end

local function apply_runtime_configuration()
    if not configurator_enabled then
        return
    end

    local runtime_config = deserialize_value(stored_config)
    for key, value in pairs(runtime_config) do
        if key ~= "CommandPermissions" then
            if type(Config[key]) == "table" and type(value) == "table" then
                apply_runtime_table(Config[key], value)
            else
                Config[key] = value
            end
        end
    end
    local runtime_media = deserialize_value(stored_media)
    if type(Config.Media) == "table" and type(runtime_media) == "table" then
        apply_runtime_table(Config.Media, runtime_media)
    else
        Config.Media = runtime_media
    end
end

local function humanize(value)
    local text = tostring(value or "")
        :gsub("[_%-]+", " ")
        :gsub("(%l)(%u)", "%1 %2")
        :gsub("(%a)(%d)", "%1 %2")
    return text:gsub("^%l", string.upper)
end

local function sensitive_path(path)
    local leaf = tostring(path):match("([^.]+)$") or path
    local normalized = leaf:lower():gsub("[^%w]", "")
    if normalized:find("apikey", 1, true)
        or normalized:find("secret", 1, true)
        or normalized:find("pepper", 1, true)
        or normalized == "password"
        or normalized == "token"
        or normalized == "authorization"
        or normalized == "credential"
        or normalized == "connectionstring"
    then
        return true
    end
    return false
end

local function mask_admin_value(value, path)
    if type(value) ~= "table" then
        return type(value) == "string" and sensitive_path(path) and REDACTED_VALUE or value
    end
    if value.__skyType == "map" then
        local entries = {}
        for index, entry in ipairs(value.entries or {}) do
            entries[index] = {
                key = entry.key,
                keyType = entry.keyType,
                value = mask_admin_value(entry.value, path .. "." .. tostring(entry.key)),
            }
        end
        return { __skyType = "map", entries = entries }
    end

    local masked = {}
    for key, child in pairs(value) do
        masked[key] = mask_admin_value(child, path .. "." .. tostring(key))
    end
    return masked
end

local function restore_redacted_values(value, current, path)
    if type(value) ~= "table" then
        if value == REDACTED_VALUE and sensitive_path(path) then
            return current
        end
        return value
    end
    if value.__skyType == "map" then
        local current_entries = {}
        if type(current) == "table" and current.__skyType == "map" then
            for _, entry in ipairs(current.entries or {}) do
                current_entries[entry.keyType .. ":" .. tostring(entry.key)] = entry.value
            end
        end

        local entries = {}
        for index, entry in ipairs(value.entries or {}) do
            local identity = entry.keyType .. ":" .. tostring(entry.key)
            entries[index] = {
                key = entry.key,
                keyType = entry.keyType,
                value = restore_redacted_values(
                    entry.value,
                    current_entries[identity],
                    path .. "." .. tostring(entry.key)
                ),
            }
        end
        return { __skyType = "map", entries = entries }
    end

    local restored = {}
    for key, child in pairs(value) do
        local current_child = type(current) == "table" and current[key] or nil
        restored[key] = restore_redacted_values(child, current_child, path .. "." .. tostring(key))
    end
    return restored
end

local function empty_structure(scope, path)
    if scope ~= "config" then
        return nil
    end
    if path == "Garage.VehicleImages.ModelNames" then
        return {
            entries = {},
            keyType = "number",
            kind = "map",
            template = { kind = "value", valueType = "string" },
        }
    end
    if path == "CrewLink.ExternalPingResources" or path == "CustomApps.TrustedAdapters" then
        return {
            fields = {},
            kind = "table",
            mutableKeys = true,
            template = { kind = "value", valueType = "boolean" },
        }
    end
    if path == "FlipTok.MusicTracks" then
        return {
            items = {},
            kind = "list",
            template = {
                fields = {
                    Artist = { kind = "value", valueType = "string" },
                    Id = { kind = "value", valueType = "string" },
                    Title = { kind = "value", valueType = "string" },
                    Url = { kind = "value", valueType = "string" },
                },
                kind = "table",
            },
        }
    end
    if path == "Music.Tracks" then
        return {
            items = {},
            kind = "list",
            template = {
                fields = {
                    Artist = { kind = "value", valueType = "string" },
                    Id = { kind = "value", valueType = "string" },
                    Title = { kind = "value", valueType = "string" },
                },
                kind = "table",
            },
        }
    end
    if path == "Payphones.CustomLocations" then
        return {
            items = {},
            kind = "list",
            template = { kind = "vector", vectorType = "vector4" },
        }
    end
    if path:match("^Companies%.Definitions%.[^.]+%.Services$") then
        return {
            items = {},
            kind = "list",
            template = {
                fields = {
                    Description = { kind = "value", valueType = "string" },
                    Id = { kind = "value", valueType = "string" },
                    Price = { kind = "value", valueType = "string" },
                    RequestsEnabled = { kind = "value", valueType = "boolean" },
                    Title = { kind = "value", valueType = "string" },
                },
                kind = "table",
            },
        }
    end
    return nil
end

local function build_structure(value, scope, path)
    local value_type = type(value)
    if scope == "config" and path == "Phone.Keybind" then
        return { kind = "optionalString" }
    end
    if scope == "config" and path == "Companies.Definitions" and value_type == "table" then
        local keys = {}
        for key in pairs(value) do
            keys[#keys + 1] = key
        end
        table.sort(keys, function(left, right)
            return tostring(left) < tostring(right)
        end)
        local fields = {}
        for _, key in ipairs(keys) do
            fields[key] = build_structure(value[key], scope, path .. "." .. tostring(key))
        end
        return {
            fields = fields,
            kind = "table",
            mutableKeys = true,
            template = keys[1] and fields[keys[1]] or nil,
        }
    end
    if value_type ~= "table" then
        return {
            kind = "value",
            valueType = value_type,
        }
    end

    if value.__skyType == "vector2" or value.__skyType == "vector3" or value.__skyType == "vector4" then
        return {
            kind = "vector",
            vectorType = value.__skyType,
        }
    end
    if value.__skyType == "map" then
        local entries = {}
        local key_type
        for index, entry in ipairs(value.entries or {}) do
            entries[index] = {
                key = entry.key,
                keyType = entry.keyType,
                structure = build_structure(entry.value, scope, path .. "." .. tostring(entry.key)),
            }
            if index == 1 then
                key_type = entry.keyType
            elseif key_type ~= entry.keyType then
                key_type = nil
            end
        end
        return {
            entries = entries,
            keyType = key_type,
            kind = "map",
            template = entries[1] and entries[1].structure or nil,
        }
    end
    local configured_empty_structure = next(value) == nil and empty_structure(scope, path) or nil
    if configured_empty_structure then
        return configured_empty_structure
    end
    if is_sequence(value) then
        local items = {}
        for index, child in ipairs(value) do
            items[index] = build_structure(child, scope, path .. "." .. tostring(index))
        end
        return {
            items = items,
            kind = "list",
            template = items[1],
        }
    end

    local fields = {}
    for key, child in pairs(value) do
        fields[key] = build_structure(child, scope, path .. "." .. tostring(key))
    end
    return {
        fields = fields,
        kind = "table",
    }
end

local function add_field(fields, field_index, scope, path, value, default_value)
    local value_type = type(value)
    local field_type = value_type
    if value_type == "table" then
        field_type = "json"
    elseif value_type ~= "boolean" and value_type ~= "number" and value_type ~= "string" then
        return
    end
    if scope == "config" and path == "Phone.Keybind" then
        field_type = "stringOrFalse"
    end

    local sensitive = type(value) == "string" and sensitive_path(path)
    local field = {
        configured = sensitive and value ~= "" or nil,
        label = scope == "config" and path == "Companies.Definitions"
            and "Jobs"
            or humanize(path:match("([^.]+)$") or path),
        path = path,
        scope = scope,
        sensitive = sensitive,
        structure = field_type == "json" and build_structure(default_value, scope, path) or nil,
        type = field_type,
        value = sensitive and "" or mask_admin_value(value, path),
    }
    fields[#fields + 1] = field
    field_index[scope .. ":" .. path] = field
end

local function flatten_company_fields(fields, field_index, path, value, default_value)
    if path == "Companies.Definitions"
        or type(value) ~= "table"
        or value.__skyType
        or is_sequence(value)
        or next(value) == nil
    then
        add_field(fields, field_index, "config", path, value, default_value)
        return
    end

    local keys = {}
    for key in pairs(value) do
        keys[#keys + 1] = key
    end
    table.sort(keys, function(left, right)
        return tostring(left) < tostring(right)
    end)
    for _, key in ipairs(keys) do
        flatten_company_fields(
            fields,
            field_index,
            path .. "." .. tostring(key),
            value[key],
            type(default_value) == "table" and default_value[key] or nil
        )
    end
end

local function build_sections(scope, payload, defaults, sections, field_index)
    local general_fields = {}
    local keys = {}
    for key in pairs(payload) do
        keys[#keys + 1] = key
    end
    table.sort(keys, function(left, right)
        return tostring(left) < tostring(right)
    end)

    for _, key in ipairs(keys) do
        local value = payload[key]
        if scope == "config" and key == "Companies" then
            local fields = {}
            flatten_company_fields(fields, field_index, "Companies", value, defaults[key])
            table.sort(fields, function(left, right)
                if left.path == "Companies.Definitions" then
                    return false
                end
                if right.path == "Companies.Definitions" then
                    return true
                end
                return left.path < right.path
            end)
            sections[#sections + 1] = {
                fields = fields,
                id = "config:Companies",
                label = humanize(key),
                scope = scope,
            }
        elseif type(value) == "table" and not value.__skyType and not is_sequence(value) and next(value) ~= nil then
            local fields = {}
            add_field(fields, field_index, scope, tostring(key), value, defaults[key])
            sections[#sections + 1] = {
                fields = fields,
                id = scope .. ":" .. tostring(key),
                label = humanize(key),
                scope = scope,
            }
        else
            add_field(general_fields, field_index, scope, tostring(key), value, defaults[key])
        end
    end

    if #general_fields > 0 then
        table.insert(sections, scope == "config" and 1 or (#sections + 1), {
            fields = general_fields,
            id = scope .. ":general",
            label = "General",
            scope = scope,
        })
    end
end

local function build_admin_data()
    local sections = {}
    local field_index = {}
    build_sections("config", stored_config, default_config, sections, field_index)
    build_sections("media", stored_media, default_media, sections, field_index)
    return {
        enabled = configurator_enabled,
        revision = revision,
        sections = sections,
        updatedAt = updated_at,
        updatedBy = updated_by_name,
    }, field_index
end

local function split_path(path)
    local parts = {}
    for part in path:gmatch("[^.]+") do
        parts[#parts + 1] = part
    end
    return parts
end

local function set_path(root, path, value)
    local parts = split_path(path)
    if #parts == 0 then
        return false
    end

    local parent = root
    for index = 1, #parts - 1 do
        parent = parent[parts[index]]
        if type(parent) ~= "table" then
            return false
        end
    end
    parent[parts[#parts]] = value
    return true
end

local function get_path(root, path)
    local value = root
    for _, part in ipairs(split_path(path)) do
        if type(value) ~= "table" then
            return nil
        end
        value = value[part]
    end
    return value
end

local function validate_structured_value(value, depth, state)
    local value_type = type(value)
    if value_type == "string" then
        return #value <= 65535
    end
    if value_type == "number" then
        return value == value and value ~= math.huge and value ~= -math.huge
    end
    if value_type == "boolean" or value_type == "nil" then
        return true
    end
    if value_type ~= "table" or depth > MAX_STRUCTURED_DEPTH then
        return false
    end

    state.entries = state.entries + 1
    if state.entries > MAX_STRUCTURED_ENTRIES then
        return false
    end

    local sky_type = value.__skyType
    if sky_type then
        if sky_type == "vector2" or sky_type == "vector3" or sky_type == "vector4" then
            local axes = sky_type == "vector2" and { "x", "y" }
                or sky_type == "vector3" and { "x", "y", "z" }
                or { "x", "y", "z", "w" }
            for _, axis in ipairs(axes) do
                local coordinate = value[axis]
                if type(coordinate) ~= "number"
                    or coordinate ~= coordinate
                    or coordinate == math.huge
                    or coordinate == -math.huge
                then
                    return false
                end
            end
            return true
        end
        if sky_type ~= "map" or type(value.entries) ~= "table" then
            return false
        end
        if next(value.entries) and not is_sequence(value.entries) then
            return false
        end

        local seen = {}
        for _, entry in ipairs(value.entries) do
            if type(entry) ~= "table" or (entry.keyType ~= "string" and entry.keyType ~= "number") then
                return false
            end
            local key = entry.key
            if entry.keyType == "string" then
                if type(key) ~= "string" or key == "" or #key > 255 then
                    return false
                end
            elseif type(key) ~= "number"
                or key ~= key
                or key == math.huge
                or key == -math.huge
            then
                return false
            end

            local identity = entry.keyType .. ":" .. tostring(key)
            if seen[identity] then
                return false
            end
            seen[identity] = true
            if not validate_structured_value(entry.value, depth + 1, state) then
                return false
            end
        end
        return true
    end

    for key, child in pairs(value) do
        if type(key) ~= "string" and type(key) ~= "number" then
            return false
        end
        if not validate_structured_value(child, depth + 1, state) then
            return false
        end
    end
    return true
end

local function validate_locked_structure(structure, value)
    if type(structure) ~= "table" or type(structure.kind) ~= "string" then
        return false
    end
    if structure.kind == "value" then
        return type(value) == structure.valueType
    end
    if structure.kind == "optionalString" then
        return value == false or type(value) == "string"
    end
    if structure.kind == "vector" then
        return type(value) == "table" and value.__skyType == structure.vectorType
    end
    if structure.kind == "list" then
        if type(value) ~= "table" or value.__skyType or (next(value) and not is_sequence(value)) then
            return false
        end
        local fixed_items = structure.items or {}
        for index, item_structure in ipairs(fixed_items) do
            if value[index] == nil or not validate_locked_structure(item_structure, value[index]) then
                return false
            end
        end
        if structure.template then
            for index = #fixed_items + 1, #value do
                if not validate_locked_structure(structure.template, value[index]) then
                    return false
                end
            end
        end
        return true
    end
    if structure.kind == "table" then
        if type(value) ~= "table" or value.__skyType or is_sequence(value) then
            return false
        end
        if not structure.mutableKeys then
            for key in pairs(value) do
                if not structure.fields[key] then
                    return false
                end
            end
            for key, field_structure in pairs(structure.fields or {}) do
                if value[key] == nil or not validate_locked_structure(field_structure, value[key]) then
                    return false
                end
            end
        end
        if structure.mutableKeys and structure.template then
            for key, child in pairs(value) do
                if type(key) ~= "string"
                    or #key > 64
                    or not key:match("^[a-z0-9_-]+$")
                    or not validate_locked_structure(structure.template, child)
                then
                    return false
                end
            end
        end
        return true
    end
    if structure.kind ~= "map" or type(value) ~= "table" then
        return false
    end
    if value.__skyType ~= "map" then
        return next(value) == nil and #(structure.entries or {}) == 0
    end

    local entries = {}
    for _, entry in ipairs(value.entries or {}) do
        entries[entry.keyType .. ":" .. tostring(entry.key)] = entry.value
    end
    for _, entry in ipairs(structure.entries or {}) do
        local child = entries[entry.keyType .. ":" .. tostring(entry.key)]
        if child == nil or not validate_locked_structure(entry.structure, child) then
            return false
        end
    end
    if structure.template or structure.keyType then
        local fixed_entries = {}
        for _, entry in ipairs(structure.entries or {}) do
            fixed_entries[entry.keyType .. ":" .. tostring(entry.key)] = true
        end
        for _, entry in ipairs(value.entries or {}) do
            local identity = entry.keyType .. ":" .. tostring(entry.key)
            local invalid_key_type = structure.keyType and entry.keyType ~= structure.keyType
            local invalid_value = structure.template
                and not validate_locked_structure(structure.template, entry.value)
            if not fixed_entries[identity] and (invalid_key_type or invalid_value) then
                return false
            end
        end
    end
    return true
end

local function normalize_change_value(field, value)
    if field.type == "boolean" then
        return type(value) == "boolean" and value or nil
    end
    if field.type == "number" then
        if type(value) ~= "number" or value ~= value or value == math.huge or value == -math.huge then
            return nil
        end
        return value
    end
    if field.type == "string" then
        if type(value) ~= "string" or #value > 65535 or value == REDACTED_VALUE then
            return nil
        end
        return value
    end
    if field.type == "stringOrFalse" then
        if value == false then
            return false
        end
        if type(value) ~= "string" or #value > 65535 then
            return nil
        end
        return value
    end
    if field.type == "json" and type(value) == "table" then
        if not validate_structured_value(value, 0, { entries = 0 }) then
            return nil
        end
        local serialized = serialize_value(value)
        if not validate_locked_structure(field.structure, serialized) then
            return nil
        end
        return serialized
    end
    return nil
end

local function client_payload()
    local payload = {}
    for key in pairs(CLIENT_CONFIG_KEYS) do
        if stored_config[key] ~= nil then
            payload[key] = copy_value(stored_config[key])
        end
    end
    return payload
end

function SkyPhoneConfigurator.Broadcast(target)
    TriggerClientEvent("sky_phone:configurator:sync", target or -1, {
        config = client_payload(),
        enabled = configurator_enabled,
        revision = revision,
    })
end

local function read_stored_row()
    local rows = Bridge.Database.Query(([[
        SELECT `config_payload`, `media_payload`, `revision`, `updated_at`, `updated_by_name`
        FROM `%s`
        WHERE `id` = ?
        LIMIT 1
    ]]):format(TABLE_NAME), { CONFIG_ROW_ID })
    local row = rows[1]
    if not row then
        error("[sky_phone] Phone configurator could not load its SQL row.")
    end
    return row
end

local function apply_stored_row(row)
    stored_config = merge_values(
        default_config,
        decode_payload(row.config_payload, "config"),
        "",
        FIXED_CONFIG_PATHS
    )
    stored_media = merge_values(default_media, decode_payload(row.media_payload, "media"), "")
    revision = tonumber(row.revision) or 1
    updated_at = row.updated_at
    updated_by_name = row.updated_by_name
end

default_config = {}
for key, value in pairs(ConfigDefaults) do
    if key ~= "Media" and key ~= "PhoneConfigurator" and key ~= "CommandPermissions" then
        default_config[key] = serialize_value(value)
    end
end
default_media = serialize_value(ConfigDefaults.Media)

Bridge.Database.Migrate("sky_phone_configurator", { SkyPhoneConfiguratorSchema })
Bridge.Database.Query(([[
    INSERT IGNORE INTO `%s` (`id`, `config_payload`, `media_payload`, `revision`)
    VALUES (?, ?, ?, 1)
]]):format(TABLE_NAME), {
    CONFIG_ROW_ID,
    encode_payload(default_config, "config"),
    encode_payload(default_media, "media"),
})

apply_stored_row(read_stored_row())
apply_runtime_configuration()

function SkyPhoneConfigurator.GetAdminData()
    local data = build_admin_data()
    return data
end

function SkyPhoneConfigurator.Save(expected_revision, changes, actor_identifier, actor_name)
    if not configurator_enabled then
        return { success = false, error = "configurator_disabled" }
    end
    if tonumber(expected_revision) ~= revision then
        return { success = false, error = "revision_conflict", data = SkyPhoneConfigurator.GetAdminData() }
    end
    if type(changes) ~= "table" or #changes < 1 or #changes > MAX_CHANGES then
        return { success = false, error = "invalid_request" }
    end

    local _, field_index = build_admin_data()
    local next_config = copy_value(stored_config)
    local next_media = copy_value(stored_media)
    local seen = {}
    for _, change in ipairs(changes) do
        if type(change) ~= "table" or type(change.scope) ~= "string" or type(change.path) ~= "string" then
            return { success = false, error = "invalid_request" }
        end

        local field_key = change.scope .. ":" .. change.path
        local field = field_index[field_key]
        if not field or seen[field_key] then
            return { success = false, error = "invalid_field" }
        end
        seen[field_key] = true

        local normalized = normalize_change_value(field, change.value)
        if normalized == nil then
            return { success = false, error = "invalid_value" }
        end
        local target = change.scope == "config" and next_config or next_media
        if change.scope ~= "config" and change.scope ~= "media" then
            return { success = false, error = "invalid_field" }
        end
        if field.type == "json" then
            normalized = restore_redacted_values(normalized, get_path(target, change.path), change.path)
        end
        if not set_path(target, change.path, normalized) then
            return { success = false, error = "invalid_field" }
        end
    end

    local config_encoded = encode_payload(next_config, "config")
    local media_encoded = encode_payload(next_media, "media")
    local result = Bridge.Database.Query(([[
        UPDATE `%s`
        SET `config_payload` = ?, `media_payload` = ?, `revision` = `revision` + 1,
            `updated_by_identifier` = ?, `updated_by_name` = ?
        WHERE `id` = ? AND `revision` = ?
    ]]):format(TABLE_NAME), {
        config_encoded,
        media_encoded,
        tostring(actor_identifier or ""):sub(1, 80),
        tostring(actor_name or ""):sub(1, 120),
        CONFIG_ROW_ID,
        revision,
    })
    if affected_rows(result) ~= 1 then
        local latest = Bridge.Database.Query(("SELECT `revision` FROM `%s` WHERE `id` = ? LIMIT 1"):format(TABLE_NAME), {
            CONFIG_ROW_ID,
        })
        revision = latest[1] and tonumber(latest[1].revision) or revision
        return { success = false, error = "revision_conflict", data = SkyPhoneConfigurator.GetAdminData() }
    end

    local expected_revision = revision + 1
    local persisted_row = read_stored_row()
    if persisted_row.config_payload ~= config_encoded
        or persisted_row.media_payload ~= media_encoded
        or tonumber(persisted_row.revision) ~= expected_revision
    then
        error("[sky_phone] Phone configurator SQL verification failed after saving config and media payloads.")
    end
    apply_stored_row(persisted_row)
    apply_runtime_configuration()
    TriggerEvent("sky_phone:configurator:serverUpdated", revision)
    SkyPhoneConfigurator.Broadcast(-1)
    Bridge.Debug(
        "info",
        "[sky_phone] Applied Phone Configurator revision %s through the internal runtime refresh.",
        tostring(revision),
        { always = true }
    )

    return { success = true, data = SkyPhoneConfigurator.GetAdminData() }
end

Bridge.Callbacks.Register("sky_phone:configurator:runtime", function()
    return {
        success = true,
        data = {
            config = configurator_enabled and client_payload() or {},
            enabled = configurator_enabled,
            revision = revision,
        },
    }
end)

Bridge.Debug(
    "info",
    "[sky_phone] Phone configurator initialized enabled=%s revision=%s.",
    tostring(configurator_enabled),
    tostring(revision)
)
