```lua
local VK_S, VK_A, VK_D, VK_F5 = 0x53, 0x41, 0x44, 0x74
local DETECTION_MULTIPLIER_BASE = 5
local DETECTION_MULTIPLIER_DECREMENT = 0.2
local DETECTION_RANGE = 0.6
local DETECTION_ANGLE_INCREMENT = 2
local DETECTION_LINES = 9
local OBSTACLE_ANGLE_THRESHOLD = 60
local function isObstacleInFront(x, y, z, targetX, targetY, targetZ)
    local hit, col = processLineOfSight(x, y, z, targetX, targetY, targetZ, true, true, true, true, true, false, false, false)
    if hit and col.normal[3] then
        local dotProduct = col.normal[3]
        local angle = math.deg(math.acos(dotProduct))
        return angle >= OBSTACLE_ANGLE_THRESHOLD
    end
    return false
end

local function getCarSpeed()
    local speedVector = {getCarSpeedVector(getCarCharIsUsing(PLAYER_PED), true)}
    return math.sqrt(speedVector[1] ^ 2 + speedVector[2] ^ 2 + speedVector[3] ^ 2)
end

local DrivingAssistant = {
    drivingEnabled = false,
    lastTurnDirection = nil,
    userInputMultiplier = DETECTION_MULTIPLIER_BASE
}

function DrivingAssistant:toggleDriving()
    self.drivingEnabled = not self.drivingEnabled
    sampAddChatMessage(self.drivingEnabled and "Driving Assistant Enabled." or "Driving Assistant Disabled.", -1)
end

function DrivingAssistant:processDriving()
    if not isCharInAnyCar(PLAYER_PED) then
        sampAddChatMessage("You must be on a bike.", -1)
        return
    end

    local playerCoords, playerHeading = {getCharCoordinates(PLAYER_PED)}, getCharHeading(PLAYER_PED)
    local radOffset = math.rad(-playerHeading)
    local offsetX, offsetY = playerCoords[1] + DETECTION_RANGE * math.sin(radOffset), playerCoords[2] + DETECTION_RANGE * math.cos(radOffset)
    local obstacleInfo, allLinesOccupied = self:detectObstacles(offsetX, offsetY, playerCoords[3], radOffset)
    self:handleObstacles(obstacleInfo, allLinesOccupied)
end

function DrivingAssistant:detectObstacles(offsetX, offsetY, offsetZ, radOffset)
    local obstacleInfo = {
        detected = {
            left = false,
            right = false,
            front = false,
            center = false
        },
        specific = {}
    }

    local allLinesOccupied = true
    local pitch = getCarPitch(getCarCharIsUsing(PLAYER_PED))
    for i = -DETECTION_LINES / 2, DETECTION_LINES / 2 do
        local radian = radOffset + math.rad(DETECTION_ANGLE_INCREMENT * i)
        local multiplier = self:getMultiplier(i)
        local targetX, targetY, targetZ = offsetX + multiplier * math.sin(radian), offsetY + multiplier * math.cos(radian), offsetZ + multiplier * math.sin(math.rad(pitch))
        local obstacle = isObstacleInFront(offsetX, offsetY, offsetZ, targetX, targetY, targetZ)
        allLinesOccupied = allLinesOccupied and obstacle
        self:processObstacleDetection(obstacleInfo, obstacle, i)
    end
    return obstacleInfo, allLinesOccupied
end

function DrivingAssistant:getMultiplier(index)
    local speedFactor = 1 + getCarSpeed() / 10
    return index == 0 and 0.5 * speedFactor or (self.userInputMultiplier - DETECTION_MULTIPLIER_DECREMENT * math.abs(index)) * speedFactor
end

function DrivingAssistant:processObstacleDetection(obstacleInfo, obstacle, index)
    if obstacle then
        obstacleInfo.detected.center = obstacleInfo.detected.center or index == 0
        obstacleInfo.detected.right = obstacleInfo.detected.right or index > 0
        obstacleInfo.detected.left = obstacleInfo.detected.left or index < 0
        obstacleInfo.detected.front = obstacleInfo.detected.front or index ~= 0
        obstacleInfo.specific[index] = true
    end
end

function DrivingAssistant:handleObstacles(obstacleInfo, allLinesOccupied)
    if obstacleInfo.detected.center then self:pressKey(VK_S, 1000) end
    if allLinesOccupied then
        self:pressKey(self.lastTurnDirection == "right" and VK_D or VK_A, 250)
    elseif obstacleInfo.detected.front then
        local leftCount, rightCount = 0, 0
        for i = -DETECTION_LINES / 2, DETECTION_LINES / 2 do
            if i ~= 0 and obstacleInfo.specific[i] then
                if i < 0 then
                    leftCount = leftCount + 1
                else
                    rightCount = rightCount + 1
                end
            end
        end

        self.lastTurnDirection = leftCount > rightCount and "right" or "left"
        self:pressKey(self.lastTurnDirection == "right" and VK_D or VK_A, 250)
    end
end

function DrivingAssistant:pressKey(key, duration)
    lua_thread.create(function()
        setVirtualKeyDown(key, true)
        wait(duration)
        setVirtualKeyDown(key, false)
    end)
end

function main()
    local act = false
    local assistant = setmetatable({}, {
        __index = DrivingAssistant
    })

    while not isSampAvailable() do
        wait(0)
    end

    sampRegisterChatCommand("helpmedrive", function() assistant:toggleDriving() end)
    sampRegisterChatCommand("setmultiplier", function(param)
        local value = tonumber(param)
        if value then
            assistant.userInputMultiplier = value
            sampAddChatMessage("Distance multiplier set to: " .. value, -1)
        else
            sampAddChatMessage("Error: Invalid multiplier value.", -1)
        end
    end)

    while true do
        wait(0)
        local keydown = isKeyDown(VK_F5)
        if keydown and not act then assistant:toggleDriving() end
        act = keydown
        if assistant.drivingEnabled then
            local status, err = pcall(assistant.processDriving, assistant)
            if not status then print("ERROR: " .. err, -1) end
        end
    end
end
```
Приведи следующий код к профессиональному уровню с использованием принципов ООП, сделай архитектуру модульной и расширяемой, добавь инкапсуляцию, абстракцию и наследование там, где это уместно, оптимизируй производительность и читаемость, улучшай именования переменных и функций, убери дублирование, применяй паттерны проектирования, обеспечь обработку ошибок и логирование, добавь комментарии и документацию для основных классов и методов.