_G.copilotsByGroupID = {}

do
    local currentKillSoundIndex = 1
    local currentPowerOnSoundIndex = 1
    local currentPowerOffSoundIndex = 1
    local currentLaserOnSoundIndex = 1
    local currentLaserOffSoundIndex = 1
    local currentRangefinderOnSoundIndex = 1
    local currentRangefinderOffSoundIndex = 1
    local currentSmokeMarkSoundIndex = 1
    local currentFlareMarkSoundIndex = 1
    local currentScanningSoundIndex = 1
    local currentTrackingSoundIndex = 1
    local currentNoTargetsFoundSoundIndex = 1
    local currentComeLeftSoundIndex = 1
    local currentComeRightSoundIndex = 1
    local currentInFrontSoundIndex = 1
    local currentTargetListClearedSoundIndex = 1
    local currentMovingCameraSoundIndex = 1
    
    local killSounds = {
        "good hits.ogg",
        "good hits2.ogg",
        "good hits3.ogg",
        "good hits4.ogg",
        "good hits5.ogg",
        "good hits6.ogg",
        "good hits7.ogg",
        "good hits8.ogg",
        "good hits9.ogg"
    }
    
    local powerOnSounds = {
        "copilot on.ogg",
        "copilot on2.ogg",
        "copilot on3.ogg",
        "copilot on4.ogg",
        "copilot on5.ogg",
        "copilot on6.ogg"
    }
    
    local powerOffSounds = {
        "copilot off.ogg",
        "copilot off2.ogg"
    }
    
    local laserOnSounds = {
        "lase on.ogg",
        "lase on2.ogg",
        "lase on3.ogg",
        "lase on4.ogg"
    }
    
    local laserOffSounds = {
        "lase off.ogg"
    }
    
    local rangefinderOnSounds = {
        "rangefinder on.ogg",
        "rangefinder on2.ogg",
        "rangefinder on3.ogg",
        "rangefinder on4.ogg",
        "rangefinder on5.ogg",
    }
    
    local rangefinderOffSounds = {
        "rangefinder off.ogg"
    }
    
    local smokeMarkSounds = {
        "target marked with smoke.ogg",
        "target marked with smoke2.ogg",
        "target marked with smoke3.ogg",
        "target marked with smoke4.ogg"
    }
    
    local flareMarkSounds = {
        "target marked with flare.ogg",
        "target marked with flare2.ogg",
        "target marked with flare3.ogg",
        "target marked with flare4.ogg"
    }
    
    local scanningSounds = {
        "scanning.ogg",
        "scanning2.ogg",
        "scanning3.ogg",
        "scanning4.ogg",
        "scanning5.ogg"
    }
    
    local trackingSounds = {
        "tracking.ogg",
        "tracking2.ogg",
        "tracking3.ogg",
        "tracking4.ogg",
        "tracking5.ogg"
        
    }
    
    local noTargetsFoundSounds = {
        "no targets found.ogg",
        "no targets found2.ogg",
        "no targets found3.ogg",
        "no targets found4.ogg",
        "no targets found5.ogg",
        "no targets found6.ogg",
        "no targets found7.ogg"
    }
    
    local comeLeftSounds = {
        "come left.ogg",
        "come left2.ogg",
        "come left3.ogg"
    }
    
    local comeRightSounds = {
        "come right.ogg",
        "come right2.ogg",
        "come right3.ogg"
    }
    
    local inFrontSounds = {
        "in front.ogg",
        "in front2.ogg",
        "in front3.ogg"
    }
    
    local targetListClearedSounds = {
        "target list cleard.ogg",
        "target list cleard2.ogg",
        "target list cleard3.ogg"
    }
    
    local movingCameraSounds = {
        "moving camera.ogg",
        "moving camera2.ogg"
    }
    
    local function getNextKillSound()
        local sound = killSounds[currentKillSoundIndex]
        currentKillSoundIndex = currentKillSoundIndex + 1
        if currentKillSoundIndex > #killSounds then
            currentKillSoundIndex = 1
        end
        return sound
    end
    
    local function getNextPowerOnSound()
        local sound = powerOnSounds[currentPowerOnSoundIndex]
        currentPowerOnSoundIndex = currentPowerOnSoundIndex + 1
        if currentPowerOnSoundIndex > #powerOnSounds then
            currentPowerOnSoundIndex = 1
        end
        return sound
    end
    
    local function getNextPowerOffSound()
        local sound = powerOffSounds[currentPowerOffSoundIndex]
        currentPowerOffSoundIndex = currentPowerOffSoundIndex + 1
        if currentPowerOffSoundIndex > #powerOffSounds then
            currentPowerOffSoundIndex = 1
        end
        return sound
    end
    
    local function getNextLaserOnSound()
        local sound = laserOnSounds[currentLaserOnSoundIndex]
        currentLaserOnSoundIndex = currentLaserOnSoundIndex + 1
        if currentLaserOnSoundIndex > #laserOnSounds then
            currentLaserOnSoundIndex = 1
        end
        return sound
    end
    
    local function getNextLaserOffSound()
        local sound = laserOffSounds[currentLaserOffSoundIndex]
        currentLaserOffSoundIndex = currentLaserOffSoundIndex + 1
        if currentLaserOffSoundIndex > #laserOffSounds then
            currentLaserOffSoundIndex = 1
        end
        return sound
    end
    
    local function getNextRangefinderOnSound()
        local sound = rangefinderOnSounds[currentRangefinderOnSoundIndex]
        currentRangefinderOnSoundIndex = currentRangefinderOnSoundIndex + 1
        if currentRangefinderOnSoundIndex > #rangefinderOnSounds then
            currentRangefinderOnSoundIndex = 1
        end
        return sound
    end
    
    local function getNextRangefinderOffSound()
        local sound = rangefinderOffSounds[currentRangefinderOffSoundIndex]
        currentRangefinderOffSoundIndex = currentRangefinderOffSoundIndex + 1
        if currentRangefinderOffSoundIndex > #rangefinderOffSounds then
            currentRangefinderOffSoundIndex = 1
        end
        return sound
    end
    
    local function getNextSmokeMarkSound()
        local sound = smokeMarkSounds[currentSmokeMarkSoundIndex]
        currentSmokeMarkSoundIndex = currentSmokeMarkSoundIndex + 1
        if currentSmokeMarkSoundIndex > #smokeMarkSounds then
            currentSmokeMarkSoundIndex = 1
        end
        return sound
    end
    
    local function getNextFlareMarkSound()
        local sound = flareMarkSounds[currentFlareMarkSoundIndex]
        currentFlareMarkSoundIndex = currentFlareMarkSoundIndex + 1
        if currentFlareMarkSoundIndex > #flareMarkSounds then
            currentFlareMarkSoundIndex = 1
        end
        return sound
    end
    
    local function getNextScanningSound()
        local sound = scanningSounds[currentScanningSoundIndex]
        currentScanningSoundIndex = currentScanningSoundIndex + 1
        if currentScanningSoundIndex > #scanningSounds then
            currentScanningSoundIndex = 1
        end
        return sound
    end
    
    local function getNextTrackingSound()
        local sound = trackingSounds[currentTrackingSoundIndex]
        currentTrackingSoundIndex = currentTrackingSoundIndex + 1
        if currentTrackingSoundIndex > #trackingSounds then
            currentTrackingSoundIndex = 1
        end
        return sound
    end
    
    local function getNextNoTargetsFoundSound()
        local sound = noTargetsFoundSounds[currentNoTargetsFoundSoundIndex]
        currentNoTargetsFoundSoundIndex = currentNoTargetsFoundSoundIndex + 1
        if currentNoTargetsFoundSoundIndex > #noTargetsFoundSounds then
            currentNoTargetsFoundSoundIndex = 1
        end
        return sound
    end
    
    local function getNextComeLeftSound()
        local sound = comeLeftSounds[currentComeLeftSoundIndex]
        currentComeLeftSoundIndex = currentComeLeftSoundIndex + 1
        if currentComeLeftSoundIndex > #comeLeftSounds then
            currentComeLeftSoundIndex = 1
        end
        return sound
    end
    
    local function getNextComeRightSound()
        local sound = comeRightSounds[currentComeRightSoundIndex]
        currentComeRightSoundIndex = currentComeRightSoundIndex + 1
        if currentComeRightSoundIndex > #comeRightSounds then
            currentComeRightSoundIndex = 1
        end
        return sound
    end
    
    local function getNextInFrontSound()
        local sound = inFrontSounds[currentInFrontSoundIndex]
        currentInFrontSoundIndex = currentInFrontSoundIndex + 1
        if currentInFrontSoundIndex > #inFrontSounds then
            currentInFrontSoundIndex = 1
        end
        return sound
    end
    
    local function getNextTargetListClearedSound()
        local sound = targetListClearedSounds[currentTargetListClearedSoundIndex]
        currentTargetListClearedSoundIndex = currentTargetListClearedSoundIndex + 1
        if currentTargetListClearedSoundIndex > #targetListClearedSounds then
            currentTargetListClearedSoundIndex = 1
        end
        return sound
    end
    
    local function getNextMovingCameraSound()
        local sound = movingCameraSounds[currentMovingCameraSoundIndex]
        currentMovingCameraSoundIndex = currentMovingCameraSoundIndex + 1
        if currentMovingCameraSoundIndex > #movingCameraSounds then
            currentMovingCameraSoundIndex = 1
        end
        return sound
    end
    
    local aircraftStats = {
        ["Hercules"] = { minDist = 10, maxDeviation = 360, laserOffset = { x = 1.0, y = -4.0, z = -2.0 } },
        ["UH-60L"]   = { minDist = 10, maxDeviation = 30,  laserOffset = { x = 4.65, y = -1.8, z = 0 } },
    }
    
    local activeTimers = {}
    local lastMarker = nil
    
    local function logMessage(groupID, text, duration)
        if not groupID then return end
        trigger.action.outTextForGroup(groupID, "Copilot: " .. text, duration or 3)
    end
    
    local function getDistance(p1, p2)
        if not p1 or not p2 then return 0 end
        local dx = p1.x - p2.x
        local dy = p1.y - p2.y
        local dz = p1.z - p2.z
        return math.sqrt(dx * dx + dy * dy + dz * dz)
    end
    
    local function getBearing(from, to)
        if not from or not to then return 0 end
        local dx = to.x - from.x
        local dz = to.z - from.z
        local bearing = math.deg(math.atan2(dz, dx))
        if bearing < 0 then bearing = bearing + 360 end
        return bearing
    end
    
    local function getHeadingDeviation(heading, bearing)
        if not heading or not bearing then return 0 end
        local diff = math.abs(heading - bearing)
        if diff > 180 then diff = 360 - diff end
        return diff
    end
    
    local function isValidGroup(groupName)
        if not groupName then return false end
        local group = Group.getByName(groupName)
        return group ~= nil and group:isExist()
    end
    
    local function getValidUnit(groupName)
        if not isValidGroup(groupName) then return nil end
        local group = Group.getByName(groupName)
        local unit = group:getUnit(1)
        if not unit or not unit:isExist() then return nil end
        return unit
    end
    
    local function isValidTarget(target)
        return target ~= nil and target:isExist() and target:getLife() > 0
    end
    
    local function isValidAircraftType(typeName)
        return typeName ~= nil and aircraftStats[typeName] ~= nil
    end
    
    local function safeRemoveFunction(timerID)
        if timerID and type(timerID) == "number" and timerID > 0 then
           pcall(function() timer.removeFunction(timerID) end)
           return true
        end
        return false
    end
    
    local function formatDMS(deg, isLat)
        local direction = isLat and (deg >= 0 and "N" or "S") or (deg >= 0 and "E" or "W")
        deg = math.abs(deg)
        local degrees = math.floor(deg)
        local minutes = math.floor((deg - degrees) * 60)
        local seconds = math.floor(((deg - degrees) * 60 - minutes) * 60 + 0.5)
        if seconds >= 60 then
            seconds = 0
            minutes = minutes + 1
        end
        if minutes >= 60 then
            minutes = 0
            degrees = degrees + 1
        end
        return string.format("%s%d°%02d'%02d\"", direction, degrees, minutes, seconds)
    end
    
    local Copilot = {}
    Copilot.__index = Copilot
    
    function Copilot.new(groupID, groupName)
        local unit = getValidUnit(groupName)
        if not unit then return nil end
        local aircraftType = unit:getDesc().typeName
        if not isValidAircraftType(aircraftType) then return nil end
        local self = setmetatable({}, Copilot)
        self.groupID = groupID
        self.groupName = groupName
        self.coalition = unit:getCoalition()
        self.aircraftType = aircraftType
        self.laserCode = 1688
        self.targets = {}
        self.customTargets = {}
        self.currentTarget = nil
        self.currentTargetIndex = 0
        self.laserSpot = nil
        self.irSpot = nil
        self.active = false
        self.showRange = false
        self.lastDirection = nil
        self.isNewTarget = false
        self.timerIDs = {}
        self.powerToggleCmd = nil
        self.soundEnabled = false
        return self
    end
    
    function Copilot:createMenus()
        self:removeMenus()
        self.rootMenu = missionCommands.addSubMenuForGroup(self.groupID, "Copilot")
        self.scanMenu = missionCommands.addSubMenuForGroup(self.groupID, "Targeting", self.rootMenu)
        missionCommands.addCommandForGroup(self.groupID, "Scan Ahead", self.scanMenu, function() self:scanTargets() end)
        missionCommands.addCommandForGroup(self.groupID, "Recon Mark", self.scanMenu, function() self:reconMark() end)
        missionCommands.addCommandForGroup(self.groupID, "Clear Targets", self.scanMenu, function() self:clearTargets() end)
        self.markingMenu = missionCommands.addSubMenuForGroup(self.groupID, "Marking", self.rootMenu)
        missionCommands.addCommandForGroup(self.groupID, "Mark with Smoke", self.markingMenu, function() self:markWithSmoke() end)
        missionCommands.addCommandForGroup(self.groupID, "Pop Flare", self.markingMenu, function() self:popFlare() end)
        self.laserMenu = missionCommands.addSubMenuForGroup(self.groupID, "Laser Options", self.rootMenu)
        missionCommands.addCommandForGroup(self.groupID, "Toggle IR Pointer", self.laserMenu, function() self:toggleBeamType() end)
        local label = self.showRange and "Rangefinder-Off" or "Rangefinder-On"
        self.rangefinderCmd = missionCommands.addCommandForGroup(self.groupID, label, self.laserMenu, function() self:toggleRangeDisplay() end)
        self.codeMenu = missionCommands.addSubMenuForGroup(self.groupID, "Laser Code", self.laserMenu)
        local commonCodes = { 1688, 1677, 1666, 1655 }
        for _, code in ipairs(commonCodes) do
            missionCommands.addCommandForGroup(self.groupID, tostring(code), self.codeMenu, function() self:setLaserCode(code) end)
        end
        self.active = true
        self.soundEnabled = true
        logMessage(self.groupID, "Copilot powered on")
        local nextSound = getNextPowerOnSound()
        trigger.action.outSoundForGroup(self.groupID, nextSound)
    end
    
    function Copilot:removeMenus()
        if self.rootMenu then
            missionCommands.removeItemForGroup(self.groupID, self.rootMenu)
            self.rootMenu = nil
            self.scanMenu = nil
            self.markingMenu = nil
            self.laserMenu = nil
            self.codeMenu = nil
            self.rangefinderCmd = nil
        end
    end
    
    function Copilot:togglePower()
        if self.active then
            -- Complete cleanup when turning off
            self:cleanupSpots()
            self:removeMenus()
            for _, timerID in pairs(self.timerIDs) do
                safeRemoveFunction(timerID)
            end
            logMessage(self.groupID, "Copilot powered off")
            local nextSound = getNextPowerOffSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
            
            -- Remove from global table and nullify the instance
            _G.copilotsByGroupID[self.groupID] = nil
            updatePowerToggle(self.groupID, self.groupName, false)
            
            -- Nullify all instance variables
            self.groupID = nil
            self.groupName = nil
            self.coalition = nil
            self.aircraftType = nil
            self.laserCode = nil
            self.targets = nil
            self.customTargets = nil
            self.currentTarget = nil
            self.currentTargetIndex = nil
            self.laserSpot = nil
            self.irSpot = nil
            self.active = false
            self.showRange = nil
            self.lastDirection = nil
            self.isNewTarget = nil
            self.timerIDs = nil
            self.powerToggleCmd = nil
            self.soundEnabled = nil
            self.rootMenu = nil
            self.scanMenu = nil
            self.markingMenu = nil
            self.laserMenu = nil
            self.codeMenu = nil
            self.rangefinderCmd = nil
            self.targetMenu = nil
        else
            -- When turning on, create a completely new instance
            local newCopilot = Copilot.new(self.groupID, self.groupName)
            if newCopilot then
                newCopilot:createMenus()
                _G.copilotsByGroupID[self.groupID] = newCopilot
                updatePowerToggle(self.groupID, self.groupName, true)
            end
        end
    end
    
    function Copilot:scanTargets()
        local unit = getValidUnit(self.groupName)
        if not unit then return end
        local stats = aircraftStats[self.aircraftType]
        if not stats then return end
        local pos = unit:getPoint()
        local heading = math.deg(math.atan2(unit:getPosition().x.z, unit:getPosition().x.x))
        local searchVolume = { id = world.VolumeType.SPHERE, params = { point = pos, radius = stats.minDist * 1000 } }
        local customTargets = self.customTargets
        self.targets = {}
        self.customTargets = customTargets
        logMessage(self.groupID, "Scanning...")
        if self.soundEnabled then
            local nextSound = getNextScanningSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
        end
        local scanStart = timer.getTime()
        local targetCount = 0
        world.searchObjects({ Object.Category.UNIT, Object.Category.STATIC }, searchVolume, function(obj)
            if timer.getTime() - scanStart > 2 then
                logMessage(self.groupID, "Scan time limit reached", 3)
                return false
            end
            if not isValidTarget(obj) then return true end
            local objGroup = obj:getGroup()
            if objGroup and (objGroup:getName() ~= self.groupName) and (obj:getCoalition() ~= self.coalition) then
                local tgtPos = obj:getPoint()
                local distance = getDistance(pos, tgtPos) / 1000
                local bearing = getBearing(pos, tgtPos)
                local deviation = getHeadingDeviation(heading, bearing)
                if distance <= stats.minDist and deviation <= stats.maxDeviation then
                    if land.isVisible({ x = pos.x, y = pos.y + 1.5, z = pos.z }, { x = tgtPos.x, y = tgtPos.y + 1.5, z = tgtPos.z }) then
                        table.insert(self.targets, obj)
                        targetCount = targetCount + 1
                    end
                end
            end
            return true
        end)
        for _, customTarget in ipairs(self.customTargets) do
            table.insert(self.targets, customTarget)
            targetCount = targetCount + 1
        end
        logMessage(self.groupID, "Found " .. targetCount .. " targets")
        if targetCount > 0 and self.soundEnabled then
            timer.scheduleFunction(function()
                if self.active and self.soundEnabled then
                    local nextSound = getNextTrackingSound()
                    trigger.action.outSoundForGroup(self.groupID, nextSound)
                end
            end, nil, timer.getTime() + 2)
        elseif targetCount == 0 and self.soundEnabled then
            timer.scheduleFunction(function()
                if self.active and self.soundEnabled then
                    local nextSound = getNextNoTargetsFoundSound()
                    trigger.action.outSoundForGroup(self.groupID, nextSound)
                end
            end, nil, timer.getTime() + 2)
        end
        self:updateTargetMenu()
    end
    
    function Copilot:reconMark()
        if not lastMarker then return end
        local markerPos = lastMarker.pos
        local searchVolume = { id = world.VolumeType.SPHERE, params = { point = markerPos, radius = 15000 } }
        local customTargets = self.customTargets
        self.targets = {}
        self.customTargets = customTargets
        logMessage(self.groupID, "Scanning around map marker...")
        if self.soundEnabled then
            local nextSound = getNextMovingCameraSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
        end
        local scanStart = timer.getTime()
        local targetCount = 0
        world.searchObjects({ Object.Category.UNIT, Object.Category.STATIC }, searchVolume, function(obj)
            if timer.getTime() - scanStart > 2 then
                logMessage(self.groupID, "Scan time limit reached", 3)
                return false
            end
            if not isValidTarget(obj) then return true end
            local objGroup = obj:getGroup()
            if objGroup and (objGroup:getName() ~= self.groupName) and (obj:getCoalition() ~= self.coalition) then
                table.insert(self.targets, obj)
                targetCount = targetCount + 1
            end
            return true
        end)
        for _, customTarget in ipairs(self.customTargets) do
            table.insert(self.targets, customTarget)
            targetCount = targetCount + 1
        end
        logMessage(self.groupID, "Found " .. targetCount .. " targets around marker")
        if targetCount > 0 and self.soundEnabled then
            timer.scheduleFunction(function()
                if self.active and self.soundEnabled then
                    local nextSound = getNextTrackingSound()
                    trigger.action.outSoundForGroup(self.groupID, nextSound)
                end
            end, nil, timer.getTime() + 2)
        elseif targetCount == 0 and self.soundEnabled then
            timer.scheduleFunction(function()
                if self.active and self.soundEnabled then
                    local nextSound = getNextNoTargetsFoundSound()
                    trigger.action.outSoundForGroup(self.groupID, nextSound)
                end
            end, nil, timer.getTime() + 2)
        end
        self:updateTargetMenu()
    end
    
    function Copilot:addCustomTarget(name, lat, lon, elev)
        local pos = coord.LLtoLO(lat, lon)
        pos.y = elev or 0
        local customTarget = {
            isCustom = true,
            name = name,
            getPoint = function() return pos end,
            getTypeName = function() return "Custom: " .. name end,
            isExist = function() return true end,
            getLife = function() return 100 end,
            getCoalition = function() return 0 end
        }
        table.insert(self.customTargets, customTarget)
        table.insert(self.targets, customTarget)
        self:updateTargetMenu()
        logMessage(self.groupID, "Added custom target: " .. name, 5)
        return true
    end
    
    function Copilot:updateTargetMenu()
        if self.targetMenu then
            missionCommands.removeItemForGroup(self.groupID, self.targetMenu)
            self.targetMenu = nil
        end
        if #self.targets == 0 then return end
        local unit = getValidUnit(self.groupName)
        if not unit then return end
        local myPos = unit:getPoint()
        local validTargets = {}
        for i, tgt in ipairs(self.targets) do
            if isValidTarget(tgt) then
                table.insert(validTargets, tgt)
            end
        end
        self.targets = validTargets
        self.targetMenu = missionCommands.addSubMenuForGroup(self.groupID, "Select Target", self.rootMenu)
        for i, tgt in ipairs(self.targets) do
            local tgtPos = tgt:getPoint()
            local distKm = getDistance(myPos, tgtPos) / 1000
            local bearing = getBearing(myPos, tgtPos)
            local lat, lon = coord.LOtoLL(tgtPos)
            local latStr = formatDMS(lat, true)
            local lonStr = formatDMS(lon, false)
            local label = string.format("%s (%d) - %.1f km, %03d°, %s %s", tgt:getTypeName(), i, distKm, math.floor(bearing), latStr, lonStr)
            missionCommands.addCommandForGroup(self.groupID, label, self.targetMenu, function() self:designateTarget(i) end)
        end
    end
    
    function Copilot:designateTarget(index)
        if index < 1 or index > #self.targets then return end
        local unit = getValidUnit(self.groupName)
        if not unit then return end
        local target = self.targets[index]
        if not isValidTarget(target) then
            table.remove(self.targets, index)
            self:updateTargetMenu()
            return
        end
        local stats = aircraftStats[self.aircraftType]
        self:cleanupSpots()
        self.currentTarget = target
        self.currentTargetIndex = index
        self.isNewTarget = true
        self.laserSpot = Spot.createLaser(unit, stats.laserOffset, target:getPoint(), self.laserCode)
        self.irSpot = Spot.createInfraRed(unit, stats.laserOffset, target:getPoint())
        logMessage(self.groupID, "Laser on")
        if self.soundEnabled then
            local nextSound = getNextLaserOnSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
        end
        self.timerIDs.updateTarget = timer.scheduleFunction(function() self:updateTarget() end, nil, timer.getTime() + 0.5)
    end
    
    function Copilot:updateTarget()
        if not self.active or not self.currentTarget then return end
        local unit = getValidUnit(self.groupName)
        if not unit then
            self:cleanupSpots()
            return
        end
        if not isValidTarget(self.currentTarget) then
            self:updateTargetMenu()
            local remaining = #self.targets
            local message
            if remaining == 0 then
                message = "No targets remaining"
            elseif remaining == 1 then
                message = "1 target remaining"
            else
                message = remaining .. " targets remaining"
            end
            logMessage(self.groupID, message)
            self:cleanupSpots()
            if self.soundEnabled then
                local nextSound = getNextKillSound()
                timer.scheduleFunction(function()
                    if self.active and self.soundEnabled then
                        trigger.action.outSoundForGroup(self.groupID, nextSound)
                    end
                end, nil, timer.getTime() + 2)
            end
            return
        end
        if self.laserSpot then
            self.laserSpot:setPoint(self.currentTarget:getPoint())
        end
        if self.irSpot then
            self.irSpot:setPoint(self.currentTarget:getPoint())
        end
        self.timerIDs.updateTarget = timer.scheduleFunction(function() self:updateTarget() end, nil, timer.getTime() + 0.5)
    end
    
    function Copilot:cleanupSpots()
        if self.currentTarget then
            logMessage(self.groupID, "Laser off")
            if self.soundEnabled then
                local nextSound = getNextLaserOffSound()
                trigger.action.outSoundForGroup(self.groupID, nextSound)
            end
        end
        if self.laserSpot then
            self.laserSpot:destroy()
            self.laserSpot = nil
        end
        if self.irSpot then
            self.irSpot:destroy()
            self.irSpot = nil
        end
        self.currentTarget = nil
        self.currentTargetIndex = 0
        self.lastDirection = nil
        self.isNewTarget = false
        if self.timerIDs.updateTarget then
            safeRemoveFunction(self.timerIDs.updateTarget)
            self.timerIDs.updateTarget = nil
        end
    end
    
    function Copilot:displayRange()
        if not self.active or not self.showRange then return end
        local unit = getValidUnit(self.groupName)
        if not unit then
            self.timerIDs.displayRange = timer.scheduleFunction(function() self:displayRange() end, nil, timer.getTime() + 2.0)
            return
        end
        if not self.currentTarget or not isValidTarget(self.currentTarget) then
            self.lastDirection = nil
            self.isNewTarget = false
            self.timerIDs.displayRange = timer.scheduleFunction(function() self:displayRange() end, nil, timer.getTime() + 2.0)
            return
        end
        if self.isNewTarget then
            self.isNewTarget = false
            self.timerIDs.displayRange = timer.scheduleFunction(function() self:displayRange() end, nil, timer.getTime() + 2.0)
            return
        end
        local myPos = unit:getPoint()
        local tgtPos = self.currentTarget:getPoint()
        local distKm = getDistance(myPos, tgtPos) / 1000
        local bearing = getBearing(myPos, tgtPos)
        local heading = math.deg(math.atan2(unit:getPosition().x.z, unit:getPosition().x.x))
        if heading < 0 then heading = heading + 360 end
        local relativeAngle = bearing - heading
        if relativeAngle > 180 then relativeAngle = relativeAngle - 360 end
        if relativeAngle < -180 then relativeAngle = relativeAngle + 360 end
        local direction = ""
        local directionState = ""
        local threshold = 10
        if math.abs(relativeAngle) <= threshold then
            direction = "(in front)"
            directionState = "in front"
        elseif relativeAngle > 0 then
            direction = "(right)"
            directionState = "right"
        else
            direction = "(left)"
            directionState = "left"
        end
        if self.lastDirection ~= directionState and self.soundEnabled then
            local soundToPlay
            if directionState == "left" then
                soundToPlay = getNextComeLeftSound()
            elseif directionState == "right" then
                soundToPlay = getNextComeRightSound()
            elseif directionState == "in front" then
                soundToPlay = getNextInFrontSound()
            end
            if soundToPlay then
                trigger.action.outSoundForGroup(self.groupID, soundToPlay)
            end
        end
        self.lastDirection = directionState
        logMessage(self.groupID, string.format("Range: %.1f km, %03d° %s", distKm, math.floor(bearing), direction), 2.0)
        self.timerIDs.displayRange = timer.scheduleFunction(function() self:displayRange() end, nil, timer.getTime() + 2.0)
    end
    
    function Copilot:toggleRangeDisplay()
        self.showRange = not self.showRange
        if self.showRange then
            logMessage(self.groupID, "Rangefinder ON")
            if self.soundEnabled then
                local nextSound = getNextRangefinderOnSound()
                trigger.action.outSoundForGroup(self.groupID, nextSound)
            end
            self.timerIDs.displayRange = timer.scheduleFunction(function() self:displayRange() end, nil, timer.getTime() + 2.0)
        else
            logMessage(self.groupID, "Rangefinder OFF")
            if self.soundEnabled then
                local nextSound = getNextRangefinderOffSound()
                trigger.action.outSoundForGroup(self.groupID, nextSound)
            end
            if self.timerIDs.displayRange then
                safeRemoveFunction(self.timerIDs.displayRange)
                self.timerIDs.displayRange = nil
            end
            self.lastDirection = nil
            self.isNewTarget = false
        end
        if self.rangefinderCmd then
            missionCommands.removeItemForGroup(self.groupID, self.rangefinderCmd)
        end
        local label = self.showRange and "Rangefinder-Off" or "Rangefinder-On"
        self.rangefinderCmd = missionCommands.addCommandForGroup(self.groupID, label, self.laserMenu, function() self:toggleRangeDisplay() end)
    end
    
    function Copilot:markWithSmoke()
        if not self.currentTarget then return end
        local pos = self.currentTarget:getPoint()
        local smokePos = { x = pos.x + math.random(-50, 50), y = pos.y, z = pos.z + math.random(-50, 50) }
        trigger.action.smoke(smokePos, trigger.smokeColor.White)
        logMessage(self.groupID, "Target marked with smoke")
        if self.soundEnabled then
            local nextSound = getNextSmokeMarkSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
        end
    end
    
    function Copilot:popFlare()
        if not self.currentTarget then return end
        local pos = self.currentTarget:getPoint()
        local flarePos = { x = pos.x + math.random(-20, 20), y = pos.y, z = pos.z + math.random(-20, 20) }
        trigger.action.signalFlare(flarePos, trigger.flareColor.White, 0)
        logMessage(self.groupID, "Flare popped near target")
        if self.soundEnabled then
            local nextSound = getNextFlareMarkSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
        end
    end
    
    function Copilot:toggleBeamType()
        if not self.currentTarget then return end
        local unit = getValidUnit(self.groupName)
        if not unit then return end
        local stats = aircraftStats[self.aircraftType]
        if self.irSpot then
            self.irSpot:destroy()
            self.irSpot = nil
            logMessage(self.groupID, "IR laser off")
            if self.soundEnabled then
                local nextSound = getNextLaserOffSound()
                trigger.action.outSoundForGroup(self.groupID, nextSound)
            end
        else
            self.irSpot = Spot.createInfraRed(unit, stats.laserOffset, self.currentTarget:getPoint())
            logMessage(self.groupID, "IR laser on")
            if self.soundEnabled then
                local nextSound = getNextLaserOnSound()
                trigger.action.outSoundForGroup(self.groupID, nextSound)
            end
        end
    end
    
    function Copilot:setLaserCode(code)
        if code < 1111 or code > 1788 then return end
        local codeStr = tostring(code)
        local lastThree = {tonumber(codeStr:sub(2,2)), tonumber(codeStr:sub(3,3)), tonumber(codeStr:sub(4,4))}
        for _, digit in ipairs(lastThree) do
            if digit < 1 or digit > 8 then return end
        end
        self.laserCode = code
        logMessage(self.groupID, "Laser code set to " .. code)
        if self.currentTarget and self.currentTargetIndex > 0 then
            self:designateTarget(self.currentTargetIndex)
        end
    end
    
    function Copilot:clearTargets()
        self:cleanupSpots()
        self.targets = {}
        for _, customTarget in ipairs(self.customTargets) do
            table.insert(self.targets, customTarget)
        end
        self:updateTargetMenu()
        logMessage(self.groupID, "Target list cleared")
        if self.soundEnabled then
            local nextSound = getNextTargetListClearedSound()
            trigger.action.outSoundForGroup(self.groupID, nextSound)
        end
    end
    
    function updatePowerToggle(groupID, groupName, isActive)
        -- Remove any existing power commands
        pcall(function() missionCommands.removeItemForGroup(groupID, {"Power On Copilot"}) end)
        pcall(function() missionCommands.removeItemForGroup(groupID, {"Power Off Copilot"}) end)
        
        local copilot = _G.copilotsByGroupID[groupID]
        if copilot and copilot.powerToggleCmd then
            missionCommands.removeItemForGroup(groupID, copilot.powerToggleCmd)
            copilot.powerToggleCmd = nil
        end
        
        -- Create new command based on state
        local label = isActive and "Power Off Copilot" or "Power On Copilot"
        local newCmd = missionCommands.addCommandForGroup(groupID, label, nil, function()
            local existingCopilot = _G.copilotsByGroupID[groupID]
            if existingCopilot and isActive then
                existingCopilot:togglePower()
            else
                local newCopilot = Copilot.new(groupID, groupName)
                if newCopilot then
                    newCopilot:togglePower()
                end
            end
        end)
        
        if copilot then
            copilot.powerToggleCmd = newCmd
        end
    end
    
    local eventHandler = {}
    function eventHandler:onEvent(event)
        if not event or not event.id then return end
        if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT then
            if event.initiator and event.initiator:getPlayerName() then
                local unit = event.initiator
                local group = unit:getGroup()
                if group then
                    local groupID = group:getID()
                    local groupName = group:getName()
                    local aircraftType = unit:getDesc().typeName
                    if isValidAircraftType(aircraftType) then
                        timer.scheduleFunction(function()
                            local copilot = _G.copilotsByGroupID[groupID]
                            if copilot then
                                if copilot.active then
                                    updatePowerToggle(groupID, groupName, true)
                                else
                                    updatePowerToggle(groupID, groupName, false)
                                end
                            else
                                updatePowerToggle(groupID, groupName, false)
                            end
                        end, nil, timer.getTime() + 2)
                    end
                end
            end
        end
        if event.id == world.event.S_EVENT_MARK_ADDED then
            if event.pos then
                lastMarker = {
                    pos = { x = event.pos.x, y = event.pos.y, z = event.pos.z },
                    id = event.idx or 0,
                    time = event.time or os.time()
                }
                trigger.action.outText("Copilot: Map marker added", 3)
            end
        end
    end
    world.addEventHandler(eventHandler)
    
    timer.scheduleFunction(function()
        local coalitions = {coalition.side.BLUE, coalition.side.RED}
        for _, coal in ipairs(coalitions) do
            local players = coalition.getPlayers(coal)
            if players then
                for _, unit in pairs(players) do
                    if unit and unit:isExist() and unit:getPlayerName() then
                        local group = unit:getGroup()
                        local aircraftType = unit:getDesc().typeName
                        if group and isValidAircraftType(aircraftType) then
                            local groupID = group:getID()
                            local groupName = group:getName()
                            local copilot = _G.copilotsByGroupID[groupID]
                            if copilot then
                                if copilot.active then
                                    updatePowerToggle(groupID, groupName, true)
                                else
                                    updatePowerToggle(groupID, groupName, false)
                                end
                            else
                                updatePowerToggle(groupID, groupName, false)
                            end
                        end
                    end
                end
            end
        end
        return nil
    end, nil, timer.getTime() + 5)
    
    trigger.action.outText("Copilot initialized", 5)
end